Tighten the returns, give each chapter its own boss, and let the umpire talk
Five reports from Oliver's second playtest. The parry was too generous — at 6 m of arm with a 0.34 s window the shot flew into reach on its own, so any tap once it was vaguely close worked. 3.4 m / 0.2 s / 1.7 m reach now: a tap at 4.5 m is rejected and costs blood, 3.2 m and in returns. The boss telegraphs in 0.5 s against the machine's 0.9 and throws harder, because a boss should be worse than furniture. His three balls were decoration: fanned ±1.9 m across space, so only the middle one could ever reach you. They are a timed salvo now — three shots 0.42 s apart, each aimed at where you are at that moment. hurtGrace still caps a whole volley at one point of damage, which is what it is for. Every chapter was the same boss. Three designs cycling now, each with its own sheet, stride, throw pose, wrecked skin and wrecked-throw skin: the GROUNDSKEEPER, the LINE JUDGE, the CHAIR UMPIRE with his chair strapped on. The chapter card names them. Designs are data in BOSS_ART, so a fourth is a sheet plus a row. Healing announces itself with a first-aid tin and a red cross instead of a 1.4 s flash. And 14 new umpire lines: ten sarcastic asides on a jittered timer, four hooked to specific beats. He still has not acknowledged that the match became a massacre, so he keeps officiating it. Two traps found and written into the pipeline notes. Sprite loads now carry ?v=SPRV, because python http.server sends no cache headers and the browser caches 404s — a name added before its PNG exists stays permanently missing for that browser. And a scripted replace that silently didn't match left drawZombie on the old hardcoded boss names while everything else moved to BOSS_ART; the built-in fallback hid it perfectly, all four designs rendering as design 1 with no error. Assert your rewiring edits matched. Tennis untouched: 30 tapped points, 50 hits / 0 whiffs. 45-game soak clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
295470b73c
commit
8bc57e9358
34 files changed
+180
-40
No files matched your search
+73
-6
@@ -5,11 +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, after Oliver's first playtest of the boss/ranged build. All six things he
|
||||
reported are fixed (see CLOSED at the bottom) — including the one that made the whole new mechanic
|
||||
unusable: **the parry was unreachable**, because a tap only ever became a swing in `phase === 'rally'`
|
||||
and splatter sits in `pretoss` almost all the time. 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 SECOND playtest of the boss/ranged build. Both playtest
|
||||
rounds are closed at the bottom. Step 6 (crowd turning, ball-boy catcher) stays PARKED by his
|
||||
decision. **Awaiting his next phone playtest.**
|
||||
|
||||
## What this is
|
||||
SNES-style pseudo-3D tennis (Super Tennis reference) that transitions into a zombie splatter game
|
||||
@@ -144,6 +142,14 @@ zombie (now an occasional opponent, `TUNE.racketEvery`, not the every-game fixtu
|
||||
layer on top"*. His health reads as `bleedPlayer()`'s live drip plus the screen-edge wash on impact,
|
||||
and nothing else. This is the same settled rule that governs the zombies — blood is never painted
|
||||
into a sprite — and a `drawPlayerWounds` overlay broke it.
|
||||
- **Boss designs are DATA: `BOSS_ART` in config.js, one row per chapter, cycling.** Each row is
|
||||
`{ name, walk: [2], wounds: [...], throws: [...] }`; `spawnChapterBoss` stamps the row onto the body
|
||||
as `zb.art` and `drawZombie` reads only that. Damage picks a wound stage as a fraction of `hpMax`
|
||||
spread over however many stages the design HAS, and `throws` is index-clamped, so a design with
|
||||
fewer throw frames than wound stages just reuses its last one. Adding a boss is a sheet plus a row —
|
||||
never a change to drawZombie. Missing/unloaded sprites degrade to design 1 rather than vanishing,
|
||||
which is convenient and also means **a wiring bug looks exactly like working code** (see pipeline
|
||||
note 7).
|
||||
- **`UI` in config.js is the accent palette** (hot/warm/pick/cream/dim/bad/ball). Every HUD colour
|
||||
goes through it. Gore reds are deliberately NOT in it — the splatter stays loud.
|
||||
- **`F(size, bold)` in render.js builds every canvas font string.** Bare `monospace` resolves to
|
||||
@@ -249,7 +255,22 @@ Clear the intro first: `document.getElementById('intro')?.remove()`.
|
||||
4. Standalone art (logo, popup panels) uses `assets/_key.py` instead: chroma-key + crop to the
|
||||
content bbox + optional downscale, no frame splitting, no forced height.
|
||||
5. Register new names in `render.js`'s `SPR` preload array.
|
||||
6. **NEVER let two sprite names differ only by CASE.** macOS is case-insensitive: writing
|
||||
6. **New sprite not showing up? Bump `SPRV` in render.js.** Sprite loads go through
|
||||
`assets/<n>.png?v=SPRV` for the same reason index.html versions main.js: python http.server sends
|
||||
no cache headers, so the browser caches sprite responses heuristically — **including 404s**. Add a
|
||||
name to the `SPR` list before its PNG exists, load the page once, and that browser treats the
|
||||
sprite as permanently missing while `drawZombie` silently falls back to another design. Bump SPRV
|
||||
alongside `main.js?v=N`.
|
||||
7. **When a scripted `str.replace` edit is meant to rewire logic, ASSERT it matched.** A silent
|
||||
no-op replace left `drawZombie` on its old hardcoded boss names while everything else had moved to
|
||||
the BOSS_ART table, and the built-in fallback then hid the failure perfectly — all four boss
|
||||
designs rendered as design 1 and nothing errored. Cost a full debugging round. `drawImage`
|
||||
instrumentation is what finally settled it:
|
||||
```js
|
||||
const o = CanvasRenderingContext2D.prototype.drawImage;
|
||||
CanvasRenderingContext2D.prototype.drawImage = function (img, ...a) { log(img.src); return o.call(this, img, ...a); };
|
||||
```
|
||||
8. **NEVER let two sprite names differ only by CASE.** macOS is case-insensitive: writing
|
||||
`assets/z_B1.png` for the chapter boss silently OVERWROTE the 'b' shambler's `assets/z_b1.png`
|
||||
(and `z_B2` ate `z_b2`), so a 3-metre boss started showing up in the shambler crowd and `git`
|
||||
reported the shambler frames as "modified" rather than reporting a new file. `ls` shows only the
|
||||
@@ -292,6 +313,52 @@ Known generator quirks, all still true:
|
||||
|
||||
---
|
||||
|
||||
# CLOSED — Oliver's second boss/ranged playtest (2026-07-30)
|
||||
|
||||
### The parry was too generous
|
||||
*"I can tap pretty early and it returns the ball."* At `parryArm` 6.0 / window 0.34 / reach 2.4 the
|
||||
shot flew INTO reach during the window, so any tap once it was vaguely close worked. Now 3.4 / 0.2 /
|
||||
1.7 — verified: a tap at 6.0 m or 4.5 m is rejected and costs blood, 3.2 m and closer returns.
|
||||
|
||||
### The boss's three balls were decoration
|
||||
*"he throws 3 of them from which only ever one can reach me. that does not make sense."* Correct — they
|
||||
were fanned +-1.9 m across space, so only the middle one was ever aimed at the player. They are a
|
||||
**timed salvo** now: three shots `TUNE.bossSalvoGap` apart, each aimed at where you are AT THAT MOMENT
|
||||
with only a hand's width of scatter. Verified firing at 0.02 / 0.45 / 0.88 s, all within 0.3 m of the
|
||||
player. Note `hurtGrace` (1.1 s) still caps a whole volley at one point of damage by design.
|
||||
Reaction time also tightened: bosses get `bossWind` 0.5 s of telegraph against the machine's 0.9, and
|
||||
throw at `bossShotSpeed` 15.5 m/s against its 13.
|
||||
|
||||
### Every chapter was the same boss
|
||||
Right — chapters only differed in hp and scale. There are three designs now, cycling, each with its
|
||||
own sheet, a 2-frame stride, a throw pose, a wrecked skin and a wrecked-throw skin:
|
||||
**THE GROUNDSKEEPER** (ch 1/4/7…, blazer + ball hopper, and the only one with three wound stages),
|
||||
**THE LINE JUDGE** (ch 2/5/…, fat, striped officials' polo, cap, ball apron),
|
||||
**THE CHAIR UMPIRE** (ch 3/6/…, tall and gaunt, panama hat, the umpire's chair strapped to his back).
|
||||
The chapter card names the design. Chapter 4 repeats design 1 but at 43 hp and 3.67 m.
|
||||
**The chair-umpire sheet came back on a WHITE background**, and it wears a cream blazer and a white
|
||||
hat — a colour key would have eaten both. Fixed by flood-filling the background inward from the image
|
||||
BORDER instead of keying by colour; that script is in this session's git history and is the right tool
|
||||
whenever a generated sheet's background colour also appears on the character.
|
||||
|
||||
### Healing needed to announce itself
|
||||
Winning a duel while wounded now opens a real modal — `x_cross` first-aid tin, "WOUNDS TREATED / THE
|
||||
TRAINER PATCHED YOU UP / N OF M BACK" — plus an umpire line. It was a 1.4 s flash before.
|
||||
|
||||
### The umpire needed to comment on the new reality
|
||||
14 new recorded lines (`say -v Daniel -r 168` -> `afconvert`), all `ump_*`. Ten are a rotating pool of
|
||||
sarcastic asides fired on a jittered `TUNE.asideEvery` timer whenever nothing else owns the audio
|
||||
(`umpireAside` in gameplay.js); four are hooked to specific beats — a machine arriving, a boss
|
||||
arriving, the trainer, and taking a hit. The joke is that he has still never acknowledged that the
|
||||
match became a massacre, so he keeps officiating it: *"Code violation. Unsportsmanlike conduct.
|
||||
Eating."*
|
||||
|
||||
### Regression net
|
||||
30 tapped tennis points = 50 hits / 0 whiffs. 45-game soak: no exceptions, `mgT` drains, gore 40 /
|
||||
piles 30 / bodies capped at 34.
|
||||
|
||||
---
|
||||
|
||||
# CLOSED — Oliver's first boss/ranged playtest (2026-07-30)
|
||||
|
||||
Six reports, all fixed. Two were serious.
|
||||
|
||||
Reference in new issue
Block a user