diff --git a/PROJECT.md b/PROJECT.md index 9273108..651c812 100644 --- a/PROJECT.md +++ b/PROJECT.md @@ -491,6 +491,71 @@ zombie (now an occasional opponent, `TUNE.racketEvery`, not the every-game fixtu `RETURNED`, `DOUBLE KILL`) is a footnote to something you already watched happen. Sharing the loud FACE with the milestone cards is right; sharing their SIZE made every footnote read as a milestone. +## Session log: THE READ, the walk-on modal, the training court, movement reads (2026-08-28) + +Four items from one phone report. PHONE VERDICT PENDING on all of them. + +### 1. THE READ — the twist for the post-COACH wall +Oliver: past the HEAD COACH *"the opponents are very fast and barely make mistakes any more — we +need a twist, maybe a kind of cognitive fatigue when you make the same move ten times and then +surprise them."* Built as HABITUATION, his own idea with the numbers made playable: +- **doHit buckets every aimed player rally shot into three lanes** (target x at ±1.1) and keeps a + same-lane run on `G.match.laneRun`. `TUNE.readHabit` (3) same-lane shots = the habit is learned. +- **One more of the same lane is `expected`**: the opponent's `aiRead` climbs toward + `TUNE.readAnticipate` (0.97) — spam feeds the machine, so the late ladder punishes one-note play. +- **Breaking the habit is `surprised`, for that one flight**: the read collapses (`surpRead`), the + chase speed drops (`surpSlow`, applied inside `rzChaseSpeed` so the intercept planner and the + early-fall test believe it too), and `surpFlub` lands on top of a whiff rate the champion had + reduced to almost nothing. A "WRONG-FOOTED." flash announces it (rungs past ~20% only). +- **Everything scales with the rung** (`learnK`, (opp−1)/(LADDER−1)): the shambler never learns, + the champion — "HE RETURNS EVERYTHING" — is exactly the one you beat by setting a habit and + breaking it on the big point. Solo match only (netplay's opponent is a human), rally lanes only + (serves keep their own game — open: should serve placement feed the same book?). The one-ball + states are cleared at the racket and at every point end (`matchReady`); a fresh walk-on resets + the book. Tests pin the state machine (real swings through `keyPressed`) and the dial + relationships (a wrong-footed champion runs slower than the head coach at full stride). + +### 2. THE WALK-ON IS A MODAL (tournament + arcade + training) +Oliver: *"die Intros der einzelnen Spieler bitte als richtiges Modal, das man wegtappen muss — +man kann dann wirklich lesen was da steht, und schnell wegklicken wenn man es schon 100x gelesen +hat."* `matchOpponent` now calls `showModal` (head icon `z_head`, the punchline, the cup's +round line) instead of the timed 4 s card. The walk-on court is guaranteed-calm (the mop beat has +just ended), so the popup policy allows it; the freeze holds the 1.6 s intro bar until the tap. +The mode-rules popup fires BEFORE `matchOpponent` in `startMatch` so it lands first and the +opponent queues behind it. Netplay untouched (no walk-on there at all). The test harness's +`passMopBeat` now taps the walk-on away like a player would. + +### 3. THE TRAINING COURT (`MODES` id `train`, third kid of ZOMBIE TENNIS) +Oliver: *"ein Trainingsbereich — Auswahl des Gegners, ein Spiel, tap to replay, bis man keine +Lust mehr hat."* One flag (`d.train`, set by `startMatch(false, null, rung)`) on the whole match +machinery: +- SERVE UP routes to a picker screen (`#train`, built from the roster like the boss picker, the + same `z_*head` crops the draw uses); tapping a rung IS the start — tap-advances, like every + other screen. `mode_train.png` rolled in the banner style (golden-hour practice court, ball + basket, cones, no text) via the famroll OpenRouter route. +- The rung is PARKED: `d.opp` never advances, a felled body walks back on as itself, and the + walk-on modal (item 2) is the "tap to replay" gate for free. Between reps the blood resets + silently (no WOUNDS TREATED ceremony — a popup per rep is the tap-tax the mode avoids). +- NOTHING BANKS: no `G.runGames`, no `endRun` (pagehide guarded), and bleeding out is a modal + ("IT BLED YOU OUT") plus a fresh pool, never the death screen. The quit chip is the exit. +- The board's progress slot reads `TRAINING · `; the setup footer says "NOTHING AT STAKE". + +### 4. MOVEMENT READS — dust and speed ghosts (feel-tunable, verdict pending) +Oliver: *"die Bewegung der Spieler optisch anreichern — motion blur / particles / shader, hier +bin ich auf deinen Input angewiesen."* Two cheap effects in the existing registers, no shaders: +- **Sprint dust**: feet crossing `TUNE.trailMin` (3.6 m/s — above every shamble) kick one + per-court `dustPuff` per `runDustEvery` metres. racketAI's chase and the player's mover + (auto-run, horde steer) both emit; the mover measures its travel AFTER the clamps so a body + pinned at a bound reads as standing. +- **Speed ghosts**: `drawZombie`/`drawChar` draw two alpha ghosts sampled back along the body's + own velocity (`trailK` s × 1 and 2) — the modal masthead's speed-streak trick applied to + bodies. Only genuine sprints qualify; never during a named frame (`showFrame` — a collapsing + corpse must not smear on stale velocity). racketAI now records `vz` beside `vx`; the player's + `P.vx/vz` are written by the mover and zeroed outside it. +Both are panel-tunable (`trailMin` 0 kills both). Not tried, on the shelf if these fail the +phone: a one-frame lean/skid sprite on hard direction changes (needs drawn frames, per the +draw-frames rule), and court-colored skid streaks on the ground behind a hard stop. + ## Session log: THE LOB (BUILT, THEN CUT) AND TWO THINGS THE MULTIPLAYER TEST FOUND (2026-08-22, same day) ### THE LOB WAS BUILT AND REMOVED THE SAME DAY — and the reason is the useful part diff --git a/assets/mode_train.png b/assets/mode_train.png new file mode 100644 index 0000000..6a6066b Binary files /dev/null and b/assets/mode_train.png differ diff --git a/index.html b/index.html index 01929cd..a7630be 100644 --- a/index.html +++ b/index.html @@ -174,7 +174,7 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat /* ONE overlay, four panels, swapped by #intro[data-stage]. Keeping the backdrop and the card frame alive across every screen is the point — a screen that unmounts the whole overlay flashes the court through between menus. */ - #splash, #setup, #stats, #ranks, #newbest, #secret, #bosses, #stmode, #stsub, #bracket, #vs { display: none; } + #splash, #setup, #stats, #ranks, #newbest, #secret, #bosses, #stmode, #stsub, #bracket, #vs, #train { display: none; } #intro[data-stage="splash"] #splash { display: flex; flex-direction: column; flex: 1; } /* flex COLUMN, not block: it is what lets the footer's margin-top:auto reach the bottom of a screen whose content does not fill it */ @@ -186,6 +186,7 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat #intro[data-stage="secret"] #secret, #intro[data-stage="bosses"] #bosses, #intro[data-stage="bracket"] #bracket, + #intro[data-stage="train"] #train, #intro[data-stage="vs"] #vs, #intro[data-stage="newbest"] #newbest { display: flex; flex-direction: column; flex: 1; animation: drop 0.3s ease-out; } @@ -264,16 +265,19 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat /* THE BOSS PICKER (back room). Two columns of portraits, because you pick a boss by looking at him — the tile is mostly art. Same plate as the tour tiles above; the grid is what makes ten of them fit on a phone. */ - #bossList { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; padding-bottom: 14px; } - #bossList button { display: flex; flex-direction: column; align-items: center; gap: 5px; + #bossList, #trainList { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; padding-bottom: 14px; } + #bossList button, #trainList button { display: flex; flex-direction: column; align-items: center; gap: 5px; padding: 8px 6px 10px; background: var(--panel); border: 2px solid var(--line); color: inherit; font: inherit; cursor: pointer; -webkit-tap-highlight-color: transparent; } #bossList img { height: 78px; width: auto; display: block; image-rendering: pixelated; } - #bossList b { font: 400 9px Silkscreen, monospace; color: var(--cream); letter-spacing: 0.5px; + #bossList b, #trainList b { font: 400 9px Silkscreen, monospace; color: var(--cream); letter-spacing: 0.5px; text-align: center; line-height: 1.35; } - #bossList i { font: 400 8px Silkscreen, monospace; color: var(--dim); font-style: normal; + #bossList i, #trainList i { font: 400 8px Silkscreen, monospace; color: var(--dim); font-style: normal; letter-spacing: 1px; } + /* the training picker is 18 rungs deep — the tile carries the HEAD (the same z_*head crop the + draw and the walk-on wear), which reads at a third of the boss portrait's height */ + #trainList img { height: 44px; width: auto; display: block; image-rendering: pixelated; } /* THE DRAW. Four rungs read top-down as a ladder, because that is what the mode is — the bracket shape a real draw is printed in needs width a phone has not got, and half of it would be blank anyway (you only ever play one side of it). The rows spin their portrait and their name while @@ -561,6 +565,13 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat + +
+

TRAINING

+

PICK YOUR OPPONENTONE GAME · GO AGAIN

+
+