The opponent is complete: 3 wound stages x 11 states, and a gate for wounds that never happened
The serve and the fall now exist at every wound stage, so a one-armed opponent still serves and a headless one still goes down, instead of reverting to a whole body to do it. 33 frames, no gaps. THE BALL IS OUT OF THE SERVE SPRITES. Oliver: it was painted into three frames and the game draws the ball itself, so every one of them would have ridden along at the wrong place, the wrong time and in the wrong number. Hands open and empty now, poses unchanged as if a ball were there. THE FOURTH GATE, and the one nothing else could see: "copy this exactly, change ONE thing" loses when the change is a REMOVAL. Taking a head off worked every time — it is dramatic. Taking an ARM off mostly did not: five of seven wound frames came back as the reference faithfully redrawn, 0.02-0.05 of their own silhouette different, and the ladder quietly HEALED the body the score had just maimed. Frame count, background, clipping, detail, kit and stride all pass that sheet. A batch now declares how far it must move from the sheet it was drawn from — a copy scores 0.02-0.05, a real amputation 0.12-0.38, a decapitation 0.27-0.75 — and the whole set now measures 0.18-0.65. AND THE BACKGROUND RULE MOVED TO THE FRONT OF EVERY PROMPT. Six rejections in eleven rolls were "background is not magenta", and the generator was not glitching: it was painting the zombies ON A TENNIS COURT. The chained restyle subjects are long, and the magenta instruction sat behind three hundred words of "keep everything exactly as the reference", where it stopped being honoured. Same sentence, said before anything can bury it. One design consequence kept rather than papered over: the missing arm IS the tossing arm, so a wounded opponent tosses off the racket face the way one-armed players actually do. The wound changes how he plays, which is what slice 3 has been asking for. Known and NOT fixed: the shorts hue drifts from -9 to -19 down the ladder. Each stage is measured against the one before it, so a legal 4-point hop three times over is a 14-point total nobody authorised. The fix is to anchor every stage to the healthy frame with a total budget instead of a per-hop one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4bbdf6357a
commit
5c0db0a22e
24 files changed
+181
-24
No files matched your search
+43
-5
@@ -55,6 +55,9 @@ CANVAS = (
|
||||
"The canvas is a WIDE LANDSCAPE STRIP and every figure stands side by side in ONE SINGLE HORIZONTAL "
|
||||
"ROW on the same ground line — never a grid, never two rows, never stacked."
|
||||
)
|
||||
BG_FIRST = ("THE BACKGROUND OF THE OUTPUT IMAGE MUST BE ONE FLAT SOLID MAGENTA #FF00FF. No court, "
|
||||
"no grass, no lines, no scenery, no sky, no gradient — nothing behind the figures but flat "
|
||||
"magenta, edge to edge. ")
|
||||
STYLE = RES + FIDELITY + CANVAS
|
||||
NEW_STYLE = RES + (
|
||||
"Match the reference's DRAWING STYLE, its overall figure height and its facing — but the "
|
||||
@@ -177,16 +180,40 @@ def kit_off(outs, ref_name, tol=(12, 10, 1.5, 4)):
|
||||
# opt-in per batch (`stride: [[i, j]]`) rather than applied to everything with two frames in it.
|
||||
STRIDE_H, STRIDE_TOP, STRIDE_LEG, STRIDE_HIP = 0.04, 0.20, 0.30, 0.62
|
||||
|
||||
def stride_stats(pa, pb):
|
||||
def _masks(pa, pb):
|
||||
a, b = Image.open(pa).convert('RGBA'), Image.open(pb).convert('RGBA')
|
||||
w, h = max(a.width, b.width), max(a.height, b.height)
|
||||
def m(im):
|
||||
c = Image.new('RGBA', (w, h)); c.paste(im, ((w - im.width) // 2, h - im.height))
|
||||
return np.array(c)[..., 3] > 110
|
||||
ma, mb = m(a), m(b)
|
||||
return a, b, m(a), m(b), h
|
||||
|
||||
def _d(x, y):
|
||||
return round(float((x ^ y).sum() / max(1, (x | y).sum())), 3)
|
||||
|
||||
def stride_stats(pa, pb):
|
||||
a, b, ma, mb, h = _masks(pa, pb)
|
||||
hip = int(h * STRIDE_HIP)
|
||||
d = lambda x, y: round(float((x ^ y).sum() / max(1, (x | y).sum())), 3)
|
||||
return round(a.height / max(1, b.height), 3), d(ma[:hip], mb[:hip]), d(ma[hip:], mb[hip:])
|
||||
return round(a.height / max(1, b.height), 3), _d(ma[:hip], mb[:hip]), _d(ma[hip:], mb[hip:])
|
||||
|
||||
# ---- DID THE CHANGE ACTUALLY HAPPEN? A chained restyle asks for one sheet redrawn with ONE THING
|
||||
# different, and "copy this exactly" reliably beats "…but change that": removing a HEAD works every
|
||||
# time (it is dramatic), removing an ARM mostly did not — five of seven wound frames came back as
|
||||
# the reference redrawn, differing by 0.02-0.05 of their own silhouette, and the ladder silently
|
||||
# healed the body it had just maimed. So a batch can declare how much it must differ from the sheet
|
||||
# it was drawn FROM. Measured against the known numbers: a copy scores 0.02-0.05, a real amputation
|
||||
# 0.12-0.38, a decapitation 0.27-0.75. Nothing else in the pipeline can see this — the art is
|
||||
# beautiful, correctly coloured, correctly posed and completely wrong.
|
||||
def changed_off(outs, refs, want):
|
||||
for o, r in zip(outs, refs):
|
||||
rp = os.path.join(HERE, r + '.png')
|
||||
if not os.path.exists(rp): continue
|
||||
_, _, ma, mb, _ = _masks(o, rp)
|
||||
d = _d(ma, mb)
|
||||
if d < want:
|
||||
return (f'{os.path.basename(o)} is {r} redrawn — only {d:.3f} of the silhouette differs '
|
||||
f'(want >= {want}). The thing that was supposed to change did not change.')
|
||||
return None
|
||||
|
||||
def stride_off(outs, pairs):
|
||||
for i, j in pairs:
|
||||
@@ -245,7 +272,14 @@ def run(name, spec, dry=False):
|
||||
ref = ref_sheet(shown, f'{SCRATCH}/{name}_ref.png', spec.get('scale', 2.0),
|
||||
frames=len(sprites) + (1 if lead else 0))
|
||||
sheet = f'{SCRATCH}/{name}_hi.png'
|
||||
prompt = f"{spec['subject']} {STATE_STYLE if spec.get('state') else POSE_STYLE if spec.get('pose') else NEW_STYLE if spec.get('newart') else STYLE}"
|
||||
# THE BACKGROUND RULE GOES FIRST, not only at the end of the style block. The chained restyle
|
||||
# subjects are long, and with the magenta instruction sitting behind three hundred words of
|
||||
# "keep everything exactly as the reference" the generator started painting the figures on a
|
||||
# COURT — grey-blue, grey-green, dark green backgrounds, six rejections in eleven rolls. Same
|
||||
# sentence, said before anything else has had a chance to bury it.
|
||||
prompt = (BG_FIRST + spec['subject'] + ' '
|
||||
+ (STATE_STYLE if spec.get('state') else POSE_STYLE if spec.get('pose')
|
||||
else NEW_STYLE if spec.get('newart') else STYLE))
|
||||
if dry:
|
||||
print(f'[{name}] would generate {len(sprites)} frames: {", ".join(sprites)}'); return True
|
||||
print(f'[{name}] generating {len(sprites)} frames...')
|
||||
@@ -287,6 +321,10 @@ def run(name, spec, dry=False):
|
||||
if spec.get('stride'):
|
||||
bad = stride_off(outs, spec['stride'])
|
||||
if bad: print(f'[{name}] REJECTED: {bad}'); return False
|
||||
# a chained restyle declares how far it must move from the sheet it was drawn from
|
||||
if spec.get('changed') and len(spec.get('ref_only', [])) == len(sprites):
|
||||
bad = changed_off(outs, spec['ref_only'], spec['changed'])
|
||||
if bad: print(f'[{name}] REJECTED: {bad}'); return False
|
||||
for o, s in zip(outs, sprites):
|
||||
if spec.get('check', 'finer') == 'finer': colour_match(o, s)
|
||||
Image.open(o).save(os.path.join(HERE, s + '.png'))
|
||||
|
||||
Reference in new issue
Block a user