Builds the last missing v0.2 floor mechanic (design §3.2). A fight is not a
modal: two patrons square up, a 7s fuse burns through five escalating shove
rungs, and you stop it by physically standing between them — it spends the
floor's only real resource, where you are. The tell (shoving silhouettes +
pulsing marker) deliberately reads OUTSIDE the torch cone, because spotting it
is the gameplay.
Adversarially reviewed the whole lane, then hand-adjudicated the findings. Six
real defects fixed, several in code from round 1:
- PatDownOverlay compared 'Escape' against the scene's uppercased 'ESCAPE', so
ESC was dead there while the on-screen hint promised it worked.
- The spawn cap counted departed agents (CrowdSim keeps them so find() works),
so every ejection permanently shrank the venue and the floor drained.
- FloorView's sprite cache is keyed by patron id and outlived resetRun, while
_resetPatronSerial recycles ids from p0 — reseeding gave the new crowd the
old run's dolls.
- escalation's per-incident `resolved` was consumed as a permanent per-patron
`handled`, retiring a correctly-watered `loose` patron for the whole night
while they kept drinking. Now settles per stage and re-arms when they worsen.
- Stall busts retired the captured occupant list, but the crowd keeps simulating
behind the modal. Re-queries at resolution.
- Stall infractions put a stall id in the frozen `patronId` field.
Also wired teardown to Phaser's SHUTDOWN event; a `shutdown()` method is never
called, so leaving the floor leaked the view, sim, overlays and bus listeners.
Fight balance was measured, not guessed: at a 20s cooldown an idle night brewed
28 fights and 28 heat strikes against a run cap of 3. Now 150s (~5/night) with a
deferred heat strike, matching §3.2's "possible Heat". An idle night ends at
vibe 0 / 5 strikes; an attentive one at vibe 54 / 0 strikes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>