From 4670bfc08becf7d0a1d6ce63fa9e179ed146ebc6 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 8 Mar 2026 22:45:34 +0000 Subject: [PATCH] feat(spawner): add productivity need and inventory to spawned NPCs Co-Authored-By: Claude Opus 4.6 --- server/src/game/__tests__/spawner.test.ts | 21 ++++++++++++++++++- server/src/game/spawner.ts | 2 ++ .../__tests__/narrationEmitter.test.ts | 2 +- .../__tests__/relationshipSystem.test.ts | 2 +- .../systems/__tests__/socialSystem.test.ts | 1 + .../__tests__/statModifierSystem.test.ts | 2 +- .../systems/__tests__/thoughtSystem.test.ts | 2 +- 7 files changed, 27 insertions(+), 5 deletions(-) diff --git a/server/src/game/__tests__/spawner.test.ts b/server/src/game/__tests__/spawner.test.ts index 367d52a..bb9cae6 100644 --- a/server/src/game/__tests__/spawner.test.ts +++ b/server/src/game/__tests__/spawner.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest'; import { World } from '../../ecs/World.js'; import { GameMap } from '../../map/GameMap.js'; import { spawnNPC } from '../spawner.js'; -import type { Position, Stats } from '@dflike/shared'; +import type { Position, Stats, Needs } from '@dflike/shared'; describe('spawnNPC', () => { it('spawns at random position when no hint given', () => { @@ -49,6 +49,25 @@ describe('spawnNPC', () => { expect(world.getComponent(entity, 'backstory')).toBe(''); }); + it('spawned NPC has productivity need', () => { + const world = new World(); + const map = new GameMap(); + const entity = spawnNPC(world, map); + const needs = world.getComponent(entity, 'needs')!; + expect(needs.productivity).toBeDefined(); + expect(needs.productivity).toBeGreaterThanOrEqual(60); + }); + + it('spawned NPC has empty inventory', () => { + const world = new World(); + const map = new GameMap(); + const entity = spawnNPC(world, map); + const inventory = world.getComponent>(entity, 'inventory')!; + expect(inventory).toBeDefined(); + expect(inventory).toBeInstanceOf(Map); + expect(inventory.size).toBe(0); + }); + it('generates stats in 3-18 range', () => { const world = new World(); const map = new GameMap(); diff --git a/server/src/game/spawner.ts b/server/src/game/spawner.ts index 9ad2367..85b8ddd 100644 --- a/server/src/game/spawner.ts +++ b/server/src/game/spawner.ts @@ -16,6 +16,7 @@ export function spawnNPC(world: World, map: GameMap, positionHint?: Position, ev world.addComponent(entity, 'needs', { hunger: 40 + Math.random() * 40, // 40-80 energy: 40 + Math.random() * 40, + productivity: 60 + Math.random() * 40, // 60-100 }); world.addComponent(entity, 'movement', { state: 'idle', @@ -46,6 +47,7 @@ export function spawnNPC(world: World, map: GameMap, positionHint?: Position, ev world.addComponent(entity, 'statModifiers', { modifiers: [] }); world.addComponent(entity, 'relationships', new Map()); world.addComponent(entity, 'backstory', ''); + world.addComponent>(entity, 'inventory', new Map()); eventMemoryService?.record(entity, { type: 'spawned', diff --git a/server/src/systems/__tests__/narrationEmitter.test.ts b/server/src/systems/__tests__/narrationEmitter.test.ts index 4b4072f..cab9486 100644 --- a/server/src/systems/__tests__/narrationEmitter.test.ts +++ b/server/src/systems/__tests__/narrationEmitter.test.ts @@ -33,7 +33,7 @@ function createNPC( ): EntityId { const e = world.createEntity(); world.addComponent(e, 'position', { x: 0, y: 0 }); - world.addComponent(e, 'needs', { hunger: 80, energy: 80 }); + world.addComponent(e, 'needs', { hunger: 80, energy: 80, productivity: 80 }); world.addComponent(e, 'movement', { state: 'idle', target: null, path: [], direction: 0, moveProgress: 0, }); diff --git a/server/src/systems/__tests__/relationshipSystem.test.ts b/server/src/systems/__tests__/relationshipSystem.test.ts index 1b71d6b..5d97c9b 100644 --- a/server/src/systems/__tests__/relationshipSystem.test.ts +++ b/server/src/systems/__tests__/relationshipSystem.test.ts @@ -14,7 +14,7 @@ function createNPC( ): EntityId { const e = world.createEntity(); world.addComponent(e, 'position', { x, y }); - world.addComponent(e, 'needs', { hunger: 80, energy: 80 }); + world.addComponent(e, 'needs', { hunger: 80, energy: 80, productivity: 80 }); world.addComponent(e, 'movement', { state: 'idle', target: null, path: [], direction: 0, moveProgress: 0, }); diff --git a/server/src/systems/__tests__/socialSystem.test.ts b/server/src/systems/__tests__/socialSystem.test.ts index 10fac21..3f37066 100644 --- a/server/src/systems/__tests__/socialSystem.test.ts +++ b/server/src/systems/__tests__/socialSystem.test.ts @@ -22,6 +22,7 @@ function createNPC( world.addComponent(e, 'needs', { hunger: opts?.hunger ?? 80, energy: opts?.energy ?? 80, + productivity: 80, }); world.addComponent(e, 'movement', { state: opts?.walking ? 'walking' : 'idle', diff --git a/server/src/systems/__tests__/statModifierSystem.test.ts b/server/src/systems/__tests__/statModifierSystem.test.ts index dffef62..97199ed 100644 --- a/server/src/systems/__tests__/statModifierSystem.test.ts +++ b/server/src/systems/__tests__/statModifierSystem.test.ts @@ -18,7 +18,7 @@ function setupEntity( world.addComponent(e, 'stats', base); world.addComponent(e, 'statModifiers', { modifiers: [...modifiers] }); if (needs) { - world.addComponent(e, 'needs', { hunger: 80, energy: 80, ...needs }); + world.addComponent(e, 'needs', { hunger: 80, energy: 80, productivity: 80, ...needs }); } return e; } diff --git a/server/src/systems/__tests__/thoughtSystem.test.ts b/server/src/systems/__tests__/thoughtSystem.test.ts index 970dc99..ef999e8 100644 --- a/server/src/systems/__tests__/thoughtSystem.test.ts +++ b/server/src/systems/__tests__/thoughtSystem.test.ts @@ -29,7 +29,7 @@ function makeNpc(world: World, name: string, needs?: Partial): number { world.addComponent(entity, 'name', name); world.addComponent(entity, 'npcBrain', { currentGoal: 'wander' }); world.addComponent(entity, 'stats', makeStats()); - world.addComponent(entity, 'needs', { hunger: 80, energy: 80, ...needs }); + world.addComponent(entity, 'needs', { hunger: 80, energy: 80, productivity: 80, ...needs }); return entity; }