f9b26e53803176d5276c7ba74a8d83f94d0ab031
116
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c6218a6284 |
Exact touchdown, the boy finishes his shift, healing announces itself, 4K heads
Five of Oliver's six (the sixth is a play-mode discussion): - THE DOUBLE FAULTS WERE FRAME QUANTIZATION: the bounce registered a full integration step after true contact — ~0.75 m of travel at serve pace — so serves aimed half a metre safe were judged long. Classic enforces the service box, the match only the court, hence 'zombie tennis does not'. The bounce now rewinds to y=0 before anything reads the position. Measured in-pane: 30/40 faults on good taps before, 0/40 after (mistimed and smudged taps also 0/40; aimed hard serves keep their scatter risk). Also: the strike flick dead zone is 44 px so a smudged tap stays a tap, and the aim base clamps 0.18 m inside the lines — scatter, not the aim, is what faults. - The mop beat holds the walk-on until the boy actually LEAVES the court (ordered off at beat end, 6 s cap). - A match game win that restores blood shows the duel's WOUNDS TREATED popup; full-blood wins stay silent. - The opponent walk-on card holds 4.0 s (was 2.4). - Card heads are cut from the 4K sheets at 144 px (assets/_heads.py, z_<fam>head.png, HEAD_CROP stays as fallback) — the old 65 px crops were the 'very low res' heads. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c847bcbfa1 |
The racket holds still: both player run cycles re-rolled steady-handed
Oliver, after the facing fix: still flashing, female worst. Film-strip diagnosis in the pane: with facing pinned the racket STILL alternated — frame 4 of the female cycle tucked it across the body, and half the frames of both player cycles lost or shrank the hoop to cut fragments (overlapping figures sliced by the even-spaced valley cuts). Both cycles re-rolled with three new prompt rules that now live in _runroll.py: the racket stays right of the midline in EVERY figure (named again for the airborne pose, where it kept vanishing), and the figures never overlap (no more sliced hoops — a debris-stripping pass was tried and removed: after the recrop a real racket is indistinguishable from a fragment, prevention beats cleanup). The model returns 2-row grids at times; a coherent band is cropped and cut. Installed: racket-side spread +0.01..+0.05 across all frames of both cycles (was a full side swap). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
9a49acff51 |
The twelve-rung ladder: director, champion, physio and mascot walk on
Oliver's four picks, each rolled off its own settled BOSS frame through the family pipeline (rows in _famroll.py) — the rung and the chapter boss are the same person. matchFoe decoupled from bossArt's cycling on his call: the match ladder is 12 with the champion capping rung 12; chapters keep their ten. The champion refused to bulk through four rolls (words lost, then a new pair-reference in char_ref only half-won), so his mass is stature: OPPONENTS rows carry an optional world height h read by zombieH — champion 2.1 m, the Bulk 2.0 m — and the serve aims at the body actually drawn. HEAD_CROP measured for all four; ramp test extended to 12 rungs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
f1af5c9a72 |
The corpse was levitating, and the wounded ones walked backwards
All of it found from the sprite bench, and most of it was the same mistake twice: a correction applied on top of art that already handled it. THE HEADLESS BODY SHRANK. "Every headless mode shrinks the whole opponent a little" — measured, 34.0 px healthy / 33.8 arm off / 27.8 headless on all EIGHT families, a flat -18%, and the FEET shrank 16-19% with it. A body that loses its head does not get smaller feet. drawZombie carried `if (st === 2) h = 1.52` to take a head off the top, and a headless sprite is still a FULL-HEIGHT frame, because the generator draws a gushing neck stump whose blood plume fills the space the head occupied: 18.3% of z_rw2's frame sits above its shoulders against 10.7% above z_rw1's head. So the art had taken the head off, the per-frame PNG scaling had handled the height, and the override took it off a third time. Wrong on the fallback skins too (z_cw2 is a SHORTER png, which the scaling handles): its body drew at 1.30 m against the healthy 1.78 m. Gone. The HP bar and the aim mark read config.zombieH directly, so they had been floating above a shrunken body for as long as it existed; they re-align for free. THE WOUNDED BODIES WALKED BACKWARDS, and this is not bench-only — racketAI and updateBench both set face as the sign of x movement, so a match did it too. The racket is what measures it, Oliver's own test: "if this racket is facing to the right I would assume that he also walks to the right." Its string bed keys out to an enclosed hole (or is painted pale on the master), so the hoop locates itself. The healthy stride is drawn racket-RIGHT (+0.38..+0.42) and every wound stride racket-LEFT (-0.39..-0.45): THE WOUNDED BODY IS DRAWN FACING THE OTHER WAY, on the master and therefore in the reference and therefore in all seven families. One flip rule cannot serve both rows — fixing the healthy walk inverted the wound walk, which is the second thing he saw. The flip is derived from the frame name now, and only for the WALK frames (w1/w1b/w2/w2b), because they are the only ones whose orientation tracks travel: the wound forehand measures racket-right like the healthy one, and a serve or a fall puts the racket overhead where it says nothing about direction. Checked against 18 frame names including the tour-suffixed ones. AND _famref.py PINS THOSE SIDES. That branch is only correct while the art stays this way, and this is the THIRD art inconsistency this session that reached the court because nothing measured it. A re-roll that mirrors a row now fails the reference build pointing at drawZombie's artFacesRight, instead of shipping. A DEAD WRITE, found on the way: racketAI set rz.facing, and zombies render off zb.face. `facing` is the PLAYER's field and uses the OPPOSITE convention (a char flips on facing < 0, a body on face > 0), so the match opponent's turn was never driven by its chase at all — it fell through to the generic sticky-face line. THE PRONE FRAME LAY ON ITS RACKET. "The player basically levitates over the ground held by his racket." Every frame is bottom-aligned on the ground point, and in the fall3 art the lowest ink is the racket hoop thrown out beside the body — z_shw1fall3 is 249x85 with the body's underside at y67 and rows 68-84 racket, so the torso floated a fifth of the frame up. TUNE.proneSink drops it by that fraction, putting the body line where fall2 leaves its knees. The shadow made it worse and is suppressed for the whole beat: the contact ellipse is sized off DRAWN WIDTH, and a prone frame is three times wider than a standing one, so the widest darkest shadow in the game was painted under the one body not standing on it. THE FALL CARRIES THE RUN. "When the opponent runs to the ball from left to right and then falls, the player must still move to the right while falling." The velocity is INHERITED (racketAI and updateBench both record zb.vx), so a body that went down standing still does not slide and one caught at a sprint slides furthest. It was exponential decay first, which put 93% of the travel in the first two frames — "the visible slide should also extend to the 3rd and 4th frame" — and no retuning fixes that: stretch a wall-clock decay far enough to move the prone body and it creeps for the whole floor hold. So the slide is a DISTANCE BUDGET shared across the frames (TUNE.fallSlideMix), eased out within each so it still decelerates. x is set absolutely from the start, so the total cannot drift with frame rate. 0.05 / 0.22 / 0.35 / 0.11 m — the landing skids and the rise drags itself the last of the way. THE BENCH GOT A FALL BUTTON to tune all of this, and it plays the REAL beat: same FALL table, same tickMatchFall, because a bench that re-implemented the timing would be refining a second copy of it. matchReady() is skipped while the bench owns the court (it runs setupPoint and re-places the body — a match doing match things inside an art test), the walk stop stops walking during a fall, and startFall reads the table LIVE via fallLen() so tuning the numbers cannot leave the 'over' beat expiring mid-collapse. FALL is 0.06 / 0.25 / 2.0 / 0.45 after a session in the bench. fall1 and fall2 are deliberately the same pace (0.87 and 0.88 m/s of slide at a 1.54 m/s entry) so the collapse reads as one movement decelerating into the ground; the 0.07 s fall2 it replaced gave each drawn frame ~4 display frames and read as a cut. THE LENS, portrait camZ 48 -> 49. The three camera numbers are not independent: holding the near player's drawn size fixed means camF = s_player * (camZ - zMax), holding the court's on-screen depth fixed means camY = depth / (s_player - camF/camZ), so 49 gives 795 and 11.23 and horizon re-derives to 43. Far opponent 33.1 -> 33.8 px, player unchanged, court band within a pixel. One metre is deliberate: at camZ 55 the umpire is already clipped off the right edge and by 70 the court reads top-down, and it narrows the player's lateral room because xVisible clamps him to what fits. A figScale experiment that scaled every body 1.2x got further (far body 40.6 px) but is NOT in here — it needs the vertical contact constants to scale with it (ball-vs-body is gated on ball.y < 1.9, which sits just above the tallest chest at 1.856 m, so the roller man would have gone invulnerable) and Oliver reverted it pending a real playtest. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e121df569c |
The reference sheet was the bug: a clipped head, five scales, frozen legs
Oliver walked the sprite bench and reported the same two faults on every family: the healthy walk loses its head, and the wounded walk's feet do not move. Both were in the ONE FILE every zombie tennis character is drawn from — and the family pipeline had copied it faithfully, which is the pipeline working. WHERE IT CAME FROM. That sheet was not composed from the sprites. It was a GENERATED RE-DRAW of the club pro, made in nine separate 4K calls, one per row band, then stacked (gen4k.py, in a session scratchpad, untracked and now gone). Nine calls returned nine scales, and call 1 came back with the shuffle frame decapitated. Measured against the sprite it was drawn from, that one figure scaled 2.341 wide and 1.996 tall where all thirty others were proportional to three decimals: 33 px missing off the top, width untouched. The local input to that call was exact at 2.000x. The sheet lost the head, not the sprite. THE FIX IS TO STOP PUTTING THE REFERENCE THROUGH THE MODEL. _famref.py composes it from the 31 z_r* frames on disk — one scale, applied once, NEAREST — and costs no tokens. That kills the head and the five scales together, and it explains two things in _zsheet.py that read like fudges: the fall1 re-anchor and the walk-frame levelling exist only to undo per-band scale drift. The levelling is also what turned a headless z_sh2 into a headless AND 13% oversized one, by reading the missing head as a height error and scaling up to compensate. THE GATE WAS MISSING ON THE INPUT. _zsheet.py checks what comes BACK from the model — band count, figure count, stacked bodies, edges, palette, heights — and nothing had ever checked the sheet handed TO it. _famref.check() re-reads the file it just wrote with _zsheet's own detection and refuses one where any figure's width and height scales disagree by 2%, or where a stride pair is frozen. THE THIRD DEFECT NEEDED NEW ART. z_rw1b had the same legs as z_rw1 (15.1% against the healthy pair's 51.1%), so the club pro has slid through both wound stages since the day he shipped, and every family inherited it. _stride.py draws the missing twin, and what made it work after words had failed for as long as they were tried is SHOWING the edit: image 2 is the healthy pair side by side, the same character performing exactly the change being asked for. The headless twin then took the ARM-OFF pair as its example — a wounded body is a closer picture of the edit, the same chaining the wound ladder already uses. Its gate measures three things and prints all three: legs moved, body above the hips did not, racket kept its bed. The thresholds are CALIBRATED on pairs that already ship, not chosen — my first guesses rejected z_e1/z_e2 and z_g1/z_g2, both accepted art. And a twin FILLS the racket bed instead of keying it out: the first rolls came back with 959 px of enclosed transparency against their base's 0, which would have flickered the racket face empty/filled once per step. AFTER, on the installed sprites — leg difference within each stride pair, where the wounded pairs used to sit between 4.8% and 19.7%: r 51.1 / 68.1 / 36.7 ru 55.9 / 59.1 / 38.7 br 40.7 / 42.0 / 26.8 gk 41.1 / 44.6 / 30.6 sh 59.4 / 71.4 / 43.9 lj 38.4 / 45.5 / 27.4 bb 56.5 / 67.1 / 43.2 st 51.6 / 66.1 / 35.7 All seven families re-rolled off the rebuilt reference, every play row with its head, and all 24 family/wound-stage states alternating a real pair — checked by instrumenting drawImage on the bench, not by eye. br and lj sit lowest on the headless pair and are worth a re-roll if they ever read as stiff; lj needed one, its first roll came back frozen on all three pairs with the headless row 31 px short. AND THE REFERENCE IS TRACKED NOW. It lived in gitignored _refcheck/, so the thing the entire pipeline rests on was unreproducible for as long as it existed. It is assets/_pose_ref_family.png, next to _pose_ref_serve.png, and the scripts that build and consume it are in the repo instead of a scratchpad. Also in here: the SHAMBLE BOB, which was a 1 px lift on a wall clock applied to every body every frame whether it was moving or not — a held pose bobbed forever and a corpse bobbed while it lay on the ground. It only happens while walking now, and it steps off ground covered, which is the same rule the strides follow. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
eb0e8775a4 |
The timing cues were match-only, and the walk cycles bounced
CLASSIC TENNIS, third time asked and this time actually found. Both cues open with a mode gate and I checked the wrong two features twice: drawApproachRing (the closing circles that show when to tap) and drawApexBand (the serve bar with the gold perfect window) each began `if (!G.match ...)`. So the mode whose whole pitch is "if the core tennis is good everything else is good" was the one mode with neither. They run on `G.match || G.mode === 'tennis'` now — the gore mode stays out on purpose, there is no rally there. A test reads both function bodies and fails if either is ever gated on the match alone again. (The two I kept pointing at — the landing ring and the placement ring — really are shared. They were just not the ones being asked about.) THE BOUNCING WAS IN THE ART, and measurable: the six generated families came back with the shuffle frame 30-44 px shorter than the stance it walks out of, against 9 px on the hand-made racket family. The game draws every frame at its own PNG height, so the body rose and dropped once per step. A walk cycle is one drawing with the legs moved and must be one height; _zsheet levels the stance and the stride pair now and leaves the forehand its own, exactly as the serve and the fall keep theirs. All seven families re-cut: spreads are 1-8 px. THE BENCH GETS BUTTONS. Arrow keys are not a control scheme on a phone, which is the device the walk has to be judged on — so every family and every wound stage was unreachable from the place the problem shows. Three on-screen pads: BODY, WOUND, POSE, the two-way ones split left/right. Geometry lives beside the drawing and input hit-tests the same function, the rule the rack established. THE FALL is quicker still (0.05/0.07/2.0/0.30): drop, lie there, get up. Three umpire lines pulled — ump_wound_you, ump_felled, ump_wound_opp2. Not funny enough to keep. Files deleted, call sites collapsed to the two that stay. 60 tests green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e1ce2486c0 |
SERVE UP was dropping you into gore whatever you picked
THE MENU BUG, and it explains three reports at once. cStart's handler was
`addEventListener('click', start)` — and addEventListener hands a listener the
MouseEvent as its first argument, so `start(modeId = pickedMode)` never saw its
default: modeId was an EVENT OBJECT. `G.classic = modeId === 'classic'` was
therefore always false and the zombie branch never fired. Picking CLASSIC or
ZOMBIE TENNIS on the setup screen did nothing at all; SERVE UP always started
the gore mode. A default parameter only applies to `undefined`, and a click
handler always passes something. `() => start()`.
That is also why "in normal tennis i dont see the ball circles and the serve
hint bar": there was no normal tennis to be in. Both are drawn unconditionally
in render() and the tennis struggle hints are already mode-gated correctly.
Verified through the real menu after the fix: CLASSIC gives mode=tennis with
no bodies on court, ZOMBIE gives match=true against the SHAMBLER in its own
body. Every other bare handler in the codebase takes no parameters — checked.
THE MOP BOY had the same class of bug one layer down. sendBoy was called and
mopWorthIt() refused: it tests `d.z >= COURT.netZ - 2`, the NEAR half, because
in the gore mode everything dies on the near court. A match's blood is all on
the FAR side, where the body stands and bleeds through four wound stages, so
the boy could never qualify. The near-side bias stays; the match is the
exception.
DANIEL was being dropped, not missing. Two causes, both mine: the asides were
queued at 0.7-1.5 s and matchPoint fires the score call at 0.4 s with a clip
over a second long — voice() drops anything that would start while he is still
talking (one man, one microphone). And asideEvery is 55 s, a whole match game.
Delays moved past the score call; matchAsideEvery is 15 s, which fits a mode
whose events are a limb and your blood rather than a wave of bodies.
THE FALL: quicker down, longer on the floor (0.09/0.11/2.0/0.30). And it was
genuinely SHRUNKEN — the generator draws fall rows smaller than standing rows
(the brute's buckle came back 190 px against a 224 px stance) and _zsheet's one
global scale preserved that faithfully, so the body shrank the moment it
started to fall. A fall row is anchored on its own buckle now, which must draw
at standing height. All seven generated families re-cut: 224/153/98/181 against
the hand-made racket family's 224/154/85/179 — the convergence is the check.
THE FLICK: 0.42 made going out impossible, which fixed the 8-in-10 bug and
overshot it ("too easy. i must be able to play out"). 0.62 puts risk back where
it belongs — ~15% out at quality 0, 5% at 0.75, and never on a perfect flick.
The test now asserts that shape: bounded, monotonically falling, zero at 1.
59 tests green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
52b38e4a39 |
Eight bodies on the card, and the umpire gets his voice back
THE FLICK, again. "i can barely reach the extremes of the court." It could reach 3.3 m against a 4.115 m line — safe, and too safe. TUNE.flickWidth is 3.95 now (96% of the way to the corner, 17 cm to spare) with flickReach 0.42, which keeps the guarantee intact: reach x timing + scatter stays inside the line at EVERY quality, worst case 4.06. 20% more reach, still impossible to send out by aiming. A FORWARD FLICK IS A SMASH. Up-screen is the far court, so flicking into it asked for depth and nothing else — a direction with no consequence behind it. It buys pace now (smashBoost, scaled by timing). Deliberately NOT distance: solveShot still lands it on the same spot, so the shot arrives flatter and sooner rather than longer. A smash you can hit out on a whim is just the old bug wearing a hat. THE FALL HOLDS. "the opponent should fall and then lie there for a quick moment, the player stands up too quick." fall3 goes 0.30 -> 0.62. A corpse that touches the ground and is instantly upright reads as a stumble. THE CARD. Ten opponents, six of them NEW bodies off the one-sheet pipeline (shambler, ball boy, steward, runner, groundskeeper, line judge), with the club pro's own body and the brute woven in as 7 and 8. It is a ladder of STAFF: the further you get, the further up the club the thing across the net used to be. Narrative and difficulty are the same list read twice, and a test walks all ten — whiff must fall every rung, names must fit the board's 8-char slot, speed may zigzag by character but never regress. Steeper per rung (speedPer .08->.11, pacePer .035->.05, flubKeep .82->.76): "in general this is too easy now". With aimed returns no longer sailing out, the player stopped handing over free points and the ladder had to earn them back. The opening got SOFTER at the same time — 0.72 where the old first opponent was 0.85. THE UMPIRE WAS MUTED IN THIS MODE and nobody noticed. aside() refused during any interlude; the match runs on a permanent one. So the single mode that is literally a tennis match he is sitting over was the one mode he never spoke in. Same bug shape as sendBoy, same fix. Six dry lines via say -v Daniel: two on taking a limb, two on your blood, one when there is nearly none left, one on felling the thing. asideEvery still gates him. ...and sendBoy had the identical bug, so the mop boy now comes out between games to the FAR court, where one body has been bleeding through four wound stages. Between games only, never mid-point. The line judge took two rolls: the first cut to 6 figures in its last row and _zsheet.py refused it rather than mis-slicing. That refusal is the feature. 59 tests green. Verified in the browser: SHAMBLER on court in its own body. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
cba2fb8a8b |
One roll, one character: the whole zombie family from a single sheet
Nine sheets meant nine chances for the character to drift, and all day it took
every one of them — a brute that was three different brutes, a corpse that came
back a living man. Asking for the WHOLE family in one image removes that by
construction: one roll, one light, one palette, 31 figures.
What was missing was the way back down. _fine.py cuts a single row; a family
sheet is nine. _zsheet.py is that step and nothing else — it reuses _fine's
keyer, block detection and column cutter, and adds the two things that have to
be GLOBAL or the family breaks:
SCALE taken once from the play row's standing frame. Per-band scaling would
make the serve strike the same height as the idle stance, and the game
sizes a sprite off its own PNG height. Measured after: serve 319,
prone fall 83 — the racket family's are 307 and 85.
PALETTE taken once from the largest figure anywhere, so a wound row cannot
quantise onto slightly different colours than the row above it.
The enclosed magenta rule is INVERTED here, deliberately. _fine.py repaints
magenta trapped inside a figure because the generator used to fill string beds
with the backdrop colour by accident. This sheet asks for exactly that and gets
it, so the bed cuts to transparency and the racket is an open hoop.
_regen.py keeps three fixes found on the way: margins as a PROPORTION (an
absolute pixel margin means nothing to a model that picks its own resolution —
it clipped 3 rolls in 4), reference sheets that fill keyed-out string beds
instead of showing the model a magenta racket, and --verify, which asks the
question no gate here ever asked: is the art already on disk what it claims to
be. It found 27 of the brute's 31 frames dead — z_brw1fall was one drawing
pasted four times, 0.011 apart where a real sequence is 0.53.
The brute is live: ZFAMS gains 'br', the KEEPER wears it. Verified in #bench,
healthy and arm-off, 54 tests green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
759488dd44 |
Recover the brute's serve and fall sheets, which I overwrote
Re-running --variant to rewrite a spec re-seeded EVERY placeholder from the
master, and placeholders are copies of the lean red-shirted man the poses came
from. That silently replaced a finished brute's bounce, toss, strike and four
fall frames with racket-zombie art. I restored z_br1..4 by hand afterwards and
did not notice the other seven were gone — so three subsequent passes were
regenerating sheets that already existed and had been good since this morning.
Recovered from
|
||
|
|
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> |
||
|
|
525f017636 |
Park the brute — and name what its next attempt has to change
The agreed single pass did not land: br_w1 installed, br_serve and br_fall were refused by the palette gate at 12 to 22 away from z_br1, and everything drawn from them was correctly skipped rather than built on sand. 'br' comes out of ZFAMS. The art stays on disk and the nine batches stay in the JSON, so re-registering it later is one line — this is parked, not deleted, and nothing in the game referenced it (no opponent row names it). THE FINDING WORTH KEEPING, written at the top of _master.py: the brute's PLAY sheet is a good brute; its serve and fall sheets are each a slightly DIFFERENT brute. All three restyle from the master independently, so each invents the character afresh from the words, and "pale grey-green work shirt" lands somewhere new every time. The gate is not being fussy — it is measuring three different zombies. The fix is to hand the later sheets BOTH references: the master frame for the POSE and the variant's own first sheet for the CHARACTER, instead of the words for the character. Which is the lesson the wound stages already taught — a reference beats a description — applied one level up. Until that lands, a variant is reliable for its play sheet and a gamble for the other two. The racket zombie is untouched and complete: 33 frames, every gate green, and it is the opponent the match actually plays. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
4bec0cb09c |
--variant now emits the whole ladder: nine batches, thirty-one frames
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. <key>_play / _serve / _fall restyled off the master 11 frames <key>_w1 / _serve_w1 / _fall_w1 arm off, drawn from healthy 10 frames <key>_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 <noreply@anthropic.com> |
||
|
|
7056945e62 |
THE MASTER SHEETS: a new zombie is now three calls and no pose design
Oliver asked what the reference sheet is called. It did not have a name — it was assembled per batch into /tmp and thrown away, so the pose set that took a day to get right existed only as 33 loose PNGs. assets/_sheet_master_play.png stance, shuffle A, shuffle B, forehand assets/_sheet_master_serve.png bounce, toss, strike assets/_sheet_master_fall.png buckle, knees, down, rise Magenta-backed, spaced, feet on one ground line — loadable straight into a generator as-is. `python3 assets/_master.py` rebuilds them from the installed frames, so the master can never drift from what the game actually draws. THREE SHEETS AND NOT ONE, for a measured reason: the generator answers at a fixed width around 1400-1800 px. Four figures across gives each 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 what fits. And the thing the master is FOR: python3 assets/_master.py --variant br "an ENORMOUS FAT ZOMBIE BRUTE: …" python3 assets/_regen.py br_play br_serve br_fall --tries=4 which writes three restyle batches, seeds their placeholders and declares the stride pair so the gate covers the new body from its first roll. A variant is three generator calls and NO POSE DESIGN AT ALL — the reference specifies eleven poses that already work and the prompt only says who to draw. Measured on the brute test: the restyled stride pair 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. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
4bbdf6357a |
The opponent can serve and fall, and there is a bench to judge it on
THE MISSING ART, generated by the whole-sheet restyle method: bouncing the ball at the baseline, the toss with the ball leaving the fingertips, the strike at full stretch — and the fall, which slice 4 has been waiting for: buckling, down on the knees, flat on the court face down, then dragging itself back up. One zombie is now complete as a tennis player rather than as a body that shuffles. (Healthy only so far; the wound variants are the next sheet.) THE SPRITE BENCH, in the back room. No ball, no scoring, no AI — one opponent on an empty court, walking, at the size and speed it is actually played at, because that is the only place a walk cycle can be judged. A sheet that looks fine at 224 px on a contact sheet is a different animal at 30 px crossing a lawn, and every sprite argument this project has had was really about that gap. It steps through every state including the ones the game cannot reach yet, so the serve and the fall could be looked at the day they were drawn instead of the day they get wired up. It prints the FILENAME it is drawing and the hp the wound stage was read from — the two numbers that separate "the art is wrong" from "the wiring picked the wrong art" — and it says HEALTHY ONLY where a wound variant does not exist rather than implying one does. The stride steps off ground covered, like the real thing: a bench animating on a clock would flatter exactly the sprites that foot-slide in play. It owns the court like every other mode and every start* refuses while it runs. FOUND BY IT, ten seconds in: `start()` applies the mode the setup screen picked, so a remembered ZOMBIE TENNIS was taking the court before the back room could — every back-room entry (weapons, bosses, drill, match, bench) had been silently refusing since the mode select landed this morning. The back room passes null. And a third calibration lesson: POSE MOVES A GLOBAL COLOUR STATISTIC. A body face down shows a fraction of the shirt it shows standing, so `redness` fell 14 points across the fall sheet without a colour changing. The measure that survives a pose change is the shorts HUE — mid-tone neutrals, present in every frame in the same proportion — which is why it is the one that caught the drift Oliver actually saw. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
ffc9cd3a60 |
Damage accumulates, because stage 2 is now a RESTYLE of stage 1's whole sheet
The arm kept coming back at the headless stage no matter how the prose was worded, and the reason was the picture: stage 2's character reference was the OLD two-armed headless drawing, so the model was faithfully copying two arms while the words asked for one. Prose does not win an argument with a reference. Stage 2 now takes stage 1's ENTIRE THREE-FRAME SHEET as its reference and the job becomes "redraw this sheet with ONE thing changed — the head comes off". The missing arm rides in the picture. Both tours installed FIRST TRY, where the previous wording burned twelve rolls and never once got there. The ladder finally reads: whole -> left arm gone -> headless AND still one-armed, same body, same charcoal, same shoes, all the way down. In a mode where the score IS the body that is the whole point, and it has never been true until now. --audit stays clean: all six stride pairs pass. This is Oliver's method and it is better than what it replaced. What makes it work is that each call TRANSFORMS a picture instead of describing one, so the reference does the specifying and the prose only says what changes. Fewer calls is the smaller half of it; the consistency is the point. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
cccdaba8f0 |
The slicer stops throwing away good sheets because two bellies touch
Oliver's idea, tested: give the generator a WHOLE POSE SHEET and ask it to redraw it as a different zombie. It works, and better than what I was doing — the master set's stride pair is 0.99/0.157/0.565 and the restyled brute came back 1.00/0.148/0.618. The pose relationship SURVIVED the character swap, in one call, four frames. It was reported as a failure only because the four brutes stand shoulder to shoulder and their bellies overlap, so segmentation by empty columns saw ONE blob and the sheet was discarded. That same rejection ate five rolls earlier today — "found 2 frames, expected 3" — and the sheet it lost last was the best roll of the day. A row is evenly spaced by construction, because that is what we ask for. So when the blob count comes up short of a count we ALREADY ASKED FOR, look near each even division and cut at the thinnest column in that window: between two figures the ink thins even where it does not vanish. Never used to guess how many figures there are, only to reach the number requested — a wrong guess would install garbage silently, and the frame count is the one thing this pipeline has always been able to trust. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4e7b26f874 |
Every stride pair now passes the gate — the legs actually move
`_regen.py --audit` is clean for the first time: racket_play z_r2/z_r3 1.00 0.157 0.565 ok racket_w1 z_rw1/z_rw1b 1.00 0.112 0.424 ok racket_w2 z_rw2/z_rw2b 1.00 0.077 0.372 ok racket_f_play z_r2_f/z_r3_f 1.00 0.049 0.552 ok racket_f_w1 z_rw1_f/…b_f 1.01 0.112 0.716 ok racket_f_w2 z_rw2_f/…b_f 1.00 0.054 0.793 ok Against the reference pairs (brute 1.00/0.000/0.638, shambler 1.00/0.070/0.520) these are the real thing: one body, same camera, legs swapped. Fifteen rolls were rejected getting here, every one for a named measurable reason — which is the whole point of the gate: none of it shipped on luck. Two calibration lessons, both from watching what the gate rejected: BRIGHT is off for the racket batches. It counts near-white pixels and was calibrated on the LIVING opponent, whose racket barely registers — these frames hold a pale-strung racket FACE-ON and it swings 3%..20% purely with how the racket is angled. It was rejecting rolls for racket geometry. Redness, shorts brightness and shorts hue are the kit, and hue is the one that caught the drift Oliver actually saw. EACH WOUND STAGE IS HELD TO THE STAGE BEFORE IT, not to the healthy frame two steps away. Stage 2 kept being rejected at hue -4.2 against the healthy -8.7 while the stage it actually follows sits at -5.0 — the roll was more consistent with its own neighbour than the gate would allow. Chained references stop drift compounding without demanding stage 2 match a frame the player never sees next to it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
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> |
||
|
|
d5942e8aad |
The match gets its own board, and it names what is across the net
A second plate, drawn rather than tinted (assets/_board.py — _regen.py cannot do this job: it keys magenta, crops to ink and rescales figures to a common height, and a scoreboard is a full-bleed rectangle with no figure in it). The club's bottle-green board is the club's; the match is played on one nobody has cleaned in a long time, and the ink changes with it — a green 'dim' over dried blood reads as a printing fault. Two column changes go with it: the name slot says KEEPER / JUDGE / CHAMPION instead of THEM (eight characters, `short` on the ladder rows), and the SETS column is gone in the match, where it was a permanent 0 — the bar under the board already carries that mode's progress. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c629ae1108 |
The ladder has names, the bodies play tennis, and the stats screen splits
THE SPRITES. The match opponent borrowed the turned CPU's art — a body drawn to SHAMBLE at the camera with its racket hanging at its side, which is what was playing tennis points against you. It has its own family now, 'r', drawn off the LIVING opponent's poses: a ready stance, a shuffle pair and a real forehand, at every wound stage, men's and women's — 20 frames, six batches. 'c' keeps its shamble untouched, because in the gore mode shambling is exactly what the turned CPU should be doing. The pose comes from racketAI's own `pose`, so the picture cannot drift from the behaviour, and a one-armed opponent still plays tennis instead of reverting to a walk. Two pipeline bugs found doing it, both fixed at the root in assets/: - _fine.py keyed EVERY magenta pixel, not just the background, and the generator paints racket strings in the backdrop colour more often than not — so frame after frame installed a man holding an empty hoop. Background is now the region connected to the border; magenta left INSIDE a body is repainted pale grey rather than punched out. - the flood fill silently did nothing, because Image.fromarray images are read-only and PIL's floodfill swallows the write error. .copy() plus a real error if it ever fills zero pixels. THE LADDER (handoff slice 2). Rung N takes design N's name off the chapter roster — the club's dead staff play you at tennis here and come for you in gore, same joke twice — plus a personality that is DIALS, not new mechanics: pace, width, flub and speed scalings multiplying numbers racketAI already reads. A rung is a data row. Name plate on the walk-on, and the kill files under the rung rather than under the generic body. THE STATS SCREEN follows the mode split: BODY COUNT / THE LADDER / THE RECORD behind a mode strip, each with its own three figures, its own roster heading and its own footer. The weapon shelf and the roster hide in the modes that have neither, rather than printing a line about something the mode cannot do. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
8011186cda |
The blood you have left, the bodies behind you, and a face for staging
Three things, all in service of the match reading as its own mode: BLOOD, COUNTED. The vignette says "worse" and never says "how many". One pip per hit left, top-right beside the board, in the panel language the game/sets bar already uses; the last two breathe. Pips rather than a bar on purpose — the pool is 4 or 8, and a smooth readout of a small integer is the exact ambiguity being fixed. OPPONENTS FELLED IS THE MEASURE (handoff slice 1). Blood-only scoring means a lost game costs nothing but the blood already spent, so games survived is not the achievement. The in-play bar prints OPPONENT n · m FELLED instead of the game/sets line, the death screen says BLED OUT / FELLED n OPPONENTS · m GAMES, and it keeps its own matchBest — drillBest's pattern. The kills-to-next-point pips are hidden in a match: nothing there is killed for points, and they were colliding with the new line anyway. STAGING WEARS A DIFFERENT ICON. Both builds live on the same phone and two identical bloody tennis balls is a coin toss. Driven by HOSTNAME, not by the branch — the block is identical on staging and main, so it never conflicts on a merge and never has to be remembered. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
ef8a413031 |
You aim it yourself now, and no two rounds are dealt alike
Oliver played it: "the auto aim just mean no skills involved at all. this should just go away.. there is no penalty at the moment.. make some variations of the position on the court and the color". The first two notes are one finding. With the shot locked to the called body it was unmissable, so the fault could never fire — there was no penalty because there was no way to earn one. Both snaps are deleted. What replaced them is one line: aimTie is 0 during a drill. That is not auto-aim, it is removing the guess — the ordinary magnet lets a nearer body inside the same cone steal a shot aimed past it, which is right for a crowd you are mowing down and exactly wrong where hitting the wrong body IS the failure. An unaimed tap now lands on grass. And the variation: - six colours (purple, white) with the court dealt three to five of them, so which colours turn up and how many decoys there are are live questions. At four out of four every round dealt every colour and only the order moved. - PLACEMENTS: fan, cluster, column, stagger, wing — weighted, mirrored per round, lanes shuffled against the bib order. column stacks bodies in one lane, and the ball hits the first thing it touches, so feet matter. - pips are dice faces now, shared by bib and HUD chip. A row of five dots has to be counted; a die is recognised. Measured over 30 deals: all five shapes, counts even, all six colours, and the first target screened by another body in ~10% of rounds. Verified end to end in the browser that FAULT and DOUBLE FAULT both fire. 26 tests, including a named regression for the auto-aim staying gone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
22df96cc1d |
The umpire reads a list, and the bibs come out to meet it
The late game is repetitive because the optimal move never changes: kill the nearest thing, now. This is the first swing at a mode where it does — a coaching drill where the umpire CALLS a colour sequence aloud while bibbed bodies take their marks, and you must put them down in that order while they walk at you. Uncalled colours are decoys you have to ignore. Two faults is a double fault and the round is theirs. Back room only (DRILL TEST, or #drill): this is a feel-test for one question — tense, or just noisy? — so it keeps its own round count and touches neither tennis scoring nor the run. - voiceSeq: voice() drops a line that would start while the umpire is still talking, which is fatal for a list. Decode first, queue at accumulated offsets, and the anti-overlap rule is satisfied instead of fought. - Four colours with PIPS as well as hue — red/green alone makes the mode unplayable for ~8% of men. - drawBib is a ponytail-marked overlay that knowingly breaks the sprite rule; it is the thing to delete if the mode survives. - BOTH target pickers snap (flickTarget and zombieTarget). Snapping only the slingshot left fifteen tap-serves in a row scoring nothing. - Contact costs the point, not the run — permadeath stays in the gore mode. - startDrill disarms the ordinary schedule; updateDrill redeals and logs an INVARIANT if anything empties its court. Eight new tests (24 total) pin the ownership contract both ways, the call beat, the win, the double fault, the un-winnable-round guard and the death. PROJECT.md carries the four open questions for the playtest. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
8307a05f79 |
The wind-up is a drawn bow, not a crouch: her pull re-rolled off his arch
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
70642a0a3d |
Her serve is his serve: the women's pull and smash redrawn off the men's poses
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
04fe4098a7 |
Pin the sprite generator to Nano Banana Pro on OpenRouter
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d57433ed2b |
A charge that passes, two strides that actually stride, a door in the corner
THE LEGS. Measured as lower-third silhouette difference between the two stride frames: the groundskeeper, whose walk has always been right, is 0.80. The seller was 0.017 — the same legs twice, which is why he hopped along on one of them. Both sheets regenerated with the stride as the entire subject (one foot planted ahead, the other trailing, and they SWAP): seller 0.42, champion 0.67. Three things the champion's re-rolls proved, all of them the same lesson — THE REFERENCE IS WHAT IT COPIES. Anchored on his own one-legged frame it drew that pose again. Given a working walk from another boss alongside him "for the legs only" it blended the two and returned a slim side-on walker, then a pot-bellied man in cream. One character in the reference, one character out — and pick the keeper by EYE, because the metric happily crowned the side view. THE CHARGE. It rushed, then withdrew a fraction of what it covered, so it ended the fight parked on top of you: dodge or die every two seconds with no window to hit it, and nothing but a hitscan laser quick enough to use the window there was — Oliver, "they come too close, they seem unbeatable". A rush now RETREATS to its line afterwards, and to keep it from being toothless the other way (from the band it ran out of clock 8 m short) the rush is TIMED rather than paced: it covers whatever ground the retreat left in bossChargeT, so every charge arrives and every charge has the same readable run-up. Measured over 30 s: five rushes, each one reaching you, 13% of the fight in your face instead of all of it. And the back room moved off the whole splash onto an invisible box in the TOP- LEFT CORNER. Five taps in the middle of the screen — which is where your thumb goes on the way to PLAY MATCH — now do nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
04ee0174ce |
Three more chapters, and at game 100 the champion returns everything
The roster ran out at chapter 6 and then repeated itself; game 70 got the
groundskeeper again, bigger. bossArt() now walks nine designs, one per chapter,
and hands out THE CHAMPION on every tenth — the floor(ch/10) term takes his
chapters back out of the count so chapter 11 opens the second lap on the
groundskeeper rather than skipping him.
70 THE STRINGER — leather apron, bandoliers of gut string, racket frames on
his back. He takes your lane and puts two dead-straight ones on it.
80 THE TOURNAMENT DIRECTOR — blazer, lanyard, radio. `adds` is new: he calls
stewards onto the court while you fight him, which is the one thing a boss
fight could not do before, since the stage is cleared for it.
90 THE STRAWBERRY SELLER — tray of cream, apron soaked red, ladle. A tank
that sprays four at once and then comes for you.
100 THE CHAMPION — torn whites, a laurel of strapping tape, the trophy still
in one fist. `rally`: anything under splatter quality comes straight back
at your head, so he is the one body you have to out-HIT rather than
out-shoot. `phases` lays three rows over his own as his health falls — he
holds the baseline and rallies, then runs the line drilling volleys, then
drops to a charge — and each turn is an event: cry, shake, the stand up.
Thirty-six frames through assets/_regen.py, the mascot's three-batch recipe
(2 stride, 3 wounds, 4 throws). The trap that cost the re-rolls: a throw batch
references its own wound frames, so a wounds batch that fails leaves the throw
sheet drawn against the SEED — two of the four came back half groundskeeper.
Wounds first, verify, then throws.
And the physio, who Oliver reported "does basically nothing but move towards
the net": he kept 4.2 of every 6.3 m he charged, so the fight was always over
before he arrived. `withdraw` is his own number now (0.2) and he charges half
again as fast — measured, he is on top of you in 11 s instead of never — and he
drills three balls a cycle on the way in.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
9476f354c2 |
A real hand for the ghost, and the countdown hugs its frame
- x_hand.png generated to Oliver's reference pose (index up, curled knuckles, thumb) — drawGhostHand draws the art, vector stays as fallback - fingertip anchor measured off the PNG, not guessed - RESUMING panel top no longer floats above the glyphs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
a89e8ff419 |
Seven verdicts from the test group
- opening CPU beatable in a game or two (softer cap, more whiffs, rubber band) - modals are for calm courts only; live-court news is a card now - the transition howto is gone: a ghost hand teaches the pull, the toss, the apex and the swing, each on the exact struggle it answers - a shot barrel keeps your weapon and glows its slot instead - switching weapons clicks, the live tile is unmistakable - barrels come faster the deeper the run goes - the umpire triple-tap cheat is deleted Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d847c2e4a8 |
Sweep the dead code off the court
EATEN_GRAB (write-only marker), ball.firstLand (written five places, read nowhere), zombies' unused VOLLEY import, three scoreboard palette fields from the pre-PNG board, the x_tin.png nothing references, and a dozen export keywords on functions no module ever imported. Kept on purpose: o_run3/o_run4.png — the SPR list excludes them so both tours run on two frames (the women's set has no 3/4 twins), per the comment at the preload list. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c5edc94b73 |
Redraw the backdrop and cool the menus down
The court art had three faults: a hard-edged pale panel sitting on the near half, mown stripes that stopped and restarted where the court began, and the blood pool stranded behind the baseline where a portrait phone cropped it off. Redrawn with the stripes running unbroken the full depth and the stain out on the court where it can be seen. The picture also read as zoomed in, which was not the art but the crop: at 4:6 `cover` was throwing away a third of the width on a phone. The sky is extended to 1:2 instead, so the whole arena survives the crop. Menu palette rotated off olive towards a cool desaturated blue-green with much quieter borders, and the boxed BEST RUN / WORLD RANK bar is gone from the splash — it sat on the best part of the art to say what the two buttons under it already said. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
f3b0b791d6 |
Trade the tutorial for a screen that counts the dead
The menus were placeholders next to the art. Three screens now: a title over an empty, dimmed, blood-stained court; a MATCH SETUP screen where each court wears meters read straight off its own bounce numbers; and BODY COUNT, which needed the game to start measuring anything at all. stats.js keeps one capped array of per-run records — all time, last match and seven days are three filters over it rather than three counters that can drift apart. Kills file under the body AND the weapon: killZombie only ever knew an index, so every damage loop stamps the cause on its way past. The tutorial is gone. Marvin explained what the in-game modals already teach, and his stage is where the stats live now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
0ca43bf68f |
Let the ball follow the mirror, and stop the serve shrinking her
Four things Oliver caught on the phone. The screen title is the ANNOUNCEMENT face now. A menu heading is a thing that shouts, the same register the canvas gives SET and CHAPTER — it was wearing the HUD font, which is the voice for scores and key hints. Bangers carries its own weight, so it takes size and air instead of Silkscreen's letter-spacing, plus the padding under it he asked for. Her toss and smash were shrinking the whole woman. The slice anchors frame 0 to 224 px, and frame 0 was the toss — whose arm is straight up, so the BODY got squeezed to make room for it. This is the trap already written down for the raised racket; it just moved to a different sheet. Re-sliced the retained sheet at the men's toss height (250) instead, so toss/smash/pull come out 250/293/191 against his 250/320/213 and her body matches her own idle again. No re-roll. The pre-serve ball did not follow the MIRROR. drawChar flips the whole sprite when a server faces left, so dodging during the bounce swapped his hands over and left the ball hanging out by the racket — one `* sv.facing` on the offset. It was wrong in the men's world the whole time, it just took a second body standing next to it before anyone saw it. And she reaches further out and higher than he does, so BOUNCE_IDLE's pair is overridable per tour now (0.46/0.78 against his 0.36/0.68). Measured off the drawn hand by eye, which is the only way those two numbers have ever been set. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
55c0f75c37 |
Give him a sidestep, and split the messages into two voices
He died at every chapter boss because he could not move, and there were two independent reasons for it. The movement block is 'rally'-only — a settled feel decision the six boss AI styles were written around — and a boss is fought from 'pretoss'. On top of that canAimServe() is true through a boss fight, so his one finger was already spent on the slingshot: there was no gesture left over that could have meant "move". So it is a SIDESTEP, not a walk. One committed step on a cooldown, carrying invulnerability that beats both damage channels — the chip damage in hurtPlayer and the instant-death contact test. The rule about walking stands untouched. The gesture is picked by WHICH WAY the drag leaves the deadzone, decided once and never revisited for that touch: down-ish is the pull the game already teaches, sideways is the step. No timing window, no invisible mode, so "one gesture means one thing" still holds. The finger-steer modes are untouched — stick.aimable is false in a horde, so the drag is the absolute steer it always was. The apron demo now alternates PULL DOWN and SWIPE TO DODGE, and Marvin has a page for it. The rest of the playtest list: - Bangers is bundled beside Silkscreen, and the register IS the category. The quiet face keeps the HUD, the rack, the control hints and RESUMING IN; the loud one takes SET, CHAPTER, EPIC!, PERFECT and every free-floating flash. Floating text gets one shared y and a drop shadow, so it survives crowd, clay and blood. - Modals queue instead of being dropped. That was why the trainer and the weapon had to be crammed into one card after a won duel; the heal gets its own popup with the x_cross tin over it — a sprite loaded since that merge and drawn by nothing. - weaponModal's first-sight gate moved inside the function. The barrels gated themselves and the tin ladder did not, so four weapons re-explained themselves every three games while a barrel weapon explained itself once. - Ten rack slots. Past five it grows a second row upward rather than shrinking ten tiles under the thumb target; slots 0-4 stay welded to the bottom edge. - The boss win holds for its own 3.6s and says EPIC!, with no GAME n NEXT line — the bar under the scoreboard already prints that permanently. That bar went from 6px to 7px, which is the smallest type in the game becoming words. - The turn punches the camera in on him and the ground shakes harder at each beat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
25f915f236 |
Fill the rack yourself, and roll barrels out that fill it
The rack had five hardcoded slots where the index WAS the weapon, spelled out in
five places: SLOTS, slotStock, WDESC, drawRack's three parallel arrays,
deliverTin's w+1, and bare wsel === 1|2|3 / G.weapon === 4 tests across five
files. config.WEAPONS is the one table now, G.rack holds up to four {id, n} in
the order you GATHERED them, and slot 0 stays BALL. Everything reads through
slotId/slotStock/slotEntry, so nothing else knows how the rack is stored.
A fifth weapon is always allowed, per Oliver: it rides slot 0 as G.temp until
spent, then slot 0 goes back to the infinite ball. A barrel arms on the spot and
is spent quickly, so refusing it would mean a barrel you shot did nothing. And a
spent weapon LEAVES its slot -- with gathered slots, an exhausted weapon holding
one is a quarter of the rack you can never pick up into. (The old rack kept its
empties dimmed, which was right when a slot meant "BOMB goes here".)
Four new weapons, and they are all the ONE existing pellet loop. firePellet(w)
stamps the WEAPONS row onto the round -- r, dmg, hp, pierce, boom, ignite, col,
speed, life -- so the beacon cannon, flamethrower, laser and rocket needed no new
array and no new update; the break after the first body was the only thing making
a pellet single-target. All four go through the same isBoss branch, which is why
none can silently no-op on a boss the way three of five once did. Measured:
gun/beacon/laser/rocket reach past 16 m, the flamer is a ~6 m cone that ignites.
G.mgT stays the mode timer for all of them, so the eighteen !(G.mgT > 0) guards
across five files did not have to change. kind:'burst' auto-fires at its own rate
for len seconds; kind:'shot' holds the same braced stance and fires one per tap.
The barrels reuse the ball machine's shape: a rolling, shootable non-body. All
three of the registrations that fail silently are done -- blocksClear (loot must
not gate the hand-clear), the contact guard (furniture cannot eat you), and
hitR/GORE_MUL/zombieH. It scores nothing.
The pink had to become procedural. The chroma key deletes every pixel where red
and blue both beat green by 40, so a magenta drum and a magenta laser pistol both
came back as hollow outlines -- 19% and 15% solid against 50-56% for the icons in
other colours. The drum is grey steel with hazard chevrons and every bit of its
pink is the halo drawBarrel paints; the laser went electric blue. Both are solid
now (92% and 58%), and the rule is written down in HANDOFF.
tinTriples 3 -> 10 as asked, and the four dead TUNE ammo constants are gone --
nothing read them once the table landed, which is a panel value you can tune all
day for no effect.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
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> |
||
|
|
0ce857fedc |
Let the ball eat its own aim band, mark the body, grow the machine
Four things from the phone. THE BAND NO LONGER BLINKS OUT. Cutting it at the instant of release made a fast rally strobe — band, gone, band, gone. hitServe now records the arc it actually fired (error included, so the ball tracks what it is consuming to within a centimetre) and the ball eats it from behind as it flies. The aim resolves into the shot instead of vanishing. It ends early when the ball does, because a shambler swallowing the shot is a cut you want. THE TARGET MARK SITS ON THE BODY. A ring on the grass pointed at the one part of a shambler the shot has nothing to do with — the ball strikes anywhere under 1.9 m and the wound lands on the torso. So the mark rides at chest height when there is a body to hit and lies flat only when the lane is empty. Filled and washed out in the band's own colour instead of a hard 2px ring, which stops it reading as a decal stuck to the lawn. x_howto REDRAWN for the current gesture: thumb dragging back to a print, the shot leaving as a curved band onto the shambler's chest, shadow dots beneath. The old one still taught a straight arrow. MACH_H 1.15 -> 1.55. Waist high read as a prop you could ignore. Verified on a cache-busted build (the pane was serving a stale config.js, so the first pass measured nothing): preview velocity and gravity still identical to the fired ball, same flight time in the trail, unravel smooth to the hit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
2d4fa79258 |
Give the menu buttons room, a frame, and bigger type
The logo now sits exactly as it does on the title screen — same width, no menu override — with 30px under it so the blood running off its bottom edge stops touching the first button. That button also had no top border to cut into: the crop was a BOTTOM band of a taller plate, so it carried the plate's bottom border and nothing above. Both plates are cropped to their interior now and CSS draws one consistent frame on each, gold for START and slate for TUTORIAL. Lettering stays live text and goes to 15px with wider tracking. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4a034773aa |
Draw the tutorial properly, and put the logo and gore on the menu
The tutorial was borrowing a court tile, the in-game how-to icon and a weapon icon, none of which were drawn to be looked at. Four purpose-made 2:1 panels now: the serve with a tapping hand, the slingshot pull with chevrons into a zombie, the weapon rack, and the floodlit duel. They get the full card width above Marvin instead of being squeezed into the text column beside him. The menu wears the title logo instead of a Silkscreen headline, and the two buttons wear generated plates — START GAME bloodied, TUTORIAL nearly clean. The lettering stays Silkscreen on top: generated type is a lottery and would not match the rest of the HUD. Two things the art needed. Quantise the panels at 128 colours, not 64 — big flat fields with saturated accents is where a small palette shows, and 64 turned the tennis ball beige. And find a plate by its BRIGHT BORDER, never by the corner colour: one plate came back on a near-black margin, the other on a transparency checkerboard that differs from itself everywhere and matched the whole canvas. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
9a65a50cc4 |
Put a menu in front of the game, with Marvin to explain it
The overlay is a four-stage machine now — splash -> menu -> {courts, tutorial}
— swapped by data-stage while the backdrop and card frame stay mounted, so the
live court never flashes through between screens.
Court tiles SELECT only. Tapping one used to start the match on the spot, which
gave you no way to compare the three and no way back out; START GAME and BACK
are their own buttons now. Headlines are 15px against the 11px body, so a screen
reads as a screen rather than as a list.
The tutorial is a data table: a Marvin pose, an optional picture already in
assets/, and his lines. Seven pages covering tennis, the turn, the slingshot,
the rack and the duel. Marvin is four new frames off x_boymop1 — waving,
pointing, shrugging, and clutching his mop in terror.
The theme no longer starts at boot. A title screen that plays at you before you
have touched it is rude, and the autoplay policy would have held it silent until
that first tap anyway.
Death returns to the menu instead of the title. The reload stays — it is the
honest full reset — and a sessionStorage flag is what picks the stage on the way
back in.
Machine-gun rounds are tennis balls, so they are drawn as balls and take the
perspective. A fixed 4x4 fillRect was exactly as big at the far baseline as at
your feet, which read as square confetti pinned to the screen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
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> |