From 4bec0cb09cc048bf2708efcaa0ffca6ff9522a41 Mon Sep 17 00:00:00 2001 From: Oliver Rieger Date: Wed, 12 Aug 2026 12:56:53 +0200 Subject: [PATCH] --variant now emits the whole ladder: nine batches, thirty-one frames MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A variant used to stop at the healthy eleven, which is enough for a gore-mode shambler and not enough for a MATCH opponent — that mode scores you on the BODY, so an opponent with no wounded art would go from untouched to dead with nothing in between. _play / _serve / _fall restyled off the master 11 frames _w1 / _serve_w1 / _fall_w1 arm off, drawn from healthy 10 frames _w2 / _serve_w2 / _fall_w2 headless, drawn from arm off 10 frames Each stage is a restyle of THE STAGE ABOVE IT, in that order, because that chaining is what finally made damage accumulate this morning: the missing arm rides in the picture instead of having to win an argument with it. The helper prints the batches in dependency order for exactly that reason. Every gate the racket zombie's ladder needed is declared automatically — the stride pair on both the healthy shuffle and each wound row, `changed` at 0.10 for an arm and 0.15 for a head (the generator will happily redraw the reference and skip the amputation), and the pose-tolerant kit bounds with the shorts hue kept tight, since hue is the measure that survives a pose change. The wound rows carry three frames, not four: the shuffle doubles as the stance, which is how drawZombie already reads them. And the one-armed serve is generated, not hand-waved — the missing arm IS the tossing arm, so the stump goes up and the racket hand flicks the ball off the face, the way one-armed players actually serve. Co-Authored-By: Claude Opus 5 --- assets/_master.py | 88 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 78 insertions(+), 10 deletions(-) diff --git a/assets/_master.py b/assets/_master.py index a582e32..fb27b86 100644 --- a/assets/_master.py +++ b/assets/_master.py @@ -68,21 +68,89 @@ STRIDE_NOTE = ("In particular frames 2 and 3 must stay what they are in the refe "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. ") + +# ---- THE WOUND LADDER. A match opponent is scored ON ITS BODY: every point won takes a stage off +# it, so a variant that only exists whole would jump from untouched to dead with nothing between. +# Each stage is a restyle of THE STAGE ABOVE IT — that chaining is what finally made damage +# accumulate, because the missing arm then rides in the PICTURE instead of having to win an argument +# with it. The wound rows have no separate stance frame: the shuffle doubles as one, which is how +# drawZombie already reads them. +WOUND = ( + "The reference image is a {n}-FRAME SPRITE SHEET of one zombie tennis player. REDRAW THE WHOLE " + "SHEET with ONE THING CHANGED: {change} " + "EVERYTHING ELSE STAYS EXACTLY AS IT IS IN THE REFERENCE — the same character, the same clothing " + "and its exact shades, the same skin, the same racket, the same {n} poses in the same order, the " + "same leg positions, the same facing, the same build. There is NO TENNIS BALL in any frame. " + "Output exactly {n} figures side by side in ONE horizontal row, all on the same ground line. ") +ARM_OFF = ( + "the arm WITHOUT the racket is COMPLETELY GONE — torn off at the shoulder, leaving a ragged " + "stump gushing blood down that side and the clothing slashed open across the ribs. COUNT THE " + "ARMS: there must be EXACTLY ONE ARM in every single frame, the one holding the racket. Nothing " + "hangs from the other shoulder. A frame with two arms in it is wrong — this is the whole change " + "being asked for, so do not copy the reference's second arm across. Where the reference uses " + "that arm, the stump is thrown out in its place instead. ") +HEADLESS = ( + "the HEAD IS COMPLETELY GONE — no head, no face, no hair anywhere above the shoulders, only a " + "ragged gushing neck stump, so every figure is A CLEAR HEAD SHORTER than in the reference. It is " + "ALREADY missing one arm in the reference and it STAYS missing — it still has only one arm. ") +# ...and because the missing arm IS the tossing arm, a wounded server has to toss the way one-armed +# players really do. The wound changing how it plays is the point, not a detail. +ONE_ARM_SERVE = ( + "Because it has only one arm it can no longer throw the ball up with a free hand: in the TOSS " + "frame the stump is flung upward instead and the racket hand flicks up in front of the face, as " + "a one-armed player tosses off the racket face. ") + 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) + seed = lambda src, dst: shutil.copy(os.path.join(HERE, src + '.png'), os.path.join(HERE, dst + '.png')) + made = [] + + def batch(name, sprites, refs, subject, stride=None, changed=None, newart=False): + for src, dst in zip(refs, sprites): # placeholders: the density bar needs a baseline to beat + seed(src, dst) + spec = dict(sprites=sprites, ref_only=list(refs), check='loose', anchor=0, subject=subject) + spec['newart' if newart else 'state'] = True + if stride: spec['stride'] = stride + if changed: + spec['changed'] = changed + # pose-varied sheets move redness and shorts BRIGHTNESS on their own (a body face down + # shows a fraction of the shirt it shows standing); the shorts HUE is the measure that + # survives a pose change, so it stays the tight one. See the note in _regen.kit. + spec['palette_ref'] = refs[0] + spec['palette_tol'] = [26, 14, 99, 5] + B[name] = spec + made.append(name) + print(f'{name:16s} <- {refs[0]}… {", ".join(sprites)}') + + K = 'z_' + key + # --- the whole body, restyled off the master 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)}') + batch(f'{key}_{part}', [K + f[3:] for f in frames], frames, + RESTYLE.format(n=len(frames), who=who) + (STRIDE_NOTE if part == 'play' else ''), + stride=[[1, 2]] if part == 'play' else None, newart=True) + + # --- the two wound stages, each chained off the one above it + WALK = ['2', '3', '4'] # shuffle A, shuffle B, forehand — the wound rows' three frames + SERVE = ['bounce', 'toss', 'serve'] + FALL = ['fall1', 'fall2', 'fall3', 'rise'] + for st, change, extra, moved in ((1, ARM_OFF, ONE_ARM_SERVE, 0.10), (2, HEADLESS, '', 0.15)): + up = K + (f'w{st - 1}' if st > 1 else '') # the stage this one is drawn from + walk_src = [up + s for s in (['', 'b', 's'] if st > 1 else WALK)] + batch(f'{key}_w{st}', [f'{K}w{st}', f'{K}w{st}b', f'{K}w{st}s'], walk_src, + WOUND.format(n=3, change=change) + + 'In particular frames 1 and 2 are ONE DRAWING SHOWN TWICE with only the legs ' + 'different — identical above the hips, legs clearly swapped, and NOT identical copies. ', + stride=[[0, 1]], changed=moved) + batch(f'{key}_serve_w{st}', [f'{K}w{st}' + s for s in SERVE], [up + s for s in SERVE], + WOUND.format(n=3, change=change) + extra, changed=moved) + batch(f'{key}_fall_w{st}', [f'{K}w{st}' + s for s in FALL], [up + s for s in FALL], + WOUND.format(n=4, change=change), changed=moved) + 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') + print(f'\n{len(made)} batches, 31 frames. Run them IN THIS ORDER — each wound stage is drawn ' + f'from the stage above it, so an earlier one must be installed first:\n\n' + f' python3 assets/_regen.py {" ".join(made)} --tries=4\n') if __name__ == '__main__': if '--variant' in sys.argv: