// === THE NEXUS — Main Entry Point === // All modules are imported here. This file wires them together. import * as THREE from 'three'; import { S } from './modules/state.js'; import { NEXUS } from './modules/constants.js'; import { setAnimateFn, setTotalActivityFn } from './modules/matrix-rain.js'; import { scene, camera, renderer, raycaster, forwardVector, ambientLight, overheadLight, stars, starMaterial, constellationLines, STAR_BASE_OPACITY, STAR_PEAK_OPACITY, STAR_PULSE_DECAY } from './modules/scene-setup.js'; import { glassEdgeMaterials, voidLight, cloudMaterial, GLASS_RADIUS } from './modules/platform.js'; import { heatmapMat, zoneIntensity, drawHeatmap, updateHeatmap, HEATMAP_ZONES } from './modules/heatmap.js'; import { sigilMesh, sigilMat, sigilRing1, sigilRing1Mat, sigilRing2, sigilRing2Mat, sigilRing3, sigilRing3Mat, sigilLight } from './modules/sigil.js'; import { NORMAL_CAM, OVERVIEW_CAM, composer, orbitControls, bokehPass, WARP_DURATION } from './modules/controls.js'; import { animateEnergyBeam, sovereigntyGroup, meterLight, runeSprites, RUNE_RING_Y, RUNE_ORBIT_SPEED, rebuildRuneRing } from './modules/effects.js'; import { earthGroup, earthMesh, earthSurfaceMat, earthGlowLight, EARTH_Y, EARTH_ROTATION_SPEED } from './modules/earth.js'; import { clock, warpPass, startWarp, totalActivity, crystals, CRYSTAL_COLORS, LIGHTNING_POOL_SIZE, LIGHTNING_REFRESH_MS, lightningArcs, lightningArcMeta, updateLightningArcs, batcaveGroup, batcaveProbe, batcaveMetallicMats, batcaveProbeTarget_texture } from './modules/warp.js'; import { dualBrainSprite, dualBrainLight, dualBrainScanSprite, dualBrainScanTexture, cloudOrb, cloudOrbMat, cloudOrbLight, localOrb, localOrbMat, localOrbLight, BRAIN_PARTICLE_COUNT, brainParticleGeo, brainParticleMat, brainParticlePhases, brainParticleSpeeds, _scanCtx } from './modules/dual-brain.js'; import { updateAudioListener, initAudioListeners, startPortalHums } from './modules/audio.js'; import { initDebug, initWebSocket, wsClient, logMessage, initSessionExport } from './modules/debug.js'; import { triggerSovereigntyEasterEgg, triggerFireworks, triggerMergeFlash, triggerShockwave, initSovereigntyEasterEgg, shockwaveRings, SHOCKWAVE_DURATION, fireworkBursts, FIREWORK_BURST_PARTICLES, FIREWORK_BURST_DURATION, FIREWORK_GRAVITY } from './modules/celebrations.js'; import { portalGroup, portals, loadPortals, setRebuildGravityZonesFn, setRunPortalHealthChecksFn } from './modules/portals.js'; import { commitBanners, bookshelfGroups, agentPanelSprites, initCommitBanners, initBookshelves } from './modules/bookshelves.js'; import { tomeGroup, tomeGlow, oathSpot, enterOath, exitOath, initOathListeners } from './modules/oath.js'; import { loraPanelSprite, refreshAgentBoard, initAgentBoard, loadLoRAStatus } from './modules/panels.js'; import { rainParticles, rainGeo, rainVelocities, snowParticles, snowGeo, snowDrift, PRECIP_COUNT, PRECIP_AREA, PRECIP_HEIGHT, PRECIP_FLOOR, runPortalHealthChecks, initPortalHealthChecks, setWeatherPortalRefs, initWeather } from './modules/weather.js'; import { gravityZoneObjects, GRAVITY_ANOMALY_CEIL, rebuildGravityZones, TIMMY_SPEECH_POS, SPEECH_DURATION, SPEECH_FADE_IN, SPEECH_FADE_OUT, showTimmySpeech, setExtrasPortalsRef, timelapseCommits, timelapseWindow, TIMELAPSE_DURATION_S, fireTimelapseCommit, updateTimelapseHeatmap, updateTimelapseHUD, stopTimelapse, initTimelapse, initBitcoin } from './modules/extras.js'; // === WIRE UP CROSS-MODULE REFERENCES === setTotalActivityFn(totalActivity); setAnimateFn(() => animate()); setRebuildGravityZonesFn(rebuildGravityZones); setRunPortalHealthChecksFn(runPortalHealthChecks); // === ANIMATION LOOP === function animate() { requestAnimationFrame(animate); animateEnergyBeam(); const elapsed = clock.getElapsedTime(); // Overview mode const targetT = S.overviewMode ? 1 : 0; S.overviewT += (targetT - S.overviewT) * 0.04; const _basePos = new THREE.Vector3().lerpVectors(NORMAL_CAM, OVERVIEW_CAM, S.overviewT); // Zoom-to-object if (!S.photoMode) { S.zoomT += (S.zoomTargetT - S.zoomT) * 0.07; } if (S.zoomT > 0.001 && !S.photoMode && !S.overviewMode) { camera.position.lerpVectors(_basePos, S._zoomCamTarget, S.zoomT); camera.lookAt(new THREE.Vector3(0, 0, 0).lerp(S._zoomLookTarget, S.zoomT)); } else { camera.position.copy(_basePos); camera.lookAt(0, 0, 0); } const rotationScale = S.photoMode ? 0 : (1 - S.overviewT); S.targetRotX += (S.mouseY * 0.3 - S.targetRotX) * 0.02; S.targetRotY += (S.mouseX * 0.3 - S.targetRotY) * 0.02; stars.rotation.x = (S.targetRotX + elapsed * 0.01) * rotationScale; stars.rotation.y = (S.targetRotY + elapsed * 0.015) * rotationScale; // Star pulse if (S._starPulseIntensity > 0) { S._starPulseIntensity = Math.max(0, S._starPulseIntensity - STAR_PULSE_DECAY); } starMaterial.opacity = STAR_BASE_OPACITY + (STAR_PEAK_OPACITY - STAR_BASE_OPACITY) * S._starPulseIntensity; constellationLines.rotation.x = stars.rotation.x; constellationLines.rotation.y = stars.rotation.y; constellationLines.material.opacity = 0.12 + Math.sin(elapsed * 0.5) * 0.06; // Batcave reflection probe if (elapsed - S.batcaveProbeLastUpdate > 2.0) { S.batcaveProbeLastUpdate = elapsed; batcaveGroup.visible = false; batcaveProbe.update(renderer, scene); batcaveGroup.visible = true; for (const mat of batcaveMetallicMats) { mat.envMap = batcaveProbeTarget_texture.texture; mat.needsUpdate = true; } } // Glass platform edge glow for (const { mat, distFromCenter } of glassEdgeMaterials) { const phase = elapsed * 1.1 - distFromCenter * 0.18; mat.opacity = 0.25 + Math.sin(phase) * 0.22; } voidLight.intensity = 0.35 + Math.sin(elapsed * 1.4) * 0.2; heatmapMat.opacity = 0.75 + Math.sin(elapsed * 0.6) * 0.2; // Sigil animation sigilMesh.rotation.z = elapsed * 0.04; sigilRing1.rotation.z = elapsed * 0.06; sigilRing2.rotation.z = -elapsed * 0.10; sigilRing3.rotation.z = elapsed * 0.08; sigilMat.opacity = 0.65 + Math.sin(elapsed * 1.3) * 0.18; sigilRing1Mat.opacity = 0.38 + Math.sin(elapsed * 0.9) * 0.14; sigilRing2Mat.opacity = 0.32 + Math.sin(elapsed * 1.6 + 1.2) * 0.12; sigilRing3Mat.opacity = 0.28 + Math.sin(elapsed * 0.7 + 2.4) * 0.10; sigilLight.intensity = 0.30 + Math.sin(elapsed * 1.1) * 0.15; cloudMaterial.uniforms.uTime.value = elapsed; if (S.photoMode) { orbitControls.update(); } // Sovereignty meter sovereigntyGroup.position.y = 3.8 + Math.sin(elapsed * 0.8) * 0.15; meterLight.intensity = 0.5 + Math.sin(elapsed * 1.8) * 0.25; // Commit banners const FADE_DUR = 1.5; commitBanners.forEach(banner => { const ud = banner.userData; if (ud.spawnTime === null) { if (elapsed < ud.startDelay) return; ud.spawnTime = elapsed; } const age = elapsed - ud.spawnTime; let opacity; if (age < FADE_DUR) { opacity = age / FADE_DUR; } else if (age < ud.lifetime - FADE_DUR) { opacity = 1; } else if (age < ud.lifetime) { opacity = (ud.lifetime - age) / FADE_DUR; } else { ud.spawnTime = elapsed + 3; opacity = 0; } banner.material.opacity = opacity * 0.85; banner.position.y = ud.baseY + Math.sin(elapsed * ud.floatSpeed + ud.floatPhase) * 0.4; }); // Agent panels float for (const sprite of agentPanelSprites) { const ud = sprite.userData; sprite.position.y = ud.baseY + Math.sin(elapsed * ud.floatSpeed + ud.floatPhase) * 0.22; } // LoRA panel float if (loraPanelSprite) { const ud = loraPanelSprite.userData; loraPanelSprite.position.y = ud.baseY + Math.sin(elapsed * ud.floatSpeed + ud.floatPhase) * 0.22; } // Bookshelves float for (const shelf of bookshelfGroups) { const ud = shelf.userData; shelf.position.y = ud.baseY + Math.sin(elapsed * ud.floatSpeed + ud.floatPhase) * 0.18; } // Speech bubble if (S.timmySpeechState) { const age = elapsed - S.timmySpeechState.startTime; let opacity; if (age < SPEECH_FADE_IN) { opacity = age / SPEECH_FADE_IN; } else if (age < SPEECH_DURATION - SPEECH_FADE_OUT) { opacity = 1.0; } else if (age < SPEECH_DURATION) { opacity = (SPEECH_DURATION - age) / SPEECH_FADE_OUT; } else { scene.remove(S.timmySpeechState.sprite); if (S.timmySpeechState.sprite.material.map) S.timmySpeechState.sprite.material.map.dispose(); S.timmySpeechState.sprite.material.dispose(); S.timmySpeechSprite = null; S.timmySpeechState = null; opacity = 0; } if (S.timmySpeechState) { S.timmySpeechState.sprite.material.opacity = opacity; S.timmySpeechState.sprite.position.y = TIMMY_SPEECH_POS.y + Math.sin(elapsed * 1.1) * 0.1; } } // Tome float tomeGroup.position.y = 5.8 + Math.sin(elapsed * 0.6) * 0.18; tomeGroup.rotation.y = elapsed * 0.3; tomeGlow.intensity = 0.3 + Math.sin(elapsed * 1.4) * 0.12; if (S.oathActive) { oathSpot.intensity = 3.8 + Math.sin(elapsed * 0.9) * 0.4; } // Shockwave rings for (let i = shockwaveRings.length - 1; i >= 0; i--) { const ring = shockwaveRings[i]; const age = elapsed - ring.startTime - ring.delay; if (age < 0) continue; const t = Math.min(age / SHOCKWAVE_DURATION, 1); if (t >= 1) { scene.remove(ring.mesh); ring.mesh.geometry.dispose(); ring.mat.dispose(); shockwaveRings.splice(i, 1); continue; } const eased = 1 - Math.pow(1 - t, 2); ring.mesh.scale.setScalar(eased * 14 + 0.1); ring.mat.opacity = (1 - t) * 0.9; } // Fireworks for (let i = fireworkBursts.length - 1; i >= 0; i--) { const burst = fireworkBursts[i]; const age = elapsed - burst.startTime; const t = Math.min(age / FIREWORK_BURST_DURATION, 1); if (t >= 1) { scene.remove(burst.points); burst.geo.dispose(); burst.mat.dispose(); fireworkBursts.splice(i, 1); continue; } burst.mat.opacity = t < 0.6 ? 1.0 : (1.0 - t) / 0.4; const pos = burst.geo.attributes.position.array; const vel = burst.velocities; const org = burst.origins; const halfGAge2 = 0.5 * FIREWORK_GRAVITY * age * age; for (let j = 0; j < FIREWORK_BURST_PARTICLES; j++) { pos[j * 3] = org[j * 3] + vel[j * 3] * age; pos[j * 3 + 1] = org[j * 3 + 1] + vel[j * 3 + 1] * age + halfGAge2; pos[j * 3 + 2] = org[j * 3 + 2] + vel[j * 3 + 2] * age; } burst.geo.attributes.position.needsUpdate = true; } // Rune ring for (const rune of runeSprites) { const angle = rune.baseAngle + elapsed * RUNE_ORBIT_SPEED; rune.sprite.position.x = Math.cos(angle) * 7.0; rune.sprite.position.z = Math.sin(angle) * 7.0; rune.sprite.position.y = RUNE_RING_Y + Math.sin(elapsed * 0.7 + rune.floatPhase) * 0.4; const baseOpacity = rune.portalOnline ? 0.85 : 0.12; const pulseRange = rune.portalOnline ? 0.15 : 0.03; rune.sprite.material.opacity = baseOpacity + Math.sin(elapsed * 1.2 + rune.floatPhase) * pulseRange; } // Earth const earthActivity = totalActivity(); const targetEarthSpeed = 0.005 + earthActivity * 0.045; const _eSmooth = 0.02; const currentEarthSpeed = earthMesh.userData._currentSpeed || EARTH_ROTATION_SPEED; const smoothedEarthSpeed = currentEarthSpeed + (targetEarthSpeed - currentEarthSpeed) * _eSmooth; earthMesh.userData._currentSpeed = smoothedEarthSpeed; earthMesh.rotation.y += smoothedEarthSpeed; earthSurfaceMat.uniforms.uTime.value = elapsed; earthGlowLight.intensity = 0.30 + Math.sin(elapsed * 0.7) * 0.12; earthGroup.position.y = EARTH_Y + Math.sin(elapsed * 0.22) * 0.6; // Weather particles if (rainParticles.visible) { const rpos = rainGeo.attributes.position.array; for (let i = 0; i < PRECIP_COUNT; i++) { rpos[i * 3 + 1] -= rainVelocities[i]; if (rpos[i * 3 + 1] < PRECIP_FLOOR) { rpos[i * 3 + 1] = PRECIP_HEIGHT; rpos[i * 3] = (Math.random() - 0.5) * PRECIP_AREA * 2; rpos[i * 3 + 2] = (Math.random() - 0.5) * PRECIP_AREA * 2; } } rainGeo.attributes.position.needsUpdate = true; } if (snowParticles.visible) { const spos = snowGeo.attributes.position.array; for (let i = 0; i < PRECIP_COUNT; i++) { spos[i * 3 + 1] -= 0.025 + Math.sin(snowDrift[i]) * 0.005; spos[i * 3] += Math.sin(elapsed * 0.4 + snowDrift[i]) * 0.008; if (spos[i * 3 + 1] < PRECIP_FLOOR) { spos[i * 3 + 1] = PRECIP_HEIGHT; spos[i * 3] = (Math.random() - 0.5) * PRECIP_AREA * 2; spos[i * 3 + 2] = (Math.random() - 0.5) * PRECIP_AREA * 2; } } snowGeo.attributes.position.needsUpdate = true; } // Gravity anomalies for (const gz of gravityZoneObjects) { const pos = gz.geo.attributes.position.array; const count = gz.zone.particleCount; for (let i = 0; i < count; i++) { pos[i * 3 + 1] += gz.velocities[i]; pos[i * 3] += Math.sin(elapsed * 0.5 + gz.driftPhases[i]) * 0.003; pos[i * 3 + 2] += Math.cos(elapsed * 0.5 + gz.driftPhases[i]) * 0.003; if (pos[i * 3 + 1] > GRAVITY_ANOMALY_CEIL) { const angle = Math.random() * Math.PI * 2; const r = Math.sqrt(Math.random()) * gz.zone.radius; pos[i * 3] = gz.zone.x + Math.cos(angle) * r; pos[i * 3 + 1] = 0.2 + Math.random() * 2.0; pos[i * 3 + 2] = gz.zone.z + Math.sin(angle) * r; } } gz.geo.attributes.position.needsUpdate = true; gz.ringMat.opacity = 0.3 + Math.sin(elapsed * 1.5 + gz.zone.x) * 0.15; gz.discMat.opacity = 0.02 + Math.sin(elapsed * 1.5 + gz.zone.x) * 0.02; } // Dual-brain dualBrainSprite.position.y = dualBrainSprite.userData.baseY + Math.sin(elapsed * dualBrainSprite.userData.floatSpeed + dualBrainSprite.userData.floatPhase) * 0.22; dualBrainScanSprite.position.y = dualBrainSprite.position.y; const cloudPulse = 0.08 + Math.sin(elapsed * 0.6) * 0.03; const localPulse = 0.08 + Math.sin(elapsed * 0.6 + Math.PI) * 0.03; cloudOrbMat.emissiveIntensity = cloudPulse; localOrbMat.emissiveIntensity = localPulse; cloudOrbLight.intensity = 0.1 + Math.sin(elapsed * 0.6) * 0.05; localOrbLight.intensity = 0.1 + Math.sin(elapsed * 0.6 + Math.PI) * 0.05; cloudOrb.position.y = 3.0 + Math.sin(elapsed * 0.9) * 0.15; localOrb.position.y = 3.0 + Math.sin(elapsed * 0.9 + 1.0) * 0.15; cloudOrbLight.position.y = cloudOrb.position.y; localOrbLight.position.y = localOrb.position.y; if (BRAIN_PARTICLE_COUNT > 0) { const pos = brainParticleGeo.attributes.position.array; const startX = cloudOrb.position.x; const endX = localOrb.position.x; const arcHeight = 1.2; const simRate = 0.73; for (let i = 0; i < BRAIN_PARTICLE_COUNT; i++) { brainParticlePhases[i] += brainParticleSpeeds[i] * simRate * 0.016; if (brainParticlePhases[i] > 1.0) brainParticlePhases[i] -= 1.0; const t = brainParticlePhases[i]; pos[i * 3] = startX + (endX - startX) * t; const midY = (cloudOrb.position.y + localOrb.position.y) / 2 + arcHeight; pos[i * 3 + 1] = cloudOrb.position.y + (midY - cloudOrb.position.y) * 4 * t * (1 - t) + (localOrb.position.y - cloudOrb.position.y) * t; pos[i * 3 + 2] = Math.sin(t * Math.PI * 4 + elapsed * 2 + i) * 0.12; } brainParticleGeo.attributes.position.needsUpdate = true; brainParticleMat.opacity = 0.6 + Math.sin(elapsed * 2.0) * 0.2; } // Scanning line { const W = 512, H = 512; _scanCtx.clearRect(0, 0, W, H); const scanY = ((elapsed * 60) % H); _scanCtx.fillStyle = 'rgba(68, 136, 255, 0.5)'; _scanCtx.fillRect(0, scanY, W, 2); const grad = _scanCtx.createLinearGradient(0, scanY - 8, 0, scanY + 10); grad.addColorStop(0, 'rgba(68, 136, 255, 0)'); grad.addColorStop(0.4, 'rgba(68, 136, 255, 0.15)'); grad.addColorStop(0.6, 'rgba(68, 136, 255, 0.15)'); grad.addColorStop(1, 'rgba(68, 136, 255, 0)'); _scanCtx.fillStyle = grad; _scanCtx.fillRect(0, scanY - 8, W, 18); dualBrainScanTexture.needsUpdate = true; } dualBrainLight.intensity = 0.4 + Math.sin(elapsed * 1.1) * 0.2; // Portal collision forwardVector.set(0, 0, -1).applyQuaternion(camera.quaternion); raycaster.set(camera.position, forwardVector); const intersects = raycaster.intersectObjects(portalGroup.children); if (intersects.length > 0) { const intersectedPortal = intersects[0].object; console.log(`Entered portal: ${intersectedPortal.name}`); if (!S.isWarping) { startWarp(intersectedPortal); } } // Warp effect if (S.isWarping) { const warpElapsed = elapsed - S.warpStartTime; const progress = Math.min(warpElapsed / WARP_DURATION, 1.0); warpPass.uniforms['time'].value = elapsed; warpPass.uniforms['progress'].value = progress; if (!S.warpNavigated && progress >= 0.88 && S.warpDestinationUrl) { S.warpNavigated = true; setTimeout(() => { window.location.href = S.warpDestinationUrl; }, 180); } if (progress >= 1.0) { S.isWarping = false; warpPass.enabled = false; warpPass.uniforms['progress'].value = 0.0; if (!S.warpNavigated && S.warpDestinationUrl) { S.warpNavigated = true; window.location.href = S.warpDestinationUrl; } } } // Crystals const activity = totalActivity(); for (const crystal of crystals) { crystal.mesh.position.x = crystal.basePos.x; crystal.mesh.position.y = crystal.basePos.y + Math.sin(elapsed * 0.65 + crystal.floatPhase) * 0.35; crystal.mesh.position.z = crystal.basePos.z; crystal.mesh.rotation.y = elapsed * 0.4 + crystal.floatPhase; crystal.light.position.copy(crystal.mesh.position); const flashAge = elapsed - crystal.flashStartTime; const flashBoost = flashAge < 0.25 ? (1.0 - flashAge / 0.25) * 2.0 : 0.0; crystal.light.intensity = 0.2 + activity * 0.8 + Math.sin(elapsed * 2.0 + crystal.floatPhase) * 0.1 + flashBoost; crystal.mesh.material.emissiveIntensity = 1.0 + flashBoost * 0.8; } // Lightning flicker for (let i = 0; i < LIGHTNING_POOL_SIZE; i++) { const meta = lightningArcMeta[i]; if (meta.active) { lightningArcs[i].material.opacity = meta.baseOpacity * (0.55 + Math.random() * 0.45); } } if (elapsed * 1000 - S.lastLightningRefreshTime > LIGHTNING_REFRESH_MS) { S.lastLightningRefreshTime = elapsed * 1000; updateLightningArcs(elapsed); } // Timelapse if (S.timelapseActive) { const realElapsed = elapsed - S.timelapseRealStart; S.timelapseProgress = Math.min(realElapsed / TIMELAPSE_DURATION_S, 1.0); const span = timelapseWindow.endMs - timelapseWindow.startMs; const virtualMs = timelapseWindow.startMs + span * S.timelapseProgress; while ( S.timelapseNextCommitIdx < timelapseCommits.length && timelapseCommits[S.timelapseNextCommitIdx].ts <= virtualMs ) { fireTimelapseCommit(timelapseCommits[S.timelapseNextCommitIdx]); S.timelapseNextCommitIdx++; } updateTimelapseHeatmap(virtualMs); updateTimelapseHUD(S.timelapseProgress, virtualMs); if (S.timelapseProgress >= 1.0) stopTimelapse(); } updateAudioListener(); composer.render(); } // === START === animate(); // === INIT ALL SUBSYSTEMS === initAudioListeners(); initDebug(); initWebSocket(); initSessionExport(); initSovereigntyEasterEgg(); initCommitBanners(); loadPortals(); initBookshelves(); initOathListeners(); initAgentBoard(); loadLoRAStatus(); initPortalHealthChecks(); initWeather(); initTimelapse(); initBitcoin(); // === EVENT LISTENERS === window.addEventListener('beforeunload', () => { wsClient.disconnect(); }); window.addEventListener('chat-message', (event) => { console.log('Chat message:', event.detail); if (typeof event.detail?.text === 'string') { logMessage(event.detail.speaker || 'TIMMY', event.detail.text); showTimmySpeech(event.detail.text); if (event.detail.text.toLowerCase().includes('sovereignty')) { triggerSovereigntyEasterEgg(); } if (event.detail.text.toLowerCase().includes('milestone')) { triggerFireworks(); } } }); window.addEventListener('milestone-complete', (event) => { console.log('[nexus] Milestone complete:', event.detail); triggerFireworks(); }); window.addEventListener('status-update', (event) => { console.log('[hermes] Status update:', event.detail); }); window.addEventListener('pr-notification', (event) => { console.log('[hermes] PR notification:', event.detail); if (event.detail && event.detail.action === 'merged') { triggerMergeFlash(); } });