Commit Graph
30 Commits
Author SHA1 Message Date
wuwiandClaude Opus 5.5 75b450f105 Story phase 1: the spine — acts, cups, chapters, checkpoints
The front door now continues the story. An act is its league cup, whose
final is the boss's own body (acts II and IV play their band out of
ladder order for that), then a ten-game gore chapter on the act's court.
The chapter has no duel at all, takes the act's weapon stock into the
rack, meets a mid-boss after game 5 (the checkpoint: restock and full
blood) and the act boss after game 10, then the act card. Death is not
permadeath: it counts the attempt, banks nothing on the boards, and
TRY AGAIN restarts from the checkpoint the profile holds. Barrels hold
what the act has earned. The chapter plays the arcade's games
base+1..base+10 so every director curve keeps its slope.

Profiles from before the story start it from the top (league, cups and
champion reset; level kept). The server mirror stores the act and
checkpoint and only ever moves them forward.

Plain cards stand in for the screens (phase 3) and the transformation
(phase 2). Six new tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 14:10:09 +02:00
wuwiandClaude Opus 5.5 d561050af8 Steady the gold ring, and move the praise word off the ball
Oliver: the target ring twitches sometimes, and "shot of the day",
"textbook" etc. cover the ball. The timed ring re-solved every frame and a
small shift in the auto-run's target moved its moment by up to 158 ms in a
frame; the drawn cue now counts down on its own clock and eases toward
each solve (p99 jump 100 -> 17 ms). The rally's praise word rose straight
into the struck ball's path (and a serve's sat on the far bounce, where the
return comes back); it now lands under the player, behind his feet, and
stays put. Same moment, out of the way. v371.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:52:34 +02:00
wuwiandClaude Opus 5.5 74aa209753 Give the machine, fire, gun and boss their missing sounds
All four synthesized in audio.js like every other weapon: a squeaking
castor, a court-wide crackle clock plus a throttled catch, a real gun
crack, and one shared boss voice (stomp per stride, wind-up breath,
bark, pain, death roar) pitched by body size. Closes OPEN 13 pending
the phone verdict. v355.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 09:16:27 +02:00
wuwi fe64242090 Add opt-in phone gameplay recording and JSON export 2026-09-07 10:19:38 +02:00
wuwiandClaude Fable 5 5c7521f867 THE SEASON, phases 1-4: the front door is the fixture, and the club remembers
The tournament is the canonical path, so the game finally says so. The splash's
primary button names the next cup fixture and enters the draw; the poster wall
moved one tap down and deals three named rooms (the season, the training, out of
competition) with the cup at the top. Two profile fields remember every attempt
— rounds ever reached per league, and the body that put you out — so a lost cup
is a line on the board instead of a null event, and the training court offers
that body first, marked. The promotion names what waits in the new league.

Overlays consolidated in the same pass: three modal drawers became one box with
a masthead, hairline-separated rows and a real footer button (any tap still
dismisses), and the run-end summary wears the same box — headline, xp, mark, and
the fixture with ENTER THE DRAW beside PLAY AGAIN. The cup's own exit is a
ceremony now: OUT IN THE QUARTER-FINAL, your best standing beside it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 11:14:25 +02:00
wuwiandClaude Fable 5 41f1b6815f The copy audit: the climb card stops calling itself a death match, the machine gets named, the drill decrypts, the duel names both stakes
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 17:25:41 +02:00
wuwiandClaude Fable 5 7d39032907 The weapon licence, the peer line, and the steward gets his vest back
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 15:52:51 +02:00
wuwiandClaude Fable 5 787877bce6 The leagues, phase 4: the duel learns, tin tiers by league, level buffs, the board wears the badge
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 12:53:12 +02:00
wuwiandClaude Fable 5 16bf5b9fed The leagues, phase 2: five bands, pace tiers, locked courts, the members' ground, the promotion ceremony
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 12:43:23 +02:00
wuwiandClaude Fable 5 9133c0d351 The leagues, phase 1: the profile clock ticks — xp, levels, the identity screen, the server mirror
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 12:22:33 +02:00
wuwiandClaude Opus 5 4f4534e272 The lob is the flick you already have, and a game stops being a death
Three from Oliver.

