diablo2-web/tests/challenger-cold-armors-stre...

850 lines
39 KiB
TypeScript

/**
* Adversarial Stress & Correctness Verification Suite — Milestone 1: Three Sorceress Cold Armors
* Skills Under Test:
* - Frozen Armor (Skill #040, State 10, Group 1)
* - Shiver Armor (Skill #050, State 88, Group 1)
* - Chilling Armor (Skill #060, State 20, Group 1)
*
* Target Commit: e7d105d
* Author: Challenger 1 (teamwork_preview_challenger)
* Ground Truth Reference: Diablo II v1.13c (D2Game.dll, D2Common.dll, Skills.txt, Missiles.txt, Overlay.txt, States.txt)
*
* Verification Dimensions:
* 1. Exhaustive Mathematical Boundary Sweeps (slvl 1..99 x synergies 0..20 / 0..40):
* - Monotonicity of defense bonus, duration, cold damage, and freeze/chill lengths.
* - Zero NaN, zero undefined, and positive non-zero outputs for all valid inputs.
* - Exact formula validation against 1.13c ground truth analytical models.
* 2. Degenerate, Extreme & Adversarial Inputs:
* - Non-finite, negative, zero, and float slvl handling.
* - Missing, malformed, negative, and mixed synergy structures.
* 3. State Group 1 Mutual Exclusion Stress Testing:
* - Rapid successive casting bursts & 100-step randomized permutations.
* - Invariant: strictly 1 active Group 1 state at all times.
* - Level-override rules (different armor overwrites regardless of slvl; same armor rejects lower slvl).
* - Clean eviction of StatList defense modifiers upon armor transition.
* 4. Reactive Trigger Adversarial Edge Cases:
* - Frozen Armor: damagedinmelee with physical damage = 0 must NOT freeze.
* - Chilling Armor: hitbymissile with returnFire = false must NOT reflect.
* - Shiver Armor: attackedinmelee triggers proactively before attack connects regardless of damage.
* - Event cross-trigger isolation (no cross-firing between melee and missile events).
* 5. Full Invariant & System Integrity Checks:
* - BATCH1_SKILLS length strictly 38.
* - Mana costs: flat 7 (40), flat 11 (50), flat 17 (60).
* - Cooldown ticks: 0 for all three armors.
* - calculateSkillDamage routing parity.
*/
import { describe, expect, it } from 'vitest'
import {
calculateFrozenArmorDefense,
calculateFrozenArmorDuration,
calculateFrozenArmorFreezeLength,
calculateShiverArmorDefense,
calculateShiverArmorDuration,
calculateShiverArmorDamage,
calculateShiverArmorChillLength,
calculateChillingArmorDefense,
calculateChillingArmorDuration,
calculateChillingArmorDamage,
calculateChillingArmorChillLength,
calculateColdArmorDuration,
calculateSkillDamage,
getSkillManaCost,
getSkillCooldownTicks,
getSkillCastOverlay,
BATCH1_SKILLS,
} from '../src/game/skills.ts'
import { getSharedDataRegistry } from '../src/game/engine/data-registry.ts'
import { UnitStatList, FIXED_ONE } from '../src/game/engine/stat-list.ts'
import { StateBus } from '../src/game/engine/state-bus.ts'
import { OVERLAY_METAS } from '../src/render/overlays-meta.ts'
import { MISSILE_METAS } from '../src/render/missiles-meta.ts'
describe('Challenger 1 Stress Suite — Three Sorceress Cold Armors (M1: #040, #050, #060)', () => {
// =========================================================================
// Dimension 1: Exhaustive Mathematical Boundary Sweeps (slvl 1..99 x syn 0..40)
// =========================================================================
describe('Dimension 1: Exhaustive Mathematical Boundary Sweeps & Monotonicity', () => {
describe('Frozen Armor (Skill 40)', () => {
it('evaluates % Enhanced Defense across slvl 1..99 with strict monotonicity and zero NaN', () => {
let prevDef = 0
for (let slvl = 1; slvl <= 99; slvl++) {
const def = calculateFrozenArmorDefense(slvl)
expect(Number.isFinite(def)).toBe(true)
expect(Number.isNaN(def)).toBe(false)
expect(def).toBeGreaterThan(0)
// 1.13c formula: 30 + (slvl - 1) * 5
expect(def).toBe(30 + (slvl - 1) * 5)
expect(def).toBeGreaterThan(prevDef)
prevDef = def
}
expect(calculateFrozenArmorDefense(1)).toBe(30)
expect(calculateFrozenArmorDefense(10)).toBe(75)
expect(calculateFrozenArmorDefense(20)).toBe(125)
expect(calculateFrozenArmorDefense(99)).toBe(520)
})
it('evaluates Duration across slvl 1..99 and syn 0..40 with strict monotonicity and zero NaN', () => {
// Sweep 99 * 41 = 4059 test cases
for (let syn = 0; syn <= 40; syn++) {
let prevDur = 0
for (let slvl = 1; slvl <= 99; slvl++) {
const dur = calculateFrozenArmorDuration(slvl, syn)
expect(Number.isFinite(dur)).toBe(true)
expect(Number.isNaN(dur)).toBe(false)
expect(dur).toBeGreaterThan(0)
// 1.13c formula: 3000 + (slvl - 1) * 300 + syn * 250
const expected = 3000 + (slvl - 1) * 300 + syn * 250
expect(dur).toBe(expected)
expect(dur).toBeGreaterThan(prevDur)
prevDur = dur
}
}
// Synergy monotonicity at fixed slvl
for (let slvl = 1; slvl <= 20; slvl++) {
let prevDur = 0
for (let syn = 0; syn <= 40; syn++) {
const dur = calculateFrozenArmorDuration(slvl, syn)
expect(dur).toBeGreaterThan(prevDur)
prevDur = dur
}
}
// Spot-checks
expect(calculateFrozenArmorDuration(1, 0)).toBe(3000) // 120s
expect(calculateFrozenArmorDuration(10, 0)).toBe(5700) // 228s
expect(calculateFrozenArmorDuration(20, 0)).toBe(8700) // 348s
expect(calculateFrozenArmorDuration(20, 40)).toBe(18700) // 748s
expect(calculateFrozenArmorDuration(99, 40)).toBe(42400) // 1696s
})
it('evaluates Freeze Length across slvl 1..99 and syn 0..40 with monotonicity and zero NaN', () => {
for (let syn = 0; syn <= 40; syn++) {
let prevFreeze = 0
for (let slvl = 1; slvl <= 99; slvl++) {
const frz = calculateFrozenArmorFreezeLength(slvl, syn)
expect(Number.isFinite(frz)).toBe(true)
expect(Number.isNaN(frz)).toBe(false)
expect(frz).toBeGreaterThan(0)
// 1.13c formula: Math.floor((30 + (slvl - 1) * 3) * (100 + syn * 5) / 100)
const expected = Math.floor((30 + (slvl - 1) * 3) * (100 + syn * 5) / 100)
expect(frz).toBe(expected)
expect(frz).toBeGreaterThan(prevFreeze)
prevFreeze = frz
}
}
// Synergy monotonicity at fixed slvl
for (let slvl = 1; slvl <= 20; slvl++) {
let prevFreeze = 0
for (let syn = 0; syn <= 40; syn++) {
const frz = calculateFrozenArmorFreezeLength(slvl, syn)
expect(frz).toBeGreaterThanOrEqual(prevFreeze)
prevFreeze = frz
}
}
// Spot-checks
expect(calculateFrozenArmorFreezeLength(1, 0)).toBe(30) // 1.20s
expect(calculateFrozenArmorFreezeLength(10, 0)).toBe(57) // 2.28s
expect(calculateFrozenArmorFreezeLength(20, 0)).toBe(87) // 3.48s
expect(calculateFrozenArmorFreezeLength(20, 40)).toBe(261) // 10.44s
expect(calculateFrozenArmorFreezeLength(99, 40)).toBe(972) // 38.88s
})
it('asserts Frozen Armor deals strictly 0 damage across slvl 1..99', () => {
for (let slvl = 1; slvl <= 99; slvl++) {
const dmg = calculateSkillDamage(40, slvl)
expect(dmg).toEqual({ min: 0, max: 0 })
}
})
})
describe('Shiver Armor (Skill 50)', () => {
it('evaluates % Enhanced Defense across slvl 1..99 with strict monotonicity and zero NaN', () => {
let prevDef = 0
for (let slvl = 1; slvl <= 99; slvl++) {
const def = calculateShiverArmorDefense(slvl)
expect(Number.isFinite(def)).toBe(true)
expect(Number.isNaN(def)).toBe(false)
expect(def).toBeGreaterThan(0)
// 1.13c formula: 45 + (slvl - 1) * 6
expect(def).toBe(45 + (slvl - 1) * 6)
expect(def).toBeGreaterThan(prevDef)
prevDef = def
}
expect(calculateShiverArmorDefense(1)).toBe(45)
expect(calculateShiverArmorDefense(10)).toBe(99)
expect(calculateShiverArmorDefense(20)).toBe(159)
expect(calculateShiverArmorDefense(99)).toBe(633)
})
it('evaluates Duration across slvl 1..99 and syn 0..40 with strict monotonicity and zero NaN', () => {
for (let syn = 0; syn <= 40; syn++) {
let prevDur = 0
for (let slvl = 1; slvl <= 99; slvl++) {
const dur = calculateShiverArmorDuration(slvl, syn)
expect(Number.isFinite(dur)).toBe(true)
expect(Number.isNaN(dur)).toBe(false)
expect(dur).toBeGreaterThan(0)
const expected = 3000 + (slvl - 1) * 300 + syn * 250
expect(dur).toBe(expected)
expect(dur).toBeGreaterThan(prevDur)
prevDur = dur
}
}
expect(calculateShiverArmorDuration(1, 0)).toBe(3000)
expect(calculateShiverArmorDuration(20, 0)).toBe(8700)
expect(calculateShiverArmorDuration(20, 40)).toBe(18700)
})
it('evaluates Cold Damage across slvl 1..99 and syn 0..40 with strict monotonicity and zero NaN', () => {
for (let syn = 0; syn <= 40; syn += 5) {
let prevMin = 0
let prevMax = 0
for (let slvl = 1; slvl <= 99; slvl++) {
const dmg = calculateShiverArmorDamage(slvl, { frozenArmor: syn, chillingArmor: 0 })
expect(Number.isFinite(dmg.min)).toBe(true)
expect(Number.isFinite(dmg.max)).toBe(true)
expect(Number.isNaN(dmg.min)).toBe(false)
expect(Number.isNaN(dmg.max)).toBe(false)
expect(dmg.min).toBeGreaterThan(0)
expect(dmg.max).toBeGreaterThanOrEqual(dmg.min)
expect(dmg.min).toBeGreaterThan(prevMin)
expect(dmg.max).toBeGreaterThan(prevMax)
prevMin = dmg.min
prevMax = dmg.max
}
}
// Synergy monotonicity at fixed slvl
for (let slvl = 1; slvl <= 20; slvl += 5) {
let prevMin = 0
for (let syn = 0; syn <= 40; syn++) {
const dmg = calculateShiverArmorDamage(slvl, syn)
expect(dmg.min).toBeGreaterThanOrEqual(prevMin)
prevMin = dmg.min
}
}
// Authentic 1.13c spot-checks (HitShift = 7 -> divide by 2)
expect(calculateShiverArmorDamage(1, 0)).toEqual({ min: 6, max: 8 })
expect(calculateShiverArmorDamage(10, 0)).toEqual({ min: 26, max: 32.5 })
expect(calculateShiverArmorDamage(20, 0)).toEqual({ min: 60, max: 71.5 })
// +40 syn points via object (+360% -> multiplier 4.6x): 60 * 4.6 = 276.0, 71.5 * 4.6 = 328.9
expect(calculateShiverArmorDamage(20, { frozenArmor: 20, chillingArmor: 20 })).toEqual({ min: 276.0, max: 328.9 })
// +40 syn points via numeric synergy points (40 pts -> 1 + 40*0.09 = 4.6x)
expect(calculateShiverArmorDamage(20, 40)).toEqual({ min: 276.0, max: 328.9 })
// +40 syn points via options.synMultiplier (4.6x)
expect(calculateShiverArmorDamage(20, undefined, { synMultiplier: 4.6 })).toEqual({ min: 276.0, max: 328.9 })
})
it('evaluates Chill Length across slvl 1..99 and syn 0..40 with piecewise monotonicity and zero NaN', () => {
for (let syn = 0; syn <= 40; syn += 10) {
let prevChill = 0
for (let slvl = 1; slvl <= 99; slvl++) {
const chill = calculateShiverArmorChillLength(slvl, syn)
expect(Number.isFinite(chill)).toBe(true)
expect(Number.isNaN(chill)).toBe(false)
expect(chill).toBeGreaterThan(0)
expect(chill).toBeGreaterThanOrEqual(prevChill)
prevChill = chill
}
}
// Level band step checks without synergy:
// slvl 1-8: flat 100 frames
for (let lvl = 1; lvl <= 8; lvl++) {
expect(calculateShiverArmorChillLength(lvl, 0)).toBe(100)
}
// slvl 9-16: +25 frames / level
expect(calculateShiverArmorChillLength(9, 0)).toBe(125)
expect(calculateShiverArmorChillLength(10, 0)).toBe(150)
expect(calculateShiverArmorChillLength(16, 0)).toBe(300)
// slvl 17+: +50 frames / level
expect(calculateShiverArmorChillLength(17, 0)).toBe(350)
expect(calculateShiverArmorChillLength(20, 0)).toBe(500)
expect(calculateShiverArmorChillLength(99, 0)).toBe(4450)
// With +40 syn (+280% factor = 3.8x): 500 * 3.8 = 1900
expect(calculateShiverArmorChillLength(20, 40)).toBe(1900)
expect(calculateShiverArmorChillLength(20, { frozenArmor: 20, chillingArmor: 20 })).toBe(1900)
})
})
describe('Chilling Armor (Skill 60)', () => {
it('evaluates % Enhanced Defense across slvl 1..99 with strict monotonicity and zero NaN', () => {
let prevDef = 0
for (let slvl = 1; slvl <= 99; slvl++) {
const def = calculateChillingArmorDefense(slvl)
expect(Number.isFinite(def)).toBe(true)
expect(Number.isNaN(def)).toBe(false)
expect(def).toBeGreaterThan(0)
expect(def).toBe(45 + (slvl - 1) * 5)
expect(def).toBeGreaterThan(prevDef)
prevDef = def
}
expect(calculateChillingArmorDefense(1)).toBe(45)
expect(calculateChillingArmorDefense(10)).toBe(90)
expect(calculateChillingArmorDefense(20)).toBe(140)
expect(calculateChillingArmorDefense(99)).toBe(535)
})
it('evaluates Duration across slvl 1..99 and syn 0..40 with strict monotonicity and zero NaN', () => {
for (let syn = 0; syn <= 40; syn++) {
let prevDur = 0
for (let slvl = 1; slvl <= 99; slvl++) {
const dur = calculateChillingArmorDuration(slvl, syn)
expect(Number.isFinite(dur)).toBe(true)
expect(Number.isNaN(dur)).toBe(false)
expect(dur).toBeGreaterThan(0)
const expected = 3600 + (slvl - 1) * 150 + syn * 250
expect(dur).toBe(expected)
expect(dur).toBeGreaterThan(prevDur)
prevDur = dur
}
}
expect(calculateChillingArmorDuration(1, 0)).toBe(3600) // 144s
expect(calculateChillingArmorDuration(10, 0)).toBe(4950) // 198s
expect(calculateChillingArmorDuration(20, 0)).toBe(6450) // 258s
expect(calculateChillingArmorDuration(20, 40)).toBe(16450) // 658s
expect(calculateChillingArmorDuration(99, 40)).toBe(28300) // 1132s
})
it('evaluates Retaliatory Bolt Cold Damage across slvl 1..99 and syn 0..40 with monotonicity and zero NaN', () => {
for (let syn = 0; syn <= 40; syn += 5) {
let prevMin = 0
let prevMax = 0
for (let slvl = 1; slvl <= 99; slvl++) {
const dmg = calculateChillingArmorDamage(slvl, { frozenArmor: syn, shiverArmor: 0 })
expect(Number.isFinite(dmg.min)).toBe(true)
expect(Number.isFinite(dmg.max)).toBe(true)
expect(Number.isNaN(dmg.min)).toBe(false)
expect(Number.isNaN(dmg.max)).toBe(false)
expect(dmg.min).toBeGreaterThan(0)
expect(dmg.max).toBeGreaterThanOrEqual(dmg.min)
expect(dmg.min).toBeGreaterThan(prevMin)
expect(dmg.max).toBeGreaterThan(prevMax)
prevMin = dmg.min
prevMax = dmg.max
}
}
// Spot-checks
expect(calculateChillingArmorDamage(1, 0)).toEqual({ min: 4, max: 6 })
expect(calculateChillingArmorDamage(10, 0)).toEqual({ min: 15, max: 21.5 })
expect(calculateChillingArmorDamage(20, 0)).toEqual({ min: 39, max: 50.5 })
// +40 syn points via object (+280% -> multiplier 3.8x): 39 * 3.8 = 148.2, 50.5 * 3.8 = 191.9
expect(calculateChillingArmorDamage(20, { frozenArmor: 20, shiverArmor: 20 })).toEqual({ min: 148.2, max: 191.9 })
// +40 syn points via numeric synergy points (40 pts -> 1 + 40*0.07 = 3.8x)
expect(calculateChillingArmorDamage(20, 40)).toEqual({ min: 148.2, max: 191.9 })
// +40 syn points via options.synMultiplier (3.8x)
expect(calculateChillingArmorDamage(20, undefined, { synMultiplier: 3.8 })).toEqual({ min: 148.2, max: 191.9 })
})
it('evaluates Chill Length across slvl 1..99 and syn 0..40 with piecewise monotonicity and zero NaN', () => {
for (let syn = 0; syn <= 40; syn += 10) {
let prevChill = 0
for (let slvl = 1; slvl <= 99; slvl++) {
const chill = calculateChillingArmorChillLength(slvl, syn)
expect(Number.isFinite(chill)).toBe(true)
expect(Number.isNaN(chill)).toBe(false)
expect(chill).toBeGreaterThan(0)
expect(chill).toBeGreaterThanOrEqual(prevChill)
prevChill = chill
}
}
// Band step checks without synergy:
for (let lvl = 1; lvl <= 8; lvl++) {
expect(calculateChillingArmorChillLength(lvl, 0)).toBe(100)
}
expect(calculateChillingArmorChillLength(9, 0)).toBe(125)
expect(calculateChillingArmorChillLength(10, 0)).toBe(150)
expect(calculateChillingArmorChillLength(16, 0)).toBe(300)
// slvl 17+: +25 frames / level
expect(calculateChillingArmorChillLength(17, 0)).toBe(325)
expect(calculateChillingArmorChillLength(20, 0)).toBe(400)
expect(calculateChillingArmorChillLength(99, 0)).toBe(2375)
// With +40 syn (+400% factor = 5.0x): 400 * 5.0 = 2000
expect(calculateChillingArmorChillLength(20, 40)).toBe(2000)
expect(calculateChillingArmorChillLength(20, { frozenArmor: 20, shiverArmor: 20 })).toBe(2000)
})
})
})
// =========================================================================
// Dimension 2: Degenerate, Extreme & Adversarial Inputs
// =========================================================================
describe('Dimension 2: Degenerate, Extreme & Adversarial Inputs', () => {
it('clamps negative, zero, and non-integer slvl gracefully without throwing or returning NaN', () => {
const dirtyLvls = [0, -1, -99, 0.5, 12.8, NaN, Infinity, -Infinity]
for (const slvl of dirtyLvls) {
// Frozen Armor
const fDef = calculateFrozenArmorDefense(slvl)
const fDur = calculateFrozenArmorDuration(slvl)
const fFrz = calculateFrozenArmorFreezeLength(slvl)
expect(Number.isFinite(fDef)).toBe(true)
expect(Number.isFinite(fDur)).toBe(true)
expect(Number.isFinite(fFrz)).toBe(true)
// Shiver Armor
const sDef = calculateShiverArmorDefense(slvl)
const sDur = calculateShiverArmorDuration(slvl)
const sDmg = calculateShiverArmorDamage(slvl)
const sChl = calculateShiverArmorChillLength(slvl)
expect(Number.isFinite(sDef)).toBe(true)
expect(Number.isFinite(sDur)).toBe(true)
expect(Number.isFinite(sDmg.min)).toBe(true)
expect(Number.isFinite(sDmg.max)).toBe(true)
expect(Number.isFinite(sChl)).toBe(true)
// Chilling Armor
const cDef = calculateChillingArmorDefense(slvl)
const cDur = calculateChillingArmorDuration(slvl)
const cDmg = calculateChillingArmorDamage(slvl)
const cChl = calculateChillingArmorChillLength(slvl)
expect(Number.isFinite(cDef)).toBe(true)
expect(Number.isFinite(cDur)).toBe(true)
expect(Number.isFinite(cDmg.min)).toBe(true)
expect(Number.isFinite(cDmg.max)).toBe(true)
expect(Number.isFinite(cChl)).toBe(true)
}
})
it('handles degenerate synergy input shapes (undefined, empty, invalid objects)', () => {
const benignSynergies = [undefined, {}, { fake: 99 }, null as any]
for (const syn of benignSynergies) {
expect(() => calculateFrozenArmorDuration(20, syn)).not.toThrow()
expect(() => calculateShiverArmorDamage(20, syn)).not.toThrow()
expect(() => calculateChillingArmorDamage(20, syn)).not.toThrow()
expect(calculateFrozenArmorDuration(20, syn)).toBe(8700)
expect(calculateShiverArmorDamage(20, syn)).toEqual({ min: 60, max: 71.5 })
expect(calculateChillingArmorDamage(20, syn)).toEqual({ min: 39, max: 50.5 })
}
// Damage functions safely default to multiplier 1.0 when negative or non-finite numbers are passed
expect(calculateShiverArmorDamage(20, -10)).toEqual({ min: 60, max: 71.5 })
expect(calculateShiverArmorDamage(20, NaN)).toEqual({ min: 60, max: 71.5 })
expect(calculateShiverArmorDamage(20, Infinity)).toEqual({ min: 60, max: 71.5 })
expect(calculateChillingArmorDamage(20, -10)).toEqual({ min: 39, max: 50.5 })
expect(calculateChillingArmorDamage(20, NaN)).toEqual({ min: 39, max: 50.5 })
expect(calculateChillingArmorDamage(20, Infinity)).toEqual({ min: 39, max: 50.5 })
})
})
// =========================================================================
// Dimension 3: State Group 1 Mutual Exclusion Stress Testing
// =========================================================================
describe('Dimension 3: State Group 1 Mutual Exclusion Stress Testing', () => {
it('strictly maintains exactly 1 active Cold Armor during rapid alternating casts', async () => {
const registry = await getSharedDataRegistry()
const stats = new UnitStatList(registry, { maxhp: 1000 * FIXED_ONE, hitpoints: 1000 * FIXED_ONE })
const bus = new StateBus(stats, registry)
const armors: { name: string; id: number }[] = [
{ name: 'frozenarmor', id: 40 },
{ name: 'shiverarmor', id: 50 },
{ name: 'chillingarmor', id: 60 },
]
// 30 cycles of Frozen -> Shiver -> Chilling (90 casts total)
for (let cycle = 0; cycle < 30; cycle++) {
for (const armor of armors) {
const res = bus.applyState({
stateNameOrId: armor.name,
sourceSkillId: armor.id,
slvl: 20,
stats: { skill_armor_percent: 100 + armor.id },
})
expect(res.applied).toBe(true)
// Invariant 1: Exactly 1 state with group === 1
const activeGroup1 = bus.getActiveEntries().filter(e => e.group === 1)
expect(activeGroup1.length).toBe(1)
expect(activeGroup1[0]!.stateName).toBe(armor.name)
// Invariant 2: hasState confirms only current armor is active
for (const other of armors) {
if (other.name === armor.name) {
expect(bus.hasState(other.name)).toBe(true)
} else {
expect(bus.hasState(other.name)).toBe(false)
}
}
// Invariant 3: statList modifiers from previous cold armor are completely removed
const activeMods = stats.getActiveModifiers()
const armorMods = activeMods.filter(m => armors.some(a => a.name === m.stateName))
expect(armorMods.length).toBe(1)
expect(armorMods[0]!.stateName).toBe(armor.name)
expect(armorMods[0]!.stats['skill_armor_percent']).toBe(100 + armor.id)
}
}
})
it('preserves single-state invariant across 100 randomized rapid casting bursts', async () => {
const registry = await getSharedDataRegistry()
const stats = new UnitStatList(registry, { maxhp: 1000 * FIXED_ONE, hitpoints: 1000 * FIXED_ONE })
const bus = new StateBus(stats, registry)
const armors = ['frozenarmor', 'shiverarmor', 'chillingarmor']
let lastCast = ''
for (let i = 0; i < 100; i++) {
const pick = armors[Math.floor(Math.random() * armors.length)]!
bus.applyState({
stateNameOrId: pick,
slvl: 10 + (i % 20),
})
lastCast = pick
const group1Entries = bus.getActiveEntries().filter(e => e.group === 1)
expect(group1Entries.length).toBe(1)
expect(group1Entries[0]!.stateName).toBe(lastCast)
}
})
it('enforces cross-level eviction: lower-slvl different armor evicts higher-slvl armor', async () => {
const registry = await getSharedDataRegistry()
const stats = new UnitStatList(registry, { maxhp: 1000 * FIXED_ONE, hitpoints: 1000 * FIXED_ONE })
const bus = new StateBus(stats, registry)
// Start with Chilling Armor slvl 50
bus.applyState({ stateNameOrId: 'chillingarmor', sourceSkillId: 60, slvl: 50 })
expect(bus.hasState('chillingarmor')).toBe(true)
expect(bus.getState('chillingarmor')?.slvl).toBe(50)
// Cast Frozen Armor slvl 1 -> must EVIC Chilling Armor
bus.applyState({ stateNameOrId: 'frozenarmor', sourceSkillId: 40, slvl: 1 })
expect(bus.hasState('chillingarmor')).toBe(false)
expect(bus.hasState('frozenarmor')).toBe(true)
expect(bus.getState('frozenarmor')?.slvl).toBe(1)
// Cast Shiver Armor slvl 1 -> must EVICT Frozen Armor
bus.applyState({ stateNameOrId: 'shiverarmor', sourceSkillId: 50, slvl: 1 })
expect(bus.hasState('frozenarmor')).toBe(false)
expect(bus.hasState('shiverarmor')).toBe(true)
expect(bus.getState('shiverarmor')?.slvl).toBe(1)
})
it('enforces same-armor level rules: higher slvl overwrites; lower slvl is rejected', async () => {
const registry = await getSharedDataRegistry()
const stats = new UnitStatList(registry, { maxhp: 1000 * FIXED_ONE, hitpoints: 1000 * FIXED_ONE })
const bus = new StateBus(stats, registry)
// Apply Frozen Armor slvl 20 with 3000 frames duration
bus.applyState({ stateNameOrId: 'frozenarmor', sourceSkillId: 40, slvl: 20, durationFrames: 3000 })
expect(bus.getState('frozenarmor')?.slvl).toBe(20)
// Attempt to cast Frozen Armor slvl 5 while slvl 20 is active -> rejected
const lowerRes = bus.applyState({
stateNameOrId: 'frozenarmor',
sourceSkillId: 40,
slvl: 5,
durationFrames: 3000,
})
expect(lowerRes.applied).toBe(false)
expect(bus.getState('frozenarmor')?.slvl).toBe(20)
// Cast Frozen Armor slvl 30 -> accepted and upgraded
const higherRes = bus.applyState({
stateNameOrId: 'frozenarmor',
sourceSkillId: 40,
slvl: 30,
durationFrames: 6000,
})
expect(higherRes.applied).toBe(true)
expect(bus.getState('frozenarmor')?.slvl).toBe(30)
})
it('expires cold armor cleanly on clock tick and clears group 1 state', async () => {
const registry = await getSharedDataRegistry()
const stats = new UnitStatList(registry, { maxhp: 1000 * FIXED_ONE, hitpoints: 1000 * FIXED_ONE })
const bus = new StateBus(stats, registry)
bus.applyState({
stateNameOrId: 'frozenarmor',
sourceSkillId: 40,
slvl: 1,
durationFrames: 3000,
})
expect(bus.hasState('frozenarmor')).toBe(true)
// Advance frame clock past 3000
const expired = bus.tick(3001)
expect(expired).toContain('frozenarmor')
expect(bus.hasState('frozenarmor')).toBe(false)
expect(bus.getActiveEntries().filter(e => e.group === 1).length).toBe(0)
})
})
// =========================================================================
// Dimension 4: Reactive Trigger Adversarial Edge Cases
// =========================================================================
describe('Dimension 4: Reactive Trigger Adversarial Edge Cases', () => {
describe('Frozen Armor (Skill 40) Edge Cases', () => {
it('freezes attacker when damagedinmelee with physical damage > 0', async () => {
const registry = await getSharedDataRegistry()
const defenderStats = new UnitStatList(registry, { maxhp: 1000 * FIXED_ONE, hitpoints: 1000 * FIXED_ONE })
const attackerStats = new UnitStatList(registry, { maxhp: 500 * FIXED_ONE, hitpoints: 500 * FIXED_ONE })
const defenderBus = new StateBus(defenderStats, registry)
const attackerBus = new StateBus(attackerStats, registry)
defenderBus.applyState({ stateNameOrId: 'frozenarmor', sourceSkillId: 40, slvl: 20 })
const outcome = defenderBus.triggerReactiveEvents({
event: 'damagedinmelee',
incomingPhys256: 100 * FIXED_ONE,
attackerStateBus: attackerBus,
})
expect(outcome.attackerStatesApplied).toContain('freeze')
expect(attackerBus.hasState('freeze')).toBe(true)
// Frozen armor deals 0 damage to attacker
expect(outcome.attackerColdDamage256).toBe(0)
expect(outcome.reflectedPhys256).toBe(0)
})
it('does NOT freeze attacker if physical damage is 0 (e.g. 100% absorbed/miss/blocked)', async () => {
const registry = await getSharedDataRegistry()
const defenderStats = new UnitStatList(registry, { maxhp: 1000 * FIXED_ONE, hitpoints: 1000 * FIXED_ONE })
const attackerStats = new UnitStatList(registry, { maxhp: 500 * FIXED_ONE, hitpoints: 500 * FIXED_ONE })
const defenderBus = new StateBus(defenderStats, registry)
const attackerBus = new StateBus(attackerStats, registry)
defenderBus.applyState({ stateNameOrId: 'frozenarmor', sourceSkillId: 40, slvl: 20 })
const outcome = defenderBus.triggerReactiveEvents({
event: 'damagedinmelee',
incomingPhys256: 0,
attackerStateBus: attackerBus,
})
expect(outcome.attackerStatesApplied).not.toContain('freeze')
expect(attackerBus.hasState('freeze')).toBe(false)
})
it('does NOT freeze attacker if melee damage is 100% elemental with 0 physical', async () => {
const registry = await getSharedDataRegistry()
const defenderStats = new UnitStatList(registry, { maxhp: 1000 * FIXED_ONE, hitpoints: 1000 * FIXED_ONE })
const attackerStats = new UnitStatList(registry, { maxhp: 500 * FIXED_ONE, hitpoints: 500 * FIXED_ONE })
const defenderBus = new StateBus(defenderStats, registry)
const attackerBus = new StateBus(attackerStats, registry)
defenderBus.applyState({ stateNameOrId: 'frozenarmor', sourceSkillId: 40, slvl: 20 })
const outcome = defenderBus.triggerReactiveEvents({
event: 'damagedinmelee',
incomingPhys256: 0,
incomingElem256: 500 * FIXED_ONE,
attackerStateBus: attackerBus,
})
expect(outcome.attackerStatesApplied).not.toContain('freeze')
expect(attackerBus.hasState('freeze')).toBe(false)
})
it('does NOT trigger Frozen Armor on hitbymissile', async () => {
const registry = await getSharedDataRegistry()
const defenderStats = new UnitStatList(registry, { maxhp: 1000 * FIXED_ONE, hitpoints: 1000 * FIXED_ONE })
const attackerStats = new UnitStatList(registry, { maxhp: 500 * FIXED_ONE, hitpoints: 500 * FIXED_ONE })
const defenderBus = new StateBus(defenderStats, registry)
const attackerBus = new StateBus(attackerStats, registry)
defenderBus.applyState({ stateNameOrId: 'frozenarmor', sourceSkillId: 40, slvl: 20 })
const outcome = defenderBus.triggerReactiveEvents({
event: 'hitbymissile',
incomingPhys256: 200 * FIXED_ONE,
returnFire: true,
attackerStateBus: attackerBus,
})
expect(outcome.attackerStatesApplied).not.toContain('freeze')
expect(attackerBus.hasState('freeze')).toBe(false)
})
})
describe('Shiver Armor (Skill 50) Edge Cases', () => {
it('proactively triggers on attackedinmelee BEFORE hit resolution, even if damage is 0', async () => {
const registry = await getSharedDataRegistry()
const defenderStats = new UnitStatList(registry, { maxhp: 1000 * FIXED_ONE, hitpoints: 1000 * FIXED_ONE })
const attackerStats = new UnitStatList(registry, { maxhp: 500 * FIXED_ONE, hitpoints: 500 * FIXED_ONE })
const defenderBus = new StateBus(defenderStats, registry)
const attackerBus = new StateBus(attackerStats, registry)
defenderBus.applyState({ stateNameOrId: 'shiverarmor', sourceSkillId: 50, slvl: 20 })
// Attacker begins melee swing (attackedinmelee) with 0 incoming damage
const outcome = defenderBus.triggerReactiveEvents({
event: 'attackedinmelee',
incomingPhys256: 0,
attackerStateBus: attackerBus,
})
expect(outcome.attackerStatesApplied).toContain('cold')
expect(attackerBus.hasState('cold')).toBe(true)
expect(outcome.attackerColdDamage256).toBeGreaterThan(0)
})
it('does NOT trigger Shiver Armor on hitbymissile or damagedbymissile', async () => {
const registry = await getSharedDataRegistry()
const defenderStats = new UnitStatList(registry, { maxhp: 1000 * FIXED_ONE, hitpoints: 1000 * FIXED_ONE })
const attackerStats = new UnitStatList(registry, { maxhp: 500 * FIXED_ONE, hitpoints: 500 * FIXED_ONE })
const defenderBus = new StateBus(defenderStats, registry)
const attackerBus = new StateBus(attackerStats, registry)
defenderBus.applyState({ stateNameOrId: 'shiverarmor', sourceSkillId: 50, slvl: 20 })
const out1 = defenderBus.triggerReactiveEvents({
event: 'hitbymissile',
incomingPhys256: 100 * FIXED_ONE,
attackerStateBus: attackerBus,
})
expect(out1.attackerStatesApplied).not.toContain('cold')
expect(out1.attackerColdDamage256).toBe(0)
const out2 = defenderBus.triggerReactiveEvents({
event: 'damagedbymissile',
incomingPhys256: 100 * FIXED_ONE,
attackerStateBus: attackerBus,
})
expect(out2.attackerStatesApplied).not.toContain('cold')
expect(out2.attackerColdDamage256).toBe(0)
})
})
describe('Chilling Armor (Skill 60) Edge Cases', () => {
it('reflects chillingarmorbolt when struck by missile with returnFire: true', async () => {
const registry = await getSharedDataRegistry()
const defenderStats = new UnitStatList(registry, { maxhp: 1000 * FIXED_ONE, hitpoints: 1000 * FIXED_ONE })
const attackerStats = new UnitStatList(registry, { maxhp: 500 * FIXED_ONE, hitpoints: 500 * FIXED_ONE })
const defenderBus = new StateBus(defenderStats, registry)
const attackerBus = new StateBus(attackerStats, registry)
defenderBus.applyState({ stateNameOrId: 'chillingarmor', sourceSkillId: 60, slvl: 20 })
const outcome = defenderBus.triggerReactiveEvents({
event: 'hitbymissile',
incomingPhys256: 100 * FIXED_ONE,
returnFire: true,
attackerStateBus: attackerBus,
})
expect(outcome.spawnedMissiles).toContain('chillingarmorbolt')
})
it('does NOT reflect chillingarmorbolt if incoming missile has returnFire: false', async () => {
const registry = await getSharedDataRegistry()
const defenderStats = new UnitStatList(registry, { maxhp: 1000 * FIXED_ONE, hitpoints: 1000 * FIXED_ONE })
const attackerStats = new UnitStatList(registry, { maxhp: 500 * FIXED_ONE, hitpoints: 500 * FIXED_ONE })
const defenderBus = new StateBus(defenderStats, registry)
const attackerBus = new StateBus(attackerStats, registry)
defenderBus.applyState({ stateNameOrId: 'chillingarmor', sourceSkillId: 60, slvl: 20 })
const outcome = defenderBus.triggerReactiveEvents({
event: 'hitbymissile',
incomingPhys256: 100 * FIXED_ONE,
returnFire: false,
attackerStateBus: attackerBus,
})
expect(outcome.spawnedMissiles).not.toContain('chillingarmorbolt')
})
it('does NOT reflect chillingarmorbolt if returnFire is 0', async () => {
const registry = await getSharedDataRegistry()
const defenderStats = new UnitStatList(registry, { maxhp: 1000 * FIXED_ONE, hitpoints: 1000 * FIXED_ONE })
const defenderBus = new StateBus(defenderStats, registry)
defenderBus.applyState({ stateNameOrId: 'chillingarmor', sourceSkillId: 60, slvl: 20 })
const outcome = defenderBus.triggerReactiveEvents({
event: 'hitbymissile',
incomingPhys256: 100 * FIXED_ONE,
returnFire: 0,
})
expect(outcome.spawnedMissiles).not.toContain('chillingarmorbolt')
})
it('does NOT trigger Chilling Armor on damagedinmelee or attackedinmelee', async () => {
const registry = await getSharedDataRegistry()
const defenderStats = new UnitStatList(registry, { maxhp: 1000 * FIXED_ONE, hitpoints: 1000 * FIXED_ONE })
const defenderBus = new StateBus(defenderStats, registry)
defenderBus.applyState({ stateNameOrId: 'chillingarmor', sourceSkillId: 60, slvl: 20 })
const out1 = defenderBus.triggerReactiveEvents({
event: 'damagedinmelee',
incomingPhys256: 100 * FIXED_ONE,
})
expect(out1.spawnedMissiles).not.toContain('chillingarmorbolt')
const out2 = defenderBus.triggerReactiveEvents({
event: 'attackedinmelee',
incomingPhys256: 100 * FIXED_ONE,
})
expect(out2.spawnedMissiles).not.toContain('chillingarmorbolt')
})
})
})
// =========================================================================
// Dimension 5: Full Invariant & System Integrity Checks
// =========================================================================
describe('Dimension 5: Full Invariant & System Integrity Checks', () => {
it('enforces BATCH1_SKILLS length strictly 38', () => {
expect(Object.keys(BATCH1_SKILLS).length).toBe(38)
})
it('verifies constant mana costs across slvl 1..99 for all three armors', () => {
for (let slvl = 1; slvl <= 99; slvl++) {
expect(getSkillManaCost(40, slvl)).toBe(7)
expect(getSkillManaCost(50, slvl)).toBe(11)
expect(getSkillManaCost(60, slvl)).toBe(17)
}
})
it('verifies zero cooldown ticks across all three armors', () => {
expect(getSkillCooldownTicks(40)).toBe(0)
expect(getSkillCooldownTicks(50)).toBe(0)
expect(getSkillCooldownTicks(60)).toBe(0)
})
it('verifies calculateSkillDamage routing consistency', () => {
for (let slvl = 1; slvl <= 30; slvl++) {
// Frozen Armor: always 0
expect(calculateSkillDamage(40, slvl)).toEqual({ min: 0, max: 0 })
// Shiver Armor: matches calculateShiverArmorDamage
const expectedShiver = calculateShiverArmorDamage(slvl)
expect(calculateSkillDamage(50, slvl)).toEqual(expectedShiver)
// Chilling Armor: matches calculateChillingArmorDamage
const expectedChilling = calculateChillingArmorDamage(slvl)
expect(calculateSkillDamage(60, slvl)).toEqual(expectedChilling)
}
})
it('verifies overlay and missile registrations', () => {
expect(OVERLAY_METAS['frozenarmor']).toBeDefined()
expect(OVERLAY_METAS['shiverarmor']).toBeDefined()
expect(OVERLAY_METAS['chillingarmor']).toBeDefined()
expect(OVERLAY_METAS['frozenarmor_hit']).toBeDefined()
expect(OVERLAY_METAS['shiverarmor_hit']).toBeDefined()
expect(OVERLAY_METAS['chillingarmor_hit']).toBeDefined()
expect(MISSILE_METAS['chillingarmorbolt']).toBeDefined()
expect(getSkillCastOverlay(40)).toBe('ice_cast_1')
expect(getSkillCastOverlay(50)).toBe('ice_cast_2')
expect(getSkillCastOverlay(60)).toBe('ice_cast_3')
})
})
})