Commit Graph
112 Commits
Author SHA1 Message Date
wuwiandClaude Opus 5.5 ba8d13a867 Make the double tap a stance that holds the net
Oliver on v376: "wenn man doppeltippt, geht er minimal vor". His own
ball's flight covered 4-9 m of the 10 to the net. His next ball was met
from mid-court, outside the net zone, and the recovery after that volley
walked him straight back to the baseline.

The double tap now sets a stance (P.post) that lasts until the next
double tap, a lob he has to run down, or the end of the point. Every
recovery heads for the net at full pace, and every incoming ball is
planned as a net man's. The 'back' stance clears once he is behind the
net zone, so short balls still pull him in.

Measured: contacts while committed are ~35% at the net and ~40% around
the service line, and he no longer falls back. The base ladder is
unchanged. New test; v377.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 20:40:56 +02:00
wuwiandClaude Opus 5.5 48d1069e40 Make the run to the net and back a double tap
Oliver: "das Vorlaufen und Zurücklaufen sollte durch einen Doppeltipp
ausgelöst werden". It replaces v374's flick-length rule: the long
up-flick is only the deep ball again, the long down-flick only the lob.

While his own ball flies there is nothing to hit, so a tap there is not
a swing. Two inside 0.35 s take him in to the net from the back, or back
from the net. "TO THE NET" or "BACK" pops under him. An incoming ball
keeps every tap a swing, so the two meanings never share a moment. After
his own serve it is serve and volley. The short-ball approach stays
automatic.

Measured: the base ladder is unchanged, and following the ball in still
pays against pushers and costs against big hitters. Three tests replace
v374's two; v376.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 20:30:38 +02:00
wuwiandClaude Opus 5.5 bb72a01a3e Stop the auto-run shaking while it chases a ball
Oliver: "wenn der Autoplay den Ball versucht zu bekommen, zittert der
Player schnell hin und her". Measured: the run's target flipped by
metres every other frame. intercept samples the flight every 10 ms
against a 16.7 ms frame, so any candidate on a margin (a low bounce
peaking on the 0.45 m floor, a point on the back boundary, a dying drop)
passed one frame and failed the next. Frames with no candidate sent him
home and back. 9-62 reversals inside 200 ms per five minutes.

The chase now latches its plan: the flight is fixed at the strike, so
the first point stays the answer while his legs still make it in time,
net of any time he stood (split-step, heels). Reach is judged from where
the chase began, and the target eases with a start/stop hysteresis.
After: no reversals at any rung, timing unchanged. He wastes no steps,
so he is a little stronger, mostly at the bottom of the ladder. New
test; v375.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 20:15:25 +02:00
wuwiandClaude Opus 5.5 599d5b0506 Let the player take the net and come back, and smash a lob he gets under
Oliver: "Steuerung ans Netz gehen", option (1), and "einen Lob vom Gegner
als Volley annehmen soll derb schmettern".

- A long up-flick (90 px or more) is the attack: the deep ball, and he
  follows it in. A long down-flick is the lob, and he goes back behind
  it. Short flicks keep the deep ball / the drop and leave him where he
  is. Length only, never context.
- The net costs him what it costs the CPU: he split-steps (netReact)
  before reading the pass, and a CPU facing a net man passes at the
  line. Without both, the attacker won 78-95% of his net points.
- The smash: a ball taken out of the air at 1.9-2.5 m is struck harder
  and to the open court, read late by the opponent, and pops "SMASH!".
  It wins ~80% of its points.
- Lobbed at the net, he decides once whether he can get under it. If he
  can, he smashes. If not, he runs it down and only blocks it back, even
  if he meets it in the air. Re-deciding every frame smashed every lob.

