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>
This commit is contained in:
wuwiandClaude Fable 5 committed 2026-08-29 12:22:33 +02:00
1 parent bc10216d0b
commit 9133c0d351
12 files changed
+507 -19

No files matched your search

+35 -1
View File
@@ -491,7 +491,41 @@ 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.
## THE LEAGUES — the meta-progression plan (2026-08-29, AGREED, NOT YET BUILT)
## Session log: THE LEAGUES, PHASE 1 — profile, xp, the identity screen (2026-08-29, STAGING ONLY)
Phase 1 of the plan below, built exactly to it. **STAGING ONLY for now** (Oliver: he tests himself
before the wider group) — do not merge to main until his verdict.
- **`src/profile.js`** is the profile: `{xp, lg, ch, setup}` in localStorage `splatterProfile`,
mirrored to the server (`/api/profile`, keyed by stats' existing client id). Name/tour/kit/skin
deliberately KEEP their existing homes and are only mirrored outbound, so nothing has two owners.
The mirror never goes backward on xp (server-side guard) and `pull()` adopts it only when ahead —
a wiped phone gets its identity AND its number back.
- **XP hooks are deliberately few**: `advancePoint` (the one place every mode's point converges —
points and games cannot be missed or double-counted), the `killZombie` funnel, duel win, cup
round, cup won. `award()` carries the no-stakes guard ITSELF (drill, bench, testMode, training
court), so a call site cannot forget it. Curve: `80·(l−1)^1.8`, cap 50 — first levels inside the
first run, late ones sessions apart. All dials in `profile.XP`.
- **UI**: the splash carries a LEVEL strip (#xpRow, filled by refreshSplash); the death/summary
screen shows `+N XP · LEVEL a → b` plus a progress bar (render.js, beside the best-run lines).
A mid-run level-up announces as a CARD (popup policy: never a modal on a live court).
- **THE IDENTITY SCREEN** (`#player`, first `PLAY MATCH` tap only): name prefilled with a ZOMBIE
NAME (`profile.zombieName()`, two-part roll, always ≤12 chars and board-legal), a die button
rolls another; tour/kit/skin pickers write the SAME picked* state and localStorage keys the
setup screen reads. **`syncSetup()` (called by `go('setup')`) repaints the setup rows from
picked\*** — without it the rows only tracked their own taps and showed the boot-time selection
after an identity-screen change (caught live in the pane).
- **Server**: `/api/profile` GET/POST beside the scores API, same stance (whitelist re-derive,
bounded, atomic write, 5000 profiles cap, oldest-touched evicted). `profiles.json` beside
`scores.json` under `/data` — the volume that already exists.
- **Drive-by fix**: `pagehide` now returns on `G.drill` — drill kills leak into the stats run bag
(tally sits above the drill hook in killZombie) and a tab close mid-drill banked them as a gore
run. Death was already guarded; the tab-close path was not.
- Tests: +4 (curve shape, the award funnel + every mute, run-delta reset, name legality), suite
118, seeds fuzzed. Pane-verified end to end (identity flow, POST 200, death screen with +16 XP
and the bar; NOTE this pane session has rAF dead again — `T.step(1/60)` loops drove the clock).
## THE LEAGUES — the meta-progression plan (2026-08-29, AGREED, PHASE 1 BUILT — see log above)
Worked out with Oliver in discussion, decisions settled below. The goal in his words: an incentive
to keep opening the game beyond beating the next opponent — levelling through play, with a clearly