/** * Adversarial Challenger Stress & Boundary Suite: Necromancer Curses Tree * Milestone M3: Skills 66, 71, 72, 76, 77, 81, 82, 86, 87, 91 (1.13c Ground Truth) * * Verifies: * 1. High-density multi-monster curse overwrite cascades (50 monsters in 2:1 isometric ellipses). * 2. Multi-curse rapid alternating application (Amp -> Decrep -> Weaken -> Lower Resist) across 100 cycles with 0 stat leakage. * 3. Attract inviolability under 100 continuous attempts of competing curses. * 4. Borderline physical immunity threshold transitions (99%, 100%, 109%, 110%, 119%, 120%, 130%). * 5. Iron Maiden lethal melee reflection scenarios against high-damage and physical-immune monsters. * 6. Difficulty duration scaling under Nightmare (50%) and Hell (25%) for Dim Vision. * 7. Boss and Champion mental curse rejection in ActScene (Dim Vision, Terror, Confuse, Attract). */ import { describe, expect, it, beforeEach } from 'vitest' import { ISO_GROUND_ASPECT_RATIO, calculateCurseRadiusPx, calculateCurseDurationFrames, calculateIronMaidenReturnPct, calculateLowerResistReduction, computeImmunityBreak, calculateCurseEffect, } 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 { GameEngine, type GameEngineOptions } from '../../../src/server/engine/engine.ts' import { castSkill } from '../../../src/server/scene/skill-caster.ts' import type { Monster, CombatTerrain } from '../../../src/server/engine/combat.ts' describe('Adversarial Challenger: Necromancer Curses Stress & Boundary Suite', () => { let engine: GameEngine let statusElem: HTMLElement const dummyRuntime: any = { map: null } const dummyTerrain: any = { widthPx: 1000, heightPx: 1000, overlap: () => 0 } beforeEach(() => { const dummyOpts: GameEngineOptions = { spawn: { x: 200, y: 200 }, stats: [], xpTable: [0, 100], itemBases: [], prefixAffixes: [], suffixAffixes: [], skills: [], npcDefs: [], questDefs: [], monsterCount: 0, combatOptions: { playerSpeed: 100, playerReach: 50, playerCooldownTicks: 0, playerDamage: 25, playerManaPerAttack: 0, respawnTicks: 100, }, talkRadius: 80, pickupRadius: 40, inventoryCols: 10, inventoryRows: 4, } engine = new GameEngine(dummyTerrain, dummyOpts) engine.world.player.x = 200 engine.world.player.y = 200 engine.world.player.cooldown = 0 engine.world.player.mana = 1000 statusElem = { textContent: '' } as any }) // ========================================================================= // Challenge 1: High-Density 50-Monster 2:1 Isometric AoE Curse Applications // ========================================================================= describe('Challenge 1: High-Density 50-Monster 2:1 Isometric AoE Curse Targeting', () => { it('afflicts exactly monsters inside the 2:1 ground ellipse and excludes those outside', () => { const radiusPx = calculateCurseRadiusPx(66, 1) // slvl 1 Amplify Damage: 80 px const monsters: Monster[] = [] // Generate 50 monsters in radial bands for (let i = 0; i < 50; i++) { // Vary distance from 20 px to 160 px const dist = 20 + i * 2.8 const angle = (i * 0.45) const mx = 200 + Math.cos(angle) * dist const my = 200 + Math.sin(angle) * (dist * ISO_GROUND_ASPECT_RATIO) monsters.push({ index: i, x: mx, y: my, hp: 500, cooldown: 0, hitFlash: 0, corpseTicks: 0, state: 'idle', facing: 0, stats: { id: `fallen_${i}`, name: 'Fallen', hp: 500, damage: 10, cooldownTicks: 10, reach: 20, aggroRadius: 100, speed: 2, xp: 5 }, }) } engine.world.monsters = monsters // Cast Amplify Damage (66) at (200, 200) const castSuccess = castSkill(66, 200, 200, { engine, runtime: dummyRuntime, hudManager: null, status: statusElem, }) expect(castSuccess).toBe(true) // Verify each monster's curse state against 2:1 ground geometry let afflictedCount = 0 for (const m of monsters) { const isoDist = Math.hypot(m.x - 200, (m.y - 200) / ISO_GROUND_ASPECT_RATIO) if (isoDist <= radiusPx) { expect(m.curse?.name).toBe('amplifydamage') expect(m.curse?.curseType).toBe(1) afflictedCount++ } else { expect(m.curse).toBeUndefined() } } expect(afflictedCount).toBeGreaterThan(15) expect(afflictedCount).toBeLessThan(50) }) }) // ========================================================================= // Challenge 2: Rapid Alternating Multi-Curse Overwrites (100 Cycles) // ========================================================================= describe('Challenge 2: 100 Cycles of Rapid Alternating Curse Overwrites with Zero Stat Leakage', () => { it('alternates Amp -> Decrep -> Weaken -> Lower Resist 100 times without residual modifier accumulation', async () => { const registry = await getSharedDataRegistry() const stats = new UnitStatList() const bus = new StateBus(stats, registry) for (let cycle = 0; cycle < 100; cycle++) { // Step 1: Amplify Damage const r1 = bus.applyState({ stateNameOrId: 'amplifydamage', slvl: 20, stats: { damageresist: -100, amplify_phys_pierce: 100 }, curseTypeOverride: 1, }) expect(r1.applied).toBe(true) expect(bus.hasState('amplifydamage')).toBe(true) expect(stats.getModifierBonus('damageresist')).toBe(-100) expect(stats.getModifierBonus('amplify_phys_pierce')).toBe(100) expect(stats.getModifierBonus('damagepercent')).toBe(0) expect(stats.getModifierBonus('velocitypercent')).toBe(0) // Step 2: Decrepify const r2 = bus.applyState({ stateNameOrId: 'decrepify', slvl: 20, stats: { damageresist: -50, velocitypercent: -50, damagepercent: -50 }, curseTypeOverride: 1, }) expect(r2.applied).toBe(true) expect(bus.hasState('decrepify')).toBe(true) expect(bus.hasState('amplifydamage')).toBe(false) expect(stats.getModifierBonus('damageresist')).toBe(-50) expect(stats.getModifierBonus('amplify_phys_pierce')).toBe(0) expect(stats.getModifierBonus('velocitypercent')).toBe(-50) expect(stats.getModifierBonus('damagepercent')).toBe(-50) // Step 3: Weaken const r3 = bus.applyState({ stateNameOrId: 'weaken', slvl: 20, stats: { damagepercent: -33 }, curseTypeOverride: 1, }) expect(r3.applied).toBe(true) expect(bus.hasState('weaken')).toBe(true) expect(bus.hasState('decrepify')).toBe(false) expect(stats.getModifierBonus('damageresist')).toBe(0) expect(stats.getModifierBonus('velocitypercent')).toBe(0) expect(stats.getModifierBonus('damagepercent')).toBe(-33) // Step 4: Lower Resist const r4 = bus.applyState({ stateNameOrId: 'lowerresist', slvl: 20, stats: { lowerresist_pierce: 63, fireresist: -63, lightresist: -63, coldresist: -63, poisonresist: -63 }, curseTypeOverride: 1, }) expect(r4.applied).toBe(true) expect(bus.hasState('lowerresist')).toBe(true) expect(bus.hasState('weaken')).toBe(false) expect(stats.getModifierBonus('damagepercent')).toBe(0) expect(stats.getModifierBonus('lowerresist_pierce')).toBe(63) expect(stats.getModifierBonus('fireresist')).toBe(-63) } // Final audit: only Lower Resist remains expect(bus.getActiveEntries().length).toBe(1) expect(bus.hasState('lowerresist')).toBe(true) expect(stats.getModifierBonus('lowerresist_pierce')).toBe(63) expect(stats.getModifierBonus('damageresist')).toBe(0) expect(stats.getModifierBonus('amplify_phys_pierce')).toBe(0) expect(stats.getModifierBonus('damagepercent')).toBe(0) expect(stats.getModifierBonus('velocitypercent')).toBe(0) }) }) // ========================================================================= // Challenge 3: Attract Inviolability Under 100 Competing Attacks // ========================================================================= describe('Challenge 3: Attract Inviolability Under 100 Competing Curse Attempts', () => { it('defends Attract from 100 continuous attempts by competing curses and duplicates', async () => { const registry = await getSharedDataRegistry() const stats = new UnitStatList() const bus = new StateBus(stats, registry) // Apply initial Attract const initAttract = bus.applyState({ stateNameOrId: 'attract', slvl: 10, curseTypeOverride: 2, durationFrames: 1110, }) expect(initAttract.applied).toBe(true) const competingCurses = [ 'amplifydamage', 'decrepify', 'weaken', 'ironmaiden', 'terror', 'confuse', 'lifetap', 'lowerresist', 'dimvision', 'attract', ] for (let i = 0; i < 100; i++) { const curseToTest = competingCurses[i % competingCurses.length]! const result = bus.applyState({ stateNameOrId: curseToTest, slvl: 20, curseTypeOverride: curseToTest === 'attract' ? 2 : 1, }) expect(result.applied).toBe(false) expect(result.blockedByAttract).toBe(true) expect(bus.hasState('attract')).toBe(true) } // Attract retains original slvl const activeAttract = bus.getState('attract') expect(activeAttract).toBeDefined() expect(activeAttract?.slvl).toBe(10) expect(activeAttract?.durationFrames).toBe(1110) }) }) // ========================================================================= // Challenge 4: Borderline Physical Immunity Threshold Transitions // ========================================================================= describe('Challenge 4: Borderline Physical Immunity Threshold Transitions', () => { it('verifies exact threshold boundaries under Amplify Damage (-100% / -20% on immune)', () => { // 99%: non-immune -> 99 - 100 = -1% (not immune, full pierce) const b99 = computeImmunityBreak(99, 100) expect(b99.wasImmune).toBe(false) expect(b99.effectiveRes).toBe(-1) expect(b99.broken).toBe(false) // 100%: immune -> 100 - 20 = 80% (broken!) const b100 = computeImmunityBreak(100, 100) expect(b100.wasImmune).toBe(true) expect(b100.effectiveRes).toBe(80) expect(b100.broken).toBe(true) // 109%: immune -> 109 - 20 = 89% (broken!) const b109 = computeImmunityBreak(109, 100) expect(b109.effectiveRes).toBe(89) expect(b109.broken).toBe(true) // 110%: immune -> 110 - 20 = 90% (broken!) const b110 = computeImmunityBreak(110, 100) expect(b110.effectiveRes).toBe(90) expect(b110.broken).toBe(true) // 119%: immune -> 119 - 20 = 99% (broken!) const b119 = computeImmunityBreak(119, 100) expect(b119.effectiveRes).toBe(99) expect(b119.broken).toBe(true) // 120%: immune -> 120 - 20 = 100% (remains immune!) const b120 = computeImmunityBreak(120, 100) expect(b120.wasImmune).toBe(true) expect(b120.effectiveRes).toBe(100) expect(b120.broken).toBe(false) // 130%: immune -> 130 - 20 = 110% (remains immune!) const b130 = computeImmunityBreak(130, 100) expect(b130.wasImmune).toBe(true) expect(b130.effectiveRes).toBe(110) expect(b130.broken).toBe(false) }) it('verifies exact threshold boundaries under Decrepify (-50% / -10% on immune)', () => { // 99%: non-immune -> 99 - 50 = 49% const b99 = computeImmunityBreak(99, 50) expect(b99.wasImmune).toBe(false) expect(b99.effectiveRes).toBe(49) // 100%: immune -> 100 - 10 = 90% (broken!) const b100 = computeImmunityBreak(100, 50) expect(b100.wasImmune).toBe(true) expect(b100.effectiveRes).toBe(90) expect(b100.broken).toBe(true) // 109%: immune -> 109 - 10 = 99% (broken!) const b109 = computeImmunityBreak(109, 50) expect(b109.effectiveRes).toBe(99) expect(b109.broken).toBe(true) // 110%: immune -> 110 - 10 = 100% (remains immune!) const b110 = computeImmunityBreak(110, 50) expect(b110.effectiveRes).toBe(100) expect(b110.broken).toBe(false) }) }) // ========================================================================= // Challenge 5: Iron Maiden Lethal Reflection Scenarios // ========================================================================= describe('Challenge 5: Iron Maiden Lethal Melee Reflection Scenarios', () => { it('kills a high-damage monster on melee attack via lethal reflected damage', () => { const monsterStats = new UnitStatList() monsterStats.setHp256(500 * FIXED_ONE) const monsterBus = new StateBus(monsterStats) monsterBus.applyState({ stateNameOrId: 'ironmaiden', slvl: 20, // 675% reflection curseTypeOverride: 1, }) const attacker: CombatUnitContext = { id: 'lethal_monster', name: 'Moon Lord', statList: monsterStats, stateBus: monsterBus, unitType: 'monster', } const defenderStats = new UnitStatList() defenderStats.setHp256(2000 * FIXED_ONE) const defenderBus = new StateBus(defenderStats) const defender: CombatUnitContext = { id: 'hero', name: 'Paladin', statList: defenderStats, stateBus: defenderBus, unitType: 'player', } // Moon Lord attacks Paladin for 200 physical melee damage const outcome = executeSUnitDmg(attacker, defender, { skillId: 0, attackKind: 'melee', srcDam: 0, flatPhysMin256: 200 * FIXED_ONE, flatPhysMax256: 200 * FIXED_ONE, autoHit: true, }) // Raw reflection: 200 * 6.75 = 1350 damage! // Attacker had 500 HP, so HP drops to 0! expect(outcome.reflectedPhys256).toBe(1350 * FIXED_ONE) expect(monsterStats.getHp256()).toBe(0) }) it('inflicts 0 reflected damage on a 100% physical immune monster', () => { const immuneMonsterStats = new UnitStatList() immuneMonsterStats.setHp256(1000 * FIXED_ONE) immuneMonsterStats.addStat('damageresist', 100) // Physical Immune! const monsterBus = new StateBus(immuneMonsterStats) monsterBus.applyState({ stateNameOrId: 'ironmaiden', slvl: 20, curseTypeOverride: 1, }) const attacker: CombatUnitContext = { id: 'ghost', name: 'Ghost', statList: immuneMonsterStats, stateBus: monsterBus, unitType: 'monster', } const defenderStats = new UnitStatList() defenderStats.setHp256(2000 * FIXED_ONE) const defender: CombatUnitContext = { id: 'hero', name: 'Paladin', statList: defenderStats, stateBus: new StateBus(defenderStats), unitType: 'player', } // Ghost attacks Paladin const outcome = executeSUnitDmg(attacker, defender, { skillId: 0, attackKind: 'melee', srcDam: 0, flatPhysMin256: 100 * FIXED_ONE, flatPhysMax256: 100 * FIXED_ONE, autoHit: true, }) // Since ghost is 100% physical immune, reflected damage is 0! expect(outcome.reflectedPhys256 ?? 0).toBe(0) expect(immuneMonsterStats.getHp256()).toBe(1000 * FIXED_ONE) }) }) // ========================================================================= // Challenge 6: Boss and Champion Mental Curse Immunity in ActScene // ========================================================================= describe('Challenge 6: Boss and Champion Mental Curse Immunity in ActScene', () => { it('afflicts normal monsters but skips bosses and champions for Dim Vision, Terror, Confuse, Attract', () => { const normalMonster: Monster = { index: 0, x: 210, y: 200, hp: 500, cooldown: 0, hitFlash: 0, corpseTicks: 0, state: 'idle', facing: 0, stats: { id: 'zombie_normal', name: 'Zombie', hp: 500, damage: 10, cooldownTicks: 10, reach: 20, aggroRadius: 100, speed: 2, xp: 5, rank: 'normal' }, } const champMonster: Monster = { index: 1, x: 215, y: 200, hp: 1500, cooldown: 0, hitFlash: 0, corpseTicks: 0, state: 'idle', facing: 0, stats: { id: 'zombie_champ', name: 'Zombie Champion', hp: 1500, damage: 20, cooldownTicks: 10, reach: 20, aggroRadius: 100, speed: 2, xp: 20, rank: 'champion' }, } const bossMonster: Monster = { index: 2, x: 220, y: 200, hp: 5000, cooldown: 0, hitFlash: 0, corpseTicks: 0, state: 'idle', facing: 0, stats: { id: 'andariel', name: 'Andariel', hp: 5000, damage: 50, cooldownTicks: 10, reach: 20, aggroRadius: 100, speed: 2, xp: 100, rank: 'unique', type: 'boss', isBoss: true } as any, } engine.world.monsters = [normalMonster, champMonster, bossMonster] // 1. Cast Dim Vision (71) castSkill(71, 200, 200, { engine, runtime: dummyRuntime, hudManager: null, status: statusElem }) expect(normalMonster.curse?.name).toBe('dimvision') expect(champMonster.curse).toBeUndefined() expect(bossMonster.curse).toBeUndefined() // 2. Cast Terror (77) castSkill(77, 200, 200, { engine, runtime: dummyRuntime, hudManager: null, status: statusElem }) expect(normalMonster.curse?.name).toBe('terror') expect(champMonster.curse).toBeUndefined() expect(bossMonster.curse).toBeUndefined() // 3. Cast Confuse (81) castSkill(81, 200, 200, { engine, runtime: dummyRuntime, hudManager: null, status: statusElem }) expect(normalMonster.curse?.name).toBe('confuse') expect(champMonster.curse).toBeUndefined() expect(bossMonster.curse).toBeUndefined() // 4. Attempt to cast Attract (86) on the boss const attractBoss = castSkill(86, 220, 200, { engine, runtime: dummyRuntime, hudManager: null, status: statusElem }) expect(attractBoss).toBe(false) expect(bossMonster.curse).toBeUndefined() // 5. Cast Decrepify (87) — Decrepify affects Act Bosses in Diablo II! const decrepSuccess = castSkill(87, 200, 200, { engine, runtime: dummyRuntime, hudManager: null, status: statusElem }) expect(decrepSuccess).toBe(true) expect(normalMonster.curse?.name).toBe('decrepify') expect(champMonster.curse?.name).toBe('decrepify') expect(bossMonster.curse?.name).toBe('decrepify') // Boss IS affected by Decrepify! }) }) })