diff --git a/PROJECT.md b/PROJECT.md index 344e3fa..d222e4a 100644 --- a/PROJECT.md +++ b/PROJECT.md @@ -491,6 +491,54 @@ 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 LEAGUES, PHASE 2 — bands, pace tiers, courts, the promotion (2026-08-29, STAGING ONLY) + +Phase 2 of the plan. STAGING ONLY, like phase 1. + +- **`LEAGUES` in config.js** — five bands tiling the 18-rung ladder exactly (1-4 / 5-8 / 9-12 / + 13-15 / 16-18, the club's own hierarchy), each with its home court: clay → hard → grass → + gloom → **members** (new). `leagueOf(lg)`, and the ladder-shape test now pins the tiling. +- **`CUP_ROUNDS` is GONE; the cup carries its own rounds** (`cup.rounds`, stamped by startMatch + from `cupRoundsFor(G.league)`). A league band is 3-4 rungs, so the draw is WITHOUT REPLACEMENT: + non-final rounds carry the whole sub-band, `cup.played` (and buildBracket's `used` set) stop a + body being dealt twice, the final is always the band's top rung — "only the finale is settled" + survives the narrow bands. 4-rung leagues play four rounds, 3-rung leagues three. +- **`G.league`** (state, PRISTINE 1) is stamped by startMatch — netplay and training stay league-1 + worlds. **`gameplay.leaguePace()`** = `1 + (league-1) * TUNE.leaguePace` (0.06) multiplies ball + pace on BOTH rackets (doHit, both serve paths) and `rzChaseSpeed`, so the world speeds up as one + piece and the reach tests keep believing the legs. League 1 IS the shipped game (factor 1.0 — + the calibration rule); league 5 plays ~24% faster. Everything non-match is factor 1: gore keeps + its own director. +- **The arcade opens at its band's bottom rung and CLAMPS at its top** — past it the league has + nobody stronger and the top body walks back on; the cup is the only way up. `d.felled` counts + bodies now (opp is a band position, `opp - 1` stopped meaning anything — killPlayer, pagehide + and the scoreboard slot all read felled). +- **Courts lock to the leagues**: a court unlocks for EVERY mode when you reach its league + (settled reward formula). Rows keep their tile visible but answer no tap and say the price + ("LOCKED — REACH THE OFFICIALS"); syncSetup repaints locks on entry; a remembered-but-locked + pick falls back to the league court. League matches force the league court in `start()`; the + footer says so plus "LEAGUE N". The training picker only offers rungs up to your band's top. +- **THE MEMBERS' GROUND** (Oliver's pick): new COURTS row — brightly decayed prestige, dead + grey-green lawn (fastest bounce in the table, finishing the pace ladder), pale marble, ivy, + faded gold; borrows clay's crowd/ads with `dim` 0.15. Tile rolled via the famroll OpenRouter + route (style ref = court_grass tile, 4x NEAREST downscale + quantise), SPRV 148 → 149. +- **THE PROMOTION CEREMONY**: cupWon (non-train, cup of your CURRENT league) moves `prof.lg`, + parks a ceremony in the profile module (consumed-on-read, survives restartRun), and + maybeRestart plays it before the name prompt: PROMOTED. / league name / the new court's tile / + "OPEN IN EVERY MODE" / WALK ON. League 5 crowns instead (`prof.ch`, "CHAMPION."). The cup + summary's big line is league-aware now: 'CHAMPION OF THE DEAD CLUB.' belongs to league 5 only, + lower cups say 'THE CUP IS YOURS.' +- **Fix found live**: the scoreboard's cup slot indexed `cup.rounds[cup.round]` one past the end + on the trophy frame (cup.round is incremented before cupWon is reached) — clamped. +- **XP bar wears a gradient** (Oliver, mid-session): green → acid → gold, splash strip and the + canvas bar on the death screen. +- Tests 118 → 121 (band tiling + per-league cup shape, promotion + ceremony parked, champion + crown, arcade band clamp + felled, pace-tier scaling and its exemptions; the tournament + walkthrough now also asserts no body is dealt twice and the promotion fires; freshSplatter + resets the profile league so a won cup cannot leak league 2 into later tests). Seeds fuzzed. + Pane-verified: locks, forced court + league footer, league-2 draw, full cup → trophy (+1512 XP + · LEVEL 6 → 8) → PROMOTED ceremony → league-5 arcade opening vs THE BRUTE on the members court. + ## 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 diff --git a/assets/court_members.png b/assets/court_members.png new file mode 100644 index 0000000..70dfd30 Binary files /dev/null and b/assets/court_members.png differ diff --git a/index.html b/index.html index 39223f9..a53ffc5 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, #train, #player { display: none; } + #splash, #setup, #stats, #ranks, #newbest, #secret, #bosses, #stmode, #stsub, #bracket, #vs, #train, #player, #promo { 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 */ @@ -189,6 +189,7 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat #intro[data-stage="train"] #train, #intro[data-stage="vs"] #vs, #intro[data-stage="player"] #player, + #intro[data-stage="promo"] #promo, #intro[data-stage="newbest"] #newbest { display: flex; flex-direction: column; flex: 1; animation: drop 0.3s ease-out; } /* the overlay is HIDDEN during play, not removed: a restart puts it straight back, and the @@ -257,6 +258,22 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat #nameRow { display: flex; gap: 8px; } #nameRow .tin { flex: 1; margin: 0; } #pDice { width: 52px; flex: none; margin: 0; padding: 0; font-size: 20px; line-height: 1; } + /* a LOCKED court: the tile stays visible — it is a goal — but it answers no tap and sits back */ + .court.lock { opacity: 0.45; filter: saturate(0.4); cursor: default; } + .court.lock i { color: var(--bad); } + /* THE PROMOTION CEREMONY: one centred column, the walk-on moment of the meta game. The court + tile is the reward made visible; the staggered drops are cheap CSS, no rAF (menu parks it). */ + #promo .mid { gap: 10px; } + #prTag { margin: 0; font: 700 10px Silkscreen, monospace; color: var(--dim); letter-spacing: 3px; + animation: drop 0.5s ease-out; } + #prTitle { font: 400 44px Bangers, cursive; color: var(--acid); letter-spacing: 2px; margin: 0; + animation: drop 0.5s ease-out 0.15s backwards; } + #prLeague { margin: 0; font: 700 12px Silkscreen, monospace; color: var(--cream); + letter-spacing: 2px; animation: drop 0.5s ease-out 0.3s backwards; } + #prCourt { width: 74%; max-width: 300px; margin-top: 10px; border: 2px solid var(--line); + image-rendering: pixelated; animation: drop 0.5s ease-out 0.5s backwards; } + #prCourtName { margin: 0; font: 400 10px Silkscreen, monospace; color: var(--acid); + letter-spacing: 1.5px; animation: drop 0.5s ease-out 0.62s backwards; } /* THE LEVEL STRIP: the one meta number over a thin bar, riding the splash foot. The fill colour is the acid accent for now; the leagues will hand it the skill colour in phase 3. */ #xpRow { display: flex; align-items: center; gap: 8px; margin: 0 0 10px; } @@ -264,7 +281,8 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat letter-spacing: 1px; white-space: nowrap; } #xpRow s { flex: 1; height: 6px; background: rgba(255,255,255,0.10); border: 1px solid var(--line); text-decoration: none; display: block; } - #xpRow s i { display: block; height: 100%; width: 0%; background: var(--acid); + #xpRow s i { display: block; height: 100%; width: 0%; + background: linear-gradient(90deg, #3f9560, var(--acid) 55%, #e8b23a); transition: width 0.4s ease; } /* KIT and SKIN: a row of colour squares, under a label that names whatever is selected. That is the whole UI — the pick has no meters and no art to show, and a labelled tile per colour would @@ -597,6 +615,20 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat + +
+
+

THE CUP IS YOURS

+

PROMOTED.

+

+ +

+
+
+ +
+