b0e553c290
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 2m21s
- D-SLOT-01: Verified slot cap of 7 matches spec §2.2 (no change needed) - D-COMB-03: Implement AoE damage distribution for Sand/Shadowglass frames - D-COMB-01: Reconcile armor pierce formula to spire-combat spec §9.4 (dmg × (1 + armorPierce)) - D-CIRC-01: Fix Simple Logic Circuit summon cost from raw to earth mana - D-ENCHANT-03: Add dual_attunement unlockRequirement to all golem enchantments - D-CORE-01/02: Add Guardian Core runtime override mechanism for guardian-specific mana Also increased test timeouts for module import tests that timeout in full suite runs.
146 lines
5.0 KiB
TypeScript
146 lines
5.0 KiB
TypeScript
// ─── Golem Combat Helpers Unit Tests (Issue #313) ──────────────────────────────
|
|
// Unit tests for computeBasicAttackDamage and resolveEnchantmentEffects.
|
|
|
|
import { describe, it, expect } from 'vitest';
|
|
import { computeBasicAttackDamage, resolveEnchantmentEffects } from './golem-combat-helpers';
|
|
|
|
// ─── computeBasicAttackDamage ────────────────────────────────────────────────
|
|
|
|
describe('computeBasicAttackDamage', () => {
|
|
it('returns baseDamage with no armor and no elemental effect', () => {
|
|
const dmg = computeBasicAttackDamage(
|
|
{ baseDamage: 10, armorPierce: 0, element: undefined },
|
|
0, 0, 'fire',
|
|
);
|
|
expect(dmg).toBe(10);
|
|
});
|
|
|
|
it('applies elemental bonus for super effective matchup', () => {
|
|
// Water vs Fire = 1.5x (water is opposite of fire)
|
|
const dmg = computeBasicAttackDamage(
|
|
{ baseDamage: 10, armorPierce: 0, element: 'water' },
|
|
0, 0, 'fire',
|
|
);
|
|
expect(dmg).toBe(15);
|
|
});
|
|
|
|
it('applies elemental penalty for weak matchup', () => {
|
|
// Lightning vs Earth = 0.75x (lightning is weak to earth)
|
|
const dmg = computeBasicAttackDamage(
|
|
{ baseDamage: 10, armorPierce: 0, element: 'lightning' },
|
|
0, 0, 'earth',
|
|
);
|
|
expect(dmg).toBe(7.5);
|
|
});
|
|
|
|
it('applies same-element bonus', () => {
|
|
const dmg = computeBasicAttackDamage(
|
|
{ baseDamage: 10, armorPierce: 0, element: 'fire' },
|
|
0, 0, 'fire',
|
|
);
|
|
expect(dmg).toBe(12.5);
|
|
});
|
|
|
|
it('never returns negative damage', () => {
|
|
const dmg = computeBasicAttackDamage(
|
|
{ baseDamage: 10, armorPierce: 0, element: undefined },
|
|
0, 0.99, 'fire',
|
|
);
|
|
expect(dmg).toBeGreaterThanOrEqual(0);
|
|
});
|
|
|
|
it('combines elemental bonus and armor pierce', () => {
|
|
// Water vs Fire = 1.5x, then armor pierce adds to damage multiplier
|
|
// dmg = 10 * 1.5 * (1 + 0.25) = 18.75
|
|
const dmg = computeBasicAttackDamage(
|
|
{ baseDamage: 10, armorPierce: 0.25, element: 'water' },
|
|
0, 0.5, 'fire',
|
|
);
|
|
expect(dmg).toBeCloseTo(18.75, 5);
|
|
});
|
|
|
|
it('doubles damage when armorPierce is 1.0', () => {
|
|
// dmg = 10 * (1 + 1.0) = 20
|
|
const dmg = computeBasicAttackDamage(
|
|
{ baseDamage: 10, armorPierce: 1.0, element: undefined },
|
|
0, 0.8, 'fire',
|
|
);
|
|
expect(dmg).toBe(20);
|
|
});
|
|
|
|
it('returns base damage when armorPierce is 0', () => {
|
|
// dmg = 10 * (1 + 0) = 10
|
|
const dmg = computeBasicAttackDamage(
|
|
{ baseDamage: 10, armorPierce: 0, element: undefined },
|
|
0, 0.5, 'fire',
|
|
);
|
|
expect(dmg).toBe(10);
|
|
});
|
|
|
|
it('stacks enchantment armorPierce with frame armorPierce', () => {
|
|
// totalPierce = 0.5 + 0.15 = 0.65
|
|
// dmg = 20 * (1 + 0.65) = 33
|
|
const totalPierce = 0.5 + 0.15;
|
|
const dmg = computeBasicAttackDamage(
|
|
{ baseDamage: 20, armorPierce: 0.5, element: undefined },
|
|
0.15, 0.4, 'fire',
|
|
);
|
|
expect(dmg).toBe(20 * (1 + totalPierce));
|
|
});
|
|
});
|
|
|
|
// ─── resolveEnchantmentEffects ────────────────────────────────────────────────
|
|
|
|
describe('resolveEnchantmentEffects', () => {
|
|
it('resolves sword_fire to burn effect', () => {
|
|
const effects = resolveEnchantmentEffects(['sword_fire']);
|
|
expect(effects).toHaveLength(1);
|
|
expect(effects[0].type).toBe('burn');
|
|
expect(effects[0].magnitude).toBe(3);
|
|
});
|
|
|
|
it('resolves sword_frost to slow effect', () => {
|
|
const effects = resolveEnchantmentEffects(['sword_frost']);
|
|
expect(effects).toHaveLength(1);
|
|
expect(effects[0].type).toBe('slow');
|
|
});
|
|
|
|
it('resolves sword_metal to armorPierce effect', () => {
|
|
const effects = resolveEnchantmentEffects(['sword_metal']);
|
|
expect(effects).toHaveLength(1);
|
|
expect(effects[0].type).toBe('armorPierce');
|
|
expect(effects[0].magnitude).toBe(0.15);
|
|
});
|
|
|
|
it('resolves sword_lightning to shock effect', () => {
|
|
const effects = resolveEnchantmentEffects(['sword_lightning']);
|
|
expect(effects).toHaveLength(1);
|
|
expect(effects[0].type).toBe('shock');
|
|
});
|
|
|
|
it('resolves sword_shadow to weaken effect', () => {
|
|
const effects = resolveEnchantmentEffects(['sword_shadow']);
|
|
expect(effects).toHaveLength(1);
|
|
expect(effects[0].type).toBe('weaken');
|
|
});
|
|
|
|
it('returns empty array for unknown enchantment IDs', () => {
|
|
const effects = resolveEnchantmentEffects(['nonexistent_enchant']);
|
|
expect(effects).toHaveLength(0);
|
|
});
|
|
|
|
it('resolves multiple enchantments', () => {
|
|
const effects = resolveEnchantmentEffects(['sword_fire', 'sword_lightning', 'sword_shadow']);
|
|
expect(effects).toHaveLength(3);
|
|
const types = effects.map(e => e.type);
|
|
expect(types).toContain('burn');
|
|
expect(types).toContain('shock');
|
|
expect(types).toContain('weaken');
|
|
});
|
|
|
|
it('handles empty array', () => {
|
|
const effects = resolveEnchantmentEffects([]);
|
|
expect(effects).toHaveLength(0);
|
|
});
|
|
});
|