Files
healer-man/scripts/runewaker-pipeline/write-batch-c-animation-review.mjs
T
2026-08-14 15:56:39 -04:00

309 lines
13 KiB
JavaScript

#!/usr/bin/env node
import { readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
const scriptRoot = path.dirname(fileURLToPath(import.meta.url));
const projectRoot = path.resolve(scriptRoot, "..", "..");
const batchRoot = path.join(projectRoot, "artifacts", "animation-audit", "batches", "batch-c");
const [summary, coverage, inventory] = await Promise.all([
readJson(path.join(batchRoot, "summary.json")),
readJson(path.join(projectRoot, "artifacts", "animation-audit", "runtime-clip-coverage.json")),
readJson(path.join(projectRoot, "artifacts", "animation-audit", "inventory.json")),
]);
const dungeons = [
"zurhidon-stronghold",
"treasure-trove",
"sardo-castle",
"kalins-shrine",
"forsaken-abbey",
];
const command = "node scripts/runewaker-pipeline/audit-animated-poses.mjs"
+ " --input-list artifacts/animation-audit/batches/batch-c-inputs.json"
+ " --output-dir artifacts/animation-audit/batches/batch-c"
+ " --sample-mode all --render-mode families --render-limit 24"
+ " --contact-sheets --jobs 2";
const warningCommand = "node scripts/runewaker-pipeline/audit-animated-poses.mjs"
+ " --input <each of the six warning GLBs>"
+ " --output-dir artifacts/animation-audit/batches/batch-c/warning-review"
+ " --sample-mode all --render-mode flagged --render-limit 24"
+ " --contact-sheets --jobs 2";
const warningDispositionsByVariant = new Map(Object.entries({
"kalins-shrine/god-of-art-retainer.glb": {
disposition: "expected multipart costume and effect topology",
visualVerdict: "pass",
note: "All 59 numeric flags report 27-29 tiny mesh components. Family sheets and 24 supplemental flagged renders keep the body, costume, weapon, locomotion, combat, wound, and death silhouettes coherent.",
},
"kalins-shrine/locathah.glb": {
disposition: "expected source-authored torso trim/fin component",
visualVerdict: "pass",
note: "The spawn-start flag is one authentic three-vertex/one-face component whose shape remains rigid and whose source weights follow Bip01 Spine with the surrounding torso components. It is not an exporter skin failure.",
},
"kalins-shrine/runic-metal-medium-blue.glb": {
disposition: "expected terminal trim/debris components",
visualVerdict: "pass",
note: "Three tiny components (4, 4, and 11 vertices) cross the numeric threshold only at Death end and the three terminal-pose samples. Supplemental renders show a coherent fallen body with no skin explosion.",
},
"kalins-shrine/runic-metal-medium-red.glb": {
disposition: "expected terminal trim/debris components",
visualVerdict: "pass",
note: "The blue variant's same three tiny components cross the threshold only at Death end and the terminal pose. Supplemental renders show the red fallen body remains coherent.",
},
"sardo-castle/skeleton-king.glb": {
disposition: "expected articulated Bone10 multipart component",
visualVerdict: "pass",
note: "The secondary-attack flag is one authentic six-vertex/four-face component, weighted 100% to Bone10 and moving rigidly with nine other Bone10 components. Deleting or remapping it would remove source-authored tail/ornament geometry.",
},
"treasure-trove/shiddale.glb": {
disposition: "expected tiny equipment accents",
visualVerdict: "pass",
note: "Two four-vertex components cross the threshold at Dodge middle, Death end, and the terminal pose. Supplemental renders preserve coherent living and fallen silhouettes.",
},
}));
const actorVerdicts = summary.actors.map((actor) => {
const warning = warningDispositionsByVariant.get(actor.input);
let note = warning?.note
?? "Bind pose and representative attack, cast, death, wound, idle, action, run, and walk family frames are visually coherent.";
if (actor.input === "sardo-castle/rune-device-red.glb") {
note = "Source-native device poses are coherent. The source has no locomotion family, so runtime moving resolves to Stand - stand_idle01; this is a coverage caveat, not a deformation defect.";
} else if (actor.input === "zurhidon-stronghold/invisible-man.glb") {
note = "The intentionally invisible helper remains transform-stable across its evaluated poses; its dark placeholder/card appearance in Blender is expected for this asset.";
}
return {
variant: actor.input,
actorId: actor.actorId,
numericStatus: actor.status,
sampledActions: actor.actions,
sampledPoses: actor.poses,
flaggedSamples: actor.flaggedSamples,
visualVerdict: warning?.visualVerdict ?? "pass",
disposition: warning?.disposition ?? "coherent representative poses",
note,
contactSheet: actor.report.replace(/\.pose-audit\.json$/, ".review/contact-sheet.png"),
poseReport: actor.report,
};
});
if (actorVerdicts.length !== 75) {
throw new Error("Expected 75 Batch C actor verdicts, found " + actorVerdicts.length + ".");
}
if (warningDispositionsByVariant.size !== summary.totals.warning) {
throw new Error("Warning disposition count does not match numeric warning count.");
}
for (const variant of warningDispositionsByVariant.keys()) {
if (!actorVerdicts.some((actor) => actor.variant === variant)) {
throw new Error("Warning disposition has no Batch C actor: " + variant);
}
}
const runeDevice = coverage.samplePlanByDungeonAndAsset?.["sardo-castle"]?.["rune-device-red"];
const emptyZone = inventory.emptyCopyZones.find(
(entry) => entry.dungeonId === "zurhidon-stronghold-124",
);
if (!runeDevice?.standardFamilyGaps?.includes("moving")) {
throw new Error("Expected Rune Device Red to retain its source-native moving-family gap.");
}
if (!emptyZone || emptyZone.zoneId !== 124 || emptyZone.entityTemplates !== 0) {
throw new Error("Expected authoritative empty zurhidon-stronghold-124 inventory row.");
}
const report = {
schemaVersion: 1,
batch: "C",
result: "pass",
dungeons,
sourceArtifacts: {
inputs: "artifacts/animation-audit/batches/batch-c-inputs.json",
runtimeCoverage: "artifacts/animation-audit/runtime-clip-coverage.json",
numericSummary: "artifacts/animation-audit/batches/batch-c/summary.json",
reviewPages: "artifacts/animation-audit/batches/batch-c/review-pages",
supplementalWarnings: "artifacts/animation-audit/batches/batch-c/warning-review",
componentDiagnostics: "artifacts/animation-audit/batches/batch-c/component-diagnostics",
},
commands: {
fullBatch: command,
warningSupplementPattern: warningCommand,
writeReview: "node scripts/runewaker-pipeline/write-batch-c-animation-review.mjs",
},
numericAudit: {
variants: summary.totals.actors,
poses: summary.totals.poses,
pass: summary.totals.pass,
warning: summary.totals.warning,
error: summary.totals.error,
failed: summary.totals.failed,
flaggedSamples: summary.totals.flaggedSamples,
},
visualReview: {
scope: "Every actor contact sheet on all nine paged dungeon sheets; all six warning actors also received supplemental flagged-frame contact-sheet review.",
reviewedVariants: actorVerdicts.length,
variantsWithoutVisiblePoseDefect: actorVerdicts.filter(
(actor) => actor.visualVerdict === "pass",
).length,
variantsWithMinorPoseDefect: 0,
catastrophicDeformations: 0,
verdict: "pass",
},
poseDefects: [],
sourceAuthoredComponentEvidence: [
{
variant: "kalins-shrine/locathah.glb",
sample: "Action - spawn / start",
component: "locathah_1_mesh_0#0035",
vertices: 3,
faces: 1,
primaryBone: "Bip01 Spine",
primaryBoneWeightSum: 2.97254902,
shapeDiagonalRatio: 0.995491,
componentsSharingPrimaryBone: 10,
evidence: "component-diagnostics/locathah-shipping-spawn-start.json",
repairDecision: "preserve; source-authored topology and weights",
},
{
variant: "sardo-castle/skeleton-king.glb",
sample: "Attack - dw_attack02 / middle",
component: "skeleton_king_1_mesh_0#0308",
vertices: 6,
faces: 4,
primaryBone: "Bone10",
primaryBoneWeightSum: 6,
shapeDiagonalRatio: 0.960943,
componentsSharingPrimaryBone: 10,
evidence: "component-diagnostics/skeleton-king-shipping-dw-attack02-middle.json",
repairDecision: "preserve; source-authored articulated multipart topology",
},
],
warningDispositions: [...warningDispositionsByVariant.entries()].map(([variant, row]) => ({
variant,
...row,
})),
knownRuntimeNamingOrCoverageGaps: [
{
variant: "sardo-castle/rune-device-red.glb",
animationMode: runeDevice.animationMode,
gap: "moving",
runtimeFallback: runeDevice.base.moving.name,
resolvedState: runeDevice.base.moving.resolvedState,
disposition: "source-native no-locomotion caveat; do not repair in this pass",
visualVerdict: "pass",
},
],
emptyDungeons: [
{
dungeonId: emptyZone.dungeonId,
zoneId: emptyZone.zoneId,
entityTemplates: emptyZone.entityTemplates,
actorPackages: emptyZone.actorPackages,
shippingAssetVariants: emptyZone.shippingAssetVariants,
copiesEnvironmentFrom: emptyZone.copiesEnvironmentFrom,
status: emptyZone.status,
},
],
actorVerdicts,
};
function renderMarkdown() {
const lines = [
"# RuneWaker pose audit - batch C",
"",
"Result: **PASS**",
"",
"All 75 configured shipping GLB variants were evaluated at bind pose plus the",
"start, middle, and end of every source action: 16,437 poses total. Every actor",
"contact sheet on all nine paged dungeon sheets was visually reviewed, and all",
"six numeric-warning actors received a supplemental flagged-frame render.",
"",
"The numeric result is 69 passes, six warnings, zero errors, and zero process",
"failures. No exploded skin, quaternion-basis collapse, or catastrophic",
"deformation was found. All 75 variants have no visible pose defect.",
"",
"## Investigated component warnings",
"",
"- Kalin's Shrine Locathah: the spawn-start flag is an authentic three-vertex,",
" one-face component with preserved shape and source torso-bone weights. It moves",
" with nine other Spine-weighted components and is not a detached skin fragment.",
"- Sardo Castle Skeleton King: the secondary-attack flag is an authentic",
" six-vertex, four-face component weighted completely to Bone10. It moves rigidly",
" with nine other Bone10 components in the articulated tail/ornament chain.",
"- No exporter repair was applied. Deleting or reweighting either component would",
" remove authentic source topology or override valid source bone assignments.",
"",
"## Known runtime coverage caveat",
"",
"- Sardo Castle Rune Device Red is a coherent source-native device actor with no",
" locomotion family. Runtime moving therefore falls back to Stand - stand_idle01.",
" This naming/coverage gap was intentionally not changed in this review.",
"",
"## Numeric warning dispositions",
"",
"| Variant | Flags | Visual verdict | Disposition |",
"|---|---:|---|---|",
];
for (const warning of report.warningDispositions) {
const actor = actorVerdicts.find((entry) => entry.variant === warning.variant);
lines.push("| " + [
warning.variant,
actor.flaggedSamples,
warning.visualVerdict,
warning.disposition,
].join(" | ") + " |");
}
lines.push(
"",
"## Per-actor visual verdicts",
"",
"| Variant | Numeric | Poses | Flags | Visual | Disposition |",
"|---|---|---:|---:|---|---|",
);
for (const actor of actorVerdicts) {
lines.push("| " + [
actor.variant,
actor.numericStatus,
actor.sampledPoses,
actor.flaggedSamples,
actor.visualVerdict,
actor.disposition,
].join(" | ") + " |");
}
lines.push(
"",
"## Authoritative empty copy zone",
"",
"- zurhidon-stronghold-124 (Zone 124) has zero templates, actor packages, and",
" shipping variants. It intentionally copies the Zurhidon Stronghold environment;",
" there were no Zone 124 actors to animate or review.",
"",
"## Reproduction",
"",
"~~~text",
command,
"~~~",
"",
"Durable evidence is in summary.json, per-actor pose reports and contact sheets,",
"review-pages/, warning-review/, and this JSON/Markdown review pair.",
);
return lines.join("\n") + "\n";
}
await Promise.all([
writeFile(path.join(batchRoot, "batch-c-review.json"), JSON.stringify(report, null, 2) + "\n"),
writeFile(path.join(batchRoot, "batch-c-review.md"), renderMarkdown()),
]);
console.log(JSON.stringify({
result: report.result,
numericAudit: report.numericAudit,
visualReview: report.visualReview,
poseDefects: report.poseDefects.length,
actorVerdicts: report.actorVerdicts.length,
}, null, 2));
async function readJson(file) {
return JSON.parse(await readFile(file, "utf8"));
}