Files
healer-man/scripts/runewaker-pipeline/lib/wdb.mjs
T
2026-08-14 15:56:39 -04:00

97 lines
3.3 KiB
JavaScript

import { spawn } from "node:child_process";
import { mkdir, readFile } from "node:fs/promises";
import path from "node:path";
import { pipelineDirectory, projectRoot } from "./recipe.mjs";
export const nativeExporter = path.join(
pipelineDirectory,
"native",
"bin",
"runewaker_model_exporter.exe",
);
export function run(executable, argumentsList, label) {
return new Promise((resolve, reject) => {
console.log("\n[runewaker] " + label);
const child = spawn(executable, argumentsList, {
cwd: projectRoot,
stdio: "inherit",
windowsHide: true,
});
child.once("error", reject);
child.once("exit", (code) => {
if (code === 0) resolve();
else reject(new Error(label + " exited with code " + code + "."));
});
});
}
export function rankEnvironmentCandidates(report) {
const byResource = new Map();
for (const descriptor of report.descriptors ?? []) {
const resource = String(descriptor.resource ?? "");
if (!resource.toLowerCase().endsWith(".ros")) continue;
const key = resource.replaceAll("/", "\\").toLowerCase();
const row = byResource.get(key) ?? {
resource,
placements: 0,
maximumLocalRadius: 0,
maximumWorldRadius: 0,
descriptor: null,
};
row.placements += 1;
const localRadius = Number(descriptor.localBounds?.radius ?? 0);
const worldRadius = Number(descriptor.worldBounds?.radius ?? 0);
if (localRadius > row.maximumLocalRadius) {
row.maximumLocalRadius = localRadius;
row.descriptor = descriptor;
}
row.maximumWorldRadius = Math.max(row.maximumWorldRadius, worldRadius);
byResource.set(key, row);
}
return [...byResource.values()]
.map((row) => ({
...row,
isDungeonModel: /^model[\\/]dungeon[\\/]/i.test(row.resource),
score: Number((
Math.log10(Math.max(1, row.maximumLocalRadius)) * 10
+ (/^model[\\/]dungeon[\\/]/i.test(row.resource) ? 40 : 0)
- Math.log10(Math.max(1, row.placements))
).toFixed(6)),
}))
.sort((left, right) => (
right.score - left.score
|| right.maximumLocalRadius - left.maximumLocalRadius
|| left.resource.localeCompare(right.resource)
));
}
export async function inspectWdb({ wdbFile, reportFile }) {
await mkdir(path.dirname(reportFile), { recursive: true });
await run(
nativeExporter,
["--wdb-report", wdbFile, reportFile],
"inspect " + path.basename(wdbFile) + " with the preserved object factory",
);
const report = JSON.parse(await readFile(reportFile, "utf8"));
const candidates = rankEnvironmentCandidates(report);
const first = candidates[0] ?? null;
const second = candidates[1] ?? null;
const dominance = first && second
? first.maximumLocalRadius / Math.max(1, second.maximumLocalRadius)
: first ? Number.POSITIVE_INFINITY : 0;
return {
report,
candidates,
recommendation: first ? {
resource: first.resource.replaceAll("\\", "/"),
placement: first.descriptor,
dominance: Number.isFinite(dominance) ? Number(dominance.toFixed(4)) : null,
confidence: first.isDungeonModel && dominance >= 3 ? "high" : "review-required",
reason: first.isDungeonModel && dominance >= 3
? "Largest model/dungeon ROS dominates the next candidate by at least 3x."
: "Primary environment selection is ambiguous and requires visual review.",
} : null,
};
}