113 lines
4.0 KiB
TypeScript
113 lines
4.0 KiB
TypeScript
import { readFileSync } from "node:fs";
|
|
import { describe, expect, it } from "vitest";
|
|
import { DUNGEON_DEFINITIONS } from "./dungeonRegistry";
|
|
|
|
interface CatalogRuntime {
|
|
readonly delivery: string;
|
|
readonly target: string;
|
|
readonly school: string;
|
|
readonly animation: string;
|
|
readonly range: number;
|
|
readonly cooldownMs: number;
|
|
readonly damageMultiplier: number;
|
|
}
|
|
|
|
interface CatalogSpell {
|
|
readonly spellId: number;
|
|
readonly name: string;
|
|
readonly mappingStatus: "damage-family" | "evidence-only";
|
|
readonly runtime?: CatalogRuntime;
|
|
}
|
|
|
|
interface ExecutableAttack {
|
|
readonly name: string;
|
|
readonly spellId?: number;
|
|
readonly delivery: string;
|
|
readonly target: string;
|
|
readonly school: string;
|
|
readonly animation: string;
|
|
readonly range: number;
|
|
readonly cooldownMs: number;
|
|
readonly damageMultiplier: number;
|
|
readonly runewakerFormula?: { readonly spellId: number; readonly runtimeBaselineMultiplier: number };
|
|
}
|
|
|
|
const catalog = JSON.parse(readFileSync(
|
|
"scripts/runewaker-pipeline/snapshots/runewaker-npc-spell-catalog.json",
|
|
"utf8",
|
|
)) as {
|
|
readonly status: string;
|
|
readonly spellCount: number;
|
|
readonly damageFamilyCount: number;
|
|
readonly evidenceOnlyCount: number;
|
|
readonly safety: {
|
|
readonly runtimeSqlDependency: boolean;
|
|
readonly unknownEffectsExecute: boolean;
|
|
readonly coefficientsAreSourceExact: boolean;
|
|
readonly pointValuedDamageUsesLabeledScaledAdapter: boolean;
|
|
readonly unavailableNativeInputsDefaulted: boolean;
|
|
};
|
|
readonly spells: readonly CatalogSpell[];
|
|
};
|
|
const gameplayReport = JSON.parse(readFileSync(
|
|
"scripts/runewaker-pipeline/RUNEWAKER_GAMEPLAY_CONTENT_REPORT.json",
|
|
"utf8",
|
|
)) as {
|
|
readonly summary: {
|
|
readonly sourceBackedDamageFamilyAttacks: number;
|
|
};
|
|
};
|
|
const spellsById = new Map(catalog.spells.map((spell) => [spell.spellId, spell]));
|
|
|
|
describe("portable RuneWaker source-spell mappings", () => {
|
|
it("keeps SQL and unsupported effects out of the runtime contract", () => {
|
|
expect(catalog.status).toBe("portable");
|
|
expect(catalog.spellCount).toBe(catalog.spells.length);
|
|
expect(catalog.damageFamilyCount + catalog.evidenceOnlyCount).toBe(catalog.spellCount);
|
|
expect(catalog.safety).toEqual({
|
|
runtimeSqlDependency: false,
|
|
unknownEffectsExecute: false,
|
|
coefficientsAreSourceExact: false,
|
|
pointValuedDamageUsesLabeledScaledAdapter: true,
|
|
unavailableNativeInputsDefaulted: false,
|
|
});
|
|
});
|
|
|
|
it("executes only positively classified direct-damage families", () => {
|
|
const runewaker = DUNGEON_DEFINITIONS.filter(
|
|
(definition) => definition.expansion === "runewaker",
|
|
);
|
|
let sourceBackedAttacks = 0;
|
|
|
|
for (const dungeon of runewaker) {
|
|
for (const entity of Object.values(dungeon.entities)) {
|
|
for (const attackValue of entity.combat?.attacks ?? []) {
|
|
const attack = attackValue as ExecutableAttack;
|
|
if (!attack.spellId) continue;
|
|
sourceBackedAttacks += 1;
|
|
const source = spellsById.get(attack.spellId);
|
|
expect(source, dungeon.id + ":" + entity.id + ":" + attack.spellId).toBeDefined();
|
|
expect(source?.mappingStatus).toBe("damage-family");
|
|
expect(source?.runtime).toBeDefined();
|
|
expect(attack).toMatchObject({
|
|
name: source?.name,
|
|
school: source?.runtime?.school,
|
|
animation: source?.runtime?.animation,
|
|
cooldownMs: source?.runtime?.cooldownMs,
|
|
});
|
|
expect(attack.damageMultiplier).toBeGreaterThan(0);
|
|
expect(attack.runewakerFormula).toMatchObject({ spellId: attack.spellId });
|
|
expect(["melee", "projectile", "area"]).toContain(attack.delivery);
|
|
expect(["primary", "nearby-party", "random-party"]).toContain(attack.target);
|
|
expect(attack.range).toBeGreaterThanOrEqual(source?.runtime?.range ?? 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
expect(sourceBackedAttacks).toBeGreaterThan(0);
|
|
expect(sourceBackedAttacks).toBe(
|
|
gameplayReport.summary.sourceBackedDamageFamilyAttacks,
|
|
);
|
|
});
|
|
});
|