Measured at level 26: the base ladder is unchanged. Attacking pays
against pushers and touch players and costs against big hitters,
slicers and rushers. Three new tests; v374.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 20:05:59 +02:00
wuwiandClaude Opus 5.5 4406aeafdb Let an early tap be a weak ball, and let the CPU put a sitter away
Step 4 (b), Oliver's pick. v372 measured why the ladder could not climb:
nearly every lost point was the player's own early whiff, the CPU barely
missed, and the auto-run reached everything, so no opponent dial decided
a point.

- The follow-through: a groundstroke swing meets a bounced ball up to
  60 ms after its window at q 0.05. The early whiff edge goes from ~70 ms
  back to ~130 ms, where the volley window had it before step 1.
- A sitter (a player ball under q 0.5) is put away with a chance that is
  the ladder (0.2 at the bottom of the Climb, 0.85 at the Champion, by
  difficulty in classic): clean, at the line, bounded by the rung's width.
- The player is on his heels for 0.5 s after a put-away of his own sitter.
  Without it the auto-run covered the whole court and no put-away won.
- Fixed a step-2 regression this surfaced: the drop's dig-out cap fired on
  every slice drive, so every low contact against the slicer was a sitter.

Measured at level 26: the Climb now slopes from 95% of points at the
Shambler to 28-39% at the Champion. The net rushers are still easier than
their neighbours. The log names put-aways. Three new tests; v373.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:25:49 +02:00
wuwiandClaude Opus 5.5 5dd5d71c33 Keep the praise word on screen, and take the cliff out of the groundstroke
Oliver on v371: the praise word is cut off on the left on the iPhone. It
is centred under the player's feet, so out wide it ran past the edge;
every pop is now clamped to the canvas by its measured width.

Step 4, measured first. The rally sim gets a human bot (taps on the timed
ring with a timing spread), a Climb mode and a lost-point breakdown. At
level 26 the ladder falls over six rungs and then goes flat: nearly every
lost point is the player's own early whiff, the CPU barely ever misses,
and the auto-run reaches everything, so no opponent dial decides points.
A quality ramp, a follow-through and CPU put-aways were each measured
and reverted; the options go to Oliver in PROJECT.md.

Shipped: in the rally modes q no longer drops from 1 to 0.43 one ms past
PERFECT; it falls smoothly to the window's edge (TUNE.qSeamless). A tap
30 ms early is q 0.86 instead of 0.61. Same window, same whiff edge. v372.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:01:24 +02:00
wuwiandClaude Opus 5.5 8e4b298d92 Keep the auto-run's target where the player is allowed to stand
Oliver's v369 log ("vieeel besser") showed two misses with the auto-run
heading to x 6.5 and 8.7, far outside the frame he is held in. Since the
dip ends at the bounce, a hard cross-court topspin ball drifts sideways
long enough that its comfort point lies off the court; intercept had no
lateral bound, and its last-resort target was never checked at all
(headless: x 14.8). Candidates now respect xLimit — a kicking ball is
taken earlier, on the rise — and the final target is clamped. v370.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:46:53 +02:00
wuwiandClaude Opus 5.5 dfdb699f9d Stop calling "passed you" while the ball is still on its way to him
Oliver's v366 log: a LOST "passed you" with no swing. The match gave the
CPU the point the moment its ball crossed 0.3 m behind the baseline — a
line from when the player stood inside it. Since step 1 he waits up to
2.2 m behind the line for deep balls, and a ball going by ~1.5 m beside
him only enters his reach just in front of him, so the point was called
before it was ever playable. In the match "past you" now means past his
reach. The swing log's hits also say whether the timing was early or
late. v367.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:03:30 +02:00
wuwiandClaude Opus 5.5 5bbde720ad Time flicks from touch-down, log the opponent's shots, save early half-volleys
From Oliver's first swing log (cup vs the Brute): 13 of 22 hits were
perfect, 9 badly early or late — mostly forward-right flicks — and the
weak balls became seven "passed you". Every hit was a flick, and flicks
still swung only at the flickMin crossing while the timed ring says "touch
now": they are now timed from touch-down too (capped at flickLead 0.1 s).

