From eb9cc66106889aa060a9da6c0c81e6000f7db806 Mon Sep 17 00:00:00 2001 From: Perplexity Computer Date: Thu, 26 Mar 2026 16:42:37 +0000 Subject: [PATCH] =?UTF-8?q?delete:=20app.js=20=E2=80=94=20does=20not=20ser?= =?UTF-8?q?ve=20heartbeat/harness/portal=20(#548)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.js | 1200 -------------------------------------------------------- 1 file changed, 1200 deletions(-) delete mode 100644 app.js diff --git a/app.js b/app.js deleted file mode 100644 index 080d628..0000000 --- a/app.js +++ /dev/null @@ -1,1200 +0,0 @@ -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();