Pay out every won duel, and stop the popup eating the announcement

Two separate faults, both of which read as "the duel gave me nothing".

The first is mine, from making weaponModal first-sight-only. G.flash ticked
ABOVE the `if (G.modal) return` in update(), so a flash raised in the same beat
as a popup spent its whole 1.1 s counting down invisibly behind the dim. That was
harmless while every tin opened its own modal — but once the modal went quiet the
label flash WAS the announcement, and the trainer's WOUNDS TREATED popup ate it
every single time you won a duel while wounded, which late in a run is every
time. Measured: flash null after 1.5 s of reading, nothing left on dismiss. A
modal freezes the world and a transient HUD message is part of the world, so the
tick moved below the return. It now holds at 1.1 for as long as you read, and
starts counting when you tap.

The second is older and Oliver felt it directly. deliverTin CYCLED the ladder —
TINS[G.deliveries++ % 4] — handing you whatever rung came next even when that
type was already at wCap, where grantWeapon's Math.min clamp makes the delivery a
silent no-op. Bomb and fire fill in three duels each, triple in one, so past
about the thirteenth duel three rungs of four are full and most late duels paid
out in literally nothing. It walks to the first rung with ROOM now. Simulated
twenty wins with nothing ever spent: every one delivers through #16, where before
#10, #13, #14 and #15 were already empty handovers. Past that it says RACK FULL,
which is a real state and not a bug, so it says so plainly instead of miming a
delivery — no boy, no umpire line.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5 committed 2026-08-04 06:49:41 +02:00
1 parent 5a4c6e4359
commit c75bd5b9ba
3 files changed
+32 -3

No files matched your search

+11
View File
@@ -1180,6 +1180,17 @@ this on any new pose with a raised prop.
index, never a search — which is why the laser looked fine while the other four did not.
Note also that spending re-indexes the rack (`spendSlot` splices), so a slot number goes stale the
moment any tin empties. Look weapons up by ID, not by a slot you cached a frame ago.
- **A modal freezes the WORLD, and `G.flash` is part of the world.** Its tick used to sit above the
`if (G.modal) return` in `update()`, so any flash raised in the same beat as a popup spent its
whole 1.1 s counting down invisibly behind the dim. Harmless while every tin opened a modal of its
own; the moment `weaponModal` became first-sight-only, the label flash WAS the announcement and
the trainer's popup ate it on every wounded duel win — *"later in the game a won duel does not
give you weapons anymore"*. Anything transient and drawn belongs below that return, not above it.
- **`deliverTin` walks the ladder, it does not cycle it.** `TINS[G.deliveries++ % 4]` handed you
whatever rung came next even when that type was already at `TUNE.wCap`, where `grantWeapon`'s
`Math.min` clamp made the whole delivery a no-op. Past roughly the thirteenth duel three rungs of
four are full, so most late duels paid out in nothing at all. It now advances to the first rung
with room, and only says `RACK FULL` when every one of them genuinely is.
- **Every sound in audio.js swallows its own errors** (`try { ... } catch (e) {}`), so a clean
console proves NOTHING about whether one played. To verify a sound really built its graph, count
nodes — patch `AudioContext.prototype.createBufferSource` / `createOscillator` /