PostCSS auto-loaded any path named in a sourceMappingURL comment as a
previous source map, so crafted CSS could disclose arbitrary .map files.
Transitive devDependency of Vite; overridden the same way esbuild already
is, which lifts every consumer regardless of the version Vite asked for.
Resolves to 8.5.23.
Level briefings promised mechanics the levels did not contain: a blinking
guard on five, a patroller on seven, two mirrors and two rotating beams on
eight, a chaser on nine. A briefing is a planning input in a puzzle game,
so a wrong one makes a fair level feel unfair.
- rewrite briefings for levels four, five, seven, eight and nine in both
locales, verified against the level definitions
- correct the design doc's level table and warm-tile behavior
- record that level twelve's unsolvability is intentional and CI-pinned
- document offline play, the unshipped-assets directory, and Node 24
- keep the audit and phase reports alongside the plan
There was no service worker, so a cold load with the network off failed at
the HTTP layer before any of this dependency-free code could run.
scope and start_url are relative because the site is served from a Pages
subpath, not a domain root. Icons are kept to files the precache glob does
not already sweep up: listing one twice under two revisions makes Workbox
reject its install handler, and the worker then activates with an empty
cache, which looks identical to success until the network is pulled.
Verified by serving the build from a /nntv/ subpath, killing the server,
and reloading: renders and starts a run with zero network requests.
Fifty-seven percent of the built output was reachable by nothing but a
manifest that is never fetched at runtime. The key art documented as the
menu background was never wired up; a 1.2KB placeholder was loaded instead.
- wire key art into the menu, ending screen and act four to six intros
- re-encode key art to webp: 1.63MB to 556KB at DSSIM around 0.012
- move 3.9MB of unwired voice lines and authoring sources out of the
bundle into unshipped-assets, still tracked rather than deleted
- add 192 and 512 icons, the sizes an install prompt requires
activeTrack was assigned before play() could reject, so the retry bound to
the first user gesture hit the same-track early return and menu music never
started on any browser that blocks autoplay. Also drops crossOrigin, which
is inert for same-origin audio but forces CORS-mode fetches through the
service worker and breaks file:// use.
Difficulty should come from the puzzle, not the interface. The stage was
hard-coded to 1024x768 with no media queries, stone throwing was bound to
the E key with no on-screen control, and a stuck player had no way forward
because progress advanced only on a win.
- scale the board down to 360px so the 13x13 grid fits a phone
- on-screen throw entry with 44px confirm and cancel targets
- escalating hints at three, five and seven attempts; mercy unlock at eight
- level-complete popup no longer renders the literal key name
- keyboard retry is immediate instead of seven tabs through hidden buttons
- delay the detection overlay so the death flash stays visible
- suspicion range shown before the guard reaches tier one
- dialog roles, focus trap and scene-transition focus management
- localized level names and turn counter, dead aria keys removed
Levels were authored against guards that did nothing, so the finale fell
to a twenty-move walk down two edges. Guard tax is now non-zero on every
level with guards, and search depth ramps from 50 states on the opener to
251612 on the throne room.
- fix the perimeter-walk exploit on levels four, eight, nine, ten, eleven
- unblock the mirrors on seven and ten, move guards off wall cells
- phase-align rotating, sniper and static cycles to arrival turns
- tighten par so three stars is earned rather than free
- restore preview on eleven while leaving undo disabled
Level twelve is untouched and remains unsolvable by design.
The suite asserted only that a level was solvable and within par. With no
lower bound, a level whose guards did nothing passed clean, which is how
eleven degenerate levels shipped green.
- ablation: solve with guards, then without, and require a minimum tax
- per-mechanic necessity: removing a level's named mechanic must change it
- authoring: no guard or patrol node on a wall, mirrors reachable by a beam,
sniper beams non-trivial, par not slack enough to make three stars free
- memoized baselines keep the run near 20s despite doubling the solves
Four defects left the stealth mechanics inert: warm tiles were cosmetic,
mirror beams never reflected, static guards died permanently after a few
turns, and suspicion guards reset their own tier every cycle. Deleting
every guard from every level changed the optimal path by zero moves.
- warm tiles are lethal and their decay window survives the turn cycle
- rotating and sniper beams reach mirrors and reflect
- static guards regrow, suspicion tiers persist
- a mobile guard's own cell is lethal, and cell-swap counts as detection
- turn preview simulates the player's move instead of a stationary player
- a rejected one-way move no longer opens the door it was refused
- line-of-sight extracted to one module shared by engine and solver
Every rule change lands in both the turn engine and the BFS solver so CI
cannot certify a level that a human cannot play.
Dependabot alert #16 (high, CVSS 8.1) flags esbuild < 0.28.1 for missing
SHA-256 integrity verification in the Deno download path. Even though the
npm/Vite path used by this project is not directly exploitable, bump
esbuild to the patched release via a pnpm-workspace.yaml override so the
alert closes.
esbuild 0.28.x dropped support for transforming destructuring to the
Vite 6 default browser target set (chrome87/firefox78/safari14), so
also pin build.target and esbuild.target to es2022 in vite.config.js.
Verification:
- pnpm build: success (173 modules, 1.71s)
- pnpm test: 180/180 pass
Replace procedural pixel art with pre-generated PNGs for player, princess,
guards (8 types + suspicion tiers), tiles, keys, stones, and scenes.
Add src/lib/generated-assets.js as the central path registry and update
all sprite/scene components to consume it.
All three reports are historical artifacts:
- v2 brainstorm: shipped (commit 149e07a, journal in docs/journals/)
- 260421 level-redesign brainstorm: fully superseded by v2
- 260420 full-project review: pre-v2; findings addressed by v2
Preserved in git history. Removing from working tree.
The plan and all phase reports are preserved in git history (commit
149e07a). Removing from working tree as the work is fully shipped.
Adds the v2 ship journal entry under docs/journals/.
Adds a reactive effect that fires playDoorUnlock when the count of
locked doors decreases — i.e. when player.moveTo opens a door with a
matching key. Mirrors the existing keysHeld delta pattern.
Closes the door-audio gap documented in plans/260425-1907-tight-12-design-uplift/.
Render new tile types (door/key/one-way/warm) and new guard sprites (sniper
triangle+beam, suspicion ring). Five new HUD components. Throw-targeting state
machine (E enter, arrows/click move, Enter/click confirm, Esc cancel). Per-level
affordance gates wired to Z/Y/V key handlers. GameHistory snapshots extended to
round-trip keysHeld + key/door cell state for correct undo.
Player.moveTo enforces wall/door/one-way validation with direction-of-approach
matching. keysHeld bitmask auto-collects on stepping on key cell. Capture/apply
includes inventory state.
Drop run-wide lives state. Detection routes to level-restart instead of
game-over. Repurpose GameOver scene for run-complete (L11) and bittersweet
(L12) flows. One-shot save migration wipes legacy lives field with welcome
modal.
New cell flags (door, key, one-way, warm). SniperGuard with line-of-sight beam
+ mirror reflection. SuspicionGuard with 3-tier meter. ThrowableSystem with
stone-throw distraction resolved before guard turn. Capture/apply round-trip
preserves all new state.
Static guards now emit a Manhattan aura that shrinks by 1 per turn until
harmless (clamped at -1 to keep solver state finite). Level data schema
change: 'static' guards now take 'initialRadius' instead of 'litCells'.
All 27 static-guard entries migrated in one pass.
L1 and L2 redesigned with real forced-zigzag walls — solver path is now
20 moves (vs Manhattan 14), so players must move AWAY from the goal to
reach it. L2 adds three wilting tomatoes parked on the zigzag path as
a gentle intro to the mechanic.
Tests: +3 new tests on StaticGuard wilting behavior; all 70 tests pass.
L12 widened to 13x13 with 10 guards including 2 chasers and a mirror.
Remains unsolvable by normal play (BFS-verified). window.__nntvDev.teleport
exposed as an intentional escape hatch — console-savvy users who invoke it
win the game. Hidden from README and in-game UI.
Act 5 redesign — chaser guard introduced. L9 has 8 guards with the
chaser ambushing the short route, forcing a perimeter detour. L10 is
the Hall of Mirrors with 9 guards (2 rotating + 3 mirrors + chaser).
Both solvable under 50 ms via BFS.
Act 4 redesign — mirror guard introduced. L7 has 7 guards including
one mirror that deflects a rotating beam. L8 has 8 guards with two
mirrors creating a crossfire pattern.
Act 3 redesign — patrolling guard introduced in L5 (6 guards total).
L6 widens with two patrollers, two blinkers, and a rotating guard
(7 guards). Both solvable via BFS solver.
Act 2 redesign — rotating guard (L3) and blinking guard (L4) introduced.
Both levels widened to 9x9. L4 solution requires a wait action to
synchronize with the blinking cycle, teaching the timing mechanic.
L2 right-side region was disconnected — player could not reach the goal
via any sequence of moves (verified via BFS). New L2 places three static
guards with two viable paths. L1 widens to 8x8 as a gentle Act 1 intro.
Preserves id/name/storyKey/isFinalLevel for progress persistence.
Asserts L1/5-8/10 solvable, L12 unsolvable (easter-egg invariant), and
locks the 12-level count. Known-broken levels (L2/3/4/9/11) tracked
explicitly and auto-unskip when their redesign phases flip them.
Pure BFS over (player, guard snapshots, princess state). Reuses guards.js
and princess-mechanic.js so solver verdicts match runtime behavior. State
budget cap prevents runaway search on impossible levels.
Fix cellSize at 50 and wrap board-container with overflow:auto + smooth
scrollTo effect centered on player. Unlocks grid sizes beyond 9x9 without
shrinking cells below readable size.