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

+43 -13
View File
@@ -5,9 +5,9 @@ everything they specified is shipped, and what still matters from them is record
long history, `~/.claude/projects/.../memory/splatter-tennis-history.md` is auto-loaded; for the
detail of any individual change, read the git log.
Last updated 2026-07-30. All eight issues from Oliver's 35-game retest are fixed and verified in the
browser; they are recorded at the bottom as **CLOSED** with what the fix was, because each one is a
trap worth not re-digging. **Awaiting Oliver's next phone playtest.**
Last updated 2026-07-30 after Oliver playtested the fixes. All eight retest issues are closed and
recorded at the bottom with what the fix was, because each one is a trap worth not re-digging.
Issue 1's first fix was **vetoed on feel and replaced** — read that entry before touching movement.
## What this is
SNES-style pseudo-3D tennis (Super Tennis reference) that transitions into a zombie splatter game
@@ -102,6 +102,15 @@ zombie. Getting caught = `G.mode='eaten'` = permadeath, and the run summary is t
nearer one still wins, so a straight-ahead lane still eats the nearest body first.
`aimTie: 1` restores the old "nearest along the lane always wins" for A/B.
- `TUNE.fps` 60 — 30 was vetoed on feel.
- **The player only walks during `rally`.** Never between points. Letting him walk during the 'over'
beats and `pretoss` was shipped once and vetoed immediately: *"I basically can never walk around
freely"* — so suddenly being able to, only between points, reads as the game breaking rather than
as a fix. Frozen-with-danger-approaching is fixed by holding the DEAD instead (`held` in
`updateZombies`), never by handing back movement.
- **The player must always be ON SCREEN.** `P_BOUNDS.x` is 6.5 m but portrait only shows about
±4 m either side of the centre line at the baseline, so the walk bound alone let him leave the
frame entirely. `xLim = Math.max(xVis, COURT.W / 2)` — the camera limit, floored at the sideline so
every legal ball stays catchable.
## Verification recipe (the regression net)
Drive the game deterministically via `window.T` with `javascript_tool`. The Browser-pane eval
@@ -190,16 +199,37 @@ Kept because each one is a trap worth not re-digging. Every fix was verified in
`window.T`; the numbers quoted are from those runs.
### 1 + 6. Player frozen (and stuck mid-animation) during the between-game pauses — was HIGH
Same root cause, one fix. The whole movement block — including `P.pose = moving ? 'run' : 'idle'` —
lived inside `if (G.phase === 'rally')`, so every 'over' beat (changeover, game beat, set card) and
every `pretoss` froze the player while `updateZombies` ran on. That `pretoss` case is issue 6:
`endHorde` calls `setupPoint()`, which hands back to `pretoss`.
The block now also runs whenever `G.mode === 'splatter' && G.phase !== 'toss'`. `'toss'` stays frozen
on purpose — that is the aim, and the thumb is busy drawing the bow. **Tennis is deliberately
untouched** (verified: a keyboard-held ArrowLeft moves the player 0.00 m during a tennis 'over',
−2.70 m during a splatter one).
Oliver's other option — stopping the zombies for the breather — was NOT taken. `startChangeover`'s
"it's calm, not safe" is still the design; being able to walk was the actual complaint.
**Two fixes: the first was wrong.** The movement block — pose update included — lived inside
`if (G.phase === 'rally')`, so every 'over' beat and every `pretoss` left the player standing while
zombies walked at him. The `pretoss` case is issue 6: `endHorde` calls `setupPoint()`.
The first attempt widened the block to all of splatter except `'toss'`. Oliver vetoed it on sight:
*"the mechanic where I suddenly can move freely around feels broken.. I basically can never walk
around freely - also I could walk off the screen"*. He is right — free movement for five seconds
between points, in a game where you otherwise only move while a ball is live, reads as a bug. It is
reverted, and the rule is now in Settled feel decisions above.
What actually ships, three separate pieces:
- **The dead wait too.** `held = !!G.changeover` in `updateZombies` stops every zombie's `z` advance,
its x steering, and the eat check for the breather's duration. They still bleed, still burn, still
animate — they just stand there, because the umpire called time and it is still a tennis match.
This is Oliver's own second option and the characterful one (the boys mop, everyone waits).
- **The stuck pose.** `expirePose` only ever expired `swing`/`serve`; `'run'` was the pose nothing
reset, and `P.runT` stops advancing outside the movement block, so a beat that began mid-stride
welded him to one run frame. An `else if (P.pose === 'run')` on the movement block fixes it for
every phase at once.
- **The on-screen clamp** (see Settled feel decisions) — `xVis` already existed but was applied only
in auto-fire modes, so ordinary play could walk clean off the edge. Now it always applies.
**The path where this actually bit** — worth knowing, because a clear court makes it look impossible:
a changeover normally starts only after `G.clearing` has emptied the court, so nothing is walking.
But `resolveInterlude('O')` spawns **five fresh zombies** ("the dead lose their patience") and *then*
hands back to `startChangeover` via `G.afterDuel`. Lose a duel scheduled on a changeover beat and you
get five shamblers plus five seconds with no agency. Verified fixed: they advance 0.000 m across the
breather and resume 0.75 m/s the instant "Time." is called.
If the 5 s still drags on a phone, `TUNE.changeoverT` is the knob — it is now a safe, watchable beat
rather than a dangerous one, so shortening it is purely a pacing call.
### 2. The mop boy walks on during the 1:1 duel — was LOW
`sendBoy` now returns early on `G.interlude`, and `startDuel` sets `G.boy = null`. Note the ordering