/** * Adversarial Empirical Stress Testing Suite for Milestone 4: * Sorceress Fire Combat Mechanics, Weapon Scaling, and Double-Dipping * * Target Systems: * - Skill 52: Enchant (weapon fire damage buff, AR bonus, duration) * - Skill 61: Fire Mastery (passive fire damage percentage scaling) * - SUnitDmg Combat Pipeline (executeSUnitDmg, computeEffectiveResistance) * - Combat Sandbox Simulation (CombatWorld, tickPlayer, tickCombat, tickCombatMulti) * * Key Verification Dimensions: * 1. Ranged Weapon Penalty: strictly 33% of Enchant fire damage bonus on ranged weapons (bow/crossbow/throwing) * vs 100% on melee weapons. * 2. Sorceress Melee Double-Dip: * - Cast time: Enchant damage scaled by Fire Mastery (+163% at slvl 20 -> 2.63x). * - Melee strike: Sorceress applies Fire Mastery (+163% -> 2.63x) again upon hit: base * (1 + 1.63)^2 = 473.8 - 619.1! * - Ranged strike: Sorceress attacks at range -> second Fire Mastery is NOT applied. * 3. Non-Sorceress Buff Recipient (Allies / Pets): * - Allies receive single-multiplied buff damage on melee (no double-dip). * - Allies receive 33% on ranged. * 4. Fire Immunity Preservation: * - Target Fire Resist = 100%, 110%, 150%: strictly 0 fire damage dealt. * - Confirms Fire Mastery has zero resistance pierce and cannot break immunities. * - Target Fire Resist = 50% and 0%: verified exact proportional mitigation. * 5. Resistance Format Interoperability & Lifecycle Boundary Edge Cases. */ import { describe, expect, it } from 'vitest' import { calculateEnchantDamage, calculateEnchantAttackRating, calculateEnchantDuration, calculateFireMasteryBonus, calculateEnchantAttackDamage, } 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 { UnitStatList, FIXED_ONE } from '../../../src/game/engine/stat-list.ts' import { StateBus } from '../../../src/game/engine/state-bus.ts' /** Helper to create a fully initialized CombatUnitContext */ function createCombatUnit( id: string, options?: { hp?: number isSorceress?: boolean fireMasterySlvl?: number fireResist?: number hasEnchantState?: boolean }, ): CombatUnitContext { const statList = new UnitStatList() const hp = options?.hp ?? 1000 statList.setBaseStat('hp', hp * FIXED_ONE) statList.setBaseStat('maxhp', hp * FIXED_ONE) statList.setBaseStat('hitpoints', hp * FIXED_ONE) statList.setBaseStat('level', 80) statList.setBaseStat('tohit', 10000) // guarantee 95% or autohit statList.setBaseStat('armorclass', 100) if (options?.fireMasterySlvl && options.fireMasterySlvl > 0) { statList.setBaseSkillLevel(61, options.fireMasterySlvl) } if (options?.fireResist !== undefined) { statList.setBaseStat('fireresist', options.fireResist) } const stateBus = new StateBus(statList) if (options?.hasEnchantState) { stateBus.applyState({ stateNameOrId: 'enchant', durationFrames: 15000 }) } return { id, name: id, statList, stateBus, weaponMinPhys: 10, weaponMaxPhys: 10, } } /** Flat mock terrain */ const mockTerrain: CombatTerrain = { overlap: () => 0, } /** Standard combat options for deterministic tests */ const baseOptions: CombatOptions = { playerSpeed: 100, playerReach: 50, playerCooldownTicks: 10, playerDamage: 10, // Base physical damage = 10 playerManaPerAttack: 0, respawnTicks: 100, } describe('Milestone 4 Adversarial Empirical Stress Test Suite (Combat, Scaling, Double-Dip)', () => { // ========================================================================== // Suite 1: Ranged Weapon Penalty (33% on Ranged vs 100% on Melee) // ========================================================================== describe('Suite 1: Ranged Weapon Penalty (33% Ranged vs 100% Melee)', () => { it('verifies calculateEnchantAttackDamage strictly scales ranged attacks by 33% across slvl 1..40', () => { for (const slvl of [1, 5, 10, 15, 20, 25, 30, 35, 40]) { const baseDmg = calculateEnchantDamage(slvl) // Melee non-sorceress attack (100% buff damage) const meleeRes = calculateEnchantAttackDamage({ enchantDamage: baseDmg, isRanged: false, isSorceress: false, }) expect(meleeRes.min).toBe(baseDmg.min) expect(meleeRes.max).toBe(baseDmg.max) // Ranged non-sorceress attack (strictly 33% of buff damage) const rangedRes = calculateEnchantAttackDamage({ enchantDamage: baseDmg, isRanged: true, isSorceress: false, }) const expectedRangedMin = Math.round(baseDmg.min * 0.33 * 10) / 10 const expectedRangedMax = Math.round(baseDmg.max * 0.33 * 10) / 10 expect(rangedRes.min).toBe(expectedRangedMin) expect(rangedRes.max).toBe(expectedRangedMax) expect(rangedRes.average).toBe(Math.round(((expectedRangedMin + expectedRangedMax) / 2) * 10) / 10) // Verify ratio is within rounding precision of 33% const ratio = rangedRes.average / meleeRes.average expect(ratio).toBeGreaterThan(0.32) expect(ratio).toBeLessThan(0.34) } }) it('verifies combat pipeline executeSUnitDmg applies 33% penalty to missile fire damage while physical damage is untouched', () => { const attacker = createCombatUnit('attacker_unit', { hasEnchantState: true }) const defender = createCombatUnit('defender_unit', { fireResist: 0 }) // Base Enchant fire damage in 256ths: 100.0 fire damage = 25600 const enchantFire256 = 100 * FIXED_ONE // 1. Melee attack: full 100% fire damage const meleePacket: SUnitDmgPacket = { skillId: 52, attackKind: 'melee', isEnchant: true, elemType: 'fire', elemMin256: enchantFire256, elemMax256: enchantFire256, flatPhysMin256: 0, flatPhysMax256: 0, autoHit: true, } const meleeOutcome = executeSUnitDmg(attacker, defender, meleePacket) expect(meleeOutcome.hit).toBe(true) expect(meleeOutcome.physDamage256).toBe(10 * FIXED_ONE) expect(meleeOutcome.elemDamage256).toBe(enchantFire256) // 2. Missile attack: strictly 33% fire damage, physical damage remains 10 * FIXED_ONE const rangedPacket: SUnitDmgPacket = { skillId: 52, attackKind: 'missile', isEnchant: true, elemType: 'fire', elemMin256: enchantFire256, elemMax256: enchantFire256, flatPhysMin256: 0, flatPhysMax256: 0, autoHit: true, } const rangedOutcome = executeSUnitDmg(attacker, defender, rangedPacket) expect(rangedOutcome.hit).toBe(true) // Physical damage is completely unaffected by the 33% elemental ranged penalty! expect(rangedOutcome.physDamage256).toBe(10 * FIXED_ONE) // Elemental fire damage is strictly Math.trunc(25600 * 33 / 100) = 8448 (= 33.0 fire damage) const expectedRangedFire256 = Math.trunc((enchantFire256 * 33) / 100) expect(rangedOutcome.elemDamage256).toBe(expectedRangedFire256) expect(rangedOutcome.elemDamage256 / FIXED_ONE).toBe(33) }) it('verifies CombatWorld sandbox tickPlayer correctly differentiates melee (100%) vs ranged (33%) weapons', () => { const world: CombatWorld = createWorld(100, 100) const targetMonster: Monster = { index: 0, stats: { id: 'test_zombie', name: 'Zombie', hp: 2000, damage: 0, cooldownTicks: 25, reach: 20, aggroRadius: 0, speed: 0, xp: 50, }, x: 120, y: 100, hp: 2000, cooldown: 0, state: 'idle', facing: 0, hitFlash: 0, corpseTicks: 0, } world.monsters.push(targetMonster) // 1. Melee attack with Enchant slvl 20 (base 68.5 - 89.5, average 79) const baseDmg20 = calculateEnchantDamage(20) world.player.enchantTicks = 5000 world.player.enchantDamage = { min: baseDmg20.min, max: baseDmg20.max } world.player.isRanged = false world.player.isSorceress = false // non-sorc for pure 100% vs 33% check const prevHp = targetMonster.hp tickCombat(world, { movement: { x: 0, y: 0 }, attack: true }, baseOptions, mockTerrain, [0, 100]) const meleeHitEvent = world.events.find(e => e.kind === 'monsterHit') expect(meleeHitEvent).toBeDefined() // total = baseOptions.playerDamage (10) + Math.round(79) = 89 const meleeTotalDamage = meleeHitEvent!.amount! expect(meleeTotalDamage).toBe(10 + Math.round((baseDmg20.min + baseDmg20.max) / 2)) expect(targetMonster.hp).toBe(prevHp - meleeTotalDamage) // Reset cooldown and switch to ranged weapon (bow/throwing) world.player.cooldown = 0 world.player.isRanged = true const beforeRangedHp = targetMonster.hp tickCombat(world, { movement: { x: 0, y: 0 }, attack: true }, baseOptions, mockTerrain, [0, 100]) const rangedHitEvent = world.events.find(e => e.kind === 'monsterHit') expect(rangedHitEvent).toBeDefined() const rangedTotalDamage = rangedHitEvent!.amount! const expectedRangedFire = Math.round(calculateEnchantAttackDamage({ enchantDamage: baseDmg20, isRanged: true, isSorceress: false, }).average) expect(rangedTotalDamage).toBe(10 + expectedRangedFire) expect(targetMonster.hp).toBe(beforeRangedHp - rangedTotalDamage) // Confirm Enchant fire component on ranged is strictly 33% of melee fire component const meleeFireComponent = meleeTotalDamage - 10 const rangedFireComponent = rangedTotalDamage - 10 expect(rangedFireComponent / meleeFireComponent).toBeCloseTo(0.33, 1) }) it('verifies dynamic weapon swapping mid-buff alternates between 100% melee double-dip and 33% ranged penalty', () => { const world: CombatWorld = createWorld(100, 100) const dummy: Monster = { index: 0, stats: { id: 'd', name: 'D', hp: 10000, damage: 0, cooldownTicks: 25, reach: 20, aggroRadius: 0, speed: 0, xp: 10 }, x: 120, y: 100, hp: 10000, cooldown: 0, state: 'idle', facing: 0, hitFlash: 0, corpseTicks: 0, } world.monsters.push(dummy) applyEnchantBuff(world.player, 20, { casterFireMasterySlvl: 20 }) world.player.isSorceress = true world.player.fireMasterySlvl = 20 // Strike 1: Melee -> deals 10 physical + 547 fire = 557 world.player.isRanged = false world.player.cooldown = 0 tickCombat(world, { movement: { x: 0, y: 0 }, attack: true }, baseOptions, mockTerrain, [0, 100]) expect(world.events.find(e => e.kind === 'monsterHit')?.amount).toBe(557) // Strike 2: Swap to bow (isRanged: true) -> deals 10 physical + 69 fire = 79 world.events = [] world.player.isRanged = true world.player.cooldown = 0 tickCombat(world, { movement: { x: 0, y: 0 }, attack: true }, baseOptions, mockTerrain, [0, 100]) expect(world.events.find(e => e.kind === 'monsterHit')?.amount).toBe(79) // Strike 3: Swap back to melee weapon -> deals 557 again world.events = [] world.player.isRanged = false world.player.cooldown = 0 tickCombat(world, { movement: { x: 0, y: 0 }, attack: true }, baseOptions, mockTerrain, [0, 100]) expect(world.events.find(e => e.kind === 'monsterHit')?.amount).toBe(557) }) }) // ========================================================================== // Suite 2: Sorceress Melee Double-Dip Mechanics // ========================================================================== describe('Suite 2: Sorceress Melee Double-Dip Mechanics', () => { it('verifies exact mathematical double-dip formula at slvl 20: base * (1 + 1.63)^2 = 473.8 - 619.1 fire damage', () => { const slvl = 20 const fmSlvl = 20 const fmBonus = calculateFireMasteryBonus(fmSlvl) // +163% expect(fmBonus).toBe(163) const fmMultiplier = 1.0 + fmBonus / 100 // 2.63 // Base Enchant slvl 20 damage (no FM): 68.5 - 89.5 const baseDmg = calculateEnchantDamage(slvl) expect(baseDmg.min).toBe(68.5) expect(baseDmg.max).toBe(89.5) // 1. Cast phase: Enchant damage is amplified by Fire Mastery upon casting const castDmg = calculateEnchantDamage(slvl, undefined, fmSlvl) expect(castDmg.min).toBe(180.2) // Math.round(68.5 * 2.63 * 10) / 10 = 180.2 expect(castDmg.max).toBe(235.4) // Math.round(89.5 * 2.63 * 10) / 10 = 235.4 // 2. Melee strike phase: Sorceress hits in melee, applying Fire Mastery (+163%) AGAIN const meleeStrike = calculateEnchantAttackDamage({ enchantDamage: castDmg, isRanged: false, isSorceress: true, fireMasterySlvl: fmSlvl, }) // Exact double-dip bounds: // min = Math.round(180.2 * 2.63 * 10) / 10 = 473.9 // max = Math.round(235.4 * 2.63 * 10) / 10 = 619.1 expect(meleeStrike.min).toBe(473.9) expect(meleeStrike.max).toBe(619.1) expect(meleeStrike.average).toBe(546.5) // Raw unrounded base * (1 + 1.63)^2: // 68.5 * 2.63^2 = 473.80775 // 89.5 * 2.63^2 = 619.06275 expect(meleeStrike.min).toBeCloseTo(baseDmg.min * fmMultiplier * fmMultiplier, 0) expect(meleeStrike.max).toBeCloseTo(baseDmg.max * fmMultiplier * fmMultiplier, 0) }) it('verifies Sorceress attacking at range strictly does NOT receive the second Fire Mastery application', () => { const slvl = 20 const fmSlvl = 20 const castDmg = calculateEnchantDamage(slvl, undefined, fmSlvl) // 180.2 - 235.4 const rangedStrike = calculateEnchantAttackDamage({ enchantDamage: castDmg, isRanged: true, isSorceress: true, fireMasterySlvl: fmSlvl, }) // Strictly 33% of cast damage: // min = Math.round(180.2 * 0.33 * 10) / 10 = 59.5 // max = Math.round(235.4 * 0.33 * 10) / 10 = 77.7 expect(rangedStrike.min).toBe(59.5) expect(rangedStrike.max).toBe(77.7) expect(rangedStrike.average).toBe(68.6) // Prove that if second FM had been erroneously applied, it would have been: // 473.9 * 0.33 = 156.4, which is 2.63x higher! expect(rangedStrike.min).toBeLessThan(100) expect(rangedStrike.max).toBeLessThan(100) }) it('verifies double-dip scaling across full Fire Mastery progression (slvl 1, 10, 20, 30, 40)', () => { for (const fmSlvl of [1, 10, 20, 30, 40]) { const fmBonus = calculateFireMasteryBonus(fmSlvl) const fmMul = 1.0 + fmBonus / 100 const expectedDoubleMultiplier = fmMul * fmMul const baseDmg = calculateEnchantDamage(20) // base 68.5 - 89.5 const castDmg = calculateEnchantDamage(20, undefined, fmSlvl) const meleeHit = calculateEnchantAttackDamage({ enchantDamage: castDmg, isRanged: false, isSorceress: true, fireMasterySlvl: fmSlvl, }) // Verify that melee damage scales as (1 + fmBonus/100)^2 const actualRatioMin = meleeHit.min / baseDmg.min const actualRatioMax = meleeHit.max / baseDmg.max expect(actualRatioMin).toBeCloseTo(expectedDoubleMultiplier, 1) expect(actualRatioMax).toBeCloseTo(expectedDoubleMultiplier, 1) } }) it('verifies interaction of Warmth synergy (+180% = 2.8x) and Fire Mastery double-dip ((2.63)^2 = 6.9169x)', () => { // Warmth 20 hard points (+180%), Fire Mastery 20 (+163%) const warmthPts = 20 const fmSlvl = 20 const baseDmg = calculateEnchantDamage(20) // 68.5 - 89.5 const buffedCastDmg = calculateEnchantDamage(20, { warmth: warmthPts }, fmSlvl) // Warmth multiplier = 1 + 20*0.09 = 2.8 // FM multiplier = 1 + 1.63 = 2.63 // Total cast multiplier = 2.8 * 2.63 = 7.364 // Min: 68.5 * 7.364 = 504.434 -> 504.4 // Max: 89.5 * 7.364 = 659.078 -> 659.1 expect(buffedCastDmg.min).toBe(504.4) expect(buffedCastDmg.max).toBe(659.1) // Melee double dip: applies FM 2.63 again! // Total multiplier = 2.8 * (2.63)^2 = 19.36732x! const meleeHit = calculateEnchantAttackDamage({ enchantDamage: buffedCastDmg, isRanged: false, isSorceress: true, fireMasterySlvl: fmSlvl, }) // Min: 504.4 * 2.63 = 1326.572 -> 1326.6 // Max: 659.1 * 2.63 = 1733.433 -> 1733.4 expect(meleeHit.min).toBe(1326.6) expect(meleeHit.max).toBe(1733.4) expect(meleeHit.average).toBe(1530.0) // Ranged hit: 33% penalty and NO second FM const rangedHit = calculateEnchantAttackDamage({ enchantDamage: buffedCastDmg, isRanged: true, isSorceress: true, fireMasterySlvl: fmSlvl, }) expect(rangedHit.min).toBe(166.5) // 504.4 * 0.33 = 166.452 -> 166.5 expect(rangedHit.max).toBe(217.5) // 659.1 * 0.33 = 217.503 -> 217.5 expect(rangedHit.average).toBe(192.0) }) it('verifies extreme skill level scaling (slvl 60 Enchant + slvl 60 Fire Mastery) maintains numerical stability', () => { const slvl = 60 const fmBonus = calculateFireMasteryBonus(60) // 30 + 59 * 7 = 443% -> 5.43x expect(fmBonus).toBe(443) const baseDmg = calculateEnchantDamage(60) expect(baseDmg.min).toBeGreaterThan(0) expect(baseDmg.max).toBeGreaterThan(baseDmg.min) expect(Number.isFinite(baseDmg.min)).toBe(true) const castDmg = calculateEnchantDamage(60, undefined, 60) expect(castDmg.min).toBeCloseTo(baseDmg.min * 5.43, 0) const meleeDmg = calculateEnchantAttackDamage({ enchantDamage: castDmg, isRanged: false, isSorceress: true, fireMasterySlvl: 60, }) // Double dip = base * (5.43)^2 expect(meleeDmg.min).toBeCloseTo(baseDmg.min * 5.43 * 5.43, 0) expect(Number.isFinite(meleeDmg.average)).toBe(true) }) }) // ========================================================================== // Suite 3: Non-Sorceress Buff Recipient (Allies / Pets) // ========================================================================== describe('Suite 3: Non-Sorceress Buff Recipient (Allies / Pets)', () => { it('verifies non-Sorceress melee ally receives single-multiplied buff damage without second Fire Mastery', () => { // Sorceress with slvl 20 Enchant and slvl 20 Fire Mastery casts on an ally const castDmg = calculateEnchantDamage(20, undefined, 20) // 180.2 - 235.4 // Ally (isSorceress: false, no Fire Mastery) strikes in melee const allyMelee = calculateEnchantAttackDamage({ enchantDamage: castDmg, isRanged: false, isSorceress: false, }) // Damage must match the single-multiplied cast buff exactly: expect(allyMelee.min).toBe(180.2) expect(allyMelee.max).toBe(235.4) expect(allyMelee.average).toBe(207.8) // Contrast with Sorceress attacking: const sorcMelee = calculateEnchantAttackDamage({ enchantDamage: castDmg, isRanged: false, isSorceress: true, fireMasterySlvl: 20, }) expect(sorcMelee.average).toBe(546.5) expect(sorcMelee.average).toBeCloseTo(allyMelee.average * 2.63, 0) }) it('verifies non-Sorceress ranged ally receives strictly 33% of single-multiplied buff damage', () => { const castDmg = calculateEnchantDamage(20, undefined, 20) // 180.2 - 235.4 // Rogue mercenary or bow Amazon strikes at range const allyRanged = calculateEnchantAttackDamage({ enchantDamage: castDmg, isRanged: true, isSorceress: false, }) expect(allyRanged.min).toBe(59.5) // 180.2 * 0.33 = 59.466 -> 59.5 expect(allyRanged.max).toBe(77.7) // 235.4 * 0.33 = 77.682 -> 77.7 expect(allyRanged.average).toBe(68.6) }) it('verifies Party Sorceress 2 with lower Fire Mastery applies her own Fire Mastery upon melee strike', () => { // Sorc 1 (slvl 20 Enchant, slvl 20 FM) casts on Sorc 2 (who has slvl 10 FM = +93% / 1.93x) const castDmg = calculateEnchantDamage(20, undefined, 20) // 180.2 - 235.4 // Sorc 2 attacks in melee with her own FM slvl 10: const sorc2Melee = calculateEnchantAttackDamage({ enchantDamage: castDmg, isRanged: false, isSorceress: true, fireMasterySlvl: 10, }) // Min: 180.2 * 1.93 = 347.786 -> 347.8 // Max: 235.4 * 1.93 = 454.322 -> 454.3 expect(sorc2Melee.min).toBe(347.8) expect(sorc2Melee.max).toBe(454.3) expect(sorc2Melee.average).toBe(401.1) // When Sorc 2 attacks at range, her FM is not applied, strictly 33% of cast damage: const sorc2Ranged = calculateEnchantAttackDamage({ enchantDamage: castDmg, isRanged: true, isSorceress: true, fireMasterySlvl: 10, }) expect(sorc2Ranged.min).toBe(59.5) expect(sorc2Ranged.max).toBe(77.7) }) it('verifies CombatWorld multi-player sandbox: Sorceress vs Barbarian ally dealing Enchant damage', () => { const world: CombatWorld = createWorld(100, 100) // Player 0: Sorceress const barbarianAlly = addPlayer(world, 105, 100) // Player 1: Barbarian const castDmg = calculateEnchantDamage(20, undefined, 20) // 180.2 - 235.4 // Apply buff to both applyEnchantBuff(world.player, 20, { casterFireMasterySlvl: 20 }) world.player.isSorceress = true world.player.fireMasterySlvl = 20 world.player.isRanged = false applyEnchantBuff(barbarianAlly, 20, { casterFireMasterySlvl: 20 }) barbarianAlly.isSorceress = false barbarianAlly.fireMasterySlvl = 0 barbarianAlly.isRanged = false // Spawn 2 target dummies const dummySorc: Monster = { index: 0, stats: { id: 'dummy_s', name: 'Dummy Sorc', hp: 2000, damage: 0, cooldownTicks: 25, reach: 20, aggroRadius: 0, speed: 0, xp: 10 }, x: 120, y: 100, hp: 2000, cooldown: 0, state: 'idle', facing: 0, hitFlash: 0, corpseTicks: 0, } const dummyBarb: Monster = { index: 1, stats: { id: 'dummy_b', name: 'Dummy Barb', hp: 2000, damage: 0, cooldownTicks: 25, reach: 20, aggroRadius: 0, speed: 0, xp: 10 }, x: 105, y: 120, hp: 2000, cooldown: 0, state: 'idle', facing: 0, hitFlash: 0, corpseTicks: 0, } world.monsters.push(dummySorc, dummyBarb) // Tick player 0 (Sorceress melee attack) tickCombat(world, { movement: { x: 0, y: 0 }, attack: true }, baseOptions, mockTerrain, [0, 100]) const sorcEvent = world.events.find(e => e.kind === 'monsterHit') expect(sorcEvent).toBeDefined() // Sorc damage = base (10) + double-dip fire average (546.5 -> 547) = 557 expect(sorcEvent!.amount).toBe(10 + Math.round(546.5)) // Now tick barbarian ally melee attack world.events = [] // Let barbarian attack barbarianAlly.cooldown = 0 // We simulate barbarian's tick via executeSUnitDmg directly or checking calculation const barbHit = calculateEnchantAttackDamage({ enchantDamage: barbarianAlly.enchantDamage!, isRanged: barbarianAlly.isRanged, isSorceress: barbarianAlly.isSorceress, fireMasterySlvl: barbarianAlly.fireMasterySlvl, }) // Barbarian damage is single-dipped: 207.8 -> 208 expect(barbHit.average).toBe(207.8) expect(sorcEvent!.amount! - 10).toBeGreaterThan(barbHit.average * 2.5) }) }) // ========================================================================== // Suite 4: Fire Immunity Preservation & Resistance Handling // ========================================================================== describe('Suite 4: Fire Immunity Preservation & Resistance Handling', () => { it('strictly preserves fire immunity at 100%, 110%, and 150% Fire Resist (0 fire damage dealt)', () => { const castDmg = calculateEnchantDamage(20, undefined, 20) for (const res of [100, 110, 125, 150, 200]) { // Melee Sorceress with slvl 20 Fire Mastery vs immune monster const immuneMelee = calculateEnchantAttackDamage({ enchantDamage: castDmg, isRanged: false, isSorceress: true, fireMasterySlvl: 20, targetFireResist: res, }) expect(immuneMelee.min).toBe(0) expect(immuneMelee.max).toBe(0) expect(immuneMelee.average).toBe(0) // Ranged Sorceress vs immune monster const immuneRanged = calculateEnchantAttackDamage({ enchantDamage: castDmg, isRanged: true, isSorceress: true, fireMasterySlvl: 20, targetFireResist: res, }) expect(immuneRanged.min).toBe(0) expect(immuneRanged.max).toBe(0) expect(immuneRanged.average).toBe(0) // Extreme Fire Mastery slvl 40 (+303%) vs immune monster const extremeFmMelee = calculateEnchantAttackDamage({ enchantDamage: castDmg, isRanged: false, isSorceress: true, fireMasterySlvl: 40, targetFireResist: res, }) expect(extremeFmMelee.min).toBe(0) expect(extremeFmMelee.max).toBe(0) expect(extremeFmMelee.average).toBe(0) } }) it('verifies combat pipeline executeSUnitDmg flags immuneToElem = true and elemDamage256 = 0 for res >= 100', () => { const attacker = createCombatUnit('attacker_fm40', { fireMasterySlvl: 40, // +303% FM! hasEnchantState: true, }) for (const res of [100, 110, 150]) { const defender = createCombatUnit(`defender_res_${res}`, { fireResist: res }) const packet: SUnitDmgPacket = { skillId: 52, attackKind: 'melee', isEnchant: true, elemType: 'fire', elemMin256: 50000, elemMax256: 50000, flatPhysMin256: 0, flatPhysMax256: 0, autoHit: true, } const outcome = executeSUnitDmg(attacker, defender, packet) expect(outcome.hit).toBe(true) expect(outcome.immuneToElem).toBe(true) expect(outcome.elemDamage256).toBe(0) // Physical damage is applied normally (weapon base 10) expect(outcome.physDamage256).toBe(10 * FIXED_ONE) expect(outcome.totalDamage).toBe(10) } }) it('verifies proportional mitigation at 50% and 0% Fire Resist without floor clamp violations', () => { const castDmg = calculateEnchantDamage(20, undefined, 20) // 0% Fire Resist: 100% damage unmitigated const res0 = calculateEnchantAttackDamage({ enchantDamage: castDmg, isRanged: false, isSorceress: true, fireMasterySlvl: 20, targetFireResist: 0, }) expect(res0.min).toBe(473.9) expect(res0.max).toBe(619.1) expect(res0.average).toBe(546.5) // 50% Fire Resist: exactly 50% reduction const res50 = calculateEnchantAttackDamage({ enchantDamage: castDmg, isRanged: false, isSorceress: true, fireMasterySlvl: 20, targetFireResist: 50, }) expect(res50.min).toBe(Math.round(473.9 * 0.5 * 10) / 10) // 237.0 expect(res50.max).toBe(Math.round(619.1 * 0.5 * 10) / 10) // 309.6 expect(res50.average).toBe(Math.round(546.5 * 0.5 * 10) / 10) // 273.3 // 75% Fire Resist: exactly 75% reduction (25% remaining) const res75 = calculateEnchantAttackDamage({ enchantDamage: castDmg, isRanged: false, isSorceress: true, fireMasterySlvl: 20, targetFireResist: 75, }) expect(res75.min).toBe(118.5) expect(res75.max).toBe(154.8) expect(res75.average).toBe(136.7) // 99% Fire Resist: non-immune boundary, exactly 1% remaining const res99 = calculateEnchantAttackDamage({ enchantDamage: castDmg, isRanged: false, isSorceress: true, fireMasterySlvl: 20, targetFireResist: 99, }) expect(res99.min).toBeGreaterThan(0) expect(res99.average).toBe(Math.round(546.5 * 0.01 * 10) / 10) // 5.5 }) it('verifies computeEffectiveResistance 1.13c immunity breaking and -100% floor clamp rules', () => { // 1. Natural immunity without Conviction / Lower Resist cannot be broken by passive pierce const immuneRes = computeEffectiveResistance({ baseRes: 100, passiveEnemyPierce: 50, // e.g. facets / gear }) expect(immuneRes.isImmune).toBe(true) expect(immuneRes.effectiveRes).toBe(100) // 2. Negative resistance cannot go below authentic 1.13c -100% cap const extremeNegRes = computeEffectiveResistance({ baseRes: -80, convictionPierce: 50, }) // -80 - 50 = -130, clamped to -100! expect(extremeNegRes.effectiveRes).toBe(-100) }) it('verifies CombatWorld sandbox tickCombat against immune monster (res=100) deals only physical damage', () => { const world: CombatWorld = createWorld(100, 100) const immuneMonster: Monster = { index: 0, stats: { id: 'fire_immune_pitlord', name: 'Pit Lord', hp: 500, damage: 0, cooldownTicks: 25, reach: 20, aggroRadius: 0, speed: 0, xp: 50 }, x: 120, y: 100, hp: 500, cooldown: 0, state: 'idle', facing: 0, hitFlash: 0, corpseTicks: 0, } ;(immuneMonster as any).fireResist = 100 // Set fire resist to 100% world.monsters.push(immuneMonster) // Buff player with massive Enchant + Fire Mastery applyEnchantBuff(world.player, 20, { casterFireMasterySlvl: 20 }) world.player.isSorceress = true world.player.fireMasterySlvl = 20 world.player.isRanged = false // Player attacks tickCombat(world, { movement: { x: 0, y: 0 }, attack: true }, baseOptions, mockTerrain, [0, 100]) const hitEvent = world.events.find(e => e.kind === 'monsterHit') expect(hitEvent).toBeDefined() // Because monster is fire immune, only baseOptions.playerDamage (10) is dealt! expect(hitEvent!.amount).toBe(10) expect(immuneMonster.hp).toBe(490) }) it('ADVERSARIAL GAP CHECK: verifies whether tickPlayer handles monster.stats.resistances?.fire', () => { const world: CombatWorld = createWorld(100, 100) const monsterWithResistancesBag: Monster = { index: 0, stats: { id: 'typed_res_monster', name: 'Resistant Monster', hp: 500, damage: 0, cooldownTicks: 25, reach: 20, aggroRadius: 0, speed: 0, xp: 50, resistances: { fire: 100 }, resists: { fire: 100 }, }, x: 120, y: 100, hp: 500, cooldown: 0, state: 'idle', facing: 0, hitFlash: 0, corpseTicks: 0, } world.monsters.push(monsterWithResistancesBag) applyEnchantBuff(world.player, 20, { casterFireMasterySlvl: 20 }) world.player.isSorceress = true world.player.fireMasterySlvl = 20 world.player.isRanged = false tickCombat(world, { movement: { x: 0, y: 0 }, attack: true }, baseOptions, mockTerrain, [0, 100]) const hitEvent = world.events.find(e => e.kind === 'monsterHit') expect(hitEvent).toBeDefined() // In combat.ts line 895: targetFireResist: (target as any).fireResist ?? (target.stats as any).fireResist // Because target.stats.resistances?.fire is NOT inspected, targetFireResist resolves to undefined! // This results in full unmitigated fire damage (557 instead of 10): const actualDmg = hitEvent!.amount! expect(actualDmg).toBe(557) // EMPIRICAL PROOF OF GAP: resistances bag ignored by combat.ts:895 }) it('proves empirically that Fire Mastery provides 0% enemy fire resistance pierce (unlike Cold Mastery)', () => { // Monster with 50% fire resist const castDmg = calculateEnchantDamage(20, undefined, 20) // With FM slvl 20 (+163% damage bonus) const withFm = calculateEnchantAttackDamage({ enchantDamage: castDmg, isRanged: false, isSorceress: true, fireMasterySlvl: 20, targetFireResist: 50, }) // Without FM (e.g. ally) const withoutFm = calculateEnchantAttackDamage({ enchantDamage: castDmg, isRanged: false, isSorceress: false, targetFireResist: 50, }) // Unresisted baselines const unresistedWithFm = calculateEnchantAttackDamage({ enchantDamage: castDmg, isRanged: false, isSorceress: true, fireMasterySlvl: 20, targetFireResist: 0, }) const unresistedWithoutFm = calculateEnchantAttackDamage({ enchantDamage: castDmg, isRanged: false, isSorceress: false, targetFireResist: 0, }) // The damage reduction ratio for both is EXACTLY (100 - 50)/100 = 0.5! // This proves Fire Mastery does not pierce enemy resistance by even 1%! expect(withFm.average / unresistedWithFm.average).toBeCloseTo(0.5, 2) expect(withoutFm.average / unresistedWithoutFm.average).toBeCloseTo(0.5, 2) }) }) // ========================================================================== // Suite 5: Adversarial Boundary & Lifecycle Stress Tests // ========================================================================== describe('Suite 5: Adversarial Boundary & Lifecycle Stress Tests', () => { it('verifies buff expiration countdown: damage drops to 0 fire bonus after enchantTicks reaches 0', () => { const world: CombatWorld = createWorld(100, 100) const monster: Monster = { index: 0, stats: { id: 'dummy', name: 'Dummy', hp: 2000, damage: 0, cooldownTicks: 25, reach: 20, aggroRadius: 0, speed: 0, xp: 50 }, x: 120, y: 100, hp: 2000, cooldown: 0, state: 'idle', facing: 0, hitFlash: 0, corpseTicks: 0, } world.monsters.push(monster) applyEnchantBuff(world.player, 20, { casterFireMasterySlvl: 20 }) world.player.isSorceress = true world.player.fireMasterySlvl = 20 // Set ticks to exactly 2 ticks left world.player.enchantTicks = 2 // Tick 1: buff still active (enchantTicks decreases to 1) tickCombat(world, { movement: { x: 0, y: 0 }, attack: true }, baseOptions, mockTerrain, [0, 100]) expect(world.events.find(e => e.kind === 'monsterHit')?.amount).toBe(10 + Math.round(546.5)) expect(world.player.enchantTicks).toBe(1) // Tick 2: buff expires (enchantTicks decrements to 0, enchantDamage cleared) world.player.cooldown = 0 world.events = [] tickCombat(world, { movement: { x: 0, y: 0 }, attack: false }, baseOptions, mockTerrain, [0, 100]) expect(world.player.enchantTicks).toBe(0) expect(world.player.enchantDamage).toBeUndefined() // Tick 3: player attacks with expired buff -> strictly physical damage only (10) world.player.cooldown = 0 world.events = [] tickCombat(world, { movement: { x: 0, y: 0 }, attack: true }, baseOptions, mockTerrain, [0, 100]) expect(world.events.find(e => e.kind === 'monsterHit')?.amount).toBe(10) }) it('verifies Attack Rating bonus scaling across slvl 1..40 and non-positive inputs', () => { expect(calculateEnchantAttackRating(1)).toBe(20) expect(calculateEnchantAttackRating(10)).toBe(101) expect(calculateEnchantAttackRating(20)).toBe(191) expect(calculateEnchantAttackRating(30)).toBe(281) expect(calculateEnchantAttackRating(40)).toBe(371) expect(calculateEnchantAttackRating(0)).toBe(0) expect(calculateEnchantAttackRating(-5)).toBe(0) expect(calculateEnchantAttackRating(NaN)).toBe(0) }) it('verifies Enchant duration scaling across slvl 1..40 and non-positive inputs', () => { expect(calculateEnchantDuration(1)).toBe(3600) // 144s expect(calculateEnchantDuration(10)).toBe(9000) // 360s expect(calculateEnchantDuration(20)).toBe(15000) // 600s expect(calculateEnchantDuration(30)).toBe(21000) // 840s expect(calculateEnchantDuration(40)).toBe(27000) // 1080s expect(calculateEnchantDuration(0)).toBe(0) expect(calculateEnchantDuration(-1)).toBe(0) expect(calculateEnchantDuration(NaN)).toBe(0) }) it('verifies whiff event when attacking while no monster is within playerReach', () => { const world: CombatWorld = createWorld(100, 100) const distantMonster: Monster = { index: 0, stats: { id: 'far_monster', name: 'Far', hp: 100, damage: 0, cooldownTicks: 25, reach: 20, aggroRadius: 0, speed: 0, xp: 50 }, x: 300, y: 300, hp: 100, cooldown: 0, state: 'idle', facing: 0, hitFlash: 0, corpseTicks: 0, } world.monsters.push(distantMonster) applyEnchantBuff(world.player, 20, { casterFireMasterySlvl: 20 }) tickCombat(world, { movement: { x: 0, y: 0 }, attack: true }, baseOptions, mockTerrain, [0, 100]) const whiffEvent = world.events.find(e => e.kind === 'playerHit' && e.text === 'whiff') expect(whiffEvent).toBeDefined() expect(whiffEvent!.amount).toBe(0) expect(distantMonster.hp).toBe(100) // unaffected }) }) })