diff --git a/CLAUDE.md b/CLAUDE.md index 007bd0c..e261f7b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,7 +16,7 @@ Multiplayer NPC simulation game (Dwarf Fortress-inspired). Server-authoritative - `npm install` -- install all workspace dependencies - `npm -w server run dev` -- start server (port 3001) -- `npm -w server run test` -- run server tests (86 tests) +- `npm -w server run test` -- run server tests (115 tests) - `npm -w server run test:watch` -- run server tests in watch mode - `npm -w client run dev` -- start client dev server (port 3000) - `npm -w client run build` -- build client for production @@ -39,6 +39,9 @@ Multiplayer NPC simulation game (Dwarf Fortress-inspired). Server-authoritative - `server/src/systems/statHelpers.ts` -- getEffectiveStat (base + modifiers, clamped 3-18) - `server/src/systems/statModifierSystem.ts` -- modifier decay and needs-based modifiers - `server/src/systems/socialSystem.ts` -- NPC social interaction phases and stat integration +- `server/src/systems/relationshipSystem.ts` -- relationship delta calculation and despawn handling +- `server/src/systems/relationshipHelpers.ts` -- classify, getPartnerCap, getFriendCap, getEffectiveClassification +- `server/src/config/relationshipConfig.ts` -- tuning values for relationship system (config-driven) - `client/src/main.ts` -- Phaser game bootstrap - `client/src/scenes/GameScene.ts` -- main game scene (camera/avatar/follow modes) - `client/src/sprites/CharacterCompositor.ts` -- sprite layer compositing @@ -57,9 +60,18 @@ Multiplayer NPC simulation game (Dwarf Fortress-inspired). Server-authoritative - Portrait assets are in `chars/baseman/accessories/portraits/` organized by skin folder (skin01-07) - Sprite skin `shape00_skinXX` maps to portrait folder `skin{XX+1}`, base file `skin{XX}.png` - Shared types ensure client/server protocol agreement -- Systems run in order: statModifier -> needsDecay -> npcBrain -> social -> movement -> broadcast +- Systems run in order: statModifier -> needsDecay -> npcBrain -> social -> relationship -> movement -> broadcast - 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); `getEffectiveStat` floors to int for gameplay use - Wire protocol sends effective (computed) stats only; modifiers stay server-side +- Relationships: per-NPC Map 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) diff --git a/client/src/main.ts b/client/src/main.ts index b78fc60..a517324 100644 --- a/client/src/main.ts +++ b/client/src/main.ts @@ -5,10 +5,14 @@ import { GameScene } from './scenes/GameScene.js'; const config: Phaser.Types.Core.GameConfig = { type: Phaser.AUTO, parent: 'game', - width: 1600, - height: 1200, + width: window.innerWidth, + height: window.innerHeight, backgroundColor: '#1a1a2e', pixelArt: true, + scale: { + mode: Phaser.Scale.RESIZE, + autoCenter: Phaser.Scale.CENTER_BOTH, + }, scene: [BootScene, GameScene], }; diff --git a/client/src/ui/CommandPanel.ts b/client/src/ui/CommandPanel.ts index bbdc51c..14e639f 100644 --- a/client/src/ui/CommandPanel.ts +++ b/client/src/ui/CommandPanel.ts @@ -78,7 +78,7 @@ export class CommandPanel { // Title const title = document.createElement('div'); title.style.cssText = ` - font-size: 6px; + font-size: 9px; color: ${EB.textMuted}; text-align: center; letter-spacing: 4px; @@ -118,7 +118,7 @@ export class CommandPanel { const keyBadge = document.createElement('div'); keyBadge.style.cssText = ` - font-size: 7px; + font-size: 10px; color: ${EB.bgDeep}; background: ${EB.borderOuter}; border-radius: 3px; @@ -133,7 +133,7 @@ export class CommandPanel { const label = document.createElement('div'); label.style.cssText = ` - font-size: 7px; + font-size: 10px; color: ${EB.textSecondary}; letter-spacing: 0.5px; `; diff --git a/client/src/ui/NpcInfoPanel.ts b/client/src/ui/NpcInfoPanel.ts index 48d5e4b..27973eb 100644 --- a/client/src/ui/NpcInfoPanel.ts +++ b/client/src/ui/NpcInfoPanel.ts @@ -52,12 +52,12 @@ export class NpcInfoPanel { position: fixed; top: 16px; right: 16px; - width: 232px; + width: 280px; border-radius: 16px; border: 3px solid ${EB.borderOuter}; background: ${EB.borderGap}; z-index: 1000; - transform: translateX(300px); + transform: translateX(350px); transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1); box-shadow: 0 0 20px rgba(80, 60, 160, 0.4), @@ -196,7 +196,7 @@ export class NpcInfoPanel { // Name this.nameEl = document.createElement('div'); this.nameEl.style.cssText = ` - font-size: 9px; + font-size: 13px; color: ${EB.textPrimary}; text-align: center; padding: 2px 0 4px; @@ -208,7 +208,7 @@ export class NpcInfoPanel { // Activity this.activityEl = document.createElement('div'); this.activityEl.style.cssText = ` - font-size: 7px; + font-size: 10px; color: ${EB.textSecondary}; text-align: center; padding-bottom: 2px; @@ -219,7 +219,7 @@ export class NpcInfoPanel { this.separatorEl = document.createElement('div'); this.separatorEl.style.cssText = ` text-align: center; - font-size: 5px; + font-size: 7px; color: ${EB.textMuted}; padding: 4px 0; letter-spacing: 6px; @@ -241,7 +241,7 @@ export class NpcInfoPanel { const statsSeparator = document.createElement('div'); statsSeparator.style.cssText = ` text-align: center; - font-size: 5px; + font-size: 7px; color: ${EB.textMuted}; padding: 4px 0; letter-spacing: 6px; @@ -286,7 +286,7 @@ export class NpcInfoPanel { hide(): void { this.visible = false; - this.outerFrame.style.transform = 'translateX(300px)'; + this.outerFrame.style.transform = 'translateX(350px)'; } updateInfo(entity: EntityState): void { @@ -345,7 +345,7 @@ export class NpcInfoPanel { const labelEl = document.createElement('div'); labelEl.style.cssText = ` - font-size: 6px; + font-size: 9px; color: ${EB.textSecondary}; text-transform: uppercase; letter-spacing: 1px; @@ -354,7 +354,7 @@ export class NpcInfoPanel { const valueEl = document.createElement('div'); valueEl.style.cssText = ` - font-size: 7px; + font-size: 10px; color: ${EB.textPrimary}; text-shadow: 1px 1px 0 rgba(0,0,0,0.5); `; @@ -436,7 +436,7 @@ export class NpcInfoPanel { el.style.cssText = ` display: flex; justify-content: space-between; - font-size: 6px; + font-size: 9px; `; const labelEl = document.createElement('span'); labelEl.style.cssText = `color: ${EB.textSecondary}; text-transform: uppercase; letter-spacing: 0.5px;`;