#!/usr/bin/env node /** Compile deterministic human-review JSON/Markdown from pose-audit reports. */ import { readFile, writeFile } from "node:fs/promises"; import path from "node:path"; import process from "node:process"; function slash(value) { return value.replaceAll("\\", "/"); } function countBy(values, selector) { const counts = new Map(); for (const value of values) { const key = selector(value); counts.set(key, (counts.get(key) ?? 0) + 1); } return Object.fromEntries([...counts].sort(([left], [right]) => left.localeCompare(right))); } function markdownCell(value) { return String(value).replaceAll("|", "\\|").replaceAll("\n", " "); } async function main() { const [batchRootValue, reviewInputValue, outputJsonValue, outputMarkdownValue] = process.argv.slice(2); if (!outputMarkdownValue) { throw new Error( "Usage: compile-pose-audit-review.mjs " + " ", ); } const batchRoot = path.resolve(batchRootValue); const reviewInput = JSON.parse(await readFile(path.resolve(reviewInputValue), "utf8")); const summary = JSON.parse(await readFile(path.join(batchRoot, "summary.json"), "utf8")); const expected = summary.actors.map((actor) => actor.input).sort(); const reviewed = [...new Set(reviewInput.reviewedVariants)].sort(); if (JSON.stringify(expected) !== JSON.stringify(reviewed)) { const missing = expected.filter((variant) => !reviewed.includes(variant)); const extra = reviewed.filter((variant) => !expected.includes(variant)); throw new Error(`Visual review inventory mismatch. Missing=${missing.join(",")} Extra=${extra.join(",")}`); } const actors = []; const warningSamples = []; const assetWarnings = []; for (const summaryActor of [...summary.actors].sort((left, right) => left.input.localeCompare(right.input))) { const reportFile = path.join(batchRoot, summaryActor.report); const report = JSON.parse(await readFile(reportFile, "utf8")); const override = reviewInput.notes[summaryActor.input]; const visualVerdict = override?.verdict ?? (summaryActor.status === "pass" ? "visually-coherent" : "visually-coherent-with-numeric-warning"); const visualNote = override?.note ?? "Bind and every rendered semantic-family pose preserve a coherent silhouette, joint layout, " + "and attached equipment; no visible quaternion-basis deformation."; const actionWarnings = []; for (const sample of report.samples) { if (!sample.issues.length) continue; const row = { variantId: summaryActor.input, action: sample.action, semanticFamily: sample.semanticFamily, frameLabel: sample.frameLabel, frame: sample.frame, issues: sample.issues, }; actionWarnings.push(row); warningSamples.push(row); } for (const issue of report.assetIssues ?? []) { assetWarnings.push({ variantId: summaryActor.input, ...issue }); } const reviewDirectory = summaryActor.report.replace(/\.pose-audit\.json$/i, ".review"); actors.push({ dungeonId: summaryActor.input.split("/")[0], variantId: summaryActor.input, numericStatus: summaryActor.status, actions: summaryActor.actions, evaluatedPoses: summaryActor.poses, flaggedSamples: summaryActor.flaggedSamples, warningCodes: countBy( actionWarnings.flatMap((sample) => sample.issues), (issue) => issue.code, ), warningActions: countBy(actionWarnings, (sample) => sample.action), assetIssues: report.assetIssues ?? [], visualVerdict, visualNote, report: slash(path.relative(batchRoot, reportFile)), contactSheet: slash(path.join(reviewDirectory, "contact-sheet.png")), }); } const dungeonIds = [ ...new Set([...actors.map((actor) => actor.dungeonId), ...reviewInput.explicitEmptyDungeons]), ].sort(); const dungeons = dungeonIds.map((dungeonId) => { const rows = actors.filter((actor) => actor.dungeonId === dungeonId); return { dungeonId, configuredVariants: rows.length, numericPass: rows.filter((actor) => actor.numericStatus === "pass").length, numericWarning: rows.filter((actor) => actor.numericStatus === "warning").length, numericError: rows.filter((actor) => actor.numericStatus === "error").length, failed: rows.filter((actor) => actor.numericStatus === "failed").length, actions: rows.reduce((sum, actor) => sum + actor.actions, 0), evaluatedPoses: rows.reduce((sum, actor) => sum + actor.evaluatedPoses, 0), flaggedSamples: rows.reduce((sum, actor) => sum + actor.flaggedSamples, 0), visualAccepted: rows.filter((actor) => !actor.visualVerdict.startsWith("defect")).length, confirmedDefects: rows.filter((actor) => actor.visualVerdict.startsWith("defect")).length, emptyReason: rows.length === 0 ? reviewInput.emptyReasons?.[dungeonId] ?? "No configured runtime variants." : null, }; }); const result = { schemaVersion: 1, batch: reviewInput.batch, reviewedOn: reviewInput.reviewedOn, authority: { runtimeCoverage: reviewInput.runtimeCoverage, numericSummary: "summary.json", method: reviewInput.method, command: reviewInput.command, }, totals: { configuredVariants: actors.length, contactSheetsReviewed: reviewed.length, numericPassActors: actors.filter((actor) => actor.numericStatus === "pass").length, numericWarningActors: actors.filter((actor) => actor.numericStatus === "warning").length, numericErrorActors: actors.filter((actor) => actor.numericStatus === "error").length, failedActors: actors.filter((actor) => actor.numericStatus === "failed").length, sampledActions: actors.reduce((sum, actor) => sum + actor.actions, 0), evaluatedPoses: actors.reduce((sum, actor) => sum + actor.evaluatedPoses, 0), flaggedSamples: actors.reduce((sum, actor) => sum + actor.flaggedSamples, 0), sampleWarnings: warningSamples.length, assetWarnings: assetWarnings.length, visualAccepted: actors.filter((actor) => !actor.visualVerdict.startsWith("defect")).length, confirmedDefects: actors.filter((actor) => actor.visualVerdict.startsWith("defect")).length, }, disposition: reviewInput.disposition, dungeons, warningActors: actors.filter( (actor) => actor.numericStatus !== "pass" || actor.assetIssues.length > 0, ), warningSamples, assetWarnings, errors: actors.filter((actor) => ["error", "failed"].includes(actor.numericStatus)), actors, limitations: reviewInput.limitations, }; await writeFile(path.resolve(outputJsonValue), `${JSON.stringify(result, null, 2)}\n`, "utf8"); const lines = [ `# RuneWaker animation pose audit - ${reviewInput.batch}`, "", reviewInput.disposition, "", `- Configured shipping variants: ${result.totals.configuredVariants}`, `- Contact sheets reviewed: ${result.totals.contactSheetsReviewed}`, `- Evaluated poses: ${result.totals.evaluatedPoses} across ${result.totals.sampledActions} actions`, `- Numeric actor results: ${result.totals.numericPassActors} pass, ${result.totals.numericWarningActors} warning, ${result.totals.numericErrorActors} error, ${result.totals.failedActors} failed`, `- Numeric issue events: ${result.totals.sampleWarnings} sampled-pose warnings and ${result.totals.assetWarnings} asset warning`, `- Visual results: ${result.totals.visualAccepted} accepted, ${result.totals.confirmedDefects} confirmed defects`, "", "## Dungeon totals", "", "| Dungeon | Variants | Pass | Warning | Error/failed | Actions | Poses | Flagged | Visual accepted |", "| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |", ...dungeons.map( (dungeon) => `| ${dungeon.dungeonId} | ${dungeon.configuredVariants} | ${dungeon.numericPass} | ${dungeon.numericWarning} | ${dungeon.numericError + dungeon.failed} | ${dungeon.actions} | ${dungeon.evaluatedPoses} | ${dungeon.flaggedSamples} | ${dungeon.visualAccepted} |`, ), "", "## Warning disposition", "", "Every numeric warning is retained in `batch-a-review.json` down to action, frame label, frame value, and issue payload.", "", "| Variant | Numeric warnings | Affected actions | Visual verdict | Disposition |", "| --- | ---: | --- | --- | --- |", ...result.warningActors.map((actor) => { const warningCount = Object.values(actor.warningCodes).reduce((sum, count) => sum + count, 0); const actions = Object.entries(actor.warningActions) .map(([name, count]) => `${name} (${count})`) .join(", "); return `| ${actor.variantId} | ${warningCount + actor.assetIssues.length} | ${markdownCell(actions || actor.assetIssues.map((issue) => issue.code).join(", "))} | ${actor.visualVerdict} | ${markdownCell(actor.visualNote)} |`; }), "", "## Per-actor visual verdicts", ]; for (const dungeon of dungeons) { lines.push("", `### ${dungeon.dungeonId}`, ""); if (dungeon.configuredVariants === 0) { lines.push(dungeon.emptyReason, ""); continue; } lines.push( "| Variant | Numeric | Actions/poses | Visual verdict | Review note | Evidence |", "| --- | --- | ---: | --- | --- | --- |", ); for (const actor of actors.filter((row) => row.dungeonId === dungeon.dungeonId)) { lines.push( `| ${actor.variantId.split("/").at(-1)} | ${actor.numericStatus} | ${actor.actions}/${actor.evaluatedPoses} | ${actor.visualVerdict} | ${markdownCell(actor.visualNote)} | [sheet](${actor.contactSheet}) / [metrics](${actor.report}) |`, ); } } lines.push( "", "## Command", "", "```powershell", reviewInput.command, "```", "", "## Limits", "", ...reviewInput.limitations.map((limitation) => `- ${limitation}`), "", ); await writeFile(path.resolve(outputMarkdownValue), lines.join("\n"), "utf8"); } main().catch((error) => { console.error(error.stack ?? error.message); process.exitCode = 1; });