Cut the serve down to three poses

Oliver, after playing the four-stage version on the phone: idle, then the racket
dropping on the pull, then the drive on release — "that's all, forget all the
others". At thumb speed the middle stages are never read, so they are deleted
rather than kept around: p_pull is the wind-up, p_smash is the drive, and both
come out of the photo-referenced sheet, so the whole serve is one drawing style.

The dead batches and the arm_left guard go with them. assets/_pose_ref_serve.png
is kept as the recipe: his photo sequence over our own sprites, which is what
turned a fifteen-generation prose argument into one generation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5 committed 2026-08-01 01:09:57 +02:00
1 parent f6dfc57a06
commit 6378ff2a1b
11 files changed
+30 -97

No files matched your search

-17
View File
@@ -91,19 +91,6 @@ def touches_edge(sheet):
return best
return max(run(fg[0]), run(fg[-1]), run(fg[:, 0]), run(fg[:, -1])) >= 8
def arm_not_left(outs):
"""The generator MIRRORS the later frames of a serve about half the time — the coil is a strong
silhouette and it draws it whichever way it likes, which puts the racket in the other hand
halfway through the animation. Oliver spotted it twice. Every frame after the first must reach
its raised arm up the LEFT-hand side, so a frame whose left half has nothing near the top (the
arm went right instead) is a rejected roll, not something to notice later in the file browser."""
for o in outs[1:]:
ink = np.array(Image.open(o).convert('RGBA'))[..., 3] > 128
h, w = ink.shape
left = np.nonzero(ink[:, :w // 2].any(1))[0]
if not len(left) or left.min() > 0.18 * h: return o
return None
def colour_match(src, dst_name):
"""Fit the new frame onto the palette statistics of the sprite it replaces, so a redrawn body
does not change colour halfway through a walk cycle."""
@@ -146,10 +133,6 @@ def run(name, spec, dry=False):
print(r.stdout.strip() or r.stderr.strip())
if r.returncode or not all(os.path.exists(o) for o in outs):
print(f'[{name}] REJECTED: slicing failed (frames merged?) — re-run or add "cut"'); return False
if spec.get('arm_left'):
bad = arm_not_left(outs)
if bad:
print(f'[{name}] REJECTED: {bad} was drawn MIRRORED — racket in the wrong hand'); return False
if lead: outs = outs[1:] # the scale reference is not installed
before = [density(os.path.join(HERE, s + '.png')) for s in sprites]
after = [density(o) for o in outs]