# Codebase Summary - Night Ninja: Twilight Voyage ## Overview NNTV is a turn-based stealth puzzle game built with Svelte 5 and Vite 6.x. The codebase separates pure JS game engine (classes in `lib/game/`) from Svelte rendering (scenes + components). State flows one-way: game classes mutate internally, then `renderVersion++` triggers Svelte re-derivation. ## Module Inventory ### Entry Point - **src/main.js** — Mounts `App.svelte` into `#app` ### Scene Router - **src/App.svelte** — `{#key currentScene}` with `transition:fade`, passes `navigate` function as prop to all scenes ### Scenes (src/scenes/) | File | Purpose | |------|---------| | MainMenu.svelte | Start game, level select, settings, guide | | StoryIntro.svelte | Scrolling narrative with skip button | | LevelIntro.svelte | Level name + story text + continue | | LevelSelect.svelte | 4x3 grid of level buttons (locked/unlocked/completed) | | Game.svelte | Main gameplay: state owner, input, turn loop, rendering | | GameOver.svelte | Loss/twist screen, retry/menu | | Settings.svelte | Language toggle (EN/VI) | | Guide.svelte | Rules, controls, enemy types | ### Components (src/components/) | File | Purpose | |------|---------| | Button.svelte | Reusable styled button | | GameBoard.svelte | CSS grid of cells, each rendering a pixel-art tile | | GameHud.svelte | Level, lives (pixel hearts), turns, pixel-icon action buttons | | PlayerSprite.svelte | Pixel-art ninja rabbit sprite | | GuardSprite.svelte | Pixel-art veggie sprite dispatched by guard.type + direction indicator | | DetectionPopup.svelte | "Detected!" overlay with retry | | LevelCompletePopup.svelte | Star rating, best moves, next-level action | | PauseMenu.svelte | Resume / restart / main menu | | ControlsOverlay.svelte | Keyboard / tap / swipe reference overlay | ### Game Engine (src/lib/game/) — Pure JS, no Svelte | File | Purpose | |------|---------| | grid-system.js | GridSystem class: cell state, walls, goals, lighting | | player.js | Player class: position, movement validation | | guards.js | Guard base + 6 subclasses (Static, Rotating, Blinking, Mirror, Patrolling, Chaser) | | turn-manager.js | TurnManager: turn cycle, guard updates, detection | | level-manager.js | loadLevel(): GUARD_REGISTRY factory pattern for guard instantiation | | level-solver.js | BFS solvability checker (test-only); reuses runtime AI via capture()/apply() | | game-history.js | GameHistory class: undo/redo snapshots (Z/Y keys) | | princess-mechanic.js | Princess detection logic: escalating light rings on level 12 | | touch-controls.js | TouchControls class: pointer gesture detection (legacy; no mobile support claim) | ### Level Data (src/lib/levels/) | File | Purpose | |------|---------| | levels.js | LEVELS array: 12 level definitions (grid, guards, walls, goals); L1-L11 solvable, L12 intentionally unsolvable | | levels.solvability.test.js | CI-enforced invariants: each L1-L11 solvable, L12 unsolvable, no wall/light overlaps, exactly 12 levels | ### Pixel-Art Pipeline (src/lib/pixel/) | File | Purpose | |------|---------| | Pixel.svelte | SVG renderer: string-art + palette → run-length-merged ``s | | palette.js | NNTV color constants (mirrors theme.css guard colors; extended atmosphere palette) | | art-characters.js | 32×32 sprites: rabbit, princess, 6 guard veggies + GUARD_SPRITES map | | art-tiles.js | 16×16 board tiles: empty, wall, goal, lit, mirror, preview | | art-ui.js | Heart (full/empty), moon, logo, pixel icons (undo/redo/eye/pause/settings/lang/arrow) | | art-scenes.js | 80×N act backdrops + SCENE_BY_LEVEL mapping (garden/walls/fortress/underground/palace/chamber) | ### Audio System (src/lib/) | File | Purpose | |------|---------| | audio.js | Web Audio API procedural sound: playTone, playMoveSound, playDetectionSound, playCompleteSound, toggleMute | ### Utilities (src/lib/) | File | Purpose | |------|---------| | localization.js | getText/setLanguage/getLanguage/initLanguage | | progress.js | getProgress/completeLevel via localStorage | ### Localization (src/lib/locales/) | File | Keys | Purpose | |------|------|---------| | en.json | ~55 | English translations | | vi.json | ~55 | Vietnamese translations | ### Styles (src/styles/) | File | Purpose | |------|---------| | theme.css | CSS variables: colors, fonts, guard colors, grid colors | ## Class Hierarchy ### Guard Inheritance ``` Guard (abstract base: grid, row, col, type, direction, isOn) ├── StaticGuard — wilting tomato: Manhattan aura shrinks by 1 per turn (initialRadius → currentRadius) ├── RotatingGuard — rotates beam 90°/turn, castBeam with mirror bounce ├── BlinkingGuard — toggles isOn, lights litCells when on ├── MirrorGuard — lights own cell, stores reflectDirection (cw/ccw) ├── PatrollingGuard — follows path array, lights front + right cells └── ChaserGuard — BFS pathfinding to player, detectionRadius range ``` ## Key Data Structures ### Level Definition ```javascript { id: 1, name: "Garden Path", storyKey: "level1Story", grid: { rows: 6, cols: 6 }, player: { row: 0, col: 0 }, goal: { row: 5, col: 5 }, walls: [{ row: 1, col: 1 }, ...], guards: [ { type: "static", position: { row: 2, col: 4 }, initialRadius: 2 }, { type: "rotating", position: { row: 3, col: 3 }, startDirection: 0 }, { type: "blinking", position: {...}, litCells: [...], startState: true }, { type: "mirror", position: {...}, reflectDirection: "cw" }, { type: "patrolling", startPosition: {...}, path: [...] }, { type: "chaser", position: {...}, detectionRadius: 3 }, ], isFinalLevel: false } ``` ### Cell State ```javascript { isWall: boolean, isGoal: boolean, isLight: boolean } ``` ### Progress (localStorage) ```javascript { maxLevel: 1, completedLevels: [1, 2, 3] } ``` ## Public API Summary ### GridSystem ``` new GridSystem(rows, cols, cellSize) .isValidPosition(row, col), .isWall(row, col), .setWall(row, col, value) .isGoal(row, col), .setGoal(row, col, value) .isLight(row, col), .setLight(row, col, value) .clearAllLight(), .getAllCells() → [{row, col, isWall, isGoal, isLight}] ``` ### Player ``` new Player(grid, row, col) .move(direction) → boolean, .moveTo(row, col) → boolean .isInLitCell() → boolean, .isAtGoal() → boolean ``` ### Guards ``` All: .updateLight(allGuards?), .onTurnChange(allGuards?) All: .capture() → state, .apply(state) # dynamic-state snapshot for undo/preview RotatingGuard: .castBeam(dir, fromRow, fromCol, range, allGuards, depth) PatrollingGuard: .checkIfCircularPath(), path traversal with reversing MirrorGuard: .reflectDirection ('cw' or 'ccw') ChaserGuard: .bfsNextStep(targetRow, targetCol), hunting/returning state ``` ### PrincessMechanic ``` new PrincessMechanic() .update(grid, player, goalRow, goalCol) → { showMessage, detected } .lightRing(grid, goalRow, goalCol, radius) .capture() → { alerted, alertRadius, messageShown }, .apply(state) .reset() ``` ### GameHistory ``` new GameHistory() .createSnapshot(player, guards, turnCount, princessState) → snapshot .pushSnapshot(snap), .undo(...), .redo(...) .canUndo(), .canRedo(), .reset() ``` ### TurnManager ``` new TurnManager() .nextTurn(grid, player, guards) → { detected, levelComplete } .reset() ``` ## Dependencies | Package | Version | Purpose | |---------|---------|---------| | svelte | 5.x | UI framework (runes mode) | | vite | 6.3.6 | Build tool, dev server | | @sveltejs/vite-plugin-svelte | 6.x | Svelte compiler for Vite | ## File Dependency Map ``` main.js → App.svelte App.svelte → all scenes Game.svelte (central hub) ├── lib/game/level-manager.js → grid-system, player, guards, levels ├── lib/game/turn-manager.js ├── lib/game/game-history.js, princess-mechanic.js, touch-controls.js ├── lib/audio.js, lib/progress.js, lib/localization.js ├── lib/pixel/Pixel.svelte, lib/pixel/art-scenes.js (act backdrop) ├── components/GameBoard, PlayerSprite, GuardSprite, GameHud ├── components/DetectionPopup, LevelCompletePopup, PauseMenu, ControlsOverlay └── renderVersion pattern for reactivity LevelSelect.svelte → level-manager.getTotalLevels(), progress.js, localization.js LevelIntro.svelte → levels.js, lib/pixel/art-scenes.js, localization.js MainMenu.svelte → lib/pixel/{Pixel, art-ui, art-characters} Pixel components (PlayerSprite/GuardSprite/GameBoard/GameHud) → lib/pixel/Pixel + art-* All scenes → localization.js (for UI text) ``` ## Statistics | Metric | Value | |--------|-------| | Total Source Files | ~36 (8 scenes + 9 components + 8 engine + 6 pixel + audio + utils) | | Number of Classes | 10 (GridSystem, Player, Guard + 6 subclasses, TurnManager, GameHistory, TouchControls) | | Number of Levels | 12 (across 6 acts) | | Guard Types | 6 (Static, Rotating, Blinking, Mirror, Patrolling, Chaser) | | Localization Keys | ~67 per language | | Max Grid Size | 10x10 | | Max Guards/Level | 8 |