feat: extract core foundation modules — scene, ticker, theme, state (#420)
Phase 1 of app.js modularization (Refs #409, Fixes #420).
Adds four modules under modules/core/:
- theme.js: exports NEXUS with full NEXUS.theme (colors, fonts, lineWeights,
glowParams, opacity). NEXUS.colors preserved as backwards-compat alias.
- state.js: exports zoneIntensity (shared mutable object), state (agentCount,
blockHeight, starPulseIntensity, weather), and totalActivity().
- scene.js: creates and exports scene, camera, renderer, orbitControls,
raycaster, lighting. Registers camera+renderer resize handler. Intentionally
does not append renderer.domElement — app.js controls DOM insertion order.
- ticker.js: single requestAnimationFrame loop. Exports subscribe(), unsubscribe(),
start(), stop(). app.js registers animate() via tickerSubscribe and calls
tickerStart() instead of RAF directly.
app.js changes:
- Imports NEXUS from theme.js (removes inline NEXUS definition)
- Imports scene/camera/renderer/orbitControls/raycaster from scene.js
(removes duplicate creation blocks)
- Imports zoneIntensity/state/totalActivity from state.js (removes local defs)
- animate() is now a ticker subscriber function, not a recursive RAF caller
- _activeAgentCount → state.agentCount
- _starPulseIntensity → state.starPulseIntensity
- lastKnownBlockHeight → state.blockHeight
- EffectComposer resize listener preserved separately in app.js
node --check app.js passes. No visual regressions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>