managarten/apps/manavoxel/CLAUDE.md
Till JS 45a17188e1 feat(manavoxel): complete game engine with behavior system, NPCs, lighting, and dialog
Major systems added to ManaVoxel:
- Behavior runtime: EventBus + 10 triggers + 11 action executors
- Item persistence: save/load items, inventory, area pixels to IndexedDB
- NPC system: 4 types (hostile/passive/merchant/guard), patrol/chase/attack AI
- Lighting: darkness overlay with emissive material light sources
- Day/night cycle: time-based ambient lighting on streets
- Sound system: 8 synthesized Web Audio API presets
- Sprite animation: multi-frame support in editor with play/stop
- Dialog system: NPC interaction with text bubbles and options
- Item properties: range, speed, durability, element all functional
- Health endpoint for Docker, durability bar in inventory UI

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 15:04:07 +02:00

11 KiB

ManaVoxel Project Guide

Overview

ManaVoxel is a 2D top-down pixel platform where players create detailed miniature worlds, program items with behaviors, and share them — all in the browser.

App Port URL
Web App 5195 http://localhost:5195

Project Structure

apps/manavoxel/
├── apps/
│   └── web/
│       └── src/
│           ├── lib/
│           │   ├── engine/          # PixiJS game engine
│           │   │   ├── game.ts          # Main engine, game loop, event integration
│           │   │   ├── tilemap.ts       # Chunk-based renderer (32x32), auto-save dirty flag
│           │   │   ├── camera.ts        # Camera with lerp follow + shake effect
│           │   │   ├── player.ts        # Player movement, collision (8-point AABB)
│           │   │   ├── input.ts         # Keyboard + mouse input manager
│           │   │   ├── particles.ts     # 8 particle presets (sparks, fire, ice, etc.)
│           │   │   ├── area-manager.ts  # Area loading, portal transitions, floor switching
│           │   │   ├── inventory.svelte.ts  # Inventory (8 slots) + GameItem type + pickup/drop hooks
│           │   │   ├── behavior.ts      # Event bus + behavior runtime + action executors
│           │   │   ├── audio.ts         # Web Audio API sound system (8 synth presets)
│           │   │   ├── npc.ts           # NPC class + NPCManager (AI, combat, rendering)
│           │   │   ├── lighting.ts      # Lighting engine + day/night cycle
│           │   │   └── dialog.ts        # NPC dialog system + merchant trading
│           │   ├── editor/          # World & item editing
│           │   │   ├── tools.ts         # Brush, eraser, fill, pipette, undo stack
│           │   │   ├── sprite-editor.svelte  # Pixel art editor (24 colors, mirror, zoom)
│           │   │   ├── property-panel.svelte # Item stats: damage, range, speed, durability, element
│           │   │   ├── trigger-editor.svelte # Behavior rule builder (WHEN/THEN/AND)
│           │   │   └── types.ts         # SpriteData interface
│           │   ├── data/            # Local-first persistence
│           │   │   ├── local-store.ts   # Dexie collections + Base64 encoding
│           │   │   ├── world-loader.ts  # DB ↔ engine converters, item/inventory persistence
│           │   │   ├── guest-seed.ts    # Demo village + house
│           │   │   └── templates.ts     # 5 world templates
│           │   └── components/      # UI components
│           │       └── Inventory.svelte # Inventory bar with rarity colors
│           └── routes/
│               ├── +page.svelte     # Main game page
│               ├── worlds/          # World management
│               └── health/          # Health endpoint for Docker
├── packages/
│   └── shared/src/types.ts  # Material, Area, Item, Network types (@manavoxel/shared)
├── package.json
└── CLAUDE.md

Commands

# From monorepo root
pnpm dev:manavoxel:web    # Start web app (port 5195)

# From apps/manavoxel
pnpm dev                  # Start all apps
pnpm dev:web              # Start web only

Technology Stack

Layer Technology
Rendering PixiJS 8 (WebGL), chunk-based tilemap
UI SvelteKit 2, Svelte 5 (runes), Tailwind CSS 4
Local-First Dexie.js via @manacore/local-store
Auth Mana Core Auth (JWT), guest mode
PWA @vite-pwa/sveltekit

Zoom Levels

Level 1 Pixel = Use
Street 10cm Walking, interaction, combat
Interior 5cm Exploring rooms, furniture
Detail 1cm Item/character sprite editing

Core Concepts

  • Areas: Streets (10cm) and interiors (5cm) are separate pixel grids connected by portals
  • Items: Pixel sprites (1cm) with properties and behaviors, persisted in IndexedDB
  • Floors: Interiors have multiple floors, connected by stairs (F key)
  • Local-First: Everything works offline via Dexie.js, syncs via mana-sync

Data Model (IndexedDB)

Collection Indexes Purpose
worlds creatorId, isPublished, name, template World metadata + startAreaId
areas worldId, type, [worldId+name] Pixel grid data (Base64 Uint16), portals, entities
items creatorId, rarity, isPublished, name Sprite data, properties, behaviors
inventories playerId, [playerId+slot], itemId Slot assignments per player

Persistence

  • Items saved to IndexedDB on create/edit (sprite, properties, behaviors)
  • Inventory saved on item add/remove/drop and on page unload
  • Area pixels auto-saved every 10s when dirty (tilemap.isDirty flag)
  • Worlds persisted on create/delete via world-loader.ts

Behavior System

Items can have programmable behaviors via the Trigger Editor:

WHEN [trigger] THEN [action] AND [action] ...

Architecture

