Files
healer-man/scripts/runewaker-pipeline/POSE_AUDIT.md
T
2026-08-14 15:56:39 -04:00

11 KiB

RuneWaker evaluated-pose audit

audit-animated-poses.mjs checks the actual evaluated, skinned vertices of the RuneWaker actors shipped by HealerMan. It is intentionally separate from the clip-name/readiness audit: an animation can have the right name and duration while applying bone rotations in the wrong coordinate basis.

What is audited

The batch runner can discover packaged GLBs under public/assets/creatures, but the authoritative full-game input is the 322 runtime-referenced shipping paths in artifacts/animation-audit/runtime-clip-coverage.json. Discovery can include unused or non-combat assets and must not be substituted for that exact list in a release verdict. Each Meshopt shipping GLB is copied through glTF Transform to a temporary, uncompressed GLB because Blender does not import EXT_meshopt_compression. Blender then evaluates:

  • the armature rest/bind pose;
  • the first, middle, and final frame of every action by default;
  • finite and non-finite skinned vertices;
  • posed AABB, axis extents, diagonal, centroid, maximum radius, and RMS radius;
  • bind-relative size, radius, and centroid-shift ratios;
  • connected-topology component movement, resizing, and separation from the bind envelope.

The temporary decompressed file is removed after each actor. The report hashes and identifies the original packaged GLB, not the temporary copy.

Commands

Run from D:\Projects\HealerMan. First regenerate the inventory and runtime selector plan from the same shipping build that will be pose-audited:

npm run runewaker:audit:animation-inventory
npm run runewaker:audit:runtime-clips

The package aliases pass additional CLI arguments through to the batch runner and regenerate the deterministic all-dungeon report:

npm run runewaker:audit:pose-batch -- --input-list <inputs.json> --jobs 2
npm run runewaker:audit:pose-master

The expected mapping is 436 entity templates to 268 base actor configurations to 322 runtime-referenced shipping variants. Do not continue if the inventory contract fails. Create the exact input list from the runtime plan, assert its size, and run the complete numeric audit plus semantic-family render pass:

