/** * Adversarial Challenger Stress & Boundary Suite: Necromancer Summoning & Curses * Cohort 3 Parity Stress-Testing (IDs 66..75, 86..95) * * Ground Truth Invariants Tested: * 1. Skeleton Max Count: Exact formula across slvl 1..50 (slvl 1->1, 2->2, 3->3, 4->3, 6->4, 9->5, 12->6, 15->7, 18->8, 20->8, 30->12, 50->18). * Skeleton Mastery: +8 life/lvl, +2 dmg/lvl, +10 def/lvl, +5% revive life/lvl, +10% damage/lvl. * 2. Skeletal Mages: 4 elemental variants (Fire, Cold, Lightning, Poison) generate correct missile types, damage, and duration scaling. * 3. Golems: * - Clay Golem: slow-on-hit percentage formula min(75, floor(11 + 40*(slvl-1)/(slvl+6))), slow attacker when hit. * - Blood Golem: 70% life steal to player, 30% damage transfer to master. * - Iron Golem: inherits item properties and stats from targeted metallic item, innate Thorns reflection. * - Fire Golem: Holy Fire radial aura and 100% fire absorb healing. * - Golem exclusivity: Exactly 1 active golem allowed across all 4 types. * 4. Revive: 180.0s (4500 frames) lifetime, +200% life (+5%/lvl SM), +215% damage (+10%/lvl SM), pet cap equals slvl. * 5. Curse Exclusivity: Casting any cursetype=1 curse cleanly overwrites prior cursetype=1 curse without stat leakage across 100 cycles. * 6. Attract Immunity: A monster affected by Attract (cursetype=2) CANNOT have Attract overwritten by any other curse. * 7. Lower Resist Immunity Breaking: When base resistance >= 100%, Lower Resist applies at 1/5th efficiency. */ import { describe, expect, it, beforeEach } from 'vitest' import { calculateSkeletonMaxCount, calculateSkeletalMageMaxCount, calculateReviveMaxCount, calculateSkeletonMasteryStats, calculateGolemMasteryStats, calculateSummonResistPct, calculateClayGolemStats, calculateBloodGolemStats, calculateIronGolemStats, calculateFireGolemStats, calculateReviveStats, calculateCurseEffect, calculateCurseDurationFrames, calculateLowerResistReduction, computeImmunityBreak, type IronGolemItemProps, } from '../../../src/server/skills/skills.ts' import { computeEffectiveResistance, executeSUnitDmg, type CombatUnitContext, } from '../../../src/server/engine/combat-pipeline.ts' import { FIXED_ONE, UnitStatList } from '../../../src/server/engine/stat-list.ts' import { StateBus } from '../../../src/server/engine/state-bus.ts' import { getSharedDataRegistry } from '../../../src/baker/mpq-archive-source.ts' import { SummonManager, type SummonedPetUnit } from '../../../src/server/ai/summon-manager.ts' import { MissileEngine } from '../../../src/server/engine/missile-engine.ts' import { AuraScanner } from '../../../src/server/engine/aura-scanner.ts' describe('Adversarial Challenger: Necromancer Summoning & Curses Stress Suite', () => { let registry: any beforeEach(async () => { registry = await getSharedDataRegistry() }) // ========================================================================= // Target 1: Skeleton Max Count Formula (slvl 1..50) & Skeleton Mastery // ========================================================================= describe('Target 1: Skeleton Max Count & Skeleton Mastery Scaling', () => { it('verifies exact 1.13c max skeleton count across all skill levels from 1 to 50', () => { // Oracle: slvl < 4 ? slvl : 2 + Math.floor(slvl / 3) const expectedMilestones: Record = { 1: 1, 2: 2, 3: 3, 4: 3, 5: 3, 6: 4, 7: 4, 8: 4, 9: 5, 10: 5, 11: 5, 12: 6, 13: 6, 14: 6, 15: 7, 16: 7, 17: 7, 18: 8, 19: 8, 20: 8, 21: 9, 24: 10, 27: 11, 30: 12, 40: 15, 50: 18, } for (let slvl = 1; slvl <= 50; slvl++) { const actual = calculateSkeletonMaxCount(slvl) const expected = slvl < 4 ? slvl : 2 + Math.floor(slvl / 3) expect(actual).toBe(expected) if (expectedMilestones[slvl] !== undefined) { expect(actual).toBe(expectedMilestones[slvl]) } } // Edge case: invalid/zero/negative levels expect(calculateSkeletonMaxCount(0)).toBe(0) expect(calculateSkeletonMaxCount(-5)).toBe(0) expect(calculateSkeletonMaxCount(NaN)).toBe(0) }) it('verifies Skeleton Mastery linear bonuses: +8 life/lvl, +2 dmg/lvl, +10 def/lvl', () => { for (let slvl = 1; slvl <= 40; slvl++) { const stats = calculateSkeletonMasteryStats(slvl) expect(stats.skeletonHp).toBe(8 * slvl) expect(stats.skeletonDamage).toBe(2 * slvl) expect(stats.defense).toBe(10 * slvl) expect(stats.reviveHpPct).toBe(5 * slvl) expect(stats.damagePct).toBe(10 * slvl) } // Edge case: slvl 0 returns zeroed stats const zero = calculateSkeletonMasteryStats(0) expect(zero.skeletonHp).toBe(0) expect(zero.skeletonDamage).toBe(0) expect(zero.defense).toBe(0) }) it('stress-tests SummonManager pet cap and Mastery scaling in actual summon creation', () => { const sm = new SummonManager(registry) const playerStats = new UnitStatList(registry, { level: 80, }) // Give slvl 20 Skeleton Mastery (Skill 69) playerStats.setBaseSkillLevel(69, 20) const owner: CombatUnitContext = { id: 'player-necro', name: 'Necromancer', statList: playerStats, stateBus: new StateBus(playerStats, registry), } // Prepare 20 corpses const corpses = Array.from({ length: 20 }, (_, i) => ({ id: `corpse-${i}`, consumed: false, baseMaxHp: 500, isBoss: false, })) // At slvl 20 Raise Skeleton, max count is 8 for (let i = 0; i < 15; i++) { const outcome = sm.createPet({ owner, skillId: 70, // Raise Skeleton slvl: 20, corpses, }) expect(outcome.created).toBe(true) } const activeSkeletons = sm.getActivePets().filter(p => p.kind === 'skeleton') expect(activeSkeletons.length).toBe(8) // Verify each skeleton's HP includes mastery: // (base (21) + 20*15 + 8*20) * (1 + 10%*20) = (21 + 300 + 160) * 3 = 481 * 3 = 1443 const sample = activeSkeletons[0]! expect(sample.maxHp).toBeGreaterThanOrEqual(1400) }) }) // ========================================================================= // Target 2: Skeletal Mages Elemental Variants & Scaling // ========================================================================= describe('Target 2: Skeletal Mages 4 Elemental Variants & Missiles', () => { it('verifies 4 elemental variants (Fire, Cold, Lightning, Poison) generate correct missile types', () => { const sm = new SummonManager(registry) const playerStats = new UnitStatList(registry, { level: 80 }) const owner: CombatUnitContext = { id: 'player-necro', name: 'Necromancer', statList: playerStats, stateBus: new StateBus(playerStats, registry), } const corpses = Array.from({ length: 16 }, (_, i) => ({ id: `corpse-${i}`, consumed: false, baseMaxHp: 500, isBoss: false, })) // Summon 8 mages (slvl 20 max count is 8) const elementsFound = new Set() for (let i = 0; i < 8; i++) { const outcome = sm.createPet({ owner, skillId: 80, // Raise Skeletal Mage slvl: 20, corpses, }) expect(outcome.created).toBe(true) if (outcome.pet?.mageElement) { elementsFound.add(outcome.pet.mageElement) } } // All 4 elements must be represented in rotation expect(elementsFound.has('fire')).toBe(true) expect(elementsFound.has('cold')).toBe(true) expect(elementsFound.has('lightning')).toBe(true) expect(elementsFound.has('poison')).toBe(true) const activeMages = sm.getActivePets().filter(p => p.kind === 'skeleton_mage') expect(activeMages.length).toBe(8) }) it('verifies tickPets executes the authentic elemental missile for each mage type', () => { const sm = new SummonManager(registry) const playerStats = new UnitStatList(registry, { level: 80 }) playerStats.setBaseSkillLevel(69, 10) // Skeleton Mastery slvl 10 (+20 flat dmg) const owner: CombatUnitContext = { id: 'player-necro', name: 'Necromancer', statList: playerStats, stateBus: new StateBus(playerStats, registry), } const spawnedMissiles: { missile: string; elem: string; min: number; max: number }[] = [] const mockMissileEngine: any = { spawnMissile: (params: any) => { spawnedMissiles.push({ missile: params.missileNameOrId, elem: params.dmgPacket.elemType, min: params.dmgPacket.elemMin256, max: params.dmgPacket.elemMax256, }) }, } const corpses = Array.from({ length: 8 }, (_, i) => ({ id: `corpse-${i}`, consumed: false, baseMaxHp: 500, isBoss: false, })) // Create 4 mages (one of each element) for (let i = 0; i < 4; i++) { sm.createPet({ owner, skillId: 80, slvl: 15, corpses }) } const enemyStats = new UnitStatList(registry, { hitpoints: 1000 * FIXED_ONE, maxhp: 1000 * FIXED_ONE }) const enemy: CombatUnitContext = { id: 'monster-1', name: 'Zombie', statList: enemyStats, stateBus: new StateBus(enemyStats, registry), } const enemyPositions = new Map() enemyPositions.set('monster-1', { x: 50, y: 120 }) sm.tickPets({ currentTick: 1, owner, enemies: [enemy], enemyPositions, missileEngine: mockMissileEngine, auraScanner: {} as any, }) expect(spawnedMissiles.length).toBe(4) const missileNames = spawnedMissiles.map(m => m.missile) expect(missileNames).toContain('firebolt') expect(missileNames).toContain('icebolt') expect(missileNames).toContain('chargedbolt') expect(missileNames).toContain('poisonexplosioncloud') // Verify elemental damage scaling: (25 + 15*12 + 20) = 225 min, (45 + 15*18 + 20) = 335 max for (const m of spawnedMissiles) { expect(m.min).toBe(225 * FIXED_ONE) expect(m.max).toBe(335 * FIXED_ONE) } }) }) // ========================================================================= // Target 3: Golems Deep Mechanics // ========================================================================= describe('Target 3: Golems Parity & Stress Testing', () => { it('verifies Clay Golem slow % formula across slvl 1..100 and attacker slow on hit', () => { // Formula: min(75, floor(11 + 40 * (slvl - 1) / (slvl + 6))) const checkpoints: Record = { 1: 11, 2: 16, 5: 25, 10: 33, 20: 40, 50: 46, } for (let slvl = 1; slvl <= 100; slvl++) { const stats = calculateClayGolemStats(slvl) const expected = Math.min(75, Math.floor(11 + (40 * (slvl - 1)) / (slvl + 6))) expect(stats.slowPct).toBe(expected) expect(stats.slowPct).toBeLessThanOrEqual(75) if (checkpoints[slvl] !== undefined) { expect(stats.slowPct).toBe(checkpoints[slvl]) } } // Verify attacker is slowed when hitting Clay Golem in melee const clayStats = new UnitStatList(registry, { hitpoints: 500 * FIXED_ONE, maxhp: 500 * FIXED_ONE, clay_golem_slow: 40, // 40% slow }) const clayUnit: CombatUnitContext = { id: 'clay-golem', name: 'Clay Golem', statList: clayStats, stateBus: new StateBus(clayStats, registry), } const attackerStats = new UnitStatList(registry, { hitpoints: 500 * FIXED_ONE, maxhp: 500 * FIXED_ONE, }) const attacker: CombatUnitContext = { id: 'attacker-monster', name: 'Fallen', statList: attackerStats, stateBus: new StateBus(attackerStats, registry), } // Attacker hits Clay Golem in melee const combatOut = executeSUnitDmg(attacker, clayUnit, { skillId: 0, attackKind: 'melee', flatPhysMin256: 30 * FIXED_ONE, flatPhysMax256: 30 * FIXED_ONE, autoHit: true, }) expect(combatOut.attackerStatesApplied).toContain('slowed') expect(attacker.stateBus.hasState('slowed')).toBe(true) expect(attacker.statList.getAccruedStat('item_slow')).toBe(40) }) it('verifies Blood Golem life link: 70% life steal to master, 30% damage transfer', () => { const stats = calculateBloodGolemStats(20, 10) expect(stats.lifeLeechPct).toBe(70) expect(stats.damageSharePct).toBe(30) // Test life link healing in SummonManager.tickPets const sm = new SummonManager(registry) const playerStats = new UnitStatList(registry, { hitpoints: 200 * FIXED_ONE, maxhp: 500 * FIXED_ONE, }) const owner: CombatUnitContext = { id: 'player-necro', name: 'Necromancer', statList: playerStats, stateBus: new StateBus(playerStats, registry), } const outcome = sm.createPet({ owner, skillId: 85, slvl: 10 }) // Blood Golem expect(outcome.created).toBe(true) const enemyStats = new UnitStatList(registry, { hitpoints: 1000 * FIXED_ONE, maxhp: 1000 * FIXED_ONE }) const enemy: CombatUnitContext = { id: 'enemy-1', name: 'Gargoyle', statList: enemyStats, stateBus: new StateBus(enemyStats, registry), } const enemyPositions = new Map([['enemy-1', { x: 0, y: 60 }]]) // Tick at tick 10 (when melee attack triggers) sm.tickPets({ currentTick: 10, owner, enemies: [enemy], enemyPositions, missileEngine: {} as any, auraScanner: {} as any, }) // Master should have been healed by 70% of Blood Golem physical damage const curHp = playerStats.getHp256() / FIXED_ONE expect(curHp).toBeGreaterThan(200) }) it('verifies Iron Golem inherits metallic item properties and has innate Thorns', () => { const item: IronGolemItemProps = { baseDamage: 85, defense: 250, auras: [119], // Meditation } const stats = calculateIronGolemStats(15, item, 10) expect(stats.inheritedDamage).toBe(85) expect(stats.inheritedDefense).toBe(250) expect(stats.inheritedAuras).toEqual([119]) // Thorns return pct: 150 + (15 - 1) * 15 = 150 + 210 = 360% expect(stats.thornsReturnPct).toBe(360) }) it('verifies Fire Golem Holy Fire pulse damage and 100% Fire Absorb healing', () => { const fgStats = calculateFireGolemStats(20, 10) expect(fgStats.fireAbsorbPct).toBe(100) expect(fgStats.holyFirePulseDmg).toBe(42) // 2 + 20 * 2 = 42 // Verify Fire Absorb in combat pipeline: Fire damage heals instead of damages const golemStats = new UnitStatList(registry, { hitpoints: 300 * FIXED_ONE, maxhp: 600 * FIXED_ONE, fireresist: 0, fire_golem_absorb: 100, }) const fireGolem: CombatUnitContext = { id: 'fire-golem', name: 'Fire Golem', statList: golemStats, stateBus: new StateBus(golemStats, registry), } const fireAttackerStats = new UnitStatList(registry, { hitpoints: 100 * FIXED_ONE }) const fireAttacker: CombatUnitContext = { id: 'fire-caster', name: 'Fire Mage', statList: fireAttackerStats, stateBus: new StateBus(fireAttackerStats, registry), } const out = executeSUnitDmg(fireAttacker, fireGolem, { skillId: 47, // Fire Ball attackKind: 'spell', elemType: 'fire', elemMin256: 100 * FIXED_ONE, elemMax256: 100 * FIXED_ONE, autoHit: true, }) expect(golemStats.getHp256()).toBe(400 * FIXED_ONE) expect(out.totalDamage).toBe(0) }) it('verifies strict Golem mutual exclusivity (Group 3): exactly 1 active Golem permitted', () => { const sm = new SummonManager(registry) const stats = new UnitStatList(registry, { level: 80 }) const owner: CombatUnitContext = { id: 'player-necro', name: 'Necromancer', statList: stats, stateBus: new StateBus(stats, registry), } // Sequentially summon Clay (75), Blood (85), Iron (90), Fire (94) const r1 = sm.createPet({ owner, skillId: 75, slvl: 1 }) expect(r1.created).toBe(true) expect(sm.getActivePets().length).toBe(1) expect(sm.getActivePets()[0]?.kind).toBe('clay_golem') const r2 = sm.createPet({ owner, skillId: 85, slvl: 1 }) expect(r2.created).toBe(true) expect(sm.getActivePets().length).toBe(1) expect(sm.getActivePets()[0]?.kind).toBe('blood_golem') expect(r2.evictedPetUids).toContain(r1.pet!.uid) const r3 = sm.createPet({ owner, skillId: 90, slvl: 1 }) expect(r3.created).toBe(true) expect(sm.getActivePets().length).toBe(1) expect(sm.getActivePets()[0]?.kind).toBe('iron_golem') expect(r3.evictedPetUids).toContain(r2.pet!.uid) const r4 = sm.createPet({ owner, skillId: 94, slvl: 1 }) expect(r4.created).toBe(true) expect(sm.getActivePets().length).toBe(1) expect(sm.getActivePets()[0]?.kind).toBe('fire_golem') expect(r4.evictedPetUids).toContain(r3.pet!.uid) }) }) // ========================================================================= // Target 4: Revive 1.13c Invariants // ========================================================================= describe('Target 4: Revive 1.13c Invariants & Expiration', () => { it('verifies 180s (4500 frames) lifetime, +200% life, +215% damage, and pet cap = slvl', () => { for (let slvl = 1; slvl <= 30; slvl++) { expect(calculateReviveMaxCount(slvl)).toBe(slvl) const stats0 = calculateReviveStats(slvl, 0) expect(stats0.durationFrames).toBe(4500) expect(stats0.hpMultiplier).toBe(3.0) // 1.0 + 200% = 3.0x expect(stats0.damageMultiplier).toBe(3.15) // 1.0 + 215% = 3.15x // With Skeleton Mastery slvl 20: +5%/lvl HP (+100%), +10%/lvl DMG (+200%) const stats20 = calculateReviveStats(slvl, 20) expect(stats20.hpMultiplier).toBe(4.0) // 3.0 + 20*0.05 = 4.0x expect(stats20.damageMultiplier).toBe(5.15) // 3.15 + 20*0.10 = 5.15x } }) it('verifies Revive despawns when 4500-tick lifetime expires', () => { const sm = new SummonManager(registry) const stats = new UnitStatList(registry, { level: 80 }) const owner: CombatUnitContext = { id: 'player-necro', name: 'Necromancer', statList: stats, stateBus: new StateBus(stats, registry), } const corpses = [{ id: 'corpse-revive', consumed: false, baseMaxHp: 800, isBoss: false }] const outcome = sm.createPet({ owner, skillId: 95, slvl: 5, corpses }) expect(outcome.created).toBe(true) const pet = outcome.pet! expect(pet.lifespanTicks).toBe(4500) // Fast forward lifespan ticks pet.lifespanTicks = 2 sm.tickPets({ currentTick: 1, owner, enemies: [], enemyPositions: new Map(), missileEngine: {} as any, auraScanner: {} as any, }) expect(sm.getActivePets().length).toBe(1) expect(pet.lifespanTicks).toBe(1) // Final tick: should expire and despawn sm.tickPets({ currentTick: 2, owner, enemies: [], enemyPositions: new Map(), missileEngine: {} as any, auraScanner: {} as any, }) expect(sm.getActivePets().length).toBe(0) }) }) // ========================================================================= // Target 5: Curse Exclusivity (cursetype=1 Mutual Overwrite Without Stat Leakage) // ========================================================================= describe('Target 5: Curse Exclusivity Across 100 Alternating Cycles', () => { it('rapidly alternates across all 8 cursetype=1 curses with clean overwrites and 0 stat leakage', () => { const monsterStats = new UnitStatList(registry, { level: 85, hitpoints: 1000 * FIXED_ONE, maxhp: 1000 * FIXED_ONE, damageresist: 50, fireresist: 50, coldresist: 50, lightresist: 50, poisonresist: 50, }) const bus = new StateBus(monsterStats, registry) const curseSequence = [ { id: 66, name: 'amplifydamage', statKey: 'damageresist', val: -100 }, { id: 72, name: 'weaken', statKey: 'damagepercent', val: -33 }, { id: 76, name: 'ironmaiden', statKey: 'ironmaiden_return_pct', val: 200 }, { id: 82, name: 'lifetap', statKey: 'lifetap_leech_pct', val: 50 }, { id: 87, name: 'decrepify', statKey: 'item_slow', val: 50 }, { id: 91, name: 'lowerresist', statKey: 'fireresist', val: -32 }, ] // Run 100 cycles of alternating curse applications for (let cycle = 0; cycle < 100; cycle++) { const curse = curseSequence[cycle % curseSequence.length]! const res = bus.applyState({ stateNameOrId: curse.name, sourceSkillId: curse.id, slvl: 1, durationFrames: 300, curseTypeOverride: 1, stats: { [curse.statKey]: curse.val }, }) expect(res.applied).toBe(true) // Invariant 1: Exactly 1 curse state exists in the stateBus const activeCurses = bus.getActiveEntries().filter(s => s.curseType === 1) expect(activeCurses.length).toBe(1) expect(activeCurses[0]?.stateName).toBe(curse.name) // Invariant 2: Current curse's stat modifier is active expect(monsterStats.getAccruedStat(curse.statKey as any)).toBeDefined() // Invariant 3: Overwritten curse stats do NOT leak into other stats for (const other of curseSequence) { if (other.name !== curse.name && other.statKey !== curse.statKey) { // Modifiers from previous curses must be zero expect(monsterStats.getModifierBonus(other.statKey as any)).toBe(0) } } } }) }) // ========================================================================= // Target 6: Attract Overwrite Immunity (cursetype=2) // ========================================================================= describe('Target 6: Attract Overwrite Immunity', () => { it('verifies that an Attracted monster cannot have Attract overwritten by any curse', () => { const monsterStats = new UnitStatList(registry, { level: 85, hitpoints: 1000 * FIXED_ONE, maxhp: 1000 * FIXED_ONE, damageresist: 0, }) const bus = new StateBus(monsterStats, registry) // Apply Attract (Skill 86, cursetype = 2) const attractRes = bus.applyState({ stateNameOrId: 'attract', sourceSkillId: 86, slvl: 5, durationFrames: 660, curseTypeOverride: 2, }) expect(attractRes.applied).toBe(true) expect(bus.hasState('attract')).toBe(true) // Attempt to cast competing curses against Attracted target const competingCurses = [ { id: 66, name: 'amplifydamage', statKey: 'damageresist', val: -100 }, { id: 72, name: 'weaken', statKey: 'damagepercent', val: -33 }, { id: 76, name: 'ironmaiden', statKey: 'ironmaiden_return_pct', val: 200 }, { id: 82, name: 'lifetap', statKey: 'lifetap_leech_pct', val: 50 }, { id: 87, name: 'decrepify', statKey: 'item_slow', val: 50 }, { id: 91, name: 'lowerresist', statKey: 'fireresist', val: -32 }, { id: 71, name: 'dimvision', statKey: 'item_blind', val: 1 }, { id: 81, name: 'confuse', statKey: 'item_confuse', val: 1 }, { id: 86, name: 'attract', statKey: 'attract', val: 1 }, // Re-casting Attract ] for (const comp of competingCurses) { const attempt = bus.applyState({ stateNameOrId: comp.name, sourceSkillId: comp.id, slvl: 10, durationFrames: 500, curseTypeOverride: comp.name === 'attract' ? 2 : 1, stats: { [comp.statKey]: comp.val }, }) // Must be rejected due to Attract immunity expect(attempt.applied).toBe(false) expect(attempt.blockedByAttract).toBe(true) // Attract state must remain active and undisturbed expect(bus.hasState('attract')).toBe(true) expect(bus.getState('attract')?.slvl).toBe(5) // Zero stat contamination from the rejected curse if (comp.statKey !== 'damageresist') { expect(monsterStats.getModifierBonus(comp.statKey as any)).toBe(0) } } }) }) // ========================================================================= // Target 7: Lower Resist 1/5th Immunity Breaking Rule // ========================================================================= describe('Target 7: Lower Resist Immunity Breaking Rule (1/5th Efficiency)', () => { it('verifies Lower Resist slvl 20 (-63%) applies at 1/5th (12%) against elemental immunes', () => { const lrSlvl20 = calculateLowerResistReduction(20) expect(lrSlvl20).toBe(63) // 63% reduction // Case 1: Non-immune (baseRes = 99%) // Full reduction applies: 99 - 63 = 36% const r99 = computeEffectiveResistance({ baseRes: 99, lowerResistPierce: lrSlvl20, }) expect(r99.convictionAndLRApplied).toBe(63) expect(r99.resAfterConvictionLR).toBe(36) expect(r99.isImmune).toBe(false) expect(r99.effectiveRes).toBe(36) // Case 2: Immune (baseRes = 100%) // 1/5th rule: floor(63 / 5) = 12% // 100 - 12 = 88% -> Broken! const r100 = computeEffectiveResistance({ baseRes: 100, lowerResistPierce: lrSlvl20, }) expect(r100.convictionAndLRApplied).toBe(12) expect(r100.resAfterConvictionLR).toBe(88) expect(r100.isImmune).toBe(false) expect(r100.effectiveRes).toBe(88) // Case 3: Immune (baseRes = 110%) // 110 - 12 = 98% -> Broken! const r110 = computeEffectiveResistance({ baseRes: 110, lowerResistPierce: lrSlvl20, }) expect(r110.convictionAndLRApplied).toBe(12) expect(r110.resAfterConvictionLR).toBe(98) expect(r110.isImmune).toBe(false) expect(r110.effectiveRes).toBe(98) // Case 4: Immune unbreakable (baseRes = 120%) // 120 - 12 = 108% >= 100% -> NOT Broken! const r120 = computeEffectiveResistance({ baseRes: 120, lowerResistPierce: lrSlvl20, }) expect(r120.convictionAndLRApplied).toBe(12) expect(r120.resAfterConvictionLR).toBe(108) expect(r120.isImmune).toBe(true) expect(r120.effectiveRes).toBe(100) // Case 5: Conviction (-85%) + Lower Resist (-63%) stacking: // Total pierce = 148% -> 1/5th = 29% // Against baseRes = 125%: 125 - 29 = 96% -> Broken! const rStackedBreak = computeEffectiveResistance({ baseRes: 125, convictionPierce: 85, lowerResistPierce: lrSlvl20, }) expect(rStackedBreak.convictionAndLRApplied).toBe(29) expect(rStackedBreak.resAfterConvictionLR).toBe(96) expect(rStackedBreak.isImmune).toBe(false) expect(rStackedBreak.effectiveRes).toBe(96) // Case 6: Against baseRes = 130%: 130 - 29 = 101% -> Unbreakable! const rStackedImmune = computeEffectiveResistance({ baseRes: 130, convictionPierce: 85, lowerResistPierce: lrSlvl20, }) expect(rStackedImmune.convictionAndLRApplied).toBe(29) expect(rStackedImmune.resAfterConvictionLR).toBe(101) expect(rStackedImmune.isImmune).toBe(true) expect(rStackedImmune.effectiveRes).toBe(100) }) }) })