Fix stale values (test count, cooldown constants, system execution order), add missing subsystems (LLM, desires, pickup, invention, persistence), and document newer features (thirst, productivity, day/night cycle, industry/crafting, LLM configuration). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
9.0 KiB
9.0 KiB
CLAUDE.md
Project
Multiplayer NPC simulation game (Dwarf Fortress-inspired). Server-authoritative ECS architecture.
Structure
shared/-- Types and constants (no runtime code)server/-- Node.js + Socket.io game server with ECSserver/src/llm/-- LLM integration (backstory, narration, invention, thought generation)server/src/services/-- Runtime services (logging)server/src/config/-- Config and tuning values (relationship, industry, runtime constants)
client/-- Phaser 3 + TypeScript rendererchars/-- Character sprite assets (do not modify)saves/-- SQLite save files (gitignored, one .db per region)docs/plans/-- Design and implementation docs
Commands
npm install-- install all workspace dependenciesnpm -w server run dev-- start server (port 3001), loads existing save or creates new worldnpm -w server run dev -- --new-world-- start server with fresh world (overwrites save)npm -w server run test-- run server testsnpm -w server run test:watch-- run server tests in watch modenpm -w client run dev-- start client dev server (port 3000)npm -w client run build-- build client for productionnpx -w shared tsc-- rebuild shared types (required after modifying shared/src/)
Tooling
- ESM modules throughout (
"type": "module") - Server: tsx (runtime), vitest (tests), better-sqlite3 (persistence)
- Client: vite (bundler), Phaser 3
- Shared: compiled to
shared/dist/(auto-built)
Key Entry Points
Core:
server/src/main.ts-- server startup, --new-world flag, graceful shutdown (SIGINT/SIGTERM)server/src/game/GameLoop.ts-- tick loop orchestration, save/load integrationshared/src/types.ts-- protocol typesshared/src/constants.ts-- game constants (incl. portrait slots, folder mapping)
Persistence:
server/src/persistence/database.ts-- SQLite connection, schema creation, migration runnerserver/src/persistence/saveManager.ts-- orchestrates periodic saves, shutdown saves, load-on-startupserver/src/persistence/worldSerializer.ts-- save/load map tiles and metadataserver/src/persistence/entitySerializer.ts-- save/load entities, components, relationships, bondsserver/src/persistence/eventSerializer.ts-- save/load narration, memory, stockpile, invention events
Spawner:
server/src/spawner/appearanceGenerator.ts-- NPC appearance + portrait feature generationserver/src/spawner/nameGenerator.ts-- NPC name generationserver/src/spawner/statGenerator.ts-- 3d6 stat generation for NPCs
Stats & Social:
server/src/systems/statHelpers.ts-- getEffectiveStat (base + modifiers, clamped 3-18)server/src/systems/statModifierSystem.ts-- modifier decay and needs-based modifiersserver/src/systems/socialSystem.ts-- NPC social interaction phases and stat integrationserver/src/systems/relationshipSystem.ts-- relationship delta calculation and despawn handlingserver/src/systems/relationshipHelpers.ts-- classify, getPartnerCap, getFriendCap, getEffectiveClassificationserver/src/systems/bondRegistry.ts-- mutual bond tracking between NPCsserver/src/config/relationshipConfig.ts-- tuning values for relationship system (config-driven)
Industry & Crafting:
server/src/systems/industrySystem.ts-- craft/build/gather decision treeserver/src/systems/craftingSystem.ts-- input consumption, timer, output productionserver/src/systems/buildingSystem.ts-- structure creation with build progressserver/src/systems/dropoffSystem.ts-- stockpile item depositserver/src/systems/pickupSystem.ts-- stockpile item retrieval for craftingserver/src/industry/recipeRegistry.ts-- recipe registry with seed recipesserver/src/config/industryConfig.ts-- tuning values for gathering, crafting, building
LLM & AI:
server/src/llm/llmService.ts-- LLM client abstraction and request queuingserver/src/llm/backstoryGenerator.ts-- NPC backstory and desire generationserver/src/llm/narrationService.ts-- narration text generationserver/src/llm/eventMemoryService.ts-- event memory management for LLM contextserver/src/systems/inventionSystem.ts-- LLM-driven recipe inventionserver/src/systems/thoughtSystem.ts-- NPC thought generation for followed entitiesserver/src/systems/desireGeneratorSystem.ts-- LLM-driven desire generationserver/src/systems/desireFulfillmentSystem.ts-- desire progress tracking and completion
Config & Services:
server/src/config/runtimeConstants.ts-- runtime-adjustable game constantsserver/src/services/logService.ts-- structured logging service
Client:
client/src/main.ts-- Phaser game bootstrapclient/src/scenes/GameScene.ts-- main game scene (camera/avatar/follow modes)client/src/sprites/CharacterCompositor.ts-- sprite layer compositingclient/src/sprites/PortraitCompositor.ts-- portrait layer compositing (data URL output)client/src/ui/NpcInfoPanel.ts-- follow-mode NPC info panel (HTML/CSS overlay)
Key Conventions
- ECS components are plain data objects, systems are pure functions
- Server owns all game state; clients are renderers
- Character sprites are 48x48 per frame, 6 cols x 4 rows spritesheet layout
- Compositing: skins + accessories layered in z-order, cached as single textures
- Portraits: skin-specific layers composited client-side, exported as data URLs for HTML panel
- Portrait assets are in
chars/baseman/accessories/portraits/organized by skin folder (skin01-07) - Sprite skin
shape00_skinXXmaps to portrait folderskin{XX+1}, base fileskin{XX}.png - Shared types ensure client/server protocol agreement
- Systems run in order: statModifier -> needsDecay -> npcBrain -> social -> relationship -> desireFulfillment -> desireGenerator -> industry -> pickup -> gathering -> crafting -> building -> dropoff -> movement -> thought -> invention
- Follow mode shows EarthBound-styled NPC info panel with live-updating needs bars and stats
- NPC stats use 3d6 generation (range 3-18, bell curve): 5 physical (STR/DEX/CON/INT/PER) + 5 personality (SOC/COU/CUR/EMP/TMP)
- Stats have transient modifiers (needs-based permanent + event-based decaying); effective value = base + modifiers, clamped 3-18
- Base stats are floats (drift slowly from experience);
getEffectiveStatfloors to int for gameplay use - Wire protocol sends effective (computed) stats only; modifiers stay server-side
- Relationships: per-NPC Map<EntityId, RelationshipData> component, lazy-initialized on first encounter
- Relationship values range -100 to 100, classified into tiers (Partner/Friend/Acquaintance/Stranger/Wary/Rival/Enemy/Nemesis)
- Relationship deltas use diminishing returns and blended asymmetry (30% shared + 70% individual outcome)
- Stats influence relationships: empathy (positive gain), temperament (negative loss), sociability (friend cap + bonus)
- Partner cap: default 1, poly (2) requires sociability 15+ AND empathy 14+; friend cap scales with sociability
- Despawned strong relationships (|value| >= 20) persist as memories; weak ones fade and get cleaned up
- SocialSystem emits lastOutcome on SocialState; relationshipSystem consumes and clears it
- NPC info panel has Status/Relations tabs; relationships tab shows tier-grouped sliders with colored markers
- All relationship tuning values in server/src/config/relationshipConfig.ts (designed for future admin UI)
- Industry system decides craft/build/gather based on inventory and existing structures
- RecipeRegistry singleton holds seed recipes (craft_wooden_axe, craft_hammer, build_stockpile, build_workbench)
- Crafting: 40 tick base, dexterity modifier; consumes inputs, produces output item
- Building: creates structure entity with buildProgress 0→1; structures have type (stockpile/workshop) + subtype + inventory
- After gathering, NPCs drop off items at nearest completed stockpile via 'dropoff' goal
- Movement pauses during gathering, crafting, and building (same skip pattern)
- World persistence uses SQLite (better-sqlite3) with single file per region at
saves/default.db - Event data (narration, memory, stockpile, inventions) is written to DB immediately on occurrence
- Entity state (NPCs, structures, relationships, bonds) is batch-saved every 30 seconds
- Graceful shutdown (Ctrl-C) triggers final save before exit
- On startup, loads from existing save or generates fresh world;
--new-worldflag forces fresh - Schema versioning via
schema_versionin metadata table; migration runner for future schema changes - In-memory event buffers (50 entries) are caches over full DB history; populated from DB on startup
- Map tiles stored fully in DB (not regenerated from seed); decoupled from map generator
- Entity IDs preserved across save/load via
World.createEntityWithId(); nextId counter restored - Components with Map types (inventory, pairCooldowns, structure.inventory) serialize via Object.fromEntries/new Map
- Transient state (movement paths, active social interactions, NPC goals) reset on load; NPCs resume from idle
- Player entities are not persisted; players reconnect fresh