Seven verdicts from the test group
- opening CPU beatable in a game or two (softer cap, more whiffs, rubber band) - modals are for calm courts only; live-court news is a card now - the transition howto is gone: a ghost hand teaches the pull, the toss, the apex and the swing, each on the exact struggle it answers - a shot barrel keeps your weapon and glows its slot instead - switching weapons clicks, the live tile is unmistakable - barrels come faster the deeper the run goes - the umpire triple-tap cheat is deleted Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f13cbef28a
commit
a89e8ff419
10 files changed
+242
-79
No files matched your search
+62
-4
@@ -5,7 +5,7 @@ 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-31 (serve wind-up pass). **Start with "How to work on this", "OPEN — leftovers" and "LATER —
|
||||
Last updated 2026-08-08 (testgroup round: popup policy, ghost hand, barrel economy). **Start with "How to work on this", "OPEN — leftovers" and "LATER —
|
||||
shipping it"** (all three just above the CLOSED sections) — the first tells you how Oliver's feedback
|
||||
should be handled, the second is everything known to be outstanding in the game, the third is the
|
||||
demo/store/campaign plan, which is decided but entirely unbuilt. All four playtest rounds are closed
|
||||
@@ -52,7 +52,8 @@ P/O/ball/score + `setupPoint`/`sendBoy` · `gameplay.js` ballistics/swings/serve
|
||||
transition + `splatPoint` + ball-boy routines · `zombies.js` zombie/gore/particle state +
|
||||
`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.
|
||||
`main.js` boot, loop, scoreboard test-mode taps, `window.T` test surface (the umpire-tap cheat is
|
||||
removed — the testteam found it; `T.forceZombies()` is the console replacement).
|
||||
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
|
||||
@@ -80,6 +81,10 @@ zombie (now an occasional opponent, `TUNE.racketEvery`, not the every-game fixtu
|
||||
it, and the `pointerdown` dismisser is registered **only on `pointer: fine`** — on touch,
|
||||
pointerdown fires *before* touchstart, so a shared handler cleared the modal and let the
|
||||
touchstart underneath serve. `G.seen.<key>` gates first-time-only modals.
|
||||
**POLICY (2026-08-08, testgroup round): a modal may only fire on a guaranteed-calm court** (duel
|
||||
intro, boss walk-on, post-duel healing, test mode, the horde's first-ever arrival). Anything that
|
||||
can land on a live court announces as a CARD or a flash — the freeze itself is safe (measured:
|
||||
a body moves 0.000 m behind a modal), but the stolen tap and the broken rhythm cost runs.
|
||||
**`m.in` (0..1 over `TUNE.modalIn`) is the arcade entrance** and the ONE thing that animates behind
|
||||
the freeze — it is ticked inside that same early-return, so it advances while nothing else does.
|
||||
Icon and title fly in from the right (with a two-ghost speed streak), body lines cascade from the
|
||||
@@ -371,6 +376,55 @@ zombie (now an occasional opponent, `TUNE.racketEvery`, not the every-game fixtu
|
||||
`RETURNED`, `DOUBLE KILL`) is a footnote to something you already watched happen. Sharing the loud
|
||||
FACE with the milestone cards is right; sharing their SIZE made every footnote read as a milestone.
|
||||
|
||||
## Session log: the testgroup round — popups, ghost hand, barrel economy (2026-08-08)
|
||||
|
||||
First round of feedback from the small test group (via Oliver), seven items, all landed.
|
||||
|
||||
**The popup policy changed: a modal is for CALM courts only.** The report was "popups block
|
||||
interaction while the game loop continues and the zombies arrive". Measured first, per the
|
||||
meta-lesson: the world genuinely FREEZES behind a modal (a shambler moved 0.000 m over 3 s with one
|
||||
up — the invariant holds). What kills is the CONTROL steal: a popup lands mid-crowd, eats the next
|
||||
tap, and hands the court back with the player's rhythm gone. So every announcement that can land on
|
||||
a live court is a CARD now (non-blocking): the weapon first-sight modals are deleted outright
|
||||
(deliverTin's card and popBarrel's new card carry the one desc line that matters), and the
|
||||
barrel/machine/racket introductions are first-sight cards. The horde popup shows as a modal ONCE
|
||||
per run (it changes the control mode) and every later set starts on the flash alone — re-teaching
|
||||
the same rules at the hottest moment of every set was the worst offender. Still modals: sudden
|
||||
death, the chapter boss, WOUNDS TREATED, test mode — all fire on guaranteed-calm courts.
|
||||
|
||||
**The transition howto modal is gone; a GHOST HAND teaches instead.** "Nobody seems to read this."
|
||||
`drawGhostHand` (render.js) is a drawn pointing hand — canvas paths, UI palette, deliberately not a
|
||||
sprite — with `drawTapRing` for the contact ripple that makes a mimed tap read as a tap. The apron
|
||||
demo's old thumb-dot now rides its strokes, and the transition primes `G.idleHintT` so the pull
|
||||
demo starts miming the moment splatter begins. TENNIS got three lessons on the same hand, each
|
||||
keyed to the exact failure it answers and reset by doing the thing: idle in the serve stance →
|
||||
tap demo on the apron (`drawTapHint`); `G.tossDrops` ≥ 2 → the hand rides the toss and PRESSES AT
|
||||
THE APEX (`drawApexHint` — the timing is the lesson); `G.passStreak` ≥ 2 untouched passes → the
|
||||
hand presses as the ball closes (`drawHitHint`).
|
||||
|
||||
**The opening CPU is beatable in a game or two now.** Testers ground for rounds. `aiFlub` 0.09 →
|
||||
0.18, `aiQualityMax` 0.85 → 0.72, plus a rubber band: every game the CPU takes adds `aiFlubLoss`
|
||||
(0.08) to its whiff chance. Tennis mode only exists before the first transition, so these are
|
||||
purely the opening-match dials (`racket*` covers the duels). **The old "CPU returns ~91.7%"
|
||||
benchmark is deliberately retired by this** — the expected first-swing return rate is now ~82%
|
||||
at 0–0 and falls as the CPU loses games.
|
||||
|
||||
**A shot barrel no longer yanks the new weapon into your hand.** `popBarrel` restores `G.weapon`
|
||||
after `grantWeapon` and pulses the new slot instead (`G.rackGlow`, drawn in drawRack) — "the
|
||||
current weapon should stay active, the new slot should glow briefly". The duel's `deliverTin`
|
||||
still auto-equips; that one lands on a calm court and IS the prize.
|
||||
|
||||
**Switching weapons clicks** (`assets/audio/weapon-click2.mp3`, 0.45 gain — `sfx()` now accepts
|
||||
names carrying their own extension) **and the live rack tile is unmistakable**: brighter well,
|
||||
2 px gold frame.
|
||||
|
||||
**Barrels ramp with the run** (`barrelRamp`): the gap between barrels shrinks by one every 8
|
||||
finished games, floored at every game (g0–7 every 3rd, g8–15 every 2nd, g16+ every game). The
|
||||
late game was "fast tapping until your thumb gives out" once the tins capped; the rack is the
|
||||
strategic answer, so the run keeps feeding it.
|
||||
|
||||
**The umpire triple-tap cheat is deleted** — the test group found it. `T.forceZombies()` remains.
|
||||
|
||||
## Session log: the sidestep, and two type registers (2026-08-03)
|
||||
|
||||
Oliver's post-ship playtest. One blocker and seven polish items; all eight landed.
|
||||
@@ -630,7 +684,7 @@ something looks. `T.pause(false)` runs again.
|
||||
## TEST MODE — triple-tap the scoreboard
|
||||
|
||||
A sandbox for trying new content without playing twenty games to reach it. Three taps on the
|
||||
scoreboard (the same shape as the umpire's triple-tap cheat next to it in main.js) toggles
|
||||
scoreboard (main.js; the umpire's neighbouring triple-tap cheat is deleted) toggles
|
||||
`G.testMode`; three more turns it off, and a SINGLE tap is still the miss-log overlay.
|
||||
|
||||
- a fixed MEDIUM wave: `testZMax` 8 bodies, `testZSpeed` 1.15 m/s, `testSpawnEvery` 1.6 s — it
|
||||
@@ -669,7 +723,11 @@ Clear the intro first: `document.getElementById('intro')?.remove()`.
|
||||
`T.flickTarget`, `T.showModal`/`T.dismissModal`, `T.render`.
|
||||
|
||||
**Benchmarks that must keep holding** (re-run after any physics/aim change):
|
||||
- CPU returns ~11/12 serves (91.7%). Recent 150-sample runs: 90.7%, 89.3%, 90.7%, 88.7% (±2.5%).
|
||||
- **RETIRED 2026-08-08**: the ~91.7% CPU return rate. The opening CPU was deliberately softened
|
||||
(aiFlub 0.18 + aiFlubLoss rubber band, see that session log) — expect ~82% at 0–0, less as it
|
||||
drops games. The differential method below is still how to prove "no unintended regression".
|
||||
The old record, for the archaeology:
|
||||
CPU returns ~11/12 serves (91.7%). Recent 150-sample runs: 90.7%, 89.3%, 90.7%, 88.7% (±2.5%).
|
||||
Watch this — the last few sit under the bar rather than scattered around it.
|
||||
**The harness for this number was never written down and I could not reconstruct it.** A rig that
|
||||
counts "did the CPU get a racket on the served ball" scores 100/100, because after a flub the CPU
|
||||
|
||||
Reference in new issue
Block a user