The entrance is no longer a one-off on the boot: dealStage re-arms the class at
the funnel every stage change already passes, so the title screen flies its
plaque and buttons in whenever you arrive at it. The sub-menus join in — their
header, content and footer arrive one after the other instead of the panel
dropping in as one slab — and the poster wall deals its three rooms rather than
its list, since a moving child inside a moving parent starts at twice the
offset and reads as a stutter.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The splash's elements fly in from the left on the boot, 70ms apart, on the same
easeOutBack overshoot the canvas modals have always used. It hangs off
:not(.busy), so taking the loading state off IS the trigger and it never plays
again — returning from a run should not make you wait for a flourish you have
seen. Reduced motion turns it off.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two things the phone found. The splash's loading state was applied by main.js —
but this is a module script, so the browser paints the markup, placeholders and
all, before it runs: LEVEL 1 stood there and then snapped to the real values.
The busy class ships in index.html now and main.js only takes it off; the
keyboard hints hide with it.
And #intro is the one scroller the menus share, so a scroll earned deep in the
stats screen outlived it: going back put the splash up scrolled, with its
buttons off screen. go() resets it, at the funnel every stage change passes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
"The draw" was tennis for the field of entrants, and nothing has been drawn
since the cup rounds went deterministic — Oliver had to ask what the button
meant, which settled it. Beside it, an unqualified PLAY AGAIN was an
instruction without a subject: both buttons read as "start something" and only
someone who remembered how the evening began could tell them apart. It carries
the mode now — GORE AGAIN, CLIMB AGAIN, CLASSIC AGAIN, TRAIN AGAIN.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A 100px label in a 120px half-button left ten pixels of air, which reads as a
squeezed chip. A tall screen has the room, so the two doors stack full width —
filled primary over ghost secondary, the same shape the modal footer and the
splash already use. A short landscape cannot stack, so there the box itself
grows to 344 and the halves come out comfortable instead.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Oliver's second pass over the new box language. The modal is narrower and the
button spans it; every row got real spacing and the footer its own margin, so
the end screens stop reading as cramped. The xp bar is taller and its ramp now
rides the FILL — spanning the gradient across the whole track meant an early
level only ever showed one flat green inch of it. An illustrated announcement
puts its two lines beside the picture, under the headline, instead of in a
boxed row below its own icon.
The poster wall's section headings leave the announcement face to the posters
(it read as a second card headline), drop the left band and the rule, and let
air do the grouping. On the splash, OTHER GAMES gets its gap to the stats row,
and the boot shows nothing but the court, the title and the loading bar — the
plaque skeleton was still a plaque telling a level-25 player he was level 1.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Oliver: the group headings were "sehr klein". Each group is a section now with
its posters inside it — the announcement face one size under the screen title,
a rule closing the header, and an inset band down the left. The canonical room
wears the primary's colour, because it is the one that moves your career.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
Oliver's read: the modes feel decoupled, the menus and end screens enforce no
story-line. Settled in discussion: the tournament is the canonical path and
everything else is Beiwerk, a cup attempt is one evening (no resume, no
seeding), classic stays a side room. The spine — the splash names the next
fixture, the club remembers your attempts, the side modes are described in cup
terms, every ending hands over, and the season opens inside the promotion.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The lingering doubles were a real bug, not a taste problem: racketAI's
!incoming return kept the last chase's vx/vz on the body, so the speed
ghosts trailed a zombie that had already stopped. The velocity dies with
the chase now (tested). The look is halved with it: ghost alphas
0.22/0.10 -> 0.11/0.05, trailK 0.05 -> 0.035 — a hint of speed in the
corner of the eye, never a second body.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Oliver, same day: 'die Gegner sind jetzt etwas zuu einfach.' The habit takes
four same-lane shots to learn (was three) and the wrong-foot costs about a
third less on every dial: surpRead 0.85->0.6, surpSlow 0.45->0.3, surpFlub
0.35->0.22. The whiff-window test pin rides down with it (0.25->0.15); the
slower-than-the-coach pin still holds.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Four answers to one phone report:
- THE READ: repeat a lane and the opponent anticipates it; break the habit
and it is wrong-footed for that ball — scaled by rung, so surprise is the
weapon that opens the ladder past the HEAD COACH (TUNE block THE READ).
- The opponent walk-on is a real modal now (head, punchline, tap to dismiss)
in arcade, tournament and training — readable without a clock.
- THE TRAINING COURT: third card under ZOMBIE TENNIS, pick any rung, one
game at a time, the felled body walks straight back on, nothing banks,
bleeding out is a popup instead of a death. mode_train.png rolled in the
banner style.
- MOVEMENT READS: sprinting feet kick per-court dust and fast bodies leave
a two-ghost speed streak (trailMin gates both; feel verdict pending).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Oliver, on the build: "all good but the lob. please remove it - this is
totally unpredictable."
Reverted in full — oppAtNet, farBody, TUNE.lobArm, TUNE.lobApex,
SHOT_DEPTH.lob, the branch in doHit and its two tests. The forward flick is
the smash it always was. Everything else from that commit stays: the pace
feedback (streak, crack, camera kick) and the death match's blood alignment.
The reason is worth more than the code, and PROJECT.md carries it as a rule:
a gesture whose MEANING depends on where the opponent is standing is
unpredictable however well the shot is tuned. It is the same objection
input.js already states about the slingshot — the direction you moved is the
thing you asked for, no invisible modes — and the written comparison let
option (c) past it on the argument that context cannot mis-fire. It can: you
do not track where the far body is standing while a ball is coming at you, so
one thumb movement produced two shots for reasons you were not watching. The
LOB word at contact explained the surprise; it did not prevent it.
If it comes back it needs a gesture of its own. The ballistics are solved and
are in the history at 4f4534e.
111 tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
Oliver, three notes; two of them are here.
"when the opponent serves the player does not stand at the baseline - but
that's how it should be." setupPoint's receiving marks sat over a metre
INSIDE the court on both sides (22.5 near, 1.2 far) — where you stand to
attack a second serve, not where you wait for a first one. They mirror the
server's depth now, in the one line every serving mode shares.
"when serving results in an out there should be a second service... the
umpire should announce 2nd service and the player must get another chance."
True in tennis mode since forever and absent everywhere else: the duel, the
match and the death match had no service rule at all, so a serve long or
into the net was judged as an ordinary ground stroke — a point, and in the
match a point costs blood. The out branch of onBounce's interlude block
routes a serve to fault() instead, so the second serve, the HUD line, the
umpire and the double fault all come for free.
Three things it dragged out with it:
- fault()'s double-fault branch called pointTo — TENNIS scoring — which in
an interlude banks a number and skips the mode that owns the court. It
goes through resolveInterlude there now. Nothing had ever reached that
line from a duel or a match, because those modes could not fault.
- startDuel never reset G.serveNum: harmless while a duel could not fault,
the next duel's death loop the moment it can.
- the match puts the body back on its receiving mark for the second serve
(placeReceiver, split out of matchReady, which resets the serve number).
"Fault. Second service." is one voiceSeq — a second voice() call would be
dropped, one man one microphone. New recording via the usual say/afconvert.
The service BOX is still not enforced in the match, deliberately: this is
strictly more forgiving than what shipped. The lob gesture is OPEN 14.
4 new tests, 110 pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>