203 lines
6.9 KiB
TypeScript
203 lines
6.9 KiB
TypeScript
// ─── 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);
|
|
});
|
|
});
|