Commit Graph
17 Commits
Author SHA1 Message Date
wuwiandClaude Opus 5 a58e8b18aa Colour is arithmetic: match each frame onto the anchor instead of asking for it
The character-as-image fix landed and did NOT close the gap, which is the answer
I promised to report rather than paper over. The brute's serve sheet came back
with his build, his garments and his DARK TONES PIXEL-IDENTICAL to the anchor —
(56,56,32) both — and his big pale shirt two shades brighter, (200,200,184)
against (176,176,152). The model was looking straight at the character. It held
identity perfectly and re-lit the largest flat area anyway, because every call
re-renders and re-rendering re-lights.

No prompt fixes that. Six gates spent a day trying to make a generator do
arithmetic.

So the arithmetic happens here now. Pair each of a frame's dominant colours with
its nearest anchor colour, fit one gain+offset per channel across those pairs,
apply it to every pixel. Pairs further apart than NEW_COLOUR are dropped from the
FIT — colours the anchor genuinely has not got, i.e. blood on a fresh wound — but
they still RECEIVE the transform, so a wound stays the right red relative to the
body it is on. The gain is clamped so one odd pair cannot posterise a frame.

PROVEN ON THE REJECTED FRAME BEFORE SPENDING A SINGLE CALL, which is the
discipline I skipped all day: the frame that measured 20.0 away passes after
correction, its shirt pulled from (200,200,184) to (176,176,160).

And the order is now CORRECT, THEN CHECK — the palette gate is a safety net for
what arithmetic could not fix, not a tripwire for what it can.

The model this leaves:
  the generator, given references  ->  identity, pose, composition
  the pipeline, deterministically  ->  exact colour
  the gates                        ->  layout accidents only

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 14:46:56 +02:00
wuwiandClaude Opus 5 0e92c72457 ONE palette gate replaces four feature-picked ones
Oliver, on the brute's fall frames: "now the fall zombie has a different skin
tone.. man this is all so tiresome." Both halves were right. The skin IS twenty
points lighter across that sheet, and the tiresome part is mine: I had been
adding one hand-picked gate per drift I discovered — redness, then hue, then
`changed`, then no-grids — which is a loop with no end, because naming features
is not the same as comparing pictures.

`kit` names four: shirt redness, shorts brightness, shorts hue, near-white
fraction. Every character breaks a different one. The brute's shirt has no red
in it at all, so its reference redness was 0 and blood scored 70-100 against a
tolerance meant for a garment. A body face down shows a fraction of the shirt it
shows standing, so two of the four move with POSE. And nothing in that list ever
measured SKIN, which is exactly how a brute's flesh drifted across a whole sheet
with six gates green.

The palette gate asks the only question that generalises: are the big colour
areas of this frame the same colours as the anchor's? Top SIX by area — the
garments and the skin. Proportions ignored, so a pose change is invisible; blood
is never in the top six, so a fresh wound cannot look like drift.

CALIBRATED BEFORE IT WAS WIRED IN, which is the discipline I skipped all day.
Frames known to be right score 0.0-8.5; the drifts we could actually see score
12.0-15.9. The threshold is 10, in the gap. Three formulations were measured and
the two that overlapped were thrown away.

And every sheet of a variant is now anchored to that variant's FIRST sheet, not
to the stage above it. Chaining let a legal hop compound into a drift nobody
authorised — the shorts walking -9 to -19 down the wound ladder was that, and so
was this.

Its first verdict on the existing brute: br_play is the character, and every
other sheet sits 15 to 48 away from it. It would have caught all of this on the
first roll.

Also in: the GRID guard from the same session — the valley-cut rescue would slice
a 2x2 sheet into four frames of a torso above someone else's boots, turning a
loud failure into a silent one. Asked per cut PIECE, because a body is vertically
continuous and a piece with a gap in it caught two of them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 13:45:41 +02:00
wuwiandClaude Opus 5 5c0db0a22e The opponent is complete: 3 wound stages x 11 states, and a gate for wounds that never happened
The serve and the fall now exist at every wound stage, so a one-armed opponent
still serves and a headless one still goes down, instead of reverting to a
whole body to do it. 33 frames, no gaps.

THE BALL IS OUT OF THE SERVE SPRITES. Oliver: it was painted into three frames
and the game draws the ball itself, so every one of them would have ridden
along at the wrong place, the wrong time and in the wrong number. Hands open
and empty now, poses unchanged as if a ball were there.

