The short ball gets chased, intercept gets legs, the vignette gets corners

Second recorded session: 5 wins and a deuce war to 5-6 — the mode is a
contest — but all seven losses shared one signature: the ball died in court
1.4-2m short of him while he ran 0.2-0.5m. Three defects:

The pace cut created dying rollers. Slow returns bounce and die at z 18-20,
outside intercept's near zone, so nothing chased them, and the rolled-dead
rule hands the point to the zombie. They also arrive at his feet below legal
contact height — the "ball arrives right at the player" feel. intercept keeps
an anyHit candidate now: the earliest reachable point on his half where the
ball is still at hitting height, so the assist runs forward onto the short
ball. Verified: he sprints z 23 -> 16.4 and meets it at gap ~0.

intercept computed reachability with autoAssist — 0 on any fine pointer — so
a desktop match's planner answered "home" every time. He had legs, his
destination was always the baseline. assistK() is shared by mover and
planner now.

And the vignette was a radial gradient in a portrait canvas: the circle
reaches the corners but hits the side edges at fraction zero — "red pulsing
from below" was the bottom edge being the only place it got close. Four
linear edge fades now, hugging the rectangle on any aspect.

48 tests, 12 seeds clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5 committed 2026-08-11 23:36:15 +02:00
1 parent 3a789eb2e9
commit f37fb93875
5 files changed
+93 -16

No files matched your search

+27
View File
@@ -588,6 +588,33 @@ test('the match brings its own legs: auto-run works even where autoAssist is 0',
} finally { TUNE.autoAssist = assist; }
});
test('THE SHORT BALL: the assist runs FORWARD onto a dying return, desktop conditions included', () => {
// Seven of seven recorded losses: a slow return bounced and died at z 18-20 — outside the
// intercept zone, so nothing chased it and it rolled dead at his feet (below legal contact
// height). Doubly broken on desktop: intercept computed reachability with autoAssist (0 there),
// so its answer was always "run home". Both fixed via assistK() + the anyHit candidate.
const assist = TUNE.autoAssist; TUNE.autoAssist = 0; // the desktop pane condition
try {
startMatch();
for (let i = 0; i < 2.2 * 60; i++) update(DT);
G.phase = 'rally';
P.x = 0; P.z = 23;
// a slow short return: incoming, will bounce and die around z ~19
ball.active = true; ball.lastHit = 'O'; ball.x = 1.5; ball.z = 10; ball.y = 1.6;
ball.vx = 0.4; ball.vy = 2.2; ball.vz = 5.5; ball.bounces = 0; ball.g = TUNE.gravity;
const z0 = P.z;
let minGap = 99, minPz = P.z; // deepest he RAN, read during the chase — the point reset
// teleports him home after, so the final position proves nothing
for (let i = 0; i < 2.5 * 60 && ball.active && ball.lastHit === 'O'; i++) {
update(DT);
minPz = Math.min(minPz, P.z);
minGap = Math.min(minGap, Math.hypot(ball.x - P.x, (ball.z - P.z) * 0.8));
}
assert.ok(minPz < z0 - 3.0, 'he ran FORWARD to meet it: z ' + z0 + ' -> ' + minPz.toFixed(1));
assert.ok(minGap < 1.6, 'and got the ball into reach (closest ' + minGap.toFixed(1) + ' m)');
} finally { TUNE.autoAssist = assist; }
});
test('the match recovers to CENTRE between shots — a wide return must not strand you', () => {
// The match inherited gore-splatter's depth-only recovery (a sidestep-protection rule) by being
// G.mode 'splatter' — so one wide return pulled you off centre and you STAYED there, and the next