Find a burst weapon wherever it is on the rack, not just in the first five
endBurst looked its tin up with [0, 1, 2, 3, 4].find(...) — correct for exactly as long as the rack held five slots. Growing RACK_MAX to 9 meant a burst weapon picked up into slot 5..9 could never be found, so it was never spent: you fired it and the count stayed where it was, forever. Oliver hit it with the beacon, the rocket and the flamer, which together with the gun are precisely the four kind:'burst' weapons — endBurst is the only spend path that searched for its slot instead of being handed one. The serve weapons were never affected, and that is the tell: bomb, triple, fire and the laser all spend through spendSlot(G.weapon), the live index. Checked the laser end to end at slot 8 rather than reasoning about it — 6, 5, 4, 3, 2, 1, gone. Fixed at the invariant rather than at the call site. The rack has always had a comment saying everything reads through the slot accessors so nothing else needs to know how it is stored; slotOf(id) is now the fourth one, and it is the single place that knows the range. Verified all four burst weapons arm from a real tap in slots 5-8 and leave the rack when the burst expires. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ebce08199f
commit
0c12157702
3 files changed
+27
-6
No files matched your search
+13
@@ -1172,6 +1172,19 @@ scaled so the BODY matches the standing frame (98% of sprite height) and the rac
|
|||||||
this on any new pose with a raised prop.
|
this on any new pose with a raised prop.
|
||||||
|
|
||||||
## Gotchas learned the hard way
|
## Gotchas learned the hard way
|
||||||
|
- **Nothing outside the four slot accessors may know how many slots there are.** `endBurst` had
|
||||||
|
`[0, 1, 2, 3, 4].find(i => slotId(i) === id)`, which was correct for exactly as long as the rack
|
||||||
|
held five. `RACK_MAX` 4 → 9 and a burst weapon in slot 5..9 was suddenly unfindable, so it was
|
||||||
|
never spent: beacon, flamer, rocket and gun sat at their pickup count forever. Use `slotOf(id)`.
|
||||||
|
The serve weapons were untouched because they all spend through `spendSlot(G.weapon)` — the LIVE
|
||||||
|
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 test that walks the game forward will quietly kill the player.** Three separate diagnostic runs
|
||||||
|
above returned nonsense (`stillCarried: true`, no laser fired at all) purely because `G.mode` had
|
||||||
|
become `'eaten'` several steps earlier and every gameplay entry point bails on that. When a probe
|
||||||
|
says a feature is broken, check `T.G.mode` before believing it. `setInterval(() => T.zombies.length = 0, 30)`
|
||||||
|
for the duration of the test is the cheap fix.
|
||||||
- **Stale ES modules are the #1 time sink.** python http.server sends no cache headers, so an edited
|
- **Stale ES modules are the #1 time sink.** python http.server sends no cache headers, so an edited
|
||||||
module often loads STALE. `preview_stop`/`preview_start` only helps if the port actually changes,
|
module often loads STALE. `preview_stop`/`preview_start` only helps if the port actually changes,
|
||||||
and holding 8641 with a throwaway socket **did not work reliably**. What DOES work:
|
and holding 8641 with a throwaway socket **did not work reliably**. What DOES work:
|
||||||
|
|||||||
+13
-5
@@ -511,12 +511,20 @@ function startChangeover() {
|
|||||||
// no tins here anymore — weapons are won in the duel, never handed out on a schedule
|
// no tins here anymore — weapons are won in the duel, never handed out on a schedule
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- the courtside rack. Five slots still, but only slot 0 is fixed: it is BALL (infinite), or a
|
// ---- the courtside rack. RACK_MAX + 1 slots, and only slot 0 is fixed: it is BALL (infinite), or
|
||||||
// FIFTH gathered weapon riding it temporarily. Slots 1..4 are whatever you have picked up, in order.
|
// an overflow weapon riding it temporarily. Slots 1..RACK_MAX are what you have picked up, in order.
|
||||||
// Everything reads through these three, so nothing else has to know how the rack is stored.
|
// EVERYTHING READS THROUGH THESE FOUR, so nothing else has to know how the rack is stored — and
|
||||||
|
// nothing else may hardcode how MANY slots there are. `slotOf` exists because endBurst did exactly
|
||||||
|
// that ("[0,1,2,3,4].find(...)"), so the moment the rack grew past five, a burst weapon sitting in
|
||||||
|
// slot 5..9 could never be found and never spent: beacon, flamer and rocket sat at their pickup
|
||||||
|
// count forever. One accessor, one place that knows the range.
|
||||||
export const slotEntry = i => (i === 0 ? G.temp : G.rack[i - 1]) || null;
|
export const slotEntry = i => (i === 0 ? G.temp : G.rack[i - 1]) || null;
|
||||||
export const slotId = i => slotEntry(i)?.id || (i === 0 ? 'ball' : null);
|
export const slotId = i => slotEntry(i)?.id || (i === 0 ? 'ball' : null);
|
||||||
export const slotStock = i => { const e = slotEntry(i); return e ? e.n : (i === 0 ? Infinity : 0); };
|
export const slotStock = i => { const e = slotEntry(i); return e ? e.n : (i === 0 ? Infinity : 0); };
|
||||||
|
export const slotOf = id => { // -1 if you are not carrying it
|
||||||
|
for (let i = 0; i <= RACK_MAX; i++) if (slotId(i) === id) return i;
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
export function selectWeapon(i) {
|
export function selectWeapon(i) {
|
||||||
if (G.interlude) return; // the sudden-death duel is played under tennis rules — plain balls only
|
if (G.interlude) return; // the sudden-death duel is played under tennis rules — plain balls only
|
||||||
if (slotStock(i) <= 0) return; // dud: nothing in that slot (slot 0 is only ever empty if a spent
|
if (slotStock(i) <= 0) return; // dud: nothing in that slot (slot 0 is only ever empty if a spent
|
||||||
@@ -1022,8 +1030,8 @@ export function endBurst() {
|
|||||||
const id = G.gunId;
|
const id = G.gunId;
|
||||||
G.gunId = null;
|
G.gunId = null;
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
const slot = [0, 1, 2, 3, 4].find(i => slotId(i) === id);
|
const slot = slotOf(id);
|
||||||
if (slot !== undefined) spendSlot(slot);
|
if (slot >= 0) spendSlot(slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The laser fired from a TAP (the keyboard/two-stage path). The drag path solves its own direction
|
// The laser fired from a TAP (the keyboard/two-stage path). The drag path solves its own direction
|
||||||
|
|||||||
+1
-1
@@ -184,6 +184,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="module" src="main.js?v=116"></script>
|
<script type="module" src="main.js?v=117"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Reference in new issue
Block a user