diablo2-web/tests/skills/sor/adv-cold-m1-armors-stress.t...

684 lines
31 KiB
TypeScript

/**
* Adversarial Test Coverage & Boundary Stress Suite — Milestone 1: Three Sorceress Cold Armors
* Skills: #040 Frozen Armor, #050 Shiver Armor, #060 Chilling Armor
*
* Playbook: test_coverage_audit (Omega Teamwork)
* Mode: Whitebox Adversarial Verification
*
* Focus Areas:
* 1. 7-Dimensional Full-Spectrum Coverage Audit:
* - D1: 1.13c Data Table Parity (Skills.txt, Missiles.txt, States.txt, Overlay.txt)
* - D2: Baked Visual Assets & Zero-Runtime MPQ Invariance
* - D3: Kinematics & Physics Simulation
* - D4: Collision, Damage Filtering & Obstacle Handling
* - D5: Synergy & Mastery Calculations (1.13c blvl hard points rule)
* - D6: Spellcasting Lifecycle & Resource Management (Mana, Cooldown, Group 1 Mutual Eviction)
* - D7: Regression & WorldArena Invariants (BATCH1_SKILLS length 38)
*
* 2. Deep Adversarial Probes:
* - Probe A: Infinite Reflection Loop Protection (Chilling Armor vs Chilling Armor, returnFire=false)
* - Probe B: Mid-Duration Armor Switching & Lingering Effect Eviction (Defense % clearing, Reactive handler replacement)
* - Probe C: Fixed-Point 256-Precision Bitrate Cold Damage & HP Arithmetic (HitShift 7 parity, 256th integer exactness)
*/
import { describe, expect, it } from 'vitest'
import fs from 'node:fs'
import path from 'node:path'
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 { WorldArena } from '../../../src/game/engine/world-arena.ts'
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 {
FROZEN_ARMOR_OVERLAY_META,
FROZEN_ARMOR_HIT_OVERLAY_META,
OVERLAY_METAS,
} from '../../../src/render/overlays-meta.ts'
import {
ICEBOLT_META,
MISSILE_METAS,
} from '../../../src/render/missiles-meta.ts'
describe('Adversarial Test Coverage & Boundary Stress Suite — Milestone 1 Cold Armors (Skills 40, 50, 60)', () => {
const skillsTxtPath = path.resolve(__dirname, '../../../samples/fixtures/data/global/excel/Skills.txt')
const missilesTxtPath = path.resolve(__dirname, '../../../samples/fixtures/data/global/excel/Missiles.txt')
const overlayTxtPath = path.resolve(__dirname, '../../../samples/fixtures/data/global/excel/Overlay.txt')
const statesTxtPath = path.resolve(__dirname, '../../../samples/fixtures/data/global/excel/States.txt')
// =========================================================================
// Dimension 1: 1.13c Data Table Parity & Cross-Table Relational Integrity
// =========================================================================
describe('Dimension 1: Data Table Parity & Cross-Table Relational Integrity', () => {
it('verifies State Group 1 definition in States.txt for all 3 cold armors via registry', async () => {
const registry = await getSharedDataRegistry()
const coldStates = ['frozenarmor', 'shiverarmor', 'chillingarmor']
for (const st of coldStates) {
const stateRec = registry.getStateByName(st)
expect(stateRec, `State ${st} must exist in registry`).toBeDefined()
expect(stateRec!.group, `State ${st} must belong to State Group 1`).toBe(1)
}
})
it('verifies Missiles.txt row 264 (chillingarmorbolt) vs row 59 (icebolt) ReturnFire contract', () => {
const content = fs.readFileSync(missilesTxtPath, 'latin1')
const lines = content.split('\n')
const headers = lines[0]!.split('\t').map(h => h.trim())
const missileIdx = headers.indexOf('Missile')
const idIdx = headers.indexOf('Id')
const returnFireIdx = headers.indexOf('ReturnFire')
const hitShiftIdx = headers.indexOf('HitShift')
const velIdx = headers.indexOf('Vel')
// icebolt (ID 59) allows return fire
const iceboltRow = lines.slice(1).map(l => l.split('\t')).find(r => r[idIdx]?.trim() === '59')
expect(iceboltRow).toBeDefined()
expect(iceboltRow![missileIdx]?.trim()).toBe('icebolt')
expect(Number(iceboltRow![returnFireIdx])).toBe(1)
// chillingarmorbolt (ID 264) forbids return fire to prevent infinite loops
const boltRow = lines.slice(1).map(l => l.split('\t')).find(r => r[idIdx]?.trim() === '264')
expect(boltRow).toBeDefined()
expect(boltRow![missileIdx]?.trim()).toBe('chillingarmorbolt')
const rf = boltRow![returnFireIdx]?.trim()
expect(rf === '' || rf === '0' || rf === undefined).toBe(true)
expect(Number(boltRow![velIdx])).toBe(18) // Faster velocity than base icebolt (12)
expect(Number(boltRow![hitShiftIdx])).toBe(8)
})
it('verifies HitShift contracts across Skills 40, 50, and 60', () => {
const content = fs.readFileSync(skillsTxtPath, 'latin1')
const lines = content.split('\n')
const headers = lines[0]!.split('\t').map(h => h.trim())
const idIdx = headers.indexOf('Id')
const hitShiftIdx = headers.indexOf('HitShift')
const getRow = (id: string) => lines.slice(1).map(l => l.split('\t')).find(r => r[idIdx]?.trim() === id)
// Skill 40 (Frozen Armor): HitShift = 8
expect(Number(getRow('40')![hitShiftIdx])).toBe(8)
// Skill 50 (Shiver Armor): HitShift = 7 -> divide by 2
expect(Number(getRow('50')![hitShiftIdx])).toBe(7)
// Skill 60 (Chilling Armor): HitShift = 7 -> divide by 2
expect(Number(getRow('60')![hitShiftIdx])).toBe(7)
})
})
// =========================================================================
// Dimension 2: Baked Visual Assets & Zero-Runtime MPQ Invariance
// =========================================================================
describe('Dimension 2: Baked Visual Assets & Zero-Runtime MPQ Invariance', () => {
it('verifies pre-baked overlay files have valid PNG magic bytes and JSON sprite atlases', () => {
const publicDir = path.resolve(__dirname, '../../../public')
const assetFiles = [
'overlays/FrozenArmor.png',
'overlays/FrozenArmor.json',
'overlays/AuraResistColdCast.png',
'overlays/AuraResistColdCast.json',
'missiles/icebolt.png',
'missiles/icebolt.json',
]
for (const rel of assetFiles) {
const full = path.join(publicDir, rel)
expect(fs.existsSync(full), `Asset file ${rel} must exist`).toBe(true)
const stat = fs.statSync(full)
expect(stat.size, `Asset file ${rel} must not be empty`).toBeGreaterThan(100)
if (rel.endsWith('.png')) {
const buf = fs.readFileSync(full)
// PNG magic bytes: 0x89 0x50 0x4E 0x47 0x0D 0x0A 0x1A 0x0A
expect(buf[0]).toBe(0x89)
expect(buf[1]).toBe(0x50) // P
expect(buf[2]).toBe(0x4E) // N
expect(buf[3]).toBe(0x47) // G
} else if (rel.endsWith('.json')) {
const content = JSON.parse(fs.readFileSync(full, 'utf8'))
expect(content.frames || content.framesPerDirection || content.directions).toBeTruthy()
}
}
})
it('verifies typed metadata registration in OVERLAY_METAS and MISSILE_METAS', () => {
// Overlays
expect(OVERLAY_METAS['frozenarmor']).toBe(FROZEN_ARMOR_OVERLAY_META)
expect(OVERLAY_METAS['shiverarmor']).toBe(FROZEN_ARMOR_OVERLAY_META)
expect(OVERLAY_METAS['chillingarmor']).toBe(FROZEN_ARMOR_OVERLAY_META)
expect(OVERLAY_METAS['frozenarmor_hit']).toBe(FROZEN_ARMOR_HIT_OVERLAY_META)
expect(OVERLAY_METAS['shiverarmor_hit']).toBe(FROZEN_ARMOR_HIT_OVERLAY_META)
expect(OVERLAY_METAS['chillingarmor_hit']).toBe(FROZEN_ARMOR_HIT_OVERLAY_META)
// Missiles
expect(MISSILE_METAS['chillingarmorbolt']).toBe(ICEBOLT_META)
// Cast overlays
expect(getSkillCastOverlay(40)).toBe('ice_cast_1')
expect(getSkillCastOverlay(50)).toBe('ice_cast_2')
expect(getSkillCastOverlay(60)).toBe('ice_cast_3')
})
})
// =========================================================================
// Dimension 3 & 4: Kinematics, Physics & Damage Filtering Stress
// =========================================================================
describe('Dimension 3 & 4: Kinematics, Physics & Damage Filtering Stress', () => {
it('Frozen Armor: strictly requires physical damage > 0 to freeze', 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 })
// Case 1: 0 phys damage, 1000 fire damage -> NO freeze
let out = defenderBus.triggerReactiveEvents({
event: 'damagedinmelee',
incomingPhys256: 0,
incomingElem256: 1000 * FIXED_ONE,
elemType: 'fire',
attackerStateBus: attackerBus,
})
expect(out.attackerStatesApplied).not.toContain('freeze')
expect(attackerBus.hasState('freeze')).toBe(false)
// Case 2: 1 unit of physical damage (1/256) -> DOES freeze
out = defenderBus.triggerReactiveEvents({
event: 'damagedinmelee',
incomingPhys256: 1, // 1/256th of 1 point of damage
incomingElem256: 0,
attackerStateBus: attackerBus,
})
expect(out.attackerStatesApplied).toContain('freeze')
expect(attackerBus.hasState('freeze')).toBe(true)
})
it('Shiver Armor: triggers proactively on attack declaration regardless of attack success', 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: 10 })
// Attacker initiates swing that deals 0 damage (missed / dodged / blocked)
const out = defenderBus.triggerReactiveEvents({
event: 'attackedinmelee',
incomingPhys256: 0,
attackerStateBus: attackerBus,
})
expect(out.attackerStatesApplied).toContain('cold')
expect(attackerBus.hasState('cold')).toBe(true)
expect(out.attackerColdDamage256).toBeGreaterThan(0)
})
it('Chilling Armor: ignores melee damage and melee attacks completely', 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 })
// 1. Struck in melee with massive damage
const outMelee = defenderBus.triggerReactiveEvents({
event: 'damagedinmelee',
incomingPhys256: 10000 * FIXED_ONE,
attackerStateBus: attackerBus,
})
expect(outMelee.spawnedMissiles.length).toBe(0)
// 2. Attacked in melee
const outAttack = defenderBus.triggerReactiveEvents({
event: 'attackedinmelee',
incomingPhys256: 10000 * FIXED_ONE,
attackerStateBus: attackerBus,
})
expect(outAttack.spawnedMissiles.length).toBe(0)
})
})
// =========================================================================
// Dimension 5: Synergy & 1.13c Blvl Invariant
// =========================================================================
describe('Dimension 5: Synergy & 1.13c Blvl Invariant', () => {
it('strictly enforces hard points rule: +allskills and charges give zero synergy bonus', async () => {
const registry = await getSharedDataRegistry()
const skillRec = registry.getSkillById(50)! // Shiver Armor
// Hard points only (20 in Frozen Armor, 20 in Chilling Armor)
const statsHard = new UnitStatList()
statsHard.setBaseSkillLevel(50, 20)
statsHard.setBaseSkillLevel(40, 20)
statsHard.setBaseSkillLevel(60, 20)
const resHard = skillRec ? skillRec : null
expect(resHard).toBeDefined()
const dmgHard = calculateShiverArmorDamage(20, { frozenArmor: 20, chillingArmor: 20 })
expect(dmgHard.min).toBe(276)
expect(dmgHard.max).toBe(328.9)
// Soft skills (+20 all skills, +33 charged skill levels)
const statsSoft = new UnitStatList()
statsSoft.setBaseSkillLevel(50, 20)
statsSoft.setBonusSkillLevel(40, 20)
statsSoft.setBonusSkillLevel(60, 20)
statsSoft.setChargedSkillLevel(40, 33)
statsSoft.setChargedSkillLevel(60, 33)
// In 1.13c, soft skills must NOT contribute to synergies
const dmgSoft = calculateShiverArmorDamage(20, {
frozenArmor: statsSoft.getBaseSkillLevel(40),
chillingArmor: statsSoft.getBaseSkillLevel(60),
})
// Without hard points, base damage only: 60 - 71.5
expect(dmgSoft.min).toBe(60)
expect(dmgSoft.max).toBe(71.5)
})
it('validates mathematical epsilon parity (+1e-9) prevents IEEE-754 floor truncation', () => {
// 50.5 * 3.8 = 191.89999999999998 in standard JS IEEE-754 floating point arithmetic!
const rawProduct = 50.5 * 3.8
expect(rawProduct).toBeLessThan(191.9)
// Without epsilon, Math.floor(rawProduct * 10) / 10 would erroneously yield 191.8
expect(Math.floor(rawProduct * 10) / 10).toBe(191.8)
// With authentic epsilon guard (+ 1e-9), calculateChillingArmorDamage produces authentic 191.9
const dmg = calculateChillingArmorDamage(20, { frozenArmor: 20, shiverArmor: 20 })
expect(dmg.max).toBe(191.9)
})
})
// =========================================================================
// Dimension 6 & 7: Lifecycle, Resource Management & BATCH1 Invariants
// =========================================================================
describe('Dimension 6 & 7: Lifecycle, Resource Management & BATCH1 Invariants', () => {
it('enforces constant mana costs and zero cooldowns for all 3 armors', () => {
expect(getSkillManaCost(40, 1)).toBe(7)
expect(getSkillManaCost(40, 99)).toBe(7)
expect(getSkillCooldownTicks(40)).toBe(0)
expect(getSkillManaCost(50, 1)).toBe(11)
expect(getSkillManaCost(50, 99)).toBe(11)
expect(getSkillCooldownTicks(50)).toBe(0)
expect(getSkillManaCost(60, 1)).toBe(17)
expect(getSkillManaCost(60, 99)).toBe(17)
expect(getSkillCooldownTicks(60)).toBe(0)
})
it('enforces BATCH1_SKILLS length invariant strictly 38', () => {
expect(Object.keys(BATCH1_SKILLS).length).toBe(38)
})
})
// =========================================================================
// Deep Adversarial Probe A: Infinite Reflection Loop Protection
// =========================================================================
describe('Deep Adversarial Probe A: Infinite Reflection Loop Protection', () => {
it('terminates Chilling Armor retaliation loop after exactly 1 cycle (depth <= 1)', async () => {
const registry = await getSharedDataRegistry()
// Entity A (Player Sorceress) with Chilling Armor
const statsA = new UnitStatList(registry, { maxhp: 1000 * FIXED_ONE, hitpoints: 1000 * FIXED_ONE })
const busA = new StateBus(statsA, registry)
busA.applyState({ stateNameOrId: 'chillingarmor', sourceSkillId: 60, slvl: 20 })
// Entity B (Enemy Sorceress / Monster with Chilling Armor)
const statsB = new UnitStatList(registry, { maxhp: 1000 * FIXED_ONE, hitpoints: 1000 * FIXED_ONE })
const busB = new StateBus(statsB, registry)
busB.applyState({ stateNameOrId: 'chillingarmor', sourceSkillId: 60, slvl: 20 })
// Step 1: An initial arrow (returnFire: true) hits Entity B
const initialHit = busB.triggerReactiveEvents({
event: 'hitbymissile',
incomingPhys256: 100 * FIXED_ONE,
returnFire: true,
attackerStateBus: busA,
})
expect(initialHit.spawnedMissiles).toContain('chillingarmorbolt')
expect(initialHit.spawnedMissiles.length).toBe(1)
// Step 2: The spawned chillingarmorbolt travels and hits Entity A.
// Lookup chillingarmorbolt in data registry:
const boltRec = registry.getMissileByName('chillingarmorbolt')!
expect(boltRec).toBeDefined()
expect(boltRec.returnFire).toBe(false) // Must be false per 1.13c Missiles.txt!
// Step 3: chillingarmorbolt impacts Entity A with returnFire: boltRec.returnFire (false)
const retaliatoryHit = busA.triggerReactiveEvents({
event: 'hitbymissile',
incomingPhys256: 150 * FIXED_ONE,
returnFire: boltRec.returnFire,
attackerStateBus: busB,
})
// Must NOT spawn another missile — loop terminates immediately!
expect(retaliatoryHit.spawnedMissiles.length).toBe(0)
})
it('simulates a 100-cycle ping-pong loop test verifying zero missile explosion cascades', async () => {
const registry = await getSharedDataRegistry()
const statsA = new UnitStatList(registry, { maxhp: 5000 * FIXED_ONE, hitpoints: 5000 * FIXED_ONE })
const statsB = new UnitStatList(registry, { maxhp: 5000 * FIXED_ONE, hitpoints: 5000 * FIXED_ONE })
const busA = new StateBus(statsA, registry)
const busB = new StateBus(statsB, registry)
busA.applyState({ stateNameOrId: 'chillingarmor', sourceSkillId: 60, slvl: 20 })
busB.applyState({ stateNameOrId: 'chillingarmor', sourceSkillId: 60, slvl: 20 })
let totalMissilesSpawned = 0
const boltRec = registry.getMissileByName('chillingarmorbolt')!
// Simulate 100 consecutive incoming missiles with returnFire: false
for (let i = 0; i < 100; i++) {
const outA = busA.triggerReactiveEvents({
event: 'hitbymissile',
incomingPhys256: 50 * FIXED_ONE,
returnFire: boltRec.returnFire, // false
attackerStateBus: busB,
})
totalMissilesSpawned += outA.spawnedMissiles.length
const outB = busB.triggerReactiveEvents({
event: 'hitbymissile',
incomingPhys256: 50 * FIXED_ONE,
returnFire: boltRec.returnFire, // false
attackerStateBus: busA,
})
totalMissilesSpawned += outB.spawnedMissiles.length
}
// Zero missiles must be spawned from non-returnFire impacts
expect(totalMissilesSpawned).toBe(0)
})
it('handles concurrent barrage of 100 missiles with mixed returnFire flags deterministically', async () => {
const registry = await getSharedDataRegistry()
const stats = new UnitStatList(registry, { maxhp: 10000 * FIXED_ONE, hitpoints: 10000 * FIXED_ONE })
const bus = new StateBus(stats, registry)
bus.applyState({ stateNameOrId: 'chillingarmor', sourceSkillId: 60, slvl: 20 })
let retTrueCount = 0
let retFalseCount = 0
let missilesProduced = 0
// 100 mixed missiles: even indices have returnFire: true, odd have false
for (let i = 0; i < 100; i++) {
const rf = i % 2 === 0
if (rf) retTrueCount++
else retFalseCount++
const out = bus.triggerReactiveEvents({
event: 'hitbymissile',
incomingPhys256: 10 * FIXED_ONE,
returnFire: rf,
})
missilesProduced += out.spawnedMissiles.length
}
expect(retTrueCount).toBe(50)
expect(retFalseCount).toBe(50)
expect(missilesProduced).toBe(50) // Exactly 50 retaliations for 50 returnFire: true missiles
})
})
// =========================================================================
// Deep Adversarial Probe B: Mid-Duration Armor Switching & Lingering Effects
// =========================================================================
describe('Deep Adversarial Probe B: Mid-Duration Armor Switching & Lingering Effects', () => {
it('instantly removes previous armor defense modifiers upon switching armors', async () => {
const registry = await getSharedDataRegistry()
const stats = new UnitStatList(registry, { armorclass: 100, maxhp: 1000 * FIXED_ONE, hitpoints: 1000 * FIXED_ONE })
const bus = new StateBus(stats, registry)
// Step 1: Base defense is 100
expect(stats.getAccruedStat('armorclass')).toBe(100)
// Step 2: Cast Frozen Armor (slvl 20) -> +125% defense
bus.applyState({
stateNameOrId: 'frozenarmor',
sourceSkillId: 40,
slvl: 20,
stats: { skill_armor_percent: calculateFrozenArmorDefense(20) }, // 125
})
expect(bus.hasState('frozenarmor')).toBe(true)
expect(stats.getModifierBonus('skill_armor_percent')).toBe(125)
// 100 * (100 + 125) / 100 = 225
expect(stats.getAccruedStat('armorclass')).toBe(225)
// Step 3: Mid-duration switch to Shiver Armor (slvl 1) -> +45% defense
// Must evict Frozen Armor completely: defense must become 145, NOT 100 + 125 + 45 = 270!
bus.applyState({
stateNameOrId: 'shiverarmor',
sourceSkillId: 50,
slvl: 1,
stats: { skill_armor_percent: calculateShiverArmorDefense(1) }, // 45
})
expect(bus.hasState('frozenarmor')).toBe(false)
expect(bus.hasState('shiverarmor')).toBe(true)
expect(stats.getModifierBonus('skill_armor_percent')).toBe(45)
expect(stats.getAccruedStat('armorclass')).toBe(145)
// Step 4: Mid-duration switch to Chilling Armor (slvl 10) -> +90% defense
bus.applyState({
stateNameOrId: 'chillingarmor',
sourceSkillId: 60,
slvl: 10,
stats: { skill_armor_percent: calculateChillingArmorDefense(10) }, // 90
})
expect(bus.hasState('shiverarmor')).toBe(false)
expect(bus.hasState('chillingarmor')).toBe(true)
expect(stats.getModifierBonus('skill_armor_percent')).toBe(90)
expect(stats.getAccruedStat('armorclass')).toBe(190)
// Step 5: Remove armor -> defense resets to baseline 100
bus.removeState('chillingarmor')
expect(bus.hasState('chillingarmor')).toBe(false)
expect(stats.getModifierBonus('skill_armor_percent')).toBe(0)
expect(stats.getAccruedStat('armorclass')).toBe(100)
})
it('ensures no lingering reactive event triggers from previous armors after switching', 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)
// Start with Frozen Armor
defenderBus.applyState({ stateNameOrId: 'frozenarmor', sourceSkillId: 40, slvl: 20 })
// Immediately switch to Chilling Armor (Skill 60)
defenderBus.applyState({ stateNameOrId: 'chillingarmor', sourceSkillId: 60, slvl: 20 })
// Attacker strikes with melee physical damage -> Chilling Armor does NOT react to melee!
// If Frozen Armor lingered, attacker would be frozen solid.
const out = defenderBus.triggerReactiveEvents({
event: 'damagedinmelee',
incomingPhys256: 200 * FIXED_ONE,
attackerStateBus: attackerBus,
})
expect(out.attackerStatesApplied).not.toContain('freeze')
expect(attackerBus.hasState('freeze')).toBe(false)
expect(out.spawnedMissiles.length).toBe(0)
})
it('stress tests 100 rapid armor switches in tight loop verifying zero orphaned state entries', 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: 'frozenarmor', id: 40, def: 125 },
{ name: 'shiverarmor', id: 50, def: 159 },
{ name: 'chillingarmor', id: 60, def: 140 },
]
for (let i = 0; i < 100; i++) {
const armor = armors[i % 3]!
bus.applyState({
stateNameOrId: armor.name,
sourceSkillId: armor.id,
slvl: 20,
stats: { skill_armor_percent: armor.def },
})
// Invariant: at any point, exactly 1 Group 1 armor state can exist in entries
const group1Entries = bus.getActiveEntries().filter(e => e.group === 1 || ['frozenarmor', 'shiverarmor', 'chillingarmor'].includes(e.stateName))
expect(group1Entries.length).toBe(1)
expect(group1Entries[0]!.stateName).toBe(armor.name)
expect(stats.getModifierBonus('skill_armor_percent')).toBe(armor.def)
}
})
})
// =========================================================================
// Deep Adversarial Probe C: Fixed-Point 256-Precision Bitrate Cold Damage
// =========================================================================
describe('Deep Adversarial Probe C: Fixed-Point 256-Precision Bitrate Cold Damage', () => {
it('verifies exact integer 256-bitrate scaling across all 60 skill levels and synergies for Shiver Armor', () => {
// EMin: 12, EMax: 16, HitShift: 7
for (let slvl = 1; slvl <= 60; slvl++) {
for (const synPoints of [0, 10, 20, 40]) {
const dmg = calculateShiverArmorDamage(slvl, synPoints)
expect(dmg.min).toBeGreaterThan(0)
expect(dmg.max).toBeGreaterThanOrEqual(dmg.min)
expect(Number.isFinite(dmg.min)).toBe(true)
expect(Number.isFinite(dmg.max)).toBe(true)
// In 256-bitrate integer arithmetic:
const min256 = Math.round(dmg.min * FIXED_ONE)
const max256 = Math.round(dmg.max * FIXED_ONE)
expect(Number.isInteger(min256)).toBe(true)
expect(Number.isInteger(max256)).toBe(true)
}
}
})
it('verifies exact HitShift 7 bitrate conversion for base Shiver Armor (Skill 50)', () => {
// Level 1: EMin=12, EMax=16. HitShift 7 -> 12 * 128 = 1536 (6.0), 16 * 128 = 2048 (8.0)
const dmg1 = calculateShiverArmorDamage(1, 0)
expect(dmg1.min * 256).toBe(12 << 7)
expect(dmg1.max * 256).toBe(16 << 7)
// Level 10: rawMin = 52, rawMax = 65 -> 52 << 7 = 6656 (26.0), 65 << 7 = 8320 (32.5)
const dmg10 = calculateShiverArmorDamage(10, 0)
expect(dmg10.min * 256).toBe(52 << 7)
expect(dmg10.max * 256).toBe(65 << 7)
// Level 20: rawMin = 120, rawMax = 143 -> 120 << 7 = 15360 (60.0), 143 << 7 = 18304 (71.5)
const dmg20 = calculateShiverArmorDamage(20, 0)
expect(dmg20.min * 256).toBe(120 << 7)
expect(dmg20.max * 256).toBe(143 << 7)
})
it('verifies exact HitShift 7 bitrate conversion for base Chilling Armor (Skill 60)', () => {
// Level 1: EMin=8, EMax=12. HitShift 7 -> 8 * 128 = 1024 (4.0), 12 * 128 = 1536 (6.0)
const dmg1 = calculateChillingArmorDamage(1, 0)
expect(dmg1.min * 256).toBe(8 << 7)
expect(dmg1.max * 256).toBe(12 << 7)
// Level 10: rawMin = 30, rawMax = 43 -> 30 << 7 = 3840 (15.0), 43 << 7 = 5504 (21.5)
const dmg10 = calculateChillingArmorDamage(10, 0)
expect(dmg10.min * 256).toBe(30 << 7)
expect(dmg10.max * 256).toBe(43 << 7)
// Level 20: rawMin = 78, rawMax = 101 -> 78 << 7 = 9984 (39.0), 101 << 7 = 12928 (50.5)
const dmg20 = calculateChillingArmorDamage(20, 0)
expect(dmg20.min * 256).toBe(78 << 7)
expect(dmg20.max * 256).toBe(101 << 7)
})
it('simulates 100 consecutive Shiver Armor hits verifying zero floating point HP drift', async () => {
const registry = await getSharedDataRegistry()
const defenderStats = new UnitStatList(registry, { maxhp: 10000 * FIXED_ONE, hitpoints: 10000 * FIXED_ONE })
const attackerStats = new UnitStatList(registry, { maxhp: 500000 * FIXED_ONE, hitpoints: 500000 * FIXED_ONE })
const defenderBus = new StateBus(defenderStats, registry)
const attackerBus = new StateBus(attackerStats, registry)
defenderBus.applyState({ stateNameOrId: 'shiverarmor', sourceSkillId: 50, slvl: 20 })
let totalDamageDeducted256 = 0
const initialHp256 = attackerStats.getHp256()
for (let i = 0; i < 100; i++) {
const out = defenderBus.triggerReactiveEvents({
event: 'attackedinmelee',
incomingPhys256: 100 * FIXED_ONE,
attackerStateBus: attackerBus,
})
const dmg256 = out.attackerColdDamage256
expect(Number.isInteger(dmg256)).toBe(true)
attackerStats.setHp256(attackerStats.getHp256() - dmg256)
totalDamageDeducted256 += dmg256
}
// Assert HP arithmetic is exact to the 256th bit
expect(attackerStats.getHp256()).toBe(initialHp256 - totalDamageDeducted256)
})
})
// =========================================================================
// Fuzzing & Degenerate Input Boundary Testing
// =========================================================================
describe('Fuzzing & Degenerate Input Boundary Testing', () => {
it('gracefully handles boundary and degenerate slvl values without crashes or NaN', () => {
const degenerateSlvls = [-10, 0, 0.5, 1, 99, 500, NaN, Infinity, -Infinity]
for (const slvl of degenerateSlvls) {
expect(() => calculateFrozenArmorDefense(slvl)).not.toThrow()
expect(() => calculateFrozenArmorDuration(slvl)).not.toThrow()
expect(() => calculateFrozenArmorFreezeLength(slvl)).not.toThrow()
expect(() => calculateShiverArmorDefense(slvl)).not.toThrow()
expect(() => calculateShiverArmorDuration(slvl)).not.toThrow()
expect(() => calculateShiverArmorDamage(slvl)).not.toThrow()
expect(() => calculateShiverArmorChillLength(slvl)).not.toThrow()
expect(() => calculateChillingArmorDefense(slvl)).not.toThrow()
expect(() => calculateChillingArmorDuration(slvl)).not.toThrow()
expect(() => calculateChillingArmorDamage(slvl)).not.toThrow()
expect(() => calculateChillingArmorChillLength(slvl)).not.toThrow()
const dmgS = calculateShiverArmorDamage(slvl)
expect(Number.isFinite(dmgS.min)).toBe(true)
expect(Number.isFinite(dmgS.max)).toBe(true)
const dmgC = calculateChillingArmorDamage(slvl)
expect(Number.isFinite(dmgC.min)).toBe(true)
expect(Number.isFinite(dmgC.max)).toBe(true)
}
})
it('gracefully handles degenerate synergy inputs', () => {
const degenerateSyns = [-50, 0, 1000, NaN, undefined, null, {} as any]
for (const syn of degenerateSyns) {
expect(() => calculateShiverArmorDamage(20, syn)).not.toThrow()
expect(() => calculateChillingArmorDamage(20, syn)).not.toThrow()
expect(() => calculateFrozenArmorDuration(20, syn)).not.toThrow()
expect(() => calculateFrozenArmorFreezeLength(20, syn)).not.toThrow()
}
})
})
})