HANDOFF-MATCH.md: what landed, and the record shape a future session must not re-derive
Slices 1, 2 and 5 struck through with what they actually became; 3, 4, 6 and 7 left standing, with a note on 3 that the wound-stage art now exists for it and on 4 that the racket_* batches are its template. The parts of slice 2 that are still open (shots[] and the PARRY — the cheapest depth in the mode) are called out rather than buried under a DONE. Added: the record shape table (which of g/s/dead each mode uses and why), the classic-tennis mode, the second ending, and four new traps — the stats banked flag that no-kill modes never clear, the magenta key that ate racket strings, the read-only PIL image that made the fix look like a no-op, and the detail bar that ratchets against what it is replacing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d5942e8aad
commit
19ee62067a
1 file changed
+65
-20
+65
-20
@@ -49,36 +49,34 @@ communicating its own rules. Model A only works if the player can see they are b
|
||||
|
||||
## The build slice, in order
|
||||
|
||||
1. **Run summary + the measure** *(small, do first — it is what makes A legible)*
|
||||
- Track and display **opponents felled**, not `G.runGames`. Today `matchGameWon` bumps
|
||||
`G.runGames` (beats.js:308) because every mode uses it as the ladder metric; the match needs
|
||||
its own count (`G.match.opp - 1` already is it) surfaced on the death screen and in a
|
||||
match-specific localStorage best (`drillBest` is the pattern to copy).
|
||||
- Death screen should speak tennis: "FELLED 3 OPPONENTS · 14 GAMES". `killPlayer` already owns
|
||||
the single death path and the run summary.
|
||||
- While here: make the blood legible so A reads correctly. The vignette exists (four linear edge
|
||||
fades, sqrt curve) — check on a phone whether hits 1-2 of 8 land hard enough.
|
||||
1. ~~**Run summary + the measure**~~ **DONE** (2026-08-12, staging). The measure is OPPONENTS
|
||||
FELLED, in play (`OPPONENT n · m FELLED` under the board) and on the death screen (`BLED OUT.` /
|
||||
`FELLED n OPPONENTS · m GAMES`). No second localStorage key: a match run is banked through the
|
||||
same `endRun` with a mode tag — see "The record shape" below. The blood also got a readout, one
|
||||
pip per hit left beside the board, because the vignette says "worse" and never says "how many".
|
||||
|
||||
2. **Named opponent ladder** *(medium, mostly data)*
|
||||
- `BOSS_ART` already holds **ten named designs with wound stages and throw frames** —
|
||||
GROUNDSKEEPER, LINE JUDGE, CHAIR UMPIRE, HEAD COACH, PHYSIO, SPONSOR MASCOT, THE CHAMPION at
|
||||
ten. Rung N gets a name plate and a personality row.
|
||||
- Personalities are the point, not the names: serve-and-volley, drop-shot merchant, **body-shot
|
||||
thrower** — note `shots[]` and the PARRY are fully built and completely unused in the match.
|
||||
That is the cheapest depth available anywhere in this mode.
|
||||
2. ~~**Named opponent ladder**~~ **DONE, partly.** Rung N takes design N's name off the chapter
|
||||
roster (`matchRung` in config) plus a personality that is DIALS — pace, width, flub, speed
|
||||
scalings on numbers `racketAI` already reads. Name plate on the walk-on, board names it
|
||||
(`short`), kills file under the rung so THE LADDER screen can list them.
|
||||
**Still open, and still the cheapest depth in the mode:** `shots[]` and the PARRY are fully built
|
||||
and completely unused here. A body-shot thrower is a personality no dial can express.
|
||||
|
||||
3. **Wound-driven play** *(small, big payoff)* — the injuries should MEAN something: a one-armed
|
||||
opponent loses reach, a headless one flubs more. Score-is-the-body becomes score-changes-the-game.
|
||||
Read the fraction the way `drawZombie` and `bleed()` do (never literal hp).
|
||||
*The art is now there for it:* every wound stage has its own ready stance, shuffle pair and
|
||||
forehand (`z_rw1*`, `z_rw2*`), so a wounded opponent still plays rather than reverting to a walk.
|
||||
|
||||
4. **The fall animation** *(art pass — Oliver's idea, and a good one)* — point lost → the corpse
|
||||
COLLAPSES → drags itself up minus the limb. It explains the wound stage popping between points and
|
||||
gives every point a payoff beat. `matchReady()` is the natural slot. **Constraint: draw frames,
|
||||
do not fake them** (settled rule) — this needs a small fall/rise sheet per design through
|
||||
`assets/_regen.py`, not a JS rotate/sink.
|
||||
do not fake them** (settled rule) — this needs a small fall/rise sheet per stage through
|
||||
`assets/_regen.py`, not a JS rotate/sink. The `racket_*` batches are the template.
|
||||
|
||||
5. **Menu entry** *(small)* — the three-mode select Oliver planned: CLASSIC TENNIS / ZOMBIE TENNIS /
|
||||
SPLATTER GORE, replacing the back-room door for this mode.
|
||||
5. ~~**Menu entry**~~ **DONE.** Three modes on the setup screen, from `config.MODES`. The back room
|
||||
keeps its door (MATCH TEST still works) — the menu did not replace it, it just stopped being the
|
||||
only way in.
|
||||
|
||||
6. **Teaching** *(small)* — a ghost-hand lesson for the PLACEMENT FLICK. It is the counter-attack
|
||||
(flick during the swing aims the return; wrong-footing is how points are won) and it is taught
|
||||
@@ -88,6 +86,42 @@ communicating its own rules. Model A only works if the player can see they are b
|
||||
7. **Opponent serve games** *(bigger, later)* — the zombie never serves today. Alternating service
|
||||
is real tennis and doubles the skill surface (return timing).
|
||||
|
||||
---
|
||||
|
||||
## What the 2026-08-12 session added beyond the slice
|
||||
|
||||
**CLASSIC TENNIS is a real mode, not the opener with the turn switched off.** Oliver: *"if the core
|
||||
tennis is good everything else is good."* Six games with a two-game lead is a set,
|
||||
`TUNE.classicSets` is the match, no tiebreak (marked `ponytail:` — add it when a real match actually
|
||||
stalls at 6-6). The CPU stops rubber-banding there: `aiFlubLoss` exists to get a new player PAST the
|
||||
opener and would have him in pieces by the second set.
|
||||
|
||||
**A second ENDING, with no corpse in it.** `G.done = {won, t}` — same summary and gated tap as the
|
||||
death screen, its own words, the man still standing. All three endings share one render block; three
|
||||
copies of it would drift the first time one moved.
|
||||
|
||||
**THE RECORD SHAPE — read this before touching stats.js.** One character `m` on the run record
|
||||
(`g`ore / `z`ombie tennis / `c`lassic; absent means gore, so no migration). The record's two headline
|
||||
slots carry each MODE's own two numbers:
|
||||
|
||||
| mode | `g` | `s` | `dead` |
|
||||
|---|---|---|---|
|
||||
| gore | games survived | sets banked | died |
|
||||
| zombie tennis | **opponents felled** | games played | died |
|
||||
| classic | games played | sets won | **match LOST** |
|
||||
|
||||
`g` is what everything ranks on, so each mode's real measure goes in that slot and there is no second
|
||||
best stored anywhere to drift. `read(period, runs, mode)` filters; the stats screen is one function
|
||||
with three sets of words (BODY COUNT / THE LADDER / THE RECORD). The world board stays the gore
|
||||
mode's — it ranks kills in a run, and a five-body match would post a "5 kills" row next to a
|
||||
300-kill massacre. A tennis board would rank FELLED with points won as the tiebreak; that is a
|
||||
server change.
|
||||
|
||||
**THE MATCH OPPONENT HAS ITS OWN SPRITES** (`z_r*`), drawn off the living opponent's poses: ready
|
||||
stance, shuffle pair, forehand, at each wound stage, both tours. `z_c*` (the turned CPU) is
|
||||
untouched and keeps its shamble — in the gore mode shambling is the right thing for it to do. The
|
||||
frame is picked off `racketAI`'s own `pose`, so the picture cannot drift from the behaviour.
|
||||
|
||||
Expansion ideas that survived discussion, in `PROJECT.md`'s LATER: the **tournament frame**
|
||||
(QUALIFYING → ROUND OF 16 → THE FINAL vs THE CHAMPION — gives the run a destination), the Hawk-Eye
|
||||
**challenge** as an earned continue, sudden-death deuce past a rung, doubles, and **item 7:
|
||||
@@ -151,6 +185,17 @@ he ran, where the ball died. That triple diagnosed every defect in the contact c
|
||||
a fixed seed can silently turn a guarded test into a no-op, so check guards still run.
|
||||
- **`?v=` in index.html on every push, or phones keep the old build.** Bump `SPRV` too if sprites
|
||||
change.
|
||||
- **`stats.banked` is only cleared by a KILL**, and classic tennis has none — so the second and every
|
||||
later classic match of a session was silently swallowed by the double-bank guard. `beginRun()` is
|
||||
the reset now and `restartRun` calls it. Any future mode with no kills in it needs that path.
|
||||
- **`_fine.py` used to key EVERY magenta pixel, not just the background** — and the generator paints
|
||||
racket strings in the backdrop colour more often than not, so sheet after sheet installed a body
|
||||
holding an empty hoop. Fixed (flood from the border; interior magenta is repainted pale grey), but
|
||||
check a new sheet's rackets anyway. While fixing it: `Image.fromarray` images are READ-ONLY and
|
||||
PIL's `floodfill` swallows the write error and returns having filled nothing.
|
||||
- **The regen detail bar RATCHETS against the file being replaced.** A repose of art that is already
|
||||
fine (`check: 'same'`) can be rejected for not out-detailing a redraw; `'loose'` is the right bar
|
||||
for that job, and resetting the placeholder to the coarse original resets the bar.
|
||||
|
||||
## Do not
|
||||
|
||||
|
||||
Reference in new issue
Block a user