$projectRoot = (Resolve-Path '.').Path
$coverageFile = 'artifacts/animation-audit/runtime-clip-coverage.json'
$coverage = Get-Content -Raw -LiteralPath $coverageFile | ConvertFrom-Json
$inputs = @(
  foreach ($dungeon in $coverage.samplePlanByDungeonAndAsset.PSObject.Properties) {
    foreach ($asset in $dungeon.Value.PSObject.Properties) {
      (Join-Path $projectRoot $asset.Value.glb).Replace('\', '/')
    }
  }
) | Sort-Object -Unique
if ($inputs.Count -ne 322) {
  throw "Expected 322 runtime shipping variants; found $($inputs.Count)."
}
$inputList = Join-Path $projectRoot 'artifacts/animation-audit/v5-pose-inputs.json'
$inputJson = @{ inputs = @($inputs) } | ConvertTo-Json -Depth 3
[System.IO.File]::WriteAllText(
  $inputList,
  $inputJson + [Environment]::NewLine,
  [System.Text.UTF8Encoding]::new($false)
)
node scripts/runewaker-pipeline/audit-animated-poses.mjs `
  --root public/assets/creatures `
  --input-list $inputList `
  --output-dir artifacts/animation-audit/batches/v5-full `
  --summary artifacts/animation-audit/batches/v5-full/summary.json `
  --jobs 2 `
  --sample-mode all `
  --render-mode families `
  --render-limit 24 `
  --contact-sheets
node scripts/runewaker-pipeline/make-pose-audit-review-pages.mjs `
  artifacts/animation-audit/batches/v5-full

--sample-mode all numerically evaluates first, middle, and final samples from every action. --render-mode families renders a bounded representative semantic-family set for every shipping variant; it does not reduce the numeric action plan. Preserve the runtime coverage JSON, input list, pose reports, contact sheets, and review pages together. Their hashes identify the exact shipping build under review.

Running discovery without an input list remains useful as a broader diagnostic, but its count is not the runtime contract:

node scripts/runewaker-pipeline/audit-animated-poses.mjs --jobs 2

Audit one dungeon and keep a deterministic report for each packaged variant:

node scripts/runewaker-pipeline/audit-animated-poses.mjs `
  --include '^paspers-shrine/' `
  --jobs 2 `
  --output-dir runewaker-export-work/pose-audit-paspers

Render one representative middle frame per detected semantic family and make a contact sheet:

node scripts/runewaker-pipeline/audit-animated-poses.mjs `
  --input public/assets/creatures/paspers-shrine/fog-ferocity-03.glb `
  --render-mode families `
  --contact-sheets

Use --render-mode flagged to render only numeric outliers, or --render-mode all for every sampled frame. --render-limit bounds disk and rendering cost. Reports can be reused only when the shipping SHA-256 still matches by adding --resume.

An inventory job can supply the exact runtime-used variant set as a JSON array or an object containing an inputs array:

{
  "inputs": [
    "D:/Projects/HealerMan/public/assets/creatures/paspers-shrine/fog-ferocity-03.glb",
    { "file": "D:/Projects/HealerMan/public/assets/creatures/paspers-shrine/fog-loster-03.glb" }
  ]
}

Pass it with --input-list inventory.json. Relative paths are resolved from the inventory file's directory. Input and Blender paths are converted to absolute paths before subprocesses start, so Blender changing its working directory cannot redirect a source or report.

Required human visual review

Numeric status is triage, not acceptance. A wrong quaternion basis can keep all values finite and leave the GLB Khronos-clean. A reviewer must inspect the contact sheet for every one of the 322 variant paths, including numeric passes, and compare warnings with their per-sample metrics. Any folded or inverted limb, exploded skin, detached equipment, discontinuity, or ambiguous extreme pose must be rerun with --render-mode all and, when necessary, compared with the original-client animation.

After actual inspection, create a review input using batch-a-visual-review-input.json as the schema reference. Its reviewedVariants must exactly match summary.json; include both demon-stronghold-125 and zurhidon-stronghold-124 in explicitEmptyDungeons, and record a reason for each. The compiler verifies inventory equality but cannot perform or infer the visual review:

node scripts/runewaker-pipeline/compile-pose-audit-review.mjs `
  artifacts/animation-audit/batches/v5-full `
  artifacts/animation-audit/batches/v5-full/v5-visual-review-input.json `
  artifacts/animation-audit/batches/v5-full/v5-review.json `
  artifacts/animation-audit/batches/v5-full/v5-review.md

Completed v5 disposition

artifacts/animation-audit/post-v5/summary.json contains all 322 runtime paths and 44,305 evaluated poses: 282 actors passed numerically, 40 are accepted-warning candidates, and there were zero errors and zero Blender/decompression/report failures. The 634 flagged samples exactly match the totals from the visually reviewed v4 batches; no new numeric deformation signature appeared after the v5 cast-semantics rebuild. Mantarick accounts for the expected static-pose warning, and Sardo's red Rune Warning Device retains its intentional native no-locomotion exception.

The rebuilt inventory is green at 30/436/268/322 with zero blockers, and the runtime selector reports zero semantic near misses. The packaged/offline browser showed all 30 dungeons Ready and loaded both Pasper's Shrine and Hall of Survivors with no animation or WebGL errors. Only an unrelated library deprecation warning appeared. These results complete the v5 pose disposition; the sampling and original-client comparison limits below still apply.

All 19 focused RuneWaker animation tests pass. The unfinished RoM class catalog and missing Manastorm fixtures remain unrelated repo-wide build/test blockers.

Report contract

Every <actor>.pose-audit.json is timestamp-free and deterministically ordered. variantId is the stable packaged path below the selected discovery root, so actors with the same basename in different dungeons remain distinct. Its sampling.actionPlan is the machine-readable audit plan: action index, exact action name, semantic family, action frame range, and every frame label and value selected for evaluation. samples records the resulting metrics, bind comparisons, and issue codes. summary.status is one of pass, warning, or error.

The batch summary.json records one row per packaged input path. Numeric warnings do not fail the command because unusual animations can intentionally detach weapons or body parts. A Blender/decompression/report failure does fail the command.

The documented Mantarick exception (Hall of Survivors template 102452, asset judgement-light-pose) contains static posed meshes without an armature. It is still checked for finite geometry and bounds, and is reported as static-pose-only with a pose-only-no-armature warning rather than failing the batch. It is the only approved pose-only runtime variant.

Sardo Castle template 105758 (rune-device-red, Rune Warning Device) is a native stationary device, not pose-only. Its source animation set intentionally has no locomotion. A missing-moving warning is acceptable only for this asset; its idle, configured combat, wound, and death actions still require numeric and visual review.

Regression proof for the Pasper goatmen

The preserved pre-fix v3 cache files and the shipping v4 files captured for the quaternion repair were audited with all actions:

Actor Source Poses Flagged samples
Giant Assassin pre-fix cache 2dd9e271a1296f39cb35864b.glb 61 51
Blackhorn Silencer pre-fix cache 921ccbe5f2225bf2f02a3136.glb 61 51
Giant Assassin v4 paspers-shrine/fog-ferocity-03.glb capture 61 0
Blackhorn Silencer v4 paspers-shrine/fog-loster-03.glb capture 61 0

The old poses were caught by connected-component movement even though their whole-character AABB diagonal stayed near the bind size. This is why global bounds alone are not sufficient for this regression. The v4 comparison proves the quaternion repair, while the completed 322-variant post-v5 audit proves the cast-semantics rebuild did not change the aggregate evaluated-pose result.

Limits

  • Numeric shape checks cannot prove semantic correctness, timing quality, attack impact alignment, or that a clip is the intended RuneWaker clip.
  • Start/middle/end sampling can miss a defect isolated between those frames.
  • Family contact sheets render representative frames, not every frame of every action. Use --render-mode all for suspicious actors, then test timing and transitions in the browser.
  • Deliberately detached parts, projectiles, trails, and extreme attacks may require visual review rather than automatic rejection.
  • Component metrics require the evaluated modifier stack to preserve vertex count and ordering.
  • Contact sheets are evidence aids, not replacements for an in-game playtest.