managarten/apps/manavoxel/CLAUDE.md
Till JS 22a73943e1 chore: complete ManaCore → Mana rename (docs, go modules, plists, images)
Final cleanup of references missed in previous rename commits:

- Dockerfiles: PUBLIC_MANA_CORE_AUTH_URL → PUBLIC_MANA_AUTH_URL
- Go modules: github.com/manacore/* → github.com/mana/* (7 go.mod files)
- launchd plists: com.manacore.* → com.mana.* (14 files renamed + content)
- Image assets: *_Manacore_AI_Credits* → *_Mana_AI_Credits* (11 files)
- .env.example files: ManaCore brand strings → Mana
- .prettierignore: stale apps/manacore/* paths → apps/mana/*
- Markdown docs (CLAUDE.md, /docs/*): mana-core-auth → mana-auth, etc.

Excluded from rename: .claude/, devlog/, manascore/ (historical content),
client testimonials, blueprints, npm package refs (@mana-core/*).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 12:26:10 +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 @mana/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 (9 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
onDayNight Day/night change

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

Economy System

  • Gold — Earned from defeating NPCs (hostile: 5-15g, guard: 10-25g)
  • Merchants — Buy items with gold, prices shown on buy button
  • Persistence — Gold saved/loaded with inventory in IndexedDB
  • HUD — Gold counter shown in top bar during gameplay