/** * Diablo II: Lord of Destruction v1.13c — Adversarial Challenger Verification Suite: Iteration 2 * Focus: Paladin Skills (IDs 96..125) Mechanics Remediation & Ground Truth Verification * * Adversarially probes: * 1. Smite Equipment Leech Suppression: * - Gear life leech (lifesteal, life_leech, item_parasite, life_leech_pct) is strictly 0. * - Gear mana leech (manasteal, mana_leech, item_parasitemana, mana_leech_pct) is strictly 0. * - Contrast: non-smite physical melee attacks correctly leech life and mana. * 2. Life Tap Curse Healing Preservation: * - Smite against a target cursed with Life Tap restores exactly 50% of dealt physical damage. * - Smite with BOTH Life Tap on defender AND 50% gear leech on attacker heals strictly 50% (not 100%). * - Smite against Physical Immune target with Life Tap heals strictly 0 HP. * - Smite against 50% Physical Resistance target heals 50% of the mitigated damage. * 3. Holy Shield Blocking Chance Parity: * - dm56 formula parity across all levels 1..50: 10 + Math.floor((3300 * lvl) / (100 * (lvl + 6))). * - slvl 1 evaluates to exactly 14% (not 13%). * - slvl 20 evaluates to exactly 35% (not 36%). * - Runtime state application toblock bonus matches across slvls. * 4. SrvSt Pre-Cast Equipment Guards: * - hasShield === false rejects Smite (#97) and Holy Shield (#117) with 0 mana spent. * - weaponItemType === 'bow' or 'crossbow' or isRanged === true rejects Sacrifice (#96) and Zeal (#106). * - undefined/omitted equipment properties retain permissive fallback for minimal fixtures. */ 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 { executeSUnitDmg, type CombatUnitContext, } from '../../../src/game/engine/combat-pipeline.ts' import { calculateHolyShieldStats, validatePaladinSkillEquipment, } from '../../../src/game/skills/paladin-combat.ts' import { computeDiminishingReturns } from '../../../src/game/engine/calc-ast.ts' import { executeSkillCore113c, evaluateSkill113c } from '../../../src/game/skills/registry.ts' import { MissileEngine } from '../../../src/game/engine/missile-engine.ts' import { AuraScanner } from '../../../src/game/engine/aura-scanner.ts' import { SummonManager } from '../../../src/game/engine/summon-manager.ts' import type { SkillExecContext } from '../../../src/game/skills/types.ts' function createExecContext( registry: any, skillId: number, slvl: number, caster: CombatUnitContext, targets: CombatUnitContext[] = [], ): SkillExecContext { const skill = registry.getSkillById(skillId)! const evalResult = evaluateSkill113c({ registry, skill, slvl, blvl: slvl, statList: caster.statList, }) return { registry, skill, evalResult, caster, targets, targetPositions: new Map(), corpses: [], missileEngine: new MissileEngine(registry), auraScanner: new AuraScanner(registry), summonManager: new SummonManager(registry), currentTick: 0, targetX: 0, targetY: 0, } } describe('Adversarial Challenger Verification Suite — Paladin Iteration 2', () => { // ========================================================================== // Pillar 1: Smite Equipment Leech Suppression // ========================================================================== describe('Pillar 1: Smite Equipment Leech Suppression', () => { it('suppresses all gear life leech and mana leech keys during Smite physical attack', async () => { const registry = await getSharedDataRegistry() const initialHp = 500 * FIXED_ONE const initialMana = 300 * FIXED_ONE // Test multiple leech keys simultaneously const attackerStats = new UnitStatList(registry, { hitpoints: initialHp, maxhp: 2000 * FIXED_ONE, mana: initialMana, maxmana: 1000 * FIXED_ONE, lifesteal: 20, life_leech: 15, item_parasite: 10, manasteal: 25, mana_leech: 10, item_parasitemana: 15, }) const attacker: CombatUnitContext = { id: 'pal_smiter', name: 'Paladin', statList: attackerStats, stateBus: new StateBus(attackerStats, registry), unitType: 'player', hasShield: true, } const defenderStats = new UnitStatList(registry, { hitpoints: 50000 * FIXED_ONE, maxhp: 50000 * FIXED_ONE, damageresist: 0, }) const defender: CombatUnitContext = { id: 'target_dummy', name: 'Target Dummy', statList: defenderStats, stateBus: new StateBus(defenderStats, registry), unitType: 'monster', } // Smite attack dealing 1000 physical damage (srcDam: 0 for weapon physical damage) const out = executeSUnitDmg(attacker, defender, { skillId: 97, attackKind: 'melee', srcDam: 0, flatPhysMin256: 1000 * FIXED_ONE, flatPhysMax256: 1000 * FIXED_ONE, autoHit: true, unblockable: true, }) expect(out.hit).toBe(true) expect(out.physDamage256).toBe(1000 * FIXED_ONE) expect(out.attackerHealed256 ?? 0).toBe(0) expect(out.attackerManaLeeched256 ?? 0).toBe(0) expect(attackerStats.getHp256()).toBe(initialHp) expect(attackerStats.getMana256()).toBe(initialMana) }) it('contrast test: regular non-Smite attack leeches life and mana normally with same gear', async () => { const registry = await getSharedDataRegistry() const initialHp = 500 * FIXED_ONE const initialMana = 300 * FIXED_ONE const attackerStats = new UnitStatList(registry, { hitpoints: initialHp, maxhp: 2000 * FIXED_ONE, mana: initialMana, maxmana: 1000 * FIXED_ONE, lifesteal: 20, // 20% life leech manasteal: 10, // 10% mana leech }) const attacker: CombatUnitContext = { id: 'pal_standard_attacker', name: 'Paladin', statList: attackerStats, stateBus: new StateBus(attackerStats, registry), unitType: 'player', weaponMinPhys: 0, weaponMaxPhys: 0, } const defenderStats = new UnitStatList(registry, { hitpoints: 50000 * FIXED_ONE, maxhp: 50000 * FIXED_ONE, damageresist: 0, }) const defender: CombatUnitContext = { id: 'target_dummy', name: 'Target Dummy', statList: defenderStats, stateBus: new StateBus(defenderStats, registry), unitType: 'monster', } // Standard melee attack dealing 500 physical damage const out = executeSUnitDmg(attacker, defender, { skillId: 0, // Standard Attack attackKind: 'melee', srcDam: 128, flatPhysMin256: 500 * FIXED_ONE, flatPhysMax256: 500 * FIXED_ONE, autoHit: true, }) expect(out.hit).toBe(true) expect(out.physDamage256).toBe(500 * FIXED_ONE) // 20% of 500 = 100 HP healed expect(out.attackerHealed256).toBe(100 * FIXED_ONE) // 10% of 500 = 50 Mana leeched expect(out.attackerManaLeeched256).toBe(50 * FIXED_ONE) expect(attackerStats.getHp256()).toBe(initialHp + 100 * FIXED_ONE) expect(attackerStats.getMana256()).toBe(initialMana + 50 * FIXED_ONE) }) it('suppresses gear leech across an adversarial sweep of high leech percentages (1% to 200%)', async () => { const registry = await getSharedDataRegistry() const testPcts = [1, 5, 10, 25, 50, 100, 200] for (const pct of testPcts) { const initialHp = 1000 * FIXED_ONE const initialMana = 500 * FIXED_ONE const attackerStats = new UnitStatList(registry, { hitpoints: initialHp, maxhp: 10000 * FIXED_ONE, mana: initialMana, maxmana: 5000 * FIXED_ONE, lifesteal: pct, manasteal: pct, }) const attacker: CombatUnitContext = { id: `pal_sweep_${pct}`, name: 'Paladin', statList: attackerStats, stateBus: new StateBus(attackerStats, registry), unitType: 'player', hasShield: true, } const defenderStats = new UnitStatList(registry, { hitpoints: 20000 * FIXED_ONE, maxhp: 20000 * FIXED_ONE, damageresist: 0, }) const defender: CombatUnitContext = { id: `dummy_${pct}`, name: 'Dummy', statList: defenderStats, stateBus: new StateBus(defenderStats, registry), unitType: 'monster', } const out = executeSUnitDmg(attacker, defender, { skillId: 97, attackKind: 'melee', srcDam: 0, flatPhysMin256: 800 * FIXED_ONE, flatPhysMax256: 800 * FIXED_ONE, autoHit: true, }) expect(out.hit).toBe(true) expect(out.attackerHealed256 ?? 0).toBe(0) expect(out.attackerManaLeeched256 ?? 0).toBe(0) expect(attackerStats.getHp256()).toBe(initialHp) expect(attackerStats.getMana256()).toBe(initialMana) } }) }) // ========================================================================== // Pillar 2: Life Tap Curse Healing Preservation on Smite // ========================================================================== describe('Pillar 2: Life Tap Curse Healing Preservation on Smite', () => { it('restores exactly 50% of physical damage dealt as HP when defender is cursed with Life Tap', async () => { const registry = await getSharedDataRegistry() const initialHp = 300 * FIXED_ONE const attackerStats = new UnitStatList(registry, { hitpoints: initialHp, maxhp: 2000 * FIXED_ONE, }) const attacker: CombatUnitContext = { id: 'pal_smiter_lt', name: 'Paladin', statList: attackerStats, stateBus: new StateBus(attackerStats, registry), unitType: 'player', hasShield: true, } const defenderStats = new UnitStatList(registry, { hitpoints: 10000 * FIXED_ONE, maxhp: 10000 * FIXED_ONE, damageresist: 0, }) const defenderBus = new StateBus(defenderStats, registry) defenderBus.applyState({ stateNameOrId: 'lifetap', slvl: 1, durationFrames: 500 }) const defender: CombatUnitContext = { id: 'dummy_lt', name: 'Dummy', statList: defenderStats, stateBus: defenderBus, unitType: 'monster', } const out = executeSUnitDmg(attacker, defender, { skillId: 97, attackKind: 'melee', srcDam: 0, flatPhysMin256: 600 * FIXED_ONE, flatPhysMax256: 600 * FIXED_ONE, autoHit: true, unblockable: true, }) expect(out.hit).toBe(true) expect(out.physDamage256).toBe(600 * FIXED_ONE) // 50% of 600 = 300 HP healed expect(out.attackerHealed256).toBe(300 * FIXED_ONE) expect(attackerStats.getHp256()).toBe(initialHp + 300 * FIXED_ONE) }) it('when attacker has BOTH gear leech (50%) AND target has Life Tap, heals strictly 50% from Life Tap (gear leech is 0)', async () => { const registry = await getSharedDataRegistry() const initialHp = 400 * FIXED_ONE const initialMana = 200 * FIXED_ONE const attackerStats = new UnitStatList(registry, { hitpoints: initialHp, maxhp: 3000 * FIXED_ONE, mana: initialMana, maxmana: 1000 * FIXED_ONE, lifesteal: 50, // 50% gear life leech manasteal: 50, // 50% gear mana leech }) const attacker: CombatUnitContext = { id: 'pal_smiter_both', name: 'Paladin', statList: attackerStats, stateBus: new StateBus(attackerStats, registry), unitType: 'player', hasShield: true, } const defenderStats = new UnitStatList(registry, { hitpoints: 10000 * FIXED_ONE, maxhp: 10000 * FIXED_ONE, damageresist: 0, }) const defenderBus = new StateBus(defenderStats, registry) defenderBus.applyState({ stateNameOrId: 'lifetap', slvl: 5, durationFrames: 500 }) const defender: CombatUnitContext = { id: 'dummy_both', name: 'Dummy', statList: defenderStats, stateBus: defenderBus, unitType: 'monster', } const out = executeSUnitDmg(attacker, defender, { skillId: 97, attackKind: 'melee', srcDam: 0, flatPhysMin256: 800 * FIXED_ONE, flatPhysMax256: 800 * FIXED_ONE, autoHit: true, }) expect(out.hit).toBe(true) expect(out.physDamage256).toBe(800 * FIXED_ONE) // Must heal ONLY 50% (400), NOT 50% + 50% (800) expect(out.attackerHealed256).toBe(400 * FIXED_ONE) // Mana leech must remain strictly 0 expect(out.attackerManaLeeched256 ?? 0).toBe(0) expect(attackerStats.getHp256()).toBe(initialHp + 400 * FIXED_ONE) expect(attackerStats.getMana256()).toBe(initialMana) }) it('heals 0 HP when striking a Physical Immune target cursed with Life Tap (damage dealt = 0)', async () => { const registry = await getSharedDataRegistry() const initialHp = 500 * FIXED_ONE const attackerStats = new UnitStatList(registry, { hitpoints: initialHp, maxhp: 2000 * FIXED_ONE, }) const attacker: CombatUnitContext = { id: 'pal_smiter_pi', name: 'Paladin', statList: attackerStats, stateBus: new StateBus(attackerStats, registry), unitType: 'player', hasShield: true, } const defenderStats = new UnitStatList(registry, { hitpoints: 10000 * FIXED_ONE, maxhp: 10000 * FIXED_ONE, damageresist: 100, // Physical Immune }) const defenderBus = new StateBus(defenderStats, registry) defenderBus.applyState({ stateNameOrId: 'lifetap', slvl: 1, durationFrames: 500 }) const defender: CombatUnitContext = { id: 'dummy_pi', name: 'Dummy PI', statList: defenderStats, stateBus: defenderBus, unitType: 'monster', } const out = executeSUnitDmg(attacker, defender, { skillId: 97, attackKind: 'melee', srcDam: 0, flatPhysMin256: 1000 * FIXED_ONE, flatPhysMax256: 1000 * FIXED_ONE, autoHit: true, }) expect(out.hit).toBe(true) expect(out.physDamage256).toBe(0) expect(out.immuneToPhys).toBe(true) expect(out.attackerHealed256 ?? 0).toBe(0) expect(attackerStats.getHp256()).toBe(initialHp) }) it('heals exactly 50% of reduced physical damage when target has 50% Physical Resistance', async () => { const registry = await getSharedDataRegistry() const initialHp = 500 * FIXED_ONE const attackerStats = new UnitStatList(registry, { hitpoints: initialHp, maxhp: 2000 * FIXED_ONE, }) const attacker: CombatUnitContext = { id: 'pal_smiter_res', name: 'Paladin', statList: attackerStats, stateBus: new StateBus(attackerStats, registry), unitType: 'player', hasShield: true, } const defenderStats = new UnitStatList(registry, { hitpoints: 10000 * FIXED_ONE, maxhp: 10000 * FIXED_ONE, damageresist: 50, // 50% Physical Resistance }) const defenderBus = new StateBus(defenderStats, registry) defenderBus.applyState({ stateNameOrId: 'lifetap', slvl: 1, durationFrames: 500 }) const defender: CombatUnitContext = { id: 'dummy_res', name: 'Dummy 50% DR', statList: defenderStats, stateBus: defenderBus, unitType: 'monster', } const out = executeSUnitDmg(attacker, defender, { skillId: 97, attackKind: 'melee', srcDam: 0, flatPhysMin256: 1000 * FIXED_ONE, flatPhysMax256: 1000 * FIXED_ONE, autoHit: true, }) expect(out.hit).toBe(true) // 1000 raw -> 500 final phys damage expect(out.physDamage256).toBe(500 * FIXED_ONE) // 50% of 500 = 250 HP healed expect(out.attackerHealed256).toBe(250 * FIXED_ONE) expect(attackerStats.getHp256()).toBe(initialHp + 250 * FIXED_ONE) }) }) // ========================================================================== // Pillar 3: Holy Shield Blocking Chance Parity (dm56) // ========================================================================== describe('Pillar 3: Holy Shield Blocking Chance Parity (dm56)', () => { // Exact D2 1.13c dm56 oracle function dm56Oracle(lvl: number): number { const l = Math.max(1, lvl) return 10 + Math.floor((3300 * l) / (100 * (l + 6))) } it('verifies 100% exact parity with dm56 oracle across slvls 1 through 50', () => { const mismatches: { slvl: number; hs: number; oracle: number }[] = [] for (let slvl = 1; slvl <= 50; slvl++) { const hs = calculateHolyShieldStats(slvl).blockChancePct const oracle = dm56Oracle(slvl) const calcAst = computeDiminishingReturns(10, 40, slvl) if (hs !== oracle || hs !== calcAst) { mismatches.push({ slvl, hs, oracle }) } } expect(mismatches).toEqual([]) }) it('specifically validates critical anchor levels: slvl 1 = 14% and slvl 20 = 35%', () => { // slvl 1: 10 + Math.floor(3300*1 / 700) = 10 + 4 = 14% (NOT 13%) expect(calculateHolyShieldStats(1).blockChancePct).toBe(14) // slvl 20: 10 + Math.floor(3300*20 / 2600) = 10 + Math.floor(66000 / 2600) = 10 + 25 = 35% (NOT 36%) expect(calculateHolyShieldStats(20).blockChancePct).toBe(35) }) it('verifies runtime executeSkillCore113c applies correct toblock stat to caster', async () => { const registry = await getSharedDataRegistry() const stats = new UnitStatList(registry, {}) const casterBus = new StateBus(stats, registry) const caster: CombatUnitContext = { id: 'hs_pal', name: 'Paladin', statList: stats, stateBus: casterBus, unitType: 'player', hasShield: true, } // Cast Holy Shield slvl 1 const ctx1 = createExecContext(registry, 117, 1, caster) const res1 = executeSkillCore113c(ctx1) expect(res1.executed).toBe(true) expect(stats.getModifierBonus('toblock')).toBe(14) // Cast Holy Shield slvl 20 const ctx20 = createExecContext(registry, 117, 20, caster) const res20 = executeSkillCore113c(ctx20) expect(res20.executed).toBe(true) expect(stats.getModifierBonus('toblock')).toBe(35) }) }) // ========================================================================== // Pillar 4: SrvSt Pre-Cast Equipment Guards & Tri-State Semantics // ========================================================================== describe('Pillar 4: SrvSt Pre-Cast Equipment Guards & Tri-State Semantics', () => { it('Smite (#97) and Holy Shield (#117) fail fast when hasShield === false', async () => { const registry = await getSharedDataRegistry() const stats = new UnitStatList(registry, { mana: 500 * FIXED_ONE, maxmana: 500 * FIXED_ONE }) const unshieldedCaster: CombatUnitContext = { id: 'pal_no_shield', name: 'Paladin', statList: stats, stateBus: new StateBus(stats, registry), unitType: 'player', hasShield: false, } // 1. Holy Shield (#117) const hsCtx = createExecContext(registry, 117, 1, unshieldedCaster) const hsRes = executeSkillCore113c(hsCtx) expect(hsRes.executed).toBe(false) expect(hsRes.manaSpent256).toBe(0) expect(hsRes.notes).toContain('fail: requires equipped shield') // 2. Smite (#97) via executeSkillCore113c const smiteCtx = createExecContext(registry, 97, 1, unshieldedCaster) const smiteCoreRes = executeSkillCore113c(smiteCtx) expect(smiteCoreRes.executed).toBe(false) expect(smiteCoreRes.manaSpent256).toBe(0) expect(smiteCoreRes.notes).toContain('fail: requires equipped shield') // 3. Smite (#97) via executeSUnitDmg const dummyDef: CombatUnitContext = { id: 'dummy', name: 'Dummy', statList: stats, stateBus: new StateBus(stats, registry), unitType: 'monster', } const smiteDmgRes = executeSUnitDmg(unshieldedCaster, dummyDef, { skillId: 97, attackKind: 'melee', srcDam: 0, flatPhysMin256: 100 * FIXED_ONE, flatPhysMax256: 100 * FIXED_ONE, }) expect(smiteDmgRes.hit).toBe(false) expect(smiteDmgRes.avoidedReason).toBe('miss') expect(smiteDmgRes.totalDamage256).toBe(0) }) it('Sacrifice (#96) and Zeal (#106) fail fast when weaponItemType is bow or crossbow or isRanged is true', async () => { const registry = await getSharedDataRegistry() const stats = new UnitStatList(registry, { mana: 500 * FIXED_ONE, maxmana: 500 * FIXED_ONE }) const dummyDef: CombatUnitContext = { id: 'dummy', name: 'Dummy', statList: stats, stateBus: new StateBus(stats, registry), unitType: 'monster', } const invalidRangedAttackers: CombatUnitContext[] = [ { id: 'bow_pal', name: 'Paladin Bow', statList: stats, stateBus: new StateBus(stats, registry), unitType: 'player', weaponItemType: 'bow', }, { id: 'xbow_pal', name: 'Paladin Xbow', statList: stats, stateBus: new StateBus(stats, registry), unitType: 'player', weaponItemType: 'crossbow', }, { id: 'ranged_pal', name: 'Paladin Ranged Flag', statList: stats, stateBus: new StateBus(stats, registry), unitType: 'player', isRanged: true, }, ] for (const attacker of invalidRangedAttackers) { // Sacrifice (#96) in registry const sacCtx = createExecContext(registry, 96, 1, attacker) const sacRes = executeSkillCore113c(sacCtx) expect(sacRes.executed).toBe(false) expect(sacRes.manaSpent256).toBe(0) expect(sacRes.notes).toContain('fail: requires melee weapon') // Zeal (#106) in registry const zealCtx = createExecContext(registry, 106, 1, attacker) const zealRes = executeSkillCore113c(zealCtx) expect(zealRes.executed).toBe(false) expect(zealRes.manaSpent256).toBe(0) expect(zealRes.notes).toContain('fail: requires melee weapon') // Sacrifice (#96) in combat pipeline const sacDmgRes = executeSUnitDmg(attacker, dummyDef, { skillId: 96, attackKind: 'melee', srcDam: 128, flatPhysMin256: 100 * FIXED_ONE, flatPhysMax256: 100 * FIXED_ONE, }) expect(sacDmgRes.hit).toBe(false) expect(sacDmgRes.avoidedReason).toBe('miss') // Zeal (#106) in combat pipeline const zealDmgRes = executeSUnitDmg(attacker, dummyDef, { skillId: 106, attackKind: 'melee', srcDam: 128, flatPhysMin256: 100 * FIXED_ONE, flatPhysMax256: 100 * FIXED_ONE, }) expect(zealDmgRes.hit).toBe(false) expect(zealDmgRes.avoidedReason).toBe('miss') } }) it('permissive fallback: undefined/omitted properties permit execution for legacy test fixtures', async () => { const registry = await getSharedDataRegistry() const stats = new UnitStatList(registry, { mana: 500 * FIXED_ONE, maxmana: 500 * FIXED_ONE }) const bareAttacker: CombatUnitContext = { id: 'bare_pal', name: 'Paladin Bare', statList: stats, stateBus: new StateBus(stats, registry), unitType: 'player', // hasShield is undefined, weaponItemType is undefined } // Smite (#97) executes when hasShield is undefined const smiteCtx = createExecContext(registry, 97, 1, bareAttacker) const smiteRes = executeSkillCore113c(smiteCtx) expect(smiteRes.executed).toBe(true) // Holy Shield (#117) executes when hasShield is undefined const hsCtx = createExecContext(registry, 117, 1, bareAttacker) const hsRes = executeSkillCore113c(hsCtx) expect(hsRes.executed).toBe(true) // Sacrifice (#96) executes when weaponItemType is undefined const sacCtx = createExecContext(registry, 96, 1, bareAttacker) const sacRes = executeSkillCore113c(sacCtx) expect(sacRes.executed).toBe(true) // Zeal (#106) executes when weaponItemType is undefined const zealCtx = createExecContext(registry, 106, 1, bareAttacker) const zealRes = executeSkillCore113c(zealCtx) expect(zealRes.executed).toBe(true) // validatePaladinSkillEquipment helper truth table expect(validatePaladinSkillEquipment(97, undefined).valid).toBe(true) expect(validatePaladinSkillEquipment(117, undefined).valid).toBe(true) expect(validatePaladinSkillEquipment(96, undefined).valid).toBe(true) expect(validatePaladinSkillEquipment(106, undefined).valid).toBe(true) expect(validatePaladinSkillEquipment(97, { hasShield: true }).valid).toBe(true) expect(validatePaladinSkillEquipment(97, { hasShield: false }).valid).toBe(false) expect(validatePaladinSkillEquipment(117, { hasShield: true }).valid).toBe(true) expect(validatePaladinSkillEquipment(117, { hasShield: false }).valid).toBe(false) expect(validatePaladinSkillEquipment(96, { weaponItemType: 'sword' }).valid).toBe(true) expect(validatePaladinSkillEquipment(96, { weaponItemType: 'bow' }).valid).toBe(false) expect(validatePaladinSkillEquipment(106, { weaponItemType: 'axe' }).valid).toBe(true) expect(validatePaladinSkillEquipment(106, { weaponItemType: 'bow' }).valid).toBe(false) }) }) })