diff --git a/PROJECT.md b/PROJECT.md index 2558e99..563537d 100644 --- a/PROJECT.md +++ b/PROJECT.md @@ -636,6 +636,87 @@ Each phase ships to staging and gets a phone verdict, and tests ride along with 5. **LATER.** The metal bed (when Oliver has a track: start at the transformation, gore only) and Members' Night (option C). +## Session log: THE STORY, PHASES 1-4 — built on staging (2026-10-15, branch `story`, v380-v382, STAGING ONLY) + +Oliver: *"mache alle, ich vertraue. alles erst mal auf staging"*. The plan is in "THE STORY" below. +Phases 1-4 are built; phase 5 (metal, Members' Night) waits on him. `main`/prod stays at v379 until he +releases it; staging carries the diff until then (`git push origin story:staging`). Verified headless +(182 tests, several seeds) and in a real Chromium at 390×844, where every screen was looked at. + +**Phase 1, the spine.** +- **Data:** `config.STORY`, with acts (rounds with the boss's body as the final, mid/boss designs, + pool, arcade) plus `midAt` 5, `length` 10 and `base(act)` = (act-1)·20, and its helpers. +- **Persistence:** `prof.story` = {act, cp: pro|cup|chap|mid|done, rack, tries, deaths, help}. + - It is saved in the profile, because `restartRun` wipes G. + - The server whitelists act and cp only. + - `pull()` moves the story forward only, and ignores a pre-story mirror's league. + - A profile without `story` loads reset: league 1, no cups, no champion, level kept (xp is the + mirror's ratchet). +- **Flow, in beats.js:** + - `startStory` dispatches on the checkpoint. The cup is `startMatch(true, storyRounds)` (the dead + `draw` slot became `rounds`). + - `startStoryChapter(fromMid, keepCrowd)` sets `runGames = base(+5)`, fills the rack from + `storyStock` or the saved rack, and gives full blood. + - `splatPoint` arms `G.storyBoss` ('mid' at chapter game 5, 'end' at 10) and never calls a duel in a + chapter. `proceedAfterClear` runs `startStoryBoss`, which is `startBossFight(chapter, design)`; + `spawnChapterBoss` takes a design. + - `endBossFight` handles both: 'mid' → `storyCheckpoint` (restock to the stock, full blood, save); + 'end' → keeps `bossFight` up (no spawns) and `winT` → `storyActCleared`. + - `startDuel` refuses in the story, and so does the lucky-clear trigger in update. + - `killPlayer` → `storyDeath`: the attempt counts, nothing is banked (no `endRun`: chapter games + are on the arcade's scale). + - Barrels draw from `storyPool`. +- **Main:** the front door says `storyFixture`; `start('story')`; the summaries say TRY AGAIN / + CONTINUE (door 'story'). + +**Phase 2, the transformation and the prologue.** +- **The transformation** (`storyMorph` / `tickMorph`, beats.js). At the final's winning point the body + does NOT fall. The ceremony screen pauses the world, then: + - 1.2 s: applause cut, "quiet please", zoom. + - 2.8 s: hitstop and flash; the body bursts and the act boss spawns at its spot, growing from 0.45 to + full scale. + - 3.3 s: name card, "GAME 10". + - 4.6 s: four bodies come down from the stands, "remain seated", the chapter music comes up; the + boss runs off to the left. + - 6.4 s: `startStoryChapter(false, keepCrowd)`: game 1 without a cut. + + The interlude holds serving, spawns and the boss AI. `cine` holds the racket body. A rebuild of the + old `startTransition` was impossible, since the cup runs in splatter under an interlude, keyed on + the `'r'` body. +- **The prologue** (`cp 'pro'`): `start('story')` stays in mode 'tennis' (not classic) and plays the + old transformation unchanged. Its 'go' beat calls `storyPrologueGo`, and 2.4 s later act I's cup + opens. + +**Phase 3, the screens** (`#sty` overlay + `main.storyScreen`, through the `beats.storyUI.show` hook; +headless, the old cards stand in). The world is paused under each: +- the act title card (auto 2.8 s or a tap); +- the ceremony: stats, the weapons dropping into a rack, the chapter preview with "10 · ???", + TO THE COURT; +- the act card: bodies, bosses, deaths this act, new in arcade, the next act's court, CONTINUE / + THE CLUBHOUSE. + +The checkpoint is an in-game card. Gotcha found in the browser: a second `style=""` on an element is +silently dropped, so `delay(k, more)` merges them. + +**Phase 4, help, arcade, unlocks.** +- **Help** (`config.STORY_HELP`). From `tries` ≥ 2 at a checkpoint, the death screen offers + TRY AGAIN · WITH HELP (max 2 tiers, reset by every checkpoint passed). + - Cup: `legLevel` ×(1+0.08·tier) and `winLevel` ×(1+0.12·tier). + - Gore: +1 heart per tier, and the director's gap and the horde rate are slowed by 1+0.25·tier. +- **Arcade:** a door opens when the act listed in `acts[].arcade` is cleared, or once the story is told. + - Locked doors are greyed with "CLEAR ACT n TO OPEN"; a tap shakes them. + - Splatter Gore (and its court link) opens after act III. + - Hosting vs a friend opens after act IV; joining stays free. +- **Store switches:** `STORE.freeTier` and `STORE.arcadePremium` (both off) and a `premium()` stub. With + the free tier on, act I's card points at the full game. +- The non-story summaries' fixture is the story now (door 'next' → `start('story')`). + +**Not done / open:** +- Phase 5 (the metal bed when Oliver has a track; Members' Night). +- The tutorial and the ghost hand for the new double tap. +- Stats: story kills are not banked into the career totals (no `endRun`). +- The old cup mode is unreachable but still in MODES. + ## Session log: THE SEASON, PHASES 1-4 — the front door, the record, the rooms, the season beat (2026-08-31, STAGING ONLY) All four phases of the plan below, plus the overlay consolidation Oliver asked for in the same @@ -5907,7 +5988,8 @@ stance the old recovery still takes him back. Nothing here is broken; these are known gaps and unverified risks. -0. **THE STORY (2026-10-15) is agreed and planned (five phases), NOT built.** See "THE STORY" above. +0. **THE STORY (2026-10-15): phases 1-4 BUILT ON STAGING (v380-v382), awaiting Oliver's phone verdict; prod still + v379.** See "Session log: THE STORY, PHASES 1-4" and "THE STORY". 00. **THE RALLY GOES FLAT (2026-10-13).** Every match converged on serve, both at the net, reflex volleys, won by a wrong-foot. Step 1 (the court gets its length) SHIPPED v361, awaiting the diff --git a/index.html b/index.html index 4a066bb..fef0482 100644 --- a/index.html +++ b/index.html @@ -601,6 +601,12 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat background-size: cover; background-position: center; image-rendering: pixelated; text-align: left; cursor: pointer; } #modeList button.mode:last-child { grid-column: 1 / -1; } + /* THE ARCADE OPENS WITH THE STORY: a shut door is shown, greyed, with the act that opens it */ + #modeList button.mode.locked, #mGore.locked { filter: grayscale(1) brightness(.5); cursor: default; } + #modeList button.mode .cb i { display: block; margin-top: 4px; font: 400 9px Silkscreen, monospace; letter-spacing: 1px; color: #e0cc72; } + .nope { animation: nope .35s; } + @keyframes nope { 20%, 60% { transform: translateX(-5px); } 40%, 80% { transform: translateX(5px); } } + #vsHostLock { font: 400 10px Silkscreen, monospace; color: #e0cc72; letter-spacing: 1px; margin: 0 0 8px; } #modeList button.mode::after { content: ''; position: absolute; inset: 0; pointer-events: none; background: linear-gradient(0deg, rgba(6,10,8,.94), rgba(6,10,8,.2) 65%, transparent); } #modeList .cb { position: relative; z-index: 1; } @@ -1094,6 +1100,7 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat