diff --git a/app.js b/app.js index 58b4431..c63dc48 100644 --- a/app.js +++ b/app.js @@ -22,6 +22,68 @@ const NEXUS = { } }; +// ═══ SOVEREIGN STATE (The Heartbeat) ═══ +const STATE = { + metrics: { + fps: 0, + drawCalls: 0, + triangles: 0, + uptime: 0, + activeLoops: 5, + cpu: 12, + mem: 4.2 + }, + agents: { + timmy: 'RUNNING', + kimi: 'STANDBY', + claude: 'ACTIVE', + perplexity: 'STANDBY' + }, + thoughts: [ + 'ANALYZING WORLD...', + 'SYNCING MEMORY...', + 'WAITING FOR INPUT', + 'SOUL ON BITCOIN' + ], + lastUpdate: 0, + pulseRate: 1.0 // Hz +}; + +// ═══ STATE BROADCASTER ═══ +const Broadcaster = { + listeners: [], + subscribe(fn) { this.listeners.push(fn); }, + broadcast() { this.listeners.forEach(fn => fn(STATE)); } +}; + +// ═══ STATE UPDATER ═══ +function updateSovereignState(elapsed) { + STATE.metrics.uptime = elapsed; + + // Simulate some jitter/activity + if (Math.random() > 0.95) { + STATE.metrics.cpu = 10 + Math.floor(Math.random() * 15); + STATE.metrics.activeLoops = 4 + Math.floor(Math.random() * 3); + + // Random thought shift + if (Math.random() > 0.7) { + const newThoughts = [ + 'DECENTRALIZING COGNITION', + 'ZAPPING CONTRIBUTORS', + 'MAPPING SPATIAL LOOPS', + 'REFINING LORA WEIGHTS', + 'OBSERVING ALEXANDER', + 'NEXUS INTEGRITY: 100%', + 'HERMES LINK STABLE' + ]; + STATE.thoughts.shift(); + STATE.thoughts.push(newThoughts[Math.floor(Math.random() * newThoughts.length)]); + } + + Broadcaster.broadcast(); + } +} + // ═══ STATE ═══ let camera, scene, renderer, composer; let clock, playerPos, playerRot; @@ -346,22 +408,23 @@ function createBatcaveTerminal() { const terminalGroup = new THREE.Group(); terminalGroup.position.set(0, 0, -8); - const panelData = [ - { title: 'NEXUS COMMAND', color: NEXUS.colors.primary, rot: -0.4, x: -6, y: 3, lines: ['> STATUS: NOMINAL', '> UPTIME: 142.4h', '> HARNESS: STABLE', '> MODE: SOVEREIGN'] }, - { title: 'DEV QUEUE', color: NEXUS.colors.gold, rot: -0.2, x: -3, y: 3, lines: ['> ISSUE #4: CORE', '> ISSUE #5: PORTAL', '> ISSUE #6: TERMINAL', '> ISSUE #7: TIMMY'] }, - { title: 'METRICS', color: NEXUS.colors.secondary, rot: 0, x: 0, y: 3, lines: ['> CPU: 12% [||....]', '> MEM: 4.2GB', '> COMMITS: 842', '> ACTIVE LOOPS: 5'] }, - { title: 'THOUGHTS', color: NEXUS.colors.primary, rot: 0.2, x: 3, y: 3, lines: ['> ANALYZING WORLD...', '> SYNCING MEMORY...', '> WAITING FOR INPUT', '> SOUL ON BITCOIN'] }, - { title: 'AGENT STATUS', color: NEXUS.colors.gold, rot: 0.4, x: 6, y: 3, lines: ['> TIMMY: ● RUNNING', '> KIMI: ○ STANDBY', '> CLAUDE: ● ACTIVE', '> PERPLEXITY: ○'] }, + const panels = [ + { id: 'command', title: 'NEXUS COMMAND', color: NEXUS.colors.primary, rot: -0.4, x: -6, y: 3 }, + { id: 'queue', title: 'DEV QUEUE', color: NEXUS.colors.gold, rot: -0.2, x: -3, y: 3 }, + { id: 'metrics', title: 'METRICS', color: NEXUS.colors.secondary, rot: 0, x: 0, y: 3 }, + { id: 'thoughts',title: 'THOUGHTS', color: NEXUS.colors.primary, rot: 0.2, x: 3, y: 3 }, + { id: 'agents', title: 'AGENT STATUS', color: NEXUS.colors.gold, rot: 0.4, x: 6, y: 3 }, ]; - panelData.forEach(data => { - createTerminalPanel(terminalGroup, data.x, data.y, data.rot, data.title, data.color, data.lines); + panels.forEach(data => { + createTerminalPanel(terminalGroup, data); }); scene.add(terminalGroup); } -function createTerminalPanel(parent, x, y, rot, title, color, lines) { +function createTerminalPanel(parent, data) { + const { x, y, rot, title, color, id } = data; const w = 2.8, h = 3.5; const group = new THREE.Group(); group.position.set(x, y, 0); @@ -392,25 +455,6 @@ function createTerminalPanel(parent, x, y, rot, title, color, lines) { textCanvas.height = 640; const ctx = textCanvas.getContext('2d'); - // Header - ctx.fillStyle = '#' + new THREE.Color(color).getHexString(); - ctx.font = 'bold 32px "Orbitron", sans-serif'; - ctx.fillText(title, 20, 45); - ctx.fillRect(20, 55, 472, 2); - - // Lines - ctx.font = '20px "JetBrains Mono", monospace'; - ctx.fillStyle = '#a0b8d0'; - lines.forEach((line, i) => { - // Color active indicators - let fillColor = '#a0b8d0'; - if (line.includes('● RUNNING') || line.includes('● ACTIVE')) fillColor = '#4af0c0'; - else if (line.includes('○ STANDBY')) fillColor = '#5a6a8a'; - else if (line.includes('NOMINAL')) fillColor = '#4af0c0'; - ctx.fillStyle = fillColor; - ctx.fillText(line, 20, 100 + i * 40); - }); - const textTexture = new THREE.CanvasTexture(textCanvas); textTexture.minFilter = THREE.LinearFilter; const textMat = new THREE.MeshBasicMaterial({ @@ -423,6 +467,56 @@ function createTerminalPanel(parent, x, y, rot, title, color, lines) { textMesh.position.z = 0.01; group.add(textMesh); + // Update function for this specific panel + const updatePanel = (state) => { + ctx.clearRect(0, 0, 512, 640); + + // Header + ctx.fillStyle = '#' + new THREE.Color(color).getHexString(); + ctx.font = 'bold 32px "Orbitron", sans-serif'; + ctx.fillText(title, 20, 45); + ctx.fillRect(20, 55, 472, 2); + + ctx.font = '20px "JetBrains Mono", monospace'; + ctx.fillStyle = '#a0b8d0'; + + let lines = []; + if (id === 'command') { + lines = [ + `> STATUS: NOMINAL`, + `> UPTIME: ${state.metrics.uptime.toFixed(1)}s`, + `> HARNESS: STABLE`, + `> MODE: SOVEREIGN` + ]; + } else if (id === 'queue') { + lines = ['> ISSUE #4: CORE', '> ISSUE #5: PORTAL', '> ISSUE #6: TERMINAL', '> ISSUE #39: HEART']; + } else if (id === 'metrics') { + lines = [ + `> CPU: ${state.metrics.cpu}%`, + `> MEM: ${state.metrics.mem}GB`, + `> LOOPS: ${state.metrics.activeLoops}`, + `> FPS: ${state.metrics.fps}` + ]; + } else if (id === 'thoughts') { + lines = state.thoughts.map(t => `> ${t}`); + } else if (id === 'agents') { + lines = Object.entries(state.agents).map(([name, status]) => `> ${name.toUpperCase()}: ${status}`); + } + + lines.forEach((line, i) => { + let fillColor = '#a0b8d0'; + if (line.includes('RUNNING') || line.includes('ACTIVE')) fillColor = '#4af0c0'; + ctx.fillStyle = fillColor; + ctx.fillText(line, 20, 100 + i * 40); + }); + + textTexture.needsUpdate = true; + }; + + // Initial draw + updatePanel(STATE); + Broadcaster.subscribe(updatePanel); + // Scanline effect overlay const scanGeo = new THREE.PlaneGeometry(w, h); const scanMat = new THREE.ShaderMaterial({ @@ -458,18 +552,6 @@ function createTerminalPanel(parent, x, y, rot, title, color, lines) { scanMesh.position.z = 0.02; group.add(scanMesh); - // Glow behind panel - const glowGeo = new THREE.PlaneGeometry(w + 0.5, h + 0.5); - const glowMat = new THREE.MeshBasicMaterial({ - color: color, - transparent: true, - opacity: 0.06, - side: THREE.DoubleSide, - }); - const glowMesh = new THREE.Mesh(glowGeo, glowMat); - glowMesh.position.z = -0.05; - group.add(glowMesh); - parent.add(group); batcaveTerminals.push({ group, scanMat, borderMat }); } @@ -877,6 +959,9 @@ function gameLoop() { const delta = Math.min(clock.getDelta(), 0.1); const elapsed = clock.elapsedTime; + // ─── Sovereign State Update ───────────────────────────────────── + updateSovereignState(elapsed); + // ─── Navigation update ─────────────────────────────────────────── const mode = NAV_MODES[navModeIdx]; const chatActive = document.activeElement === document.getElementById('chat-input'); @@ -993,6 +1078,7 @@ function gameLoop() { core.position.y = 2.5 + Math.sin(elapsed * 1.2) * 0.3; core.rotation.y = elapsed * 0.4; core.rotation.x = elapsed * 0.2; + // Core pulses in sync with state updates (simulated heartbeat) core.material.emissiveIntensity = 1.5 + Math.sin(elapsed * 2) * 0.5; } @@ -1006,11 +1092,14 @@ function gameLoop() { fps = frameCount; frameCount = 0; lastFPSTime = now; + // Update state metrics + STATE.metrics.fps = fps; + STATE.metrics.drawCalls = renderer.info.render.calls; + STATE.metrics.triangles = renderer.info.render.triangles; } if (debugOverlay) { - const info = renderer.info; debugOverlay.textContent = - `FPS: ${fps} Draw: ${info.render?.calls} Tri: ${info.render?.triangles} [${performanceTier}]\n` + + `FPS: ${fps} Draw: ${renderer.info.render.calls} Tri: ${renderer.info.render.triangles} [${performanceTier}]\n` + `Pos: ${playerPos.x.toFixed(1)}, ${playerPos.y.toFixed(1)}, ${playerPos.z.toFixed(1)} NAV: ${NAV_MODES[navModeIdx]}`; } renderer.info.reset();