tiennm99 c2c8ad0ab2 feat(assets,audio): regenerate broken keyart/welcome, add VI voices, wire BGM
Asset review + regeneration
- Regenerate keyart-palace.png to remove STUDIOSWAG watermark
- Regenerate keyart-portrait.png as true 16-bit pixel art (was 3D/cinematic)
- Regenerate vi-welcome.mp3 as a 31KB short line (was 808KB misplaced story)
- Generate 10 missing VI voice lines (levelclear, princess-help, princess-run,
  gameover, 6 act intros) in Vietnamese_kindhearted_girl
- Replace broken PHASER logo + 16x16 favicon with proper NNTV wordmark + 32x32
  rabbit sprite
- Add UI scene backgrounds (scene-menu/story/guide/settings) for in-app use
- Add 6 keyart images (1280x720) + portrait (1024x1024) + poster for store/social
- Delete orphan assets: bg.png, nntv-generated-sheet.png, scraps/sketch.napkin
- Add MANIFEST.json + MEDIA.json documenting the asset corpus

BGM integration
- New src/lib/bgm.js: HTMLAudioElement BGM manager with 600ms crossfade,
  per-track loop, autoplay-blocked fallback, persistent sfxMuted/bgmMuted/
  bgmVolume in localStorage (key: nntv-audio-settings)
- App.svelte scene router picks the track per scene; L12 uses the climactic
  chamber track, all other Game scenes use the levels loop
- audio.js: setMuted/isMuted now mirror bgm.js so GameHud's SND/MUTE button
  silences both SFX and BGM (master toggle)
- Settings.svelte: new Audio panel with SFX toggle, BGM toggle, BGM volume
  slider (0-100%, disabled when BGM is off); bilingual
- MainMenu/StoryIntro/Guide/Settings: top band scene backdrops at ~0.5 opacity
- New i18n keys: audioSettings, sfxLabel, bgmLabel, volumeLabel, on, off,
  muted, sound (en + vi)
- GameHud label localized (SND / MUTED)

Tests: 180/180 passing. Build: clean (139KB JS / 28KB CSS gzipped).
2026-06-17 17:08:50 +07:00
2026-05-13 10:20:14 +07:00

Night Ninja: Twilight Voyage

Turn-based stealth puzzle game built with Svelte 5. Play as a ninja rabbit navigating grid-based levels inside the Vegetable Kingdom, avoiding detection by vegetable guards to rescue the missing carrot princess.

Play: pnpm dev → http://localhost:5173


Features

  • 12 levels across 6 acts — grids grow from 8×8 to 13×13; difficulty escalates per act
  • 8 guard types — Static (wilting aura), Rotating (beam), Blinking (toggle), Patrolling, Mirror (beam redirect), Chaser (BFS hunt), Sniper (fixed line of sight), Suspicion (tier alert)
  • Throwable stones — press E to distract rotating, patrolling, and chaser guards
  • Doors and keys — color-coded key items unlock matching doors
  • One-way tiles — arrow tiles enforce entry direction
  • Decay tiles — cells that cool over turns; dangerous while warm
  • Undo/redo — press Z / Y (when level allows); full state snapshots including door state
  • Turn preview — press V to preview next-turn lighting (when level allows)
  • Procedural audio — Web Audio API; move, detect, throw, key pickup, door unlock, suspicion
  • Pixel-art rendering — all sprites drawn as string-art palettes → SVG rects
  • BFS solvability CI — levels L1–L11 verified beatable in CI; L12 intentionally unsolvable
  • Bilingual — English and Vietnamese (VI) locale support

Requirements

Node.js 18+ and pnpm.


Quick Start

git clone https://github.com/tiennm99/nntv
cd nntv
pnpm install
pnpm dev        # dev server at http://localhost:5173

Commands

Command Description
pnpm dev Start development server
pnpm build Production build → dist/
pnpm preview Preview production build locally
pnpm test Run all unit + solvability tests
pnpm test:solvability Run only the BFS solvability suite

Controls

Input Action
Arrow keys / WASD Move one cell
Click adjacent cell Move
Space Wait one turn
V Preview next-turn lighting
Z / Y Undo / redo
E Enter stone-throw targeting mode
Enter / E (in throw mode) Throw stone
Esc Cancel throw

Architecture

Pure JavaScript game engine with a Svelte 5 rendering layer.

src/
├── lib/game/        # Pure JS engine
│   ├── grid.js      # Cell state: walls, goals, lighting, doors, warm tiles
│   ├── player.js    # Position, movement validation, key inventory (bitmask)
│   ├── guards.js    # 8 guard type implementations
│   ├── throwable.js # Stone inventory, throw validation (Manhattan ≤3, LoS)
│   ├── turn.js      # Turn cycle with preview simulation
│   ├── history.js   # Undo/redo via state snapshots
│   └── solver.js    # BFS solvability checker (reused by CI + runtime AI)
├── lib/levels/      # 12 level definitions + solvability test suite
├── lib/pixel/       # Pixel-art renderer, palette, sprite/tile/scene art
├── lib/locales/     # en.json + vi.json
├── lib/audio.js     # Procedural Web Audio sound effects
├── scenes/          # MainMenu, Game, Guide, Settings, GameOver
└── components/      # GameBoard, HUD, sprites, overlays

Engine loop: each player action triggers TurnManager, which advances all guard states simultaneously, recomputes lit cells, checks detection, and appends a snapshot to GameHistory. LevelSolver (BFS over the same state machine) is run in CI against every level definition to catch regressions before merge.


Project Structure

Path Description
index.html HTML entry point
src/main.js Application bootstrap
src/App.svelte Scene router + migration modal
src/styles/theme.css CSS variables: colors, fonts, guard palette
svelte.config.js Svelte 5 + Vite config
vite.config.js Vite build config

License

Apache 2.0

Languages
JavaScript 98.2%
CSS 0.9%
HTML 0.8%
TypeScript 0.1%