docs: update CLAUDE.md for relationship system; fix client styling
- Update CLAUDE.md with relationship system entry points, conventions, test count - Restore NPC info panel to 280px width with original font sizes - Add responsive scaling to Phaser game config - Bump CommandPanel font sizes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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<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)
|
||||
|
||||
+6
-2
@@ -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],
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
`;
|
||||
|
||||
@@ -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;`;
|
||||
|
||||
Reference in New Issue
Block a user