THE FOURTH GATE, and the one nothing else could see: "copy this exactly, change
ONE thing" loses when the change is a REMOVAL. Taking a head off worked every
time — it is dramatic. Taking an ARM off mostly did not: five of seven wound
frames came back as the reference faithfully redrawn, 0.02-0.05 of their own
silhouette different, and the ladder quietly HEALED the body the score had just
maimed. Frame count, background, clipping, detail, kit and stride all pass that
sheet. A batch now declares how far it must move from the sheet it was drawn
from — a copy scores 0.02-0.05, a real amputation 0.12-0.38, a decapitation
0.27-0.75 — and the whole set now measures 0.18-0.65.

AND THE BACKGROUND RULE MOVED TO THE FRONT OF EVERY PROMPT. Six rejections in
eleven rolls were "background is not magenta", and the generator was not
glitching: it was painting the zombies ON A TENNIS COURT. The chained restyle
subjects are long, and the magenta instruction sat behind three hundred words
of "keep everything exactly as the reference", where it stopped being honoured.
Same sentence, said before anything can bury it.

One design consequence kept rather than papered over: the missing arm IS the
tossing arm, so a wounded opponent tosses off the racket face the way one-armed
players actually do. The wound changes how he plays, which is what slice 3 has
been asking for.

Known and NOT fixed: the shorts hue drifts from -9 to -19 down the ladder. Each
stage is measured against the one before it, so a legal 4-point hop three times
over is a 14-point total nobody authorised. The fix is to anchor every stage to
the healthy frame with a total budget instead of a per-hop one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 12:45:26 +02:00
wuwiandClaude Opus 5 ef88429887 The kit gate learns hue, because a greyscale check cannot see grey turning blue
Oliver: "look at the headless men — the trouser color changed." He was right and
every number in kit() said he was not. Brightness sat at 85 against the healthy
frame's 89, comfortably inside tolerance, while the shorts' blue-minus-red
walked from a warm charcoal -8.7 to a cold +0.2 across the wound stages. All
three existing measures are greyscale or red-channel; none of them can see a
grey going blue, which is most of what "the colour changed" ever means here.

Fourth number: the shorts' hue. It immediately fails the headless men AND finds
a second drift in the wounded women that the brightness test had passed.

The tolerance default went to four entries deliberately — kit_off zips got/ref/
tol, so a three-long tuple would have SKIPPED the new check rather than failed
it. That is the same shape of silent no-op as the read-only floodfill.

Also: DAMAGE NOW ACCUMULATES. Stage 2 said only "headless", so the arm torn off
at stage 1 came back — in a mode where the score IS the body, taking a point off
it made the body look healthier. Stage 2 is now headless AND still one-armed.

And the stride rule got its counterweight: "copy it exactly, only the legs
change" has a second failure mode where the model copies it exactly INCLUDING
the legs and returns the same picture twice (upper 0.017, legs 0.022 — a body
sliding along with frozen feet, the very thing being fixed). Both halves are
stated now: identical above the hips, obviously different below.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 10:35:29 +02:00
wuwiandClaude Opus 5 1c405f045f THE STRIDE GATE: a walk cycle is one drawing with the legs moved, and now something checks
Oliver, on the sprites, again: "when people on a tennis court walk or run their
legs move... this pattern is almost always failing." It has failed repeatedly
because NOTHING IN THE PIPELINE MEASURED IT. Frame count, magenta background,
clipped edges and detail density all pass cleanly on two unrelated
illustrations of the same character, which is exactly what the generator
produces when asked for "the same frame with the other leg forward".

Three numbers per declared pair, silhouette only, on the feet-aligned centred
overlay drawStatic actually draws:
  HGT  height ratio — off 1.00 means the body grows and shrinks once per step
  TOP  difference ABOVE the hip — near zero for a real pair: same man, same camera
  LEG  difference BELOW it — this is the step
Calibrated on the pairs that read (brute wound lurch 1.00/0.000/0.638, shambler
1.00/0.070/0.520) against the ones that do not (TOP 0.47-0.85, or LEG 0.09).
Opt-in per batch via `stride: [[i, j]]`, because the player's four-frame RUN
sits at TOP 0.23 and is right — a run swings the arms too.

`--audit` runs the same gate over the sprites ALREADY INSTALLED and exits
non-zero on a failure: a gate that only fires on new rolls says nothing about
the art already in the game, which is where this was noticed. Five of the six
racket pairs fail it today.

