mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-17 02:59:40 +02:00
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>
273 lines
11 KiB
Markdown
273 lines
11 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
`idle` → `patrol` (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
|