diff --git a/assets/_master.py b/assets/_master.py new file mode 100644 index 0000000..a582e32 --- /dev/null +++ b/assets/_master.py @@ -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) diff --git a/assets/_sheet_master_fall.png b/assets/_sheet_master_fall.png new file mode 100644 index 0000000..136cd57 Binary files /dev/null and b/assets/_sheet_master_fall.png differ diff --git a/assets/_sheet_master_play.png b/assets/_sheet_master_play.png new file mode 100644 index 0000000..a261bc9 Binary files /dev/null and b/assets/_sheet_master_play.png differ diff --git a/assets/_sheet_master_serve.png b/assets/_sheet_master_serve.png new file mode 100644 index 0000000..c01e0a7 Binary files /dev/null and b/assets/_sheet_master_serve.png differ