import * as THREE from 'three'; import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'; import { SMAAPass } from 'three/addons/postprocessing/SMAAPass.js'; import { ArchonAssembler } from './archon_assembler.js'; // ═══════════════════════════════════════════ // NEXUS v2.0 — WebSocket Bridge to Timmy // ═══════════════════════════════════════════ const NEXUS = { colors: { primary: 0x4af0c0, secondary: 0x7b5cff, bg: 0x050510, panelBg: 0x0a0f28, nebula1: 0x1a0a3e, nebula2: 0x0a1a3e, gold: 0xffd700, danger: 0xff4466, gridLine: 0x1a2a4a, } }; // ═══ STATE ═══ let camera, scene, renderer, composer; let clock, playerPos, playerRot; let keys = {}; let mouseDown = false; let batcaveTerminals = []; let portals = []; let visionPoints = []; let agents = []; let activePortal = null; let activeVisionPoint = null; let portalOverlayActive = false; let visionOverlayActive = false; let thoughtStreamMesh; let harnessPulseMesh; let powerMeterBars = []; let particles, dustParticles; let dualBrainGroup, dualBrainPanelTexture, dualBrainPanelCanvas; let cloudOrb, localOrb, dualBrainLight; let debugOverlay; let voidLight = null; let frameCount = 0, lastFPSTime = 0, fps = 0; let chatOpen = true; let loadProgress = 0; let performanceTier = 'high'; let archonAssembler; // ═══ COMMIT HEATMAP ═══ let heatmapMesh = null, heatmapMat = null, 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 ═══ const NAV_MODES = ['walk', 'orbit', 'fly']; let navModeIdx = 0; const orbitState = { target: new THREE.Vector3(0, 2, 0), radius: 14, theta: Math.PI, phi: Math.PI / 6, minR: 3, maxR: 40, lastX: 0, lastY: 0, }; let flyY = 2; // ═══ WEBSOCKET BRIDGE ═══ const WS_URL = new URLSearchParams(location.search).get('ws') || 'ws://localhost:8765'; let ws = null; let wsConnected = false; let wsReconnectTimer = null; function initWebSocket() { if (ws) { try { ws.close(); } catch (_) {} } try { ws = new WebSocket(WS_URL); ws.onopen = () => { wsConnected = true; addChatMessage('system', `Connected to Timmy @ ${WS_URL}`); wsSend({ type: 'presence', event: 'join' }); if (wsReconnectTimer) { clearInterval(wsReconnectTimer); wsReconnectTimer = null; } }; ws.onmessage = (e) => { try { onWsMessage(JSON.parse(e.data)); } catch (_) {} }; ws.onclose = () => { wsConnected = false; addChatMessage('system', 'Connection to Timmy lost. Reconnecting...'); scheduleReconnect(); }; ws.onerror = () => { wsConnected = false; scheduleReconnect(); }; } catch (_) { wsConnected = false; scheduleReconnect(); } } function scheduleReconnect() { if (wsReconnectTimer) return; wsReconnectTimer = setInterval(() => { if (!wsConnected) initWebSocket(); }, 5000); } function wsSend(msg) { if (ws && ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(msg)); } function onWsMessage(data) { switch (data.type) { case 'agent_state': { const agent = agents.find(a => a.id === data.agent); if (!agent) break; if (data.state === 'thinking') { setAgentActivity(agent, ACTIVITY_STATES.THINKING); agent.activityLocked = true; } else if (data.state === 'processing') { setAgentActivity(agent, ACTIVITY_STATES.PROCESSING); agent.activityLocked = true; } else if (data.state === 'waiting') { setAgentActivity(agent, ACTIVITY_STATES.WAITING); agent.activityLocked = true; } else { setAgentActivity(agent, ACTIVITY_STATES.NONE); agent.activityLocked = false; } if (data.thought) addAgentLog(data.agent, data.thought); break; } case 'agent_move': { const agent = agents.find(a => a.id === data.agent); if (agent) agent.targetPos.set(data.x, 0, data.z); break; } case 'chat_response': addChatMessage(data.agent || 'timmy', data.text); // Clear Timmy's thinking state const timmy = agents.find(a => a.id === 'timmy'); if (timmy) { setAgentActivity(timmy, ACTIVITY_STATES.NONE); timmy.activityLocked = false; } break; case 'system_metrics': updateTerminalMetrics(data); break; case 'dual_brain': updateDualBrainFromWS(data); break; case 'heartbeat': break; } } // ═══ INIT ═══ async function init() { clock = new THREE.Clock(); playerPos = new THREE.Vector3(0, 2, 12); playerRot = new THREE.Euler(0, 0, 0, 'YXZ'); const canvas = document.getElementById('nexus-canvas'); renderer = new THREE.WebGLRenderer({ canvas, antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.toneMapping = THREE.ACESFilmicToneMapping; renderer.toneMappingExposure = 1.2; renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap; performanceTier = detectPerformanceTier(); updateLoad(10); scene = new THREE.Scene(); scene.fog = new THREE.FogExp2(0x050510, 0.012); camera = new THREE.PerspectiveCamera(65, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.copy(playerPos); updateLoad(20); createSkybox(); updateLoad(30); createLighting(); updateLoad(40); createFloor(); createCommitHeatmap(); updateLoad(50); createBatcaveTerminal(); updateLoad(60); try { const portalData = await (await fetch('./portals.json')).json(); createPortals(portalData); } catch (e) { addChatMessage('error', 'Portal registry offline.'); } try { const visionData = await (await fetch('./vision.json')).json(); createVisionPoints(visionData); } catch (_) {} updateLoad(80); createParticles(); createDustParticles(); updateLoad(85); createAmbientStructures(); createAgentPresences(); createThoughtStream(); createHarnessPulse(); createSessionPowerMeter(); createDualBrainPanel(); updateLoad(90); // Test Archon Assembler const testManifest = { head: true, torso: true, arms: true, legs: true, hands: true, eyes: true, mouth: true, wings: true, aura: true, crown: true, }; archonAssembler = new ArchonAssembler(scene, testManifest); archonAssembler.assemble(); archonAssembler.spawn(new THREE.Vector3(0, 0, -15)); composer = new EffectComposer(renderer); composer.addPass(new RenderPass(scene, camera)); composer.addPass(new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 0.6, 0.4, 0.85)); composer.addPass(new SMAAPass(window.innerWidth, window.innerHeight)); updateLoad(95); setupControls(); window.addEventListener('resize', onResize); debugOverlay = document.getElementById('debug-overlay'); updateLoad(100); setTimeout(() => { document.getElementById('loading-screen').classList.add('fade-out'); const enterPrompt = document.getElementById('enter-prompt'); enterPrompt.style.display = 'flex'; enterPrompt.addEventListener('click', () => { enterPrompt.classList.add('fade-out'); document.getElementById('hud').style.display = 'block'; setTimeout(() => enterPrompt.remove(), 600); }, { once: true }); setTimeout(() => document.getElementById('loading-screen').remove(), 900); }, 600); initWebSocket(); requestAnimationFrame(gameLoop); } function updateLoad(pct) { loadProgress = pct; const fill = document.getElementById('load-progress'); if (fill) fill.style.width = pct + '%'; } // ═══ PERFORMANCE ═══ function detectPerformanceTier() { const isMobile = /Mobi|Android|iPhone|iPad/i.test(navigator.userAgent) || window.innerWidth < 768; const cores = navigator.hardwareConcurrency || 4; if (isMobile) { renderer.setPixelRatio(1); renderer.shadowMap.enabled = false; return 'low'; } if (cores < 8) { renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5)); renderer.shadowMap.type = THREE.BasicShadowMap; return 'medium'; } renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); return 'high'; } function particleCount(base) { return Math.floor(base * (performanceTier === 'low' ? 0.25 : performanceTier === 'medium' ? 0.6 : 1)); } // ═══ SKYBOX ═══ function createSkybox() { const skyGeo = new THREE.SphereGeometry(400, 64, 64); const skyMat = new THREE.ShaderMaterial({ uniforms: { uTime: { value: 0 }, uColor1: { value: new THREE.Color(0x0a0520) }, uColor2: { value: new THREE.Color(0x1a0a3e) }, uColor3: { value: new THREE.Color(0x0a1a3e) }, uStarDensity: { value: 0.97 }, }, vertexShader: ` varying vec3 vPos; void main() { vPos = position; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: ` uniform float uTime; uniform vec3 uColor1, uColor2, uColor3; uniform float uStarDensity; varying vec3 vPos; float hash(vec3 p) { p = fract(p * vec3(443.897,441.423,437.195)); p += dot(p,p.yzx+19.19); return fract((p.x+p.y)*p.z); } float noise(vec3 p) { vec3 i = floor(p), f = fract(p); f = f*f*(3.0-2.0*f); return mix(mix(mix(hash(i),hash(i+vec3(1,0,0)),f.x),mix(hash(i+vec3(0,1,0)),hash(i+vec3(1,1,0)),f.x),f.y), mix(mix(hash(i+vec3(0,0,1)),hash(i+vec3(1,0,1)),f.x),mix(hash(i+vec3(0,1,1)),hash(i+vec3(1,1,1)),f.x),f.y),f.z); } float fbm(vec3 p) { float v=0.0,a=0.5; for(int i=0;i<5;i++){v+=a*noise(p);p*=2.0;a*=0.5;} return v; } void main() { vec3 dir = normalize(vPos); float n1=fbm(dir*3.0+uTime*0.02), n2=fbm(dir*5.0-uTime*0.015+100.0), n3=fbm(dir*2.0+uTime*0.01+200.0); vec3 col = mix(mix(uColor1,uColor2,smoothstep(0.3,0.7,n1)),uColor3,smoothstep(0.4,0.8,n2)*0.5); col += vec3(0.15,0.05,0.25)*pow(n1*n2,2.0)*1.5 + vec3(0.05,0.15,0.25)*pow(n3,3.0); float sf=hash(dir*800.0), stars=step(uStarDensity,sf)*(0.5+0.5*hash(dir*1600.0)); float tw=0.7+0.3*sin(uTime*2.0+hash(dir*400.0)*6.28); col += vec3(stars*tw) + vec3(0.8,0.9,1.0)*step(0.998,sf)*tw; gl_FragColor = vec4(col, 1.0); } `, side: THREE.BackSide, }); const sky = new THREE.Mesh(skyGeo, skyMat); sky.name = 'skybox'; scene.add(sky); } // ═══ LIGHTING ═══ function createLighting() { scene.add(new THREE.AmbientLight(0x1a1a3a, 0.4)); const dirLight = new THREE.DirectionalLight(0x4466aa, 0.6); dirLight.position.set(10, 20, 10); dirLight.castShadow = renderer.shadowMap.enabled; dirLight.shadow.mapSize.set(performanceTier === 'high' ? 2048 : performanceTier === 'medium' ? 1024 : 512, performanceTier === 'high' ? 2048 : performanceTier === 'medium' ? 1024 : 512); scene.add(dirLight); const tl = new THREE.PointLight(NEXUS.colors.primary, 2, 30, 1.5); tl.position.set(0, 1, -5); scene.add(tl); const pl = new THREE.PointLight(NEXUS.colors.secondary, 1.5, 25, 1.5); pl.position.set(-8, 3, -8); scene.add(pl); } // ═══ FLOOR ═══ function createFloor() { const platGeo = new THREE.CylinderGeometry(25, 25, 0.3, 6); const platMat = new THREE.MeshPhysicalMaterial({ color: NEXUS.colors.bg, transparent: true, opacity: 0.2, transmission: 0.9, roughness: 0.1, metalness: 0.2 }); const platform = new THREE.Mesh(platGeo, platMat); platform.position.y = -0.15; platform.receiveShadow = true; scene.add(platform); const gridHelper = new THREE.GridHelper(50, 50, NEXUS.colors.gridLine, NEXUS.colors.gridLine); gridHelper.material.opacity = 0.15; gridHelper.material.transparent = true; gridHelper.position.y = 0.02; scene.add(gridHelper); const ringGeo = new THREE.RingGeometry(24.5, 25.2, 6); const ringMat = new THREE.MeshBasicMaterial({ color: NEXUS.colors.primary, transparent: true, opacity: 0.4, side: THREE.DoubleSide }); const ring = new THREE.Mesh(ringGeo, ringMat); ring.rotation.x = Math.PI / 2; ring.position.y = 0.05; scene.add(ring); _buildGlassFloor(); } function _buildGlassFloor() { const TILE = 0.85, GAP = 0.14, STEP = TILE + GAP, RADIUS = 4.55; const group = new THREE.Group(); // Frame ring const frameMat = new THREE.MeshStandardMaterial({ color: 0x0a1828, metalness: 0.9, roughness: 0.1, emissive: new THREE.Color(NEXUS.colors.primary).multiplyScalar(0.06) }); const rim = new THREE.Mesh(new THREE.RingGeometry(4.7, 5.3, 64), frameMat); rim.rotation.x = -Math.PI / 2; rim.position.y = 0.01; group.add(rim); const torus = new THREE.Mesh(new THREE.TorusGeometry(5.0, 0.1, 6, 64), frameMat); torus.rotation.x = Math.PI / 2; torus.position.y = 0.01; group.add(torus); // Collect tile positions const slots = []; for (let r = -5; r <= 5; r++) for (let c = -5; c <= 5; c++) { const x = c * STEP, z = r * STEP; if (Math.sqrt(x * x + z * z) <= RADIUS) slots.push({ x, z }); } // Instanced glass tiles const tileMat = new THREE.MeshPhysicalMaterial({ color: new THREE.Color(NEXUS.colors.primary), transparent: true, opacity: 0.09, roughness: 0, metalness: 0, transmission: 0.92, thickness: 0.06, side: THREE.DoubleSide, depthWrite: false, }); const tileMesh = new THREE.InstancedMesh(new THREE.PlaneGeometry(TILE, TILE), tileMat, slots.length); tileMesh.instanceMatrix.setUsage(THREE.StaticDrawUsage); const dummy = new THREE.Object3D(); dummy.rotation.x = -Math.PI / 2; slots.forEach((s, i) => { dummy.position.set(s.x, 0.005, s.z); dummy.updateMatrix(); tileMesh.setMatrixAt(i, dummy.matrix); }); tileMesh.instanceMatrix.needsUpdate = true; group.add(tileMesh); // Edge lines (single draw call) const HS = TILE / 2; const verts = new Float32Array(slots.length * 24); let vi = 0; for (const { x, z } of slots) { const y = 0.008; verts[vi++]=x-HS;verts[vi++]=y;verts[vi++]=z-HS; verts[vi++]=x+HS;verts[vi++]=y;verts[vi++]=z-HS; verts[vi++]=x+HS;verts[vi++]=y;verts[vi++]=z-HS; verts[vi++]=x+HS;verts[vi++]=y;verts[vi++]=z+HS; verts[vi++]=x+HS;verts[vi++]=y;verts[vi++]=z+HS; verts[vi++]=x-HS;verts[vi++]=y;verts[vi++]=z+HS; verts[vi++]=x-HS;verts[vi++]=y;verts[vi++]=z+HS; verts[vi++]=x-HS;verts[vi++]=y;verts[vi++]=z-HS; } const edgeGeo = new THREE.BufferGeometry(); edgeGeo.setAttribute('position', new THREE.BufferAttribute(verts, 3)); const edgeMat = new THREE.LineBasicMaterial({ color: NEXUS.colors.primary, transparent: true, opacity: 0.55 }); group.add(new THREE.LineSegments(edgeGeo, edgeMat)); // Void light voidLight = new THREE.PointLight(NEXUS.colors.primary, 0.5, 14); voidLight.position.set(0, -3.5, 0); group.add(voidLight); scene.add(group); } // ═══ COMMIT HEATMAP ═══ 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); updateHeatmap(); setInterval(updateHeatmap, 5 * 60 * 1000); } function drawHeatmap() { const ctx = _heatmapCanvas.getContext('2d'), cx = 256, cy = 256, r = 246; 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, 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 - Math.PI / 4, baseRad + Math.PI / 4); 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('/api/commits'); if (res.ok) commits = await res.json(); } catch {} const DECAY_MS = 24 * 60 * 60 * 1000, now = Date.now(); const raw = Object.fromEntries(HEATMAP_ZONES.map(z => [z.name, 0])); for (const c of commits) { const author = c.commit?.author?.name || c.author?.login || ''; const age = now - new Date(c.commit?.author?.date || 0).getTime(); if (age > DECAY_MS) continue; const weight = 1 - age / DECAY_MS; for (const z of HEATMAP_ZONES) { if (z.authorMatch.test(author)) { raw[z.name] += weight; break; } } } for (const z of HEATMAP_ZONES) _heatZoneIntensity[z.name] = Math.min(raw[z.name] / 8, 1.0); drawHeatmap(); } // ═══ BATCAVE TERMINAL ═══ function createBatcaveTerminal() { const group = new THREE.Group(); group.position.set(0, 0, -8); const panels = [ { title: 'NEXUS COMMAND', color: NEXUS.colors.primary, rot: -0.4, x: -6, y: 3, lines: ['> STATUS: NOMINAL', '> UPTIME: —', '> HARNESS: STABLE', '> MODE: SOVEREIGN'] }, { title: 'DEV QUEUE', color: NEXUS.colors.gold, rot: -0.2, x: -3, y: 3, lines: ['> AWAITING WS...', '> CONNECT TIMMY', '> TO SEE LIVE', '> ISSUE DATA'] }, { title: 'METRICS', color: NEXUS.colors.secondary, rot: 0, x: 0, y: 3, lines: ['> CPU: —', '> MEM: —', '> COMMITS: —', '> AGENTS: —'] }, { title: 'THOUGHTS', color: NEXUS.colors.primary, rot: 0.2, x: 3, y: 3, lines: ['> AWAITING', '> WEBSOCKET', '> CONNECTION', '> TO TIMMY'] }, { title: 'AGENT STATUS', color: NEXUS.colors.gold, rot: 0.4, x: 6, y: 3, lines: ['> TIMMY: ○ OFFLINE', '> KIMI: ○ OFFLINE', '> CLAUDE: ○ OFFLINE', '> PERPLEXITY: ○'] }, ]; panels.forEach(d => createTerminalPanel(group, d.x, d.y, d.rot, d.title, d.color, d.lines)); scene.add(group); } function createTerminalPanel(parent, x, y, rot, title, color, lines) { const w = 2.8, h = 3.5, group = new THREE.Group(); group.position.set(x, y, 0); group.rotation.y = rot; const bg = new THREE.Mesh(new THREE.PlaneGeometry(w, h), new THREE.MeshPhysicalMaterial({ color: NEXUS.colors.panelBg, transparent: true, opacity: 0.6, roughness: 0.1, metalness: 0.5, side: THREE.DoubleSide })); group.add(bg); const border = new THREE.Mesh(new THREE.PlaneGeometry(w + 0.05, h + 0.05), new THREE.MeshBasicMaterial({ color, transparent: true, opacity: 0.3, side: THREE.DoubleSide })); border.position.z = -0.01; group.add(border); const tc = document.createElement('canvas'); tc.width = 512; tc.height = 640; const ctx = tc.getContext('2d'); 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'; lines.forEach((line, i) => { ctx.fillStyle = line.includes('●') ? '#4af0c0' : line.includes('○') ? '#5a6a8a' : '#a0b8d0'; ctx.fillText(line, 20, 100 + i * 40); }); const tex = new THREE.CanvasTexture(tc); tex.minFilter = THREE.LinearFilter; const textMesh = new THREE.Mesh(new THREE.PlaneGeometry(w * 0.95, h * 0.95), new THREE.MeshBasicMaterial({ map: tex, transparent: true, side: THREE.DoubleSide, depthWrite: false })); textMesh.position.z = 0.01; group.add(textMesh); const scanMat = new THREE.ShaderMaterial({ transparent: true, depthWrite: false, uniforms: { uTime: { value: 0 }, uColor: { value: new THREE.Color(color) } }, vertexShader: `varying vec2 vUv; void main(){vUv=uv;gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0);}`, fragmentShader: `uniform float uTime;uniform vec3 uColor;varying vec2 vUv;void main(){float s=pow(sin(vUv.y*200.0+uTime*2.0)*0.5+0.5,8.0);float w=smoothstep(0.0,0.02,abs(fract(vUv.y-uTime*0.1)-0.5));float a=s*0.04+(1.0-w)*0.08;gl_FragColor=vec4(uColor,a);}`, side: THREE.DoubleSide, }); const scan = new THREE.Mesh(new THREE.PlaneGeometry(w, h), scanMat); scan.position.z = 0.02; group.add(scan); parent.add(group); batcaveTerminals.push({ group, scanMat, borderMat: border.material }); } function updateTerminalMetrics(data) { // WS-driven terminal update — could redraw canvases in the future // For now, metrics come through and are visible in the thought stream } // ═══ AGENT SYSTEM ═══ const AGENT_STATES = { IDLE: 'IDLE', PACING: 'PACING', LOOKING: 'LOOKING', READING: 'READING' }; const ACTIVITY_STATES = { NONE: 'NONE', WAITING: 'WAITING', THINKING: 'THINKING', PROCESSING: 'PROCESSING' }; function createActivityIndicator(color) { const group = new THREE.Group(); group.position.y = 4.2; group.visible = false; const waitMesh = new THREE.Mesh(new THREE.SphereGeometry(0.18, 16, 16), new THREE.MeshBasicMaterial({ color, transparent: true, opacity: 0.85 })); waitMesh.name = 'indicator_waiting'; waitMesh.visible = false; group.add(waitMesh); const thinkMesh = new THREE.Mesh(new THREE.OctahedronGeometry(0.2, 0), new THREE.MeshBasicMaterial({ color, wireframe: true })); thinkMesh.name = 'indicator_thinking'; thinkMesh.visible = false; group.add(thinkMesh); const procMesh = new THREE.Mesh(new THREE.TorusGeometry(0.18, 0.04, 8, 32), new THREE.MeshBasicMaterial({ color })); procMesh.name = 'indicator_processing'; procMesh.visible = false; group.add(procMesh); return { group, waitMesh, thinkMesh, procMesh }; } function setAgentActivity(agent, state) { agent.activityState = state; agent.indicator.group.visible = (state !== ACTIVITY_STATES.NONE); agent.indicator.waitMesh.visible = (state === ACTIVITY_STATES.WAITING); agent.indicator.thinkMesh.visible = (state === ACTIVITY_STATES.THINKING); agent.indicator.procMesh.visible = (state === ACTIVITY_STATES.PROCESSING); } function buildPacingPath(station) { const r = 1.8; return [ new THREE.Vector3(station.x - r, 0, station.z), new THREE.Vector3(station.x, 0, station.z + r), new THREE.Vector3(station.x + r, 0, station.z - r * 0.5), ]; } function pickNextState() { const w = { IDLE: 40, PACING: 25, LOOKING: 20, READING: 15 }; let r = Math.random() * 100; for (const [s, wt] of Object.entries(w)) { r -= wt; if (r <= 0) return s; } return 'IDLE'; } function createAgentPresences() { const agentData = [ { id: 'timmy', name: 'TIMMY', color: NEXUS.colors.primary, pos: { x: -4, z: -4 } }, { id: 'kimi', name: 'KIMI', color: NEXUS.colors.secondary, pos: { x: 4, z: -4 } }, { id: 'claude', name: 'CLAUDE', color: NEXUS.colors.gold, pos: { x: 0, z: -6 } }, { id: 'perplexity', name: 'PERPLEXITY', color: 0x4488ff, pos: { x: -6, z: -2 } }, ]; agentData.forEach(data => { const group = new THREE.Group(); group.position.set(data.pos.x, 0, data.pos.z); const color = new THREE.Color(data.color); const orb = new THREE.Mesh(new THREE.SphereGeometry(0.4, 32, 32), new THREE.MeshPhysicalMaterial({ color, emissive: color, emissiveIntensity: 2, roughness: 0, metalness: 1, transmission: 0.8, thickness: 0.5, })); orb.position.y = 3; group.add(orb); const halo = new THREE.Mesh(new THREE.TorusGeometry(0.6, 0.02, 16, 64), new THREE.MeshBasicMaterial({ color, transparent: true, opacity: 0.4 })); halo.position.y = 3; halo.rotation.x = Math.PI / 2; group.add(halo); const lc = document.createElement('canvas'); lc.width = 256; lc.height = 64; const lctx = lc.getContext('2d'); lctx.font = 'bold 24px "Orbitron",sans-serif'; lctx.fillStyle = '#' + color.getHexString(); lctx.textAlign = 'center'; lctx.fillText(data.name, 128, 40); const label = new THREE.Mesh(new THREE.PlaneGeometry(2, 0.5), new THREE.MeshBasicMaterial({ map: new THREE.CanvasTexture(lc), transparent: true, side: THREE.DoubleSide })); label.position.y = 3.8; group.add(label); const indicator = createActivityIndicator(color); group.add(indicator.group); scene.add(group); agents.push({ id: data.id, group, orb, halo, color, station: data.pos, targetPos: new THREE.Vector3(data.pos.x, 0, data.pos.z), state: AGENT_STATES.IDLE, stateTimer: 2 + Math.random() * 4, lookAngle: 0, lookSpeed: 0.4 + Math.random() * 0.3, pacingPath: buildPacingPath(data.pos), pacingIdx: 0, indicator, activityState: ACTIVITY_STATES.NONE, activityLocked: false, }); }); } // ═══ THOUGHT STREAM ═══ function createThoughtStream() { const mat = new THREE.ShaderMaterial({ transparent: true, side: THREE.BackSide, depthWrite: false, uniforms: { uTime: { value: 0 }, uColor: { value: new THREE.Color(NEXUS.colors.primary) } }, vertexShader: `varying vec2 vUv; void main(){vUv=uv;gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0);}`, fragmentShader: `uniform float uTime;uniform vec3 uColor;varying vec2 vUv; float hash(vec2 p){return fract(sin(dot(p,vec2(12.9898,78.233)))*43758.5453);} void main(){float flow=fract(vUv.y-uTime*0.1);float lines=step(0.98,fract(vUv.x*20.0+uTime*0.05));float dots=step(0.99,hash(vUv*50.0+floor(uTime*10.0)*0.01)); float a=(lines*0.1+dots*0.5)*smoothstep(0.0,0.2,vUv.y)*smoothstep(1.0,0.8,vUv.y);gl_FragColor=vec4(uColor,a*0.3);}`, }); thoughtStreamMesh = new THREE.Mesh(new THREE.CylinderGeometry(8, 8, 12, 32, 1, true), mat); thoughtStreamMesh.position.y = 6; scene.add(thoughtStreamMesh); } function createHarnessPulse() { const mat = new THREE.MeshBasicMaterial({ color: NEXUS.colors.primary, transparent: true, opacity: 0, side: THREE.DoubleSide }); harnessPulseMesh = new THREE.Mesh(new THREE.RingGeometry(0.1, 0.2, 64), mat); harnessPulseMesh.rotation.x = -Math.PI / 2; harnessPulseMesh.position.y = 0.1; scene.add(harnessPulseMesh); } function createSessionPowerMeter() { const group = new THREE.Group(); group.position.set(0, 0, 3); const barGeo = new THREE.BoxGeometry(0.2, 0.1, 0.1); for (let i = 0; i < 12; i++) { const mat = new THREE.MeshStandardMaterial({ color: NEXUS.colors.primary, emissive: NEXUS.colors.primary, emissiveIntensity: 0.2, transparent: true, opacity: 0.6 }); const bar = new THREE.Mesh(barGeo, mat); bar.position.y = 0.2 + i * 0.2; group.add(bar); powerMeterBars.push(bar); } const lc = document.createElement('canvas'); lc.width = 256; lc.height = 64; const ctx = lc.getContext('2d'); ctx.font = 'bold 24px "Orbitron",sans-serif'; ctx.fillStyle = '#4af0c0'; ctx.textAlign = 'center'; ctx.fillText('POWER LEVEL', 128, 40); const label = new THREE.Mesh(new THREE.PlaneGeometry(2, 0.5), new THREE.MeshBasicMaterial({ map: new THREE.CanvasTexture(lc), transparent: true, side: THREE.DoubleSide })); label.position.y = 3; group.add(label); scene.add(group); } // ═══ DUAL-BRAIN PANEL ═══ function createDualBrainPanel() { dualBrainGroup = new THREE.Group(); dualBrainGroup.position.set(10, 3, -8); dualBrainGroup.lookAt(0, 3, 0); scene.add(dualBrainGroup); dualBrainPanelCanvas = document.createElement('canvas'); dualBrainPanelCanvas.width = 512; dualBrainPanelCanvas.height = 512; drawDualBrainTexture(null); dualBrainPanelTexture = new THREE.CanvasTexture(dualBrainPanelCanvas); const panelSprite = new THREE.Sprite(new THREE.SpriteMaterial({ map: dualBrainPanelTexture, transparent: true, opacity: 0.92, depthWrite: false })); panelSprite.scale.set(5, 5, 1); dualBrainGroup.add(panelSprite); dualBrainLight = new THREE.PointLight(0x4488ff, 0.6, 10); dualBrainLight.position.set(0, 0.5, 1); dualBrainGroup.add(dualBrainLight); const orbMat = (c) => new THREE.MeshStandardMaterial({ color: c, emissive: new THREE.Color(c), emissiveIntensity: 0.1, metalness: 0.3, roughness: 0.2, transparent: true, opacity: 0.85 }); cloudOrb = new THREE.Mesh(new THREE.SphereGeometry(0.35, 32, 32), orbMat(0x334466)); cloudOrb.position.set(-2, 3, 0); dualBrainGroup.add(cloudOrb); localOrb = new THREE.Mesh(new THREE.SphereGeometry(0.35, 32, 32), orbMat(0x334466)); localOrb.position.set(2, 3, 0); dualBrainGroup.add(localOrb); } function drawDualBrainTexture(data) { const W = 512, H = 512, ctx = dualBrainPanelCanvas.getContext('2d'); ctx.fillStyle = 'rgba(0,6,20,0.90)'; ctx.fillRect(0, 0, W, H); ctx.strokeStyle = '#4488ff'; ctx.lineWidth = 2; ctx.strokeRect(1, 1, W - 2, H - 2); ctx.font = 'bold 22px "Courier New",monospace'; ctx.fillStyle = '#88ccff'; ctx.textAlign = 'center'; ctx.fillText('\u25C8 DUAL-BRAIN STATUS', W / 2, 40); ctx.strokeStyle = '#1a3a6a'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(20, 52); ctx.lineTo(W - 20, 52); ctx.stroke(); if (!data) { ctx.font = 'bold 18px "Courier New",monospace'; ctx.fillStyle = '#334466'; ctx.fillText('AWAITING CONNECTION', W / 2, H / 2); ctx.font = '11px "Courier New",monospace'; ctx.fillStyle = '#223344'; ctx.fillText('Connect Timmy via WebSocket', W / 2, H / 2 + 28); } else { const y = 74; ctx.font = '13px "Courier New",monospace'; ctx.textAlign = 'left'; ctx.fillStyle = data.cloud?.status === 'connected' ? '#4af0c0' : '#ff4466'; ctx.fillText(`CLOUD: ${data.cloud?.model || '—'} [${data.cloud?.status || 'offline'}]`, 20, y); ctx.fillStyle = data.local?.status === 'connected' ? '#4af0c0' : '#ff4466'; ctx.fillText(`LOCAL: ${data.local?.model || '—'} [${data.local?.status || 'offline'}]`, 20, y + 24); } if (dualBrainPanelTexture) dualBrainPanelTexture.needsUpdate = true; } function updateDualBrainFromWS(data) { drawDualBrainTexture(data); // Activate orbs based on connection status if (cloudOrb && data.cloud?.status === 'connected') { cloudOrb.material.color.setHex(0x4488ff); cloudOrb.material.emissive.setHex(0x4488ff); cloudOrb.material.emissiveIntensity = 1.5; } if (localOrb && data.local?.status === 'connected') { localOrb.material.color.setHex(0x4af0c0); localOrb.material.emissive.setHex(0x4af0c0); localOrb.material.emissiveIntensity = 1.5; } } // ═══ VISION SYSTEM ═══ function createVisionPoints(data) { data.forEach(c => visionPoints.push(createVisionPoint(c))); } function createVisionPoint(config) { const group = new THREE.Group(); group.position.set(config.position.x, config.position.y, config.position.z); const color = new THREE.Color(config.color); const crystal = new THREE.Mesh(new THREE.OctahedronGeometry(0.6, 0), new THREE.MeshPhysicalMaterial({ color, emissive: color, emissiveIntensity: 1, roughness: 0, metalness: 1, transmission: 0.5, thickness: 1 })); crystal.position.y = 2.5; group.add(crystal); const ring = new THREE.Mesh(new THREE.TorusGeometry(0.8, 0.02, 16, 64), new THREE.MeshBasicMaterial({ color, transparent: true, opacity: 0.5 })); ring.position.y = 2.5; ring.rotation.x = Math.PI / 2; group.add(ring); const light = new THREE.PointLight(color, 1, 10); light.position.set(0, 2.5, 0); group.add(light); scene.add(group); return { config, group, crystal, ring, light }; } // ═══ PORTAL SYSTEM ═══ function createPortals(data) { data.forEach(c => portals.push(createPortal(c))); } function createPortal(config) { const group = new THREE.Group(); group.position.set(config.position.x, config.position.y, config.position.z); if (config.rotation) group.rotation.y = config.rotation.y; const pc = new THREE.Color(config.color); // Ring const ring = new THREE.Mesh(new THREE.TorusGeometry(3, 0.15, 16, 64), new THREE.MeshStandardMaterial({ color: pc, emissive: pc, emissiveIntensity: 1.5, roughness: 0.2, metalness: 0.8 })); ring.position.y = 3.5; ring.name = `portal_ring_${config.id}`; group.add(ring); // Swirl const swirlMat = new THREE.ShaderMaterial({ transparent: true, side: THREE.DoubleSide, uniforms: { uTime: { value: 0 }, uColor: { value: pc } }, vertexShader: `varying vec2 vUv; void main(){vUv=uv;gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0);}`, fragmentShader: `uniform float uTime;uniform vec3 uColor;varying vec2 vUv;void main(){vec2 c=vUv-0.5;float r=length(c),a=atan(c.y,c.x);float s1=sin(a*3.0+r*10.0-uTime*3.0)*0.5+0.5,s2=sin(a*5.0-r*8.0+uTime*2.0)*0.5+0.5;float mask=smoothstep(0.5,0.1,r);vec3 col=mix(mix(uColor,vec3(1),s1*0.3),vec3(1),s2*0.2);gl_FragColor=vec4(col,mask*(0.5+0.3*s1));}`, }); const swirl = new THREE.Mesh(new THREE.CircleGeometry(2.8, 64), swirlMat); swirl.position.y = 3.5; group.add(swirl); // Particles const pCount = 120, pPos = new Float32Array(pCount * 3); for (let i = 0; i < pCount; i++) { const angle = Math.random() * Math.PI * 2, r = 3.2 + Math.random() * 0.5; pPos[i*3] = Math.cos(angle)*r; pPos[i*3+1] = 3.5 + (Math.random()-0.5)*6; pPos[i*3+2] = (Math.random()-0.5)*0.5; } const pGeo = new THREE.BufferGeometry(); pGeo.setAttribute('position', new THREE.BufferAttribute(pPos, 3)); const pSystem = new THREE.Points(pGeo, new THREE.PointsMaterial({ color: pc, size: 0.08, transparent: true, opacity: 0.6, blending: THREE.AdditiveBlending, depthWrite: false })); group.add(pSystem); // Light const light = new THREE.PointLight(pc, 2, 15, 1.5); light.position.set(0, 3.5, 1); group.add(light); // Runes const runes = []; for (let i = 0; i < 8; i++) { const angle = (i / 8) * Math.PI * 2; const rune = new THREE.Mesh(new THREE.BoxGeometry(0.3, 0.8, 0.1), new THREE.MeshStandardMaterial({ color: pc, emissive: pc, emissiveIntensity: 0.8, transparent: true, opacity: 0.7, roughness: 0.2, metalness: 0.5 })); rune.position.set(Math.cos(angle) * 4.5, 4, Math.sin(angle) * 4.5); rune.rotation.y = angle + Math.PI / 2; group.add(rune); runes.push(rune); } // Label const lc = document.createElement('canvas'); lc.width = 512; lc.height = 64; const lctx = lc.getContext('2d'); lctx.font = 'bold 32px "Orbitron",sans-serif'; lctx.fillStyle = '#' + pc.getHexString(); lctx.textAlign = 'center'; lctx.fillText(`\u25C8 ${config.name.toUpperCase()}`, 256, 42); const labelMesh = new THREE.Mesh(new THREE.PlaneGeometry(4, 0.5), new THREE.MeshBasicMaterial({ map: new THREE.CanvasTexture(lc), transparent: true, side: THREE.DoubleSide })); labelMesh.position.y = 7.5; group.add(labelMesh); // Pillars for (const side of [-1, 1]) { const pillar = new THREE.Mesh(new THREE.CylinderGeometry(0.2, 0.3, 7, 8), new THREE.MeshStandardMaterial({ color: 0x1a1a2e, roughness: 0.5, metalness: 0.7, emissive: pc, emissiveIntensity: 0.1 })); pillar.position.set(side * 3, 3.5, 0); pillar.castShadow = true; group.add(pillar); } scene.add(group); return { config, group, ring, swirl, pSystem, light, runes }; } // ═══ PARTICLES ═══ function createParticles() { const count = particleCount(1500); const positions = new Float32Array(count * 3), colors = new Float32Array(count * 3), sizes = new Float32Array(count); const c1 = new THREE.Color(NEXUS.colors.primary), c2 = new THREE.Color(NEXUS.colors.secondary), c3 = new THREE.Color(NEXUS.colors.gold); for (let i = 0; i < count; i++) { positions[i*3] = (Math.random()-0.5)*60; positions[i*3+1] = Math.random()*20; positions[i*3+2] = (Math.random()-0.5)*60; const t = Math.random(), col = t < 0.5 ? c1.clone().lerp(c2, t*2) : c2.clone().lerp(c3, (t-0.5)*2); colors[i*3] = col.r; colors[i*3+1] = col.g; colors[i*3+2] = col.b; sizes[i] = 0.02 + Math.random() * 0.06; } const geo = new THREE.BufferGeometry(); geo.setAttribute('position', new THREE.BufferAttribute(positions, 3)); geo.setAttribute('color', new THREE.BufferAttribute(colors, 3)); geo.setAttribute('size', new THREE.BufferAttribute(sizes, 1)); particles = new THREE.Points(geo, new THREE.ShaderMaterial({ uniforms: { uTime: { value: 0 } }, vertexShader: `attribute float size;attribute vec3 color;varying vec3 vColor;uniform float uTime;void main(){vColor=color;vec3 p=position;p.y+=sin(uTime*0.5+position.x*0.5)*0.3;p.x+=sin(uTime*0.3+position.z*0.4)*0.2;vec4 mv=modelViewMatrix*vec4(p,1.0);gl_PointSize=size*300.0/-mv.z;gl_Position=projectionMatrix*mv;}`, fragmentShader: `varying vec3 vColor;void main(){float d=length(gl_PointCoord-0.5);if(d>0.5)discard;gl_FragColor=vec4(vColor,smoothstep(0.5,0.1,d)*0.7);}`, transparent: true, depthWrite: false, blending: THREE.AdditiveBlending, })); scene.add(particles); } function createDustParticles() { const count = particleCount(500), positions = new Float32Array(count * 3); for (let i = 0; i < count; i++) { positions[i*3] = (Math.random()-0.5)*40; positions[i*3+1] = Math.random()*15; positions[i*3+2] = (Math.random()-0.5)*40; } const geo = new THREE.BufferGeometry(); geo.setAttribute('position', new THREE.BufferAttribute(positions, 3)); dustParticles = new THREE.Points(geo, new THREE.PointsMaterial({ color: 0x8899bb, size: 0.03, transparent: true, opacity: 0.3, depthWrite: false })); scene.add(dustParticles); } // ═══ AMBIENT STRUCTURES ═══ function createAmbientStructures() { const crystalMat = new THREE.MeshPhysicalMaterial({ color: 0x3355aa, roughness: 0.1, metalness: 0.2, transmission: 0.6, thickness: 2, emissive: 0x1122aa, emissiveIntensity: 0.3 }); [{ x:-18,z:-15,s:1.5,ry:0.3 },{x:-20,z:-10,s:1,ry:0.8},{x:-15,z:-18,s:2,ry:1.2},{x:18,z:-15,s:1.8,ry:2.1},{x:20,z:-12,s:1.2,ry:0.5},{x:-12,z:18,s:1.3,ry:1.8},{x:14,z:16,s:1.6,ry:0.9}] .forEach(p => { const c = new THREE.Mesh(new THREE.ConeGeometry(0.4*p.s, 2.5*p.s, 5), crystalMat.clone()); c.position.set(p.x, 1.25*p.s, p.z); c.rotation.y = p.ry; c.rotation.z = (Math.random()-0.5)*0.3; c.castShadow = true; scene.add(c); }); const core = new THREE.Mesh(new THREE.IcosahedronGeometry(0.6, 2), new THREE.MeshPhysicalMaterial({ color: 0x4af0c0, emissive: 0x4af0c0, emissiveIntensity: 2, roughness: 0, metalness: 1, transmission: 0.3, thickness: 1 })); core.position.set(0, 2.5, 0); core.name = 'nexus-core'; scene.add(core); const pedestal = new THREE.Mesh(new THREE.CylinderGeometry(0.8, 1.2, 1.5, 8), new THREE.MeshStandardMaterial({ color: 0x0a0f1a, roughness: 0.4, metalness: 0.8, emissive: 0x1a2a4a, emissiveIntensity: 0.3 })); pedestal.position.set(0, 0.75, 0); pedestal.castShadow = true; scene.add(pedestal); } // ═══ NAVIGATION ═══ function cycleNavMode() { navModeIdx = (navModeIdx + 1) % NAV_MODES.length; const mode = NAV_MODES[navModeIdx]; if (mode === 'orbit') { const dir = new THREE.Vector3(0, 0, -1).applyEuler(playerRot); orbitState.target.copy(playerPos).addScaledVector(dir, orbitState.radius); orbitState.target.y = Math.max(0, orbitState.target.y); const toCamera = new THREE.Vector3().subVectors(playerPos, orbitState.target); orbitState.radius = toCamera.length(); orbitState.theta = Math.atan2(toCamera.x, toCamera.z); orbitState.phi = Math.acos(Math.max(-1, Math.min(1, toCamera.y / orbitState.radius))); } if (mode === 'fly') flyY = playerPos.y; const el = document.getElementById('nav-mode-label'); if (el) el.textContent = mode.toUpperCase(); } // ═══ CONTROLS ═══ function setupControls() { const chatInput = document.getElementById('chat-input'); document.addEventListener('keydown', (e) => { keys[e.key.toLowerCase()] = true; if (e.key === 'Enter') { e.preventDefault(); document.activeElement === chatInput ? sendChatMessage() : chatInput.focus(); } if (e.key === 'Escape') { chatInput.blur(); if (portalOverlayActive) closePortalOverlay(); if (visionOverlayActive) closeVisionOverlay(); } if (e.key.toLowerCase() === 'v' && document.activeElement !== chatInput) cycleNavMode(); if (e.key.toLowerCase() === 'f' && activePortal && !portalOverlayActive) activatePortal(activePortal); if (e.key.toLowerCase() === 'e' && activeVisionPoint && !visionOverlayActive) activateVisionPoint(activeVisionPoint); }); document.addEventListener('keyup', (e) => { keys[e.key.toLowerCase()] = false; }); const canvas = document.getElementById('nexus-canvas'); canvas.addEventListener('mousedown', (e) => { if (e.target !== canvas) return; mouseDown = true; orbitState.lastX = e.clientX; orbitState.lastY = e.clientY; if (!portalOverlayActive) { const mouse = new THREE.Vector2((e.clientX/window.innerWidth)*2-1, -(e.clientY/window.innerHeight)*2+1); const rc = new THREE.Raycaster(); rc.setFromCamera(mouse, camera); const hit = rc.intersectObjects(portals.map(p => p.ring)); if (hit.length) { const p = portals.find(p => p.ring === hit[0].object); if (p) activatePortal(p); } } }); document.addEventListener('mouseup', () => { mouseDown = false; }); document.addEventListener('mousemove', (e) => { if (!mouseDown || document.activeElement === chatInput) return; if (NAV_MODES[navModeIdx] === 'orbit') { orbitState.theta -= (e.clientX - orbitState.lastX) * 0.005; orbitState.phi = Math.max(0.05, Math.min(Math.PI * 0.85, orbitState.phi + (e.clientY - orbitState.lastY) * 0.005)); orbitState.lastX = e.clientX; orbitState.lastY = e.clientY; } else { playerRot.y -= e.movementX * 0.003; playerRot.x = Math.max(-Math.PI/3, Math.min(Math.PI/3, playerRot.x - e.movementY * 0.003)); } }); canvas.addEventListener('wheel', (e) => { if (NAV_MODES[navModeIdx] === 'orbit') orbitState.radius = Math.max(orbitState.minR, Math.min(orbitState.maxR, orbitState.radius + e.deltaY * 0.02)); }, { passive: true }); document.getElementById('chat-toggle').addEventListener('click', () => { chatOpen = !chatOpen; document.getElementById('chat-panel').classList.toggle('collapsed', !chatOpen); }); document.getElementById('chat-send').addEventListener('click', sendChatMessage); document.getElementById('portal-close-btn').addEventListener('click', closePortalOverlay); document.getElementById('vision-close-btn').addEventListener('click', closeVisionOverlay); } // ═══ CHAT ═══ function sendChatMessage() { const input = document.getElementById('chat-input'); const text = input.value.trim(); if (!text) return; addChatMessage('user', text); input.value = ''; const timmy = agents.find(a => a.id === 'timmy'); if (wsConnected) { wsSend({ type: 'chat_message', text, sender: 'Alexander' }); if (timmy) { timmy.activityLocked = true; setAgentActivity(timmy, ACTIVITY_STATES.THINKING); } } else { addChatMessage('system', 'OFFLINE — Timmy is not connected. Start gateway: ws://hermes:8765'); if (timmy) { setAgentActivity(timmy, ACTIVITY_STATES.NONE); timmy.activityLocked = false; } } input.blur(); } function addChatMessage(type, text) { const container = document.getElementById('chat-messages'); const div = document.createElement('div'); div.className = `chat-msg chat-msg-${type}`; const prefixes = { user: '[ALEXANDER]', timmy: '[TIMMY]', system: '[NEXUS]', error: '[ERROR]' }; div.innerHTML = `${prefixes[type] || '[???]'} ${text}`; container.appendChild(div); container.scrollTop = container.scrollHeight; } // ═══ PORTAL INTERACTION ═══ function checkPortalProximity() { if (portalOverlayActive) return; let closest = null, minDist = Infinity; portals.forEach(p => { const d = playerPos.distanceTo(p.group.position); if (d < 4.5 && d < minDist) { minDist = d; closest = p; } }); activePortal = closest; const hint = document.getElementById('portal-hint'); if (activePortal) { document.getElementById('portal-hint-name').textContent = activePortal.config.name; hint.style.display = 'flex'; } else hint.style.display = 'none'; } function activatePortal(portal) { portalOverlayActive = true; document.getElementById('portal-name-display').textContent = portal.config.name.toUpperCase(); document.getElementById('portal-desc-display').textContent = portal.config.description; const dot = document.getElementById('portal-status-dot'); dot.style.background = portal.config.color; dot.style.boxShadow = `0 0 10px ${portal.config.color}`; document.getElementById('portal-overlay').style.display = 'flex'; if (portal.config.destination?.url) { document.getElementById('portal-redirect-box').style.display = 'block'; document.getElementById('portal-error-box').style.display = 'none'; let count = 5; document.getElementById('portal-timer').textContent = count; const iv = setInterval(() => { if (--count <= 0) { clearInterval(iv); if (portalOverlayActive) window.location.href = portal.config.destination.url; } if (!portalOverlayActive) clearInterval(iv); document.getElementById('portal-timer').textContent = count; }, 1000); } else { document.getElementById('portal-redirect-box').style.display = 'none'; document.getElementById('portal-error-box').style.display = 'block'; } } function closePortalOverlay() { portalOverlayActive = false; document.getElementById('portal-overlay').style.display = 'none'; } // ═══ VISION INTERACTION ═══ function checkVisionProximity() { if (visionOverlayActive) return; let closest = null, minDist = Infinity; visionPoints.forEach(vp => { const d = playerPos.distanceTo(vp.group.position); if (d < 3.5 && d < minDist) { minDist = d; closest = vp; } }); activeVisionPoint = closest; const hint = document.getElementById('vision-hint'); if (activeVisionPoint) { document.getElementById('vision-hint-title').textContent = activeVisionPoint.config.title; hint.style.display = 'flex'; } else hint.style.display = 'none'; } function activateVisionPoint(vp) { visionOverlayActive = true; document.getElementById('vision-title-display').textContent = vp.config.title.toUpperCase(); document.getElementById('vision-content-display').textContent = vp.config.content; const dot = document.getElementById('vision-status-dot'); dot.style.background = vp.config.color; dot.style.boxShadow = `0 0 10px ${vp.config.color}`; document.getElementById('vision-overlay').style.display = 'flex'; } function closeVisionOverlay() { visionOverlayActive = false; document.getElementById('vision-overlay').style.display = 'none'; } // ═══ GAME LOOP ═══ let pulseTimer = 0; function gameLoop() { requestAnimationFrame(gameLoop); const delta = Math.min(clock.getDelta(), 0.1), elapsed = clock.elapsedTime; // Harness Pulse pulseTimer += delta; if (pulseTimer > 8) { pulseTimer = 0; triggerHarnessPulse(); } if (harnessPulseMesh) { harnessPulseMesh.scale.addScalar(delta * 15); harnessPulseMesh.material.opacity = Math.max(0, harnessPulseMesh.material.opacity - delta * 0.5); } // Navigation const mode = NAV_MODES[navModeIdx]; const chatActive = document.activeElement === document.getElementById('chat-input'); if (mode === 'walk') { if (!chatActive && !portalOverlayActive) { const speed = 6 * delta, dir = new THREE.Vector3(); if (keys['w']) dir.z -= 1; if (keys['s']) dir.z += 1; if (keys['a']) dir.x -= 1; if (keys['d']) dir.x += 1; if (dir.length() > 0) { dir.normalize().multiplyScalar(speed).applyAxisAngle(new THREE.Vector3(0,1,0), playerRot.y); playerPos.add(dir); const d = Math.sqrt(playerPos.x**2+playerPos.z**2); if (d > 24) { playerPos.x *= 24/d; playerPos.z *= 24/d; } } } playerPos.y = 2; camera.position.copy(playerPos); camera.rotation.set(playerRot.x, playerRot.y, 0, 'YXZ'); } else if (mode === 'orbit') { if (!chatActive && !portalOverlayActive) { const speed = 8 * delta, pan = new THREE.Vector3(); if (keys['w']) pan.z -= 1; if (keys['s']) pan.z += 1; if (keys['a']) pan.x -= 1; if (keys['d']) pan.x += 1; if (pan.length() > 0) { pan.normalize().multiplyScalar(speed).applyAxisAngle(new THREE.Vector3(0,1,0), orbitState.theta); orbitState.target.add(pan); orbitState.target.y = Math.max(0, Math.min(20, orbitState.target.y)); } } const r = orbitState.radius; camera.position.set(orbitState.target.x+r*Math.sin(orbitState.phi)*Math.sin(orbitState.theta), orbitState.target.y+r*Math.cos(orbitState.phi), orbitState.target.z+r*Math.sin(orbitState.phi)*Math.cos(orbitState.theta)); camera.lookAt(orbitState.target); playerPos.copy(camera.position); playerRot.y = orbitState.theta; } else if (mode === 'fly') { if (!chatActive && !portalOverlayActive) { const speed = 8 * delta; const fwd = new THREE.Vector3(-Math.sin(playerRot.y), 0, -Math.cos(playerRot.y)); const right = new THREE.Vector3(Math.cos(playerRot.y), 0, -Math.sin(playerRot.y)); if (keys['w']) playerPos.addScaledVector(fwd, speed); if (keys['s']) playerPos.addScaledVector(fwd, -speed); if (keys['a']) playerPos.addScaledVector(right, -speed); if (keys['d']) playerPos.addScaledVector(right, speed); if (keys['q'] || keys[' ']) flyY += speed; if (keys['e'] || keys['shift']) flyY -= speed; flyY = Math.max(0.5, Math.min(30, flyY)); playerPos.y = flyY; } camera.position.copy(playerPos); camera.rotation.set(playerRot.x, playerRot.y, 0, 'YXZ'); } checkPortalProximity(); checkVisionProximity(); // Animate scene const sky = scene.getObjectByName('skybox'); if (sky) sky.material.uniforms.uTime.value = elapsed; 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; }); // Portals portals.forEach(p => { p.ring.rotation.z = elapsed * 0.3; p.ring.rotation.x = Math.sin(elapsed * 0.5) * 0.1; if (p.swirl.material.uniforms) p.swirl.material.uniforms.uTime.value = elapsed; p.light.intensity = 1.5 + Math.sin(elapsed * 2) * 0.5; const pos = p.pSystem.geometry.attributes.position.array; for (let i = 0; i < pos.length / 3; i++) pos[i*3+1] += Math.sin(elapsed + i) * 0.002; p.pSystem.geometry.attributes.position.needsUpdate = true; p.runes.forEach((rune, i) => { rune.position.y = 4 + Math.sin(elapsed*2+i*0.5)*0.2; rune.rotation.z = elapsed*0.8+i; }); }); // Vision points visionPoints.forEach(vp => { vp.crystal.rotation.y = elapsed * 0.8; vp.crystal.rotation.x = Math.sin(elapsed * 0.5) * 0.2; vp.crystal.position.y = 2.5 + Math.sin(elapsed * 1.5) * 0.2; vp.ring.rotation.z = elapsed * 0.5; vp.ring.scale.setScalar(1 + Math.sin(elapsed * 2) * 0.05); vp.light.intensity = 1 + Math.sin(elapsed * 3) * 0.3; }); // Agents updateAgents(elapsed, delta); // Dual-brain float if (dualBrainGroup) { dualBrainGroup.position.y = 3 + Math.sin(elapsed * 0.22) * 0.15; if (cloudOrb) { cloudOrb.position.y = 3 + Math.sin(elapsed*1.3)*0.15; cloudOrb.rotation.y = elapsed*0.4; } if (localOrb) { localOrb.position.y = 3 + Math.sin(elapsed*1.3+Math.PI)*0.15; localOrb.rotation.y = -elapsed*0.4; } if (dualBrainLight) dualBrainLight.intensity = 0.4 + Math.sin(elapsed*1.5)*0.2; } // Power meter powerMeterBars.forEach((bar, i) => { const level = Math.sin(elapsed*2+i*0.5)*0.5+0.5, active = level > i/powerMeterBars.length; bar.material.emissiveIntensity = active ? 2 : 0.2; bar.material.opacity = active ? 0.9 : 0.3; bar.scale.x = active ? 1.2 : 1; }); if (voidLight) voidLight.intensity = 0.35 + Math.sin(elapsed * 1.4) * 0.2; if (thoughtStreamMesh) { thoughtStreamMesh.material.uniforms.uTime.value = elapsed; thoughtStreamMesh.rotation.y = elapsed * 0.05; } if (particles?.material?.uniforms) particles.material.uniforms.uTime.value = elapsed; if (dustParticles) dustParticles.rotation.y = elapsed * 0.01; const core = scene.getObjectByName('nexus-core'); if (core) { 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.material.emissiveIntensity = 1.5 + Math.sin(elapsed*2)*0.5; } composer.render(); frameCount++; const now = performance.now(); if (now - lastFPSTime >= 1000) { fps = frameCount; frameCount = 0; lastFPSTime = now; } if (debugOverlay) { const info = renderer.info; debugOverlay.textContent = `FPS: ${fps} Draw: ${info.render?.calls} Tri: ${info.render?.triangles} [${performanceTier}] WS: ${wsConnected ? 'ON' : 'OFF'}\nPos: ${playerPos.x.toFixed(1)}, ${playerPos.y.toFixed(1)}, ${playerPos.z.toFixed(1)} NAV: ${NAV_MODES[navModeIdx]}`; } renderer.info.reset(); } function onResize() { const w = window.innerWidth, h = window.innerHeight; camera.aspect = w / h; camera.updateProjectionMatrix(); renderer.setSize(w, h); composer.setSize(w, h); } // ═══ AGENT ANIMATION ═══ function updateAgents(elapsed, delta) { const ATTENTION_RADIUS = 7; agents.forEach((agent, i) => { const station = new THREE.Vector3(agent.station.x, 0, agent.station.z); const toPlayer = new THREE.Vector3(playerPos.x - agent.group.position.x, 0, playerPos.z - agent.group.position.z); const playerNearby = toPlayer.length() < ATTENTION_RADIUS && !agent.activityLocked; if (playerNearby) { const ta = Math.atan2(toPlayer.x, toPlayer.z); agent.group.rotation.y += ((ta - agent.group.rotation.y + Math.PI*3) % (Math.PI*2) - Math.PI) * Math.min(delta*3, 1); } if (!playerNearby && !agent.activityLocked) { agent.stateTimer -= delta; if (agent.stateTimer <= 0) { agent.state = pickNextState(); agent.stateTimer = agent.state === 'IDLE' ? 4+Math.random()*6 : agent.state === 'PACING' ? 8+Math.random()*6 : 4+Math.random()*4; if (agent.state === 'PACING') agent.pacingIdx = 0; if (agent.state === 'LOOKING') agent.lookAngle = agent.group.rotation.y; if (agent.state !== 'PACING') agent.targetPos.copy(station); } if (agent.state === 'PACING') { const wp = agent.pacingPath[agent.pacingIdx]; const toWp = new THREE.Vector3(wp.x - agent.group.position.x, 0, wp.z - agent.group.position.z); if (toWp.length() < 0.3) agent.pacingIdx = (agent.pacingIdx + 1) % agent.pacingPath.length; else { agent.group.position.addScaledVector(toWp.normalize(), delta*1.2); agent.group.rotation.y += (Math.atan2(toWp.x,toWp.z)-agent.group.rotation.y)*Math.min(delta*4,1); } } else if (agent.state === 'READING') { const tt = new THREE.Vector3(-agent.group.position.x, 0, -8-agent.group.position.z); agent.group.rotation.y += (Math.atan2(tt.x,tt.z)-agent.group.rotation.y)*Math.min(delta*2,1); agent.group.position.lerp(agent.targetPos, delta*0.4); } else if (agent.state === 'LOOKING') { agent.lookAngle += Math.sin(elapsed*agent.lookSpeed+i)*delta*0.8; agent.group.rotation.y += (agent.lookAngle-agent.group.rotation.y)*Math.min(delta*1.5,1); agent.group.position.lerp(agent.targetPos, delta*0.3); } else { agent.group.position.lerp(agent.targetPos, delta*0.3); } } // Orb & halo const bob = agent.activityState === ACTIVITY_STATES.THINKING ? 0.25 : 0.15; agent.orb.position.y = 3 + Math.sin(elapsed*2+i)*bob; agent.halo.rotation.z = elapsed * 0.5; agent.halo.scale.setScalar(1 + Math.sin(elapsed*3+i)*0.1); agent.orb.material.emissiveIntensity = (agent.activityState === ACTIVITY_STATES.NONE ? 2 : 3) + Math.sin(elapsed*4+i); // Activity indicators if (agent.activityState !== ACTIVITY_STATES.NONE) { agent.indicator.group.position.y = 4.2 + Math.sin(elapsed*2+i*1.3)*0.1; if (agent.activityState === ACTIVITY_STATES.WAITING) { const p = 0.7+Math.sin(elapsed*4+i)*0.3; agent.indicator.waitMesh.scale.setScalar(p); agent.indicator.waitMesh.material.opacity = 0.5+p*0.35; } else if (agent.activityState === ACTIVITY_STATES.THINKING) { agent.indicator.thinkMesh.rotation.y = elapsed*2.5; agent.indicator.thinkMesh.rotation.x = elapsed*1.5; } else if (agent.activityState === ACTIVITY_STATES.PROCESSING) { agent.indicator.procMesh.rotation.z = elapsed*4; agent.indicator.procMesh.rotation.x = Math.sin(elapsed*1.2)*0.5; } const tc = new THREE.Vector3(camera.position.x-agent.group.position.x, 0, camera.position.z-agent.group.position.z); if (tc.length() > 0.01) agent.indicator.group.rotation.y = Math.atan2(tc.x, tc.z); } }); } function addAgentLog(agentId, text) { const container = document.getElementById('agent-log-content'); if (!container) return; const entry = document.createElement('div'); entry.className = 'agent-log-entry'; entry.innerHTML = `[${agentId.toUpperCase()}]${text}`; container.prepend(entry); if (container.children.length > 6) container.lastElementChild.remove(); } function triggerHarnessPulse() { if (!harnessPulseMesh) return; harnessPulseMesh.scale.setScalar(0.1); harnessPulseMesh.material.opacity = 0.8; const core = scene.getObjectByName('nexus-core'); if (core) { core.material.emissiveIntensity = 10; setTimeout(() => { if (core) core.material.emissiveIntensity = 2; }, 200); } } // ═══ BITCOIN BLOCK HEIGHT ═══ (function initBitcoin() { const display = document.getElementById('block-height-display'); const value = document.getElementById('block-height-value'); if (!display || !value) return; let lastHeight = null; async function fetch_() { try { const res = await fetch('https://blockstream.info/api/blocks/tip/height'); if (!res.ok) return; const h = parseInt(await res.text(), 10); if (isNaN(h)) return; if (lastHeight !== null && h !== lastHeight) { display.classList.remove('fresh'); void display.offsetWidth; display.classList.add('fresh'); } lastHeight = h; value.textContent = h.toLocaleString(); } catch (_) {} } fetch_(); setInterval(fetch_, 60000); })(); init();