The log gains its other half: a THEM line per opponent shot (kind, pace,
where it lands relative to you, coming in) and "never swung" judged per
incoming ball instead of once a point. And an early swing on a low ball
digs it out as a capped half-volley instead of whiffing. v366.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 11:48:51 +02:00
wuwiandClaude Opus 5.5 d110e12321 Bring the swing log to every rally mode, and time the gold ring
Oliver wants to hand over a log of why he missed balls. It existed behind
a scoreboard tap but was gated on G.mode === 'tennis', so the Climb, the
cup and training (which run as 'splatter') logged nothing. It now writes
in every rally mode: hits with shot, tap age and any quality cap, misses
with the new let-it-bounce reason, and a WON/LOST line naming why each
point went. Tapping the open log copies all 200 entries with a header.

Its first catch: the gold ring went gold at a distance, which is too
early for the slow balls steps 1-3 brought in. It is now timed off the
ball's real path to his reach (against where his run is taking him):
gold means touch now. ringTime 0 restores the old ring. v365.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 11:41:05 +02:00
wuwiandClaude Opus 5.5 180288ef8f Give every opponent a style, and split the soft ball into drop and lob
Step 2 of the rally rework: eight styles as dials on the opponent rows —
pusher, retriever, slicer, big hitter, baseliner, touch, net rusher,
all-court — read where the CPU chooses its shot (moonball, drop, lob,
spin), plans its approach and serves (serve and volley sprints in). The
walk-on names the style in one line so the player can plan against it.
A player lobbed at the net now backpedals and blocks, like the CPU.

Oliver on v363: the down-flick "always felt like a lob". The length is read
at contact and a thumb has usually travelled on; on the continuum even
50 px floated the ball. Below dropMax (90 px) it is now the drop, above it
the lob. Also un-flakes the machine-squeak test (seed 31337). v364.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 11:25:52 +02:00
wuwiandClaude Opus 5.5 72760a9d1e Make the down-flick the soft ball: a short one drops, a long one lobs
Step 3 of the rally rework, Oliver's pick: in rally modes the down-flick
is soft, always, and its length is how far — a dying drop just over the
net at one end, an attacking lob over a net man at the other. One gesture,
one meaning; not the context-armed lob that was cut.

Ballistics from the cut lob (pace derived from the apex); backspin lives
on ball.spin and every bounce planner reads it, netplay sends it. Against
the CPU it was returned ~100% until the real-tennis reasons went in: soft
balls are read late (disguise), a net man reacts to a pass and backpedals
from a lob, and a lobbed or dug-out reply is a weak block. Four new tests;
v363.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 11:17:25 +02:00
wuwiandClaude Opus 5.5 f9b0dd2efb Time a tap from touch-down, and give the opponents' legs a knee
Oliver after step 1: much harder, stuck at the Steward, "trotz gutem
Timing nicht mehr bekommen". A tap only proves it is a tap when the
finger lifts, so the swing started 100-150 ms after he meant it; the old
volley window hid that, step 1's groundstrokes did not. A tap made on the
gold ring fell from q 0.82 to 0.09 (and a third were missed at 150 ms),
murky balls landed short and the opponent came in. The swing clock now
starts at the touch's age (rally modes only); flicks keep their crossing.

The opponents' legs had no ceiling: 7.3 m/s at the Runner, 15.7 at the
Champion against the player's ~6.2. Past legKnee (6 m/s) only 15% of the
growth survives, monotone, so the ladder order holds; champion ~7.5. v362.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 11:06:08 +02:00
wuwiandClaude Opus 5.5 4e7c816a25 Give the rally its length back: deep balls, baseliners, approaches off short balls
Every match had collapsed into serve, both at the net, reflex volleys at
~0.7 s per shot. The rally ball landed on the service line by definition,
the match CPU stood on the bounce and volleyed 90-100% of everything, and
the auto-run chased the earliest contact.

