12 Commits
Author SHA1 Message Date
wuwiandClaude Fable 5 ad624d281c Her legs finally swap: the face-transplant cycle ships, and the prompt rules are law now
The female run took the whole evening because the model would not draw her
RUNNING: roll after roll came back with the same leg forward in every frame
— amplitude wiggle, no gait ("she never changes her leg... floating while
strobing"). The set that ships is the FACE TRANSPLANT: her old cycle's poses
(which always alternated properly — swaps at positions 2 and 6, 78%/74%,
where a runner's legs actually cross) redrawn with only the friendly face
and open beds changed. The model returned that roll on a painted CHECKERBOARD
— it has no concept of transparency, the word alone summons the pattern —
and the figures were custom-keyed off it: light checker blanket, dark
checker border-flood only (the dark cell is nearly her skirt colour; the
first blanket pass shredded the pleats), verified whole over a light ground.

Oliver gated every strip by eye. The alternatives lost on their own terms:
hex-only prompts obeyed the background but drew her dainty (max 44%, no
swap); the explicit leg-swap clause got alternation but clustered it in the
back half (step-step-skip). His pick: the transplant.

The lesson is in PROJECT.md now, next to the bed flag: NEVER say
"transparent" to the model — name one flat hex — and never freehand
background prose in one-off scripts; import _regen.py's CANVAS block, which
has said it correctly all along. SPRV 123, main.js v246, all 14 run frames
verified 200.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 14:28:29 +02:00
wuwiandClaude Fable 5 0f9738f5f2 The run cycles get their camera back, and the racket finally holds still
Oliver's verdict on the friendly cycles: wrong on two axes. The new rolls
were PURE PROFILE — "what we actually need is running slightly towards or
away from the player... this was better before although the art was not
coherent" — and the racket re-imagined itself every frame: "it's now jitter
town, strobe like because of the rapidly changing rackets."

Take two runs on TWO references, which is the recipe that has worked all
day: image 1 the friendly identity (anchor frames), image 2 the OLD run row
pulled from git history as the CAMERA law — three-quarter front, chest to
the viewer, exactly the orientation the far baseline needs — with the prompt
pinning the racket ("as if copy-pasted between figures; only the LEGS run").
One-row layouts kept coming back interlocked (nine figures, one 3141px blob,
uncuttable), so the ask became a 2x4 GRID — separation the model actually
delivers, re-laid locally. His roll drew 12 clean figures; the smoothest
8-cycle was chosen by maximising the weakest adjacent stride: 72.7%, against
20-30% on every earlier roll. Hers landed exactly 8. Oliver gated both
strips by eye before install — the automated gates sat this one out on his
call. SPRV 122, main.js v245, all 16 frames verified 200.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 13:39:12 +02:00
wuwiandClaude Fable 5 80bf6c4cee 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>
2026-08-14 13:16:55 +02:00
wuwiandClaude Fable 5 c6404c6032 The opponent is ONE man now: serve re-posed, run re-rolled, face flicker gone
Oliver: "the serving and running of the opponent during normal tennis still
feels odd. which spritesheet was used?" Two sheets, two defects. The run
(o_run1-8, _refcheck/run_o.png via _runroll.py) FLICKERED IDENTITY — frames
alternated a tanned living face with a grey red-eyed one, because the
identity reference bakes in o_toss/o_smash and THOSE were the second defect:
the serve pair came from the legacy opponent_toss regen batch in a chibi
register (big head, heavy outline) that matched nothing around it.

Fixed in dependency order. The serve pair re-rolled pose-style (o_idle1 the
identity anchor): the batch's kit gate rejected all three tries but try 3
was right by eye — the "bright" channel it tripped on is the white-filled
string bed the run frames already carry — so it was installed on the montage
verdict, which is the pipeline's own rule. Then _pose_ref_run_o recomposed
off the cleaned frames and the run re-rolled: SIX frames, one face, one
racket side (+0.18..+0.24), real strides. The model drew 6 and a clean 6
beats a fought-for 8 (the player's cycle is already 6) — o_run7/8 deleted,
the SPR list trimmed, render's run8/run6/run4 probe does the rest. All six
verified 200 in the live game. The women's cycle (a floating shoe behind
her in every frame) is still rolling and lands separately. SPRV 118.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 12:34:30 +02:00
wuwiandClaude Fable 5 e8b5c1905f The run cycles anchor on their feet, and stride at a real cadence
Oliver: 'the motion seems very flashy and unnatural'. Two causes, both
measured: drawChar centres a frame by its total width while the racket's
reach varies across the cycle, so the BODY teleported sideways up to 28
source px on a frame swap (the female player was worst) — every frame is now
padded until its lower-third centroid sits at the frame centre (residuals
±0.2 px, the normalisation lives in _runroll.py's cut). And the hardcoded
4.5 cycles/s was tuned on 2-4 near-duplicate frames; on eight real phases it
was a strobe — rolled sets stride at TUNE.runHz (2.4), old art keeps 4.5.

In-pane: body wobble down from ~36 device px per swap to 3.2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 18:53:22 +02:00
wuwiandClaude Fable 5 99a87115b3 Four real run cycles, rolled one-call-one-row through a new _runroll.py
The old run frames were generated one at a time and it showed: 91/110/92/98 px
of body-width drift, duplicated poses drawn differently, no racket. Each
character (player m/f 6 frames, opponent m/f 8) now comes from ONE 4K call
anchored on a composed identity reference of its settled frames — the broken
run art stayed out of the reference on purpose. Stride-gated, cut with _fine,
judged by eye per roll. drawChar probes run8/run6/run4/run2.

Side effect: the male opponent's old run art faced LEFT against the
art-faces-RIGHT convention — he had been moonwalking right since install.
Fixed by drawing the new rows to the convention.

Pipeline lessons recorded in PROJECT.md (the model negotiates the count; a
back view has no direction; the racket clause goes early and loud).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 18:17:16 +02:00
wuwiandClaude Opus 5 494bab1b69 Stop the opponent shrinking, skating and sprinting on the spot
The "smaller when swinging or tossing" was an anchor bug, not art. _fine.py
scales a whole sheet so the --anchor frame is 224 px, and drawChar draws every
sprite at img.height/SPRITE_H — so anchoring opponent_play on the TOSS, with
arm and ball above the head, made 224 px cover the raised arm and shrank every
body on that sheet. o_swing1 was 162 px against a 224 px standing pose. Each
opponent batch now leads with a standing `ref` frame, which _fine.py anchors on
and _regen.py discards. Correctly anchored the toss is 278 px and the man is
full size; the swing is 221.

The receiver no longer slides 0.12 m side to side while waiting on a serve —
sliding a sprite sideways reads as skating, and the split-step swap to the run
frames (removed earlier) had already been the other half of that.

The idle bounce ball had one hardcoded offset for both men. They reach to
opposite sides of the screen — the player is drawn from behind, the opponent
from the front — so the opponent's ball sat out by his racket. dx and apex are
per-man now, and o_bounce/o_bounce2 were redrawn with a patting hand to put it
under.

Also: the opponent wears a RED headband, and a prompt of mine that said "white"
had quietly repainted it on six frames.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 11:37:16 +02:00
wuwiandClaude Opus 5 cd1c7a0cd8 Encode the splatter bed too, and give the opponent his racket back
zombie-mode: 5.9 MB of 24-bit WAV down to 287 KB. Its master is 982597 frames
at 48 kHz, so loopEnd is the un-round 20.47077 — the decoder hands back
20.4787, and those 7.9 ms of AAC padding would tick once a loop. Nothing in
assets/audio/ is a multi-megabyte WAV any more.

o_run1/o_run2 had no racket in either hand. The cause was the batch prompt:
opponent_run never said "racket", and its reference sheet was built from the
racket-less frames it was replacing, so there was nothing to copy one from. It
is a ref_only batch off o_idle1 now — same man, front-facing, racket in his
right hand — with the subject spelling out that he still carries it mid-stride.
The stride reads as well as the old one did.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 10:55:18 +02:00
wuwiandClaude Opus 5 ff6515670f Redraw the player and the opponent
Both full sets through the same pipeline: stances and ball-bounce, serve toss,
smash and both swing frames, the four-frame run, and the five-frame death. The
opponent's ten frames with them. Inner-edge density roughly doubles across the
board — the serve frames go 0.22-0.32 to 0.41-0.54.

_regen.py retries a rejected batch itself now (default three attempts). A
rejection is usually just a bad roll — two figures drawn close enough that the
slicer sees one frame, or a limb off the canvas — and re-rolling fixes it without
me sitting in the loop.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 21:47:29 +02:00
wuwiandClaude Opus 5 49d391dc64 Make it readable: 2x sprites, a flatter camera, honest hit timing
The umpire is situational again. Each line is tied to the thing that justifies
it — a new variant appearing, the court filling, one crossing your baseline, a
game lost, a horde, a changeover — behind one shared cooldown, so several
triggers at once still yield one remark. The random timer pool is gone, the
interval went 26s to 55s, and ump_out_of_order, ump_let and ump_no_appeal are
deleted.

"Visually the ball is not yet at the player but somehow the player already is
hit" was real and measurable. The hostile-ball hit test reused the parry's
hypot(dx, dz*0.8), and weighting depth shrinks it: contact fired while the ball
was still 0.68 m up-court and drawn up around his head. It has its own
unweighted test now, and contact lands at 0.25 m with the ball on his chest.
The parry keeps its weighting — a racket sweep really is wider than it is deep.

On "everything is a tad too small": measured first, because higher-resolution
sprites are only half the answer. At RES 3 the player rasterises into 170
device pixels from a 112px source — upscaled 1.5x — while a far shambler is
downscaled to 78. So near bodies needed resolution and far bodies needed the
camera.

Camera: pushed back and lengthened (camZ 42->48, camF 580->760) to flatten the
perspective so distant bodies grow relative to near ones rather than everything
zooming, camY raised for a steeper look-down, horizon re-centred. Far shambler
26->30px, boss 39->45px, player unchanged, court in the same band.

Sprites: the original sheets were still in the repo at full resolution, so
nothing needed regenerating — IDLE_TARGET_H went to 224 and every sheet was
re-sliced. The sheet-to-output mapping was never recorded, so it was recovered
by matching sliced frames to existing assets by image signature: 58 came back
from sheets, the 29 without a confident match were 2x-upscaled to stay on the
shared scale, since a sprite off that scale renders at half height.

Tennis untouched: 50 hits / 0 whiffs. 45-game soak clean, all four chapters.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 16:23:04 +02:00
wuwiandClaude Fable 5 9e6e686cff Landing rings, ball-boy fetching, 2x sprite density, assist flicker fix
- white ring shows where the incoming ball will land; yellow/red ring shows
  where your shot will land (red = going out) — Mario-Tennis-style feedback
- ball boys wait at the net posts, fetch every dead ball, and trot home
- canvas renders at 2x density, all sprites re-sliced at 112px; run/swing
  sheets regenerated in the finer style
- assist: 0.35m deadzone + facing hysteresis kills the left/right flicker;
  player reach 1.5 -> 1.7

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 13:56:18 +02:00
wuwi 7bedb2d215 initial commit 2026-07-11 09:23:01 +02:00