The toss is the server's ball — the real death loop
"still when I lost a single point and try to serve again every further tap does hurt me more until I am dead". Reproduced tap-for-tap this time, and the serveNum fix was a red herring — a real bug, but not this one. tossBall never stamped ball.lastHit. After any rally the zombie touched last, the freshly tossed ball still said lastHit 'O' — and the previous pass moved the server behind the baseline, past the passing-shot line. So the moment the toss went up, the game saw an opponent ball beyond your baseline: "its passing shot ends the duel", point to them, blood. Every tap = a toss = an instant lost point, faster the more he panicked. Two fixes composed into a death loop; the stale field was harmless while serving happened inside the court. One stamp in tossBall — ball.lastHit = sv.side — fixes every caller. The duel and the gore mode carried the same latent bug for anyone tossing deep behind the line after a racket-zombie touch. Verified with the exact field shape: lose a rally the zombie touched last, then eight badly-timed panic taps — one honestly lost point, zero toss-bleeds. Before the fix every single toss bled. 41 tests, 15 seeds clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f67411e330
commit
24be613c61
4 files changed
+52
-2
No files matched your search
@@ -82,7 +82,7 @@ globalThis.getComputedStyle = () => ({ paddingTop: '0' });
|
||||
|
||||
// ---- game modules (real code) ----------------------------------------------
|
||||
const { G, P, O, score, ball, setupPoint } = await import('./src/state.js');
|
||||
const { update, forceZombies, splatPoint, restartRun, flickTarget } = await import('./src/gameplay.js');
|
||||
const { update, forceZombies, splatPoint, restartRun, flickTarget, keyPressed } = await import('./src/gameplay.js');
|
||||
const { startDuel, resolveInterlude, startBossFight, endBossFight, startDrill: _startDrill, endDrill, startMatch: _startMatch, endMatch } = await import('./src/beats.js');
|
||||
// the drill's first-sight modal freezes the world (that is what modals do) — dismiss it the way a
|
||||
// player would, so the tests step a live court
|
||||
@@ -570,6 +570,24 @@ test('THE DEATH LOOP: a fault before a lost point must not arm second-serve fore
|
||||
assert.equal(G.serveNum, 1, '...whoever won it');
|
||||
});
|
||||
|
||||
test('THE REAL DEATH LOOP: a toss after their rally touch is not their passing shot', () => {
|
||||
// tossBall left ball.lastHit stale. After any rally the zombie touched last, the fresh toss still
|
||||
// said lastHit 'O' — and the match serves from BEHIND the baseline (z 24.3), past the passing-shot
|
||||
// line (COURT.L + 0.3), so the moment the toss went up the game scored "its passing shot ends the
|
||||
// duel": a point and blood per TAP. serveNum was a red herring; this was the loop Oliver hit twice.
|
||||
startMatch();
|
||||
for (let i = 0; i < 2.0 * 60; i++) update(DT); // ride out the intro beat — it blocks serving
|
||||
const hp0 = G.hp, pts0 = score.pts.slice();
|
||||
ball.lastHit = 'O'; ball.active = false; // the zombie touched the last rally ball
|
||||
assert.ok(P.z > COURT.L + 0.3, 'setup: the server stands behind the passing-shot line');
|
||||
keyPressed('Space'); // the toss goes up
|
||||
assert.equal(G.phase, 'toss', 'the toss is up');
|
||||
for (let i = 0; i < 40; i++) update(DT); // two thirds of a second of flight
|
||||
assert.equal(G.hp, hp0, 'no blood for tossing a ball');
|
||||
assert.deepEqual(score.pts, pts0, 'and no point changes hands');
|
||||
assert.equal(ball.lastHit, 'P', 'the toss is the server\'s ball');
|
||||
});
|
||||
|
||||
test('every point starts with the receiver on its mark', () => {
|
||||
startMatch();
|
||||
const rz = oppOf();
|
||||
|
||||
Reference in new issue
Block a user