// Splatter Tennis — boot, main loop, intro overlay, and the window.T test hooks. // World units: meters. z axis = court depth, 0 at FAR baseline, 23.77 at NEAR baseline. // Module map: config (tuning/constants) · state (mutable game state) · gameplay (logic/update) // render (camera/drawing) · input (keys/touch) · audio (sfx/umpire) · panel (T tuning UI) import { TUNE } from './config.js'; import { P, O, ball, score, G, setupPoint } from './state.js'; import { update, keyPressed, solveShot, inServeBox, inCourt } from './gameplay.js'; import { fitViewport, render, proj } from './render.js'; import { voice } from './audio.js'; import './input.js'; let last = performance.now(); function frame(now) { const dt = Math.min(0.033, (now - last) / 1000); last = now; update(dt); render(); requestAnimationFrame(frame); } fitViewport(); setupPoint(); // intro overlay: first tap dismisses it, unlocks audio (iOS needs the gesture) and starts play const intro = document.getElementById('intro'); function dismissIntro() { if (!intro.parentNode) return; intro.remove(); voice('quiet_please'); } intro.addEventListener('pointerdown', e => { e.preventDefault(); dismissIntro(); }); addEventListener('keydown', () => dismissIntro(), { once: true }); requestAnimationFrame(frame); // exposed for in-browser sanity checks and simulated playtests window.T = { TUNE, ball, P, O, score, solveShot, inServeBox, inCourt, proj, setupPoint, step: dt => update(dt), press: keyPressed, get phase() { return G.phase; }, set phase(v) { G.phase = v; } };