Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<StrictMode>` 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?
Expand All @@ -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://<app>.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.

---

Expand All @@ -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 `<StrictMode>` 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.
Expand Down
Loading