Let the player walk between points, and fix seven retest gripes
All eight issues from Oliver's 35-game retest. The freeze (1 and 6 — one root cause): the movement block, pose update included, lived inside `if (G.phase === 'rally')`, so every 'over' beat and every pretoss stranded the player mid-animation while the zombies walked on. It now also runs whenever splatter is in any phase but 'toss' — 'toss' is the aim, and the thumb is busy. Tennis keeps its old gating. The rest: - the mop boy is waved off the sudden-death duel and barred for its duration - triple-serve clones inherit the serve's topspin gravity, so the wings stop floating while the centre ball dives - "Time." belongs to the changeover's own countdown, not to whatever beat happened to expire while the breather ran - the tutorial illustration is a finger pressing and pulling back now, not a curved arrow that read as a scroll - aiming holds the pretoss stance; the smash plays when the shot fires - GAME N sits on the scoreboard permanently HANDOFF records all eight as closed with what the fix was, plus two ways a soak rig lies to you and why the CPU-return benchmark can't be reproduced from the number alone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8dca9b6d45
commit
e00886f57e
7 files changed
+89
-52
No files matched your search
+19
-6
@@ -196,7 +196,10 @@ function hitServe(sv, quality, aim = null) {
|
||||
G.triples--;
|
||||
for (const deg of [-6, 6]) {
|
||||
const r = deg * Math.PI / 180, c = Math.cos(r), sn = Math.sin(r);
|
||||
extras.push({ x: ball.x, y: ball.y, z: ball.z, vy: ball.vy, pow: quality, t: 2.5,
|
||||
// g: ball.g — a serve carries up to 2.3x gravity in topspin; clones integrated at flat
|
||||
// TUNE.gravity floated long while the centre ball dived, which is the "wings lob off at
|
||||
// the wrong height" report. They are the SAME shot, fanned.
|
||||
extras.push({ x: ball.x, y: ball.y, z: ball.z, vy: ball.vy, pow: quality, t: 2.5, g: ball.g,
|
||||
vx: ball.vx * c - ball.vz * sn, vz: ball.vx * sn + ball.vz * c });
|
||||
}
|
||||
}
|
||||
@@ -335,7 +338,7 @@ export function proceedAfterClear() {
|
||||
// changeover (after odd games, as the rules demand): a forced breather — no spawns,
|
||||
// no serving, the boys mop. Zombies already on court keep walking; it's calm, not safe.
|
||||
function startChangeover() {
|
||||
G.changeover = true;
|
||||
G.changeover = TUNE.changeoverT; // seconds left, not a flag: "Time." is called when THIS runs out
|
||||
G.phase = 'over'; G.overT = TUNE.changeoverT;
|
||||
// out with the mop, across the near court where the gore is — NOT to the ball: a dead ball
|
||||
// parked off the field (sendBoy's z guard) meant no boy came out at all, and the breather is
|
||||
@@ -387,6 +390,7 @@ export function startDuel() {
|
||||
G.weapon = 0; // tennis rules for the duel: plain balls, no tins
|
||||
G.mgT = 0; pellets.length = 0; // ...and a burst in progress is over. The umpire has restored order.
|
||||
G.interlude = { introT: 2.4 }; // the intro beat: serving blocked while the umpire announces
|
||||
G.boy = null; // ...and the mop boy is waved off the court (sendBoy is barred for the duration)
|
||||
ball.active = false; setupPoint();
|
||||
zombieDuck(true); // fade the zombie music out for the 1:1 duel
|
||||
voice('play_resume', 0.2); voice('sudden_death', 2.4);
|
||||
@@ -683,7 +687,7 @@ export function dragServe(aim) {
|
||||
// rally clock. This is what lets the drag mean ONE thing at all times: a second gesture that
|
||||
// walked the player around between shots was the confusing part, not the fire rate.
|
||||
if (ball.active && G.phase !== 'toss') // the old ball flies on as a ballistic clone
|
||||
extras.push({ x: ball.x, y: ball.y, z: ball.z, vx: ball.vx, vy: ball.vy, vz: ball.vz, pow: ball.pow || 0.5, t: 2.5 });
|
||||
extras.push({ x: ball.x, y: ball.y, z: ball.z, vx: ball.vx, vy: ball.vy, vz: ball.vz, pow: ball.pow || 0.5, t: 2.5, g: ball.g });
|
||||
ball.active = true; ball.collected = false; ball.bounces = 0; ball.firstLand = null;
|
||||
ball.x = P.x + 0.35; ball.z = P.z; ball.y = 2.2; ball.g = TUNE.gravity;
|
||||
hitServe(P, pullQuality(aim.mag), aim); // a full pull is quality 1.0 — PERFECT, hitstop and all
|
||||
@@ -797,14 +801,24 @@ export function update(dt) {
|
||||
if (G.modal) { G.idleT += dt; return; }
|
||||
crowd.cheer = Math.max(0, crowd.cheer - dt);
|
||||
G.idleT += dt;
|
||||
// "Time." belongs to the changeover alone. It used to ride the generic 'over' expiry, so any
|
||||
// other beat that overwrote overT while the breather ran (a melee kill taking a game, say)
|
||||
// had the umpire calling time on a pause that was never a changeover.
|
||||
if (G.changeover > 0 && (G.changeover -= dt) <= 0) { G.changeover = false; voice('time', 0, 0.95); }
|
||||
const w = BOUNCE_IDLE.tempo;
|
||||
// idle in the serve stance and not touching anything: after a beat, show the pull gesture
|
||||
if (G.phase === 'pretoss' && !G.aim) G.idleHintT += dt; else G.idleHintT = 0;
|
||||
if (G.phase === 'pretoss' && Math.sin(G.idleT * w) * Math.sin((G.idleT - dt) * w) < 0) pock(0.12, 420); // ball bounce
|
||||
if (G.phase === 'toss') { const sv = score.server === 'P' ? P : O; sv.pose = 'serve'; sv.poseT = 0; }
|
||||
// the toss/smash pose belongs to the SHOT, not to the aim: while the slingshot is drawn (G.aim)
|
||||
// the server stays in the pretoss stance and only snaps into 'serve' when hitServe fires.
|
||||
if (G.phase === 'toss' && !G.aim) { const sv = score.server === 'P' ? P : O; sv.pose = 'serve'; sv.poseT = 0; }
|
||||
|
||||
// --- player movement (near half only, generous margins)
|
||||
if (G.phase === 'rally') {
|
||||
// In splatter it also runs between points ('over' — game beats, changeovers, set cards — and
|
||||
// 'pretoss'): the zombies never stop walking, so gating movement on 'rally' froze the player
|
||||
// mid-stride with the dead closing in, and left him stuck in whatever pose he held. 'toss' is
|
||||
// the one exception — that's the aim, and the thumb is busy drawing the bow.
|
||||
if (G.phase === 'rally' || (G.mode === 'splatter' && G.phase !== 'toss')) {
|
||||
if (G.mode === 'tennis' && ball.active && ball.lastHit === 'O') { // closest approach, for never-swung passes
|
||||
const d = Math.hypot(ball.x - P.x, (ball.z - P.z) * 0.8);
|
||||
if (!G.rallyNear || d < G.rallyNear.d) G.rallyNear = { d, y: ball.y, bz: ball.z, px: P.x, pz: P.z };
|
||||
@@ -1003,7 +1017,6 @@ export function update(dt) {
|
||||
onDismiss: startHorde });
|
||||
return;
|
||||
}
|
||||
if (G.changeover) { G.changeover = false; voice('time', 0, 0.95); } // "Time." — play resumes
|
||||
// auto-fire modes must NEVER re-enter the serve loop: an 'over' beat expiring into
|
||||
// setupPoint() -> pretoss left the player locked in the serve posture while the gun
|
||||
// fired (no movement outside 'rally', no serving during auto-fire). Back to rally.
|
||||
|
||||
Reference in new issue
Block a user