[claude] Commit heatmap on Nexus floor (#469) #493

Merged
Timmy merged 1 commits from claude/issue-469 into main 2026-03-25 03:07:10 +00:00

107
app.js
View File

@@ -45,6 +45,21 @@ let chatOpen = true;
let loadProgress = 0;
let performanceTier = 'high';
// ═══ COMMIT HEATMAP ═══
let heatmapMesh = null;
let heatmapMat = null;
let heatmapTexture = null;
const _heatmapCanvas = document.createElement('canvas');
_heatmapCanvas.width = 512;
_heatmapCanvas.height = 512;
const HEATMAP_ZONES = [
{ name: 'Claude', color: [255, 100, 60], authorMatch: /^claude$/i, angleDeg: 0 },
{ name: 'Timmy', color: [ 60, 160, 255], authorMatch: /^timmy/i, angleDeg: 90 },
{ name: 'Kimi', color: [ 60, 255, 140], authorMatch: /^kimi/i, angleDeg: 180 },
{ name: 'Perplexity', color: [200, 60, 255], authorMatch: /^perplexity/i, angleDeg: 270 },
];
const _heatZoneIntensity = Object.fromEntries(HEATMAP_ZONES.map(z => [z.name, 0]));
// ═══ NAVIGATION SYSTEM ═══
const NAV_MODES = ['walk', 'orbit', 'fly'];
let navModeIdx = 0;
@@ -92,6 +107,7 @@ async function init() {
createLighting();
updateLoad(40);
createFloor();
createCommitHeatmap();
updateLoad(50);
createBatcaveTerminal();
updateLoad(60);
@@ -332,6 +348,94 @@ function createFloor() {
scene.add(ring);
}
// ═══ COMMIT HEATMAP FUNCTIONS ═══
function createCommitHeatmap() {
heatmapTexture = new THREE.CanvasTexture(_heatmapCanvas);
heatmapMat = new THREE.MeshBasicMaterial({
map: heatmapTexture,
transparent: true,
opacity: 0.9,
depthWrite: false,
blending: THREE.AdditiveBlending,
side: THREE.DoubleSide,
});
heatmapMesh = new THREE.Mesh(new THREE.CircleGeometry(24, 64), heatmapMat);
heatmapMesh.rotation.x = -Math.PI / 2;
heatmapMesh.position.y = 0.005;
scene.add(heatmapMesh);
// Kick off first fetch; subsequent updates every 5 min
updateHeatmap();
setInterval(updateHeatmap, 5 * 60 * 1000);
}
function drawHeatmap() {
const ctx = _heatmapCanvas.getContext('2d');
const cx = 256, cy = 256, r = 246;
const SPAN = Math.PI / 2;
ctx.clearRect(0, 0, 512, 512);
ctx.save();
ctx.beginPath();
ctx.arc(cx, cy, r, 0, Math.PI * 2);
ctx.clip();
for (const zone of HEATMAP_ZONES) {
const intensity = _heatZoneIntensity[zone.name] || 0;
if (intensity < 0.01) continue;
const [rr, gg, bb] = zone.color;
const baseRad = zone.angleDeg * (Math.PI / 180);
const gx = cx + Math.cos(baseRad) * r * 0.55;
const gy = cy + Math.sin(baseRad) * r * 0.55;
const grad = ctx.createRadialGradient(gx, gy, 0, gx, gy, r * 0.75);
grad.addColorStop(0, `rgba(${rr},${gg},${bb},${0.65 * intensity})`);
grad.addColorStop(0.45, `rgba(${rr},${gg},${bb},${0.25 * intensity})`);
grad.addColorStop(1, `rgba(${rr},${gg},${bb},0)`);
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.arc(cx, cy, r, baseRad - SPAN / 2, baseRad + SPAN / 2);
ctx.closePath();
ctx.fillStyle = grad;
ctx.fill();
if (intensity > 0.05) {
ctx.font = `bold ${Math.round(13 * intensity + 7)}px "Courier New", monospace`;
ctx.fillStyle = `rgba(${rr},${gg},${bb},${Math.min(intensity * 1.2, 0.9)})`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(zone.name, cx + Math.cos(baseRad) * r * 0.62, cy + Math.sin(baseRad) * r * 0.62);
}
}
ctx.restore();
if (heatmapTexture) heatmapTexture.needsUpdate = true;
}
async function updateHeatmap() {
let commits = [];
try {
const res = await fetch(
'http://143.198.27.163:3000/api/v1/repos/Timmy_Foundation/the-nexus/commits?limit=50',
{ headers: { 'Authorization': 'token dc0517a965226b7a0c5ffdd961b1ba26521ac592' } }
);
if (res.ok) commits = await res.json();
} catch { /* network error — use zero baseline */ }
const DECAY_MS = 24 * 60 * 60 * 1000;
const now = Date.now();
const raw = Object.fromEntries(HEATMAP_ZONES.map(z => [z.name, 0]));
for (const commit of commits) {
const author = commit.commit?.author?.name || commit.author?.login || '';
const ts = new Date(commit.commit?.author?.date || 0).getTime();
const age = now - ts;
if (age > DECAY_MS) continue;
const weight = 1 - age / DECAY_MS;
for (const zone of HEATMAP_ZONES) {
if (zone.authorMatch.test(author)) { raw[zone.name] += weight; break; }
}
}
const MAX_W = 8;
for (const zone of HEATMAP_ZONES) {
_heatZoneIntensity[zone.name] = Math.min(raw[zone.name] / MAX_W, 1.0);
}
drawHeatmap();
}
// ═══ BATCAVE TERMINAL ═══
function createBatcaveTerminal() {
const terminalGroup = new THREE.Group();
@@ -1408,6 +1512,9 @@ function gameLoop() {
const sky = scene.getObjectByName('skybox');
if (sky) sky.material.uniforms.uTime.value = elapsed;
// Pulse heatmap opacity
if (heatmapMat) heatmapMat.opacity = 0.75 + Math.sin(elapsed * 0.6) * 0.2;
batcaveTerminals.forEach(t => {
if (t.scanMat?.uniforms) t.scanMat.uniforms.uTime.value = elapsed;
});