Keep the auto-run's target where the player is allowed to stand

Oliver's v369 log ("vieeel besser") showed two misses with the auto-run
heading to x 6.5 and 8.7, far outside the frame he is held in. Since the
dip ends at the bounce, a hard cross-court topspin ball drifts sideways
long enough that its comfort point lies off the court; intercept had no
lateral bound, and its last-resort target was never checked at all
(headless: x 14.8). Candidates now respect xLimit — a kicking ball is
taken earlier, on the rise — and the final target is clamped. v370.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5.5 committed 2026-10-14 13:46:53 +02:00
1 parent e25047ed0c
commit 8e4b298d92
4 files changed
+53 -6

No files matched your search

+28
View File
@@ -1437,6 +1437,34 @@ test('a deep ball the player waits for BEHIND his baseline is still his to hit
} finally { TUNE.matchFlub = flub; TUNE.autoAssist = assist; TUNE.matchAssist = 1.15; }
});
test('a topspin ball kicking out wide is planned where the player may go, and returned', () => {
// Oliver's v369 log, 84.26 s: "ball line 2.72m wide — run, don't swing", runTo=8.73. Since the dip
// ends at the bounce, a ball landing just inside the line drifts on sideways long enough that the
// auto-run planned him far outside the frame he is held in (xLimit) — he waited at the edge.
const flub = TUNE.matchFlub; TUNE.matchFlub = 0;
const assist = TUNE.autoAssist; TUNE.autoAssist = 1.15;
try {
startMatch(); G.interlude.introT = 0;
oppOf().swingCool = 999;
G.phase = 'rally'; G.wasServe = false; G.missLog.length = 0;
Object.assign(P, { x: -1.2, z: 24.4, swingT: -1, tgt: null });
const g = TUNE.gravity * (1 + 0.68 * TUNE.spinFactor);
const v = solveShot(-4, 1, -0.6, 3.3, COURT.L - 3.67, 30, g); // hard cross-court: lands 3.3, kicks on out
Object.assign(ball, { active: true, scored: false, lastHit: 'O', bounces: 0, rx: null, spin: 1.15, soft: false,
x: -4, y: 1, z: -0.6, vx: v.vx, vy: v.vy, vz: v.vz, g });
let lift = -1, widest = 0;
for (let i = 0; i < 3 * 60 && ball.active && ball.lastHit === 'O'; i++) {
if (lift < 0 && ringCue()?.gold) lift = i + 6;
if (i === lift) keyPressed('Space', 0.1);
update(DT);
if (P.tgt) widest = Math.max(widest, Math.abs(P.tgt.x));
}
// (headless the frame is wide enough that the bound is P_BOUNDS.x; on a phone it is the screen, ~4.3)
assert.ok(widest <= cfg.P_BOUNDS.x + 1e-6, 'the run stays where he may stand: widest target x ' + widest.toFixed(2));
assert.equal(ball.lastHit, 'P', 'and he gets it back: ' + JSON.stringify(G.missLog.map(e => e.result + ' ' + e.reason)));
} finally { TUNE.matchFlub = flub; TUNE.autoAssist = assist; }
});
test('rally depth: deep by default, mistiming lands it SHORTER, never longer', () => {
// the only out a rally ball may find is wide — depth must never put one past the baseline
startMatch(); G.interlude.introT = 0;