Split the monolithic 5393-line app.js into 32 focused ES modules under modules/ with a thin ~330-line orchestrator. No bundler required — runs in-browser via import maps. Module structure: core/ — scene, ticker, state, theme, audio data/ — gitea, weather, bitcoin, loaders terrain/ — stars, clouds, island effects/ — matrix-rain, energy-beam, lightning, shockwave, rune-ring, gravity-zones panels/ — heatmap, sigil, sovereignty, dual-brain, batcave, earth, agent-board, lora-panel portals/ — portal-system, commit-banners narrative/ — bookshelves, oath, chat utils/ — perlin All files pass node --check. No new dependencies. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
54 lines
1.2 KiB
JavaScript
54 lines
1.2 KiB
JavaScript
// modules/core/ticker.js — Single animation clock
|
|
// Every module subscribes here instead of calling requestAnimationFrame directly.
|
|
|
|
const subscribers = [];
|
|
let running = false;
|
|
let _renderer, _scene, _camera, _composer;
|
|
|
|
export function subscribe(fn) {
|
|
if (!subscribers.includes(fn)) subscribers.push(fn);
|
|
}
|
|
|
|
export function unsubscribe(fn) {
|
|
const i = subscribers.indexOf(fn);
|
|
if (i >= 0) subscribers.splice(i, 1);
|
|
}
|
|
|
|
export function setRenderTarget(renderer, scene, camera, composer) {
|
|
_renderer = renderer;
|
|
_scene = scene;
|
|
_camera = camera;
|
|
_composer = composer;
|
|
}
|
|
|
|
export function start() {
|
|
if (running) return;
|
|
running = true;
|
|
let lastTime = performance.now();
|
|
|
|
function tick() {
|
|
if (!running) return;
|
|
requestAnimationFrame(tick);
|
|
const now = performance.now();
|
|
const elapsed = now / 1000;
|
|
const delta = (now - lastTime) / 1000;
|
|
lastTime = now;
|
|
|
|
for (const fn of subscribers) {
|
|
fn(elapsed, delta);
|
|
}
|
|
|
|
if (_composer) {
|
|
_composer.render();
|
|
} else if (_renderer && _scene && _camera) {
|
|
_renderer.render(_scene, _camera);
|
|
}
|
|
}
|
|
|
|
tick();
|
|
}
|
|
|
|
export function stop() {
|
|
running = false;
|
|
}
|