Write down the leftovers and what four playtest rounds taught

Adds two sections to HANDOFF. "How to work on this" records the meta-lesson:
every single "this feels odd" note Oliver gave turned out to be a real defect
rather than a tuning preference — seven for seven — so the rule is to measure
before touching a tuning value. It also records that screenshots are not a
verification tool at phone scale (small pixel art is unreadable, and a silent
sprite fallback renders a wiring bug as working code), that drawImage
instrumentation is what settles those, and that a scripted str.replace which
matches nothing is a silent no-op that must be asserted.

"OPEN — leftovers" lists the ten known gaps in priority order: RES 4 unverified
under load on a real phone, the horde melee fix unplayed, the player idle frames
upscaled rather than re-sliced, asymmetric boss wound stages, only three boss
designs, landscape drifting untended, cards without an entrance, the deliberate
chapter-skip, parked step 6, and one audio line to confirm.

Also corrects the module map: there are three runtime-only import cycles now,
not two — state<->zombies joined when sendBoy started reading gore.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5 committed 2026-07-30 18:57:41 +02:00
1 parent c601035eab
commit 047d6fb665
1 file changed
+80 -4
+80 -4
View File
@@ -5,9 +5,10 @@ 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, after Oliver's FOURTH playtest round (viewport, horde survivability). All playtest
rounds are closed at the bottom. Step 6 (crowd turning, ball-boy catcher) stays PARKED by his
decision. **Awaiting his next phone playtest.**
Last updated 2026-07-30, after Oliver's fourth playtest round. **Start with "How to work on this"
and "OPEN — leftovers"** (both just above the CLOSED sections) — the first tells you how his feedback
should be handled, the second is everything known to be outstanding. All four playtest rounds are
closed below them, newest first. **Awaiting his next phone playtest.**
## What this is
SNES-style pseudo-3D tennis (Super Tennis reference) that transitions into a zombie splatter game
@@ -31,7 +32,10 @@ transition + `splatPoint` + ball-boy routines · `zombies.js` zombie/gore/partic
`updateZombies` · `render.js` camera/palette-lerp/sprites/all drawing · `input.js` keys/touch/flick ·
`audio.js` sfx + recorded umpire (`assets/audio/*`, macOS `say -v Daniel`) · `panel.js` the T panel ·
`main.js` boot, loop, umpire-tap cheat, `window.T` test surface.
Two runtime-only import cycles (documented in headers): input↔gameplay, zombies↔gameplay.
THREE runtime-only import cycles (documented in headers): input↔gameplay, zombies↔gameplay,
and state↔zombies (`sendBoy` reads `gore` to decide whether a mopping round is worth walking out for).
All three are safe for the same reason: the imported binding is only ever touched inside a function at
runtime, never during module evaluation.
## How the game flows
Tennis → win a game → **transition** (`G.mode='transition'`, timeline on `G.transT`) → **splatter**.
@@ -371,6 +375,78 @@ Known generator quirks, all still true:
---
## How to work on this — the meta-lesson from four playtest rounds
**Every single "this feels odd" note Oliver gave this session turned out to be a real defect, not
taste.** Not one was a tuning preference. The score so far:
| what he said | what it actually was |
|---|---|
| "the racket zombie doesn't seem harmable, but that could just be a feeling" | 3 of 5 weapons silently no-oped on it |
| "I could not return a single ball" | the parry was unreachable — the tap only became a swing in `rally` |
| "the first boss is way too easy" | a flame ball re-wounded it every frame; one killed a 16 hp boss |
| "the ball is not yet at the player but he is already hit" | contact fired 0.68 m early (weighted depth) |
| "one of the endboss sprites is used for a normal zombie" | a case-insensitive filename collision |
| "everything is a tad too small" | near sprites were being upscaled 1.5x from a 112px source |
| "I die all the time at the game-20 horde" | the horde scales, the panic button was a flat 1 s |
So: **when he reports a feel problem, go measure it before touching a tuning value.** The measurement
is usually cheap (project both objects to screen space at the moment of the event; count device pixels
against source pixels; print the schedule for 45 games) and it has found a bug every time.
**Verify with instrumentation, not with screenshots.** Small pixel art at phone scale is genuinely
unreadable in a screenshot — during the boss-design work I twice concluded from screenshots that the
sprites were fine when they were all silently falling back to design 1. What settled it in one call:
```js
const o = CanvasRenderingContext2D.prototype.drawImage;
CanvasRenderingContext2D.prototype.drawImage = function (img, ...a) { log(img.src); return o.call(this, img, ...a); };
```
Same idea elsewhere: project to screen coords and compare numbers, don't eyeball. A silent fallback
(`SPR[wanted] ? wanted : <default>`) is convenient in production and *poisonous* while debugging,
because a wiring bug renders as perfectly working code.
**Assert your scripted edits matched.** A `str.replace` that finds nothing is a silent no-op. One of
those left `drawZombie` on hardcoded sprite names while everything else moved to the `BOSS_ART` table,
and the fallback above hid it completely. Every scripted edit in this repo should `assert a in s`.
---
# OPEN — leftovers, in rough priority order
Nothing here is broken; these are known gaps and unverified risks.
1. **`RES` 4 is unverified on a real phone under load.** It nearly doubled the fill rate (810 -> 1080
device px across). Oliver has not reported stutter, but he also has not played a late horde since.
**If the game ever stutters, `RES` back to 3 is the first move** — not reducing zombie counts.
2. **The game-20 horde fix is unplayed.** The crowd-scaled swish (1.00 s at one body -> 0.22 s at
fourteen) is measured but not yet felt. If it now trivialises hordes, `meleeCoolMin` is the knob.
3. **`p_idle1`/`p_idle2` and most `o_*` frames are 2x-upscaled, not re-sliced.** They are on the right
scale but gained no detail in the remaster, and they are the two most-seen poses in the game.
Fix by hand-slicing `_sheet_player2/3` and `_sheet_opp2/3` at `IDLE_TARGET_H` 224 rather than going
through the signature matcher, which assigned those frames elsewhere.
4. **Boss designs are asymmetric.** Design 1 (GROUNDSKEEPER) has 3 wound stages and 4 throw frames;
designs 2 (LINE JUDGE) and 3 (CHAIR UMPIRE) have 1 wound skin and 2 throw frames each. Legal — the
`BOSS_ART` indices are clamped — but a chapter-2 boss degrades in one visible step instead of three.
5. **Only 3 boss designs, cycling.** Chapter 4 repeats the groundskeeper at 43 hp and 3.67 m. A fourth
design is a sheet plus one row in `BOSS_ART`.
6. **Landscape is under-maintained.** The readability pass (camZ/camY/camF/horizon), the adaptive
height and the safe-area work are all portrait-only; landscape still runs the old camera and a fixed
480x270. Portrait is the stated primary target, but landscape has drifted and is untested.
7. **`drawCard` (the chapter/set cards) still cuts in** with no entrance, while modals swoosh
(`TUNE.modalIn`). Offered and not yet requested.
8. **A chapter can be skipped.** `G.bossDue` takes the max, so going a long stretch without ever
clearing the court means you face the current chapter, not a backlog. Deliberate; noted in case it
ever reads as a bug.
9. **Step 6 stays PARKED** by Oliver's decision: the crowd turning into zombies (spawning from the
stands — attacks from behind, high spectacle, `x_crowd` sheets already exist) and the ball-boy zombie
that catches your shots. Recorded so the ideas are not lost, not as a to-do.
10. **`ump_no_deal` does not exist.** Oliver asked for its removal; `ump_no_appeal` was removed as the
closest match. Worth one confirmation that no other line is still unwanted.
---
# CLOSED — Oliver's fourth playtest (2026-07-30)
### The game did not use the full height of his iPhone