Fold the menu into the title screen, and stop the theme restarting

The menu stage was the same logo on the same backdrop with two buttons, so it
read as the title screen failing to advance. The buttons move onto the title
screen in place of the control list, which is the tutorial's job now.

Death no longer reloads the page. The reload was an honest full reset, but it
also tore down the AudioContext — so the theme restarted from silence every
time you died, when it should carry straight on from the death summary into the
title screen. restartRun() puts G, P, O, ball and score back from a
structuredClone taken at module load (all plain data, so the clone is the whole
job and a field added later is covered for free), and the two modules with
timers of their own reset themselves: every zombies array plus spawnT/moanT/
mgTick, and input's held keys and half-finished drags, which would otherwise be
inherited by the next run's first frame.

The overlay is hidden rather than removed so it can come straight back, which
needs its own [hidden] rule to beat the display:grid on #intro.

The theme starts on the first tap and never restarts: loopBed's beds.has guard
already makes every later menuMusic() a no-op.

Verified: one background-music fetch across launch -> run -> death -> title ->
second run, and a full second run through the transition after the in-place
reset.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5 committed 2026-08-01 16:25:20 +02:00
1 parent 2d4fa79258
commit d3aabc4bd6
7 files changed
+114 -74

No files matched your search

+27 -12
View File
@@ -454,11 +454,29 @@ death summary is the end menu, and the zombie bed ducks out as it comes in. Two
bed's gain at 0. Without it, a multi-megabyte file ducked mid-fetch comes up at full level several
seconds later, over the match.
**The overlay is a four-stage machine**, not a screen per DOM node: `#intro[data-stage]` swaps
`splash -> menu -> {courts, tutorial}` while the backdrop and card frame stay mounted, because a
screen that unmounts the overlay flashes the live court through between menus. Court tiles SELECT
only; `START GAME` and `BACK` are their own buttons. The tutorial is a data table (`TUT` in main.js)
of Marvin poses, an optional picture already in `assets/`, and his lines — adding a page is a row.
**The overlay is a three-stage machine**, not a screen per DOM node: `#intro[data-stage]` swaps
`splash -> {courts, tutorial}` while the backdrop and card frame stay mounted, because a screen that
unmounts the overlay flashes the live court through between menus. **The title screen IS the menu** —
a separate menu stage behind it was the same logo on the same backdrop with two buttons, and read as
the title screen failing to advance. Court tiles SELECT only; `START GAME` and `BACK` are their own
buttons. The tutorial is a data table (`TUT` in main.js) of Marvin poses, an optional picture in
`assets/`, and his lines — adding a page is a row.
**DEATH NO LONGER RELOADS THE PAGE.** `location.reload()` was an honest full reset and for a long
time the right call — but it also tore down the AudioContext, so the menu theme restarted from
silence every time you died, when it is supposed to carry straight on from the death summary into
the title screen. `gameplay.restartRun()` replaces it:
- `state.resetState()` restores `G`, `P`, `O`, `ball` and `score` from a `structuredClone` taken at
module load. Everything in those bags is plain data, so the clone is the whole job — nothing has
to enumerate fields, and a field added later is covered for free.
- The two modules with timers of their own put them back themselves: `zombies.resetZombies()` (every
array plus `spawnT`/`moanT`/`mgTick`) and `input.resetInput()` — a key still held or a
half-finished drag would otherwise be inherited by the next run's first frame.
- The overlay is **hidden, not removed**, so it can come straight back. `#intro[hidden]` needs its
own rule because the `display: grid` on `#intro` would otherwise beat the hidden attribute.
**If you add module-level mutable state anywhere, it belongs in one of those reset functions.**
**Menu and tutorial art is generated DIRECTLY, not through `_regen.py`.** That pipeline keys magenta
and slices figures out of a sheet; these are whole illustrated panels with backgrounds, so they go
@@ -478,13 +496,10 @@ Generated type is a lottery and would not match the rest of the HUD. `btn_start`
a taller plate: by there the drips have run out to the ends and the centre is clear enough to print
over, and the plate's own bottom border comes with it.
**The menu theme starts on the FIRST TAP, never at boot** (`go()` calls `menuMusic`). A title screen
that plays at you before you touch it is rude, and on a phone the autoplay policy would have held it
silent until that tap regardless.
**Death returns to the MENU, not the splash.** `maybeRestart` still reloads — that is the honest
full reset and nothing has to unwind a run by hand — but it sets `sessionStorage.splatterStage` on
the way out, and boot consumes it to open at the menu.
**The menu theme starts on the FIRST TAP, never at boot.** A title screen that plays at you before
you touch it is rude, and on a phone the autoplay policy would have held it silent until that tap
regardless. `loopBed`'s `beds.has` guard makes every later `menuMusic()` a no-op, which is what lets
the title screen pick up the track already playing instead of restarting it.
**The theme is AAC** (`background-music.m4a`, 768 KB — the WAV was 8.3 MB, and this one downloads on
every page load before the player has decided to play). `loopBed` takes the FULL filename now,