Draw the whole serve right-handed, and let the pull wind it up
The racket kept changing hands because the art disagreed with itself: p_idle, p_bounce and p_toss held it in his LEFT hand while p_smash and p_swing used his right, so every new frame copied whichever reference it was handed. The idle, the bounce pair, the toss and the four wind-up frames are all regenerated with the side named — drawn from behind, his right hand is the right-hand side of the image, and only the racket HEAD crosses behind his back. What separates the four wind-up stages is how far the racket has fallen, and that needed the racket head pinned to a body landmark per frame — above his head, beside his ear, between his shoulder blades, at the back of his thighs. Described as "further into it each time" it drew four near-identical poses twice running. They are wired late in the pull, Oliver's call: frame 1 owns the first half and the three coiled frames are packed into the top half. assets/_derive.py is deleted. Fixing generated art by mangling it afterwards is always worse than regenerating; three guards in _regen.py turn the bad rolls that used to install silently into automatic re-rolls — a portrait reference canvas (the generator answered with a 2x2 grid), a non-magenta background (a green sheet keyed away four racket heads), and a mirrored frame. The bounce pair is one drawing from one sheet now, differing only in the free hand, so BOUNCE_IDLE moved to his left side and was re-measured off the sprite. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
231cd8650a
commit
bcff17812f
17 files changed
+181
-170
No files matched your search
+66
-34
@@ -409,41 +409,66 @@ Known generator quirks, all still true:
|
||||
- **Colour-match any regenerated frame** of an existing pair before shipping: fit a per-channel
|
||||
linear map onto the original's mean and sd, then verify with a mean-difference number.
|
||||
|
||||
## Session log: the serve is the gesture (2026-07-31, after the art pass)
|
||||
## Session log: the serve is the gesture (2026-08-01)
|
||||
|
||||
Oliver's notes on the art pass: the zombies are good, the runner "flickers very hard", and the
|
||||
player has three problems. All four fixed, all four measured first.
|
||||
player has three problems. The serve is now a drawn wind-up driven by the pull.
|
||||
|
||||
- **The pull IS the wind-up.** Four new frames (`p_pull1..4`, `assets/_regen_batches.json` batch
|
||||
`player_pull`) — racket above the head, behind the head, behind the back, fully coiled — chosen in
|
||||
`drawChar` off `pullFrac(G.aim.mag)`, the same 0..1 that sets the shot's power. Drag further, coil
|
||||
further; release fires `p_smash`. Verified per stage with drawImage instrumentation:
|
||||
mag 10/50/90/130 -> pull1/pull2/pull3/pull4.
|
||||
- **The racket must stay in ONE hand across a whole sheet, and the generator will not do it unasked.**
|
||||
Oliver, straight off the file browser: *"the racket changes the hand in 3 and 4"*. It did — frames
|
||||
1-2 came back right-handed and 3-4 left-handed. Both mirrored into line (a back-view figure mirrors
|
||||
for free) and the batch prompt now names the side explicitly. The convention the rest of the art
|
||||
uses: **racket on the LEFT-HAND SIDE OF THE IMAGE, free arm on the RIGHT** — which is also the side
|
||||
the game drops the bounced ball (`BOUNCE_IDLE.dx` > 0) and tosses the served one (`P.x + 0.35`).
|
||||
`p_smash` is the deliberate exception: the racket comes over to the right to meet the ball it is
|
||||
actually hitting.
|
||||
- **The bounce pair is now ONE drawing.** `p_bounce2` is `p_bounce` with the free arm sheared up 12
|
||||
px (0.10 m) — everything else is pixel-identical, so the swap cannot flicker. The swap follows the
|
||||
BALL now (`|sin| > 0.5`) instead of its rising phase, and `BOUNCE_IDLE` was re-measured off the
|
||||
sprite (`apex` 0.55 -> 0.6, `dx` 0.5 -> 0.43) so the ball tops out under the raised palm instead of
|
||||
passing beside it.
|
||||
- **`p_toss` had the tossed ball drawn into it.** Cut, without re-cropping the canvas: the game
|
||||
scales him off `img.height`, so trimming the dead space would have shrunk him. Still 100x308.
|
||||
- **The runner.** Two measured defects, both in the PNGs, no code change. Its four frames were
|
||||
bbox-cropped and drawn centred on the body's x, so hip centres sat -2.0/+7.1/-6.0/-0.1 px off
|
||||
centre — a 13 px lateral jump at 12 frames a second, which is the flicker. And Oliver's own
|
||||
diagnosis was right: all four frames leaned the same way and lifted the SAME leg, so the cycle
|
||||
never used his other side. Frames 3 and 4 are mirrored (he is front-on, so a mirror costs nothing)
|
||||
and all four are padded to one width with the hip on the centre line.
|
||||
- **The pull IS the wind-up.** Four new frames (`p_pull1..4`, batch `player_windup`), chosen in
|
||||
`drawChar` off `pullFrac(G.aim.mag)`. What separates the four is HOW FAR THE RACKET HAS FALLEN, and
|
||||
that only came out once the prompt pinned the racket head to a different BODY LANDMARK per frame —
|
||||
above his head, beside his ear, between his shoulder blades, down at the back of his thighs.
|
||||
Described as "further into the wind-up each time" it drew four near-identical poses twice running
|
||||
(Oliver: *"pull3 and pull4 are now basically the same image"*), which is the generator quirk the
|
||||
pipeline notes already warned about, in a place I did not expect it. The stages are NOT spread evenly: **frame 1 owns the whole
|
||||
first half of the pull and the three coiled frames are packed into the top half**, Oliver's call,
|
||||
so the part of the gesture that costs power is the part that visibly costs effort. Release fires
|
||||
`p_smash`. Verified: 30/74/95/125 px of drag -> pull1/2/3/4.
|
||||
- **The whole player is RIGHT-HANDED now, and that was the real bug.** `p_idle`, `p_bounce` and
|
||||
`p_toss` had been drawn holding the racket in his LEFT hand while `p_smash` and `p_swing` used his
|
||||
right, so any new frame copied whichever reference it was given and the racket changed hands
|
||||
mid-serve. Everything from the idle to the wind-up was regenerated with the hand named explicitly:
|
||||
**drawn from behind, his right hand is the RIGHT-HAND SIDE OF THE IMAGE**. Only the racket HEAD
|
||||
crosses behind his back in the deep frames — the arm never does.
|
||||
- **The bounce pair is one drawing from one sheet**, differing only in the free hand, so the swap
|
||||
cannot flicker; it follows the BALL (`|sin| > 0.5`) rather than its rising phase. He pats the ball
|
||||
with his LEFT hand now, so `BOUNCE_IDLE` moved to the other side and was re-measured off the sprite
|
||||
(`dx` 0.36, `apex` 0.68). The served toss moved to `+0.12` — nearly over his head, because the
|
||||
tossing hand is on the left and the racket meets it on the right, and a big offset either way is
|
||||
wrong for one of those two frames.
|
||||
- **The runner** still carries a hand-mirrored fix from the previous commit (see OPEN) — its frames
|
||||
need regenerating, not mangling.
|
||||
|
||||
Not done, and the honest reason: the runner's cadence is still time-driven (12 fps) rather than
|
||||
stepped off distance covered like every other body. It did not read as wrong once the frames stopped
|
||||
jumping, and it is one line if a playtest says otherwise.
|
||||
**THE RULE, from Oliver, that this session existed to learn:** *"fixing generated art by mangling
|
||||
with it afterwards is always worse than regenerating. it's just a fact."* Mirroring a frame,
|
||||
shearing a limb, cutting a prop out — a wrong frame is wrong in more ways than the one you spotted
|
||||
(perspective, foreshortening, where the weight sits) and none of those survive a flip. There was an
|
||||
`assets/_derive.py` doing exactly that; it is deleted. Fix the PROMPT and re-run the batch.
|
||||
|
||||
**Three pipeline guards were added to `_regen.py`, each after a bad sheet installed silently:**
|
||||
1. **The reference's ASPECT is the answer's aspect.** One tall reference figure gave a portrait
|
||||
reference canvas and the generator answered with a 2x2 GRID, which `_fine.py` cannot cut (it
|
||||
segments columns). `ref_sheet` now widens to a landscape strip with one slot per frame asked for.
|
||||
2. **A non-magenta background is a rejected roll.** "It sometimes ignores the magenta" was written
|
||||
off as harmless because the key uses the corner colour — until it returned a GREEN sheet and keyed
|
||||
away four grey-green racket heads with it, installing four men holding a grip and nothing else.
|
||||
3. **`arm_left` rejects a MIRRORED frame.** The generator flips the later frames of a serve about
|
||||
half the time, which puts the racket in the other hand halfway through the animation. Oliver
|
||||
caught it twice by eye before the check existed. **It is a net, not a proof**: it asks whether
|
||||
anything reaches the top of the LEFT half, and a mirrored frame whose racket head hangs over there
|
||||
passes. A tighter discriminator was tried (ink mass in the top third, left vs right) and does NOT
|
||||
separate the two — measured 0.61/0.57/0.41 on a mirrored sheet against 0.54/0.56/0.55 on a good
|
||||
one. Every wind-up sheet still has to be looked at.
|
||||
|
||||
Still imperfect, and worth a look on the phone: the empty tossing arm WANDERS between the four
|
||||
wind-up frames (up, out, bent, up) instead of staying thrown up at the ball. The roll that held the
|
||||
arm still put the racket back in the wrong hand, and the racket is the load-bearing half of the
|
||||
gesture, so this is the set that shipped. One more roll may fix both.
|
||||
|
||||
Also worth knowing: a batch of raised-arm poses has no honest scale to anchor on, so frame 0 is
|
||||
always a throwaway STANDING frame (`p_pull0`, deleted after installing). And a photo reference of a
|
||||
real serve is what finally got the poses right — Oliver supplied a 10-frame sequence, and the four
|
||||
stages are its frames 3, 4, 5 and 6, the last being maximum tension before release.
|
||||
|
||||
## Session log: the art-resolution pass (2026-07-31)
|
||||
|
||||
@@ -582,12 +607,19 @@ Nothing here is broken; these are known gaps and unverified risks.
|
||||
8. **A chapter can be skipped.** `G.bossDue` takes the max, so going a long stretch without ever
|
||||
clearing the court means you face the current chapter, not a backlog. Deliberate; noted in case it
|
||||
ever reads as a bug.
|
||||
9. **Step 6 stays PARKED** by Oliver's decision: the crowd turning into zombies (spawning from the
|
||||
9. **The runner's four frames were fixed BY HAND and must be regenerated.** `z_f1..4` are currently
|
||||
hip-registered (padded to one width with the hip on the centre line) and frames 3 and 4 are
|
||||
mirrored copies, because all four were drawn leaning the same way and lifting the same leg. That
|
||||
is exactly the post-processing Oliver ruled out — it shipped before he stated the rule. The batch
|
||||
to write asks for a four-frame front-on sprint whose stride and lean ALTERNATE, and the numbers to
|
||||
check afterwards are the per-frame hip centroid (it was 13 px of lateral jump at 12 frames a
|
||||
second) and which leg leads.
|
||||
10. **Step 6 stays PARKED** by Oliver's decision: the crowd turning into zombies (spawning from the
|
||||
stands — attacks from behind, high spectacle, `x_crowd` sheets already exist) and the ball-boy zombie
|
||||
that catches your shots. Recorded so the ideas are not lost, not as a to-do.
|
||||
10. **`ump_no_deal` does not exist.** Oliver asked for its removal; `ump_no_appeal` was removed as the
|
||||
11. **`ump_no_deal` does not exist.** Oliver asked for its removal; `ump_no_appeal` was removed as the
|
||||
closest match. Worth one confirmation that no other line is still unwanted.
|
||||
11. **Four sounds are missing and need RESEARCH first** (find/choose the source before implementing —
|
||||
12. **Four sounds are missing and need RESEARCH first** (find/choose the source before implementing —
|
||||
every existing sfx is either synthesised in `audio.js` or a wav in `assets/audio/`):
|
||||
- the **ball machine** while it moves — a squeaking wheel/castor, so the furniture announces
|
||||
itself. It already telegraphs its shot (`machWind` 0.9 s); this is the *approach* cue.
|
||||
|
||||
Reference in new issue
Block a user