The fall anticipates the pass: a decided winner resolves mid-flight

Once the player's winner is past the net its landing is closed-form knowable
(predictLanding is exact), so a ball that will bounce in beyond the body's
provable reach resolves while still in the air — the collapse starts as the
ball flies past, roughly half a second earlier than the bounce-1 call, which
stays as the fallback for anything too close to call. Same conservative
margins, same willFall gate, same decided-latch discipline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Fable 5 committed 2026-08-13 22:06:41 +02:00
1 parent 1e601ca33d
commit 098aefeb63
1 file changed
+40 -2
+40 -2
View File
@@ -941,6 +941,9 @@ function matchFallWillPlay() {
return racketStage({ hp: after, hpMax: rz.hpMax }) !== racketStage(rz); // a limb
}
const rzChaseSpeed = rz => TUNE.racketSpeed
* (TUNE.matchSpeedBase + (G.match.opp - 1) * TUNE.matchSpeedPer) * ((rz.foe && rz.foe.speed) || 1);
// Can the match opponent still reach this ball before its second bounce? Closed-form flight from
// the POST-BOUNCE state (velocities already damped when the line call runs) against the body's own
// chase speed — the same dials racketAI moves with — plus a full swing reach and a 0.6 m benefit
@@ -950,8 +953,7 @@ function matchOutOfReach() {
if (!rz || ball.vz > 0) return false;
const g = ball.g || TUNE.gravity;
const t2 = Math.max(0.05, 2 * ball.vy / g); // time until the second touchdown
const spd = TUNE.racketSpeed * (TUNE.matchSpeedBase + (G.match.opp - 1) * TUNE.matchSpeedPer)
* ((rz.foe && rz.foe.speed) || 1);
const spd = rzChaseSpeed(rz);
for (let i = 0; i <= 8; i++) {
const t = t2 * i / 8;
const bx = ball.x + ball.vx * t, bz = ball.z + ball.vz * t;
@@ -960,6 +962,25 @@ function matchOutOfReach() {
return true;
}
// The MID-FLIGHT version (Oliver: "the losing point could still be more anticipated"): the same
// question asked while the ball is still in the air, over the piecewise track flight -> bounce ->
// roll to the second touchdown. Same conservative margins; pred is predictLanding()'s exact spot.
function matchOutOfReachFlight(rz, pred) {
const g = ball.g || TUNE.gravity;
const spd = rzChaseSpeed(rz);
const vyl = ball.vy - g * pred.t; // vertical speed at touchdown
const t2 = Math.max(0.05, 2 * (-vyl * TUNE.bounceY) / g);
const vx2 = ball.vx * TUNE.bounceXZ, vz2 = ball.vz * TUNE.bounceXZ;
const T = pred.t + t2;
for (let i = 0; i <= 10; i++) {
const t = T * i / 10;
const bx = t <= pred.t ? ball.x + ball.vx * t : pred.x + vx2 * (t - pred.t);
const bz = t <= pred.t ? ball.z + ball.vz * t : pred.z + vz2 * (t - pred.t);
if (Math.hypot(bx - rz.x, bz - rz.z) <= spd * t + TUNE.aiReach + 0.6) return false;
}
return true;
}
function onBounce() {
ball.bounces++;
pock(0.35, 500);
@@ -1752,6 +1773,23 @@ export function update(dt) {
G.netRipple = { x: ball.x, t: 0.4 }; // the tape carries the hit: a damped wobble where it clipped
}
}
// THE FALL ANTICIPATES THE PASS (Oliver: "the losing point could still be more anticipated
// timing-wise"). Once the player's winner is past the net its landing is closed-form knowable
// (predictLanding is exact), so a ball that will bounce in and is provably beyond the body's
// reach resolves MID-FLIGHT — the collapse starts while the ball is still in the air. The
// bounce-1 hook in onBounce stays as the fallback for anything too close to call up here.
if (G.match && !G.match.ending && !G.match.decided && G.phase === 'rally'
&& ball.active && ball.lastHit === 'P' && ball.vz < 0 && ball.z < COURT.netZ - 0.3
&& matchFallWillPlay()) {
const arz = zombies.find(z => z.variant === 'r');
const pred = arz && predictLanding();
if (pred && inCourt(pred.x, pred.z) && matchOutOfReachFlight(arz, pred)) {
G.matchEarly = true;
resolveInterlude('P');
G.matchEarly = false;
if (G.match) G.match.decided = true;
}
}
if (ball.y <= 0 && ball.vy < 0) {
// EXACT TOUCHDOWN, not the frame after it. The integrated position lands up to one full
// step PAST the true contact — at serve pace that is ~0.75 m of travel in 1/60 s, so a