GameEventBus          →  BehaviorRuntime         →  Action Executors
├── emit(event)          ├── registerItem()          ├── damage/heal
├── on(type, fn)         ├── match triggers          ├── particle/sound
├── tickTimer()          ├── check conditions         ├── setPixel/deletePixel
└── get/setVariable()    └── execute actions          ├── teleport/message
                                                      ├── cameraShake
                                                      └── setVariable/sendEvent

Triggers (10 types)

Trigger Fires when
onUse Player presses Space with item held
onTouch Player collides with entity (not yet wired)
onPickup Item added to inventory
onDrop Item removed from inventory
onTimer Every X seconds (frame-based tick)
onHpBelow Player HP drops below threshold (with param check)
onAreaEnter Player enters a portal
onCustomEvent Fired by sendEvent action
onNearItem Item proximity (not yet wired)
onDayNight Day/night change (not yet implemented)

Actions (11 implemented)

Action Effect
damage Reduce player HP by amount (fires onHpBelow)
heal Restore player HP by amount
particle Spawn particle effect at facing direction
sound Play synthesized sound preset
setPixel Place material in radius at facing direction
deletePixel Destroy pixels in radius at facing direction
teleport Move player to x,y coordinates
message Show floating text for 3 seconds
setVariable Set a global game variable
sendEvent Fire a custom event (chains behaviors)
cameraShake Shake camera with intensity

Default Behavior (no rules defined)

Items without behaviors use properties directly:

  • Sound → play configured sound preset on use
  • Damage ≥ 20 → destroy pixels in facing direction (radius = damage/30)
  • Particle → spawn configured particle (or element-based default)
  • Element → auto-selects particle: fire→fire_burst, ice→ice_shards, etc.
  • Durability → decreases per use, item breaks with shatter + sound at 0

Item Properties

Property Range Effect
Damage 0-100 Pixel destruction radius, action damage amount
Range 1-10 Effect distance: 10 + range * 3 pixels
Speed 1-10 Cooldown: 30 / speed frames (higher = faster)
Durability 1-200 Uses before item breaks (-1 per use, shatter on 0)
Element neutral/fire/ice/poison/lightning Auto-particle selection
Rarity common→legendary Visual border color in inventory
Sound preset list Synthesized via Web Audio API (8 presets)
Particle preset list Overrides element-based default

NPC System

NPCs are spawned from EntityDef entries in area data. Place them via the NPC tool in editor mode.

NPC Types

Type Color AI Behavior
hostile Red Patrol → Chase → Attack player on sight
passive Green Idle, no aggression
merchant Yellow Idle, no aggression (future: trading)
guard Blue Patrol → Chase on sight

AI States

idlepatrol (wander ±40px from spawn) → chase (within 60px range) → attack (within 8px, deals contact damage)

Combat

  • NPCs have HP (30 hostile, 50 others) and deal contact damage (5 for hostile)
  • Items damage NPCs in facing direction based on item range
  • Dead NPCs show shatter particles and despawn
  • NPC damage triggers aggro (idle/patrol → chase)
  • Attack cooldown: ~1.5s between NPC attacks

Editor Placement

  • Select NPC tool (N key) in editor
  • Choose type (hostile/passive/merchant/guard)
  • Click on map to place
  • Entities auto-saved with area data every 10s

Lighting System

  • Darkness overlay with radial light sources using PixiJS Graphics
  • Emissive materials (Torch, Lava) auto-detected as light sources
  • Interiors are dark by default (ambient 0.2), streets follow day/night cycle
  • Light sources have radius, color, and intensity
  • Sampling every 4th pixel for performance

Day/Night Cycle

  • Time runs from 0.0 (midnight) → 0.25 (sunrise) → 0.5 (noon) → 0.75 (sunset) → 1.0 (midnight)
  • ~10 min real time = 1 full day cycle
  • Ambient light: 1.0 during day, 0.15 at night, smooth transitions
  • HUD shows current time (HH:MM format), blue at night, yellow during day
  • onDayNight trigger fires on day↔night transitions
  • Only affects streets (interiors have fixed ambient)

Sprite Animation

  • Items support multi-frame animation (stored as concatenated RGBA frames)
  • Sprite Editor: Add/Remove frames, navigate with ←/→, Play/Stop preview
  • New frame copies current frame (easy keyframe workflow)
  • frames field in SpriteData, persisted in IndexedDB via animationFrames

Dialog System

  • E key near non-hostile NPCs opens dialog
  • Dialog templates per NPC type (merchant, guard, passive)
  • Options with actions: close, trade, next
  • Passive NPCs have randomized flavor text
  • Merchant NPCs offer "Show wares" / "Maybe later"
  • Game input paused during dialog

Game Controls

Key Game Mode Editor Mode
WASD/Arrows Move player Pan camera
Space Use held item
E Enter portal
F Switch floor
Tab Toggle editor Toggle editor
1-9 Select material
B/E/G/I Brush/Eraser/Fill/Pipette
[ / ] Brush size
Ctrl+Z/Y Undo/Redo Undo/Redo
Scroll Zoom Zoom

Key Patterns

  • SSR disabled (+layout.ts: ssr = false) — pure client-side SPA
  • Game loop via app.ticker.add() — ~60fps update cycle
  • Chunk rendering — 32x32 pixel chunks, only dirty chunks re-render
  • Base64 encoding — binary data (pixelData, spriteData) encoded for Dexie storage
  • Svelte 5 runes$state, $derived, $effect for reactive UI state

Not Yet Implemented

  • Multiplayer — 10+ message types defined, no WebSocket server
  • Some triggers — onNearItem not wired to game events yet
  • Trading UI — Merchant dialog opens but trade screen not yet implemented