import { describe, it, expect } from 'vitest'; import { World } from '../../ecs/World.js'; import { statModifierSystem } from '../statModifierSystem.js'; import type { Stats, StatModifiers, Needs } from '@dflike/shared'; function setupEntity( world: World, statsOverrides: Partial = {}, needs?: Partial, modifiers: StatModifiers['modifiers'] = [], ): number { const e = world.createEntity(); const base: Stats = { strength: 10, dexterity: 10, constitution: 10, intelligence: 10, perception: 10, sociability: 10, courage: 10, curiosity: 10, empathy: 10, temperament: 10, ...statsOverrides, }; world.addComponent(e, 'stats', base); world.addComponent(e, 'statModifiers', { modifiers: [...modifiers] }); if (needs) { world.addComponent(e, 'needs', { hunger: 80, energy: 80, ...needs }); } return e; } describe('statModifierSystem', () => { describe('modifier decay', () => { it('decrements remaining on each modifier', () => { const world = new World(); const e = setupEntity(world, {}, undefined, [ { stat: 'strength', value: 2, remaining: 10 }, ]); statModifierSystem(world); const mods = world.getComponent(e, 'statModifiers')!; expect(mods.modifiers[0].remaining).toBe(9); }); it('removes expired modifiers (remaining reaches 0)', () => { const world = new World(); const e = setupEntity(world, {}, undefined, [ { stat: 'strength', value: 2, remaining: 1 }, { stat: 'dexterity', value: -1, remaining: 5 }, ]); statModifierSystem(world); const mods = world.getComponent(e, 'statModifiers')!; expect(mods.modifiers).toHaveLength(1); expect(mods.modifiers[0].stat).toBe('dexterity'); }); it('does not decrement permanent modifiers (remaining = -1)', () => { const world = new World(); const e = setupEntity(world, {}, undefined, [ { stat: 'intelligence', value: -2, remaining: -1 }, ]); statModifierSystem(world); const mods = world.getComponent(e, 'statModifiers')!; expect(mods.modifiers[0].remaining).toBe(-1); }); }); describe('needs-based modifiers', () => { it('applies intelligence -2 and sociability -1 when hunger < 30', () => { const world = new World(); const e = setupEntity(world, {}, { hunger: 20, energy: 80 }); statModifierSystem(world); const mods = world.getComponent(e, 'statModifiers')!; const intMod = mods.modifiers.find(m => m.stat === 'intelligence' && m.remaining === -1); const socMod = mods.modifiers.find(m => m.stat === 'sociability' && m.remaining === -1); expect(intMod?.value).toBe(-2); expect(socMod?.value).toBe(-1); }); it('applies dexterity -2 and temperament -2 when energy < 20', () => { const world = new World(); const e = setupEntity(world, {}, { hunger: 80, energy: 10 }); statModifierSystem(world); const mods = world.getComponent(e, 'statModifiers')!; const dexMod = mods.modifiers.find(m => m.stat === 'dexterity' && m.remaining === -1); const tempMod = mods.modifiers.find(m => m.stat === 'temperament' && m.remaining === -1); expect(dexMod?.value).toBe(-2); expect(tempMod?.value).toBe(-2); }); it('removes needs-based modifiers when needs recover', () => { const world = new World(); const e = setupEntity(world, {}, { hunger: 20, energy: 80 }); statModifierSystem(world); // Now hunger recovers const needs = world.getComponent(e, 'needs')!; needs.hunger = 80; statModifierSystem(world); const mods = world.getComponent(e, 'statModifiers')!; const needsMods = mods.modifiers.filter(m => m.remaining === -1); expect(needsMods).toHaveLength(0); }); it('does not stack needs-based modifiers across ticks', () => { const world = new World(); const e = setupEntity(world, {}, { hunger: 20, energy: 80 }); statModifierSystem(world); statModifierSystem(world); statModifierSystem(world); const mods = world.getComponent(e, 'statModifiers')!; const intMods = mods.modifiers.filter(m => m.stat === 'intelligence' && m.remaining === -1); expect(intMods).toHaveLength(1); }); }); });