The racket holds still: both player run cycles re-rolled steady-handed

Oliver, after the facing fix: still flashing, female worst. Film-strip
diagnosis in the pane: with facing pinned the racket STILL alternated —
frame 4 of the female cycle tucked it across the body, and half the frames
of both player cycles lost or shrank the hoop to cut fragments (overlapping
figures sliced by the even-spaced valley cuts).

Both cycles re-rolled with three new prompt rules that now live in
_runroll.py: the racket stays right of the midline in EVERY figure (named
again for the airborne pose, where it kept vanishing), and the figures
never overlap (no more sliced hoops — a debris-stripping pass was tried
and removed: after the recrop a real racket is indistinguishable from a
fragment, prevention beats cleanup). The model returns 2-row grids at
times; a coherent band is cropped and cut. Installed: racket-side spread
+0.01..+0.05 across all frames of both cycles (was a full side swap).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Fable 5 committed 2026-08-13 19:30:40 +02:00
1 parent 35671c689b
commit c847bcbfa1
15 files changed
+27 -5

No files matched your search

+25 -3
View File
@@ -98,12 +98,21 @@ def prompt(k):
'garments, the hair and every one of its colours exactly. The poses in image 1 exist only to '
'show the character — do NOT copy the poses.\n\n'
f'EVERY ONE OF THE {N} FIGURES GRIPS THE WOODEN TENNIS RACKET IN THE RIGHT HAND, carried low '
'at the side mid-run. A figure with an empty hand is WRONG.\n\n'
'at the side mid-run. A figure with an empty hand is WRONG. THE RACKET STAYS ON THE RIGHT '
'SIDE OF THE BODY IN EVERY SINGLE FIGURE — it never swings across the body, never tucks '
'behind the back to the left, never changes sides between neighbouring figures. The right '
'arm swings a LITTLE; the racket head stays visibly right of the body\'s midline throughout. '
'A single figure with the racket on the left side makes the whole animation flicker and is '
'WRONG.\n\n'
f'THE {N} FIGURES ARE ONE CONTINUOUS RUN CYCLE, {c["view"]}. Evenly spaced, every figure on '
'one shared ground line, every figure at the same scale as its neighbours.\n\n'
'one shared ground line, every figure at the same scale as its neighbours. THE FIGURES '
'NEVER TOUCH OR OVERLAP: leave a clear band of empty magenta between every pair of '
'neighbours — no part of any figure, the racket included, reaches into its neighbour\'s '
'space. A racket overlapping the next figure is WRONG.\n\n'
'The cycle, frame by frame: 1 the right foot planted under the body, left knee driving up and '
'forward; 2 push-off, the right leg trailing straight behind, the left leg reaching forward; '
'3 full flight, both feet off the ground, the stride at its widest; 4 the left foot landing '
'3 full flight, both feet off the ground, the stride at its widest — THE RACKET STILL '
'GRIPPED IN THE RIGHT HAND in this airborne figure, exactly as in every other; 4 the left foot landing '
'ahead of the body. Frames 5 to 8 are the SAME four moments with the legs swapped — the left '
'foot planted, the right knee driving. Across the row the gap between the feet opens, closes, '
'opens and closes again. The arms counter-swing: the opposite arm to the driving knee comes '
@@ -194,6 +203,19 @@ def cut(k, install=False, n=0):
pad = Image.new('RGBA', (im.width + abs(off), im.height), (0, 0, 0, 0))
pad.paste(im, (0 if off > 0 else abs(off), 0))
pad.save(o)
# THE RACKET-SIDE GATE. One frame with the racket swung across the body makes the whole cycle
# flicker left-right at stride rate — Oliver: "the racket she is holding flashing left and
# right very fast" (the female cycle's frame 4 was the offender). racket_side locates the bed
# (pale blob or keyed hole); every frame must keep it right of centre, or at worst dead-centre.
# ...as a WARNING, not a gate: on back-view art the bed is part-occluded and the centroid
# reads ±0.05 of noise — it never flagged the original offender (old frame 4 measured -0.02).
# The montage review is the real gate; a strongly negative number here is what to look at.
from _famref import racket_side
sides = [racket_side(Image.open(o)) for o in outs]
print(' racket sides:', ['%s%+.2f' % ('?' if s is None else '', s or 0) for s in sides])
crossed = [i + 1 for i, s in enumerate(sides) if s is not None and s < -0.12]
if crossed:
print(f' WARNING: racket may cross LEFT in frame(s) {crossed} — check the montage hard')
# THE STRIDE GATE, on what was actually cut: every neighbouring pair (wrapping — it loops) must
# differ in the legs, or the cycle holds a duplicated pose. Threshold calibrated against
# _famref's (30% = a real stride pair; 12 catches only true near-duplicates).
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 17 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 17 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 20 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 19 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 19 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 18 KiB

+1 -1
View File
@@ -431,6 +431,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one') {
</div>
</div>
</div>
<script type="module" src="src/main.js?v=217"></script>
<script type="module" src="src/main.js?v=218"></script>
</body>
</html>
+1 -1
View File
@@ -221,7 +221,7 @@ function farB(z) {
// Asset cache-buster — bump with main.js?v=N (see the img.src note below). EXPORTED because the
// menu screens hang <img> tags on the same files, and two copies of this number is one copy too
// many: the one that does not get bumped is the one that serves a cached 404.
export const SPRV = 110;
export const SPRV = 111;
// The boot bar in main.js watches this. Nothing here CHANGES when things load — every sprite was
// already requested at import — it just makes the wait visible and holds the menu back until the
// last sheet has landed. See the onerror note below for the other half of "sometimes missing".