diff --git a/plans/260421-2042-level-redesign-solvability/phase-01-scrollable-viewport.md b/plans/260421-2042-level-redesign-solvability/phase-01-scrollable-viewport.md new file mode 100644 index 0000000..c14b0e2 --- /dev/null +++ b/plans/260421-2042-level-redesign-solvability/phase-01-scrollable-viewport.md @@ -0,0 +1,64 @@ +# Phase 1: Scrollable Viewport + +## Context +- Brainstorm: `plans/reports/brainstorm-260421-2042-level-redesign-solvability.md` §5 +- Board component: `src/components/GameBoard.svelte` (90 LOC) +- Game scene: `src/scenes/Game.svelte` (409 LOC) + +## Overview +- **Priority:** CRITICAL — blocks all grid >9x9 redesigns +- **Status:** pending +- Prereq for Acts 3–6 (grids 10x10–13x13). Desktop-first; no mobile handling. + +## Key Insights +- Current rendering assumes full grid fits on screen → unplayable at 12x12+. +- Simple solution: CSS scroll container + `scrollIntoView` on player cell each turn. +- Svelte 5 runes: `$effect(() => ...)` to react to player position. + +## Requirements +- Viewport shows ~9x9 cells. Grid larger than viewport scrolls. +- After each player move, scroll so player cell is visible (prefer centered). +- Smooth scroll (CSS `scroll-behavior: smooth`). +- No layout break at grids ≤8x8 (viewport = grid size, no scroll). + +## Architecture +- Wrap `GameBoard.svelte` grid in a scrollable container `
`. +- Fixed viewport size (e.g., `min(80vh, 720px)` square). +- Inner grid expands naturally based on rows × cols × cellSize. +- `$effect` triggers `cell_element.scrollIntoView({ block: 'center', inline: 'center', behavior: 'smooth' })` on `player.row`/`player.col` change. + +## Related Files +- **Modify:** `src/components/GameBoard.svelte` (add viewport wrapper, scroll effect) +- **Modify:** `src/styles/theme.css` (viewport CSS vars if needed) +- **Read for context:** `src/scenes/Game.svelte`, `src/lib/game/player.js` + +## Implementation Steps +1. Add `
` wrapping the `.grid` element in `GameBoard.svelte`. +2. CSS: `.viewport { max-width: 80vh; max-height: 80vh; overflow: auto; scroll-behavior: smooth; }`. +3. Bind `ref` to the player's cell element (use Svelte action or id). +4. Add `$effect` watching `player.row`, `player.col` → call `scrollIntoView` on the player cell. +5. Test with temporary 12x12 dummy level (don't commit dummy level). +6. Verify no regression at 6x6, 8x8. +7. Run `npm run build` to check compile. + +## Todo List +- [ ] Add viewport wrapper + CSS in `GameBoard.svelte` +- [ ] Wire player cell ref + `$effect` scrollIntoView +- [ ] Manual test: 6x6 (no scroll), 12x12 (scroll follows) +- [ ] `npm run build` passes +- [ ] `npm run dev` smoke test on L1 + temp 12x12 + +## Success Criteria +- 12x12 grid fully playable; camera follows player. +- 6x6/8x8 grids render identically to before (no scrollbars when grid fits). +- Smooth scroll animation on each move. + +## Risk Assessment +- **Risk:** `scrollIntoView` with smooth animation may lag on fast moves. **Mitigation:** test with held arrow key; switch to instant scroll if lag visible. +- **Risk:** Svelte 5 reactivity on ref may not fire. **Mitigation:** use `bind:this` + derived runes or simple DOM query. + +## Security Considerations +N/A — pure UI. + +## Next Steps +Phase 2 (solver) can run in parallel with this phase — no file overlap. diff --git a/plans/260421-2042-level-redesign-solvability/phase-02-level-solver.md b/plans/260421-2042-level-redesign-solvability/phase-02-level-solver.md new file mode 100644 index 0000000..a5a1814 --- /dev/null +++ b/plans/260421-2042-level-redesign-solvability/phase-02-level-solver.md @@ -0,0 +1,79 @@ +# Phase 2: Level Solver (BFS) + +## Context +- Brainstorm: `plans/reports/brainstorm-260421-2042-level-redesign-solvability.md` §4 +- Existing engine: `src/lib/game/grid-system.js`, `guards.js`, `turn-manager.js`, `player.js` + +## Overview +- **Priority:** CRITICAL — blocks Phase 3 (tests) and all redesigns. +- **Status:** pending +- Pure-JS BFS solver that replays the real turn simulation to prove reachability. + +## Key Insights +- **Must import guard AI from `guards.js`** — do NOT reimplement, or solver disagrees with runtime. +- Chaser is stateful → include positions in state key. +- Mirror + rotating beams are pure function of rotating direction + mirror positions → not part of state key (recomputed each turn). +- Cycle length = `LCM(4 [rotating], 2 [blinking], patrol_path_lengths...)`. Typical 4–24. +- L12 expanding-wave mechanic lives in `princess-mechanic.js` — solver must hook in `waveRadius` as state dim. + +## Requirements +- Function `solveLevel(levelDef, { maxStates = 10_000_000 } = {})` → `{ solvable: boolean, path?: string[], states_explored: number }`. +- `path` is array of actions: `'up' | 'down' | 'left' | 'right' | 'wait'`. +- BFS guarantees shortest path. +- Hard cap on state count; returns `solvable: false, reason: 'budget_exhausted'` if exceeded. +- Returns `solvable: false, reason: 'no_path'` if BFS completes without reaching goal. + +## Architecture + +State key: +``` +key = `${pRow},${pCow}|${chaserPositions.map(c => c.row+','+c.col).join(';')}|${turn % cycle}|${waveRadius ?? 0}` +``` + +Per step: +1. Apply action (move or wait); validate against walls + bounds. +2. Run `turnManager.nextTurn()` equivalent on a cloned grid + guards. +3. If player cell is lit after guard update → prune. +4. If player at goal → return path. +5. Enqueue new state if unseen. + +## Related Files +- **Create:** `src/lib/game/level-solver.js` (main BFS + state cloning helpers) +- **Create:** `src/lib/game/level-solver.test.js` (unit tests for solver itself) +- **Read for context:** `src/lib/game/guards.js`, `turn-manager.js`, `grid-system.js`, `princess-mechanic.js` + +## Implementation Steps +1. Extract or reuse guard instantiation from `level-manager.js` so solver can build guards from a level def. +2. Write `cloneState(grid, guards, player)` — deep copies cells, guard positions, player. +3. Write `stateKey(player, guards, turn, cycle, waveRadius)`. +4. Write `computeCycle(level)` → LCM of per-guard periods. +5. Implement BFS loop with visited Set and state budget. +6. Hook expanding-wave: include `waveRadius` in state when level has `isFinalLevel` + princess mechanic. +7. Return `{ solvable, path, states_explored, par }`. + +## Todo List +- [ ] Create `level-solver.js` scaffolding + exports +- [ ] Implement state cloning helpers +- [ ] Implement `computeCycle` +- [ ] Implement BFS core loop with state budget +- [ ] Wire chaser positions into state key +- [ ] Wire princess-mechanic `waveRadius` into state key +- [ ] Unit tests: toy 3x3 level (trivial solve), 4x4 blocked level (no path), cycle computation +- [ ] `npm test` passes +- [ ] Solver finishes L1 in <5s; L11 in <60s on dev machine + +## Success Criteria +- Solver correctly solves L1 (current) returning a path ≤ par. +- Solver returns `solvable: false` for current L2 (matches known bug). +- Solver returns `solvable: false` for current L12 (preserves invariant). +- Unit tests pass. + +## Risk Assessment +- **Risk:** State explosion on 12x12 + 2 chasers. **Mitigation:** profile; if >10M states, tighten cycle (shorter patrol paths) or add heuristic dedup (e.g., ignore chaser when >N cells away). +- **Risk:** Chaser simulation non-deterministic (random tiebreak in pathfinding). **Mitigation:** ensure `guards.js` chaser AI is deterministic; if not, make it so (sorted tiebreak). + +## Security Considerations +N/A — pure compute in test harness. + +## Next Steps +Phase 3 writes the Vitest suite using this solver. diff --git a/plans/260421-2042-level-redesign-solvability/phase-03-solvability-tests.md b/plans/260421-2042-level-redesign-solvability/phase-03-solvability-tests.md new file mode 100644 index 0000000..bd6eed0 --- /dev/null +++ b/plans/260421-2042-level-redesign-solvability/phase-03-solvability-tests.md @@ -0,0 +1,53 @@ +# Phase 3: Solvability Test Suite + CI Wiring + +## Context +- Brainstorm §4.2 +- Depends on: Phase 2 (solver) + +## Overview +- **Priority:** HIGH — gate for all redesign phases. +- **Status:** pending +- Vitest suite that asserts invariants on every PR touching `levels.js`. + +## Requirements +- For L1, L3–L11: assert `solveLevel(level).solvable === true`. +- For L2: known fail (baseline); test marked `.skip` with TODO to unskip after Phase 4. +- For L12: assert `solveLevel(level).solvable === false` (unsolvable invariant). +- Each passing level also asserts `par = path.length + 2` (or logs mismatch for manual par tuning). + +## Architecture +- One describe block per level; `test.each(LEVELS)` pattern. +- Long-running: set Vitest timeout to 120s per test. +- Separate suite file so dev can skip fast unit tests vs full solvability run. + +## Related Files +- **Create:** `src/lib/levels/levels.solvability.test.js` +- **Modify:** `package.json` — add script `"test:solvability": "vitest run src/lib/levels/levels.solvability.test.js"` +- **Modify:** `.github/workflows/*.yml` if CI exists (else note for later) + +## Implementation Steps +1. Create test file, import `LEVELS` and `solveLevel`. +2. Loop levels, assert expected solvability per hard rules. +3. Add par validation (warn only, don't fail yet — par tuning happens in redesign phases). +4. Add `test.each` table for grid size validation (L1-2 = 8x8, L3-4 = 9x9, etc.) — initially all fail; phase-by-phase unskips. +5. Add npm script. +6. If `.github/workflows` exists, add step: `npm run test:solvability`. + +## Todo List +- [ ] Write solvability test file +- [ ] Skip L2 initially with TODO +- [ ] Add par-delta warning (console.warn, not fail) +- [ ] Add grid-size table (initially all skipped; unskip per phase) +- [ ] Add npm script +- [ ] CI integration (if CI present) +- [ ] Confirm local: L1/3-11/12 assertions pass + +## Success Criteria +- `npm run test:solvability` passes with L2 skipped and L12 asserted-unsolvable. +- CI (if present) runs solvability on PRs. + +## Risk Assessment +- **Risk:** Solver too slow for CI. **Mitigation:** run solvability only on PRs that touch `levels.js` or `level-solver.js` (path-filter in CI). + +## Next Steps +Phase 4 redesigns L1-L2. After L2 passes, unskip its assertion here. diff --git a/plans/260421-2042-level-redesign-solvability/phase-04-redesign-act1-l1-l2.md b/plans/260421-2042-level-redesign-solvability/phase-04-redesign-act1-l1-l2.md new file mode 100644 index 0000000..597daa9 --- /dev/null +++ b/plans/260421-2042-level-redesign-solvability/phase-04-redesign-act1-l1-l2.md @@ -0,0 +1,46 @@ +# Phase 4: Act 1 Redesign (L1–L2, 8x8) + +## Context +- Brainstorm §3.1, §6 (migration) +- Depends on: Phase 3 (tests) + +## Overview +- **Priority:** HIGH (fixes L2 unsolvability bug). +- **Status:** pending +- Act 1 teaches movement + static guards. + +## Requirements +- Both levels: 8x8 grid, preserved `id`/`name`/`storyKey`. +- **L1 "Garden Path":** 0 guards (gentle intro, pure movement/walls); par ~12. +- **L2 "The Watchtower":** 3 static guards, ≥2 viable paths; par ~16. +- L2 must be SOLVABLE this time. + +## Implementation Steps +1. Sketch L1 8x8 grid: start (0,0), goal (7,7), ~10 walls forming winding but connected path. +2. Run solver on L1 → assert `solvable`; set par = path.length + 2. +3. Sketch L2 8x8 grid: 3 static guards placed so ≥2 distinct paths exist; walls widen the arena. +4. Run solver on L2 → assert solvable; set par = path.length + 2. +5. Update `src/lib/levels/levels.js` — replace L1, L2 entries in place. +6. Unskip L2 assertion in `levels.solvability.test.js`. +7. Run `npm run test:solvability` → both pass. +8. Manual playthrough via `npm run dev`. + +## Related Files +- **Modify:** `src/lib/levels/levels.js` (L1, L2 entries only) +- **Modify:** `src/lib/levels/levels.solvability.test.js` (unskip L2, enable L1-2 grid-size check) + +## Todo List +- [ ] Draft L1 layout, verify with solver +- [ ] Draft L2 layout with 3 guards + 2 paths, verify with solver +- [ ] Replace entries in `levels.js` +- [ ] Unskip L2 test +- [ ] Solvability suite green +- [ ] Manual playthrough L1 + L2 via `npm run dev` + +## Success Criteria +- L1 and L2 solvable per solver. +- L2 has ≥2 distinct solutions (verify by running solver with different seeds or via manual BFS count). +- Par values match `solver_optimal + 2`. + +## Risks +- **Overlapping wall+light** (original L2 bug). **Mitigation:** lint via assertion in solver — no wall cell should appear in any guard's `litCells` array. diff --git a/plans/260421-2042-level-redesign-solvability/phase-05-redesign-act2-l3-l4.md b/plans/260421-2042-level-redesign-solvability/phase-05-redesign-act2-l3-l4.md new file mode 100644 index 0000000..a132eef --- /dev/null +++ b/plans/260421-2042-level-redesign-solvability/phase-05-redesign-act2-l3-l4.md @@ -0,0 +1,41 @@ +# Phase 5: Act 2 Redesign (L3–L4, 9x9) + +## Context +- Brainstorm §3.1 +- Depends on: Phase 4 (Act 1 done; solver + tests trusted) + +## Overview +- **Priority:** MEDIUM +- **Status:** pending +- Act 2 introduces **rotating** + **blinking** (blinking promoted from old Act 3). + +## Requirements +- Both levels: 9x9 grid, preserved metadata. +- **L3 "Vegetable Patrol":** 4 guards total — mix of static + rotating (≥1 rotating); par ~18. +- **L4 "The Searchlight":** 5 guards — static + rotating + 1 blinking; timing window required; par ~22. + +## Implementation Steps +1. Draft L3 9x9 with 4 guards; verify solver solvable + ≥2 paths. +2. Draft L4 9x9 with 5 guards including blinking (introduce teaching level); verify solvable; ensure `wait` action is useful (solver path includes at least one `wait`). +3. Replace L3, L4 in `levels.js`. +4. Enable L3-4 grid-size assertion in test. +5. `npm run test:solvability` green. +6. Manual playthrough. + +## Related Files +- **Modify:** `src/lib/levels/levels.js` (L3, L4) +- **Modify:** `src/lib/levels/levels.solvability.test.js` + +## Todo List +- [ ] L3 drafted + solver-verified +- [ ] L4 drafted + solver-verified (blinking intro) +- [ ] Entries replaced +- [ ] Tests green +- [ ] Manual playthrough + +## Success Criteria +- Solver proves both solvable with pars reasonable. +- L4 solution requires a `wait` action (teaches mechanic). + +## Risks +- **Rotating guard cycle + blinking cycle interaction** may create tight windows. **Mitigation:** solver par check; if par >30, simplify. diff --git a/plans/260421-2042-level-redesign-solvability/phase-06-redesign-act3-l5-l6.md b/plans/260421-2042-level-redesign-solvability/phase-06-redesign-act3-l5-l6.md new file mode 100644 index 0000000..5b95d13 --- /dev/null +++ b/plans/260421-2042-level-redesign-solvability/phase-06-redesign-act3-l5-l6.md @@ -0,0 +1,41 @@ +# Phase 6: Act 3 Redesign (L5–L6, 10x10) + +## Context +- Brainstorm §3.1 +- Depends on: Phase 1 (viewport for 10x10), Phase 5 + +## Overview +- **Priority:** MEDIUM +- **Status:** pending +- Act 3 introduces **patrolling** (promoted from old Act 4). + +## Requirements +- Both levels: 10x10 grid, preserved metadata. +- **L5 "Fortress Gate":** 6 guards — static + rotating + blinking + 1 patrolling (intro); par ~22. +- **L6 "The Flickering Corridor":** 7 guards — mix, ≥2 patrollers OR 1 patroller with longer path; par ~28. + +## Implementation Steps +1. Draft L5 10x10 with 6 guards (1 patrolling); solver-verify. +2. Draft L6 10x10 with 7 guards; solver-verify. +3. Replace in `levels.js`. +4. Enable 10x10 grid-size checks. +5. Test green. +6. Manual playthrough — verify viewport scrolls correctly (phase-1 integration smoke). + +## Related Files +- **Modify:** `src/lib/levels/levels.js` (L5, L6) +- **Modify:** `src/lib/levels/levels.solvability.test.js` + +## Todo List +- [ ] L5 drafted + verified (patrolling intro) +- [ ] L6 drafted + verified +- [ ] Entries replaced +- [ ] Tests green +- [ ] Manual playthrough confirms viewport scroll works + +## Success Criteria +- Both solvable; ≥2 paths per level. +- Patroller path length ≥4 cells (meaningful patrol). + +## Risks +- **Patrol path length inflates cycle** → solver state blowup. **Mitigation:** cap patrol path ≤6 cells in Act 3. diff --git a/plans/260421-2042-level-redesign-solvability/phase-07-redesign-act4-l7-l8.md b/plans/260421-2042-level-redesign-solvability/phase-07-redesign-act4-l7-l8.md new file mode 100644 index 0000000..5294e7d --- /dev/null +++ b/plans/260421-2042-level-redesign-solvability/phase-07-redesign-act4-l7-l8.md @@ -0,0 +1,41 @@ +# Phase 7: Act 4 Redesign (L7–L8, 11x11) + +## Context +- Brainstorm §3.1 +- Depends on: Phase 1 (viewport), Phase 6 + +## Overview +- **Priority:** MEDIUM +- **Status:** pending +- Act 4 introduces **mirror** guards (promoted from old Act 5). + +## Requirements +- Both levels: 11x11 grid, preserved metadata. +- **L7 "The Underground Passage":** 7 guards — mix + 1 mirror (intro); par ~26. +- **L8 "The Gauntlet":** 8 guards — ≥2 mirrors + ≥2 patrolling + rotating; par ~32. + +## Implementation Steps +1. Draft L7 11x11 with mirror intro; solver-verify (requires solver to correctly model mirror deflection). +2. Draft L8 11x11 with complex mirror setup; solver-verify. +3. Replace in `levels.js`. +4. Tests green. +5. Manual playthrough. + +## Related Files +- **Modify:** `src/lib/levels/levels.js` (L7, L8) +- **Modify:** `src/lib/levels/levels.solvability.test.js` + +## Todo List +- [ ] L7 drafted (mirror intro) + verified +- [ ] L8 drafted + verified +- [ ] Entries replaced +- [ ] Tests green +- [ ] Manual playthrough + +## Success Criteria +- Both solvable; mirror deflection creates a hazard that player must account for. +- L8 has ≥3 guards of same dynamic type (patrol/rotating) — "gauntlet" flavor. + +## Risks +- **Solver mirror logic mismatch with runtime.** **Mitigation:** Phase 2 solver imports guards.js — single source of truth. +- **11x11 + mirrors + many guards → state blowup.** **Mitigation:** profile; may need state cap bump or heuristic pruning. diff --git a/plans/260421-2042-level-redesign-solvability/phase-08-redesign-act5-l9-l10.md b/plans/260421-2042-level-redesign-solvability/phase-08-redesign-act5-l9-l10.md new file mode 100644 index 0000000..081d596 --- /dev/null +++ b/plans/260421-2042-level-redesign-solvability/phase-08-redesign-act5-l9-l10.md @@ -0,0 +1,40 @@ +# Phase 8: Act 5 Redesign (L9–L10, 12x12) + +## Context +- Brainstorm §3.1 +- Depends on: Phase 1 (viewport), Phase 7 + +## Overview +- **Priority:** MEDIUM +- **Status:** pending +- Act 5 introduces **chaser** guards. + +## Requirements +- Both levels: 12x12 grid, preserved metadata. +- **L9 "The Decoy Path":** 8 guards — mix + 1 chaser (intro); par ~34. +- **L10 "Hall of Mirrors":** 9 guards — chaser + multiple mirrors + rotating; par ~38. + +## Implementation Steps +1. Draft L9 12x12 with chaser intro; solver-verify (longest run; may take minutes). +2. Draft L10 12x12 with mirror-heavy layout + chaser; solver-verify. +3. Replace in `levels.js`. +4. Tests green. +5. Manual playthrough — verify viewport + camera follow smooth at 12x12. + +## Related Files +- **Modify:** `src/lib/levels/levels.js` (L9, L10) +- **Modify:** `src/lib/levels/levels.solvability.test.js` + +## Todo List +- [ ] L9 drafted (chaser intro) + verified +- [ ] L10 drafted + verified +- [ ] Entries replaced +- [ ] Tests green +- [ ] Manual playthrough at 12x12 + +## Success Criteria +- Both solvable; chaser creates genuine threat (solver proves shortest path avoids chaser). +- Solver completes within 10min per level (or raise state cap). + +## Risks +- **Solver budget exhausted on 12x12 + chaser + long cycle.** **Mitigation:** shorten patrol paths; cap chaser detection radius at 3. diff --git a/plans/260421-2042-level-redesign-solvability/phase-09-redesign-l11.md b/plans/260421-2042-level-redesign-solvability/phase-09-redesign-l11.md new file mode 100644 index 0000000..024b123 --- /dev/null +++ b/plans/260421-2042-level-redesign-solvability/phase-09-redesign-l11.md @@ -0,0 +1,42 @@ +# Phase 9: L11 Redesign — Throne Room (12x12) + +## Context +- Brainstorm §3.1 +- Depends on: Phase 1 (viewport), Phase 8 + +## Overview +- **Priority:** MEDIUM +- **Status:** pending +- L11 is the climax before the impossible finale. All guard types combined. + +## Requirements +- 12x12 grid, preserved metadata (`id: 11`, `name: "The Throne Room"`, `storyKey: level11Story`). +- 9 guards: ≥1 of each type (static, rotating, blinking, patrolling, mirror, chaser). +- Must be solvable but hard (par ~42). +- Multiple paths; each path tests a different mix of skills learned. + +## Implementation Steps +1. Draft 12x12 with all 6 guard types represented; solver-verify. +2. Ensure par > L10 par (difficulty climb). +3. Replace L11 in `levels.js`. +4. Tests green. +5. Manual playthrough — note difficulty feel; adjust if solver par drastically exceeds 42. + +## Related Files +- **Modify:** `src/lib/levels/levels.js` (L11) +- **Modify:** `src/lib/levels/levels.solvability.test.js` + +## Todo List +- [ ] L11 drafted with all 6 guard types +- [ ] Solver-verified +- [ ] Par sensible (≤~50) +- [ ] Tests green +- [ ] Manual playthrough + +## Success Criteria +- Solvable. +- Uses every guard type at least once. +- Feels like a climactic combo of all mechanics. + +## Risks +- **Mirror + chaser + rotating interaction too chaotic.** **Mitigation:** if solver par >50 or feels unfair, reduce to 8 guards (drop one redundant type). diff --git a/plans/260421-2042-level-redesign-solvability/phase-10-redesign-l12-easter-egg.md b/plans/260421-2042-level-redesign-solvability/phase-10-redesign-l12-easter-egg.md new file mode 100644 index 0000000..dc05108 --- /dev/null +++ b/plans/260421-2042-level-redesign-solvability/phase-10-redesign-l12-easter-egg.md @@ -0,0 +1,66 @@ +# Phase 10: L12 Redesign — Princess Chamber + Console Easter Egg (13x13) + +## Context +- Brainstorm §6, §10 +- Memory: `project_level12_unsolvable.md` +- Depends on: Phase 9 + +## Overview +- **Priority:** HIGH (preserves core narrative invariant). +- **Status:** pending +- L12 MUST remain unsolvable by normal play. Only console-based teleport can trigger the win screen. + +## Hard Invariant +Solver MUST return `solvable: false` for L12 after redesign. Test asserts this; CI blocks any change that makes L12 solvable. + +## Requirements +- 13x13 grid (biggest), preserved metadata (`id: 12`, `isFinalLevel: true`). +- 10 guards + expanding-wave mechanic from `princess-mechanic.js`. +- Console easter-egg hook exposed on `window` so dev-tools users can teleport to goal. +- Normal win flow triggers regardless of how goal was reached. + +## Architecture +- **Guards:** all 6 types represented, packed aggressively around goal. +- **Expanding wave:** intact from current impl — lights ring around goal after player within distance 4. +- **Console hook:** expose `window.__nntvDev = { teleport(row, col) { ... }, reveal() { ... } }`. + - `teleport` mutates the live game state via existing reactive store. + - Documented in code comments only; NOT in README, NOT in guide UI. +- **Win flow unchanged:** existing `isAtGoal()` check + `LevelCompletePopup.svelte` fire normally when player position = goal. + +## Implementation Steps +1. Draft 13x13 layout with 10 guards + wave mechanic. +2. Solver-verify `solvable: false` within 10M state budget. +3. Replace L12 in `levels.js`. +4. Add `window.__nntvDev` hook in `src/main.js` (or `Game.svelte`) — gated behind Svelte store reference. +5. Manual test: + - Normal play → impossible confirmed. + - `window.__nntvDev.teleport(goal.row, goal.col)` in console → win flow triggers. +6. Add test in `levels.solvability.test.js`: assert L12 unsolvable. +7. Manual playthrough. + +## Related Files +- **Modify:** `src/lib/levels/levels.js` (L12) +- **Modify:** `src/main.js` or `src/scenes/Game.svelte` (expose dev hook) +- **Modify:** `src/lib/levels/levels.solvability.test.js` +- **Read:** `src/lib/game/princess-mechanic.js` + +## Todo List +- [ ] L12 drafted (13x13, 10 guards + wave) +- [ ] Solver confirms unsolvable +- [ ] Dev hook (`window.__nntvDev.teleport`) wired +- [ ] Manual: normal play impossible, console win works +- [ ] Solvability test asserts `!solvable` +- [ ] Code comment documents easter egg + +## Success Criteria +- Solver asserts `solvable: false` — test green. +- Pressing every direction + wait indefinitely never reaches goal (manual spot-check). +- `window.__nntvDev.teleport(12, 12)` from console → LevelCompletePopup fires. +- No in-game UI/readme hint about the easter egg. + +## Risks +- **Dev hook security.** **Mitigation:** non-sensitive (just game state); no auth/data exposure. Fine for a client-only game. +- **Wave mechanic solvability.** **Mitigation:** solver includes `waveRadius` in state key (Phase 2). + +## Security Considerations +- `window.__nntvDev` is intentionally world-accessible in-browser. Acceptable — no server, no user data. diff --git a/plans/260421-2042-level-redesign-solvability/phase-11-docs-and-smoke-test.md b/plans/260421-2042-level-redesign-solvability/phase-11-docs-and-smoke-test.md new file mode 100644 index 0000000..b8fb901 --- /dev/null +++ b/plans/260421-2042-level-redesign-solvability/phase-11-docs-and-smoke-test.md @@ -0,0 +1,53 @@ +# Phase 11: Docs + Full Smoke Test + +## Context +- Brainstorm §11 +- Depends on: all prior phases + +## Overview +- **Priority:** LOW–MEDIUM (final polish, CI gate). +- **Status:** pending +- Update `docs/`, verify full playthrough, tune pars if needed. + +## Requirements +- Update `docs/system-architecture.md` — add solver + viewport sections. +- Update `docs/code-standards.md` — note "all level edits must run solvability suite". +- README unchanged re: easter egg (keep hidden). +- Full manual playthrough L1→L11; verify L12 is impossible but console-teleport wins. + +## Implementation Steps +1. Run full test suite: `npm test && npm run test:solvability`. +2. Check `docs/` files exist — if not, create. +3. Append solver architecture section to `docs/system-architecture.md`. +4. Append level-authoring rule to `docs/code-standards.md`: "never commit `levels.js` without running `npm run test:solvability`". +5. Add codebase-summary.md entry for `level-solver.js`. +6. Manual playthrough L1 → L11 (screenshot key moments for regression visibility). +7. Par tuning pass: if solver_par + 2 feels too loose/tight per actual play, adjust. +8. `npm run build` produces clean dist. + +## Related Files +- **Modify:** `docs/system-architecture.md` +- **Modify:** `docs/code-standards.md` +- **Modify:** `docs/codebase-summary.md` (if exists) +- **Modify (maybe):** `src/lib/levels/levels.js` (par tuning) + +## Todo List +- [ ] Full test suite green +- [ ] docs/system-architecture.md updated +- [ ] docs/code-standards.md updated +- [ ] Manual playthrough L1→L11 complete, no blockers +- [ ] L12 console win confirmed +- [ ] Par tuning (if any) complete +- [ ] `npm run build` clean + +## Success Criteria +- All 11 solvable levels playable end-to-end without test failures. +- L12 impossible by normal play; console win works. +- Docs reflect new solver + viewport architecture. + +## Risks +- **Par values feel off in real play.** **Mitigation:** dedicated tuning pass here; adjust before merge. + +## Next Steps +- Archive plan via `/ck:plan archive`. +- Merge to main, tag release if versioned. diff --git a/plans/260421-2042-level-redesign-solvability/plan.md b/plans/260421-2042-level-redesign-solvability/plan.md new file mode 100644 index 0000000..97fbd89 --- /dev/null +++ b/plans/260421-2042-level-redesign-solvability/plan.md @@ -0,0 +1,61 @@ +--- +name: Level Redesign & Solvability +slug: level-redesign-solvability +created: 2026-04-21 +status: pending +blockedBy: [] +blocks: [] +source: plans/reports/brainstorm-260421-2042-level-redesign-solvability.md +--- + +# Level Redesign & Solvability — Implementation Plan + +Redesign all 12 levels of Night Ninja: Twilight Voyage. Fix L2 unsolvability. Widen grids (8x8→13x13). Add more guards per act. Introduce automated BFS solver + CI-enforced solvability tests. Preserve L12 as unsolvable (console easter-egg win only). + +## Hard Rules + +1. Exactly 12 levels, 6 acts. No adds/removes/renumbers. +2. L12 must remain unsolvable by normal play. +3. Desktop-first. Large grids via scrollable viewport that follows player. +4. Preserve `id`, `name`, `storyKey`, `isFinalLevel` per level (no progress migration). + +## Progression Target + +| Act | Levels | Grid | Guard types (cumulative) | Guard count | +|-----|--------|------|--------------------------|-------------| +| 1 | 1–2 | 8x8 | static | 0 / 3 | +| 2 | 3–4 | 9x9 | + rotating + blinking | 4 / 5 | +| 3 | 5–6 | 10x10 | + patrolling | 6 / 7 | +| 4 | 7–8 | 11x11 | + mirror | 7 / 8 | +| 5 | 9–10 | 12x12 | + chaser | 8 / 9 | +| 6 | 11 | 12x12 | all | 9 | +| 6 | 12 | 13x13 | all + expanding wave | 10 (unsolvable) | + +## Phases + +| # | File | Status | Description | +|---|------|--------|-------------| +| 1 | [phase-01-scrollable-viewport.md](phase-01-scrollable-viewport.md) | pending | Camera-follow viewport for grids >9x9 | +| 2 | [phase-02-level-solver.md](phase-02-level-solver.md) | pending | BFS solver in `src/lib/game/level-solver.js` | +| 3 | [phase-03-solvability-tests.md](phase-03-solvability-tests.md) | pending | Vitest suite + CI wiring; assert L2 fails as baseline | +| 4 | [phase-04-redesign-act1-l1-l2.md](phase-04-redesign-act1-l1-l2.md) | pending | Act 1 (L1–L2, 8x8, static) | +| 5 | [phase-05-redesign-act2-l3-l4.md](phase-05-redesign-act2-l3-l4.md) | pending | Act 2 (L3–L4, 9x9, + rotating + blinking) | +| 6 | [phase-06-redesign-act3-l5-l6.md](phase-06-redesign-act3-l5-l6.md) | pending | Act 3 (L5–L6, 10x10, + patrolling) | +| 7 | [phase-07-redesign-act4-l7-l8.md](phase-07-redesign-act4-l7-l8.md) | pending | Act 4 (L7–L8, 11x11, + mirror) | +| 8 | [phase-08-redesign-act5-l9-l10.md](phase-08-redesign-act5-l9-l10.md) | pending | Act 5 (L9–L10, 12x12, + chaser) | +| 9 | [phase-09-redesign-l11.md](phase-09-redesign-l11.md) | pending | L11 Throne Room (12x12, all guards) | +| 10 | [phase-10-redesign-l12-easter-egg.md](phase-10-redesign-l12-easter-egg.md) | pending | L12 Princess Chamber (13x13, unsolvable) + console win hook | +| 11 | [phase-11-docs-and-smoke-test.md](phase-11-docs-and-smoke-test.md) | pending | Update docs/ + manual playthrough QA | + +## Dependencies + +- Phase 1 (viewport) blocks Phases 6–10 (grids ≥10x10 unplayable without it). +- Phase 2 (solver) blocks Phase 3 (tests need solver). +- Phase 3 (tests) blocks Phases 4–10 (each redesign validated by tests). +- Phases 4–9 independent of each other but best done in order (progression curve). +- Phase 10 depends on Phase 9 (L11 finalized before L12). +- Phase 11 depends on all previous. + +## Source + +Brainstorm summary: `plans/reports/brainstorm-260421-2042-level-redesign-solvability.md` diff --git a/plans/reports/brainstorm-260421-2042-level-redesign-solvability.md b/plans/reports/brainstorm-260421-2042-level-redesign-solvability.md new file mode 100644 index 0000000..fc0c385 --- /dev/null +++ b/plans/reports/brainstorm-260421-2042-level-redesign-solvability.md @@ -0,0 +1,145 @@ +# Level Redesign & Solvability — Brainstorm Summary + +**Date:** 2026-04-21 +**Scope:** Redesign all 12 levels of Night Ninja: Twilight Voyage. Fix L2 unsolvability. Expand grid sizes. Add more guards. Preserve L12 as intentionally unsolvable. +**Status:** Design approved — ready for implementation plan. + +--- + +## 1. Problem Statement + +- **L2 unsolvable** — BFS from `(0,0)` cannot reach `(5,5)`. Right-side component `{(3,5),(4,4),(4,5),(5,3),(5,4),(5,5)}` is disconnected by permanent light walls from static guards at `(2,4)` and `(4,2)`. +- **No solvability gate** — levels authored manually; no automated proof. Risk of regressions on future edits. +- **Levels feel narrow** — current grids 6x6–9x9 have single corridors; user wants wider, more complex puzzles. +- **Progression imbalanced** — mirror/chaser/patrolling introduced too late (Acts 4–5); early acts feel sparse. + +## 2. Hard Rules (invariants) + +1. **Exactly 12 levels, 6 acts.** No adds, removes, renumbers. +2. **L12 must remain unsolvable by normal play.** Only console easter-egg win (user teleports player to goal via dev tools). Win condition / congratulations screen still fires when goal reached by any means. +3. **Desktop-first.** No mobile responsive work. Large grids handled via scrollable viewport that follows the player. +4. **Preserve** `id`, `name`, `storyKey`, `isFinalLevel` per level — zero progress-persistence migration. + +## 3. Solution — Redesign Philosophy + +### 3.1 Progression (rebalanced acts) + +| Act | Levels | Grid | Guard types (cumulative) | Guard count | +|-----|--------|------|--------------------------|-------------| +| 1 | 1–2 | 8x8 | static | 0 / 3 | +| 2 | 3–4 | 9x9 | + rotating + **blinking** (promoted from Act 3) | 4 / 5 | +| 3 | 5–6 | 10x10 | + **patrolling** (promoted from Act 4) | 6 / 7 | +| 4 | 7–8 | 11x11 | + **mirror** (promoted from Act 5) | 7 / 8 | +| 5 | 9–10 | 12x12 | + chaser | 8 / 9 | +| 6 | 11 | 12x12 | all types combined | 9 | +| 6 | 12 | 13x13 | all types + expanding-wave mechanic | 10 (unsolvable) | + +### 3.2 Design Rules Per Level + +- **Connectivity gate:** at least one reachable path from start→goal in solver (except L12). +- **Widen, don't narrow:** add 2–3 alternative routes so guards force *decisions*, not single chokepoints. +- **More guards ≠ more walls.** Keep wall density ~20–25% of grid area; increase guard count per act tier. +- **Par = solver_optimal + 2** (small slack for undo experimentation). +- **No redundant wall-on-light** (L2 had wall `(3,4)` over guard light `(3,4)` — sloppy overlap, remove). +- **Guard beam pruning:** rotating beams stop at walls — use walls strategically to create safe windows. + +## 4. Solvability Solver (automated validation) + +### 4.1 State & Search + +**State key:** `(player_row, player_col, chaser_positions_tuple, turn_mod_cycle)` +- `cycle = LCM(4 [rotating], 2 [blinking], patrol_path_length_i...)`. Typical cycles 4–24. +- **Chaser** is stateful — include each chaser's position in state key. State space stays tractable (<10M for 12x12 + 2 chasers + cycle 24). +- **Mirror beams** are pure function of rotating-guard directions + mirror positions each turn — not part of state. + +**Transitions:** 5 actions per turn — `up / down / left / right / wait`. +**Pruning:** after applying action + running guard update for next turn, if player cell `isLight` → dead branch. +**Algorithm:** BFS (guarantees shortest). Bail out at state-count cap (~10M) with "unsolvable within budget" → manual check. + +### 4.2 Location & Modules + +- `src/lib/game/level-solver.js` — pure BFS solver, reuses existing `grid-system.js`, `guards.js`, `turn-manager.js`. +- `src/lib/levels/levels.solvability.test.js` — Vitest suite asserting: + - For L1–L11: solver returns a valid path. + - For L12: solver returns no path within budget (unsolvable invariant). + - Par values match `solver_optimal + 2`. +- CI hook: run solvability suite on every PR that touches `levels.js`. + +### 4.3 Known Limitations + +- Chaser path depends on guard AI implementation — solver must use *identical* chaser logic as runtime (import from `guards.js`, don't reimplement). +- Cycle computation must account for patrol path length (may be odd / prime). +- L12's expanding-wave mechanic needs the solver to model princess-mechanic.js state — include `waveRadius` in state key. + +## 5. Scrollable Viewport (prereq for large grids) + +- Current rendering assumes full grid fits on screen. +- Need: camera-follow the player so 12x12 / 13x13 grids are playable on 1080p screens. +- Implementation touch points: + - `src/components/GameBoard.svelte` (or equivalent) — CSS `overflow: auto` + `scroll-behavior: smooth` + programmatic `scrollIntoView` on player position change. + - Viewport: show ~9x9 cells visible; pan on player move. +- Out-of-scope for this redesign brainstorm but **blocker** for Acts 4–6 playability. + +## 6. L12 Easter Egg — Console Win + +- Expose a minimal dev hook on `window` (e.g., `window.__nntvDev = { teleportToGoal() { ... } }`) OR rely on existing game-state reactivity so user can directly mutate player position via devtools. +- Reaching goal by any means → normal win flow (congratulations, progress saved). +- Document the easter egg ONLY in code comments, not in-game UI or README. + +## 7. Migration & Rollout + +- **In-place rewrite** of `src/lib/levels/levels.js`. No new file. +- Preserve each level's `id`, `name`, `storyKey`, `isFinalLevel`, array position. No changes to `src/lib/localization/*` story keys needed. +- Update `parMoves` from solver output for all levels. +- Tests first: land solver + solvability suite **before** level content changes, so each new level design is validated as it's written. + +## 8. Evaluated Alternatives + +| Approach | Pros | Cons | Verdict | +|----------|------|------|---------| +| Fix L2 only, leave others | Minimal churn | Doesn't address "narrow levels" complaint; no regression protection | **Rejected** | +| Keep grids, add guards only | No viewport work | Crowded; still narrow corridors; low decision value | **Rejected** | +| Aggressive grid expansion + more guards + solver | Solves all issues; prevents future regressions | Requires viewport work; solver effort | **Chosen** | +| Expand to 15–18 levels | Richer progression | Breaks progress persistence, story keys, count invariant | **Rejected** (violates hard rule) | + +## 9. Success Criteria + +- [ ] Vitest suite: 11/11 levels solvable, L12 unsolvable. Passes on CI. +- [ ] Every level has ≥2 distinct BFS-discovered paths OR clear single-path justification in code comment. +- [ ] All acts visually wider than before (row/col counts match Section 3.1). +- [ ] Scrollable viewport works on 1080p / 1440p screens for 12x12 grids. +- [ ] Win condition fires when L12 goal is reached via console manipulation. +- [ ] No changes to story keys, level IDs, or progress-persistence schema. + +## 10. Next Steps (Implementation Plan Phases) + +1. **Phase 1:** Implement scrollable viewport (prereq for >9x9 grids). +2. **Phase 2:** Build `level-solver.js` + unit tests on current L1, L3–L11 (should solve) and L12 (should fail). Accept that L2 will fail — that's the baseline bug. +3. **Phase 3:** Solvability test suite — wire into CI. +4. **Phase 4:** Redesign L1–L2 (Act 1, 8x8). Run solver, lock par values. +5. **Phase 5:** Redesign L3–L4 (Act 2, 9x9, introduce blinking earlier). +6. **Phase 6:** Redesign L5–L6 (Act 3, 10x10, introduce patrolling). +7. **Phase 7:** Redesign L7–L8 (Act 4, 11x11, introduce mirror). +8. **Phase 8:** Redesign L9–L10 (Act 5, 12x12, introduce chaser). +9. **Phase 9:** Redesign L11 (Act 6, 12x12, all guards combined). +10. **Phase 10:** Redesign L12 (Act 6, 13x13, preserve unsolvable invariant; expose console win hook). +11. **Phase 11:** Update `docs/` (code-standards, system-architecture) with solver + easter-egg notes. Smoke-test full playthrough. + +## 11. Risks + +| Risk | Mitigation | +|------|------------| +| Solver too slow on chaser+long-cycle levels | State cap 10M; if exceeded, reduce cycle via shorter patrol paths | +| Viewport work reveals rendering bugs | Isolate in Phase 1; land before level changes | +| Solver disagrees with runtime (chaser logic) | Import guard logic from `guards.js` — single source of truth | +| L12 accidentally solvable after redesign | Solver test asserts unsolvability; fails CI | +| Parsing console easter egg as "cheating" concern | Document in code comments only; feature is intentional | + +--- + +## Unresolved Questions + +1. **Console easter-egg API shape** — expose `window.__nntvDev.teleport()` explicitly, or rely on Svelte 5 reactive store mutation? Simplest path TBD in Phase 10. +2. **Viewport implementation owner** — is scrollable viewport a separate brainstorm/plan, or bundled into this redesign's Phase 1? +3. **Story text updates** — do any `storyKey` copy lines need rewriting to match new grid sizes/themes (e.g., "narrow corridor" lines on now-wider levels)? Decision deferred to QA after redesigns land. +4. **Chaser count in Act 5+** — cap at 1 per level, or allow 2 in L10/L11? Affects solver state-space budget.