THE MASTER SHEETS: a new zombie is now three calls and no pose design
Oliver asked what the reference sheet is called. It did not have a name — it was assembled per batch into /tmp and thrown away, so the pose set that took a day to get right existed only as 33 loose PNGs. assets/_sheet_master_play.png stance, shuffle A, shuffle B, forehand assets/_sheet_master_serve.png bounce, toss, strike assets/_sheet_master_fall.png buckle, knees, down, rise Magenta-backed, spaced, feet on one ground line — loadable straight into a generator as-is. `python3 assets/_master.py` rebuilds them from the installed frames, so the master can never drift from what the game actually draws. THREE SHEETS AND NOT ONE, for a measured reason: the generator answers at a fixed width around 1400-1800 px. Four figures across gives each 350-440 px, which carries the 90-110 px of figure height the style block asks for. All eleven on one strip would be ~130 px each and the detail gate would reject every roll. Four, four and three is what fits. And the thing the master is FOR: python3 assets/_master.py --variant br "an ENORMOUS FAT ZOMBIE BRUTE: …" python3 assets/_regen.py br_play br_serve br_fall --tries=4 which writes three restyle batches, seeds their placeholders and declares the stride pair so the gate covers the new body from its first roll. A variant is three generator calls and NO POSE DESIGN AT ALL — the reference specifies eleven poses that already work and the prompt only says who to draw. Measured on the brute test: the restyled stride pair came back at upper 0.148 / legs 0.618 against the master's own 0.157 / 0.565, very slightly better than what it copied. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5c0db0a22e
commit
7056945e62
4 files changed
+96
No files matched your search
@@ -0,0 +1,96 @@
|
||||
# _master.py — build THE MASTER SHEETS: the canonical pose set a new zombie is restyled FROM.
|
||||
#
|
||||
# python3 assets/_master.py # rebuild assets/_sheet_master_*.png from the installed frames
|
||||
#
|
||||
# WHY THIS EXISTS. A new zombie variant used to mean inventing a character AND a pose set, and the
|
||||
# generator answers that by inventing a fresh figure per frame — which is how the walk cycles ended
|
||||
# up as eleven unrelated illustrations. Handing it a FINISHED SHEET and asking for a restyle turns
|
||||
# the job into a transform: the reference does the specifying and the prompt only says who to draw.
|
||||
# Measured, the poses survive it intact (the brute restyle came back at upper 0.148 / legs 0.618
|
||||
# against the master's own 0.157 / 0.565 — very slightly BETTER than what it copied).
|
||||
#
|
||||
# WHY THREE SHEETS AND NOT ONE. The generator answers at a fixed width, around 1400-1800 px. At four
|
||||
# figures across each gets 350-440 px, which carries the 90-110 px of figure height the style block
|
||||
# asks for. All eleven on one strip would be ~130 px each and the detail gate would reject every
|
||||
# roll. Four, four and three is the shape that fits — and it is already the shape of the three
|
||||
# batches that made them.
|
||||
import os, sys
|
||||
from PIL import Image
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
MAGENTA = (255, 0, 255, 255)
|
||||
|
||||
# The master pose set, split the way it is generated. Keep these in step with config.BENCH — that is
|
||||
# the screen these frames are judged on.
|
||||
SHEETS = {
|
||||
'play': ['z_r1', 'z_r2', 'z_r3', 'z_r4'], # stance, shuffle A, shuffle B, forehand
|
||||
'serve': ['z_rbounce', 'z_rtoss', 'z_rserve'], # bounce, toss, strike
|
||||
'fall': ['z_rfall1', 'z_rfall2', 'z_rfall3', 'z_rrise'], # buckle, knees, down, rise
|
||||
}
|
||||
|
||||
def build(name, frames, scale=2, gap=90, pad=70):
|
||||
ims = [Image.open(os.path.join(HERE, f + '.png')).convert('RGBA') for f in frames]
|
||||
ims = [i.resize((i.width * scale, i.height * scale), Image.NEAREST) for i in ims]
|
||||
w = sum(i.width for i in ims) + gap * (len(ims) - 1) + pad * 2
|
||||
h = max(i.height for i in ims) + pad * 2
|
||||
s = Image.new('RGBA', (w, h), MAGENTA)
|
||||
x = pad
|
||||
for i in ims:
|
||||
s.paste(i, (x, h - pad - i.height), i) # feet on one ground line, as the game draws them
|
||||
x += i.width + gap
|
||||
out = os.path.join(HERE, f'_sheet_master_{name}.png')
|
||||
s.save(out)
|
||||
print(f'{os.path.basename(out):26s} {w}x{h} {len(frames)} frames ({", ".join(frames)})')
|
||||
|
||||
|
||||
# ---- A NEW ZOMBIE OFF THE MASTER ------------------------------------------------------------
|
||||
# python3 assets/_master.py --variant br "an ENORMOUS FAT ZOMBIE BRUTE: a huge sagging belly, a
|
||||
# filthy stretched shirt, heavy brown work trousers…"
|
||||
# python3 assets/_regen.py br_play br_serve br_fall --tries=4
|
||||
#
|
||||
# Writes three batches that RESTYLE the master sheets into a new character, seeds their placeholder
|
||||
# PNGs, and declares the stride pair so the gate covers the new body from its first roll. The whole
|
||||
# variant is then three generator calls and no pose design at all — which is the point of having a
|
||||
# master: the reference specifies eleven poses that already work, and the prompt only says who.
|
||||
import json, shutil, collections
|
||||
|
||||
RESTYLE = (
|
||||
"The reference image is a {n}-FRAME SPRITE SHEET of one zombie tennis player. Redraw THE WHOLE "
|
||||
"SHEET as a COMPLETELY DIFFERENT ZOMBIE: {who} "
|
||||
"KEEP ALL {n} POSES EXACTLY AS THEY ARE IN THE REFERENCE — the same stance, the same steps with "
|
||||
"the same leg forward in each, the same swing, the same facing, the same arm and racket "
|
||||
"positions, the same order left to right, the same total height and the same distance from the "
|
||||
"camera. Only the CHARACTER changes; the POSES do not. "
|
||||
"There is NO TENNIS BALL anywhere in any frame — the game draws the ball itself, so the hands and "
|
||||
"the air are empty and the poses are the same as if a ball were there. "
|
||||
"Output exactly {n} figures side by side in ONE horizontal row, all on the same ground line. ")
|
||||
STRIDE_NOTE = ("In particular frames 2 and 3 must stay what they are in the reference: ONE DRAWING "
|
||||
"SHOWN TWICE with only the legs different — everything above the hips identical "
|
||||
"between them, the legs clearly swapped, and the two frames NOT identical copies. ")
|
||||
|
||||
def variant(key, who):
|
||||
bpath = os.path.join(HERE, '_regen_batches.json')
|
||||
B = json.load(open(bpath, encoding='utf-8'), object_pairs_hook=collections.OrderedDict)
|
||||
for part, frames in SHEETS.items():
|
||||
names = ['z_' + key + f[3:] for f in frames] # z_r1 -> z_br1, z_rtoss -> z_brtoss
|
||||
for src, dst in zip(frames, names): # placeholders: the density bar needs a baseline
|
||||
shutil.copy(os.path.join(HERE, src + '.png'), os.path.join(HERE, dst + '.png'))
|
||||
spec = dict(sprites=names, ref_only=frames, newart=True, check='loose', anchor=0,
|
||||
subject=RESTYLE.format(n=len(frames), who=who)
|
||||
+ (STRIDE_NOTE if part == 'play' else ''))
|
||||
if part == 'play': spec['stride'] = [[1, 2]] # the gate covers it from the first roll
|
||||
B[f'{key}_{part}'] = spec
|
||||
print(f'{key}_{part:6s} -> {", ".join(names)}')
|
||||
json.dump(B, open(bpath, 'w', encoding='utf-8'), indent=1, ensure_ascii=False)
|
||||
print(f'\nnow run: python3 assets/_regen.py {key}_play {key}_serve {key}_fall --tries=4')
|
||||
|
||||
if __name__ == '__main__':
|
||||
if '--variant' in sys.argv:
|
||||
i = sys.argv.index('--variant')
|
||||
variant(sys.argv[i + 1], sys.argv[i + 2])
|
||||
sys.exit()
|
||||
for name, frames in SHEETS.items():
|
||||
missing = [f for f in frames if not os.path.exists(os.path.join(HERE, f + '.png'))]
|
||||
if missing:
|
||||
print(f'[{name}] SKIPPED, missing: {", ".join(missing)}'); continue
|
||||
build(name, frames)
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 100 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
Reference in new issue
Block a user