/** * Diablo II: Lord of Destruction v1.13c — Sorceress Cross-Tree E2E Integration Stress Test Suite * Milestone 5: Cold Tree & Fire Tree Cross-Skill Mechanics and Synergies * * Ground Truth References: * - 1.13c Assembly: D2Game.dll (SUnitDmg.cpp, SkillSor.cpp), D2Common.dll (D2StatListStrc) * - Authentic Data Tables: Skills.txt, Missiles.txt, States.txt * * Core Verification Dimensions: * 1. Simultaneous Buff Stacking: * - Cold Armors (Frozen Armor #40, Shiver Armor #50, Chilling Armor #60) + Enchant (#52). * - State Group 1 mutual exclusion among Cold Armors (switching replaces Cold Armor). * - Independent coexistence: Enchant persists across Cold Armor replacements without stat clobbering. * 2. Dual-Tree Sorceress Combat: * - Dual mastery scaling: Cold Mastery (#65) pierces enemy cold resistance. * - Fire Mastery (#61) amplifies outgoing fire damage without piercing enemy fire resistance. * - Melee attacks benefit from Enchant + Fire Mastery double-dip, while Glacial Spike cold spell pierces res. * 3. Dual Immunity Stress: * - Monsters with natural dual immunities (Cold Resist >= 100, Fire Resist >= 100). * - Neither Cold Mastery nor Fire Mastery can break immunities on their own. * - Net elemental damage is strictly 0; physical damage component penetrates normally. * 4. Resistance Floor Clamping & Immunity-Breaking: * - Conviction (#123) / Lower Resist (#91) 1/5th efficiency rule against immune targets. * - Broken immunities allow full Cold Mastery pierce power. * - Strict -100% resistance floor clamp (effectiveRes >= -100) under extreme reductions. * 5. Ground & Stream Coexistence: * - Running with Blaze (#46) footstep trail while Fire Wall (#51) burns on the ground. * - Ice Blast (#45) or Glacial Spike (#55) cast through active fire patches. * - Independent projectile kinematics, tick timers, and crowd-control synergy (freeze in fire). */ import { describe, expect, it } from 'vitest' 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 { calculateFrozenArmorDefense, calculateFrozenArmorDuration, calculateFrozenArmorFreezeLength, calculateShiverArmorDefense, calculateShiverArmorDuration, calculateShiverArmorDamage, calculateShiverArmorChillLength, calculateChillingArmorDefense, calculateChillingArmorDuration, calculateChillingArmorDamage, calculateChillingArmorChillLength, calculateIceBlastDamage, calculateIceBlastFreezeLength, calculateGlacialSpikeDamage, calculateGlacialSpikeFreezeLength, calculateColdMasteryPierce, calculateFireWallDamage, calculateFireWallPatches, calculateBlazeDamage, calculateBlazeDuration, calculateBlazePatchDuration, calculateEnchantDamage, calculateEnchantAttackRating, calculateEnchantDuration, calculateFireMasteryBonus, calculateEnchantAttackDamage, BATCH1_SKILLS, tickProjectiles, ISO_GROUND_ASPECT_RATIO, type Projectile, type ProjectileTarget, } from '../../../src/game/skills.ts' import { createWorld, addPlayer, tickCombat, applyEnchantBuff, type CombatPlayer, type CombatWorld, type Monster, type CombatOptions, type CombatTerrain, } from '../../../src/game/combat.ts' import { executeSUnitDmg, computeEffectiveResistance, type CombatUnitContext, type SUnitDmgPacket, } from '../../../src/game/engine/combat-pipeline.ts' import { GameEngine } from '../../../src/game/engine.ts' /** Helper to create a fully initialized CombatUnitContext */ function createMockUnit( id: string, options?: { hp?: number coldResist?: number fireResist?: number isSorceress?: boolean coldMasterySlvl?: number fireMasterySlvl?: number hasEnchantState?: boolean hasFrozenArmorState?: boolean hasShiverArmorState?: boolean hasChillingArmorState?: boolean armorSlvl?: number weaponMinPhys?: number weaponMaxPhys?: number }, ): CombatUnitContext { const statList = new UnitStatList() const hp = options?.hp ?? 2000 statList.setBaseStat('hp', hp * FIXED_ONE) statList.setBaseStat('maxhp', hp * FIXED_ONE) statList.setBaseStat('hitpoints', hp * FIXED_ONE) statList.setBaseStat('level', 85) statList.setBaseStat('tohit', 5000) statList.setBaseStat('armorclass', 200) if (options?.coldResist !== undefined) { statList.setBaseStat('coldresist', options.coldResist) } if (options?.fireResist !== undefined) { statList.setBaseStat('fireresist', options.fireResist) } if (options?.coldMasterySlvl && options.coldMasterySlvl > 0) { const pierce = calculateColdMasteryPierce(options.coldMasterySlvl) statList.addStat('passive_cold_pierce', pierce) statList.setBaseSkillLevel(65, options.coldMasterySlvl) } if (options?.fireMasterySlvl && options.fireMasterySlvl > 0) { // UnitStatList.getAccruedStat('passive_fire_mastery') calculates Fire Mastery bonus // automatically from base skill level 61. statList.setBaseSkillLevel(61, options.fireMasterySlvl) } const stateBus = new StateBus(statList) if (options?.hasEnchantState) { stateBus.applyState({ stateNameOrId: 'enchant', sourceSkillId: 52, durationFrames: 15000, stats: { tohit_percent: calculateEnchantAttackRating(20) }, }) } const armorSlvl = options?.armorSlvl ?? 20 if (options?.hasFrozenArmorState) { stateBus.applyState({ stateNameOrId: 'frozenarmor', sourceSkillId: 40, slvl: armorSlvl, durationFrames: 15000, stats: { skill_armor_percent: calculateFrozenArmorDefense(armorSlvl) }, }) } else if (options?.hasShiverArmorState) { stateBus.applyState({ stateNameOrId: 'shiverarmor', sourceSkillId: 50, slvl: armorSlvl, durationFrames: 15000, stats: { skill_armor_percent: calculateShiverArmorDefense(armorSlvl) }, }) } else if (options?.hasChillingArmorState) { stateBus.applyState({ stateNameOrId: 'chillingarmor', sourceSkillId: 60, slvl: armorSlvl, durationFrames: 15000, stats: { skill_armor_percent: calculateChillingArmorDefense(armorSlvl) }, }) } return { id, name: id, statList, stateBus, weaponMinPhys: options?.weaponMinPhys ?? 15, weaponMaxPhys: options?.weaponMaxPhys ?? 25, } } /** Flat mock terrain */ const flatTerrain: CombatTerrain = { overlap: () => 0, } /** Base combat options for deterministic tests */ const standardOptions: CombatOptions = { playerSpeed: 100, playerReach: 50, playerCooldownTicks: 10, playerDamage: 15, playerManaPerAttack: 0, respawnTicks: 100, } describe('Diablo II v1.13c Sorceress Cold & Fire Cross-Tree E2E Integration Stress Suite', () => { // ========================================================================= // Section 1: Simultaneous Buff Stacking & Group 1 Cold Armor Mutual Exclusion // ========================================================================= describe('Section 1: Simultaneous Buff Stacking & Cold Armor Mutual Exclusion', () => { it('verifies Frozen Armor (+125% def) and Enchant (+191% AR) stack concurrently on UnitStatList & StateBus', async () => { const registry = await getSharedDataRegistry() const stats = new UnitStatList(registry, { armorclass: 100, tohit: 1000, maxhp: 1000 * FIXED_ONE, hitpoints: 1000 * FIXED_ONE, }) const bus = new StateBus(stats, registry) // Baseline stats before buffs expect(stats.getAccruedStat('armorclass')).toBe(100) expect(stats.getAccruedStat('tohit')).toBe(1000) // 1. Cast Frozen Armor (slvl 20 -> +125% defense) const frozenArmorDef = calculateFrozenArmorDefense(20) expect(frozenArmorDef).toBe(125) bus.applyState({ stateNameOrId: 'frozenarmor', sourceSkillId: 40, slvl: 20, stats: { skill_armor_percent: frozenArmorDef }, }) expect(bus.hasState('frozenarmor')).toBe(true) expect(stats.getAccruedStat('armorclass')).toBe(225) // 100 * (100 + 125) / 100 expect(stats.getAccruedStat('tohit')).toBe(1000) // 2. Cast Enchant (slvl 20 -> +191% Attack Rating) const enchantAr = calculateEnchantAttackRating(20) expect(enchantAr).toBe(191) // 20 + 19 * 9 bus.applyState({ stateNameOrId: 'enchant', sourceSkillId: 52, slvl: 20, stats: { tohit_percent: enchantAr }, }) // 3. Verify concurrent coexistence expect(bus.hasState('frozenarmor')).toBe(true) expect(bus.hasState('enchant')).toBe(true) expect(stats.getAccruedStat('armorclass')).toBe(225) // Defense remains boosted expect(stats.getAccruedStat('tohit')).toBe(2910) // 1000 * (100 + 191) / 100 }) it('enforces State Group 1 mutual exclusion: casting Shiver Armor replaces Frozen Armor while preserving Enchant', async () => { const registry = await getSharedDataRegistry() const stats = new UnitStatList(registry, { armorclass: 100, tohit: 1000, maxhp: 1000 * FIXED_ONE, hitpoints: 1000 * FIXED_ONE, }) const bus = new StateBus(stats, registry) // Apply Frozen Armor (slvl 20 -> +125% def) and Enchant (slvl 20 -> +191% AR) bus.applyState({ stateNameOrId: 'frozenarmor', sourceSkillId: 40, slvl: 20, stats: { skill_armor_percent: calculateFrozenArmorDefense(20) }, }) bus.applyState({ stateNameOrId: 'enchant', sourceSkillId: 52, slvl: 20, stats: { tohit_percent: calculateEnchantAttackRating(20) }, }) expect(bus.hasState('frozenarmor')).toBe(true) expect(bus.hasState('enchant')).toBe(true) // Cast Shiver Armor (slvl 10 -> +99% def) const shiverArmorDef = calculateShiverArmorDefense(10) expect(shiverArmorDef).toBe(99) // 45 + 9 * 6 const shiverResult = bus.applyState({ stateNameOrId: 'shiverarmor', sourceSkillId: 50, slvl: 10, stats: { skill_armor_percent: shiverArmorDef }, }) // Verify Frozen Armor was evicted due to Group 1 conflict expect(shiverResult.applied).toBe(true) expect(shiverResult.evictedState).toBe('frozenarmor') expect(bus.hasState('frozenarmor')).toBe(false) expect(bus.hasState('shiverarmor')).toBe(true) // Defense must be 100 * (100 + 99) / 100 = 199, NOT 100 + 125 + 99 = 324! expect(stats.getAccruedStat('armorclass')).toBe(199) expect(stats.getModifierBonus('skill_armor_percent')).toBe(99) // Enchant MUST remain active and unmolested! expect(bus.hasState('enchant')).toBe(true) expect(stats.getAccruedStat('tohit')).toBe(2910) expect(stats.getModifierBonus('tohit_percent')).toBe(191) // Now switch to Chilling Armor (slvl 15 -> +115% def) const chillingArmorDef = calculateChillingArmorDefense(15) expect(chillingArmorDef).toBe(115) // 45 + 14 * 5 const chillingResult = bus.applyState({ stateNameOrId: 'chillingarmor', sourceSkillId: 60, slvl: 15, stats: { skill_armor_percent: chillingArmorDef }, }) expect(chillingResult.applied).toBe(true) expect(chillingResult.evictedState).toBe('shiverarmor') expect(bus.hasState('shiverarmor')).toBe(false) expect(bus.hasState('chillingarmor')).toBe(true) expect(stats.getAccruedStat('armorclass')).toBe(215) // 100 * (100 + 115) / 100 // Enchant still completely untouched! expect(bus.hasState('enchant')).toBe(true) expect(stats.getAccruedStat('tohit')).toBe(2910) }) it('verifies CombatPlayer sandbox buff stacking, replacement, and reactive event integrity', () => { const world: CombatWorld = createWorld(100, 100) const dummyMonster: Monster = { index: 0, stats: { id: 'dummy_mon', name: 'Target Dummy', hp: 10000, damage: 10, cooldownTicks: 25, reach: 25, aggroRadius: 100, speed: 50, xp: 10, }, x: 110, y: 100, hp: 10000, cooldown: 0, state: 'idle', facing: 0, hitFlash: 0, corpseTicks: 0, } world.monsters.push(dummyMonster) // Apply Enchant buff (slvl 20) with Fire Mastery (slvl 20) applyEnchantBuff(world.player, 20, { casterFireMasterySlvl: 20 }) world.player.isSorceress = true world.player.fireMasterySlvl = 20 // Apply Shiver Armor (slvl 10) world.player.coldArmorType = 'shiverarmor' world.player.coldArmorSlvl = 10 world.player.coldArmorTicks = calculateShiverArmorDuration(10) expect(world.player.coldArmorType).toBe('shiverarmor') expect(world.player.coldArmorTicks).toBe(calculateShiverArmorDuration(10)) expect(world.player.enchantTicks).toBe(calculateEnchantDuration(20)) expect(world.player.enchantAttackRatingPercent).toBe(calculateEnchantAttackRating(20)) expect(world.player.enchantDamage).toBeDefined() // Player attacks monster in melee: deals weapon damage + double-dipped Enchant fire damage const prevMonsterHp = dummyMonster.hp tickCombat(world, { movement: { x: 0, y: 0 }, attack: true }, standardOptions, flatTerrain, [0, 100]) const attackEvent = world.events.find(e => e.kind === 'monsterHit') expect(attackEvent).toBeDefined() expect(dummyMonster.hp).toBeLessThan(prevMonsterHp) // Switch Cold Armor to Frozen Armor (slvl 15) world.player.coldArmorType = 'frozenarmor' world.player.coldArmorSlvl = 15 world.player.coldArmorTicks = calculateFrozenArmorDuration(15) expect(world.player.coldArmorType).toBe('frozenarmor') expect(world.player.coldArmorTicks).toBe(calculateFrozenArmorDuration(15)) // Enchant buff remains 100% active and un-truncated expect(world.player.enchantTicks).toBeGreaterThan(0) expect(world.player.enchantDamage).toBeDefined() }) it('stress tests 100 rapid cycles of alternating Cold Armors with persistent Enchant', async () => { const registry = await getSharedDataRegistry() const stats = new UnitStatList(registry, { armorclass: 200, tohit: 2000 }) const bus = new StateBus(stats, registry) // Permanent Enchant buff bus.applyState({ stateNameOrId: 'enchant', sourceSkillId: 52, slvl: 30, stats: { tohit_percent: calculateEnchantAttackRating(30) }, }) const expectedToHit = Math.trunc((2000 * (100 + calculateEnchantAttackRating(30))) / 100) const armors: readonly ('frozenarmor' | 'shiverarmor' | 'chillingarmor')[] = [ 'frozenarmor', 'shiverarmor', 'chillingarmor', ] const skillIds = { frozenarmor: 40, shiverarmor: 50, chillingarmor: 60 } const defFuncs = { frozenarmor: calculateFrozenArmorDefense, shiverarmor: calculateShiverArmorDefense, chillingarmor: calculateChillingArmorDefense, } for (let i = 0; i < 100; i++) { const armor = armors[i % 3]! const slvl = 1 + (i % 30) const defPct = defFuncs[armor](slvl) bus.applyState({ stateNameOrId: armor, sourceSkillId: skillIds[armor], slvl, stats: { skill_armor_percent: defPct }, }) // Exactly one cold armor active for (const other of armors) { if (other === armor) { expect(bus.hasState(other)).toBe(true) } else { expect(bus.hasState(other)).toBe(false) } } // Defense matches current armor exactly const expectedDef = Math.trunc((200 * (100 + defPct)) / 100) expect(stats.getAccruedStat('armorclass')).toBe(expectedDef) // Enchant is invariant throughout all 100 cycles expect(bus.hasState('enchant')).toBe(true) expect(stats.getAccruedStat('tohit')).toBe(expectedToHit) } }) }) // ========================================================================= // Section 2: Dual-Tree Sorceress Combat: Cold Mastery vs Fire Mastery // ========================================================================= describe('Section 2: Dual-Tree Sorceress Combat (Mastery Mechanics Asymmetry)', () => { it('verifies Cold Mastery pierces enemy cold res while Fire Mastery increases fire damage without piercing fire res', () => { const sorceress = createMockUnit('dual_sorceress', { isSorceress: true, coldMasterySlvl: 20, // 115% cold pierce fireMasterySlvl: 20, // +163% fire damage }) // Target with 50% Cold Resist and 50% Fire Resist const defender = createMockUnit('dual_res_enemy', { coldResist: 50, fireResist: 50, }) // 1. Cold Spell Attack: Glacial Spike (55) // Base cold damage in 256ths: 100.0 cold damage = 25600 const baseCold256 = 100 * FIXED_ONE const coldPacket: SUnitDmgPacket = { skillId: 55, attackKind: 'spell', elemType: 'cold', elemMin256: baseCold256, elemMax256: baseCold256, autoHit: true, } const coldOutcome = executeSUnitDmg(sorceress, defender, coldPacket) expect(coldOutcome.hit).toBe(true) expect(coldOutcome.immuneToElem).toBe(false) // Cold Mastery pierces 50% res down to 50 - 115 = -65%! // Multiplier = (100 - (-65)) / 100 = 1.65x // Expected damage = Math.trunc(25600 * 165 / 100) = 42240 (165 cold damage) expect(coldOutcome.elemDamage256).toBe(Math.trunc((baseCold256 * 165) / 100)) expect(coldOutcome.totalDamage).toBe(165) // 2. Fire Attack: Enchant Fire Damage (52) // Base fire damage in 256ths: 100.0 fire damage = 25600 const baseFire256 = 100 * FIXED_ONE const firePacket: SUnitDmgPacket = { skillId: 52, attackKind: 'melee', isEnchant: true, elemType: 'fire', elemMin256: baseFire256, elemMax256: baseFire256, flatPhysMin256: 0, flatPhysMax256: 0, autoHit: true, } const fireOutcome = executeSUnitDmg(sorceress, defender, firePacket) expect(fireOutcome.hit).toBe(true) expect(fireOutcome.immuneToElem).toBe(false) // Fire Mastery scales fire damage by +163%: 25600 * 2.63 = 67328 // CRITICAL: Fire Mastery does NOT pierce Fire Resistance! // Defender has 50% Fire Resist -> takes (100 - 50) / 100 = 50% of the scaled damage: // Math.trunc(67328 * 50 / 100) = 33664 const scaledFire256 = Math.trunc((baseFire256 * (100 + 163)) / 100) const expectedFireDamage256 = Math.trunc((scaledFire256 * (100 - 50)) / 100) expect(fireOutcome.elemDamage256).toBe(expectedFireDamage256) // totalDamage is Math.ceil((finalPhys256 + finalElem256) / 256) expect(fireOutcome.totalDamage).toBe(Math.ceil((fireOutcome.physDamage256 + fireOutcome.elemDamage256) / FIXED_ONE)) }) it('proves Cold Mastery does NOT affect fire damage and Fire Mastery does NOT affect cold damage', () => { const pureColdSorc = createMockUnit('cold_sorc', { coldMasterySlvl: 20 }) const pureFireSorc = createMockUnit('fire_sorc', { fireMasterySlvl: 20 }) const dualSorc = createMockUnit('dual_sorc', { coldMasterySlvl: 20, fireMasterySlvl: 20 }) const enemy = createMockUnit('neutral_enemy', { coldResist: 25, fireResist: 25 }) const baseDmg256 = 80 * FIXED_ONE // Cold attack: dual sorc deals identical damage to pure cold sorc const coldPacket: SUnitDmgPacket = { skillId: 45, // Ice Blast attackKind: 'spell', elemType: 'cold', elemMin256: baseDmg256, elemMax256: baseDmg256, autoHit: true, } const pureColdOutcome = executeSUnitDmg(pureColdSorc, enemy, coldPacket) const dualColdOutcome = executeSUnitDmg(dualSorc, enemy, coldPacket) expect(dualColdOutcome.elemDamage256).toBe(pureColdOutcome.elemDamage256) // Fire attack: dual sorc deals identical damage to pure fire sorc const firePacket: SUnitDmgPacket = { skillId: 52, attackKind: 'melee', isEnchant: true, elemType: 'fire', elemMin256: baseDmg256, elemMax256: baseDmg256, flatPhysMin256: 0, flatPhysMax256: 0, autoHit: true, } const pureFireOutcome = executeSUnitDmg(pureFireSorc, enemy, firePacket) const dualFireOutcome = executeSUnitDmg(dualSorc, enemy, firePacket) expect(dualFireOutcome.elemDamage256).toBe(pureFireOutcome.elemDamage256) }) it('verifies quantitative cross-tree asymmetry across resistance spectrum [0%, 25%, 50%, 75%]', () => { const sorceress = createMockUnit('sorceress', { coldMasterySlvl: 10, // 65% pierce fireMasterySlvl: 10, // +93% damage }) const baseDmg256 = 100 * FIXED_ONE for (const res of [0, 25, 50, 75]) { const enemy = createMockUnit(`enemy_${res}`, { coldResist: res, fireResist: res }) // Cold calculation: effectiveRes = max(-100, res - 65) const expectedEffectiveColdRes = Math.max(-100, res - 65) const expectedColdDmg256 = Math.trunc((baseDmg256 * (100 - expectedEffectiveColdRes)) / 100) const coldOutcome = executeSUnitDmg(sorceress, enemy, { skillId: 55, attackKind: 'spell', elemType: 'cold', elemMin256: baseDmg256, elemMax256: baseDmg256, autoHit: true, }) expect(coldOutcome.elemDamage256).toBe(expectedColdDmg256) // Fire calculation: base * 1.93 * (100 - res) / 100 const scaledFire256 = Math.trunc((baseDmg256 * (100 + 93)) / 100) const expectedFireDmg256 = Math.trunc((scaledFire256 * (100 - res)) / 100) const fireOutcome = executeSUnitDmg(sorceress, enemy, { skillId: 52, attackKind: 'melee', isEnchant: true, elemType: 'fire', elemMin256: baseDmg256, elemMax256: baseDmg256, flatPhysMin256: 0, flatPhysMax256: 0, autoHit: true, }) expect(fireOutcome.elemDamage256).toBe(expectedFireDmg256) } }) }) // ========================================================================= // Section 3: Dual Immunity Stress (Cold Res >= 100 & Fire Res >= 100) // ========================================================================= describe('Section 3: Dual Immunity Stress (Natural Immunities Cannot Be Broken)', () => { it('verifies monsters with dual cold & fire immunity (Res >= 100) take strictly 0 elemental damage', () => { const sorceress = createMockUnit('max_sorceress', { isSorceress: true, coldMasterySlvl: 40, // 215% pierce fireMasterySlvl: 40, // +303% damage }) // Dual immune boss monster (e.g. Cold Res 110%, Fire Res 110%) const dualImmune = createMockUnit('dual_immune_boss', { coldResist: 110, fireResist: 110, hp: 5000, }) const baseCold256 = 500 * FIXED_ONE const baseFire256 = 500 * FIXED_ONE // 1. Cold attack alone const coldOutcome = executeSUnitDmg(sorceress, dualImmune, { skillId: 55, // Glacial Spike attackKind: 'spell', elemType: 'cold', elemMin256: baseCold256, elemMax256: baseCold256, autoHit: true, }) expect(coldOutcome.immuneToElem).toBe(true) expect(coldOutcome.elemDamage256).toBe(0) expect(coldOutcome.totalDamage).toBe(0) // 2. Fire attack alone const fireOutcome = executeSUnitDmg(sorceress, dualImmune, { skillId: 52, // Enchant attackKind: 'melee', isEnchant: true, elemType: 'fire', elemMin256: baseFire256, elemMax256: baseFire256, flatPhysMin256: 0, flatPhysMax256: 0, autoHit: true, }) expect(fireOutcome.immuneToElem).toBe(true) expect(fireOutcome.elemDamage256).toBe(0) // Physical portion (20) still connects expect(fireOutcome.physDamage256).toBe(20 * FIXED_ONE) expect(fireOutcome.totalDamage).toBe(20) // 3. Combined attack: physical + fire + cold const dualOutcome = executeSUnitDmg(sorceress, dualImmune, { skillId: 52, attackKind: 'melee', elemType: 'fire', elemMin256: baseFire256, elemMax256: baseFire256, flatPhysMin256: 25 * FIXED_ONE, flatPhysMax256: 25 * FIXED_ONE, autoHit: true, }) expect(dualOutcome.immuneToElem).toBe(true) expect(dualOutcome.elemDamage256).toBe(0) // Only physical damage (45 total = 20 weapon + 25 flat) penetrates expect(dualOutcome.totalDamage).toBe(45) }) it('verifies sharp immunity cliff at the exact 100% boundary (99% takes massive damage, 100% takes 0)', () => { const sorceress = createMockUnit('sorceress', { coldMasterySlvl: 20 }) // 115% pierce const baseCold256 = 100 * FIXED_ONE // Case A: 99% Cold Resistance (NOT immune) const nearImmune = createMockUnit('near_immune', { coldResist: 99 }) const nearOutcome = executeSUnitDmg(sorceress, nearImmune, { skillId: 45, attackKind: 'spell', elemType: 'cold', elemMin256: baseCold256, elemMax256: baseCold256, autoHit: true, }) expect(nearOutcome.immuneToElem).toBe(false) // 99 - 115 = -16% effective resistance -> takes 1.16x damage! expect(nearOutcome.totalDamage).toBe(116) // Case B: 100% Cold Resistance (STRICT IMMUNITY) const exactImmune = createMockUnit('exact_immune', { coldResist: 100 }) const exactOutcome = executeSUnitDmg(sorceress, exactImmune, { skillId: 45, attackKind: 'spell', elemType: 'cold', elemMin256: baseCold256, elemMax256: baseCold256, autoHit: true, }) expect(exactOutcome.immuneToElem).toBe(true) expect(exactOutcome.totalDamage).toBe(0) // Case C: 101% Cold Resistance (IMMUNE) const overImmune = createMockUnit('over_immune', { coldResist: 101 }) const overOutcome = executeSUnitDmg(sorceress, overImmune, { skillId: 45, attackKind: 'spell', elemType: 'cold', elemMin256: baseCold256, elemMax256: baseCold256, autoHit: true, }) expect(overOutcome.immuneToElem).toBe(true) expect(overOutcome.totalDamage).toBe(0) }) it('simulates 100 consecutive attacks against extreme dual-immune targets (Res 100% to 200%) verifying net elemental damage = 0', () => { for (let i = 0; i < 100; i++) { const coldRes = 100 + (i % 50) const fireRes = 100 + ((i * 3) % 50) const coldResRec = computeEffectiveResistance({ baseRes: coldRes, passiveEnemyPierce: calculateColdMasteryPierce(30), }) expect(coldResRec.isImmune).toBe(true) expect(coldResRec.effectiveRes).toBe(100) const fireResRec = computeEffectiveResistance({ baseRes: fireRes, passiveEnemyPierce: 0, }) expect(fireResRec.isImmune).toBe(true) expect(fireResRec.effectiveRes).toBe(100) } }) }) // ========================================================================= // Section 4: Resistance Floor Clamping (-100% Floor Cap) & Immunity Breaking // ========================================================================= describe('Section 4: Resistance Floor Clamping (-100% Floor Cap) & Immunity-Breaking Interactions', () => { it('enforces strict -100% resistance floor clamp under extreme resistance reduction (-375% raw -> -100% clamped)', () => { // Enemy with negative base resistance -50% // Conviction 150% + Lower Resist 60% + Cold Mastery 115% // Raw: -50 - 150 - 60 - 115 = -375% const resResult = computeEffectiveResistance({ baseRes: -50, convictionPierce: 150, lowerResistPierce: 60, passiveEnemyPierce: 115, }) expect(resResult.isImmune).toBe(false) // Clamped strictly to -100% (2.0x damage cap) expect(resResult.effectiveRes).toBe(-100) // Verify combat pipeline damage calculation respects the 2.0x cap const attacker = createMockUnit('attacker') const defender = createMockUnit('defender', { coldResist: -50 }) const baseDmg256 = 100 * FIXED_ONE const outcome = executeSUnitDmg(attacker, defender, { skillId: 45, attackKind: 'spell', elemType: 'cold', elemMin256: baseDmg256, elemMax256: baseDmg256, convictionPierce: 150, lowerResistPierce: 60, passiveEnemyPierce: 115, autoHit: true, }) expect(outcome.immuneToElem).toBe(false) // (100 - (-100)) / 100 = 200% -> exactly 200 total damage! expect(outcome.elemDamage256).toBe(200 * FIXED_ONE) expect(outcome.totalDamage).toBe(200) }) it('verifies Conviction breaking cold immunity allows Cold Mastery to apply at full 100% power', () => { // Monster with 110% Cold Resistance (Immune) // Conviction slvl 25 has 150% pierce. // 1.13c Rule: 1/5th efficiency against immune -> 150 / 5 = 30% // Res after Conviction: 110 - 30 = 80% (< 100%, IMMUNITY BROKEN!) // Now Cold Mastery (slvl 20: 115% pierce) applies at 100% full effectiveness: // Effective res: 80 - 115 = -35% const resResult = computeEffectiveResistance({ baseRes: 110, convictionPierce: 150, passiveEnemyPierce: calculateColdMasteryPierce(20), // 115 }) expect(resResult.isImmune).toBe(false) expect(resResult.convictionAndLRApplied).toBe(30) expect(resResult.resAfterConvictionLR).toBe(80) expect(resResult.effectiveRes).toBe(-35) // Damage multiplier is (100 - (-35)) / 100 = 135% (1.35x) const attacker = createMockUnit('attacker', { coldMasterySlvl: 20 }) const defender = createMockUnit('defender', { coldResist: 110 }) const baseDmg256 = 100 * FIXED_ONE const outcome = executeSUnitDmg(attacker, defender, { skillId: 55, attackKind: 'spell', elemType: 'cold', elemMin256: baseDmg256, elemMax256: baseDmg256, convictionPierce: 150, autoHit: true, }) expect(outcome.immuneToElem).toBe(false) expect(outcome.totalDamage).toBe(135) }) it('verifies Conviction breaking fire immunity: Fire Mastery scales base damage but cannot pierce the remaining resistance', () => { // Monster with 110% Fire Resistance (Immune) // Conviction slvl 25 has 150% pierce -> 1/5th = 30%. // Res after Conviction: 110 - 30 = 80% (< 100%, BROKEN!). // Fire Mastery (slvl 20: +163% damage) scales base damage, but has 0% pierce: // Effective res remains 80%. const resResult = computeEffectiveResistance({ baseRes: 110, convictionPierce: 150, passiveEnemyPierce: 0, }) expect(resResult.isImmune).toBe(false) expect(resResult.resAfterConvictionLR).toBe(80) expect(resResult.effectiveRes).toBe(80) const attacker = createMockUnit('attacker', { fireMasterySlvl: 20 }) const defender = createMockUnit('defender', { fireResist: 110 }) const baseFire256 = 100 * FIXED_ONE const outcome = executeSUnitDmg(attacker, defender, { skillId: 52, attackKind: 'melee', isEnchant: true, elemType: 'fire', elemMin256: baseFire256, elemMax256: baseFire256, flatPhysMin256: 0, flatPhysMax256: 0, convictionPierce: 150, autoHit: true, }) expect(outcome.immuneToElem).toBe(false) // Scaled fire damage: 100 * 2.63 = 263 // Mitigated by 80% resistance: 263 * (100 - 80) / 100 = 52.6 -> 52 (in 256ths: 13465) const scaledFire256 = Math.trunc((baseFire256 * (100 + 163)) / 100) const expectedFireDmg256 = Math.trunc((scaledFire256 * (100 - 80)) / 100) expect(outcome.elemDamage256).toBe(expectedFireDmg256) expect(outcome.totalDamage).toBe(Math.ceil((outcome.physDamage256 + outcome.elemDamage256) / FIXED_ONE)) }) it('verifies unbreakable cold immunity (Res 160% > 150%/5 = 30% reduction) remains 100% immune', () => { // Monster with 160% Cold Resistance // Conviction 150% (30% effective) -> 160 - 30 = 130% (>= 100%, UNBROKEN) const resResult = computeEffectiveResistance({ baseRes: 160, convictionPierce: 150, passiveEnemyPierce: calculateColdMasteryPierce(40), // 215% pierce }) expect(resResult.isImmune).toBe(true) expect(resResult.effectiveRes).toBe(100) const attacker = createMockUnit('attacker', { coldMasterySlvl: 40 }) const defender = createMockUnit('defender', { coldResist: 160 }) const outcome = executeSUnitDmg(attacker, defender, { skillId: 55, attackKind: 'spell', elemType: 'cold', elemMin256: 100 * FIXED_ONE, elemMax256: 100 * FIXED_ONE, convictionPierce: 150, autoHit: true, }) expect(outcome.immuneToElem).toBe(true) expect(outcome.elemDamage256).toBe(0) }) it('differential fuzzing oracle: 500 randomized parameter sets verify effectiveRes >= -100 and correct immunity logic', () => { for (let seed = 1; seed <= 500; seed++) { // Pseudo-random generation based on seed const baseRes = -100 + (seed * 17) % 300 // range [-100..199] const convictionPierce = (seed * 31) % 160 // range [0..159] const lowerResistPierce = (seed * 13) % 70 // range [0..69] const passivePierce = (seed * 23) % 220 // range [0..219] const actual = computeEffectiveResistance({ baseRes, convictionPierce, lowerResistPierce, passiveEnemyPierce: passivePierce, }) // Oracle calculation const rawConvLR = convictionPierce + lowerResistPierce const convLRApplied = baseRes >= 100 ? Math.trunc(rawConvLR / 5) : rawConvLR const resAfter = baseRes - convLRApplied const expectedImmune = resAfter >= 100 const expectedEffRes = expectedImmune ? 100 : Math.max(-100, Math.min(99, resAfter - passivePierce)) expect(actual.isImmune).toBe(expectedImmune) expect(actual.effectiveRes).toBe(expectedEffRes) expect(actual.effectiveRes).toBeGreaterThanOrEqual(-100) expect(actual.effectiveRes).toBeLessThanOrEqual(100) } }) }) // ========================================================================= // Section 5: Ground & Stream Coexistence (Blaze + Fire Wall + Cold Projectiles) // ========================================================================= describe('Section 5: Ground & Stream Coexistence (Blaze + Fire Wall + Cold Projectiles)', () => { it('verifies multi-missile simulation coexistence: Fire Wall + Blaze + Ice Blast + Glacial Spike tick independently', () => { // 1. Generate 15 Fire Wall patches centered at (200, 200) const fwPatches = calculateFireWallPatches(100, 200, 200, 200, { count: 15, spacing: 24 }) expect(fwPatches).toHaveLength(15) const activeProjectiles: Projectile[] = [] // Add Fire Wall patches (stationary ground fire) for (const p of fwPatches) { activeProjectiles.push({ skillId: '51', x: p.x, y: p.y, vx: 0, vy: 0, damage: 50, ttl: 90, fromPlayer: true, missileType: 'firewall', pierce: true, aoeRadius: 24, }) } // Add 4 Blaze footstep patches at player footsteps const blazeCoords = [ { x: 100, y: 150 }, { x: 125, y: 150 }, { x: 150, y: 150 }, { x: 175, y: 150 }, ] for (const b of blazeCoords) { activeProjectiles.push({ skillId: '46', x: b.x, y: b.y, vx: 0, vy: 0, damage: 30, ttl: 115, fromPlayer: true, missileType: 'blaze', pierce: true, aoeRadius: 24, }) } // Add 1 Ice Blast moving horizontally across the screen (vx = 12, vy = 0) activeProjectiles.push({ skillId: '45', x: 50, y: 200, vx: 12, vy: 0, damage: 100, ttl: 50, fromPlayer: true, missileType: 'iceblast', statusEffect: 'freeze', statusDuration: 100, }) // Add 1 Glacial Spike moving vertically across the screen (vx = 0, vy = 16) activeProjectiles.push({ skillId: '55', x: 200, y: 50, vx: 0, vy: 16, damage: 150, ttl: 40, fromPlayer: true, missileType: 'glacialspike', aoeRadius: 64, statusEffect: 'freeze', statusDuration: 80, }) expect(activeProjectiles).toHaveLength(15 + 4 + 1 + 1) // 21 projectiles // Step simulation for 5 ticks with no obstacles let currentProjectiles: readonly Projectile[] = activeProjectiles for (let tick = 1; tick <= 5; tick++) { const outcome = tickProjectiles(currentProjectiles, [], { overlap: () => 0 }) currentProjectiles = outcome.alive } expect(currentProjectiles).toHaveLength(21) // All still alive after 5 ticks // Verify Fire Wall patches: stationary, ttl decremented by 5 (90 -> 85) const fwAlive = currentProjectiles.filter(p => p.missileType === 'firewall') expect(fwAlive).toHaveLength(15) for (const p of fwAlive) { expect(p.ttl).toBe(85) expect(p.vx).toBe(0) expect(p.vy).toBe(0) } // Verify Blaze patches: stationary, ttl decremented by 5 (115 -> 110) const blazeAlive = currentProjectiles.filter(p => p.missileType === 'blaze') expect(blazeAlive).toHaveLength(4) for (const p of blazeAlive) { expect(p.ttl).toBe(110) expect(p.vx).toBe(0) expect(p.vy).toBe(0) } // Verify Ice Blast: moved 5 * 12 = 60 pixels on X (50 + 60 = 110), ttl = 45 const iceBlastAlive = currentProjectiles.find(p => p.missileType === 'iceblast')! expect(iceBlastAlive).toBeDefined() expect(iceBlastAlive.x).toBe(110) expect(iceBlastAlive.y).toBe(200) expect(iceBlastAlive.ttl).toBe(45) // Verify Glacial Spike: moved 5 * 16 = 80 pixels on Y (50 + 80 = 130), ttl = 35 const glacialSpikeAlive = currentProjectiles.find(p => p.missileType === 'glacialspike')! expect(glacialSpikeAlive).toBeDefined() expect(glacialSpikeAlive.x).toBe(200) expect(glacialSpikeAlive.y).toBe(130) expect(glacialSpikeAlive.ttl).toBe(35) }) it('verifies Ice Blast traversing cleanly through an active Fire Wall field to hit a distant target', () => { // Fire Wall patch at x = 100, y = 100 const fwPatch: Projectile = { skillId: '51', x: 100, y: 100, vx: 0, vy: 0, damage: 40, ttl: 90, fromPlayer: true, missileType: 'firewall', pierce: true, aoeRadius: 24, } // Ice Blast fired from (50, 100) toward target at (150, 100) const iceBlast: Projectile = { skillId: '45', x: 50, y: 100, vx: 25, // 25 px per tick -> will reach 75, 100, 125, 150 vy: 0, damage: 120, ttl: 10, fromPlayer: true, missileType: 'iceblast', statusEffect: 'freeze', statusDuration: 100, } // Monster standing past the Fire Wall at (150, 100) const monster: ProjectileTarget = { index: 1, alive: true, x: 150, y: 100, radius: 16, } let projs: readonly Projectile[] = [fwPatch, iceBlast] let hitTarget: { readonly targetIndex: number; readonly damage: number; readonly statusEffect?: string | undefined } | undefined // Tick 1: Ice Blast moves to 75, Fire Wall remains at 100 let out = tickProjectiles(projs, [monster], { overlap: () => 0 }) expect(out.hits).toHaveLength(0) projs = out.alive // Tick 2: Ice Blast moves to 100 (passes through Fire Wall patch without collision) out = tickProjectiles(projs, [monster], { overlap: () => 0 }) expect(out.hits).toHaveLength(0) expect(out.alive.some(p => p.missileType === 'iceblast')).toBe(true) expect(out.alive.some(p => p.missileType === 'firewall')).toBe(true) projs = out.alive // Tick 3: Ice Blast moves to 125 out = tickProjectiles(projs, [monster], { overlap: () => 0 }) expect(out.hits).toHaveLength(0) projs = out.alive // Tick 4: Ice Blast moves to 150 and impacts monster! out = tickProjectiles(projs, [monster], { overlap: () => 0 }) expect(out.hits).toHaveLength(1) hitTarget = out.hits[0] expect(hitTarget?.targetIndex).toBe(1) expect(hitTarget?.damage).toBe(120) expect(hitTarget?.statusEffect).toBe('freeze') // Ice Blast expired on hit, while Fire Wall is STILL burning! const remainingFw = out.alive.find(p => p.missileType === 'firewall') expect(remainingFw).toBeDefined() expect(remainingFw?.ttl).toBe(86) // 90 - 4 ticks }) it('demonstrates Glacial Spike freezing an enemy on top of an active Fire Wall field for continuous burn ticks', () => { // Monster at (120, 120) const monster: ProjectileTarget = { index: 1, alive: true, x: 120, y: 120, radius: 16, } // Fire Wall patch at (120, 120) directly under the monster const fwPatch: Projectile = { skillId: '51', x: 120, y: 120, vx: 0, vy: 0, damage: 50, ttl: 30, fromPlayer: true, missileType: 'firewall', pierce: true, aoeRadius: 24, } // Glacial Spike impacting on tick 1 const glacialSpike: Projectile = { skillId: '55', x: 110, y: 120, vx: 10, vy: 0, damage: 80, ttl: 10, fromPlayer: true, missileType: 'glacialspike', aoeRadius: 64, statusEffect: 'freeze', statusDuration: 50, } let projs: readonly Projectile[] = [fwPatch, glacialSpike] let totalFireDamage = 0 let coldDamageDealt = 0 let freezeDurationApplied = 0 for (let tick = 1; tick <= 5; tick++) { const out = tickProjectiles(projs, [monster], { overlap: () => 0 }) for (const hit of out.hits) { if (hit.missileType === 'firewall') { totalFireDamage += hit.damage } else if (hit.missileType === 'glacialspike') { coldDamageDealt += hit.damage freezeDurationApplied = hit.statusDuration ?? 0 } } projs = out.alive } // Glacial Spike struck once for 80 damage and 50 ticks of freeze expect(coldDamageDealt).toBe(80) expect(freezeDurationApplied).toBe(50) // Fire Wall struck 5 times (once per tick): 5 * 50 = 250 fire damage! expect(totalFireDamage).toBe(250) // Fire Wall patch is still burning (30 - 5 = 25 ttl) const fwPatches = projs.filter(p => p.missileType === 'firewall') expect(fwPatches).toHaveLength(1) expect(fwPatches[0]!.ttl).toBe(25) // Glacial Spike explosion & ejecta submissiles are active expect(projs.some(p => p.missileType === 'freezeexplode')).toBe(true) expect(projs.some(p => p.missileType === 'glacialspikeejecta')).toBe(true) }) it('verifies player running with Blaze active generates footstep patches while casting Glacial Spike with zero self-damage', () => { const terrain = { widthPx: 2000, heightPx: 2000, overlap: () => 0 } const engineOpts = { spawn: { x: 100, y: 100 }, stats: [], xpTable: [], itemBases: [], prefixAffixes: [], suffixAffixes: [], skills: [], npcDefs: [], questDefs: [], combatOptions: { playerSpeed: 100, playerReach: 40, playerCooldownTicks: 12, playerDamage: 5, playerManaPerAttack: 1, respawnTicks: 50, disableMonsterAggro: false, }, lootSeed: 1, talkRadius: 50, pickupRadius: 30, inventoryCols: 10, inventoryRows: 4, } const engine = new GameEngine(terrain as any, engineOpts as any) const idleInput = { movement: { x: 0, y: 0 }, attacking: false, digits: [], pickingUp: false, talking: false, saving: false, loading: false, } // Activate Blaze on player engine.world.player.x = 100 engine.world.player.y = 100 engine.world.player.blazeTicks = 200 engine.world.player.blazeDamage = 50 engine.world.player.blazePatchDuration = 115 // Tick 1: Spawns initial Blaze patch at (100, 100) engine.tick(idleInput) expect(engine.projectiles).toHaveLength(1) expect(engine.projectiles[0]!.missileType).toBe('blaze') const playerInitialHp = engine.world.player.hp // Player casts Glacial Spike projectile while standing over Blaze patch const spikeProj: Projectile = { skillId: '55', x: engine.world.player.x, y: engine.world.player.y, vx: 16, vy: 0, damage: 120, ttl: 30, fromPlayer: true, missileType: 'glacialspike', aoeRadius: 64, statusEffect: 'freeze', statusDuration: 50, } engine.projectiles.push(spikeProj) expect(engine.projectiles).toHaveLength(2) // Move player 30px (>= 24px) to (130, 100) engine.world.player.x = 130 engine.world.player.y = 100 engine.tick(idleInput) // Spawns 2nd Blaze patch at (130, 100); Glacial Spike advances by 16px to 116px expect(engine.projectiles.length).toBeGreaterThanOrEqual(2) // Invariant: Player takes ZERO self-damage from Blaze patches or from their own Glacial Spike! expect(engine.world.player.hp).toBe(playerInitialHp) expect(engine.world.player.alive).toBe(true) // The Glacial Spike moved independently without being affected by Blaze ground patches const spike = engine.projectiles.find(p => p.missileType === 'glacialspike') expect(spike).toBeDefined() expect(spike!.x).toBe(116) expect(spike!.y).toBe(100) expect(spike!.ttl).toBe(29) }) }) // ========================================================================= // Section 6: Architectural Invariants // ========================================================================= describe('Section 6: Architectural Invariants', () => { it('strictly preserves BATCH1_SKILLS length at exactly 38 skills', () => { expect(Object.keys(BATCH1_SKILLS).length).toBe(38) }) }) })