97 lines
3.3 KiB
JavaScript
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,
|
|
};
|
|
}
|