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
+23
@@ -889,6 +889,29 @@ again and again until i die"*.
|
||||
seven straight games: **it now loses the match** (2-3, then blooded out in game two). That may
|
||||
even be too hot — `matchSpeedBase`/`matchFlub` are the two dials, and Oliver's thumb is the judge.
|
||||
|
||||
**SIXTEENTH PASS — the REAL death loop, and my serveNum fix was a red herring.** Oliver, again:
|
||||
*"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 before touching anything, and the previous diagnosis
|
||||
was wrong — serveNum was A bug, but not THE bug.
|
||||
|
||||
**`tossBall` never stamped `ball.lastHit`.** After any rally the zombie touched last, the freshly
|
||||
tossed ball still said `lastHit: 'O'` — and the fourteenth pass moved the server BEHIND the baseline
|
||||
(z 24.3, past the passing-shot line at COURT.L + 0.3). 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 of my 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` — the toss is the server's ball) fixes every
|
||||
caller. **The duel and the gore mode carried the same latent bug** for anyone tossing from deep
|
||||
behind the line after a racket-zombie touch; it never fired only because splatter serving used to
|
||||
happen from inside the court. Verified in the browser 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.
|
||||
|
||||
**The lesson for this file: when a report survives a fix, the fix was wrong — reproduce before
|
||||
re-diagnosing.** The serveNum reset was real and stays, but it was found by reading code, not by
|
||||
reproducing the report, and the actual loop needed the exact field conditions (a zombie touch, the
|
||||
new serve position, panic-speed taps) to show itself.
|
||||
|
||||
**Open, and every one of them is Oliver's call after he plays it:**
|
||||
1. ~~**A ball that carries through the lane can complete two steps at once.**~~ Largely CLOSED by
|
||||
the slice removal (seventh pass) — a cut ball picking its own next victim was the main way this
|
||||
|
||||
+1
-1
@@ -400,6 +400,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="src/main.js?v=167"></script>
|
||||
<script type="module" src="src/main.js?v=168"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -192,6 +192,15 @@ function tossBall(sv) {
|
||||
// keeps the reach he was drawn with.
|
||||
ball.x = sv.x + (sv.side === 'P' ? 0.12 : -0.35); ball.z = sv.z; ball.y = TOSS.y0;
|
||||
ball.vx = 0; ball.vz = 0; ball.vy = TOSS.vy; ball.g = TUNE.gravity;
|
||||
// THE TOSS IS THE SERVER'S BALL. lastHit was left stale from the previous rally, and if the
|
||||
// OPPONENT had touched it last, the freshly tossed ball read as *their* ball in flight — which
|
||||
// armed every rule that judges an opponent ball, most fatally the passing-shot test: the match
|
||||
// serves from BEHIND the baseline (z 24.3 > the test's 24.07), so the moment you tossed after any
|
||||
// rally they had touched, "its passing shot ends the duel" fired, the point went to them and drew
|
||||
// blood. That was Oliver's death loop: every tap = a toss = an instant lost point, faster the
|
||||
// more he panicked. One stamp here fixes every caller — the duel and the gore mode carried the
|
||||
// same latent bug for anyone tossing from deep behind the line.
|
||||
ball.lastHit = sv.side;
|
||||
G.phase = 'toss';
|
||||
}
|
||||
export function serveApexY() { return TOSS.y0 + TOSS.vy * TOSS.vy / (2 * TUNE.gravity); } // ~2.95
|
||||
|
||||
@@ -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