Audit items 4 and 5. Both are shell behaviour a finished game is expected to have and this one
never did.
THE RUN NOW STARTS WHEN YOU START IT. cards.js has emitted `ui:start` on title dismissal since
round 1 and its own comment says nothing consumes it — so the simulation ran behind the title
card. Measured: 1.2 s of sitting on the title moved the ship and burned 1.2 s off the par clock
before the player had touched anything. L1's first checkpoint (s=20) and the opening comms line
both fired while the title was still up, and a hazard could hurt you before you started, which is
why cards.js already carries a player:damage auto-dismiss as a safety valve. Now `started` gates
step(); verified s stays at 2 across a second of real frames.
PAUSE ON BLUR. The ui:pause consumer has worked since round 2 and nothing but the pause key ever
produced for it. Alt-tab mid-fight and you came back to a dead ship. blur + visibilitychange now
produce it.
AUDIO HEARS THE PAUSE. engine.js handled eleven bus events and not that one, so the bed, drone,
grain scheduler and heartbeat ran at full level behind the pause card, and the heart slewed to
its resting 50 bpm while you read and snapped back on resume. Ducks the master rather than
suspending the context — the heartbeat's pump schedules against ctx.currentTime and would wake
up in the past.
FAILURE HAS A FACE. Every failure mode in this game presented as the same black rectangle, so a
player could not tell "loading" from "crashed" and had nothing to report:
- loadLevel did `return mod.getLevel(id)` without awaiting, so a rejection escaped its own
try/catch and killed module evaluation. A mistyped ?lvl= now falls back to the stub tube
(which names itself STUB ESOPHAGUS) instead of a dead page.
- frame() re-armed its rAF at the BOTTOM, so one throw anywhere stopped the loop forever while
the canvas kept showing the last good frame. Body is guarded; the loop always re-arms.
- bus.emit ran listeners bare, so a throw in one subscriber aborted the rest of the emit — and
player:state is emitted every frame from inside step(), so one bad frame in the HUD took
combat and audio down with it. Listeners are isolated now.
- webglcontextlost had no handler at all: a laptop sleeping left a white page with a floating
HUD.
All four now land on a FEED LOST card naming the cause, in the game's own voice.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>