// ─── Golem Maintenance Upkeep Tests (Issue #315) ─────────────────────────────── // Tests that multi-type golem cores split upkeep evenly across all mana types. import { describe, it, expect } from 'vitest'; import { CORES, FRAMES, MIND_CIRCUITS, } from '@/lib/game/data/golems'; import type { GolemDesign, SerializedDesign } from '@/lib/game/data/golems/types'; import type { RuntimeActiveGolem } from '@/lib/game/types'; import { processGolemMaintenance } from './golem-combat-actions'; // ─── Helpers ─────────────────────────────────────────────────────────────────── function makeDesign( coreId: string, frameId: string, circuitId: string, enchantIds: string[] = [], selectedSpells: string[] = [], ): GolemDesign { return { id: `test_${coreId}_${frameId}_${circuitId}`, name: `Test ${coreId} ${frameId}`, core: CORES[coreId], frame: FRAMES[frameId], mindCircuit: MIND_CIRCUITS[circuitId], enchantments: [], selectedManaTypes: [], selectedSpells, }; } function makeSerialized(design: GolemDesign): SerializedDesign { return { id: design.id, name: design.name, coreId: design.core.id, frameId: design.frame.id, mindCircuitId: design.mindCircuit.id, enchantmentIds: [], selectedManaTypes: design.selectedManaTypes, selectedSpells: design.selectedSpells, }; } function makeActiveGolem(design: GolemDesign, currentMana?: number, attackProgress = 0): RuntimeActiveGolem { return { designId: design.id, summonedFloor: 1, attackProgress, roomsRemaining: 3, currentMana: currentMana ?? design.core.manaCapacity, spellCastIndex: 0, }; } // ─── Tests ───────────────────────────────────────────────────────────────────── describe('processGolemMaintenance - multi-type core upkeep splitting (fix #315)', () => { it('single-type core (basic) deducts upkeep from one element', () => { const design = makeDesign('basic', 'earth', 'simple'); const serialized = makeSerialized(design); const golem = makeActiveGolem(design); const elements = { earth: { current: 50, max: 100, unlocked: true }, }; const result = processGolemMaintenance( [golem], { [design.id]: serialized }, 100, elements, ); expect(result.maintainedGolems.length).toBe(1); // Basic core: manaRegen=0.5, upkeep=1.0/hr, HOURS_PER_TICK=0.04 => 0.04 per tick const expectedDeduction = 0.5 * 2 * 0.04; // 0.04 expect(result.elements.earth.current).toBeCloseTo(50 - expectedDeduction); }); it('multi-type core splits upkeep evenly across selected mana types', () => { const design = makeDesign('intermediate', 'earth', 'simple'); design.selectedManaTypes = ['fire', 'water']; const serialized = makeSerialized(design); const golem = makeActiveGolem(design); const elements = { fire: { current: 50, max: 100, unlocked: true }, water: { current: 50, max: 100, unlocked: true }, }; const result = processGolemMaintenance( [golem], { [design.id]: serialized }, 100, elements, ); expect(result.maintainedGolems.length).toBe(1); // Intermediate core: manaRegen=1.5, upkeep=3.0/hr split across 2 types // Per type: 1.5/hr * 0.04 = 0.06 per tick const expectedDeduction = (1.5 * 2 * 0.04) / 2; // 0.06 expect(result.elements.fire.current).toBeCloseTo(50 - expectedDeduction); expect(result.elements.water.current).toBeCloseTo(50 - expectedDeduction); }); it('advanced core (3 types) splits upkeep three ways', () => { const design = makeDesign('advanced', 'steel', 'simple'); design.selectedManaTypes = ['fire', 'water', 'earth']; const serialized = makeSerialized(design); const golem = makeActiveGolem(design); const elements = { fire: { current: 50, max: 100, unlocked: true }, water: { current: 50, max: 100, unlocked: true }, earth: { current: 50, max: 100, unlocked: true }, }; const result = processGolemMaintenance( [golem], { [design.id]: serialized }, 100, elements, ); expect(result.maintainedGolems.length).toBe(1); // Advanced core: manaRegen=3.0, upkeep=6.0/hr split across 3 types // Per type: 2.0/hr * 0.04 = 0.08 per tick const expectedDeduction = (3.0 * 2 * 0.04) / 3; // 0.08 expect(result.elements.fire.current).toBeCloseTo(50 - expectedDeduction); expect(result.elements.water.current).toBeCloseTo(50 - expectedDeduction); expect(result.elements.earth.current).toBeCloseTo(50 - expectedDeduction); }); it('dismisses golem when one mana type is insufficient for split upkeep', () => { const design = makeDesign('intermediate', 'earth', 'simple'); design.selectedManaTypes = ['fire', 'water']; const serialized = makeSerialized(design); const golem = makeActiveGolem(design); const elements = { fire: { current: 50, max: 100, unlocked: true }, water: { current: 0.001, max: 100, unlocked: true }, // Almost empty }; const result = processGolemMaintenance( [golem], { [design.id]: serialized }, 100, elements, ); expect(result.maintainedGolems.length).toBe(0); expect(result.logMessages[0]).toContain('dismissed'); expect(result.logMessages[0]).toContain('water'); }); it('dismisses golem when one mana type is not unlocked', () => { const design = makeDesign('intermediate', 'earth', 'simple'); design.selectedManaTypes = ['fire', 'water']; const serialized = makeSerialized(design); const golem = makeActiveGolem(design); const elements = { fire: { current: 50, max: 100, unlocked: true }, // water not present => not unlocked }; const result = processGolemMaintenance( [golem], { [design.id]: serialized }, 100, elements, ); expect(result.maintainedGolems.length).toBe(0); expect(result.logMessages[0]).toContain('dismissed'); }); it('does not deduct any mana when upkeep cannot be paid (atomic check)', () => { const design = makeDesign('intermediate', 'earth', 'simple'); design.selectedManaTypes = ['fire', 'water']; const serialized = makeSerialized(design); const golem = makeActiveGolem(design); const elements = { fire: { current: 50, max: 100, unlocked: true }, water: { current: 0.001, max: 100, unlocked: true }, }; const result = processGolemMaintenance( [golem], { [design.id]: serialized }, 100, elements, ); // fire should NOT have been deducted since water couldn't pay its share expect(result.elements.fire.current).toBe(50); }); });