Release Healer Man 0.1.21
This commit is contained in:
1 parent
699fc858c9
commit
f6f2375145
210 files changed
+976039
-6247
No files matched your search
@@ -0,0 +1,181 @@
|
||||
#!/usr/bin/env node
|
||||
import { readdir, readFile, stat, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { buildWdbPlacementPlan } from "./lib/environment-assembly.mjs";
|
||||
import { sourceRootFor } from "./lib/recipe.mjs";
|
||||
|
||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
const recipeDirectory = path.join(root, "scripts/runewaker-pipeline/recipes");
|
||||
const reportJsonFile = path.join(
|
||||
root,
|
||||
"scripts/runewaker-pipeline/RUNEWAKER_ENVIRONMENT_ASSEMBLY_REPORT.json",
|
||||
);
|
||||
const reportMarkdownFile = path.join(
|
||||
root,
|
||||
"scripts/runewaker-pipeline/RUNEWAKER_ENVIRONMENT_ASSEMBLY_REPORT.md",
|
||||
);
|
||||
const projectPath = (file) => path.relative(root, file).split(path.sep).join("/");
|
||||
|
||||
async function exists(file) {
|
||||
try {
|
||||
await stat(file);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function renderMarkdown(report) {
|
||||
const rows = report.dungeons.map((dungeon) => [
|
||||
"|", dungeon.id, "|", dungeon.mode, "|", dungeon.sourceDescriptors, "|",
|
||||
dungeon.uniqueResources, "|", dungeon.staticPlacements, "|",
|
||||
dungeon.dynamicPlacements, "|", dungeon.dynamicReviewCandidates, "|",
|
||||
dungeon.unsupportedPlacements, "|", dungeon.missingSourceResources, "|",
|
||||
].join(" "));
|
||||
return [
|
||||
"# RuneWaker WDB environment assembly audit",
|
||||
"",
|
||||
"Structural validation: **" + report.validationStatus + "**",
|
||||
"Source-behavior review: **" + report.reviewStatus + "**",
|
||||
"",
|
||||
"This deterministic audit reads the checked-in WDB reports and environment recipes. It does",
|
||||
"not convert or replace shipping GLBs. The builder now supports every valid ROS placement",
|
||||
"resource with normalized-path model caching validated by source hashes and the complete translation/quaternion/scale",
|
||||
"transform relative to the reviewed primary placement.",
|
||||
"",
|
||||
"## Coverage",
|
||||
"",
|
||||
"- " + report.summary.dungeons + " dungeon WDB packages and "
|
||||
+ report.summary.sourceDescriptors + " source descriptors.",
|
||||
"- " + report.summary.uniqueDungeonResources
|
||||
+ " per-dungeon unique ROS resources (resources reused across dungeons share cache keys).",
|
||||
"- " + report.summary.staticPlacements + " static placements and "
|
||||
+ report.summary.dynamicPlacements + " explicitly reviewed dynamic placements.",
|
||||
"- " + report.summary.dynamicReviewCandidates
|
||||
+ " filename-based dynamic candidates remain static and review-required; names never prove behavior.",
|
||||
"- " + report.summary.unsupportedPlacements + " unsupported placement records and "
|
||||
+ report.summary.missingSourceResources + " missing source ROS resources.",
|
||||
"",
|
||||
"| Dungeon | Mode | Descriptors | Resources | Static | Dynamic | Dynamic review | Unsupported | Missing ROS |",
|
||||
"| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |",
|
||||
...rows,
|
||||
"",
|
||||
"## Runtime-facing output",
|
||||
"",
|
||||
"A full build writes `wdb-placement-manifest.json` beside the environment package. Each",
|
||||
"placement has a stable WDB GUID ID, normalized resource/cache key, source TRS, primary-local",
|
||||
"Three.js matrix, bounds, flags, asset status, and optional reviewed dynamic definition.",
|
||||
"Static placements are assembled into the optimized visual and navigation collision. Reviewed",
|
||||
"dynamic placements are omitted from static collision and packaged as separately addressable GLBs.",
|
||||
"",
|
||||
"## Unsupported or review-required behavior",
|
||||
"",
|
||||
...report.unsupportedResourceTypes.map((item) => "- " + item),
|
||||
"- The primary-only top-down preview and editable `.blend` do not include the final merged WDB",
|
||||
" assembly; the generated GLB and placement manifest are authoritative.",
|
||||
"- Dynamic-candidate filenames are not enough to remove geometry from the static package. Add a",
|
||||
" reviewed `source.wdbAssembly.dynamicPlacements` rule with evidence first.",
|
||||
"",
|
||||
"## Validation issues",
|
||||
"",
|
||||
...(report.issues.length ? report.issues.map((item) => "- " + item) : ["- None."]),
|
||||
"",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
export async function buildEnvironmentAssemblyAudit() {
|
||||
const recipeNames = (await readdir(recipeDirectory))
|
||||
.filter((name) => name.endsWith(".json")
|
||||
&& !name.endsWith("-population.json")
|
||||
&& name !== "runewaker-player-avatars.json")
|
||||
.sort();
|
||||
const dungeons = [];
|
||||
const issues = [];
|
||||
for (const recipeName of recipeNames) {
|
||||
const recipe = JSON.parse(await readFile(path.join(recipeDirectory, recipeName), "utf8"));
|
||||
const wdbReportFile = path.join(
|
||||
root,
|
||||
"src/assets/game/dungeons",
|
||||
recipe.slug,
|
||||
"wdb-report.json",
|
||||
);
|
||||
if (!await exists(wdbReportFile)) {
|
||||
issues.push(recipe.slug + ": checked-in WDB report is missing.");
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const wdbReport = JSON.parse(await readFile(wdbReportFile, "utf8"));
|
||||
const plan = buildWdbPlacementPlan(wdbReport, recipe);
|
||||
const resourceRoot = path.join(sourceRootFor(recipe), recipe.source.resourceRoot);
|
||||
const missingResources = [];
|
||||
for (const resource of plan.resources) {
|
||||
const file = path.join(resourceRoot, ...resource.normalizedResource.split("\\"));
|
||||
if (!await exists(file)) missingResources.push(resource.resource);
|
||||
}
|
||||
dungeons.push({
|
||||
id: recipe.slug,
|
||||
mode: recipe.source.proceduralTerrainFallback
|
||||
? "primary-only-procedural-fallback"
|
||||
: recipe.source.wdbAssembly?.mode ?? "all-ros",
|
||||
recipe: projectPath(path.join(recipeDirectory, recipeName)),
|
||||
wdbReport: projectPath(wdbReportFile),
|
||||
sourceDescriptors: plan.sourceDescriptorCount,
|
||||
uniqueResources: plan.resources.length,
|
||||
staticPlacements: plan.staticPlacements.length,
|
||||
dynamicPlacements: plan.dynamicPlacements.length,
|
||||
dynamicReviewCandidates: plan.reviewRequiredPlacements.length,
|
||||
unsupportedPlacements: plan.unsupportedPlacements.length,
|
||||
missingSourceResources: missingResources.length,
|
||||
missingResources,
|
||||
unsupported: plan.unsupportedPlacements,
|
||||
});
|
||||
} catch (error) {
|
||||
issues.push(recipe.slug + ": " + error.message);
|
||||
}
|
||||
}
|
||||
dungeons.sort((left, right) => left.id.localeCompare(right.id));
|
||||
const sum = (field) => dungeons.reduce((total, dungeon) => total + dungeon[field], 0);
|
||||
const unsupportedResourceTypes = [
|
||||
"WDB placement resources other than ROS are recorded but not converted.",
|
||||
"Particle, light, sound, camera, and script-only components absent from ROS mesh descriptors remain unsupported.",
|
||||
"Animated/stateful behavior remains unsupported until an exact reviewed dynamic-placement rule supplies its kind, initial state, collision policy, and evidence.",
|
||||
"A ROS that the preserved exporter cannot decode is skipped, recorded with its resource path and failure, and never replaced with guessed geometry.",
|
||||
];
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
generatedBy: "scripts/runewaker-pipeline/audit-environment-assembly.mjs",
|
||||
deterministic: true,
|
||||
validationStatus: issues.length ? "blocked" : "green",
|
||||
reviewStatus: sum("dynamicReviewCandidates") || sum("unsupportedPlacements")
|
||||
|| sum("missingSourceResources") ? "review-required" : "green",
|
||||
summary: {
|
||||
dungeons: dungeons.length,
|
||||
sourceDescriptors: sum("sourceDescriptors"),
|
||||
uniqueDungeonResources: sum("uniqueResources"),
|
||||
staticPlacements: sum("staticPlacements"),
|
||||
dynamicPlacements: sum("dynamicPlacements"),
|
||||
dynamicReviewCandidates: sum("dynamicReviewCandidates"),
|
||||
unsupportedPlacements: sum("unsupportedPlacements"),
|
||||
missingSourceResources: sum("missingSourceResources"),
|
||||
},
|
||||
unsupportedResourceTypes,
|
||||
issues,
|
||||
dungeons,
|
||||
};
|
||||
}
|
||||
|
||||
const report = await buildEnvironmentAssemblyAudit();
|
||||
await Promise.all([
|
||||
writeFile(reportJsonFile, JSON.stringify(report, null, 2) + "\n", "utf8"),
|
||||
writeFile(reportMarkdownFile, renderMarkdown(report), "utf8"),
|
||||
]);
|
||||
console.log(JSON.stringify({
|
||||
validationStatus: report.validationStatus,
|
||||
reviewStatus: report.reviewStatus,
|
||||
reportJson: projectPath(reportJsonFile),
|
||||
reportMarkdown: projectPath(reportMarkdownFile),
|
||||
summary: report.summary,
|
||||
issues: report.issues,
|
||||
}, null, 2));
|
||||
if (report.issues.length) process.exitCode = 1;
|
||||
Reference in new issue
Block a user