Files
healer-man/src/game/enemyControlImmunityPurge.test.ts
T
2026-10-07 13:05:04 -04:00

302 lines
13 KiB
TypeScript

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: [],
});
});
});