Rally modes only (classic + every match; gore and the duel untouched):
rally balls land deep and mistiming only shortens them (no new long outs);
each shot latches the receiver's plan as it leaves the racket — from the
back he lets it bounce and meets it falling, up to 2.2 m behind the line,
a short ball pulls him in and he follows it to the net, nobody approaches
a man already there, and a volley below the tape is a weak block. Measured:
contacts 10.5-13.7 m from the net, back-court volleys 0-8%, 0.8-1.0 s per
baseline shot. Three new tests pin the shape; v361.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 12:55:05 +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
wuwiandClaude Opus 5 78946015b4 Give the friend's racket a sound and the duel a rematch
Two netplay-only finds from Oliver's two-phone session.

THE FRIEND'S STRIKE was silent: every other opponent is simulated locally
and cracks through gameplay's shared return path, but the friend's swing
runs on their phone alone and arrives as a 'shot' message that wrote the
ball and nothing else. netApplyShot now plays the same sample at the same
volume, pitched off the pace that was actually sent (strikeRate, read after
the ball is written). Both netplay modes, not just the death match.

THE REMATCH: the summary's only netplay door was THE CLUBHOUSE, so playing
the same friend again meant two trips through the lobby and a fresh code.
The relay cannot re-pair, so the rematch is the socket that is still open —
one 'again' frame and both phones reset the run under the decisions the room
already carries (same side, same mode, same court, host serves). netAgain
keeps G.net across restartRun(true) and replays netFresh, netStart's own
tail split out so pairing and rematch cannot drift into two courts. Applied,
never echoed; a 2 s window swallows the second frame when both tap at once;
a dead line refuses and falls through to the clubhouse.

Also: the dev server runs with SPLAT_AUTH= (server.py always said local dev
should), which the shared password had been 401-ing. audio.sfxTest is a new
test seam in netTest's shape. 146 tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 21:28:31 +02:00
wuwi a399e4929c Fix match AI short-ball contact and recovery 2026-09-07 10:53:14 +02:00
wuwi fe64242090 Add opt-in phone gameplay recording and JSON export 2026-09-07 10:19:38 +02:00
wuwi ede1f48152 Sharpen league and Climb difficulty and give Gore a front door 2026-09-07 09:53:49 +02:00
wuwi e3faeb0a3a Strengthen final-league placement and returns 2026-09-04 09:11:12 +02:00
wuwi bfc70d7388 Make the opponent level matter in the cup 2026-09-03 16:41:02 +02:00
wuwiandClaude Fable 5 80eb60d181 The cup board: the league ranks, the level breaks the tie
Oliver wanted to rank the level a league was taken at. The challenge held: xp
comes only from playing, so a board that rewards a low level rewards not
playing — it would pay you to avoid the three rooms the season leans on. It
also inverts every other number in the game, freezes after five entries, and
makes the player who played more look worse.

