The latch is stale-proof by construction, the cup divisor honest, the trail visible
Oliver's tournament freeze was the stale latch by a THIRD route: matchFallWillPlay kept the deathmatch /4 while cup matchPoint divides by 4*need, so it promised a fall on a no-crossing point — whose synchronous matchReady left the after-set latch scoping a dead rally. No more ordering arguments: the latch is set BEFORE the resolve, the swallow exempts the in-progress resolution via G.matchEarly, and every path that arms a rally clears it in matchReady. willFall now mirrors the cup divisor exactly and knows a mid-round game keeps its body. Regression test drives the full QF sequence (no-crossing points, the fall, the mid-round game, game two). And the two 'not really' reports: the trail's 0.26 alpha was tuned on the gore palette and vanished on the bright lawn — 0.38 now, gate at 13 m/s; oppBright 1.18 -> 1.3. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f9b26e5380
commit
129a001109
6 files changed
+67
-21
No files matched your search
+19
-14
@@ -1,6 +1,6 @@
|
||||
// Game logic: ballistics, swings, serves, rules/scoring, AI, and the per-frame update.
|
||||
|
||||
import { TUNE, UI, bossArt, APPLAUSE_T, EATEN_BURST, EATEN_SUMMARY, COURT, CONTACT, VOLLEY, ZONE_DEPTH, HOME, P_BOUNDS, TOSS, SHOT_DEPTH, POSE_TIME, BOUNCE_IDLE, zombieH, Z_CHEST , WEAPONS, RACK_MAX , BARREL_WEAPONS, racketStage } from './config.js';
|
||||
import { TUNE, UI, bossArt, APPLAUSE_T, EATEN_BURST, EATEN_SUMMARY, COURT, CONTACT, VOLLEY, ZONE_DEPTH, HOME, P_BOUNDS, TOSS, SHOT_DEPTH, POSE_TIME, BOUNCE_IDLE, zombieH, Z_CHEST , WEAPONS, RACK_MAX , BARREL_WEAPONS, racketStage, CUP_ROUNDS } from './config.js';
|
||||
import { P, O, ball, score, crowd, G, court, tour, other, deuceSide, setupPoint, sendBoy, resetState } from './state.js';
|
||||
import { audio, pock, swish, whoosh, applause, voice, sfx, aww, zombieMusic, zombieDuck, menuDuck, splat, shriek, boom } from './audio.js';
|
||||
import { axisX, axisZ, takeFlickAim, touchPX, resetInput } from './input.js';
|
||||
@@ -936,8 +936,14 @@ export function inServeBox(x, z) {
|
||||
function matchFallWillPlay() {
|
||||
const rz = zombies.find(z => z.variant === 'r');
|
||||
if (!rz) return false;
|
||||
if (score.pts[0] + 1 >= 4 && score.pts[0] + 1 - score.pts[1] >= 2) return true; // the kill
|
||||
const after = Math.max(1, rz.hp - Math.ceil(rz.hpMax / 4));
|
||||
const cup = G.match.cup;
|
||||
const gameWin = score.pts[0] + 1 >= 4 && score.pts[0] + 1 - score.pts[1] >= 2;
|
||||
// a game win is only THE KILL when it ends the round — a cup mid-round game keeps the body
|
||||
if (gameWin) return !cup || cup.g + 1 >= CUP_ROUNDS[cup.round].need;
|
||||
// the same divisor matchPoint will use — the first version kept the deathmatch /4 and in the
|
||||
// cup it promised falls that matchPoint then didn't deliver (a route to the stale-latch lock)
|
||||
const div = 4 * ((cup && CUP_ROUNDS[cup.round].need) || 1);
|
||||
const after = Math.max(1, rz.hp - Math.ceil(rz.hpMax / div));
|
||||
return racketStage({ hp: after, hpMax: rz.hpMax }) !== racketStage(rz); // a limb
|
||||
}
|
||||
|
||||
@@ -1007,20 +1013,19 @@ function onBounce() {
|
||||
// - only when a FALL (or the game-winning kill) will actually play — an ordinary point
|
||||
// keeps its ordinary bounce-2 ending, or the ball would vanish mid-roll for nothing;
|
||||
// - conservative reach test — a borderline ball waits for the ordinary call.
|
||||
// THE LATCH ORDER IS LOAD-BEARING, both ways. Set BEFORE the resolve, the hook's own
|
||||
// decided-check swallows the resolution being made. Set AFTER, it must never outlive its
|
||||
// rally — the first shipped version latched after a NO-FALL point, whose matchPoint had
|
||||
// already re-armed the next rally (matchReady runs synchronously there), and the stale
|
||||
// latch swallowed the whole match: "the fall never happens, the game does not continue,
|
||||
// the ball is gone." matchFallWillPlay is what makes after-set safe: both paths it admits
|
||||
// (the collapse, the game-winning kill) defer matchReady until their beat finishes, so the
|
||||
// latch always dies with the rally it scoped.
|
||||
// THE LATCH IS SET BEFORE THE RESOLVE AND CLEARED WHEREVER A RALLY ARMS. Version one set
|
||||
// it after and a no-fall point's synchronous matchReady left it stale (the first lock);
|
||||
// version two argued after-set safe because "every admitted path defers matchReady" — and
|
||||
// the cup's mid-round game win promptly broke the argument (the second lock, "like freeze
|
||||
// from before"). No more ordering proofs: the latch goes up BEFORE, the resolution in
|
||||
// progress is exempted via G.matchEarly in resolveInterlude's swallow, and any path that
|
||||
// re-arms play clears it in matchReady. Stale by construction impossible.
|
||||
else if (G.match && !G.match.ending && !G.match.decided && ball.lastHit === 'P'
|
||||
&& matchFallWillPlay() && matchOutOfReach()) {
|
||||
G.matchEarly = true; // startFall leaves the ball rolling
|
||||
G.match.decided = true; // scope: the REMAINING ball events of this rally
|
||||
G.matchEarly = true; // startFall leaves the ball rolling; the swallow exempts us
|
||||
resolveInterlude('P');
|
||||
G.matchEarly = false;
|
||||
if (G.match) G.match.decided = true; // scope: the REMAINING ball events of this rally
|
||||
}
|
||||
} else if (ball.bounces === 2) { ball.active = false; resolveInterlude(ball.lastHit); }
|
||||
return;
|
||||
@@ -1784,10 +1789,10 @@ export function update(dt) {
|
||||
const arz = zombies.find(z => z.variant === 'r');
|
||||
const pred = arz && predictLanding();
|
||||
if (pred && inCourt(pred.x, pred.z) && matchOutOfReachFlight(arz, pred)) {
|
||||
G.match.decided = true; // same latch discipline as the bounce-1 hook below
|
||||
G.matchEarly = true;
|
||||
resolveInterlude('P');
|
||||
G.matchEarly = false;
|
||||
if (G.match) G.match.decided = true;
|
||||
}
|
||||
}
|
||||
if (ball.y <= 0 && ball.vy < 0) {
|
||||
|
||||
Reference in new issue
Block a user