Files
healer-man/scripts/runewaker-pipeline/runewaker-loot-import.test.mjs
T
2026-09-08 10:40:26 -04:00

65 lines
2.8 KiB
JavaScript

import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { readFile, readdir } from "node:fs/promises";
import path from "node:path";
import test from "node:test";
import { fileURLToPath } from "node:url";
const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const reportFile = path.join(projectRoot, "scripts/runewaker-pipeline/RUNEWAKER_LOOT_IMPORT_REPORT.json");
const catalogFile = path.join(projectRoot, "src/game/generated/runewakerLootCatalog.json");
async function json(file) {
return JSON.parse(await readFile(file, "utf8"));
}
test("RuneWaker loot catalog covers every source-classified boss", async () => {
const [catalog, recipeNames] = await Promise.all([
json(catalogFile),
readdir(path.join(projectRoot, "scripts/runewaker-pipeline/recipes")),
]);
let expectedBosses = 0;
for (const name of recipeNames.filter((candidate) => candidate.endsWith("-population.json"))) {
const recipe = await json(path.join(projectRoot, "scripts/runewaker-pipeline/recipes", name));
expectedBosses += recipe.templates.filter((template) => template.classification === "boss").length;
}
assert.equal(catalog.bossCount, expectedBosses);
assert.equal(catalog.bosses.length, expectedBosses);
assert.equal(new Set(catalog.bosses.map((boss) => boss.bossKey)).size, expectedBosses);
});
test("source probabilities and fail-closed outcomes are internally valid", async () => {
const catalog = await json(catalogFile);
for (const boss of catalog.bosses) {
assert.ok(["portable", "partial", "no-source-drops", "blocked"].includes(boss.status));
for (const roll of boss.rolls) {
assert.ok(roll.chancePerKill > 0 && roll.chancePerKill <= 1);
const total = roll.outcomes.reduce((sum, outcome) => sum + outcome.conditionalChance, 0);
assert.ok(total <= 1.000000001, `${boss.bossKey} slot ${roll.sourceSlot} exceeds probability 1`);
for (const outcome of roll.outcomes) {
assert.ok(outcome.conditionalChance > 0 && outcome.conditionalChance <= 1);
if (outcome.runtimeStatus === "blocked") assert.ok(outcome.blockedReason);
else assert.ok(outcome.sourceCatalog);
}
}
}
});
test("the compact report agrees with the full catalog and pins every source hash", async () => {
const [catalog, report] = await Promise.all([json(catalogFile), json(reportFile)]);
for (const key of [
"bossCount",
"bossWithSourceRollsCount",
"portableBossCount",
"partialBossCount",
"noSourceDropsBossCount",
"blockedBossCount",
"executableOutcomeCount",
"blockedOutcomeCount",
]) assert.equal(report[key], catalog[key]);
for (const source of report.generatedFrom.sourceFiles) {
const bytes = await readFile(path.resolve(projectRoot, source.file));
assert.equal(createHash("sha256").update(bytes).digest("hex"), source.sha256);
}
});