// boot.js (Lane F) — the shell. Creates renderer/scene/loop, picks the world (real or // stub), hosts the debug camera + DBG. Lanes: request wiring via NOTES, don't edit here. import * as THREE from 'three'; import { parseFlags } from './core/flags.js'; import { createRng } from './core/rng.js'; import { createBus } from './core/bus.js'; import { createWorld as createStubWorld, STUB_LEVEL } from './stub/world_stub.js'; const flags = parseFlags(); const bus = createBus(); const renderer = new THREE.WebGLRenderer({ antialias: true, preserveDrawingBuffer: true }); renderer.setPixelRatio(Math.min(devicePixelRatio, 2)); renderer.setSize(innerWidth, innerHeight); document.getElementById('game').appendChild(renderer.domElement); const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, innerWidth / innerHeight, 0.1, 600); async function pickWorld() { if (!flags.stub) { try { const mod = await import('./world/index.js'); // Lane A's world, when it lands const level = await loadLevel(flags.lvl); return mod.createWorld(level, { rng: createRng(flags.seed ?? level.seed) }); } catch (e) { console.info('[boot] Lane A world not available, using stub —', e.message); } } return createStubWorld(STUB_LEVEL, { rng: createRng(flags.seed ?? STUB_LEVEL.seed) }); } async function loadLevel(id) { const mod = await import('./levels/index.js'); // Lane C's registry, when it lands return mod.getLevel(id); } const world = await pickWorld(); scene.add(world.group); scene.background = new THREE.Color(world.biomeAt(0).palette.void); // --- debug drift/fly camera (placeholder until Lane B's player rig; ?fly=1 = manual) --- let sCam = 5, look = { yaw: 0, pitch: 0 }, dragging = false, speed = world.biomeAt(0).flow * 0.6; const keys = new Set(); addEventListener('keydown', (e) => keys.add(e.code)); addEventListener('keyup', (e) => keys.delete(e.code)); addEventListener('mousedown', () => (dragging = true)); addEventListener('mouseup', () => (dragging = false)); addEventListener('mousemove', (e) => { if (!dragging) return; look.yaw -= e.movementX * 0.003; look.pitch = THREE.MathUtils.clamp(look.pitch - e.movementY * 0.003, -1.2, 1.2); }); function updateCamera(dt) { if (flags.fly) { if (keys.has('KeyW')) sCam += speed * 2 * dt; if (keys.has('KeyS')) sCam -= speed * 2 * dt; } else { sCam += speed * dt; // title-screen auto-drift } sCam = ((sCam % world.length) + world.length) % world.length; const f = world.sample(sCam), ahead = world.sample(Math.min(sCam + 12, world.length)); camera.position.copy(f.pos).addScaledVector(f.nor, -f.radius * 0.25); const target = ahead.pos.clone() .addScaledVector(f.bin, Math.sin(look.yaw) * 8) .addScaledVector(f.nor, Math.sin(look.pitch) * 8); camera.up.copy(f.nor); camera.lookAt(target); } // --- DBG (?dbg=1) — cite these numbers in NOTES; DBG.shot('name') saves a frame --- const dbgEl = document.getElementById('dbg'); let frames = 0, fpsT = 0, fps = 0; renderer.info.autoReset = false; // three resets info at render end — cache per-frame instead const stats = { draws: 0, tris: 0 }; window.DBG = { get draws() { return stats.draws; }, get tris() { return stats.tris; }, get fps() { return fps; }, world, shot(name = 'shot') { const a = document.createElement('a'); a.download = `${name}.png`; a.href = renderer.domElement.toDataURL('image/png'); a.click(); }, }; addEventListener('resize', () => { camera.aspect = innerWidth / innerHeight; camera.updateProjectionMatrix(); renderer.setSize(innerWidth, innerHeight); }); let last = performance.now(); function frame(now) { const dt = Math.min((now - last) / 1000, 0.05); last = now; world.update(dt, sCam); updateCamera(dt); renderer.render(scene, camera); stats.draws = renderer.info.render.calls; stats.tris = renderer.info.render.triangles; renderer.info.reset(); frames++; fpsT += dt; if (fpsT >= 0.5) { fps = Math.round(frames / fpsT); frames = 0; fpsT = 0; if (flags.dbg && !flags.shots) dbgEl.textContent = `${fps} fps · ${DBG.draws} draws · ${(DBG.tris / 1000) | 0}k tris · s=${sCam.toFixed(0)} · ${world.level?.id ?? '?'} ${world.hash?.() ?? ''}`; } requestAnimationFrame(frame); } if (flags.dbg && !flags.shots) dbgEl.style.display = 'block'; requestAnimationFrame(frame); export { scene, camera, renderer, bus, flags, world }; // read-only handles for lanes