forked from phenom/healer-man
65 lines
2.8 KiB
JavaScript
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);
|
|
}
|
|
});
|