The umpire reads a list, and the bibs come out to meet it

The late game is repetitive because the optimal move never changes: kill the
nearest thing, now. This is the first swing at a mode where it does — a
coaching drill where the umpire CALLS a colour sequence aloud while bibbed
bodies take their marks, and you must put them down in that order while they
walk at you. Uncalled colours are decoys you have to ignore. Two faults is a
double fault and the round is theirs.

Back room only (DRILL TEST, or #drill): this is a feel-test for one question —
tense, or just noisy? — so it keeps its own round count and touches neither
tennis scoring nor the run.

- voiceSeq: voice() drops a line that would start while the umpire is still
  talking, which is fatal for a list. Decode first, queue at accumulated
  offsets, and the anti-overlap rule is satisfied instead of fought.
- Four colours with PIPS as well as hue — red/green alone makes the mode
  unplayable for ~8% of men.
- drawBib is a ponytail-marked overlay that knowingly breaks the sprite rule;
  it is the thing to delete if the mode survives.
- BOTH target pickers snap (flickTarget and zombieTarget). Snapping only the
  slingshot left fifteen tap-serves in a row scoring nothing.
- Contact costs the point, not the run — permadeath stays in the gore mode.
- startDrill disarms the ordinary schedule; updateDrill redeals and logs an
  INVARIANT if anything empties its court.

Eight new tests (24 total) pin the ownership contract both ways, the call
beat, the win, the double fault, the un-winnable-round guard and the death.
PROJECT.md carries the four open questions for the playtest.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5 committed 2026-08-11 18:01:33 +02:00
1 parent c659b6882f
commit 22df96cc1d
17 files changed
+556 -19

No files matched your search

+76 -3
View File
@@ -10,7 +10,7 @@ detail of any individual change, read the git log.
the state-machine contracts that have historically produced the show-stoppers. The Dockerfile runs
it as a build stage, so a red suite cannot deploy — but finding out from Coolify is the slow way.
Last updated 2026-08-11 (the horde's breather restored, the boss walk-on overlay; beats.js
Last updated 2026-08-11 (the coaching drill lands as a back-room prototype; the horde's breather restored, the boss walk-on overlay; beats.js
extraction, this file renamed, tests gate the build). **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
@@ -57,8 +57,10 @@ the primary target.
`config.js` TUNE (live T panel) + structural constants + `UI` palette · `state.js` the `G` object +
P/O/ball/score + `setupPoint`/`sendBoy` · `gameplay.js` ballistics/swings/serve/rules/AI/`update()` +
transition + `splatPoint` + ball-boy routines · `beats.js` **the beat machinery: every mode
transition (duel/horde/boss/changeover/`proceedAfterClear`) — ONE MODE OWNS THE COURT, and each
`start*` enforces that itself; a new mode starts there and refuses there** · `zombies.js`
transition (duel/horde/boss/changeover/drill/`proceedAfterClear`) — ONE MODE OWNS THE COURT, and
each `start*` enforces that itself; a new mode starts there and refuses there** · `drill.js` the
coaching drill's own clock, bodies and rules (its transitions live in beats.js like every other
mode's) · `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, scoreboard test-mode taps,
@@ -431,6 +433,77 @@ WELL drains instead: a gold wash across the whole tile, emptying leftward, clipp
rounded path (`panel` leaves it as the current path) and drawn under the icon and labels. Measured
lit vs empty on the live tile: rgb(88,125,68) against rgb(31,84,49).
## Session log: THE COACHING DRILL — the cognitive mode, as a prototype (2026-08-11, same day)
Oliver and his team-mate decided the game needs **a different angle alongside the gore mode**, after
he reported the late game going repetitive: *"something that requires some immediate strategy other
than saving specific weapons for a boss I was killed by last time"*. Three modes planned — classic
tennis, **splatter tennis** (this), splatter tennis GORE (what exists). What went in today is the
middle one's feel-test, not the mode: **back room only** (`DRILL TEST`, or `#drill`), no menu entry,
nothing wired to tennis scoring.
**Why the late game is flat, for the record** (diagnosed before proposing anything, per the
meta-lesson). The optimal move never changes: kill the nearest thing, now. Every interruption is on
a fixed metronome (duel/3, machine/4, horde/6, boss/10, barrel/every game past 16), the director's
dials all bottom out by ~game 25, nothing is scarce once the rack is full — and **the zombie side's
score is unreachable by construction**: contact is a radius around the player and bodies home on his
x, so anything that could cross the baseline eats him first. The crossing branch in `updateZombies`
(and the `ump_unsportsmanlike` line with it) is very nearly dead code. Oliver caught that himself —
the first design direction proposed this session was built on it and had to be thrown away.
**The mode.** The umpire CALLS a colour sequence aloud while bibbed bodies jog on and take their
marks; then they come for you and you put them down in the called order. Colours never called are
DECOYS — bodies walking at you that you are required to ignore. Two faults = double fault = the
round is theirs. Settled in discussion: **audio-first, and shown as a pattern too.**
- **`voiceSeq` in audio.js** is what makes the call possible at all. `voice()` DROPS a line that
would start while the umpire is still talking (one man, one microphone) — correct for unrelated
triggers, fatal for a list. It decodes every clip FIRST, then queues them all at accumulated
offsets, so the anti-overlap rule is satisfied by construction rather than fought. Five new
recordings (`say -v Daniel -r 168` -> `afconvert`, the existing pipeline).
- **Four colours, not six**, and every bib carries PIPS as well as a hue: ~8% of men cannot separate
red from green, and a mode whose entire input is "which colour is that" owes them a second
channel. The same pips appear on the HUD strip, so the two readouts teach each other.
- **The bib is a DRAWN OVERLAY and it knowingly breaks the sprite rule** (`ponytail:` marked in
`drawBib`). Six bib sheets x every walk frame is a week of art to answer a question a trapezoid
answers today. If the mode survives its playtest, `drawBib` is the thing to DELETE and replace
with a real bib sprite per colour, drawn like a wound skin. Shoulders wider than waist — the first
cut flared the other way and every body was wearing a sandwich board.
- **TWO functions pick a target, and snapping only one of them was the bug.** `flickTarget` (the
slingshot) and `zombieTarget` (the plain tap) are separate paths; with only the first snapped,
fifteen tap-serves in a row scored zero and the round just ran out. Both snap now. Anything that
changes how a shot chooses a body has to touch both — same shape as the "three of five weapons
silently no-oped on the racket zombie" bug.
- **Contact costs the POINT, not the run** — a deliberate contract change, hooked in `killPlayer`
(the single death path for both damage channels, which is why one hook covers every way in).
Permadeath stays settled in the gore mode: a memory game where one lapse deletes a thirty-minute
run punishes the exact skill it is trying to teach.
- **The empty-court net** in `updateDrill`: a live round with nothing bibbed on court means something
outside the drill emptied it (seen once while wiring up, not reproducible afterwards). Unrepaired
that is a dead mode — no targets, no clock, nothing to tap. It redeals and logs `INVARIANT`, the
same contract `watchdog` uses. `startDrill` also DISARMS the ordinary schedule (`pendingHorde`,
`hordeWarn`, `bossDue`, `duelDue`): taking the court is not enough if someone else's arming latch
is still loaded behind you.
- **Eight new tests** (24 total) pin the ownership contract both ways, the call beat, the clean win,
the double fault, the un-winnable-round guard and the softer death.
**Open, and every one of them is Oliver's call after he plays it:**
1. **A ball that carries through the lane can complete two steps at once** — measured: one serve
killed bib 1 and then bib 2 standing behind it, banking two correct steps in one shot. Had the
second body been out of order that is an instant fault the player could not have intended. Either
the lane is part of the puzzle (it is the same occlusion that makes footwork matter) or the pierce
has to stop on a drill kill. Do not patch it silently — it is a design fork, not a bug.
2. **The tap now aims itself completely.** With `zombieTarget` snapped, a bare tap is a legal shot at
the called body, so the mode currently has NO aiming skill in it at all — only order and tempo.
That is probably right for a cognitive mode played one-thumbed, and occlusion is the only thing
left that makes standing position matter. Worth a deliberate look rather than an accident.
3. Difficulty climbs on sequence LENGTH today (`drillSeq` 3 -> `drillSeqMax` 4, +1 every 3 rounds)
and is deliberately capped low: a longer list is a memory test and memory tests fatigue. The dial
with real headroom is the GEOMETRY — how often the called order fights the order they reach you
in, and how often the target is behind someone. Unbuilt.
4. Nothing is wired to tennis scoring, the menu or the run, and the mode has no ending. That is the
next job IF the answer to "tense or noisy?" comes back tense.
## Session log: beats.js, the rename, and the build gate (2026-08-11, same day)
Oliver's structural question ("framework? smaller files?") answered with a deliberate NO on both —