So the league ranks and the level decides between everyone who got equally far,
where "lower is better" reads naturally. BOARDS.u carries subLow; server
rank_key is the other half, and the same key decides whether a submission beats
the row already standing. The level is written once at the promotion — before
the league moves, because it is the level that WON the cup — mirrored like the
rest of the profile, and inscribed under the shield it belongs to. Earned
shields smoulder while they are on screen: five CSS embers each, no loop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 15:51:38 +02:00
wuwiandClaude Fable 5 a331a1ae37 The menu text reduction: the design handoff, built
Sixty words on the wall became a dozen. The handoff's own summary of the rule:
Jersey 15 NAMES (screen titles, rooms, cards), Bangers CALLS exactly once per
screen (the fixture, the primary button, a modal headline), Silkscreen INFORMS
(numbers, labels, chips) — and a sentence survives only where it carries a
decision. Everything else moved into a bar, a pip or the modal that opens with
the mode.

  · Jersey 15 bundled subset (9 KB) and Bungee retired after one afternoon
  · SPLASH: the plaque is one row, the goal line is gone (the fixture button
    below said it already), the two doors carry a number each
  · PICK A GAME: rooms are one word, posters are their names, the fixture is a
    hero with the only second line — the whole wall fits one screen now
  · MATCH SETUP: unnumbered blocks, the meters' legend once instead of a
    description per court, no summary footer
  · DIFFICULTY: three pips instead of three sentences
  · TRAINING: the revanche is a full-width row, rungs are numbers, the list
    ends on the league that opens the rest of the ladder
  · VS: three paragraphs became YOU / HOST / JOIN
  · MODALS: one rule per row (the sub was the lead again in other words), the
    blood pool as pips on that same line, and the walk-on's two levels in ONE
    face at ONE size — a comparison drawn at two sizes is a headline with a
    footnote (Oliver)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 14:41:25 +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 00f121bec7 The walk-on gets its own face: mugshot masthead, the measure, the round strip — no more wall of text
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 16:22:20 +02:00
wuwiandClaude Fable 5 a38c804069 Training pays a quarter, and the level bar wears its endpoints
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 15:58:34 +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 7e2649c888 The level is you, in the league: quicker legs, a wider window, and a curve that pays on the way up
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 13:28:24 +02:00
wuwiandClaude Fable 5 a5a681f9f2 The cup climbs: easy to hard, deterministic — the shuffle retires with its screen
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 16:49:36 +02:00
wuwiandClaude Fable 5 dbfd085493 The draw screen retires: the league cup rolls inside the run, ENTER THE DRAW moves onto the setup button
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 16:46:34 +02:00
wuwiandClaude Fable 5 689550d2d5 The climb: the arcade runs the whole earned ladder, the tempo follows the territory, the court is yours
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 16:24:43 +02:00
wuwiandClaude Fable 5 87ac3294bc The leagues, the plaque round: the meta speaks — the player plaque, the goal line, the way-up tag, the centred ceremony
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 15:30:24 +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 Fable 5 98b01fea5a The trail reads as motion, never as residue: the ghosts fade with the sprint
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>
2026-08-28 16:36:19 +02:00
wuwiandClaude Fable 5 4ea9af5442 The surprise is an opening, not a free point: THE READ turned down a notch
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>
2026-08-28 16:34:38 +02:00
wuwiandClaude Fable 5 43784c5b33 The staff learn your habits, the walk-on waits for your tap, and the club opens a practice court
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>
2026-08-28 16:08:18 +02:00
wuwiandClaude Opus 5 a42dc6fc81 The lob is out: a gesture the opponent's position redefines is unpredictable
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>
2026-08-22 13:13:56 +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 6e07dc74bb The receiver waits behind his line, and a missed serve buys a second one
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>
2026-08-22 09:40:48 +02:00
wuwiandClaude Opus 5 74afd5988c The stand stops wobbling: a real clap frame, a wave, and three spectators who have already turned
The wobble was never the animation. x_crowd2 was a RE-ROLL of x_crowd, not a
pose of it — 13-16% of its pixels differed, over faces, torsos, seat backs and
the railing — so swapping 16 columns at 4.5 Hz re-hashed a strip of crowd twice
a second. assets/_crowdpass.py rebuilds the pair: the clap frame is now the
seated frame plus 36 genuine raises, each contained in ONE of render's columns,
so any column swap is seamless by construction and an empty column is invisible
instead of being a wobble. The three courts are one composition in three
palettes, so a raise found in one court's roll is transplanted into the other
two through a colour map learnt from the aligned seated pair.

The same tool paints the crowd, because at ~8 logical px a spectator detail can
only mean chroma and silhouette: 55 shirts and 44 heads of hair in colours the
court does not own, 28 caps, 4 flags above the heads, 8 emptied seats, 8 people
permanently standing — and 3 who are already turned, grey-green, mid-row, who
are also the only three who never applaud.

Movement, one draw path and three behaviours: applause scatters per column, a
WAVE crosses the stand for a moment big enough to deserve one (crowd.pop >=
0.75), and with nobody cheering ONE column stands for a third of every
TUNE.fidgetEvery, so the stand between points is no longer a photograph.

