import { readFileSync } from "node:fs"; import { describe, expect, it } from "vitest"; import { DUNGEON_DEFINITIONS } from "./dungeonRegistry"; import { normalizeEnemyAttacks } from "./enemyAttacks"; const catalog = JSON.parse(readFileSync( "dungeon-pipeline/boss-mechanics.json", "utf8", )) as { readonly bosses: Readonly>; }; describe("modular RuneWaker boss mechanics", () => { const dungeons = DUNGEON_DEFINITIONS.filter( (definition) => definition.expansion === "runewaker", ); const bosses = dungeons.flatMap((dungeon) => ( Object.values(dungeon.entities) .filter((entity) => entity.kind === "boss") .map((entity) => ({ dungeon, entity })) )); it("gives every packaged boss a catalog-backed special-mechanic loadout", () => { expect(bosses).toHaveLength(62); for (const { dungeon, entity } of bosses) { const label = `${dungeon.id}:${entity.id}`; const authored = catalog.bosses[entity.id]; expect(authored, label).toBeDefined(); expect(authored.mechanics.length, label).toBeGreaterThan(0); expect(entity.combat?.mechanicIds, label).toEqual( authored.mechanics.map((mechanic) => mechanic.id), ); expect(entity.combat?.attacks[0]?.id, label).toBe("basic-attack"); expect(entity.combat?.attacks.length, label).toBeGreaterThan(1); } }); it("normalizes every harmful spatial mechanic with matching warning geometry", () => { let telegraphed = 0; let healthTriggered = 0; for (const { dungeon, entity } of bosses) { const label = `${dungeon.id}:${entity.id}`; const attacks = normalizeEnemyAttacks(entity.combat?.attacks); for (const attack of attacks.slice(1)) { if (attack.condition?.kind === "self-health-percent") healthTriggered += 1; if (attack.telegraph) { telegraphed += 1; expect(attack.castTimeMs, label).toBeGreaterThanOrEqual(attack.telegraph.warningMs); if (attack.telegraph.shape === "circle") { expect(attack.telegraph.radius, label).toBeGreaterThan(0); } else { expect(attack.telegraph.length, label).toBeGreaterThan(0); } } } } expect(telegraphed).toBeGreaterThanOrEqual(100); expect(healthTriggered).toBeGreaterThanOrEqual(25); }); it("keeps source-gap fallbacks explicit instead of presenting them as recovered mechanics", () => { const sourceGaps = bosses.filter(({ entity }) => ( catalog.bosses[entity.id]?.source?.kind === "healer-man-source-gap" )); expect(sourceGaps.map(({ entity }) => entity.id).sort()).toEqual([ "rw-fa-100070", "rw-fa-100561", "rw-fa-100562", "rw-fa-100563", "rw-grafu-castle-105351", ]); }); });