THE LOB. He picked option (c) from the written comparison — "no additional
gesture to learn" — and corrected the premise: the zombie does come to the
net (racketAI chases to the far service line, the classic CPU to z 9.5). So
the forward flick keeps its one meaning, "put it deep behind him", and the
game picks the trajectory that actually does that: a smash while he is back,
over his head once he is in. oppAtNet() is the whole switch, and it reads the
BODY, not O — in the match O is the invisible tennis body and only moves
between points, so it would have answered "on his mark" all rally.

lobArm 7.0 m is measured, not taste: a lob peaking at lobApex passes a man 7 m
from the net at almost exactly CONTACT.maxY, so the shot arms exactly as far
out as it can still clear him. The pace is derived from the height, so the same
arc comes off the baseline and off mid-court, and SHOT_DEPTH.lob is deeper than
deep — at the ordinary deep target the ball passed 2.05 m over him at the edge
of the band, which is inside his reach.

PACE, SEEN FROM THE STRIKER. "the opponent sees the ball really fast - the
player barely sees the speed increase." Measured: the ball is the same ball on
both phones, the camera is not — it compresses your own ball into the distance
and expands theirs as it arrives. Pace untouched; three channels added on the
striker's side: the ghost streak now reads pace (3-8 ghosts, was 3 at every
speed), the crack is pitched by it, and a fast ball kicks the camera. The
serve's own ladder is 49.2 plain / 50.8-52.4 hard, because serveArc's loft
margin takes 10% of the +9 back — widening that is his call, and PROJECT.md
records the knob and the risk.

THE DEATH MATCH. "each player has 8 blood points but the player explodes for
the winner already at 0:40." Two clocks: a game is four points, a life is
eight. A won game is only a game now (flash, serve, heal); the burst moved to
the death itself, on the dead message, where both phones agree. The summary
waits 1.4 s for the gore, because update() freezes the world on G.done and
would have hung a still frame of an explosion behind the trophy screen. And
bleedO tracks THEIR blood off the pos stream instead of our points-this-game,
which reset every game and ignored the heal.

4 new tests, 113 pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 12:58:48 +02:00
wuwiandClaude Opus 5 d71a26831a The game gets a shell: it buzzes in your hand, never turns sideways, and a swipe walks you back
A Capacitor iOS project (`npm run ios`), and the first three bullets of LATER item 2 built. The web
build is untouched and stays primary: every line here degrades to a no-op in a browser.

- HAPTICS ride the sound map, as the plan said they would: six call sites, all of them places an
  sfx already fires — your racket contact, the serve strike, `killZombie` (the one funnel every
  weapon routes through), `explode`, `hurtPlayer`, `killPlayer`, the weapon swap. The restraint
  rule lives in `haptics.js`, not at the call sites: a minimum gap per kind, so a bomb that kills
  twelve bodies is felt twice and the machine gun is a metronome rather than one long rumble.
  Pinned by a test. Untested on hardware — the simulator has no Taptic Engine.
- The plugin is reached through the INJECTED bridge, `Capacitor.nativePromise('Haptics', ...)`.
  `Capacitor.Plugins.Haptics` reads like the zero-dependency answer and is silently empty without
  @capacitor/core's registerPlugin — it would have fallen through to the web path inside the app
  and looked exactly like a phone with haptics switched off. No package JS ships.
- PORTRAIT is enforced where it can be: one orientation in Info.plist, so the app never rotates.
  That retires landscape as a surface rather than fixing it (OPEN 6). The web half is best-effort —
  `orientation` in both manifests and one guarded `screen.orientation.lock()`.
- SWIPE LEFT GOES BACK. The gesture finds the screen's one visible chevron and clicks it, so the
  shape of the wizard stays in the handlers that already own it. Not passive on purpose: a swipe
  inside a poster card also fires that card's click, and preventDefault is what stops the screen
  answering forward in the frame the chevron walked it back.