Paparazzi now scale with the moment: cheer() records the size of it (vol was
already exactly that at every call site) and queues shutters proportional to it,
drained at a fifth of the normal gap. pop is held at the loudest while an
applause is live, so a won rally landing inside a match-point ovation cannot
demote the stand to a polite clap — that rule has a test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 12:58:26 +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 Opus 5 c5eb6846e3 The tour rides the wire: your friend plays in the body they picked
A tour is which body you play in, so it belongs in the look message next to the
kit, the skin and the name. Without it both phones drew the friend in whatever
tour THIS phone had picked, and a men's/women's pairing showed each player
someone their friend was not using.

`oTour()` is the one accessor — yours everywhere, theirs in netplay — and it
feeds the three things that must agree about a body: the sprite suffix, how the
art takes a colour (her whites wash, his blue dyes) and the palm the idle ball
bounces under. An older build sends no tour and falls back to ours, which is
what it did before.

The palm knobs were re-checked against the new front sheets rather than assumed
— the poses were copied, so the ball still lands under the drawn hand.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 16:11:13 +02:00
wuwiandClaude Opus 5 5f9170b7d7 The friend across the net wears your kit, drawn from the front
His colour landed on the shirt while yours lands on the shorts, because he was
drawn on the roster opponent's red sheets and dyed on an arc of his own. Red is
the one hue that art cannot hang a kit on: the men's shorts are neutral grey at
saturation 0.05, so no rotation ever reached them, and the women's red top sits
at her skin's and her hair's hue — a dark pick was painting her arms and her
ponytail, live, on the shipped build.

So he gets his own sheets. `assets/_frontroll.py` is `_famroll`'s trick pointed
at the tennis poses — image 1 is the ten `o_*` frames composed from disk, image 2
is the player from behind — and two calls drew all twenty. He rides the PLAYER's
arcs and the tour's own mode now: his shorts dye, her whites wash. `TINT.oKit` is
gone. The roster CPU keeps the red art; it is not a person.

The setup screen shows that front too — picking a face off the back of a head was
only ever what the art allowed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 16:04:10 +02:00
wuwiandClaude Fable 5 4052ab1404 The relative lane is vetoed; an aimed ball never lands on the wrong side of you
'No that's way too difficult now - I am dying constantly of out. revert
it.' aimAnchor is gone — the lane is absolute again, every out invariant
back exactly as tuned. What survives is the answer to his follow-up: the
ball sometimes flew a LITTLE RIGHT out of a left flick, because a
mistimed left flick's target sits only slightly left of CENTRE — which
is right of a player standing in the left service box. The absolute
lane's blind spot, stated the other way round.

The aimed target is now clamped to the player's own x on the flicked
side, bounded at flickWidth so a body parked in the alley clamps to the
corner rather than conjuring an out. From the left box a left flick goes
at least down your own line, from centre it can no longer cross centre,
a perfect corner flick and the crosscourt sweep are untouched, and the
outward scatter tail keeps the whole tuned out-risk. Verified in his
scene: ten rushed weak left flicks land -2.0..-2.7 where the same case
used to target -0.7..-1.3 (a visibly rightward flight); the clean full
flick still lands -3.92.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 14:39:10 +02:00
wuwiandClaude Fable 5 7e5c3ab399 The flick aims relative to the player: a full sweep from where you stand, not from the court's centre
Third round of the flick report, and this was the core all along: 'when
the opponent and the player are both standing in the left service box it
is virtually impossible to flick the ball into the left out. no matter
how hard/good I flick.' The lane was absolute — a full flick asked for
flickWidth off the court's CENTRE — so the gesture saturated near a
sideline: from x -2 a perfect left flick moved the ball under 2 m and
could never pass the line, and the better the timing the firmer the cap,
because scatter was the only thing that could cross it. 'Left is
impossible, right works' was never left-vs-right: it was
toward-the-near-sideline vs across-the-court.

