Files
healer-man/src/game/romAbilityCatalogSemantics.test.ts
T
2026-09-23 16:14:19 -04:00

183 lines
7.9 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type { AbilityEffect } from "./abilityCatalog";
import { ROM_ALL_ABILITIES } from "./romAbilityCatalog";
import generated from "./romPlayerCatalog.generated.json";
function named(name: string) {
const ability = ROM_ALL_ABILITIES.find((entry) => entry.name === name);
if (!ability) throw new Error(`Missing RuneWaker ability: ${name}`);
return ability;
}
function isMeaningfulEffect(effect: AbilityEffect): boolean {
if (effect.kind === "scripted" || effect.kind === "unsupported") return false;
if (["damage", "heal", "dot", "hot", "shield"].includes(effect.kind)) {
const output = effect as Extract<AbilityEffect, { kind: "damage" | "heal" | "dot" | "hot" | "shield" }>;
return (
output.coefficient > 0
|| (output.coefficientPerLevel ?? 0) > 0
|| (output.basePoints ?? 0) > 0
|| (output.pointsPerLevel ?? 0) > 0
|| (output.kind === "heal" && (output.percentMaxHealth ?? 0) > 0)
);
}
if (effect.kind === "apply-aura") {
return Boolean(
effect.control
|| effect.aura.modifiers?.length
|| effect.aura.absorbs?.length
|| effect.aura.procs?.length
|| effect.aura.utilityTags?.length
|| effect.aura.controlImmunities?.length
|| effect.aura.reflectSpellChance
|| effect.aura.redirectDamagePercent,
);
}
return true;
}
describe("executable RuneWaker ability mappings", () => {
it("maps Taunt, Shout, and Cleanse to their real combat mechanics", () => {
expect(named("Taunt").effects).toEqual(expect.arrayContaining([
expect.objectContaining({ kind: "taunt" }),
]));
expect(named("Shout").effects).toEqual(expect.arrayContaining([
expect.objectContaining({ kind: "apply-aura", control: { kind: "stun" } }),
]));
expect(named("Cleanse").effects).toEqual(expect.arrayContaining([
expect.objectContaining({ kind: "dispel", mode: "cleanse", relationship: "friendly" }),
]));
});
it("retains every executable part of multi-effect abilities", () => {
const kinds = named("Sneak Attack").effects.map((effect) => effect.kind);
expect(kinds).toEqual(["damage", "apply-aura", "dot"]);
});
it("executes one projectile hit for Rogue Throw and Scout Shot", () => {
for (const name of ["Throw", "Shot"]) {
const ability = named(name);
expect(ability.effects).toHaveLength(1);
expect(ability.effects[0]).toMatchObject({ kind: "damage" });
for (const version of ability.ranks ?? []) {
expect(version.effects).toHaveLength(1);
expect(version.effects[0]).toMatchObject({ kind: "damage" });
}
}
});
it("implements source-authored immunity and absorbs as named auras", () => {
const holyAura = named("Holy Aura").effects.find((effect) => effect.kind === "apply-aura");
expect(holyAura).toMatchObject({
aura: {
disposition: "buff",
modifiers: [{ kind: "damage", direction: "taken", operation: "percent", value: -1 }],
},
});
const electrostatic = named("Electrostatic Charge").effects.find((effect) => (
effect.kind === "apply-aura" && (effect.aura.absorbs?.length ?? 0) > 0
));
expect(electrostatic).toMatchObject({
aura: { disposition: "buff", absorbs: [expect.objectContaining({ amount: 30 })] },
});
});
it("never substitutes a generic mark or legacy shield for a RuneWaker mechanic", () => {
const effects = ROM_ALL_ABILITIES.flatMap((ability) => ability.effects);
expect(effects.some((effect) => effect.kind === "status" && effect.status === "marked")).toBe(false);
expect(effects.some((effect) => effect.kind === "shield")).toBe(false);
expect(generated.skills.some((ability) => ["shield", "status", "utility", "cleanse"].includes(ability.effect.kind))).toBe(false);
});
it("executes standard elite cleanses, movement, conversion, and triggered spells", () => {
expect(named("Antidote").effects).toEqual(expect.arrayContaining([
expect.objectContaining({ kind: "dispel", categories: ["poison"] }),
]));
expect(named("Spirit Jump").effects).toEqual(expect.arrayContaining([
expect.objectContaining({ kind: "pull", toward: "caster" }),
]));
expect(named("Essence Extraction").effects).toEqual(expect.arrayContaining([
expect.objectContaining({ kind: "resource-drain", resource: "mana", amount: 10, percentage: true }),
expect.objectContaining({ kind: "resource", resource: "nature-power", amount: 4 }),
]));
expect(named("Spreading Pain").effects.filter((effect) => effect.kind === "trigger-spell")).toHaveLength(3);
});
it("gives every active base skill and playable rank a concrete runtime effect", () => {
const versions = ROM_ALL_ABILITIES.flatMap((ability) => [
{ name: ability.name, rank: 0, passive: ability.passive, effects: ability.effects },
...(ability.ranks ?? []).map((rank) => ({
name: ability.name,
rank: rank.rank,
passive: ability.passive,
effects: rank.effects,
})),
]);
expect(versions).toHaveLength(1_863);
const scriptOnly = ROM_ALL_ABILITIES
.filter((ability) => !ability.passive && ability.effects.every((effect) => effect.kind === "scripted"))
.map((ability) => ability.name)
.sort();
expect(scriptOnly).toEqual([]);
for (const version of versions.filter((entry) => !entry.passive)) {
expect(
version.effects.some(isMeaningfulEffect),
`${version.name} rank ${version.rank}`,
).toBe(true);
}
});
it("implements source state for RuneWaker's active transformations", () => {
expect(named("Frost Arrow").effects).toEqual(expect.arrayContaining([
expect.objectContaining({
kind: "apply-aura",
aura: expect.objectContaining({
utilityTags: ["rom-frost-arrow"],
procs: [expect.objectContaining({ action: { kind: "custom", id: "rom-frost-arrow" } })],
}),
}),
]));
expect(named("Electric Compression").effects).toEqual(expect.arrayContaining([
expect.objectContaining({ kind: "apply-aura", aura: expect.objectContaining({ utilityTags: ["rom-static-field-charge"] }) }),
]));
expect(named("Entling Offering").effects).toEqual(expect.arrayContaining([
expect.objectContaining({
kind: "apply-aura",
aura: expect.objectContaining({
utilityTags: ["rom-entling-offering"],
procs: [expect.objectContaining({ action: { kind: "custom", id: "rom-entling-offering" } })],
}),
}),
]));
expect(named("Holy Salvation Candle").effects).toEqual(expect.arrayContaining([
expect.objectContaining({ kind: "apply-aura", aura: expect.objectContaining({ utilityTags: ["rom-holy-salvation-candle"] }) }),
]));
expect(named("Remodeled Body").effects).toEqual(expect.arrayContaining([
expect.objectContaining({ kind: "apply-aura", aura: expect.objectContaining({ utilityTags: ["rom-remodeled-body"] }) }),
]));
expect(named("Revival").effects).toEqual(expect.arrayContaining([
expect.objectContaining({ kind: "utility", action: "class-script" }),
]));
});
it("models the Knight's Holy Seal build-and-spend loop", () => {
expect(named("Holy Strike").effects).toEqual(expect.arrayContaining([
{ kind: "combo", amount: 1 },
]));
expect(named("Holy Seal").effects).toEqual(expect.arrayContaining([
{ kind: "combo", amount: 4 },
]));
expect(named("Charge").effects).toEqual(expect.arrayContaining([
{ kind: "combo", amount: 2 },
]));
expect(named("Punishment").effects).toEqual(expect.arrayContaining([
{ kind: "combo", amount: -5 },
]));
expect(named("Holy Strike").effects.filter((effect) => effect.kind === "dot")).toHaveLength(1);
expect(named("Holy Seal").effects.filter((effect) => effect.kind === "dot")).toHaveLength(1);
expect(named("Punishment").effects.filter((effect) => effect.kind === "damage")).toHaveLength(1);
expect(named("Punishment").effects.filter((effect) => effect.kind === "dot")).toHaveLength(1);
});
});