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:
1 parent
e25047ed0c
commit
8e4b298d92
4 files changed
+53
-6
No files matched your search
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user