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); } });