import { describe, expect, it } from "vitest"; import { abilityAtLevel } from "./abilityCatalog"; import type { ActiveAura } from "./combatAuras"; import { WOW335_ABILITIES_BY_CLASS } from "./wow335AbilityCatalog"; import { enemyControlAbilityCanEscapeAuras, enemyControlAbilityUsableWhileControlled, enemyControlImmunityPurgeProofForAbility, purgeNamedEnemyControlShoutAuras, } from "./enemyControlImmunityPurge"; function selectedAbility( spellId: number, classId: keyof typeof WOW335_ABILITIES_BY_CLASS = "warrior", ) { const base = WOW335_ABILITIES_BY_CLASS[classId].find((ability) => ability.dbcSpellId === spellId); if (!base) throw new Error(`Missing generated ${classId} ability ${spellId}`); return abilityAtLevel(base, 80); } function activeAura(id: string, targetId: string, expiresAt: number | null = 500): ActiveAura { return { instanceId: `${targetId}:${id}:enemy-source`, definition: { id, name: id, disposition: "debuff", durationMs: expiresAt }, sourceId: "enemy-source", targetId, appliedAt: 0, expiresAt, stacks: 1, absorbs: [], procStates: [], }; } describe("source-proven enemy control immunity purge", () => { it("proves Berserker Rage fear purge and its fear-only controlled cast exception from the selected row", () => { const berserkerRage = selectedAbility(18499); const proof = enemyControlImmunityPurgeProofForAbility(berserkerRage); expect(proof).toMatchObject({ sourceProfile: "wow335", sourceSpellId: 18499, grantsMechanics: ["fear"], purgesNamedShoutSpellMechanics: ["fear"], usableWhileControlledBy: ["fear"], directCastBypasses: [], }); expect(enemyControlAbilityUsableWhileControlled(berserkerRage, "fear")).toBe(true); expect(enemyControlAbilityUsableWhileControlled(berserkerRage, "stun")).toBe(false); }); it("proves Bladestorm fear and stun grants, but no controlled-casting bypass", () => { const bladestorm = selectedAbility(46924); const proof = enemyControlImmunityPurgeProofForAbility(bladestorm); expect(proof).toMatchObject({ sourceProfile: "wow335", sourceSpellId: 46924, grantsMechanics: ["fear", "stun"], purgesNamedShoutSpellMechanics: ["fear"], usableWhileControlledBy: [], directCastBypasses: [], }); expect(enemyControlAbilityUsableWhileControlled(bladestorm, "fear")).toBe(false); expect(enemyControlAbilityUsableWhileControlled(bladestorm, "stun")).toBe(false); }); it("proves Lichborne's exact Charm/Fear/Sleep purge and its separate cast-state flags", () => { const lichborne = selectedAbility(49039, "death-knight"); const proof = enemyControlImmunityPurgeProofForAbility(lichborne); expect(lichborne).toMatchObject({ id: "wow335-death-knight-lichborne", name: "Lichborne", classId: "death-knight", source: "wow335-progression", dbcSpellId: 49039, wowSpellDefense: { sourceProfile: "wow335", damageClass: 0, schoolMask: 32, attributes0: 262144, attributes1: 163872, attributes3: 0, attributes5: 393224, directDamageFromEffects: false, }, }); expect(lichborne.effects).toHaveLength(3); const expectedEffects = [ { miscValue: 1, sourceBasePoints: 0, sourceDieSides: 1, immunity: undefined }, { miscValue: 5, sourceBasePoints: 0, sourceDieSides: 0, immunity: "fear" }, { miscValue: 10, sourceBasePoints: 0, sourceDieSides: 0, immunity: "sleep" }, ] as const; lichborne.effects.forEach((effect, index) => { const expected = expectedEffects[index]; expect(effect.kind).toBe("apply-aura"); if (effect.kind !== "apply-aura") throw new Error("Expected selected Lichborne immunity aura"); expect(effect).toMatchObject({ sourceEffectType: 6, sourceAuraType: 77, sourceBasePoints: expected.sourceBasePoints, sourceDieSides: expected.sourceDieSides, sourcePointsPerLevel: 0, miscValue: expected.miscValue, recipient: "caster", }); if (expected.immunity) expect(effect.aura.controlImmunities).toContain(expected.immunity); expect(effect.aura.id).toBe(`wow335:49039:effect:${index}:aura:77`); }); expect(proof).toMatchObject({ sourceProfile: "wow335", sourceSpellId: 49039, grantsMechanics: ["charm", "fear", "sleep"], purgesNamedShoutSpellMechanics: ["fear"], usableWhileControlledBy: ["fear", "stun", "confuse"], directCastBypasses: ["fear", "stun", "confuse"], }); expect(enemyControlAbilityUsableWhileControlled(lichborne, "fear")).toBe(true); expect(enemyControlAbilityUsableWhileControlled(lichborne, "stun")).toBe(true); expect(enemyControlAbilityUsableWhileControlled(lichborne, "confuse")).toBe(true); expect(enemyControlAbilityUsableWhileControlled(lichborne, "charm")).toBe(false); expect(enemyControlAbilityUsableWhileControlled(lichborne, "sleep")).toBe(false); }); it("fails closed for a changed profile, missing or changed raw flags, and changed aura scalars", () => { const berserkerRage = selectedAbility(18499); const defense = berserkerRage.wowSpellDefense!; const fearAuraIndex = berserkerRage.effects.findIndex((effect) => ( effect.kind === "apply-aura" && effect.sourceAuraType === 77 && effect.miscValue === 5 )); const fearAura = berserkerRage.effects[fearAuraIndex]; if (fearAura.kind !== "apply-aura") throw new Error("Expected source fear immunity aura"); expect(enemyControlImmunityPurgeProofForAbility({ ...berserkerRage, wowSpellDefense: { ...defense, sourceProfile: "ascension" }, })).toBeUndefined(); expect(enemyControlImmunityPurgeProofForAbility({ ...berserkerRage, wowSpellDefense: { ...defense, attributes1: undefined }, })).toBeUndefined(); expect(enemyControlImmunityPurgeProofForAbility({ ...berserkerRage, wowSpellDefense: { ...defense, attributes1: defense.attributes1! ^ 0x00008000 }, })).toBeUndefined(); expect(enemyControlImmunityPurgeProofForAbility({ ...berserkerRage, effects: berserkerRage.effects.map((effect, index) => index === fearAuraIndex ? { ...fearAura, sourceBasePoints: 0 } : effect), })).toBeUndefined(); }); it("fails closed for Lichborne when its selected WoW profile, full flags, or effect scalars change", () => { const lichborne = selectedAbility(49039, "death-knight"); const defense = lichborne.wowSpellDefense!; const fearIndex = lichborne.effects.findIndex((effect) => ( effect.kind === "apply-aura" && effect.sourceAuraType === 77 && effect.miscValue === 5 )); const fearAura = lichborne.effects[fearIndex]; if (fearAura.kind !== "apply-aura") throw new Error("Expected selected Lichborne fear immunity aura"); expect(enemyControlImmunityPurgeProofForAbility({ ...lichborne, wowSpellDefense: { ...defense, sourceProfile: "ascension" }, })).toBeUndefined(); expect(enemyControlImmunityPurgeProofForAbility({ ...lichborne, wowSpellDefense: { ...defense, attributes1: undefined }, })).toBeUndefined(); expect(enemyControlImmunityPurgeProofForAbility({ ...lichborne, wowSpellDefense: { ...defense, attributes5: defense.attributes5! ^ 0x00020000 }, })).toBeUndefined(); expect(enemyControlImmunityPurgeProofForAbility({ ...lichborne, effects: lichborne.effects.map((effect, index) => index === fearIndex ? { ...fearAura, sourceDieSides: 1 } : effect), })).toBeUndefined(); }); it("does not infer active purge from Icebound Fortitude's stun-immunity row", () => { const base = WOW335_ABILITIES_BY_CLASS["death-knight"].find((ability) => ability.dbcSpellId === 48792); if (!base) throw new Error("Missing generated Death Knight ability 48792"); const iceboundFortitude = abilityAtLevel(base, 80); expect(iceboundFortitude.wowSpellDefense?.attributes1).toBe(0); expect(enemyControlImmunityPurgeProofForAbility(iceboundFortitude)).toBeUndefined(); }); it("removes only active named shout auras on the proven target and retains unrelated state", () => { const target = "party-warrior"; const proof = enemyControlImmunityPurgeProofForAbility(selectedAbility(18499)); const auras = [ activeAura("enemy-control:source-spell-5246", target), activeAura("enemy-control:source-spell-19134", target), activeAura("enemy-control:source-spell-20511", target), activeAura("enemy-control:source-spell-29544", target), activeAura("enemy-control:other-control", target), activeAura("enemy-control:source-spell-5246", "another-party-member"), activeAura("enemy-control:source-spell-20511", target, 100), ]; const result = purgeNamedEnemyControlShoutAuras(auras, target, proof, 100); expect(result.removed.map((aura) => aura.definition.id)).toEqual([ "enemy-control:source-spell-5246", "enemy-control:source-spell-19134", "enemy-control:source-spell-20511", "enemy-control:source-spell-29544", ]); expect(result.auras).toEqual([auras[4], auras[5], auras[6]]); }); it("does not accept a structurally forged proof", () => { const aura = activeAura("enemy-control:source-spell-5246", "party-warrior"); const forged = { sourceProfile: "wow335", sourceSpellId: 18499, grantsMechanics: ["fear"], purgesNamedShoutSpellMechanics: ["fear"], usableWhileControlledBy: ["fear"], directCastBypasses: [], } as const; expect(purgeNamedEnemyControlShoutAuras([aura], "party-warrior", forged, 0)).toEqual({ auras: [aura], removed: [], }); }); it("allows Berserker Rage through the sourced 20511 stun only when every action-blocking control is covered", () => { const berserkerRage = selectedAbility(18499); const sourceChildStun = activeAura("enemy-control:source-spell-20511", "party-warrior"); const genericStun = activeAura("enemy-control:another-caster:another-spell:stun", "party-warrior"); const sourceFear = activeAura("enemy-control:source-spell-5246", "party-warrior"); expect(enemyControlAbilityCanEscapeAuras( berserkerRage, "stun", [sourceChildStun], "party-warrior", 100, )).toBe(true); expect(enemyControlAbilityCanEscapeAuras( berserkerRage, "stun", [sourceChildStun, genericStun], "party-warrior", 100, )).toBe(false); expect(enemyControlAbilityCanEscapeAuras( berserkerRage, "fear", [sourceFear, genericStun], "party-warrior", 100, )).toBe(false); expect(enemyControlAbilityCanEscapeAuras( berserkerRage, "stun", [sourceFear], "party-warrior", 100, )).toBe(false); }); it("does not let Bladestorm borrow its purge grant as a control cast bypass", () => { const bladestorm = selectedAbility(46924); const sourceFear = activeAura("enemy-control:source-spell-5246", "party-warrior"); const sourceChildStun = activeAura("enemy-control:source-spell-20511", "party-warrior"); expect(enemyControlAbilityCanEscapeAuras( bladestorm, "fear", [sourceFear], "party-warrior", 100, )).toBe(false); expect(enemyControlAbilityCanEscapeAuras( bladestorm, "stun", [sourceChildStun], "party-warrior", 100, )).toBe(false); }); it("allows Lichborne through sourced or directly supported control and retains unrelated auras", () => { const lichborne = selectedAbility(49039, "death-knight"); const proof = enemyControlImmunityPurgeProofForAbility(lichborne); const sourceChildStun = activeAura("enemy-control:source-spell-20511", "party-death-knight"); const genericStun = activeAura("enemy-control:another-caster:another-spell:stun", "party-death-knight"); const genericConfuse = activeAura("enemy-control:another-caster:another-spell:confuse", "party-death-knight"); const genericFear = activeAura("enemy-control:another-caster:another-spell:fear", "party-death-knight"); expect(enemyControlAbilityCanEscapeAuras( lichborne, "stun", [sourceChildStun], "party-death-knight", 100, )).toBe(true); expect(enemyControlAbilityCanEscapeAuras( lichborne, "stun", [genericStun], "party-death-knight", 100, )).toBe(true); expect(enemyControlAbilityCanEscapeAuras( lichborne, "confuse", [genericConfuse], "party-death-knight", 100, )).toBe(true); expect(enemyControlAbilityCanEscapeAuras( lichborne, "fear", [genericFear], "party-death-knight", 100, )).toBe(true); expect(enemyControlAbilityCanEscapeAuras( selectedAbility(18499), "fear", [genericFear], "party-death-knight", 100, )).toBe(false); const mixedFear = purgeNamedEnemyControlShoutAuras( [genericFear, activeAura("enemy-control:source-spell-19134", "party-death-knight")], "party-death-knight", proof, 100, ); expect(mixedFear.removed.map((aura) => aura.definition.id)).toEqual(["enemy-control:source-spell-19134"]); expect(mixedFear.auras).toEqual([genericFear]); expect(purgeNamedEnemyControlShoutAuras( [genericStun, genericConfuse], "party-death-knight", proof, 100, )).toEqual({ auras: [genericStun, genericConfuse], removed: [], }); }); });