TUNE.aimAnchor: the anchor slides from the court centre to the player's
own x as the swipe commits (ch.x * |sway| * aimAnchor), so a flat tap is
exactly what it always was and a full flick is a full sweep from where
you stand — including past the line. Out-by-aim is a choice now, per
Oliver's explicit direction; the placement ring already turns red for
it. 0 restores the absolute lane, blendable live in the panel. Player
rally lanes only; the opponent's lanes and the centre-stated pOut
invariants are untouched. Verified in his exact scene: full left flick
from the left box lands -5 (the alley), half swipe -3.07, full right
+1.93.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 14:25:24 +02:00
wuwiandClaude Fable 5 60127436a0 An aimed shot keeps its direction: the scatter can push it wider, never back past centre
Second round on the service-line flick report. The crossing-fired swing
alone did not move the feel, and Oliver's sharpened observation — the
flick only lands left when the opponent already stands left — pointed at
a magnet that does not exist: measured with the body teleported at
contact, the aim never tracks it. The real mechanism: at rushed quality
the symmetric scatter (aimError x (1-q)) outweighs the quality-shrunk
aim, so a mistimed left flick landed anywhere in a ~4 m blob around
'slightly left' — usually centre — and the eye anchors that on the
opponent. Its position was a proxy for preparation time all along.

TUNE.aimTrust: an outward scatter roll is untouched — the committed
swipe's chance of sailing keeps its exact tuned shape, flickReach stays
the only out dial — but an inward roll keeps only 0.3 of itself. Bad
timing costs width, never direction. Player lane shots only; the
opponent's symmetric scatter is its width mechanic. Verified at
worst-case q~0.08: 14/14 rushed left flicks landed left, 9/9 right
flicks right, where before it was a coin toss. The suite pins the
direction guarantee and the aimTrust/aimError/flickReach relationship.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 14:03:57 +02:00
wuwiandClaude Fable 5 6f9dea4ffc The flick fires the moment it is a flick: the swing launches at the crossing, and the aim rides the thumb to contact
Oliver's service-line report: flicking the incoming ball LEFT was near
impossible mid-court at the service line while RIGHT worked, easing toward
the baseline. The lane math was symmetric to the centimetre — the defect
was latency. The swing waited for the RELEASE, so the whole gesture ran as
dead time on top of the windup: ~120 ms of lateness drops contact quality
1.0 -> 0.08, where flickReach halves the width and aimError drowns the
rest. A left swipe is a right thumb's slowest direction, the service line
leaves ~700-870 ms of budget (measured), and any swipe over FLICK_MS was
silently dead. Left + service line was the slowest gesture meeting the
shortest budget.

Now, in rally modes: crossing flickMin fires the swing (once per gesture,
rally phase only) and the vector stays LIVE — trySwing reads G.swipe at
contact, with the released flickAim carrying the aim only when the ball
arrives after the finger lifts. The FLICK_MS deadline goes with it where
the crossing can fire; it survives where it cannot (gore free-aim, the
serve strike). Serve gesture, duel, gore and keys untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 13:26:30 +02:00
wuwiandClaude Opus 5 7ff8671a22 The aim cloud goes quiet, and a mistimed flick falls short of the line
Hidden behind TUNE.aimCloud rather than deleted: the reading it gives — how
much width the swipe is buying, before the ball leaves — is the only teacher
the gesture has, and the panel can flip it back live for the next look at it.

And "a little harder to hit the out" is a number: flickReach 0.62 -> 0.52,
which halves the worst-case rate from about 15% to about 7%, with the test
tightened so it cannot drift back. The shape is untouched — a perfect flick
still never sails, a badly timed full swipe still can — but a mistimed one now
lands nearer the middle than the line, which is the only half of that trade a
player who cannot see his own timing can do anything about.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 07:58:47 +02:00