- The app has no server behind it, so `config.API` and one branch in `net.js` point the shell at the
  live host by name. Consequence: the app always talks to PROD, never staging.

`node test.js` green (105) over four seeds; builds, launches and reaches the native plugin on the
iOS 26.5 simulator; swipe-back walks the whole wizard in the browser pane.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 17:00:36 +02:00
wuwiandClaude Fable 5 4c47295a73 The friend across the net is no shambler: he just bleeds, and then he bursts
Two retest findings, one root: the death match borrowed the roster's
body. Gone — nothing spawns, no walk-on card, the living opponent is the
whole picture. His damage reads as blood (bleedO: an anchor per point
taken this game, love to game point, thin drops as ever), the game
point bursts him where he stands, and IT RISES AGAIN brings him back
clean. The heal beat became a card in netplay — a modal freezes this
world while the other phone's keeps moving.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 18:10:50 +02:00
wuwiandClaude Fable 5 c0b59eee90 The host names the stakes: classic tennis, or the death match
Three chips under VS A FRIEND — the host's pick rides the room and the
start message rebuilds both courts on pairing. In the death match your
opponent IS the zombie: their lost points tear its wound stages, their
won points draw your blood, and bleeding out crosses the wire as the
other phone's win. The same receiver-is-judge verdicts drive the match
machinery through resolveInterlude, so the fall, the mop beat and the
burst come along unmodified. New: gushProne — the game-winning body
bleeds out on the ground, from prone-height anchors, until it bursts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 18:02:44 +02:00
wuwiandClaude Fable 5 f9b26e5380 THE TOURNAMENT, the pace-lit trail, and brighter opponents
The fourth mode: four rounds up the dead hierarchy (CUP_ROUNDS), each round
drawing a rung from its band, the champion always the final. It rides the
match machinery whole — cupDraw feeds the drawn rung through d.opp so every
ladder dial scales untouched; the body's pool spans the round (need-scaled
pool and divisor, so wounds carry between games); cupGameWon holds the same
body mid-round, kills it on the round, and past the final cupWon banks a
NOT-dead match run under CHAMPION OF THE DEAD CLUB. Blood carries; round one
is a single game; deathmatch stays as the arcade run. Full-run state test.

Sidequests: the ghost trail is pace-qualified (TUNE.trailSpeed) so both
tennis modes streak — pow>0.8 was only ever reached by the gore slingshot;
opponent sprites draw ~18% brighter via a load-time baked copy
(render.brighten, TUNE.oppBright) with no PNG touched; the cup scoreboard
line names the round instead of lying with deathmatch arithmetic; the rack
and the apron demo no longer draw under a trophy screen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 22:20:10 +02:00
wuwiandClaude Fable 5 c084d53497 Serve reads, the mop beat, the head-icon card, and Daniel benched
Oliver's batch, the code half:
- The tennis serve lost its ghost fingers (idle tap demo + apex rider deleted
  with their counters; the rally-return hint survives).
