The receiver stands where the serve can actually go

The trick: hard serve, slight slide, and the first ten opponents died where
they stood. Measured headless — that serve was returned 0 times out of 24 at
EVERY rung. Three things were wrong and all three were the same thing: he
waited on the tennis mark 1.3 m off the centre line (covering the T, conceding
the whole corner), he took no first step, and he chased the ball's LIVE
position, which during a serve is still up by the server's baseline.

Now: he waits on the bisector of the serves he has to cover, takes a
returner's first step (serve-scoped — the rally is untouched), and runs at the
bounce instead of at the ball. The slid serve comes back from rung one; a
flush serve into the very corner of the box still aces the bottom of the
ladder, which is the reward placement is supposed to earn.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5 committed 2026-08-15 20:29:17 +02:00
1 parent ece27827e5
commit 86b8275f5d
6 files changed
+77 -10

No files matched your search

+32 -1
View File
@@ -84,7 +84,7 @@ globalThis.getComputedStyle = () => ({ paddingTop: '0' });
// ---- game modules (real code) ----------------------------------------------
const state = await import('./src/state.js');
const { G, P, O, score, ball, setupPoint } = state;
const { update, forceZombies, startGore, splatPoint, restartRun, flickTarget, keyPressed, classicGame, serveApexY, assistK, rallyFeel, barrelGap, machCap } = await import('./src/gameplay.js');
const { update, forceZombies, startGore, splatPoint, restartRun, flickTarget, keyPressed, classicGame, serveApexY, assistK, rallyFeel, barrelGap, machCap, hitServe } = await import('./src/gameplay.js');
const { startDuel, resolveInterlude, startBossFight, endBossFight, startDrill: _startDrill, endDrill, startMatch: _startMatch, endMatch, FALL_LEN, startNetVs, endNetVs } = await import('./src/beats.js');
const { mirror, netMsg, netTest, netTick } = await import('./src/net.js');
// the drill's first-sight modal freezes the world (that is what modals do) — dismiss it the way a
@@ -1083,6 +1083,37 @@ test('REACHABILITY: an auto-run player can cover the first opponent\'s returns',
} finally { TUNE.autoAssist = assist; }
});
test('a served ball with a slide on it is no longer a free kill', () => {
// Oliver: "the moment you serve you can serve hard with a slight slide and almost all of them
// fail to get the ball" — the first ten opponents died to one gesture. Measured before the fix:
// a flush, hard, swayed serve was returned 0 times out of 24 at EVERY rung of the ladder, because
// the receiver waited on the tennis mark (1.3 m off the centre line, covering the T and conceding
// the whole corner) and then chased the ball's LIVE position, which during a serve is still up by
// the server's baseline. Mark, first step and read are all dialled in config; this asserts the
// EXPERIENCE, not the dials, exactly like the reachability soak above.
// A serve into the very corner of the box is deliberately still an ace at the bottom of the
// ladder — the trick that had to die is the one that needed no placement.
const serve = sx => {
freshSplatter();
startMatch();
for (let i = 0; i < 2.2 * 60; i++) update(DT);
ball.active = true; ball.collected = false; ball.bounces = 0; ball.scored = false;
ball.x = P.x + 0.12; ball.z = P.z; ball.y = 2.6; ball.vx = ball.vy = ball.vz = 0;
G.wasServe = true;
hitServe(P, 1, { sx, hard: 1 }); // flush timing, full pace, `sx` of slide
for (let i = 0; i < 5 * 60; i++) {
update(DT);
if (ball.lastHit === 'O') return true; // he got a racket on it
if (!ball.active || G.phase !== 'rally') break;
}
return false;
};
let got = 0;
const n = 10;
for (let i = 0; i < n; i++) if (serve(-0.5)) got++;
assert.ok(got >= n * 0.6, `opponent one returns a slid serve: ${got}/${n} (was 0)`);
});
test('the match racket is longer, and only the match racket', () => {
assert.ok(TUNE.matchReach > 1.2, 'match contact reach is widened — the recorder showed misses by centimetres');
// the gore modes' contact stays untouched: matchReach only multiplies inside G.match