import { describe, expect, it } from "vitest"; import rawCatalog from "./romPlayerCatalog.generated.json"; import { romSemanticCoverage, translateRomAbilitySemantics, type RomGeneratedAbilityLike, type RomSourceComponent, } from "./romAbilitySemantics"; const skills = rawCatalog.skills as unknown as readonly RomGeneratedAbilityLike[]; function named(name: string): RomGeneratedAbilityLike { const result = skills.find((skill) => skill.name === name); if (!result) throw new Error(`Missing generated RuneWaker skill: ${name}`); return result; } function semantics(name: string) { return translateRomAbilitySemantics(named(name)); } describe("RuneWaker component-backed ability semantics", () => { it("retains authoritative attack, flags, DOT, modifiers, and magic-shield fields", () => { const sneak = named("Sneak Attack"); const direct = sneak.source?.components?.find((component) => component.id === 620284); const stun = sneak.source?.components?.find((component) => component.id === 500725); const bleed = sneak.source?.components?.find((component) => component.id === 500726); expect(direct).toMatchObject({ specialActionFlags: 4, attack: { type: 0, damagePower: -1.3, fixedValue: -0.3 }, }); expect(stun).toMatchObject({ effectTime: 3, effectFlagWords: [16_777_283, 0] }); expect(bleed).toMatchObject({ dot: { timeSeconds: 2, type: 0, base: -8 } }); const fireWard = named("Fire Ward").source?.components?.[0]; expect(fireWard).toMatchObject({ effectTime: 600, abilityModifiers: [{ type: 29, value: 10 }], abilitySkillLevelArg: 20, }); const electrostatic = named("Electrostatic Charge").source?.components?.[0]; expect(electrostatic).toMatchObject({ settingFlags: 355, magicShield: { rawBaseWord: 0, type: 3, effect: 0, point: 2, skillLevelArg: 28 }, }); }); it("translates taunts and stuns instead of the old healing fallback", () => { const taunt = semantics("Taunt"); expect(taunt).toEqual(expect.arrayContaining([ expect.objectContaining({ kind: "taunt", scope: "single", hateDelta: 30 }), ])); expect(taunt.some((semantic) => semantic.kind === "heal")).toBe(false); const shout = semantics("Shout"); expect(shout).toEqual(expect.arrayContaining([ expect.objectContaining({ kind: "control", effect: "stun", durationSeconds: 3 }), ])); expect(shout.some((semantic) => semantic.kind === "heal")).toBe(false); }); it("translates Cleanse as a harmful-effect dispel", () => { expect(semantics("Cleanse")).toEqual(expect.arrayContaining([ expect.objectContaining({ kind: "dispel", polarity: "harmful", mode: "remove" }), ])); }); it("keeps every Sneak Attack effect and its specialized use conditions", () => { const result = semantics("Sneak Attack"); expect(result).toEqual(expect.arrayContaining([ expect.objectContaining({ kind: "damage", school: "physical", power: 1.3 }), expect.objectContaining({ kind: "control", effect: "stun", durationSeconds: 3 }), expect.objectContaining({ kind: "dot", intervalSeconds: 2, durationSeconds: 6, basePerTick: 8 }), expect.objectContaining({ kind: "custom", mechanicId: "conditional-use", unresolved: false }), ])); }); it("translates resistance, immunity, silence, and source-authored absorbs", () => { expect(semantics("Fire Ward")).toEqual(expect.arrayContaining([ expect.objectContaining({ kind: "aura", effect: "fire-resistance", value: 10, durationSeconds: 600 }), ])); expect(semantics("Holy Aura")).toEqual(expect.arrayContaining([ expect.objectContaining({ kind: "aura", effect: "damage-immunity", durationSeconds: 5 }), ])); expect(semantics("Silence")).toEqual(expect.arrayContaining([ expect.objectContaining({ kind: "control", effect: "silence", durationSeconds: 4 }), ])); expect(semantics("Electrostatic Charge")).toEqual(expect.arrayContaining([ expect.objectContaining({ kind: "absorb", basis: "max-health-percent", school: "all", amount: 2, perSkillLevel: 28, durationSeconds: 60, }), ])); }); it("retains multi-effect interrupt/resource skills and passive cooldown modifiers", () => { const bindingSilence = semantics("Binding Silence"); expect(bindingSilence).toEqual(expect.arrayContaining([ expect.objectContaining({ kind: "damage" }), expect.objectContaining({ kind: "interrupt", lockoutSeconds: 0 }), expect.objectContaining({ kind: "resource", resource: "nature's power", operation: "restore", amount: 1 }), ])); expect(semantics("Electrostatic Charge Expertise")).toEqual(expect.arrayContaining([ expect.objectContaining({ kind: "cooldown-reset", mode: "reduce", seconds: 30 }), ])); }); it("emits proc semantics from component trigger records", () => { const procSkill = skills.find((skill) => skill.source?.components?.some((component) => ( (component.onAttackTrigger?.id ?? 0) > 0 || Object.entries(component.triggers ?? {}).some(([key, value]) => key.endsWith("MagicId") && value > 0) ))); expect(procSkill).toBeDefined(); expect(translateRomAbilitySemantics(procSkill!).some((semantic) => semantic.kind === "proc")).toBe(true); }); it("gives every generated ability an honest semantic and reports fallback coverage", () => { const coverage = romSemanticCoverage(skills); expect(coverage.abilities).toBe(929); expect(coverage.semantics).toBeGreaterThanOrEqual(coverage.abilities); expect(skills.every((skill) => translateRomAbilitySemantics(skill).length > 0)).toBe(true); expect(coverage.customSemantics).toBeGreaterThan(0); expect(coverage.unresolvedAbilities).toBeGreaterThanOrEqual(0); }); it("does not expose generic mark or substitute-shield semantics", () => { const serialized = JSON.stringify(skills.flatMap((skill) => translateRomAbilitySemantics(skill))); expect(serialized).not.toContain('"effect":"marked"'); expect(serialized).not.toContain("generic-mark"); expect(serialized).not.toContain("substitute-shield"); }); it("allows the pure translator to consume an individual generated rank", () => { const rank = (rawCatalog.skills.find((skill) => skill.name === "Sneak Attack")?.ranks[0]) as unknown as RomGeneratedAbilityLike; expect(translateRomAbilitySemantics(rank).map((semantic) => semantic.kind)).toEqual(expect.arrayContaining(["damage", "dot", "control"])); }); }); // Structural sentinel: generated components remain assignable to the public // neutral source type without importing runtime ability definitions. const _componentShapeSentinel: RomSourceComponent | undefined = named("Taunt").source?.components?.[0]; void _componentShapeSentinel;