Stop the dead for the changeover instead of letting the player wander

Oliver vetoed the free movement between points on sight: he never walks
freely during play, so being able to only between points reads as the game
breaking, not as a fix. Reverted to 'rally' only, and the rule is written into
the settled-feel list so it doesn't get re-invented.

The frozen changeover is answered his other way instead — the dead wait too.
`held` in updateZombies stops their advance, their steering and the eat check
for the breather; they still bleed and burn, they just stand there, because
the umpire called time and it is still a tennis match. The path where this
actually bit: losing a duel scheduled on a changeover beat spawns five fresh
shamblers and THEN starts five seconds you have no agency in.

The stuck pose was never about movement — 'run' is the one pose expirePose
doesn't expire, and runT stops advancing outside the block, so a beat that
began mid-stride welded him to a single frame. Reset it and it's fixed for
every phase.

And he could walk off the screen in ordinary play, not just during the beat I
had added: P_BOUNDS.x is 6.5 m, portrait shows about ±4 m at the baseline.
The xVis clamp already existed but only applied during auto-fire; it now
always applies, floored at the sideline so every legal ball stays catchable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5 committed 2026-07-30 10:07:38 +02:00
1 parent e00886f57e
commit 79fb43f60d
4 files changed
+68 -23

No files matched your search

+14 -6
View File
@@ -814,11 +814,11 @@ export function update(dt) {
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)
// 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')) {
// 'rally' ONLY, deliberately. Letting the player walk during the between-point beats was tried
// and vetoed on feel: you never walk freely during play, so suddenly being able to between points
// reads as the game breaking, not as a fix. The frozen-changeover complaint is answered by
// holding the DEAD instead (see updateZombies), and the stuck pose by the reset below.
if (G.phase === 'rally') {
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 };
@@ -854,7 +854,11 @@ export function update(dt) {
assist = dx;
}
}
const xLim = mgMode ? xVis : P_BOUNDS.x; // auto-fire keeps even keyboard players on screen
// Stay ON SCREEN — always, not just in auto-fire. P_BOUNDS.x is 6.5 m, but at the baseline in
// portrait the camera only shows about ±4 m either side of the centre line, so walking to the
// bound put the player clean off the edge (x = -6.5 projects to screen -61 of 270). Floored at
// the sideline so every legal tennis ball stays catchable — the court is never out of bounds.
const xLim = Math.max(xVis, COURT.W / 2);
P.x = Math.max(-xLim, Math.min(xLim, P.x));
P.z = Math.max(P_BOUNDS.zMin, Math.min(P_BOUNDS.zMax, P.z));
const moving = mx || mz || assist !== 0;
@@ -863,6 +867,10 @@ export function update(dt) {
if (mx) P.facing = mx > 0 ? 1 : -1;
else if (Math.abs(assist) > 0.25) P.facing = assist > 0 ? 1 : -1; // hysteresis: small corrections don't flip facing
}
// ...and out of 'rally' he STANDS. 'run' is the one pose nothing else expires (expirePose only
// handles swing/serve), and P.runT stops advancing outside the block, so a beat that began
// mid-stride left him welded to a single run frame — the "stuck mid-animation" report.
else if (P.pose === 'run') { P.pose = 'idle'; P.poseT = 0; }
// splatter: you START at the baseline but you're free to advance up-court (bounded at the net by
// P_BOUNDS) — hunt the shamblers down, especially when mopping up a won game. Auto-assist still
// recovers you toward the baseline between shots on touch; the duel is plain tennis as ever.