/** * Adversarial Empirical Stress Testing Suite for Milestone 4: * Sorceress Fire Tree — Enchant (Skill 52) & Fire Mastery (Skill 61) Math & Mechanics * * 1.13c Ground Truth Sources: * - Skills.txt (row 52 Enchant, row 61 Fire Mastery, row 37 Warmth) * - D2Common.dll (HitShift 7 256-fixed-point, 5-band damage progression, ln12 duration) * - D2Game.dll (Enchant buff attachment, ranged 33% penalty, Sorceress melee double-dip) * * Test Dimensions: * 1. Monotonicity sweeps across slvl 1..99 for Enchant damage, AR bonus %, duration, and Fire Mastery % * 2. Synergy scaling: Warmth synergy (+9%/lvl) linear scaling without overflow/underflow at syn 0, 20, 50, 99 * 3. Fire Mastery cast multiplier: (1 + calculateFireMasteryBonus(fmSlvl) / 100) base buff scaling & melee double-dip * 4. Discrete values verification: exact 1.13c values at slvl 1, 10, 20 * 5. Extreme inputs: slvl 0, negative, and NaN inputs safely clamped * 6. Differential testing against independent brute-force oracle (1000+ randomized iterations) */ import { describe, it, expect } from 'vitest' import { calculateEnchantDamage, calculateEnchantAttackRating, calculateEnchantDuration, calculateFireMasteryBonus, calculateEnchantAttackDamage, } from '../../../src/server/skills/skills.ts' // ============================================================================ // Independent Brute-Force Oracles for Differential Testing // ============================================================================ /** Independent Oracle for raw 5-band minimum fire damage */ function oracleRawMin(lvl: number): number { const l = Number.isFinite(lvl) ? Math.max(1, Math.floor(lvl)) : 1 let min = 16 for (let i = 2; i <= l; i++) { if (i <= 8) min += 3 else if (i <= 16) min += 7 else if (i <= 22) min += 11 else if (i <= 28) min += 15 else min += 19 } return min } /** Independent Oracle for raw 5-band maximum fire damage */ function oracleRawMax(lvl: number): number { const l = Number.isFinite(lvl) ? Math.max(1, Math.floor(lvl)) : 1 let max = 20 for (let i = 2; i <= l; i++) { if (i <= 8) max += 5 else if (i <= 16) max += 9 else if (i <= 22) max += 13 else if (i <= 28) max += 17 else max += 21 } return max } /** Independent Oracle for Attack Rating bonus % */ function oracleEnchantAR(slvl: number): number { if (!Number.isFinite(slvl) || slvl <= 0) return 0 const lvl = Math.floor(slvl) return 20 + (lvl - 1) * 9 } /** Independent Oracle for Duration in frames */ function oracleEnchantDuration(slvl: number): number { if (!Number.isFinite(slvl) || slvl <= 0) return 0 const lvl = Math.floor(slvl) return 3600 + (lvl - 1) * 600 } /** Independent Oracle for Fire Mastery bonus % */ function oracleFireMasteryBonus(slvl: number): number { if (!Number.isFinite(slvl) || slvl <= 0) return 0 const lvl = Math.floor(slvl) return 30 + (lvl - 1) * 7 } /** Independent Oracle for complete Enchant damage */ function oracleEnchantDamage(slvl: number, warmth: number = 0, fireMastery: number = 0) { const rawMin = oracleRawMin(slvl) const rawMax = oracleRawMax(slvl) const wPts = Number.isFinite(warmth) ? Math.max(0, warmth) : 0 const fmPts = Number.isFinite(fireMastery) ? Math.max(0, fireMastery) : 0 const warmthMul = 1.0 + (wPts * 9) / 100 const fmMul = 1.0 + oracleFireMasteryBonus(fmPts) / 100 const totalMul = warmthMul * fmMul const min256 = Math.floor(rawMin * 128 * totalMul) const max256 = Math.floor(rawMax * 128 * totalMul) const min = Math.round((rawMin * 0.5 * totalMul) * 10) / 10 const max = Math.round((rawMax * 0.5 * totalMul) * 10) / 10 return { min, max, min256, max256 } } describe('Milestone 4 Math Adversarial Stress Suite (Enchant & Fire Mastery)', () => { // ========================================================================== // Suite 1: Monotonicity Sweeps (slvl 1..99) // ========================================================================== describe('Suite 1: Monotonicity Sweeps across slvl 1..99', () => { it('Enchant fire damage strictly increases monotonically across slvl 1..99', () => { let prev = calculateEnchantDamage(1) expect(prev.min).toBe(8.0) expect(prev.max).toBe(10.0) expect(prev.min256).toBe(2048) expect(prev.max256).toBe(2560) for (let slvl = 2; slvl <= 99; slvl++) { const curr = calculateEnchantDamage(slvl) // Strict monotonicity expect(curr.min).toBeGreaterThan(prev.min) expect(curr.max).toBeGreaterThan(prev.max) expect(curr.min256).toBeGreaterThan(prev.min256) expect(curr.max256).toBeGreaterThan(prev.max256) // Invariant: min <= max expect(curr.min).toBeLessThanOrEqual(curr.max) expect(curr.min256).toBeLessThanOrEqual(curr.max256) prev = curr } }) it('Enchant Attack Rating bonus % strictly increases monotonically across slvl 1..99', () => { let prev = calculateEnchantAttackRating(1) expect(prev).toBe(20) for (let slvl = 2; slvl <= 99; slvl++) { const curr = calculateEnchantAttackRating(slvl) // Strict linear increase of exactly +9% per level expect(curr).toBe(prev + 9) expect(curr).toBe(20 + (slvl - 1) * 9) prev = curr } expect(calculateEnchantAttackRating(99)).toBe(902) }) it('Enchant duration in frames strictly increases monotonically across slvl 1..99', () => { let prev = calculateEnchantDuration(1) expect(prev).toBe(3600) // 144 seconds for (let slvl = 2; slvl <= 99; slvl++) { const curr = calculateEnchantDuration(slvl) // Strict linear increase of exactly +600 frames (24 seconds) per level expect(curr).toBe(prev + 600) expect(curr).toBe(3600 + (slvl - 1) * 600) prev = curr } expect(calculateEnchantDuration(99)).toBe(62400) // 2496s = 41.6 minutes }) it('Fire Mastery fire damage bonus % strictly increases monotonically across slvl 1..99', () => { let prev = calculateFireMasteryBonus(1) expect(prev).toBe(30) // +30% for (let slvl = 2; slvl <= 99; slvl++) { const curr = calculateFireMasteryBonus(slvl) // Strict linear increase of exactly +7% per level expect(curr).toBe(prev + 7) expect(curr).toBe(30 + (slvl - 1) * 7) prev = curr } expect(calculateFireMasteryBonus(99)).toBe(716) // +716% }) }) // ========================================================================== // Suite 2: Synergy Scaling & Linear Progression (Warmth +9%/lvl) // ========================================================================== describe('Suite 2: Synergy Scaling & Precision (Warmth +9%/lvl)', () => { it('Warmth synergy scales linearly at slvl 1, 10, 20, 50, 99 with syn 0, 20, 50, 99', () => { const testLevels = [1, 10, 20, 50, 99] const testSynergies = [0, 20, 50, 99] for (const slvl of testLevels) { const base = calculateEnchantDamage(slvl, 0) for (const syn of testSynergies) { const withSyn = calculateEnchantDamage(slvl, syn) const expectedMultiplier = 1.0 + (syn * 9) / 100 // Check finiteness and positivity expect(Number.isFinite(withSyn.min)).toBe(true) expect(Number.isFinite(withSyn.max)).toBe(true) expect(Number.isFinite(withSyn.min256)).toBe(true) expect(Number.isFinite(withSyn.max256)).toBe(true) expect(withSyn.min).toBeGreaterThan(0) expect(withSyn.max).toBeGreaterThanOrEqual(withSyn.min) // Exact 256-fixed-point integer parity const rawMin = oracleRawMin(slvl) const rawMax = oracleRawMax(slvl) expect(withSyn.min256).toBe(Math.floor(rawMin * 128 * expectedMultiplier)) expect(withSyn.max256).toBe(Math.floor(rawMax * 128 * expectedMultiplier)) // Floating point 0.5 step parity rounded to 1 decimal expect(withSyn.min).toBe(Math.round((rawMin * 0.5 * expectedMultiplier) * 10) / 10) expect(withSyn.max).toBe(Math.round((rawMax * 0.5 * expectedMultiplier) * 10) / 10) } } }) it('verifies exact step increments per Warmth hard point across syn 0..20', () => { const slvl = 20 const rawMin = 137 const rawMax = 179 let prev = calculateEnchantDamage(slvl, 0) for (let w = 1; w <= 20; w++) { const curr = calculateEnchantDamage(slvl, w) // Strict monotonicity per point of synergy expect(curr.min256).toBeGreaterThan(prev.min256) expect(curr.max256).toBeGreaterThan(prev.max256) // Multiplier check: 1 + 0.09 * w const mul = 1 + 0.09 * w expect(curr.min256).toBe(Math.floor(rawMin * 128 * mul)) expect(curr.max256).toBe(Math.floor(rawMax * 128 * mul)) prev = curr } // At Warmth 20: +180% synergy bonus (2.8x base) const maxWarmth = calculateEnchantDamage(20, 20) expect(maxWarmth.min256).toBe(Math.floor(137 * 128 * 2.8)) // 49100 expect(maxWarmth.max256).toBe(Math.floor(179 * 128 * 2.8)) // 64153 }) it('handles negative or invalid synergy values by safe clamping to 0', () => { const base = calculateEnchantDamage(20, 0) const negSyn = calculateEnchantDamage(20, -10) expect(negSyn).toEqual(base) const nanSyn = calculateEnchantDamage(20, NaN) expect(nanSyn).toEqual(base) }) }) // ========================================================================== // Suite 3: Fire Mastery Cast Multiplier & Interaction // ========================================================================== describe('Suite 3: Fire Mastery Cast Multiplier & Combat Integration', () => { it('scales Enchant base buff damage by (1 + calculateFireMasteryBonus(fmSlvl) / 100) at cast time', () => { const slvls = [1, 10, 20, 50, 99] const fmLvls = [0, 1, 10, 20, 50, 99] for (const slvl of slvls) { const base = calculateEnchantDamage(slvl) for (const fmSlvl of fmLvls) { const fmBonus = calculateFireMasteryBonus(fmSlvl) const fmMul = 1.0 + fmBonus / 100 const castDmg = calculateEnchantDamage(slvl, 0, fmSlvl) const expectedMin = Math.round(base.min * fmMul * 10) / 10 const expectedMax = Math.round(base.max * fmMul * 10) / 10 expect(castDmg.min).toBe(expectedMin) expect(castDmg.max).toBe(expectedMax) } } }) it('supports synergies object argument { warmth, fireMastery } and positional arguments equivalently', () => { const slvl = 20 const warmth = 20 const fmSlvl = 20 const viaObj = calculateEnchantDamage(slvl, { warmth, fireMastery: fmSlvl }) const viaPos = calculateEnchantDamage(slvl, warmth, fmSlvl) expect(viaObj).toEqual(viaPos) expect(viaObj.min).toBe(504.4) expect(viaObj.max).toBe(659.1) }) it('verifies compound scaling: Warmth synergy combined with Fire Mastery multiplier', () => { // slvl 20 Enchant: rawMin=137, rawMax=179 // Warmth 20 (+180% -> 2.80x) // Fire Mastery 20 (+163% -> 2.63x) // Total multiplier = 2.80 * 2.63 = 7.364x const buffed = calculateEnchantDamage(20, { warmth: 20, fireMastery: 20 }) const expectedMin256 = Math.floor(137 * 128 * 2.8 * 2.63) const expectedMax256 = Math.floor(179 * 128 * 2.8 * 2.63) expect(buffed.min256).toBe(expectedMin256) expect(buffed.max256).toBe(expectedMax256) const expectedMin = Math.round((137 * 0.5 * 2.8 * 2.63) * 10) / 10 const expectedMax = Math.round((179 * 0.5 * 2.8 * 2.63) * 10) / 10 expect(buffed.min).toBe(expectedMin) expect(buffed.max).toBe(expectedMax) }) it('verifies Sorceress melee double-dip vs ranged 33% penalty via calculateEnchantAttackDamage', () => { const castDmg = calculateEnchantDamage(20, 0, 20) // min: 180.2, max: 235.4 const fmBonus = calculateFireMasteryBonus(20) // 163% -> 2.63x // 1. Sorceress Melee: Double-Dip multiplies fire damage again! const meleeSorc = calculateEnchantAttackDamage({ enchantDamage: castDmg, isRanged: false, isSorceress: true, fireMasterySlvl: 20, }) const expectedMeleeMin = Math.round(castDmg.min * (1 + fmBonus / 100) * 10) / 10 // 473.9 const expectedMeleeMax = Math.round(castDmg.max * (1 + fmBonus / 100) * 10) / 10 // 619.1 expect(meleeSorc.min).toBe(expectedMeleeMin) expect(meleeSorc.max).toBe(expectedMeleeMax) // 2. Ranged attack: 33% penalty, zero second Fire Mastery multiplier const rangedSorc = calculateEnchantAttackDamage({ enchantDamage: castDmg, isRanged: true, isSorceress: true, fireMasterySlvl: 20, }) const expectedRangedMin = Math.round(castDmg.min * 0.33 * 10) / 10 // 59.5 const expectedRangedMax = Math.round(castDmg.max * 0.33 * 10) / 10 // 77.7 expect(rangedSorc.min).toBe(expectedRangedMin) expect(rangedSorc.max).toBe(expectedRangedMax) // 3. Non-Sorceress ally in melee: 100% buff damage, no secondary multiplier const allyMelee = calculateEnchantAttackDamage({ enchantDamage: castDmg, isRanged: false, isSorceress: false, }) expect(allyMelee.min).toBe(castDmg.min) expect(allyMelee.max).toBe(castDmg.max) // 4. Fire immunity barrier: enemies with >= 100% resistance take 0 damage const immune = calculateEnchantAttackDamage({ enchantDamage: castDmg, isRanged: false, isSorceress: true, fireMasterySlvl: 20, targetFireResist: 100, }) expect(immune.min).toBe(0) expect(immune.max).toBe(0) expect(immune.average).toBe(0) }) }) // ========================================================================== // Suite 4: Exact 1.13c Discrete Values Verification (slvl 1, 10, 20) // ========================================================================== describe('Suite 4: Exact 1.13c Discrete Values Verification (slvl 1, 10, 20)', () => { it('verifies exact discrete values at slvl 1', () => { // slvl 1: 8.0-10.0 fire dmg, +20% AR, 3600 frames duration (144s), +30% Fire Mastery const dmg = calculateEnchantDamage(1) expect(dmg.min).toBe(8.0) expect(dmg.max).toBe(10.0) expect(dmg.min256).toBe(2048) expect(dmg.max256).toBe(2560) expect(calculateEnchantAttackRating(1)).toBe(20) expect(calculateEnchantDuration(1)).toBe(3600) expect(calculateFireMasteryBonus(1)).toBe(30) }) it('verifies exact discrete values at slvl 10', () => { // slvl 10: 25.5-36.5 fire dmg, +101% AR, 9000 frames duration (360s), +93% Fire Mastery const dmg = calculateEnchantDamage(10) expect(dmg.min).toBe(25.5) expect(dmg.max).toBe(36.5) expect(dmg.min256).toBe(6528) expect(dmg.max256).toBe(9344) expect(calculateEnchantAttackRating(10)).toBe(101) expect(calculateEnchantDuration(10)).toBe(9000) expect(calculateFireMasteryBonus(10)).toBe(93) }) it('verifies exact discrete values at slvl 20', () => { // slvl 20: 68.5-89.5 fire dmg, +191% AR, 15000 frames duration (600s), +163% Fire Mastery const dmg = calculateEnchantDamage(20) expect(dmg.min).toBe(68.5) expect(dmg.max).toBe(89.5) expect(dmg.min256).toBe(17536) expect(dmg.max256).toBe(22912) expect(calculateEnchantAttackRating(20)).toBe(191) expect(calculateEnchantDuration(20)).toBe(15000) expect(calculateFireMasteryBonus(20)).toBe(163) }) it('verifies 5-band breakpoint transition step deltas across slvl 2..99', () => { for (let lvl = 2; lvl <= 99; lvl++) { const prev = calculateEnchantDamage(lvl - 1) const curr = calculateEnchantDamage(lvl) const dMin256 = curr.min256 - prev.min256 const dMax256 = curr.max256 - prev.max256 let expDMin = 3 * 128 let expDMax = 5 * 128 if (lvl >= 9 && lvl <= 16) { expDMin = 7 * 128 expDMax = 9 * 128 } else if (lvl >= 17 && lvl <= 22) { expDMin = 11 * 128 expDMax = 13 * 128 } else if (lvl >= 23 && lvl <= 28) { expDMin = 15 * 128 expDMax = 17 * 128 } else if (lvl >= 29) { expDMin = 19 * 128 expDMax = 21 * 128 } expect(dMin256).toBe(expDMin) expect(dMax256).toBe(expDMax) } }) }) // ========================================================================== // Suite 5: Extreme Inputs & Boundary Values Clamping // ========================================================================== describe('Suite 5: Extreme Inputs & Boundary Values Clamping', () => { it('safely clamps slvl 0 to valid baselines', () => { // Enchant damage clamps slvl <= 0 to slvl 1 const dmg0 = calculateEnchantDamage(0) const dmg1 = calculateEnchantDamage(1) expect(dmg0).toEqual(dmg1) // AR, Duration, and Fire Mastery clamp slvl <= 0 to 0 expect(calculateEnchantAttackRating(0)).toBe(0) expect(calculateEnchantDuration(0)).toBe(0) expect(calculateFireMasteryBonus(0)).toBe(0) }) it('safely clamps negative slvl inputs (-1, -10, -99)', () => { const dmgNeg = calculateEnchantDamage(-10) const dmg1 = calculateEnchantDamage(1) expect(dmgNeg).toEqual(dmg1) expect(calculateEnchantAttackRating(-5)).toBe(0) expect(calculateEnchantDuration(-50)).toBe(0) expect(calculateFireMasteryBonus(-99)).toBe(0) }) it('safely handles NaN and non-finite inputs', () => { const dmgNaN = calculateEnchantDamage(NaN) const dmg1 = calculateEnchantDamage(1) expect(dmgNaN).toEqual(dmg1) expect(calculateEnchantAttackRating(NaN)).toBe(0) expect(calculateEnchantDuration(NaN)).toBe(0) expect(calculateFireMasteryBonus(NaN)).toBe(0) expect(calculateEnchantAttackRating(Infinity)).toBe(0) expect(calculateEnchantDuration(-Infinity)).toBe(0) expect(calculateFireMasteryBonus(Infinity)).toBe(0) }) it('correctly truncates / floors floating-point skill levels', () => { expect(calculateEnchantAttackRating(10.9)).toBe(calculateEnchantAttackRating(10)) expect(calculateEnchantDuration(15.7)).toBe(calculateEnchantDuration(15)) expect(calculateFireMasteryBonus(20.3)).toBe(calculateFireMasteryBonus(20)) expect(calculateEnchantDamage(20.8)).toEqual(calculateEnchantDamage(20)) }) }) // ========================================================================== // Suite 6: Differential Testing against Brute-Force Oracle // ========================================================================== describe('Suite 6: Differential Testing against Brute-Force Oracle (1000+ Iterations)', () => { it('matches independent brute-force oracle across 1000 randomized (slvl, warmth, fireMastery) inputs', () => { // Deterministic pseudorandom generator (LCG) let seed = 42 function rand() { seed = (seed * 1664525 + 1013904223) >>> 0 return seed / 4294967296 } const ITERATIONS = 1000 for (let i = 0; i < ITERATIONS; i++) { // Biased input generation: mix of boundary values and uniform randoms let slvl: number let warmth: number let fireMastery: number if (i < 50) { // Boundary testing phase slvl = (i % 30) + 1 warmth = i % 25 fireMastery = i % 25 } else if (i < 100) { // High level and extreme synergy phase slvl = 80 + Math.floor(rand() * 20) warmth = Math.floor(rand() * 100) fireMastery = Math.floor(rand() * 100) } else { // General distribution 1..99 slvl = Math.floor(rand() * 99) + 1 warmth = Math.floor(rand() * 50) fireMastery = Math.floor(rand() * 50) } const actual = calculateEnchantDamage(slvl, warmth, fireMastery) const expected = oracleEnchantDamage(slvl, warmth, fireMastery) // Strict differential checks against oracle expect(actual.min).toBe(expected.min) expect(actual.max).toBe(expected.max) expect(actual.min256).toBe(expected.min256) expect(actual.max256).toBe(expected.max256) // AR and Duration oracle differential checks expect(calculateEnchantAttackRating(slvl)).toBe(oracleEnchantAR(slvl)) expect(calculateEnchantDuration(slvl)).toBe(oracleEnchantDuration(slvl)) expect(calculateFireMasteryBonus(fireMastery)).toBe(oracleFireMasteryBonus(fireMastery)) } }) }) })