- opaque canvas: the compositor stops alpha-blending 2.5Mpx at 60Hz
- settled modals and the death summary idle at 20-24 fps; play untouched
- TUNE.lowPower caps RES at 3 (-44% pixels/frame), flippable live
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
game.js (830 lines) -> 9 modules, native ES modules so still zero build step:
dom canvas handles (imports nothing; keeps cycles harmless)
config TUNE (live-tunable) + structural constants
state all mutable game state (G) + setupPoint/sendBoy
audio sfx synthesis, recorded umpire voice, reverb, speech fallback
input keyboard + touch, exports axes and takeFlickAim()
gameplay ballistics, swings, serve, rules, AI, update loop
render camera, viewport, sprites, crowd, all drawing
panel T tuning UI
main boot, loop, intro, window.T test hooks
Deduped into named constants: bounce tempo (was in 3 places), contact
height band, intercept zone depth, home positions, player bounds, toss
params, shot depths, pose hold time, sprite scale, actor heights. Also
extracted drawShadow/drawBall/expirePose from repeated code.
One intentional import cycle (input <-> gameplay), safe: both sides only
call functions at runtime, never during module evaluation.
Regression-verified in browser sims: point cycle, CPU returns 11/12,
assist contract 8/8, corner-winner gradient intact (0/12 perfect corners
returned, 10/12 center balls returned).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>