From 022c6d688155daaf4bb32c4a330f76f65e4b3f40 Mon Sep 17 00:00:00 2001 From: ksdisch Date: Sat, 11 Jul 2026 19:53:51 -0500 Subject: [PATCH] =?UTF-8?q?docs(m13):=20mark=20the=20audit=20hardening=20i?= =?UTF-8?q?tem=20Done=20=E2=80=94=20Phase=206=20deferred=20to=20the=20depl?= =?UTF-8?q?oy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phases 0–5 landed with gates green (PRs #26–#31), meeting the item's own acceptance ('phase 6 rides the actual public deploy'). Phase 6 (F-34/F-37/F-38b/F-39/F-60/F-61) is now pointed to from the open Deploy item so it can't get lost; the Progress line is preserved byte-identical. Co-Authored-By: Claude Fable 5 --- BACKLOG.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/BACKLOG.md b/BACKLOG.md index d9dfa97..f30ae04 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -13,13 +13,6 @@ Pick items with the `project-backlog` skill in Claude Code. ## Open -### [Improvement] Hardening pass — fix the 2026-07-09 audit findings -- **Why:** A full-project audit ([`docs/AUDIT-2026-07-09.md`](docs/AUDIT-2026-07-09.md)) found two remotely triggerable relay crashes, disconnect-blindness on both clients (the couch-playtest killer), two bugs that undermine shipped features (freeze re-cast truncates the freeze; **every particle burst renders off-screen**), a protocol-guard hook that auto-approves the edits it should guard, and onboarding docs stuck at M2/M4 that misdirect every fresh session. -- **Acceptance:** Phases 0–5 of the audit's fix plan landed with their per-phase gates green (phase 6 rides the actual public deploy). Findings F-01…F-59 closed or explicitly deferred with a note. -- **Size:** L (≈5–7 focused days across 6 independently shippable phases) -- **Added:** 2026-07-09 -- **Progress:** Phase 0 landed 2026-07-10 (PR #26) — F-01, F-02, F-09, F-10, F-11, F-12, F-20, F-38a, F-58 closed; relay smoke now probes hostile frames and terminates when piped. Phase 1 landed 2026-07-10 (PR #27) — F-05, F-06, F-07, F-08, F-16, F-17, F-18, F-21, F-35, F-36 closed (rooms map bounded, F-26-adjacent): relay heartbeat sweeps ghosts, scenes release their handlers, both clients surface socket death (phone gets a one-tap same-code rejoin), dedicated `peer-disconnected` message, pure `RoomRegistry` unit-tested; smoke:relay now proves the ghost-sweep → same-code-rejoin couch scenario. Phase 2 landed 2026-07-10 (PR #28) — F-03, F-04, F-13, F-14, F-15, F-19, F-40, F-41 closed: particle bursts render on-screen for the first time (`explode()` args were emitter-local), freeze re-cast extends via a token guard, post-win casts are ignored, the end card is no longer striped by the curtain/dark zone, solo drops the false phone-linked indicator, a swallowed platform re-cast flashes PLATFORM HOLDS!, hub music resumes on first tap, and the master GainNode is released per track switch; gate = full bridge playbook (planet-1/2/3 clears, freeze re-cast extension, phase ±) plus a screenshot pass that finally SEES the bursts — the human playtest is now worth running. Phase 3 landed 2026-07-11 (PR #29) — F-24, F-25, F-26, F-27 (banner-only), F-33, F-59 closed: README/CLAUDE.md/session-start now describe the shipped game (4 powers, 3 planets, hub/talents/portrait; solo keys 1–4; `?test=1` + `smoke:relay` pointers; allowlist relay wording everywhere), PROJECT_GUIDE.md carries a loud historical-snapshot banner and sessions are no longer routed into it, this file's playtest item is reframed to the shipped game, and the F-59 one-liners are fixed (plus three gate-caught stragglers in protocol-steward, the new-power skill, and CLAUDE.md's server line); gate = the audit's grep sweep (`Level.ts` / pass-through / three powers / M2-M4) with zero live hits outside deliberately historical text. Phase 4 landed 2026-07-11 (PR #30) — F-28, F-29, F-30, F-31, F-32, F-57 closed (F-21 had landed in Phase 1): the colocated test files finally typecheck (`tsconfig.tests.json` + `typecheck:tests`, chained into the edit hook; two latent test-type drifts fixed, not suppressed), the colocated-test hook covers `server/` + repo-relative paths, the typecheck hook skips out-of-repo files, `engines` pins Node ≥20.19, planet-1 gets the geometry+progression suite its siblings had (13 tests — pit un-jumpability asserted via reconstructed tile EDGES, since the raw 220px skip range is narrower than a 245px running jump; suite 177 → 190), and — decision #1 approved — a minimal GitHub Actions gate (npm ci → typecheck → typecheck:tests → vitest → build → smoke:relay) runs on every PR/main push with session-start's no-CI rule amended to "don't extend beyond this"; gate = live hook blocked a deliberate test-type break, `server/relay.ts` edits run their sibling test (absolute + relative forms), full battery green, CI green on the PR itself. Phase 5 landed 2026-07-11 (PR #31) — F-22, F-23, F-48–F-56 closed: wire-sends and talent persists left the setState updaters (decide-from-current-phase via render-synced refs; `saveTalents` is a `useEffect` of the talents state) and `` went ON right behind it (F-54), pure puzzle logic moved into colocated framework-free modules with Vitest (makeProblem ranges/answers, QUESTIONS invariants, no-repeat sampler, never-aligned dials; suite 190 → 204), QuickMath — the documented clone template — gained its siblings' solvedRef/timeout guards plus a wrong-flash on unparsable input, TapSequence gained solvedRef, every one-shot timer is a tracked ref cleared on unmount, cancels hit the 44px floor, palette drift aligned (`#ff9090`→`#ff6b9d` ×4, error banner to panel `#1a1b3a`/`#ff6b9d`, `#667`/`#556` dims → the documented opacity-.6-on-#fff), pinch-zoom unblocked (WCAG 1.4.4) with `touch-action: manipulation` on controls and `100dvh` (+vh fallback), aria-live on toast/try-again/last-5s timers, and — decision #4 approved: cap — `solveMs` is capped at the puzzle's effective talent-tuned timer via the exhaustive `PUZZLE_TIMER_SECONDS` map (defaults imported from the puzzles, pinned by tests); gate = typecheck ×2 · vitest 204 · build · live two-client co-op with StrictMode ON (join → QuickMath solve → freeze lands → co-op planet-1 clear → +3 bonus stardust) with every double-fire detector clean (relay logged ONE phone join, `telemetry.solves['freeze-stars'].count === 1`, stardust exactly 1 → 4) plus the mandated Freeze Stars smoke. Next: Phase 6 (deploy hardening, optional — alongside the actual public deploy; decisions #2 Prettier / #3 jump feel / #5 PROJECT_GUIDE regen / #6 Docker slim remain open). - ### [Exploration] Playtest the shipped game with girlfriend — the "is it fun?" gate - **Why:** The fun-gate playtest has genuinely never run. Originally framed as the M2 gate "before any M3 work", but the game has since grown into a hub + 3 planets + 4 powers + talents + the rhythm portrait without ever being tested on the only audience that matters. The whole asymmetric premise lives or dies here. (Audit Phase 2 fixed the feel bugs — invisible bursts, truncated freezes — so the playtest is finally worth running.) - **Acceptance:** Play at least one full co-op session (handshake → hub → clear planets 1–3 with real phone casts → spend stardust in the talent tree → read the portrait card). Write down: did casts feel rewarding? Was puzzle difficulty right? Did the freeze/platform/phase windows feel tight or generous? Did the pairing feel meaningful — and did the portrait feel true to how you two played? @@ -31,6 +24,7 @@ Pick items with the `project-backlog` skill in Claude Code. - **Acceptance:** `fly deploy` the relay (config is in-repo), build the clients with `VITE_RELAY_URL=wss://.fly.dev`, upload `dist/` to itch.io. End-to-end: laptop on home wifi, phone on cellular, full co-op loop works. Step-by-step in `docs/DEPLOY.md`. - **Size:** S (groundwork done; this is the credentialed finish) - **Added:** 2026-05-12 +- **Note:** Audit Phase 6 (deploy hardening — F-34, F-37, F-38b, F-39, F-60, F-61; [docs/AUDIT-2026-07-09.md](docs/AUDIT-2026-07-09.md) §Phase 6) deliberately rides this deploy — do them together. Decision #6 (Docker slim approach) is still open. --- @@ -42,6 +36,14 @@ _(nothing in flight)_ ## Done +### [Improvement] Hardening pass — fix the 2026-07-09 audit findings +- **Why:** A full-project audit ([`docs/AUDIT-2026-07-09.md`](docs/AUDIT-2026-07-09.md)) found two remotely triggerable relay crashes, disconnect-blindness on both clients (the couch-playtest killer), two bugs that undermine shipped features (freeze re-cast truncates the freeze; **every particle burst renders off-screen**), a protocol-guard hook that auto-approves the edits it should guard, and onboarding docs stuck at M2/M4 that misdirect every fresh session. +- **Acceptance:** Phases 0–5 of the audit's fix plan landed with their per-phase gates green (phase 6 rides the actual public deploy). Findings F-01…F-59 closed or explicitly deferred with a note. +- **Size:** L (≈5–7 focused days across 6 independently shippable phases) +- **Added:** 2026-07-09 +- **Completed:** 2026-07-11 +- **Progress:** Phase 0 landed 2026-07-10 (PR #26) — F-01, F-02, F-09, F-10, F-11, F-12, F-20, F-38a, F-58 closed; relay smoke now probes hostile frames and terminates when piped. Phase 1 landed 2026-07-10 (PR #27) — F-05, F-06, F-07, F-08, F-16, F-17, F-18, F-21, F-35, F-36 closed (rooms map bounded, F-26-adjacent): relay heartbeat sweeps ghosts, scenes release their handlers, both clients surface socket death (phone gets a one-tap same-code rejoin), dedicated `peer-disconnected` message, pure `RoomRegistry` unit-tested; smoke:relay now proves the ghost-sweep → same-code-rejoin couch scenario. Phase 2 landed 2026-07-10 (PR #28) — F-03, F-04, F-13, F-14, F-15, F-19, F-40, F-41 closed: particle bursts render on-screen for the first time (`explode()` args were emitter-local), freeze re-cast extends via a token guard, post-win casts are ignored, the end card is no longer striped by the curtain/dark zone, solo drops the false phone-linked indicator, a swallowed platform re-cast flashes PLATFORM HOLDS!, hub music resumes on first tap, and the master GainNode is released per track switch; gate = full bridge playbook (planet-1/2/3 clears, freeze re-cast extension, phase ±) plus a screenshot pass that finally SEES the bursts — the human playtest is now worth running. Phase 3 landed 2026-07-11 (PR #29) — F-24, F-25, F-26, F-27 (banner-only), F-33, F-59 closed: README/CLAUDE.md/session-start now describe the shipped game (4 powers, 3 planets, hub/talents/portrait; solo keys 1–4; `?test=1` + `smoke:relay` pointers; allowlist relay wording everywhere), PROJECT_GUIDE.md carries a loud historical-snapshot banner and sessions are no longer routed into it, this file's playtest item is reframed to the shipped game, and the F-59 one-liners are fixed (plus three gate-caught stragglers in protocol-steward, the new-power skill, and CLAUDE.md's server line); gate = the audit's grep sweep (`Level.ts` / pass-through / three powers / M2-M4) with zero live hits outside deliberately historical text. Phase 4 landed 2026-07-11 (PR #30) — F-28, F-29, F-30, F-31, F-32, F-57 closed (F-21 had landed in Phase 1): the colocated test files finally typecheck (`tsconfig.tests.json` + `typecheck:tests`, chained into the edit hook; two latent test-type drifts fixed, not suppressed), the colocated-test hook covers `server/` + repo-relative paths, the typecheck hook skips out-of-repo files, `engines` pins Node ≥20.19, planet-1 gets the geometry+progression suite its siblings had (13 tests — pit un-jumpability asserted via reconstructed tile EDGES, since the raw 220px skip range is narrower than a 245px running jump; suite 177 → 190), and — decision #1 approved — a minimal GitHub Actions gate (npm ci → typecheck → typecheck:tests → vitest → build → smoke:relay) runs on every PR/main push with session-start's no-CI rule amended to "don't extend beyond this"; gate = live hook blocked a deliberate test-type break, `server/relay.ts` edits run their sibling test (absolute + relative forms), full battery green, CI green on the PR itself. Phase 5 landed 2026-07-11 (PR #31) — F-22, F-23, F-48–F-56 closed: wire-sends and talent persists left the setState updaters (decide-from-current-phase via render-synced refs; `saveTalents` is a `useEffect` of the talents state) and `` went ON right behind it (F-54), pure puzzle logic moved into colocated framework-free modules with Vitest (makeProblem ranges/answers, QUESTIONS invariants, no-repeat sampler, never-aligned dials; suite 190 → 204), QuickMath — the documented clone template — gained its siblings' solvedRef/timeout guards plus a wrong-flash on unparsable input, TapSequence gained solvedRef, every one-shot timer is a tracked ref cleared on unmount, cancels hit the 44px floor, palette drift aligned (`#ff9090`→`#ff6b9d` ×4, error banner to panel `#1a1b3a`/`#ff6b9d`, `#667`/`#556` dims → the documented opacity-.6-on-#fff), pinch-zoom unblocked (WCAG 1.4.4) with `touch-action: manipulation` on controls and `100dvh` (+vh fallback), aria-live on toast/try-again/last-5s timers, and — decision #4 approved: cap — `solveMs` is capped at the puzzle's effective talent-tuned timer via the exhaustive `PUZZLE_TIMER_SECONDS` map (defaults imported from the puzzles, pinned by tests); gate = typecheck ×2 · vitest 204 · build · live two-client co-op with StrictMode ON (join → QuickMath solve → freeze lands → co-op planet-1 clear → +3 bonus stardust) with every double-fire detector clean (relay logged ONE phone join, `telemetry.solves['freeze-stars'].count === 1`, stardust exactly 1 → 4) plus the mandated Freeze Stars smoke. Next: Phase 6 (deploy hardening, optional — alongside the actual public deploy; decisions #2 Prettier / #3 jump feel / #5 PROJECT_GUIDE regen / #6 Docker slim remain open). + ### [Feature] Master-mute toggle — silence the procedural audio in-game (M11) - **Why:** M5 shipped procedural SFX (`src/game/juice/audio.ts`) + ambient music (`src/game/juice/music.ts`) with **no way to silence them** — awkward the moment you share the game with someone (a meeting, a quiet room, a partner who's heard the loop enough). Game-side only; the phone has no audio. - **Acceptance:** An in-game 🔊/🔇 toggle on **Hub and Planet** (pinned, ≥44px, hand cursor) flips a single master mute that **both** engines honor — SFX and music go silent — while the bridge keeps RECORDING cues/tracks (the "requested ≠ audible" split M5 already had). Default unmuted; the choice **persists across reload** via a versioned/guarded/never-throws `localStorage` twin of `save.ts` (own key `constellation:settings`), applied on boot before any scene plays audio. Freeze Stars + planet-1 unaffected.