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:
1 parent
c601035eab
commit
047d6fb665
1 file changed
+80
-4
+80
-4
@@ -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
|
||||
|
||||
Reference in new issue
Block a user