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:
1 parent
1e601ca33d
commit
098aefeb63
1 file changed
+40
-2
+40
-2
@@ -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
|
||||
|
||||
Reference in new issue
Block a user