And the kit gate, which already existed and was never switched on here: Oliver
spotted the headless men's trousers had changed colour. Measured — the headless
set's near-white fraction DOUBLES (9.5 -> 19.5) where losing a head only
accounts for ~1.3 of that, and the wounded swing drifted the other way (shorts
60.5 against its own stage's 85). palette_ref + a per-batch tolerance now catch
all three drifted sets and pass the one that looked right.

Fixed while in there, mine from the last commit: the wounded stride stepped off
walkT, a wall clock, while the healthy one steps off ground covered — the exact
foot-slide this project banned for the heavy bodies.

No art regenerated yet: the gate and the audit come first, so a re-roll can be
told from a lucky one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 10:09:31 +02:00
wuwiandClaude Opus 5 c89d4a3ff9 Three modes, picked up front — and CLASSIC TENNIS is one of them
The setup screen picks WHAT before WHO and WHERE. Three rows out of a table,
in the court row's own markup:

CLASSIC TENNIS is not the opener with the turn switched off and left there —
it is the mode the other two are built on, so it gets real match scoring: six
games with a two-game lead is a set, best of TUNE.classicSets is the match,
the board shows the sets column (hidden until now outside splatter) and the
bar under it says which set this is. The CPU stops rubber-banding and flubs
less: aiFlubLoss exists to get a new player PAST the opener, and a mode whose
whole promise is a real match cannot be won by an opponent that falls apart
the moment he goes ahead. No tiebreak — 6-6 plays on to 8-6. Marked ponytail.

That needed a second ENDING, the one with no corpse in it: G.done, the same
summary and gated tap as the death screen, its own words (GAME, SET, MATCH. /
BEATEN.) and the man still standing. The three endings now share one block
rather than three copies of it.

Caught by the first classic test: stats banked only the FIRST classic run of a
session. `banked` was cleared by the next KILL, and a classic match has no
kills in it at all — every later match was swallowed by the double-bank guard.
beginRun() is the honest reset and restartRun calls it.

ZOMBIE TENNIS from the menu is one line; the back room keeps its door.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 09:27:49 +02:00
wuwiandClaude Fable 5 04fe4098a7 Pin the sprite generator to Nano Banana Pro on OpenRouter
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-10 21:38:02 +02:00
wuwiandClaude Opus 5 14a1e5d261 Let the tour be a choice, and give her a body of her own
The court screen picks WHO as well as WHERE now: two tiles above the court
rows, men's or women's singles, remembered like the court is. No new stage —
three rows and a pair of buttons already fill a phone, so the tiles go side by
side and the tap count from splash to play does not change.

Everything downstream is one line: drawChar already built every frame name off
a single prefix, so SK() appends '_f' when the women's tour is picked and falls
straight through to the men's frame for anything without a twin. The horde, the
officials and the ball boys are shared — they are staff, not the draw — but the
turned CPU is not: a male corpse opposite a female player reads as a bug.

Her frames came back as a different woman in every batch — blonde in one,
auburn in the next, greyscale line art in a third — because each was anchored on
the MAN whose pose it was. "newart" repaints the character and "state" is welded
to the corpse palette, so neither could say "the same living woman, a new pose".
That mode now exists, and every batch anchors on her own settled frame.

The death is drawn to frame 3 and recoloured after it. The generator will not
draw a dead woman in any wording worth using; frames 4 and 5 survive that
because by then she is flat and then a heap of cloth, so they are the men's
drawing with the blue shorts painted into her white (see assets/_die_f.py). The
collapse still falls 224/159/117/103/43 straight through the handoff.

Her first bounce frames reached to the shoulder, which is a catching pose, not
patting a ball — redrawn low rather than bending BOUNCE_IDLE to fit a wrong
drawing, so both tours share one apex and the ball meets her palm.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 12:22:21 +02:00
wuwiandClaude Opus 5 4cda6bde76 Make the bosses move, and put six of them and three new bodies on court
A chapter boss had no AI at all: it matched no branch in the variant chain and
fell through to the crowd's aimless wobble, so "the boss AI owns its x" was a
comment describing code that did not exist. Movement is now data on the BOSS_ART
row -- pace, shadow, surge, charge, bounce -- and bossAI is the one place it
happens. Every behaviour field is optional, so a row carrying only art behaves
exactly as a boss did before.

Three earlier drafts of those styles failed the same way: written as "walk toward
the player's x", which in splatter means arrive in four seconds and stand still,
because the player is rooted in pretoss for nearly all of a boss fight. Every
style now moves under its own power and only biases toward him. Measured over a
simulated minute each: 50-136 m travelled, none static.

Three new designs -- THE HEAD COACH (a 5-shot drill 0.14s apart), THE PHYSIO
(charges, and withdraws less than it charges, so it reaches you at 38s and that
is the fight's clock), THE SPONSOR MASCOT (gap 0, so the whole wall of five
leaves in ONE frame and is dodged rather than parried). Harder to kill: 16 -> 20
hp, 9 -> 10 per chapter, plus per-design multipliers.

Three new bodies: the ball boy (3 hp, 2 skins, from game 3), the bouncer (6/3,
game 8) and the roller man (9/4, game 15), each with a 4-frame walk and a drawn
two-frame lurch for every wound stage.

Three wound rules became one. The brute had a hardcoded stage array, everything
else a single skin behind 'abd'.includes(...), and the boss a fraction of hpMax.
The boss's rule reproduces the other two exactly -- verified: the brute still
draws z_gw/gw2/gw3/gw4 at hp 4/3/2/1 -- and keeps working when hp is not
one-per-skin, which all three new bodies need. So a multi-hit body is now a row
in config.ZWOUND and no code. bleed() reads the same call, which also gave the
steward the trickle his wound skin had always implied.

Fixes a real crash on the way past: killPlayer empties shots[] while the shots
loop is mid-iteration, so every remaining index read undefined and the frame
threw. The mascot's five-at-once volley makes it easy to hit; the shipped 3-shot
volley could always have hit it.

_regen.py gains two style blocks and a check tier. One constant used to say
"keep every colour exactly as the reference" -- right for a remaster, and it
fights every word of the subject otherwise. newart is for a new character, state
for the same character wounded or mid-throw; using newart for the wound sheets
licensed a repaint and got one, five came back with living tan skin. And the
detail gate ratchets against the file it replaces, so check: any exists for
re-rolling already-fine art to fix something the gate cannot see, such as colour.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 11:45:35 +02:00
wuwiandClaude Opus 5 af30db2ff3 Make the regen pipeline check the kit, and fix the swing and toss
Every roll drifts the palette and nothing measured it: a white headband on six
frames, pale grey shorts on the toss twice, brick-red shirts and brown shoes on
the swing all sailed past --tries. check: "same"/"loose" skips colour_match, and
colour_match is a global mean/sd fit far too weak to pull brick-red back to red
— a nearest-colour palette lock is worse still, it drags the duller reds into
the racket's browns.

So _regen.py measures three numbers and a batch opts in with palette_ref:
redness (r minus mean(g,b) over saturated warm pixels — brightness alone cannot
tell brick-red from red), shorts brightness (charcoal 61 vs the drift's 83), and
the near-white fraction that is the shoes and socks (brown drops it to 0.9%,
pale shorts push it to 7.9%, correct frames sit at 2.8%). Frames known to be
right cluster tightly on all three; every drifted frame fell outside on at least
one. It rejected five bad rolls in a row, then passed a good one.

The swing also kept re-inheriting its own drift: ref_sheet is built from
[lead] + the sprites being replaced, so it was showing the generator the very
brick-red frame it was about to overwrite. Copying a canonical frame over the
targets first breaks the loop — it passed on the next roll.

Swing is now a real backswing and follow-through (silhouettes differ by 1.0,
racket low behind vs up over the shoulder), both at full standing height, and
the toss and smash wear charcoal shorts again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 11:51:27 +02:00
wuwiandClaude Opus 5 6378ff2a1b Cut the serve down to three poses
Oliver, after playing the four-stage version on the phone: idle, then the racket
dropping on the pull, then the drive on release — "that's all, forget all the
others". At thumb speed the middle stages are never read, so they are deleted
rather than kept around: p_pull is the wind-up, p_smash is the drive, and both
come out of the photo-referenced sheet, so the whole serve is one drawing style.

The dead batches and the arm_left guard go with them. assets/_pose_ref_serve.png
is kept as the recipe: his photo sequence over our own sprites, which is what
turned a fifteen-generation prose argument into one generation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 01:09:57 +02:00
wuwiandClaude Opus 5 bcff17812f Draw the whole serve right-handed, and let the pull wind it up
The racket kept changing hands because the art disagreed with itself: p_idle,
p_bounce and p_toss held it in his LEFT hand while p_smash and p_swing used his
right, so every new frame copied whichever reference it was handed. The idle, the
bounce pair, the toss and the four wind-up frames are all regenerated with the
side named — drawn from behind, his right hand is the right-hand side of the
image, and only the racket HEAD crosses behind his back.

What separates the four wind-up stages is how far the racket has fallen, and that
needed the racket head pinned to a body landmark per frame — above his head,
beside his ear, between his shoulder blades, at the back of his thighs. Described
as "further into it each time" it drew four near-identical poses twice running.
They are wired late in the pull, Oliver's call: frame 1 owns the first half and
the three coiled frames are packed into the top half.

assets/_derive.py is deleted. Fixing generated art by mangling it afterwards is
always worse than regenerating; three guards in _regen.py turn the bad rolls that
used to install silently into automatic re-rolls — a portrait reference canvas
(the generator answered with a 2x2 grid), a non-magenta background (a green sheet
keyed away four racket heads), and a mirrored frame.

The bounce pair is one drawing from one sheet now, differing only in the free
hand, so BOUNCE_IDLE moved to his left side and was re-measured off the sprite.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 00:49:28 +02:00
wuwiandClaude Opus 5 e4c59e2c26 Turn the player around, point the runner at you, match the wounds
The playtest list, art side. The generator had been copying the very frames I
wanted changed, so:

- The player is drawn FROM BEHIND again in every pose — standing, bouncing,
  serving, swinging. He had turned to face the camera in the redraw, which is
  also why he "looked at you" in different poses.
- The serve frames are back at full size. That batch had been anchored on the
  toss frame, so the whole serve set came out about a fifth too small.
- The runner CHARGES AT YOU down the court instead of sprinting sideways across
  it, which is what he actually does in the game.
- The brute's wound stages are drawn a pair at a time from their own A frame, so
  the injury stops moving between the two feet.
- The bounce frames had a ball painted into them while the game draws a live one
  on top — he was bouncing two. The painted one is gone, and the live one now
  falls on the side the sprite actually reaches for.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 22:46:19 +02:00
wuwiandClaude Opus 5 67a410e707 Show the generator what to copy, not what to replace
The reference sheet is what it imitates, so building that sheet out of the very
sprites I was trying to change is why the front-facing player came back
front-facing and the side-on runner came back side-on. A batch can now name
`ref_only` — the frames it looks at — separately from the frames it replaces:
the back view is drawn from the one genuine back-view frame, the charging runner
from a front-facing zombie, and each brute wound pair from its own A frame so the
injury cannot wander between the two.

Also stopped colour-matching a REPOSE. Fitting a differently-posed frame onto the
old one's colour statistics is what greyed out the serve shorts; when the pose
changes the generator's own palette is the better answer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 22:36:13 +02:00
wuwiandClaude Opus 5 01498eddad Turn the bodies around, calm the racket zombie, break up the pack
Three from the playtest, all code:

Every side-on sprite is DRAWN facing left, and nothing ever mirrored them, so a
zombie walking right moonwalked — obvious the moment they close on you across
your own half. They face where they last walked now. The front-facing ones (the
turned CPU, the machine, a chapter boss) have no left or right and are left alone.

The racket zombie holds a position and only moves when the ball makes him, so a
time-driven walk cycle flicked him between two frames on the spot. His stride
steps off ground covered, like the brute's — standing still is standing still.

And the pack no longer stands on the body twitching for as long as the summary is
up: once it appears they break up and shamble away up-court. Measured — feeding
at 3s, dispersed to 3m by 6.5s.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 22:24:11 +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 0c499b60b5 Automate the redraw, and run the first eight batches through it
assets/_regen.py plus a batch file: build a reference sheet from the sprites in
the repo, ask for a HIGH-resolution redraw, slice with _fine.py, colour-match
each frame onto the one it replaces, install. The point of the whole thing is the
style block — the old prompts specified coarse art and got it.

It refuses to install a bad sheet. Wrong frame count, any figure touching a
canvas edge (that is what clipped z_f1 in the first place), or a redraw that is
not measurably finer than what it replaces, and the batch is left alone with a
reason printed. Re-running a batch is the fix; three of the first eleven needed
it. The bar is 1.25x inner-edge density rather than 1.5x, because a sheet that
already has blood on it starts high and a genuinely finer wounded body still
scores a smaller gain.

Done so far: both shamblers and their wound skins, the brute's walk and its two
sets of wound stages, the steward and his wound skins, the turned CPU.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 21:43:58 +02:00