Files
healer-man/src/game/romPlayerCatalog.test.ts
T
2026-08-20 14:05:50 -04:00

131 lines
6.8 KiB
TypeScript

import { describe, expect, it } from "vitest";
import generated from "./romPlayerCatalog.generated.json";
import audit from "../../scripts/runewaker-pipeline/snapshots/runewaker-player-skill-audit.json";
import {
abilitiesForCharacter,
abilityAtLevel,
defaultActionBarForCharacter,
} from "./abilityCatalog";
import { ROM_ALL_ABILITIES, ROM_ELITE_ABILITIES } from "./romAbilityCatalog";
import { romDualClassBaseStats, romNativeClassBaseStats } from "./romStats";
import { EMPTY_ITEM_STATS } from "./itemStats";
import { deriveCharacterStats } from "./wotlkStats";
const RACE_MATRIX = {
"rom-human": ["rom-warrior", "rom-scout", "rom-rogue", "rom-mage", "rom-priest", "rom-knight"],
"rom-elf": ["rom-warrior", "rom-scout", "rom-rogue", "rom-mage", "rom-warden", "rom-druid"],
"rom-dwarf": ["rom-warrior", "rom-rogue", "rom-mage", "rom-priest", "rom-warlock", "rom-champion"],
};
const ELITE_LEVELS = [15, 20, 25, 30, 35, 40, 45, 50, 60, 70];
describe("generated RuneWaker player catalog", () => {
it("imports the exact released roster and race eligibility without Duelist", () => {
expect(generated.canonicalClasses.map((entry) => entry.name)).toEqual([
"Warrior", "Scout", "Rogue", "Mage", "Priest", "Knight", "Warden", "Druid", "Warlock", "Champion",
]);
expect(generated.canonicalClasses.some((entry) => entry.name === "Duelist")).toBe(false);
expect(generated.raceMatrix).toEqual(RACE_MATRIX);
expect(generated.eliteLevels).toEqual(ELITE_LEVELS);
expect(audit.coverage).toMatchObject({
releasedClasses: 10,
duelistExcluded: true,
races: 3,
orderedPairs: 66,
eliteMilestones: 660,
ordinaryFamilies: 269,
iconResolved: 929,
uniqueNativeIcons: 918,
unresolvedVisible: 0,
});
expect(audit.hashes.interfaceFdb).toMatch(/^[a-f0-9]{64}$/);
});
it("resolves every ordered pair to all ten executable elite milestones", () => {
expect(ROM_ELITE_ABILITIES).toHaveLength(660);
const pairGroups = new Map<string, typeof ROM_ELITE_ABILITIES[number][]>();
for (const ability of ROM_ELITE_ABILITIES) {
const key = `${ability.classId}:${ability.secondaryClassId}`;
pairGroups.set(key, [...(pairGroups.get(key) ?? []), ability]);
}
expect(pairGroups.size).toBe(66);
for (const elites of pairGroups.values()) {
expect(elites.map((ability) => ability.unlockLevel)).toEqual(ELITE_LEVELS);
expect(elites.every((ability) => ability.source === "runewaker" && ability.effects.length > 0)).toBe(true);
}
expect(ROM_ALL_ABILITIES).toHaveLength(929);
expect(ROM_ALL_ABILITIES.every((ability) => (
/^\/assets\/ui\/rom-spells\/\d+\.png$/.test(ability.icon)
&& ability.icon === generated.skills.find((skill) => skill.id === ability.id)?.icon
))).toBe(true);
expect(generated.skills.every((ability) => (
ability.iconResolution === "native-interface-fdb"
&& /^\/assets\/ui\/rom-spells\/\d+\.png$/.test(ability.icon)
))).toBe(true);
});
it("composes only primary, secondary-general, and active-orientation elite skills", () => {
const warriorScout = abilitiesForCharacter("rom-warrior", "rom-scout");
const scoutWarrior = abilitiesForCharacter("rom-scout", "rom-warrior");
expect(warriorScout.some((ability) => ability.classId === "rom-scout" && ability.romSkillGroup === "primary")).toBe(false);
expect(warriorScout.some((ability) => ability.classId === "rom-scout" && ability.romSkillGroup === "general")).toBe(true);
expect(warriorScout.filter((ability) => ability.secondaryClassId)).toHaveLength(10);
expect(warriorScout.filter((ability) => ability.secondaryClassId).every((ability) => (
ability.classId === "rom-warrior" && ability.secondaryClassId === "rom-scout"
))).toBe(true);
expect(scoutWarrior.filter((ability) => ability.secondaryClassId).every((ability) => (
ability.classId === "rom-scout" && ability.secondaryClassId === "rom-warrior"
))).toBe(true);
expect(defaultActionBarForCharacter("rom-warrior", "rom-scout", 14).some((id) => id.includes("-elite-"))).toBe(false);
expect(defaultActionBarForCharacter("rom-warrior", "rom-scout", 15).filter((id) => id.includes("-elite-"))).toHaveLength(1);
});
it("selects the highest eligible source rank automatically", () => {
const ranked = ROM_ALL_ABILITIES.find((ability) => (ability.ranks?.length ?? 0) > 1)!;
const eligible = ranked.ranks!.filter((rank) => rank.level <= 80).at(-1)!;
expect(abilityAtLevel(ranked, 80).dbcSpellId).toBe(eligible.spellId);
});
it("preserves every source rank and its full flat or percentage cost", () => {
const normalizedCosts = (rows: readonly { resource: string; amount: number; percentage?: boolean }[]) => rows.map((cost) => ({
resource: cost.resource,
amount: cost.amount,
...(cost.percentage ? { percentage: true } : {}),
}));
const generatedRanks = generated.skills.reduce((sum, ability) => sum + ability.ranks.length, 0);
expect(generatedRanks).toBe(934);
expect(Math.max(...generated.skills.flatMap((ability) => (
ability.costs.filter((cost) => !cost.percentage).map((cost) => cost.amount)
)))).toBe(400);
for (const source of generated.skills) {
const runtime = ROM_ALL_ABILITIES.find((ability) => ability.id === source.id)!;
expect(runtime.costs, `${source.name} base costs`).toEqual(normalizedCosts(source.costs));
expect(runtime.ranks).toHaveLength(source.ranks.length);
for (const [index, sourceRank] of source.ranks.entries()) {
expect(runtime.ranks?.[index]?.spellId, `${source.name} rank ${sourceRank.rank}`).toBe(sourceRank.sourceSkillId);
expect(runtime.ranks?.[index]?.costs, `${source.name} rank ${sourceRank.rank} costs`).toEqual(normalizedCosts(sourceRank.costs));
}
}
});
it("adds exactly ten percent of the secondary class base stats", () => {
const primary = romNativeClassBaseStats("rom-warrior", 40);
const secondary = romNativeClassBaseStats("rom-scout", 40);
const combined = romDualClassBaseStats("rom-warrior", "rom-scout", 40)!;
for (const stat of Object.keys(primary) as (keyof typeof primary)[]) {
expect(combined[stat]).toBeCloseTo(primary[stat] + secondary[stat] * 0.1, 8);
}
});
it("gives level-one healers native mana pools and imports their real spell costs", () => {
expect(deriveCharacterStats("rom-priest", 1, EMPTY_ITEM_STATS, "rom-human").mana).toBe(103);
expect(deriveCharacterStats("rom-druid", 1, EMPTY_ITEM_STATS, "rom-elf").mana).toBe(86);
expect(generated.skills.find((ability) => ability.id === "rom-priest-general-1")?.costs).toEqual([
{ resource: "mana", amount: 12, percentage: false },
]);
expect(generated.skills.find((ability) => ability.id === "rom-druid-primary-0")?.costs).toEqual([
{ resource: "mana", amount: 12, percentage: false },
]);
});
});