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

+11 -5
View File
@@ -1491,6 +1491,10 @@ function intercept(side) {
if (tc > 0 && y + vy * tc - 0.5 * g * tc * tc > CONTACT.maxY) { plan.net = false; plan.lob = true; }
}
const back = plan && !plan.net; // a baseliner lets it bounce
// ...and only where his legs may go: the player is held on screen (xLimit), and since the dip ends at
// the bounce a kicking ball drifts on sideways long enough to plan him out to x 6.5-8.7 — he ran to
// the edge of the frame and watched it pass (his v369 log, 65.77 s and 84.26 s)
const xl = side === 'P' ? xLimit() : 99;
let fallback = null, deepest = null, anyHit = null, rise = null, nb = ball.bounces;
for (let i = 0; i < 300; i++) {
vy -= g * dt; x += vx * dt; y += vy * dt; z += vz * dt;
@@ -1501,7 +1505,7 @@ function intercept(side) {
// (up to rallyTop since the dip ends at the bounce: a kicking ball is taken at the top of its
// bounce, chest high, rather than chased forward and volleyed — 40% volleys was the first cut)
if (back && nb === 1 && i > 5 && y > 0.45 && y < (plan.short ? TUNE.rallyHit + 0.6 : TUNE.rallyTop)) {
const inB = side === 'P' ? z <= COURT.L + TUNE.rallyBehind : z >= -TUNE.rallyBehind;
const inB = (side === 'P' ? z <= COURT.L + TUNE.rallyBehind : z >= -TUNE.rallyBehind) && Math.abs(x) <= xl;
if (inB && Math.hypot(x - ch.x, z - ch.z) / spd <= i * dt) {
if (plan.short || vy < 1) return { x, z };
rise ||= { x, z };
@@ -1514,17 +1518,19 @@ function intercept(side) {
// height: running forward onto the short ball is the tennis answer, and now it is the assist's.
if (side === 'P' && rallyFeel() && !anyHit && i > 5 && z > COURT.netZ && y < 1.6 && y > 0.35) {
const pt = { x, z: Math.min(24.5, z) };
if (Math.hypot(pt.x - ch.x, pt.z - ch.z) / spd <= i * dt) anyHit = pt;
if (Math.abs(x) <= xl && Math.hypot(pt.x - ch.x, pt.z - ch.z) / spd <= i * dt) anyHit = pt;
}
if (i > 5 && inZone(z) && y < 1.6 && y > 0.3) {
const pt = { x, z: side === 'O' ? Math.max(0.8, z) : Math.min(24.5, z) };
const reachable = Math.hypot(pt.x - ch.x, pt.z - ch.z) / spd <= i * dt;
const reachable = Math.abs(x) <= xl && Math.hypot(pt.x - ch.x, pt.z - ch.z) / spd <= i * dt;
if (y > 0.7 && reachable && !back) return pt; // waist-high AND arrivable in time
if (reachable && !fallback) fallback = pt; // low but arrivable
deepest = pt; // last resort: run at the deepest hittable point
}
}
return rise || fallback || deepest || anyHit || home; // zone points first: meeting deep is still better
const to = rise || fallback || deepest || anyHit || home; // zone points first: meeting deep is still better
// a ball no point of which he can reach still gets a run — to the nearest spot he is allowed to stand
return Math.abs(to.x) > xl ? { x: Math.sign(to.x) * xl, z: to.z } : to;
}
// THE GOLD RING, TIMED (2026-10-14). The approach ring used to go gold at a DISTANCE (matchRingSweet
@@ -1549,7 +1555,7 @@ export function ringCue() {
const legal = y <= CONTACT.maxY && (y >= CONTACT.minY || vy > 0)
&& !(volley && y < COURT.netH && ball.rx && !ball.rx.net);
const k = togo > 0.01 ? Math.min(1, sp * t / togo) : 0;
const px = P.x + (tg ? tg.x - P.x : 0) * k, pz = P.z + (tg ? tg.z - P.z : 0) * k;
const xl = xLimit(), px = Math.max(-xl, Math.min(xl, P.x + (tg ? tg.x - P.x : 0) * k)), pz = P.z + (tg ? tg.z - P.z : 0) * k;
if (legal && Math.hypot(x - px, (z - pz) * 0.8) <= reach) {
const win = TUNE.swingWindow * (volley ? VOLLEY.window : 1) * winLevel();
const sweet = TUNE.swingWindup + win / 2, band = win / 2 * TUNE.ringBand;