- The toss rises over the tossing HAND, measured off the toss frames' raised
  fingertips (TOSS.dx/oDx, women's tour override) — not over the head.
- An uncaught toss returns to the bounce stance instead of flashing the smash
  frame the player never earned.
- Between match games the court belongs to the mop boy: 5 s (TUNE.matchMopT),
  full job allowance, a hurry speed between stains, and the far-court reach
  limit lifted for the match (his stains are exactly where opponents die).
  The next opponent walks on only after — d.ending guards the empty court.
- The walk-on card wears the opponent's own head: config.HEAD_CROP, straight
  crops measured off the family sheets, no new art.
- The match intro is DEATH MATCH, counting the real blood pool.
- Daniel is benched: every ump_* personality line routes through aside()'s
  early return; officiating calls keep speaking. Personality pass later.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 17:10:45 +02:00
wuwiandClaude Opus 5 031a203bb4 Blood fountains at neck height over a body lying flat
Two notes off the bench, both about the fall.

THE BLEEDING DID NOT STOP. bleed()'s wound anchors are ABSOLUTE WORLD HEIGHTS —
the headless neck spurt is pinned at y = 1.5 m, the torn arm at 1.15 — so a
corpse lying flat through the whole 2 s floor hold kept spraying from where its
neck used to be, two metres above itself. Oliver: "the dynamic bleeding must
stop during fall as this looks like blood fountains midair." The line calling it
already said "open wounds bleed every frame they're STANDING" and never enforced
it; now it does. Measured on the headless stage: 20 particles emitted while
walking, 0 during the fall. Only the body G.matchFall owns is suppressed —
everything else on court bleeds as before.

THE SLIDE NOW FINISHES BEFORE THE RISE. "The slide must be finished before the
rise" — a body pushing itself back onto its feet is planted, and still drifting
while it stands up reads as the court moving under it. fallSlideMix goes
[.10 .30 .45 .15] -> [.12 .35 .53 0], the rise's share redistributed across the
first three IN PROPORTION so the total travel is unchanged: 0.06 / 0.26 / 0.41 /
0.000 m, still 0.77 m end to end.

Watch fall3: it carries 0.41 m over its 2 s hold now. The ease-out front-loads
it so it should still read as a skid that settles rather than a creep, and if it
drags, share moves back into fall2.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 08:23:19 +02:00
wuwiandClaude Opus 5 df454c5d92 Stop calling three different things "the ladder"
Oliver: "the talk about ladder and rung is extremely confusing." It was, and it
was my vocabulary, not his reading. "Ladder" had come to mean three unrelated
things in this codebase — the sequence of opponents in zombie tennis, the
sequence of WOUND STAGES on one body, and the name of a stats screen — and
"rung" meant a position in whichever of those you happened to be reading about.

One word per thing now:

  the OPPONENTS   who you play, in order, one game each (config.OPPONENTS,
                  matchFoe(n), zb.foe). Never "rungs".
  WOUND STAGES    healthy -> arm off -> headless, on one body. Unchanged, and
                  now the only thing allowed to be called a progression.
  a FAMILY        the sprite set a body wears (config.ZFAMS).

Renames: LADDER_TRAITS -> OPPONENTS, matchRung -> matchFoe, zb.rung -> zb.foe,
and the stats screen THE LADDER -> OPPONENTS FELLED, which is what it lists.

The 'L0'..'L9' record keys are deliberately NOT renamed: they are written into
saved run records, and renaming them would orphan every match anyone has
played. A comment says so where they are built.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 13:11:55 +02:00
wuwiandClaude Opus 5 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>
2026-08-12 11:25:29 +02:00
wuwiandClaude Opus 5 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>
2026-08-12 09:34:26 +02:00
wuwiandClaude Opus 5 66fdad3d74 A match run is a different kind of run, and stops polluting the gore stats
The two modes share no currency: gore counts kills, multi-kills and weapons;
zombie tennis counts opponents felled and has exactly one kill per opponent,
always credited to 'bomb' (matchGameWon's explode). Every one of those was
landing in splatterRuns as a gore run, on the roster as THE CLUB PRO, and on
the KILLS world board — where a five-body match would post a row next to a
300-kill massacre and mean something else entirely by it.

One character on the record fixes all of it: `m`, absent meaning gore (which
is what every existing record is, so no migration). read() takes a mode and
so does endRun, which now compares you only against your own mode and refuses
a name prompt from a match.

The record's two headline slots carry each MODE's two headline numbers — gore
g=games s=sets, match g=felled s=games — so `g` stays the thing everything
ranks on and the match best is derived from the record instead of living in a
second key that drifts. That retires the matchBest key from the last commit.

Found on the way, pre-existing and visible: endRun returned `{...best, kills,
games}` and the later key won, so G.best.games was the run that had just died.
The gore death screen has been printing "BEST <this run> GAMES · <the record>
SETS". The return now carries the best and nothing else.

And stats.check() — the module's own fixture check — was exported and called
by nothing. Wired into the suite, with the mode split added to it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 08:58:04 +02:00
wuwiandClaude Opus 5 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>
2026-08-12 08:53:02 +02:00
wuwiandClaude Opus 5 ea6e50f8f8 The match plays fair: deeper pool, no regicide, serves from the line
"the player is too weak in this mode. also the killing by smashing occurs too
often. i cant really control it. maybe obsolete? also the serving after the
first serve does not happen from behind the groundline".

matchPlayerHp 8 — the match brings its own pool. The gore mode's 4 was an
inheritance, not a decision, and a real game has rallies you deserve to lose
without the run ending.

Regicide is out of the match (kept in the duel, where any-weapon-counts is
the point of sudden death). In a full game every hard serve that happened to
meet the body was a free kill nobody aimed. A match ball never damages the
opponent — it passes through like a soft ball and racketAI plays it — so the
wound stages are exactly the scoreboard. That also made hp-per-opponent
meaningless, so the ladder escalates in racketAI instead: +12% speed per body
felled, whiff chance x0.82 per rung.

And serving resets to behind the baseline with real deuce/ad mirroring:
setupPoint's "serve from wherever you're standing" branch now excludes the
match, which falls through to the tennis positioning. Verified live: second
serve from z 24.3, x mirrored to the ad side, opponent unwounded by anything
but the score.

38 tests, 15 seeds clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 22:31:57 +02:00
wuwiandClaude Opus 5 5b2f1d55ce The roster is the alphabet: the sequence is the zombies themselves
Oliver's insight, and it retires a whole subsystem: "we don't need colors at
all! we already have different zombie types.. so the sequence is the zombies
themselves".

The bib and then the ground disc were both inventions bolted on to give a
body an identity it already had. A brute does not look like a runner, the
game is built on reading those silhouettes at speed, and the strip can show
the body's own walk frame instead of a swatch — no legend, no mapping, no new
art ever. It also deletes the accessibility problem outright rather than
deferring it, because a silhouette is not a hue. DRILL_COLS, drawColourRing,
drawBib, PIP_FACES, blip(), drillRingR and drillTones are all gone.

Two rules keep a type unambiguous: the plain shambler is never in the
alphabet (it is what the eaters are, so "not the puzzle" needs no marking),
and one of each type is on court at a time.

The new failure mode this created is instructive. Types move at different
speeds and drill bodies hold their lane, so one in a lane you are not in
walks the whole court and off the back untouched — stranding the sequence
asking for a body that no longer existed, i.e. the un-winnable state the mode
exists to prevent, reintroduced by a change that looked cosmetic. A batch
body crossing your baseline counts as caught now ("GOT PAST YOU"), which is
also just better design, and the speed spread is narrowed to 1.35..0.70 so a
batch stays coherent. Their real difference is HP and that is untouched: a
runner is one shot, a roller is seven.

Verified: 14400 frames of simulation, zero frames where the sequence named a
body that was not on court.

29 tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 20:24:58 +02:00
wuwiandClaude Opus 5 d775ac40ec The colour goes on the court, and the dead go off like everyone else
"the colored zombies should also explode like they did before and make the
normal splash sound on destruction.. no bleeps please.. can we make the color
as a color circle the walk on? the sign they're holding is not nice. also
remove the ball slicing in this mode.. this leads to unwanted hits".

The silent deaths were my bug: the hook in killZombie did drillKill(zb) and
then RETURNED, and everything below that return is the death — decal, pile,
36-particle burst, splat, headshot crack. It resolves after the splice and
without returning now. Both halves are load-bearing and each was a bug in
turn: splice first or a freshly drawn combination names the corpse; don't
return, or the body simply vanishes.

The colour is a disc on the court the body walks on, drawn before the sprite.
The bib was a flat quad pinned to a walking sprite — UI stuck onto a
character, covering the art it sat on. On the ground it takes the court's own
perspective, measures 12.4px across at the spawn line against a chest patch's
few, and hides none of the sprite. A hard dark rim plus a bright inner line
is what keeps it from reading as another gore decal.

No slicing in the drill: a cut ball leaves the corpse and strikes whatever it
finds next, which in a mode where the wrong body is the failure is the game
choosing your target for you, badly.

And the mode has no sound of its own now, deliberately. The umpire reading
the list was vetoed, the arpeggio that replaced him was vetoed. blip() is
deleted. A kill bursts and splats like every other kill in this game; the
strip carries the list.

28 tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 19:57:56 +02:00
wuwiandClaude Opus 5 abd6d4f789 Every colour you are asked for is already on the far side
"make the game that it is actually winable meaning that all required colors
are visible on the far side before when a color combination is shown.. for
now no colors that are not in the current combination but occasional zombies
which don't have a color and are converging to the player trying to eat him".

Two rules define the file now. Every colour on court is IN the current
combination — no coloured decoys, so the strip can never name a body that is
not there. And a combination is only shown once its bodies are all on the far
side, which is free because the batch is BORN there: it is a property of the
construction rather than a check that could be wrong.

The pressure moved to the eaters: plain unbibbed shamblers on their own
clock, deliberately not flagged `drill` so they keep the gore mode's homing
and converge on you while the bibbed batch walks its lanes. Killing one is
never a fault; letting one reach you is the ordinary caught. The puzzle plays
fair and the danger arrives separately, which is a better trade than a puzzle
that cheats.

The batch is the round: kill the last of it and the next walks in on the same
frame with its combination already called.

One real bug, caught by the new test: killZombie resolved the drill BEFORE
splicing the body, so a combination drawn on the spot could name the corpse —
a round asking for a colour that is not on court, i.e. exactly the unwinnable
state this pass exists to prevent. Splice first, resolve second.

28 tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 19:29:04 +02:00
wuwiandClaude Opus 5 1d49c4343b The rounds stay, the seams go: one continuous stream
Oliver: "daniel announcing is maximum annoying" / "don't let the rush in
position just let them coming from the back in steady speed.. try to space
them not too close" / "when one combination is done just let the new one in..
in a continuous stream that increases in speed and difficulty" / "the rounds
can stay, they should be continuous".

The round survives as the unit. Every seam around it is deleted: no walk-on
to a mark, no announcement beat, no between-rounds pause, no deal. Bodies
walk in from the far baseline forever and a round is drawn from whoever is on
court — finish one and the next is drawn in the same frame, off the same
rolling population. The bodies you were ignoring a second ago are the ones
being called now.

- Daniel is gone from this mode. Each colour has a pitch; a round announces
  itself as a four-note figure in half a second (blip()). drillTones 0 mutes
  it. The recordings stay in assets.
- PLACEMENTS is deleted with the marks — nothing rushes anywhere and nothing
  stops, so there are no shapes to place.
- A DRILL BODY HOLDS ITS LANE. updateZombies homes bodies onto the player's x
  past the net, so they arrived spread and bunched into a knot right in front
  of you; spawn spacing cannot survive that. Their lane is the puzzle here.
- One ramp on TIME SURVIVED drives speed, spawn gap and round length. Speed
  is stamped per body at spawn, so the ramp reads as newcomers outpacing the
  ones already walking, not the court accelerating under you.
- The empty-court watchdog is gone and nothing replaced it: with a stream
  there is no state to be stuck in. One line covers every case.

27 tests, rewritten around the stream.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 19:06:48 +02:00
wuwiandClaude Opus 5 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>
2026-08-11 18:01:33 +02:00
wuwiandClaude Opus 5 c659b6882f A burst fires for its whole window, and the countdown is the tile itself
The gun, beacon, flamer and rocket fell silent the moment they cleared the court:
firing and steering were both gated on phase 'rally', and every beat raised by the
kill that empties the court (a point, a won game, the mop-up) parks the phase in
'over'. A burst is its own control mode for its whole len, like a horde — its own
clock is the only thing that ends it. Not fixed by forcing the phase back to rally:
a parked 'over' beat that never expires is how pendingHorde gets stranded.

The countdown moved off the tile's bottom edge (the rounded frame, and the bottom of
the screen) into the tile: the armed slot's well drains as a gold wash, clipped to
its own rounded path, under the icon and labels.

16 tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 16:49:02 +02:00
wuwiandClaude Fable 5 1f636530b9 The code moves into src/: index, server and the test harness keep the root
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 13:45:23 +02:00