/** * Adversarial Stress-Testing & Boundary Verification Suite * Milestone 2: Cold Projectiles & Cold Mastery (Ice Blast #199, Glacial Spike #209, Cold Mastery #219) * * Verification Categories: * 1. Monotonicity & Continuity Sweeps (slvl 1 to 99 across all 5 level progression bands) * 2. Strict Freeze Duration Monotonicity (slvl 1 to 99) * 3. Linear Synergy Stress Testing (0 to 60 synergy points, overrides & object structures) * 4. Extreme Boundaries & Robustness (slvl <= 0, fractional, NaN, negative, huge values > 1000) * 5. Cold Mastery Resistance Pipeline & Floor Clamping (-100% cap, immunity bypass, broken immunities) * 6. Differential Oracle Testing (Naive band summation vs closed-form implementations) * 7. System Integration & Invariant Enforcement (BATCH1_SKILLS length, mana cost curves, 0 cooldown) */ import { describe, expect, it } from 'vitest' import { calculateIceBlastDamage, calculateIceBlastFreezeLength, calculateGlacialSpikeDamage, calculateGlacialSpikeFreezeLength, calculateColdMasteryPierce, } from '../../../src/server/engine/missile-engine.ts' import { calculateSkillDamage, getSkillManaCost, getSkillCooldownTicks, BATCH1_SKILLS, } from '../../../src/server/skills/skills.ts' import { computeEffectiveResistance } from '../../../src/server/engine/combat-pipeline.ts' describe('Adversarial Stress Test: Cold Projectiles & Mastery Milestone 2', () => { // ========================================================================= // 1. Monotonicity & Continuity Sweeps (slvl 1 to 99 across 5 Progression Bands) // ========================================================================= describe('1. Monotonicity & Continuity Sweeps (slvl 1 to 99)', () => { it('Ice Blast: min <= max and strict positive monotonicity across all 99 levels', () => { let prevDamage = calculateIceBlastDamage(1) expect(prevDamage.min).toBe(8) expect(prevDamage.max).toBe(12) expect(prevDamage.min).toBeLessThanOrEqual(prevDamage.max) for (let slvl = 2; slvl <= 99; slvl++) { const current = calculateIceBlastDamage(slvl) // 1. Min damage must never exceed max damage expect(current.min).toBeLessThanOrEqual(current.max) // 2. Strict monotonicity: every single level up MUST increase damage expect(current.min).toBeGreaterThan(prevDamage.min) expect(current.max).toBeGreaterThan(prevDamage.max) prevDamage = current } }) it('Ice Blast: progression band slope verification across all 5 bands', () => { // Band 1: slvl 1-8 -> +7.0 min, +7.5 max per level for (let slvl = 1; slvl <= 7; slvl++) { const dCurrent = calculateIceBlastDamage(slvl) const dNext = calculateIceBlastDamage(slvl + 1) expect(dNext.min - dCurrent.min).toBeCloseTo(7.0, 5) expect(dNext.max - dCurrent.max).toBeCloseTo(7.5, 5) } // Band 2: slvl 8-16 -> +14.0 min, +14.5 max per level for (let slvl = 8; slvl <= 15; slvl++) { const dCurrent = calculateIceBlastDamage(slvl) const dNext = calculateIceBlastDamage(slvl + 1) expect(dNext.min - dCurrent.min).toBeCloseTo(14.0, 5) expect(dNext.max - dCurrent.max).toBeCloseTo(14.5, 5) } // Band 3: slvl 16-22 -> +21.0 min, +21.5 max per level for (let slvl = 16; slvl <= 21; slvl++) { const dCurrent = calculateIceBlastDamage(slvl) const dNext = calculateIceBlastDamage(slvl + 1) expect(dNext.min - dCurrent.min).toBeCloseTo(21.0, 5) expect(dNext.max - dCurrent.max).toBeCloseTo(21.5, 5) } // Band 4: slvl 22-28 -> +28.0 min, +28.5 max per level for (let slvl = 22; slvl <= 27; slvl++) { const dCurrent = calculateIceBlastDamage(slvl) const dNext = calculateIceBlastDamage(slvl + 1) expect(dNext.min - dCurrent.min).toBeCloseTo(28.0, 5) expect(dNext.max - dCurrent.max).toBeCloseTo(28.5, 5) } // Band 5: slvl 28-99 -> +35.0 min, +35.5 max per level for (let slvl = 28; slvl <= 98; slvl++) { const dCurrent = calculateIceBlastDamage(slvl) const dNext = calculateIceBlastDamage(slvl + 1) expect(dNext.min - dCurrent.min).toBeCloseTo(35.0, 5) expect(dNext.max - dCurrent.max).toBeCloseTo(35.5, 5) } }) it('Glacial Spike: min <= max and strict positive monotonicity across all 99 levels', () => { let prevDamage = calculateGlacialSpikeDamage(1) expect(prevDamage.min).toBe(16) expect(prevDamage.max).toBe(24) expect(prevDamage.min).toBeLessThanOrEqual(prevDamage.max) for (let slvl = 2; slvl <= 99; slvl++) { const current = calculateGlacialSpikeDamage(slvl) // 1. Min damage must never exceed max damage expect(current.min).toBeLessThanOrEqual(current.max) // 2. Strict monotonicity expect(current.min).toBeGreaterThan(prevDamage.min) expect(current.max).toBeGreaterThan(prevDamage.max) prevDamage = current } }) it('Glacial Spike: progression band slope verification across all 5 bands', () => { // Band 1: slvl 1-8 -> +7.0 min, +7.5 max per level for (let slvl = 1; slvl <= 7; slvl++) { const dCurrent = calculateGlacialSpikeDamage(slvl) const dNext = calculateGlacialSpikeDamage(slvl + 1) expect(dNext.min - dCurrent.min).toBeCloseTo(7.0, 5) expect(dNext.max - dCurrent.max).toBeCloseTo(7.5, 5) } // Band 2: slvl 8-16 -> +13.0 min, +13.5 max per level for (let slvl = 8; slvl <= 15; slvl++) { const dCurrent = calculateGlacialSpikeDamage(slvl) const dNext = calculateGlacialSpikeDamage(slvl + 1) expect(dNext.min - dCurrent.min).toBeCloseTo(13.0, 5) expect(dNext.max - dCurrent.max).toBeCloseTo(13.5, 5) } // Band 3: slvl 16-22 -> +14.0 min, +14.5 max per level for (let slvl = 16; slvl <= 21; slvl++) { const dCurrent = calculateGlacialSpikeDamage(slvl) const dNext = calculateGlacialSpikeDamage(slvl + 1) expect(dNext.min - dCurrent.min).toBeCloseTo(14.0, 5) expect(dNext.max - dCurrent.max).toBeCloseTo(14.5, 5) } // Band 4: slvl 22-28 -> +15.0 min, +15.5 max per level for (let slvl = 22; slvl <= 27; slvl++) { const dCurrent = calculateGlacialSpikeDamage(slvl) const dNext = calculateGlacialSpikeDamage(slvl + 1) expect(dNext.min - dCurrent.min).toBeCloseTo(15.0, 5) expect(dNext.max - dCurrent.max).toBeCloseTo(15.5, 5) } // Band 5: slvl 28-99 -> +16.0 min, +16.5 max per level for (let slvl = 28; slvl <= 98; slvl++) { const dCurrent = calculateGlacialSpikeDamage(slvl) const dNext = calculateGlacialSpikeDamage(slvl + 1) expect(dNext.min - dCurrent.min).toBeCloseTo(16.0, 5) expect(dNext.max - dCurrent.max).toBeCloseTo(16.5, 5) } }) it('Cold Mastery: strict positive monotonicity across all 99 levels (+5% per level)', () => { let prevPierce = calculateColdMasteryPierce(1) expect(prevPierce).toBe(20) for (let slvl = 2; slvl <= 99; slvl++) { const currentPierce = calculateColdMasteryPierce(slvl) expect(currentPierce).toBeGreaterThan(prevPierce) expect(currentPierce - prevPierce).toBe(5) prevPierce = currentPierce } expect(calculateColdMasteryPierce(1)).toBe(20) expect(calculateColdMasteryPierce(10)).toBe(65) expect(calculateColdMasteryPierce(20)).toBe(115) expect(calculateColdMasteryPierce(30)).toBe(165) expect(calculateColdMasteryPierce(99)).toBe(510) }) }) // ========================================================================= // 2. Strict Freeze Duration Monotonicity (slvl 1 to 99) // ========================================================================= describe('2. Strict Freeze Duration Monotonicity', () => { it('Ice Blast: freeze length increases strictly by +5 frames per level across slvl 1 to 99', () => { let prevLen = calculateIceBlastFreezeLength(1) expect(prevLen).toBe(75) // 3.0s for (let slvl = 2; slvl <= 99; slvl++) { const currentLen = calculateIceBlastFreezeLength(slvl) expect(currentLen).toBeGreaterThan(prevLen) expect(currentLen - prevLen).toBe(5) prevLen = currentLen } expect(calculateIceBlastFreezeLength(1)).toBe(75) expect(calculateIceBlastFreezeLength(10)).toBe(120) expect(calculateIceBlastFreezeLength(20)).toBe(170) expect(calculateIceBlastFreezeLength(99)).toBe(75 + 98 * 5) }) it('Ice Blast: freeze length strictly monotonic with non-zero synergy across slvl 1 to 99', () => { for (const syn of [1, 10, 20, 60]) { let prevLen = calculateIceBlastFreezeLength(1, syn) for (let slvl = 2; slvl <= 99; slvl++) { const currentLen = calculateIceBlastFreezeLength(slvl, syn) expect(currentLen).toBeGreaterThan(prevLen) prevLen = currentLen } } }) it('Glacial Spike: freeze length increases strictly by +3 frames per level across slvl 1 to 99', () => { let prevLen = calculateGlacialSpikeFreezeLength(1) expect(prevLen).toBe(50) // 2.0s for (let slvl = 2; slvl <= 99; slvl++) { const currentLen = calculateGlacialSpikeFreezeLength(slvl) expect(currentLen).toBeGreaterThan(prevLen) expect(currentLen - prevLen).toBe(3) prevLen = currentLen } expect(calculateGlacialSpikeFreezeLength(1)).toBe(50) expect(calculateGlacialSpikeFreezeLength(10)).toBe(77) expect(calculateGlacialSpikeFreezeLength(20)).toBe(107) expect(calculateGlacialSpikeFreezeLength(99)).toBe(50 + 98 * 3) }) it('Glacial Spike: freeze length strictly monotonic with non-zero synergy across slvl 1 to 99', () => { for (const syn of [1, 10, 20, 60]) { let prevLen = calculateGlacialSpikeFreezeLength(1, syn) for (let slvl = 2; slvl <= 99; slvl++) { const currentLen = calculateGlacialSpikeFreezeLength(slvl, syn) expect(currentLen).toBeGreaterThan(prevLen) prevLen = currentLen } } }) }) // ========================================================================= // 3. Synergy Stress Testing (0 to 60 Points, Linearity & Objects) // ========================================================================= describe('3. Synergy Stress Testing', () => { it('Ice Blast: 0 to 60 synergy points scaling (+8% per point) is strictly linear and positive', () => { const baseDmg = calculateIceBlastDamage(20, 0) expect(baseDmg).toEqual({ min: 253, max: 266.5 }) for (let syn = 0; syn <= 60; syn++) { const current = calculateIceBlastDamage(20, syn) const expectedMultiplier = 1.0 + syn * 0.08 const expectedMin = Math.floor((253 * expectedMultiplier + 1e-9) * 10) / 10 const expectedMax = Math.floor((266.5 * expectedMultiplier + 1e-9) * 10) / 10 expect(current.min).toBeCloseTo(expectedMin, 1) expect(current.max).toBeCloseTo(expectedMax, 1) expect(current.min).toBeLessThanOrEqual(current.max) } // Check key milestones expect(calculateIceBlastDamage(20, 0)).toEqual({ min: 253, max: 266.5 }) expect(calculateIceBlastDamage(20, 20)).toEqual({ min: 657.8, max: 692.9 }) // (1 + 1.6 = 2.6x) expect(calculateIceBlastDamage(20, 60)).toEqual({ min: 1467.4, max: 1545.7 }) // (1 + 4.8 = 5.8x) }) it('Ice Blast: multi-skill synergy object decomposition parity', () => { // 4 synergy skills: Ice Bolt, Blizzard, Frozen Orb, Glacial Spike const combined = calculateIceBlastDamage(20, { iceBolt: 15, blizzard: 15, frozenOrb: 15, glacialSpike: 15, }) // 15 * 4 = 60 total points -> exactly identical to passing numeric 60 const numeric = calculateIceBlastDamage(20, 60) expect(combined).toEqual(numeric) // Verify each individual synergy contributes identically (symmetric +8%) const viaIceBolt = calculateIceBlastDamage(20, { iceBolt: 20 }) const viaBlizzard = calculateIceBlastDamage(20, { blizzard: 20 }) const viaFrozenOrb = calculateIceBlastDamage(20, { frozenOrb: 20 }) const viaGlacialSpike = calculateIceBlastDamage(20, { glacialSpike: 20 }) expect(viaIceBolt).toEqual(viaBlizzard) expect(viaBlizzard).toEqual(viaFrozenOrb) expect(viaFrozenOrb).toEqual(viaGlacialSpike) }) it('Ice Blast: synergy override options (synPctPerLvl and synMultiplier)', () => { // Custom % per level override const customPct = calculateIceBlastDamage(20, 10, { synPctPerLvl: 12 }) // 10 * 12% = +120% -> 2.2x expect(customPct.min).toBeCloseTo(253 * 2.2, 1) expect(customPct.max).toBeCloseTo(266.5 * 2.2, 1) // Direct multiplier override const directMult = calculateIceBlastDamage(20, undefined, { synMultiplier: 3.5 }) expect(directMult.min).toBeCloseTo(253 * 3.5, 1) expect(directMult.max).toBeCloseTo(266.5 * 3.5, 1) }) it('Glacial Spike: 0 to 60 synergy points scaling (+5% per point) is strictly linear and positive', () => { const baseDmg = calculateGlacialSpikeDamage(20, 0) expect(baseDmg).toEqual({ min: 225, max: 242.5 }) for (let syn = 0; syn <= 60; syn++) { const current = calculateGlacialSpikeDamage(20, syn) const expectedMultiplier = 1.0 + syn * 0.05 const expectedMin = Math.floor((225 * expectedMultiplier + 1e-9) * 10) / 10 const expectedMax = Math.floor((242.5 * expectedMultiplier + 1e-9) * 10) / 10 expect(current.min).toBeCloseTo(expectedMin, 1) expect(current.max).toBeCloseTo(expectedMax, 1) expect(current.min).toBeLessThanOrEqual(current.max) } // Check key milestones expect(calculateGlacialSpikeDamage(20, 0)).toEqual({ min: 225, max: 242.5 }) expect(calculateGlacialSpikeDamage(20, 20)).toEqual({ min: 450, max: 485 }) // (1 + 1.0 = 2.0x) expect(calculateGlacialSpikeDamage(20, 60)).toEqual({ min: 900, max: 970 }) // (1 + 3.0 = 4.0x) }) it('Glacial Spike: multi-skill synergy object decomposition parity', () => { // 4 synergy skills: Ice Bolt, Ice Blast, Blizzard, Frozen Orb const combined = calculateGlacialSpikeDamage(20, { iceBolt: 15, iceBlast: 15, blizzard: 15, frozenOrb: 15, }) // 15 * 4 = 60 total points -> exactly identical to passing numeric 60 const numeric = calculateGlacialSpikeDamage(20, 60) expect(combined).toEqual(numeric) // Verify each individual synergy contributes identically (symmetric +5%) const viaIceBolt = calculateGlacialSpikeDamage(20, { iceBolt: 20 }) const viaIceBlast = calculateGlacialSpikeDamage(20, { iceBlast: 20 }) const viaBlizzard = calculateGlacialSpikeDamage(20, { blizzard: 20 }) const viaFrozenOrb = calculateGlacialSpikeDamage(20, { frozenOrb: 20 }) expect(viaIceBolt).toEqual(viaIceBlast) expect(viaIceBlast).toEqual(viaBlizzard) expect(viaBlizzard).toEqual(viaFrozenOrb) }) it('Glacial Spike: freeze length synergy scaling with Blizzard (+3% per point)', () => { const baseLen = calculateGlacialSpikeFreezeLength(20, 0) expect(baseLen).toBe(107) for (let syn = 0; syn <= 60; syn++) { const len = calculateGlacialSpikeFreezeLength(20, syn) const expected = Math.floor((107 * (100 + syn * 3)) / 100) expect(len).toBe(expected) } // Object format parity expect(calculateGlacialSpikeFreezeLength(20, { blizzard: 20 })).toBe( calculateGlacialSpikeFreezeLength(20, 20), ) }) }) // ========================================================================= // 4. Extreme Boundaries & Robustness Testing // ========================================================================= describe('4. Extreme Boundaries & Robustness', () => { it('handles slvl <= 0 gracefully without exceptions or negative damage', () => { for (const invalidLvl of [0, -1, -50, -9999]) { const ibDmg = calculateIceBlastDamage(invalidLvl) expect(ibDmg).toEqual({ min: 8, max: 12 }) // Clamps to lvl 1 base const ibFreeze = calculateIceBlastFreezeLength(invalidLvl) expect(ibFreeze).toBe(75) // Clamps to lvl 1 base const gsDmg = calculateGlacialSpikeDamage(invalidLvl) expect(gsDmg).toEqual({ min: 16, max: 24 }) // Clamps to lvl 1 base const gsFreeze = calculateGlacialSpikeFreezeLength(invalidLvl) expect(gsFreeze).toBe(50) // Clamps to lvl 1 base const cmPierce = calculateColdMasteryPierce(invalidLvl) expect(cmPierce).toBe(0) // Passive returns 0 when inactive/unlearned // Mana costs for invalid levels clamp to level 1 expect(getSkillManaCost(45, invalidLvl)).toBe(6.0) expect(getSkillManaCost(55, invalidLvl)).toBe(10.0) expect(getSkillManaCost(65, invalidLvl)).toBe(0) } }) it('handles NaN, Infinity, -Infinity, null, and undefined safely', () => { for (const nonFinite of [NaN, Infinity, -Infinity, undefined as any, null as any]) { const ibDmg = calculateIceBlastDamage(nonFinite) expect(Number.isFinite(ibDmg.min)).toBe(true) expect(Number.isFinite(ibDmg.max)).toBe(true) expect(ibDmg).toEqual({ min: 8, max: 12 }) const ibFreeze = calculateIceBlastFreezeLength(nonFinite) expect(Number.isFinite(ibFreeze)).toBe(true) expect(ibFreeze).toBe(75) const gsDmg = calculateGlacialSpikeDamage(nonFinite) expect(Number.isFinite(gsDmg.min)).toBe(true) expect(Number.isFinite(gsDmg.max)).toBe(true) expect(gsDmg).toEqual({ min: 16, max: 24 }) const gsFreeze = calculateGlacialSpikeFreezeLength(nonFinite) expect(Number.isFinite(gsFreeze)).toBe(true) expect(gsFreeze).toBe(50) const cmPierce = calculateColdMasteryPierce(nonFinite) expect(cmPierce).toBe(0) expect(getSkillManaCost(45, nonFinite)).toBe(6.0) expect(getSkillManaCost(55, nonFinite)).toBe(10.0) expect(getSkillManaCost(65, nonFinite)).toBe(0) } }) it('handles fractional slvl values by rounding down consistently (Math.floor)', () => { // 5.2 and 5.9 both evaluate to slvl 5 expect(calculateIceBlastDamage(5.2)).toEqual(calculateIceBlastDamage(5)) expect(calculateIceBlastDamage(5.9)).toEqual(calculateIceBlastDamage(5)) expect(calculateIceBlastFreezeLength(5.2)).toBe(calculateIceBlastFreezeLength(5)) expect(calculateIceBlastFreezeLength(5.9)).toBe(calculateIceBlastFreezeLength(5)) expect(calculateGlacialSpikeDamage(5.2)).toEqual(calculateGlacialSpikeDamage(5)) expect(calculateGlacialSpikeDamage(5.9)).toEqual(calculateGlacialSpikeDamage(5)) expect(calculateGlacialSpikeFreezeLength(5.2)).toBe(calculateGlacialSpikeFreezeLength(5)) expect(calculateGlacialSpikeFreezeLength(5.9)).toBe(calculateGlacialSpikeFreezeLength(5)) expect(calculateColdMasteryPierce(5.2)).toBe(calculateColdMasteryPierce(5)) expect(calculateColdMasteryPierce(5.9)).toBe(calculateColdMasteryPierce(5)) }) it('handles very large values (> 1000) stably without overflow or precision collapse', () => { const largeLvl = 1000 const ibDmg = calculateIceBlastDamage(largeLvl) expect(Number.isFinite(ibDmg.min)).toBe(true) expect(Number.isFinite(ibDmg.max)).toBe(true) expect(ibDmg.min).toBeLessThanOrEqual(ibDmg.max) expect(ibDmg.min).toBeGreaterThan(10000) const gsDmg = calculateGlacialSpikeDamage(largeLvl) expect(Number.isFinite(gsDmg.min)).toBe(true) expect(Number.isFinite(gsDmg.max)).toBe(true) expect(gsDmg.min).toBeLessThanOrEqual(gsDmg.max) expect(gsDmg.min).toBeGreaterThan(10000) const cmPierce = calculateColdMasteryPierce(largeLvl) expect(cmPierce).toBe(20 + 999 * 5) // 5015% pierce }) it('handles adversarial negative synergy values and clamped multiplier overrides', () => { // Negative synergy numbers clamped to 0 expect(calculateIceBlastDamage(20, -50)).toEqual(calculateIceBlastDamage(20, 0)) expect(calculateGlacialSpikeDamage(20, -50)).toEqual(calculateGlacialSpikeDamage(20, 0)) expect(calculateIceBlastFreezeLength(20, -50)).toBe(calculateIceBlastFreezeLength(20, 0)) expect(calculateGlacialSpikeFreezeLength(20, -50)).toBe(calculateGlacialSpikeFreezeLength(20, 0)) // Negative synergy object members clamped to 0 expect( calculateIceBlastDamage(20, { iceBolt: -10, blizzard: -20, frozenOrb: -5 } as any), ).toEqual(calculateIceBlastDamage(20, 0)) expect( calculateGlacialSpikeDamage(20, { iceBolt: -10, blizzard: -20, frozenOrb: -5 } as any), ).toEqual(calculateGlacialSpikeDamage(20, 0)) // Multiplier overrides: negative or zero multiplier clamped to >= 0 expect(calculateIceBlastDamage(20, undefined, { synMultiplier: -2 })).toEqual({ min: 0, max: 0 }) expect(calculateIceBlastDamage(20, undefined, { synMultiplier: 0 })).toEqual({ min: 0, max: 0 }) expect(calculateGlacialSpikeDamage(20, undefined, { synMultiplier: -2 })).toEqual({ min: 0, max: 0 }) expect(calculateGlacialSpikeDamage(20, undefined, { synMultiplier: 0 })).toEqual({ min: 0, max: 0 }) }) }) // ========================================================================= // 5. Cold Mastery Combat Pipeline Adversarial Scenarios // ========================================================================= describe('5. Cold Mastery Combat Pipeline Adversarial Scenarios', () => { it('authentically clamps effective resistance at -100% floor for vulnerable enemies', () => { // Slvl 20 Cold Mastery: -115% pierce const cm20 = calculateColdMasteryPierce(20) expect(cm20).toBe(115) // Case 1: Enemy has 0% Cold Res -> 0 - 115 = -115%, but clamped at -100% const res0 = computeEffectiveResistance({ baseRes: 0, passiveEnemyPierce: cm20 }) expect(res0.effectiveRes).toBe(-100) expect(res0.isImmune).toBe(false) // Case 2: Enemy has -50% Cold Res -> -50 - 115 = -165%, clamped at -100% const resNeg50 = computeEffectiveResistance({ baseRes: -50, passiveEnemyPierce: cm20 }) expect(resNeg50.effectiveRes).toBe(-100) // Case 3: Enemy has 50% Cold Res -> 50 - 115 = -65% (within [-100, 99]) const res50 = computeEffectiveResistance({ baseRes: 50, passiveEnemyPierce: cm20 }) expect(res50.effectiveRes).toBe(-65) expect(res50.isImmune).toBe(false) }) it('Cold Mastery alone CANNOT pierce Cold Immune monsters (res >= 100%)', () => { const cm20 = calculateColdMasteryPierce(20) // 115% pierce const cm99 = calculateColdMasteryPierce(99) // 510% pierce // Target with 100% Cold Res (immune) const res100 = computeEffectiveResistance({ baseRes: 100, passiveEnemyPierce: cm20 }) expect(res100.isImmune).toBe(true) expect(res100.effectiveRes).toBe(100) // Target with 140% Cold Res (immune) with slvl 99 Cold Mastery const res140 = computeEffectiveResistance({ baseRes: 140, passiveEnemyPierce: cm99 }) expect(res140.isImmune).toBe(true) expect(res140.effectiveRes).toBe(100) }) it('Broken Cold Immunities receive 100% Cold Mastery pierce without 1/5 penalty', () => { // Target with 110% Cold Res // Conviction Level 25: -150% pierce // When baseRes >= 100%, Conviction applies at 1/5th effectiveness -> -30% // New resistance = 110 - 30 = 80% (< 100% -> immunity broken!) // Once broken, Cold Mastery (slvl 20 = -115%) applies at full 100% effectiveness: // Final effective resistance = 80 - 115 = -35% const cm20 = calculateColdMasteryPierce(20) const brokenResult = computeEffectiveResistance({ baseRes: 110, convictionPierce: 150, passiveEnemyPierce: cm20, }) expect(brokenResult.isImmune).toBe(false) expect(brokenResult.convictionAndLRApplied).toBe(30) expect(brokenResult.resAfterConvictionLR).toBe(80) expect(brokenResult.effectiveRes).toBe(-35) }) it('Unbreakable Cold Immunities stay immune and bypass Cold Mastery entirely', () => { // Target with 140% Cold Res // Conviction Level 25 (-150% -> 1/5 = -30%) // Res after Conviction = 140 - 30 = 110% (still >= 100% -> immune remains unbroken!) // Cold Mastery must NOT apply! const cm20 = calculateColdMasteryPierce(20) const unbrokenResult = computeEffectiveResistance({ baseRes: 140, convictionPierce: 150, passiveEnemyPierce: cm20, }) expect(unbrokenResult.isImmune).toBe(true) expect(unbrokenResult.convictionAndLRApplied).toBe(30) expect(unbrokenResult.resAfterConvictionLR).toBe(110) expect(unbrokenResult.effectiveRes).toBe(100) }) }) // ========================================================================= // 6. Differential Oracle Testing (Naive Band Summation vs Implementation) // ========================================================================= describe('6. Differential Oracle Testing', () => { // Naive brute-force simulator for 1.13c 5-band table evaluation function naiveBandEvaluator( baseMin: number, baseMax: number, minSteps: number[], maxSteps: number[], slvl: number, ): { min: number; max: number } { const lvl = Math.max(1, Math.floor(slvl)) let curMin = baseMin let curMax = baseMax for (let l = 2; l <= lvl; l++) { let stepIdx = 0 if (l >= 2 && l <= 8) stepIdx = 0 else if (l >= 9 && l <= 16) stepIdx = 1 else if (l >= 17 && l <= 22) stepIdx = 2 else if (l >= 23 && l <= 28) stepIdx = 3 else stepIdx = 4 curMin += minSteps[stepIdx] curMax += maxSteps[stepIdx] } return { min: curMin / 2, max: curMax / 2 } } it('Ice Blast: matches naive brute-force 5-band oracle across all 99 levels', () => { const minSteps = [14, 28, 42, 56, 70] const maxSteps = [15, 29, 43, 57, 71] for (let slvl = 1; slvl <= 99; slvl++) { const oracle = naiveBandEvaluator(16, 24, minSteps, maxSteps, slvl) const actual = calculateIceBlastDamage(slvl) expect(actual.min).toBe(oracle.min) expect(actual.max).toBe(oracle.max) } }) it('Glacial Spike: matches naive brute-force 5-band oracle across all 99 levels', () => { const minSteps = [14, 26, 28, 30, 32] const maxSteps = [15, 27, 29, 31, 33] for (let slvl = 1; slvl <= 99; slvl++) { const oracle = naiveBandEvaluator(32, 48, minSteps, maxSteps, slvl) const actual = calculateGlacialSpikeDamage(slvl) expect(actual.min).toBe(oracle.min) expect(actual.max).toBe(oracle.max) } }) }) // ========================================================================= // 7. System Integration & Invariant Enforcement // ========================================================================= describe('7. System Integration & Invariant Enforcement', () => { it('BATCH1_SKILLS length remains strictly 38', () => { expect(Object.keys(BATCH1_SKILLS).length).toBe(38) }) it('calculateSkillDamage dispatcher cleanly routes skills 45, 55, and 65', () => { expect(calculateSkillDamage(45, 10, { iceBolt: 5 })).toEqual( calculateIceBlastDamage(10, { iceBolt: 5 }), ) expect(calculateSkillDamage('iceblast', 10, 5)).toEqual( calculateIceBlastDamage(10, 5), ) expect(calculateSkillDamage(55, 10, { blizzard: 5 })).toEqual( calculateGlacialSpikeDamage(10, { blizzard: 5 }), ) expect(calculateSkillDamage('glacialspike', 10, 5)).toEqual( calculateGlacialSpikeDamage(10, 5), ) // Cold Mastery is passive, returns { min: 0, max: 0 } expect(calculateSkillDamage(65, 20)).toEqual({ min: 0, max: 0 }) expect(calculateSkillDamage('coldmastery', 20)).toEqual({ min: 0, max: 0 }) }) it('mana costs and cooldown ticks adhere strictly to 1.13c specification', () => { // Ice Blast: (12 + slvl - 1) * 0.5 expect(getSkillManaCost(45, 1)).toBe(6.0) expect(getSkillManaCost(45, 10)).toBe(10.5) expect(getSkillManaCost(45, 20)).toBe(15.5) // Glacial Spike: (20 + slvl - 1) * 0.5 expect(getSkillManaCost(55, 1)).toBe(10.0) expect(getSkillManaCost(55, 10)).toBe(14.5) expect(getSkillManaCost(55, 20)).toBe(19.5) // Cold Mastery: passive 0 mana expect(getSkillManaCost(65, 1)).toBe(0) expect(getSkillManaCost(65, 20)).toBe(0) // 0 cooldown ticks expect(getSkillCooldownTicks(45)).toBe(0) expect(getSkillCooldownTicks(55)).toBe(0) expect(getSkillCooldownTicks(65)).toBe(0) }) }) })