"please make the zombie killable with a single hit for testing" and "remove
the fake zombies for now".
drillOneShot drops every body on a drill court to 1 hp, applied at spawn to
the batch and the eaters alike. Worth knowing what it removes: the roster's
HP is what makes the called ORDER matter — a roller is normally seven serves,
so being told to take it third means starting on it first. At one hit that
planning layer is gone and the sequence is pure recall. Flagged in PROJECT.md
so it gets decided on feel rather than left on by drift.
drillEaterMax 3 -> 0 switches the plain shamblers off. The mechanism is
untouched — spawnEater, the wide entry away from the gang, the never-a-fault
rule — so one number brings them back.
The eater test now asserts whichever contract the knob describes (at 0:
nothing but the puzzle ever appears; above 0: they arrive, capped, always
plain shamblers) instead of being deleted and rotting. And the never-a-fault
rule gets its own test with a hand-placed body, so the rule the eaters depend
on stays covered while they are switched off.
31 tests, 15 seeds clean. Verified live: brute/steward/roller all at 1 hp
where the roster says 5/5/7, zero strays over 40s, one aimed serve puts down
the roller.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The pipeline caught two flaky tests of mine that sixty local runs had not.
Both were bad tests rather than bad code, and both were mine from this
session:
- the eater test counted SURVIVORS, not arrivals. An eater crosses the whole
court in ~24s and despawns, so a snapshot at 40s can legitimately show an
empty court — that read as "the eaters never came". It tracks arrivals over
the window now, and asserts the cap as a running peak.
- the auto-aim test never actually aimed at the body it claimed to: fy was
pinned at -0.95 and fx was a scaled x offset, so the ray leaned up-court and
the called body could genuinely be the best-aligned thing in the cone. That
is the magnet working. It now normalises a vector straight at the wrong
body, picks the one furthest in x from the called one so the two are
separable, and asserts the magnet takes what you pointed at.
Measured 8 failures in 40 runs before, 0 in 60 after.
And the durable fix: Math.random is seeded at the top of test.js. This suite
is a Dockerfile build stage, so a red run blocks the deploy, which makes a
1-in-5 flake worse than no gate — it trains you to re-run it. The game leans
on randomness everywhere, so the stream is made reproducible rather than the
randomness stripped out. SEED=1234 node test.js re-rolls it; 25 seeds pass.
The cost is noted in PROJECT.md: under one seed a guarded test can silently
take its early-out forever, so I checked all four guards still run their
bodies.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
"remove the mob boy - make the normal zombies rarer and not so close to the
color gang".
sendBoy refuses during a drill, guarded at the function rather than at the
call sites — same shape as its existing G.interlude guard, so every present
and future caller inherits it. He is one more body crossing the lanes you are
reading colours down, and unlike everything else out there he is neither the
puzzle nor the threat. Measured: 0 boy-frames in 90 seconds. Gore no longer
gets mopped in this mode; the existing 40/30 caps are what bound it.
The eaters are rarer — every 12s falling to 5s, capped at 3 on court. At the
old rate they were a second crowd rather than an interruption, and a threat
you break off to deal with only works if breaking off is occasional. 8
arrivals in 90s where the old numbers gave ~13.
And they enter wide of the colour gang: best-of-9 lane sampling against the
bibbed bodies, the same trick the batch uses. Narrowest clearance measured
across 8 spawns was 0.91 m. They still converge on you afterwards — that is
what they are for — this only decides where they walk in, so they stop
screening the bodies you are trying to read.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"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>
"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>
"what are the dice dots for?" -> "drop them", and "the dodge swipe should not
reset to the middle when firing".
The pips were colourblind redundancy so the mode did not rest on hue alone,
but they were noise to everyone who can separate the colours and they shrank
to nothing on a body at distance — exactly where the channel had to work.
Gone from the bib and the chip. Recorded in PROJECT.md as a known gap rather
than pretended away: if the mode ships, the answer is bibs that differ by
shape at sprite scale, not dots.
The dodge one was never a drill bug. The auto-assist recovered toward HOME.P
including its x, so firing walked you back to the centre line: measured, a
step out to 1.94 m was dragged to 0.77 m over one ball's flight — most of a
dodge, handed back for free, which is the welded-in-place complaint the
sidestep exists to answer arriving slowly instead of all at once. Splatter
recovers in DEPTH ONLY now and holds whatever lane you stepped to. Tennis
keeps the full recovery: recovering to centre is the real sport's footwork
and there is no sidestep there to protect.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The doc edit threw on its second assert last commit, so the code shipped
without its notebook entry. Also records the two ways a drill test lies to
you: one that never shoots gets caught and the sweep looks like a stalled
stream (use eatenR = 0), and zombies[i] is not the same body across frames
because the array shifts at the baseline.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
"the drill mode immediately goes into some horrible audio loop" — on a phone,
never on desktop, and that is the tell. Three of the four defects can only
happen on a phone, and every one of them is a rule voice() had followed for
months that my new function quietly broke.
- A SUSPENDED AudioContext HAS A FROZEN CLOCK. Scheduling four clips at
currentTime +0/+0.9/+1.7/+2.4 while the context is suspended puts all of
them in the past by the time it starts, so they fire on one instant through
a 1.3s reverb tail. Resume first, re-read the clock after the decode.
- A second call layered instead of replacing. voiceSeq SET voiceUntil and
never checked it. It stops the outstanding list now.
- ...and cancelling a list has to give back the time it booked, or the
replacement queues behind a list that will never play. Measured at 6.6s of
silence before the call the player is waiting on.
- The amplifier was in drill.js: the empty-court net redealt on the spot,
making the repair rate the frame rate — and every deal calls the umpire. It
goes through the ordinary between-rounds beat now.
Verified by tagging each decoded buffer with its source file and counting
start() calls: one drill_call, three colours, spaced, zero overlapping voice
lines. The earlier "clean" reading counted fetches, which proves nothing —
VOICE[] caches, so a repeat play never fetches again.
27 tests; the new one empties the court every frame for six seconds and
asserts at most one umpire call per beat.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
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>
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>
THEY KEEP COMING is a 2.4s 'over' beat again (warnHorde in beats.js) instead of a
flash raised on the same frame the wave lands, and the chapter boss walks on behind
an overlay — his name in red over ENTERED THE COURT — held for 3s by the new
self-dismissing modal clock (m.hold) rather than announced on a routine-set card.
startHorde returns a bool: if the court has been taken when the warning expires the
pendingHorde latch is re-armed rather than the set losing the horde it paid for. The
warning ticks on its own clock, not on the 'over' beat, so a beat that moves the
phase underneath it can't strand it (the mgT lock, again).
Three tests, 15 total.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Tapping a tile and landing a thumb-width off it returned -1 — free court — and
the tap became Space, which instantly armed whatever burst weapon was selected.
Reaching for the flamer and missing by 10px fired the gun: 22px tiles, a second
row the thumb hasn't learned, and a selection that moves under you (a won duel
re-equips the tin it tops up). rackHit now reports a dead margin around the
band (-2), and only the TAP dies there, decided at touchend — a drag or hold
starting in the same spot still steers and still serves, so burst steering
parked low keeps working.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
dropStale trusted e.touches — but a finger stolen for a system edge gesture can
stay listed there forever, so the stick survived and the player stayed welded to
the edge (field-proven: escape taps still swished, meaning dropStale ran and
found the id). The zombie is indistinguishable from a parked thumb by data, so
it's told apart by intent: in the absolute-steering modes, a new finger that
drags past the deadzone while the stick has been silent over a second inherits
the stick. Taps still swish and steal nothing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
"They just overrun you without a chance" — two reasons, and the second one only
existed because of the first.
IT FOLLOWED YOU. The rush tracked your x every frame it was running, so the
sidestep was decoration: it simply came across and took you anyway. The lane is
LOCKED when the rush starts now. You get the whole 2.2 s run-up to leave it, and
what it runs down is that line, not you.
AND IT LANDED ON YOU. A rush that stopped at your feet left the boss standing
there afterwards, and nothing gets you out from under that — dodgeCool is 0.5 s
against a 0.28 s i-frame, so the second contact kills you while the first is
still fading. It stops on a STRIKE LINE 0.4 m short instead, reached on the same
frame the rush timer expires, so it turns and withdraws the instant it arrives.
The version of this that ran the rush THROUGH you and out the far side is why
bossStrike is a short stop and not an overrun: a body past the baseline is
deleted and scores a point, so the boss quietly despawned itself mid-fight and
left the fight running with nothing in it.
Measured, 45 s against the seller, the champion's third phase and the physio:
stand still and the first rush kills you (4.1 s); step out of the lane and you
survive all of it — seven or eight rushes, one sidestep each, the boss on court
the whole time and out at its line between them where you can hit it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
The weapon-test cheat sat on the SCOREBOARD, where a single tap is the miss-log
overlay — so the thing you tap to read a log was also the thing that opened a
sandbox mid-run, three taps in, by accident. That tap does one job again.
The cheats now live behind five taps on the splash logo, where nothing marks
them and nothing in play can reach them:
WEAPON TEST the old sandbox. It stocked only the BARREL weapons, on a screen
called the weapon test — it stocks every weapon in the game now,
eight of the nine slots.
BOSS TEST a portrait of every chapter boss, built by asking bossArt() what
each chapter gets, so the list cannot drift from what a run
actually spawns. Pick one and you land in a real fight at that
chapter's game number with the full arsenal at ×99.
Neither is a special mode: both ride the transition out with laserScene's trick
(force the turn, then step the ten-second cinematic out in one frame) and hand
you the ordinary update loop, ordinary scoring, ordinary death. A test that
plays by different rules tells you nothing about the game.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
The right edge is where iOS keeps Control Center, and it is exactly where the
horde's absolute finger-steering parks your thumb. When the system takes that
gesture it delivers neither touchend nor touchcancel: `stick` survives forever,
so every later touchstart falls through to the extra-finger branch (swish, and
only swish) while touchmove ignores the new finger's id. steerPX stays frozen at
0.97 and the player is pinned to the screen edge, melee firing, for the rest of
the run. Any new touch event proves the old finger is gone if it is not in
e.touches — drop the stick then. Verified against the bug: pinned at x=6.11
before, steers to -5.20 after, and a genuine second finger still only swishes.
And wCap 9 -> 99, which is no cap in practice: game 70 wants a bigger stack, not
a rationed one. The barrel weapons move from "paid out only once all four tins
are full" (a branch an uncapped rack would never reach again) to rungs of the
same ladder, so a late duel still hands out a rocket instead of a fifth bomb.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Oliver, watching a sliced ball circle a shambler it had only wounded: "an
orbiting tennis ball midair, nobody knows what is going on." He is right,
and not only about that shot. Every ball in this game flies a parabola you
can read, so the one that thinks reads as broken even on the shots where it
works. The hook was the wrong primitive, not a hook with two bugs in it.
So there is no hook. At the instant of a kill the ball is struck again —
ONE velocity out of solveShot, the same solver the serve uses, aimed at the
next body's chest — and from the next frame it is an ordinary ball under
ordinary gravity. It cannot orbit, cannot hover and cannot outlive its shot,
because after the strike nothing about the slice is still running.
That also deletes the wound bug rather than fixing it. Six sites spend the
ball (lastHit = 'X') and not one of them cleared the hook, so a ball that
merely WOUNDED its target was left steering at 30% pace toward a point that
body had already walked off — the loop he saw. With no persistent state
there is nothing to clear: a ball spent on a body it failed to kill just
drops, like every other spent ball.
The hop draws its own band, white, so a chain leaves the same chalk streak
every other shot leaves. sliceTurn and sliceHookT are gone with the steering
that needed them; sliceArc stays, no longer as a turn-radius limit but to
keep a carom from doubling back down-court, which reads as a stray
deflection rather than as a cut.
Net 24 lines lighter than the thing it replaces.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A slice hooking onto a body two metres to the side flew a closed loop around
it forever: 285 frames of the same circuit, never arriving, never falling.
A constant turn rate has a constant turn radius (v/w = 2 m), and anything
inside that circle cannot be reached however long the ball steers — while
the hook pins vy at 0, so nothing else ends it either. A crowd is nothing
but close bodies, which made the one place this weapon should shine the one
place it hung.
The rate is no longer a constant. An arc leaving along the current heading
and passing through a body at distance d, bearing a, has radius d/(2 sin a),
so the ball needs 2 v sin(a) / d — solved every frame, because the body is
walking while the ball travels. sliceTurn stays as the FLOOR, which is what
it was always really for: how wide the curl looks when there is room.
Turning faster than needed is safe, the aim being re-solved each frame.
Two things the maths cannot cover, so they are ruled out instead: a body
behind the ball, which no tangent arc reaches at any rate (sliceArc), and a
target that walks out of reach mid-hook (sliceHookT, a hard 0.7 s licence to
steer). An endless orbit is now impossible by construction rather than by
the geometry happening to work out.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Slice is not a tin and not a rack slot. It is what a perfect serve DOES —
the toss ritual's 45 ms window, or a pull that stops inside perfectBand.
Every other weapon is handed to you; this one is a technique, and it hands
the plain ball a real route to the DOUBLE/TRIPLE KILL ladder that until now
only a bomb or a lucky line-up could reach. Banking your specials stops
meaning playing worse in the meantime.
Only a kill hooks, so a wound ends the chain and no brute, steward or boss
ever extends one. It is a crowd skill shot, which is the half of the arsenal
the tins do not cover.
The pull had to learn a third state to say this. Quality SATURATES at
pullFull, so an overpull and a perfect pull drew an identical band: the game
was asking you to hit a window it never showed you. The band now goes chalk
white inside it and the mark on the target grows a second ring — the fill
says where the ball lands, the ring says it will not stop there.
The chain path is deliberately NOT previewed. It is a scallop laid over the
exact patch of court you need to read, it solves the shot before you take
it, and the bodies walk — any chain drawn at pull time is a lie by the time
the ball arrives, which is the one thing the band exists not to be.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The band work from f352612 rides along; everything else is this round's list.
- proj returns a NEGATIVE scale behind the camera, so four ellipse call sites
could be handed negative radii — ctx.ellipse throws on those, and the throw
killed the rest of the frame (no HUD, no rack, no band). All four now skip
points behind the camera.
- a burst tin is no longer a commitment: tapping another slot aborts it, spends
it and hands back the serve loop. Lives in selectWeapon, so the rack tap and
the 1-0 keys both get it. A horde still locks the rack — that is the one place
your finger is already spoken for. Spending splices the rack, so the tap
resolves to a weapon id and re-finds it rather than trusting the index.
- beacon 1.6 -> 2.4 rounds/s: a lob cannon you could not leave AND could not
feel working was two problems.
- FIRE. Flamer throws fat embers instead of a one-pixel thread; burning bodies
get nine tongues over a heat bloom plus smoke; the fire ball gets a halo and a
real tail; an ash death gets twice the motes at twice the life over a ground
bloom and a column. The flamer had no sfx case at all and was using the gun's
dry tick — it has its own roar now.
- chapter bosses carry an HP bar over their head. The no-HP-bar rule stands for
everything else and the comment says why a boss is the exception.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every transient message — milestone card, RESUMING clock, one-line flash — now
centres its ink on CARD_MID and only one is ever up. Three shared pieces do all
of it: inkSpan measures, plate() sizes the fill to what it holds, slideDraw
swipes it in from the left and off to the right with a four-ghost smear sampled
back along its own easing curve.
- weapon card is icon + name + count, one centred row, no prose
- plain cards centre as a block instead of hanging off the band's top edge
- the clock left the net for the band; it takes over when a card or flash clears
- band moved up 20px into the flash's old row, which it can now that they never
draw together
- hold times +0.8s: the swipe eats ~0.55s that the old numbers didn't budget for
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- card: padding 20, smaller icon (46x44 box), band up to 88
- RESUMING IN: Bangers over the net tape, no panel — the countdown
belongs to the court the pause is holding
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- opaque canvas: the compositor stops alpha-blending 2.5Mpx at 60Hz
- settled modals and the death summary idle at 20-24 fps; play untouched
- TUNE.lowPower caps RES at 3 (-44% pixels/frame), flippable live
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- icon LEFT of the text: every card is 62px at a fixed top, fully over
the crowd — the stacked layout hung its lower third over live court,
and the art draws bigger this way, not smaller
- nothing draws or ticks under a modal: a card raised in the same beat
used to burn out dimmed beneath it — it holds and plays after
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- praise was firing at RELEASE, half a second before impact (measured:
32-frame flight, word on frame 0) — it now arms on the ball and pops
on the first body it meets, or its first bounce
- DOUBLE KILL / MASSACRE / RETURNED rise off the thing that earned them,
as world-anchored pop texts — the announcement band is for milestones
- the band got rows: a card can no longer print over the flash line
- GAME N flash deleted (the scoreboard bar already says it), CHANGEOVER
folded into the countdown panel
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- 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>
- one definite band for cards, flash and the countdown — never on the court
- the card heading down a size; RESUMING IN finally on a readable panel
- the gesture-teaching strings are gone, the ghost hand is the teacher
- ghost hand v2: bigger, bigger type, and knuckles that make it a hand
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- 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>
LATER gains two items. Item 5: how a viewer actually reaches the game — bio
links cost two taps and convert badly, Shorts descriptions and pinned comments
click, and most people search later instead, so the handle, an on-screen name
and a resolving domain matter more than any link. splattertennis.com is
registered.
Item 6: a bio-link tap opens TikTok's or Instagram's webview, not Safari, so
that embedded browser is the first-run environment for most players the
campaign delivers and nothing has ever been tested there — autoplay audio and
canvas perf at RES 4 being the two that would actually break the hook.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The PERFECT spark ring is out — the hitstop, the click and the praise word
already carry the moment. The net-cord wobble drops to ~1 px. And the
floodlight stutter is gone entirely: on the iPhone a random dark dip over
the stands reads as the crowd flickering black — a rendering bug, not
ambience.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Kills stutter the world a touch more per streak; a massacre borrows the
boss's slow-mo; a boss death gets the transition's zoom punch. PERFECT
contact flashes a spark ring. The sidestep and every real bounce kick up
court-tinted dust. The net cord ripples where the ball clips it. The
landing ring contracts on time-to-touchdown. Hot rally balls drag three
fading ghosts. And every 8-15 seconds the floodlights stutter.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
- The opaque menu no longer has the whole court rendered behind it at full
resolution — minutes of battery burn nobody could see.
- Stands, ads, rot tint, court, lines and the run-off are one cached blit,
rebuilt only when the palette actually moves; the clap ripple stays live.
- RES derives from devicePixelRatio (capped at 4), so a 2x phone stops
painting double the pixels it can show.
- One shared noise buffer for every synthesized sound — applause alone was
allocating ~200 fresh buffers per cheer.
- The touch-steer path stops re-measuring a fixed canvas rect per move event.
Skipped as not-quick: the z-sort closure churn and swap-remove splices —
real but small, and each is a behavior-preserving refactor for another day.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>