Stop the opponent shrinking, skating and sprinting on the spot

The "smaller when swinging or tossing" was an anchor bug, not art. _fine.py
scales a whole sheet so the --anchor frame is 224 px, and drawChar draws every
sprite at img.height/SPRITE_H — so anchoring opponent_play on the TOSS, with
arm and ball above the head, made 224 px cover the raised arm and shrank every
body on that sheet. o_swing1 was 162 px against a 224 px standing pose. Each
opponent batch now leads with a standing `ref` frame, which _fine.py anchors on
and _regen.py discards. Correctly anchored the toss is 278 px and the man is
full size; the swing is 221.

The receiver no longer slides 0.12 m side to side while waiting on a serve —
sliding a sprite sideways reads as skating, and the split-step swap to the run
frames (removed earlier) had already been the other half of that.

The idle bounce ball had one hardcoded offset for both men. They reach to
opposite sides of the screen — the player is drawn from behind, the opponent
from the front — so the opponent's ball sat out by his racket. dx and apex are
per-man now, and o_bounce/o_bounce2 were redrawn with a patting hand to put it
under.

Also: the opponent wears a RED headband, and a prompt of mine that said "white"
had quietly repainted it on six frames.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5 committed 2026-08-01 11:37:16 +02:00
1 parent cd1c7a0cd8
commit 494bab1b69
17 files changed
+84 -14

No files matched your search

+17
View File
@@ -467,6 +467,23 @@ the title screen. `menuMusic` passes `48`. Re-encode the file and re-measure:
frames at 48 kHz — an un-round master, and the decoder returns 20.4787, so 7.9 ms of padding is
trimmed). Both beds are AAC now; nothing in `assets/audio/` is still a multi-megabyte WAV.
**A SPRITE THAT LOOKS "SHRUNK" IS AN ANCHOR BUG, NOT AN ART BUG.** `_fine.py` scales the whole
generated sheet so that `--anchor`'s frame is exactly 224 px, and `drawChar` then draws every sprite
at `img.height / SPRITE_H`. So if the anchor frame is one where a PROP goes above the head, 224 px
has to cover the prop too and every body on that sheet comes out a head short. `opponent_play` was
`[o_toss, o_smash, o_swing1, o_swing2]` with `anchor: 0` — anchored on the TOSS, arm and ball
stretched overhead — which is why the opponent visibly shrank whenever he served or swung
(o_swing1 came out 162 px against a 224 px standing pose). **Every batch must lead with a plain
standing figure via `ref`, which `_fine.py` anchors on and `_regen.py` then discards** — the shape
the player's own batches already used. Correctly anchored, a toss frame is TALLER than 224 (278 px
here) and the body renders at full size. Quick audit: a standing frame should be ~224, a raised-arm
frame >224, and nothing should be well under it.
**The opponent wears a RED headband.** A prompt that said "white headband" silently repainted it on
six frames, and it only showed up against the untouched `o_toss`/`o_idle1`. When regenerating part
of a character, name his colours in the prompt AND check the regenerated frames against an untouched
one — `check: "same"`/`"loose"` skips `colour_match`, so nothing else will catch a palette drift.
**The opponent was running with empty hands.** `o_run1`/`o_run2` had no racket in either fist, and
the cause was the batch prompt: `opponent_run` never said the word "racket" and its reference sheet
was built from the racket-less frames it was replacing, so the generator had nothing to copy one