Files
healer-man/src/game/forsakenAbbeyPopulation.test.ts
T
2026-08-14 15:56:39 -04:00

102 lines
4.3 KiB
TypeScript

import { existsSync, readFileSync } from "node:fs";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { FORSAKEN_ABBEY_DEFINITION } from "./dungeons/forsakenAbbey";
import {
FORSAKEN_ABBEY_BOSSES,
FORSAKEN_ABBEY_ENTITIES,
FORSAKEN_ABBEY_POPULATION_PROVENANCE,
FORSAKEN_ABBEY_STATIC_SPAWNS,
} from "./generated/forsakenAbbeyPopulation.generated";
const projectFile = (...parts: string[]) => path.join(process.cwd(), ...parts);
const snapshot = JSON.parse(readFileSync(projectFile(
"scripts",
"runewaker-pipeline",
"snapshots",
"forsaken-abbey-zone-102.json",
), "utf8"));
const report = JSON.parse(readFileSync(projectFile(
"src",
"assets",
"game",
"dungeons",
"forsaken-abbey",
"population-import-report.json",
), "utf8"));
describe("Forsaken Abbey portable RuneWaker population", () => {
it("accounts for every active Zone 102 source row reproducibly", () => {
expect(snapshot.rowCount).toBe(251);
expect(snapshot.rows).toHaveLength(251);
expect(new Set(snapshot.rows.map((row: { spawnId: number }) => row.spawnId)).size).toBe(251);
expect(snapshot.contentSha256)
.toBe("13ef6edd3df6e71389aa92d01201889148c0794cf9ecaafdbaf90d3145d2bf7b");
const classifications = snapshot.rows.reduce(
(counts: Record<string, number>, row: { classification: string }) => {
counts[row.classification] = (counts[row.classification] ?? 0) + 1;
return counts;
},
{},
);
expect(classifications).toEqual({
combat: 142,
boss: 8,
"deferred-object": 101,
});
});
it("generates unique static combat spawns without AzerothCore id collisions", () => {
expect(FORSAKEN_ABBEY_STATIC_SPAWNS).toHaveLength(150);
expect(new Set(FORSAKEN_ABBEY_STATIC_SPAWNS.map((spawn) => spawn.id)).size).toBe(150);
expect(Object.keys(FORSAKEN_ABBEY_ENTITIES)).toHaveLength(15);
for (const entity of Object.values(FORSAKEN_ABBEY_ENTITIES)) {
expect(entity.id).toMatch(/^rw-fa-[0-9]+$/);
expect(entity.combat).not.toHaveProperty("serverEntry");
expect(entity.visual.model.url.startsWith("/assets/creatures/forsaken-abbey/")).toBe(true);
expect(entity.visual.model.url.endsWith(".glb")).toBe(true);
expect(entity.visual.model.animationMode).toBe("native");
expect(existsSync(projectFile("public", entity.visual.model.url.slice(1)))).toBe(true);
}
for (const spawn of FORSAKEN_ABBEY_STATIC_SPAWNS) {
expect(spawn.id).toMatch(/^rw-fa-spawn-[0-9]+$/);
expect(spawn.position.every(Number.isFinite)).toBe(true);
expect(Number.isFinite(spawn.yaw)).toBe(true);
}
});
it("classifies the authoritative five boss templates into eight objectives", () => {
const bossTemplateIds = [...new Set<number>(snapshot.rows
.filter((row: { classification: string }) => row.classification === "boss")
.map((row: { templateId: number }) => row.templateId))]
.sort((a, b) => a - b);
expect(bossTemplateIds).toEqual([100070, 100074, 100561, 100562, 100563]);
expect(FORSAKEN_ABBEY_BOSSES).toHaveLength(8);
expect(FORSAKEN_ABBEY_BOSSES.filter((boss) => boss.name.startsWith("Necromage Keeper")))
.toHaveLength(4);
for (const boss of FORSAKEN_ABBEY_BOSSES) {
const spawn = FORSAKEN_ABBEY_STATIC_SPAWNS.find((candidate) => candidate.id === boss.id);
expect(spawn).toBeDefined();
expect(FORSAKEN_ABBEY_ENTITIES[spawn!.entityId].kind).toBe("boss");
}
});
it("projects every source row and keeps all combat spawns within two meters", () => {
expect(report.records).toHaveLength(251);
expect(report.excluded).toHaveLength(101);
expect(FORSAKEN_ABBEY_POPULATION_PROVENANCE.navmeshProjection.maximumCombatDistance)
.toBeLessThanOrEqual(2);
expect(report.records.every((row: { projectedPosition: number[] }) => (
row.projectedPosition.length === 3 && row.projectedPosition.every(Number.isFinite)
))).toBe(true);
});
it("is the packaged population used by the playable dungeon definition", () => {
expect(FORSAKEN_ABBEY_DEFINITION.availability).toBe("available");
expect(FORSAKEN_ABBEY_DEFINITION.entities).toBe(FORSAKEN_ABBEY_ENTITIES);
expect(FORSAKEN_ABBEY_DEFINITION.staticSpawns).toBe(FORSAKEN_ABBEY_STATIC_SPAWNS);
expect(FORSAKEN_ABBEY_DEFINITION.bosses).toBe(FORSAKEN_ABBEY_BOSSES);
});
});