The opponents come back to life: the friendly refresh, open string beds, and a steadier chase
Oliver's batch, delivered in dependency order. "The opponents look pretty harsh, almost already pre-zombie" — so BOTH tennis opponents got a new identity: warm skin, human eyes, an easy near-smile, rolled anchor-first so every later sheet inherits the face. The male's bounce pair also ADOPTS the female's palm-out posture (his old crouched receiver stance was the wrong pose for bouncing a ball — her better one is now the shared convention), and the pre-serve ball was re-measured onto the drawn palms: BOUNCE_IDLE's oDx 0.45 -> 0.24 / oApex 0.55 -> 0.72 for his close carry, with her wider reach riding TOURS (oDx 0.48 / oApex 0.73) the same way the player's pair already does. "parts of the body as well as the inner of the racket are solid" — the beds were the cutter's own doing: _fine.key repaints enclosed magenta pale grey (protection against accidental magenta strings), which defeated the beds we now draw magenta ON PURPOSE. key()/main() grow a bed=True switch (the famroll convention, used by _runroll and the refresh cuts); the run cycles' beds went from ~70% to ~44% opaque — strings, not paddles. Enclosed pockets of a roll's off-spec background (one sheet came back on blue) are scrubbed by corner-colour distance too. Both RUN CYCLES re-rolled off the friendly anchors: his landed 8 clean frames (racket +0.05..+0.30, one side); hers took four rolls — the gates caught a white background, a crossing racket, near-dup neighbours — and the keeper drew a 10-frame cycle, from which the best 8 were chosen by maximising the minimum adjacent stride (29.7%, smoother than his 22.7). o_run7/8 return to the SPR list. AND THE CHASE STOPS DITHERING (Oliver: "switching between a left and right movement increasingly faster the nearer the ball comes"): intercept() re-solves every frame and the earliest-reachable point jumps around its solution as the flight shortens, flipping his run direction each time. The CPU now commits to its stored target unless a fresh solve moves it by more than 0.35 m. SPRV 121, main.js v244, 66 tests, every opponent frame verified 200 in the live game. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
No files matched your search
@@ -17,7 +17,7 @@ KEY_D2 = 60 * 60
|
||||
MIN_GAP = 12
|
||||
MIN_BLOB = 60
|
||||
|
||||
def key(im, defringe=2):
|
||||
def key(im, defringe=2, bed=False):
|
||||
a = np.array(im.convert('RGBA'))
|
||||
bg = a[0, 0, :3].astype(int)
|
||||
d = ((a[..., :3].astype(int) - bg) ** 2).sum(2)
|
||||
@@ -39,7 +39,14 @@ def key(im, defringe=2):
|
||||
# ...and what that leaves — magenta INSIDE a body — is repainted as pale grey rather than kept,
|
||||
# because bright pink strings are as wrong as no strings. Luminance is preserved, so the drawn
|
||||
# crosshatch survives as crosshatch; only the hue goes.
|
||||
# UNLESS the sheet MEANS its magenta beds (`bed=True` — the famroll convention, adopted by the
|
||||
# opponent refresh and _runroll): then enclosed magenta is the string bed and CUTS TO
|
||||
# TRANSPARENCY, exactly as _zsheet already treats it. The grey repaint is what shipped the
|
||||
# opponents holding solid white paddles.
|
||||
inner = magenta & ~outside
|
||||
if bed:
|
||||
a[inner] = 0
|
||||
inner = np.zeros_like(inner)
|
||||
if inner.any():
|
||||
lum = a[..., :3].mean(2)
|
||||
a[inner, 0] = a[inner, 1] = a[inner, 2] = np.clip(lum[inner] * 0.5 + 115, 0, 255).astype('uint8')
|
||||
@@ -152,8 +159,8 @@ def frames(a, cuts, want=0):
|
||||
out = list(zip(edges, edges[1:]))
|
||||
return out
|
||||
|
||||
def main(sheet, outs, target_h, cuts, anchor, defringe=2):
|
||||
a = key(Image.open(sheet), defringe)
|
||||
def main(sheet, outs, target_h, cuts, anchor, defringe=2, bed=False):
|
||||
a = key(Image.open(sheet), defringe, bed)
|
||||
b = block_size(a)
|
||||
crops = []
|
||||
spans = frames(a, cuts, len(outs))
|
||||
|
||||
|
Before Width: | Height: | Size: 224 KiB After Width: | Height: | Size: 192 KiB |
|
Before Width: | Height: | Size: 181 KiB After Width: | Height: | Size: 169 KiB |
@@ -408,7 +408,8 @@
|
||||
"anchor": 0,
|
||||
"check": "loose",
|
||||
"subject": "Three frames of the same tennis opponent, drawn as coarse low-resolution pixel art, side by side. FRAME 1: a plain standing frame for scale, exactly like the reference. FRAME 2 \u2014 THE BACKSWING: he has turned his shoulders and taken the racket BACK BEHIND HIS RIGHT HIP, low and well behind his body, arm coiled, racket head pointing backwards away from the net, weight on his back foot. He has NOT hit the ball yet. FRAME 3 \u2014 THE FOLLOW-THROUGH: the swing is FINISHED. His arm has swept all the way ACROSS HIS CHEST to the OTHER SIDE of his body and the racket head is now UP AND OVER HIS LEFT SHOULDER, near his left ear, his chest turned to face the net, weight on his front foot. FRAMES 2 AND 3 MUST LOOK COMPLETELY DIFFERENT FROM EACH OTHER \u2014 in frame 2 the racket is BEHIND him on his RIGHT, in frame 3 it is HIGH on his LEFT. This contrast is the whole point of the pair. Every figure is the SAME man as the reference and the SAME size: RED polo shirt, DARK CHARCOAL GREY shorts, RED cloth headband, blond hair, white shoes, seen from the FRONT. Same face, same colours, same proportions. His headband is RED, never white or cream. In both swing frames he stays UPRIGHT at his full standing height \u2014 head at the same level as the standing frame, back straight, knees only slightly bent, never crouching or lunging low. The whole racket is drawn and fully inside the canvas. His shoes are WHITE with RED socks and his shirt is a BRIGHT PURE RED, exactly like the reference \u2014 not brick, not brown, not orange.",
|
||||
"palette_ref": "o_idle1"
|
||||
"palette_ref": "o_idle1",
|
||||
"pose": true
|
||||
},
|
||||
"opponent_idle": {
|
||||
"sprites": [
|
||||
|
||||
@@ -103,7 +103,9 @@ def prompt(k):
|
||||
'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'
|
||||
'WRONG. THE INSIDE OF EVERY RACKET HEAD IS EXACTLY THE BACKGROUND MAGENTA #FF00FF — never '
|
||||
'white, never grey, never solid: it cuts to transparency, the same rule the family sheets '
|
||||
'follow (a white-filled bed shipped as a solid paddle once — that is what this line prevents).\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. THE FIGURES '
|
||||
'NEVER TOUCH OR OVERLAP: leave a clear band of empty magenta between every pair of '
|
||||
@@ -184,7 +186,7 @@ def cut(k, install=False, n=0):
|
||||
outdir = HERE if install else os.path.join(SCRATCH, f'runcut_{k}')
|
||||
os.makedirs(outdir, exist_ok=True)
|
||||
outs = [os.path.join(outdir, f'{pre}{i}{suf}.png') for i in range(1, got + 1)]
|
||||
_fine.main(sheet, outs, TARGET_H, [], -1)
|
||||
_fine.main(sheet, outs, TARGET_H, [], -1, bed=True)
|
||||
# ANCHOR EVERY FRAME ON ITS FEET. drawChar centres a frame by its total width, and the racket's
|
||||
# reach varies wildly across a cycle (the cut female frames measured -28..+15 px of feet drift
|
||||
# from frame centre) — so the BODY teleported sideways every frame, which is exactly Oliver's
|
||||
|
||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |