Why animsmith
What animsmith is, why it exists, and what it is worth to your team.
This page is for someone deciding whether to adopt animsmith — a lead evaluating tools, a producer weighing pipeline changes, an engineer asked "should we use this?". If you already use animsmith and want reference material, start from all pages instead.
What animsmith is
animsmith validates, measures, reports, and prepares skeletal animation clips for game-engine import and runtime use.
Concretely: point it at a glTF/GLB or FBX export and it answers — before engine import, in seconds, with frame- and bone-level detail — questions that otherwise wait for a playtest: Does the loop pop? Do the feet slide? Does the measured root-motion speed match what the character controller expects? Did this re-export silently change the motion? It works from the command line, from CI via stable exit codes and versioned JSON, or from Rust as a library — and it repairs the mechanical problems that are provably safe to fix.
Why it exists
Every game team answers "is this clip game-ready?" by hand today: a tech artist scrubbing a timeline, a reviewer squinting at a playblast, a bug report from playtest. Nothing open-source does game-semantics clip validation. Format validators stop at spec conformance — a run cycle whose feet skate across the ground is perfectly valid glTF; the validator has no concept of a foot, a loop, or root motion. Engine importers do see content problems, but they see them late, inside editor workflows, after import-time conversion has already reshaped the data — and every studio re-derives its custom import checks from scratch. Academic motion-metric code lives in ML-evaluation repos, not artist tools.
animsmith is the missing layer in between: a content quality gate and repair assistant for animation clips — the linter and test suite counterpart to engine import inspectors, packaged as a standalone Rust library and CLI: glTF/GLB native, FBX ingested for DCC exports, engine-agnostic, with machine-readable output for CI. It gives animation teams the same repeatable, reviewable, CI-friendly confidence that code teams expect from tests and linters.
What "game-ready" means
"Game-ready" is staged evidence, not a stamp — a clip is ready for a consumer, and the last stages of readiness belong to that consumer. The readiness ladder is the canonical definition; it stages the evidence from file-ready data to shipped acceptance. What animsmith contributes splits three ways:
- Validated generically, on every file: the data is mechanically sound — finite values, unit rotations, monotonic key times, sane durations, no accidental scale animation or export bloat — and the losslessly repairable defects are repaired.
- Validated against your declarations: the semantic contracts only you can state — loops, in-place policy, speed pins, required bones, blend sets — declared in a project config, resolved through a rig profile, with missing prerequisites recorded as typed, nonblocking coverage gaps rather than guessed.
- Consumer-owned: importer and blend-graph behavior, controller feel, artistic quality, and shipping sign-off. animsmith's reports and measurements are evidence for that review, not a substitute for it — no standalone tool can certify behavior inside a runtime it has never seen.
What game-ready means covers the measurable ground symptom by symptom: the runtime failure you see when a declared contract is violated, the mechanics behind it, and the check, repair, and config that address it.
What it is worth, by role
Artists and animators get actionable frame- and bone-level reports
instead of "the import looked wrong": which clip, which bone, which
frame, and a fix hint that maps to a DCC action — trim frames, freeze
scale, rename bones, fix the rest pose. animsmith lint export.fbx
runs seconds after export, while the DCC session is still open.
Technical animators codify engine and rig assumptions once — loop flags, speed pins, blend rings, rig profiles — in a project config, then batch-validate whole libraries against it instead of re-checking every delivery by hand.
Gameplay engineers stop debugging capsule, root-motion, and foot-slide problems after import. Root travel, stride, and gait phase become measured numbers checked against declared tolerances, and CI gates catch a drifted re-export before it reaches a playtest.
Producers and pipeline owners turn outsourced-asset acceptance into measurable requirements: a machine-readable acceptance contract that both sides can run, fewer review round-trips, and asset state that is visible in a report rather than in someone's head.
Tool engineers build on a Rust library and a versioned JSON envelope with published schemas, instead of scraping engine import logs — the same checks the CLI runs, inside your own pipeline.
What animsmith is not
animsmith is not a converter, an animation editor, or a DCC replacement. It converts FBX exports to glTF as pipeline plumbing and applies byte-surgical repairs whose correctness its own checks can verify, but artistic transformation — retargeting, motion editing, re-authoring — is DCC work and stays out of scope. It also does not replace glTF-Validator or engine importers: it sits between them, checking the content semantics the format validator cannot see, earlier and more repeatably than the engine can.
Where to go next
Convinced, or curious? The what game-ready means covers each failure mode in depth — mechanics, check, repair, config — and everything else, from install to runnable workflows, is routed from the all pages.
What game-ready means
What animsmith checks for, and why exported clips fail at runtime.
A skeletal animation clip can pass every format validator and still break the game it ships in: the loop pops, the character glides, feet skate through blends, a limb stays T-posed. Those are not file-format errors — the file is spec-conformant — they are content problems that only surface after the slowest step of the pipeline: engine import, a bake, a playtest.
This page defines what "game-ready" means here. The readiness ladder below stages the evidence from file-ready data to shipped acceptance and says who owns each level. The checks, repairs and config surfaces themselves live in the symptom index, which routes every runtime symptom — and the narrower presentations of one — to the page that walks it, and carries every registered check id in one table.
Each symptom has its own page under symptoms: what you see in the engine, what AnimSmith measured, the finding, the repair, and the precise contract one click down. If you want runnable commands, use the examples cookbook; if you want the current per-check reference for IDs, default findings, prerequisites, config keys, coverage gaps, and remediation boundaries, see the built-in check reference; if you want the reasoning behind the tool itself — why it exists and what it is worth to your team — see why animsmith.
A valid file is not a usable clip
Format validators — Khronos glTF-Validator being the canonical one — check spec conformance: accessor validity, buffer bounds, quaternion norms at the container level. They have no concept of a loop, a gait, or root motion. A clip whose walk cycle was cut a quarter-stride short is perfectly valid glTF; it will also visibly pop every second of gameplay.
The characteristics below fall into two groups, and animsmith treats them differently:
- Mechanical characteristics hold for every clip, with no knowledge of your project: finite values, monotonic key times, unit quaternions, consistent durations.
- Semantic characteristics are contracts only you can declare: this clip loops, this one is authored in place, these four form a blend ring — declared in a project config and resolved through a rig profile.
These two groups are the first two levels of the readiness ladder, which states what animsmith does about each.
Two loops benefit. The artist inner loop — animsmith lint export.fbx seconds after a DCC export catches "the loop pops" or
"wrong rig" while the DCC session is still open, instead of after
import and bake. And the CI gate — the same checks with
machine-readable output and stable exit codes hold every committed
asset to the contract, so a re-export can't silently drift.
The readiness ladder
"Game-ready" is not one property a tool can certify, because most of it is relative to a consumer: your engine, your controllers, your bar for quality. It is a ladder of evidence, and each level has a different owner. animsmith's job is to make the early levels checked and repeatable, make the declared middle measurable, and say plainly what it did not evaluate — not to stamp the whole ladder.
-
File-ready — the data is parseable, finite, and mechanically valid: no NaN/Inf, monotonic key times, unit quaternions, sane durations, clean track hygiene. This is animsmith's primary generic coverage: the mechanical checks (
nan,time-monotonic,quat-norm,quat-flip,duration-sanity,scale-keys,non-uniform-scale,constant-track) run on every file with no configuration. The narrowerconstant-nonunit-scalepolicy signal is registered but opt-in.fixrepairs the two losslessly repairable defect classes (quat-norm,quat-flip). -
Clip-ready — the clip honors what you declared about it: loop closure, duration and frame grid, in-place vs root-motion policy, required bone motion, structural rig presence, bind-pose consistency. Strong, config-backed coverage where a check exists:
fps,loop-seam,in-place,root-motion-speed,foot-slide,missing-bones,required-bones,frozen-bone, andbind-posejudge exactly the expectations you declare — and the checks that need rig roles report a typed coverage gap instead of guessing when a role cannot be resolved. One member is heuristic:foot-slideships as a warning (see feet slide within one clip). -
Set-ready — clips that blend or sync together are compatible as a set. Generic measurement and checking where implemented:
gait-groupholds a declared directional blend ring to a shared stride phase,sync-groupchecks same-time timing surfaces, andtime-complementexplains pairs whose stride phase aligns materially better under reflected time.measuresupplies the per-clip numbers, andanimsmith diffcatches drift between revisions. Set compatibility beyond the implemented checks is yours to review. -
Rig and use prerequisites — which bones play which roles on the target rig, which bones must exist, which bones a clip must animate, and what each clip is for. A shared boundary: you supply the meaning (a rig profile or
[rig.roles],[rig] required_bones,animates_bones, per-clip expectations), and animsmith resolves roles against the skeleton, checks the declarations, and reports the resolved roles it used. Nothing at this level can be inferred from the file alone. -
Runtime integration — importer behavior, blend-graph topology, animation target IDs, masks, sync and reset behavior, and the poses your engine actually evaluates. Consumer-owned: animsmith's exact Bevy 0.19.0 profiles can predict the canonical
Animation{i}source-animation selector and, in the current revision 3 slice, negative animation/channel gate outcomes. It does not run the engine, prove runtime asset existence, or validate graph wiring, target survival, or positive playback. A dropped source row is prediction evidence, not a content finding; when both gates allow loading, runtime survival is required-unavailable. Its measurements come from its own documented sampling model — a model of engine samplers, not a reproduction of yours. -
Gameplay, artistic, and production acceptance — controller feel and timing, readability, visual quality, provenance, reproducibility, shipping sign-off. Consumer-owned: reports and measurements inform the review; people make the call.
A clean run is evidence, and evidence has scope: it covers the checks that ran, on the file that ran, against the contract you declared. Only an actual animsmith run on the actual file establishes that evidence — nothing transfers from vendor previews, other files in the pack, or another export's report. And where generic validation touches a later level, it supplies prerequisites or evidence for that level, never blanket certification of it: a mechanically pristine, contract-clean clip can still be rejected by your importer, your blend graph, or your art director.
Reading a lint run
One animsmith lint run answers five independent questions. Keep
them separate when you automate on the output:
- Was the check active? The full catalog is selected by default;
--selectnarrows the selected set, and[checks.<id>] severity = "off"disables a check. A built-in opt-in check stays disabled until its severity is set to "note", "warn", or "error". Final JSON still records inactive checks without executing them. - Did it apply here? Contract-aware checks judge only declared
expectations. With no
loop = trueclip in the config,loop-seamhas nothing to judge and is recorded asnot_applicable. - Was the work evaluated? When declared work exists but a prerequisite
or measurement is missing, the check reports a typed coverage gap. A check
can also complete part of its work:
gait-groupstill validates declared ring members when unresolved roles keep it from measuring phase, then reports member existence as completed and phase coherence as a gap. Engine prediction work uses a separaterequired_prediction_unavailablefacet; unlike an ordinary coverage gap, it makeslintexit1and cannot be suppressed with severity or--allow. - What did the evaluated work find? Content findings at note, warning, or error severity, carrying clip, bone, time, and measured-vs-expected context.
- What blocks? Gate policy is yours, not animsmith's verdict:
exit
1on error findings,--deny-warningsto promote warnings, per-check severity overrides, and presentation-only--allowin text or Markdown. Coverage gaps never fail a run, while required-unavailable engine prediction work does. Exit0means no failing findings and no required prediction work remains unavailable; it still does not imply ordinary gap completeness. A gate that requires full coverage must inspect gaps too.
There is deliberately no single "pass" state: a run can complete with warnings, and it can evaluate some declared work while skipping the rest. See machine-readable output for the current v19 representation. It models selection, configuration, applicability, and evaluation independently, keeps content findings separate from typed gaps, and records completed work scopes. This is evidence about animsmith's checks, not runtime certification; stricter completeness policy belongs to the consuming pipeline.
Each clip symptom the levels above describe has a page of its own under symptoms, whose one table routes it to the checks that measure it, the repair, the config surface, and who owns the fix; the three runtime problems that are not about a clip are answered on that index itself.
Why animsmith exists
The positioning case — what animsmith is, why nothing else fills this role, and what it is worth to each role on a team — lives in why animsmith.
Everything else — the symptom pages themselves, runnable workflows, pipeline scenarios, the CLI reference, embedding, and the dated engine survey behind this page's contract — is routed from all pages.
Install
AnimSmith is one binary. Pick the route that fits your machine, then confirm it runs.
Prebuilt binary
Download the archive for your platform from the
latest GitHub release.
The archive names for each platform are listed in the
CLI reference. Each archive ships with a matching .sha256
file, so you can verify the download before unpacking it:
$ sha256sum -c animsmith-vX.Y.Z-x86_64-unknown-linux-gnu.tar.gz.sha256
$ tar -xzf animsmith-vX.Y.Z-x86_64-unknown-linux-gnu.tar.gz
Put the animsmith binary somewhere on your PATH.
Cargo
With a Rust toolchain installed:
$ cargo install animsmith
The default build includes FBX input and HTML reports. For a pure-Rust, glTF-only binary with no C build step:
$ cargo install animsmith --no-default-features
That build omits report, convert, and assemble; everything else works.
Check it
$ animsmith --version # exits 0
Rust pipelines
If you would rather call the checks from your own Rust code than shell out to the binary, start with the embedding guide; the crates are on crates.io and documented on docs.rs.
The project README is the same install summary in the form crates.io and GitHub show, plus a condensed check and configuration reference.
Next: first lint in 60 seconds.
First lint in 60 seconds
Two synthetic clips from this repository are enough to see what a finding
looks like, repair one, and watch a declared contract catch a popped loop.
The commands run from a checkout of the repository. With an installed binary
and no checkout, download the files they name from
examples/assets/ and
examples/walk.animsmith.toml and adjust
the paths.
1. Lint a clip that flickers
Mechanical checks need no configuration. This clip carries one non-unit rotation key and one hemisphere flip, the two representation defects that make a pose flicker or spin the long way round:
$ animsmith lint examples/assets/clip-dirty.glb
examples/assets/clip-dirty.glb:
error[quat-norm] clip 'swing' bone 'spine' @0.500s: non-unit rotation key (worst at key 2) (measured 1.0500, expected 1.0000)
warning[quat-flip] clip 'swing' bone 'spine' @0.750s: 2 hemisphere flip(s) between adjacent rotation keys (first between keys 2 and 3) — engines without neighborhood correction will spin the long way (measured 2.0000)
coverage[bind-pose] insufficient_rotation_evidence ×1 (scopes: first_frame_rest_delta; subjects: swing): only 1 usable first-frame rotation track(s); at least three are required
1 error(s), 1 warning(s), 0 note(s), 1 coverage gap(s), 0 available prediction facet(s), 0 required-unavailable prediction facet(s) # exits 1
Every finding names the check, the clip, the bone and the time, then the
measured value against the expected one. The coverage line is not a
finding: a check that lacked the evidence it needs (here, at least three
rotation tracks) says so instead of guessing, and never fails the run.
2. Repair what is mechanically safe
Both defects are lossless to repair, so fix can do it. --dry-run reports
the pending repairs and exits 1 without writing anything; -o writes the
repaired copy.
$ animsmith fix examples/assets/clip-dirty.glb --dry-run
would fix[quat-norm] clip 'swing' bone 'spine': 1 key(s) unit-normalized
1 key(s) would be fixed across 1 track(s) -> no output written
would fix[quat-flip] clip 'swing' bone 'spine': 1 key(s) hemisphere-normalized
1 key(s) would be fixed across 1 track(s) -> no output written # exits 1
$ animsmith fix examples/assets/clip-dirty.glb -o fixed.glb
fixed[quat-norm] clip 'swing' bone 'spine': 1 key(s) unit-normalized
1 key(s) fixed across 1 track(s) -> fixed.glb
fixed[quat-flip] clip 'swing' bone 'spine': 1 key(s) hemisphere-normalized
1 key(s) fixed across 1 track(s) -> fixed.glb # exits 0
$ animsmith lint fixed.glb
fixed.glb:
coverage[bind-pose] insufficient_rotation_evidence ×1 (scopes: first_frame_rest_delta; subjects: swing): only 1 usable first-frame rotation track(s); at least three are required
0 error(s), 0 warning(s), 0 note(s), 1 coverage gap(s), 0 available prediction facet(s), 0 required-unavailable prediction facet(s) # exits 0
The repair changes only the bytes of the keys it names; meshes, skins and
every other accessor pass through byte-identical, and animsmith diff reports
no measurement moved.
3. Lint against a contract
Whether a clip loops is your knowledge, not the file's. Without a declaration, this walk cycle lints clean:
$ animsmith lint examples/assets/walk-dirty.glb
examples/assets/walk-dirty.glb:
coverage[bind-pose] insufficient_rotation_evidence ×1 (scopes: first_frame_rest_delta; subjects: walk): only 0 usable first-frame rotation track(s); at least three are required
0 error(s), 0 warning(s), 0 note(s), 1 coverage gap(s), 0 available prediction facet(s), 0 required-unavailable prediction facet(s) # exits 0
Declare it as a loop through a project config and the seam checks arm:
$ animsmith --config examples/walk.animsmith.toml lint examples/assets/walk-dirty.glb
examples/assets/walk-dirty.glb:
error[loop-closure] clip 'walk' bone 'foot_r' @1.000s: loop does not close in position: bone 'foot_r' is 0.1581 m from its first-frame model-space position (cap 0.0100 m) (measured 0.1581, expected 0.0100)
error[loop-seam] clip 'walk' @1.000s: loop seam pops: wrap discontinuity is 6.82× the neighbouring in-clip step (cap 1.60) — the clip does not close its cycle (measured 6.8152, expected 1.6000)
error[loop-seam-vel] clip 'walk' bone 'foot_r' @1.000s: loop velocity changes at the seam: bone 'foot_r' differs by 0.7972 m/s between the incoming and outgoing model-space velocities (cap 0.1000 m/s) (measured 0.7972, expected 0.1000)
coverage[bind-pose] insufficient_rotation_evidence ×1 (scopes: first_frame_rest_delta; subjects: walk): only 0 usable first-frame rotation track(s); at least three are required
3 error(s), 0 warning(s), 0 note(s), 1 coverage gap(s), 0 available prediction facet(s), 0 required-unavailable prediction facet(s) # exits 1
The clip was cut a quarter-cycle short, so the right foot is 16 cm from where
the loop needs it to be. The config file
is short and commented; most of the loop caps it sets are the defaults, and it
says where it departs from them — a stricter 0.5 degree per-clip rotation cap
(default 1.0) and a 1.6 loop-seam ratio (default 1.5). Which parts of
such a contract you can declare on your own, and which need the game
developer, is who writes what.
4. Read the exit code
0 means no failing findings (warnings, notes and coverage gaps may remain),
1 means failing findings, 2 means an operator error such as a missing
file. --deny-warnings promotes warnings to failures for a stricter gate.
Next: your first report shows the same findings as skeleton playback and charts. When something specific looks wrong in the engine, start from the symptoms.
Your first report
A finding tells you which clip, bone and frame are wrong. The HTML report shows you: it plays back the exact pose-grid frames the checks judged, with foot and root trails, metric charts, and a findings list that scrubs the viewer to the judged frame when you click it. One file, no server, no dependencies; attach it to a pull request or send it to whoever owns the fix.
The commands below run from a checkout of the repository; the clips are the same synthetic samples used in first lint.
Render the report
$ animsmith --config examples/walk.animsmith.toml report examples/assets/walk-dirty.glb -o report.html
wrote report.html (1 clip(s), 3 finding(s), 0.1 MB) # exits 0
Open report.html in a browser. This is the same report, embedded here and
scrubbed to the first finding:
Open it full size. Drag to orbit, use the
wheel to zoom, and click a finding to jump to its frame. When the file holds
more than one clip, the with control plays a second one beside the first at
the same normalized phase, with both source times labelled — a presentation
mapping between two timelines, not a retime. With a pair selected, enable
illustrative blend for an optional third pane and a continuous weight slider.
This engine-agnostic presentation interpolates sampled local transforms and
runs forward kinematics; it adds no measured finding or runtime evidence. See
the blend contract.
The source charts share a
playhead with the 3D view, and each chart caption says what to look for in
it — derived from what your configuration declares for that clip and what the
checks judged, not from an assumption about what the clip is. A clip declared
loop = true is told its curves should end where they began; one that pins
speed_mps with animation-owned travel is told the root should keep
travelling at that speed, with no claim about the shape it travels; a clip
that declares neither is told so, and named the checks that judged it. A root
path is also marked where the track starts (a hollow circle) and where it ends
(a filled square), and the caption states how far apart the two are. A
configuration that declares a [gait_groups.<name>] blend ring adds one
further figure per group, drawing every member's left-minus-right foot height
on one shared stride-phase axis with the declared tolerance as a band; that
figure stays on screen whichever member you select, because comparing them is
what it is for.
Compare before and after
When a clip has been repaired or re-exported, render both versions into one comparison report. The pair below is a synthetic gait with a seam and a sliding foot on the left, and the corrected clip on the right:
$ animsmith --config examples/report-comparison.animsmith.toml report \
examples/assets/report-comparison-before.glb \
--compare-after examples/assets/report-comparison-after.glb \
--before-clip acceptance-matrix --after-clip acceptance-matrix \
-o comparison.html
wrote comparison.html (1 clip(s), 5 finding(s), 0.1 MB) # exits 0
Open the comparison. Press play beside the shared phase to run both sides together, or scrub it by hand. Tick Overlay after on before beside it to draw the two skeletons in one pane — the before solid, the after dashed over it — at those same frames. The judged poses come first, then the shared root trajectory — fitted to its own panel, with a scale bar in the corner and the magnification against the role-trajectory panels in its caption, so a two-centimetre sway is legible and still reads as two centimetres — and then each side's trails and gait. The gait panel shades the sampled stance intervals, and every panel's caption says what to look for in it.
Share evidence without sharing the asset
The report embeds the sampled pose grid, which is the motion itself. When the clip is licensed or confidential, render an evidence-only report: findings, coverage gaps, engine predictions and charts stay, the poses are omitted, and the file is safe to attach to a vendor ticket.
$ animsmith --config examples/walk.animsmith.toml report examples/assets/walk-dirty.glb -o evidence.html --evidence-only
wrote evidence.html (1 clip(s), 3 finding(s), 0.1 MB) # exits 0
Where next
- Something specific looks wrong in the engine: start from the symptoms.
- You want this in your export loop or CI: for artists or for game developers.
- You want the full flag list: the CLI reference.
Mixamo to game-ready: an end-to-end tutorial
Take one free Mixamo download through the whole pipeline: download, convert to glTF, inspect the rig, lint, fix what is mechanically fixable, and grow a project contract config from the results.
This is the bring-your-own-asset counterpart to the examples cookbook: the cookbook runs against small assets committed to this repo, while this tutorial runs against a real marketplace rig you download yourself. Each step links to the page that owns its why rather than re-explaining — what game-ready means for everything the checks judge, and the pipeline scenario guide for acquiring and handling the raw asset.
The complete key, default, and precedence reference for the contract used below is the configuration reference.
About the transcripts. The command output in this tutorial is
real, captured from a small generated rig that carries the same
mixamorig:* bone names, the same nine profile roles, and the same
mixamo.com take name as a real download (this repo does not ship
Mixamo's own files — see the
asset policy). Your download
will show many more bones — fingers, twist joints — and possibly more
findings; the roles, the commands, and the behavior are the same.
Excerpts marked real download were captured once (July 2026) from
Mixamo's default Walking; unlike the stand-in transcripts they are not
regenerated by this repo's tests, so treat their exact numbers as a
snapshot.
Build requirements. convert (step 2) ships in the default
install — it sits behind the fbx feature, which is enabled unless
you opted out. If you installed with --no-default-features, use the
default build for this one step (see
feature flags). Everything from step 3 on
works in any build.
1. Download a clip from Mixamo
Mixamo is free with an Adobe ID, and Adobe documents its animations as royalty-free for personal and commercial projects — see Adobe's Mixamo FAQ for the current terms. That covers using the assets in your project; it does not clearly permit redistributing the downloaded files, so keep them out of public repos and do not commit them here.
- Sign in and pick any character.
- Search the animations for Walking and select a walk cycle.
- Check the In Place option. Mixamo's default walks travel through the world; In Place keeps the hips over the origin, which is the shape most capsule-driven character controllers expect. (If your game uses root motion, download without In Place — step 6 shows how the contract differs.)
- Download as FBX Binary with skin. The skin is optional for
linting, but keeping it lets
reportrender the mesh later.
We recommend keeping the downloaded FBX as your immutable raw source and treating every animsmith output as a derived artifact — the pipeline scenario guide explains that raw-vs-generated split.
2. Convert to glTF
$ animsmith convert Walking.fbx -o walking.glb # real download
wrote walking.glb (70 node(s), 1 clip(s), 2 mesh(es) / 28374 position(s), 2 material(s)); dropped 1 clip(s) with no writable tracks
(Counts vary by character. This download contains a second take named
Take 001 with zero tracks and keys. Conversion omits that empty take,
writes the animated mixamo.com clip, and reports the omission. The
licensed download is not committed here.)
animsmith lints FBX directly, so conversion is not required to run
checks — but glTF/GLB is the native format for the rest of the
pipeline (fix, transform, and diff operate on glTF), and a
convert-once step gives later commands a stable baseline to compare
against. A conversion that succeeds proves the container is
well-formed, not that the motion is usable —
a valid file is not a usable clip
— which is what steps 4–6 judge.
3. Inspect the rig
$ animsmith inspect walking.glb
walking.glb
rig profile: mixamo (9 roles)
hips -> mixamorig:Hips (exact)
spine -> mixamorig:Spine (exact)
head -> mixamorig:Head (exact)
left_foot -> mixamorig:LeftFoot (exact)
right_foot -> mixamorig:RightFoot (exact)
left_toe -> mixamorig:LeftToeBase (exact)
right_toe -> mixamorig:RightToeBase (exact)
left_hand -> mixamorig:LeftHand (exact)
right_hand -> mixamorig:RightHand (exact)
skeleton: 9 bones
mixamorig:Hips
mixamorig:LeftFoot
mixamorig:LeftToeBase
mixamorig:RightFoot
mixamorig:RightToeBase
mixamorig:Spine
mixamorig:Head
mixamorig:LeftHand
mixamorig:RightHand
materials: 0
mesh instances: 0
clips: 1
mixamo.com: 1.000s, 2 tracks, 33 keys max
These are the same nine roles a real download resolves; your skeleton list will just be longer. From the real download (abridged):
$ animsmith inspect walking.glb # real download
walking.glb
rig profile: mixamo (9 roles)
...the same nine-role table as above...
skeleton: 70 bones
<fbx-root>
mixamorig:Hips [skinned]
mixamorig:Spine [skinned]
...
materials: 2
...the exact quoted source material names...
mesh instances: 2
node "<first mesh node name>"
...mesh, skin, and primitive/material context...
node "<second mesh node name>"
...mesh, skin, and primitive/material context...
clips: 1
mixamo.com: 1.033s, 156 tracks, 32 keys max
The extra bones — fingers, twist joints, end markers — carry no role,
and the two mesh nodes ride along for report playback.
Two things to read off this output:
- The
mixamoprofile resolved without any config. Mixamo is a built-in rig profile: it binds the semantic roles from Mixamo'smixamorig:*bone names, which is what arms the role-based checks in step 6. Without a resolved rig, those checks skip rather than guess (see the symptom index). - The clip is named
mixamo.com. Mixamo names every export's animation takemixamo.com, and animsmith uses the take name as the clip name. Your contract config must address the clip by this name.
The
mixamoprofile has no Root role. Mixamo rigs have no dedicated root bone —mixamorig:Hipsis the top of the hierarchy, and any root motion is baked into it. animsmith therefore judgesin-placeandroot-motion-speedon the Hips track for Mixamo rigs: travel measurements still work, but they describe hip movement, not a separate locomotion root. If your engine workflow retargets hip motion onto a dedicated root bone (a common Unreal step), that happens downstream in your DCC or importer — lint the export you actually ship to the engine.
4. Lint the mechanical checks
$ animsmith lint walking.glb
walking.glb:
coverage[bind-pose] first_frame_rest_delta 'mixamo.com': insufficient_rotation_evidence: ...
0 error(s), 0 warning(s), 0 note(s), 1 coverage gap(s)
With no config, expectation-driven checks are not applicable. Mechanical
checks still evaluate, as does bind-pose; this analytic fixture has no
rotation tracks, so bind-pose records a nonblocking coverage gap rather than
claiming a clean evaluation.
Findings like constant-track notes are normal on a marketplace rig.
From the real download (abridged):
$ animsmith lint walking.glb # real download
walking.glb:
warning[scale-keys] clip 'mixamo.com' bone 'mixamorig:Hips': scale animation present ...
note[constant-track] clip 'mixamo.com' bone 'mixamorig:Spine': translation track has 2 keys but never moves — export bloat
...
0 error(s), 1 warning(s), 103 note(s), 1 coverage gap(s)
Of the clip's 156 tracks, 103 never move — pure export bloat — and the hips carry a scale track worth checking before retargeting. Nothing here is broken motion; the serious mechanical defects — flicker-inducing quaternion problems — tend to creep in later, after the asset has been through your DCC, a retargeter, or an exporter plugin.
5. Fix what is mechanically fixable
$ animsmith fix walking.glb --dry-run
0 key(s) would be fixed across 0 track(s) -> no output written
0 key(s) would be fixed across 0 track(s) -> no output written
(One summary line per default repair — quat-norm, then quat-flip.)
fix applies only repairs that are provably lossless: quat-norm and
quat-flip, the representation defects behind
the pose flickers, spins, or explodes.
That makes it safe to run unconditionally; on a clean export it is a
no-op, as here. When a re-export does pick up defects:
$ animsmith fix walking.glb -o walking-fixed.glb
$ animsmith lint walking-fixed.glb
$ animsmith diff walking.glb walking-fixed.glb
The re-lint proves the findings are gone; the diff proves the
repair moved no measured motion. The cookbook's
repair section shows the
full transcript of that loop on a committed dirty asset.
6. Grow the contract config
The mechanical checks passing does not make the clip game-ready — a
walk whose loop pops or whose feet skate is mechanically pristine (see
a valid file is not a usable clip).
The semantic checks need your expectations declared. Start from what
measure reports. The shortened JSON below is illustrative; capture and retain
the exact numbers from your own downloaded asset rather than treating these
sample values as vendor evidence:
$ animsmith measure --format json walking.glb
{
"schema_version": 19,
"schema": "urn:animsmith:schema:output:19",
"tool": { "name": "animsmith", "version": "0.14.0",
"source": { "revision": null, "dirty": null } },
"command": "measure",
"summary": { "files": 1 },
"files": [
{
"path": "walking.glb",
"input": {
"sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"bytes": 123456
},
"rig": { "profile": "mixamo", "resolution_outcome": "resolved",
"resolved_roles": { "hips": "mixamorig:Hips", "spine": "mixamorig:Spine",
"left_foot": "mixamorig:LeftFoot", "right_foot": "mixamorig:RightFoot" },
"resolved_role_policies": { "hips": "exact", "spine": "exact",
"left_foot": "exact", "right_foot": "exact" } },
"measurements": {
"schema_version": 18,
"schema": "urn:animsmith:schema:measurements:18",
"clips": { "mixamo.com": {
"duration_s": 1.0, "frame_count": 33,
"animated_bones": ["mixamorig:Hips", "mixamorig:LeftFoot", "mixamorig:RightFoot"],
"bone_channels": [
{ "bone_index": 0, "bone_name": "mixamorig:Hips",
"availability": "measured",
"properties": ["translation", "rotation"] },
{ "bone_index": 1, "bone_name": "mixamorig:LeftFoot",
"availability": "measured",
"properties": ["translation"] },
{ "bone_index": 2, "bone_name": "mixamorig:RightFoot",
"availability": "measured",
"properties": ["translation"] }
],
"bone_rotation_range_deg": { "mixamorig:Hips": 3.2 },
"loop_continuity": { "bones": [
{ "bone_index": 0, "bone_name": "mixamorig:Hips",
"position_delta_m": 0.0, "rotation_delta_deg": 0.0,
"seam_velocity_delta_mps": 0.0,
"seam_angular_velocity_delta_degps": 0.0 },
{ "bone_index": 1, "bone_name": "mixamorig:LeftFoot",
"position_delta_m": 3.7e-17, "rotation_delta_deg": 0.0,
"seam_velocity_delta_mps": 0.0,
"seam_angular_velocity_delta_degps": 0.0 },
{ "bone_index": 2, "bone_name": "mixamorig:RightFoot",
"position_delta_m": 3.7e-17, "rotation_delta_deg": 0.0,
"seam_velocity_delta_mps": 0.0,
"seam_angular_velocity_delta_degps": 0.0 }
] },
"loop_continuity_availability": "measured",
"loop_endpoint_mode_availability": "not_applicable",
"frame_grid_availability": "not_applicable",
"loop_seam_ratio": 1.2e-15,
"loop_seam_ratio_availability": "measured",
"gait": { "phase": 0.75, "phase_availability": "measured", "lr_amplitude_m": 0.2 },
"gait_availability": "measured",
"root_trajectory": {
"bone_index": 0, "bone_name": "mixamorig:Hips",
"source_role": "hips_fallback",
"translation": {
"horizontal_displacement_x_m": 0.0,
"horizontal_displacement_z_m": 0.0,
"horizontal_travel_m": 0.0,
"vertical_displacement_m": 0.0,
"vertical_min_displacement_m": 0.0,
"vertical_max_displacement_m": 0.0
},
"translation_availability": "measured",
"yaw": {
"heading_axis": "positive_z", "net_yaw_deg": 0.0,
"unwrapped_yaw_deg": 0.0, "yaw_travel_deg": 0.0
},
"yaw_availability": "measured"
},
"root_trajectory_availability": "measured",
"speed_mps": 0.0,
"speed_mps_availability": "measured"
} },
"mesh_definitions": [],
"node_instances": [],
"scenes": [],
"skeleton_source_coverage": "unavailable",
"skeleton_nodes": [],
"skins": [],
"material_resource_coverage": "complete",
"material_definitions": [],
"textures": [],
"images": []
}
}
]
}
(Abridged — all nine roles resolve; head, toes, and hands are elided
here.) The numbers become the contract:
near-zero per-bone loop deltas and loop_seam_ratio say the cycle closes in
pose, velocity, and locomotion phase; speed_mps of zero confirms the In Place
download, and the gait numbers seed a
blend-ring group
once you have more than one direction. Declare what must stay true by
saving this as animsmith.toml next to your clip — animsmith
auto-loads it from the working directory:
[rig]
profile = "mixamo"
[clips."mixamo.com"]
loop = true
movement_owner_xz = "gameplay"
[checks.loop-seam]
max_ratio = 1.6
[checks.loop-closure]
max_position_delta_m = 0.01
max_rotation_delta_deg = 1.0
[checks.loop-seam-vel]
max_velocity_delta_mps = 0.1
[checks.loop-seam-rot]
max_angular_velocity_delta_degps = 5.0
The same contract is committed as
examples/mixamo.animsmith.toml
for reference (from a repo checkout, pass it with
--config examples/mixamo.animsmith.toml instead of saving a local
copy). It pins the profile rather than trusting auto-detection, declares
the clip a loop (arming the loop checks) and gives XZ movement to gameplay
(arming in-place,
judged on the Hips track per the callout above). Every key, glob
pattern, and severity override is documented in the
configuration reference.
Lint against the contract:
$ animsmith lint walking.glb
walking.glb:
coverage[bind-pose] first_frame_rest_delta 'mixamo.com': insufficient_rotation_evidence: ...
0 error(s), 0 warning(s), 0 note(s), 1 coverage gap(s) # exits 0
And this is what a violation looks like — the same contract against a copy whose cycle was cut short, the classic popped loop:
$ animsmith lint walking-popped.glb
walking-popped.glb:
error[loop-closure] clip 'mixamo.com' bone 'mixamorig:RightFoot' @1.000s:
loop does not close in position (measured 0.1581, expected 0.0100)
error[loop-seam] clip 'mixamo.com' @1.000s: loop seam pops: wrap discontinuity
is 6.82× the neighbouring in-clip step (cap 1.60) — the clip does not
close its cycle (measured 6.8152, expected 1.6000)
error[loop-seam-vel] clip 'mixamo.com' bone 'mixamorig:RightFoot' @1.000s:
loop velocity changes at the seam (measured 0.7972, expected 0.1000)
3 error(s), 0 warning(s), 0 note(s), 1 coverage gap(s) # exits 1
The contract also catches downloading the wrong variant. The real download above is Mixamo's default Walking — the traveling one — and under this In Place contract it fails exactly as it should:
$ animsmith lint walking.glb # real download
walking.glb:
error[in-place] clip 'mixamo.com': declared in-place but the root travels
at 1.79 m/s — the character will glide at runtime (measured 1.7857,
expected 0.0000)
...
For a root-motion download like that one, give XZ ownership to the animation
and add a speed pin, using the speed_mps that measure
reported as the starting value:
[clips."mixamo.com"]
loop = true
movement_owner_xz = "animation"
speed_mps = { value = 1.8, tolerance = 0.15 }
Older configs may spell XZ gameplay/animation ownership as in_place = true
or false. That alias remains supported, but one selector entry cannot declare
both in_place and movement_owner_xz.
Under that contract the real download passes the loop and speed pins and surfaces genuine content findings instead (abridged):
$ animsmith lint walking.glb # real download
walking.glb:
warning[foot-slide] clip 'mixamo.com' bone 'mixamorig:LeftFoot' @0.333s:
left foot skates during stance: speed deviates 0.52 m/s from the
expected 0.00 m/s (cap 0.30) — foot plants will slip at runtime ...
warning[foot-slide] clip 'mixamo.com' bone 'mixamorig:RightFoot' @0.833s: ...
0 error(s), 3 warning(s), 103 note(s), 0 coverage gap(s) # exits 0
Whether that stance drift is acceptable for your game is exactly the
kind of decision the contract records — tighten the cap, accept it
with --allow, or send the clip back for cleanup (see
feet slide within one clip).
From here the contract grows with your project: rename clips as you
import more animations, add [clips."run_*"] globs, speed pins, and
gait_groups blend rings as the locomotion set fills out — the
cookbook's
project contract section
and the worked
character.animsmith.toml
show that full shape. Commit animsmith.toml next to your assets and
every bare animsmith lint — local or
in CI — enforces it.
Where to go next
What game-ready means explains every failure mode these checks catch; everything else — the cookbook, the CLI reference, embedding — is routed from all pages.
Symptoms
Start from what you see in the engine, not from a check id. One row per symptom: what it looks like, the checks that measure it, the repair, the config surface, who owns the fix, and the page that walks it. Each page opens with the runtime symptom, shows what AnimSmith measured on a synthetic clip, and keeps the precise contract one click down; the built-in check reference and the configuration reference remain the exact authorities.
| Symptom | Check(s) | Repair / transform | Config surface | Who fixes it | Page |
|---|---|---|---|---|---|
| A joint snaps the long way round, or the pose turns to noise | nan, quat-norm, quat-flip, time-monotonic | fix (quat repairs, lossless) | [checks.<id>] severity | AnimSmith repairs the representation; the DCC owns non-finite data | The pose flickers, spins, or explodes |
| A limb stops before the rest of the body, or the clip is a frame short | duration-sanity, fps | transform --slice, --hold-extend | [clips.<name>] duration_s, fps | Pipeline edit or re-export | The clip is the wrong length or freezes at the end |
| The cycle jumps or hitches every time it wraps | duplicate-loop-endpoint, loop-closure, loop-seam-vel, loop-seam-rot, loop-seam | transform --drop-duplicate-loop-endpoint for a strict duplicated endpoint; otherwise re-author the endpoint pose or tangents | [clips.<name>] loop = true, [checks.loop-closure], [checks.loop-seam-vel], [checks.loop-seam-rot] | Artist in the DCC; loop policy is a project decision | The loop pops |
| The character slides across the floor, or runs without moving | in-place, root-motion-speed | re-export; measure for ground truth | [clips.<name>] movement_owner_xz, speed_mps | Gameplay decides ownership; artist re-exports | The character glides or runs in place |
| Feet skate, pop or desync when the runtime blends two clips — including travel speed or foot slide that changes by direction, and a same-time pair that looks mirrored or swaps footfall timing | gait-group, sync-group, time-complement | transform --gait-anchor for explicitly in-place cycles; runtime phase offsets for root motion | [gait_groups.<name>], [sync_groups.<name>] | Technical animator declares the ring | Feet skate when clips blend; narrower: members travel at different speeds, a pair is time-complementary |
| A planted foot drifts during stance inside one clip | foot-slide | re-author in the DCC | [clips.<name>] speed_mps | Artist; contact cleanup is DCC work | Feet slide within a clip |
| An arm hangs in a T-pose, or a bone never moves | missing-bones, required-bones, frozen-bone, bind-pose | re-export | [clips.<name>] animates_bones, [rig] required_bones | Artist repairs the source rig | A limb is T-posed, or a bone never moves |
| Two files disagree about the skeleton, or a pack reuses one clip name | no per-file check; animsmith inspect plus a collection manifest retain identity | none — retain the exact (file, clip) binding | collection manifest sources, clips, and runtime sets | Pack owner and the ingesting pipeline | Files disagree about skeleton or clip identity |
| An attachment, socket or helper imports at the wrong size | rest-world-scale | apply or rebake the unintended source hierarchy scale, then re-export | [runtime_nodes] selectors, [checks.rest-world-scale] expected_uniform_scale | Artist or exporter settings | The file is bloated, or the retargeter chokes |
| Clips cost far more than the motion in them, or a retargeter chokes | constant-track, scale-keys, non-uniform-scale, opt-in constant-nonunit-scale | transform --prune-constant-tracks after reviewing transition coverage | [checks.<id>] severity, [clips.<name>] animates_bones | Artist or exporter settings | The file is bloated, or the retargeter chokes |
Where the repair column says re-export, that is deliberate: AnimSmith rewrites a clip only in ways whose within-clip correctness its own checks can verify. Those are the lossless quaternion repairs and the bounded mechanical edits — slice, hold-extend, in-place gait anchor, duplicate-loop-endpoint removal, constant-track pruning and FBX-to-glTF conversion — while artistic transformation stays DCC work, for the reason in what animsmith is not. A clean run on any row here is also still scoped evidence: runtime integration caveats apply after it, including the sparse transition coverage a blend or transition set is judged on.
The table above carries every registered check id, including the narrower presentations of a symptom that no page of its own owns. The readiness ladder stages what a clean run on any of these pages is evidence for.
Whichever row you follow, capture the current lint --format json result, and
when the motion itself is contested, the offline report or a before/after
diff. A current, repository-safe diagnostic pair is:
# workflow-exit: 0
$ANIMSMITH --config "$CONFIG_DIR/report-comparison.animsmith.toml" report \
"$ASSET_DIR/report-comparison-before.glb" \
--compare-after "$ASSET_DIR/report-comparison-after.glb" \
--before-clip acceptance-matrix --after-clip acceptance-matrix \
-o "$WORK_DIR/symptom-comparison.html"
It shows a deliberately defective loop and stance trail; it is diagnostic evidence, not an engine or artistic acceptance result.
Not about the clip
Three runtime problems produce no content finding, so none of them has a row above. Each is answered by reading the run itself and then by the project that consumes the asset.
A loader error or an AnimSmith refusal. Read stderr and the command's exit
code, then inspect engine-track-support, engine-addressability, input
identity, and coverage facets. Correct a bad path, config, or supported source
fact; do not reinterpret a refusal. Engine feature and import settings belong
to the engine project. The gate closes on a successful rerun with complete
applicable source evidence, plus engine-observed load evidence where the route
requires it — for Bevy, the revision-3 lint command in the
intake workflow.
A clip exists but cannot be addressed in-engine. Inspect
generate addressability source rows, selector and index evidence, the
dependency closure, and the engine profile's coverage state. Regenerate an
application manifest after a source-order change; application name policy,
loaded handles, targets, and graph wiring are engine code. The gate closes when
the source inventory is complete and the target project records a resolved
runtime asset, selector, target, and playback.
Animations vanish in Bevy with no lint error. No content finding exists,
because the loader's bevy_animation feature or its load_animations setting
dropped them; the
Bevy profile's revision-3 gate
predicts exactly that negative outcome. The gate closes in the Bevy project
that loads the asset, not in a lint run.
The exact fields, severities, prerequisites, and configuration keys stay authoritative in the built-in check reference and the configuration reference. If evidence identifies a vendor or source delivery defect, return the source and its evidence; if it identifies runtime or artistic uncertainty, keep the project-owned gate open.
The pose flickers, spins, or explodes
A joint snaps the long way round between two keys, a limb jitters for a frame, or the whole character turns into visual noise.
Checks: nan ·
quat-norm ·
quat-flip ·
time-monotonic
Why it happens
Rotation is stored as quaternions, and engines are stricter about the maths than exporters are. A key that is not unit length skews skinning, two adjacent keys on opposite hemispheres make an engine interpolate the long way round, and one non-finite float poisons every pose that interpolates through it. Nothing here is an artistic decision — it is the representation of the rotation, which is why these are the checks that need no configuration at all.
What AnimSmith measures
The synthetic clip below carries one non-unit rotation key and one
sign-flipped key on the same bone; everything else is the clean clip.glb.
The report names both, with the frame each was judged at.
Both defects are in the representation rather than the pose. AnimSmith normalizes every rotation key it samples, and a quaternion and its negation are the same rotation, so the judged frames below play back as the clean swing — the findings list is what marks the two defective keys. That is the point: an engine that does not normalize, or that slerps without neighborhood correction, is the one that flickers.
Open the interactive report to scrub the exact keys the checks judged.
What the finding looks like
$ animsmith lint examples/assets/clip-dirty.glb
examples/assets/clip-dirty.glb:
error[quat-norm] clip 'swing' bone 'spine' @0.500s: non-unit rotation key (worst at key 2) (measured 1.0500, expected 1.0000)
warning[quat-flip] clip 'swing' bone 'spine' @0.750s: 2 hemisphere flip(s) between adjacent rotation keys (first between keys 2 and 3) — engines without neighborhood correction will spin the long way (measured 2.0000)
coverage[bind-pose] insufficient_rotation_evidence ×1 (scopes: first_frame_rest_delta; subjects: swing): only 1 usable first-frame rotation track(s); at least three are required
1 error(s), 1 warning(s), 0 note(s), 1 coverage gap(s), 0 available prediction facet(s), 0 required-unavailable prediction facet(s) # exits 1
Both defects are lossless to repair, so this is the one symptom family
AnimSmith fixes for you. --dry-run reports the pending repairs and writes
nothing:
$ animsmith fix examples/assets/clip-dirty.glb --dry-run
would fix[quat-norm] clip 'swing' bone 'spine': 1 key(s) unit-normalized
1 key(s) would be fixed across 1 track(s) -> no output written
would fix[quat-flip] clip 'swing' bone 'spine': 1 key(s) hemisphere-normalized
1 key(s) would be fixed across 1 track(s) -> no output written # exits 1
$ animsmith fix examples/assets/clip-dirty.glb -o fixed.glb
fixed[quat-norm] clip 'swing' bone 'spine': 1 key(s) unit-normalized
1 key(s) fixed across 1 track(s) -> fixed.glb
fixed[quat-flip] clip 'swing' bone 'spine': 1 key(s) hemisphere-normalized
1 key(s) fixed across 1 track(s) -> fixed.glb # exits 0
$ animsmith lint fixed.glb
fixed.glb:
coverage[bind-pose] insufficient_rotation_evidence ×1 (scopes: first_frame_rest_delta; subjects: swing): only 1 usable first-frame rotation track(s); at least three are required
0 error(s), 0 warning(s), 0 note(s), 1 coverage gap(s), 0 available prediction facet(s), 0 required-unavailable prediction facet(s) # exits 0
What AnimSmith can do
The comparison below is that repair, run by the generator that renders this
page: the before side is clip-dirty.glb and the after side is the file
animsmith fix wrote from it, not a hand-authored clean copy of the clip.
Each side prints its own SHA-256 above the pose, which is what pins the after
side to that output — the document records both inputs by content identity and
carries no path.
Open the interactive comparison and press play beside the shared phase to run both sides together, or tick Overlay after on before to draw the two skeletons in one pane — the before solid, the after dashed over it.
The findings list is what changed: the non-unit key and the hemisphere flip on
the before side, and nothing on the after side. The two pose panels play the
same swing, for the reason above — both defects were in the representation,
and AnimSmith normalizes every rotation key it samples — which is also why
diff reports no measurement moved.
No rig role resolves on this three-bone chain, so the trajectory and gait
panels report their evidence unavailable: the findings and the two identities
are what this comparison has to show.
What to do
- Non-unit or flipped rotation keys. Run
animsmith fix. The repair is byte-surgical: meshes, skins, materials and textures pass through byte-identical, andanimsmith diffreports no measurement moved. - A NaN or Inf anywhere. There is no safe automatic repair for a value that carries no information. Find the step that produced it — a divide in a constraint, a bad bake, a corrupted transfer — and re-export.
- Non-monotonic or late key times. Fix the retiming or export range in the DCC. A first key that starts late is its own hazard: the engine clamp-holds an unauthored pose for the gap.
Who fixes it: AnimSmith repairs the representation, and the DCC owns the data.
fix closes the two quaternion classes; a non-finite value, a broken timeline
or a bad bake goes back to the export that produced it. The gate closes when
the repaired copy re-lints clean and its diff against the original shows no
measurement moved.
Config
These checks are mechanical: they run on every file with no configuration. Configuration only changes how hard they fail — promote the hemisphere warning for a project whose runtime slerps without neighborhood correction:
[checks.quat-flip]
severity = "error"
Precise contract: quaternion representation, non-finite values, key times, and what the repairs preserve
Rotation in a clip is stored as quaternions, and engines are strict about the math even when exporters are not.
- A non-finite value anywhere poisons everything. A single NaN or
Inf in a key time or value poisons interpolation and, in most
engines, the whole pose — one bad float turns a character into
visual noise. The
nancheck treats this as an error, always; there is no safe automatic repair for a value that carries no information. - Non-unit quaternions skew skinning. Rotation keys must be unit
quaternions. Engines renormalize inconsistently (or not at all); a
non-unit key skews blend weights and skinning. The
quat-normcheck catches it, andanimsmith fixrepairs it losslessly — scaling a finite, non-zero quaternion back to unit length preserves the rotation it represents. - Hemisphere flips spin the long way around. A quaternion and its
negation represent the same rotation, but interpolation between them
does not: adjacent keys on opposite hemispheres (
dot < 0) make engines that slerp without neighborhood correction take the long way — a visible 360°-minus-θ spin between two keys. Thequat-flipcheck catches it;animsmith fixrepairs it losslessly by negating keys until each track is hemisphere-consistent. - Key times must move forward. glTF requires strictly increasing
key times, and engines misbehave without them. A first key that
starts late is its own hazard: the engine clamp-holds an unauthored
pose for the gap. The
time-monotoniccheck covers both.
Workflow: a first CLI gate
detects these; repairing an
asset walks the
fix --dry-run → fix → verify loop. The repairs are byte-surgical:
meshes, skins, materials, and textures pass through byte-identical.
The checks' prerequisites, configuration keys and gap semantics are listed with every other check in the built-in check reference.
The clip is the wrong length or freezes at the end
A limb stops dead a fraction of a second before the rest of the body, or a clip that should be one second long plays a frame short in the engine.
Checks: duration-sanity ·
fps
Why it happens
A clip's duration is not one number in the file: it is whatever its longest channel reaches, and an engine clamp-holds every channel that ends earlier. A partial export, a slice that dropped one bone's tail keys, or a retiming step that drifted off the frame grid all leave a file that looks valid and plays wrong. The length a clip is supposed to be is project knowledge, so AnimSmith checks the mechanical part always and the declared part only once you pin it.
What AnimSmith measures
The synthetic walk below has one left-ankle rotation channel that stops at 0.75 s while both translation channels run to 1.0 s. The report's judged frame is the moment the short channel starts being held.
Open the interactive report to scrub the frames after the ankle stops.
What the finding looks like
The spread is mechanical, so no configuration is needed to see it:
$ animsmith lint examples/assets/walk-short-channel.glb
examples/assets/walk-short-channel.glb:
warning[duration-sanity] clip 'walk_short_channel': channels end at different times (0.750s..1.000s) — shorter channels will be clamp-held (measured 0.2500, expected 0.0000)
coverage[bind-pose] insufficient_rotation_evidence ×1 (scopes: first_frame_rest_delta; subjects: walk_short_channel): only 1 usable first-frame rotation track(s); at least three are required
0 error(s), 1 warning(s), 0 note(s), 1 coverage gap(s), 0 available prediction facet(s), 0 required-unavailable prediction facet(s) # exits 0
The other half of this symptom — a clip whose channels agree but whose length
no longer matches the manifest — has no mechanical signature at all. Only a
declared duration_s (and fps for the frame grid) turns it into a finding.
What to do
- Channels ending at different times. The export dropped part of a
channel. Re-export the full range;
duration-sanityreports the spread, it does not resample the clip. - A declared length that is missed. Pin
duration_sfor the clip and let the check report the measured and expected seconds. The tolerance absorbs harmless exporter rounding, not a wrong authored range. - Keys off the frame grid. Declare
fpsand fix the retiming step that drifted. Unreal, for example, documents that animations with non-whole end frames do not import correctly. - When the input is deliberately being edited.
animsmith transform --slicecuts a window on the frame grid and retimes it to start at zero, and--hold-extendappends a linear hold of the final pose for charge and block poses. See editing a clip.
Who fixes it: the pipeline. A partial export or a drifted retime is repaired
where it was produced — a re-export, or an explicit transform when the cut
itself is the intended edit. The gate closes when the clip's channels agree,
its declared duration and frame grid are met, and the engine plays the whole
range.
Config
[clips.walk_short_channel]
# The authored range. Linting reports a missed pin; it never resamples.
duration_s = { value = 1.0, tolerance = 0.02 }
# The grid the clip was authored on, so `fps` can check that its keys stay
# on that grid and that it spans a whole number of frames.
fps = 32.0
Precise contract: channel ends, declared durations, the frame grid, and the mechanical edits
- Channels that end at different times mean a partial export. When
one bone's track is shorter than the clip, the engine clamp-holds the
shorter channel — a limb freezes while the rest of the body keeps
moving. The
duration-sanitycheck flags degenerate durations and mismatched channel ends. - A valid-looking clip can still be one frame too short. Export slicing,
inclusive-vs-exclusive frame ranges, and endpoint removal can produce a
positive duration whose channels agree but which no longer matches the
gameplay or animation manifest. Declare
duration_s = { value = 1.033, tolerance = 0.02 }for the clip andduration-sanityreports the measured and expected seconds when that pin is missed. The value must be finite and positive, and the tolerance finite and non-negative; invalid pins are errors instead of silently disabling the contract. The tolerance absorbs harmless exporter rounding. Linting only reports the mismatch; it does not repair or resample the clip. Re-export when the authored range is wrong, or use the explicit slice/hold transforms below when the source is intentionally being edited. - Keys off the frame grid mean a retiming step drifted. A clip with
a declared frame rate should keep its keys on that rate's time grid
and span a whole number of frames. Off-grid keys mean a resample or
retiming step drifted; a fractional frame count means a slice cut
mid-frame — and engines care: Unreal, for example, documents that
animations with non-whole end frames do not import
correctly. The
fpscheck verifies both once the config declares a rate.
When the wrong length is the input problem — a capture with garbage
at the head, a one-shot that should hold its final pose — the
transform command does the mechanical edit:
--slice cuts a window on the frame grid and retimes it to start at
zero, and --hold-extend appends a linear hold of the final pose
(charge and block poses). See editing a
clip.
The checks' prerequisites, configuration keys and gap semantics are listed with every other check in the built-in check reference.
The loop pops
Every time the cycle wraps, the character jumps or hitches for one frame.
Checks: loop-closure ·
loop-seam-vel ·
loop-seam-rot ·
loop-seam ·
duplicate-loop-endpoint
Why it happens
A looping clip needs the pose and the motion to be continuous across the wrap. An export range that ends a frame early, an unbaked cycle modifier, or copied endpoint keys with mismatched tangents all leave a gap that the engine crosses in a single frame. A pose gap is a visible jump; a velocity gap is a hitch or pulse once per cycle.
What AnimSmith measures
The synthetic walk below was cut a quarter-cycle short. Foot height relative to the hips no longer returns to its first-frame value, so the loop cannot close.
Cut short: walk-dirty.glb | Closed cycle: walk.glb |
|---|---|
walk.glb is the clean control this fixture was cut from, not a repair of
walk-dirty.glb: it is there to show what the closed contract looks like.
AnimSmith does not re-author the endpoint pose.
Open the interactive report to scrub the exact frames the checks judged; the findings list on the right jumps the viewer to the seam.
What the finding looks like
$ animsmith --config examples/walk.animsmith.toml lint examples/assets/walk-dirty.glb
examples/assets/walk-dirty.glb:
error[loop-closure] clip 'walk' bone 'foot_r' @1.000s: loop does not close in position: bone 'foot_r' is 0.1581 m from its first-frame model-space position (cap 0.0100 m) (measured 0.1581, expected 0.0100)
error[loop-seam] clip 'walk' @1.000s: loop seam pops: wrap discontinuity is 6.82× the neighbouring in-clip step (cap 1.60) — the clip does not close its cycle (measured 6.8152, expected 1.6000)
error[loop-seam-vel] clip 'walk' bone 'foot_r' @1.000s: loop velocity changes at the seam: bone 'foot_r' differs by 0.7972 m/s between the incoming and outgoing model-space velocities (cap 0.1000 m/s) (measured 0.7972, expected 0.1000)
coverage[bind-pose] insufficient_rotation_evidence ×1 (scopes: first_frame_rest_delta; subjects: walk): only 0 usable first-frame rotation track(s); at least three are required
3 error(s), 0 warning(s), 0 note(s), 1 coverage gap(s), 0 available prediction facet(s), 0 required-unavailable prediction facet(s) # exits 1
The closed cycle passes the same contract. What remains is the fourth, narrower warning: this export repeats frame 0 at the authored endpoint.
$ animsmith --config examples/walk.animsmith.toml lint examples/assets/walk.glb
examples/assets/walk.glb:
warning[duplicate-loop-endpoint] clip 'walk' @1.000s: declared loop repeats its first pose at the authored endpoint: 1 redundant closing key(s) per track; ...
coverage[bind-pose] insufficient_rotation_evidence ×1 (scopes: first_frame_rest_delta; subjects: walk): only 0 usable first-frame rotation track(s); at least three are required
0 error(s), 1 warning(s), 0 note(s), 1 coverage gap(s), 0 available prediction facet(s), 0 required-unavailable prediction facet(s) # exits 0
What to do
- Nonzero position or rotation delta. Fix the export range or the endpoint pose in the DCC, then re-export. AnimSmith does not re-author poses.
- Zero pose delta, high velocity delta. Match the endpoint tangents on both sides of the seam in the DCC.
- Only a duplicated final frame. When
duplicate-loop-endpointwarns,animsmith transform --drop-duplicate-loop-endpointturns the clip into an open cycle losslessly. Read the loop contract againstloop_endpoint_modeafterwards:loop-closurecompares a repeated final sample with frame 0, so an open cycle intentionally does not keep it green.
Who fixes it: the artist, in the DCC. Only a strict redundant endpoint can be removed mechanically; pose, tangent and contact repair is DCC work, and loop policy (which clips loop, how strict the caps are) is a project decision recorded in the config. The gate closes when the declared closure and seam checks pass and the loop plays cleanly in the target engine's graph.
Config
Declare the clip as a loop; the caps below are the defaults, shown so you can tighten or loosen them per project.
[clips.walk]
loop = true
[checks.loop-closure]
max_position_delta_m = 0.01
max_rotation_delta_deg = 1.0
[checks.loop-seam-vel]
max_velocity_delta_mps = 0.1
[checks.loop-seam-rot]
max_angular_velocity_delta_degps = 5.0
Precise contract: the four seam checks, model space, endpoint modes, root-motion loops and the diagnostic split
A looping clip needs both pose and motion continuity at the wrap. A pose offset is a C0 discontinuity: the runtime jumps when it returns to frame 0. A matching pose with mismatched incoming and outgoing velocity is a C1 discontinuity: it reaches the right point but changes direction or speed abruptly, producing a hitch or pulse once per cycle. Unity's looping-clip guide shows the same artist-facing start/end match problem and the special treatment root-motion axes may need.
Animsmith separates four questions:
loop-closurefinds the largest last-to-first model-space position and shortest-path model-space rotation delta across all skeleton bones. The default caps are 0.01 m and 1 degree.loop-seam-velfinds the largest difference between the model-space linear velocity entering the last sample and leaving frame 0. Its default cap is 0.1 m/s. A closed triangle-wave trajectory demonstrates the problem: the first and last position are identical, but the bone reverses direction at the wrap.loop-seam-rotfinds the largest difference between the shortest-path model-space angular velocities entering the last sample and leaving frame 0. Its default cap is 5 deg/s. This is rotational C1 continuity: the pose can close rotationally (C0) yet still snap in direction or turn rate at the wrap when the two seam-adjacent rotation steps disagree.loop-seamremains the locomotion-specific test. It compares feet relative to hips and normalizes by the neighbouring stride step, so it needs resolved hips/foot roles and deliberately has no result for a stationary clip.
There is a fourth, narrower warning for an export-mode problem rather than a
pose-continuity judgment: duplicate-loop-endpoint. Many DCC workflows export
an inclusive range or bake a cycle by copying frame 0 again at the final frame.
For a declared loop, animsmith recognizes only the mechanically certain subset:
every authored channel has one common finite, strictly increasing timeline and
valid cardinality; first and final vector components match within 1e-5 and
quaternions match within a sign-invariant 1e-4 radians; and the clip has real
interior motion. A
stationary hold is therefore not mistaken for a cycle. The warning is
default-on, needs no rig roles, and is otherwise not applicable unless
loop = true is declared.
That duplicate can matter to an engine that loops by advancing over the clip
duration: it evaluates or holds the same pose twice before wrapping, which can
look like a one-frame hitch. transform --drop-duplicate-loop-endpoint turns
only an eligible candidate into an open cycle: it atomically removes the same
complete terminal key count from every channel (and cubic-spline key triplets),
preserves retained authored data, and re-pins duration. It does not repair a
nonclosing/root-travel clip, mismatched timelines, a C1 tangent problem,
retargeting damage, or a runtime loop blend. Blender's Cycles F-curve
modifier
and Unity's looping-clip guide
are useful places to inspect the authored range and engine import behavior.
An open cycle intentionally does not keep loop-closure green: that existing
inclusive check compares a repeated final sample with frame 0. The
loop_endpoint_mode measurement distinguishes strict duplicate endpoints,
non-duplicate closing cycles, and non-closing cycles for declared loops.
The first three checks are per-bone and role independent, so idle, guard,
block, aim-offset, facial, and prop loops remain testable without a humanoid
profile or detectable stride. Model-space is intentional: a parent mismatch
can move or rotate many descendants even when their local keys match. JSON
measurements retain a stable bone_index plus display bone_name for every
row, while findings name only the maximum offending bone for each judged
dimension.
Typical causes are an export range ending one frame early, a cycle modifier or procedural controller not being baked, copied endpoint keys whose Bezier tangents do not match, retargeting or resampling that changes one endpoint, and an engine importer using a different clip range from the DCC. A useful diagnostic split is:
- nonzero position/rotation delta: repair the endpoint pose or clip range;
- zero pose delta but high velocity delta: repair the endpoint tangents or the seam-adjacent keys;
- closed rotational pose but high angular-velocity delta: repair the endpoint rotation tangents or the seam-adjacent rotation keys using the intended shortest turn;
- many descendants reporting the same displacement: inspect their first mismatching ancestor;
- only
loop-seamfailing: inspect locomotion phase and foot/hips-relative motion rather than a stationary pose loop.
The normal fix is in the DCC: select the intended complete cycle, bake
procedural motion, make the last pose match the first where appropriate, match
the derivative on both sides, then re-export and rerun animsmith measure and
animsmith lint.
Blender's Cycles F-curve
modifier
can preview repeated curves, but the evaluated endpoint and tangents still
need to survive baking and export. Engine-side loop-pose blending can hide a
small mismatch at runtime; it does not make the source measurement close.
Root-motion locomotion is the important exception. Intentional horizontal root
travel does not return to its starting model-space position, and every child
inherits that travel. Tune the global max_position_delta_m, or the clip-level
max_loop_position_delta_m, to the contract; alternatively disable
loop-closure for such a pipeline. Keep using loop-seam for feet-relative
locomotion. loop-seam-vel and loop-seam-rot can still validate constant
inherited linear and angular motion respectively.
When one project contains both stationary idles and root-motion locomotion,
keep the global [checks.loop-closure], [checks.loop-seam-vel], and
[checks.loop-seam-rot] caps strict, then put a finite, non-negative
max_loop_position_delta_m, max_loop_rotation_delta_deg,
max_loop_velocity_delta_mps, or
max_loop_angular_velocity_delta_degps on the relevant [clips."run_*"]
family. A [clips.run_forward] entry wins over matching globs for only the
fields it supplies, so a one-off authored clip need not copy every inherited
value. This is a contract choice, not a repair: it does not make a
discontinuous source clip smooth.
There is no general automatic repair. transform --gait-anchor can rotate an
explicitly in-place locomotion cycle in time to choose a better stride cut; it
refuses accumulating root translation or yaw. Every nonconstant selected
Root/Hips trajectory channel must contain exactly one key at each declared
whole-frame sample; sparse, differently framed, duplicate-time, or off-grid
evidence refuses so the phase shift cannot synthesize values at omitted frames.
Duplicate (bone, property) channels refuse too. The safety grid refuses before
allocation when declared frames × skeleton bones, declared frames × tracks, or
maximum authored keys × skeleton bones exceeds 1,000,000 samples. It does not rewrite
arbitrary bone endpoint poses or tangents. Angular-velocity C1 continuity,
acceleration/jerk continuity, root-motion extraction policy, and runtime blend
settings remain out of scope.
All four checks are judged only on clips declared loop = true — whether a
clip is intended to loop is project knowledge — while raw measurements are
always available for measurable clips through animsmith measure. Workflow:
a project contract config
shows the same walk cycle passing clean and failing with a popped seam, and why
an undeclared loop is reported clean.
The character glides or runs in place
The character slides across the floor without its feet driving the motion, or it pumps its legs and never leaves the spot.
Checks: in-place ·
root-motion-speed
Why it happens
Exactly one thing should move the character horizontally: the controller, or the clip's own root motion. Both is a glide; neither is running in place. The file cannot tell you which was intended — a treadmill cycle and a travelling cycle are both valid glTF — so this symptom only becomes checkable once the project declares who owns each movement component.
What AnimSmith measures
The two synthetic cycles below are the same walk. One keeps its root under the character; the other carries the hips 1.2 m forward over the same second. The root-path figure is measured from the file, so a declared contract can be held against it.
In place: walk.glb | Travelling: walk-travel.glb |
|---|---|
Open the interactive report to read the measured trajectory beside the finding.
What the finding looks like
Declare that gameplay owns horizontal travel and the baked 1.2 m becomes a glide:
$ animsmith --config examples/walk-travel-in-place.animsmith.toml lint examples/assets/walk-travel.glb
examples/assets/walk-travel.glb:
error[in-place] clip 'walk_travel': declared in-place but the root travels at 1.20 m/s — the character will glide at runtime (measured 1.2000, expected 0.0000)
coverage[bind-pose] insufficient_rotation_evidence ×1 (scopes: first_frame_rest_delta; subjects: walk_travel): only 0 usable first-frame rotation track(s); at least three are required
1 error(s), 0 warning(s), 0 note(s), 1 coverage gap(s), 0 available prediction facet(s), 0 required-unavailable prediction facet(s) # exits 1
The same file, the same measured 1.2 m/s. Declare that the animation owns the travel and pin the speed the controller was built around, and the finding changes to a stale pin:
$ animsmith --config examples/walk-travel-root-motion.animsmith.toml lint examples/assets/walk-travel.glb
examples/assets/walk-travel.glb:
error[root-motion-speed] clip 'walk_travel': measured root-motion speed 1.20 m/s disagrees with the declared 1.00 ± 0.10 m/s — playback scaled by this pin will slide or moonwalk (measured 1.2000, expected 1.0000)
coverage[bind-pose] insufficient_rotation_evidence ×1 (scopes: first_frame_rest_delta; subjects: walk_travel): only 0 usable first-frame rotation track(s); at least three are required
1 error(s), 0 warning(s), 0 note(s), 1 coverage gap(s), 0 available prediction facet(s), 0 required-unavailable prediction facet(s) # exits 1
With no declaration at all the same file is silent. The measurement is always available; the contract is what makes it a finding.
What to do
- Decide ownership per component, once. XZ, Y and yaw are independent.
Declare each as
"gameplay"(the controller owns it) or"animation"(extracted root motion owns it), and configure the importer and controller to match. - Get the ground truth before pinning a speed. Run
animsmith measureand read the measured horizontal root speed; a pin copied from a design document is the usual cause of a staleroot-motion-speed. - Re-export when the file disagrees with the decision. AnimSmith does not
convert root motion to in-place motion, and
transform --gait-anchorrefuses accumulating root translation precisely so it cannot be misused for that.
Who fixes it: gameplay decides ownership and the artist re-exports. AnimSmith reports the disagreement between the declaration and the measurement, and has no automatic movement-policy repair. The gate closes when the declaration and the measurements agree and an engine trial proves exactly one — not zero, not two — movement producer per component.
Config
[rig]
# `in-place` and `root-motion-speed` need a resolvable root; both fall back
# to the hips role when the rig has no dedicated root bone.
profile = "auto"
# A treadmill cycle: the controller owns horizontal travel.
[clips.walk_travel]
movement_owner_xz = "gameplay"
# Or a travelling cycle, pinned to the speed the controller expects:
#
# [clips.walk_travel]
# movement_owner_xz = "animation"
# speed_mps = { value = 1.2, tolerance = 0.1 }
Precise contract: movement ownership, declared speed, and the root-role fallback
Locomotion clips carry a travel contract between the asset and the runtime, and nothing inside the file can verify it alone.
- Movement ownership. Declare XZ, Y, and yaw independently as
"gameplay"(the entity/controller owns that component) or"animation"(extracted root motion owns it). An in-place/treadmill clip normally uses XZ gameplay ownership; a travelling root-motion clip uses XZ animation ownership. Thein-placecheck compares only declared XZ ownership against measured horizontal root motion. Missing axes remain unspecified and are never inferred from another axis, a filename, or measured magnitude. - Declared speed drift. Runtimes scale playback by a clip's
declared locomotion speed to keep foot plants locked to world
velocity; a stale speed pin plays the clip visibly too fast or too
slow. The
root-motion-speedcheck compares the declaredspeed_mpsagainst the measured horizontal root displacement. Useanimsmith measureto obtain the ground-truth number before pinning it.
Both checks need a resolvable root: they use the rig profile's root
role, falling back to the hips role when no dedicated root bone
exists. That fallback matters in practice — the built-in mixamo
profile resolves mixamorig:* bone names but has no root role (Mixamo
rigs have no dedicated root bone), so root-motion checks on Mixamo
assets judge the hips track.
The legacy in_place boolean remains an XZ-only input alias (true is
gameplay, false is animation); one selector entry cannot declare both
spellings. The checks' prerequisites, configuration keys and gap semantics are
listed with every other check in the
built-in check reference.
Feet skate when clips blend
Each locomotion clip looks clean on its own, but the moment the runtime crossfades between two of them the feet skate, stutter or pop.
Checks: gait-group ·
sync-group ·
time-complement
Why it happens
A directional set — run forward, back, left, right — is blended at runtime, and a blend is only seamless when every member is at the same point in its stride. If one cycle's left foot plants at the top of the loop and another's plants a quarter cycle later, every mix of the two puts the feet somewhere neither clip authored. Nothing in a single clip is wrong; the incompatibility only exists between them, which is why it survives per-clip review.
What AnimSmith measures
The synthetic ring below holds four clips built from one analytic gait, with
run_left entered a quarter of the way into that gait. The stride anchor is
measured from the left-minus-right foot height relative to the hips, so the
shift is visible as the same curve starting at a different point.
The report draws one figure per declared gait group. Every member is on the
same normalized source-phase axis and none of them is shifted onto another:
aligning them would draw one curve four times and hide the very thing the
group is checked for. run_forward, run_backward and run_right share one
gait, so their curves coincide and their stride-anchor marks land together at
0.75; run_left runs a quarter cycle earlier and its mark sits at 0.50. The
shaded band is the declared 0.15 cap either side of the circular mean of the
measured anchors, and run_left is the anchor outside it. No member is
nominated as the reference: the check judges the set's spread, so the figure
names the members whose anchors fall outside the band and leaves the choice of
which one to re-anchor to the project.
Open the interactive report to play the two
members together at one phase; the with selector pairs any two members, and
both source times are labelled because the pairing is a presentation mapping
between two timelines rather than a retime. Enable illustrative blend and
move the weight slider to inspect a third pose computed from their sampled
local transforms. The source panes keep the judged frames; the third pane is
engine-agnostic presentation and adds no measured finding. It does not model
your engine's masks, controller, root-motion policy or contact handling. Check
the result in the intended runtime; zoom out if an interior pose extends beyond
the source-based camera fit.
What the finding looks like
$ animsmith --config examples/run-ring.animsmith.toml lint examples/assets/run-ring.glb
examples/assets/run-ring.glb:
error[gait-group]: gait group 'run-ring': stride-anchor phases spread by 0.20 cycle (cap 0.15) — directional blends between these clips will skate or pop. Measured: [run_forward=0.75, run_backward=0.75, run_left=0.50, run_right=0.75] (measured 0.1988, expected 0.1500)
coverage[bind-pose] insufficient_rotation_evidence ×4 (scopes: first_frame_rest_delta; subjects: run_backward, run_forward, run_left, run_right): only 0 usable first-frame rotation track(s); at least three are required
1 error(s), 0 warning(s), 0 note(s), 4 coverage gap(s), 0 available prediction facet(s), 0 required-unavailable prediction facet(s) # exits 1
The finding is group-level, not per-clip: it names the ring, prints every member's measured anchor, and identifies the one that disagrees. Linted without the config the same file is silent — a blend ring is a runtime intent, not something four clips reveal on their own.
What to do
- Declare the ring before you debug it.
[gait_groups.<name>]is what arms the check. Until the set exists in the config, its coherence is unverified rather than verified-clean. - Re-anchor an explicitly in-place cycle.
animsmith transform --gait-anchorrotates a cyclic clip in time so its stride anchor lands at t=0. Selecting it declares the clip in-place, and it refuses accumulating root translation or yaw. - Use runtime phase offsets for root motion. A travelling cycle keeps its trajectory; align the members in the blend graph instead of rewriting the clip.
- Check the timing surfaces too.
[sync_groups.<name>]holds same-time members to a shared duration, frame grid and endpoint convention, and the optionaltime_complementdiagnostic warns when a pair aligns materially better under reflected time.
Who fixes it: the technical animator declares the ring, and the DCC or the blend graph fixes the disagreement. AnimSmith can re-anchor an eligible in-place cycle and nothing else — mask topology, contact events and graph timing are project work. The gate closes when the declared ring lints clean and a playback capture covers the transitions and contacts on the intended character.
Config
[rig]
# The stride anchor is measured from left-minus-right foot height relative to
# the hips, so the group needs hips plus both foot roles.
profile = "auto"
[gait_groups.run-ring]
clips = ["run_forward", "run_backward", "run_left", "run_right"]
# Cycle fraction: members spread wider than this skate or pop when the runtime
# crossfades between them.
max_gait_phase_spread = 0.15
# Members with less left/right alternation than this are excluded as noise.
min_lr_amplitude_m = 0.03
[sync_groups.run-ring]
clips = ["run_forward", "run_backward", "run_left", "run_right"]
max_duration_delta_s = 0.001
max_frame_count_delta = 0
max_fps_delta = 0.01
[sync_groups.run-ring.time_complement]
min_reflected_time_advantage = 0.25
min_lr_amplitude_m = 0.03
Precise contract: the stride anchor, gait anchoring's refusals, time-complementary pairs, and per-direction travel speed
A directional locomotion set — run forward, back, left, right — is blended at runtime, and blending is only seamless when every member strides in phase. If one cycle's left foot plants at t=0 and another's at mid-cycle, every blend between them skates the feet.
The gait-group check holds a declared blend ring to a shared gait
phase (the stride anchor measured from the left−right foot-height
fundamental). Members with too little left/right alternation are
excluded from the spread — their phase is noise — and a member whose
gait cannot be measured at all is an error, so the group's coherence is
never silently unverified.
transform --gait-anchor is the matching repair-by-transform: it
rotates a cyclic clip so its stride anchor lands at t=0, aligning the
set member by member. Selecting it explicitly declares the clip in-place.
AnimSmith verifies the Root role (or Hips fallback) before rewriting and
refuses missing/non-finite evidence, horizontal endpoint displacement above 1
cm, or yaw accumulation above 1°. No interior step is subtracted as an
allowance. Every nonconstant channel the operation would rotate must contain
exactly one key at each declared whole-frame sample over the clip duration, at
the exact representable f32 key / fps time and period endpoint. Sparse,
differently framed, duplicate-time, or off-grid evidence refuses, as do
duplicate (bone, property) channels (including constant channels). Verification
samples those exact times and mutation uses an integer key-index permutation;
exempt constant-track endpoints cannot influence the period or shift. The whole
skeleton, roles, and track shapes are validated before declared frames ×
skeleton bones, declared frames × tracks, and maximum authored keys × skeleton
bones are independently bounded at an inclusive 1,000,000 samples. Yaw uses
f64 first/final headings plus counted full-turn crossings. At sample zero it
selects the local +Z, +Y, or +X basis axis with the greatest finite
horizontal projection, in that tie order, and retains it for the whole proof.
This accepts different source-axis conventions without switching axes later to
hide yaw; loss of the selected horizontal projection refuses. The calculation
avoids segment-count-dependent accumulation error. Four f32 successors at the
inclusive 1 cm and 1° caps cover only authored endpoint
translation/quaternion quantization. Do not apply it to
authored root motion: retain that trajectory, use
runtime phase offsets, or use a separately designed trajectory-preserving
operation. Gait anchoring does not convert root motion to in-place motion.
A blend pair is time-complementary
A pair can be individually clean yet unsuitable for a runtime that samples both clips at the same normalized time. One clip's left/right gait signal may align much better with the other at one minus normalized cycle time than at the same cycle time; blending them together then mixes different stride moments. Unity's Blend Tree guidance similarly calls for blended movements and foot contacts to occur at matching normalized times, while Godot documents explicit cyclic sync modes for keeping blend-space phases aligned.
time-complement compares every unordered pair using the existing
left-minus-right foot-height fundamental. It reports same-time and
reflected-time similarity on [0, 1] (higher is closer) and warns only when
the reflected score wins by more than the configured advantage. An exact-zero
left-minus-right swing has no phase subject even when the configured floor is
zero. Positive signals below the amplitude floor are coverage gaps, not
findings: near-idle or noisy motion does not carry enough phase evidence to
classify.
This warning belongs to the declared same-time/absolute-sync contract, not to
either animation in isolation. For a declared in-place ring that satisfies the
V1 contact-topology, root-trajectory, track, and proof constraints,
collection transform-foot-cycle
can retime every member to one explicit reference member's contact phases and
publish the independently proved generation. Unsupported rings remain
unchanged and need aligned contacts from the DCC, explicit contact/phase
markers, or a phase-remap strategy in the runtime. animsmith does not reverse
clips, choose the runtime strategy, rewrite root motion, or claim that
full-body motion is identical under time reflection.
Directional blend members travel at different speeds
Equal cycle duration does not imply equal travel. If two root-motion members have the same duration but different measured horizontal speeds, they cover different distances per cycle: their authored stride lengths differ. A diagonal faster than forward may be intentional, but a controller that assigns one gait-wide speed or normalizes diagonal input can then produce visibly faster diagonal travel or direction-dependent foot sliding.
Compare animsmith measure speed results across every declared directional
member and record the project's movement policy. Valid policies include
preserving per-direction authored velocities, scaling controller motion or
playback per member, or deliberately accepting the variation. If the project
requires uniform authored travel, re-time or re-author in the DCC and recheck
contacts, phase, and loop seams. Gait anchoring changes phase; it does not make
stride lengths coherent and must not be used to rewrite accumulating root
trajectories.
Speed variation alone is not a defect because the intended controller policy
does not live in the file. AnimSmith currently checks a clip against a declared
speed_mps but does not compare a runtime set against a declared cross-member
speed/stride policy; #411
tracks that evidence gap.
The checks' prerequisites, configuration keys and gap semantics are listed with every other check in the built-in check reference.
Feet slide within a clip
A planted foot drifts along the ground during stance, so the character looks like it is skating.
Checks: foot-slide
Why it happens
During stance the foot must move consistently with the clip's declared travel:
at speed_mps relative to the character for an in-place clip, or planted in
the world for a root-motion clip. Retargeting, resampling, a wrong speed pin,
or hand-keyed contacts break that relationship, and runtime IK and blend
band-aids exist to hide the result.
What AnimSmith measures
The synthetic gait below has a left foot that skates during stance. The report samples stance intervals with the run's contact threshold and shades them, so the slide is visible as foot motion inside a shaded interval.
Open the interactive report to
scrub the exact frames the checks judged; it opens on the first of the two
foot-slide findings. This clip declares a loop and a speed_mps pin, so the
gait panel's caption says its curves should end where they began and that the
shaded bands are the stance intervals foot-slide judged: a foot that moves
horizontally inside its band is the slide.
What the finding looks like
$ animsmith --config examples/report-comparison.animsmith.toml lint examples/assets/report-comparison-before.glb
examples/assets/report-comparison-before.glb:
error[loop-closure] clip 'acceptance-matrix' bone 'left_foot' @1.000s: loop does not close in position: bone 'left_foot' is 0.4000 m from its first-frame model-space position (cap 0.0100 m) (measured 0.4000, expected 0.0100)
error[loop-seam-vel] clip 'acceptance-matrix' bone 'left_foot' @1.000s: loop velocity changes at the seam: bone 'left_foot' differs by 1.3416 m/s between the incoming and outgoing model-space velocities (cap 0.1000 m/s) (measured 1.3416, expected 0.1000)
warning[foot-slide] clip 'acceptance-matrix' bone 'left_foot' @0.250s: left foot skates during stance: speed deviates 0.72 m/s from the expected 1.00 m/s (cap 0.30) — foot plants will slip at runtime (measured 0.7200, expected 0.3000)
warning[foot-slide] clip 'acceptance-matrix' bone 'right_foot' @1.000s: right foot skates during stance: speed deviates 0.88 m/s from the expected 1.00 m/s (cap 0.30) — foot plants will slip at runtime (measured 0.8800, expected 0.3000)
note[constant-track] clip 'acceptance-matrix' bone 'hand': rotation track has 5 keys but never moves — export bloat
coverage[bind-pose] insufficient_rotation_evidence ×1 (scopes: first_frame_rest_delta; subjects: acceptance-matrix): only 1 usable first-frame rotation track(s); at least three are required
2 error(s), 2 warning(s), 1 note(s), 1 coverage gap(s), 0 available prediction facet(s), 0 required-unavailable prediction facet(s) # exits 1
What the repair looks like
The after clip below is an authored repair: both sides come from this repository's fixture generator, and the after side is the same gait with those defects repaired. It is not the output of an AnimSmith transform, because AnimSmith does not re-author contacts.
Open the interactive comparison to see the two side by side; press play beside the shared phase to run them together, or tick Overlay after on before to put the two skeletons in one pane. The skeleton and the root path are the same on both sides, so what the panels show is the stance repair.
What AnimSmith itself does here is the measuring and one narrow transform: it
reports the slide with the stance intervals it judged, and
transform --gait-anchor can rotate an eligible in-place cycle's phase to
choose a better stride cut. Anchoring changes which frame the cycle starts on,
not how fast a planted foot travels, so it does not repair this finding.
Contact cleanup stays DCC work.
What to do
- Check the speed pin first. Run
animsmith measureon the clip and compare the measured horizontal root speed with the declaredspeed_mps; a stale pin produces a slide finding on a clean clip. - Re-author the contacts in the DCC. Plant the stance foot (or move it at the declared speed for an in-place cycle), then re-export and re-lint.
- Do not paper over it with runtime IK before the source is clean; the check is there so the band-aid stays optional.
Who fixes it: the artist. AnimSmith can report the slide and, for an eligible in-place gait, re-anchor the cycle, but contact cleanup and blend timing are DCC and runtime work. The gate closes when the clip re-lints clean under the declared contract and stance behaves in the actual blend.
Config
[rig]
roles = { root = "root", hips = "hips", left_foot = "left_foot", right_foot = "right_foot" }
[clips.acceptance-matrix]
loop = true
speed_mps = { value = 1.0, tolerance = 0.1 }
movement_owner_xz = "gameplay"
[checks.foot-slide]
contact_height_m = 0.03
max_slide_mps = 0.3
Precise contract: stance detection, the declared speed, and why this check is a warning
During stance — the part of the stride where a foot is planted — the
foot must move consistently with the clip's declared travel: at
speed_mps relative to the character for an in-place clip, or planted
in the world for a root-motion clip. Deviation is the skate that
runtime IK and blend band-aids exist to hide.
The foot-slide check measures stance-phase foot velocity against the
declared speed_mps and the clip's measured horizontal root speed. It is the
research-grade check of the catalog: contact
detection is heuristic, so it ships as a warning with generous
defaults, and is judged only on clips that declare speed_mps.
The check's prerequisites, configuration keys and gap semantics are listed with every other check in the built-in check reference.
A limb is T-posed, or a bone never moves
One arm hangs out sideways while the rest of the body animates, a socket is missing on the target rig, or the whole character deforms as if it were authored on somebody else's skeleton.
Checks: missing-bones ·
required-bones ·
frozen-bone ·
bind-pose
Why it happens
A bone can fail in four ways that all look alike in the engine: it is absent from the skeleton, it is present but carries no keys, it carries keys that never move it, or it moves relative to a bind pose that was not this rig's. A presence-only check passes the third case, and a motion-only check passes a socket that is meant to be static. What separates them is a declaration: which bones the clip is supposed to animate, and which must simply exist.
What AnimSmith measures
The synthetic walk below is declared to animate both arms. arm_l is keyed at
five identical values, and arm_r never reached the file at all. The report
plays back the exact frames the checks judged, so the still arm is visible in
the pose grid rather than inferred from the message.
Open the interactive report to scrub
the cycle and watch the left arm stay where it started; it opens on the
frozen-bone finding that names it.
What the finding looks like
$ animsmith --config examples/walk-frozen-arm.animsmith.toml lint examples/assets/walk-frozen-arm.glb
examples/assets/walk-frozen-arm.glb:
error[missing-bones] clip 'walk_frozen_arm' bone 'arm_r': required bone does not exist in the skeleton
error[frozen-bone] clip 'walk_frozen_arm' bone 'arm_l': required bone rotates only 0.00° over the clip (floor 1.00°) — frozen/T-posed limb or a wrong-source slice (measured 0.0000, expected 1.0000)
note[constant-track] clip 'walk_frozen_arm' bone 'arm_l': rotation track has 5 keys but never moves — export bloat
coverage[bind-pose] insufficient_rotation_evidence ×1 (scopes: first_frame_rest_delta; subjects: walk_frozen_arm): only 1 usable first-frame rotation track(s); at least three are required
2 error(s), 0 warning(s), 1 note(s), 1 coverage gap(s), 0 available prediction facet(s), 0 required-unavailable prediction facet(s) # exits 1
Without the declaration only the mechanical note survives. Nothing in the file says the arms were supposed to move, so nothing in the file can be wrong about it:
$ animsmith lint examples/assets/walk-frozen-arm.glb
examples/assets/walk-frozen-arm.glb:
note[constant-track] clip 'walk_frozen_arm' bone 'arm_l': rotation track has 5 keys but never moves — export bloat
coverage[bind-pose] insufficient_rotation_evidence ×1 (scopes: first_frame_rest_delta; subjects: walk_frozen_arm): only 1 usable first-frame rotation track(s); at least three are required
0 error(s), 0 warning(s), 1 note(s), 1 coverage gap(s), 0 available prediction facet(s), 0 required-unavailable prediction facet(s) # exits 0
What to do
- Declare what the clip must animate.
animates_bonesturns a silently static limb into a failing gate. It matches bone names, not rig roles, so it needs no profile. - Declare what must merely exist. Sockets, IK targets and mask bones are
intentionally static, so they belong in
[rig] required_bonesrather than in a per-clip motion rule. - Missing bone: repair the source rig. AnimSmith does not create bones, rename an export, or retarget a rig. Fix the export skeleton in the DCC and re-export.
- Keyed but frozen: find the slice that dropped it. A masked-out channel, a wrong-source slice, or a bake that never ran leaves keys that go nowhere.
- Wide bind deviation: check which rig it was authored against. A clip whose first frame is far from this skeleton's rest pose was almost certainly authored against a different bind and will deform incorrectly when retargeted onto this one.
Who fixes it: the artist repairs the source rig. AnimSmith reports structural absence, declared motion that never happened, and a first frame far from rest; target binding, Avatar/Skeleton setup and masks are engine work. The gate closes when a re-export meets the structural contract and the required bones visibly move on the target character.
Config
# Bones this clip must animate. Both checks match names, not roles.
[clips.walk_frozen_arm]
animates_bones = ["arm_l", "arm_r"]
[rig]
# Bones that must exist even though nothing is expected to move them.
required_bones = ["root", "weapon_socket", "ik_hand_l"]
[checks.frozen-bone]
# Real motion moves required bones tens of degrees; the default floor catches
# a truly static bone without flagging subtle idle sway.
min_rotation_deg = 1.0
Precise contract: the four rig-integrity failures, and what each one refuses to guess
Four related rig-integrity failures, in increasing subtlety:
-
A structural rig bone is absent or ambiguous. Runtime sockets, IK targets, mask bones, and attachment points can be intentionally static, so they do not belong in a per-clip motion rule. Put their exact names in
[rig] required_bones = ["weapon_socket", "ik_hand_l"].required-bonespasses a present static bone, errors for a missing name, and refuses to guess if duplicate skeleton names make the declaration ambiguous. It also reports an empty or absent skeleton as unable to meet a nonempty structural contract. This check does not create bones, rename an export, retarget a rig, or validate engine-side socket use: repair the source rig in the DCC and re-export. -
A declared bone is missing entirely. Bones the clip is declared to animate (via
animates_bonesin the config) must exist in the skeleton and carry at least one keyframed track. Themissing-bonescheck catches slices that accidentally dropped a channel — leaving a limb static — and exports against the wrong rig. -
A bone has keys but never moves. A required bone whose rotation never exceeds a floor is frozen: a T-posed limb, a wrong-source slice, or a masked-out channel that a presence-only check would pass. Real motion moves required bones tens of degrees; the
frozen-bonecheck's default 1° floor catches truly static bones without flagging subtle idle sway. -
The clip was authored against a different bind. A clip whose first frame deviates wildly from the skeleton's rest pose was almost certainly authored against a different bind — wrong seed rig, wrong export skeleton — and will deform incorrectly when retargeted onto this one. Small deviations are normal (few clips start exactly at rest); the
bind-posecheck fires only on a large mean deviation across the animated bones.
The checks' prerequisites, configuration keys and gap semantics are listed with every other check in the built-in check reference.
Files disagree about skeleton or clip identity
Two deliveries that were supposed to be the same character import as different
skeletons, or a report says Take 001 and nobody can tell which of the
thirty files in the pack it came from.
Commands: animsmith inspect ·
animsmith collection lint
Why it happens
Marketplace packs commonly ship one clip per file and reuse an embedded take
name such as Take 001 across every one of them, so a clip's display name is
not an identifier. Skeletons drift the same way: two exports of "the same" rig
can differ in hierarchy or rest signature and stop being exact-skeleton
interchangeable. Both are properties of a set of files, and AnimSmith's
ordinary checks resolve clips inside one loaded document — so this symptom is
answered by retaining identity, not by a check firing.
What AnimSmith measures
There is no chart here, because the evidence is identity rather than motion.
inspect reports the exact skeleton and the embedded clip names of one
physical file, and a collection manifest binds each (file, clip) pair to a
logical id that survives the display name.
The repository's own collection-boundary fixtures are shaped like the
marketplace case: walk-a.gltf and walk-b.gltf are distinct physical files
with identical bytes and the same embedded name.
$ animsmith inspect crates/animsmith/testdata/collection-spike/source/walk-a.gltf
crates/animsmith/testdata/collection-spike/source/walk-a.gltf
rig profile: none detected
skeleton: 1 bones
root
materials: 0
mesh instances: 0
clips: 1
Take 001: 0.000s, 0 tracks, 0 keys max # exits 0
$ animsmith inspect crates/animsmith/testdata/collection-spike/source/walk-b.gltf
crates/animsmith/testdata/collection-spike/source/walk-b.gltf
rig profile: none detected
skeleton: 1 bones
root
materials: 0
mesh instances: 0
clips: 1
Take 001: 0.000s, 0 tracks, 0 keys max # exits 0
Two files, one name. A third file in the same set carries Take 001 and
Take 002, so even the take index is only meaningful together with its file:
$ animsmith inspect crates/animsmith/testdata/collection-spike/source/multi.gltf
crates/animsmith/testdata/collection-spike/source/multi.gltf
rig profile: none detected
skeleton: 1 bones
root
materials: 0
mesh instances: 0
clips: 2
Take 001: 0.000s, 0 tracks, 0 keys max
Take 002: 0.000s, 0 tracks, 0 keys max # exits 0
What the evidence looks like
A collection manifest is what turns those rows into stable identities: each source keeps its safe locator and expected digest, and each logical clip names the source plus the exact take index and embedded name it came from.
[[sources]]
key = "walk-a"
path = "walk-a.gltf"
config = "fixture.animsmith.toml"
expected_sha256 = "277f55812602cc560dbb432dede43bb145b3caa6cb90493675442a8f5499f044"
[[clips]]
id = "com.example.collection-spike/locomotion/walk-a"
source = "walk-a"
take_index = 0
take_name = "Take 001"
animsmith collection lint runs every declared source and emits that retained
binding as machine-readable evidence — the source digest, the config basis,
the observed takes, and the runtime sets that cross file boundaries. The
fixture set above
is the worked example, and
collection contracts is the reference.
What to do
- Record the physical file, not the display name. A runtime-set member must preserve the exact source path plus the embedded clip name and index. Normalized display names are not reproducible identifiers.
- Reconcile the pack's own list separately. State it as a separate fact when a bundled animation list uses different spelling, casing, or ranges from the files; do not silently rename to make them match.
- Do not force a skeleton reference. A copied-avatar or skeleton-reference hierarchy mismatch means the referenced mapping is not evidence for that file. Use a compatible individual asset, or obtain an authoritative re-export.
- Keep retargeting a separate decision. An engine humanoid or retarget profile may still make two hierarchies compatible, but only after every required chain maps and the target character's deformation, transitions, masks, sockets and root ownership pass in the intended runtime.
Who fixes it: the pack owner and the pipeline that ingests it. AnimSmith retains identity and reports what each file actually contains; it does not merge, rename, or infer set membership, and it does not retarget. The gate closes when the recorded source-to-target mapping is complete and the target character plays the intended clips with visual evidence.
Config
Identity lives in the collection manifest rather than in animsmith.toml.
Every source keeps its own document-local config, so declaring a set never
changes what a single file means:
schema = "urn:animsmith:schema:collection-manifest:1"
schema_version = 1
collection_id = "com.example.collection-spike"
input_root = "source"
[[runtime_sets]]
id = "com.example.collection-spike/sets/cross-file-gait"
kind = "gait-group"
members = [
"com.example.collection-spike/locomotion/walk-a",
"com.example.collection-spike/locomotion/walk-b",
]
Precise contract: skeleton identity, file-scoped clip identity, and what stays per file
Two collection-level contracts are easy to confuse with single-clip rig health:
- Skeleton/retarget identity. Different bone hierarchies or rest/bind signatures are not exact-skeleton interchangeable. An engine humanoid or retarget profile may still make them compatible, but only after every required chain maps and target-character deformation, transitions, masks, sockets, and root ownership pass in the intended runtime. A copied-avatar or skeleton-reference hierarchy mismatch means the referenced mapping is not evidence for that file; use a compatible individual asset or obtain an authoritative re-export instead of forcing the reference.
- File-scoped clip identity. Marketplace packs commonly put one clip in
each file while reusing an embedded name such as
Take 001. A runtime-set member must then preserve the exact source file/path plus embedded clip name; normalized display names are not reproducible identifiers. Reconcile report members against the retained manifest and state separately when a bundled animation list uses different spelling, casing, or ranges.
AnimSmith's gait and sync groups still resolve clips inside one loaded document. The file-scoped collection identity and cross-file set contract are recorded in DESIGN.md Appendix F and emitted by the explicit collection command. The contact-fragment and transition-family additions are interchange declarations. The strict contact-fragment producer now emits one manifest-witnessed sidecar after reloading that source; it does not make ordinary checks cross-file or infer membership, and it adds no transform or runtime policy. Keep per-file evidence and the collection manifest authoritative; do not merge, rename, or infer set membership merely to make a check run.
The file is bloated, or the retargeter chokes
Clips are far larger than the motion in them justifies, a retargeter or importer mangles a character it should handle, or a weapon parented to a socket imports at the wrong size.
Checks: constant-track ·
scale-keys ·
non-uniform-scale ·
rest-world-scale ·
constant-nonunit-scale
Why it happens
"Key everything" exports and baked control rigs write channels that store hundreds of identical values, and every one of them still costs storage and work in every blend the runtime evaluates. Animated and non-uniform scale come from the same place — a constraint bake, a unit conversion, an unapplied object transform — and are the curves retargeters and physics are least consistent about. None of this is invalid, which is exactly why it accumulates: the file plays, so nobody looks.
What AnimSmith measures
The synthetic walk below stretches its pelvis to 1.2 on Y and back, and
carries a five-key weapon_socket translation channel that never moves. The
report separates the two facts: temporal scale variation, unequal axes, and
redundant data are three different findings about the same file.
Open the interactive report to read the three findings against the frames they were judged at.
What the finding looks like
These checks are mechanical, so they run with no configuration:
$ animsmith lint examples/assets/walk-scaled.glb
examples/assets/walk-scaled.glb:
warning[scale-keys] clip 'walk_scaled' bone 'pelvis': scale animation present — verify it is intentional; many rigs and retargeters mishandle animated scale
warning[non-uniform-scale] clip 'walk_scaled' bone 'pelvis' @0.500s: non-uniform scale present — verify it is intentional; many rigs and retargeters mishandle non-uniform scale
note[constant-track] clip 'walk_scaled' bone 'weapon_socket': translation track has 5 keys but never moves — export bloat
coverage[bind-pose] insufficient_rotation_evidence ×1 (scopes: first_frame_rest_delta; subjects: walk_scaled): only 0 usable first-frame rotation track(s); at least three are required
0 error(s), 2 warning(s), 1 note(s), 1 coverage gap(s), 0 available prediction facet(s), 0 required-unavailable prediction facet(s) # exits 0
The note is the only removable one. transform --prune-constant-tracks names
every channel it removes, with its original track index:
$ animsmith transform examples/assets/walk-scaled.glb -o compact.glb --prune-constant-tracks
constant-track removed 'walk_scaled': track index 3 bone 'weapon_socket' translation Linear 5 key(s)
wrote compact.glb (4 node(s), 1 clip(s), 0 mesh(es) / 0 position(s), 0 material(s)) # exits 0
$ animsmith lint compact.glb
compact.glb:
warning[scale-keys] clip 'walk_scaled' bone 'pelvis': scale animation present — verify it is intentional; many rigs and retargeters mishandle animated scale
warning[non-uniform-scale] clip 'walk_scaled' bone 'pelvis' @0.500s: non-uniform scale present — verify it is intentional; many rigs and retargeters mishandle non-uniform scale
coverage[bind-pose] insufficient_rotation_evidence ×1 (scopes: first_frame_rest_delta; subjects: walk_scaled): only 0 usable first-frame rotation track(s); at least three are required
0 error(s), 2 warning(s), 0 note(s), 1 coverage gap(s), 0 available prediction facet(s), 0 required-unavailable prediction facet(s) # exits 0
The two scale warnings survive on purpose. Animated scale is authored content, not bloat, and removing it is not a mechanical decision.
What to do
- Review transition coverage before pruning. Removing a constant channel
keeps that clip's standalone pose but makes its
(bone, property)coverage sparse. Leave tracks intact when a runtime transition does not explicitly reset the omitted property. - Decide whether the scale motion is intentional. Squash-and-stretch, a visibility trick and a gameplay deformation are all legitimate; a constraint bake, a unit-conversion curve and an unapplied object scale are not. Inspect the curve in the DCC's Graph Editor and remove or rebake only the unwanted channel.
- Check what a socket actually inherits. A node's local scale is not its
effective scale. Declare the attachment nodes your runtime contract cares
about in
[runtime_nodes] selectorsand letrest-world-scalereport the inherited factor. - Fix the source, then verify the export. Re-export, re-lint, and preview in the target engine; the desired end state is that deliberate scale motion remains and accidental curves are gone.
Who fixes it: the artist or the exporter settings. AnimSmith can remove provably constant multi-key tracks and nothing else — it does not judge a non-rest constant pin, rewrite cubic tangents, flatten skeletal scale into geometry, or decide whether an effect is artistically correct. The gate closes when a source recheck plus the target engine's own scale, attachment and visual observation agree.
Config
# Shared attachment/socket/IK policy. Each exact name or `*` glob must resolve
# to exactly one source node; a miss or multiple matches is a coverage gap.
[runtime_nodes]
selectors = ["weapon_socket", "ik_*_target"]
[checks.rest-world-scale]
expected_uniform_scale = 1.0
uniform_scale_tolerance = 0.0001
# Opt into a unit-scale policy only when the project expects unit scale in
# animation channels.
[checks.constant-nonunit-scale]
severity = "note" # or "warn" / "error" for your project
Precise contract: constant tracks, inherited rest-world scale, the five separated scale facts, and the pruning boundary
Export hygiene problems rarely break playback outright, which is why they accumulate:
- Constant tracks are export bloat. A multi-key track whose values
never move comes from unbaked rig channels, baked controls, or "key
everything" exports. It is harmless motion-wise but costs disk space and
work in every blend the runtime evaluates — the
constant-trackcheck reports it as a note, and the opt-in transform can remove candidates that are constant within the clip. Removal preserves that clip's standalone pose but makes its(bone, property)coverage sparse; leave tracks intact when a runtime transition does not explicitly reset omitted properties.
Attachment nodes and inherited rest-world scale
A node's local scale is not its effective scale. The
glTF node hierarchy
composes every ancestor transform, so a socket with local scale (1,1,1) can
still have rest-world scale 0.01 under a unit-conversion helper. Skinning may
look correct because inverse-bind matrices compensate when deforming mesh
vertices; an ordinary effect, collision shape, or weapon parented to that
socket does not automatically receive the same skinning compensation. It
usually inherits the node hierarchy, including the non-unit scale. This is the
same parent-scale failure mode described by Unity's
Transform documentation,
although the exact runtime consequences remain engine-specific.
Declare only source nodes your runtime contract cares about — the
config above is that declaration. The shared policy also supplies
future engine-unit-scale evaluation; rest-world-scale consumes the same
resolved set.
Each exact name or * glob must resolve to one named source node. A miss or
multiple matches is reported as a coverage gap, not guessed. A finding carries
an ancestor path with source indices and reports either the measured uniform
factor or the distinct non-uniform, sheared, reflected, or singular affine
class. The tolerance is inclusive for uniform factors. Unavailable/non-finite
rest evidence remains a coverage gap.
The older [checks.rest-world-scale].node_selectors field remains a
compatibility alias. Do not declare it together with [runtime_nodes].
Fix an unintended result in the source hierarchy or exporter, then rerun lint
against the exported asset. AnimSmith does not rescale the file, decide which
node names your project uses, infer units from mesh height, or predict a
runtime's whole attachment system. Animation-channel scale remains under
scale-keys, non-uniform-scale, and constant-nonunit-scale; this check
judges the static inherited rest domain only.
Why scale animation deserves its own review
A transform scale is a three-component value (x, y, z). A value of
(1, 1, 1) preserves the authored size, a uniform value such as (2, 2, 2)
doubles every axis, and a non-uniform value such as (1, 2, 1) stretches one
axis. The glTF animation model
allows a node's scale to be keyframed with STEP, LINEAR, or CUBICSPLINE
interpolation. Blender's
keyframe guide
explains the artist-facing version of the same idea: keys store property values
and interpolation curves determine every value between them.
Scale animation is not automatically invalid. It may be an intentional squash-and-stretch effect, visibility technique, or gameplay deformation. Unreal, for example, explicitly supports non-uniform scale animation and stores scale only for animations that need it. The warning exists because unintentional scale curves are also a common export artifact and because runtime consequences are project- and engine-dependent. Unity documents that non-uniform parent scale can skew rotated children and disagree with some collider shapes.
animsmith separates five facts so a team can make that policy decision without conflating them:
| Check | Literal fact | Typical source | Why review it |
|---|---|---|---|
scale-keys | At least one scale component changes over time after interpolation. | Intentional squash/stretch; constraint or retarget bake; unit-conversion keys; exporter-created curves. | It can change proportions, child placement, blending, physics assumptions, and animation storage. Confirm the motion is intentional in the target engine. |
non-uniform-scale | X, Y, and Z differ somewhere on the evaluated trajectory. | Stretching one bone axis; unapplied object scale; cubic interpolation overshoot between apparently harmless keys. | Parent/child hierarchies, normals, colliders, and engine components may treat non-uniform scale differently from uniform scale. |
constant-nonunit-scale | A scale channel or single-key pin stays away from (1, 1, 1). Disabled by default. | Unit conversion; a deliberately resized character; an unapplied static transform that survived into the rig. | Often harmless, sometimes a pipeline-policy violation. Enable it only when the project expects unit scale in animation channels. |
rest-world-scale | A selected source node's inherited rest-world affine scale differs from its configured uniform policy. Quiet until node selectors are supplied. | Unit-conversion ancestor; non-uniform or reflected helper hierarchy; unapplied object scale. | Runtime attachments can inherit this scale even when inverse binds make the skinned mesh look correct. |
constant-track | A multi-key track stores repeated values and never changes. | "Key everything" export, baked controls, or importer-generated constant curves. | It is redundant data even when its value is valid. Unity exposes a corresponding importer option to remove constant scale curves. |
Examples:
- Keys
(1,1,1) → (1.1,1.1,1.1) → (1,1,1)triggerscale-keysbut notnon-uniform-scale: the character grows uniformly and returns. - A constant
(1,1.2,1)channel triggersnon-uniform-scale, and triggersconstant-nonunit-scaleonly when that opt-in check is enabled. Multiple repeated keys also triggerconstant-track. - Dense
(1,1,1)keys triggerconstant-track, notscale-keys; there is no temporal scale motion. - Equal CUBICSPLINE key values can still trigger
scale-keyswhen their tangents move the curve between keys. Inspect the curve, not only the key diamonds.
Opting into a unit-scale policy is the constant-nonunit-scale severity in
the config above.
Fix the source, then verify the exported result
For an unintentional finding, inspect scale channels in the DCC's Graph Editor,
identify whether the curve belongs to a deform bone, control, helper, or object,
and remove or rebake only the unwanted channel. Check exporter options that key
all transforms or resample the FBX transform stack. Blender's
Apply transforms
can move object-level scale into object data before rigging, but its manual
explicitly warns that applying an armature object transform does not rewrite
pose animation curves or constraints. Do not treat Ctrl-A as a universal fix
for an already animated rig.
Re-export, rerun animsmith lint, and preview the result in the target engine.
The desired end state depends on intent:
- deliberate scale motion remains and is accepted by project policy;
- unnecessary dense keys are removed while the evaluated pose stays the same;
- accidental scale motion or non-uniformity is removed at its authoring source;
- a constant non-unit pin remains only when the rig/import contract requires it.
After the constant-track note identifies redundant multi-key data,
transform --prune-constant-tracks can remove flat
translation, rotation, or scale tracks (vector tolerance 1e-4,
sign-invariant rotation tolerance 1e-3 radians). This is useful when a DCC
keys every property or bakes controls into dense holds: the resulting clip has
the same standalone modeled motion with fewer evaluated channels and less
animation data. As above, the sparser (bone, property) coverage can change
runtime transition behavior when omitted properties are not explicitly reset.
It prints each exact original track index so you can compare the source and
result; review transition coverage, then re-lint and preview in the target
engine.
The transform refuses candidate tracks on animates_bones targets, when
removal changes sampled local TRS or model-space position/rotation, or when
removal would empty the clip. Single-key pins, malformed data, and cubic tangents that create
motion above tolerance are not candidates and remain unchanged. These cases
can carry semantics AnimSmith cannot safely erase. It does not model or remove
custom curves, judge a non-rest constant pin, reduce changing keys, rewrite
cubic tangents, perform DCC cleanup, flatten skeletal scale into mesh geometry,
retarget the clip, or decide whether an effect is artistically correct. Those
operations can change deformation and must stay in the DCC or an engine-aware
retarget/import pipeline. The checks turn exported facts into a reviewable work
order; they are not general animation cleanup.
For artists: from export to handoff
Where you are: Artist export → Contract → Developer intake → CI gate → Engine check
You have exported a clip, or cleaned one up, and somebody downstream has to trust it. This page is the order to do that in: keep the file you exported from, check what you actually shipped, make only the changes a tool can prove, and hand over the evidence with the asset instead of a verbal "it's fine".
It keeps the original immutable, makes mechanical changes reviewable, and leaves artistic approval where it belongs. For the meaning of the stages, start with why AnimSmith and the readiness ladder. When something already looks wrong in the engine, start from the symptoms instead and come back here to hand it off.
Outcome and boundary
AnimSmith can inspect source facts, measure and compare motion, evaluate a declared contract, safely repair two quaternion representations, and perform bounded mechanical transforms. It can also produce an offline HTML report. It does not decide whether a performance, retarget, contact, controller, or visual result is acceptable in a game. A clean lint result is scoped evidence, not a blanket “game-ready” verdict.
Keep these artifacts separate:
| Artifact | Owner | Rule |
|---|---|---|
| Raw delivery or DCC export | author, vendor, or capture team | Preserve immutably with provenance and export settings. |
| Candidate GLB/FBX | animation author | Regenerate or replace only after reviewing evidence. |
animsmith.toml | project | Declare only facts the project authoritatively owns. |
| Measurements, lint JSON, diff, and HTML report | review/build pipeline | Attach them to the candidate they describe. |
The broader storage split and its ownership rationale are in the pipeline guide.
Work the candidate, not the source
The commands below are executable examples. $ANIMSMITH, $ASSET_DIR,
$CONFIG_DIR, and $WORK_DIR are placeholders supplied by your checkout or
automation: the repository test binds them to the current binary, checked-in
self-authored fixtures, committed configs, and a disposable output directory.
The first comment gives the command's expected exit status; an exit of 1 can
be expected evidence that a candidate materially differs.
-
Inventory the untouched baseline. Run
inspectandmeasurebefore changing the file. Keep the JSON measurement output with the baseline.# workflow-exit: 0 $ANIMSMITH inspect "$ASSET_DIR/report-comparison-before.glb" $ANIMSMITH measure --format json "$ASSET_DIR/report-comparison-before.glb" -
Declare only project facts. Add loop, movement-owner, speed, rig-role, required-bone, and set membership declarations only after the team agrees they are true — who writes what says which of those you can settle alone and which need the game developer. The configuration reference owns every key and precedence rule; the contract-config cookbook is the runnable starting shape.
-
Lint and report the baseline. Treat a finding as a precise work order: its check id, measured value, subject, and coverage state say what was and was not evaluated. The built-in check reference owns the check contracts and the machine-output reference owns the JSON fields. Use the existing self-authored comparison pair when a reviewer needs a judged frame, skeleton, root/foot trails, or charts.
# workflow-exit: 0 $ANIMSMITH --config "$CONFIG_DIR/report-comparison.animsmith.toml" report \ "$ASSET_DIR/report-comparison-before.glb" \ --compare-after "$ASSET_DIR/report-comparison-after.glb" \ --before-clip acceptance-matrix --after-clip acceptance-matrix \ -o "$WORK_DIR/author-comparison.html" -
Choose the right remedy.
fixcan losslessly normalize finite quaternions and correct quaternion hemisphere continuity.transformcan make its explicitly bounded slice, hold, gait-anchor, duplicate-endpoint, or constant-track operation. Use the editing cookbook for those operations. Retargeting, pose/contact cleanup, motion redesign, and an intentional scale change remain DCC work; then export a new candidate. Importer settings and controller behavior remain engine/project work. A source delivery defect or missing contractual clip goes back to the vendor/source owner.Handed over: the finding as a work order, and a new candidate export back — the immutable source never moves. That round trip is the mocap cleanup gate.
-
Diff the candidate against the accepted baseline. A binary rewrite is not review evidence. Compare the measured effects and hand off the output with the candidate.
# workflow-exit: 1 $ANIMSMITH diff "$ASSET_DIR/report-comparison-before.glb" \ "$ASSET_DIR/report-comparison-after.glb" -
Hand off exact evidence. Include input identity, committed config, AnimSmith version, command, lint JSON (including coverage gaps), diff, and any generated report. State the remaining engine, gameplay, and visual gates instead of converting unknowns into a pass.
Handed over: the candidate plus that evidence bundle, to whoever accepts it — the receiving side runs the same contract, which is what outsourced-asset acceptance makes a two-sided gate rather than a request.
When to stop and route
| Evidence says | Next owner |
|---|---|
| A lossless quaternion repair or documented mechanical transform resolves the finding and the diff is understood. | AnimSmith workflow; re-lint and submit the candidate. |
| Pose, contacts, timing, retargeting, rig mapping, or authored scale must change. | DCC/animation author; export a new candidate. |
| The asset is mechanically sound but importer settings, masks, graph wiring, root-motion application, or target lookup fails. | Engine/project integrator; follow the game-developer workflow. |
| The delivered source cannot satisfy the agreed contract or is incomplete. | Vendor/source owner; return it with the evidence, as in outsourced-asset acceptance. |
| Runtime feel, readability, or style remains disputed after the technical evidence is clean. | Gameplay/art direction; perform project-owned visual acceptance. |
Next step
Use the symptom index for a symptom-first route, or hand the candidate to the game-developer intake workflow for the engine-observed gates.
For game developers: from pack to engine gate
Where you are: Artist export → Contract → Developer intake → CI gate → Engine check
A pack has landed, and somebody has to say whether it can ship in your game. This page is the order to find out in: inventory what the delivery really contains, pin the engine you are judging it against, settle who moves the character, gather the source evidence, and then close the question in the project itself.
It begins with source evidence and ends with an observed engine gate; AnimSmith predictions are useful prerequisites, not engine execution. When one clip already misbehaves, the symptoms pages route it faster than a full intake pass.
Intake path
-
Inventory the actual delivery. Keep source files immutable. Inspect every physical file, record logical clip names separately from file names, and classify the real runtime sets: locomotion rings, transitions, attacks, masked overlays, contacts, and interaction chains. A collection manifest can retain that physical-to-logical binding; see collection contracts.
Handed over: the untouched delivery, with its licence and vendor metadata, into an immutable raw location — marketplace-pack intake is the sorting pass that follows, and raw vs transformed artifact storage is where each artifact lives.
-
Choose the engine tuple before interpreting an engine check. An engine profile identifies one documented importer boundary. It does not select a skeleton, create a controller, retarget clips, or replace target-engine testing. Use the configuration reference for the profile schema and the maintained engine page for the exact tuple.
-
Make movement ownership explicit. For each locomotion clip, decide whether gameplay/controller code or extracted animation owns XZ, Y, and yaw movement. This is one of the surfaces only your project can settle; who writes what has the rest. Declare that contract, then inspect
in-place,root-motion-speed, and any applicable engine prediction. Do not apply extracted root motion and controller movement twice, or omit both. -
Run source evidence before conversion.
lintandreportestablish file and declared-contract evidence;measureexposes the facts used in a disagreement. The built-in check reference and output reference are canonical for those records. Convert or assemble only when the target boundary needs it, and retain the conversion/assembly evidence beside the derived artifact. -
Exercise the target project. Load the exact emitted artifact, play each intended clip, test skeleton/retarget mapping, blend/transition timing, masks and contacts, root-motion application, target lookup, and a visual gameplay scene. This is the gate that establishes engine-observed evidence.
Handed over: the exact emitted artifact and its evidence, to the project that will ship it. Once it is accepted there, every later re-export is judged by CI gating on animation changes against this same contract.
Worked path: Bevy 0.19.0, profile revision 3
The executable examples use the placeholder convention from the
animation-author workflow.
$FBX_FIXTURE is an authorized self-authored FBX input and $BEVY_CONFIG is
the exact TOML fence below, materialized by automation before the command runs.
This path is exact only for bevy / revision 3 / 0.19.0 /
gltf-asset-loader. It applies to glTF JSON or GLB, not FBX. Preserve an FBX
source, then convert the candidate at the format boundary before applying a
Bevy profile:
# workflow-exit: 0
$ANIMSMITH convert "$FBX_FIXTURE" -o "$WORK_DIR/candidate.glb"
The conversion is source-to-GLB evidence, not a Bevy import. Then use the
versioned configuration below; it is also committed as
examples/bevy-v3.animsmith.toml.
[engine]
profile = "bevy"
profile_revision = 3
engine_version = "0.19.0"
importer = "gltf-asset-loader"
[engine.settings]
extension_handler_environment = "bare_empty"
bevy_animation_feature = true
load_animations = true
Run the source-side gate and generate the neutral addressability inventory:
# workflow-exit: 1
$ANIMSMITH --config "$BEVY_CONFIG" lint \
--select engine-track-support "$ASSET_DIR/walk.glb" --format json
# workflow-exit: 0
$ANIMSMITH generate addressability "$ASSET_DIR/walk.glb"
The profile's engine-track-support prediction can prove only negative loader
gate outcomes from the declared feature/settings and bounded source
animation/channel inventory. If both gates allow loading, the correct result
is required-unavailable runtime survival: AnimSmith does not run Bevy, read
back a Bevy import, or prove that an asset, target, graph, or playback survived.
Likewise, addressability records source animation indices and canonical
selector evidence; it does not prove a live handle resolves in an application.
The Bevy profile guide
owns the precise rule and coverage boundary.
Close the Bevy gate in the project that will ship the asset:
- load
candidate.glbthrough the selected application path and record the loaded asset/animation evidence; - resolve the expected typed selector and verify the target mapping after the project's own scene root, names, and spawned hierarchy are present;
- create and exercise the application-owned
AnimationPlayer, graph/state transitions, masks, and reset policy; - make one explicit decision about each root-motion component, then test it against controller/physics movement so it is neither double-applied nor missing; and
- capture a visual gameplay trial for clip playback, blending, contacts, attachments, and scale.
Those are project-owned runtime and visual gates. Their recorded observations, not a prediction facet, are the evidence that closes them.
Other engine routes
Use the maintained profile page for each narrower route; do not copy settings from a different project or version.
| Target | Route |
|---|---|
| Unity 6000.3 | Unity profile guide: exact Generic revision-2 root-motion prediction or preserved Humanoid route, then Unity import/readback, Avatar, controller, and visual test. |
| Unreal Engine 5.8 | Unreal profile guide: FBX tuple, Skeleton and sequence boundary, then import, retarget/graph, root-motion, and visual test. |
| Godot 4.7 | Godot profile guide: glTF/GLB scene-import tuple, then scene import, retarget/AnimationTree, root motion, and visual test. |
| Custom glTF runtime | glTF and generic runtime guide: establish the consumer's own mapping and runtime gates. |
Close the intake decision
Keep four results separate: source mechanics, declared contract, engine observation, and gameplay/artistic acceptance. A pack can be usable for a prototype while a blend, retarget, mask/contact, or visual gate remains open; record the owner and the exact missing evidence. For commercial evidence that already exists, use the commercial-pack evaluation guide instead of treating a report as an approval for a different project.
Declaring the contract: who writes what
Where you are: Artist export → Contract → Developer intake → CI gate → Engine check
animsmith.toml is where a team writes down what a clip is supposed to
do. Mechanical checks need none of it — they run on any file. Everything
else waits for a declaration: with no loop = true, the seam checks have
nothing to judge and record themselves as not applicable, and a check that
needs a rig role it cannot resolve reports a typed coverage gap instead of
guessing.
So the question this page answers is not what the keys mean — the configuration reference is the exhaustive authority for that — but who is allowed to write each one. Some surfaces are facts of the export and an animator can declare them alone. Some are decisions only the game can make, and an animator who guesses at them writes a contract that fails for the wrong reason. The rest have to be agreed once and then committed beside the assets, like code.
The surfaces, and who owns each
"Owner" is who decides the value, not who types it: the file itself is committed once, reviewed like code, and read by everyone.
| Surface | What it declares | Who owns the value | Where the value comes from | Minimal example |
|---|---|---|---|---|
[rig] profile, [rig.roles] | Which bones are hips, feet, toes and root | Artist alone — a fact of the exported skeleton | animsmith inspect prints the profile it resolved | [rig]profile = "mixamo" |
[rig] required_bones | Bones that must exist even without keys | Together — the game names them, the export carries them | The engine project's sockets, masks and IK targets | [rig]required_bones = ["weapon_socket"] |
[clips."<name>"] loop, max_loop_position_delta_m and its three sibling caps | That the clip is a cycle, and how far its endpoints may drift | Artist alone for loop; the caps are a project bar set together | The take was authored as a cycle; the caps start at the defaults | [clips.walk]loop = true |
[clips."<name>"] duration_s, fps | The length and frame grid the clip owes | Together — an export setting against a gameplay expectation | The DCC export range, confirmed by animsmith measure | [clips.walk]fps = 30.0 |
[clips."<name>"] movement_owner_xz, movement_owner_y, movement_owner_yaw | Whether gameplay or the animation moves the character on each axis | Game developer alone — a character-controller decision | The engine project: whichever is actually driving movement | [clips.walk]movement_owner_xz = "gameplay" |
[clips."<name>"] speed_mps | The travel speed a root-motion clip promises | Together — the controller states it, the clip holds it | animsmith measure reports the travel; the pin is the agreed value | [clips.run]speed_mps = { value = 3.5, tolerance = 0.2 } |
[clips."<name>"] animates_bones | Bones this take must actually move, not merely carry | Together — gameplay needs the motion, the rig produces it | The engine project, checked against the DCC export | [clips.walk]animates_bones = ["arm_l"] |
[gait_groups."<name>"], [sync_groups."<name>"] | Which clips blend as one ring, and which play at the same time | Together — the technical animator declares the ring and its phase tolerance, the game developer confirms the runtime blend set | The blend graph or state machine that will crossfade them | [gait_groups.walk-ring]clips = ["walk_f", "walk_b"]max_gait_phase_spread = 0.15 |
[runtime_nodes] selectors | Attachment nodes whose rest-world scale the runtime relies on | Together — the game names the socket, the export scales it | The engine project's socket names | [runtime_nodes]selectors = ["weapon_socket"] |
[engine], [engine.settings] | Which importer boundary an engine prediction is about; all four selection keys are required together | Game developer alone — the shipping project's own tuple | The engine project: version, importer, import settings | [engine]profile = "bevy"profile_revision = 3engine_version = "0.19.0"importer = "gltf-asset-loader"[engine.settings]extension_handler_environment = "bare_empty"bevy_animation_feature = trueload_animations = true |
[checks.<id>] severity | What blocks the gate, what only reports, what is off | Together — it is the project's quality bar | A lint run shows what promoting a check would cost | [checks.quat-flip]severity = "note" |
Collection manifest, [transition_families] | Which files hold which clips, and which takes form a family | Together — the pack owner states what the delivery contains, the game developer binds it to runtime sets | The delivery itself; see collection contracts | See collection contracts |
Two rules follow from that table. An artist can go a long way alone:
a rig profile, loop, fps and animates_bones already arm the checks
that catch a popped seam, a short channel and a frozen arm. But
movement_owner_*, speed_mps and the blend groups are the game's
decisions — declared without the game developer, they measure the clip
against a guess.
Nothing is inferred from omission. Leaving loop out does not declare the
clip non-cyclic; it declares nothing, and the loop checks stay not
applicable. Ownership is the same: each movement axis is independent, and
an omitted one is never assumed.
A complete minimal contract
This is examples/walk.animsmith.toml,
the committed contract for the sample walk cycle in
first lint in 60 seconds — the smallest file that makes
the semantic checks fire:
# A runnable contract for the committed `walk.glb` / `walk-dirty.glb`
# example rigs — the smallest config that makes the semantic checks fire.
#
# animsmith lint --config examples/walk.animsmith.toml examples/assets/walk.glb
#
# See examples/character.animsmith.toml for the full game-character shape
# (locomotion rings, speed pins, per-check overrides).
[rig]
# The rig's bone names (pelvis / foot_l / foot_r) resolve the built-in
# ue-mannequin profile, so "auto" binds hips + feet with no inline map.
profile = "auto"
[clips.walk]
# Declaring the clip as a loop arms role-free per-bone C0/C1 checks plus the
# locomotion-specific feet-relative-to-hips loop-seam check.
loop = true
# Gameplay owns horizontal travel (the clip is a treadmill cycle), so
# `in-place` expects no measured XZ root travel.
movement_owner_xz = "gameplay"
# A per-clip cap can be stricter than a project-wide loop default. Omitted
# dimensions continue to inherit their global caps.
max_loop_rotation_delta_deg = 0.5
[checks.loop-seam]
# Fail a wrap discontinuity more than 1.6× the in-clip stride step.
max_ratio = 1.6
[checks.loop-closure]
# Model-space endpoint pose caps. Inherited root travel may require a larger
# position cap (or disabling this check) for extracted root-motion clips.
max_position_delta_m = 0.01
max_rotation_delta_deg = 1.0
[checks.loop-seam-vel]
# Fail when incoming/outgoing model-space linear velocity differs by >0.1 m/s.
max_velocity_delta_mps = 0.1
[checks.loop-seam-rot]
# Fail when shortest-path model-space angular velocity differs by >5 deg/s.
max_angular_velocity_delta_degps = 5.0
# The same three caps can live in a `[clips."name_*"]` glob. They do not
# configure angular seam velocity, which is outside these overrides.
Run it against the committed clip and the loop checks pass; run it against
the same clip cut a quarter-cycle short and they fail with the measured
distance. The examples cookbook
walks that pair, and
examples/character.animsmith.toml
grows the same shape to a full character with rings, speed pins and
per-check overrides.
Where to go next
The configuration reference owns every key, default, unit and precedence rule; the built-in check reference owns what each declaration arms. When a declaration turns out to be wrong rather than missing, the symptom index routes what you see in the engine to the page that walks it.
Raw asset to game-ready pipeline
Where you are: Artist export → Contract → Developer intake → CI gate → Engine check
Where animsmith fits in an animation asset pipeline.
This guide is the process-level companion to what game-ready means and the examples cookbook. That page defines the readiness ladder and explains why a check fires; the cookbook gives runnable command transcripts. This page shows how teams place those commands in the larger path from raw source animation to engine-facing, reviewable, CI-gated assets.
Which scenario belongs to which handoff
Every handoff the two reader workflows name lands in one of the scenarios below. Start from the row that matches the artifact currently moving:
| Handoff | What moves | Scenario |
|---|---|---|
| A vendor or marketplace pack arrives | The untouched delivery, with its licence and vendor metadata | Marketplace-pack intake |
| A finding goes back to the DCC and a new export comes out | The candidate GLB/FBX, beside the immutable source it came from | Mocap cleanup gate |
| An animator hands a candidate to the game developer | The candidate plus its lint JSON, diff and report | Outsourced-asset acceptance |
| An accepted asset enters the repository | The committed asset and the committed animsmith.toml | CI gating on animation changes |
| Anything above is stored | Raw source, work in progress, generated output, config and reports | Raw vs transformed artifact storage |
The common shape:
- Acquire the source asset.
- Preserve the raw source and provenance.
- Parse and inspect the file.
- Segment long takes into named clips.
- Decide root-motion policy per clip.
- Retarget, conform, or clean up in DCC tools.
- Validate motion semantics.
- Optimize the exported data.
- Export engine-facing artifacts.
- Gate and report changes in CI.
animsmith does not replace DCC work, engine importers, or a team's
contract format. It gives the pipeline a repeatable measurement and
linting step around them: inspect for inventory, measure for raw
numbers, lint for the declared contract, fix for safe repairs,
transform for mechanical edits, convert for FBX/glTF handoff,
diff for regression checks, and report for review artifacts.
Pipeline map
| Stage | Pipeline decision | animsmith workflow |
|---|---|---|
| Acquire | Is this source usable and redistributable? | Keep the vendor/source asset outside generated outputs; record license and intended rig/profile in project metadata. |
| Preserve raw | What must stay immutable? | Store raw FBX/glTF/GLB separately from any fix, transform, or convert outputs. |
| Inspect | What clips, bones, tracks, durations, and rig roles exist? | Use inspect for inventory and measure for metrics without judgment. |
| Segment | Which gameplay clips come from the take? | Use DCC slicing or mechanical transform slicing; declare clip names and frame policy in config. |
| Root motion | Who owns horizontal, vertical, and yaw world motion? | Pin [clips.<name>] movement_owner_xz, movement_owner_y, movement_owner_yaw, and speed_mps; verify horizontal intent with lint and measure trajectory evidence with measure. |
| Conform | What needs artistic cleanup or retargeting? | Use findings as DCC work orders; reserve fix and transform for safe mechanical operations only. |
| Validate | Does the clip meet the game contract? | Run config-backed lint; use --deny-warnings when warnings must block CI. |
| Optimize | Did cleanup change what matters? | Run lint for constant-track, scale-keys, and non-uniform-scale hygiene, then use diff for metric deltas that catch motion drift. |
| Export | Which artifact does the engine consume? | Use convert for FBX/glTF handoff; keep transformed GLB and any sidecars separate from raw source. |
| Gate/report | Can reviewers and automation trust the change? | Attach JSON or Markdown lint output to CI, and generate report artifacts for visual review. |
Bevy animation gate
When the target is Bevy 0.19.0's stock gltf-asset-loader, the current profile
revision 3 adds a deliberately narrow gate after source
inspection. engine-track-support binds raw animation rows and independent
per-animation channel coverage from the same load to the resolved
bevy_animation_feature and load_animations settings. The compiled feature
gate wins when the two settings disagree. A complete inventory reports only
negative dropped-row outcomes; a complete empty inventory is not applicable.
Partial or unavailable inventory reports exactly one subjectless, unsuppressible required-unavailable facet and no retained-prefix prediction, including bounded N+1 overflow. If both gates allow loading, the result is instead required-unavailable runtime-survival evidence: the pipeline must still exercise the actual Bevy application. This slice has no extension/construct coverage, no positive runtime-survival claim, and no content finding for a dropped row. Current output-v19 carries its immutable V5 provenance unchanged; output-v17 and output-v16 remain historical readers, and rev1/rev2 with output-v15 remain preserved for existing consumers.
Shared contract config
Every scenario below becomes much more useful once the team commits an
animsmith.toml next to the asset set. The exact tolerances belong to
your project, and the config is where the team records facts such as
which clips loop, who owns each clip's XZ/Y/yaw movement, what speed a locomotion clip
promises, which bones must move, and which clips form a directional
blend set. animsmith checks those facts against the measured asset. It
records typed, nonblocking coverage gaps when rig roles cannot be resolved,
rather than guessing.
Keep the process guide focused on where the contract is used. For the
canonical config shape, use the
project contract config cookbook,
the committed
examples/character.animsmith.toml,
and the configuration reference.
Cookbook routing
This guide keeps the pipeline shape here and the runnable transcripts in the command-owned references:
| Pipeline need | Recipe or reference |
|---|---|
| Inventory, measurement, lint, and exit-code gating | A first CLI gate |
| Mechanical slicing, hold extension, or gait anchoring | Editing a clip |
| Shared clip contract and severity policy | A project contract config |
| Conversion and HTML review reports | Converting exports and generating reports |
| JSON, Markdown, and schema details | Machine-readable output |
Scenario: marketplace-pack intake
Marketplace packs usually arrive as many clips with inconsistent naming, export settings, skeletons, and quality. The intake goal is to sort the pack into "usable now", "needs cleanup", and "reject or replace" before artists build gameplay on top of it — and to record what each verdict is based on.
Intake evidence comes from running animsmith on the actual delivered files. Vendor preview media and partial sample downloads cannot establish raw loop boundaries, complete blend-set compatibility, exact rig and channel behavior, or how a clip will integrate in your runtime. Treat vendor claims as context, keep them distinct from run-backed findings in the intake notes, and preserve open unknowns instead of rounding them to "fine".
Store the downloaded source in a raw, immutable location with its license and vendor metadata. Convert FBX assets into the format your pipeline uses, inspect representative files, measure the pack, then lint the batch before adding stricter project contract expectations. Use the conversion and reporting recipe for conversion and reports, the first CLI gate recipe for inventory and batch linting, and the project contract recipe once clip names and policies settle.
When physical files, repeated take names, and runtime sets are declared in a
collection manifest, retain the collection lint JSON and render a local
collection dashboard from that exact output. The dashboard is a current-state
inventory: it keeps unavailable and incomplete rows visible and can link
separately generated offline per-asset reports. Keep those reports and the raw
assets outside source control when they contain licensed material. Do not use
the dashboard as a quality score or an engine/artistic/gameplay acceptance
decision. Its versioned machine authority is
urn:animsmith:schema:collection-dashboard:1.
Observed physical takes remain visible under their source even when no logical
clip declaration binds them; their evidence contributes to the unfiltered
summary without creating a logical identity. Source-level findings and
subjectless required_prediction_unavailable facets without one exact witness
remain counted once at source scope rather than being guessed onto a take or
logical clip row.
Record intake as a coverage matrix over the representative files rather than one verdict per file: what was evaluated and clean, what was evaluated and produced findings, and what was not evaluated — because no expectations are declared yet, because a rig role did not resolve (the result carries a typed coverage gap), or because a check was disabled. Not-evaluated cells are neither pass nor fail; they stay visible until the contract grows to cover them or the team accepts them:
| File | Evaluated clean | Findings | Not evaluated |
|---|---|---|---|
run_forward.glb | mechanical checks except scale-keys | scale-keys warning | loop, speed, and gait checks — no expectations declared yet; opt-in scale-channel policy disabled |
idle.glb | mechanical checks, loop-seam | — | root-motion-speed — no speed_mps declared |
Use the first pass to catch obvious importer hazards: non-finite values, quaternion problems, inconsistent durations, scale keys, constant tracks, missing required bones, loop pops, wrong in-place/root-motion policy, and gait-phase drift in blend sets. When a finding requires artistic judgment, treat it as a DCC cleanup ticket instead of trying to auto-fix motion.
Scenario: mocap cleanup gate
Mocap data often moves through capture, solve, cleanup, retarget, slice, and export steps. The risk is subtle drift: the cleaned clip looks better, but a loop seam, stride phase, speed pin, or required-bone motion changed without anyone noticing.
Measure the raw solve before cleanup. Use transform only for
mechanical edits such as slicing, hold extension, or gait anchoring, then
measure and lint before and after DCC cleanup with the same config.
Compare the cleaned output against the pre-cleanup or last accepted
revision with diff. Use the first CLI gate recipe for measurement and
linting, the editing recipe for mechanical transform work, and the
project contract recipe for semantic checks.
diff is the key review step: it catches meaningful changes in the
measurements animators and gameplay programmers care about, rather than
asking reviewers to compare binary assets or trust a re-export.
Scenario: outsourced-asset acceptance
Outsourced animation acceptance works best when the vendor receives a machine-readable contract instead of a vague "make it game-ready" request. animsmith can be the acceptance gate both sides run locally.
Recommended contract package:
- The target skeleton or rig profile expectations.
- The committed
animsmith.toml. - A naming table for required clips.
- Required source and engine-facing artifact paths.
- The named cookbook recipe or wrapper script the vendor and the receiving team will run.
Use exit codes as the automation boundary:
| Exit code | Acceptance meaning |
|---|---|
| 0 | No failing findings and no required-unavailable engine prediction facets; warnings, notes, and ordinary coverage gaps may remain for review. |
| 1 | Rejected for a failing finding or any required_prediction_unavailable facet. |
| 2 | Delivery or command error: missing file, unreadable asset, bad config, or unsupported format. |
Exit 0 does not assert that every declared check evaluated. A
delivery whose rig the profile cannot resolve records role-dependent work as
coverage gaps and still exits 0, so an acceptance gate should also review
gaps and the resolved rig roles in the JSON output
before accepting. Required-unavailable engine prediction work is distinct from
those ordinary gaps: it exits 1 and cannot be suppressed by severity or
--allow.
Use the project contract recipe for the shared animsmith.toml, then
pair the first CLI gate recipe with the machine-readable output
reference for JSON lint output and exit-code gating.
For review, attach the JSON output to the ticket and generate an HTML report for clips with contested findings. The structured output makes it clear whether rejection came from a semantic contract failure such as a popped loop or from a mechanical problem such as bad quaternion keys.
If your acceptance system is already written in Rust, embed the check
catalog instead of shelling out. Load with animsmith-gltf or
animsmith-fbx, build Config from your own contract format, and map
Finding severities to your vendor workflow. The
embedding guide shows the API boundary.
Scenario: CI gating on animation changes
CI should answer two questions on every asset change: does the new asset still meet the declared contract, and did any important measurement move?
Run lint on changed clips with the project config, compare changed
clips against the previous accepted revision with diff, publish
human-readable review artifacts, and gate on process exit rather than by
scraping text. Use JSON for automation, Markdown and HTML for humans.
Use the first CLI gate recipe for CI lint, the project contract recipe
for config-backed checks, the conversion and reporting recipe for
report, and the machine-readable output reference for JSON and
Markdown outputs.
--deny-warnings is a project policy choice. Many teams start by gating
only errors, then promote warnings once the back catalog is clean.
Severity overrides in [checks.<id>] let a team make that transition one
check at a time without losing visibility.
Scenario: raw vs transformed artifact storage
A reliable pipeline treats raw source and engine-facing outputs as different artifacts with different ownership.
Use this split:
| Artifact | Ownership | Typical path | Notes |
|---|---|---|---|
| Raw source | Immutable vendor/capture/DCC input | assets/raw/vendor_pack/run_forward.fbx | Keep license, author, DCC version, export settings, and original units here. |
| Work-in-progress cleanup | Animator-owned intermediate | assets/work/run_forward.blend or assets/work/run_forward.ma | Artistic changes happen here, outside animsmith. |
| Mechanical transform output | Pipeline-generated | assets/generated/run_forward.glb | Produced by convert, fix, or transform; regenerate rather than hand-edit. |
| Contract config | Team-owned policy | assets/animsmith.toml | Version with the asset set; review changes like code. |
| Reports and measurements | CI/review artifacts | reports/run_forward.html, animsmith-lint.json | Usually generated per run, not committed unless the project wants baselines. |
This separation keeps fix and transform honest. Quaternion repairs,
frame slicing, hold extension, gait anchoring, and conversion are
mechanical steps whose results can be rechecked. Retargeting, pose edits,
contact cleanup, and motion redesign remain DCC work, then re-enter the
pipeline through measure, lint, and diff.
Where to go next
What game-ready means explains the runtime symptoms behind each check, and the examples cookbook turns the stages above into runnable commands. Everything else — embedding, the JSON envelope, the dated source research behind this process model — is routed from the all pages.
Engine profiles
- Unity 6000.3 profile guide — Configure the exact Unity Generic/Humanoid profiles, importer advice, root-motion choices, and scale boundary.
- Unreal Engine 5.8 profile guide — Map FBX animation, Skeleton, frame, unit, root-motion, and scale concerns to AnimSmith evidence without inventing importer settings.
- Godot 4.7 profile guide — Plan scene import, retargeting, animation slicing, root scale, and the current profile's explicit prediction boundary.
- Bevy 0.19.0 profile guide — Generate exact
Animation{i}selector evidence and keep runtime loading, graph, target, and scale responsibilities explicit. - glTF and generic runtime guide — Use the engine-neutral contract for custom runtimes, glTF units, source identity, scale repair, and downstream validation.
Unity 6000.3 animation profile
Use this page when FBX animation will be imported by Unity 6.3 LTS. AnimSmith has a current Unity Generic root-motion profile plus the preserved Humanoid profile:
| Rig path | Exact tuple |
|---|---|
| Generic | unity-generic / revision 2 / 6000.3 / fbx-model-importer |
| Humanoid | unity-humanoid / revision 1 / 6000.3 / fbx-model-importer |
These are importer contracts, not claims that every Unity 6000.3 project uses
the same rig, controller, physics setup, or root-motion policy. Both accept FBX
only. Revision 2 is the exact Generic contract used by the
engine-root-motion prediction; revision 1 remains readable for historical
advice artifacts. An engine profile never changes AnimSmith measurements.
What Unity expects
Unity's Rig tab maps a Generic rig through a chosen root node and a Humanoid rig through an Avatar. Its Animation tab defines clip ranges, loops, and whether rotation, vertical position, and XZ position are baked into the pose or extracted as root motion. A clean source therefore still needs an intentional rig type, Avatar/root selection, clip cuts, movement ownership, and controller setup.
The Model tab
documents three separate scale influences: source-file scale, Scale Factor,
and Transform scale. The preserved revision-1 advice profile models Convert Units and Bake Axis Conversion; Generic revision 2 deliberately does not
claim either setting. It models only the closed animation/root-motion controls
listed below, not Scale Factor, Avatar construction, or the imported hierarchy
that Unity ultimately creates.
AnimSmith checks and thresholds
The ordinary mechanical and contract-aware catalog runs unchanged under either Unity profile. The most importer-sensitive rows are:
| Check id | Exact default boundary | Why it matters in Unity |
|---|---|---|
rest-world-scale | selected node factor 1.0, inclusive tolerance 0.0001 | Finds inherited scale at sockets, IK targets, and attachment nodes before import. |
scale-keys | component range greater than 1e-4 | Flags animated scale that may complicate retargeting and attachments. |
non-uniform-scale | relative component spread greater than 1e-4 | Identifies non-uniform scale before Avatar/Transform composition. |
constant-nonunit-scale | unit deviation greater than 1e-4; off by default | Opt in when the project forbids retained constant scale channels. |
in-place | measured XZ root speed at least 0.5 m/s counts as travelling | Cross-checks declared gameplay-owned versus animation-owned XZ motion. |
loop-closure | 0.01 m position and 1.0° rotation | Checks declared loops before Unity's Loop Pose processing. |
loop-seam-vel / loop-seam-rot | 0.1 m/s / 5.0°/s | Finds velocity discontinuities that matching endpoint poses can hide. |
The current Unity-only check is engine-root-motion, and it is applicable only
for the exact Generic revision-2 tuple, an FBX source, and declared movement
ownership. engine-addressability remains a Bevy-only rule. The separate
generate import-advice contract still projects only settings declared under
the historical revision-1 Unity advice profile.
The ownership line is explicit: #267
provides the parent/rest-world/bind scale measurements,
#268 provides the selected-node
rest-world-scale finding, and #155
provides the Unity importer suggestion document. Static placement baking
(#224) and skinned rest/bind
reparameterization (#269)
are different repair domains.
Configure the exact importer contract
Every applicable revision-2 setting is required; the profile does not invent a default. The exact Generic root-motion tuple is:
[engine]
profile = "unity-generic"
profile_revision = 2
engine_version = "6000.3"
importer = "fbx-model-importer"
[engine.settings]
animation_type = "generic"
avatar_setup = "create_from_this_model"
import_animation = true
root_motion_source = "Reference/Root"
[clips."*".engine_settings]
root_rotation = "extract"
root_position_y = "bake"
root_position_xz = "extract"
[runtime_nodes]
selectors = ["weapon_socket", "ik_*_target"]
[checks.rest-world-scale]
expected_uniform_scale = 1.0
uniform_scale_tolerance = 0.0001
For Humanoid, select the preserved unity-humanoid revision-1 profile and omit
root_motion_source; that setting is not applicable to the Humanoid profile.
Generic revision 2 freezes animation_type = "generic",
avatar_setup = "create_from_this_model", and import_animation = true; other
values are rejected. The three clip-scoped bake or extract choices are
required for every actual Generic clip. The example above is a prediction
configuration, not a Unity project file.
Root-motion prediction
engine-root-motion compares one declared movement owner with one materialized
Unity clip setting for each declared axis: horizontal_xz uses
root_position_xz, vertical_y uses root_position_y, and yaw uses
root_rotation. bake means baked_into_pose; extract means
stored_as_root_motion. Gameplay ownership is compatible with baking, while
animation ownership is compatible with extraction. A conflict is an ordinary
error finding with a prediction_scope, not a rewritten measurement or an
automatic fix.
The check's lifecycle is explicit. A selected, enabled, applicable check emits
one available facet per clip/axis when all evidence is present. Its machine
result is RootMotionRouting with project_owner, importer_disposition, and
compatibility. A clip with no declared axis is not applicable for that axis.
Missing raw-path coverage, incomplete project intent, duplicate/overflowed
settings, a missing or ambiguous source path, a path that does not identify the
resolved Root role, or unavailable axis-specific trajectory evidence emits a
required_prediction_unavailable facet instead. Required-unavailable work is
not a content finding, cannot be suppressed with --allow, makes the check
not_evaluated when all work is unavailable (or partial when mixed), and
makes lint exit 1.
Project-intent construction is bounded at each retained source/declaration
prefix plus one overflow witness. An unmapped declaration tail is therefore
not silently treated as ownerless: NPlusOne intent work makes the exact
profile unavailable and produces one atomic summary. A complete, exhaustively
observed source/intent inventory with no declared owner remains genuinely not
applicable; path evidence is required once declared work exists.
The source path must resolve to the explicitly resolved Root role. This rule
does not use the consumer-neutral Hips fallback: a resolved Hips role cannot
stand in for a missing Root. Root trajectory measurements may be unavailable
for an individual axis, but their numeric magnitude is never an applicability
or routing threshold. A zero-travel clip and a travelling clip use the same
ownership/setting comparison when their required measurement availability is
measured.
Closed raw FBX path evidence
root_motion_source is a case-sensitive, byte-exact relative transform path.
The V1 grammar uses / as its only separator and has no escaping or Unicode
normalization. Paths must have nonempty segments; leading/trailing /, //,
. and .. segments, backslashes, control or Unicode format characters are
rejected. A segment is at most 1,024 UTF-8 bytes, a complete path at most 4,096
bytes, and a path at most 256 segments.
The FBX loader projects this path inventory from the same input bytes through a
raw-preserving ufbx load. Original source node identities, parent chains, and
names are retained; the implicit ufbx root and generated geometry/scale helper
nodes remain evidence but cannot satisfy a configured path. Complete coverage
is required to prove NoMatch; incomplete or unavailable coverage produces
CoverageIncomplete, never a guessed absence. This is raw-source evidence,
not a claim that Unity executed the import.
Generate the bounded advice document with the historical advice profile using:
animsmith --config unity.animsmith.toml generate import-advice character.fbx
The result maps the declared values to Unity importer properties. It does not assert that Unity imported successfully or that the imported motion matches the source visually. The root-motion prediction likewise performs no Unity editor execution, imported-asset readback, runtime playback, or engine certification. Repository and CI evidence for this slice uses only self-authored synthetic FBX fixtures; commercial animation packs are not fixtures or uploaded artifacts.
Common failures and fixes
| Symptom | Evidence to inspect | Correct owner |
|---|---|---|
| Character is 100 times too large or small | source units, rest-world-scale, Unity Scale Factor and Convert Units | Fix source units in the DCC or set the importer intentionally; do not infer a factor from height. |
| Prop or IK target is offset after import | rest-world-scale ancestry plus Unity root compensation | Remove a supported compensating skinned scale with scale rest-bind, or fix the hierarchy/import policy. |
| Character glides or runs in place | in-place, root-motion-speed, declared movement owners, three root extraction settings | Align clip intent, Unity bake/extract choices, and controller ownership. |
| Loop hitches once per cycle | duplicate-loop-endpoint, loop-closure, seam velocity checks | Correct the cut in the DCC or use the exact eligible endpoint transform; verify again in Unity. |
| Humanoid retarget differs from source | required-bones, bind-pose, scale checks, Unity import messages | Repair mapping/rest pose in the DCC or Avatar configuration; AnimSmith does not certify retarget quality. |
Scale and unit workflow
Unity documents one imported world unit as one metre for its physics-oriented scale convention. That does not mean every file should be multiplied until its root Transform reads one: source units, importer conversion, and inherited bone scale are separate domains. An inherited bone scale also multiplies descendant socket, attachment, IK, and collision-anchor offsets, authored animation translations, and measured root travel.
- If every represented length is wrong, use DCC unit correction or the
supported glTF/GLB
scale whole-documentoperation before producing the Unity FBX. FBX whole-document scaling is not supported. - If world geometry is already correct but one skinned hierarchy carries a
compensating uniform scale, use the explicit
scale rest-binddesign. FBX input is re-encoded to a proved GLB, so a downstream FBX requirement still needs a deliberate DCC/export step. convert --bake-static-mesh-transformsis for static, unskinned placement. It is not a substitute for skinned rest/bind reparameterization.
Before: a hand socket inherits scale 0.01, so a unit-sized prop or IK offset
is scaled unexpectedly. After a successful rest/bind operation: the measured
world geometry and joint trajectories are preserved while the selected
hierarchy is reparameterized to the declared factor. Still verify Avatar
mapping, attachments, collision anchors, root motion, compression, blending,
and a player build in Unity. See Scaling safely for the exact write
set and proof boundary.
Unreal Engine 5.8 animation profile
Use this page for the exact AnimSmith tuple unreal / revision 1 / 5.8 /
fbx-importer. V1 accepts FBX only.
What Unreal expects
Unreal imports skeletal motion as Animation Sequence assets tied to a Skeleton. The FBX animation pipeline documents FBX 2020.2 and requires an existing compatible Skeleton for an animation-only import. The Animation Sequences guide also documents frame ranges, sample rates, custom attributes, bone-name and hierarchy matching, and the requirement that animation end frames be whole.
The frozen profile knows that Unreal uses a left-handed, Z-up basis with +X forward and a centimetre distance unit. It also knows that the importer exposes unit and axis conversion controls. Those facts do not prove the exact imported hierarchy, conversion result, or runtime root-motion policy.
AnimSmith checks and thresholds
The normal check catalog is engine-neutral. Prioritize:
| Check id | Exact default boundary | Unreal-facing use |
|---|---|---|
fps | duration and keys must be within 0.1 frame of the explicitly declared frame grid | Detects source timing that disagrees with the project contract; it does not prove Unreal's imported frame range. |
loop-closure | 0.01 m and 1.0° | Checks declared loop endpoints before import/resampling. |
loop-seam-vel / loop-seam-rot | 0.1 m/s / 5.0°/s | Finds a wrap pulse after pose equality. |
scale-keys | component range greater than 1e-4 | Makes imported scale tracks explicit; Unreal may store non-unit scale only when present. |
non-uniform-scale | relative spread greater than 1e-4 | Flags a portability and retargeting risk even though Unreal can import scale animation. |
rest-world-scale | selected node factor 1.0 ± 0.0001 inclusive | Finds inherited socket, attachment, IK, and collision-anchor scale. |
in-place | XZ speed at least 0.5 m/s counts as travelling | Cross-checks whether gameplay or animation owns horizontal motion. |
For the frozen Unreal revision 1 / 5.8 / fbx-importer profile,
engine-clip-boundary evaluates each retained FBX animation stack against the
documented whole-end-frame requirement. The FBX adapter retains the
parser-resolved absolute KTime end coordinate and exact frame period; the
format-neutral core records that evidence without converting it through seconds
or decimal FPS. A complete selected range whose end coordinate is off that
integer lattice is a finding. Missing, partial, or unavailable timing evidence
is a required-unavailable facet, never a guessed frame number. This bounded
check does not predict Animation Length, Frame Import Range, resampling,
compression, root-motion, or any other Unreal import setting.
engine-addressability remains Bevy-only.
Scale evidence and policy stay separated: #267 owns the measured scale domains and #268 owns the selected-node finding. The #155 advice contract reports an honest refusal for this profile. Static placement baking #224 does not replace the skinned rest/bind design #269.
Configure the profile
[engine]
profile = "unreal"
profile_revision = 1
engine_version = "5.8"
importer = "fbx-importer"
[clips."locomotion_*"]
loop = true
movement_owner_xz = "animation"
max_loop_position_delta_m = 0.01
max_loop_rotation_delta_deg = 1.0
Unreal revision 1 has no materialized setting vocabulary. Supplying
[engine.settings] or clip engine settings is an error. generate import-advice returns the typed profile_settings_unmodeled refusal rather
than inventing Convert Scene Unit, frame ranges, sample rates, or root-motion
settings.
Revision 2 import advice
The exact revision-2 advice tuple is unreal / 2 / 5.8 / fbx-importer.
It accepts FBX only and has one required document setting. There is no safe
profile default, so the setting must be explicit:
[engine]
profile = "unreal"
profile_revision = 2
engine_version = "5.8"
importer = "fbx-importer"
[engine.settings]
sample_rate = "default_30" # or "source_determined"
# sample_rate = "custom_hz(60)" # 1..48000
The closed sample-rate domain is default_30, source_determined, or
custom_hz(n) with n from 1 through 48,000. The unreal_fbx_import_data
projection in urn:animsmith:schema:engine-import-advice:2
projection emits bUseDefaultSampleRate=true for default_30; the other two
forms emit false and a CustomSampleRate value (0 for
source_determined, otherwise the explicit hertz). No frame range, unit
conversion, skeleton, compression, root-motion, or runtime result is inferred.
The advice is a same-load projection of the declared setting, not an Unreal
import execution or certification.
Common failures and fixes
| Symptom | Evidence to inspect | Correct owner |
|---|---|---|
| Animation is the wrong physical size | source units, Unreal Convert Scene Unit, rest-world-scale | Author centimetre/metre intent in the DCC or choose the importer option explicitly. |
| Sequence truncates or imports the wrong range | source take range, exact end-frame facet, Unreal Animation Length/Frame Import Range | Fix the exact frame cut in the DCC or importer; the bounded Unreal check only tests the source end-frame lattice. |
| Animation-only import cannot bind | required-bones, missing-bones, hierarchy and names | Export against the target Skeleton or retarget in Unreal/DCC. |
| Root motion does not drive the actor | in-place, root-motion-speed, root role evidence | Ensure a root bone carries the intended motion and configure the Animation Sequence/graph. |
| Socket, IK, or collision attachment scales incorrectly | rest-world-scale source path and ancestry | Repair inherited scale or use supported rest/bind reparameterization, then retest in Unreal. |
Scale and unit workflow
Unreal's units guide
uses centimetres for distance by default, while AnimSmith measurements are
normalized to metres. Compare physical quantities, not the raw number printed
in two different unit systems. The FBX import option Convert Scene Unit
converts source units to centimetres.
Inherited bone scale also multiplies descendant sockets, attachments, IK and
collision anchors, animation translations, and root-motion distance.
Use scale whole-document only for self-contained glTF/GLB whose entire
represented length domain is wrong. Use scale rest-bind when world geometry
is correct but a selected skinned hierarchy carries a compensating uniform
scale; narrow FBX rest/bind produces GLB. Use static transform baking only for
unskinned placement. None of these operations chooses Unreal Skeleton,
retarget, root-motion extraction, socket, IK, collision, or compression policy.
Before: a weapon socket inherits 100 while the mesh is visually compensated.
After rest/bind: the proved world geometry and trajectories are unchanged and
the selected hierarchy is canonicalized. Unreal import, socket attachment,
physics asset, root motion, retargeting, and runtime playback still require
engine validation. See Scaling safely.
Godot 4.7 animation profile
Use this page for godot / revision 1 / 4.7 /
resource-importer-scene. The frozen profile accepts glTF JSON, GLB, and FBX.
That is AnimSmith's evidence boundary, not a complete list of formats Godot can
import.
What Godot expects
Godot's scene importer turns a source scene into Godot nodes and resources. Its advanced settings can slice animation timelines, optimize tracks, save animations externally, and configure subresources. Retargeting 3D skeletons depends on a BoneMap, compatible hierarchy, and aligned rest poses; matching bone names alone is not enough.
Godot documents a right-handed, Y-up, metre-oriented 3D convention. Its
nodes/root_scale defaults to 1.0; nodes/apply_root_scale decides whether
that value is applied into descendants, meshes, animations, and bones or left
on the root node. AnimSmith revision 1 does not model either setting or predict
the resulting imported hierarchy.
AnimSmith checks and thresholds
All ordinary checks remain engine-neutral. The most relevant are:
| Check id | Exact default boundary | Godot-facing use |
|---|---|---|
rest-world-scale | selected node factor 1.0 ± 0.0001 inclusive | Finds inherited scale before choosing root_scale/apply_root_scale. |
scale-keys | component range greater than 1e-4 | Finds scale animation that can complicate retargeting. |
non-uniform-scale | relative spread greater than 1e-4 | Flags a hierarchy/physics/attachment risk. |
constant-nonunit-scale | unit deviation greater than 1e-4; off by default | Enforce a project policy against retained scale channels. |
bind-pose | mean first-frame/rest deviation greater than 45° | Surfaces a likely rest-pose mismatch before BoneMap retargeting. |
frozen-bone | required bone rotation never exceeds 1° | Finds wrongly mapped or static required roles. |
loop-closure | 0.01 m and 1.0° | Checks a declared source loop before Godot slicing/optimization. |
engine-addressability is Bevy-only. Godot has no engine-specific prediction
check in V1.
The measured parent/rest-world/bind domains come from #267, while #268 owns the selected-node finding. The #155 advice contract refuses rather than inventing Godot settings. Static placement baking #224 and the skinned rest/bind design #269 solve different transform problems.
Configure the profile
[engine]
profile = "godot"
profile_revision = 1
engine_version = "4.7"
importer = "resource-importer-scene"
[runtime_nodes]
selectors = ["weapon_socket", "ik_*_target"]
[checks.rest-world-scale]
expected_uniform_scale = 1.0
uniform_scale_tolerance = 0.0001
Godot revision 1 has no setting descriptors. Any engine setting is rejected,
and generate import-advice emits profile_settings_unmodeled. Configure
timeline slices, root scale, animation optimization, external animation files,
BoneMap, and root-motion behavior in Godot or the source DCC.
Revision 2 import advice
The exact revision-2 advice tuple is godot / 2 / 4.7 /
resource-importer-scene. It accepts only glTF JSON and GLB for this
document-level projection. The closed document setting vocabulary is:
[engine]
profile = "godot"
profile_revision = 2
engine_version = "4.7"
importer = "resource-importer-scene"
[engine.settings]
animation_fps = 30 # positive integer; 1..120
animation_trimming = false
animation_fps defaults to the verified profile value 30 when omitted and
animation_trimming defaults to false; explicit values retain their
explicit_config origin in the advice. The JSON prediction projects exactly
animation/fps and animation/trimming under the godot_params projection
kind in urn:animsmith:schema:engine-import-advice:2. It does not emit root
scale, optimization, external-animation, BoneMap,
timeline-slice, or runtime playback conclusions. Values are proposed importer
parameters derived from same-load evidence, not a Godot import execution or
certification.
Common failures and fixes
| Symptom | Evidence to inspect | Correct owner |
|---|---|---|
| Imported character has the wrong size | source units, rest-world-scale, Godot root_scale and apply_root_scale | Fix DCC units or set the importer deliberately; avoid stacked compensation. |
| Socket, IK target, or collision anchor is offset | selected-node ancestry and affine class | Repair the hierarchy or canonicalize a supported rest/bind scale; verify after import. |
| Retarget pose twists or limbs drift | required-bones, bind-pose, first-frame and hierarchy evidence | Align rest pose/BoneMap in Godot or DCC. |
| One long source timeline is not usable as clips | source take inventory and intended cuts | Define slices in Advanced Import Settings or author separate clips in the DCC. |
| Loop pops after optimization | loop closure and velocity checks | Repair the cut/source curves, then verify the imported optimized animation. |
Scale and unit workflow
Godot's 3D introduction states that 3D uses metres and recommends authoring at the correct scale because post-import scaling can create precision problems. Keep three decisions separate:
- Whole-file unit correction changes every represented length and is supported
by
scale whole-documentonly for self-contained glTF/GLB. - Skinned rest/bind reparameterization preserves world geometry while removing one declared compensating hierarchy scale; glTF/GLB and the narrow FBX-to-GLB path are supported.
nodes/root_scaleis a Godot importer choice. AnimSmith does not emit it.
An inherited bone scale multiplies descendant sockets, attachments, IK and collision anchors, animation translations, and root-motion distance no matter which layer introduced the compensation.
Static transform baking fixes only supported unskinned placement. It cannot repair a Skeleton3D/BoneMap contract. After any source rewrite, retest attachments, IK, collision shapes, retarget pose, root motion, animation slices, and runtime playback in Godot. See Scaling safely.
Bevy 0.19.0 animation profile
Use this page for the exact bevy / 0.19.0 / gltf-asset-loader
profiles. Revision 1 owns animation-label addressability. Revision 2 owns the
unit/effective-scale prediction substrate and its version-pinned load settings.
Revision 3 is the current slice for animation/channel gate support and the
separately versioned rich addressability-rule bundle. All three
accept glTF JSON and GLB only; none applies to a later Bevy
release.
What Bevy expects
Bevy loads glTF subassets through typed labels. In 0.19.0,
GltfAssetLabel::Animation(index)
formats as Animation{index}. An animation name is separate optional metadata,
not part of that typed selector. Runtime playback still requires the relevant
animation feature/settings, loaded assets, an AnimationPlayer, targets, and
an animation graph or equivalent application setup.
The frozen profile also records that animation target ids are derived from name paths. Revision 3's separately versioned rich addressability bundle emits bounded predictions for those paths, named-map winners, scene/skin labels, and the optional default-scene route. It does not emit graph templates, extension support, or proof that a runtime asset exists.
AnimSmith checks and thresholds
| Check id | Exact boundary | Bevy-facing use |
|---|---|---|
engine-addressability | no numeric tolerance; exactly one Animation{i} facet per completely inventoried source animation | Predicts the canonical typed label spelling from source order. Partial inventory produces one unsuppressible required-unavailable inventory facet. |
engine-unit-scale | exact revision-2 profile facts and settings; no tolerance and no content finding | Emits exact 1:1 glTF-metre to Bevy world-length-unit mapping plus distinct loader-scene, primitive-child, and selected-source-node affine classifications. Missing required evidence is unsuppressible required-unavailable. |
engine-track-support | exact revision-3 gate settings and bounded same-load animation/channel inventory; no content finding | Emits only negative gate outcomes for source animation/channel rows, or required-unavailable evidence when inventory is incomplete or both gates allow loading. Extensions, other constructs, and positive runtime survival are outside this slice. |
engine-addressability (rich V2) | exact revision-3 Bevy addressability-rule bundle; no numeric tolerance or content finding | Emits typed scene, default-scene, skin/IBM, named-map, and target path/UUID projections beside the unchanged Animation{i} facets. Incomplete, unreachable, multiply reachable, feature-disabled, or colliding work is required-unavailable. |
rest-world-scale | selected node factor 1.0 ± 0.0001 inclusive | Finds authored inherited scale at attachment/IK nodes; it does not predict Bevy's imported result. |
scale-keys | component range greater than 1e-4 | Finds animated scale before runtime transform propagation. |
non-uniform-scale | relative spread greater than 1e-4 | Flags possible shear/attachment/physics consequences in composed transforms. |
loop-closure | 0.01 m and 1.0° | Checks declared loops before graph playback. |
in-place | XZ speed at least 0.5 m/s counts as travelling | Makes gameplay versus animation movement ownership explicit. |
The rest of the check catalog is unchanged by the profile.
For scale, #267 owns the measurement domains and #268 owns the selected-node finding. The #155 import-advice work has no Bevy payload. Static placement baking #224 and skinned rest/bind reparameterization #269 remain separate from selector generation.
Configure and generate selectors
[engine]
profile = "bevy"
profile_revision = 1
engine_version = "0.19.0"
importer = "gltf-asset-loader"
[runtime_nodes]
selectors = ["weapon_socket", "ik_*_target"]
[checks.rest-world-scale]
expected_uniform_scale = 1.0
uniform_scale_tolerance = 0.0001
animsmith --config bevy.animsmith.toml lint --select engine-addressability character.glb
animsmith --config bevy.animsmith.toml generate addressability character.glb
The standalone addressability inventory retains source animation/channel indices, optional names, raw targets/accessors, and dependency closure. The optional Bevy section uses the same existing check. Named, unnamed, and duplicate-named animations remain distinct because source index is the authority. The selector can change when animation array order changes.
Bevy revision 1 has no settings vocabulary, so [engine.settings] and clip
engine settings are invalid. generate import-advice is not a Bevy settings
generator.
For unit/effective-scale prediction, select revision 2 and declare the two required environment facts. Defaults are retained with their origin; list them explicitly only when the load differs:
[engine]
profile = "bevy"
profile_revision = 2
engine_version = "0.19.0"
importer = "gltf-asset-loader"
[engine.settings]
extension_handler_environment = "bare_empty" # or "bevy_pbr_stock_0_19"
bevy_animation_feature = true
rotate_scene_entity = false # default
rotate_meshes = false # default
load_meshes = "nonempty" # default; or "empty"
load_animations = true # default
[runtime_nodes]
selectors = ["weapon_socket", "ik_*_target"]
animsmith --config examples/bevy-v2.animsmith.toml lint \
--select engine-unit-scale character.glb --format json
The handler environment is closed to an empty registry or exactly Bevy 0.19's
stock PBR handler. Arbitrary application handlers are not approximated. With
load_meshes = "empty", the rule reports each otherwise-created primitive
child as suppressed while retaining the source-node entity semantics.
Revision 3: animation/channel gate support
Revision 3 preserves the revision-2 Bevy tuple and adds the narrow
engine-track-support check. The only new settings are the document-scoped
bevy_animation_feature and load_animations, each recorded with its
explicit/default origin:
[engine]
profile = "bevy"
profile_revision = 3
engine_version = "0.19.0"
importer = "gltf-asset-loader"
[engine.settings]
extension_handler_environment = "bare_empty" # or "bevy_pbr_stock_0_19"
bevy_animation_feature = true
load_animations = true
The compiled bevy_animation feature gate has precedence over
load_animations. A disabled feature gate therefore predicts a negative drop
even when load_animations = true; with the feature enabled, a false
load_animations setting predicts the corresponding loader drop. These are
negative importer outcomes, not content findings. If both gates allow loading,
the result is a stable required-unavailable runtime-survival state because
AnimSmith does not execute Bevy or inspect its runtime asset.
The rule inventories raw source animation rows and independent per-animation
channel rows from the same load. Complete-empty source animation inventory is
not applicable. Partial or unavailable animation/channel coverage emits
exactly one subjectless, unsuppressible inventory
required_prediction_unavailable facet and no retained-prefix prediction;
bounded N+1 evidence is therefore distinguishable from a complete result.
Extensions, unsupported constructs, target survival, graph wiring, and other
positive runtime claims remain outside revision 3. Current output-v19 carries
its immutable V5 provenance unchanged; the output-v17 and output-v16 readers and the
revision-2/V4 output-v15 reader remain preserved.
Revision 3 rich addressability
The rich standalone contract is the immutable
urn:animsmith:schema:gltf-addressability:2; see
gltf-addressability-v2.schema.json.
It is selected only for the exact revision-3 tuple and preserves the V1
gltf-animation-addressability:1 inventory as a nested animation domain.
The adapter runs one engine-addressability evaluation and does not create a
second lifecycle.
The separately versioned authority pins Bevy tag v0.19.0, commit
c6f634ca9f406d68ba5109d921247b654cb42c10, bevy_gltf 0.19.0, locked
gltf 1.4.1, and the label, loader, node-path, AnimationTargetId, feature,
and root Cargo.lock sources. The isolated readback probe's committed lock is
the graph authority: every Bevy 0.19 release crate in that lock is 0.19.0
(independently-versioned helpers such as bevy_mikktspace retain their own
version); its lock guard rejects internal Bevy patch drift. It requires
explicit target pointer width
(bits32 or bits64) because Bevy hashes path segment lengths using the target
pointer width; AnimSmith never uses the host width. Missing bevy_animation or disabled
load_animations is typed unavailable, not runtime success.
Scene{i} is emitted for every declared source scene. Gltf.default_scene
is only a route to the selected existing Scene{i}: there is no
DefaultScene label and no fabricated Scene0. Every source skin eagerly
gets Skin{i}/InverseBindMatrices, including unreferenced skins, with Bevy's
identity fallback when inverse-bind data is absent. Skin{i} is created when
any source node references it during the all-source-node construction pass;
source skin indices, never collected-vector position, are authoritative.
Explicit skin.skeleton remains source evidence only because Bevy ignores it;
scene-instantiated SkinnedMesh attachment is outside this static slice.
Named scene/animation/skin maps are separate from typed labels and are
source-order last-write-wins; skin winners follow lazily created skins in
first-reference order. Target rows are per unique source animation target node
with contributing animation/channel identities. Authored names or
GltfNode{source_index} fallbacks form the path, excluding the scene
world-root. A target path/UUID is published only when complete hierarchy,
reachability, closure, feature/settings, and collision evidence exists.
The report carries an independent target_coverage projection alongside the
retained target rows. It is complete when the unique-target domain is
exhaustively represented by complete raw node/scene/path and animation/channel
evidence, including an empty domain; incomplete evidence makes it
required_unavailable. It also becomes
required_unavailable with target_domain_truncated when more than 4,096
targets exist, or with projection_bounds_exceeded when a new rich projection
exceeds its aggregate structural/text budget.
Each new rich projection scene/node/skin/attachment/path/target/map domain is
capped at 4,096 rows, with aggregate projection structural references at
65,536 and dynamic projection text at 1 MiB. The sealed V1 animation
inventory and CheckEvaluation scopes retain their own bounds. One name/path
segment is capped at 1,024 UTF-8 bytes, a path at 4,096 bytes and 256
segments, and a report at 256 MiB. The explicit target-coverage projection
reports target_domain_truncated at the target row ceiling and
projection_bounds_exceeded for other rich-projection budget exhaustion;
readback rejects N+1 collections and contradictory states. This is prediction
evidence only and does not certify Bevy loading, spawning, target survival,
graph wiring, or playback.
If the shared check's core per-file facet budget would be exceeded, V2 exposes
one subjectless engine-addressability:facet-budget scope with
facet_budget_exceeded rather than a misleading retained-facet prefix. This
is compaction within the same check lifecycle, not an additional check.
Common failures and fixes
| Symptom | Evidence to inspect | Correct owner |
|---|---|---|
| Code loads the wrong animation after a source edit | generated source index and Animation{i} selector | Regenerate the manifest and update the code/asset contract; a numeric typed label is not stable across reordering. |
| Selector exists but nothing plays | dependency closure, Bevy loader settings/features, AnimationPlayer, graph and targets | Fix runtime loading/graph wiring; the selector prediction does not prove existence. |
| Duplicate names collide in application lookup | source names and distinct indices | Use typed index labels or define an application-owned name policy. |
| Attachment or IK target is scaled/sheared | rest-world-scale path, ancestry, affine class | Repair source hierarchy or supported rest/bind scale; validate the composed Bevy transform at runtime. |
| Clip loops or movement ownership disagree with gameplay | loop checks, in-place, root-motion-speed | Fix source/contract and graph/controller behavior. |
Scale and unit workflow
glTF defines all linear distances in metres. Revision 2 proves that one glTF metre-valued length reaches one Bevy world-space length unit with no loader scale conversion; it deliberately does not claim a universal application, physics, or gameplay metre convention. Treat the format mapping and the application's world convention as two separate authorities. Inherited scale still multiplies descendant attachment, IK, and collision offsets, authored animation translations, and root travel in the composed transform hierarchy.
Use scale whole-document only when every represented glTF length is wrong.
Use scale rest-bind when geometry is already correct and one skinned
hierarchy carries a declared compensating uniform scale. Use static transform
baking only for unskinned placement. None of these operations proves Bevy
scene spawning, target-id paths, attachment behavior, physics/collision scale,
root motion, animation-graph wiring, or visual playback.
Before: a selected weapon node has a world affine classification that includes
uniform factor 0.01. After rest/bind: AnimSmith proves preserved geometry and
joint trajectories under the reparameterized hierarchy. Then load the emitted
asset in the exact Bevy application, validate the caller-owned WorldAssetRoot
and ancestors that are outside importer output, resolve the typed label, attach
the prop, and exercise the graph and physics setup. See
Scaling safely.
glTF and generic runtime profile
Use this page when glTF/GLB is consumed by a custom runtime or when no frozen
engine tuple matches the actual consumer. This is intentionally not a sixth
V1 engine profile. Leave [engine] absent and use AnimSmith's engine-neutral
measurements, checks, transforms, and evidence.
What the format guarantees
The glTF 2.0 specification defines a right-handed, Y-up coordinate system and metres for linear distances. Animation sampler input is time in seconds and must be finite, non-negative, and strictly increasing; channels target node translation, rotation, scale, or morph weights. Names are optional and not guaranteed unique. The runtime still chooses scene selection, asset addressability, graph/controller behavior, root-motion extraction, blending, events, masks, physics, and error handling.
AnimSmith checks and thresholds
No engine-specific check applies without a profile. The engine-neutral catalog still provides exact source contracts:
| Check id | Exact default boundary | Generic-runtime use |
|---|---|---|
time-monotonic | strict increase; negative tolerance 0.0001 s; late-first-key note after 0.017 s | Enforces the glTF timing requirement and identifies an unauthored clamp-held prefix. |
quat-norm | absolute length deviation greater than 0.001 | Prevents unstable runtime interpolation. |
scale-keys | component range greater than 1e-4 | Makes animated scale explicit. |
non-uniform-scale | relative spread greater than 1e-4 | Flags a transform/skin/physics portability risk. |
rest-world-scale | selected node factor 1.0 ± 0.0001 inclusive | Checks project-declared attachment/IK nodes without guessing units from bounds. |
loop-closure | 0.01 m and 1.0° | Checks a declared cyclic endpoint. |
loop-seam-vel / loop-seam-rot | 0.1 m/s / 5.0°/s | Finds wrap velocity discontinuity. |
in-place | XZ speed at least 0.5 m/s counts as travelling | Enforces declared movement ownership. |
Use the complete check catalog for structural rig,
duration, FPS, gait, sync, foot-slide, and bind-pose contracts. Do not select
engine-addressability; it is defined only for the exact Bevy tuple.
The scale-domain measurements are the engine-neutral output of #267, and the selected-node policy is #268. The profile-specific advice work in #155 does not invent a generic importer. Static placement baking #224 and skinned rest/bind reparameterization #269 remain distinct repair contracts.
Configure the runtime contract
Declare only what the project actually knows:
[rig]
profile = "humanoid"
[runtime_nodes]
selectors = ["weapon_socket", "ik_*_target"]
[checks.rest-world-scale]
expected_uniform_scale = 1.0
uniform_scale_tolerance = 0.0001
[clips."locomotion_*"]
loop = true
movement_owner_xz = "gameplay"
speed_mps = { value = 3.0, tolerance = 0.2 }
There is no fallback or nearest engine profile. Adding a made-up [engine]
tuple is an operator error, not a way to label a custom runtime.
Common failures and fixes
| Symptom | Evidence to inspect | Correct owner |
|---|---|---|
| Asset is the wrong physical size | glTF metre contract, source exporter, rest-world-scale | Correct all lengths in DCC/whole-document conversion, or repair one compensating hierarchy scale. |
| Animation name lookup is ambiguous | source animation indices and optional names | Define a runtime-owned unique id policy; glTF names are not identities. |
| Attachment, IK, or collision anchor drifts | source node path, ancestry, affine class | Repair transforms or canonicalize supported rest/bind scale, then test runtime composition. |
| Loop or movement slides | loop checks, in-place, root-motion-speed, foot-slide | Align source motion, declared intent, and controller behavior. |
| Valid glTF still fails to load | dependency closure, required extensions, runtime logs | Fix resource packaging/extension support in the exporter or runtime. |
Scale and unit workflow
If an exporter wrote centimetre-valued numbers into a format that defines
metres, the whole asset is physically wrong: a factor 0.01 whole-document
conversion is the relevant operation when the supported-source boundary is
met. If the mesh is already the correct world size but a skeleton root carries
0.01 with compensating descendants, whole-document conversion is wrong;
use the explicit rest/bind operation instead.
That inherited scale also multiplies descendant socket, attachment, IK, and
collision-anchor offsets, animation translations, and root-motion distance.
Static transform baking moves supported unskinned placement into geometry. It does not repair a skinned hierarchy. Rest/bind reparameterization covers the accepted skinned design and preserves its stated world-space invariants. The runtime still owns sockets, attachments, IK, collision anchors, root motion, graph behavior, extensions, rendering, physics, and visual acceptance. See Scaling safely and the static asset workflow.
animsmith examples
A cookbook of runnable workflows. Each section is a self-contained task you can copy into your own project — several double as CI/acceptance gates. Each workflow addresses a runtime failure described in what game-ready means, which explains why the checks behind these commands exist. For the larger process that turns raw source assets into engine-facing outputs, see the pipeline scenario guide.
Commands that reference examples/assets/ run
against small assets committed there, so you can follow along from a
source checkout with no downloads; the assets are procedurally
generated (see their README for
provenance and how to regenerate them). The conversion and reporting
section operates on assets you supply, using placeholder filenames —
export.fbx, old.glb — for your own exports and baselines.
For complete contract semantics, see the configuration reference.
Console transcripts are real command output, with long finding messages
elided as .... JSON envelopes are abridged illustrative projections with
placeholder digests, byte counts, and build provenance; capture those
identity fields from your own run. Exit-code annotations remain # comments.
Running the commands
Examples use the installed CLI form, animsmith <command>. From a
source checkout, prefix each command with cargo run -p animsmith --:
animsmith lint examples/assets/clip.glb
cargo run -p animsmith -- lint examples/assets/clip.glb # source checkout
Two examples need the default build's feature-gated commands (report,
convert); they are marked default features only. Everything else
works in the pure-Rust --no-default-features build too.
Exit codes
Every example relies on the same convention, so scripts can gate on it:
| Code | Meaning |
|---|---|
| 0 | No failing findings and no required-unavailable engine prediction facets; warnings, notes, and ordinary coverage gaps may remain. |
| 1 | A failing finding, any required_prediction_unavailable facet, a significant diff, or pending fix --dry-run repairs. |
| 2 | Operator error: unreadable input, bad config, bad flags. |
1. A first CLI gate
When a clip enters CI, the first question is not just whether the file
parses — it is whether the motion is safe to ship. A gate built from
inspect, measure, and lint catches the gap described in
a valid file is not a usable clip:
valid containers can still hide content problems that only show up
after import, bake, or playtest. inspect summarizes structure;
measure reports metrics without judgment; lint runs the checks and
sets the exit code.
$ animsmith inspect examples/assets/clip.glb
examples/assets/clip.glb
rig profile: none detected
skeleton: 3 bones
root
spine
chest
materials: 0
mesh instances: 0
clips: 1
swing: 1.000s, 1 tracks, 5 keys max
$ animsmith lint examples/assets/clip.glb
examples/assets/clip.glb:
coverage[bind-pose] insufficient_rotation_evidence ×1 (scopes: first_frame_rest_delta; ...
0 error(s), 0 warning(s), 0 note(s), 1 coverage gap(s), 0 available prediction facet(s), ... # exits 0
A defective asset produces findings and a non-zero exit:
$ animsmith lint examples/assets/clip-dirty.glb
examples/assets/clip-dirty.glb:
error[quat-norm] clip 'swing' bone 'spine' @0.500s: non-unit rotation key ...
warning[quat-flip] clip 'swing' bone 'spine' @0.750s: 2 hemisphere flip(s) ...
coverage[bind-pose] insufficient_rotation_evidence ×1 (scopes: first_frame_rest_delta; ...
1 error(s), 1 warning(s), 0 note(s), 1 coverage gap(s), 0 available prediction facet(s), ... # exits 1
Warnings alone keep the exit code at 0. Use --deny-warnings when CI
should fail on warnings too:
$ animsmith lint --deny-warnings examples/assets/clip-dirty.glb # exits 1
For machine consumption, --format json emits the current v19 result envelope
(see output.md). This jq projection keeps the example
short while showing where retained/promotion evidence, content findings, and
independently versioned measurement evidence live:
$ animsmith lint --format json examples/assets/clip-dirty.glb | jq \
'{schema_version, schema, command,
prediction_facets: .summary.prediction_facets,
prediction_provenance: .files[0].prediction_provenance,
input: .files[0].input,
check: (.files[0].checks[] | select(.check_id == "quat-norm") |
{check_id, selection, configuration, applicability, evaluation, findings}),
measurements: (.files[0].measurements | {schema_version, schema})}'
{
"schema_version": 19,
"schema": "urn:animsmith:schema:output:19",
"command": "lint",
"prediction_facets": {
"available": 0,
"required_prediction_unavailable": 0
},
"prediction_provenance": null,
"input": {
"sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"bytes": 123456
},
"check": {
"check_id": "quat-norm",
"selection": "selected",
"configuration": "enabled",
"applicability": "applicable",
"evaluation": "complete",
"findings": [
{ "check_id": "quat-norm", "severity": "error", "clip": "swing",
"bone": "spine", "time_s": 0.5, "measured": 1.05, "expected": 1.0,
"message": "non-unit rotation key (worst at key 2)" }
]
},
"measurements": {
"schema_version": 18,
"schema": "urn:animsmith:schema:measurements:18"
}
}
As a CI gate
Lint every clip and fail the job on any error (add --deny-warnings to
also fail on warnings):
# .github/workflows/animate.yml
- run: cargo install animsmith --no-default-features
- run: animsmith lint --deny-warnings clips/*.glb
The step's exit code is the gate: 1 fails the job, 0 passes it.
2. Repairing an asset
Use repair when a clip plays as a sudden flicker, spin, or explosion,
but the authored motion is still recoverable. The symptoms in
the pose flickers, spins, or explodes
are often quaternion representation problems, so fix --dry-run lets a
gate report the exact lossless repairs before writing anything.
quat-norm and quat-flip are not just checks — they are lossless,
idempotent repairs. fix --dry-run is the check mode: it reports what
it would repair and exits 1 if anything is pending, writing nothing.
$ animsmith fix examples/assets/clip-dirty.glb --dry-run
would fix[quat-norm] clip 'swing' bone 'spine': 1 key(s) unit-normalized
1 key(s) would be fixed across 1 track(s) -> no output written
would fix[quat-flip] clip 'swing' bone 'spine': 1 key(s) hemisphere-normalized
1 key(s) would be fixed across 1 track(s) -> no output written # exits 1
Write the repaired asset with -o (or --in-place), then confirm it
lints clean:
$ animsmith fix examples/assets/clip-dirty.glb -o fixed.glb
fixed[quat-norm] clip 'swing' bone 'spine': 1 key(s) unit-normalized
1 key(s) fixed across 1 track(s) -> fixed.glb
fixed[quat-flip] clip 'swing' bone 'spine': 1 key(s) hemisphere-normalized
1 key(s) fixed across 1 track(s) -> fixed.glb
$ animsmith lint fixed.glb
fixed.glb:
coverage[bind-pose] insufficient_rotation_evidence ×1 (scopes: first_frame_rest_delta; ...
0 error(s), 0 warning(s), 0 note(s), 1 coverage gap(s), 0 available prediction facet(s), ... # exits 0
Because the repairs are lossless, diff confirms no measurement moved —
the fix changed representation, not motion:
$ animsmith diff examples/assets/clip-dirty.glb fixed.glb
no significant movement
0 significant change(s) # exits 0
Pin an exact repair set with --repair id[,id] (animsmith fix --help
lists the ids). Repairs that cannot be applied byte-surgically — data-URI
.gltf buffers, cubic tracks, quantized rotations — are reported as
skipped[...] and do not fail the check. Gate on lint when detection
alone should fail CI.
3. Editing a clip
Use editing when the animation content is right but the pipeline cut is wrong: a capture has junk at the head, a one-shot needs a final hold, or a loop's stride anchor lands in the wrong place. Those are the cases in the clip is the wrong length or freezes at the end and the loop pops, where a mechanical transform can make the clip conform without touching geometry.
transform applies mechanical pipeline edits — slice a window, hold the
final pose, re-anchor a gait cycle, remove an eligible duplicate loop
endpoint, or prune provably constant multi-key tracks. Geometry passes through
unchanged.
Slice a sub-window (retimed to start at 0):
$ animsmith transform examples/assets/clip.glb -o sliced.glb --slice 0.5:1.0
sliced 'swing' to [0.5:1]s (3 keys max)
wrote sliced.glb (3 node(s), 1 clip(s), 0 mesh(es) / 0 position(s), 0 material(s))
$ animsmith diff examples/assets/clip.glb sliced.glb
swing duration_s: moved 1.0000 -> 0.5000
swing frame_count: moved 5.0000 -> 3.0000
swing loop_continuity.bones[1].rotation_delta_deg: moved 22.9183 -> 11.4592
swing loop_continuity.bones[2].position_delta_m: moved 0.1987 -> 0.0998
swing loop_continuity.bones[2].rotation_delta_deg: moved 22.9183 -> 11.4592
swing loop_continuity.bones[2].seam_velocity_delta_mps: moved 0.0598 -> 0.0200
swing bone_rotation_range_deg[spine]: moved 22.9183 -> 11.4591
7 significant change(s) # exits 1
Extend the final pose (useful for hold frames at the end of a one-shot):
$ animsmith transform examples/assets/clip.glb -o held.glb --hold-extend 0.5
hold-extended 'swing' by 0.5s
wrote held.glb (3 node(s), 1 clip(s), 0 mesh(es) / 0 position(s), 0 material(s))
Other transforms: --gait-anchor rotates a cyclic clip so its stride
anchor lands at t=0. It is an explicit in-place declaration and needs
resolvable Root (or Hips fallback), Hips, and feet roles; accumulating root
translation/yaw refuses without writing the output or emitting buffered success
stdout. Every nonconstant selected trajectory channel must contain exactly one
key at each --fps whole-frame sample over the clip duration; sparse,
differently framed, duplicate-time, or off-grid evidence and duplicate (bone, property) channels refuse. Declared frames × skeleton bones, declared frames ×
tracks, and maximum authored keys × skeleton bones are each capped at 1,000,000
samples. Retain root motion or use runtime phase offsets instead. --fps N sets the grid used for retiming. See
cli.md for the full flag list.
For a loop exported with frame 0 copied again at the inclusive final frame,
use --drop-duplicate-loop-endpoint only after the default warning identifies
the strict authored-key case:
$ animsmith transform cycle.glb -o cycle-open.glb --drop-duplicate-loop-endpoint
The transform removes the same closing key count from every channel and re-pins
the duration; it does not repair a stationary hold, root-travel/nonclosing
clip, mismatched timelines, endpoint tangents, or retargeting damage. The open
cycle is for engines that wrap over duration, so its inclusive
loop-closure result is expected to change.
For exported clips with redundant “key everything” or baked-control channels,
start with the default constant-track note, inspect the DCC/engine result,
then make the explicit mechanical edit:
$ animsmith lint exported.glb --select constant-track
$ animsmith transform exported.glb -o compact.glb --prune-constant-tracks
constant-track removed 'idle': track index 3 bone 'control' scale Linear 90 key(s)
The transform keeps a deterministic text record for every removal and every
candidate it declines, including the source track index. It leaves an
animates_bones target in place so the declared motion contract can still be
checked; it does not protect [rig] required_bones, which only declares that
the skeleton must contain a name. Re-lint and preview compact.glb in the
target engine before replacing the export.
4. A project contract config
Use a contract config when the failure depends on what your game expects, not on file validity alone. The character glides or runs in place, feet skate when clips blend, and feet slide within one clip all need declared locomotion or blend assumptions before animsmith can judge them.
Mechanical checks run with no config. Contract-aware checks run only for the
expectations you declare. duplicate-loop-endpoint, loop-closure,
loop-seam-vel, and loop-seam-rot need only loop = true; role-dependent checks such as
loop-seam, gait-group, root-motion-speed, in-place, and foot-slide
also need resolvable rig roles. Without those roles they report a typed coverage
gap rather than guess, so a config that pins a [rig] profile (or inline
[rig.roles]) is what makes them fire.
sync-group is role-independent: use it for same-time or absolute-sync blend
sets, where compatible duration, longest-channel key count, validated declared
FPS grid, and loop endpoint mode matter. Each group declares non-negative
duration, key-count, and FPS spread tolerances; incompatible findings retain a
machine-readable row for every configured member.
The optional nested time_complement policy activates the separate
warning-level time-complement check for every unordered pair in that sync
group. It uses the already measured left-minus-right foot-height gait phase,
so it needs resolvable hips plus left and right foot/toe roles. The
exact-zero signal has no phase subject even when min_lr_amplitude_m = 0;
positive near-idle or noisy phase evidence below that floor is also excluded.
Among the remaining pairs, a warning appears only when reflected-time
similarity beats same-time similarity by more than
min_reflected_time_advantage. The finding's member rows retain both scores,
their advantage, each phase, and each amplitude.
examples/assets/walk.glb is a committed rig for this: a hips + two-foot
skeleton with a one-second walk cycle. Its bone names resolve a built-in
profile, so inspect binds the rig with no config at all:
$ animsmith inspect examples/assets/walk.glb
examples/assets/walk.glb
rig profile: ue-mannequin (3 roles)
hips -> pelvis (exact)
left_foot -> foot_l (exact)
right_foot -> foot_r (exact)
skeleton: 3 bones
pelvis
foot_l
foot_r
materials: 0
mesh instances: 0
clips: 1
walk: 1.000s, 2 tracks, 33 keys max
measure reports the semantic metrics the checks judge — per-bone C0 pose
closure and C1 seam velocity, the feet-relative loop-seam ratio (≈ 0 here,
since this cycle returns its feet exactly), gait phase, L/R foot amplitude,
and sampled Hips-fallback trajectory. This fixture's pelvis stays fixed, so
all trajectory values are zero while the selected source remains explicit:
$ animsmith measure --format json examples/assets/walk.glb
{
"schema_version": 19,
"schema": "urn:animsmith:schema:output:19",
"tool": { "name": "animsmith", "version": "0.14.0",
"source": { "revision": null, "dirty": null } },
"command": "measure",
"summary": { "files": 1 },
"files": [
{
"path": "examples/assets/walk.glb",
"input": {
"sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"bytes": 123456
},
"rig": { "profile": "ue-mannequin", "resolution_outcome": "coverage",
"resolved_roles": { "hips": "pelvis", "left_foot": "foot_l", "right_foot": "foot_r" },
"resolved_role_policies": { "hips": "exact", "left_foot": "exact", "right_foot": "exact" } },
"measurements": {
"schema_version": 18,
"schema": "urn:animsmith:schema:measurements:18",
"clips": { "walk": {
"duration_s": 1.0, "frame_count": 33,
"animated_bones": ["foot_l", "foot_r"],
"bone_channels": [
{ "bone_index": 1, "bone_name": "foot_l",
"properties": ["translation"] },
{ "bone_index": 2, "bone_name": "foot_r",
"properties": ["translation"] }
],
"bone_rotation_range_deg": {},
"loop_continuity": { "bones": [
{ "bone_index": 0, "bone_name": "pelvis",
"availability": "measured",
"position_delta_m": 0.0, "rotation_delta_deg": 0.0,
"seam_velocity_delta_mps": 0.0,
"seam_angular_velocity_delta_degps": 0.0 },
{ "bone_index": 1, "bone_name": "foot_l",
"availability": "measured",
"position_delta_m": 3.7e-17, "rotation_delta_deg": 0.0,
"seam_velocity_delta_mps": 0.0,
"seam_angular_velocity_delta_degps": 0.0 },
{ "bone_index": 2, "bone_name": "foot_r",
"availability": "measured",
"position_delta_m": 3.7e-17, "rotation_delta_deg": 0.0,
"seam_velocity_delta_mps": 0.0,
"seam_angular_velocity_delta_degps": 0.0 }
] },
"loop_continuity_availability": "measured",
"loop_endpoint_mode_availability": "not_applicable",
"frame_grid_availability": "not_applicable",
"loop_seam_ratio": 1.2e-15,
"loop_seam_ratio_availability": "measured",
"gait": { "phase": 0.75, "phase_availability": "measured", "lr_amplitude_m": 0.2 },
"gait_availability": "measured",
"root_trajectory": {
"bone_index": 0, "bone_name": "pelvis",
"source_role": "hips_fallback",
"translation": {
"horizontal_displacement_x_m": 0.0,
"horizontal_displacement_z_m": 0.0,
"horizontal_travel_m": 0.0,
"vertical_displacement_m": 0.0,
"vertical_min_displacement_m": 0.0,
"vertical_max_displacement_m": 0.0
},
"translation_availability": "measured",
"yaw": {
"heading_axis": "positive_z", "net_yaw_deg": 0.0,
"unwrapped_yaw_deg": 0.0, "yaw_travel_deg": 0.0
},
"yaw_availability": "measured"
},
"root_trajectory_availability": "measured",
"speed_mps": 0.0,
"speed_mps_availability": "measured"
} },
"mesh_definitions": [],
"node_instances": [],
"scenes": [],
"skeleton_source_coverage": "unavailable",
"skeleton_nodes": [],
"skins": [],
"material_resource_coverage": "complete",
"material_definitions": [],
"textures": [],
"images": []
}
}
]
}
examples/walk.animsmith.toml is the
contract: it declares the clip a loop (which arms the loop checks) and
in-place, and caps their tolerances. Against the clean rig every semantic check
passes, and the run exits 0. The one warning is not a defect in the motion: a
cycle whose last key repeats its first is a representation an engine can trim,
which editing a clip covers with
transform --drop-duplicate-loop-endpoint.
$ animsmith lint --config examples/walk.animsmith.toml examples/assets/walk.glb
examples/assets/walk.glb:
warning[duplicate-loop-endpoint] clip 'walk' @1.000s: declared loop repeats its first pose ...
coverage[bind-pose] insufficient_rotation_evidence ×1 (scopes: first_frame_rest_delta; ...
0 error(s), 1 warning(s), 0 note(s), 1 coverage gap(s), 0 available prediction facet(s), ... # exits 0
examples/assets/walk-dirty.glb is the same rig with the clip cut a
quarter-cycle short, so the feet never return to their first-frame pose —
the classic popped loop seam. The same contract catches it:
$ animsmith lint --config examples/walk.animsmith.toml examples/assets/walk-dirty.glb
examples/assets/walk-dirty.glb:
error[loop-closure] clip 'walk' bone 'foot_r' @1.000s: loop does not close in position: ...
error[loop-seam] clip 'walk' @1.000s: loop seam pops: wrap discontinuity is 6.82× ...
error[loop-seam-vel] clip 'walk' bone 'foot_r' @1.000s: loop velocity changes at the seam: ...
coverage[bind-pose] insufficient_rotation_evidence ×1 (scopes: first_frame_rest_delta; ...
3 error(s), 0 warning(s), 0 note(s), 1 coverage gap(s), 0 available prediction facet(s), ... # exits 1
The contract is load-bearing: a bare animsmith lint examples/assets/walk-dirty.glb
(no config) reports no findings — with no loop = true declared, the three
loop checks are explicitly not applicable. Semantic checks
enforce your declared expectations, not a guess.
walk-dirty.glb is one of a set. examples/assets/ commits one mutation
of this same walk per runtime symptom — a channel that ends early, a clip
that travels, a blend ring whose members disagree, a frozen arm, an
animated non-uniform scale — each alongside the contract config that arms
its checks. The asset table lists what every fixture
fires and which config to pass.
Keeping the exported rig shape stable
animates_bones is deliberately per clip: it says that a named bone must
exist and carry animation keys in that take. That is the right contract
for a swinging arm or a locomotion root, but it is too strict for a static
weapon socket, an IK target, a retargeting mask bone, or another runtime
attachment point. Put those structural requirements on the rig instead:
[rig]
profile = "auto"
required_bones = ["root", "weapon_socket", "ik_hand_l"]
required-bones checks only that each declared name identifies exactly one
bone in the input skeleton. A present-but-static bone is clean; a missing name
is an error. Duplicate skeleton names are ambiguous rather than silently
matched, and an export with no skeleton cannot satisfy the contract. This is
also useful for a static mesh or a take with no clips: the structural check
does not need animation data to run.
It is not a repair tool. AnimSmith does not add sockets, rename a duplicate,
retarget a rig, or decide which bones your engine needs; fix the source rig or
its export in the DCC, then lint it again. Use [clips.<name>] animates_bones = [...] when the same bones must move in a particular clip,
and frozen-bone when keyed rotation is required to exceed a meaningful
motion floor.
Presence and transform policy are separate. To require a runtime-facing node to inherit a unit effective rest scale, select it explicitly:
[runtime_nodes]
selectors = ["weapon_socket", "ik_*_target"]
[checks.rest-world-scale]
expected_uniform_scale = 1.0
uniform_scale_tolerance = 0.0001
An exact name or * glob must match exactly one source node. A miss or
multiple matches produces explicit coverage instead of guessing. The finding's
node path includes source indices and ancestors, so a local scale of 1 can
still be traced to a non-unit effective scale inherited from a parent. This
does not inspect animation scale keys, infer units from geometry, or rescale
the file. The legacy [checks.rest-world-scale].node_selectors spelling is an
alias; declaring it together with [runtime_nodes] is a configuration error.
Scaling up to a full character
examples/character.animsmith.toml
is the full game-character shape the small walk contract grows into:
[rig]
profile = "auto" # or mixamo / ue-mannequin / humanoid
[checks.loop-seam]
max_ratio = 1.6 # per-check tuning
[checks.loop-closure]
max_position_delta_m = 0.01
max_rotation_delta_deg = 1.0
[checks.loop-seam-vel]
max_velocity_delta_mps = 0.1
[checks.loop-seam-rot]
max_angular_velocity_delta_degps = 5.0
[runtime_nodes]
selectors = ["weapon_socket", "ik_*_target"]
[checks.rest-world-scale]
expected_uniform_scale = 1.0
uniform_scale_tolerance = 0.0001
[checks.frozen-bone]
min_rotation_deg = 0.5
[checks.quat-flip]
severity = "note" # demote while an upstream fix lands
[clips."run_*"] # glob: every run clip loops
loop = true
movement_owner_xz = "animation" # extracted root motion owns horizontal travel
movement_owner_y = "gameplay" # controller owns vertical motion
movement_owner_yaw = "animation"
max_loop_position_delta_m = 0.04 # per-family pose/velocity seam caps
max_loop_rotation_delta_deg = 2.0
max_loop_velocity_delta_mps = 0.2
max_loop_angular_velocity_delta_degps = 200.0
[clips.run_forward]
max_loop_rotation_delta_deg = 0.5 # exact entry wins for this dimension
duration_s = { value = 1.033, tolerance = 0.02 } # authored clip-length contract
speed_mps = { value = 3.1, tolerance = 0.25 } # root-motion contract
[gait_groups.run-ring] # a directional blend ring
clips = ["run_forward", "run_backward", "run_left", "run_right"]
max_gait_phase_spread = 0.15 # members must stride in phase
min_lr_amplitude_m = 0.03 # exclude near-idle members
[sync_groups.run-ring]
clips = ["run_forward", "run_backward", "run_left", "run_right"]
max_duration_delta_s = 0.001
max_frame_count_delta = 0
max_fps_delta = 0.01
[sync_groups.run-ring.time_complement]
min_reflected_time_advantage = 0.25 # reflected score must beat same-time score
min_lr_amplitude_m = 0.03 # skip low-confidence gait signals
A gait_groups block is the payoff for a real character: it holds every
clip in a directional blend ring to the same stride phase, so runtime
blends between them don't skate. animsmith.toml is auto-loaded from the
working directory, so committing one next to your assets makes every bare
animsmith lint enforce the contract.
Movement ownership is independent on XZ, Y, and yaw. Each field is either
"gameplay" (the entity/controller owns that component) or "animation"
(extracted root motion owns it); omitted axes stay absent. in_place = true
and false remain compatibility aliases for XZ gameplay and animation,
respectively, but one selector entry cannot use both in_place and
movement_owner_xz. Exact/glob layers normalize the alias before applying the
usual field-by-field precedence.
The four role-free loop-continuity caps (max_loop_position_delta_m,
max_loop_rotation_delta_deg, max_loop_velocity_delta_mps, and
max_loop_angular_velocity_delta_degps) refine the
corresponding global check caps in a [clips.<exact-name>] entry or
[clips."glob_*"] family. An exact name wins over matching globs and an
omitted field keeps its inherited cap. This is useful when root-motion runs
need a different position allowance but idles must stay tightly closed. All
four values must be finite and non-negative.
Predicting a Bevy animation selector
The small bevy.animsmith.toml profile selects Bevy
0.19.0's exact glTF loader contract. Its first production prediction maps each
completely inventoried source animation index to the canonical typed subasset
label spelling Animation{index}:
$ animsmith --config examples/bevy.animsmith.toml lint \
--select engine-addressability examples/assets/walk.glb
The available facet's subject is the predicted display label, such as
Animation0. Names do not control this selector: unnamed animations work, and
duplicate names remain distinct indices. This predicts the selector spelling,
not that Bevy loaded the file, retained its targets, or wired it into an
animation graph. Incomplete source-animation inventory produces a blocking
required_prediction_unavailable facet instead of guessing from the retained
prefix. Prediction provenance v1 separately caps actual clip settings at
4,096, so a 4,097-clip file is a bounded operator error rather than a truncated
prediction; issue #485 owns a future overflow representation.
Revision 2 uses bevy-v2.animsmith.toml for the
unit/effective-scale rule. The example selects the closed empty-handler
environment, declares the compiled animation feature, and demonstrates shared
runtime-node selectors:
$ animsmith --config examples/bevy-v2.animsmith.toml lint \
--select engine-unit-scale examples/assets/walk.glb --format json
The available file result is an exact 1:1 glTF-metre to Bevy world-length-unit mapping, not a claim that arbitrary application world state uses metres. Scene, primitive-child, and selected-node facets remain distinct; incomplete evidence is blocking rather than inferred.
Revision 3 is the current example in
bevy-v3.animsmith.toml. It selects the narrow
engine-track-support gate-support prediction:
$ animsmith --config examples/bevy-v3.animsmith.toml lint \
--select engine-track-support examples/assets/walk.glb --format json
The prediction binds raw animation rows and independent per-animation channel
coverage from the same load. The compiled bevy_animation feature gate takes
precedence over load_animations; a disabled gate yields only negative dropped
animation/channel outcomes, never a content finding. If both gates allow
loading, runtime survival is required-unavailable because this contract does
not run Bevy. Complete-empty inventory is not applicable; partial or
unavailable inventory yields one subjectless, unsuppressible inventory facet
and no retained-prefix prediction, including bounded N+1 overflow. Extensions,
other animation constructs, and positive runtime survival are outside this
slice. V5 provenance remains immutable inside the current output-v19 envelope;
output-v17, output-v16, the rev1/rev2 profiles, and output-v15 contracts remain
preserved.
Generate a reusable engine-neutral inventory as canonical JSON without a profile:
$ animsmith generate addressability examples/assets/walk.glb \
> walk.addressability.json
Select the exact profile to add the unchanged Bevy evaluation to the same document; the neutral inventory and its identity stay unchanged:
$ animsmith --config examples/bevy.animsmith.toml generate addressability \
examples/assets/walk.glb --format markdown
The inventory keeps source animation and channel indices, optional source names, raw targets and accessors, and dependency-closure identity. It does not infer scenes, skins, named-map winners, target UUIDs, extension support, or successful runtime loading.
The richer exact-Bevy path is a separate V2 contract,
urn:animsmith:schema:gltf-addressability:2; see
gltf-addressability-v2.schema.json.
It retains bounded same-load scene, node, skin, attachment, root-to-node path,
default-scene, named-map, and unique-target evidence while preserving this V1
animation inventory. Complete empty coverage proves absence; partial prefixes
and unavailable domains do not. Explicit skin.skeleton is retained as
source evidence, not inferred into a Bevy root, and scene-instantiated
SkinnedMesh attachment is not claimed.
The V2 adapter is pinned to Bevy 0.19.0 revision 3 and commit
c6f634ca9f406d68ba5109d921247b654cb42c10 (bevy_gltf 0.19.0, locked
gltf 1.4.1, and root Cargo.lock). It reuses one engine-addressability lifecycle and the
existing Animation{i} primitive. Scene{i} is emitted for every declared
scene, and Gltf.default_scene routes only to an existing scene; there is no
DefaultScene label or fabricated Scene0. Bevy eagerly emits
Skin{i}/InverseBindMatrices for every source skin, including unreferenced
skins, and emits Skin{i} when any source node references it. Named maps are
separate source-order last-write-wins projections.
Exact target UUIDs require explicitly selecting 32- or 64-bit target pointer
width because Bevy hashes segment lengths using the target pointer width;
AnimSmith never infers width from the host. Missing feature/settings,
incomplete or unreachable targets, multiple paths, and path/UUID collisions are typed
required_unavailable, not guessed. target_coverage distinguishes complete
(including empty) target coverage from incomplete raw/animation evidence and
target_domain_truncated; other rich projection budget failures use
projection_bounds_exceeded. Each V2 domain is capped at 4,096 rows,
references at 65,536, path segments at 1,024 bytes and 256 segments, retained
text at 1 MiB, and reports at 256 MiB. This remains prediction evidence only,
not runtime-load, spawning, target-survival, graph, or playback evidence.
Predicting Unity Generic root motion
Select an exact engine profile and fully materialize every required importer setting before generating advice. For Unity Generic, for example:
[engine]
profile = "unity-generic"
profile_revision = 2
engine_version = "6000.3"
importer = "fbx-model-importer"
[engine.settings]
animation_type = "generic"
avatar_setup = "create_from_this_model"
import_animation = true
root_motion_source = "Reference/Root"
[clips."*"]
loop = true
movement_owner_xz = "animation"
[clips."*".engine_settings]
root_rotation = "extract"
root_position_y = "bake"
root_position_xz = "extract"
$ animsmith --config unity.animsmith.toml lint \
--select engine-root-motion export.fbx
The current Generic revision-2 lint prediction compares the three declared
movement-owner axes with the three bake | extract clip settings. bake
means baked_into_pose; extract means stored_as_root_motion. It requires a
case-sensitive, byte-exact Reference/Root path to the explicitly resolved
Root role and never uses Hips as a fallback. The path grammar has no escaping
or Unicode normalization, rejects empty/reserved/control/format segments, and
limits segments, paths, and depth to 1,024 bytes, 4,096 bytes, and 256
segments. Raw path evidence is projected from the same FBX bytes; the implicit
root and generated helpers cannot match.
An available facet records a typed routing result; a conflict is an error
finding. Missing path, Root, intent, settings, raw inventory, or measured
translation/yaw availability is required_prediction_unavailable, not a
content finding, and exits 1. Measurement magnitude does not affect routing:
zero and nonzero travel use the same comparison. This is prediction evidence
only—no Unity editor execution, imported-asset readback, runtime playback, or
engine certification occurs. The contract uses prediction provenance V6 and
output V19; historical output V9 and other readers remain immutable. The
checked-in/CI FBX inputs for this slice are self-authored synthetic fixtures.
Generating engine import advice
The separate historical revision-1 advice profile remains the route for
projecting Convert Units, Bake Axis Conversion, Generic Root Motion Source, and the three ModelImporterClipAnimation.lockRoot* booleans. Use a
revision-1 Unity advice config for this command:
$ animsmith --config unity-advice.animsmith.toml generate import-advice export.fbx \
> export.import-advice.json
Unreal 5.8 and Godot 4.7 revision 1 deliberately exit 1 with
profile_settings_unmodeled; their frozen profiles do not yet define importer
settings. That command does not guess frame ranges, sample rates, unit
conversion, or root-motion behavior.
Revision 2 provides the narrow document-setting projection for the exact Godot and Unreal tuples:
# Godot 4.7 / revision 2 / resource-importer-scene (glTF JSON or GLB)
[engine]
profile = "godot"
profile_revision = 2
engine_version = "4.7"
importer = "resource-importer-scene"
[engine.settings]
animation_fps = 30
animation_trimming = false
Godot's only projected keys are animation/fps (1..120, default 30) and
animation/trimming (default false). Unreal 5.8 / revision 2 / fbx-importer
accepts FBX and requires one explicit document setting:
[engine]
profile = "unreal"
profile_revision = 2
engine_version = "5.8"
importer = "fbx-importer"
[engine.settings]
sample_rate = "default_30" # or "source_determined" / "custom_hz(60)"
The V2 JSON contract is
urn:animsmith:schema:engine-import-advice:2. It contains one available
native import-setting projection or one typed refusal; it does not claim importer
execution, frame ranges, units, skeleton/retargeting, compression, root
motion, runtime behavior, or project-file changes.
Steering a run without a config
You can also shape a run from the command line. --select restricts the
run set, --allow suppresses findings, and [checks.<id>] severity
(including "off") reshapes how hard each check fails:
$ animsmith lint --select quat-norm examples/assets/clip-dirty.glb # only quat-norm
$ animsmith lint --allow quat-flip examples/assets/clip-dirty.glb # hide quat-flip
Demote a check while an upstream fix is pending (a [checks.quat-flip]
severity = "note" override turns the warning into a note):
$ cat demote.toml
[checks.quat-flip]
severity = "note"
$ animsmith lint --config demote.toml examples/assets/clip-dirty.glb
error[quat-norm] clip 'swing' bone 'spine' @0.500s: non-unit rotation key ...
note[quat-flip] clip 'swing' bone 'spine' @0.750s: 2 hemisphere flip(s) ...
1 error(s), 0 warning(s), 1 note(s), 1 coverage gap(s), 0 available prediction facet(s), ... # exits 1
See the configuration reference for the full key reference.
5. Converting exports and generating reports (default features only)
Use this workflow when a DCC or marketplace export reaches your importer but brings bloated constant tracks, stray scale keys, or a rig the retargeter cannot trust. That is the hygiene side of the file is bloated, or the retargeter chokes and the rig-contract side of a limb is T-posed, or a bone never moves: convert to glTF, measure/lint/report, then compare against the previous asset before committing the migration.
convert normalizes an FBX (or glTF) export into a clean glTF, and
report renders a self-contained HTML report with skeleton playback and
metric charts. Both are in the default build; a --no-default-features
binary omits them. report --evidence-only writes the same findings,
coverage gaps and engine predictions without the sampled pose grid — keeping
the charts in a single-clip report, though not in a comparison, whose panels
are drawn from that grid — for sharing evidence where the clip itself cannot
travel.
$ animsmith convert export.fbx -o clip.glb
$ animsmith measure clip.glb
$ animsmith report clip.glb -o report.html
$ animsmith diff old.glb clip.glb
The repository also carries a byte-checked, self-authored visual-comparison pair. It exercises the report's acceptance context without a commercial or private source asset:
$ animsmith --config examples/report-comparison.animsmith.toml report \
examples/assets/report-comparison-before.glb \
--compare-after examples/assets/report-comparison-after.glb \
--before-clip acceptance-matrix --after-clip acceptance-matrix \
-o comparison.html
In comparison.html, select the before-side loop finding to see the exact
first and last judged poses overlaid with the affected bone highlighted.
The trajectory panels disclose root, hips, and bilateral-foot trails; the
gait panel shades the typed foot-slide stance intervals and names the
selected role. The combined root chart uses one metres scale and distinct
before/after legends. The before-side constant-track row is explicitly
structural evidence: its quaternion keys can be removed while the sampled
poses look unchanged. Neither the fixture nor the report certifies artistic,
gameplay, importer, or engine acceptance.
cargo run -p animsmith --example gen_example_assets regenerates both inputs.
The example_assets_match_generator_output integration test rebuilds every
committed example and compares its bytes, so CI catches hand edits or generator
drift.
When one authoritative skinned base and its animation takes live in separate
files, use the versioned character-assembly.toml
recipe instead. assemble produces one GLB and evidence pair; source-package
extraction and project publication stay outside animsmith.
The example uses current recipe v7 without its optional rest/bind operation.
For glTF/GLB or inventory-complete normalized/baked FBX assembly, add
[rest_bind_scale] with the required exact root_node_name and
expected_factor fields. The base must resolve that name to one source node
and exactly one non-empty skin whose every joint is that node or a descendant.
Every distinct FBX clip is projected to its normalized skeleton and takes. A
glTF/GLB clip retains its existing successful full rest/bind or meshless
track-only path; its clip-track gate additionally admits only failures in
unused geometry, deformation, material, or bind domains while retaining
framing, dependency, raw-coverage, named-skeleton, and animation
accessor/layout safety. The basis proof covers the selected base skin joints, exact
root, their named ancestry, and actual track targets; unreferenced base-only
geometry descendants need not appear in the clip. Every input passes basis
compatibility before remapping.
$ animsmith assemble examples/character-assembly.toml \
-o character.glb --evidence character.assembly.json
Getting a test asset
We do not ship third-party assets. To try this on a real rig:
- Mixamo — free with an Adobe ID and royalty-free for personal and
commercial use. Download a character + animation as FBX, then
convertit. Mixamo is also a built-in rig profile, so[rig] profile = "mixamo"resolves its roles for the semantic checks. Check Adobe's current terms before redistributing any downloaded asset; the safe path is to keep them out of your repo. The Mixamo tutorial takes one download end-to-end, from this convert step through a contract config. - CC0 / public-domain sources for assets you want to commit.
- Or generate your own — see the asset generator this repo uses for its own fixtures.
6. Embedding animsmith as a library gate
Use embedding when the team already has an importer or build pipeline and
wants animsmith's checks as a library step instead of a separate shell
command. The pipeline-library use case is the payoff in
why animsmith: load your
asset once, map your own contract into Config, and surface findings in
the same gate that owns the rest of your asset rules.
Pipelines can skip the CLI and drive the check catalog directly: load a
document, resolve rig roles, build a Config from your own contract
format, measure, run the checks, and map findings to your gate.
The runnable walkthrough is
crates/animsmith/examples/embed.rs,
paired with embedding.md:
$ cargo run -p animsmith --example embed
It exits 1 on purpose — the example declares a deliberately wrong expectation to demonstrate a failing gate, not an accidental error.
Feature matrix
| Example | Needs |
|---|---|
| 1 first gate, 2 repair, 3 transform, 4 config | any build (incl. --no-default-features) |
| 5 convert / report | default features (fbx, report) |
| 6 embedding | library crates |
Asset policy
Assets committed to this repo are procedurally generated or CC0, with provenance recorded. Third-party assets (Mixamo and similar) are used via documented download steps, not checked-in bytes or download scripts, unless their terms clearly permit redistribution.
Advanced workflows
- Static asset workflow guide — Diagnose bounds and transform domains, preserve normal maps, bake supported static placement, attach explicit textures, and understand what still needs engine validation.
- Scaling glTF safely — Choose whole-document unit conversion or rest/bind reparameterization, understand the exact-source rewrite/proof transaction, and interpret its support boundary.
- Material texture recipes — Attach explicit BaseColor, normal, metallic-roughness, and occlusion images during conversion with deterministic resizing and provenance evidence.
- Multi-source character assembly — Combine an authoritative skinned base with exact takes and timeline windows from separate inputs, producing one deterministic GLB plus evidence.
- Collection contract extensions — Read the contact-fragment, foot-cycle planner, and transition-family contracts, including strict manifest and contact-evidence bindings.
Static assets: bounds, transforms, and textures
This guide is for artists, technical artists, engine integrators, and build engineers handing a prop or environment asset from a DCC tool to a game. It explains four questions that look similar in a viewport but need different answers in an asset pipeline:
- Which bounding box are we measuring?
- Is placement stored in mesh vertices or in a node hierarchy?
- Did the normal map survive conversion as a normal map?
- Should missing texture links be preserved, repaired declaratively, or sent back to the artist?
animsmith reports or performs only the part it can prove. It does not certify an engine's import settings, generate artistic material content, or replace a runtime smoke test.
Start with the handoff problem
| What you observe | Likely source | Start with |
|---|---|---|
| The prop is the right shape but has the wrong size or placement after import. | Object or parent transforms survived as scene hierarchy instead of being applied to vertices. | animsmith measure; compare definition, instance, and scene bounds. |
| Culling, framing, or placement code sees a different box than the DCC viewport. | The code and artist are discussing different coordinate domains. | animsmith measure --format json. |
| The surface silhouette is right but grooves and bevels look flat. | The normal image or material slot was lost, or the engine imported it as color data. | Ordinary convert, then inspect the material in the engine. |
| The model is gray or flat because exported material links are incomplete. | External image paths were not portable or the exporter omitted them. | convert --material-texture-recipe. |
| You need to know which material slot uses which source image before an engine import. | A mesh view alone hides material-to-texture-to-image sharing and image encoding facts. | animsmith measure --format json. |
| A consumer requires identity transforms and mesh-local final geometry. | The consumer does not retain or intentionally does not evaluate the source node hierarchy. | convert --bake-static-mesh-transforms. |
Which bounding box do you mean?
A mesh definition contains reusable vertex data. A node places an instance of that mesh into a hierarchy. A scene chooses root nodes and therefore a set of instances. The glTF scene and node model and its transformation rules make those identities separate: a mesh's global transform comes from the node that references it, and a child inherits its parents' transforms.
That gives one asset several valid axis-aligned bounding boxes (AABBs):
| animsmith record | Coordinate domain | Question it answers |
|---|---|---|
mesh_definitions[].geometry_aabb | Mesh-local source positions | How large is the reusable geometry before any placement? |
mesh_definitions[].geometry_centroid | Mesh-local finite source positions | Where is the average authored vertex position before any placement? |
node_instances[].static_node_world_aabb | Default/rest hierarchy | Where and how large is this particular static instance after its parent and local transforms? |
scenes[].static_scene_world_aabb | One declared source scene | What box encloses the measurable static instances reachable from this scene's roots? |
For example, a unit cube can have a mesh-local box from (0, 0, 0) to
(1, 1, 1), while a node with scale 2 and translation 10 along X has a
static node-world box from (10, 0, 0) to (12, 2, 2). Neither result is
wrong. Using the first as a placed-world bound, or the second as a reusable
mesh bound, is the bug.
geometry_centroid is a separate mesh-local fact: it is the arithmetic mean
of finite decoded base POSITION rows, not the center of geometry_aabb. An
asymmetric mesh can therefore have a centroid away from its box center. It is
useful for checking authored pivots or coarse placement assumptions, but it is
not a center of mass, surface-area-weighted center, collision origin, or a
runtime placement guarantee.
For indexed geometry, each stored POSITION contributes once even when the
index stream references it repeatedly; for unindexed geometry, each stored
position contributes once. Empty or wholly non-finite geometry has no
centroid. animsmith reports this evidence but does not move the pivot, recenter
the mesh, or bake transforms as part of measure.
animsmith measure prop.glb
animsmith measure prop.glb --format json > prop.measure.json
Why the distinction matters
- Artists can tell whether unexpected size comes from modeled geometry or an unapplied object/parent transform.
- Engine integrators can choose the domain their importer, preview camera, culling system, or placement code actually consumes.
- Pipeline developers can compare the same source identities across re-exports instead of joining records by non-unique display names.
An unavailable box is not an empty or zero-sized box. Skinned deformation, non-finite transforms, or a definition without finite positions produce an explicit unavailability reason. Scene records also expose excluded instance counts, so a partial static union cannot masquerade as complete coverage.
These are default/rest source-asset measurements. They deliberately exclude animation, skin and morph deformation, runtime component transforms, engine-generated collision, and camera- or frame-dependent bounds. Validate those in the consuming engine.
Inventory material and image handoffs
Before treating a missing detail as an engine-shader problem, inspect the source material graph:
animsmith measure prop.glb --format json > prop.measure.json
For glTF/GLB, the nested measurement data records source-order material definitions and their semantic bindings, then texture-to-image references and image metadata. That exposes, for example, a normal slot that points to the same image as BaseColor, a texture shared by several materials, a linked external image, or an image whose declared MIME disagrees with the container found in its bytes. Successful image inspection reports decoded width, height, channel count, and color type. It does not decide whether a BaseColor texture is authored well, whether a normal map has the intended convention, or whether an engine importer will choose the right settings.
An unavailable image is explicit rather than silently omitted: it has an
unavailability reason and no decoded metadata. A malformed PNG/JPEG may still
identify its container, but is not repaired. Other loaders can report
material_resource_coverage: "unavailable"; do not equate that with an empty
material table. This inventory is source evidence only. It neither resizes or
transcodes images nor makes a later writer or conversion recipe authoritative
for every original resource. Keep the raw handoff asset when byte-level source
provenance matters.
Preserve normal maps as data, not color
A tangent-space normal map stores direction vectors in RGB texels. It changes lighting without changing the mesh silhouette; Unity's normal-map introduction shows why a low-poly surface can retain grooves, rivets, and other fine detail. Blender likewise documents that a tangent-space Normal Map node expects matching UVs and non-color image data.
Ordinary full-scene conversion preserves available linked or embedded PNG/JPEG
base-color and normal images. A glTF normal texture keeps its
normalTexture.scale; an FBX normal map uses glTF's default scale because the
ordinary FBX material does not expose the same scalar. The glTF material model
defines the normal slot and scale
separately from the
sRGB BaseColor slot.
animsmith convert source.fbx -o source.glb
Expected result: a material that had a discoverable normal image still has a normal texture after conversion, and converting or transforming the glTF again does not silently replace it with the BaseColor image or discard its strength. If the original bytes already satisfy the operation, they pass through without being decoded and re-encoded.
What preservation does not fix
animsmith does not decide whether an image is artistically correct, convert a height map into a normal map, flip an engine-specific green-channel convention, generate missing tangents, or configure the engine's texture importer. For example, Unity still requires the texture to be imported as a normal map and documents its expected Y+ convention. A model can therefore contain the right glTF material link and still look wrong under engine-specific import or shader settings.
Bake static placement into geometry
Use --bake-static-mesh-transforms when a static consumer needs final placement
in mesh-local vertices beneath identity nodes. A common example is a prop
modeled under rotated or scaled helper objects: it looks right in the DCC and a
hierarchy-aware viewer, but a consumer that extracts only the mesh definition
gets the unplaced shape.
animsmith convert prop.fbx -o prop-baked.glb \
--bake-static-mesh-transforms
The operation accumulates the accepted rest hierarchy into positions, applies
the inverse-transpose transform to normals and normalizes them, then emits a
canonical identity root with identity mesh children. It retains indices, UVs,
supported material assignments, BaseColor textures, and normal textures.
convert --format json records the source identity, applied world matrix,
determinant, and output identity for each baked instance.
Conceptually, this is the exchange-format counterpart of applying object transforms in a DCC. Blender's Apply transforms documentation explains the artist-side operation: transfer the current object transform into its data while keeping the visible result in place. animsmith's operation is intentionally narrower and fail-closed.
| Before | After |
|---|---|
| Placement may be split across parents and a mesh node. | Accepted placement is in vertex positions. |
| Mesh nodes may have non-identity rest transforms. | Output root and mesh children are identity transforms. |
| A hierarchy-aware viewer is required to reproduce placement. | A consumer of the emitted mesh-local geometry sees the baked placement. |
| Source normals are local to the original mesh. | Normals are inverse-transpose transformed and unit-normalized. |
Rejection is part of the contract
The bake exits with an asset refusal (1) and writes no output instead of guessing
when the source cannot be transformed without violating its preservation
promises. The CLI reference owns the exact
acceptance and rejection list.
It is not a general scene flattener. It does not bake skins or animation, duplicate shared instances, repair winding, preserve artistic pivots or helper hierarchies, choose a project-specific forward axis, or generate collision. Use the DCC or an engine-aware asset build step when one of those changes is intentional.
Attach textures with a recipe
Use --material-texture-recipe when the source material names are trustworthy
but image links are missing, non-portable, or subject to a project size cap.
Do not add a recipe merely because conversion supports one: ordinary conversion
already preserves texture links it can discover.
animsmith convert prop.fbx -o prop.glb \
--material-texture-recipe recipes/prop-materials.toml
A typical bad handoff has a material named surface, an artist-approved
BaseColor image and tangent-space normal image beside the source, but an FBX
whose external paths point to another workstation. A recipe maps the exact,
case-sensitive source material name to those two images. If either path, image,
or material match is invalid, the whole conversion fails before output.
The successful result has one explicit BaseColor/normal pair for every recipe
entry. Images within max_dimension keep their original bytes and MIME type.
Larger images use deterministic, role-aware processing: BaseColor is treated as
color, while a normal map is treated as vector data and renormalized. Conversion
evidence records every consumed and emitted image.
The recipe does not generate textures, invent material matches, pack ORM maps, add unsupported PBR slots, repair UVs or tangents, change normal-map handedness, or judge artistic quality. See the material texture recipe reference for the exact TOML schema, containment policy, limits, and processing contract.
Verify the result in layers
One successful conversion is evidence about the asset operation, not proof of the whole runtime presentation. Keep the raw source immutable and verify each layer explicitly:
# 1. Record source bounds and identities.
animsmith measure prop.fbx --format json > prop.before.json
# 2. Convert with only the operations your handoff requires.
animsmith convert prop.fbx -o prop.glb \
--bake-static-mesh-transforms \
--material-texture-recipe recipes/prop-materials.toml \
--format json > prop.conversion.json
# 3. Measure the written artifact, not only the source.
animsmith measure prop.glb --format json > prop.after.json
Then inspect the written artifact in the target engine:
- confirm size, forward/up orientation, pivot expectations, and hierarchy;
- confirm the shader uses BaseColor and normal textures in the intended slots;
- confirm the normal texture's import type and channel convention;
- exercise runtime culling, LODs, collision, lighting, and instancing;
- retain the conversion evidence beside the derived artifact so automation can explain how it was produced.
That final engine pass stays necessary because animsmith does not know the consumer's shaders, import presets, physics scale, collision generation, or runtime placement policy.
Scaling safely
animsmith scale is an evidence-emitting producer for two different scale
operations. glTF/GLB supports both; the narrow FBX boundary supports only
rest/bind and always emits a new GLB. Neither operation guesses units or scale
policy from geometry, names, or asset categories.
Use the CLI reference for the exact command grammar and machine-readable output for the scale-evidence v4/v5 schemas. This guide owns the operator workflow and the boundary between the two operations.
Choose the operation
| Operation | Use it when… | Physical result |
|---|---|---|
whole-document | The entire source uses the wrong linear unit. | Every represented length changes by the declared factor. |
rest-bind | World geometry is already correct, but one skinned hierarchy carries a compensating inherited uniform scale. | World joint translations and orientations, sampled trajectories, and skinned geometry are preserved while the hierarchy is reparameterized to remove the composed scale. |
These operations are not interchangeable. A rest/bind cleanup must not be selected merely because a character appears too large or small, and a unit conversion must not be used to remove an inherited scale from one hierarchy.
Whole-document conversion takes one finite positive --factor. Rest/bind
requires all three declarations: a raw source-skin index, a raw source-root
node index, and the finite positive factor expected at that root. Use
animsmith measure source.glb to list authored source-node and source-skin
indices; neither selector is a normalized bone id or mesh-instance ordinal.
animsmith scale whole-document source.glb -o metres.glb \
--factor 0.01 --evidence metres.scale.json
animsmith scale rest-bind source.glb -o canonical.glb \
--source-skin-index 0 --source-root-node-index 3 \
--expected-factor 0.01 --evidence canonical.scale.json
There is no in-place mode, plan file, animsmith.toml key, implicit first
skin/root, or per-run tolerance override. Input, artifact, and evidence paths
must be three distinct files. For narrow FBX rest-bind, neither destination
may name a safe dependency key retained by the same-load closure, and a
symlink-refused dependency or terminal closure-budget stop prevents
publication. glTF/GLB artifacts keep the input container; narrow FBX
rest-bind emits a new .glb.
What one run proves
One invocation performs one ordered transaction:
- Parse the source and build a complete capability inventory.
- Compile one format-neutral
ScalePlancontaining the exact topology, payload shape, field ownership, and proof claims for that source. - For glTF/GLB, apply that plan directly to source JSON and buffer bytes; the
normalized model writer is not used. Narrow FBX
rest-bindfirst writes a private normalized GLB, then applies the plan and proof to that GLB. - Reload the exact emitted bytes and run the independent normalized core proof plus the raw artifact proof. The artifact proof also reruns the writer and requires deterministic bytes.
- Stage the artifact/evidence pair, read the staged artifact back, and require its digest to match the bytes that were proved before promoting both files.
A reported refusal publishes neither file and restores any prior pair. The two destinations require two renames, so a process killed between them has the crash window documented in machine-readable output. Proof is not run again after staging: digest equality binds the already-proved bytes to the published file.
The current fixed tolerance identity is appendix-d-v6. It is recorded in
scale evidence together with each evaluated residual and both observed-factor
witness fields. Rest/bind derives those witnesses independently from the raw
source projection and normalized skeleton; whole-document conversion records
the declared factor in both because that operation has no source factor to
measure. There is no runtime alias for an older policy and no per-run tolerance
knob.
Write sets and preservation
Whole-document conversion rewrites represented lengths: node TRS translations
and matrix translation columns, mesh POSITION, translation animation values
and cubic tangents, raw glTF morph-target POSITION deltas, inverse-bind
translation columns, and corresponding accessor bounds. Rotation, scale,
normals, UVs, static and animated morph weights, key times, and other
dimensionless payloads remain outside the write set. Static JSON morph weights
retain their numeric values; animated weight accessor payloads remain
byte-exact.
Rest/bind reparameterization derives every multiplier from the selected raw topology. It rewrites the necessary node-local translation/scale or matrix components, translation and scale animation payload, and affected inverse binds. Mesh positions and the already-correct world geometry remain unchanged.
At factor one, whole-document conversion plans no semantic writes. See the
scale output contract for the exact artifact-preservation
and evidence-field semantics at that boundary.
Supported source boundary
Scale accepts self-contained glTF/GLB for both operations. A .gltf source with an
external buffer or image is refused rather than partially converted. Raw
preflight also refuses any source domain the current model and artifact proof
cannot preserve completely. Whole-document conversion admits only raw glTF
POSITION morph-target deltas with dimensionless static/animated weights;
NORMAL, TANGENT, sparse/interleaved scale-bearing accessors, unsafe aliases,
and every morph payload under rest/bind reparameterization remain refused.
It also refuses cameras and lights, along with GPU instancing, unregistered
extensions, extras, non-triangle primitives, secondary skin-influence sets,
unsafe accessor layouts, and animation targeting a matrix-authored node. The
rejection record contains the complete typed violation inventory.
Do not read the core model's static unprojected-connector rows as a current glTF capability. The glTF loader projects every accepted node today, and the raw writer has no connector-bridge implementation; that path is outside the supported source boundary.
With the default FBX feature, scale rest-bind source.fbx -o canonical.glb
accepts only the inventory-complete normalized subset. It requires the same
explicit skin/root/factor declarations, stages a private normalized GLB,
maps both selectors by exact unique named identity, then uses the existing raw
GLB rewrite/reload/proof path. The same-load gate admits exact marker,
LOD-group, stereo-camera, camera-switcher, and display-layer typed lists
because none can supply rest/bind state to that bridge, plus
shader/binding-table metadata. Display layers contribute only node membership
and editor visibility/freeze/color state; they do not alter projected nodes. It
admits a BindPose only when it covers every joint of each skin it touches and
its converted rows are finite, unambiguous, and agree with the converted
cluster bind or node rest-world matrix already consumed by the bridge; a
source without a Pose remains valid. The raw aggregate still counts all
admitted rows. Residual unmodeled typed lists fail closed with exact nonzero
kind counts. The same-load gate may also admit enumerated scale-invariant
conversion fidelity—omitted authored vertex/face/edge metadata, influence
projection with complete effective coverage, triangulation, and exact-bit
welding—without removing any inventory evidence. Missing influence coverage,
omitted point/line or mesh geometry, unsupported skinning/deformers, incomplete
binds or normals, and unclassified payload remain blockers. It
records the full FBX inventory and staged GLB digest in scale-evidence v5. The
published artifact is a re-encoded GLB: no raw FBX byte, object-property,
marker/camera/display-layer behavior, or authored-curve-key preservation is
claimed. FBX whole-document conversion remains refused. Character assembly
likewise does not silently apply either scale operation. Its existing bind-pose
canonicalization remains a distinct, explicit recipe operation.
Morph support intentionally remains a raw glTF whole-document capability; it
does not add morphs to the shared normalized model or enable rest/bind morph
reparameterization.
Outcomes and evidence
The CLI reference owns exit-status semantics, and the
output reference owns the scale-evidence v4/v5 wire contract
and publication outcomes. In brief, --format json prints the same record that
a successful run writes to --evidence; a refused run prints its rejection
record but never writes the evidence destination because there is no
artifact/evidence pair to publish.
The record carries no timestamp; declared paths remain as entered, and
identical inputs and arguments produce byte-identical evidence.
Embedding the same contract
Rust pipelines use animsmith_core::scale for planning and normalized proof,
and animsmith-gltf for raw preflight, exact-source rewrite, reload, and
artifact proof. Pass one compiled plan through rewrite_scale_plan; do not
substitute the fixture-only analytic reference builder for a format writer.
See the embedding guide and the
crate rustdoc for exact type and error contracts. Appendix D in
DESIGN.md
is the normative algebra and preservation decision; the
calibration notes record how the fixed proof policy is
measured.
Material Texture Recipes
animsmith convert --material-texture-recipe <PATH> attaches explicit
BaseColor, normal, metallic-roughness, and occlusion images to named source materials. It is for assets whose
material links are incomplete or need a reproducible texture-size policy. The
recipe is declarative: it does not generate artistic content.
animsmith convert input.fbx -o output.glb \
--material-texture-recipe recipes/materials.toml
Recipe format
Recipes are TOML documents with immutable identity
urn:animsmith:schema:material-texture-recipe:1. The retrievable JSON Schema
is material-texture-recipe-v1.schema.json.
schema_version = 1
schema = "urn:animsmith:schema:material-texture-recipe:1"
texture_root = "textures"
max_dimension = 1024
[[materials]]
name = "surface"
base_color = "surface-base.png"
normal = "surface-normal.jpg"
metallic_roughness = "surface-metallic-roughness.png"
occlusion = "surface-occlusion.png"
materials is a list. Each entry requires one exact source-material
name, one base_color path, and one normal path. metallic_roughness and
occlusion are optional paths for their corresponding glTF PBR slots. Names are matched exactly,
case-sensitively. A duplicate recipe name is an operator error because the
recipe is intrinsically ambiguous. A valid recipe name that matches no source
material, a source material omitted from the recipe, or an ambiguous source
material is an asset-property refusal (exit 1): it describes the recipe's
fit to the loaded asset rather than invalid syntax.
Every declared entry must be used; a recipe cannot partially apply and succeed.
Unknown recipe fields are rejected instead of ignored.
Run animsmith inspect <input> first and copy names from its top-level
materials section; its quoted names use TOML-compatible escapes and can be
copied directly into a recipe. The mesh instances section shows which material each
primitive references. Ambiguous source names are marked and must be corrected
in the authoring tool before a recipe can target them; inspect does not rename
or merge materials.
max_dimension is required and must be from 1 through 4096. It bounds both
the requested output size and accepted source dimensions. Source image files
are limited to 64 MiB. Only PNG and JPEG files are accepted; their type is
determined from file magic, not a filename extension.
Paths and containment
All paths are interpreted relative to the recipe file. Empty, absolute,
drive-prefixed, and backslash spellings are rejected for consistent
cross-platform behavior.
texture_root is
optional. When it is set, it too is recipe-relative and is a containment root:
the converter canonicalizes the root and every image path and rejects traversal
or symlink resolution outside the root. Missing paths, non-regular files, and
escaped paths are operator errors. Without texture_root, ordinary
recipe-relative resolution applies, including explicit .. components, and no
containment root is declared.
Deterministic processing
The converter processes entries in source-material order and image slots in
BaseColor-then-normal-then-metallic-roughness-then-occlusion order. Recipe declaration order does not affect the
artifact or evidence. If an image already fits max_dimension, it is a no-op:
its original encoded bytes and MIME type are preserved. A resized image is
emitted as PNG RGBA8.
BaseColor resizing converts sRGB into linear light, premultiplies alpha, uses
Lanczos3 filtering, then converts back to sRGB. Normal resizing decodes
tangent-space vectors, uses Triangle filtering, and renormalizes vectors before
encoding. Metallic-roughness and occlusion maps use linear-channel Triangle
filtering: they are not treated as sRGB color or tangent-space vectors. For resized output, the pinned encoder uses Best compression and
NoFilter.
Producer evidence names the pinned primary packages image 0.25.10,
png 0.18.1, and zune-jpeg 0.5.15; Cargo.lock pins their full dependency
closure. Given the same supported-platform inputs, recipe, and tool version,
this policy produces deterministic output bytes.
Evidence
With --format json, conversion evidence records the recipe path and root,
the declared dimension cap, the locked processor identity, every consumed image,
and every emitted texture. Records are ordered as described above. See
machine-readable output for the versioned evidence
contract.
Multi-source character assembly
animsmith assemble combines one authoritative skinned base with animation
takes from separate FBX or glTF inputs. Use it when a delivery spreads clips
across individual files or long master timelines but the runtime needs one GLB.
animsmith assemble character-assembly.toml \
-o character.glb \
--evidence character.assembly.json
The command is available in default builds with the fbx feature. It always
writes a GLB and versioned JSON evidence as one publication pair. It prepares
both files before replacing either destination and restores prior outputs if
the second replacement fails.
This rollback contract covers every error returned by the command. No portable filesystem primitive can make two independently named destinations power-loss-atomic, so after abrupt process or machine termination the consumer must verify the evidence artifact digest before publication.
Boundary
Assembly owns format-generic scene, skin, animation, material, and GLB work. The consuming project still owns source-package extraction, gameplay naming and acceptance policy, cache/generation policy, provenance beyond assembly evidence, and publication. DCC automation is outside this command.
Recipe
Start from examples/character-assembly.toml:
schema_version = 7
schema = "urn:animsmith:schema:character-assembly-recipe:7"
input_root = "inputs"
base_input = "base-character.fbx"
mesh_instances = ["character-mesh"]
remove_nodes = ["sword-placeholder"]
complete_tracks = true
canonicalize_skin = true
ground_and_center = true
prune_constant_tracks = false
fps = 30.0
# Optional for glTF/GLB and the inventory-complete FBX subset; the exact root
# name and factor are required together.
# [rest_bind_scale]
# root_node_name = "root"
# expected_factor = 0.01
[[clips]]
name = "idle"
input = "idle.fbx"
take = "Take 001"
frame_window = [1, 61]
drop_closing_endpoint = true
strip_bones = ["motion-root"]
All input paths are relative to input_root, which is itself relative to the
recipe. Absolute paths, parent traversal, symlink components, and paths that
escape the canonical root are rejected. Output paths are command arguments so
the recipe remains relocatable.
The input root is an operator-controlled snapshot: do not mutate or replace its entries concurrently with assembly. Symlink checks and reads are separate portable filesystem operations, so the command does not claim to defend against an actor racing the input tree while it runs.
Discover exact recipe names
Inspect the base before writing the recipe:
animsmith inspect inputs/base-character.fbx
Copy the quoted name after node in the mesh instances section exactly into
mesh_instances; quoted names use TOML-compatible escapes. Each entry also
shows its source node, referenced mesh,
skinned status, and the material used by every primitive. The top-level
materials section lists the exact, case-sensitive names required by a
material texture recipe. Duplicate skeleton node
or material names are marked ambiguous; assembly and material recipe
validation reject ambiguous authored names instead of guessing. Multiple mesh
instances attached to one uniquely named node are not ambiguous and are
selected together. inspect only reports the authored scene—it does not rename
nodes or materials, merge duplicates, or repair the asset.
The base supplies the authoritative skeleton, rest pose, meshes, skins,
materials, and textures. mesh_instances names the exact base nodes whose
mesh instances survive; omitting it retains every instance. An explicit node
name must be unique; every mesh instance attached to that selected node
survives. Unreferenced mesh and material definitions are pruned.
For a recipe-v7 FBX base, assembly resolves this selection once against the
captured FBX node names and carries the selected scene through its private GLB
scale stage. Any holder names generated by that private serialization are not
a second recipe namespace. When rest_bind_scale is enabled, an explicit
selection that retains no skinned base instance refuses as an
asset-recipe-mismatch at the selection boundary; its diagnostic names the
recipe selection rather than the private staging artifact.
Each [[clips]] entry selects one exact source take, renames it, and remaps
its tracks to the base skeleton by exact unique bone name. Missing referenced
bones and ambiguous names fail rather than retarget motion. An entry may then
apply:
frame_window = [START, END]: one-based, inclusive source frame endpoints atfps, ortime_window = [START, END]in seconds;drop_closing_endpoint = true: remove one final key from every channel;hold_frames = N: duplicate the final pose afterN / fpsseconds;gait_anchor = true: explicitly declare an in-place cyclic gait and run the measured gait-anchor transform using the selectedanimsmith.tomlrig configuration. Assembly refuses before publication when the Root role (or Hips fallback) has missing/non-finite trajectory evidence, more than 1 cm of horizontal endpoint displacement, or more than 1° of yaw accumulation; no interior step is subtracted as an allowance. Every nonconstant channel the operation would rotate must contain exactly one key at each declaredfpswhole-frame sample over the clip duration, at the exact representable f32key / fpstime and period endpoint. Sparse, differently framed, duplicate-time, or off-grid evidence refuses, as do duplicate(bone, property)channels (including constant channels). Exact admitted f32 key times drive verification, and an integer-index permutation performs the rewrite; exempt constant tracks cannot influence its period or shift. The complete skeleton, resolved roles, and track shapes are validated first; declared frames × skeleton bones, declared frames × tracks, and maximum authored keys × skeleton bones are independently bounded at an inclusive 1,000,000 samples. Yaw uses f64 first/final headings plus counted full-turn crossings. At sample zero it chooses the local+Z,+Y, or+Xbasis axis with the greatest finite horizontal projection, in that deterministic tie order, and retains it for the entire proof. This supports different source-axis conventions without allowing a later axis switch to hide yaw; loss of the selected horizontal projection refuses. Error does not grow with segment count; four f32 successors at the inclusive 1 cm/1° caps cover only authored endpoint translation/quaternion quantization;strip_bones = [...]: remove every TRS track for named base bones.
Optional rest/bind scale canonicalization
Recipe v7 can opt into the accepted rest/bind scale operation with one top-level block:
[rest_bind_scale]
root_node_name = "root"
expected_factor = 0.01
The block has no defaults: the exact normalized root-node name and finite
positive expected factor are both required. In the captured base, the name
must resolve to exactly one source node and exactly one non-empty source skin
whose every joint is that node or its descendant. Every distinct FBX
clip-only input uses the track-only case described below. A glTF/GLB clip
retains its existing successful full rest/bind or meshless track-only path;
role-specific admission additionally selects the track-only path when only
geometry, material, deformation, or bind obligations that assembly never
consumes would fail. Framing, parser, topology, dependency, raw-coverage,
named-skeleton, and animation accessor/layout checks remain strict.
Missing or repeated names and zero or multiple applicable base skins fail
closed. Leading or trailing
whitespace is invalid rather than
silently trimmed; internal whitespace remains exact. No
source-array index is reused across files. Recipe v7 accepts glTF/GLB and the
narrow FBX subset admitted by the existing normalized/baked rest-bind
capability boundary. That boundary
requires a complete ufbx inventory for the normalized metre/Y-up hierarchy,
derived bind evidence, rebuilt geometry, and baked animation. Its same-load
gate may admit enumerated scale-invariant conversion fidelity, including
omitted authored vertex/face/edge metadata, influence projection with complete
effective coverage, triangulation, and exact-bit welding, while retaining every
inventory fact. Incomplete source-skeleton projection, unclassified payload,
missing effective influences or geometry, incomplete bind evidence, residual
unmodeled source elements or extensions,
incomplete resource/construct coverage, or an incompatible basis fails closed.
The source-aware gate may admit user-defined properties and bounded external
texture/video declarations because neither supplies rest/bind state to the
normalized GLB bridge. It also admits exactly ufbx's marker, LOD-group,
stereo-camera, camera-switcher, and display-layer typed lists: they cannot
supply hierarchy transforms, skin binds, tracks, or geometry to that bridge.
Display layers contribute only node membership and editor
visibility/freeze/color state; they do not alter projected nodes. The gate
admits shader/binding-table metadata on the same basis. A BindPose is admitted
only when it covers every joint of each skin it touches and its converted rows are
finite, unambiguous, and agree with the converted cluster bind or node
rest-world matrix already consumed by the bridge; no Pose remains required.
Those elements remain counted in the raw aggregate, while every residual
unmodeled typed list fails closed and is named by kind and count in the
refusal. Rooted capture
retains supported linked texture bytes through the normalized artifact, and
neither publication destination may name one of the safe source-relative
dependency keys retained by that same-load closure, including a key whose
capture was unavailable or refused. The command
stops before publication when a retained key was refused as
symlink-mediated rather than resolving through the link, or when the closure
resource budget leaves a declaration tail unchecked. The frozen v7 evidence
retains declaration counts in its capability inventory; same-load raw
facts and closure remain loader-side rather than being added to that wire
record. Admission does not claim raw FBX object-property, marker, camera,
material, or texture-assignment preservation. A
refusal identifies the exact failed source fact, semantic domain, or inventory
counter. Nothing is inferred from filenames, bounds,
character height, inverse-bind magnitudes, or source units. Recipe v6 remains
immutable with its explicit source indices;
recipes v4/v5 remain immutable and continue to reject FBX when
rest_bind_scale is active.
Recipe v7 retains recipe v6's composition with canonicalize_skin,
ground_and_center, and
remove_nodes. It captures and validates the raw inputs first, applies the
same mesh selection and canonical assembly transforms to the staged candidate
and an independently source-rebased reference, then maps selectors by names and
ordered selected joint topology after any index changes. It serializes the
staged candidate, performs the raw rest-bind rewrite last, reloads those exact
bytes once for proof, and atomically publishes only after the final clips match
the rebased reference. Recipe v4 remains immutable and continues to reject
that combination.
Before any clip keys are remapped or copied, assembly captures and validates
the exact bytes of the base and every separately supplied clip. Each input
must agree on the selected domain and factor, named parent topology, target
local rest matrices and orientations, helper-node layout, and coordinate
convention. Each input's own target paths and plan-owned effective factors are
validated and fingerprinted; distinct clips need not contain identical target
paths. A matching digest alone is not compatibility evidence. Topology,
orientation, helper-layout, basis, selector, or factor disagreement rejects
the complete operation before publication.
Topology, resolved names, and the declared factor compare exactly. V7 compares
the exact resolved root and ordered selected-skin joint names plus the named
source/helper parent paths; format-local skin/node numbers remain per-input
evidence. Each input's selector identity is derived from its accepted plan and
source document; a declared/resolved disagreement rejects the operation.
V6 retains exact raw-selector comparison.
Rest translations, scales, and matrices use the named Appendix D tolerance
policy, while quaternion orientation uses shortest-path angular distance, so
equivalent q/-q spellings do not become false incompatibilities.
For a compatible clip, translation values and both translation tangents of a
CUBICSPLINE track are rebased in the source basis before exact-name remapping.
Assembly reuses the same validated scale request, compiled write-set, raw glTF
rewrite, tolerance policy, and paired proof residuals as animsmith scale.
The assembled artifact is serialized, reloaded from its exact staged bytes,
and proved once before the artifact/evidence pair can be published.
complete_tracks = true fills absent TRS channels from the base rest pose for
the union of selected skin joints and nodes targeted by any emitted clip.
Per-clip strip_bones entries stay excluded. Rotation key values are
unit-normalized and made hemisphere-consistent without changing cubic tangent
sign relationships. When FBX scale staging produces an independent reference
skeleton, completion targets are carried by exact unique bone name and resolved
separately in each skeleton; numeric bone IDs are document-local. Each
skeleton's own node-removal closure is excluded before completion.
canonicalize_skin = true bakes each skin's common geometry-to-world transform
into positions and normals and rewrites inverse binds consistently.
ground_and_center = true additionally places the bind-pose geometry on Y=0
and centers it in X/Z while translating skeleton roots and root-translation
animation tracks by the same offset. Inconsistent skin transforms, singular
normal transforms, or shared mesh definitions that require incompatible bakes
are rejected.
prune_constant_tracks = true removes tracks that the existing core constant-
track predicate proves constant, after completion, quaternion cleanup, and all
other assembly transforms. This is all-property pruning: it can remove
completion-generated (bone, property) coverage when the completed value is
constant, so it may undo part of complete_tracks = true. The carve-out uses
the effective output clip's animates_bones exact names; it never uses
required_bones, so declared motion evidence is preserved without retaining
unrelated tracks. For an independent scale reference, authoritative prune
decisions and the final clip proof are projected by exact unique bone name and
channel property rather than by document-local bone or track indices. The
canonical example leaves pruning disabled; opt in only after reviewing the
completed clip and the consumer's transition behavior. A consumer that does
not explicitly reset an omitted property during a transition can retain the
outgoing clip's value, so leave pruning disabled where dense transition
coverage matters until property-scoped selection is available (see
#401). Set it to false or
omit it to retain every otherwise eligible track.
remove_nodes = [...] selects exact, case-sensitive names from the
post-canonicalization base skeleton and removes each selected node together
with its complete descendant subtree. Every name must resolve exactly once;
an absent or ambiguous name fails rather than being skipped. Overlapping
ancestor and descendant selections are allowed and their union is removed
once, in original parent-before-child node order. Selecting the entire
skeleton is refused. Surviving descendants are never reparented because every
descendant of a removed node belongs to the removal closure.
Removal is planned before track completion, and closure nodes are excluded
from completion targets. The structural projection is applied only after
clip processing and constant-track pruning. It is
refused if a final track still targets the closure, if a skinned mesh instance
is attached to it, or if a selected node remains referenced by a skin joint or
complete source-skin identity. For an FBX input with rest_bind_scale, an
unskinned mesh instance attached inside the declared closure is excluded from
the private normalized scale stage and removed with that closure. The same
instance still blocks scale when its node is not declared for removal. This is
the only attachment reference consumed implicitly; per-clip strip_bones and
constant-track pruning remain necessary to make an animated decorative
subtree removable. The effective clip's
animates_bones configuration still protects named tracks from pruning, so a
protected surviving track causes removal to fail. [rig] required_bones is a
lint presence policy, not a node-removal selector or protection list.
Errors are deterministic and no output is published on any refusal. A valid
recipe that does not fit loaded asset facts is a typed asset refusal (exit
1); JSON mode emits
producer-refusal:1, while text
mode reports the same stable kind on stderr. Recipe syntax/schema/value,
input/path, and publication failures are operator errors (exit 2) and emit
no stdout. Recipe schema and value validation precede input loading. After the base's selected
mesh and canonicalization steps, selectors and their descendant closure are
resolved deterministically and the whole-skeleton rule is checked. After all
clip transforms, every surviving track, mesh attachment, skin reference, and
complete source-skin fact is checked before the hierarchy is changed. The
reported refusal identifies the offending stored record; traversal order is
an implementation detail rather than a wire contract.
Accepted projection preserves the original order and parent links of every
surviving node, remaps all surviving node references through one stable map,
and clears optional source-native skeleton projection because it can no longer
describe the authored source completely. Node removal performs no mesh,
material, or texture garbage collection. Outside the bounded FBX scale
composition above, a mesh instance inside the closure is refused. The earlier
explicit mesh_instances selection and its existing resource pruning remain
the only assembly resource-garbage-collection pass. Evidence counts an
unskinned instance consumed by declared node removal in
transforms.removed_mesh_instances and omits its node name from
retained_mesh_instances.
An optional material_texture_recipe reuses the exact material-name recipe
boundary described in Material texture recipes,
including BaseColor, normal, metallic-roughness, and occlusion slots.
The normative current recipe schema is
character-assembly-recipe-v7.schema.json.
Recipe v1 through v6 remain immutable historical contracts. Migrate a v6
rest/bind recipe by changing its identity to v7 and replacing both source-index
fields with root_node_name. Omitting the block remains ordinary assembly
behavior; v3 continues to reject the block as unknown.
Evidence and determinism
The current evidence identity is
urn:animsmith:schema:character-assembly-evidence:7. It records the effective
recipe, recipe and input SHA-256 digests, the selected configuration file's
declared path and digest (or an explicit built-in-defaults marker), selected takes and windows, exact
source/base bone remap names and indices, and track
operation counts, start/end/delta facts for named translation tracks removed by
strip_bones, mesh selection, canonicalization flags, tool identity, and the
final artifact digest and counts. When pruning is enabled, each clip records
the exact removed tracks in pruned_constant_tracks: the index in the
completed, normalized output clip immediately before pruning (retaining its
pre-prune authored order), exact bone name and bone_index in the
post-canonicalization/pre-node-removal skeleton, TRS property, interpolation,
and key count. bone_remaps.base_index uses that same pre-removal coordinate
space. Consumers derive a surviving final index by subtracting earlier
transforms.removed_nodes[].original_node_index entries; a pruned track whose
node is later removed intentionally has no final-artifact node index.
The array is empty when pruning is disabled or no track is removed. The
top-level transforms.removed_nodes array records every projected node exactly
once in original pre-removal node order: its exact name, original node index,
nullable original parent index, and whether the recipe selected it directly
rather than through an ancestor. It is empty when remove_nodes is omitted or
empty. See
character-assembly-evidence-v7.schema.json.
When rest_bind_scale is active, v7 pins each exact base/clip
input digest and its versioned basis fingerprint, the declared root name,
each input's resolved root name/source-node index, the source-skin index for
full rest/bind inputs, and the
effective staged selectors after canonicalization/removal,
the factor, every compatibility result, and the shared final-artifact scale proof.
The staged-source digest and exact emitted-byte read-back digest make the two
serialization boundaries explicit; the read-back digest equals the published
artifact identity on every accepted run.
Each input records application = rest-bind with fingerprint contract
urn:animsmith:character-assembly-scale-basis:1, or
application = skinless-clip-tracks with
urn:animsmith:character-assembly-skinless-clip-scale-basis:1, so consumers
never infer the applied domain or version from the digest. The operation record also carries
the sibling residual_comparison_counts record pairing every shared
maximum residual to its comparison count by the same stable field name. These
records let a consumer verify that assembly validated and transformed every
participating input before remapping and that proof consumed the exact bytes
subsequently published.
V7 constructs an explicit animation-only projection for each distinct FBX
clip-only input and for each glTF/GLB clip admitted to the track-only path. The
projection retains complete source-skeleton node coverage
and every normalized take, while excluding source meshes, mesh instances,
materials, skins, and inverse-bind state that cannot contribute to assembly.
The captured input digest and source-projection evidence still describe the
unmodified source, including unsupported FBX deformation or glTF bind payload.
For glTF/GLB, the role-specific raw gate excludes only obligations wholly owned
by those projected domains; malformed animation accessors, matrix-target
animation, unsafe dependencies, and incomplete raw construct coverage still
refuse. The clip projection's
exact named root, every joint in the selected base skin, and the named
ancestors connecting that rig domain must match the accepted skinned base
basis in normalized topology. Rest translation, rotation, and scale remain
strict independently for every node/property that any take omits; a component
may differ only when every take explicitly authors that named channel, so
assembly never falls back to the differing clip rest. Each actual clip track
target and its named ancestors join that compatibility domain, and the target
must belong to the base plan's affected closure. Base-only geometry and
attachment descendants do not become clip requirements merely because the
base plan rewrites them. The
base plan supplies each named translation/scale target factor; cubic-spline
in/value/out triplets are rebased together. A skinless base still refuses, as
does any clip projection with incomplete source coverage, an incompatible
named basis, or a track outside the base scale domain. Bind matrices are
neither synthesized nor claimed for the track-only row. The retained
skinless-clip-tracks label names the projected operation domain, not a
requirement that the captured source was skinless. The exact final
assembled artifact remains covered by the shared skinned-base rewrite and
proof. Independently serialized animation-only FBX clip projections are
compared under that proof's Appendix-D numeric tolerance; full rest/bind inputs
retain exact pre-remap oracle comparison.
Each v7 scale-input row also identifies the captured container and its source
projection. raw-gltf records authored-curve and raw-span preservation.
normalized-baked-fbx explicitly records both as false, embeds the complete
FBX capability inventory, and binds the private normalized GLB stage by digest
and byte count. When declared node removal consumes an unskinned instance, that
private stage excludes the instance while the raw input digest and capability
inventory remain unchanged. This stage is not a second public artifact or evidence chain:
assembly still serializes, rewrites, reloads, proves, digest-checks, and
atomically publishes one final GLB/evidence pair.
Evidence v1 through v6 remain immutable historical contracts; v1 does not
describe pruning, v1 and v2 do not describe structural node removal, and none
of them describe rest/bind scale compatibility. Consumers migrating from v2 or
v3 must verify removed_nodes against the effective
recipe and output hierarchy rather than inferring removal from track evidence
or lint configuration.
Given identical recipe bytes, input and config bytes, tool build, paths, and platform, repeated runs emit byte-identical GLB and evidence. Evidence keeps operator-declared paths rather than leaking canonical host paths.
Collection contract extensions
This document records versioned extensions to the file-scoped collection decision in DESIGN.md Appendix F. Contact-fragment V1 includes its strict standalone and manifest-selected producer/CLI/publication boundary. The directional-speed policy/evaluation V1 is a separate ordered 0.6.0 slice with one JSON-only evaluator command. The contact-transform result and foot-cycle map planner are format-neutral contracts; animation rewriting, collection publication, and runtime systems remain separate work.
Directional-speed policy V1 (#552, ordered slice)
The strict collection-directional-speed-policy:1 reader is an ordered,
manifest-bound declaration for the directional-speed evaluator. It is a
separate TOML envelope: it does not add fields to collection-manifest V1,
revise collection-output V2, or infer membership from filenames or paths.
animsmith collection evaluate-directional-speed --policy POLICY.toml --evidence COLLECTION-OUTPUT.json --format json binds its raw bounded inputs to the
typed reader, strict current collection-output V3 adapter, pure evaluator, and
immutable result. There is no output file, text/Markdown, subset, or inference
mode; publication remains a consumer responsibility.
The envelope repeats the exact manifest identity (collection_id plus
{sha256, bytes}), one directional-blend runtime_set_id, and every existing
logical member exactly once in manifest order. Each member has one nonzero,
unique semantic [x, z] coordinate. source_basis.x and source_basis.z
are orientation witnesses for raw collection-output V3 +X/+Z endpoint
displacement in that semantic plane; their magnitudes are nonsemantic, and the
evaluator uses unit axes for heading. They must be finite, bounded,
nonzero, and perpendicular. diagonal_behavior is closed and applies to
unit-input/base targets: for coordinate c, preserve uses gain
g(c) = hypot(c) while normalize uses g(c) = 1. Thus uniform expected
speed is base * g(c), authored expected speed is the member's base
speed_mps * g(c), and ratios compare
expected_measured_ratio_i_to_ref = declared_expected_ratio_i * g(c_i) / g(c_ref), with the reference declaration fixed at 1.0. Direction comparison
is unaffected by this gain.
The declaration also carries finite bounded direction_tolerance_deg in the
inclusive range 0..=180 degrees. The pure evaluator normalizes raw endpoint
displacement before source-basis projection and uses published speed_mps for
magnitude (not travel). Its immutable result binds raw policy and evidence
InputIdentity values and retains a manifest-ordered row for every member:
policy coordinate, raw evidence, projected heading, comparison values,
tolerances, deviations, and pass/violation outcome. Incomplete root travel,
zero endpoint, zero ratio reference, and numeric range are typed
not-evaluated outcomes, never implicit passes, failures, or subsets.
uniform requires uniform_speed_mps and speed_tolerance_mps; authored
requires each member's speed_mps plus speed_tolerance_mps; and ratios
requires a declared reference_member, each member's expected_ratio, and a
dimensionless ratio_tolerance. Mode-inapplicable fields are rejected. The
strict reader rejects parse/declaration failures including unknown or
duplicate fields, unknown tokens, malformed identities, duplicate member
ids or coordinates, invalid coordinates or basis, nonfinite/out-of-range
values, and N+1 members before retaining them. Separate binding validation
rejects stale manifest identity, wrong runtime-set id or kind, and missing,
extra, or reordered member sequences. No licensed asset data belongs in this
declaration or its tests.
Foot-cycle parameterization V1 (#18, planner slice)
urn:animsmith:schema:foot-cycle-parameterization:1 is a strict bounded TOML
declaration for one existing collection-manifest gait-group. It repeats the
exact manifest identity, runtime-set id, exact ordered members, one explicit
reference member, one safe contact-fragment path per member, finite positive
minimum/maximum segment slopes whose inclusive interval contains 1.0, and one
safe future output-directory path.
The reference owns canonical contact-boundary phases. Membership, ordering,
contact paths, and reference ownership are never inferred from filenames or
clip names.
schema = "urn:animsmith:schema:foot-cycle-parameterization:1"
schema_version = 1
runtime_set_id = "com.example/sets/walk"
reference_member = "com.example/walk-forward"
output_directory = "generated/walk-aligned"
minimum_segment_slope = 0.5
maximum_segment_slope = 2.0
[proof]
max_gait_phase_spread = 0.08
min_lr_amplitude_m = 0.05
max_contact_boundary_phase_error = 0.01
[manifest]
schema = "urn:animsmith:schema:collection-manifest:1"
schema_version = 1
collection_id = "com.example"
[manifest.input]
sha256 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
bytes = 1234
[[members]]
id = "com.example/walk-forward"
contact_fragment = "contacts/walk-forward.json"
[[members]]
id = "com.example/walk-right"
contact_fragment = "contacts/walk-right.json"
The reader is closed and bounded at 8 MiB and 4,096 members. Planning admits at
most 16,384 contact events and 32 MiB of canonical contact-fragment bytes
across the whole declared ring, so independent per-fragment limits cannot
multiply into unbounded retained topology or canonicalization work. Exact
supplied byte counts are preflighted before canonicalization; a false count is
then caught by exact canonical byte-count and digest comparison on the first
such row.
Safe locators use the same lexical relative-path contract as collection
manifests. It rejects unknown fields, unsupported identities/versions, unsafe
or duplicate paths, duplicate/missing members, an absent reference, invalid
manifest identities, and invalid slope ranges. The required closed [proof]
table declares finite max_gait_phase_spread and
max_contact_boundary_phase_error values in the inclusive range [0, 0.5]
plus a finite non-negative min_lr_amplitude_m. These exact values are bound
by the parameterization byte identity and retained in the plan; V1 has no
defaults, gait-group/config lookup, filename inference, or cross-member policy
merge. The published
foot-cycle-parameterization-v1.schema.json
describes the decoded TOML shape. Host canonicalization,
symlink/alias detection, and the guarantee that output_directory is a
previously absent sibling generation destination are enforced by the private
source-preparation adapter described below; this parser performs no filesystem
I/O.
The pure core planner additionally requires each supplied sidecar byte identity
to equal its canonical contact-fragment:1 identity and its collection clip
witness to equal the manifest's exact logical/source/take-index/take-name row.
Each member also supplies independently measured typed Root/Hips evidence bound
to the fragment's exact artifact, dependency closure, and collection clip
source/take witness. Cross-wired or stale measurements refuse before their
values are used. A
missing, ambiguous, malformed, or non-finite witness refuses. Evidence retains
signed endpoint X/Z displacement and signed accumulated unwrapped yaw. The
unchanged admission derives horizontal displacement with binary64 hypot(X, Z)
and uses absolute yaw; values above 0.01 m or 1 degree refuse, and both
thresholds are inclusive. The planner consumes and retains those facts but does
not sample an animation asset itself.
V1 recognizes exactly AnimSmith's contact-support-detector:1 extension and
validates its closed algorithm, sampling, frame-cap, threshold, and selected
left/right foot-or-toe role provenance. Every member must carry the exact same
finite non-negative contact_height_m binary64 value because that policy
determines the compared window boundaries. Other extensions refuse because no
operation-specific transform handler has run.
The private CLI-crate preparation adapter now bounded-reads the exact manifest
and parameterization, resolves the exact member-reachable source/config subset
in canonical manifest source-key order plus every declared contact fragment and
absent output directory under its declaration root, and rejects symlinks,
canonical collisions, and (where the platform exposes file identity) hardlink
aliases before source parsing. It first validates the pure exact runtime-set
binding, then loads only its member-reachable manifest sources/configs in
manifest order; unrelated source rows do not gate this scoped operation. It
captures each distinct canonical config once and shares that immutable
normalized snapshot and explicit byte identity across its sources for later
proof without a control-file reread. Distinct retained config snapshots share
an inclusive 32 MiB invocation ceiling; repeated use of the same canonical
config is counted and captured once. It requires complete
dependency-closure identity and exact raw take index/name witnesses,
counts primary, every retained external dependency identity, and normalized
document semantic payload against per-source and invocation byte ceilings. Each
loaded document passes strict structural shape validation before sampling or
candidate work. The adapter then preflights both frames * bones pose cells
and frames * tracks sampling work for every selected member and admits the
complete multi-member totals against the inclusive one-million-cell/work
ceiling before constructing the first metric grid. The prepared in-memory
collection retains those exact source pose-cell and sample-evaluation totals so
a later output proof can share one invocation budget without resampling or
rereading controls. After planning, it runs the
core clip-candidate preflight for every member and checks invocation-wide
candidate-key, retained-value, exact candidate-storage-byte (including name),
and work totals before constructing the first candidate.
It then
derives Root/Hips endpoint X/Z hypot and absolute unwrapped yaw through the
existing metric grid/role/trajectory authority, and cross-checks every returned
plan binding and duration before calling time_warp_clip_v1. Any refusal drops
the whole in-memory batch and creates no output or partial result.
Preparation deliberately stops contact transformation at a typed continuation.
It validates and reconstructs the closed stance-detector payload through the
operation-specific core handler, but a fresh output artifact identity and its
dependency closure do not exist until the candidate document is serialized.
Only that later serialization step may supply those exact identities and call
transform_contact_fragment_v1; preparation does not hash an in-memory Clip,
claim a generated artifact identity, serialize, reread/prove, or publish. No
user-visible command is added by this slice. A freshly serialized exact-copy
candidate may truthfully retain the input content identity; freshness comes
from capturing the serialization result, not from requiring unequal digests.
Each member must contain complete positive left/right support windows, each
with exactly one same-side marker. Fragment rows stay physically linear:
windows are partitioned by side, and same-side runs must be strictly disjoint
(even touching same-side runs refuse). When one side has both a first run
starting at 0 and a distinct last run ending at 1, those two physical rows
form one logical cyclic stance. Their artificial seam edges are omitted from
topology correspondence, while both windows and both markers remain in the
transformed fragment. A single full-cycle [0,1] stance refuses. Opposite-side
windows may overlap or touch. After seam coalescing, both sides must have the
same nonzero logical-run count and their onsets must alternate around the
cycle.
The planner derives true logical onsets/releases in deterministic cyclic chronological order, including a fixed kind order for simultaneous phases. It rotates only the correspondence sequence to a true left-support onset; it does not rotate numeric clip phases. Therefore the reference boundary phase remains its authored normalized phase and a member that would require moving phase zero, including incompatible wrap/non-wrap correspondence, refuses as non-monotone.
Independent output proof first requires the expected and freshly detected
(side, edge) sequence in absolute phase-zero chronological order to match.
It then compares those phase-zero-paired boundary phases with circular numeric
distance, but a complete left/right role swap is a topology failure even at
the maximum allowed boundary-error tolerance.
For matching signatures, corresponding source onsets/releases map to the
reference phases. Simultaneous source boundaries are admitted only when all
corresponding reference boundaries are simultaneous at one phase, and vice
versa. Identical (source, output) pairs collapse to one map knot; a group
split on either axis refuses. (0,0) and (1,1) are then included in that same
deduplication, so the planner preflights the 4,096-point contact-transform cap
from the retained knots rather than raw boundary rows. Every resulting segment
must be finite, strictly increasing, continuous, and within the inclusive
declared slope range. The planner returns one duration-preserving
ContactTransformOperationV1::TimeWarp plus exact
ContactTransformBindingV1 per member.
The separate pure time_warp_clip_v1 seam consumes one such member plan only
after its caller has selected and bound the corresponding loaded clip. It
strictly validates track-local clip/track shape; because this seam has no
skeleton input, its host must already have bound every track bone index to the
selected, validated skeleton. Fixed aggregate name-byte, track,
authored/generated key/value, and work caps refuse before candidate allocation;
candidate storage has a derived public upper bound from those admitted rows.
LINEAR tracks map every authored key and insert interior map-knot
samples; STEP tracks map only authored breakpoints. A map knot and an authored
key that are the same instant in the emitted binary32 domain — equal, or one
representable place apart, since that domain holds nothing between them — are
one emitted key: it keeps the authored time and the authored value and carries
the control point's own output, because the control point is the definition of
the map at that instant. FootCycleClipWarpKnotV1 is the one definition of that
question and time_warp_rows_v1 the one answer to which keys a track emits and
in what order; the independent output proof consumes those rows for the
sequence alone and derives every expected time and value itself, a knot's own
times included. Regression tests freeze those rows and independently perturb
producer output times for both knot-bearing row kinds and source times for
inserted knots. A coalesced row always retains its authored value: its source
time is unused metadata after row selection, and perturbing it alone does not
invalidate the correct candidate.
That holds for a track's own first and last authored key too: the span test is
inclusive of one place on either side, so which side of a rounding step the
reconstructed instant lands on does not decide whether that key takes its
control point's output. Where two authored keys are one place apart, a knot
binds to the key it is bit-equal to rather than the key it is merely beside,
and both keys are retained. The one exception is a map knot that is the same
instant as a source endpoint, 0 or the duration: the map's exact endpoint
rows define the output there and a candidate retains those two instants as
themselves, so such a knot contributes nothing. Two knots that name one instant
refuse: each is compared against both instants of the knot before it, its own
resolved source time and the instant it named, because a knot that coalesced
into an authored key answers to a time one place from its own. Distinct output
instants that collide after binary32 narrowing refuse too. One-key CUBICSPLINE
tracks are retained, and multi-key cubic tracks are retained only for bit-exact
constant values with zero tangents. The value caps are the shape-derived
three-values-per-cubic-key maxima, so malformed N+1 storage refuses at shape or
key bounds before it can become a separate valid value-only case.
Neither pure seam transforms contact fragments, derives root measurements, binds the selected asset to the plan, serializes output artifacts, proves reread results, or publishes a generation directory; those remain later #18 transaction seams described below.
Foot-cycle generation V1 (#18)
The public producer is deliberately one collection command with no destination override:
animsmith collection transform-foot-cycle COLLECTION.toml \
--parameterization FOOT-CYCLE.toml --format json
The parameterization's output_directory is resolved relative to that
document and must name a previously absent directory below an existing safe
parent. The command is available in the glTF-only build; .fbx sources remain
available only when the fbx feature is enabled. Every admitted source,
including .gltf, is emitted as a self-contained GLB.
For N members, the generation has exactly 3N+1 files in parameterization
member order:
members/000000/artifact.glb
members/000000/contact-fragment.json
members/000000/evidence.json
...
aggregate-evidence.json
Logical ids and source keys are evidence fields, never filesystem names. The
member evidence identity is
urn:animsmith:schema:foot-cycle-member-evidence:1; it binds exact manifest
and parameterization inputs, source artifact/config/complete closure, output
artifact/complete closure, transformed and independently redetected contact
identities, canonical operation identity, declared proof policy, and reread
proof residuals. The aggregate identity is
urn:animsmith:schema:foot-cycle-aggregate-evidence:1; it repeats the control
bindings and policy, preserves ordered member paths and identities, and records
checked 3N+1, component bytes, exact total, retained GLB bytes, and
source/output/combined metric work. See
foot-cycle-member-evidence-v1.schema.json
and
foot-cycle-aggregate-evidence-v1.schema.json.
All GLBs, canonical transformed fragments, member evidence, and aggregate evidence share one 256 MiB retained/staged cap in addition to their stricter component limits. Every candidate, contact transform, independent reread proof, and bounded serialization finishes before the first filesystem write. The publisher flushes the fixed file set beneath one sibling temporary directory and performs one no-replace directory rename. It does not replace an existing generation or claim power-loss, network-filesystem, or abandoned-temp cleanup guarantees.
Success exits 0 and writes the exact published aggregate-evidence.json bytes
to stdout only after publication. A broken stdout is diagnosed best effort and
cannot undo or relabel the generation. Asset/capability/map/contact/proof
refusal exits 1 with one producer-refusal:1 JSON record and no destination.
Malformed or stale controls and bindings, unsafe paths or aliases, I/O,
serialization, or publication/race failures exit 2 with empty stdout and no
published destination.
Contact fragments (#147)
The contact-fragment V1 identity is
urn:animsmith:schema:contact-fragment:1.
AnimSmith 0.6.0 ships #152's format-neutral strict core reader, bounded
canonicalization seam, and strict one-clip producer. generate contact-fragment includes a manifest-selected collection form that reloads
the declared source rather than reading collection-output evidence.
Transforms and runtime systems remain out of scope.
It is an importable envelope that
can be merged into a host's one authoritative measured sidecar. It binds
contact facts to the exact primary source bytes and complete versioned
dependency closure, plus producer/tool version and an unambiguous clip
reference. artifact is the existing primary InputIdentity;
dependency_closure_identity is the existing complete
DependencyClosureIdentityV1 rather than the full output-v10 closure record.
Both serialize as {sha256, bytes}: sha256 makes the algorithm and digest
explicit rather than adding separate algorithm and digest fields. Partial
or unavailable dependency coverage refuses fragment generation, and a
mismatch in either identity makes an existing fragment stale.
The complete captured closure's primary_input must equal artifact; producer
and consumer validate that relationship against the captured closure.
V1 deliberately binds every dependency in the complete modeled closure,
including dependencies such as textures that may not affect contacts. This can
refuse generation for an unavailable unrelated dependency, but avoids a second
format-specific relevance policy.
Collection-owned clips use the #409 logical identity
plus its source, take-index, and exact take-name witness. Standalone
documents use an exact embedded clip/take name scoped by both input identities;
ambiguous or duplicate names are refused. No animation-array index, filename,
or engine asset handle is an identity.
Collection-manifest V1 rejects duplicate logical clip ids while parsing as a
collection control error, before collection generate-contact-fragment can
select a clip. A valid collection take-index addresses the raw source take row;
the producer resolves that row's complete normalized-index witness before
sampling its internal document clip.
Points and windows use normalized clip time [0, 1], with the measured
positive duration recorded for validation. Events have stable opaque ids,
engine-neutral roles and phases, and deterministic ordering. V1's closed role
vocabulary is left_foot, right_foot, left_hand, right_hand, left_toe,
right_toe, left_knee, right_knee, left_elbow, right_elbow, root,
prop, and body; its phases are begin, end, and marker. Trim, slice, and
time-warp operations must transform the fragment with the same operation or
return structured per-event outcomes/refusal. Events outside the retained
interval are never silently clamped, dropped, or retained.
Confidence, when present, is finite and lies in [0, 1]. Core fields are
closed. Extensions use strict envelopes with exactly schema,
schema_version, and payload, where the extension's versioned schema owns
the payload object; an unsupported but well-formed extension may be preserved
as opaque data. Any transformer must understand and apply every
extension's operation contract or refuse the whole operation with
unsupported_extension; it may not copy opaque extension-owned times or event
references unchanged.
The strict V1 reader caps a contact-fragment source at 8,388,608 bytes and 32
JSON container levels, with at most 4,096 events and 256 extensions. Authored
strings are at most 4,096 UTF-8 bytes and identifiers at most 255 bytes; each
extension payload is at most 262,144 canonical bytes and 16 levels deep. A
complete fragment is at most 8,388,608 canonical bytes. A transform-result
source and complete canonical result are each at most 16,777,216 bytes, with at
most 4,096 control points and 4,096 event outcomes; its inline fragment obeys
the fragment limits. Depth counts every object/array on the root-to-value path,
including the root envelope as depth 1. A payload object also starts at depth 1
for its separate 16-level limit. Each exact maximum is accepted; N+1 is
rejected during bounded decoding before the excess value is retained or an
unbounded canonical buffer is allocated; canonical-byte limits use a bounded
JCS sink.
JSON Schema maxLength counts Unicode code points, while this reader's text
and identifier limits count UTF-8 bytes, so a non-ASCII value may satisfy the
schema and still be correctly refused by the reader. The reader accepts decoded
IEEE-754 numbers only within the safe magnitude
[-9007199254740991, 9007199254740991]; this includes identity byte counts
and opaque extension payload numbers. Extension payloads are stored from their
bounded canonical JCS bytes, so canonical serialization followed by strict
reread preserves the validated fragment value. An explicitly empty extensions array
is refused so omitted and present data do not canonicalize to the same value.
A minimal collection-scoped envelope is:
{
"schema": "urn:animsmith:schema:contact-fragment:1",
"schema_version": 1,
"producer": {"tool": "animsmith", "version": "0.6.0"},
"artifact": {"sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "bytes": 123456},
"dependency_closure_identity": {"sha256": "1111111111111111111111111111111111111111111111111111111111111111", "bytes": 456},
"clip": {"scope": "collection", "logical_id": "com.example.pack/locomotion/walk-forward-in-place", "source": "walk-forward", "take_index": 0, "take_name": "Take 001"},
"duration_s": 1.2,
"events": [{"event_id": "left-foot/0", "role": "left_foot", "phase": "marker", "time": 0.23, "confidence": 0.92}],
"extensions": [{"schema": "urn:example:contact-quality:1", "schema_version": 1, "payload": {"quality": "high"}}]
}
Seconds and frame values, when shown, are derived display values rather than
identity or comparison coordinates. Canonical bytes use the complete
RFC 8785 JSON Canonicalization Scheme (JCS),
including its object-key, string, and number rules; every extension payload
must also be JCS-canonicalizable. Before JCS serialization, events use the exact mixed-event sort tuple
(start, kind_rank, end_key, role, phase, event_id): points have kind rank 0
with start = time and a null end sentinel that sorts before every numeric
window end; windows use their declared start, have kind rank 1, and use
their numeric end. This keeps point/window order stable when starts coincide.
Tuple strings, including opaque event ids, compare by
unsigned UTF-16 code units exactly like RFC 8785 property names, without
Unicode normalization.
Trim, slice, resample, and time-warp use the separate
urn:animsmith:schema:contact-transform-result:1 result. It binds input and,
on success, output primary, dependency-closure, and canonical-fragment
identities. Its strict top-level fields are schema, schema_version,
operation, input, outcome, and event_outcomes, with output only on
success and refusal { code, message } only on refusal. operation is a
strict tagged object: trim/slice use interval { start, end }, resample uses
mapping = "identity", and time_warp uses required finite positive
output_duration_s plus ordered control_points with input_time/output_time
from (0,0) to (1,1). Between adjacent knots (x0, y0) and (x1, y1), it
maps t by the exact piecewise-linear formula
y0 + ((t - x0) / (x1 - x0)) * (y1 - y0); exact knots map exactly, and both
window endpoints use the same rule. A known V1 tag with an invalid numeric
domain or ordering remains representable for an invalid_mapping refusal and
uses an empty pre-inventory event_outcomes list. An unknown kind, version,
field, mapping token, missing field, or malformed field type is a strict
request/reader error and produces no V1 result or event outcomes. input has
exactly
{artifact:{sha256,bytes},dependency_closure_identity:{sha256,bytes},fragment:{sha256,bytes}}
and refers to a separately
supplied input fragment. Successful output has exactly
{artifact:{sha256,bytes},dependency_closure_identity:{sha256,bytes},fragment:{sha256,bytes},contact_fragment};
all three identities must match the inline complete contact-fragment:1 and
its two input bindings. The output closure identity is freshly captured rather
than copied, and its closure primary_input must equal output artifact. For
trim/slice [a,b], a point is
outside exactly when t < a || t > b; endpoints are included. A window is
outside exactly when end < a || start > b, contained exactly when
a <= start && end <= b, and otherwise boundary-crossing. A crossing window
receives refused with code partial_window and refuses the whole operation.
Known-operation mapping failures, binding mismatches, unsupported extensions,
or partial windows produce a typed refused result with no output field.
After binding and identity validation, event_outcomes has exactly one
{event_id, outcome} object per input event in canonical input order. It adds
value only for transformed exact point/window values and code only for
refused events; pre-inventory binding, identity, mapping-validation, or
extension-support refusal uses an empty list.
Global success requires all events to be transformed or outside and requires
output; global refusal requires top-level refusal and omits output.
The inline output duration is input_duration_s * (b - a) for trim/slice,
the input duration for resample, and the required output_duration_s for
time_warp; the inline fragment and operation must agree exactly. A rounded
trim/slice duration that is not finite and positive refuses before event
inventory with invalid_value and an empty event_outcomes list.
All time/duration inputs and results are finite IEEE 754 binary64. The
normative sequence is dx = rn(x1 - x0),
alpha = rn(rn(t - x0) / dx), dy = rn(y1 - y0), and
mapped = rn(y0 + rn(alpha * dy)); an exact knot bypasses interpolation.
Trim/slice uses span = rn(b - a), mapped = rn(rn(t - a) / span), and
output_duration_s = rn(input_duration_s * span). Here rn is IEEE 754
binary64 round-to-nearest, ties-to-even, with no fused or extended-precision
intermediate, so independent producers feed the same numeric results to JCS.
Refusal codes are partial_window, invalid_mapping, invalid_binding,
invalid_value, and unsupported_extension. Malformed fragments/results and
duplicate, missing, or unknown event-outcome identities are strict reader
errors, not refusal results.
animsmith-core implements this mapping and strict result reader. The reader
requires the separately supplied input fragment plus an external transform
context containing the current input/output artifacts, their complete captured
closures, the expected producer, and any handler-produced extension outputs.
It validates each closure against its artifact and independently rederives the
operation, event outcomes, and successful inline fragment. Opaque extensions
are never copied merely because a caller lists their schema/version: an exact
handler-produced output is required at every input extension position. Its
published JSON Schema is
contact-transform-result-v1.schema.json.
Asset time mutation and generation-directory publication are separate
collection-producer responsibilities.
The success shape is an illustrative frozen 0.5.0 transform-contract snapshot:
{
"schema": "urn:animsmith:schema:contact-transform-result:1",
"schema_version": 1,
"operation": {"kind": "trim", "version": 1, "interval": {"start": 0.1, "end": 0.9}},
"input": {"artifact": {"sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "bytes": 123456}, "dependency_closure_identity": {"sha256": "1111111111111111111111111111111111111111111111111111111111111111", "bytes": 456}, "fragment": {"sha256": "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789", "bytes": 789}},
"outcome": "transformed",
"event_outcomes": [
{"event_id": "left-foot/0", "outcome": "transformed", "value": {"time": 0.1625}},
{"event_id": "right-foot/0", "outcome": "transformed", "value": {"window": {"start": 0.7625, "end": 0.8625}}}
],
"output": {
"artifact": {"sha256": "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210", "bytes": 123456},
"dependency_closure_identity": {"sha256": "2222222222222222222222222222222222222222222222222222222222222222", "bytes": 654},
"fragment": {"sha256": "a1d63b5e10381d2aa635cfd74218a4e5d42f3d61c1451765e25d47a23e02d633", "bytes": 729},
"contact_fragment": {
"schema": "urn:animsmith:schema:contact-fragment:1",
"schema_version": 1,
"producer": {"tool": "animsmith", "version": "0.5.0"},
"artifact": {"sha256": "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210", "bytes": 123456},
"dependency_closure_identity": {"sha256": "2222222222222222222222222222222222222222222222222222222222222222", "bytes": 654},
"clip": {"scope": "collection", "logical_id": "com.example.pack/locomotion/walk-forward-in-place", "source": "walk-forward", "take_index": 0, "take_name": "Take 001"},
"duration_s": 0.96,
"events": [
{"event_id": "left-foot/0", "role": "left_foot", "phase": "marker", "time": 0.1625, "confidence": 0.92},
{"event_id": "right-foot/0", "role": "right_foot", "phase": "begin", "window": {"start": 0.7625, "end": 0.8625}}
]
}
}
}
The delivered contact-fragment producer samples strict stance-support contacts
and writes its own canonical fragment sidecar. Before constructing its metric
grid, the producer requires finite positive timing and checked
frames * bones pose cells plus frames * tracks sampling work, each at or
below the inclusive one-million V1 CLI-producer ceiling. A refusal preserves
the destination. These resource bounds are not engine or artistic evidence.
The producer does not transform contacts, validate foot
placement, map engine-native event types, merge into a host's final sidecar,
or establish runtime behavior, gameplay meaning, or engine correctness.
Transition families (#148)
The transition-family V1 identity is
urn:animsmith:schema:transition-family:1. A declaration has a stable family
id, an explicit ordered member list, a boundary selection (entry, exit, or
both), and closed typed tolerances with explicit units and basis.
The document-local transition_families tables are admitted by AnimSmith's
strict config loader, which retains the exact whole-config input identity with
the normalized declaration. animsmith evaluate-transition-poses INPUT --format json evaluates one document against that authority. The separate
collection envelope is evaluated by animsmith collection evaluate-transition-poses COLLECTION.toml --families TRANSITION_FAMILIES.toml --format json: it first binds one exact manifest identity and verifies every
logical/source/take witness, then reloads only the source keys selected by the
declared members. A stale manifest or member witness is control (exit 2, no
result); unavailable member input makes its whole family incomplete rather
than comparing a survivor subset.
The format-neutral core provides the strict V1 skeleton-basis identity and
immutable transition-pose evaluation result contract at
urn:animsmith:schema:transition-pose-evaluation:1. The document and
manifest-bound collection adapters publish the JSON-only commands above. The
schema is checked in as
transition-pose-evaluation-v1.schema.json.
That docs schema is canonical; the core crate ships an exact checked snapshot
so packaged integration tests retain the same contract without a repository
path dependency.
Its subject_input field binds the exact raw subject selected by the
declaration scope: the document evaluator supplies document bytes and the
collection evaluator supplies manifest bytes without forking the V1 result
wire. The document evaluator accepts the loader's same-load
DependencyClosureV1, derives subject_input only from its primary_input,
and retains the complete subject_dependency_closure_identity both at the
subject and as each member's source_dependency_closure_identity. A configured
family cannot be complete unless closure coverage is complete and that identity
is present; otherwise it is incomplete/not_evaluated with
dependency_closure_incomplete. Empty document declarations still produce
no_configured_families without requiring a closure because no source data is
evaluated.
For collection scope, subject_input is the manifest identity and no manifest
dependency closure is invented. Each available member instead binds its own
raw source_input and complete source_dependency_closure_identity. A source
that could not be opened may retain a null source_input; an incomplete member
closure leaves its closure identity absent, makes the whole family
dependency_closure_incomplete, and does not erase closure identities retained
for other available members. This closure binding covers external glTF buffers
that can change sampled animation while the primary JSON identity stays equal.
Document-local family ids are one lowercase-ASCII token, 1–255 bytes, starting
with [a-z0-9] and continuing with [a-z0-9._-]. The table key itself is the
id, with no duplicate family_id field; quote the key whenever punctuation or
a dot is present (the canonical spelling quotes it always), such as
[transition_families."walk_to_run"] or
[transition_families."combat.entry.v1"]. Collection family ids retain
Appendix F's slash-qualified logical-id grammar and collection-id prefix.
The declaration distinguishes two ownership scopes:
documentfamilies are placed in the existinganimsmith.tomlunder[transition_families."<family_id>"]and resolve exact embedded clip/take identities. The reusable config carries no artifact digest; the document evaluator binds the primary documentInputIdentityand complete dependency-closure identity in its output.collectionfamilies use a separate declaration envelope, not the collection-manifest V1 itself. The envelope binds the exact manifestInputIdentity{sha256, bytes}, then resolves declared logical clip ids plus theirsource, take-index, and take-name witnesses.
Members cannot cross scopes, point to paths or animation-array indices, or be
silently removed when missing or ambiguous. stale_digest is collection-only:
it means the declaration's manifest InputIdentity no longer matches. A
source digest pin in that manifest is enforced by manifest resolution; a
mismatch makes the member unavailable rather than creating a second identity
in this declaration. Reusable document-local config has no stale-digest state
at parse time; the document evaluator binds the exact primary document
InputIdentity plus its complete dependency-closure identity and reports
source/take resolution in its output. The existing [clips],
[gait_groups], and [sync_groups] sections remain document-local and are
not replaced by a second collection authority. Both placements share the
transition-family:1 family-record semantics; only ownership and placement
differ. Family declarations are canonicalized by stable family id while
preserving member order.
Both placements cap the exact declaration and normalized envelope at 8,388,608 source or canonical bytes and 16 container levels. They permit at most 4,096 families, 4,096 members per family, and 16,384 members in aggregate. Authored strings are at most 4,096 UTF-8 bytes, with the 255-byte identifier limits applied more strictly. Depth counts each table/array or JSON object/array on the root-to-value path, including the root as depth 1. Each exact maximum is accepted; N+1 is rejected during bounded decoding before retention or unbounded canonical allocation; canonical-byte limits use a bounded JCS sink.
A collection-scoped declaration uses the following typed TOML-like shape; each logical id is accompanied by the #409 source/take witness and must agree with the manifest:
schema = "urn:animsmith:schema:transition-family:1"
schema_version = 1
scope = "collection"
collection_id = "com.example.pack"
manifest_input_identity = { sha256 = "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789", bytes = 9876 }
[[families]]
family_id = "com.example.pack/transitions/walk-to-run"
boundary = "both"
[families.basis]
translation = "skeleton-local-metres"
rotation = "skeleton-local-degrees"
time = "normalized-clip"
[families.tolerances]
translation_m = 0.05
rotation_deg = 5.0
time_normalized = 0.02
[[families.members]]
logical_id = "com.example.pack/locomotion/walk-forward-in-place"
source = "walk-forward"
take_index = 0
take_name = "Take 001"
[[families.members]]
logical_id = "com.example.pack/locomotion/run-forward-in-place"
source = "run-forward"
take_index = 0
take_name = "Take 001"
The document-local placement uses the existing config basis and the same family-record semantics:
[transition_families."walk_to_run"]
schema = "urn:animsmith:schema:transition-family:1"
schema_version = 1
scope = "document"
boundary = "entry"
[transition_families."walk_to_run".basis]
translation = "skeleton-local-metres"
rotation = "skeleton-local-degrees"
time = "normalized-clip"
[transition_families."walk_to_run".tolerances]
translation_m = 0.05
rotation_deg = 5.0
time_normalized = 0.02
[[transition_families."walk_to_run".members]]
take_index = 0
take_name = "Walk"
[[transition_families."walk_to_run".members]]
take_index = 1
take_name = "Run"
The collection envelope's manifest digest/bytes binding makes a manifest edit or reorder stale even when collection and logical ids are unchanged. No runtime check or transition graph is implied by either placement.
Both placements normalize to the exact closed JSON envelopes shown in
DESIGN.md §F.11: one
collection form retaining collection_id and manifest_input_identity, and
one document form in which each quoted table key becomes family_id. Families
sort by id, member order is retained, and RFC 8785 JCS produces the normalized
declaration identity. V1 evidence separately binds the exact declaration
source, normalized declaration, and evaluated document or manifest identities.
AnimSmith 0.6.0 ships both the document and manifest-bound collection transition-pose commands from #153. They emit the V1 result but do not infer graph edges, add gameplay metadata, or generate engine state-machine or blend-tree data. Those runtime and gameplay consumers remain follow-up work, including #164.
The complete normative contract, including ownership, strict resolution, and canonical serialization rules, is in DESIGN.md §F.10 and §F.11.
Reference
- Configuration reference — Complete
animsmith.tomltables, keys, defaults, precedence, validation, globs, rig profiles, and engine settings. - Built-in check reference — Look up every registered built-in check: current IDs, default findings, prerequisites, config keys, gap semantics, and remediation boundaries.
- CLI reference — Look up a command, flag, or exit code.
- Machine-readable output — Parse versioned
--format jsonreports, glTF animation-addressability inventories, engine import advice, and producer evidence in a pipeline, validated by the JSON Schema underschemas/.
animsmith.toml configuration reference
animsmith.toml is an optional, strict project contract. Document-local
commands (lint, measure, inspect, report, transform, fix,
generate, and evaluate-transition-poses) use --config FILE, or auto-load
./animsmith.toml; no file means
built-in defaults. Collection commands have their own control-file rules (see
cli.md). Unknown tables and keys are errors. The parser validates
finite numeric domains; embedded callers should also call
Config::validate.
This page is the user-facing authority for what every key means. For who
owns each surface and where its value comes from, start from
declaring the contract. Exact Rust types are in
the animsmith-core::config API.
The check rationale and coverage vocabulary are in
game-ready-clips.md.
Precedence and the run model
CLI --select, --allow, and --deny-warnings apply after the file is read;
CLI selection and failure policy therefore override file run policy.
severity controls content findings, not coverage gaps or required engine
prediction-unavailable facets. off disables an applicable check only where
that check permits it; note, warn (or warning), and error enable an
opt-in check. --allow ID hides matching content findings from the CLI gate;
it never repairs input or suppresses required prediction work.
Clip selectors are resolved field-by-field. Every matching clips glob is
overlaid in lexicographic key order; a literal clip-name entry is applied last.
Thus an exact key wins over every glob, and a later lexicographically greater
glob wins only for fields it declares. * matches any sequence (including an
empty sequence); there is no ? or character-class syntax. Empty or missing
selector lists declare no policy. Group member names are exact (not globs).
Top-level tables
| Table/key | Type; default; units; allowed values | Omission and consumer | Prerequisite / precedence |
|---|---|---|---|
[rig] | table; { profile = "auto", roles = {}, required_bones = none } | Optional. Config::rig feeds role-aware checks, measurements, inspect, assembly, and engine paths. | auto scores built-ins; explicit profile or inline roles are resolved by the frontend before checks. |
[checks.<id>] | map of check id → table; absent settings use each check's built-in defaults | Config::checks is consumed by the check runner and owning check. | --select narrows the catalog; severity is evaluated after selection. |
[runtime_nodes] | table; absent; selectors is an optional string array | Config::runtime_nodes supplies the shared runtime-node policy. | If present it wins over legacy checks.rest-world-scale.node_selectors; both is an error. |
[clips."selector"] | map of exact clip name or * glob → table; all fields omitted by default | Config::clips supplies effective expectations to clip-aware checks and transforms. | Glob overlay then exact overlay; omission means “inherit/no declaration”, not false or zero. |
[gait_groups."name"] | named table; no groups by default | Config::gait_groups is consumed by gait-group. | Needs declared members, resolved feet, and measurable gait evidence; missing work is a coverage gap. |
[sync_groups."name"] | named table; no groups by default | Config::sync_groups is consumed by sync-group. | Needs exact member clips and usable timing; optional time_complement adds a phase diagnostic. |
[engine] | CLI-only table; absent means no engine prediction/advice | EngineToml supplies profile selection and settings to generate addressability, generate import-advice, and engine checks. | profile, profile_revision, engine_version, and importer are all required when any selection field is present. Unsupported tuples/settings fail closed. |
[transition_families."id"] | CLI-only strict document declaration; absent/empty means no families | evaluate-transition-poses consumes transition_families; the collection variant consumes its --families control file. It is not part of core Config. | Exact schema/scope and bounded declaration; see collection-contracts.md. |
Rig selection
| Key | Type/default/allowed values | Omission, consumer, and prerequisites |
|---|---|---|
rig.profile | string; "auto"; auto, mixamo, ue-mannequin, humanoid | auto chooses the unique best built-in match (at least two roles). A named profile is strict. Exact names are tried first, then one unique ASCII-case-insensitive match; ambiguity is typed. |
rig.roles.<role> | string map; empty; role is root, hips, spine, head, left_foot, right_foot, left_toe, right_toe, left_hand, right_hand | Inline bindings are exact and override the selected profile only for named roles. Missing/colliding bones produce typed coverage/ambiguity, never guesses. |
rig.required_bones | string array or omitted; no units; any bone names | Presence-only structural contract for sockets, IK targets, and masks. It does not require animation keys; use clips.*.animates_bones for that. |
Checks and severity
Every check accepts severity = "off", "note", "warn"/"warning", or
"error". Omitted severity uses the check's implementation-defined default:
all built-ins are enabled by default except opt-in constant-nonunit-scale.
time-complement is enabled by default, but is NotApplicable until a sync
group declares a time_complement policy; an explicit non-off severity enables
the opt-in scale check. The runner still records unselected, disabled, and
not-applicable checks. See
game-ready-clips.md.
| Check setting | Type; built-in default; units/domain | Consumer, activation, and skip behavior |
|---|---|---|
checks.<id>.severity | enum; omitted; off, note, warn/warning, error | All checks. Content findings only; it does not hide gaps or required predictions. |
checks.loop-seam.max_ratio | finite non-negative float; 1.5; dimensionless | loop-seam; declared loops with usable foot-cycle evidence. |
checks.loop-seam.min_stride_step_m | finite non-negative float; 0.02; metres | loop-seam stride floor; short/unresolved gait evidence is a gap. |
checks.loop-closure.max_position_delta_m | finite non-negative float; 0.01; metres | loop-closure; declared loops; per-clip cap overrides it. |
checks.loop-closure.max_rotation_delta_deg | finite non-negative float; 1.0; degrees | loop-closure; declared loops; per-clip cap overrides it. |
checks.loop-seam-vel.max_velocity_delta_mps | finite non-negative float; 0.1; metres/second | loop-seam-vel; declared loops; per-clip cap overrides it. |
checks.loop-seam-rot.max_angular_velocity_delta_degps | finite non-negative float; 5.0; degrees/second | loop-seam-rot; declared loops; per-clip cap overrides it. |
checks.frozen-bone.min_rotation_deg | finite non-negative float; 1.0; degrees | frozen-bone; needs animates_bones and a resolvable animated track; missing evidence is a gap. |
checks.bind-pose.max_mean_rest_delta_deg | finite non-negative float; 45.0; degrees | bind-pose; needs rotation tracks and a valid rest pose; otherwise a gap. |
checks.foot-slide.contact_height_m | finite non-negative float; 0.03; metres | foot-slide; needs speed_mps, foot roles, and stance evidence. |
checks.foot-slide.max_slide_mps | finite non-negative float; 0.3; metres/second | foot-slide; same prerequisites as contact_height_m. |
checks.rest-world-scale.expected_uniform_scale | finite positive float; 1.0; dimensionless factor | rest-world-scale; dormant unless runtime selectors are nonempty; selectors resolve exactly one source node. |
checks.rest-world-scale.uniform_scale_tolerance | finite non-negative float; 0.0001; dimensionless factor | Inclusive tolerance for the expected factor; same selector prerequisite. |
checks.rest-world-scale.node_selectors | optional string array; absent; exact names or * globs | Legacy alias for runtime_nodes.selectors; only used when shared field is absent. Both spellings are invalid together. |
Settings not consumed by a particular check are harmless in that check's
table, but a misspelled field is rejected by deny_unknown_fields.
Clip expectations
| Key | Type; default; units/allowed values | Consumer, omission, and prerequisites |
|---|---|---|
clips.<selector>.loop | optional bool; omitted; true/false | Declares cyclic intent. true activates loop checks; omission is not false evidence for other checks. |
clips.<selector>.max_loop_position_delta_m | optional finite non-negative float; omitted; metres | Per-selector loop-closure cap; inherits global then 0.01. |
clips.<selector>.max_loop_rotation_delta_deg | optional finite non-negative float; omitted; degrees | Per-selector loop-closure cap; inherits global then 1.0. |
clips.<selector>.max_loop_velocity_delta_mps | optional finite non-negative float; omitted; metres/second | Per-selector loop-seam-vel cap; inherits global then 0.1. |
clips.<selector>.max_loop_angular_velocity_delta_degps | optional finite non-negative float; omitted; degrees/second | Per-selector loop-seam-rot cap; inherits global then 5.0. |
clips.<selector>.duration_s | optional { value: finite positive float, tolerance: finite non-negative float }; omitted; seconds | duration-sanity pin. Omission means no duration contract, not zero. |
clips.<selector>.speed_mps | optional pinned value/tolerance; omitted; metres/second; value positive, tolerance non-negative | Root-motion speed and foot-slide; the latter is not applicable without this declaration. |
clips.<selector>.movement_owner_xz, clips.<selector>.movement_owner_y, clips.<selector>.movement_owner_yaw | optional enum; omitted; gameplay or animation | In-place/root-motion checks and downstream transform intent. Each axis is independent; omission is never inferred. |
clips.<selector>.in_place | optional bool; omitted; true = gameplay XZ, false = animation XZ | Legacy XZ alias for clips.<selector>.movement_owner_xz. Cannot coexist with it in one entry; across layers it is normalized before overlay. |
clips.<selector>.fps | optional finite positive float; omitted; frames/second | fps and sync-group; omission means no authored frame-grid contract. |
clips.<selector>.animates_bones | optional string array; omitted; bone names | missing-bones requires keys and frozen-bone requires actual rotation movement. Distinct from rig.required_bones. |
Runtime nodes and globs
runtime_nodes.selectors is an ordered string array. Each item is an exact
source-node name or a * glob and must resolve to exactly one named node for
rest-world-scale; no match and multiple matches are typed coverage gaps.
Duplicate spellings are de-duplicated at first occurrence. Selectors do not
cross node and bone namespaces, and do not infer a node from a rig role.
Gait and same-time groups
| Key | Type/default/domain | Consumer and skip behavior |
|---|---|---|
gait_groups.<name>.clips | string array; required; exact clip names | gait-group checks member existence, then phase for measurable members. |
gait_groups.<name>.max_gait_phase_spread | finite float [0, 0.5]; required; cycle fraction | Maximum circular phase spread; missing roles/short clips/low evidence produce coverage. |
gait_groups.<name>.min_lr_amplitude_m | finite non-negative float; 0.0; metres | Excludes low-amplitude members as noise. |
sync_groups.<name>.clips | string array; required; exact clip names | sync-group checks exact members at the same absolute time. |
sync_groups.<name>.max_duration_delta_s | finite non-negative float; required; seconds | Largest permitted duration range. |
sync_groups.<name>.max_frame_count_delta | unsigned integer; required; key-count difference | Largest permitted longest-channel key-count range. |
sync_groups.<name>.max_fps_delta | finite non-negative float; required; frames/second | Largest permitted declared-FPS range. |
sync_groups.<name>.time_complement | optional table; omitted disables diagnostic | Adds reflected-time phase comparison for each configured-order unordered pair. |
sync_groups.<name>.time_complement.min_reflected_time_advantage | finite float [0, 1]; required; score | Minimum reflected-minus-same phase similarity advantage. |
sync_groups.<name>.time_complement.min_lr_amplitude_m | finite non-negative float; required; metres | Minimum evidence amplitude before phase comparison. |
Engine profiles (CLI)
The four selection keys have no implicit default: engine.profile,
engine.profile_revision, engine.engine_version, and engine.importer. Either omit [engine]
or specify all four. Exact supported tuples and setting defaults are owned by
the versioned animsmith-engine profiles.
The profile is evidence about importer behavior, not a claim that an engine
loaded or played the asset.
engine.settings accepts profile-defined keys; unknown keys or values are
rejected when the selected profile resolves. Common revision-1 settings are
engine.settings.convert_units (bool), engine.settings.bake_axis_conversion
(bool), and engine.settings.root_motion_source (string path), plus per-clip
clips.<selector>.engine_settings.root_rotation,
clips.<selector>.engine_settings.root_position_y, and
clips.<selector>.engine_settings.root_position_xz (bake or extract).
Revision-2 profiles may define engine.settings.animation_type
(generic|humanoid|legacy), engine.settings.avatar_setup
(create_from_this_model|copy_from_other_avatar),
engine.settings.import_animation (bool),
engine.settings.root_motion_source (string),
engine.settings.root_rotation, engine.settings.root_position_y,
engine.settings.root_position_xz (bake|extract),
engine.settings.rotate_scene_entity (bool), engine.settings.rotate_meshes
(bool), engine.settings.load_meshes (empty|nonempty),
engine.settings.extension_handler_environment
(bare_empty|bevy_pbr_stock_0_19), engine.settings.bevy_animation_feature
(bool), engine.settings.load_animations (bool),
engine.settings.animation_fps (positive integer),
engine.settings.animation_trimming (bool), and engine.settings.sample_rate
(default_30, source_determined, or custom_hz(N)). Per-clip revision-2
root policies use the same clips.<selector>.engine_settings.root_rotation,
clips.<selector>.engine_settings.root_position_y, and
clips.<selector>.engine_settings.root_position_xz paths. A setting is either
explicit or the profile's verified default; no generic default is invented.
Transition families
Document-local families are strict tables, not core Config fields:
| Path | Authority and contract |
|---|---|
transition_families.<id> | DocumentConfigWire::transition_families map entry; the family id is the exact TOML key. |
transition_families.<id>.schema | DocumentFamilyWire::schema; must be the supported transition-family schema id. |
transition_families.<id>.schema_version | DocumentFamilyWire::schema_version; currently 1. |
transition_families.<id>.scope | DocumentFamilyWire::scope; currently document. |
transition_families.<id>.boundary | DocumentFamilyWire::boundary; entry, exit, or both. |
transition_families.<id>.basis | BasisWire; all three basis fields are required. |
transition_families.<id>.basis.translation | BasisWire::translation; currently skeleton-local-metres. |
transition_families.<id>.basis.rotation | BasisWire::rotation; currently skeleton-local-degrees. |
transition_families.<id>.basis.time | BasisWire::time; currently normalized-clip. |
transition_families.<id>.tolerances | TolerancesWire; all three finite non-negative limits are required. |
transition_families.<id>.tolerances.translation_m | TolerancesWire::translation_m, in metres. |
transition_families.<id>.tolerances.rotation_deg | TolerancesWire::rotation_deg, in degrees. |
transition_families.<id>.tolerances.time_normalized | TolerancesWire::time_normalized, in normalized clip time. |
transition_families.<id>.members | DocumentFamilyWire::members; bounded ordered member list. |
transition_families.<id>.members.take_index | DocumentMemberWire::take_index; zero-based take index. |
transition_families.<id>.members.take_name | DocumentMemberWire::take_name; exact source take name. |
[transition_families."walk_to_run"]
schema = "urn:animsmith:schema:transition-family:1"
schema_version = 1
scope = "document"
boundary = "both" # entry | exit | both
[transition_families."walk_to_run".basis]
translation = "skeleton-local-metres"
rotation = "skeleton-local-degrees"
time = "normalized-clip"
[transition_families."walk_to_run".tolerances]
translation_m = 0.05
rotation_deg = 5.0
time_normalized = 0.05
[[transition_families."walk_to_run".members]]
take_index = 0
take_name = "walk"
The complete bounded grammar and allowed values are documented in
collection-contracts.md.
It is read before generic config decoding, so malformed declarations report a
transition-family control error and never become ordinary check findings.
Minimal and advanced configurations
The smallest semantic contract is one loop declaration:
[clips.walk]
loop = true
For a project contract, start with
examples/character.animsmith.toml.
It demonstrates rig roles, runtime-node globs, independent movement ownership,
glob/exact cap precedence, severity, gait rings, and same-time groups. The
Mixamo tutorial and
examples/mixamo.animsmith.toml show the
profile with an in-place XZ declaration. Bevy examples are
bevy.animsmith.toml, bevy-v2.animsmith.toml, and bevy-v3.animsmith.toml.
CI parse-tests these examples and this page's key inventory; update that test
when adding configuration authority.
Common mistakes and errors
unknown fieldorunknown variant: spelling/casing is not supported.must be a finite non-negative number: a cap, tolerance, or amplitude is negative, NaN, or infinite. Positive-only values have the corresponding positive error.cannot declare both ...: choose one runtime selector spelling and one horizontal movement-owner spelling per entry.bad config ...: parser, validation, profile, and transition-family errors include the selected path and exit2; no asset is linted. A coverage gap during a valid run is evidence of a missing prerequisite, not a parse error.
When changing a contract, run with --format json and retain the config path
and tool version with the output. A clean result is evidence for that file and
declaration set, not certification of retargeting, engine runtime behavior, or
artistic quality.
Built-in check reference
This is the customer-facing reference for every registered built-in check in AnimSmith's current catalog. It complements the concise README check catalog, what game-ready means, and the exact Rust API on docs.rs.
Use this page when you need the current built-in ID, default findings, declarations or prerequisites, config keys and units, skip or coverage-gap semantics, and the practical boundary between built-in tooling, runtime policy, and DCC-side correction.
off in the inventory below means the check is registered but opt-in by
default. Once selected, every built-in check can still be made inactive with
[checks.<id>] severity = "off", and any selected opt-in check becomes active
only after an explicit note, warn, or error severity.
Inventory
| id | class | default findings | declarations or prerequisites | config keys | tooling |
|---|---|---|---|---|---|
| nan | mechanical | error | none | severity | DCC/export repair |
| time-monotonic | mechanical | error, note | none | severity | DCC/export repair |
| quat-norm | mechanical | error | none | severity | fix |
| quat-flip | mechanical | warning | none | severity | fix |
| duration-sanity | mechanical | error, warning | none | severity, clips.<name>.duration_s.value, clips.<name>.duration_s.tolerance | DCC/export repair, transform --slice, transform --hold-extend |
| scale-keys | mechanical | warning | none | severity | DCC cleanup |
| non-uniform-scale | mechanical | warning | none | severity | DCC cleanup |
| constant-nonunit-scale | mechanical | off, note | none | severity | DCC cleanup |
| constant-track | mechanical | note | none | severity | transform --prune-constant-tracks |
| required-bones | contract-aware | error | rig.required_bones, usable skeleton | severity, rig.required_bones | DCC/export repair |
| rest-world-scale | contract-aware | warning | runtime_nodes.selectors or legacy node selector alias, source-node transform evidence | severity, runtime_nodes.selectors, checks.rest-world-scale.node_selectors, expected_uniform_scale, uniform_scale_tolerance | scale, DCC/export repair |
| missing-bones | contract-aware | error | clips.<name>.animates_bones | severity, clips.<name>.animates_bones | DCC/export repair |
| frozen-bone | contract-aware | error | clips.<name>.animates_bones, keyed bone tracks | severity, min_rotation_deg, clips.<name>.animates_bones | DCC/export repair |
| duplicate-loop-endpoint | contract-aware | warning | clips.<name>.loop, authored endpoint analysis | severity, clips.<name>.loop | transform --drop-duplicate-loop-endpoint |
| loop-closure | contract-aware | error | clips.<name>.loop, usable loop-continuity samples | severity, max_position_delta_m, max_rotation_delta_deg, clips.<name>.loop, clips.<name>.max_loop_position_delta_m, clips.<name>.max_loop_rotation_delta_deg | DCC/export repair |
| loop-seam | contract-aware | error | clips.<name>.loop, hips and foot roles, sampled stride step | severity, max_ratio, min_stride_step_m, clips.<name>.loop | DCC/export repair |
| loop-seam-vel | contract-aware | error | clips.<name>.loop, usable loop-continuity samples | severity, max_velocity_delta_mps, clips.<name>.loop, clips.<name>.max_loop_velocity_delta_mps | DCC/export repair |
| loop-seam-rot | contract-aware | error | clips.<name>.loop, usable loop-continuity samples | severity, max_angular_velocity_delta_degps, clips.<name>.loop, clips.<name>.max_loop_angular_velocity_delta_degps | DCC/export repair |
| root-motion-speed | contract-aware | error | clips.<name>.speed_mps, non-gameplay XZ owner, root/hips role, sampled root travel | severity, clips.<name>.speed_mps.value, clips.<name>.speed_mps.tolerance, clips.<name>.movement_owner_xz, clips.<name>.in_place | DCC/export repair |
| gait-group | contract-aware | error | gait_groups.<name>.clips, hips and foot roles, measurable left/right gait amplitude | severity, gait_groups.<name>.clips, gait_groups.<name>.max_gait_phase_spread, gait_groups.<name>.min_lr_amplitude_m | DCC/export repair |
| sync-group | contract-aware | error | sync_groups.<name>.clips, at least two present members | severity, sync_groups.<name>.clips, sync_groups.<name>.max_duration_delta_s, sync_groups.<name>.max_frame_count_delta, sync_groups.<name>.max_fps_delta | DCC/export repair |
| time-complement | contract-aware | warning | sync_groups.<name>.time_complement, hips and foot roles, measurable same-time gait phase | severity, sync_groups.<name>.clips, sync_groups.<name>.time_complement.min_reflected_time_advantage, sync_groups.<name>.time_complement.min_lr_amplitude_m | author/runtime review |
| in-place | contract-aware | error | clips.<name>.movement_owner_xz or clips.<name>.in_place, root/hips role, sampled root travel | severity, clips.<name>.movement_owner_xz, clips.<name>.in_place | DCC/export repair |
| fps | contract-aware | warning | clips.<name>.fps | severity, clips.<name>.fps | DCC/export repair, transform --slice |
| bind-pose | contract-aware | warning | clips with at least three usable first-frame rotation tracks | severity, max_mean_rest_delta_deg | DCC/export repair |
| foot-slide | contract-aware | warning | clips.<name>.speed_mps, root/hips role, resolved foot or toe roles, sampled stance windows | severity, contact_height_m, max_slide_mps | DCC/export repair, engine review |
Mechanical checks
nan
- Default findings:
error. - Measurement and finding: flags any non-finite key time or keyed value. A finding means interpolation and pose evaluation are no longer trustworthy.
- Prerequisites and applicability: none. This is generic file-ready coverage.
- Config, defaults, and units:
[checks.nan] severityonly. - Inactive, not-applicable, and coverage gaps: never reports
not_applicableor a typed coverage gap once selected. - Remediation and boundary: there is no safe built-in repair because the source float is undefined. Re-export or correct the source in the DCC/export step.
- Runtime and references: one poisoned float can explode, freeze, or otherwise
corrupt the pose in-engine. See The pose flickers, spins, or
explodes and
API:
Nan.
time-monotonic
- Default findings:
errorfor negative or non-increasing key times,notefor a first key that starts materially after zero. - Measurement and finding: checks that key times start at or near zero and move strictly forward. A finding means the runtime may clamp-hold or sample incorrectly the track.
- Prerequisites and applicability: none. This is generic file-ready coverage.
- Config, defaults, and units:
[checks.time-monotonic] severityonly. - Inactive, not-applicable, and coverage gaps: never
not_applicableand no typed gaps once selected. - Remediation and boundary: fix the export or source timing in the DCC. Use
transform --sliceonly for an intentional clip trim, not to salvage invalid key ordering. - Runtime and references: late starts can hold an unauthored pose; bad ordering
is a hard data defect. See The pose flickers, spins, or
explodes and
API:
TimeMonotonic.
quat-norm
- Default findings:
error. - Measurement and finding: reports the worst rotation key whose quaternion norm
deviates materially from
1.0. - Prerequisites and applicability: none. This is generic file-ready coverage.
- Config, defaults, and units:
[checks.quat-norm] severityonly. - Inactive, not-applicable, and coverage gaps: never
not_applicableand no typed gaps once selected. - Remediation and boundary:
animsmith fixcan renormalize finite, non-zero quaternions losslessly. If the source exporter keeps reintroducing the issue, correct that upstream. - Runtime and references: non-unit rotations skew interpolation, skinning, and
blends. See The pose flickers, spins, or
explodes, CLI
repairs, and
API:
QuatNorm.
quat-flip
- Default findings:
warning. - Measurement and finding: counts adjacent rotation keys on opposite hemispheres. A finding means neighborhood-uncorrected slerp can take the long path.
- Prerequisites and applicability: none. This is generic file-ready coverage.
- Config, defaults, and units:
[checks.quat-flip] severityonly. - Inactive, not-applicable, and coverage gaps: never
not_applicableand no typed gaps once selected. - Remediation and boundary:
animsmith fixcan flip equivalent quaternion signs into a hemisphere-consistent track. Re-export if the source keeps emitting unstable signs. - Runtime and references: the visible symptom is a sudden long-way spin between
keys. See The pose flickers, spins, or
explodes, CLI
repairs, and
API:
QuatFlip.
duration-sanity
- Default findings:
errorfor invalid or missed declared duration pins and degenerate clip lengths,warningfor trackless clips or mismatched channel end times. - Measurement and finding: checks clip duration, optional declared
duration_s, and spread between multi-key channel end times. - Prerequisites and applicability: none. Declared duration pins refine a check that is otherwise generic.
- Config, defaults, and units:
[checks.duration-sanity] severity; optionalclips.<name>.duration_s.valueandclips.<name>.duration_s.tolerance, both in seconds. - Inactive, not-applicable, and coverage gaps: never
not_applicableand no typed gaps once selected. - Remediation and boundary: correct the authored range or export in the DCC.
transform --sliceandtransform --hold-extendare explicit editing tools when the clip is intentionally being reshaped, not automatic correctness repairs. - Runtime and references: bad duration contracts freeze shorter channels or
desync gameplay timing. See The clip is the wrong length or freezes at the
end
and API:
DurationSanity.
scale-keys
- Default findings:
warning. - Measurement and finding: reports temporally varying scale tracks.
- Prerequisites and applicability: none. This is generic file-ready coverage.
- Config, defaults, and units:
[checks.scale-keys] severityonly. - Inactive, not-applicable, and coverage gaps: never
not_applicableand no typed gaps once selected. - Remediation and boundary: remove accidental animated scale in the DCC or export settings. Treat retained scale animation as an intentional authored choice that downstream runtime and retargeter owners must explicitly accept.
- Runtime and references: many rigs and retargeters mishandle animated scale.
See The file is bloated, or the retargeter
chokes and
API:
ScaleKeys.
non-uniform-scale
- Default findings:
warning. - Measurement and finding: reports scale tracks whose evaluated axis lengths are materially unequal.
- Prerequisites and applicability: none. This is generic file-ready coverage.
- Config, defaults, and units:
[checks.non-uniform-scale] severityonly. - Inactive, not-applicable, and coverage gaps: never
not_applicableand no typed gaps once selected. - Remediation and boundary: normalize the authored transform in the DCC or export path unless your runtime and retargeter contract explicitly accepts the non-uniform scale.
- Runtime and references: non-uniform scale is a common retargeting and rig
failure source. See The file is bloated, or the retargeter
chokes and
API:
NonUniformScale.
constant-nonunit-scale
- Default findings:
offby default; once enabled, findings arenote. - Measurement and finding: reports scale channels that stay constant but not at unit scale, including single-key pins.
- Prerequisites and applicability: none. This is a policy signal, not a mandatory file-ready failure.
- Config, defaults, and units:
[checks.constant-nonunit-scale] severityonly. - Inactive, not-applicable, and coverage gaps: inactive until an explicit severity enables it; never reports typed gaps once active.
- Remediation and boundary: clean up the authored scale in the DCC if your runtime policy expects unit scale. Keep it enabled only when your team wants to surface that policy in lint results.
- Runtime and references: constant non-unit scale can still affect import,
attachment, and retargeting expectations. See The file is bloated, or the
retargeter
chokes and
API:
ConstantNonunitScale.
constant-track
- Default findings:
note. - Measurement and finding: reports multi-key translation, rotation, or scale tracks that never materially move.
- Prerequisites and applicability: none. This is generic file-ready coverage.
- Config, defaults, and units:
[checks.constant-track] severityonly. - Inactive, not-applicable, and coverage gaps: never
not_applicableand no typed gaps once selected. - Remediation and boundary:
transform --prune-constant-trackscan remove the subset AnimSmith can prove redundant. Use DCC cleanup when the track carries authoring intent, unsupported curves, or broader rig cleanup. - Runtime and references: constant multi-key tracks are usually export bloat,
but removing them still needs contract review for transition and required
motion evidence. See The file is bloated, or the retargeter
chokes,
Editing a clip, and
API:
ConstantTrack.
Contract-aware checks
required-bones
- Default findings:
error. - Measurement and finding: checks that every distinct name in
rig.required_bonesexists exactly once in the skeleton. - Prerequisites and applicability: active only when
rig.required_bonesis non-empty. A usable skeleton is required. - Config, defaults, and units:
[checks.required-bones] severityandrig.required_bones. - Inactive, not-applicable, and coverage gaps:
not_applicablewith no declared required bones; reports askeleton_unavailablecoverage gap when the file has no usable skeleton. - Remediation and boundary: fix the exported skeleton, naming, or rig mapping in the DCC/export pipeline. This is not a clip-level animation repair.
- Runtime and references: missing sockets, IK targets, or mask bones break
downstream binding even if no clip animates them. See A limb is T-posed, or
a bone never
moves and
API:
RequiredBones.
rest-world-scale
- Default findings:
warning. - Measurement and finding: checks the effective rest-world linear transform of explicitly selected source nodes against a uniform scale policy.
- Prerequisites and applicability: active only when
runtime_nodes.selectorsor the legacychecks.rest-world-scale.node_selectorsalias declares at least one selector. Source-node transform evidence must exist, and each selector must resolve exactly once. - Config, defaults, and units:
[checks.rest-world-scale] severity,expected_uniform_scaledefault1.0, anduniform_scale_tolerancedefault0.0001; selectors live inruntime_nodes.selectors, with the legacy alias accepted only when the shared field is absent. - Inactive, not-applicable, and coverage gaps:
not_applicablewithout selectors; gaps report no-match, ambiguous selector resolution, or unavailable source-node transform evidence. - Remediation and boundary: if the source is uniformly wrong, decide between
scale whole-document,scale rest-bind, or DCC/export correction based on the actual scale problem. This check reports source-node evidence; it does not infer which rewrite you intend. - Runtime and references: wrong rest-world scale breaks sockets, attachments,
gameplay references, and engine-side assumptions even when animation keys are
otherwise valid. See Scaling glTF safely, glTF and generic
runtime guide, and API:
RestWorldScale.
missing-bones
- Default findings:
error. - Measurement and finding: checks that each declared
animates_bonesmember exists and carries at least one keyed track in the clip. - Prerequisites and applicability: active only on clips that declare
animates_bones. - Config, defaults, and units:
[checks.missing-bones] severityandclips.<name>.animates_bones. - Inactive, not-applicable, and coverage gaps:
not_applicableon clips withoutanimates_bones; no typed coverage gaps once the declaration exists. - Remediation and boundary: restore the missing keyed channel or correct the source clip and rig in the DCC/export path.
- Runtime and references: declared motion that never reaches the file reads as
a static or wrong-rig limb at runtime. See A limb is T-posed, or a bone
never moves
and API:
MissingBones.
frozen-bone
- Default findings:
error. - Measurement and finding: checks whether each declared animated bone rotates beyond the configured floor across the clip.
- Prerequisites and applicability: active only on clips that declare
animates_bones. The bone must exist and carry at least one keyed track. - Config, defaults, and units:
[checks.frozen-bone] severity,min_rotation_degdefault1.0degrees, andclips.<name>.animates_bones. - Inactive, not-applicable, and coverage gaps:
not_applicableon clips withoutanimates_bones; no typed gaps once the declaration exists. Missing bones themselves belong tomissing-bones. - Remediation and boundary: correct the source clip, wrong slice, or masked-out track in the DCC/export pipeline.
- Runtime and references: the visible symptom is a limb that stays pinned,
T-posed, or otherwise fails to animate even though the clip says it should.
See A limb is T-posed, or a bone never
moves and
API:
FrozenBone.
duplicate-loop-endpoint
- Default findings:
warning. - Measurement and finding: checks for the strict authored-key case where a declared loop repeats its first pose at the final endpoint and that closing key can be removed mechanically.
- Prerequisites and applicability: active only on clips declared
loop = true, and only when authored endpoint analysis is available. - Config, defaults, and units:
[checks.duplicate-loop-endpoint] severityandclips.<name>.loop. - Inactive, not-applicable, and coverage gaps:
not_applicablewhen no clip declares a loop. Reports a measurement-unavailable gap when authored endpoint analysis cannot classify the clip. - Remediation and boundary:
transform --drop-duplicate-loop-endpointcan trim the mechanically removable endpoint subset. Non-classified loop endpoint modes still need authored review or a different transform. - Runtime and references: redundant inclusive endpoints are a common DCC export
artifact and can affect downstream loop handling. See The loop
pops, Editing a
clip, and API:
DuplicateLoopEndpoint.
loop-closure
- Default findings:
error. - Measurement and finding: checks the largest per-bone model-space position and rotation delta between the final and first loop sample.
- Prerequisites and applicability: active only on clips declared
loop = trueand only when loop-continuity samples exist. - Config, defaults, and units:
[checks.loop-closure] severity,max_position_delta_mdefault0.01metres,max_rotation_delta_degdefault1.0degrees, plus per-clip overridesclips.<name>.max_loop_position_delta_mandclips.<name>.max_loop_rotation_delta_deg. - Inactive, not-applicable, and coverage gaps:
not_applicablewhen no clip declares a loop. Reports measurement-unavailable gaps when the sampled loop-continuity evidence is missing or unusable for one or more bones. - Remediation and boundary: re-author, reslice, or re-export the loop in the DCC. This is a diagnosis, not an automatic loop repair.
- Runtime and references: a failed closure produces an obvious pose jump at the
wrap. See The loop pops and API:
LoopClosure.
loop-seam
- Default findings:
error. - Measurement and finding: checks the feet-relative-to-hips wrap discontinuity, normalized by seam-adjacent in-clip step length.
- Prerequisites and applicability: active only on clips declared
loop = trueand only when hips plus at least one foot role resolve and a usable stride step can be sampled. - Config, defaults, and units:
[checks.loop-seam] severity,max_ratiodefault1.5, andmin_stride_step_mdefaulting to the built-in minimum stride floor used by the metrics. - Inactive, not-applicable, and coverage gaps:
not_applicablewhen no clip declares a loop. Gaps report unresolved gait roles, clips too short to sample a cycle, unavailable foot-cycle metrics, or no usable stride step. - Remediation and boundary: fix the loop cut or cycle in the DCC. AnimSmith intentionally reports the seam evidence rather than inventing a repair.
- Runtime and references: the symptom is a once-per-cycle pop even when the raw
file is format-valid. See The loop pops
and API:
LoopSeam.
loop-seam-vel
- Default findings:
error. - Measurement and finding: checks the largest per-bone model-space linear velocity change between the incoming and outgoing loop seam.
- Prerequisites and applicability: active only on clips declared
loop = trueand only when loop-continuity samples exist. - Config, defaults, and units:
[checks.loop-seam-vel] severity,max_velocity_delta_mpsdefault0.1metres per second, plus the per-clip overrideclips.<name>.max_loop_velocity_delta_mps. - Inactive, not-applicable, and coverage gaps:
not_applicablewhen no clip declares a loop. Reports measurement-unavailable gaps when loop-continuity samples are missing or unusable. - Remediation and boundary: correct the authored wrap or the export timing in the DCC; there is no built-in automatic velocity repair.
- Runtime and references: this is the once-per-cycle hitch where the clip hits
the right pose but changes speed abruptly at the seam. See The loop
pops and API:
LoopSeamVelocity.
loop-seam-rot
- Default findings:
error. - Measurement and finding: checks the largest per-bone model-space angular velocity change between the incoming and outgoing loop seam.
- Prerequisites and applicability: active only on clips declared
loop = trueand only when loop-continuity samples exist. - Config, defaults, and units:
[checks.loop-seam-rot] severity,max_angular_velocity_delta_degpsdefault5.0degrees per second, plus the per-clip overrideclips.<name>.max_loop_angular_velocity_delta_degps. - Inactive, not-applicable, and coverage gaps:
not_applicablewhen no clip declares a loop. Reports measurement-unavailable gaps when loop-continuity samples are missing or unusable. - Remediation and boundary: correct the authored wrap or export timing in the DCC; there is no built-in automatic angular-velocity repair.
- Runtime and references: this is the rotational version of a loop hitch or
pulse at the seam. See The loop pops and
API:
LoopSeamRotation.
root-motion-speed
- Default findings:
error. - Measurement and finding: compares measured horizontal root travel against a
declared
speed_mpspin for clips whose XZ owner is not gameplay. - Prerequisites and applicability: active only when a clip declares
speed_mpsand does not declare gameplay-owned XZ travel. Root or hips role resolution and measurable root travel are required. - Config, defaults, and units:
[checks.root-motion-speed] severity,clips.<name>.speed_mps.value,clips.<name>.speed_mps.tolerance, and the XZ owner viaclips.<name>.movement_owner_xzor the compatibility aliasclips.<name>.in_place. - Inactive, not-applicable, and coverage gaps:
not_applicablewith no relevant speed pin. Gaps report unresolved root or hips roles or unavailable measured root-motion speed. - Remediation and boundary: fix the clip's actual travel or the declared speed contract in the DCC/export path or config. This is not a runtime retiming suggestion from AnimSmith.
- Runtime and references: stale speed pins make motion-scaled playback slide or
moonwalk. See The character glides or runs in
place and API:
RootMotionSpeed.
gait-group
- Default findings:
error. - Measurement and finding: compares measured gait phases across each declared blend-ring group and reports spread beyond the configured cap.
- Prerequisites and applicability: active only when
gait_groupsis non-empty. Hips and foot roles must resolve, and at least two members need measurable gait evidence above the configured amplitude floor. - Config, defaults, and units:
[checks.gait-group] severity, plusgait_groups.<name>.clips,gait_groups.<name>.max_gait_phase_spreadin cycle fraction, andgait_groups.<name>.min_lr_amplitude_min metres. - Inactive, not-applicable, and coverage gaps:
not_applicablewith no gait groups. Gaps report unresolved gait roles, missing members, too few measurable members, or members excluded because phase evidence is unavailable or below the amplitude floor. - Remediation and boundary: correct clip timing, loop structure, or selection in the DCC and review the declared group. AnimSmith does not reorder or retime the group automatically.
- Runtime and references: phase drift between directional blends shows up as
sliding or popping feet when clips crossfade. See Directional blend members
travel at different
speeds
and API:
GaitGroup.
sync-group
- Default findings:
error. - Measurement and finding: compares declared same-time group members for duration, frame count, frame grid, and loop endpoint mode compatibility.
- Prerequisites and applicability: active only when
sync_groupsis non-empty, and each group needs at least two present members before full comparison is possible. - Config, defaults, and units:
[checks.sync-group] severity, plussync_groups.<name>.clips,sync_groups.<name>.max_duration_delta_sin seconds,sync_groups.<name>.max_frame_count_deltain frames, andsync_groups.<name>.max_fps_deltain frames per second. - Inactive, not-applicable, and coverage gaps:
not_applicablewith no sync groups. Gaps report missing members, too few present or measurable members, unavailable durations, unusable frame-grid evidence, or unavailable loop endpoint evidence. - Remediation and boundary: realign or rebuild the compared clips and the group declaration in the DCC/export pipeline. This check reports compatibility; it does not choose a runtime retiming strategy.
- Runtime and references: mismatched same-time clips blend or switch at
different semantic moments. See Feet skate when clips
blend and API:
SyncGroupCheck.
time-complement
- Default findings:
warning. - Measurement and finding: compares each declared same-time pair's gait-phase similarity against the similarity after reflecting one member's normalized cycle time.
- Prerequisites and applicability: active only when a sync group enables
time_complement. Hips and foot roles must resolve, and at least two members need measurable gait phase above the configured amplitude floor. - Config, defaults, and units:
[checks.time-complement] severity, plussync_groups.<name>.time_complement.min_reflected_time_advantagein[0, 1]andsync_groups.<name>.time_complement.min_lr_amplitude_min metres. The check also usessync_groups.<name>.clips. - Inactive, not-applicable, and coverage gaps:
not_applicablewith no enabled time-complement policy. Gaps report missing members, unresolved gait roles, unavailable phase evidence, or too few measurable members. - Remediation and boundary: treat this as a sync-diagnostic for authoring or runtime review, not as proof that time reflection is the right runtime fix.
- Runtime and references: a warning means same-time pairing may be working
against the measured gait evidence. See Feet skate when clips
blend and API:
TimeComplement.
in-place
- Default findings:
error. - Measurement and finding: compares the declared horizontal movement owner to measured horizontal root travel.
- Prerequisites and applicability: active only when a clip declares
movement_owner_xzor its compatibility aliasin_place. Root or hips role resolution and measurable root travel are required. - Config, defaults, and units:
[checks.in-place] severity,clips.<name>.movement_owner_xz, or the compatibility aliasclips.<name>.in_place. - Inactive, not-applicable, and coverage gaps:
not_applicablewhen no clip declares horizontal movement ownership. Gaps report unresolved root or hips roles or unavailable measured root speed. - Remediation and boundary: fix the clip or the declared ownership in the DCC, export path, or contract config. This is an intent mismatch, not a built-in transform target.
- Runtime and references: a mismatch becomes visible gliding or running in
place. See The character glides or runs in
place and API:
InPlace.
fps
- Default findings:
warning. - Measurement and finding: checks that a declared clip duration spans a whole number of frames and that keyed times stay on the declared frame grid.
- Prerequisites and applicability: active only on clips that declare
fps. - Config, defaults, and units:
[checks.fps] severityandclips.<name>.fpsin frames per second. - Inactive, not-applicable, and coverage gaps:
not_applicableon clips withoutfps. Reports aninvalid_declared_fpscoverage gap when the declared frame rate is non-finite or non-positive. - Remediation and boundary: correct the authored frame rate or retiming in the
DCC/export path.
transform --sliceis appropriate for an intentional frame-aligned cut, not for arbitrary resampling drift. - Runtime and references: off-grid keys and fractional frame counts are common
importer pain points. See The clip is the wrong length or freezes at the
end
and API:
Fps.
bind-pose
- Default findings:
warning. - Measurement and finding: checks the mean first-frame rotation deviation from rest across bones with usable rotation tracks.
- Prerequisites and applicability: active whenever the file has clips. A clip needs at least three usable first-frame rotation tracks before the check can evaluate it.
- Config, defaults, and units:
[checks.bind-pose] severityandmax_mean_rest_delta_degdefault45degrees. - Inactive, not-applicable, and coverage gaps:
not_applicableonly when the document has no clips. Reportsinsufficient_rotation_evidencewhen a clip lacks enough usable first-frame rotation tracks. - Remediation and boundary: correct the seed rig, export skeleton, or clip source in the DCC/export path. This is not a rest-bind scale rewrite.
- Runtime and references: a mismatched bind often presents as the wrong base
pose or wrong-skeleton clip. See Files disagree about skeleton or clip
identity
and API:
BindPose.
foot-slide
- Default findings:
warning. - Measurement and finding: checks whether each stance foot moves consistently
with the declared
speed_mpsand the clip's measured horizontal root speed. - Prerequisites and applicability: active only on clips that declare
speed_mps. Root or hips roles must resolve, the clip must be long enough to sample stance, and a foot or toe role must resolve for each judged side. - Config, defaults, and units:
[checks.foot-slide] severity,contact_height_mdefault0.03metres,max_slide_mpsdefault0.3metres per second. The clip-levelspeed_mpsdeclaration is the prerequisite, not a foot-slide check setting. - Inactive, not-applicable, and coverage gaps:
not_applicablewith no speed pin. Gaps report unresolved root or hips roles, clips too short to sample stance, unavailable root-motion speed, or unresolved foot or toe roles. - Remediation and boundary: fix foot plants, root travel, or the declared speed pin in the DCC/export path and then review the runtime blend setup. This heuristic check intentionally does not auto-rewrite contact or infer movement ownership.
- Runtime and references: failed stance motion shows up as skating or slipping
feet in motion or blends. See Feet slide within one
clip and API:
FootSlide.
animsmith CLI
animsmith is designed for artist inner loops, CI gates, and pipeline
automation. It reads glTF/GLB everywhere; the released default build also
reads FBX through the fbx feature. The CLI is tested on Linux, macOS,
and Windows.
Install
Install the released CLI by downloading the archive for your platform from GitHub Releases:
| Platform | Archive |
|---|---|
| Linux x86_64 | animsmith-vX.Y.Z-x86_64-unknown-linux-gnu.tar.gz |
| macOS Apple Silicon | animsmith-vX.Y.Z-aarch64-apple-darwin.tar.gz |
| Windows x86_64 | animsmith-vX.Y.Z-x86_64-pc-windows-msvc.zip |
Each archive has a matching .sha256 checksum file.
You can also install from crates.io with Cargo:
cargo install animsmith
From a source checkout, prefix commands with cargo run -p animsmith --.
Help
Every command has generated help:
animsmith --help
animsmith lint --help
animsmith fix --help
animsmith generate addressability --help
animsmith generate contact-fragment --help
There are no man pages yet, so --help is the canonical installed CLI
reference. The help output reflects compile-time features: a
--no-default-features binary omits feature-gated commands such as
report, convert, and assemble.
animsmith --version states that same compile-time surface in one line:
animsmith <version> (<git describe of the source>) [features: fbx, report]
The parenthesized part appears only for a binary built from a checkout whose
commit is not exactly the release tag. [features: ...] names the Cargo
features that were compiled in, alphabetically, and reads [features: none]
for a --no-default-features build: fbx means the binary reads FBX input,
report means it can render HTML reports. The line is a label the build
stamps on itself, so treat it as identification rather than as proof — run the
command you depend on when a build's capabilities have to be established.
Commands
animsmith inspect <file>
animsmith measure <file...> [--format text|json]
animsmith lint <file...> [--format text|json|markdown] [--select id[,id]] [--allow id[,id]] [--deny-warnings]
animsmith evaluate-transition-poses <input.glb|input.gltf|input.fbx> [--config animsmith.toml] --format json
animsmith collection lint <collection.toml> [--format json]
animsmith collection dashboard --collection <collection-output.json> -o <dashboard.html> --authority <dashboard.json> [--evaluation <transition-pose-evaluation.json>] [--asset-report <logical-id>=<relative-report.html>]
animsmith collection generate-contact-fragment <manifest.toml> --clip <logical-id> -o <out.json> [--format text|json]
animsmith collection evaluate-directional-speed --policy <policy.toml> --evidence <collection-output.json> [--format json]
animsmith collection evaluate-transition-poses <collection.toml> --families <transition-families.toml> --format json
animsmith report <file> -o <report.html> [--clip name] [--evidence-only]
animsmith report <before> -o <comparison.html> --compare-after <after> --before-clip <before-name> --after-clip <after-name> [--evidence-only]
animsmith transform <file> -o <out.glb> [--clip name] [--slice START:END] [--hold-extend SECONDS] [--gait-anchor] [--drop-duplicate-loop-endpoint] [--prune-constant-tracks] [--fps N]
animsmith fix <file> (-o <out.glb>|--in-place|--dry-run) [--repair id[,id]]
animsmith convert <in.fbx|in.glb|in.gltf> -o <out.glb|out.gltf> [--material-texture-recipe recipe.toml] [--animation-only|--bake-static-mesh-transforms] [--format text|json]
animsmith assemble <recipe.toml> -o <out.glb> --evidence <out.json> [--format text|json]
animsmith scale whole-document <in.glb|in.gltf> -o <out.glb|out.gltf> --factor N --evidence <out.json> [--format text|json]
animsmith scale rest-bind <in.glb|in.gltf> -o <out.glb|out.gltf> --source-skin-index N --source-root-node-index N --expected-factor N --evidence <out.json> [--format text|json]
animsmith generate addressability <in.glb|in.gltf> [--format json|text|markdown]
animsmith generate contact-fragment <in.glb|in.gltf|in.fbx> --clip <take-name> -o <out.json> [--format text|json]
animsmith diff <before> <after> [--format text|json]
animsmith skeleton compare <source.glb|source.gltf|source.fbx> <target.glb|target.gltf|target.fbx> --correspondence <skeleton-correspondence.toml> [--format text|json]
inspect is the human-readable discovery view for exact asset-authored names.
It inventories clips, bones, materials, and mesh-instance nodes, including each
instance's mesh, skin status, and primitive/material context. Use those names
when authoring assemble or material texture recipes.
Structural skeleton comparison
skeleton compare establishes only declared structural compatibility evidence
for one source asset and one immutable target asset. It does not retarget,
rewrite rest or bind data, infer aliases, generate an engine avatar, or prove
runtime deformation, masking, contact, gameplay, or artistic acceptance.
The correspondence control is strict and identity-pinned. Generate its two
input identities from the exact source and target bytes you intend to compare;
a changed asset is a stale control input and exits 2 without a result. The
selector declares the complete finite node-name set on each side; it does not
infer a subtree from root_name. The named root must be included, every name
must resolve exactly once in its input, and only these explicitly selected
nodes contribute rows. This prevents meshes, props, cameras, and other scene
attachments from silently becoming skeleton evidence. Parent comparison walks
through unselected intermediate nodes to the nearest selected ancestor, so it
describes this declared projection rather than the complete raw hierarchy. All tolerances are
mandatory and have no defaults:
schema = "urn:animsmith:skeleton-correspondence:1"
schema_version = 1
[source]
input = { sha256 = "<64 lowercase hex characters>", bytes = 12345 }
selector = { root_name = "Hips", node_names = ["Hips", "Spine", "Head"] }
[target]
input = { sha256 = "<64 lowercase hex characters>", bytes = 23456 }
selector = { root_name = "Hips", node_names = ["Hips", "Spine", "Head"] }
[correspondence]
mode = "exact_name"
[tolerances]
translation_m = 0.001
rotation_deg = 0.1
scale_delta = 0.001
normalized_bone_length_ratio_delta = 0.01
For a deliberate rename, replace the correspondence table with a finite, one-to-one explicit map. Every map key and value must already be in the respective declared selector; the map is authority, not a suggestion:
[correspondence]
mode = "explicit"
map = { Hips = "Root", Spine = "Spine_01" }
The versioned urn:animsmith:schema:skeleton-compatibility:1 JSON result binds both primary inputs, complete dependency
closures when they are available, the correspondence bytes, normalized basis,
selectors, matching mode, and tolerances. It reports matched, missing,
extra, and parent-mismatch rows; local and rest-world transform deltas; and
child bone-length ratios where both corresponding parent/child rest-world
positions are available. compatible means all required topology/rest facts
were available and within the declared tolerances. incompatible names a
complete mismatch. partial and not_evaluated record unavailable evidence
and exit 1. Skin membership, inverse-bind evidence, and deformation-model
evidence are separate facets; they never become an opaque score or turn this
structural result into a deformation claim.
report --compare-after writes one offline synchronized before/after diagnostic.
Both input files and both clip names are explicit; it refuses duplicate or
incompatible named skeletons, missing/ambiguous clips, unavailable metric
grids, identical or incomplete rooted dependency closures, and report work
beyond its fixed bounds. Authored duplicate take names remain ambiguous even
when a loader gives normalized clips distinct display suffixes. Each panel
shows both its primary input and complete closure identity, and includes only
selected-clip or whole-input gaps and predictions. The output cannot alias
either primary input, a rooted sidecar, or the selected config; refusal
preserves any existing output. The two panels synchronize by
normalized sampled-frame phase and display both source times — that display
mapping is not an authored time warp. Findings, coverage gaps, prediction
provenance, and identities remain separate per side. Selecting a finding
scrubs the linked time and highlights its uniquely mapped bone. One toggle
beside the shared phase draws the two judged poses in a single pane instead
of two — the before skeleton solid, the after one dashed over it, at the same
two selected frames and through the same shared camera — and the two-pane
layout stays the default. The overlay stands down while a loop-seam context
is selected, because that context is one side's own two endpoint frames
rather than one shared phase. The
report is evidence for troubleshooting, not an artistic, gameplay, engine, or
retargeting acceptance verdict.
--evidence-only omits the sampled pose grid from either report form and
sets "evidence_only": true in the embedded data. The grid is the motion —
every bone's model-space position on every judged frame — so a full report of a
licensed clip carries that clip. Both forms keep their findings, coverage gaps
and engine predictions; the comparison also keeps both sides' input identities,
and the single-clip report its profile and source path (when the document
has one). Both show a notice where each pose view would be and disable
playback; the comparison also disables its pose overlay, which draws the same
grid. So the document can be attached
to an issue,
published, or sent to a vendor where the source asset itself may not go (see the
licensed-asset policy). Nothing else about the
run changes: the same checks evaluate the same frames.
What is left to look at differs by form, because only one of them draws its charts on the Rust side. The single-clip report keeps its charts, and they retain exactly the root's X/Z path, the two foot-height series relative to the hips plus their difference, the last on its own right-hand axis, and — for each declared gait group with a member in the file — one left-minus-right foot-height series per member with its measured stride anchor. Nothing in any of them is per bone.
--clip NAME and the gait-group figure meet deliberately. The clip filter
scopes the document to one clip: its pose grid, its findings and its own two
charts. A gait group is a contract between clips, so the figure for a group
that clip belongs to keeps drawing and naming every declared member — a group
compared against one of its own members is not a comparison. Groups the
selected clip does not belong to are not drawn at all. What that publishes
beyond the selected clip is one left-minus-right foot-height series and one
measured anchor per member, which is the single-series metric evidence the
licensed-asset policy allows; the pose grid
of a clip the filter excluded is never embedded. Every comparison panel is a
viewer drawing made from the pose grid — both role-trajectory panels, both gait
panels, and the shared root chart — so an evidence-only comparison replaces all
of them with the same notice. Everything else the full comparison embeds stays:
findings, coverage gaps, prediction provenance, identities, clip metadata, and
the contexts each side was judged in. Rendering those panels on the Rust side
is tracked as follow-up work and would restore them.
Both report forms follow the reader's prefers-color-scheme and read a URL
fragment of &-separated key=value options, so one generated file can be
deep-linked or embedded without regenerating it. Both honour theme=light and
theme=dark, which pin the palette; embed=1, whose rules hide the running
title and the interaction hint and nothing else, so the document fits an
<iframe> with its findings, charts, and evidence disclosures in place; and frame=N, which scrubs to a judged frame. clip=NAME,
with=NAME, and finding=INDEX are single-clip options — they land where
clicking that clip, that pairing, or that finding lands, on the same selected
row, frame, chart playhead, and 3D view
— while the comparison addresses a finding through the
#finding-<side>-<anchor> link its own panels carry, because its clip
correspondence is declared by the two inputs rather than chosen in the viewer.
with=NAME names a second clip of the same document to play beside the
selected one, which the viewer's with control also chooses. The two poses
occupy the two halves of the one canvas, drawn through a single camera fitted
to both clips' bounds so they share a scale, and coloured by the report's
left/right tokens; a key below the canvas names each half in the colour that
half is drawn in, and both clips' charts are shown.
The transport drives the selected clip, and each half is shown at the frame nearest the transport's normalized phase in its own grid, with both source times labelled. That one frame is also where that clip's chart playhead and root-path dot sit, so a chart never reports a frame other than the one its pose is drawn at. This source-phase mapping selects samples the checks already judged; it does not retime or resample authored clip time. An empty value, a missing or ambiguous name, and the selected clip's own name clear the pair. An evidence-only report has no sampled positions or local transforms to draw, so the omission notice replaces playback and the pairing selects charts only.
With two clips selected, enable illustrative blend to add a third pane.
The weight slider runs from the primary clip at 0 to the paired clip at 1,
starting at 0.5. The pane blends their sampled local transforms and runs
forward kinematics; it does not interpolate model-space joint positions.
It is labelled engine-agnostic illustrative blend, not Bevy, Unity, Unreal
or Godot runtime evidence. Both source frames/times and the weight remain
visible. The phase still chooses the nearest frame independently in each grid,
so unequal durations are paired by phase rather than source seconds.
All panes share the source-based camera and existing orbit/zoom controls. Interior rotations can extend beyond the initial source bounds; zoom out if needed. Oversized added local data or malformed local samples show a blending omission reason without removing source findings or playback. The HTML blend contract defines the math, limits and motion-data boundary.
A key that is absent leaves that state as it is, so following one of those
anchors inside an embedded, theme-pinned report does not un-pin it. A key that
is present is applied as far as the document allows: a syntactically valid
frame beyond the clip is clamped to its last frame, and a clip the document
does not contain selects the first one, because a reader who asks for a
position wants the nearest one the report can show. A value the parser cannot
read at all restores that state's default instead — an unparsable frame
restores frame 0, an unparsable clip the first clip, and a finding index
the document cannot address clears the selection. Unknown keys and malformed
pairs are ignored, no option changes the findings, coverage, or charts the
document carries, and no viewer writes the fragment back.
The checked-in synthetic comparison walkthrough demonstrates seam endpoints, sampled stance and foot trajectories, shared-scale root paths, and visually unchanged structural-track remediation. Its generated GLBs are byte-checked against the repository-owned fixture builder in CI.
--config animsmith.toml is global for document-local commands. Without it, the CLI auto-loads
./animsmith.toml when present and otherwise uses built-in defaults.
Collection commands deliberately reject the global spelling. collection lint
uses each source config declared in the collection manifest, or exact built-in
defaults when none is declared; it never discovers an ambient
./animsmith.toml. collection evaluate-directional-speed has no config or
output-path option: its policy and evidence inputs fully declare its boundary.
collection dashboard is an offline current-state view, not a second
collection ledger. It accepts only the strict current collection-output V11
contract, writes a self-contained HTML file and the separately versioned
urn:animsmith:schema:collection-dashboard:1
authority that binds the exact input bytes, and never reloads source assets.
Its optional transition-pose evaluation must bind the same manifest bytes.
--asset-report names an exact logical clip and a safe relative HTML reference;
the dashboard links it but never embeds source assets or samples. Its role
filter means already-recorded resolved skeletal roles, not inferred gameplay or
clip semantics. Filtering only hides rows locally: it cannot turn partial,
unavailable, excluded, or incomplete evidence into a complete collection.
The separate declared-source inventory remains visible even for sources with
zero logical clips and retains observed identity plus availability, loader, and
dependency-closure states. Every observed physical take remains source-owned
and records its source take index/name, normalized index/name state, evidence,
and outcome even when no logical clip declares it. Global finding/gap/prediction
totals derive from those physical rows plus unscoped source findings and
unscoped source-level required_prediction_unavailable facets; logical rows
are navigation projections and are not counted a second time.
Source/document findings without an exact clip match
remain explicit unscoped source findings and still contribute to the unfiltered
finding total; they are never guessed onto a clip row.
Subjectless required-unavailable prediction facets likewise remain explicit
source-level dashboard evidence with their recorded reasons; the dashboard does
not invent a take or logical clip witness for them.
When one source contains the same normalized name at multiple physical indices,
name-addressed nested evidence has no unique witness. The dashboard retains each
finding once as unscoped source evidence and marks the affected physical rows
duplicate_normalized_clip_name; it does not copy findings, gaps, or predictions
onto either take or logical row.
Optional transition members carry a logical clip only when their source-input
identity plus take index/name uniquely match one dashboard declaration. Strict
readback rederives that relationship and rejects missing, forged, or ambiguous
logical resolution.
Every logical row whose availability is established must also reconcile its
source, source-take index/name, evidence, coverage, and outcome with exactly one
source-owned established physical row. Runtime-set gaps and lifecycle are
also rederived from member availability: each non-established member requires
one member_unavailable gap in member order, and complete is valid only when
that derived gap list is empty. Strict readback rejects contradictions.
evaluate-transition-poses is a JSON-only, single-result transition-family
contract, not a lint/check stream. It evaluates exact named/indexed takes in
the input against document-local [transition_families."<id>"] declarations
and never changes the input. When no config file is selected or found, its
declaration authority is the exact zero-byte TOML input; this is deliberately
the same authority as an explicitly empty config file. An absent or empty
transition-family table emits no_configured_families and exits 0. With
families, all complete passes exit 0; findings or any incomplete family emit
the immutable result and exit 1. Invalid config/declaration, input loading,
or output errors emit no result and exit 2. Collection transition-family
reload/evaluation is a separate command boundary.
transform --gait-anchor is an explicit declaration that each selected clip
is an in-place cyclic gait. Before rewriting, it samples the configured Root
role (or Hips fallback) and refuses the whole command if trajectory evidence is
missing/non-finite, horizontal accumulation exceeds 1 cm, or yaw accumulation
exceeds 1°. Every nonconstant channel the operation would rotate must contain
exactly one key at each --fps whole-frame sample over the clip duration, at
the exact representable f32 key / fps time and period endpoint. Sparse,
differently framed, duplicate-time, or off-grid trajectory evidence refuses,
as do duplicate (bone, property) channels (including constant channels), so
the phase shift remains a bijective authored-value permutation. Verification
samples the exact admitted f32 key times, and mutation permutes output values
by integer key index rather than resampling. Constant channels are exempt and
cannot influence the declared period or shift. Before sampling, the command
validates the complete skeleton,
resolved metric roles, track targets/cardinalities, and finite evidence. Both
declared-frame and maximum-authored-key work must independently fit these
inclusive 1,000,000-sample bounds: declared frames × skeleton bones, declared
frames × tracks, and maximum authored keys × skeleton bones. The translation
and yaw caps apply directly; no interior step is an allowance. Yaw uses f64
first/final headings plus counted full-turn crossings. At sample zero it
selects the local +Z, +Y, or +X axis with the greatest finite horizontal
projection, in that tie order, and keeps that axis for the complete proof. A
different source-axis convention is therefore accepted without per-sample
axis switching; if the selected axis later has no horizontal projection, the
command refuses. This avoids error growth with the admitted segment count.
Four f32 successors at each inclusive cap
cover only authored endpoint translation/quaternion quantization; other checks
are unchanged. A refusal names the clip and selected bone, does not publish or
replace the requested output, and emits no earlier per-clip success lines:
standalone transform stdout is buffered until all selected clips and the
artifact write succeed. Keep authored root motion unchanged, apply a
runtime phase offset, or use separately designed trajectory-preserving tooling.
The option does not convert root motion to in-place motion.
scale is the atomic linear-scale producer. It has two distinct
subcommands because the two operations rewrite different domains and a factor
alone does not identify which one was meant: whole-document converts every
represented length by a declared factor (physical size changes; use it only
when the source was authored in a different linear unit), and rest-bind
removes one compensating inherited scale from the skinned hierarchy anchored
at a declared source skin and source root node, preserving world joint
translations and orientations, sampled trajectories, and skinned vertex
positions while removing the composed scale. The
scale workflow owns the operation-choice, glTF raw rewrite or
narrow FBX staging, proof, publication, and support-boundary walkthrough; this page remains the
installed command/flag/exit reference.
Every numeric and source-identity argument is required. Nothing is inferred
from mesh bounds, character height, joint lengths, inverse-bind magnitude,
filename, or asset category; there is no implicit first skin or root, no
animsmith.toml key, no plan file, no in-place mode, and no per-run tolerance
flag — the tolerance policy is fixed and its identity is recorded in the
evidence. Input, output, and evidence paths must all be distinct. glTF/GLB
keeps its input container; the narrow FBX rest-bind path emits a new .glb.
Accepted inputs are self-contained glTF/GLB for both operations, plus a
complete-inventory .fbx for narrow rest-bind when the default FBX feature
is enabled. See the
scale workflow for the complete support
boundary and rewrite/reload/proof sequence, and the
output reference for typed refusal
records and deterministic publication details.
scale splits its two failure codes by what the failure is a property of, not
by how far the run got. A refusal that is a property of the input asset
publishes nothing, leaves any prior pair byte-identical, and exits 1 — with
the typed reason as prose on stderr under --format text and as the same
evidence record with outcome: "rejected" on stdout under --format json.
That includes bytes that do not parse as the glTF/GLB the extension declares,
and a document whose size puts the sampled proof over the policy's work
budget. A failure that is a property of the invocation or of the
operator's filesystem exits 2 with prose on stderr: a declared factor that is
not finite and positive, an input that cannot be opened, a wrong extension, a
container the extension disagrees with, two arguments naming one file, or a
missing output directory.
The three paths must name three different files, and each is compared by the file it actually reaches: the input is resolved through a symbolic link, because reading follows one, while a destination is not, because publishing renames over a link rather than through it. Passing a symlinked input alongside a destination naming its target is refused before anything is written. A symlinked input that aliases neither destination is accepted.
transform --drop-duplicate-loop-endpoint is the narrow mechanical transform
for an inclusive DCC cycle export that copied frame 0 to the final frame. It
accepts only a strict authored-key candidate: all channels must have one common
finite, strictly increasing timeline; valid key cardinality; matching first and
last values (vector components within 1e-5, sign-invariant quaternion angle
within 1e-4 radians);
and actual interior motion. It atomically removes the same complete terminal
key count from every channel (including each cubic-spline key triplet), leaves
retained data unchanged, and re-pins duration to the last retained key. Other
clips are not generalized, retimed, or repaired by this flag. The resulting
open-cycle representation no longer satisfies inclusive loop-closure, which
expects a repeated final sample; the complete endpoint-mode classifier remains
#22.
transform --prune-constant-tracks is an opt-in companion to the default
constant-track note. It removes only multi-key translation, rotation, and
scale tracks whose evaluated keyed values are constant: vector components may
vary by at most 1e-4, and rotations by at most 1e-3 radians under
sign-invariant quaternion comparison. Single-key pins, malformed/non-finite
data, and cubic-spline tangents that create motion above tolerance are not
candidates and remain unchanged. For each candidate, the transform either
removes it or prints a refusal reason: removing it would change sampled local
TRS or model-space position/rotation, it is the clip's last remaining track, sampling is unsafe,
or it targets a bone named by that clip's effective animates_bones
expectation. The latter preserves the evidence needed for missing-bones and
frozen-bone; [rig] required_bones does not protect tracks because it is a
skeleton-presence contract.
Pruning runs after any selected slice, hold extension, duplicate-endpoint removal, and gait anchoring, so the final clip is what is judged. Text output lists every removed or retained candidate with its original track index, bone, property, interpolation, key count, and (for retained tracks) reason; no machine-readable output schema changes. It does not reduce changing keys, rewrite DCC curves, remove custom curves AnimSmith does not model, decide whether a non-rest constant pin is artistically intentional, or repair cubic tangents.
Exit Codes
| Code | Meaning |
|---|---|
| 0 | No failing findings and no required-unavailable engine prediction facets; warnings, notes, or ordinary coverage gaps may remain. |
| 1 | At least one failing finding, any required_prediction_unavailable facet (including an embedded Bevy addressability evaluation), an incomplete collection lint result, an incomplete or not-evaluable directional-speed or transition-pose evaluation, a significant diff, pending repairs under fix --dry-run, or a scale, convert, or assemble refusal that is a property of source asset bytes. |
| 2 | Operator/tool error: unopenable input, bad config, unsupported format, or invalid flags. |
The code reports what the run did, never how well it could report it. This
holds for parser-rendered help/version, text, Markdown, and every --format json
path (measure, lint,
evaluate-transition-poses, collection evaluate-transition-poses, collection lint, collection evaluate-directional-speed,
diff, convert, assemble, scale, generate addressability). If
stdout cannot accept the result — a closed pipe or full filesystem — the
checked write never panics, a best-effort checked diagnostic goes to stderr,
and the stdout-bearing path's already-established code stands except for
evaluate-transition-poses or collection evaluate-transition-poses. These immutable results have no
sidecar or previously established outcome: a failed stdout write is an
operator error, produces no usable result, and exits 2. Thus
lint … --format text | head still
exits 1 for findings it found, inspect … | head still exits 0 for an
inspection it completed, and scale still exits 1 for a refusal or 0 for
a published pair. Stderr may itself be closed; losing both streams is still
not a panic. Raising the stdout failure instead would report an operator error
for work that was actually done and make exit semantics depend on presentation
format. JSON serialization failure remains exit 2 because the CLI could not
form a truthful record; delivery failure after rendering is only reporting for
the ordinary streams. Other operator errors occur before stdout reporting,
remain stderr-only, and retain exit 2.
For artifact producers, classification is by typed provenance, never by
diagnostic wording. The JSON refusal identity is
urn:animsmith:schema:producer-refusal:1:
| Class | Examples | Code and streams |
|---|---|---|
| Asset-property refusal | Source bytes do not parse or are structurally unsupported; a valid recipe names a missing/ambiguous take, node, mesh, or material; static bake, canonicalization, gait, scale, proof, or output representability rejects the asset. | 1; JSON writes one producer-refusal:1 record to stdout and nothing to stderr, while text writes one escaped typed refusal to stderr and nothing to stdout. |
| Operator error | Invalid flags/config; recipe syntax, schema, or intrinsic values; unsupported extension; missing/unreadable declared file; unsafe, aliased, or unwritable paths; temporary/write/publication/rollback I/O; refusal-record serialization. | 2; prose on stderr and nothing on stdout in either format. |
A refusal is established before publication. It leaves any prior assembly
artifact/evidence pair and any prior convert artifact byte-identical; the
foot-cycle producer never replaces a generation. Convert
still writes its successful single artifact directly, so this is not a
rollback promise for an operator I/O failure during that write.
Commands that render several related pieces attempt one checked stream when a
single delivery boundary is promised. In particular, all selected fix
repair reports and all parts of one conversion summary produce at most one
stdout-failure diagnostic.
Help and version delivery uses clap's own fallible styled writer, so forced
ANSI color remains intact while a closed destination still follows this rule.
Only stdout is affected; nothing about artifact publication changes.
A role-dependent check with missing prerequisites reports a typed coverage
gap and does not fail the run — exit 0 means no failing findings among the
checks that evaluated, not that every declared check evaluated; see
reading a lint run for the
full outcome vocabulary. A required-unavailable engine prediction is distinct:
it exits 1 and cannot be suppressed by severity or --allow.
Use lint --deny-warnings when CI should fail on warnings as well as
errors. fix --dry-run is the repair check mode: it exits 1 when the
file has repairable defects and 0 otherwise, so CI can gate on "this
asset needs fixing" without writing anything. The exit code
reflects repairs fix would actually perform: tracks it cannot patch
(data-URI buffers, cubic quat-norm tracks, quantized rotations) are printed as
skipped[...] but do not fail the check — gate on lint (the
quat-norm or quat-flip checks) when detection alone should fail CI.
Unsupported glTF Encodings
AnimSmith reads a narrower slice of glTF than the specification permits. A
mesh accessor it cannot decode exactly as declared is refused with exit 2
rather than read as something else, so a run never reports numbers derived
from bytes it misread.
Integer TEXCOORD_0 and WEIGHTS_0 accessors are accepted only when they
declare "normalized": true, as glTF requires. The upstream reader rescales
UNSIGNED_BYTE and UNSIGNED_SHORT values even when that flag is missing;
AnimSmith refuses the unflagged accessor before decoding so measure cannot
present an unauthorized rescaling as authored data. Normalized integer and
FLOAT forms retain their decoded values. Re-export the malformed attribute
with the flag, or as FLOAT; the refusal identifies its mesh, primitive,
semantic, and accessor.
The common case is KHR_mesh_quantization: gltfpack and similar mesh
optimizers store POSITION and NORMAL as BYTE/SHORT instead of
FLOAT, and AnimSmith has no decoder for those. Which message you get
depends on how the file declares the extension:
$ animsmith lint quantized.glb
animsmith: glTF parse error: invalid glTF: extensionsRequired[0] = "KHR_mesh_quantization": Unsupported extension;
$ animsmith lint quantized-declared-used-only.glb
animsmith: mesh 0 primitive 0 POSITION: accessor 0 is VEC3 of SHORT, but the loader reads VEC3 of FLOAT
Re-export without quantization — gltfpack -noq, or turn off the
quantization option in whichever exporter produced the file — and the same
asset loads. AnimSmith measures rigs and animation, so the unquantized
source is the right input for it; keep the quantized build for shipping.
Two other shapes are refused with the same mesh N primitive M message: an
accessor typed for a different element than the slot that references it (a
VEC3 TEXCOORD_0), and one whose buffer layout the reader cannot walk (a
byteStride shorter than its own element, a sparse block of count 0, or a
dense/sparse byte extent beyond its declared buffer view or the external,
data-URI, or GLB bytes that actually resolved). Short extents are refused
rather than silently loaded as empty positions or indices, so a clean
measurement never substitutes absent geometry for unreadable authored values.
Those are invalid glTF rather than an unsupported feature, and the fix
belongs at the source. The Khronos
glTF-Validator names the
same accessor — ACCESSOR_SMALL_BYTESTRIDE for the stride, and the schema's
minimum: 1 on sparse.count for the sparse block — so run it on the file
to see the defect in its own vocabulary. AnimSmith still has to check these
itself, because the gltf crate it parses with does not: that crate's JSON
validation bounds byteStride to 4..=252 without ever relating it to the
accessor's element, so an unwalkable layout passes parsing and reaches the
reader.
The same layout defect on an animation sampler is refused too, naming the clip and channel instead of a mesh:
$ animsmith lint poisoned-track.gltf
animsmith: clip 'walk' node 3 sampler input: accessor 0 reads its elements from buffer view 0, whose byteOffset 18446744073709551615 plus byteLength 12 is a byte extent that overflows
Sampler element encodings are checked independently before the selected
reader is constructed. Inputs must be SCALAR of FLOAT; translation and
scale outputs must be VEC3 of FLOAT; rotation outputs must be VEC4 and
may use any of glTF's five decodable quaternion component types (BYTE,
UNSIGNED_BYTE, SHORT, UNSIGNED_SHORT, or FLOAT); morph-weight output
uses those same five component types as SCALAR. A mismatched type or
component type is an operator error rather than a panic or a size-coincident
reinterpretation:
$ animsmith lint mistyped-track.gltf
animsmith: animation 0 sampler 0 input for node 0 translation: accessor 0 is VEC3 of FLOAT, but the loader reads SCALAR of FLOAT
Feature Flags
The default binary enables fbx and report.
cargo install animsmith
cargo install animsmith --no-default-features
The no-default-features build has no C toolchain dependency and keeps the
glTF-only workflow: inspect, measure, lint, transform, fix, scale,
collection transform-foot-cycle, and diff. scale and the collection
foot-cycle command are the minimal build's evidence-emitting producers. The HTML report command is controlled by the report feature.
convert accepts FBX or glTF input (a glTF input is re-emitted,
carrying its geometry) but is compiled only with the fbx feature.
assemble is gated by the same feature because its maintained boundary accepts
FBX sources as well as glTF.
The exact foot-cycle producer form is:
animsmith collection transform-foot-cycle COLLECTION.toml \
--parameterization FOOT-CYCLE.toml --format json
--format json is required and JSON is the only format. There is no
--output or --output-dir: the strict parameterization document is the sole
destination authority. The destination must not exist. Success publishes one
complete generation with exactly three files per declared member plus
aggregate-evidence.json, then writes the exact aggregate file bytes to
stdout. Valid asset/capability/refinement/proof refusal exits 1 with one
producer-refusal:1 JSON record. Malformed or stale controls, unsafe paths,
I/O, serialization, and publication races exit 2 with empty stdout.
Full-scene conversion carries factor-only materials plus linked or embedded
PNG/JPEG base-color, normal, metallic-roughness, and occlusion textures. Normal textures retain their glTF
scale; FBX normal maps use glTF's default scale because ordinary FBX materials
do not expose the same scalar. See the
static asset workflow guide
for why the normal slot matters, common bad handoffs, and the engine-side checks
that conversion cannot perform.
--material-texture-recipe <PATH> applies explicit BaseColor, normal, metallic-roughness, and occlusion image
mappings during conversion. It conflicts with --animation-only, which removes
materials, and is compatible with --bake-static-mesh-transforms and either
--format value. Without a recipe, ordinary linked and embedded textures keep
their existing conversion path. See material texture recipes
for the recipe contract, deterministic processing policy, and path rules.
assemble reads a versioned multi-source character recipe,
uses one input as the authoritative skinned base, and exact-name remaps selected
takes from other FBX or glTF inputs. It writes a GLB and assembly-evidence JSON
as a rollback-safe publication pair. The command owns generic asset transforms;
source extraction, project policy, and publication remain consumer concerns.
The current recipe/evidence pair is v7. Recipe v7 may include an optional
[rest_bind_scale] block whose exact root_node_name and expected factor are
both required. The base must resolve that name to exactly one source node and
exactly one non-empty source skin whose every joint is that node or its
descendant. Every distinct FBX clip input uses an animation-only projection.
A glTF/GLB clip retains its existing successful full rest/bind or meshless
track-only path; role-specific admission additionally selects the track-only
path when only unused geometry, deformation, material, or bind obligations
would fail. Framing, dependency, raw-coverage, named-skeleton, and animation
accessor/layout obligations remain strict. The compatibility domain contains the selected base skin joints,
the root, their named ancestry, and each actual track target with its ancestry.
Unreferenced base-only geometry or attachment descendants are not required in
the clip.
Boundary whitespace is invalid rather than
trimmed. The block accepts
glTF/GLB plus the narrow normalized/baked FBX subset admitted by the existing
rest-bind capability boundary. That boundary may admit user-defined FBX
properties and bounded external texture/video declarations after same-load
evidence proves they are not scale-bearing. It also admits exactly ufbx's
marker, LOD-group, stereo-camera, camera-switcher, and display-layer typed
lists because they cannot supply hierarchy transforms, skin binds, tracks, or
geometry to the normalized rest/bind bridge. Display layers contribute only
node membership and editor visibility/freeze/color state. The boundary admits
shader/binding-table metadata on the same basis. BindPose rows are admitted
only when they cover every joint of each skin they touch and are finite,
unambiguous, and reconcile with the converted
cluster bind or node rest-world matrices already consumed by the bridge; no
Pose remains required. Those rows remain counted in the raw aggregate; every
other unmodeled typed list stays
fail-closed, with exact nonzero kind counts in the refusal. The boundary still
may admit enumerated scale-invariant conversion fidelity—omitted authored
vertex/face/edge metadata, influence projection with complete effective
coverage, triangulation, and exact-bit welding—while retaining the public
inventory evidence. It refuses incomplete resource or
construct coverage, extensions, and every unsupported transform, geometry,
bind, or animation fact. A refusal names the exact failed fact or counter. An
artifact or evidence destination that names
one of the safe source-relative dependency keys retained by the same-load
closure is an operator error even when capture was unavailable or refused;
symlink-mediated dependency keys stop before publication rather than resolving
through the link, and resource-budget truncation stops rather than trusting an
unchecked tail. Publication never replaces a source sidecar. The command
validates the base plus every clip's versioned skeleton basis before
remapping any keys. V7 retains v6's composition with
canonicalize_skin, ground_and_center, and remove_nodes: it applies those
normalized assembly transforms to both the staged source and the rebased clip
reference, then performs one final raw staged-GLB rewrite and proof. Recipe v4
retains its released refusal for that combination. Set prune_constant_tracks = true to
remove only tracks proven constant after all other transforms, including track
completion. Because this is all-property pruning, it can remove
completion-generated (bone, property) coverage whose completed value is
constant. Effective output clip animates_bones exact names are retained,
while required_bones is never used as the carve-out. Evidence records every
removed track and is empty when pruning is disabled or removes nothing. Leave
pruning disabled where consumers need dense transition coverage and do not
explicitly reset omitted properties; property-scoped selection is tracked in
#401.
Root-level remove_nodes exact-names base nodes and removes their descendant
closure after animation transforms; any surviving track, skinned mesh-instance,
or skin reference refuses the operation. With FBX rest_bind_scale, an
unskinned mesh instance attached inside that declared closure is excluded from
the private scale stage and removed with the node. It still blocks the scale
plan when the node is not declared for removal.
For every track-only clip input, v7 applies the accepted base plan's named
animation-target factors (including cubic translation tangents) through the
animation-only projection without inventing a bind rewrite. The named root and
selected base skin-joint basis must still match,
as must every actual track target and the named ancestry connecting those
nodes. Unreferenced base-only geometry descendants do not participate in that
clip proof. The projection never applies to the base; every base and full
rest/bind path retains the strict raw capability contract.
It performs no material, texture, or mesh garbage collection. Recipe/evidence
v1 through v6 remain immutable historical contracts; v3 rejects
rest_bind_scale as unknown.
The recipe identity is
urn:animsmith:schema:character-assembly-recipe:7; see
character-assembly-recipe-v7.schema.json.
Static mesh transform bake
convert --bake-static-mesh-transforms is an explicit, opt-in conversion
operation for a static mesh whose normalized placement must live directly in
mesh-local geometry. It accumulates each accepted mesh node's rest transform
through its hierarchy into positions, transforms normals with the
inverse-transpose and normalizes them, and writes the result beneath a
canonical identity root. Indices, UVs, model-supported material assignments,
and embedded base-color, normal, metallic-roughness, and occlusion textures are retained. The default
conversion is unchanged; --bake-static-mesh-transforms conflicts with
--animation-only.
The operation fails with exit code 2 rather than guessing when the input has any animation track, a skin signal, a mesh definition with no unambiguous single instance (including shared definitions), malformed or non-finite scene data, a singular or near-singular transform, or a reflection. Skin baking, animated-node baking, and reflection handling are outside this operation's contract. Same-platform runs over the same input and options produce a byte-identical artifact. The static asset workflow guide shows the before/after model, when baking is appropriate, and why this is not a general scene flattener.
Repairs
Every repair is safe, lossless, and idempotent — that is the bar for adding one. Repairs have stable ids so scripts can pin exact behavior:
| Repair id | Behavior |
|---|---|
quat-norm | Unit-normalizes finite, non-zero LINEAR/STEP quaternion keys. This is lossless because scaling a quaternion does not change the represented rotation after normalization. CUBICSPLINE tracks are skipped to preserve tangents. |
quat-flip | Normalizes adjacent quaternion keys to the same hemisphere. This is lossless because q and -q represent the same rotation. |
By default fix runs every repair. --repair id[,id] pins an exact
list (animsmith fix --help names the valid ids). fix writes only
when you explicitly choose a destination; --dry-run reports and sets
the exit code without writing:
animsmith fix clip.glb --dry-run
animsmith fix clip.glb -o fixed.glb
animsmith fix clip.glb --in-place
animsmith fix clip.glb --repair quat-norm,quat-flip -o fixed.glb
Engine profiles and importer settings
An optional [engine] section selects one exact, versioned importer contract.
There is no generic, automatic, nearest-version, or fallback profile. The
released profile tuples are:
| profile | revision | engine version | importer | accepted source |
|---|---|---|---|---|
unity-generic | 2 | 6000.3 | fbx-model-importer | FBX |
unity-humanoid | 1 | 6000.3 | fbx-model-importer | FBX |
unreal | 1 | 5.8 | fbx-importer | FBX |
godot | 1 | 4.7 | resource-importer-scene | glTF, GLB, or FBX |
bevy | 1 | 0.19.0 | gltf-asset-loader | glTF or GLB |
bevy | 2 | 0.19.0 | gltf-asset-loader | glTF or GLB |
bevy | 3 | 0.19.0 | gltf-asset-loader | glTF or GLB |
The accepted-source column is AnimSmith's bounded profile boundary, not a claim
that the named engine supports no other source formats. An absent [engine]
keeps the existing engine-neutral behavior.
Unity Generic revision 2 exposes the current closed setting vocabulary. Every applicable setting is required because the cited Unity 6000.3 documentation does not establish a default that AnimSmith can safely materialize:
[engine]
profile = "unity-generic"
profile_revision = 2
engine_version = "6000.3"
importer = "fbx-model-importer"
[engine.settings]
animation_type = "generic"
avatar_setup = "create_from_this_model"
import_animation = true
root_motion_source = "Reference/Root"
[clips."locomotion_*".engine_settings]
root_rotation = "extract"
root_position_y = "bake"
root_position_xz = "extract"
[clips.idle.engine_settings]
root_rotation = "bake"
root_position_y = "bake"
root_position_xz = "bake"
root_motion_source is an exact document-scoped source-transform path for
Unity Generic revision 2. It is not applicable to Unity Humanoid. The three
bake | extract choices are clip-scoped and resolve with the normal
field-by-field rule: matching globs in lexical order, then an exact clip name
last. Every real Generic clip must resolve all three choices. The frozen
revision-2 document controls are animation_type = "generic",
avatar_setup = "create_from_this_model", and import_animation = true.
convert_units and bake_axis_conversion belong to the preserved revision-1
advice vocabulary, not this root-motion profile. Unreal, Godot and Bevy
revision 1 expose no V1 settings, so supplying any setting is an error. Bevy
revision 2 is the exact profile used by engine-unit-scale.
It requires the load-time extension-handler environment and animation-feature
state; other loader settings have version-pinned defaults that may be
overridden explicitly:
[engine]
profile = "bevy"
profile_revision = 2
engine_version = "0.19.0"
importer = "gltf-asset-loader"
[engine.settings]
extension_handler_environment = "bare_empty" # or "bevy_pbr_stock_0_19"
bevy_animation_feature = true
rotate_scene_entity = false
rotate_meshes = false
load_meshes = "nonempty" # or "empty"
load_animations = true
The two handler values are closed, version-pinned environments: an empty
registry, or exactly Bevy 0.19's stock PBR handler. Project/user extension
handlers are outside the profile. The revision-2 settings are document-scoped;
clip engine_settings are rejected.
The current revision 3 keeps the revision-2 tuple and settings and adds only
the engine-track-support animation/channel gate. That check consumes
bevy_animation_feature and load_animations; both retain whether their
values were explicit or defaulted. The compiled feature gate has precedence:
if it is disabled, the prediction is a dropped negative outcome regardless of
the load_animations value. This revision uses load_animations to model the
stock loader's animation path, not extensions, other animation constructs, or
successful runtime survival.
The rule binds a bounded same-load raw animation inventory and independent
per-animation channel coverage. A complete empty inventory is not applicable.
Partial or unavailable source inventory emits exactly one subjectless,
unsuppressible required_prediction_unavailable inventory facet and no
retained-prefix predictions. A complete inventory may emit one negative facet
per affected animation or channel row when a gate drops it. If complete source
evidence demands more than the shared facet budget, the allocator-authorized
prefix remains available and one canonical facet_budget_exceeded summary
records the omitted suffix; this does not relabel the source inventory as
partial. When both gates allow loading, the required result is instead a stable
required_prediction_unavailable runtime-survival state. Dropped rows are not
content findings and cannot be suppressed with --allow; any required
unavailable facet still exits 1.
Revision 3's historical artifacts use output-v16 and V5 prediction provenance/readback. Revision-2/V4 and output-v15 readers and historical examples remain preserved. Unity Generic root-motion uses the separate V6 provenance and current output-v19 contract; output-v9 and every later historical reader remain immutable and readable.
All statically knowable tuple, setting, value, scope, and applicability errors
are reported before input I/O. Accepted input format and required per-clip
materialization are checked after loading. Profile selection never changes
measure values. Current output-v19 records resolved profile provenance on
lint files.
The first production rule is engine-addressability for the exact Bevy
revision 1 / 0.19.0 / gltf-asset-loader tuple. With complete glTF/GLB source
animation inventory, it emits one available facet whose subject is the exact
Bevy display label Animation{i} for each source animation index i:
animsmith --config examples/bevy.animsmith.toml lint \
--select engine-addressability examples/assets/walk.glb
Named, unnamed, and duplicate-named animations all use their distinct source
indices; names do not become typed labels. Partial or unavailable source clip
coverage emits one blocking animation_asset_label_inventory facet rather
than predictions for the retained prefix. The rule predicts only the canonical
GltfAssetLabel::Animation(i) selector spelling. It does not prove Bevy ran,
that animation loading was enabled, that the runtime asset exists, or that its
targets and graph wiring are usable. The selector can change when source
animation order changes.
Current lint uses prediction provenance v3 with bounded 4,096/N+1 settings coverage. A 4,097th clip is retained as typed partial-settings overflow evidence, never as a complete prefix.
For the exact Unity Generic revision-2 tuple, engine-root-motion uses V6
provenance. It evaluates each explicitly declared movement_owner_xz,
movement_owner_y, and movement_owner_yaw against the corresponding clip
root_position_xz, root_position_y, and root_rotation setting. bake
produces baked_into_pose, extract produces stored_as_root_motion; matching
project/importer ownership is compatible, and a mismatch is an ordinary
error finding with a scoped prediction facet. No numeric travel threshold is
used: measured translation/yaw availability is required, while the measured
magnitude is only evidence.
The path must match the explicitly resolved Root role in the raw FBX
transform inventory. Hips is never a fallback for this engine rule. A missing
or ambiguous path, Root mismatch, incomplete/unavailable raw inventory,
incomplete intent/settings, duplicate clip name, or unavailable axis
measurement emits required_prediction_unavailable; this is not a content
finding, cannot be suppressed with --allow, and makes lint exit 1. Complete
all-available work is complete, mixed work is partial, and all-unavailable
work is not_evaluated. Raw path matching is case-sensitive and byte-exact;
the only separator is unescaped /, with nonempty segments, no ./..,
backslashes, controls, or Unicode format characters, and limits of 1,024 bytes
per segment, 4,096 bytes per path, and 256 segments. The same-byte raw FBX
projection retains source identities and parent chains, excludes the implicit
root and generated helpers from matching, and reports NoMatch only under
complete coverage. AnimSmith does not execute Unity, perform imported-asset
readback or runtime playback, or certify engine behavior. The checked-in and
CI fixtures for this slice are self-authored synthetic FBX only.
For the exact Unreal revision 1 / 5.8 / fbx-importer tuple, lint also runs
engine-clip-boundary. The FBX adapter's same-load exact timing evidence is
checked only for an absolute animation-stack end coordinate on its exact frame
lattice. Missing or incomplete evidence is a required-unavailable facet; the
command does not infer import ranges or other Unreal settings.
generate addressability emits the immutable V1 animation-only contract by
default and whenever no exact Bevy revision-3 profile is selected. It emits
canonical JSON by default:
animsmith generate addressability examples/assets/walk.glb \
> walk.addressability.json
The neutral inventory retains source-order animation and channel indices,
optional non-unique source names, channel targets and accessor indices, the
primary-file identity, and the full dependency closure. Its identity covers
only those neutral fields. Without an engine profile, or with a supported
profile other than the historical exact Bevy revision-1 tuple above, the root
bevy field is null. With that historical profile, it embeds same-load
prediction provenance and the unchanged engine-addressability evaluation:
animsmith --config examples/bevy.animsmith.toml generate addressability \
examples/assets/walk.glb --format markdown
Names remain metadata and never replace source indices. Partial or unavailable animation coverage stays visible in the neutral inventory. When the exact Bevy adapter is active, the existing required-unavailable facet makes the command exit 1; without that adapter the same neutral evidence exits 0. Non-glTF input, malformed or unknown profile selections, and an actual 4,097-clip input are operator errors (exit 2). Text and Markdown are escaped presentation views of the same validated value and add no conclusions to the JSON contract. This V1 does not inventory scenes, default scenes, skins, target paths or UUIDs, Bevy named-map winners, or extension support, and it does not certify a runtime load. Consumers using the strict staged reader must also keep each report at or below 256 MiB; the reader enforces this byte cap before UTF-8 or JSON decoding.
The richer exact-Bevy path emits the separate V2 contract
urn:animsmith:schema:gltf-addressability:2, documented by
gltf-addressability-v2.schema.json.
It is selected only with the exact Bevy revision-3 profile/settings and keeps
the V1 command contract and readers immutable. The same one
engine-addressability evaluation is reused; rich rows do not introduce a
second check lifecycle. Required-unavailable rich facets retain exit 1,
complete or neutral output exits 0, and malformed tuple/config/input errors
exit 2.
V2's raw inventory independently covers scenes, nodes, skins, node-to-skin
attachments, and scene-root path candidates. It retains source indices, names,
parents/children, ordered roots/joints, explicit skin.skeleton, and exact
same-load primary/dependency-closure identity. Complete empty coverage proves
absence; partial prefixes and unavailable domains do not. The explicit
skin.skeleton member is source evidence only: Bevy ignores it and V2 makes no
inferred-root claim. Scene-instantiated SkinnedMesh attachment is outside
this static projection.
The pinned Bevy authority is tag v0.19.0, commit
c6f634ca9f406d68ba5109d921247b654cb42c10, bevy_gltf 0.19.0 with locked
gltf 1.4.1, plus commit-pinned label, loader, node-path,
AnimationTargetId, feature, and root Cargo.lock sources. The isolated
readback probe's committed lock is the graph authority: every Bevy 0.19
release crate in that lock is 0.19.0, and the probe rejects internal Bevy
patch drift; independently-versioned helpers retain their own versions.
Scene{i} exists
for each declared scene; Gltf.default_scene routes only to an existing scene handle, with no
DefaultScene label and no fabricated Scene0. Bevy eagerly creates
Skin{i}/InverseBindMatrices for every source skin, including unreferenced
skins, using identity fallback when inverse-bind data is absent. Skin{i} is
created when any source node references it during the all-source-node pass;
source skin indices, never collected-vector position, are identity.
Named scene/animation/skin maps are separate from typed labels and use
source-order last-write-wins (skin winners follow lazily created skins in
first-reference order). Target rows are per unique source animation target
node and retain contributing animation/channel identities. Paths use authored
names or GltfNode{source_index}, exclude the scene world-root name, and
publish a path/UUID only when hierarchy, reachability, closure, feature
settings, and collision evidence are complete. Missing bevy_animation,
disabled load_animations, unreachable or multiply reachable targets,
duplicate paths, UUID collisions, missing pointer width, and incomplete
evidence are typed required-unavailable states. Target UUID reproduction
requires explicitly declared 32- or 64-bit pointer width because Bevy hashes
segment lengths using the target pointer width's little-endian encoding; host
width is never inferred.
Each new rich V2 projection domain is bounded at 4,096 rows, with aggregate
projection structural references at 65,536 and dynamic projection text at
1 MiB. The sealed embedded V1 animation inventory and CheckEvaluation scopes
retain their own bounds. Names/path segments are capped at 1,024 UTF-8 bytes,
paths at 4,096 bytes and 256 segments, and staged reports at 256 MiB. The
explicit target_coverage projection reports target_domain_truncated at its
row ceiling and is also required-unavailable whenever raw or animation/channel
target-domain evidence is incomplete; other new-projection budget exhaustion
reports projection_bounds_exceeded. No retained prefix is treated as
collision-free. Strict readback rejects N+1 collections and contradictory
states. A bounded second parse of already captured primary bytes is allowed
within the same loader invocation; reopening
the input or dependencies is not. V2 is prediction evidence only and does not
claim runtime load success, target survival, scene spawning, graph wiring, or
playback.
When the one shared check would exceed core's per-file facet budget, its rich
prediction is compacted to one subjectless
engine-addressability:facet-budget scope with facet_budget_exceeded.
This preserves the existing one-lifecycle contract and does not turn omitted
facets into positive evidence.
generate contact-fragment publishes canonical contact-fragment:1 bytes to
--output. It selects one exact unique clip, samples the existing
longest-authored-channel metric grid, and records only finite bilateral
model-space stance support. Each retained two-or-more-sample run emits one
normalized support window and one earliest-minimum marker. The source primary
identity and complete dependency closure are bound into the fragment; an
incomplete role, grid, closure, or other prerequisite is a typed exit-1
refusal and leaves any existing output unchanged. --format json writes the
same canonical bytes to stdout; text is a presentation summary. The collection
form selects one exact manifest logical id and reloads its declared
source/config rather than consuming collection-output evidence. Neither form
infers physical contact, footsteps, gameplay, IK, or engine behavior.
An output-v19 measure report deliberately has no engine provenance or
loader-owned source format. diff also ignores the provenance on lint reports.
When its operands are JSON reports, diff validates the complete
version-matched records and compares their decoded version-matched measurements
(historical v15/v16 or current v18); a selected engine profile does not change
that report meaning. When its operands are source assets, the profile is still
resolved against each loader-owned source format before measurement.
Runtime-facing attachment/socket/IK nodes have one shared engine-neutral policy:
[runtime_nodes]
selectors = ["weapon_socket", "ik_*_target"]
[checks.rest-world-scale]
expected_uniform_scale = 1.0
uniform_scale_tolerance = 0.0001
The older [checks.rest-world-scale].node_selectors field remains a
compatibility alias. Declaring both spellings is a configuration error. An
absent selector field or explicit empty list means no runtime-node policy.
Machine Output
measure, lint, collection lint, collection evaluate-directional-speed,
diff, generate addressability, and
generate import-advice support
--format json. The native JSON contract is the source of truth and is
versioned with schema_version.
See output.md and the current
urn:animsmith:schema:output:19. Output-v17 remains available as historical
output-v17.schema.json with immutable
identity urn:animsmith:schema:output:17. Output-v16 remains available as historical
output-v16.schema.json with immutable
identity urn:animsmith:schema:output:16. Nested measurement
evidence has its own
urn:animsmith:schema:measurements:18
measurements-v18.schema.json contract.
urn:animsmith:schema:measurements:15, urn:animsmith:schema:output:9,
urn:animsmith:schema:output:11,
urn:animsmith:schema:output:12, urn:animsmith:schema:output:13,
urn:animsmith:schema:output:14, and urn:animsmith:schema:output:15 remain
historical immutable contracts alongside output-v16. diff
retains strict version-matched readers for output-v11/v12 with
measurements-v15 and output-v13 through output-v17 with measurements-v16;
the latter retains identity urn:animsmith:schema:measurements:16;
output-v9 and its
measurements-v9 pairing remain immutable historical contracts. Output-v10 and
earlier reports require regeneration from the original asset.
collection lint COLLECTION.toml --format json emits the separate current
urn:animsmith:schema:collection-output:11 contract. Historical
urn:animsmith:schema:collection-output:9,
urn:animsmith:schema:collection-output:8,
urn:animsmith:schema:collection-output:7,
urn:animsmith:schema:collection-output:6,
urn:animsmith:schema:collection-output:5, and
urn:animsmith:schema:collection-output:4 remain immutable; the manifest directory is
the control root; safe missing/unreadable sources, rejected readable bytes,
digest/take mismatches, and incomplete runtime-set members remain typed rows
and exit 1 while later safe sources continue. Invalid manifests, unsafe or
nonregular paths, and missing/malformed selected configs exit 2 without an
envelope. Source, clip, and set rows are canonical; member order remains the
manifest order. Each source separately retains its primary input identity and
a complete, partial, or unavailable dependency-closure state. Only a complete
closure identity can establish that source, one of its clips, or a runtime-set
member; partial/unavailable reasons remain typed and make the result incomplete
with exit 1. Each established clip binds an exact source take index/name
to a normalized clip index and carries duplicate-safe indexed measurements.
The nested whole-document lint envelope uses current output v19 and
measurements v18; output v18 and measurements v17 remain a matched historical
reader pair with immutable identities urn:animsmith:schema:output:18 and
urn:animsmith:schema:measurements:17. The preceding collection envelope is
also retained as urn:animsmith:schema:collection-output:10.
The strict reader preserves version binding for collection-output-v9 with
output-v17, collection-output-v8 with output-v16, collection-output-v7 with
output-v15, collection-output-v6 with output-v14 and collection-output-v5 with
output-v13; older historical
contracts remain immutable and are not retargeted. Regenerate current collection evidence
before passing it to collection evaluators.
Runtime sets keep decision: not_evaluated; they make no blend, controller,
engine, artistic, or gameplay claim. Every declared member carries raw
root_travel: its existing duration_s, root-trajectory translation
availability, signed horizontal X/Z displacement, sampled horizontal travel,
and speed availability/value. Binding-unavailable rows remain explicit with
unavailable root-travel facts. evidence.root_travel reports the count of
fully measured declared members and a complete/incomplete lifecycle; it never
reduces the declared set to measurable rows. Gait-group member rows additionally
carry raw gait-phase availability, and only a fully established, phase-measured
set emits evidence.gait_phase.phase_spread with basis
max_circular_deviation_from_mean; this preserves existing gait lint
threshold semantics. See
collection-output-v7.schema.json.
collection evaluate-directional-speed --policy POLICY.toml --evidence COLLECTION-OUTPUT.json --format json strictly reads a bounded
collection-directional-speed-policy:1 declaration and a bounded
collection-output V7, historical V6, or historical V5 document, then writes the separate immutable
urn:animsmith:schema:collection-directional-speed-evaluation:1 result. The
result binds the exact raw TOML and JSON byte identities and preserves every
declared member in manifest order. Invalid, stale, wrong-kind, unreadable,
malformed, or over-budget inputs exit 2 with no stdout. Incomplete root travel,
zero endpoint displacement, zero ratio reference, numeric-range outcomes, and
declared-policy findings write the result and exit 1; only a complete passing
policy exits 0. It has no text/Markdown presentation, --output, subset, or
inference mode. See
collection-directional-speed-evaluation-v1.schema.json.
generate addressability has a separate immutable contract,
urn:animsmith:schema:gltf-animation-addressability:1; see
output.md and
gltf-animation-addressability-v1.schema.json.
The richer exact-Bevy path additionally has immutable contract
urn:animsmith:schema:gltf-addressability:2; see
gltf-addressability-v2.schema.json.
Neither is output-v11 or a diff measurement operand.
generate contact-fragment is also outside output-v11: it writes the strict
urn:animsmith:schema:contact-fragment:1 sidecar. Its exit-1 JSON refusal
uses producer-refusal:1; exit 2 is reserved for CLI, config, path, or
publication control failures.
generate import-advice requires an exact [engine] selection and all
required settings, then binds that resolved profile to one input and the
same-load raw facts, dependency closure, explicit clip intent, and normalized
measurements:
animsmith --config unity.animsmith.toml generate import-advice export.fbx
animsmith --config unity.animsmith.toml generate import-advice export.fbx \
--format markdown
The default JSON is the separate immutable contract
urn:animsmith:schema:engine-import-advice:1; see
output.md and
engine-import-advice-v1.schema.json.
The strict reader caps the serialized document at 256 MiB before decoding.
Unity 6000.3 Generic/Humanoid projects only the resolved importer settings
modeled by profile revision 1. Unreal 5.8 and Godot 4.7 revision 1 return a
typed profile_settings_unmodeled refusal (exit 1). Available advice exits 0;
config, unsupported-profile/input-format, I/O, and serialization errors exit
2. The command never invents authored frame numbers, sampling rates, unit
conversion, or root-motion behavior. Text and Markdown are presentation-only
views of the same validated value.
The exact revision-2 tuples are also supported by this command. Godot uses
godot / 2 / 4.7 / resource-importer-scene with glTF JSON or GLB and
projects only animation/fps (1..120, profile default 30) and
animation/trimming (profile default false). Unreal uses unreal / 2 /
5.8 / fbx-importer with FBX and requires an explicit
sample_rate = "default_30", "source_determined", or
"custom_hz(n)" for 1..48,000 Hz. The V2 JSON identity is
urn:animsmith:schema:engine-import-advice:2; its basis is bound to the
document-scope import-setting projection and its optional projection is typed.
An available document carries the projection; a refused document
carries a typed reason and no projection.
The schema is engine-import-advice-v2.schema.json.
Neither state means the engine imported or played the asset: frame ranges,
units, skeleton/retargeting, compression, root motion, runtime behavior, and
project-file writes remain outside this output.
Measurements v16 retains the v15 clip evidence and adds source-order primitive geometry plus bounded leading-magic evidence for unsupported nonempty images. Measurements v15 added canonical per-bone local TRS channel coverage and sampled Root/Hips trajectory evidence. Root is preferred whenever that role resolves; Hips is only a typed fallback when Root is unresolved. The trajectory groups signed endpoint displacement, sampled horizontal travel and vertical extrema, plus net/unwrapped/travel yaw with independent translation and yaw availability. These are engine-neutral normalized-model-space regression facts from the shared uniform metric grid, not continuous-curve or engine root-motion extraction proof; see output.md for the full coordinate, sampling, and availability contract.
rest-world-scale is quiet until its config resolves a nonempty shared or
legacy runtime-node selector policy.
Each exact name or * glob must resolve to one source node; findings include
the stable source-node path and ancestry so attachment/import policies can be
traced back to the source projection. For glTF that projection is authored
node state; for FBX it is ufbx-normalized metre/Y-up, adjusted/inheritance-
compensated state rather than the raw FBX transform stack. See the
selected-node scale workflow.
convert --format json emits conversion evidence v2, with immutable identity
urn:animsmith:schema:conversion-evidence:2; see output.md and
conversion-evidence-v2.schema.json.
It records the requested options, counts from the written artifact, exact
static-mesh transforms when requested, and recipe provenance when a material
texture recipe is used. text is the default human-readable write summary.
An asset refusal under --format json uses the independent immutable
producer-refusal-v1.schema.json
identity instead; conversion evidence v2 remains success-only and unchanged.
assemble writes evidence v7 to its required --evidence path, with immutable
identity urn:animsmith:schema:character-assembly-evidence:7; see
character-assembly-evidence-v7.schema.json.
assemble --format json prints the same record to stdout — the identical bytes
the evidence file receives, serialized once — in place of the default text
publication summary. An asset refusal publishes neither member and exits 1;
JSON emits producer-refusal:1 on stdout, while text emits its stable kind on
stderr. Operator errors remain exit 2, stderr-only. A failed stdout delivery
does not change an already-established success or refusal code.
scale writes scale evidence v4 for glTF/GLB to its required --evidence
path, with immutable identity urn:animsmith:schema:scale-evidence:4; see
output.md and
scale-evidence-v4.schema.json. The
same record is what scale --format json prints to stdout, for a refusal as
well as for a published pair. For an artifact-proof-failed refusal from the
exact-preservation walk, rejection.artifact_proof_differences names up to 16
raw JSON locations the walk found different; omitted counts locations beyond
that fixed cap, and the full count is items.length + omitted. The field is
null for other artifact-proof claims and for capability refusals, whose
violations array retains its existing meaning.
With the default FBX feature, scale rest-bind INPUT.fbx -o OUTPUT.glb is a
separate narrow producer contract. It accepts only the complete normalized
ufbx inventory, emits the immutable
urn:animsmith:schema:scale-evidence:5 record described by
scale-evidence-v5.schema.json, and
proves the exact re-encoded GLB after reload. It never writes FBX and makes no
raw-FBX preservation claim. scale whole-document remains glTF/GLB-only.
Machine-readable lint rejects --allow so it cannot erase evidence. The flag
remains a presentation and exit-policy convenience for text and Markdown.
Native JSON is deliberately shaped so serializers can be added later without redesigning the checks: SARIF for code scanning, GitLab Code Quality/CodeClimate for MR widgets, JUnit XML for CI dashboards, and CSV for ad-hoc analysis.
Each JSON measure and lint file row records an input SHA-256 and byte
count for the exact primary file bytes parsed. Keep that row with retained
review, promotion, or publication evidence; multi-file runs calculate one
identity per argument in order. For .gltf, the identity deliberately does
not cover external buffer or image files. Profiled lint embeds the bounded
same-load dependency closure in prediction_provenance; other workflows must
retain dependency provenance separately when that scope matters.
Human-readable command output keeps asset-derived names, messages, and paths on visibly ordered lines: terminal controls, Unicode line separators, and bidirectional formatting characters are rendered as visible escapes. Markdown also flattens line separators and neutralizes table/code-span delimiters before the text is pasted into a trusted review comment. JSON retains the original strings as machine data.
measure --format json also inventories glTF/GLB material definitions,
texture-to-image links, and image metadata. Its exact core source-slot domain
is base_color, normal, metallic_roughness, occlusion, then emissive.
This lets a pipeline distinguish an emissive-only material from an untextured
one and identify shared source resources without parsing the asset again. The
nested contract reports material_resource_coverage: "complete" when that
documented five-slot glTF/GLB domain was inspected and "unavailable" when a
loader has no equivalent source-resource view. Complete does not cover
extension-defined texture slots or promise writer/conversion preservation.
This is descriptive evidence only: it does not accept, repair, resize,
transcode, or otherwise fix an image. See
machine-readable output for the full boundary.
CI Comments (lint --format markdown)
lint --format markdown renders findings as GitHub/GitLab-flavored
Markdown for pasting into a CI comment or asset-review thread. It mirrors
the text output's information — severity, check id, location, measured
and expected values, per-clip grouping, and typed coverage gaps — as tables inside a per-file
collapsible section, with a clean summary for a passing asset:
animsmith lint clip.glb --format markdown >> "$GITHUB_STEP_SUMMARY"
A file with findings is rendered as a <details> section (expanded for
short lists, collapsed once a file carries more than ten findings so one
noisy asset does not bury the rest of the comment). A file with neither
findings nor gaps collapses to a one-line ✅ Clean summary. A footer tallies
errors, warnings, notes, and gaps across every input. The exit code is
unchanged from text and JSON — see Exit Codes. Repeated gaps
with the same check id and code share one presentation row with a count and
bounded subject list; JSON retains every original scope.
Markdown is presentation-only and carries no stability guarantees —
gate automation on --format json (see output.md), and
treat the Markdown as display text that may be re-laid-out between
releases.
Machine-Readable Output
animsmith's native JSON is the stable source of truth for pipeline adapters. Text and Markdown lint output are presentation views over the same evaluation results. The HTML report renders the same typed findings, coverage gaps, and prediction facets beside its sampled-motion view.
HTML report: source poses and illustrative blending
The single-document report keeps source playback separate from its optional
illustrative blend. Source panes draw the exact Rust model-space pose-grid
positions judged by checks. The illustrative blend control appears only
after the selected pair passes admission, and adds a third pane computed from the embedded sampled-local-trs-blend-v1 presentation
authority. It produces no finding, metric, animation export or engine-runtime
conformance claim. The separate before/after comparison report keeps its
existing source-pose and overlay behavior.
A finding whose clip name is missing or ambiguous shows a refusal notice and leaves the source selection and frame unchanged. A uniquely named finding still selects its source clip and judged time.
Samples and blend math
Each included, admitted clip carries a dense local-transform stream alongside
its source positions: frame-major, then bone-major, ten little-endian f32
components per record in tx, ty, tz, qx, qy, qz, qw, sx, sy, sz order. Existing
parent indices identify roots or earlier bones. Rust sampling has already
filled absent channels from the rest transform; malformed channel storage is
not treated as an absent channel. No rest-pose copy or authored animation
tracks are added to the report.
At shared normalized phase u, each clip selects its own nearest frame,
round(u * (frames - 1)), clamped to its grid. The primary clip drives playback;
both actual source times are labelled. This selects sampled frames and does
not resample or time-warp authored clips. Pairing is available across admitted
clips of the document and does not require gait-group membership. Name-based
links resolve only unique names; an invalid or self-pair clears pairing.
At an interior weight w, translation and scale use componentwise
(1-w)*a + w*b. Rotations use shortest-hemisphere normalized linear
interpolation (nlerp): normalize both quaternions, negate the second only when
their dot product is negative, retain its sign at an exact zero-dot tie, then
normalize the weighted sum. These calculations use binary64 arithmetic.
Full column-vector affine matrices compose T * R * S, with each root's own
transform and world[parent] * local[child]; this retains shear from rotated
nonuniform-scale ancestors. Finite zero and negative scales are allowed.
Weights 0 and 1 instead draw the corresponding source positions exactly.
This explicit endpoint branch guarantees agreement with the source panes;
it does not promise a universal continuity or imperceptibility bound for
arbitrary scaled chains. Interior results must remain finite and representable
as f32 before GPU upload; unavailable results clear the prior blend instead of
displaying stale geometry. The binary64 Rust reference uses the same embedded
f32 samples as the browser. Its synthetic-fixture conformance tolerance is
1e-5 + 1e-5 * abs(reference) metres per position component, not an engine
acceptance threshold or a proof for arbitrary skeletons.
Added-data limits and omissions
The new local-authority path admits at most 1,024 bones, 4,096 included clips, 65,536 aggregate track-metadata records and 32 MiB of added raw local data (40 bytes per bone/frame, at most 838,860 complete records). Checked aggregate counts and byte arithmetic precede local-value inspection and stream allocation. The aggregate base64 allocation is also checked. Budget excess or arithmetic overflow omits all local streams, rather than keeping whichever clips fit first. Source playback, findings and report success remain intact.
After aggregate admission, malformed track targets, properties, storage,
cardinalities or duplicate channels omit that clip's complete local stream.
Non-finite sampled components or quaternion length outside 1 +/- 1e-4 do the
same. Invalid shared parent topology makes all blends unavailable. Each refusal
shows a reason; source findings remain available. The browser validates and
decodes only the selected pair when selection changes, before showing blend
controls, and retains at most two local streams. Weight, phase and repaint do
not decode streams or scan the clip array. Reusable storage supports one
O(bones) blend/FK pass per repaint, with no all-pairs table or blended trail.
This cap bounds added local data and its processing, not existing source pose
allocation, sampling, checks, loader/caller data, full JSON or full HTML memory.
render(inputs) -> String keeps its existing public API. The comparison's
separate 32 MiB pose budget remains per side and is unchanged.
All three panes use the same fixed source-based camera. Interior blended rotations can extend outside those bounds; use the existing orbit/zoom controls. There is no guarantee every interior pose fits the initial view and no weight-dependent automatic refit.
The persistent caption reads:
Engine-agnostic illustrative blend of sampled local transforms (nlerp rotations). Normalized phase, not a time warp. Not Bevy, Unity, Unreal or Godot runtime evidence.
--evidence-only omits both source positions and local transforms and skips
local-authority work. Both are motion-bearing data; neither belongs in a shared
report when the source-motion license forbids it. The notice reads:
Pose playback and illustrative blending are omitted in this evidence-only report. No sampled positions or local transforms are embedded.
Skeleton compatibility
animsmith skeleton compare emits one immutable
skeleton-compatibility-v1.schema.json
(urn:animsmith:schema:skeleton-compatibility:1) result. Its control plane is
the exact raw correspondence TOML bytes, recorded as correspondence.input.
That TOML pins the exact source and target primary identities, their explicit
selected node-name sets (including each selected root), the declared matching
mode, the resolved explicit mapping (empty for exact-name mode), and every
tolerance. It does not infer skeleton membership from scene
graph reachability. Invalid or stale
control input, unreadable input, or an input beyond the 64 MiB primary-source
bound is an operator error (exit 2) and emits no result.
Each subject records selected_skeleton_identity: the exact identity of a
canonical, name-ordered projection of the selected source-node measurements
and selector. This is provenance derived from the loaded asset, not another
authored rest-pose profile.
The result keeps structural topology/rest evidence separate from optional
source skin-membership, inverse-bind, and deformation-model facets. A result
is compatible only when every required topology/rest row was available and
within its declared tolerances. Complete mismatches are incompatible; mixed
available/unavailable required topology/rest evidence is partial even when
another retained field also mismatches; and no evaluable correspondence is
not_evaluated. The last three exit 1. Neither the outcome
nor any optional facet establishes retargeting, runtime deformation, masking,
contact, gameplay, or artistic acceptance.
rows are deterministic: correspondence-covered source rows are emitted in
source-name order, followed by unmapped source names and then unmapped target
names in their respective name orders. A matched row
contains separate local and rest-world translation, rotation, and scale deltas;
the world rotation value exists only where the existing source measurement
proves a right-handed unit-orthonormal rest-world linear transform. A
child-bone ratio value exists only when both corresponding parent/child world
positions permit an independent nonzero length comparison. Parent
correspondence and child length
use the nearest selected ancestor, walking across unselected intermediate
nodes; they compare the declared selector projection rather than claiming that
the complete raw hierarchies are identical. missing_source, missing_target,
and parent_mismatch rows are explicit rather than folded into a score.
Non-pass rows identify a deterministic owner_surface and remedy_class.
These route work to the correspondence, selected skeleton authority, selected
skin/bind evidence, or a documented measurement boundary; they do not infer
project, artist, or vendor blame from structure alone. Unavailable source and
target evidence sides carry the same routing fields. The text view renders the
exact source, target, selected-skeleton, and correspondence identities; matching
mode and explicit pairs; tolerances; every retained row delta; facet details;
and remediation routing. JSON remains the stable adapter contract.
Transition-pose evaluation
animsmith evaluate-transition-poses INPUT --format json emits exactly one
immutable urn:animsmith:schema:transition-pose-evaluation:1 result described
by transition-pose-evaluation-v1.schema.json.
It is neither an output-v11 measure/lint envelope nor a check stream. The
result binds the exact input document bytes as subject_input, the complete
exact config source as declaration_input, and the declaration's independent
normalized identity as declaration_normalized. For configured document
families it also binds the complete same-load dependency-closure identity at
the subject and each member, so a changed external glTF animation buffer cannot
be masked by unchanged primary JSON bytes. With no selected or ambient config,
the exact declaration source is the defined zero-byte TOML sequence; an
explicitly empty config is intentionally identical.
An absent or empty [transition_families] table is a complete passing result
with reason: "no_configured_families". Complete configured passes exit 0;
findings and every retained incomplete family exit 1 with this same result.
If same-load dependency-closure capture is incomplete, configured families are
retained as incomplete/not_evaluated with
dependency_closure_incomplete; an empty declaration remains the complete
no_configured_families pass because it evaluates no source data. Invalid
config/declaration, contradictory witnesses, input/load failures, and
serialization failures emit no result and exit 2. Because this result has no
sidecar or previously published artifact, failed stdout delivery is also an
operator error (exit 2), rather than a successful result with a best-effort
diagnostic. V1 evaluates one loaded document and does not imply collection
source reload, graph generation, engine runtime behavior, or input mutation.
animsmith collection evaluate-transition-poses COLLECTION.toml --families TRANSITION_FAMILIES.toml --format json uses the same immutable result schema,
with subject_input bound to the exact collection manifest bytes. It verifies
the envelope's manifest identity and every logical/source/take witness before
source work. Missing, unreadable, oversized, digest-mismatched, malformed, or
otherwise unusable source input makes its whole family
incomplete/not_evaluated with member_unavailable; it never compares a
surviving subset. A readable raw source retains its exact source_input even
when unusable; only a source for which no complete bytes were available uses
source_input: null, and only in a member_unavailable family. Per-family
members may span documents but must have one exact normalized skeleton basis.
Collection lint
animsmith collection lint COLLECTION.toml --format json emits the separate
current urn:animsmith:schema:collection-output:11 envelope. Historical
urn:animsmith:schema:collection-output:9,
urn:animsmith:schema:collection-output:8 (see
collection-output-v8.schema.json),
urn:animsmith:schema:collection-output:7,
urn:animsmith:schema:collection-output:6,
urn:animsmith:schema:collection-output:5, and
urn:animsmith:schema:collection-output:4 envelopes remain immutable.
It binds the exact manifest bytes to canonically ordered source, logical clip,
and runtime-set records while preserving each set's declared member order.
Every available source embeds its ordinary one-file output-v19 lint result;
each established logical clip separately carries the existing
ClipMeasurements value selected by raw source take index and exact authored
take name, then mapped through the loader's observed normalized clip index.
This duplicate-safe indexed projection does not revise the historical,
immutable measurements-v15 name-keyed wire contract; collection-output-v11
embeds measurements-v18 in its nested output-v19 documents. The strict reader
also preserves collection-output-v10 (urn:animsmith:schema:collection-output:10)
with its exact nested output-v18 (urn:animsmith:schema:output:18) and
measurements-v17 (urn:animsmith:schema:measurements:17) pair.
The strict reader
continues to bind historical collection-output-v9 only to output-v17 with
measurements-v16, historical collection-output-v8 only to output-v16,
historical collection-output-v7 only to output-v15,
historical collection-output-v6 only to output-v14, and historical
collection-output-v5 only to output-v13.
Source input, digest pin, config, loader, take inventory, and document-result states stay orthogonal. A readable digest mismatch can therefore retain its observed identity, loader result, and take drift without authorizing the clip binding. Missing/unreadable sources, rejected readable bytes, digest/take mismatches, and unestablished members produce a complete typed envelope and exit 1; manifest, rooted-path, selected-config, serialization, and tool errors exit 2 with no envelope. The frontend preflights the complete control plane before source execution and reads sources sequentially.
Collection dashboard
animsmith collection dashboard --collection COLLECTION-OUTPUT.json -o DASHBOARD.html --authority DASHBOARD.json consumes only a strict current
collection-output V11 document. It writes a self-contained offline HTML
presentation and the separate
urn:animsmith:schema:collection-dashboard:1
machine authority. That authority records the exact raw collection-output
identity, bounded source/take/logical-clip and ordered runtime-set projection,
and optional compatible transition-pose evaluation input/result. The optional
result must be transition-pose-evaluation V1 bound to the same manifest input;
other evaluation contracts remain separate rather than being guessed into a
per-member dashboard projection.
Every declared source has its own inventory row even when no logical clip
binds to it. The row retains its safe locator, optional observed input identity,
availability, loader state, dependency-closure state, and bounded observed-take
inventory. Each physical take preserves its exact source-owned take index/name,
normalized index/name state, evidence, and outcome without inventing a logical
ID. Summary evidence totals count physical rows plus unscoped source findings
and unscoped source-level required_prediction_unavailable facets; the
logical-clip outcome partition remains a separate declared-row view. Valid
nested findings that have no matching clip are retained as source-level
unscoped findings and included in the dashboard finding total; the projection
never assigns them to an arbitrary clip. Required-unavailable prediction facets
without one exact physical witness are likewise retained once at source scope
with their reason set rather than being guessed onto a take or logical row.
Nested check evidence is normalized-name addressed. If one source has that same
normalized name at multiple physical indices, neither index is a unique witness:
the physical rows carry duplicate_normalized_clip_name, name-addressed findings
are counted once at source scope, and no findings, gaps, or predictions are
guessed onto either physical or logical row. An established logical row must
instead identify exactly one source-owned established physical row by source,
source-take index, and take name, and its evidence/coverage/outcome must match
that row during strict readback.
When transition evidence is present, a member's optional logical_clip is
required exactly when its source_input, take_index, and take_name resolve
uniquely through the dashboard source identities and logical declarations.
Strict authority readback rederives the match rather than trusting the stored
logical ID; absent, mismatched, and ambiguous witnesses remain unresolved.
Runtime-set gaps and lifecycle are also rederived during strict readback
from the declared member rows: each non-established member contributes one
member_unavailable gap in member order, and the set is complete only when
that derived gap list is empty.
The dashboard is inventory and evidence navigation only. It has no quality score and does not establish engine loading/playback, retargeting, contacts, visual or artistic quality, or gameplay acceptance. Caller-supplied per-asset report links must be exact logical-id keyed safe relative paths; source paths, licensed assets, and motion samples are never embedded.
Collection directional-speed evaluation
animsmith collection evaluate-directional-speed --policy POLICY.toml --evidence COLLECTION-OUTPUT.json --format json consumes only strict bounded
collection-directional-speed-policy:1 TOML and collection-output V7,
historical V6, or historical V5 JSON.
It emits the JSON-only immutable
urn:animsmith:schema:collection-directional-speed-evaluation:1 result
described by
collection-directional-speed-evaluation-v1.schema.json.
The result identifies the raw policy and evidence bytes, retains all declared
members in manifest order, and records incomplete/not-evaluated outcomes or
declared-policy findings without evaluating a subset. Control-plane failures
write no result and exit 2; result-bearing incomplete, not-evaluable, and
finding outcomes exit 1; a complete passing policy exits 0.
Runtime-set evidence concludes only whether every declared membership and required
indexed measurement was established. Its decision is always not_evaluated;
it does not infer blending, synchronization, retargeting, controller policy,
engine behavior, or artistic/gameplay readiness. A gait-group additionally
projects each member's raw gait_phase availability (and phase when measured)
onto that existing manifest-ordered member row. Its set-level
evidence.gait_phase records lifecycle and members_measured; only a
complete group (every declared member established and phase-measured) carries
phase_spread and the explicit basis
max_circular_deviation_from_mean. The scalar is the existing
circular_phase_spread value, not a smallest covering arc, and is calculated
from logical-ID-sorted phases even though member rows retain manifest order.
Incomplete groups keep every member visible and omit both scalar fields;
non-gait sets omit gait-phase evidence. Every declared member also carries raw
root_travel: duration, translation availability, signed horizontal X/Z
displacement, sampled horizontal travel, and speed availability/value.
Set-level evidence.root_travel counts only members with every required raw
fact and is complete only when all declared members are fully measured; it
never reduces the set or adds direction, ratios, thresholds, or policy.
The strict reader applies a 256 MiB N+1 cap before JSON decoding, validates
current nested output-v19 plus historical collection-output-v9/output-v17,
collection-output-v8/output-v16, collection-output-v7/output-v15,
collection-output-v6/output-v14, and
historical collection-output-v5/output-v13 through their existing reader,
recomputes all summaries/work/set lifecycles, and rejects
unknown fields or contradictory identities and states. Producer and reader
also freeze 1 GiB per primary source, 16 GiB aggregate primary reads, and the
collection-manifest V1 row/member/work limits.
Derived normalized clip names allow at most 4,101 bytes: the 4,096-byte
authored-name bound plus # and the largest duplicate ordinal permitted by the
4,096-clip manifest bound. Available nested measurement keys retain
output-v19's 4,096-byte bound.
If such a derived name cannot fit the immutable 4,096-byte text bound of the
nested output-v19 contract, indexed clip measurements and physical binding are
retained, but the nested document and its name-addressed check reference are
nested_output_unavailable; the collection exits 1 instead of publishing
schema-invalid nested JSON.
After the aggregate N+1 witness is retained, later declared sources remain in
the envelope as aggregate_exhausted with zero inspected bytes; they are not
opened and cannot increase the terminal counter beyond N+1.
glTF animation addressability
animsmith generate addressability INPUT emits a separate, one-file V1
contract with immutable identity
urn:animsmith:schema:gltf-animation-addressability:1. Its retrievable schema
is
gltf-animation-addressability-v1.schema.json.
It is not an output-v11 measure/lint/diff envelope, and the two roots reject
one another on readback. The staged reader accepts at most 256 MiB and applies
that byte cap before UTF-8 or JSON decoding.
The root contains the normal bounded tool identity, the exact primary input
identity, an engine-neutral inventory, and a nullable bevy adapter. The
inventory retains the complete existing dependency-closure record plus raw
source-order animation and channel observations. Source names are optional,
non-unique metadata; source indices remain the identity authority. The
inventory's canonical identity excludes tool metadata and the optional adapter,
so selecting a profile does not change neutral evidence.
With no engine profile or another supported non-Bevy profile, bevy is null.
For the exact Bevy revision 1 / 0.19.0 / gltf-asset-loader profile, it embeds
the same-load prediction-provenance V1 record and the unchanged
engine-addressability check evaluation. The evaluation's available facet
subjects are the authoritative Animation{source_clip_index} display
selectors. A required-unavailable facet remains blocking and makes the command
exit 1. Incomplete dependency closure is retained honestly but does not, by
itself, become a claim about runtime load success.
The contract is animation-only. It makes no scene, default-scene, skin,
target-path or UUID, Bevy named-map-winner, extension-support, successful-load,
target-survival, or animation-graph claim. JSON is canonical; --format text
and --format markdown escape and render the same typed value without adding
conclusions.
Rich glTF addressability v2
The richer producer is a separate immutable contract,
urn:animsmith:schema:gltf-addressability:2; its schema is
gltf-addressability-v2.schema.json.
It preserves the V1 animation inventory and adds bounded same-load scene,
node, skin, attachment, scene-path, default-scene, named-map, and animation-
target evidence. V1 readers and the V1 Bevy revision-1 profile remain
unchanged; selecting the richer exact Bevy path selects V2 rather than widening
the historical root.
The raw inventory has independent coverage for scenes, nodes, skins,
attachments, and path candidates. Complete empty domains prove absence;
partial prefixes and unavailable domains never prove absence. Rows retain
source-array indices, authored names, parents and child order, ordered scene
roots, ordered skin joints, node-to-skin attachments, and all-scene
root-to-node path candidates. An authored skin.skeleton is retained as an
explicit source observation. Bevy 0.19 ignores that member, so V2 does not
replace it with an inferred Bevy root. The report does not claim scene
instantiation or SkinnedMesh attachment.
The exact Bevy adapter is paired with the existing one engine-addressability
evaluation and reuses the V1 Animation{i} selector primitive. It is pinned
to profile revision 3, Bevy v0.19.0, commit
c6f634ca9f406d68ba5109d921247b654cb42c10, bevy_gltf 0.19.0, locked
gltf 1.4.1, and commit-pinned label, loader, path, animation-target, feature,
and root Cargo.lock sources. Scene{i} is emitted for each declared source
scene;
Gltf.default_scene is only a route to an existing Scene{i}. There is no
DefaultScene label and no fabricated Scene0.
Bevy creates Skin{i}/InverseBindMatrices eagerly for every declared source
skin, including unreferenced skins; an absent inverse-bind accessor uses the
identity fallback. Skin{i} is materialized when any source node references
the skin during Bevy's all-source-node construction pass. Source skin indices,
not the order of Bevy's collected skin values, are identity. Named scene,
animation, and skin maps are separate from typed labels and use source-order
last-write-wins semantics (the skin map follows lazily created skins in first-
reference order).
Target projections are per unique source animation target node and retain
contributing animation/channel identities. Paths use authored node names,
including an authored empty string exactly, or
GltfNode{source_index} fallbacks, exclude the scene world-root name, and are
projected only when reachability, hierarchy, dependency closure, feature
settings, and collision checks are complete. A duplicate full path, multiple
scene candidates, target-ID collision, unreachable target, missing
bevy_animation, disabled load_animations, missing pointer width, or
incomplete evidence is typed required_unavailable; no guessed path or UUID
is published. Bevy's target-ID reproduction requires an explicit 32- or
64-bit pointer width because its segment lengths use target-width little-endian
encoding; the host width is never inferred.
The projection also carries target_coverage, independently of the retained
target rows. A target domain is complete, including when empty, only when the
raw node/scene/path and animation/channel inventories are exhaustive. Partial
or unavailable evidence makes coverage required_unavailable; a domain beyond
the 4,096 row limit additionally reports target_domain_truncated, and an
aggregate rich-projection limit uses projection_bounds_exceeded. This keeps
positive-prefix target rows from being mistaken for proof of complete,
collision-free coverage.
Each new rich projection domain is capped at 4,096 rows. Its aggregate
structural references are capped at 65,536 and its dynamic projection text at
1 MiB; the sealed embedded V1 animation inventory and CheckEvaluation scopes
retain their own bounds. Names and path segments are capped at 1,024 UTF-8
bytes, paths at 4,096 bytes and 256 segments, and the staged report reader at
256 MiB. Target coverage is an explicit projection: a retained canonical
prefix is target_domain_truncated, while any new rich projection bound
overflow is projection_bounds_exceeded; no retained target is treated as
collision-free after truncation. Strict readback rejects N+1 collections and contradictory identities or states. A second
bounded parse of already captured primary bytes is allowed within the same
loader invocation, but no primary/dependency reopen is allowed. V2 remains
prediction evidence only: it does not certify runtime loading, target
survival, graph wiring, scene spawning, or playback. Required-unavailable
prediction retains the existing exit-1 convention; malformed tuple,
configuration, or input errors remain exit 2.
If the resulting single-check prediction would exceed core's per-file facet
budget, the adapter compacts its unavailable facets to one subjectless
engine-addressability:facet-budget scope with facet_budget_exceeded. This
is a user-visible compaction marker, not a new check or a claim that omitted
facets were available.
Engine import advice
animsmith generate import-advice INPUT emits a separate one-file V1
contract with immutable identity
urn:animsmith:schema:engine-import-advice:1. Its retrievable schema is
engine-import-advice-v1.schema.json.
It is neither output-v11 nor the glTF addressability root; strict readers
reject the other shapes. The staged reader applies a 256 MiB byte cap before
UTF-8 or JSON decoding.
Every record embeds exact PredictionProvenanceV1: the frozen engine-profile
fact bundle, fully materialized settings, authoritative source format, raw
source facts, primary identity, and dependency closure from the same load.
Each available clip row binds its source-order clip index to one normalized
document index and carries the original source-name observation separately
from the normalized name. It also retains explicit project loop/movement
intent plus duration, speed, loop-endpoint, and declared-frame-grid
measurement/status pairs. Advice identity is a domain-separated canonical
digest of provenance identity, lifecycle, clip rows, and importer payload;
tool build metadata is excluded.
For Unity 6000.3 Generic and Humanoid, the payload projects only settings
already materialized by the frozen profile: Model Importer Convert Units,
Bake Axis Conversion, Generic Root Motion Source, and each clip's
lockRootRotation, lockRootHeightY, and lockRootPositionXZ. bake maps to
true; extract maps to false. Unreal 5.8 and Godot 4.7 revision 1 have no
modeled setting vocabulary, so they emit state: refused with
profile_settings_unmodeled and exit 1 rather than guessing. Incomplete raw
clip inventory, missing source-to-normalized linkage, or mismatched
measurement/settings evidence likewise refuses without exposing a prefix.
V1 does not emit frame-number ranges, sample rates, unit conversions, root-motion predictions, or project-file mutations. Source seconds multiplied by a floating-point FPS are not authoritative authored frame coordinates. Available advice exits 0, typed refusal exits 1, and configuration, profile, format, input, or serialization errors exit 2. Text and Markdown are escaped presentation views; JSON remains the contract.
Revision-2 import-setting advice
The same command also emits the immutable V2 contract
urn:animsmith:schema:engine-import-advice:2, described by
engine-import-advice-v2.schema.json.
It keeps the output-v15 tool, V4 prediction-provenance, and V4 prediction-basis
types by reference, while adding only the V2 basis and optional native
projection fields. The lifecycle is closed against the same-load dependency
closure: complete closure requires available, the exact projection, and no
refusal_reason; partial or unavailable closure requires refused,
dependency_closure_incomplete, and no projection. Unknown tuples, missing
settings, and unsupported formats are configuration errors rather than advice
documents, so they cannot be recast as a guessed projection.
Only these exact profile/input tuples can produce an available V2 projection:
| tuple | accepted input | projection |
|---|---|---|
godot / 2 / 4.7 / resource-importer-scene | glTF JSON or GLB | godot_params: animation/fps (1..120; default 30) and animation/trimming (default false) |
unreal / 2 / 5.8 / fbx-importer | FBX | unreal_fbx_import_data: explicit sample_rate as default_30, source_determined, or custom_hz(1..48000) |
The projection is bounded to those documented keys and retains each value's
explicit_config or profile_default origin. For Unreal, default_30 maps
to bUseDefaultSampleRate=true; source_determined maps to false with
CustomSampleRate=0; a custom rate maps to false with its explicit hertz.
This is a deterministic same-load parameter projection only. It does not
execute an engine importer, read back an imported asset, infer frame ranges,
units, skeleton/retargeting, compression, root motion, runtime behavior, or
write project files. The historical V1 contract and its refusal semantics
remain immutable.
Contract identities
Validation and comparison JSON commands emit output contract v19 with the
current protocol identity urn:animsmith:schema:output:19. Output-v17 retains
identity urn:animsmith:schema:output:17 and remains
retrievable historical schema evidence at
output-v17.schema.json, paired with
urn:animsmith:schema:measurements:16. Output-v16 remains
retrievable historical schema evidence at
output-v16.schema.json; its repository URL
is a retrieval location, not the protocol identity
urn:animsmith:schema:output:16. Output-v11 remains
immutable historical schema evidence, and the measurement reader retains its
validation paths for existing reports while CLI producers emit output-v19.
urn:animsmith:schema:output:15, urn:animsmith:schema:output:14,
urn:animsmith:schema:output:13, urn:animsmith:schema:output:12, and
urn:animsmith:schema:output:11 remain immutable historical contracts and are
never retargeted.
The current CLI emits output-v19. The Bevy revision-3 track-support slice continues to use V5 prediction provenance/readback with the bounded raw animation/channel inventory and gate outcomes described below. Unity Generic root-motion uses V6 prediction provenance. This does not retarget or invalidate output-v16, output-v15, or output-v9; all historical readers remain readable and their behavior remains preserved.
Those immutable prediction contracts consume measurements-v16. In output-v19,
their basis values are validated against a deterministic measurements-v18-to-v16
evidence view: fully measured loop-continuity rows omit the V17-only per-bone
status, while any unavailable bone projects to the historical unavailable outer
fact; every V18-only rotation_xyzw fact is removed from skeleton and skin
linear measurements. The output-v19 report still retains the complete
measurements-v18 rows. This projection cannot turn unavailable evidence into
measured evidence, and a projection or basis mismatch is rejected rather than
weakening the prediction.
Output-v15 retains the V3 provenance/prediction path for revision-1 profiles
and adds prediction-provenance:4, engine-profile-facts:2,
resolved-engine-settings:3, raw scene/attachment inventory V1, machine
results V1, and engine-prediction:4 for revision-2 profiles. One file cannot
mix the two revisions. Settings retain at most 4,096
canonical clip rows. A 4,097th clip records partial coverage and bounded work;
the identity commits to that state, and lint emits required-unavailable evidence
(resolved_settings_overflow) instead of treating the retained prefix as a
complete V1 inventory.
Measurement evidence is nested and independently versioned as
urn:animsmith:schema:measurements:18. Its retrievable schema is
measurements-v18.schema.json. Version
18 adds rotation_xyzw to every LinearTransformMeasurements surface exactly
when its classification is unit_orthonormal. It is the canonical XYZW
quaternion for the linear matrix's proper-orthogonal polar factor (the nearest
proper rotation): the classifier deliberately admits small scale and shear
residuals, which a quaternion does not encode. Exact rotations therefore
recompose directly; tolerance-admitted residuals retain their matrix evidence
alongside this orientation fact. Extraction uses all matrix branches near half
turns, and the largest absolute component (XYZW tie order) is always
non-negative, so equivalent q and -q rotations compare identically.
The field is absent for uniform-scaled, non-uniform, sheared, reflected,
singular, and non-finite transforms. Version 17 adds explicit availability to
every per-bone loop-continuity row. Version
16 (urn:animsmith:schema:measurements:16), preserved at
measurements-v16.schema.json,
added per-primitive measurements in source primitive order. Each primitive
records its nullable source material index, total decoded POSITION row
count, and finite row count. Indexed primitives count each stored position
once, not once per index reference. Primitive AABBs and centroids are
finite-only and omitted when the finite count is zero. Version 16 also records
leading_magic_hex only for unsupported, nonempty image payloads: at most the
first 16 bytes as lowercase hex. It is bounded evidence, not a guessed format
classification. urn:animsmith:schema:measurements:15 remains immutable
historical evidence and
has no primitive measurements or leading magic field.
Version 15 added canonical per-bone TRS channel coverage and root-trajectory evidence.
Version 14 introduced a sibling
_availability status (measured, not_applicable, or unavailable)
alongside its optional value field, so a consumer can distinguish "this clip
has no subject for the fact" from "the fact applies, but derivation failed" —
a distinction a bare optional value cannot express. Version 13 retains each
per-joint source-declaration inverse-bind matrix beside the observations
derived from it, refuses non-affine sources, and publishes a scale-free
reciprocal infinity-norm condition number before trusting an inversion.
Measurements v14 and earlier, and output v10 and earlier, remain immutable
historical contracts.
The historical output-v10 identity is urn:animsmith:schema:output:10.
Output v9 first paired measurements-v15. Output v10 retained it and added the
prediction-provenance substrate described below. Output v11 retains those
contracts and adds
role-resolution provenance: each delivered resolved bone name remains in
rig.resolved_roles, while the matching policy appears in the parallel
rig.resolved_role_policies map. Its rig.resolution_outcome is one of
resolved, coverage, ambiguous_exact_match, ambiguous_folded_match,
role_collision, or ambiguous_profile. Built-in profile fallback is only
unique ASCII case-insensitive matching; explicit [rig.roles] entries remain
exact and use explicit. Any future nested measurement revision will likewise
require a new outer identity.
convert --format json is deliberately a separate conversion-evidence
contract, not another command in the output-v11 envelope. Its immutable
identity is urn:animsmith:schema:conversion-evidence:2; its retrievable
schema is
conversion-evidence-v2.schema.json.
This lets producers pin conversion provenance independently of measurement
and lint evidence.
An asset-property refusal from convert, assemble, generate contact-fragment, or collection transform-foot-cycle is not success
evidence. Under --format json it uses the separate immutable
urn:animsmith:schema:producer-refusal:1 contract, whose retrievable schema is
producer-refusal-v1.schema.json.
This keeps conversion evidence v1/v2, assembly evidence v1-v7, output v1-v9,
and scale evidence v1-v5 immutable. The record has outcome: "rejected", a
null result, the command, and a typed {stage, kind, detail} rejection.
assemble writes a separate character-assembly-evidence v7 document to its
required --evidence path, and prints the same record to stdout under
--format json. Its immutable identity is
urn:animsmith:schema:character-assembly-evidence:7; its retrievable schema is
character-assembly-evidence-v7.schema.json.
The paired GLB and evidence are prepared before publication, so an operator
failure emits neither new destination and restores any prior pair.
scale writes scale-evidence v4 for glTF/GLB and the separate v5 FBX
rest/bind record for the narrow FBX path to its required
--evidence path, and prints the same record to stdout under
--format json. Their immutable identities are
urn:animsmith:schema:scale-evidence:4 and
urn:animsmith:schema:scale-evidence:5; their retrievable schemas are
scale-evidence-v4.schema.json and
scale-evidence-v5.schema.json.
The artifact and its evidence are prepared as temporaries and published as one
pair, so a refusal or an operator failure emits neither destination and
restores any prior pair.
Publication promotes the two temporaries with two renames, which are
individually atomic but not atomic together. Only the artifact destination is
moved aside first, so a process killed between the renames leaves the new
artifact beside the previous evidence — a complete pair whose members
disagree, which the evidence's own record of the artifact digest makes
detectable. Backing the evidence up as well would turn that same window into
a new artifact with no evidence at all. Both members are promoted from
temporary files and therefore land with mode 0600 rather than the 0644 a
plain create under the process umask would produce; this is shared by every
producer that publishes a pair.
The --format json copy on stdout is a view of the pair, never a member of
it. Both producers serialize their record exactly once and hand those same
bytes to the evidence temporary and to stdout, so the file and the stream are
identical by construction rather than by two serializers agreeing. Because the
stream is not a destination, a stdout that cannot take it — a closed pipe, a
full filesystem — changes nothing about publication and does not change the
exit code: the failure is diagnosed on stderr and the outcome stands. Read the
--evidence file, not stdout, when the record must be durable.
Conversion evidence v1 remains a historical immutable contract at
urn:animsmith:schema:conversion-evidence:1. The current CLI emits v2
exclusively; regenerate v1 evidence when a v2 consumer is required.
Output-v2,
output-v3,
output-v4,
output-v5,
output-v6,
output-v7,
output-v8,
output-v9, and
output-v10 remain historical immutable
contracts, as do output-v11 through output-v16. The current CLI emits
output-v17; diff also retains strict version-matched historical readers,
including output-v9.
Regenerate an output-v14 report with a historical producer when a
historical v14 artifact is required.
Regenerate a current output-v19 report from the original asset with
animsmith measure --format json when a current artifact is required.
Contact fragments
contact-fragment-v1.schema.json
(urn:animsmith:schema:contact-fragment:1) is the strict, portable envelope
for one selected clip's normalized contact facts. Its reader and RFC 8785
canonical serializer live in animsmith-core. animsmith generate contact-fragment is the strict V1 producer: it publishes a source-bound
sidecar only after finite bilateral stance-support preflight and a complete
dependency closure; typed refusals publish nothing. It reports sampled support,
not physical contact or gameplay meaning.
The format-neutral core also implements the separately versioned
contact-transform-result-v1.schema.json
(urn:animsmith:schema:contact-transform-result:1) reader, canonical serializer,
and exact trim/slice/resample/time-warp contact mapping. A result reader is
given the separately supplied input fragment and an external context containing
the current input/output artifacts, their complete captured closures, the
expected producer, and any handler-produced extension outputs. It validates the
closure-to-artifact bindings and independently rederives every event outcome
and successful inline fragment instead of trusting copied result rows.
Extension transformation requires exact schema/version-preserving output from
an operation-specific handler; opaque payloads are never automatically copied,
and unsupported extensions refuse the whole operation. This library contract
does not mutate an animation asset or publish a collection generation
directory.
Foot-cycle generation evidence
collection transform-foot-cycle publishes two strict success-evidence
contracts outside the ordinary output envelope:
- each
members/NNNNNN/evidence.jsonisurn:animsmith:schema:foot-cycle-member-evidence:1; aggregate-evidence.jsonisurn:animsmith:schema:foot-cycle-aggregate-evidence:1.
The aggregate is serialized once, staged as the generation member, and those
same exact bytes are written to stdout after the no-replace directory publish.
Its resource record includes itself: bounded serialization iterates the byte
count to a stable exact value before publication, then verifies that the four
component totals equal total_bytes and do not exceed 256 MiB. Member evidence
binds the exact source/output/closure/contact identities and independent proof
facts; the aggregate binds every deterministic relative path and member
evidence identity in declared order. The retrievable schemas are
foot-cycle-member-evidence-v1.schema.json
and
foot-cycle-aggregate-evidence-v1.schema.json.
A valid asset or capability refusal uses
producer-refusal-v1.schema.json
with command collection-transform-foot-cycle and exits 1. An operator failure
emits no JSON and exits 2. The transformed contact fragments themselves remain
the existing canonical contact-fragment:1 contract; the contact transform
result is proof input and is not an extra generation file.
Foot-cycle failure diagnostics
Foot-cycle planner diagnostics use
foot-cycle source preparation failed (<rule>), optionally followed by
for member <logical-id>. Asset refusals carry this text in
rejection.detail; operator failures carry it on stderr. The rule identifies
which planner check failed without exposing source paths or nested parser
errors. Logical IDs are bounded, validated collection IDs such as
com.example/walk, not source-file locators.
The planner adapter names every current core error:
| Rule | Member ID | Route when received by the planner adapter |
|---|---|---|
manifest-too-large | No | Asset refusal, exit 1 |
parameterization-too-large | No | Asset refusal, exit 1 |
too-few-members | No | Asset refusal, exit 1 |
too-many-members | No | Asset refusal, exit 1 |
invalid-slope-bounds | No | Asset refusal, exit 1 |
invalid-proof-policy | No | Asset refusal, exit 1 |
duplicate-member | Yes | Asset refusal, exit 1 |
duplicate-fragment-path | No | Asset refusal, exit 1 |
output-path-collision | No | Asset refusal, exit 1 |
missing-reference-member | No | Asset refusal, exit 1 |
manifest-mismatch | No | Asset refusal, exit 1 |
runtime-set-mismatch | No | Asset refusal, exit 1 |
wrong-runtime-set-kind | No | Asset refusal, exit 1 |
member-order-mismatch | No | Asset refusal, exit 1 |
evidence-count-mismatch | No | Asset refusal, exit 1 |
too-many-contact-events | No | Asset refusal, exit 1 |
too-many-contact-fragment-bytes | No | Asset refusal, exit 1 |
evidence-member-mismatch | No | Asset refusal, exit 1 |
non-canonical-fragment | No | Operator error, exit 2 |
fragment-clip-mismatch | No | Operator error, exit 2 |
unsupported-contact-extension | No | Asset refusal, exit 1 |
invalid-detector-provenance | No | Asset refusal, exit 1 |
detector-policy-mismatch | No | Asset refusal, exit 1 |
root-motion-binding-mismatch | Yes | Asset refusal, exit 1 |
root-motion-evidence-unavailable | Yes | Asset refusal, exit 1 |
root-motion-out-of-range | Yes | Asset refusal, exit 1 |
invalid-contact-topology | No | Asset refusal, exit 1 |
topology-mismatch | No | Asset refusal, exit 1 |
non-monotone-mapping | No | Asset refusal, exit 1 |
segment-slope-out-of-range | No | Asset refusal, exit 1 |
too-many-control-points | No | Asset refusal, exit 1 |
This is the adapter inventory, not a promise that every error can occur after
CLI admission. The bounded readers, declaration constructors, manifest-binding
preflight, and evidence assembly reject or prevent some states before the
planner runs. Their existing diagnostics, validation order, and exit routes
remain in effect. For example, an invalid declaration still reports control;
the tool does not postpone that validation to obtain a planner label.
Planner asset refusals retain stage analysis and kind
asset-recipe-mismatch. The two operator errors in the table still emit no
JSON. A future unrecognized variant from the non-exhaustive core error enum
uses the closed compatibility label unknown-planner-rule and the same
asset-refusal route; no current variant uses that fallback. The four member IDs
above are retained from the core error, and no member is inferred for other
rules. In particular, the topology and map errors currently carry no member.
Proof refusals retain stage proof, kind proof-failed, and the existing
foot-cycle proof failed (<rule>) text. ArtifactPreflight, ClipMap,
ContactBoundary, and LoopContinuity append for member <logical-id> for
the prepared member being judged, including when several members select
separate clips in one source. ArtifactPreflight covers both candidate document
shape and writer preflight. Other proof diagnostics retain their existing
text; collection-wide failures such as GaitSpread have no member attribution.
A failure still prevents publication of the generation.
Common envelope
{
"schema_version": 19,
"schema": "urn:animsmith:schema:output:19",
"tool": {
"name": "animsmith",
"version": "0.14.0",
"source": {
"revision": "0123456789abcdef0123456789abcdef01234567",
"dirty": false
}
},
"command": "measure",
"summary": { "files": 1 },
"files": []
}
tool.version is the package's plain semantic version. Source revision and
dirty state are separate fields so automation never has to parse a decorated
version string. Packaged source records its Cargo VCS revision and leaves
dirty as null; builds without trustworthy VCS metadata may leave both
fields null.
Every measure and lint file record also includes input, with the exact
primary-file byte count and lowercase SHA-256 digest of the bytes parsed for
that row. Retain this identity with the JSON evidence when a pipeline promotes
or publishes an asset: it proves which primary payload the recorded result
describes. For multi-file invocations, rows stay in argument order and each
has its own independently calculated identity.
The input identity covers only the named primary input file. For profiled
lint output, the sibling prediction provenance carries the bounded same-load
dependency closure, including identities for captured external resources when
coverage is complete. A primary-file digest alone remains insufficient closure
evidence for text glTF or another source with external dependencies.
Operator failures do not emit a JSON envelope. They exit 2, write a diagnostic
to stderr, and leave stdout empty. Content findings exit 1 at the configured
threshold. Any required_prediction_unavailable facet also exits 1 and cannot
be suppressed by severity or --allow; ordinary engine-neutral coverage gaps
remain nonblocking.
convert and assemble asset refusals exit 1. JSON mode emits exactly one
producer-refusal v1 document on stdout and leaves stderr empty; text mode
leaves stdout empty and emits one escaped stderr line carrying the same stable
kind. A consumer can therefore branch on exit plus stdout without parsing
English. Failure to serialize a truthful refusal record is an operator error;
failure to deliver already serialized bytes keeps exit 1 and is diagnosed
best-effort on stderr.
convert
convert --format json emits one conversion-evidence v2 document. It records
the input and output paths, the requested options, and counts derived from the
written artifact. It is producer evidence: consumers should use the stable
field names and schema identity rather than parsing the text write summary.
That contract is success-only. A rejected source instead emits the shared producer-refusal v1 record:
{
"schema_version": 1,
"schema": "urn:animsmith:schema:producer-refusal:1",
"tool": {
"name": "animsmith",
"version": "0.14.0",
"source": { "revision": null, "dirty": null }
},
"command": "convert",
"outcome": "rejected",
"result": null,
"rejection": {
"stage": "transform",
"kind": "transform-refused",
"detail": "cannot bake static transforms: document has no mesh instances"
}
}
{
"schema_version": 2,
"schema": "urn:animsmith:schema:conversion-evidence:2",
"tool": {
"name": "animsmith",
"version": "0.14.0",
"source": { "revision": null, "dirty": null }
},
"command": "convert",
"input": "prop.fbx",
"output": "prop.glb",
"options": {
"animation_only": false,
"bake_static_mesh_transforms": true,
"material_texture_recipe": null
},
"artifact": {
"nodes": 2,
"animations": 0,
"meshes": 1,
"primitive_positions": 3,
"materials": 1,
"clips_without_writable_tracks": 0
},
"static_mesh_bake": {
"entries": [
{
"source_node_index": 4,
"source_node_name": "prop",
"source_mesh_ordinal": 0,
"source_mesh_index": 7,
"source_mesh_name": "prop_mesh",
"output_node_index": 1,
"output_mesh_index": 0,
"world_transform": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
"linear_determinant": 1,
"primitive_count": 1,
"position_count": 3,
"normal_count": 3
}
]
}
}
static_mesh_bake is absent unless
--bake-static-mesh-transforms was requested. Its entries are in deterministic
source-node order. source_node_index, source_mesh_ordinal, and
source_mesh_index identify the source record; names are display data and
need not be unique. world_transform is the 16-element column-major rest
world matrix applied to the source positions. linear_determinant records the
accepted transform's linear determinant. The output node and mesh indices are
indices in the generated artifact.
The static bake is opt-in and conflicts with --animation-only. It only
accepts unanimated, unskinned, singly-instanced static geometry with finite,
non-reflecting, non-singular (including near-singular) transforms. It bakes
positions and inverse-transpose normalized normals into a canonical
identity-root output while retaining indices, UVs, model-supported material
assignments, and embedded base-color and normal textures. Unsupported input is
an operator error, not partial evidence. Repeated same-platform conversion
with the same input and options emits a byte-identical artifact.
When options.material_texture_recipe is a path, the top-level
material_texture_recipe object is required. When it is null, that object is
prohibited. This separates ordinary linked or embedded texture conversion from
recipe provenance. The object records the recipe identity, declared recipe path
and optional root, dimension cap, locked processor policy, and deterministic
consumed/emitted image records:
{
"schema_version": 1,
"schema": "urn:animsmith:schema:material-texture-recipe:1",
"path": "recipes/materials.toml",
"texture_root": "textures",
"max_dimension": 1024,
"processor": {
"image_crate": "image@0.25.10",
"png_crate": "png@0.18.1",
"jpeg_crate": "zune-jpeg@0.5.15",
"base_color_algorithm": "sRGB-to-linear premultiplied-alpha Lanczos3",
"normal_algorithm": "tangent-vector Triangle renormalize +Z fallback",
"data_algorithm": "linear-channel Triangle",
"output_encoding": "PNG RGBA8 compression=Best filter=NoFilter"
},
"consumed_inputs": [
{ "material_index": 0, "material_name": "surface", "slot": "base_color", "declared_path": "surface-base.png", "mime": "image/png", "dimensions": [2048, 1024] }
],
"emitted_textures": [
{ "material_index": 0, "material_name": "surface", "slot": "base_color", "declared_path": "surface-base.png", "mime": "image/png", "dimensions": [1024, 512], "resized": true, "emitted_bytes": 1234 }
]
}
Each array has the required BaseColor and normal pair plus any declared
metallic-roughness and occlusion slots for every recipe material. Records are
ordered by source-material index, then BaseColor, normal, metallic-roughness,
and occlusion; recipe declaration order does not affect them.
material_index is the source-material identity; material_name is display
data. emitted_bytes is the encoded byte count. See material texture
recipes for containment and image semantics.
assemble
assemble writes character-assembly evidence v7 beside its GLB. The evidence
binds the effective recipe and its SHA-256, every base/clip/recipe/texture input
and digest, selected source takes and windows, exact track-operation counts,
removed named-bone translation deltas, mesh and skin canonicalization, tool
identity, and the final artifact digest and counts. When prune_constant_tracks
is enabled, each clip also records the exact removed tracks in
pruned_constant_tracks, including each track's index in the completed,
normalized output clip immediately before pruning (the pre-prune authored
order). In assembly evidence v3 through v7, bone_index is the BoneId in the
post-canonicalization/pre-node-removal skeleton; removed_nodes provides the
stable compaction ledger needed to derive a surviving final index. The array
is empty when pruning is disabled or no track is removed.
transforms.removed_nodes records the final structural projection in original
pre-removal node order, with each node's name, original index, nullable
original parent index, and whether it was selected directly. It is empty when
the recipe selects no nodes.
Paths remain
operator-declared; canonical host paths used for containment checks are not
serialized.
Assembly evidence remains success-only. A valid recipe that does not fit the loaded assets exits 1 before publication and uses producer-refusal v1 in JSON mode; invalid recipe syntax/schema/values or an input/path/publication I/O failure remains exit 2 with empty stdout.
When the optional recipe-v7 rest_bind_scale operation is active, evidence
also pins the declared root name, every input's resolved root name/source-node
index and applicable source-skin index, the effective staged source selectors and factor, the exact digest
and versioned basis fingerprint for the base and every clip input, each
semantic compatibility result, its rest-bind or skinless-clip-tracks
application, and the shared scale proof over the exact staged artifact bytes.
Full rest/bind rows name
urn:animsmith:character-assembly-scale-basis:1; animation-only clip
projections name
urn:animsmith:character-assembly-skinless-clip-scale-basis:1 and omit the
inapplicable resolved skin index. A captured clip source may carry a skin or
mesh; its source_projection record remains raw-source evidence while the
track-only scale projection excludes geometry, deformation, materials, and
bind state before rebasing. For glTF/GLB, role-specific preflight retains
framing, dependency, raw-coverage, named-skeleton, and animation
accessor/layout safety. The stable skinless-clip-tracks value names the
projected operation domain, not the raw source contents. Base and full
rest/bind rows remain strict.
residual_comparison_counts uses the same twelve stable
field names as proof.residuals, keeping every measured maximum paired with
the count from the shared proof API without changing immutable scale-evidence
v4. V7 retains v6's captured input container and either its raw
glTF preservation boundary or its normalized/baked FBX capability inventory
and private staged-GLB identity. For FBX, that private stage may exclude an
unskinned mesh instance inside a declared remove_nodes closure while the
input digest and capability inventory continue to describe the captured raw
source. Omitting the block omits this operation evidence and retains ordinary
assembly behavior under the v7 envelope.
The normative recipe and evidence contracts are
character-assembly-recipe-v7.schema.json
and
character-assembly-evidence-v7.schema.json.
The recipe identity is
urn:animsmith:schema:character-assembly-recipe:7; v1 through v6 remain
immutable historical contracts.
See multi-source character assembly for operation and
consumer-boundary semantics. Migrate from v6 by selecting recipe/evidence v7
and replacing its two source indices with root_node_name;
an omitted rest_bind_scale block retains the existing behavior, while v3
continues to reject that block as unknown.
scale
The scale workflow explains when to choose each operation and how the rewrite/reload/proof/publication transaction produces this record. This section is the wire-format authority.
glTF/GLB scale writes scale evidence v4 beside its artifact. One record serves both
outcomes, discriminated by outcome:
outcome | result | rejection | Published | Exit |
|---|---|---|---|---|
"published" | the record | null | the pair, atomically | 0 |
"rejected" | null | the typed reason | nothing | 1 |
A refused run's record is printed by --format json and is never written to
the --evidence path: publication is an artifact/evidence pair, and a
refusal has no artifact. This follows lint --format json, which prints its
machine-readable result to stdout and exits 1 when the asset has a problem.
For example, a finite negative primary skin weight is rejected during planning
with rejection.kind: "negative-skin-weight"; the record has result: null
and neither destination is written.
Shared structural refusals likewise keep their specific existing kinds (such
as invalid-parent, invalid-track-shape, or invalid-mesh-instance) rather
than collapsing into a generic document-shape kind.
The record binds the operation and its declared selectors, the operator's
declared paths verbatim, the input digest and byte count, and the complete raw
capability manifest of the source. capability is null only for a refusal
raised before an inventory existed — bytes that never parsed. A published
record always carries the manifest, because publication is reachable only
through a preflight that built one. In v4 that manifest includes the sorted
JSON-pointer inventory of every static mesh/node and animated morph-weight
source accepted for preservation. Static JSON weights retain their numeric
values, while animated weight accessor payloads remain byte-exact. A published
run adds the fixed tolerance policy by identity and in full, both observed-factor witnesses with the
divergence between them and the ceiling the design expects of it, the affected
node and skin identities in the raw source index space the selectors use, the
rewritten model domains, proof coverage and results, the artifact-level
residuals, and the artifact's own digest, byte count, rewritten accessors and
rewritten JSON pointers.
Residuals are never published flat. Each is { "evaluated": bool, "max": number|null }, with max null exactly when the plan's obligations and the
source's own payloads gave that claim nothing to check. A residual reported as
0.0 for a claim nothing evaluated would be a false record rather than a
missing one, which Appendix D §D.6 forbids.
Every number in the record is guarded for finiteness before serialization.
serde_json renders NaN and both infinities as null, which in a residual
field would read as a checked-but-unmeasurable claim; the producer fails
instead, and publishes nothing.
proof.read_back_digest_matches records the producer's own third artifact
check: the staged artifact was re-read from disk and its digest compared with
the bytes that were proved. The two frontend proofs already reload the
candidate, re-run the shared core proof, and re-run the whole rewrite to
byte-compare for determinism; what they cannot see is the write path, which is
what this closes.
Paths are recorded exactly as the operator wrote them. Canonical host paths are computed only for publication-safety comparisons among input, output, evidence, and retained external dependency keys. They are deliberately not serialized, so identical arguments produce byte-identical evidence. Nothing in the record carries a timestamp.
The normative glTF/GLB contract is
scale-evidence-v4.schema.json.
The CLI emits v4 for glTF/GLB; immutable
scale-evidence-v1.schema.json,
scale-evidence-v2.schema.json, and
scale-evidence-v3.schema.json
remain available for historical records.
The narrow FBX scale rest-bind path emits v5,
scale-evidence-v5.schema.json, and
always writes a new .glb artifact. Its capability is the complete ufbx
normalized-domain inventory, not a raw FBX accessor manifest. result binds
the private staged GLB identity and then nests the v4-shaped exact GLB rewrite,
reload/proof, and read-back record for the published candidate. This is a
re-encoding proof, not a claim that FBX bytes, object properties, or authored
curve keys were preserved. Source-aware admission can therefore allow
user-defined properties and bounded external texture/video declarations when
same-load evidence proves they do not affect rest/bind state. The frozen
v5 record retains the inventory counts; the same-load loader source separately
retains raw facts and dependency-closure coverage/identities, but v5 does not
serialize those sidecars. Supported linked texture bytes are captured before
staging so the operation does not silently remove them, without claiming raw
FBX material/texture-assignment preservation. The inventory projection is
frozen with v5: a new FBX fact requires a new evidence identity rather than
silently changing this wire record. Whole-document FBX scaling remains
refused.
A whole-document factor of one has no raw write set. Its v4
result.artifact.rewritten_accessors, rewritten_json_pointers, and
reencoded_buffers arrays are empty, and
result.proof.artifact.rewritten_accessor_count is zero. This factor-one
behavior is carried forward unchanged from v3.
For rest/bind, result.domain_rewrites.scale_animation is true: every
stored scale VEC3 is rebased by its topology multiplier — 1 / s at the
selected root, 1 at strict affected descendants and unaffected nodes —
including both cubic-spline tangent elements. It is false for whole-document
conversion, which preserves dimensionless scale channels. The raw preflight
also refuses an animation channel targeting a node authored with matrix as
animated_matrix_node; no artifact or evidence file is written for that
refusal.
For an artifact-proof-failed refusal from the exact raw-JSON-preservation
walk, v2 added
rejection.artifact_proof_differences: the artifact proof's bounded,
deterministically ordered raw-JSON difference sample. items has one to 16
entries, each with a JSON-pointer location and an
artifact_added, artifact_removed, or value_changed kind. omitted
counts entries outside the prefix, so the full difference count is
items.length + omitted. The field is null when that exact-preservation
walk did not supply locations for the refusal. It does not replace
rejection.violations, which continues to describe unsupported source
capabilities.
measure and lint
Both commands put evidence under files[].measurements:
{
"schema_version": 18,
"schema": "urn:animsmith:schema:measurements:18",
"clips": {},
"mesh_definitions": [],
"node_instances": [],
"scenes": [],
"skeleton_source_coverage": "unavailable",
"skeleton_nodes": [],
"skins": [],
"material_resource_coverage": "complete",
"material_definitions": [],
"textures": [],
"images": []
}
clips maps clip names to duration, frame count, animated bones, exact local
TRS channel coverage, rotation ranges, optional per-bone loop continuity, and
optional role-dependent gait, foot-seam, root-trajectory, and speed metrics. bone_channels
is the canonical set of non-empty local channels present in the measured
document:
"bone_channels": [
{ "bone_index": 0, "bone_name": "hips",
"properties": ["translation", "rotation"] },
{ "bone_index": 3, "bone_name": "weapon_socket",
"properties": ["scale"] }
]
Rows use skeleton-index order; properties use translation, rotation, scale
order. Bone index is identity and the name is display metadata, so duplicate
bone names remain distinguishable. Duplicate tracks for one (bone, property)
pair collapse to one presence fact. This describes surviving normalized TRS
coverage in the artifact regardless of which transforms were requested; it is
not pruning evidence, track multiplicity, morph-weight coverage, or a raw
source-channel inventory. animated_bones remains the sorted unique name
projection for compatibility. Empty or structurally malformed channels do not
contribute to bone_channels, animated_bones, or
bone_rotation_range_deg; every rotation-range key is therefore also present
in animated_bones.
mesh_definitions[].primitives is a source-order array. Each row retains the
nullable source material_index, counts decoded base POSITION rows in
vertex_count, and counts finite rows separately in finite_vertex_count.
primitive_index retains the original zero-based source slot, so gaps are
valid when a loader omits unsupported source primitives; normalized documents
without source identity use contiguous retained-order fallback values.
Indexed primitives count each stored position once; index references are not
expanded or counted repeatedly. Primitive geometry_aabb and
geometry_centroid use finite positions only and are absent when there are no
finite positions. Mesh-level totals use the same finite-only geometry domain.
For unsupported nonempty images, leading_magic_hex records at most the first
16 payload bytes as lowercase hex. It is evidence of the unsupported bytes,
not an inferred or guessed image format; it is absent for supported images and
other unavailable cases.
Each optional clip fact — loop_continuity, loop_endpoint_mode,
frame_grid, loop_seam_ratio, gait (and its own gait.phase),
root_trajectory (with independent nested translation and yaw facts), and
speed_mps — carries a required sibling <field>_availability status
(gait.phase uses phase_availability, nested beside phase inside the
gait object) with one of three values:
measured: the fact was derived; the sibling value field is present.not_applicable: this clip has no subject for the fact (for example, no declared loop, no resolvable gait roles, or no root/hips travel quantity); the sibling value field is absent.unavailable: the fact applies to this clip, but it could not be derived (evidence was insufficient or a producer stopped emitting it); the sibling value field is absent.
The value field is present if and only if its status is measured; the
schema rejects a measured status without a value and a not_applicable or
unavailable status with one. This distinction matters because the two
absences require opposite handling: legitimate non-applicability should
remain acceptable, while an applicable-but-unavailable metric must not
silently pass a consumer threshold that only checks for a present value.
Field-by-field applicability:
| Field | not_applicable when | unavailable when |
|---|---|---|
loop_continuity | the skeleton has no bones | present bones exist, but the clip has fewer than three samples or the shared seam sampling grid is unusable; individual non-finite bones remain explicit unavailable rows when the shared grid is usable |
loop_endpoint_mode | the clip is not declared loop = true | the clip is declared loop = true, but neither the strict duplicate-endpoint predicate nor sampled continuity evidence can classify it |
frame_grid | the clip has no declared/configured FPS expectation | an FPS expectation is declared, but the duration or an authored key does not land on that grid |
loop_seam_ratio | the Hips role or every foot role is unresolved, or the Hips + at least one foot role resolved but no real stride was found between the seam-adjacent frames (feet did not move relative to the hips by at least the configured stride floor) — a planted/idle clip has no stride subject to normalize the seam against | the Hips + at least one foot role resolved and a real stride was found, but the ratio itself still could not be derived |
gait | the Hips role or every foot role is unresolved | the Hips + at least one foot role resolved, but the per-clip cycle sample failed |
gait.phase (nested phase_availability) | only one side (left or right) resolved a foot role, or both sides resolved but the L-R foot-height signal has exactly zero peak-to-peak swing (so it has no phase subject) | both sides resolved and the L-R foot-height swing was nonzero, but the fundamental-harmonic trough still could not be derived; current finite sampled production data has no known route to this defensive state |
root_trajectory | neither the Root nor the Hips role is resolved | Root (preferred) or the Hips fallback resolved to an index outside the measured skeleton, or its captured name no longer matches that index |
root_trajectory.translation (nested translation_availability) | never, once a trajectory bone is selected | the shared metric grid is unavailable or any sampled selected-bone position is non-finite |
root_trajectory.yaw (nested yaw_availability) | never, once a trajectory bone is selected | the shared metric grid is unavailable, sampled rotation is non-finite/zero, the fixed heading witness becomes vertical, or an adjacent sampled step is an ambiguous half turn |
speed_mps | neither the Root nor the Hips role is resolved | the Root or Hips role resolved, but root-motion speed could not be derived from the sampled grid |
root_trajectory selects the resolved Root role whenever it exists. Hips is
used only as source_role: "hips_fallback" when Root is unresolved; a bad or
unmeasurable resolved Root never causes a silent Hips fallback. A valid
selection keeps its bone_index, bone_name, and source role even when one
or both nested metric domains are unavailable. Translation and yaw are
derived independently, so bad positions do not erase usable yaw and bad
rotations do not erase usable translation.
Collection lint projects the existing per-document duration_s, root
trajectory translation availability and X/Z/travel values, plus speed_mps
availability/value, into each declared runtime-set member's root_travel
object. evidence.root_travel.members_measured counts only rows that have all
of those raw facts, while its lifecycle is complete only when that count equals
the declared member count. It retains manifest member order and does not infer
a direction, reference member, ratio, threshold, finding, or controller policy.
The translation domain is normalized right-handed, +Y-up model space in
metres: +X is right and -Z is forward. Signed X/Z and Y displacement compare
the final sample to sample zero. horizontal_travel_m sums the XZ length of
every sampled step, so horizontal out-and-back motion does not collapse to a
zero fact. Vertical min/max are sampled signed displacements from sample zero,
include the initial zero, and contain the endpoint displacement.
Yaw chooses at sample zero the positive local Z, Y, or X axis with the largest
finite horizontal projection (ties prefer Z, then Y, then X), records that
heading_axis, and holds the witness for the clip. net_yaw_deg is the
endpoint-equivalent signed result in [-180, 180]; unwrapped_yaw_deg is the
signed sum after shortest-step wrap handling; and yaw_travel_deg sums the
absolute sampled steps so reversals do not cancel. An adjacent sampled step
within 0.0001 degrees of exactly 180 degrees has no recoverable direction,
so yaw is unavailable rather than guessed. Positive yaw increases
atan2(x, z); for a +Z-aligned witness this rotates +Z toward +X, the positive
right-handed direction about normalized +Y. A multi-step endpoint-equivalent result within the same
0.0001-degree tolerance of a half turn is canonicalized to signed +180 or
-180 using the sign of the unwrapped result.
These are bounded regression facts from the same inclusive uniform
MetricGrids grid used by checks and reports. The grid has the longest
authored channel's key count and needs at least three keys and positive clip
duration. It can alias fast or irregular motion between samples, and adding an
unrelated denser track can change its resolution. Consequently travel,
vertical extrema, winding, and yaw travel are sampled observations, not proof
of continuous-curve extrema or of a transform's authored-data correctness.
Legacy speed_mps retains its existing calculation and status.
diff reports every change to a clip fact's availability status — not only
the not_applicable <-> unavailable transition that a bare optional value
would otherwise compare as unchanged (both are an absent value), but also a
measured <-> either absence transition, reported under the field's own
<field>_availability metric name (for example
"loop_seam_ratio_availability") so it never collides with that field's
ordinary value-movement delta ("loop_seam_ratio", reported "appeared" /
"disappeared" / "moved" as before). This matters most for
loop_endpoint_mode and frame_grid, which carry an enum and a small object
rather than a plain number: they have no value-movement delta of their own,
so without the status delta a measured -> unavailable transition on
either would be completely silent.
For clips declared with loop = true, loop_endpoint_mode is present when
AnimSmith can distinguish a strict mechanically removable
duplicate_endpoint, a non-duplicate unique_cycle within the effective
position/rotation closure caps, or a non_closing cycle beyond either cap.
Clips not declared as loops report loop_endpoint_mode_availability: "not_applicable"; loops without enough finite evidence report
"unavailable". When a positive declared FPS places the duration and every
authored key on its frame grid, frame_grid records that fps and the
rounded number of frame_intervals; frame_count remains the longest
authored channel's key count and is not relabeled as an authored FPS grid.
loop_continuity.bones[] is present when a clip has at least three samples and
the shared seam sampling grid is usable. Rows stay in skeleton order and carry
bone_index, bone_name, and availability; the numeric index is identity,
while the name is display context and need not be unique. measured rows carry
all four numeric fields below. unavailable rows carry none of them, so one
bone's non-finite transform evidence cannot suppress usable evidence for other
bones. not_applicable is not valid for an existing bone. Each measured row
reports:
position_delta_m: last-to-first model-space position distance (C0);rotation_delta_deg: last-to-first shortest-path model-space rotation difference (C0);seam_velocity_delta_mps: difference between the model-space linear velocities entering the last sample and leaving frame 0 (C1).seam_angular_velocity_delta_degps: shortest-path model-space angular velocity difference, in degrees per second, between those same incoming and outgoing steps (rotational C1).
The velocity comparison deliberately uses the two in-clip steps adjacent to
the wrap. The uniform grid contains both t=0 and t=duration, so treating
the duplicate last-to-first endpoint chord as a velocity would report zero on
a perfectly closed loop. Model-space values include ancestor motion; the
rotation chain is composed independently of scale rather than decomposed from
a potentially sheared matrix. These measurements need no rig profile and are
emitted for measurable clips whether or not project configuration declares
them as loops. The loop-closure, loop-seam-vel, and loop-seam-rot checks
judge them only where [clips.<name>] loop = true.
mesh_definitions contains one record per source mesh definition. Its
geometry_aabb reduces finite primitive POSITION values in the mesh's own
coordinates; it is independent of every node and scene. When finite positions
exist, optional geometry_centroid is their arithmetic mean in that same
mesh-local coordinate domain. Equivalently, it is the finite-count-weighted
mean of the available primitive centroids; primitives with zero finite rows
contribute neither weight nor position. It is not the AABB midpoint and does
not weight vertices by triangle area, volume, or skin influence. Both fields
omit non-finite positions and are absent when no finite positions remain.
Vertex and skin influence statistics are properties of that same definition.
Indexed primitives contribute each base POSITION accessor element once,
regardless of how many times the index stream references it. Unindexed
primitives contribute each base position-stream element. These are the same
counting semantics as vertex_count. In text output, the value appears as
geometry centroid (x, y, z) beside the geometry bounding-box size, or as
geometry centroid unavailable when no finite position exists. JSON carries
the optional three-number geometry_centroid array.
Each mesh definition has additional_influence_sets, an always-present array
of authored secondary glTF skin-attribute sets discovered across its
primitives. Each entry has a numeric set_index of at least 1 plus independent
joints_present and weights_present booleans. The accompanying
joints_without_weights_present and weights_without_joints_present booleans
preserve unpaired declarations on individual primitives, even when the mesh
also has the complementary side on another primitive. Entries are strictly
ascending by set_index and appear at most once. The array is empty when no
secondary set was authored.
It excludes set 0: max_joints_per_vertex and the weight-sum extrema retain
their paired primary JOINTS_0 / WEIGHTS_0 semantics and do not incorporate
additional sets.
This is source-presence evidence, not secondary-skinning evaluation. animsmith does not decode the additional per-vertex values into the core skinning model, include them in weight statistics, repair unpaired sets, or preserve their payloads when writing a converted asset. Keep the raw source when this evidence matters; a consuming pipeline decides whether a non-empty or unpaired set is acceptable.
Material and image evidence is deliberately separate from mesh definitions.
material_resource_coverage is "complete" for glTF/GLB input and
"unavailable" when the loader cannot provide the source-resource sidecar.
For glTF/GLB, complete means the loader inspected the entire documented core
source-resource domain: material, texture, and image definitions plus exactly
the base_color, normal, metallic_roughness, occlusion, and emissive
material-texture slots. It does not claim that extension-defined texture slots
or every glTF material feature are modeled.
When coverage is complete, material_definitions, textures, and images
are source-indexed records in ascending source order. A material definition
has its optional display name and zero or more bindings
{ "slot", "texture_index" }. Bindings use the five-slot order listed above;
an emissive-only material therefore has one emissive binding rather than an
empty list. A texture record has texture_index, optional name, and its
image_index. This preserves shared images and textures without duplicating
metadata per material slot.
An image record has image_index, optional name, a source_kind of
"embedded", "data_uri", or "external", and optional declared and
decoded metadata. declared_mime_type is the source's declared MIME type;
it is source-authored text, not proof of the payload. detected_container is
the byte-detected png or jpeg container. decoded_color_type is one of
l8, la8, rgb8, rgba8, l16, la16, rgb16, or rgba16; its
channel_count is respectively 1, 2, 3, or 4, while width and height are
decoded pixel dimensions. Thus MIME describes a declared media label,
container describes encoded bytes, and color type/channel count describe the
decoded pixel representation. These facts can differ without contradiction.
Available images always have decoded metadata and no unavailable_reason.
An unavailable image has a stable unavailable_reason and no decoded
dimensions, color type, or channel count; a recognizable corrupt PNG/JPEG may
still report its detected container. Reasons are source_unavailable,
invalid_data_uri, unsupported_container, decode_failed, and
resource_limit. This is inventory evidence, not an image
acceptance decision: animsmith does not repair, resize, transcode, or judge
color-space, normal-map, engine-import, or artistic suitability here.
The records describe what the loader observed, not authority for later writes
or conversion recipes. Complete coverage does not promise writer or convert
preservation of any observed binding or image payload, and a material-texture
recipe remains a separate explicit conversion input. It also makes no image
acceptance, repair, resize, transcode, color-space, or engine-import decision.
Preserve the raw source if those source details must be retained.
node_instances contains one record per mesh-bearing node. node_index and
mesh_index are stable source indices, so names need not be unique. Its
static_node_world_aabb transforms the definition's finite positions by that
node's default/rest world transform before reducing. It includes rotation and
negative or non-uniform scale, but explicitly excludes animation, skin
deformation, morph deformation, and runtime world placement. A missing box is
explained by static_node_world_aabb_unavailable_reason: no finite positions,
excluded skinned deformation, or a non-finite effective transform. The
producer must emit that reason exactly when the corresponding static box is
unavailable.
scenes contains every declared source scene. Each record counts its reachable
mesh-bearing nodes in instance_count and unions their available static
node-instance boxes in static_scene_world_aabb; excluded_instance_count
makes partial coverage explicit. A node can contribute to more than one scene.
Mesh-bearing nodes not reachable from a declared scene remain node instances
but contribute to no scene aggregate. default_scene_index is present only
when the source names a default scene; its absence does not select scene zero.
skeleton_source_coverage separates a loader that cannot expose source-node
and source-skin identity from a source that genuinely contains no nodes or
skins. When it is "unavailable", both skeleton_nodes and skins are
empty. When it is "complete", skeleton_nodes is the source node table in
ascending node_index order. node_index, parent_node_index, skin index,
and joint index are source identities; names are display data and need not be
unique. scene_root_indices lists the source scenes that directly declare the
node as a root, in ascending source-scene order. A joint can name any source
node as its parent, including a non-joint helper node.
Structurally inconsistent source identity tables, such as a missing parent or
parent cycle, downgrade the whole skeleton source domain to "unavailable"
instead of publishing a self-contradictory complete table.
Every source node records its source-projected local_rest, tagged as "trs",
"matrix", or "unavailable". For glTF this is the exact authored node
member. FBX instead reports ufbx's documented metre/Y-up,
inheritance-compensated projection, which can include adjusted transforms and
generated helper nodes; it is not the raw FBX transform stack. A TRS has
translation_parent_space_m, rotation_xyzw, and scale; the quaternion is
[x, y, z, w]. The translation is expressed in the direct parent's coordinate
frame. Even though glTF linear distances use metres, its numeric value is not
directly comparable with a mesh-local or scene-world AABB until ancestor
transforms are composed. A matrix is a 16-element column-major local
transform. Measurements v10 and earlier called the TRS field translation_m;
v11 renamed it so adapters cannot accidentally erase the coordinate domain.
rest_world_matrix is the separately derived, column-major default/rest world
matrix. rest_world_translation_m repeats indices 12, 13, and 14 of that
matrix as a directly consumable world-domain translation. rest_world_linear
describes its upper-left 3x3 matrix with X/Y/Z column lengths, determinant,
orientation sign, an optional canonical rotation_xyzw for the proper
orthogonal polar factor of a unit-classified linear matrix, an optional common
orthogonal uniform_scale, and one stable
classification: unit_orthonormal, uniform_scaled, non_uniform,
sheared, reflected, singular, or non_finite. After singularity,
reflection takes classification precedence over shear and scale shape; axis
lengths and the negative orientation retain the remaining evidence.
Measurements v12 derives its finite f64 axis,
determinant, and cross-axis facts through the shared affine facts, then applies
its tolerant measurement policy: every length is compared to the three-axis
mean using the longer operand, orthogonality is normalized by each axis pair,
and singular/reflected/sheared/unit-or-uniform/non-uniform precedence remains
measurement-specific. This reconciles the 1.0, 1.0, 1.000012 fixture
with shared uniform-affine facts and gives the same observation after an axis
permutation. v11 remains the immutable historical contract with its former
derivation. Relative 1e-5 orthogonality/equal-axis tolerances and a
scale-relative 1e-6 determinant tolerance make the result independent of a
uniform choice of source units. The derived numeric facts are calculated and
serialized with f64 precision so scale-cubed determinants do not overflow or
underflow across the finite f32 source-matrix range. A
non-finite or transitively unavailable world matrix carries its typed
unavailable reason and a non_finite linear classification without fabricated
numeric fields.
The local representation is never silently decomposed or replaced by the
world transform. A declared scene is membership only, not a transform domain:
scene_root_indices is membership evidence and adds no transform. A
transformed node selected as a scene root retains its own projected local-rest record,
and its ancestors determine the derived world matrix. These composition rules
follow the glTF node hierarchy and transform
definition.
skins is a source-skin table in ascending skin_index order. Its joints
are in declared skin-slot order; each joint row owns its joint_index,
node_index, joint_bind_to_mesh, and mesh_bind_world observations.
skeleton_root_node_index is present only when the source explicitly declares
one. The wire field inverse_bind_accessor reports whether the source
inverse-bind declaration was absent, readable, empty, count-mismatched, or
unreadable. A readable finite declaration retains its column-major matrices in
slot order. For glTF these are exact accessor values. For FBX they are matrices
projected from ufbx's converted cluster binds, not raw FBX payloads. Absent,
malformed, and non-finite inverse-bind evidence is not inferred from node-local
rest data.
Each skin's attachments names every source node that declares use of that
skin, in source-node order. In each joint row, joint_bind_to_mesh is
inverse(inverse_bind_matrix), so it maps joint bind-local coordinates into
the mesh-local bind domain declared by that skin; mesh_bind_world is
joint_rest_world * inverse_bind_matrix, mapping that mesh-local bind domain
into world bind coordinates when the projected rest and bind evidence agree. These
remain per-joint observations rather than claims that rows agree. Attachments
are identity evidence, not an extra transform folded into either calculation.
Each available derived field preserves its finite matrix and adds the same
linear facts used by rest-world nodes. Otherwise it carries a typed
unavailable reason. Both observations repeat the exact finite
source_inverse_bind_matrix for their retained declaration slot, even when a later
derivation is unavailable, so a consumer can verify the arithmetic without a
second source decoder. For glTF that matrix is the exact finite accessor
value; for FBX it is the finite matrix derived from ufbx's converted cluster
binds and is explicitly not a raw-payload preservation claim.
joint_bind_to_mesh.inversion_quality reports
1 / (norm_inf(A) * norm_inf(inverse(A))) for the source linear 3x3. This
scale-free value is zero for a singular source and approaches zero as forward
error amplification grows. Values at or below 1e-6 refuse as
inverse_bind_matrix_ill_conditioned; an exactly singular linear part uses
inverse_bind_matrix_non_invertible. A source bottom row must be affine within
absolute 1e-6 of [0, 0, 0, 1], which admits ordinary binary32 round-trip
noise while refusing projective matrices as inverse_bind_matrix_non_affine.
mesh_bind_world has no inversion quality because it multiplies rather than
inverts the retained source-declaration matrix. joint_bind_linear_summary summarizes only
joint_bind_to_mesh: it reports joint/available/unavailable counts and
distinguishes a shared consistent_uniform factor, differing mixed_uniform
factors, non_uniform_or_sheared, reflected_or_singular, mixed groups, and
partial or total unavailability. The detailed joint rows remain authoritative.
These are descriptive calculations only. animsmith does not decide whether a
consumer requires a joint, whether a skin/rest comparison is close enough,
which root is canonical, or whether an unavailable matrix is acceptable.
For example, an exporter may put a 0.01 scale on an ancestor and author a
child socket translation of 11.5. The local value remains
11.5 parent-space metres while the accumulated rest-world contribution is
approximately 0.115 m. A skin can still render at its intended size when an
inverse bind contributes the compensating factor: a joint may show effective
rest-world scale 0.01, retained inverse-bind scale 100, joint-bind-to-mesh scale
0.01, and mesh-bind-world scale 1. That compensation applies to the
skinning equation; an ordinary weapon,
effect, or collision object parented to the socket generally inherits the
socket's non-unit affine scale. The measurements expose all three domains so a
consumer can apply its own attachment policy. They do not infer authored units
from character height, mesh size, or any other plausibility heuristic. The
matrix relationship follows the glTF skinning
definition.
These facts help an artist or engine integration diagnose common handoff questions without reparsing the source: whether a joint is nested below a helper, whether a transformed scene root participates, whether two mesh nodes reuse a skin, and whether inverse-bind data is missing or singular. The node and skin terminology follows the glTF 2.0 specification. They do not select a required bone list, validate an engine's tolerance, retarget, normalize, repair, or rewrite the asset; those remain consuming project policy.
Lint adds exactly one files[].checks[] record for every built-in catalog
check. Each record keeps these dimensions independent:
selection:selectedorunselected;configuration:enabledordisabled; a check is disabled when its severity isoffor its built-in policy is opt-in and no enabling severity was configured;applicability:applicableornot_applicable;evaluation:complete,partial, ornot_evaluated;- content
findings; - completed
evaluated_scopesand typed coveragegaps.
Gap and scope code fields are the machine contract; message is display
text and must never be parsed. Disabled, unselected, and not-applicable checks
are not artificial gaps. A partial check has at least one completed scope and
at least one gap. Applicable work that completed nothing has a gap and no
content findings. A scope can appear as completed and also be named by a gap
when a group-level calculation covered some but not all members.
Built-in gap codes are:
| Gap code | Meaning | Emitted by |
|---|---|---|
roles_unresolved | Required semantic rig roles were not resolved. | loop-seam, root-motion-speed, in-place, foot-slide, gait-group, time-complement |
measurement_unavailable | A required numeric measurement could not be produced or did not meet its evidence floor. | loop-closure, duplicate-loop-endpoint, loop-seam, loop-seam-vel, loop-seam-rot, root-motion-speed, in-place, foot-slide, gait-group, sync-group, time-complement, rest-world-scale |
skeleton_unavailable | Required skeleton presence work could not run because the file has no usable skeleton. | required-bones |
node_selector_no_match | A configured source-node selector matched no named source node. | rest-world-scale |
node_selector_ambiguous | A configured source-node selector matched more than one named source node. | rest-world-scale |
insufficient_measurable_members | Fewer than two configured group members produced usable comparison evidence. | gait-group, sync-group, time-complement |
members_not_evaluated | Some configured group members did not produce usable comparison evidence. | gait-group, sync-group, time-complement |
invalid_declared_fps | A declared frame rate was zero, negative, or non-finite. | fps |
sync_frame_grid_unavailable | A same-time sync-group member lacks usable declared frame-grid evidence. | sync-group |
insufficient_rotation_evidence | Too few usable rotation tracks existed for a bind-pose comparison. | bind-pose |
Built-in completed/gap scope codes are:
| Scope code | Work unit | Emitted by |
|---|---|---|
loop_closure | One named clip's per-bone model-space pose closure was measured. | loop-closure |
duplicate_loop_endpoint | One named clip's authored tracks were analyzed for redundant closing endpoint keys. | duplicate-loop-endpoint |
member_existence | Configured group members were checked for existence. | gait-group, sync-group, time-complement |
phase_measurement | One named clip's gait phase was measured or lacked usable evidence. | gait-group, time-complement |
phase_coherence | One named group's measurable gait phases were compared. | gait-group, time-complement |
sync_member_measurement | One named same-time sync-group member's timing evidence was measured. | sync-group |
sync_compatibility | One named same-time sync group had compatible member timing evidence compared. | sync-group |
loop_seam | One named clip's positional loop seam was measured. | loop-seam |
loop_seam_velocity | One named clip's per-bone model-space seam velocity continuity was measured. | loop-seam-vel |
loop_seam_rotation | One named clip's per-bone model-space angular seam velocity continuity was measured. | loop-seam-rot |
required_bone_presence | Configured structural skeleton-bone presence requirements were evaluated. | required-bones |
selected_node_rest_scale | One configured source-node selector resolved and its effective rest-world linear scale was evaluated. | rest-world-scale |
root_motion_speed | One named clip's root-motion speed was measured. | root-motion-speed |
travel_mode | One named clip's XZ movement-owner declaration was judged. | in-place |
foot_stance | Whole-clip prerequisites for stance analysis were evaluated. | foot-slide |
left_foot_stance | The named clip's left foot/toe stance was evaluated. | foot-slide |
right_foot_stance | The named clip's right foot/toe stance was evaluated. | foot-slide |
frame_grid | The named clip's declared frame grid was evaluated. | fps |
first_frame_rest_delta | The named clip's first-frame/rest-pose rotation evidence was evaluated. | bind-pose |
animation_asset_label | One source animation index was projected to the selected engine profile's canonical asset-label selector. | engine-addressability |
animation_asset_label_inventory | Complete source-animation inventory required for asset-label prediction was unavailable. | engine-addressability |
scene_asset_label | One source scene index was projected to the selected engine profile's canonical asset-label selector. | engine-addressability |
default_scene_route | The source default-scene observation was projected to the selected engine profile's route to an existing scene asset. | engine-addressability |
skin_asset_label | One source skin index was projected to the selected engine profile's conditional canonical skin asset-label selector. | engine-addressability |
inverse_bind_matrices_asset_label | One source skin index was projected to the selected engine profile's canonical inverse-bind-matrices asset-label selector. | engine-addressability |
named_addressability_map | One selected engine profile named-addressability map and its duplicate-name policy were evaluated. | engine-addressability |
animation_target_id | One unique source animation target node's exact path and target identifier were evaluated. | engine-addressability |
gltf_addressability_inventory | Complete raw glTF scene, node, skin, attachment, and path evidence required for rich addressability prediction was unavailable. | engine-addressability |
engine_clip_boundary | One source animation clip's exact end-frame boundary was evaluated. | engine-clip-boundary |
engine_clip_boundary_inventory | Complete exact source-animation boundary inventory was unavailable. | engine-clip-boundary |
The built-in gap and scope declarations in animsmith_core are authoritative
for each code's identity, meaning, and allowed emitting check ids. Runtime
evaluation rejects a built-in code from an undeclared emitter, and the output
contract test derives this reference inventory from those same declarations.
The public code slices let consumers enumerate or allow-list animsmith's
built-in vocabulary; the meaning/emitter registry remains an implementation
detail. Custom checks may add namespaced gap codes and their own namespaced
scope vocabulary.
summary.checks reports a total and four independent partitions. Each of
selection, configuration, applicability, and evaluation sums to that
same total. summary.checks.gaps counts typed gaps, while
summary.findings counts content findings by severity.
Engine-prediction provenance and scoped facets
Every output-v19 lint file has required nullable prediction_provenance. It is
null when no exact engine profile was resolved. Revision-1 profiles carry
immutable prediction-provenance v3 (urn:animsmith:prediction-provenance:3): the typed
profile facts and sources, authoritative input format, bounded resolved
document/per-clip settings, V2 raw-source facts including optional exact
source-timing observations, the same-load dependency closure, and consumed contract
identities. The header, profile, settings coverage/work, raw evidence, closure, and primary
input identities are cross-validated; host paths and arbitrary JSON are
forbidden.
Output v10 also carries the first bounded production rule. For the exact Bevy
revision 1 / 0.19.0 / gltf-asset-loader profile on glTF or GLB,
engine-addressability predicts the canonical
GltfAssetLabel::Animation(source_clip_index) display selector. Each available
animation_asset_label facet uses the exact Animation{index} selector as its
scope subject. Names do not control the selector, and the index may change when
the source animation order changes. Complete empty source-animation inventory
is not applicable. Partial or unavailable inventory emits one subjectless
animation_asset_label_inventory required-unavailable facet and no prefix
predictions.
Output v14 introduced the bounded engine-clip-boundary rule for the exact
Unreal revision 1 / 5.8 / fbx-importer profile. Its FBX adapter supplies the
parser-resolved absolute animation-stack end coordinate and exact frame period
as generic exact source-timing evidence. The rule reports only whether that end
coordinate lies on the exact frame lattice. Missing, partial, or unavailable
evidence becomes a required-unavailable facet; it does not infer frame ranges,
resampling, root motion, or other importer behavior.
Output v15 adds engine-unit-scale for the exact Bevy revision 2 / 0.19.0 /
gltf-asset-loader profile on glTF JSON or GLB. V4 provenance binds exact
profile facts, fully materialized settings with value origins, the complete
alias-normalized runtime-node selector declaration, raw source and dependency
evidence, and a bounded same-load scene/attachment/primitive inventory.
Available facets carry typed machine results for exact unit mapping,
loader-created scene entities, primitive children, and selected source nodes.
The caller-owned WorldAssetRoot and arbitrary application world-unit policy
are outside the result. Unsupported matrix ancestry, selector miss/ambiguity,
incomplete evidence, and exhausted facet work remain typed, unsuppressible
required-unavailable states; this prediction-only rule emits no content
findings and authorizes no rescaling.
The current successor adds the narrow Bevy revision-3
engine-track-support prediction through V5 provenance and the output-v19
contract. Its same-load raw animation inventory is bounded by both animation
coverage and independent per-animation channel coverage. With a complete
inventory, the two gate settings produce only negative outcomes for dropped
animation/channel rows; a dropped row is not a content finding. The compiled
feature gate takes precedence over load_animations. When both gates allow
loading, the result is a stable required-unavailable runtime-survival facet,
because this contract does not execute Bevy or claim positive runtime
survival.
Complete-empty animation inventory is not applicable. Partial or unavailable
inventory emits exactly one subjectless, unsuppressible inventory
required_prediction_unavailable facet and no retained-prefix prediction;
bounded N+1 work is therefore distinguishable from a complete inventory.
The ordinary lifecycle remains explicit: all available work is complete, a
mix of available and unavailable facets is partial, and all required-
unavailable work is not_evaluated. V5 readback validates result state,
facet scope, inventory-row basis, and provenance identity under the same
bounds before accepting an available result; malformed or contradictory
serialized claims are rejected rather than repaired into a prediction.
Extensions, unsupported animation constructs, and other positive runtime
claims remain outside this revision-3 slice. Output-v17, output-v16, and
output-v15, V4 provenance, and revision-2 behavior remain preserved and
readable; output-v19 carries the immutable V5 provenance for the Bevy slice,
while Unity Generic
root-motion uses V6 provenance.
For the exact Unity Generic revision-2 / 6000.3 / fbx-model-importer tuple,
the engine-root-motion check is serialized in output-v19 with immutable
urn:animsmith:prediction-provenance:6 and urn:animsmith:engine-prediction:6
identities. V6 extends the same-load V5 evidence with the raw FBX transform-path
inventory and the normalized per-clip movement-owner intent. Each declared
clip/axis facet records a RootMotionRouting result: gameplay must pair with
baked_into_pose, and animation must pair with stored_as_root_motion.
Conflicts are ordinary error findings bound to the available facet's
prediction_scope.
The prediction lifecycle is independent of the ordinary measurement checks.
An all-available run is complete; mixed available and unavailable facets are
partial; all required-unavailable facets are not_evaluated. Missing or
ambiguous source-path resolution, a path that does not identify the explicitly
resolved Root role, incomplete raw-path or project-intent coverage, settings
overflow, duplicate clip names, or unavailable translation/yaw evidence yields
required_prediction_unavailable. Such a facet is not a content finding and
cannot be suppressed by --allow; it makes lint exit 1. A missing Root never
falls back to Hips for this rule. The consumer-neutral measurement layer may
still use Hips as its historical fallback, but root-motion prediction
requires the resolved Root source role.
Project-intent scans retain at most each bounded source/declaration prefix plus
one overflow witness. An ownerless unmapped tail is represented as NPlusOne
intent work and yields one atomic unavailable summary; only complete evidence
that no owner is declared preserves the check's not-applicable result. Raw path
evidence is required once declared work exists.
The numeric root trajectory is evidence, not a routing threshold. Translation
and yaw availability must be measured for the corresponding axis; zero
travel, nonzero travel, and any other finite magnitude follow the same
owner-versus-setting comparison. This check performs no Unity editor
execution, imported-asset readback, runtime playback, or engine certification.
Raw FBX paths use a closed, case-sensitive, byte-exact grammar: / is the only
separator and there is no escaping or Unicode normalization; segments are
nonempty, with no leading/trailing or doubled separators, ./.., backslashes,
controls, or Unicode format characters. Segment/path/depth limits are 1,024
UTF-8 bytes, 4,096 bytes, and 256 segments. The same input bytes are projected
through a raw-preserving ufbx load so source identities, parent chains, and
names remain auditable. The implicit ufbx root and generated geometry/scale
helpers are retained as evidence but cannot match. Only complete inventory
coverage makes zero matches a proven NoMatch; partial or unavailable
coverage is CoverageIncomplete.
Resolved engine settings and prediction provenance v3 use explicit bounded
N+1 work and coverage evidence. A 4,097th clip produces partial settings
coverage rather than an honest complete prefix; engine facets report the typed
resolved_settings_overflow reason. Current lint allocates the shared 4,096
facet budget before evaluation and emits one canonical rule-scoped
facet_budget_exceeded summary when candidates are omitted.
This is selector evidence only: it does not establish that Bevy loaded the
input, that animation loading was enabled, that an asset exists at runtime, or
that targets and graph wiring survived. Other exact profiles currently retain
nonnull provenance with no production prediction. Engine-backed checks attach
one prediction object to their existing check record. Its canonical facets
are keyed by the existing
EvaluationScope so one check can retain an available result for one clip or
selector and a required-unavailable result for another.
A facet state is exactly available or
required_prediction_unavailable. Available facets have nonempty typed basis
and no reasons. Required-unavailable facets keep any available basis prefix and
one or more stable reason codes; they are not content findings or generic
coverage gaps. Available scopes occur in evaluated_scopes; unavailable
scopes occur in neither completed scopes nor gaps. Consequently an
all-available check is complete, mixed available/unavailable work is
partial, and all-unavailable work is not_evaluated under the existing
single evaluation lifecycle.
Findings on a prediction-bearing check include prediction_scope and must bind
to exactly one available facet. Severity and --allow may filter or block a
content finding but never suppress required-unavailable prediction work.
summary.prediction_facets contains explicit available and
required_prediction_unavailable counts; any nonzero unavailable count makes
lint exit 1.
Basis rows are closed typed references to embedded profile facts, resolved
settings, project/config fields, raw-source facts, measurements-v16 scalars, or
primary sources. Measurement references use bounded canonical JSON pointers
and exact scalar values. A schema-valid measurement availability of
unavailable may make prediction work unavailable. A malformed or non-finite
present measurement remains a contract error and exit 2.
The report reader caps each serialized input at 256 MiB before UTF-8 or JSON
parsing. It validates provenance and measurement-independent prediction links,
then the complete version-matched measurements contract, then
measurement-pointer values, before diff can extract measurements. Output v10
and earlier inputs receive
the normal regeneration guidance.
lint --format json deliberately rejects --allow so machine evidence is
never deleted. --allow remains available for text and Markdown presentation
and their exit policy. Text and Markdown render coverage gaps separately from
findings and group repeated gaps by (check_id, code) for readability. Group
counts still reflect every underlying per-scope JSON gap.
Findings and numeric values
Findings carry check_id, severity, optional clip, bone, node,
time_s, measured, and expected fields, plus a human message. node is a
source-node path whose components include stable source indices; it is
distinct from the normalized skeletal bone context used by animation
checks. Treat check_id and
the structured fields as automation data; treat message as display text.
Group-level findings may also carry members, a configured-order array whose
rows contain the member name and a key-sorted map of scalar measurements.
Missing or unavailable evidence stays explicit in those rows; consumers do
not need to parse the finding message to recover the comparison table.
The nested check_id intentionally repeats its owning check record so a
finding stays self-describing when extracted or consumed through the embedded
API; the evaluator rejects mismatched parent/child ids.
For loop-closure and loop-seam-vel, expected is the effective cap for
that finding's clip after exact-name and glob expectations are resolved, with
the corresponding global check setting or built-in default as fallback.
Numeric equality in the JSON contract means equality of decoded JSON numbers,
not byte-for-byte lexical spelling. For example, 1, 1.0, and 1e0 denote
the same numeric value to a conforming adapter.
diff
diff --format json uses the current output-v19 header and emits inputs, a
delta count, and structured metric deltas:
{
"schema_version": 19,
"schema": "urn:animsmith:schema:output:19",
"tool": {
"name": "animsmith",
"version": "0.14.0",
"source": { "revision": null, "dirty": null }
},
"command": "diff",
"inputs": { "before": "old.glb", "after": "new.glb" },
"summary": { "deltas": 1 },
"deltas": [
{ "clip": "walk", "metric": "speed_mps", "before": 1.0, "after": 1.2, "note": "moved" }
]
}
diff accepts asset files, current output-v19 with measurements-v18, or
historical output-v17, output-v16, output-v15, output-v14, or output-v13
measure/lint reports with measurements-v16, and
version-matched historical output-v11/v12 reports with measurements-v15.
The output-v9 contract and its measurements-v9 pairing remain immutable
historical evidence; output-v10 and earlier reports are rejected with guidance to
regenerate them from the original asset. Multi-file reports and other
unsupported contract versions are also rejected as operator errors.
Before extracting the clip metrics it uses,
diff validates the complete measurement record, including mesh evidence, and
rejects malformed or non-finite payload values.
Loop-continuity rows compare by bone_index; availability changes are reported
at loop_continuity.bones[N].availability. Numeric fields are compared only
when present on each side. Re-export noise at or below
0.001 m for position_delta_m, 0.1 degree for rotation_delta_deg, and
0.01 m/s for seam_velocity_delta_mps is silent; the 0.5 degree/s floor
applies to seam_angular_velocity_delta_degps. Larger changes produce metric
paths such as loop_continuity.bones[12].rotation_delta_deg. These are diff
significance floors, not the lint caps configured under
[checks.loop-closure], [checks.loop-seam-vel], and [checks.loop-seam-rot].
Channel-coverage membership compares the canonical (bone_index, property)
set, so a display-name-only change is not reported as lost/gained coverage.
Every root-trajectory status, selected-bone/source identity, and heading-axis
change is exact. Each translation field uses a 0.001 m significance floor;
each yaw field uses 0.1 degree, with circular 360-degree distance only for
net_yaw_deg. Unwrapped yaw and yaw travel compare ordinarily, so 0 -> 360
remains visible even when net yaw is unchanged.
Pack evaluations
- Choose animations for your game — Compare evaluated Protofactor and Mixamo content, consider custom authoring, and choose a bounded gameplay trial.
- Animation-pack evaluation reports — Review evidence-backed marketplace-pack assessments and the reporting convention for constituent packs and collection-level cross-pack summaries.
Choose animations for your game
For a third-person action prototype with controller-driven movement, inspect Protofactor Basic first for directional walk/run/crouch. For a smaller walk/run prototype or rifle running, inspect the corresponding Mixamo set. These are starting points based on the evaluated content, not rankings of animation quality. Neither collection has an accepted complete gameplay controller.
Use the comparison below to shortlist content, then open its report for the actual clips, known problems, and setup. The collection catalog is the complete list of evaluated packs. For custom combat timing or a distinct style, compare a library plus authored changes against authoring in Cascadeur before committing to either approach.
Compare the features you need
This example brief is a humanoid action game with a kinematic controller (game code owns movement and collision), directional locomotion, melee or ranged combat, aiming, dodges, jumps, and reactions. Select the features your game needs; a focused pack is not defective for omitting others.
Candidate means content worth testing, not accepted gameplay. Named only means filenames or inventory groups suggest a use that has not been validated. Unevaluated means the reports cannot answer, not that the product lacks it.
| Game feature | Evaluated Protofactor content | Evaluated Mixamo content |
|---|---|---|
| Directional ground movement | Basic: eight-direction in-place walk, run and crouch candidates. | Basic: three-direction walk; Male and other unarmed pools: walk/run speed pairs. |
| Sprint, starts/stops and turns | Basic inventory names sprint, fast-run and U-turn candidates. A complete responsive transition set is untested. | Longbow names a forward stop; Basic includes turn candidates. Complete coverage is unverified. |
| Melee actions, dodge and recovery | Two-Handed includes dodge/parry groups; Dual Swords includes attacks/combos; Shield includes downed/recovery chains. These are hypotheses, not accepted combat sequences. | These locomotion reports do not establish a complete melee kit or dodge/recovery sequences. This does not describe the whole Mixamo catalog. |
| Ranged locomotion | Ultimate's advertised gun and bow constituents are unevaluated. | Rifle: eight-direction run; Pistol: forward/back run; Longbow: four-direction run candidates. |
| Aim/fire over moving legs | Full-body combat motions are documented; a usable upper-body aim/fire layer is unproven. | Rifle aiming filenames and armed runs do not establish usable aim/fire layers. Grip and foot contacts remain untested. |
| Jumps, hit reactions and death | Basic and combat inventories contain relevant candidates; Injured offers injury-style gait states. Interruptions, landings and return to movement are untested. | Rifle names jump/death content. Complete action sequences are unclassified or untested. |
| Traversal and interactions | Climbing and Campfire have selected sequences with loop/contact work remaining. | These reports do not establish corresponding traversal or interaction sets. |
| Combining packs and characters | Ultimate has limited named Unity source tests; whole-controller, target-character retargeting and style acceptance are untested. | Collection: no engine or cross-pool compatibility tests. Protofactor-to-Mixamo compatibility is also untested. |
Camera: these evaluations concern full-body humanoids for visible characters. Appearance through a third-person gameplay camera is untested. Dedicated first-person arms/viewmodel content and suitability are unknown; full-body motion may still be relevant to NPCs or a visible player body in a first-person game.
Scope: Ultimate advertises 24 animsets in the dated listing, with eight locally evaluated here. Mixamo's nine local pools are not a verified vendor bundle or exhaustive catalog. Follow the collection reports for exact scope.
Use a library, customize it, or author in Cascadeur
Choose the route using one representative gameplay loop on your own character:
- Library first: useful when its selected movements match the prototype. Inspect the original clips, configure movement/looping, and test one coherent set. Count retargeting, contact and transition work before expanding to more weapons or states.
- Library plus authored changes: worth trying when the base movement fits but a specific transition, grip, attack timing or style needs work. First distinguish unclassified content from a genuinely missing motion. Keep useful source clips and author only the demonstrated gaps.
- Author the motion set: worth comparing when required motion or style cannot be achieved economically from the tested library candidates, or when exact gameplay timing is central to the design. You must create and validate the directional variants, transitions, contacts and action recoveries as well as the headline walk or attack.
Cascadeur documents character animation authoring, animation import, retargeting between rigs, and export. These official pages were checked on 2026-09-23. They establish possible workflows, not a tested round trip for these packs or your character. No Cascadeur authoring, editing, export or effort trial was performed for these reports. Confirm the needed features in your edition before choosing that workflow.
Which costs less work? Unknown. There is no common-character, common-engine comparison of library integration, library customization and original authoring. AnimSmith can check declared motion and produce specific mechanical candidates; it does not choose artistic intent or author missing motion. A clean source check does not establish good gameplay motion.
What can I use now, and who must fix it?
Each report's Adoption decision describes one set and the remaining work:
- Inspect unchanged source: a diagnostic playback experiment is possible; this is not approval to ship or to include the set in the finished controller.
- Configure the game: choose movement ownership, looping, blend parameters and state transitions. This changes interpretation, not source animation.
- Try an AnimSmith candidate: a named operation improved a stated measurement. Check the resulting motion, contacts and transitions before replacing the source. An output file alone is not a successful fix.
- Repair or replace source: a confirmed defect requires the specified artist or vendor action. For example, Shield's root-motion crouch set contains a two-bone member; that blocks that set, not every Shield animation.
- Resolve missing evidence: inspect clip intent or run the named test. An untested layer, endpoint hold or phase difference is not automatically an artist defect.
The issue register records severity before changes. A demonstrated workaround may reduce the remaining impact, but only after the relevant checks pass. An unaccepted candidate leaves the outcome unresolved. Artists should read the exact affected clips, current action and acceptance condition before editing.
Do not rank libraries by warning totals or headline verdicts. The reports use different declared expectations and have different engine-test coverage. “Restricted use” and “Usable with conditions” describe scoped decisions, not comparable quality scores.
Run a small comparison before committing
Use the same character, camera, engine, controller speeds and acceptance criteria for each route. Start with idle → move/turn → one action → return to movement. If aiming while moving is essential, include it in this first experiment rather than deferring it until after buying or authoring the full set.
- Record exact clips and which motion is absent, unclassified or unsuitable. Begin with the report's linked members. A missing report entry is not proof that a vendor lacks that motion.
- Assign translation, yaw and collision to one owner per state. For a kinematic prototype, start with controller-owned travel and pose animation; test any animation-driven action as a separate mode.
- Test rapid direction/speed changes, starts/stops and interrupted actions. Check foot sliding, pose pops, ground contacts, hand/weapon grip and deformation through the gameplay camera.
- When testing a cross-pack handoff, hold gait and speed constant where the source supports it. A walk-to-armed-run example changes two variables; isolate each state's behavior first, then test the combined transition.
- Record preparation, retargeting, source edits, controller setup, authoring and retesting effort separately. Keep failed attempts and unresolved issues in the comparison. Repeat the same gameplay loop for a custom-authored alternative before concluding it saves time.
A useful result is: selected set and character; unchanged/configured/edited source; checks passed; visible problems remaining; next owner; hands-on effort. Set tolerances for your game before comparing results. For a complete intake procedure, continue to the game-developer workflow.
Read a report efficiently
Read Technical decision for the first experiment, Capability coverage for other candidate motions, and Runtime sets and authored motion for each set's adoption path. Use the Technical issue register in the selected report for blockers and repair instructions. Engine status tells you which tests actually ran.
Open Detailed evidence for exact filenames, timings, grouping assumptions, commands and provenance. Historical evaluator results remain under Changes between AnimSmith versions. This guide and the report prose were refreshed on 2026-09-23 using retained evidence; the measurement dates in each report remain the dates of those tests.
In the reports, a hypothesis is a proposed grouping to test; a declared contract is the movement, timing or loop behavior a check was asked to enforce. A passed contract is limited to those checks. The readiness ladder explains the steps from readable files to accepted gameplay.
Animation-pack evaluation reports
Latest evaluations always opens the newest reports. Older release pages keep their dated findings.
Choose animations for your controller, see what needs fixing, and find out whether that work belongs in your game, in AnimSmith, or with the artist. Each report distinguishes measured source problems from behavior that still needs testing on your character.
Choosing between Protofactor, Mixamo and custom authoring? Start with the feature comparison and first-pilot guide. It explains what the evaluated content covers and which work remains unknown.
Browse by collection
Start with a collection to compare packs, or open a pack directly for its suggested blend sets, known problems, and setup instructions.
Coverage: Protofactor Ultimate advertises 24 animsets as of the 2026-09-17 vendor listing; these reports evaluate eight locally held constituents and leave 16 unevaluated. The held revision's complete membership is unverified. The Mixamo collection groups nine locally evaluated source pools; it is not a verified current vendor bundle or complete catalog. Neither collection has established visual or gameplay acceptance.
Camera use: These are full-body humanoid animations to consider for visible characters, including third-person prototypes. Their appearance on your character and through a gameplay camera has not been tested. Dedicated first-person arms and weapon-viewmodel suitability are unknown.
What each report tells you
- Developers: which sets can be prototyped, how to blend them, who moves the character, and what must be checked before use.
- Artists: measured source-file findings, their severity, the affected clips, and what to inspect or repair. Untested behavior is not an artist defect.
- Detailed evidence: exact clip lists, measurements, tested configurations, and evaluation dates. Open this when implementing or checking a finding.
An AnimSmith trial that improves a measurement is a possible fix, not proof that the resulting animation looks right in your game.
Current reports
Use this catalog for each report’s scope, evaluation status, and evidence appendix. Its technical verdict, confidence, date, and evaluator are recorded in the report itself.
| Technical report | Evidence appendix | Scope | Evaluation status |
|---|---|---|---|
| Protofactor Basic Locomotion | Detailed evidence | Eight-direction in-place walk, run, and crouch sets. | Limited Unity source tests passed; check loop seams, foot contacts, and transitions. AnimSmith trial outputs still need in-game checks. |
| Protofactor Sword & Shield | Detailed evidence | Shield combat with separate in-place and root-motion gaits. | Limited Unity source tests passed. Root-motion crouch has a two-bone clip that needs source repair; other trial outputs still need in-game checks. |
| Protofactor Campfire | Detailed evidence | Kneel, sit, lie, and grill interaction chains. | Not tested in an engine. Set prop and posture anchors; the kneeling hold has a loop seam that pruning did not fix. |
| Protofactor Climbing | Detailed evidence | Separate wall-climbing and ladder states. | Not tested in an engine. Selected clips fail the requested loop checks; pruning did not fix WallClimbUp. Check contacts against your geometry. |
| Protofactor Injured | Detailed evidence | Seven separate injury styles with idle, walk, and run. | Not tested in an engine. Review loops and healthy/injured transitions; trial outputs still need in-game checks. |
| Protofactor 1-Handed Melee | Detailed evidence | One-handed combat with separate in-place and root-motion gaits. | Limited Unity source tests passed. Review loop seams and the 73-bone block/guard animation separately when retargeting; trial outputs still need in-game checks. |
| Protofactor 2-Handed Melee | Detailed evidence | Two-handed combat with separate in-place and root-motion gaits. | Limited Unity source tests passed. Review loop seams and the two 56-bone block/guard animations separately when retargeting; trial outputs still need in-game checks. |
| Protofactor Dual Swords | Detailed evidence | Dual-weapon combat with separate in-place and root-motion gaits. | Limited Unity source tests passed. Check loop seams, both weapon grips, and state transitions; trial outputs still need in-game checks. |
| Protofactor Ultimate Animation Collection | Detailed evidence | Comparison of the eight Protofactor packs above. | Limited Unity source tests cover named Basic/Shield-to-melee combinations. A combined controller and AnimSmith trial outputs still need in-game checks. |
| Mixamo Basic Locomotion | Detailed evidence | Three-direction unarmed walk: forward, left, and right. | No engine tests. Check loops and foot contacts; choose movement ownership per state rather than by folder name. |
| Mixamo Female Basic Locomotion | Detailed evidence | Unarmed walk/run pair for a speed blend. | No engine tests. Check timing and foot contacts; extra gameplay clips have not been grouped into usable sets. |
| Mixamo Female Locomotion | Detailed evidence | Unarmed walk/run pair; reported names and timings match Female Basic. | No engine tests. Matching measurements do not establish identical files or interchangeable use; check timing and foot contacts. |
| Mixamo Locomotion | Detailed evidence | Unarmed walk/run pair for a speed blend. | No engine tests. Check walk/run foot timing; overlap with the other unarmed packs is unverified. |
| Mixamo Longbow Locomotion | Detailed evidence | Four-direction full-body bow run. | No engine tests. Check loop duration, foot timing, and bow grip before blending. |
| Mixamo Magic Locomotion | Detailed evidence | Four-direction full-body magic run. | No engine tests. Check loop duration and foot timing; upper-body layering is untested. |
| Mixamo Male Locomotion | Detailed evidence | Unarmed walk/standard-run pair for a speed blend. | No engine tests. Check foot timing and fit on your character; the pack name does not establish retarget compatibility. |
| Mixamo Pistol-Handgun Locomotion | Detailed evidence | Forward/backward full-body pistol run. | No engine tests. The selected clips differ in foot-cycle timing; check blending and weapon grip. |
| Mixamo Rifle 8-Way Locomotion | Detailed evidence | Eight-direction full-body rifle run. | No engine tests. Check loops, contacts, and per-state movement ownership; other named actions are not yet validated as controller states. |
| Mixamo Locomotion Collection | Detailed evidence | Comparison of nine local Mixamo animation pools. | No engine or cross-pack compatibility tests. Choose a pool first; test rig, style, and transitions before combining packs. |
Source animations and generated game projects are not redistributed here. The detailed evidence explains how to repeat the checks with your own licensed assets. See the evaluation guide for how to use these findings in your project.
Animation pack evaluation: Protofactor Ultimate Animation Collection (partial: eight packs)
Technical verdict: Insufficient technical evidence
Evaluation completeness: partial — source and declared checks were rerun for eight animsets with AnimSmith 0.14.0; proposed cross-pack states and visual/gameplay behavior remain untested.
Confidence: medium
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
Detailed evidence: partial collection evidence appendix
Technical decision
Prototype decision: For a controller-driven (kinematic) pilot, start with unchanged Basic in-place walk/run sources on the target character, then add one full-body melee state. The first test is direction and speed sweeps plus the armed idle/walk handoff, checking foot slide, grip, pose pop and collision. Resolve intended loops and contacts before controller admission. Measured AnimSmith outputs are optional trials that still need those checks; source correction is required where a confirmed seam or malformed hierarchy blocks the selected set. In-place clips leave travel to the controller; root-motion clips carry authored travel.
Camera scope: Full-body humanoid animations; appearance on your target character and camera is untested. Dedicated first-person arms/viewmodel use is unverified.
As of the 2026-09-17 vendor listing, Ultimate advertises 24 animsets. Eight constituents were evaluated here; 16 were not: Combat Bare Fists, Fencing, Wizard, Bow & Arrow, 2 Handed Gun, Assault Rifle, Bazooka, Dual Guns, Minigun, Shotgun, Hostage, Zombie, Creature, Crowd, Push & Pull Cube, and Female Basic Locomotion. The older local report named “Double Guns”; its relationship to the listed “Dual Guns” is unverified. Current listing membership does not establish that the unknown local revision contained all 24.
Content in evaluated inventory: Eight evaluated constituent state families. Ground locomotion, injured styles, three melee styles, shield combat, climbing and campfire interactions have selected candidates; sixteen currently advertised constituents are unevaluated. These are identified motions; gameplay behavior has not been approved.
The official AnimSmith 0.14.0 release reran Basic Locomotion, Sword & Shield, Campfire, Climbing, Injured, 1-Handed Melee, 2-Handed Melee, and Dual Swords. They contain 918 source candidates, including 895 individual motion-labelled inputs. This is current mechanical intake evidence, not collection-level approval.
Can these packs form a kinematic animation controller? They provide useful candidate ingredients, but the evaluated combination is not an accepted controller. Use Basic Locomotion as the ground state, Injured as separate style states, melee packs as full-body armed states, and Climbing/Campfire as constrained traversal/interaction states. Shared clip naming and successful import cannot establish smooth handoffs, matching contacts, or compatible upper-body layers.
The constituent reports now name exact current scenario members and actions. They do not reconstruct a missing historical collection manifest. All 159 transform trials still need the relevant in-game checks before any output can replace its source.
Capability coverage
Content present
Eight locally evaluated constituents offer selected ground gait rings, seven injury styles, four combat families, wall/ladder traversal and campfire interaction chains. Their classifications are evaluator hypotheses tied to constituent source inventories.
Content gaps and unknowns
Sixteen animsets on the 2026-09-17 vendor listing were not locally evaluated, including Female Basic Locomotion. Their content, overlap and compatibility are unknown for this local revision. Missing evidence is not missing product content.
Evaluation still needed
Test one cross-pack state boundary at a time, then target-character blend, contact, root ownership, retarget, visual and build behavior. The partial source and Unity probes do not establish collection-wide controller acceptance.
Action-game inspection leads
These retained groupings guide source inspection, not controller acceptance. The selected gait recipes below have exact members; the action-group rows generally do not publish member-level bindings or interruption contracts.
| Need | Source lead and current status | Next check |
|---|---|---|
| Sprint and turns | Basic filenames include sprint, fast-run, U-turn and 90/180-degree turns outside the selected rings; candidate, untested | Bind takes, direction and root ownership; test stops/turns under player input |
| Dodge and parry | Two-Handed dodge-forward-back and parry-3-way; candidate groups, untested | Bind exact members, windows and contacts; check interruption and collision |
| Attacks and combos | Dual Swords combo-alternatives and single-attack-alternatives; candidate groups, untested | Bind attacks, timing, grip, hit events and cancel rules |
| Equip and recovery | Two-Handed, Dual Swords, and Sword & Shield hold draw/put-away groups; Sword & Shield also groups four death/downed/recovery chains. Candidate groups, untested | Bind source takes and test whole entry, interrupt and exit paths |
Selected gaits and candidate actions answer different questions. A declaration pass or Unity source sample permits a diagnostic trial; controller admission needs intended contracts, target-character visuals, contacts and the actual state graph. For library, hybrid and custom-authoring choices across Protofactor and Mixamo, use the shared evaluation guide. Comparable effort has not been measured.
Runtime sets and authored motion
Use each row as a separate blend or sequence proposal. The linked appendix names its exact clips, timings and measurements; controller playback and contact quality still need testing.
| Set | Controller use | Adoption decision | Exact members |
|---|---|---|---|
| Basic locomotion | Ground walk/run/crouch; controller travel | Start a controller-owned source prototype; phase-alignment output still needs in-game checks and 22/24 candidate outputs retain lint across its three rings. Set loop/contact and root policy. | Constituent exact members |
| Injured | Seven style-local idle/walk/run states | Add one style-local source state at a time; 20/21 selected files fail declared contracts. Review true loops and healthy-state handoff. | Constituent exact members |
| Sword & Shield | Full-body shield combat; in-place or root-motion gait | Choose in-place or root-motion source gait; no named set passes its declared checks. The root-motion crouch set is blocked by a two-bone source member; review one-shot policy separately. | Constituent exact members |
| 1-Handed Melee | Full-body one-hand combat; in-place or root-motion gait | Choose one source gait variant; no named set passes its declared checks. Review continuous seams and isolate the 73-bone blocked state for import/retarget. | Constituent exact members |
| 2-Handed Melee | Full-body two-hand combat; in-place or root-motion gait | Choose one source gait variant; no named set passes its declared checks. Review continuous seams and isolate the two 56-bone block states. | Constituent exact members |
| Dual Swords | Full-body dual-weapon combat; in-place or root-motion gait | Choose one source gait variant; no named set passes its declared checks. Review continuous seams, dual-grip contacts and full-body handoff. | Constituent exact members |
| Climbing | Separate wall and ladder traversal | Prototype wall and ladder separately. All ten selected members fail declared-loop contracts; pruning did not clear the WallClimbUp loop findings. Geometry/contact remains open. | Constituent exact members |
| Campfire | Posture and prop interaction chains | Prototype discrete interaction chains. Configure anchors/props/exits; kneel hold has a rotational seam that pruning did not clear. | Constituent exact members |
Integration recipe
- Members/topology: Keep ground locomotion, injury styles, armed stances, traversal and interactions as explicit states. Use direction/speed trees only within a reviewed compatible set.
- Timing/synchronization: Confirm intended loop/contact correspondence before choosing normalized-phase synchronization, then test outgoing/incoming poses for state handoffs. Do not equate identical durations with matching support feet.
- State ownership: Select in-place ground motions for a kinematic capsule. Declare any root-motion action as a separate movement mode, including collision and return of authority.
- Composition constraints: Armed/unarmed full-body handoffs are the starting hypothesis. A weapon mask needs explicit spine/root ownership, grip, stance and foot-contact checks before adoption.
- Acceptance gate: Run speed/direction sweeps, starts/stops, interruptions, attachment and geometry tests on the intended character and engine build.
Technical issue register
Severity is scoped to the intended combined controller. A missing test is an adoption risk, not evidence that the vendor authored bad motion.
| ID | Severity | Problem and impact | Primary owner | Current action | Future AnimSmith potential | Evidence/status |
|---|---|---|---|---|---|---|
| UC-PHASE | note | Evaluator-selected locomotion groups have measured phase differences; intended shared contact phase is not authoritative, so no artist defect follows from the spread. Readiness guidance. | unknown | Confirm project or vendor synchronization intent, then test the exact constituent sets against declared phase/contact tolerances. Treat per-pack loop findings separately and request artist changes only where intended playback establishes a source defect. Slicing and phase-alignment outputs still need gameplay checks. | Declared anchoring can reduce measured spread; it cannot choose or approve intended contacts. | observed-animsmith measurements; synchronization/contact acceptance not-evaluated. |
| UC-CONTROLLER | major | Arbitrary cross-pack blending has no accepted movement, transition or contact contract. Guidance: not applicable. | engine-config | Build the explicit state topology above and test exact handoffs. Residual unknown until project acceptance; no pack defect inferred. | Explicit collection contracts can record selected relationships and diagnostics. | inferred integration guidance; controller acceptance not-evaluated. |
| UC-MASK | major | Full-body weapon/interaction motions may conflict with locomotion when masked at the torso. Guidance: not applicable. | engine-config | Start with full-body state changes. Check any upper-body mask against grip, stance and planted feet before using it; compatibility remains unknown. | Mask diagnostics could expose affected channels; authored style/contact judgment remains. | Masked artistic/contact fit not-evaluated. |
| UC-SCOPE | minor | Sixteen currently advertised constituents are outside this evaluation; whole-collection coverage and value cannot be concluded. Guidance: not applicable. | unknown | Consult the explicit exclusion list and evaluate only needed missing gameplay capabilities. Residual scope limitation. | No tool can infer evidence for missing content. | not-evaluated; boundary, not vendor defect. |
Engine status
| Runtime | Evidence level | Technical result | Remaining gate |
|---|---|---|---|
| Unity 6000.5.8f1 | observed-engine | 26/26 bounded source checks: 13 samples, nine Basic/Sword & Shield-to-melee mixer schedules, four root-motion samples. Injured, Climbing and Campfire handoffs were not tested. Finite poses/fixed owner under explicit policy. | Visual/contact, actual controller, target character, compression, candidate output and build acceptance. |
| Unreal Engine | not-evaluated | No current import or playback run. | Import, retarget, graph, contact, and build tests. |
| Godot | not-evaluated | No current conversion, import, or playback run. | Conversion/import, graph, contact, and export tests. |
| Bevy | not-evaluated | No current conversion, addressability, or runtime run. | Conversion, target mapping, runtime, and performance tests. |
Fit and limitations
| Combination | Practical decision | Remaining acceptance |
|---|---|---|
| Basic + Injured | Candidate healthy/injured state handoff; keep injury letters separate | Speed/phase calibration, stance continuity and interruptions |
| Basic + Sword & Shield / 1-Handed / 2-Handed / Dual Swords | Candidate armed/unarmed full-body state change | Weapon grip, stance, turn/stop continuity; masking is a separate decision |
| Basic + Climbing | Ground-to-traversal state handoff with environment anchor | Entry/top-out, wall distance, contacts and collision ownership |
| Basic + Campfire | Stop at interaction anchor, then enter explicit posture sequence | Seat/fire placement, props, interrupt and return-to-ground behavior |
| Protofactor + Mixamo | No approved cross-library handoff | Target rig, rest pose, scale, root convention, phase, style and attachment tests |
Fresh Unity 6000.5.8f1 source tests passed 26/26 bounded checks: 13 individual Humanoid samples, nine named cross-pack mixer schedules and four root-motion samples. The nine mixers cover Basic idle/walk and Sword & Shield idle paired with 1-Handed Melee, 2-Handed Melee and Dual Swords; they do not cover Injured, Climbing or Campfire handoffs. For the nine mixers, five weights (0, 0.25, 0.5, 0.75, 1) produced finite sampled bone transforms with applyRootMotion=false and a fixed Animator owner. This supports technical feasibility of the named source combinations, not visually smooth handoffs or a finished kinematic controller.
The proposed state architecture remains inferred; this bounded feasibility evidence is not an accepted full-controller verdict. The reports are useful for selecting a prototype and assigning author/project work; they do not establish a universal game-ready controller.
Changes between AnimSmith versions
AnimSmith 0.14.0 — revalidated the eight constituent baselines/contracts and 159 bounded remediation candidates. Added exact current generic scenarios and developer/artist decisions.
AnimSmith 0.10.0 and earlier — superseded mechanical and remediation evidence. Historical collection logical-motion/set totals were not reconstructed or promoted into this run.
Evidence status
Current conclusions are constituent-derived and explicitly scoped. Canonical collection taxonomy is unavailable; selected current scenarios are linked without claiming a recovered collection binding. Transformed outputs still need gameplay and visual checks. The readiness ladder and appendix distinguish static, engine-execution and acceptance evidence.
Sources
- Constituent reports: Basic Locomotion, Sword & Shield, Campfire, Climbing, Injured, 1-Handed Melee, 2-Handed Melee, and Dual Swords.
- Protofactor, Ultimate Animation Collection and End User License Agreement.
- AnimSmith, game-ready clips and CLI reference.
- Unity 6 documentation: Blend Trees, Root Motion, and Animation Layers — engine concepts only, not evidence of pack quality.
Animation pack evidence appendix: Protofactor Ultimate Animation Collection (partial: eight packs)
Companion report: Protofactor Ultimate Animation Collection report
Evidence status: partial — current official 0.14.0 evidence is constituent-derived; new constituent scenarios guide integration; collection binding and visual/gameplay acceptance remain open.
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
This appendix preserves current constituent evidence. The canonical readiness ladder remains authoritative.
Evaluation manifest schema: urn:animsmith:skill:animation-pack-evaluation-manifest:1.
Evaluation scope and provenance
| Field | Value |
|---|---|
| Pack/edition | Protofactor Ultimate Animation Collection; partial evaluation of eight constituents |
| Vendor/source | Protofactor Ultimate Animation Collection |
| Included scope | Basic Locomotion, Sword & Shield, Campfire, Climbing, Injured, 1-Handed Melee, 2-Handed Melee, and Dual Swords |
| Excluded scope | Sixteen currently advertised constituents; no current inventory, classification, compatibility, or readiness conclusion for them |
| Delivered scope | Authorized local commercial deliveries; 918 source candidates, including 895 individual motion-labelled inputs |
| Target use | Engine-neutral, mechanically scoped collection intake |
| Target engines | Unity 6000.5.8f1 bounded source probes; other engines not evaluated |
| Source manifests | External scrubbed constituent inventories; digests and commands are recorded in the eight linked appendices |
| Evaluation manifest | Current collection binding unavailable; no current collection output was rendered or accepted |
| Acquisition/license provenance | Commercial source was authorized locally; no transaction, redistribution, or licensing conclusion is made |
Evidence coverage
| Surface | Offered/delivered | Evaluated | Findings | Not evaluated and why |
|---|---|---|---|---|
| Source candidates | 918 | 918 | constituent baseline completed | Current mechanical evidence is constituent-derived |
| Individual motion-labelled inputs | 895 | 895 | constituent declared-contract results recorded | No collection semantic classification was accepted |
| Canonical logical motions | unknown | 0 | 0 | No current collection binding or taxonomy output |
| Runtime sets | unknown collection total | selected constituent scenarios | Exact members linked above | No canonical collection binding or accepted whole-controller topology |
| AnimSmith baseline | 918 | 918 | 3,672 commands completed | Per-constituent findings are linked below |
| AnimSmith transform candidates | 159 | 159 | External outputs from all eight constituents | No promotion, engine, visual, or artistic acceptance |
| Engine import/playback | 4 runtimes | 1 current | 26/26 named cross-pack/source checks plus Basic probe | Headless samples/mixers; visual/controller/build acceptance open |
Claim legend
Current consequential claims use observed-file, observed-animsmith, documentation-stated, or not-evaluated as defined by the assessment taxonomy.
Evaluation manifest and taxonomy
Canonical clip-role inventory
| Canonical primary role | Logical motions | Delivered files | Evidence boundary |
|---|---|---|---|
idle-pose | 0 | 0 | Classification was not regenerated or accepted |
continuous-locomotion | 0 | 0 | Classification was not regenerated or accepted |
locomotion-transition | 0 | 0 | Classification was not regenerated or accepted |
airborne | 0 | 0 | Classification was not regenerated or accepted |
traversal | 0 | 0 | Classification was not regenerated or accepted |
action-interaction | 0 | 0 | Classification was not regenerated or accepted |
reaction-death | 0 | 0 | Classification was not regenerated or accepted |
emote-cinematic | 0 | 0 | Classification was not regenerated or accepted |
other-unknown | 0 | 0 | Classification was not regenerated or accepted |
| Total | 0 | 0 | 918 source candidates and 895 motion-labelled inputs remain unclassified at collection level |
Runtime-set inventory
Current rollup candidates reuse exact current constituent scenarios; they are new evaluator-selected hypotheses, not a regenerated historical collection.
| Runtime set | Type | Members/variants | Grouping evidence | Validation status |
|---|---|---|---|---|
| Walk eight directions | directional-blend | Exact members in primary report | Current constituent byte-bound generic scenario; semantic role inferred | No collection graph or gameplay acceptance |
| Injury style A speed | speed-blend | Exact members in primary report | Current constituent byte-bound generic scenario; semantic role inferred | No collection graph or gameplay acceptance |
| Kneel and sit sequence | transition-chain | Exact members in primary report | Current constituent byte-bound generic scenario; semantic role inferred | No collection graph or gameplay acceptance |
Exact runtime members
Retained selected sample: these 15 rows cover Basic walk, Injured A and Campfire kneel/sit only. They are not an exhaustive collection inventory; the other five evaluated constituents are linked in the primary report.
| Set/profile | Role or coordinate | Exact members | Variant/type | Timing or motion | Runtime contract |
|---|---|---|---|---|---|
| Walk eight directions | Candidate 1; basic-locomotion | Humanoid@WalkForwardUnarmed2.fbx::Take 001 | set_type=directional-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Walk eight directions | Candidate 2; basic-locomotion | Humanoid@WalkForwardLeftUnarmed.fbx::Take 001 | set_type=directional-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Walk eight directions | Candidate 3; basic-locomotion | Humanoid@WalkLeftUnarmed.fbx::Take 001 | set_type=directional-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Walk eight directions | Candidate 4; basic-locomotion | Humanoid@WalkBackwardsLeftUnarmed.fbx::Take 001 | set_type=directional-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Walk eight directions | Candidate 5; basic-locomotion | Humanoid@WalkBackwardsUnarmed.fbx::Take 001 | set_type=directional-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Walk eight directions | Candidate 6; basic-locomotion | Humanoid@WalkBackwardsRightUnarmed.fbx::Take 001 | set_type=directional-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Walk eight directions | Candidate 7; basic-locomotion | Humanoid@WalkRightUnarmed.fbx::Take 001 | set_type=directional-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Walk eight directions | Candidate 8; basic-locomotion | Humanoid@WalkForwardRightUnarmed.fbx::Take 001 | set_type=directional-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Injury style A speed | Candidate 1; injured | Humanoid@IdleInjuredA.fbx::Take 001 | set_type=speed-blend | duration=2.000 s | loop=unknown; movement=controller; contact=not-evaluated |
| Injury style A speed | Candidate 2; injured | Humanoid@WalkInjuredA.fbx::Take 001 | set_type=speed-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Injury style A speed | Candidate 3; injured | Humanoid@RunInjuredA.fbx::Take 001 | set_type=speed-blend | duration=0.800 s | loop=unknown; movement=controller; contact=not-evaluated |
| Kneel and sit sequence | Candidate 1; campfire | Humanoid@StandToKneelCampfire.fbx::Take 001 | set_type=transition-chain | duration=4.500 s | loop=unknown; movement=controller; contact=not-evaluated |
| Kneel and sit sequence | Candidate 2; campfire | Humanoid@IdleKneelCampfire.fbx::Take 001 | set_type=transition-chain | duration=2.167 s | loop=unknown; movement=controller; contact=not-evaluated |
| Kneel and sit sequence | Candidate 3; campfire | Humanoid@KneelToSitCampfire.fbx::Take 001 | set_type=transition-chain | duration=4.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Kneel and sit sequence | Candidate 4; campfire | Humanoid@IdleSitCampfire.fbx::Take 001 | set_type=transition-chain | duration=2.667 s | loop=unknown; movement=controller; contact=not-evaluated |
Pipeline-stage coverage
| Stage | Coverage state | Evidence / remaining gate |
|---|---|---|
| Acquire | partially-evaluated | Authorized local inventories for eight constituents only |
| Preserve raw | evaluated-clean | Commercial source was not modified |
| Inspect | evaluated-finding | Current constituent baseline completed for all source candidates |
| Segment | not-evaluated | No collection segmentation trial selected |
| Root motion | partially-evaluated | Four named RM samples and fixed-root mixer policies; actual controller/collision acceptance open |
| Conform | partially-evaluated | Bounded constituent slicing/phase candidates measured; no target-rig retarget acceptance |
| Validate | evaluated-finding | Current constituent declared contracts recorded |
| Optimize | partially-evaluated | 159 external transform candidates; no collection promotion or acceptance |
| Export | partially-evaluated | 159 external candidates written and inspected; no candidate runtime handoff acceptance |
| Gate/report | partially-evaluated | Current constituent-derived boundary recorded |
Readiness evidence by clip set
| Role or runtime set | File-ready / clip-ready | Set-ready / rig-use | Runtime / acceptance boundary |
|---|---|---|---|
| Eight-constituent source corpus | evaluated-finding | not-evaluated | not-evaluated |
| Canonical collection binding | not-evaluated | not-evaluated | not-evaluated |
| Named current constituent scenarios | Exact sources/takes measured | Topology inferred | Named source Unity subset tested; visual/gameplay acceptance open |
Validation-profile status
| Validation profile | Selection | Result / next evidence |
|---|---|---|
| Marketplace intake | selected — evaluator-selected-generic-scenario | Current source inventory and constituent baseline captured |
| Blended locomotion | selected — evaluator-selected-generic-scenario | Three named Basic/melee walk mixers sampled; phase/contact quality remains open |
| Root-motion controller | selected — evaluator-selected-generic-scenario | Four RM samples and fixed-root mixers; actual movement/collision controller still untested |
| State-machine transitions | selected — evaluator-selected-generic-scenario | Nine Playables weight schedules tested; actual AnimatorController transition/interruption policy not tested |
| Layered upper body/weapons | not-selected | Define masks and test selected clips |
| Traversal/environment | not-selected | Define environment/contact decisions |
| Contact actions/interactions | not-selected | Define prop/contact decisions |
| Retargeted/customizable characters | not-selected | Supply a target rig and retarget test |
| Motion matching/search | not-selected | Supply a database and selection policy |
| Networked movement | not-selected | Supply an authority/rollback policy |
| Runtime performance | not-selected | Supply a runtime build and budget |
Pack inventory and content evidence
| Constituent | Source candidates | Individual motion-labelled inputs | Current evidence |
|---|---|---|---|
| Basic Locomotion | 179 | 177 | baseline, declared contracts, and 39 external transform candidates |
| Sword & Shield | 136 | 132 | baseline, declared contracts, and 28 external transform candidates |
| Campfire | 29 | 25 | baseline, declared contracts, and 1 external transform candidate |
| Climbing | 77 | 75 | baseline, declared contracts, and 1 external transform candidate |
| Injured | 72 | 70 | baseline, declared contracts, and 15 external transform candidates |
| 1-Handed Melee | 113 | 110 | baseline, declared contracts, and 25 external transform candidates |
| 2-Handed Melee | 123 | 120 | baseline, declared contracts, and 25 external transform candidates |
| Dual Swords | 189 | 186 | baseline, declared contracts, and 25 external transform candidates |
| Total | 918 | 895 | Current evidence is constituent-derived |
Names and packaging are not current semantic role proof. The sixteen currently advertised unevaluated constituents are outside this inventory and were not used to infer collection capability.
Mechanical baseline
| Constituent | Current baseline or declared-contract finding | Evidence boundary |
|---|---|---|
| Basic Locomotion | 24,186 constant-track notes and 36 time-monotonic errors across 12 files; 58 pass and 119 fail per output format | observed-animsmith |
| Sword & Shield | 544 baseline commands; 34 clean and 230 non-clean declared-contract results | observed-animsmith |
| Campfire | 3,664 constant-track notes across 27 animated files; 17 pass and 8 fail per output format | observed-animsmith |
| Climbing | 9,011 constant-track notes; 34 pass and 41 fail per output format | observed-animsmith |
| Injured | 9,915 constant-track notes; 28 pass and 42 fail per output format | observed-animsmith |
| 1-Handed Melee | 452 baseline commands; 23 clean and 87 non-clean declared-contract results per output format | observed-animsmith |
| 2-Handed Melee | 492 baseline commands and one duration warning; 13 clean and 107 non-clean declared-contract results per output format | observed-animsmith |
| Dual Swords | 756 baseline commands; 24 clean and 162 non-clean declared-contract results per output format | observed-animsmith |
The aggregate baseline count is 3,672 commands. These are mechanical diagnostics, not an engine or artistic acceptance result.
AnimSmith remediation evidence
| Scope | Operation/declarations | Result | Independent verification | Remaining caveat |
|---|---|---|---|---|
| Collection | No collection transform or remediation run | No current collection output or promoted derivative | None; no collection binding was rendered | Do not infer cross-pack use from constituent candidates |
| Basic Locomotion | 12 slices, 24 gait anchors, and 3 constant-track prunes | 39/39 commands emitted candidates | Post-output inspect, measure, diff, and lint recorded externally | Candidate quality and engine use remain unaccepted |
| Campfire and Climbing | One constant-track prune each | 2/2 commands emitted candidates | Post-output inspect, measure, diff, and lint recorded externally | Candidate quality and engine use remain unaccepted |
| Injured | 14 gait anchors and one constant-track prune | 15/15 commands emitted candidates | Post-output inspect, measure, diff, and lint recorded externally | Candidate quality and engine use remain unaccepted |
| Sword & Shield | Bounded external transform declarations | 28 candidates; commands exited successfully | Constituents retain their exact command records | Candidate quality and engine use remain unaccepted |
| 1-Handed, 2-Handed, and Dual Swords | Bounded external transform declarations | 25 candidates per constituent; commands exited successfully | Constituents retain their exact command records | Candidate quality and engine use remain unaccepted |
No commercial source or derivative is published by this report.
Engine procedures and evidence
| Runtime | Procedure/evidence | Result | Boundary |
|---|---|---|---|
| Unity 6000.5.8f1 | Fresh external source-only Humanoid project; named fixed-root mixer and RM policies | 26/26 finite pose/root checks | Actual controller, visual, contacts, target-character retarget and build remain |
| Unreal Engine | No current procedure selected | not-evaluated | Import, retarget, graphs, contacts, and build remain |
| Godot | No current procedure selected | not-evaluated | Conversion/import, graphs, contacts, and export remain |
| Bevy | No current procedure selected | not-evaluated | Conversion, target mapping, runtime, and performance remain |
Rig, masking, and compatibility evidence
Fresh Unity 6000.5.8f1 source tests passed 26/26 bounded checks: 13 individual Humanoid samples, nine named cross-pack mixer schedules and four RM samples. For the nine mixers, five weights (0, 0.25, 0.5, 0.75, 1) produced finite sampled bone transforms with applyRootMotion=false and a fixed Animator owner. This supports technical feasibility of the named source combinations, not visually smooth handoffs or a finished kinematic controller.
| Source pair family | Exact imported clip members | Procedure/result |
|---|---|---|
| Basic idle → each melee idle | IdleUnarmed → IdleCombat1hMelee, IdleCombatA2HandMelee, IdleCombatDualSwords | Three fixed-root mixer schedules; finite sampled poses |
| Sword & Shield idle → each melee idle | IdleCombatS&S → IdleCombat1hMelee, IdleCombatA2HandMelee, IdleCombatDualSwords | Three fixed-root mixer schedules; finite sampled poses |
| Basic walk → each melee walk | WalkForwardUnarmed2 → WalkForwardCombat1hMelee, WalkForwardCombat2HandMelee, WalkForwardDualSwords | Three fixed-root mixer schedules; finite sampled poses |
| Explicit RM forward walks | WalkForwardUnarmed2_RM; WalkForwardCombat1hMelee_RM; WalkForwardCombat2HandMelee_RM; WalkForwardDualSwords_RM | Animation-owned-root samples produce finite displacement; not desired gameplay-speed thresholds |
No rendered-frame, contact, weapon-grip, deformation, interruption, authored AnimatorController or player-build acceptance ran. All tests use original FBXs on the shared Protof-Actor Humanoid avatar; none accepts the AnimSmith-generated GLB candidates.
External evidence: external:animsmith-0.14.0-report-refresh/unity-eight-pack-crosspack/. Probe SHA-256 28cd00897dfd739e2a7bcfa193adab03de3591a1009802946305127b67d1bfb6; result fresh-crosspack-probe.json SHA-256 f067bca91f182b65fab408a1c97b297377a8f361b03d59e809e236aba666652c; log SHA-256 0429bcb9cc7a7492c6637eb799566463ba915243d0a110b4d76cbe75590c8819. Fresh project copies only Assets/Packages/ProjectSettings; Unity builds a new import cache. Each result binds named source hashes, root policy, sample/weight schedule and pair/config digest. All 13 source FBX hashes match the fresh AnimSmith baseline identities. Every sampled pose exposes 53 Humanoid bone transforms; an empty bone set fails the probe. Invocation: Unity batch mode, no graphics, FreshCrossPackProbe.Run; exit 0.
Limitations and unknowns
- The report evaluates exactly eight constituents, not the full collection, and does not assess the sixteen currently advertised unevaluated constituents.
- Canonical collection roles/logical-motion totals and collection-wide remedial outputs remain unavailable; named current constituent scenarios are not a recovered historical collection binding.
- Current Unity source import and finite-pose/root tests are bounded to the named members. No visual, target-character retarget, contact, actual controller, compression, build or performance acceptance exists.
- Current external candidates do not establish source modification, promotion, derivative delivery, license rights, or artistic acceptance.
Changes between AnimSmith versions
AnimSmith 0.14.0 — revalidated the eight constituent baselines/contracts and 159 bounded remediation candidates. Added exact current generic scenarios and developer/artist decisions.
AnimSmith 0.10.0 and earlier — superseded mechanical and remediation evidence. Historical collection logical-motion/set totals were not reconstructed or promoted into this run.
Reproduction
Official evaluator preflight: release archive, archive SHA-256 4ecf79436f9123c779edb004050da3010b44a2b392e3031facb227d7734fc33e; exact member animsmith-v0.14.0-x86_64-unknown-linux-gnu/animsmith, binary SHA-256 c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366; tag v0.14.0, peeled commit e8321ad40be5ef6f162b31f085819c039175c3c9; working-tree state: N/A (official archive). Compiled features: fbx, report. Version and required command help plus representative FBX admission passed before evaluation. Preflight: external:animsmith-0.14.0-report-refresh/preflight.json, SHA-256 7034b5043de54e7b30064230db18605d1f96d0ea1477f7b8c56ce817cc73521a.
The official 0.14.0 evaluator and exact provenance are recorded in all eight linked constituent appendices: source commit e8321ad40be5ef6f162b31f085819c039175c3c9, binary SHA-256 c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366, archive SHA-256 4ecf79436f9123c779edb004050da3010b44a2b392e3031facb227d7734fc33e, compiled features fbx, report, output schema v19 / measurements v18. Each constituent preserves exact source/config hashes, command arguments, exits and raw outputs externally.
No fresh collection-output binding is cited. The tables aggregate constituent observations and explicit new evaluator scenarios. Do not treat them as completion of historical model migrations. Commercial sources, derivatives and engine projects remain external.
Sources
- Constituent evidence appendices: Basic Locomotion, Sword & Shield, Campfire, Climbing, Injured, 1-Handed Melee, 2-Handed Melee, and Dual Swords.
- Protofactor, Ultimate Animation Collection and End User License Agreement.
- AnimSmith, game-ready clips and CLI reference.
Animation pack evaluation: Protofactor Basic Locomotion Animset
Technical verdict: Usable with conditions
Evaluation completeness: partial — all source files and declared checks were reviewed, and a limited Unity source import/mixer ran; gameplay and visual quality remain untested.
Confidence: medium
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
Detailed evidence: Protofactor Basic Locomotion evidence
Technical decision
Prototype decision: Build separate eight-direction walk, run and crouch blends using the in-place motions, with the controller moving the character. Review loop and foot-contact timing before relying on blended travel. The phase-alignment trial still needs in-game checks.
Camera scope: Full-body humanoid animations; appearance on your target character and camera is untested. Dedicated first-person arms/viewmodel use is unverified.
This animset is one of eight locally evaluated parts of Ultimate Animation Collection. The local asset revision is unknown; the collection report separates evaluated files from the dated vendor listing.
Content in evaluated inventory: Ground movement: walk, run, crouch. Cover, turns, jumps and throws appear in evaluated filenames; starts/stops and transitions are not classified into accepted sets. These are identified motions; gameplay behavior has not been approved.
Keep cover, jumps, throws and turns outside the three gait blends; each needs its own state and movement rules. Equal duration within a direction set helps synchronization but does not prove matching foot contacts or smooth crossfades.
observed-animsmith: the official 0.14.0 evaluator inspected and measured all 179 FBXs; 58/177 declared-contract files pass and 119 fail per output format. The findings below separate source problems, project settings and untested gameplay. Transformed outputs still need gameplay and visual checks.
Capability coverage
Content present
Filename-classified candidates include three complete eight-direction in-place walk, run and crouch rings. The wider evaluated inventory also names cover, turn, jump and throw motions; these are not members of the three selected gait rings.
Other source motions worth inspecting: The measured inventory includes Humanoid@SprintForwardUnarmed.FBX, Humanoid@SprintForwardLeftUnarmed.fbx, Humanoid@SprintForwardRightUnarmed.fbx, Humanoid@RunFastForwardUnarmed.fbx, Humanoid@RunFastTurnLeftUnarmed.fbx, Humanoid@RunFastTurnRightUnarmed.fbx, Humanoid@RunUTurnLeftUnarmed.fbx, Humanoid@RunUTurnRightUnarmed.fbx, and Humanoid@Turn90LeftUnarmed.fbx / Humanoid@Turn90RightUnarmed.fbx / Humanoid@Turn180LeftUnarmed.fbx / Humanoid@Turn180RightUnarmed.fbx (plus listed root-motion variants). These are filename candidates in the retained baseline, outside the three selected gait rings. Sprint speed, planted turns, start/stop bindings and controller behavior remain unclassified and untested.
Content gaps and unknowns
Start/stop sequences and foot-contact markers are not classified into accepted runtime sets here. Their status is an evaluation gap, not a claim that the delivered pack lacks them.
Evaluation still needed
Test loop/contact correspondence, starts/stops, reversals, crouch changes and target-character playback. The bounded Unity source probe does not accept a controller, visual quality or first-person presentation.
Runtime sets and authored motion
Use each row as a separate blend or sequence proposal. The linked appendix names its exact clips, timings and measurements; controller playback and contact quality still need testing.
| Set | Controller use | Adoption decision | Exact members |
|---|---|---|---|
| Walk eight directions | Eight-direction controller gait | Prototype with controller travel. Loop/contact policy needs review; phase-alignment output still needs in-game checks (22/24 candidates retain lint across all three rings). | 8 exact members |
| Run eight directions | Eight-direction controller gait | Prototype with controller travel. Verify faster contact timing; phase-alignment output still needs in-game checks (22/24 candidates retain lint across all three rings). | 8 exact members |
| Crouch eight directions | Eight-direction controller gait | Prototype with controller travel. Configure stance changes; phase-alignment output still needs in-game checks (22/24 candidates retain lint across all three rings). | 8 exact members |
Integration recipe
- Members/topology: Create separate walk, run, and crouch two-parameter trees from the appendix members. Use local horizontal velocity for direction; handle standing/crouching and cover as states.
- Timing/synchronization: Establish the project or vendor loop/contact policy before choosing synchronized blending. The phase-alignment trial reduces measured phase spread; test the declared support-foot correspondence, loop closure, and intermediate weights before adoption.
- State ownership: The selected in-place roots have no measured travel. Let the kinematic controller own translation/collision and choose playback speed from observed stride/contact tests; zero root speed is not a usable speed threshold. Give root-motion actions an explicit separate movement policy.
- Composition constraints: Preserve authored lower-body/torso coupling initially. Weapon layers need an explicit spine mask, reference-pose convention, and planted-foot test.
- Acceptance gate: Test stops, reversals, diagonal transitions, slopes, and crouch changes on the target character. Source mixer execution does not validate exported candidates.
Technical issue register
The phase spreads below measure how far the selected gait cycles differ; they do not establish matching support feet or visual blending. The evidence appendix gives the calculation and source phases.
Severity describes impact as delivered for the stated use. Residual status is explicit; untested risks are not confirmed artist defects.
| ID | Severity | Problem and impact | Primary owner | Current action | Future AnimSmith potential | Evidence/status |
|---|---|---|---|---|---|---|
| BL-TIME | major | Twelve cover/throw files contain 36 negative-time findings; an importer can trim or interpret their pre-roll differently. Readiness guidance. | artist-author | Re-export the intended start/range or review the twelve slice trials. Slicing clears all 36 time findings; ten outputs pass their full declared checks and two still have other findings. Source files are unchanged. Check contacts and engine playback before using any output. | Explicit range slicing exists; it does not recover missing artistic intent. | Baseline affected-file list and slice verification; observed-animsmith. |
| BL-PHASE | note | Evaluator-selected walk/run/crouch rings have phase spreads 0.6598/0.4630/0.7156 cycles. Their contact correspondence and intended synchronization are unconfirmed; the numbers alone do not establish an artist defect. Readiness guidance. | unknown | Obtain project or vendor loop/contact intent, then test a declared phase/contact tolerance over the exact rings. Anchored spreads are 0.0724/0.0938/0.0502 cycles in the same order, but 22 of 24 candidates retain lint findings. Request artist cleanup only if accepted intent and playback establish a source-motion problem. Residual adoption unresolved; no output approved for gameplay use. | Current declared gait anchoring reduces measured phase disagreement; it cannot choose intended contacts or approve blending. | Exact sets and source/output phases observed-animsmith; synchronization/contact acceptance not-evaluated. |
| BL-ROOT | major | Fourteen files fail the declared in-place policy; a controller that also moves the character may double-apply displacement. Readiness guidance. | engine-config | Choose who moves the character in each state, then inspect the fourteen measured trajectories and test displacement/collision. A filename without an RM suffix does not prove a stationary root. Unresolved. | Root measurements identify the conflict; the project must choose movement ownership. | Declared contract ledger lists exact files; observed-animsmith. |
| BL-TRACK | minor | Constant tracks inflate storage; note counts alone do not establish runtime cost. Readiness guidance. | animsmith-current-declared | Three pruning outputs pass the original checks. Compare required tracks, masks and engine playback before using them; equivalence is unverified. | Current pruning is available; no measured frame-time improvement is claimed. | Three representative outputs; observed-animsmith. |
Engine status
| Runtime | Evidence level | Technical result | Remaining gate |
|---|---|---|---|
| Unity 6000.5.8f1 | observed-engine | Source inventory: 177 Humanoid clips; 6/6 sample and 3/3 representative mixer executions; the run mixer used RunForwardUnarmed.FBX, not the selected ring’s RunForward2Unarmed.fbx. A separate named cross-pack subset has finite-pose/fixed-root assertions (26 total checks across five packs). | Inventory probe is execution-only and does not validate the complete selected rings; cross-pack probe adds finite poses, not visual/contact quality. Controller, candidate-output and build acceptance remain open. |
| Unreal Engine unspecified | not-evaluated | No current import or playback run. | Import, retarget, graph, and build tests. |
| Godot unspecified | not-evaluated | No current conversion, import, or playback run. | Conversion/import, graph, and export tests. |
| Bevy unspecified | not-evaluated | No current glTF handoff or runtime run. | Conversion, addressability, runtime, and performance tests. |
Fit and limitations
Basic + Injured is a plausible state switch between healthy and injured locomotion; preserve each injury style and calibrate speed separately. Basic + melee packs is a plausible full-body armed/unarmed state change, with stance and grip continuity still untested. Basic + Climbing/Campfire needs an explicit exit from ground locomotion, environment alignment, and a return state. Fresh Unity checks support the named Basic-to-melee idle/walk combinations as technically executable with a fixed owner root; see the collection appendix. They do not approve unrestricted blending or upper-body overlays.
These are proposed integration decisions (inferred), with target-controller, contact and artistic acceptance still not-evaluated.
Changes between AnimSmith versions
AnimSmith 0.14.0 — reran 179 input baselines, 177 per-file declared contracts and 39 remediation trials using the official release. Added current exact-member generic scenarios and developer/artist actions. AnimSmith 0.10.0 — superseded historical evidence; none of its generated outputs or engine results is relabelled as a fresh run.
Evidence status
Current evidence uses the official 0.14.0 binary and the canonical readiness ladder. Commercial sources and derivatives remain external.
Sources
- Protofactor, Ultimate Animation Collection — collection-level product context.
- AnimSmith, game-ready clips and CLI reference — readiness and command boundaries.
- Unity 6 documentation: Blend Trees, Root Motion, and Animation Layers — engine concepts only, not evidence of pack quality.
Animation pack evidence appendix: Protofactor Basic Locomotion Animset
Companion report: Protofactor Basic Locomotion report
Evidence status: partial — the official evaluator loaded the delivered FBXs and produced current baseline evidence.
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
This appendix preserves current evidence only. The canonical readiness ladder remains authoritative.
Evaluation manifest schema: urn:animsmith:skill:animation-pack-evaluation-manifest:1.
Evaluation scope and provenance
| Field | Value |
|---|---|
| Pack/edition | Protofactor Basic Locomotion Animset; local constituent revision unknown |
| Vendor/source | Protofactor Ultimate Animation Collection |
| Delivered scope | Authorized local commercial delivery; 179 preserved FBXs |
| Target use | Engine-neutral third-person locomotion intake |
| Target engines | Unity 6000.5.8f1 source import/sample probe; other engines not evaluated |
| Target rigs/packs | Shared Protof-Actor character used for named current cross-pack probes; no separate project target character supplied |
| Source manifest | External scrubbed inventory, SHA-256 c6cc4d541fa2cb8e4f3e14c283d5b925f83957db35bfa079309f250cdaf101ba |
| Evaluation manifest | urn:animsmith:skill:animation-pack-evaluation-manifest:1; current binding unavailable because current output binding was not rendered for this retained report format |
| Acquisition/license provenance | Commercial source was authorized locally; no transaction or redistribution conclusion is made |
Evidence coverage
| Surface | Offered/delivered | Evaluated | Findings | Not evaluated and why |
|---|---|---|---|---|
| Animation files | 179 FBXs | 179 | baseline completed | mechanical and contract evidence captured |
| Rigs/export variants | Unknown | 0 | 0 | Not evaluated in this run |
| AnimSmith baseline | 179 | 179 | current findings recorded | inspect, JSON measure, JSON lint, and Markdown lint completed per file |
| Declared contracts | 177 motion-labelled files | 177 | 58 pass; 119 fail per output format | Mechanical contract results are current; historical taxonomy was not reconstructed; current generic scenarios are separately identified; artistic acceptance remains open |
| Offline visual reports | 0 | 0 | 0 | Not evaluated in this run |
| Engine import/playback | 4 runtimes | 1 | 177 source Humanoid clips; 6 sample/3 mixer executions | No pose assertions, visual acceptance, controller, build or candidate-output test |
| Blend/mask/retarget | Named source pairs | Six Basic/melee mixer schedules | Finite sampled poses/fixed owner | Mask, separate target-rig retarget and visual/contact acceptance remain untested |
Claim legend
Current consequential claims use observed-file, observed-animsmith, inferred, documentation-stated, or not-evaluated as defined by the assessment taxonomy.
Evaluation manifest and taxonomy
Canonical clip-role inventory
| Canonical primary role | Logical motions | Delivered files | Evidence boundary |
|---|---|---|---|
idle-pose | 0 | 0 | Classification was not regenerated or accepted |
continuous-locomotion | 0 | 0 | Classification was not regenerated or accepted |
locomotion-transition | 0 | 0 | Classification was not regenerated or accepted |
airborne | 0 | 0 | Classification was not regenerated or accepted |
traversal | 0 | 0 | Classification was not regenerated or accepted |
action-interaction | 0 | 0 | Classification was not regenerated or accepted |
reaction-death | 0 | 0 | Classification was not regenerated or accepted |
emote-cinematic | 0 | 0 | Classification was not regenerated or accepted |
other-unknown | 0 | 0 | Classification was not regenerated or accepted |
| Total | 0 | 0 | 179 delivered FBXs remain unclassified |
Runtime-set inventory
New evaluator-selected generic scenarios: current source bytes establish the exact members, take names, durations, and measured roots below. Names suggest gameplay roles; topology and semantic intent are hypotheses requiring clip review. These are not reconstructed historical manifests or measured collection-output sets. Every listed member uses Take 001; full identities are in the external selected-set ledger.
| Runtime set | Type | Members/variants | Grouping evidence | Validation status |
|---|---|---|---|---|
| Walk eight directions | directional-blend | Humanoid@WalkForwardUnarmed2.fbx, Humanoid@WalkForwardLeftUnarmed.fbx, Humanoid@WalkLeftUnarmed.fbx, Humanoid@WalkBackwardsLeftUnarmed.fbx, Humanoid@WalkBackwardsUnarmed.fbx, Humanoid@WalkBackwardsRightUnarmed.fbx, Humanoid@WalkRightUnarmed.fbx, Humanoid@WalkForwardRightUnarmed.fbx; Take 001 | New evaluator-selected scenario; observed bytes/timing, inferred gameplay roles | Mechanical measurements current; set/engine/visual acceptance open |
| Run eight directions | directional-blend | Humanoid@RunForward2Unarmed.fbx, Humanoid@RunForwardLeftUnarmed.fbx, Humanoid@RunLeftUnarmed.fbx, Humanoid@RunBackwardsLeftUnarmed.fbx, Humanoid@RunBackwardsUnarmed.fbx, Humanoid@RunBackwardsRightUnarmed.fbx, Humanoid@RunRightUnarmed.fbx, Humanoid@RunForwardRightUnarmed.fbx; Take 001 | New evaluator-selected scenario; observed bytes/timing, inferred gameplay roles | Mechanical measurements current; set/engine/visual acceptance open |
| Crouch eight directions | directional-blend | Humanoid@CrouchForwardUnarmed.fbx, Humanoid@CrouchForwardLeftUnarmed.fbx, Humanoid@CrouchLeftUnarmed.fbx, Humanoid@CrouchBackwardsLeftUnarmed.fbx, Humanoid@CrouchBackwardsUnarmed.fbx, Humanoid@CrouchBackwardsRightUnarmed.fbx, Humanoid@CrouchRightUnarmed.fbx, Humanoid@CrouchForwardRightUnarmed.fbx; Take 001 | New evaluator-selected scenario; observed bytes/timing, inferred gameplay roles | Mechanical measurements current; set/engine/visual acceptance open |
Exact runtime members
Current source members and measurements for the selected runtime scenarios. Selection is an evaluator hypothesis; source identity and measured values are retained below.
| Set/profile | Role or coordinate | Exact members | Variant/type | Timing or motion | Runtime contract |
|---|---|---|---|---|---|
| Walk eight directions | Proposed forward (0,1) | Humanoid@WalkForwardUnarmed2.fbx::Take 001 | set_type=directional-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Walk eight directions | Proposed forward-left (-1,1) | Humanoid@WalkForwardLeftUnarmed.fbx::Take 001 | set_type=directional-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Walk eight directions | Proposed left (-1,0) | Humanoid@WalkLeftUnarmed.fbx::Take 001 | set_type=directional-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Walk eight directions | Proposed back-left (-1,-1) | Humanoid@WalkBackwardsLeftUnarmed.fbx::Take 001 | set_type=directional-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Walk eight directions | Proposed back (0,-1) | Humanoid@WalkBackwardsUnarmed.fbx::Take 001 | set_type=directional-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Walk eight directions | Proposed back-right (1,-1) | Humanoid@WalkBackwardsRightUnarmed.fbx::Take 001 | set_type=directional-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Walk eight directions | Proposed right (1,0) | Humanoid@WalkRightUnarmed.fbx::Take 001 | set_type=directional-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Walk eight directions | Proposed forward-right (1,1) | Humanoid@WalkForwardRightUnarmed.fbx::Take 001 | set_type=directional-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Run eight directions | Proposed forward (0,1) | Humanoid@RunForward2Unarmed.fbx::Take 001 | set_type=directional-blend | duration=0.667 s | loop=unknown; movement=controller; contact=not-evaluated |
| Run eight directions | Proposed forward-left (-1,1) | Humanoid@RunForwardLeftUnarmed.fbx::Take 001 | set_type=directional-blend | duration=0.667 s | loop=unknown; movement=controller; contact=not-evaluated |
| Run eight directions | Proposed left (-1,0) | Humanoid@RunLeftUnarmed.fbx::Take 001 | set_type=directional-blend | duration=0.667 s | loop=unknown; movement=controller; contact=not-evaluated |
| Run eight directions | Proposed back-left (-1,-1) | Humanoid@RunBackwardsLeftUnarmed.fbx::Take 001 | set_type=directional-blend | duration=0.667 s | loop=unknown; movement=controller; contact=not-evaluated |
| Run eight directions | Proposed back (0,-1) | Humanoid@RunBackwardsUnarmed.fbx::Take 001 | set_type=directional-blend | duration=0.667 s | loop=unknown; movement=controller; contact=not-evaluated |
| Run eight directions | Proposed back-right (1,-1) | Humanoid@RunBackwardsRightUnarmed.fbx::Take 001 | set_type=directional-blend | duration=0.667 s | loop=unknown; movement=controller; contact=not-evaluated |
| Run eight directions | Proposed right (1,0) | Humanoid@RunRightUnarmed.fbx::Take 001 | set_type=directional-blend | duration=0.667 s | loop=unknown; movement=controller; contact=not-evaluated |
| Run eight directions | Proposed forward-right (1,1) | Humanoid@RunForwardRightUnarmed.fbx::Take 001 | set_type=directional-blend | duration=0.667 s | loop=unknown; movement=controller; contact=not-evaluated |
| Crouch eight directions | Proposed forward (0,1) | Humanoid@CrouchForwardUnarmed.fbx::Take 001 | set_type=directional-blend | duration=1.500 s | loop=unknown; movement=controller; contact=not-evaluated |
| Crouch eight directions | Proposed forward-left (-1,1) | Humanoid@CrouchForwardLeftUnarmed.fbx::Take 001 | set_type=directional-blend | duration=1.500 s | loop=unknown; movement=controller; contact=not-evaluated |
| Crouch eight directions | Proposed left (-1,0) | Humanoid@CrouchLeftUnarmed.fbx::Take 001 | set_type=directional-blend | duration=1.500 s | loop=unknown; movement=controller; contact=not-evaluated |
| Crouch eight directions | Proposed back-left (-1,-1) | Humanoid@CrouchBackwardsLeftUnarmed.fbx::Take 001 | set_type=directional-blend | duration=1.500 s | loop=unknown; movement=controller; contact=not-evaluated |
| Crouch eight directions | Proposed back (0,-1) | Humanoid@CrouchBackwardsUnarmed.fbx::Take 001 | set_type=directional-blend | duration=1.500 s | loop=unknown; movement=controller; contact=not-evaluated |
| Crouch eight directions | Proposed back-right (1,-1) | Humanoid@CrouchBackwardsRightUnarmed.fbx::Take 001 | set_type=directional-blend | duration=1.500 s | loop=unknown; movement=controller; contact=not-evaluated |
| Crouch eight directions | Proposed right (1,0) | Humanoid@CrouchRightUnarmed.fbx::Take 001 | set_type=directional-blend | duration=1.500 s | loop=unknown; movement=controller; contact=not-evaluated |
| Crouch eight directions | Proposed forward-right (1,1) | Humanoid@CrouchForwardRightUnarmed.fbx::Take 001 | set_type=directional-blend | duration=1.500 s | loop=unknown; movement=controller; contact=not-evaluated |
Pipeline-stage coverage
| Stage | Coverage state | Evidence / remaining gate |
|---|---|---|
| Acquire | partially-evaluated | Authorized local bytes inventory only |
| Preserve raw | evaluated-clean | Raw commercial source was not modified |
| Inspect | evaluated-finding | 179 baseline attempts completed |
| Segment | not-evaluated | No separate segmentation trial selected |
| Root motion | not-evaluated | Measurements completed; no root-motion policy or controller acceptance was evaluated |
| Conform | partially-evaluated | Twelve declared slices and 24 declared in-place gait anchors generated external candidates; no target-rig or engine acceptance was run |
| Validate | evaluated-finding | Default lint completed for every FBX |
| Optimize | partially-evaluated | Three external constant-track candidates were generated; source was unchanged and runtime equivalence remains unproved |
| Export | partially-evaluated | Candidates were written as external GLBs only; no transformed-output engine handoff was selected |
| Gate/report | partially-evaluated | Current baseline, remediation, and boundary recorded |
Readiness evidence by clip set
| Role or runtime set | File-ready / clip-ready | Set-ready / rig-use | Runtime / acceptance boundary |
|---|---|---|---|
| Entire delivered corpus | evaluated-finding | not-evaluated | not-evaluated |
| Named current generic scenarios above | Exact sources/takes measured; declared lint conditions remain | Candidate topology inferred; no set readiness approval | Target controller, contacts and artistic acceptance not evaluated |
Validation-profile status
| Validation profile | Selection | Result / next evidence |
|---|---|---|
| Marketplace intake | selected — evaluator-selected-generic-scenario | Source inventory and current baseline captured |
| Blended locomotion | selected — evaluator-selected-generic-scenario | Measurements completed; selected IP/RM ownership, blend policy, and engine acceptance remain |
| Root-motion controller | selected — evaluator-selected-generic-scenario | Measurements completed; controller trajectory policy and engine acceptance remain |
| State-machine transitions | selected — evaluator-selected-generic-scenario | Requires contract-qualified clip selection and engine test |
| Layered upper body/weapons | not-selected | No current action scope |
| Traversal/environment | not-selected | No current action scope |
| Contact actions/interactions | not-selected | No current action scope |
| Retargeted/customizable characters | not-selected | No target character supplied |
| Motion matching/search | not-selected | No motion database scope |
| Networked movement | not-selected | No controller scope |
| Runtime performance | not-selected | No runtime build |
Pack inventory and content evidence
The scrubbed inventory records 385 regular files totaling 296,205,291 bytes; 179 are FBX AnimSmith-input candidates. File names indicate locomotion, cover, airborne, turn, and action families, but names are not current semantic proof.
Mechanical baseline
| Finding/check | Affected scope | Potential impact | Evidence |
|---|---|---|---|
baseline: constant-track:note | 179 files; 24186 findings | Policy-dependent; inspect the affected take before assigning artist responsibility | observed-animsmith; exact affected files below |
baseline: time-monotonic:error | 12 files; 36 findings | Policy-dependent; inspect the affected take before assigning artist responsibility | observed-animsmith; exact affected files below |
contracts: constant-track:note | 177 files; 23922 findings | Policy-dependent; inspect the affected take before assigning artist responsibility | observed-animsmith; exact affected files below |
contracts: loop-seam-vel:error | 104 files; 104 findings | Policy-dependent; inspect the affected take before assigning artist responsibility | observed-animsmith; exact affected files below |
contracts: loop-seam-rot:error | 108 files; 108 findings | Policy-dependent; inspect the affected take before assigning artist responsibility | observed-animsmith; exact affected files below |
contracts: loop-closure:error | 58 files; 84 findings | Policy-dependent; inspect the affected take before assigning artist responsibility | observed-animsmith; exact affected files below |
contracts: in-place:error | 14 files; 14 findings | Policy-dependent; inspect the affected take before assigning artist responsibility | observed-animsmith; exact affected files below |
contracts: time-monotonic:error | 12 files; 36 findings | Policy-dependent; inspect the affected take before assigning artist responsibility | observed-animsmith; exact affected files below |
contracts: loop-seam:error | 8 files; 8 findings | Policy-dependent; inspect the affected take before assigning artist responsibility | observed-animsmith; exact affected files below |
Per-file contract results: 58 pass and 119 fail of 177; JSON and Markdown agree. Loop declarations are evaluation hypotheses, not proof that every action should repeat.
loop-seam-vel:error: Humanoid@CrouchBackwardsLeftUnarmed.fbx, Humanoid@CrouchBackwardsLeftUnarmed_RM.fbx, Humanoid@CrouchBackwardsRightUnarmed.fbx, Humanoid@CrouchBackwardsRightUnarmed_RM.fbx, Humanoid@CrouchBackwardsUnarmed.fbx, Humanoid@CrouchBackwardsUnarmed_RM.fbx, Humanoid@CrouchForwardLeftUnarmed.fbx, Humanoid@CrouchForwardLeftUnarmed_RM.fbx, Humanoid@CrouchForwardRightUnarmed.fbx, Humanoid@CrouchForwardRightUnarmed_RM.fbx, Humanoid@CrouchForwardUnarmed.fbx, Humanoid@CrouchForwardUnarmed_RM.fbx, Humanoid@CrouchIdleLookAround1Unarmed.fbx, Humanoid@CrouchIdleLookAround2Unarmed.fbx, Humanoid@CrouchLeftUnarmed.fbx, Humanoid@CrouchLeftUnarmed_RM.fbx, Humanoid@CrouchRightUnarmed.fbx, Humanoid@CrouchRightUnarmed_RM.fbx, Humanoid@CrouchThrowGrenadeUnarmed1.fbx, Humanoid@CrouchThrowGrenadeUnarmed2.fbx, Humanoid@CrouchTurn180RightUnarmed.fbx, Humanoid@CrouchTurn180RightUnarmed_RM.fbx, Humanoid@CrouchTurn90LeftUnarmed.fbx, Humanoid@CrouchTurn90LeftUnarmed_RM.fbx, Humanoid@CrouchTurn90RightUnarmed.fbx, Humanoid@CrouchTurn90RightUnarmed_RM.fbx, Humanoid@FallingUnarmed.FBX, Humanoid@Pass1MeterObstacleLeftUnarmed.FBX, Humanoid@Pass1MeterObstacleLeftUnarmed_RM.FBX, Humanoid@Pass1MeterObstacleRightUnarmed.FBX, Humanoid@Pass1MeterObstacleRightUnarmed_RM.FBX, Humanoid@RunBackwardsLeftUnarmed.fbx, Humanoid@RunBackwardsLeftUnarmed_RM.fbx, Humanoid@RunBackwardsRightUnarmed.fbx, Humanoid@RunBackwardsRightUnarmed_RM.fbx, Humanoid@RunBackwardsUnarmed.fbx, Humanoid@RunBackwardsUnarmed_RM.fbx, Humanoid@RunFastForwardUnarmed.fbx, Humanoid@RunFastForwardUnarmed_RM.fbx, Humanoid@RunFastTurnLeftUnarmed.fbx, Humanoid@RunFastTurnLeftUnarmed_RM.fbx, Humanoid@RunFastTurnRightUnarmed.fbx, Humanoid@RunFastTurnRightUnarmed_RM.fbx, Humanoid@RunForward2Unarmed.fbx, Humanoid@RunForward2Unarmed_RM.fbx, Humanoid@RunForwardLeftUnarmed.fbx, Humanoid@RunForwardLeftUnarmed_RM.fbx, Humanoid@RunForwardRightUnarmed.fbx, Humanoid@RunForwardRightUnarmed_RM.fbx, Humanoid@RunForwardUnarmed.FBX, Humanoid@RunForwardUnarmed_RM.FBX, Humanoid@RunUTurnLeftUnarmed.fbx, Humanoid@RunUTurnLeftUnarmed_RM.fbx, Humanoid@RunUTurnRightUnarmed.fbx, Humanoid@RunUTurnRightUnarmed_RM.fbx, Humanoid@SprintForwardLeftUnarmed.fbx, Humanoid@SprintForwardLeftUnarmed_RM.fbx, Humanoid@SprintForwardRightUnarmed.fbx, Humanoid@SprintForwardRightUnarmed_RM.fbx, Humanoid@SprintForwardUnarmed.FBX, Humanoid@SprintForwardUnarmed_RM.FBX, Humanoid@StrafeLeftTakeCoverCrouchingUnarmed.fbx, Humanoid@StrafeLeftTakeCoverCrouchingUnarmed_RM.fbx, Humanoid@StrafeLeftTakeCoverStandingUnarmed.fbx, Humanoid@StrafeLeftTakeCoverStandingUnarmed_RM.fbx, Humanoid@StrafeRightTakeCoverCrouchingUnarmed.fbx, Humanoid@StrafeRightTakeCoverCrouchingUnarmed_RM.fbx, Humanoid@StrafeRightTakeCoverStandingUnarmed.fbx, Humanoid@StrafeRightTakeCoverStandingUnarmed_RM.fbx, Humanoid@ThrowGrenadeLeftUnderCoverCrouching.fbx, Humanoid@ThrowGrenadeLeftUnderCoverStanding.fbx, Humanoid@ThrowGrenadeRightUnderCoverCrouching.fbx, Humanoid@ThrowGrenadeRightUnderCoverStanding.fbx, Humanoid@ThrowGrenadeUnarmed1.fbx, Humanoid@ThrowGrenadeUnarmed2.fbx, Humanoid@Turn180LeftUnarmed.fbx, Humanoid@Turn180LeftUnarmed_RM.fbx, Humanoid@Turn180RightUnarmed.fbx, Humanoid@Turn180RightUnarmed_RM.fbx, Humanoid@Turn90LeftUnarmed.fbx, Humanoid@Turn90LeftUnarmed_RM.fbx, Humanoid@Turn90RightUnarmed.fbx, Humanoid@Turn90RightUnarmed_RM.fbx, Humanoid@WalkBackwardsLeftUnarmed.fbx, Humanoid@WalkBackwardsLeftUnarmed_RM.fbx, Humanoid@WalkBackwardsRightUnarmed.fbx, Humanoid@WalkBackwardsRightUnarmed_RM.fbx, Humanoid@WalkBackwardsUnarmed.fbx, Humanoid@WalkBackwardsUnarmed_RM.fbx, Humanoid@WalkForwardLeftUnarmed.fbx, Humanoid@WalkForwardLeftUnarmed_RM.fbx, Humanoid@WalkForwardRightUnarmed.fbx, Humanoid@WalkForwardRightUnarmed_RM.fbx, Humanoid@WalkForwardUnarmed.FBX, Humanoid@WalkForwardUnarmed2.fbx, Humanoid@WalkForwardUnarmed2_RM.fbx, Humanoid@WalkForwardUnarmed_RM.FBX, Humanoid@WalkLeftUnarmed.fbx, Humanoid@WalkLeftUnarmed_RM.fbx, Humanoid@WalkRightUnarmed.fbx, Humanoid@WalkRightUnarmed_RM.fbx, Humanoid@WalkUTurnLeftUnarmed_RM.fbx, Humanoid@WalkUTurnRightUnarmed.fbx, Humanoid@WalkUTurnRightUnarmed_RM.fbx.
loop-seam-rot:error: Humanoid@CrouchBackwardsLeftUnarmed.fbx, Humanoid@CrouchBackwardsLeftUnarmed_RM.fbx, Humanoid@CrouchBackwardsRightUnarmed.fbx, Humanoid@CrouchBackwardsRightUnarmed_RM.fbx, Humanoid@CrouchBackwardsUnarmed.fbx, Humanoid@CrouchBackwardsUnarmed_RM.fbx, Humanoid@CrouchForwardLeftUnarmed.fbx, Humanoid@CrouchForwardLeftUnarmed_RM.fbx, Humanoid@CrouchForwardRightUnarmed.fbx, Humanoid@CrouchForwardRightUnarmed_RM.fbx, Humanoid@CrouchForwardUnarmed.fbx, Humanoid@CrouchForwardUnarmed_RM.fbx, Humanoid@CrouchIdleBreathe1Unarmed.fbx, Humanoid@CrouchIdleBreathe2Unarmed.fbx, Humanoid@CrouchIdleLookAround1Unarmed.fbx, Humanoid@CrouchIdleLookAround2Unarmed.fbx, Humanoid@CrouchLeftUnarmed.fbx, Humanoid@CrouchLeftUnarmed_RM.fbx, Humanoid@CrouchRightUnarmed.fbx, Humanoid@CrouchRightUnarmed_RM.fbx, Humanoid@CrouchThrowGrenadeUnarmed1.fbx, Humanoid@CrouchThrowGrenadeUnarmed2.fbx, Humanoid@CrouchTurn180RightUnarmed.fbx, Humanoid@CrouchTurn180RightUnarmed_RM.fbx, Humanoid@CrouchTurn90LeftUnarmed.fbx, Humanoid@CrouchTurn90LeftUnarmed_RM.fbx, Humanoid@CrouchTurn90RightUnarmed.fbx, Humanoid@CrouchTurn90RightUnarmed_RM.fbx, Humanoid@FallingUnarmed.FBX, Humanoid@IdleLookAroundScratchYawnUnarmed.fbx, Humanoid@IdleLookAroundUnarmed.FBX, Humanoid@Pass1MeterObstacleLeftUnarmed.FBX, Humanoid@Pass1MeterObstacleLeftUnarmed_RM.FBX, Humanoid@Pass1MeterObstacleRightUnarmed.FBX, Humanoid@Pass1MeterObstacleRightUnarmed_RM.FBX, Humanoid@RunBackwardsLeftUnarmed.fbx, Humanoid@RunBackwardsLeftUnarmed_RM.fbx, Humanoid@RunBackwardsRightUnarmed.fbx, Humanoid@RunBackwardsRightUnarmed_RM.fbx, Humanoid@RunBackwardsUnarmed.fbx, Humanoid@RunBackwardsUnarmed_RM.fbx, Humanoid@RunFastForwardUnarmed.fbx, Humanoid@RunFastForwardUnarmed_RM.fbx, Humanoid@RunFastTurnLeftUnarmed.fbx, Humanoid@RunFastTurnLeftUnarmed_RM.fbx, Humanoid@RunFastTurnRightUnarmed.fbx, Humanoid@RunFastTurnRightUnarmed_RM.fbx, Humanoid@RunForward2Unarmed.fbx, Humanoid@RunForward2Unarmed_RM.fbx, Humanoid@RunForwardLeftUnarmed.fbx, Humanoid@RunForwardLeftUnarmed_RM.fbx, Humanoid@RunForwardRightUnarmed.fbx, Humanoid@RunForwardRightUnarmed_RM.fbx, Humanoid@RunForwardUnarmed.FBX, Humanoid@RunForwardUnarmed_RM.FBX, Humanoid@RunUTurnLeftUnarmed.fbx, Humanoid@RunUTurnLeftUnarmed_RM.fbx, Humanoid@RunUTurnRightUnarmed.fbx, Humanoid@RunUTurnRightUnarmed_RM.fbx, Humanoid@SprintForwardLeftUnarmed.fbx, Humanoid@SprintForwardLeftUnarmed_RM.fbx, Humanoid@SprintForwardRightUnarmed.fbx, Humanoid@SprintForwardRightUnarmed_RM.fbx, Humanoid@SprintForwardUnarmed.FBX, Humanoid@SprintForwardUnarmed_RM.FBX, Humanoid@StrafeLeftTakeCoverCrouchingUnarmed.fbx, Humanoid@StrafeLeftTakeCoverCrouchingUnarmed_RM.fbx, Humanoid@StrafeLeftTakeCoverStandingUnarmed.fbx, Humanoid@StrafeLeftTakeCoverStandingUnarmed_RM.fbx, Humanoid@StrafeRightTakeCoverCrouchingUnarmed.fbx, Humanoid@StrafeRightTakeCoverCrouchingUnarmed_RM.fbx, Humanoid@StrafeRightTakeCoverStandingUnarmed.fbx, Humanoid@StrafeRightTakeCoverStandingUnarmed_RM.fbx, Humanoid@ThrowGrenadeLeftUnderCoverCrouching.fbx, Humanoid@ThrowGrenadeLeftUnderCoverStanding.fbx, Humanoid@ThrowGrenadeRightUnderCoverCrouching.fbx, Humanoid@ThrowGrenadeRightUnderCoverStanding.fbx, Humanoid@ThrowGrenadeUnarmed1.fbx, Humanoid@ThrowGrenadeUnarmed2.fbx, Humanoid@Turn180LeftUnarmed.fbx, Humanoid@Turn180LeftUnarmed_RM.fbx, Humanoid@Turn180RightUnarmed.fbx, Humanoid@Turn180RightUnarmed_RM.fbx, Humanoid@Turn90LeftUnarmed.fbx, Humanoid@Turn90LeftUnarmed_RM.fbx, Humanoid@Turn90RightUnarmed.fbx, Humanoid@Turn90RightUnarmed_RM.fbx, Humanoid@WalkBackwardsLeftUnarmed.fbx, Humanoid@WalkBackwardsLeftUnarmed_RM.fbx, Humanoid@WalkBackwardsRightUnarmed.fbx, Humanoid@WalkBackwardsRightUnarmed_RM.fbx, Humanoid@WalkBackwardsUnarmed.fbx, Humanoid@WalkBackwardsUnarmed_RM.fbx, Humanoid@WalkForwardLeftUnarmed.fbx, Humanoid@WalkForwardLeftUnarmed_RM.fbx, Humanoid@WalkForwardRightUnarmed.fbx, Humanoid@WalkForwardRightUnarmed_RM.fbx, Humanoid@WalkForwardUnarmed.FBX, Humanoid@WalkForwardUnarmed2.fbx, Humanoid@WalkForwardUnarmed2_RM.fbx, Humanoid@WalkForwardUnarmed_RM.FBX, Humanoid@WalkLeftUnarmed.fbx, Humanoid@WalkLeftUnarmed_RM.fbx, Humanoid@WalkRightUnarmed.fbx, Humanoid@WalkRightUnarmed_RM.fbx, Humanoid@WalkUTurnLeftUnarmed_RM.fbx, Humanoid@WalkUTurnRightUnarmed.fbx, Humanoid@WalkUTurnRightUnarmed_RM.fbx.
loop-closure:error: Humanoid@CrouchBackwardsLeftUnarmed_RM.fbx, Humanoid@CrouchBackwardsRightUnarmed_RM.fbx, Humanoid@CrouchBackwardsUnarmed_RM.fbx, Humanoid@CrouchForwardLeftUnarmed_RM.fbx, Humanoid@CrouchForwardRightUnarmed_RM.fbx, Humanoid@CrouchForwardUnarmed_RM.fbx, Humanoid@CrouchIdleBreathe2Unarmed.fbx, Humanoid@CrouchIdleLookAround2Unarmed.fbx, Humanoid@CrouchLeftUnarmed_RM.fbx, Humanoid@CrouchRightUnarmed_RM.fbx, Humanoid@CrouchThrowGrenadeUnarmed1.fbx, Humanoid@CrouchThrowGrenadeUnarmed2.fbx, Humanoid@CrouchTurn180RightUnarmed_RM.fbx, Humanoid@CrouchTurn90LeftUnarmed.fbx, Humanoid@CrouchTurn90LeftUnarmed_RM.fbx, Humanoid@CrouchTurn90RightUnarmed_RM.fbx, Humanoid@Pass1MeterObstacleLeftUnarmed_RM.FBX, Humanoid@Pass1MeterObstacleRightUnarmed_RM.FBX, Humanoid@RunBackwardsLeftUnarmed_RM.fbx, Humanoid@RunBackwardsRightUnarmed_RM.fbx, Humanoid@RunBackwardsUnarmed_RM.fbx, Humanoid@RunFastForwardUnarmed_RM.fbx, Humanoid@RunFastTurnLeftUnarmed_RM.fbx, Humanoid@RunFastTurnRightUnarmed_RM.fbx, Humanoid@RunForward2Unarmed_RM.fbx, Humanoid@RunForwardLeftUnarmed_RM.fbx, Humanoid@RunForwardRightUnarmed_RM.fbx, Humanoid@RunForwardUnarmed_RM.FBX, Humanoid@RunUTurnLeftUnarmed.fbx, Humanoid@RunUTurnLeftUnarmed_RM.fbx, Humanoid@RunUTurnRightUnarmed.fbx, Humanoid@RunUTurnRightUnarmed_RM.fbx, Humanoid@SprintForwardLeftUnarmed_RM.fbx, Humanoid@SprintForwardRightUnarmed_RM.fbx, Humanoid@SprintForwardUnarmed_RM.FBX, Humanoid@StrafeLeftTakeCoverCrouchingUnarmed_RM.fbx, Humanoid@StrafeLeftTakeCoverStandingUnarmed_RM.fbx, Humanoid@StrafeRightTakeCoverCrouchingUnarmed_RM.fbx, Humanoid@StrafeRightTakeCoverStandingUnarmed_RM.fbx, Humanoid@ThrowGrenadeUnarmed2.fbx, Humanoid@Turn180LeftUnarmed_RM.fbx, Humanoid@Turn180RightUnarmed_RM.fbx, Humanoid@Turn90LeftUnarmed_RM.fbx, Humanoid@Turn90RightUnarmed_RM.fbx, Humanoid@WalkBackwardsLeftUnarmed_RM.fbx, Humanoid@WalkBackwardsRightUnarmed_RM.fbx, Humanoid@WalkBackwardsUnarmed_RM.fbx, Humanoid@WalkForwardLeftUnarmed.fbx, Humanoid@WalkForwardLeftUnarmed_RM.fbx, Humanoid@WalkForwardRightUnarmed_RM.fbx, Humanoid@WalkForwardUnarmed2.fbx, Humanoid@WalkForwardUnarmed2_RM.fbx, Humanoid@WalkForwardUnarmed_RM.FBX, Humanoid@WalkLeftUnarmed_RM.fbx, Humanoid@WalkRightUnarmed_RM.fbx, Humanoid@WalkUTurnLeftUnarmed_RM.fbx, Humanoid@WalkUTurnRightUnarmed.fbx, Humanoid@WalkUTurnRightUnarmed_RM.fbx.
in-place:error: Humanoid@CrouchTurn180LeftUnarmed_RM.fbx, Humanoid@CrouchTurn180RightUnarmed_RM.fbx, Humanoid@CrouchTurn90LeftUnarmed_RM.fbx, Humanoid@CrouchTurn90RightUnarmed_RM.fbx, Humanoid@RunUTurnLeftUnarmed_RM.fbx, Humanoid@RunUTurnRightUnarmed_RM.fbx, Humanoid@StrafeLeftTakeCoverCrouchingUnarmed_RM.fbx, Humanoid@StrafeRightTakeCoverCrouchingUnarmed_RM.fbx, Humanoid@Turn180LeftUnarmed_RM.fbx, Humanoid@Turn180RightUnarmed_RM.fbx, Humanoid@Turn90LeftUnarmed_RM.fbx, Humanoid@Turn90RightUnarmed_RM.fbx, Humanoid@WalkUTurnLeftUnarmed_RM.fbx, Humanoid@WalkUTurnRightUnarmed_RM.fbx.
time-monotonic:error: Humanoid@GoBackToCoverLeftStandingUnarmed.fbx, Humanoid@GoBackToCoverLeftStandingUnarmed_RM.fbx, Humanoid@GoBackToCoverRightCrouchingUnarmed.fbx, Humanoid@GoBackToCoverRightCrouchingUnarmed_RM.fbx, Humanoid@GoBackToCoverRightStandingUnarmed.fbx, Humanoid@GoBackToCoverRightStandingUnarmed_RM.fbx, Humanoid@GoOutOfCoverRightStandingUnarmed_RM.fbx, Humanoid@IdleStandingToTakeCoverCrouchingUnarmed.fbx, Humanoid@IdleStandingToTakeCoverCrouchingUnarmed_RM.fbx, Humanoid@IdleTakeCoverCrouchingToIdleStandingUnarmed.fbx, Humanoid@ThrowGrenadeLeftUnderCoverStanding.fbx, Humanoid@ThrowGrenadeRightUnderCoverStanding.fbx.
loop-seam:error: Humanoid@RunFastTurnLeftUnarmed_RM.fbx, Humanoid@RunFastTurnRightUnarmed_RM.fbx, Humanoid@RunUTurnLeftUnarmed_RM.fbx, Humanoid@RunUTurnRightUnarmed_RM.fbx, Humanoid@Turn180RightUnarmed_RM.fbx, Humanoid@WalkUTurnLeftUnarmed_RM.fbx, Humanoid@WalkUTurnRightUnarmed.fbx, Humanoid@WalkUTurnRightUnarmed_RM.fbx.
AnimSmith remediation evidence
Phase spread here is the minimum covering arc in cycles: sort phases in [0,1), include the wraparound gap, then subtract the largest gap from 1. It is not max_circular_deviation_from_mean; neither measure alone proves support-foot or visual compatibility. The primary report compares source and unpromoted output spreads for the same walk/run/crouch member sets.
| Source issue | Operation/declarations | Result | Independent verification | Remaining caveat |
|---|---|---|---|---|
gait-anchor trial | Explicit original source/config; transform --gait-anchor | 24 candidates emitted; 2 pass selected output lint | All 36 original time-ordering errors are absent after the twelve slices. Every candidate inspected, measured, linted, diffed and checked with fix --dry-run; detailed exit records external | Unpromoted; no candidate engine/contact/visual acceptance |
prune-constant-tracks trial | Explicit original source/config; transform --prune-constant-tracks | 3 candidates emitted; 3 pass selected output lint | Every candidate inspected, measured, linted, diffed and checked with fix --dry-run; detailed exit records external | Unpromoted; no candidate engine/contact/visual acceptance |
slice-negative-time trial | Explicit original source/config; transform --slice | 12 candidates emitted; 10 pass selected output lint | Every candidate inspected, measured, linted, diffed and checked with fix --dry-run; detailed exit records external | Unpromoted; no candidate engine/contact/visual acceptance |
Engine procedures and evidence
| Runtime | Version | Procedure | Observed result | Remaining gate |
|---|---|---|---|---|
| Unity | 6000.5.8f1 | Fresh project from source FBX and retained importer settings; batch AnimationPackProbe.Run | 178 motion FBXs, 177 Humanoid clips, 178 valid source-avatar assignments; 6/6 samples and 3/3 pair mixers execute | No pose assertions; target controller, visual, contacts, build and transformed outputs untested |
| Unreal Engine | unspecified | None in current run | not evaluated | Disposable import, retarget, graph, and visual test |
| Godot | unspecified | None in current run | not evaluated | Disposable conversion/import and graph test |
| Bevy | unspecified | None in current run | not evaluated | Selected export handoff, runtime, and performance test |
The source sample probe selects forward walk/run/crouch and their RM-labelled counterparts; mixers blend forward with forward-left for walk/run/crouch at 0.5/0.5 for 0.1 s. It reports exceptions, not bone-pose or contact assertions. The probe uses RunForwardUnarmed.FBX, while the new selected run ring uses RunForward2Unarmed.fbx; it does not validate that entire ring. All 179 copied FBX hashes match the current source baseline. Each motion importer references the shared source avatar; zero embedded valid model avatars is not a failure of that explicit source-avatar setup.
Rig, masking, and compatibility evidence
Fresh Unity 6000.5.8f1 source tests passed 26/26 bounded checks: 13 individual Humanoid samples, nine named cross-pack mixer schedules and four RM samples. For the nine mixers, five weights (0, 0.25, 0.5, 0.75, 1) produced finite sampled bone transforms with applyRootMotion=false and a fixed Animator owner. This supports technical feasibility of the named source combinations, not visually smooth handoffs or a finished kinematic controller.
Basic-specific pairs use IdleUnarmed or WalkForwardUnarmed2 against the three named melee idle/walk clips; the explicit RM source is WalkForwardUnarmed2_RM. The collection appendix preserves exact pairs and evidence digests.
No rendered-frame, contact, weapon-grip, deformation, interruption, authored AnimatorController or player-build acceptance ran. All tests use original FBXs on the shared Protof-Actor Humanoid avatar; none accepts the AnimSmith-generated GLB candidates.
Limitations and unknowns
- The Basic inventory/sample probe tests source import and graph execution only; the separate cross-pack probe adds finite-pose/root assertions. No visual, contact, target-controller, build, transformed-output or artistic acceptance test ran.
- Commercial sources and outputs remain external and are not published.
Changes between AnimSmith versions
AnimSmith 0.14.0 — reran 179 input baselines, 177 per-file declared contracts and 39 remediation trials using the official release. Added current exact-member generic scenarios and developer/artist actions. AnimSmith 0.10.0 — superseded historical evidence; none of its generated outputs or engine results is relabelled as a fresh run.
Reproduction
Official evaluator preflight: release archive, archive SHA-256 4ecf79436f9123c779edb004050da3010b44a2b392e3031facb227d7734fc33e; exact member animsmith-v0.14.0-x86_64-unknown-linux-gnu/animsmith, binary SHA-256 c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366; tag v0.14.0, peeled commit e8321ad40be5ef6f162b31f085819c039175c3c9; working-tree state: N/A (official archive). Compiled features: fbx, report. Version and required command help plus representative FBX admission passed before evaluation. Preflight: external:animsmith-0.14.0-report-refresh/preflight.json, SHA-256 7034b5043de54e7b30064230db18605d1f96d0ea1477f7b8c56ce817cc73521a.
Evaluator: official 0.14.0 Linux release, tag commit e8321ad40be5ef6f162b31f085819c039175c3c9; archive SHA-256 4ecf79436f9123c779edb004050da3010b44a2b392e3031facb227d7734fc33e; binary SHA-256 c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366, features fbx, report. Output schema v19; measurements schema v18. Version/help and representative admission passed before corpus work. Sources/configurations were hashed before and after read-only runs.
External evidence prefix: external:animsmith-0.14.0-report-refresh/evaluations/protofactor-basic-locomotion/. Commands record exact source/config arguments, exits and raw-output paths. Commercial source and generated motion remain external. Reproduction requires the authorized delivery and captured configuration; generic one-liners below do not substitute for those declarations.
baseline/command-results.json, SHA-256693b4e0226c9d3b8a62727a210bde82d58fe65f80aa020b44f012554bb6c19b3.contracts/command-results.json, SHA-256151f4cce9337110156c8886eefbc735034f9360d5adf5682e8f147a1029b5980.remediation/command-results.json, SHA-256fdf00e9e3ff0d5ce9c40f4fc3a19f62bca879a182084db3491b4c44a3d704bb2.selected-runtime-sets.json, SHA-256eeb198563d692a400a4acd07bba02edbae85d3e52252db5bffde6d31fd83ebc1.current-summary.json, SHA-2569fb888b1ee64f82477a9f79a4143cdb67d11487c24ae3454140d8e99fb944917.
animsmith inspect --config <captured-config> <authorized-source>
animsmith measure --config <captured-config> --format json <authorized-source>
animsmith lint --config <captured-config> --format json <authorized-source>
The exact per-trial transform, slice range, configuration and post-check commands are in the remediation ledger. Current scenario sets are new evaluator choices; historical structured-model migration remains outside this refresh.
Fresh source-engine evidence: external:animsmith-0.14.0-report-refresh/evaluations/protofactor-basic-locomotion/unity/verification.json, SHA-256 78108d62fd10818ac26dd31475ca91b4d7e301805e2f063ede608b0e587c1315. The adjacent probe, result and editor log are retained externally.
Sources
- Protofactor, Ultimate Animation Collection — product context.
- AnimSmith, CLI reference and game-ready clips — current evaluator and readiness boundary.
Animation pack evaluation: Protofactor Sword and Shield Animset
Technical verdict: Usable with conditions
Evaluation completeness: partial — source files, declared checks and transform trials were reviewed, with limited Unity source playback; target-character visuals, contacts, retargeting and the game controller remain untested.
Confidence: medium
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
Detailed evidence: Protofactor Sword and Shield Animset evidence appendix
Technical decision
Prototype decision: Try one in-place or root-motion variant per gait. Correct loop declarations for one-shots; continuous gait seams need source or loop-policy correction. The two-bone root-motion crouch file is a source structure blocker. In-place clips leave travel to the controller; root-motion clips carry authored travel.
Camera scope: Full-body humanoid animations; appearance on your target character and camera is untested. Dedicated first-person arms/viewmodel use is unverified.
This animset is one of eight locally evaluated parts of Ultimate Animation Collection. The local asset revision is unknown; the collection report separates evaluated files from the dated vendor listing.
Content in evaluated inventory: Armed walk, run, crouch and equipment/actions. Evaluated filenames include combat actions and equipment transitions; airborne and additive aim are not established. These are identified motions; gameplay behavior has not been approved.
AnimSmith 0.14.0 reads all 136 delivered FBX candidates. The untouched baseline has no errors; it reports 17078 constant-track notes, one animated-scale warning. With the per-file settings used in this evaluation, 17/132 motion files pass and 115 fail, mainly because loop settings need review. These loop and movement settings still need confirmation from the project or vendor; their failures are not all source defects. Set confirmed one-time actions to play once before treating their wrap seams as source defects. For intended repeating gaits, review the loop policy and correct actual source seams. One combined-take file also carries animated scale, and the malformed 2-bone root-motion crouch file blocks that blend set.
All 28 transform trials passed file inspection and measurement. Only the combined-take pruning output passes its declared checks; the other 27 still fail. A target-engine diagnostic can play unchanged sources now to reveal seams and contact problems. The two-bone file cannot join the proposed root-motion crouch blend. Before admitting any selected gait to the controller, correct confirmed one-shot settings, replace that file if its set is needed, and establish a gait set that passes its intended checks, full blend, foot-contact, movement and visual review.
Capability coverage
Content present
Filename-classified walk, run and crouch combat gaits have separate in-place and root-motion candidates. Equipment transitions and attack/reaction content also appear in the evaluated inventory.
Action candidates to inspect: The retained runtime-set inventory groups death-downed-recover-front/back/left/right (four separate three-file in-place chains, each 3/3 passing its retained declaration) and draw-combat-put-away-1/2 (two three-file chains, each 2/3). Passing a declaration is a mechanical result, not tested recovery, interruption or equip behavior. The public grouping rows do not bind individual action filenames and event times; establish those bindings and play the whole chain in engine.
Content gaps and unknowns
The evaluated role inventory has no airborne class. Additive aim, paired interaction and dedicated viewmodel content have no established classification. The two-bone root-motion crouch source cannot join its proposed blend.
Evaluation still needed
Redeclare one-shots, obtain clean intended continuous gait contracts, then test full blend coordinates, root ownership, grip, foot contact and target-character playback. Bounded Unity execution is not visual acceptance.
Runtime sets and authored motion
Use each row as a separate blend or sequence proposal. The linked appendix names its exact clips, timings and measurements; controller playback and contact quality still need testing.
| Set | Controller use | Adoption decision | Exact members |
|---|---|---|---|
walk-combat-8-way-in-place | Walk eight directions; controller travel | Walk prototype with controller travel. Its declared checks still fail. Confirm which motions should loop, correct any genuine seam, and test foot contacts before using the full blend. | 8 exact members |
walk-combat-8-way-root-motion | Walk eight directions; animation travel; measured root-motion 0.750–1.107 m/s | Walk prototype with animation travel. Its declared checks still fail. Confirm which motions should loop, correct any genuine seam, and test foot contacts before using the full blend. | 8 exact members |
run-combat-8-way-in-place | Run eight directions; controller travel | Run prototype with controller travel. Its declared checks still fail. Confirm which motions should loop, correct any genuine seam, and test foot contacts before using the full blend. | 8 exact members |
run-combat-8-way-root-motion | Run eight directions; animation travel; measured root-motion 2.793–3.189 m/s | Run prototype with animation travel. Its declared checks still fail. Confirm which motions should loop, correct any genuine seam, and test foot contacts before using the full blend. | 8 exact members |
crouch-combat-8-way-in-place | Crouch eight directions; controller travel | Crouch prototype with controller travel. Its declared checks still fail. Confirm which motions should loop, correct any genuine seam, and test foot contacts before using the full blend. | 8 exact members |
crouch-combat-8-way-root-motion | Crouch eight directions; animation travel; measured root-motion 0.701–0.789 m/s | Blocked: the two-bone root-motion crouch member needs source replacement before this eight-way set can be admitted; other gait loops also still fail their declared checks. | 8 exact members |
The appendix gives timings, travel speeds and phase measurements for each motion. Use the in-place and root-motion rows as alternatives; neither variant has accepted contact synchronization.
Integration recipe
- Members/topology: Use only the exact members in one named gait row and map forward/cardinal/diagonal coordinates explicitly.
- Timing/synchronization: Loop only confirmed continuous states, keep named one-shots at
loop=false, and require phase/contact review before synchronized blending. - State ownership: Choose controller translation for in-place clips or animation translation for root-motion clips per state, with one owner for translation, yaw, and collision.
- Composition constraints: Keep weapon combat states namespaced to this pack and require separate mask/socket/IK evidence before layering.
- Acceptance gate: Require clean intended contracts plus target-engine import, full blend-space playback, foot/weapon contact, and artist review.
Technical issue register
| ID | Severity | Problem and impact | Primary owner | Current action | Future AnimSmith potential | Evidence/status |
|---|---|---|---|---|---|---|
| PFS-PHASE | note | The three selected in-place gait sets have measured phase differences (see appendix). Foot-contact timing and intended synchronization are unknown, so the measurements alone do not show a source defect. Guidance. | unknown | Obtain project or vendor loop/contact intent, then sweep the exact blend coordinates against a declared contact tolerance. Use engine phase markers or declared alignment only after intent is established; request artist cleanup only for a confirmed source-motion problem. Residual unresolved. | Current phase measurements inform a declared alignment trial; they do not prove contact or visual acceptance. | observed-animsmith phases; synchronization/contact acceptance not-evaluated. |
| PFS-01 | major | The six selected eight-direction gait sets, including Humanoid@RunForwardS&S.fbx and Humanoid@RunForwardS&S_RM.fbx, have no members passing the checks used in this evaluation. None is ready for a production blend. Guidance | artist-author | Re-export intended repeating gaits with matching endpoint pose and velocity, or document a play-once setting. All eight members must pass their intended checks, then pass visual and contact review across the blend. Unresolved. | AnimSmith can validate and compare candidates, but cannot infer intended contact phase or approve visible motion. | observed-animsmith; current exact-file evidence, high confidence mechanically |
| PFS-02 | moderate | Scope: Humanoid@SwordAttack1S&S.fbx and other attacks, reactions, parries, or emotes currently declared as loops; reproduce: run the retained config and observe loop seam errors; impact: a project declaration can reject valid one-shots or cause unintended replay seams. Guidance | engine-config | Set confirmed one-time actions to loop=false; keep looping only where the project or vendor intends it. Recheck the settings and play each transition once. Unresolved. | AnimSmith may suggest a play-once setting, but the project or vendor must confirm intent. | observed-animsmith; declaration defect distinguished from source-animation quality |
| PFS-03 | blocker | Scope: Humanoid@CrouchForwardRightS&S_RM.fbx; reproduce: inspect the delivered file and compare its 2-bone skeleton with the other seven 56-bone root-motion crouch members; impact: it cannot join the intended humanoid eight-way set. Guidance | artist-author | re-export the full humanoid hierarchy and animation; Acceptance: skeleton signature matches the approved 56-bone family, contract lint passes, and engine blend/contact review succeeds; Unresolved | AnimSmith can detect the structural mismatch but cannot reconstruct missing authored bones. | observed-file and observed-animsmith; high-confidence source defect |
| PFS-04 | moderate | Scope: 14 _RM attack/combo/reaction files, including Humanoid@3HitCombo1S&S_RM.fbx; reproduce: retained configs declare them in-place and trigger in-place; impact: root-motion variants are rejected under the wrong controller policy. Guidance | engine-config | change those declarations to project-owned root motion and test collision/heading ownership; Acceptance: the intended root policy lints clean and engine displacement matches design; Unresolved | AnimSmith measures displacement but cannot choose controller ownership. | observed-animsmith; current config mismatch |
Engine status
| Runtime | Evidence level | Technical result | Remaining gate |
|---|---|---|---|
| Unity 6000.5.8f1 | source-controller-feasibility | Fresh import and standalone sampling passed only for IdleCombatS&S; sword-shield-idle-to-onehand-idle, sword-shield-idle-to-twohand-idle, and sword-shield-idle-to-dual-idle produced finite mixer poses with fixed gameplay-owned root. | Inspect rendered motion, contacts, deformation, authored AnimatorController behavior, and a player build; do not generalize beyond named sources. |
| Unreal Engine unspecified | not-evaluated | No current import or retarget result. | Import, retarget, root-motion, blend, and visual tests. |
| Godot unspecified | not-evaluated | No current conversion or playback result. | Convert/import and test animation graph, roots, contacts, and visuals. |
| Bevy unspecified | not-evaluated | No current conversion or playback result. | Convert/load and test graph, root ownership, performance, and visuals. |
Fit and limitations
Best fit is a full-body combat prototype whose controller can explicitly select one gait variant and keep pack-local state names. It is a poor fit for immediate production admission, motion matching, layered combat, or retargeted characters without the missing engine and human gates.
The fresh Unity probe establishes finite source playback and the named cross-pack transitions for IdleCombatS&S; it does not establish visual quality, contact, phase, retargeting, or broad pack compatibility. Keep states namespaced and retain explicit root ownership.
Changes between AnimSmith versions
AnimSmith 0.14.0 — revalidated the unchanged retained source inventory, per-file declarations, measured runtime hypotheses, and every previously recommended bounded transform. Current transformed outputs still need gameplay and visual checks. AnimSmith 0.10.0 — retained command ledgers and classification material supplied replay controls; its conclusions are superseded for current behavior.
Evidence status
Current evidence covers 136 physical FBXs and 132 logical individual-motion files with official AnimSmith 0.14.0 and the retained evaluation-manifest schema, now presented in editorial report format 3. A bounded Unity source/controller probe is current; visual/artistic, contact, retarget, authored-controller, and player-build gates remain open. See the readiness ladder and the evidence appendix. Licensed source and generated candidates remain outside Git.
Sources
- Protofactor product context and End User License Agreement — vendor context only; it does not identify the local constituent revision.
Animation pack evidence appendix: Protofactor Sword and Shield Animset
Companion report: technical evaluation
Evidence status: partial — current mechanical, declaration, runtime-group, and remediation evidence completed; engine and human acceptance did not.
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
The canonical readiness ladder remains authoritative.
Evaluation scope and provenance
| Field | Value |
|---|---|
| Pack/edition | Authorized local Protofactor constituent; revision unknown |
| Vendor/source | Protofactor product context; local revision not established by the current listing |
| Delivered scope | 136 retained FBX candidates; source bytes unchanged during replay |
| Target use | Broad game-engine technical intake; no target controller supplied |
| Target engines | Unity 6000.5.8f1 bounded source/controller probe; Unreal, Godot, and Bevy not evaluated |
| Target rigs/packs | Delivered rigs; Basic Locomotion compatibility not freshly evaluated |
| Source manifest | external:protofactor-sword-and-shield/source-inventory.json; SHA-256 e4dc4740bf35ff2812e81ff78970fc6737e62e8022664643c14b5cb8fdf2e4b8 |
| Evaluation manifest | Retained urn:animsmith:skill:animation-pack-evaluation-manifest:1; current memberships treated as hypotheses and remeasured |
| Acquisition/license provenance | Authorized local commercial input; no legal conclusion |
Evidence coverage
| Surface | Offered/delivered | Evaluated | Findings | Not evaluated and why |
|---|---|---|---|---|
| Animation files | 136 | 136 | 134 animation-bearing | Engine semantics not implied |
| Rigs/export variants | 136 | 136 | 131 individual-motion files share the 56-bone 2b6fe49d5ae6 skeleton; Humanoid@CrouchForwardRightS&S_RM.fbx exposes only a 2-bone 646f12b4559c skeleton | Retarget/reference-pose behavior not run |
| AnimSmith baseline | 136 | 136 | All inspect/measure/two lint commands exit 0; One scale-keys warning on Protof-Actor@Sword&ShieldAnimset.fbx. | N/A |
| Declared contracts | 132 | 132 | 17 pass, 115 fail in each format | Declaration intent needs project/vendor authority |
| Offline visual reports | 0 | 0 | 0 | Not run |
| Engine import/playback | 1 named Unity sources | 1 | Fresh Humanoid import and finite standalone sampling passed | Visual/contact, authored-controller, and player-build behavior not evaluated |
| Blend/mask/retarget | Three named source Playables mixers | Three fixed-root schedules | Finite sampled poses for named cross-pack pairs | No full blend tree, mask, separate target-rig retarget, visual or contact acceptance |
Claim legend
observed-file identifies source structure, observed-animsmith identifies current tool output, inferred identifies bounded hypotheses, and not-evaluated marks open gates.
Evaluation manifest and taxonomy
Current evaluation manifest schema: urn:animsmith:skill:animation-pack-evaluation-manifest:1.
Canonical clip-role inventory
| Canonical primary role | Logical motions | Delivered files | Evidence boundary |
|---|---|---|---|
idle-pose | 6 | 6 | observed-file; evaluator classification, not vendor semantics |
continuous-locomotion | 56 | 56 | observed-file; evaluator classification, not vendor semantics |
locomotion-transition | 0 | 0 | observed-file; evaluator classification, not vendor semantics |
airborne | 0 | 0 | observed-file; evaluator classification, not vendor semantics |
traversal | 0 | 0 | observed-file; evaluator classification, not vendor semantics |
action-interaction | 46 | 46 | observed-file; evaluator classification, not vendor semantics |
reaction-death | 24 | 24 | observed-file; evaluator classification, not vendor semantics |
emote-cinematic | 0 | 0 | observed-file; evaluator classification, not vendor semantics |
other-unknown | 0 | 0 | observed-file; evaluator classification, not vendor semantics |
| Total | 132 | 132 | Current exact-file catalog |
Runtime-set inventory
The untested acceptance below applies to each complete set. Named Unity source samples and mixers ran for a smaller subset; they do not establish full-set playback or visual quality.
| Runtime set | Type | Members/variants | Grouping evidence | Validation status |
|---|---|---|---|---|
walk-combat-8-way-in-place | directional-blend | 8 exact files; 8 IP, 0 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 0/8 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
walk-combat-8-way-root-motion | directional-blend | 8 exact files; 0 IP, 8 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 0/8 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
run-combat-8-way-in-place | directional-blend | 8 exact files; 8 IP, 0 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 0/8 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
run-combat-8-way-root-motion | directional-blend | 8 exact files; 0 IP, 8 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 0/8 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
crouch-combat-8-way-in-place | directional-blend | 8 exact files; 8 IP, 0 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 0/8 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
crouch-combat-8-way-root-motion | directional-blend | 8 exact files; 0 IP, 8 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 0/8 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
walk-normal-speed-in-place | speed-blend | 2 exact files; 2 IP, 0 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 0/2 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
walk-normal-speed-root-motion | speed-blend | 2 exact files; 0 IP, 2 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 0/2 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
run-normal-fast-in-place | speed-blend | 2 exact files; 2 IP, 0 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 0/2 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
run-normal-fast-root-motion | speed-blend | 2 exact files; 0 IP, 2 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 0/2 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
death-downed-recover-front | transition-chain | 3 exact files; 3 IP, 0 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 3/3 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
death-downed-recover-back | transition-chain | 3 exact files; 3 IP, 0 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 3/3 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
death-downed-recover-left | transition-chain | 3 exact files; 3 IP, 0 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 3/3 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
death-downed-recover-right | transition-chain | 3 exact files; 3 IP, 0 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 3/3 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
draw-combat-put-away-1 | transition-chain | 3 exact files; 3 IP, 0 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 2/3 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
draw-combat-put-away-2 | transition-chain | 3 exact files; 3 IP, 0 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 2/3 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
The exact runtime members below record source members and fresh timing/motion for the decision-driving gait sets. No historical membership is promoted solely from prose or filenames.
Exact runtime members
Current source members and measurements for the selected runtime scenarios. Selection is an evaluator hypothesis; source identity and measured values are retained below.
For these multi-member gait rows, duration and RM speed cells are set minima, not per-clip values. The ranges below retain authored variation; individual speed assignments are not reproduced here.
| Set/profile | Role or coordinate | Exact members | Variant/type | Timing or motion | Runtime contract |
|---|---|---|---|---|---|
walk-combat-8-way-in-place | eight-way directional gait | Humanoid@WalkForwardS&S.fbx; Humanoid@WalkForwardLeftS&S.fbx; Humanoid@WalkLeftS&S.fbx; Humanoid@WalkBackwardsLeftS&S.fbx; Humanoid@WalkBackwardsS&S.fbx; Humanoid@WalkBackwardsRightS&S.fbx; Humanoid@WalkRightS&S.fbx; Humanoid@WalkForwardRightS&S.fbx | variant=in-place | duration=1.333 s | loop=true; sync=not-evaluated |
walk-combat-8-way-root-motion | eight-way directional gait | Humanoid@WalkForwardS&S_RM.fbx; Humanoid@WalkForwardLeftS&S_RM.fbx; Humanoid@WalkLeftS&S_RM.fbx; Humanoid@WalkBackwardsLeftS&S_RM.fbx; Humanoid@WalkBackwardsS&S_RM.fbx; Humanoid@WalkBackwardsRightS&S_RM.fbx; Humanoid@WalkRightS&S_RM.fbx; Humanoid@WalkForwardRightS&S_RM.fbx | variant=root-motion | duration=1.333 s; rm_speed=0.750 m/s | loop=true; sync=not-evaluated |
run-combat-8-way-in-place | eight-way directional gait | Humanoid@RunForwardS&S.fbx; Humanoid@RunForwardLeftS&S.fbx; Humanoid@RunLeftS&S.fbx; Humanoid@RunBackwardsLeftS&S.fbx; Humanoid@RunBackwardsS&S.fbx; Humanoid@RunBackwardsRightS&S.fbx; Humanoid@RunRightS&S.fbx; Humanoid@RunForwardRightS&S.fbx | variant=in-place | duration=0.600 s | loop=true; sync=not-evaluated |
run-combat-8-way-root-motion | eight-way directional gait | Humanoid@RunForwardS&S_RM.fbx; Humanoid@RunForwardLeftS&S_RM.fbx; Humanoid@RunLeftS&S_RM.fbx; Humanoid@RunBackwardsLeftS&S_RM.fbx; Humanoid@RunBackwardsS&S_RM.fbx; Humanoid@RunBackwardsRightS&S_RM.fbx; Humanoid@RunRightS&S_RM.fbx; Humanoid@RunForwardRightS&S_RM.fbx | variant=root-motion | duration=0.600 s; rm_speed=2.793 m/s | loop=true; sync=not-evaluated |
crouch-combat-8-way-in-place | eight-way directional gait | Humanoid@CrouchForwardS&S.fbx; Humanoid@CrouchForwardLeftS&S.fbx; Humanoid@CrouchLeftS&S.fbx; Humanoid@CrouchBackwardsLeftS&S.fbx; Humanoid@CrouchBackwardsS&S.fbx; Humanoid@CrouchBackwardsRightS&S.fbx; Humanoid@CrouchRightS&S.fbx; Humanoid@CrouchForwardRightS&S.fbx | variant=in-place | duration=1.667 s | loop=true; sync=not-evaluated |
crouch-combat-8-way-root-motion | eight-way directional gait | Humanoid@CrouchForwardS&S_RM.fbx; Humanoid@CrouchForwardLeftS&S_RM.fbx; Humanoid@CrouchLeftS&S_RM.fbx; Humanoid@CrouchBackwardsLeftS&S_RM.fbx; Humanoid@CrouchBackwardsS&S_RM.fbx; Humanoid@CrouchBackwardsRightS&S_RM.fbx; Humanoid@CrouchRightS&S_RM.fbx; Humanoid@CrouchForwardRightS&S_RM.fbx | variant=root-motion | duration=1.667 s; rm_speed=0.701 m/s | loop=true; sync=not-evaluated |
Measured RM speed spans are walk 0.750–1.107 m/s (ratio 0.677); run 2.793–3.189 m/s (ratio 0.876); crouch 0.701–0.789 m/s (ratio 0.889). Preserve authored variation unless the project declares a normalization policy; diagonals and cardinals require the full blend test. Measured source in-place phase spreads (cycles, eight measured members each) are walk-combat-8-way-in-place 0.7231; run-combat-8-way-in-place 0.6605; crouch-combat-8-way-in-place 0.6974. Phase spread here is the minimum covering arc in cycles: sort phases in [0,1), include the wraparound gap, then subtract the largest gap from 1. It is not max_circular_deviation_from_mean; neither measure alone proves support-foot or visual compatibility. Keep sync=not-evaluated until the selected synchronization and contact policy is tested. The current catalog also measures paired normal/fast speed hypotheses, four death/downed/recovery chains, and two draw/combat/put-away chains. The transition chains are mechanically stronger than the gait sets, but still lack engine and visual acceptance.
Pipeline-stage coverage
| Stage | Coverage state | Evidence / remaining gate |
|---|---|---|
| Acquire | evaluated-clean | Authorized retained source inventory |
| Preserve raw | evaluated-clean | Replay verified source and config hashes unchanged |
| Inspect | evaluated-finding | Exhaustive current inspect/measure/lint; notes and named warnings retained |
| Segment | partially-evaluated | Exact individual files measured; combined take not semantically segmented |
| Root motion | evaluated-finding | IP/RM measurements captured; controller ownership not evaluated |
| Conform | evaluated-finding | Fresh candidates produced but not adopted |
| Validate | evaluated-finding | Baseline and retained declarations complete; many declarations fail |
| Optimize | not-evaluated | Prune candidates are not production-adopted |
| Export | partially-evaluated | Candidate GLBs exist externally; no engine import acceptance |
| Gate/report | partially-evaluated | Mechanical report complete; engine and human gates open |
Readiness evidence by clip set
| Role or runtime set | File-ready / clip-ready | Set-ready / rig-use | Runtime / acceptance boundary |
|---|---|---|---|
| Individual motions | Current evaluator reads and measures exact files | Declaration-clean subset only | Named Unity source imports/samples ran; visual behavior not evaluated |
| Directional gait hypotheses | Timing, speed, skeleton, and current errors recorded | 0 contract-clean members in each named gait set | Named source samples and mixers only; full blend, phase, controller, and contact gates open |
| Transition/action hypotheses | Exact files measured | One-shot/loop intent requires authority | Named source Playables schedules ran; authored state graph, contact, and artistic gates open |
Validation-profile status
| Validation profile | Selection | Result / next evidence |
|---|---|---|
| Marketplace intake | selected — vendor-intended | Current source inventory and exhaustive mechanical intake complete. |
| Blended locomotion | selected — evaluator-selected-generic-scenario | Named gait hypotheses measured; selected source mixer schedules ran in Unity, while contracts and full blend review remain open. |
| Root-motion controller | selected — observed-pack-capability | RM variants measured mechanically; no Sword & Shield root-motion walk was sampled standalone in Unity, and controller/collision ownership remains untested. |
| State-machine transitions | selected — observed-pack-capability | Transition candidates identified; named source Playables schedules ran, but authored state transitions and interruptions were not tested. |
| Layered upper body/weapons | not-selected | No mask/socket/IK authority or test. |
| Traversal/environment | not-selected | No target scene or traversal contacts. |
| Contact actions/interactions | selected — observed-pack-capability | Combat files present; weapon/body contacts not evaluated. |
| Retargeted/customizable characters | not-selected | No target rig or reference-pose test. |
| Motion matching/search | not-selected | No database construction or query test. |
| Networked movement | not-selected | No networking or reconciliation design. |
| Runtime performance | not-selected | No target build or benchmark. |
Pack inventory and content evidence
The current inventory contains 136 FBXs: 134 animation-bearing files, 132 individual-motion files, and 1 combined take. 131 individual-motion files share the 56-bone 2b6fe49d5ae6 skeleton; Humanoid@CrouchForwardRightS&S_RM.fbx exposes only a 2-bone 646f12b4559c skeleton. Counts describe physical current inputs, not runtime acceptance.
Current in-place gait phase evidence
Phase spread here is the minimum covering arc in cycles: sort phases in [0,1), include the wraparound gap, then subtract the largest gap from 1. It is not max_circular_deviation_from_mean; neither measure alone proves support-foot or visual compatibility. These are observed-animsmith source measurements for the exact hypotheses above; synchronization/contact acceptance remains open.
| Current hypothesis | Exact source member (Take 001) | Measured phase (cycles) |
|---|---|---|
walk-combat-8-way-in-place | Humanoid@WalkForwardS&S.fbx | 0.149832 |
walk-combat-8-way-in-place | Humanoid@WalkForwardLeftS&S.fbx | 0.111639 |
walk-combat-8-way-in-place | Humanoid@WalkLeftS&S.fbx | 0.378087 |
walk-combat-8-way-in-place | Humanoid@WalkBackwardsLeftS&S.fbx | 0.800623 |
walk-combat-8-way-in-place | Humanoid@WalkBackwardsS&S.fbx | 0.839751 |
walk-combat-8-way-in-place | Humanoid@WalkBackwardsRightS&S.fbx | 0.881416 |
walk-combat-8-way-in-place | Humanoid@WalkRightS&S.fbx | 0.677664 |
walk-combat-8-way-in-place | Humanoid@WalkForwardRightS&S.fbx | 0.654982 |
run-combat-8-way-in-place | Humanoid@RunForwardS&S.fbx | 0.165717 |
run-combat-8-way-in-place | Humanoid@RunForwardLeftS&S.fbx | 0.201005 |
run-combat-8-way-in-place | Humanoid@RunLeftS&S.fbx | 0.276155 |
run-combat-8-way-in-place | Humanoid@RunBackwardsLeftS&S.fbx | 0.781110 |
run-combat-8-way-in-place | Humanoid@RunBackwardsS&S.fbx | 0.864140 |
run-combat-8-way-in-place | Humanoid@RunBackwardsRightS&S.fbx | 0.786420 |
run-combat-8-way-in-place | Humanoid@RunRightS&S.fbx | 0.615651 |
run-combat-8-way-in-place | Humanoid@RunForwardRightS&S.fbx | 0.138593 |
crouch-combat-8-way-in-place | Humanoid@CrouchForwardS&S.fbx | 0.087834 |
crouch-combat-8-way-in-place | Humanoid@CrouchForwardLeftS&S.fbx | 0.083958 |
crouch-combat-8-way-in-place | Humanoid@CrouchLeftS&S.fbx | 0.375552 |
crouch-combat-8-way-in-place | Humanoid@CrouchBackwardsLeftS&S.fbx | 0.878684 |
crouch-combat-8-way-in-place | Humanoid@CrouchBackwardsS&S.fbx | 0.891297 |
crouch-combat-8-way-in-place | Humanoid@CrouchBackwardsRightS&S.fbx | 0.885628 |
crouch-combat-8-way-in-place | Humanoid@CrouchRightS&S.fbx | 0.576121 |
crouch-combat-8-way-in-place | Humanoid@CrouchForwardRightS&S.fbx | 0.124471 |
Mechanical baseline
| Finding/check | Affected scope | Potential impact | Evidence |
|---|---|---|---|
| Current readability | 136/136 inputs | Enables mechanical intake | observed-animsmith; all four commands per input exit 0 |
| Constant tracks | 17078 notes across 134 animation-bearing files | Storage/evaluation noise; not a gameplay defect by itself | observed-animsmith; baseline ledger SHA-256 9148c23bc266d269cf69f8c1abaf7dc6bdd00ee456d3acc4d53930a3e6893ac7 |
| Retained declarations | 115/132 non-clean | Blocks admission under those exact declarations | observed-animsmith; contract ledger SHA-256 f3256aed915183c2a3923e75da1fad0a76842f472e7283c57bc9d4a43f1d2ead |
| Structural/timing warnings | Named scope above | Requires bounded author/project decision | observed-file and observed-animsmith |
AnimSmith remediation evidence
| Source issue | Operation/declarations | Result | Independent verification | Remaining caveat |
|---|---|---|---|---|
PFS-01 gait continuity | transform --gait-anchor on 24 selected in-place gait files; retained per-file configs | 24 outputs produced; selected inputs have no accumulating root translation/yaw | 24 inspect and measure commands exit 0; output hashes and diffs captured | Post-config lint remains non-clean; no RM trajectory was transformed; visual/contact residual unresolved |
| Constant-track notes | transform --prune-constant-tracks on Humanoid@SwordAttack1S&S.fbx, Humanoid@WalkForwardS&S.fbx, and Protof-Actor@Sword&ShieldAnimset.fbx | Output candidate(s) produced | Inspect, measure, diff, fix dry-run, and config lint captured | 27/28 post-transform config lints exit 1; only the combined-take prune exits 0. Candidate adoption and engine/visual acceptance remain open |
PFS-01 endpoint trial | transform --drop-duplicate-loop-endpoint on Humanoid@WalkForwardS&S.fbx | Output produced | Inspect/measure succeed; diff and config lint remain non-clean | Unadopted; visual loop/contact residual unresolved |
Fresh remediation ledger SHA-256 bace6c9618d4b4a1ea84b1300a7da0b15aefc0d88421de5e56a7551a8b5cb869. Generated motion stays outside Git.
Engine procedures and evidence
| Runtime | Version | Procedure | Observed result | Remaining gate |
|---|---|---|---|---|
| Unity | 6000.5.8f1 | Fresh named Humanoid source import and standalone sampling: IdleCombatS&S; named Playables mixers: sword-shield-idle-to-onehand-idle, sword-shield-idle-to-twohand-idle, sword-shield-idle-to-dual-idle. See collection source/controller probe. | Bounded source/controller feasibility for listed sources only; the collection’s 26/26 is a shared aggregate, not this pack’s result | Rendered motion, contact, deformation, full blend, retargeting, authored AnimatorController, player build and performance remain untested |
| Unreal Engine | not evaluated | No current procedure executed | not-evaluated | Import, retarget, root, blend, and visual review |
| Godot | not evaluated | No current procedure executed | not-evaluated | Convert/import, graph, root, contact, and visual review |
| Bevy | not evaluated | No current procedure executed | not-evaluated | Convert/load, graph, root, performance, and visual review |
Rig, masking, and compatibility evidence
| Pack/rig/set pair | Skeleton/retarget | Scale/axes | Root policy | Timing/blend | Overall evidence |
|---|---|---|---|---|---|
| Sword and Shield internal gait sets | Current signatures measured; no retarget | No engine scale/axis test | IP/RM files measured; owner not selected | Current duration/speed/phase recorded; full blend absent | Mechanical only |
| Sword & Shield idle to One-Handed Melee, Two-Handed Melee, and Dual Swords idles | Named Humanoid sources imported on shared SK_Protof-Actor | Unity import scale/axis accepted for named sources only | Gameplay-owned root fixed for these idle mixers; no Shield RM sample | Three named Playables mixers produced finite poses; visual blend not inspected | Bounded source feasibility for these pairs only; no Basic-to-Shield handoff test |
Limitations and unknowns
- Retained configs are replay controls, not proof that every attack, reaction, parry, or emote should loop.
- Current grouping hypotheses were remeasured from exact files, but vendor/project semantics and the unavailable richer historical membership authority were not reconstructed.
- Generated candidates remain external and unadopted; command success does not lower residual severity.
- Unity evidence is limited to named source import, finite headless poses, explicit root ownership, and named Playables mixers; no rendered visual, contact, retarget, mask, authored-controller, player-build, performance, or artistic acceptance was performed.
Changes between AnimSmith versions
AnimSmith 0.14.0 — current official binary revalidated source hashes, all baseline and declaration commands, named runtime hypotheses, and every retained remediation recipe. AnimSmith 0.10.0 — retained ledgers supplied immutable source/config controls; historical results are superseded for current conclusions.
Reproduction
Official evaluator preflight: release archive, archive SHA-256 4ecf79436f9123c779edb004050da3010b44a2b392e3031facb227d7734fc33e; exact member animsmith-v0.14.0-x86_64-unknown-linux-gnu/animsmith, binary SHA-256 c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366; tag v0.14.0, peeled commit e8321ad40be5ef6f162b31f085819c039175c3c9; working-tree state: N/A (official archive). Compiled features: fbx, report. Version and required command help plus representative FBX admission passed before evaluation. Preflight: external:animsmith-0.14.0-report-refresh/preflight.json, SHA-256 7034b5043de54e7b30064230db18605d1f96d0ea1477f7b8c56ce817cc73521a.
Official archive https://github.com/mmannerm/animsmith/releases/download/v0.14.0/animsmith-v0.14.0-x86_64-unknown-linux-gnu.tar.gz; archive SHA-256 4ecf79436f9123c779edb004050da3010b44a2b392e3031facb227d7734fc33e; member animsmith-v0.14.0-x86_64-unknown-linux-gnu/animsmith; binary SHA-256 c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366; tag v0.14.0; commit e8321ad40be5ef6f162b31f085819c039175c3c9; working tree N/A; features fbx/report.
Preflight verified version/help for required commands and representative GLB/FBX admission before replay; scrubbed locator external:0.14.0-report-refresh/preflight.json, SHA-256 7034b5043de54e7b30064230db18605d1f96d0ea1477f7b8c56ce817cc73521a. The read-only helper replayed the retained exact inspect, measure, and two lint invocations with four workers, recorded argv/exit/stdout/stderr and source/config hashes, and did not mutate inputs. Baseline, contract, and remediation ledger digests are 9148c23bc266d269cf69f8c1abaf7dc6bdd00ee456d3acc4d53930a3e6893ac7, f3256aed915183c2a3923e75da1fad0a76842f472e7283c57bc9d4a43f1d2ead, and bace6c9618d4b4a1ea84b1300a7da0b15aefc0d88421de5e56a7551a8b5cb869. Licensed paths, sources, and derivative outputs are excluded from Git and this report.
Fresh Unity evidence: editor 6000.5.8f1, Unity.exe SHA-256 cdc0eeca135394cde79a632aa998aca14b521b58afe8c2750b009aff608be906; probe SHA-256 28cd00897dfd739e2a7bcfa193adab03de3591a1009802946305127b67d1bfb6; result schema animsmith-unity-crosspack-probe-v1, result SHA-256 f067bca91f182b65fab408a1c97b297377a8f361b03d59e809e236aba666652c; log SHA-256 0429bcb9cc7a7492c6637eb799566463ba915243d0a110b4d76cbe75590c8819; 26/26 aggregate required checks passed with 53 nonempty Humanoid bones per sample. This report uses only the named applicable subset above.
Sources
- Protofactor product context and End User License Agreement — context only; local revision remains unknown.
Animation pack evaluation: Protofactor Campfire
Technical verdict: Usable with conditions
Evaluation completeness: partial — all source files and declared checks were reviewed; engine playback and visual quality remain untested.
Confidence: medium
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
Detailed evidence: Protofactor Campfire evidence
Technical decision
Prototype decision: Try discrete full-body interactions with project anchors and prop attachment. Configure one-shots and exits; scene contact and interruption remain unknown.
Camera scope: Full-body humanoid animations; appearance on your target character and camera is untested. Dedicated first-person arms/viewmodel use is unverified.
This animset is one of eight locally evaluated parts of Ultimate Animation Collection. The local asset revision is unknown; the collection report separates evaluated files from the dated vendor listing.
Content in evaluated inventory: Kneel, sit, lie and grill interactions. The evaluated inventory includes posture changes and prop actions; locomotion coverage is outside this pack. These are identified motions; gameplay behavior has not been approved.
Use as a full-body interaction state family, not a locomotion blend tree. The candidate kneel/sit/lie and grill sequences give a developer concrete states to prototype. The game still owns interaction anchors, interruption rules and props. If a one-time action fails a looping check, first correct its project setting before judging the animation.
observed-animsmith: the official 0.14.0 evaluator inspected and measured all 29 FBXs; 17/25 declared-contract files pass and 8 fail per output format. The findings below separate source problems, project settings and untested gameplay. Transformed outputs still need gameplay and visual checks.
Capability coverage
Content present
Filename-classified kneel/sit/lie transitions, held postures and skewer/grill actions support three proposed interaction chains. Named reverse transitions can be evaluated for exit.
Content gaps and unknowns
Ground locomotion is outside this interaction selection. Prop attachment, scene anchors and complete interrupt/return topology are unclassified in this evaluation.
Evaluation still needed
Play entry, hold, prop actions, interruption and exits on the target scene. No current target-engine or visual/contact acceptance exists.
Runtime sets and authored motion
Use each row as a separate blend or sequence proposal. The linked appendix names its exact clips, timings and measurements; controller playback and contact quality still need testing.
| Set | Controller use | Adoption decision | Exact members |
|---|---|---|---|
| Kneel and sit sequence | Discrete posture/prop chain | Prototype for discrete entry/hold/exit. The kneel hold has a measured rotational seam that pruning did not clear; review its intended loop before repetition (CF-LOOP). | 4 exact members |
| Sit and lie sequence | Discrete posture/prop chain | Prototype after configuring one-shot transitions and explicit return. Pose and seat contact still need scene review. | 4 exact members |
| Grill interaction sequence | Discrete posture/prop chain | Prototype after prop-anchor and event configuration. If kneel hold repeats, its seam remains; grip/contact is untested. | 4 exact members |
Integration recipe
- Members/topology: Choose the kneel/sit or sit/lie chain below. The listed chains cover ingress/hold only. Before returning to ground movement, select/review
Humanoid@IdleSitToIdleKneelCampfire.fbxthenHumanoid@KneelToStandCampfire.fbx(bothTake 001); exit continuity is untested. Trigger one-shots once; fire-lighting alternatives are discrete choices. - Timing/synchronization: Verify exit/entry poses and event points for reaching, eating and log release. Do not normalize every action to the idle duration.
- State ownership: Enter at an authored interaction anchor, stop ground locomotion, and define how interruption restores control. Inspect root/hip offsets before deciding whether an action may displace the capsule.
- Composition constraints: Validate skewer/log attachment and hand placement against the scene. Upper-body masking may break posture and is not established here.
- Acceptance gate: Play the whole interaction including approach, interrupt and exit, with the intended campfire and seating geometry.
Technical issue register
Severity describes impact as delivered for the stated use. Residual status is explicit; untested risks are not confirmed artist defects.
| ID | Severity | Problem and impact | Primary owner | Current action | Future AnimSmith potential | Evidence/status |
|---|---|---|---|---|---|---|
| CF-LOOP | major | The selected idle candidate Humanoid@IdleKneelCampfire.fbx has a rotational seam; repeated kneeling may pulse. Readiness guidance. | artist-author | For genuine resting loops, review endpoint pose/velocity and re-export clean cycles. Pruning IdleKneel does not remove its seam. The measured seam persists; review looping playback before repeating this hold. | Current measurements locate discontinuities; pruning is not a loop repair. | Exact Take 001, loop-seam-rot in current contract/output; observed-animsmith. |
| CF-POLICY | minor | Lighting and transition actions are also tested as loops; wrap failures do not establish defects when those actions are used once. Readiness guidance. | engine-config | Mark reviewed one-shots non-looping and test their intended start/end transitions. Residual: unresolved configuration choice, not proven bad animation. | Explicit per-clip declarations already support this distinction. | For example Humanoid@IdleKneelToIdleGrillSkewerCampfire.fbx; observed-animsmith, role inferred. |
| CF-PROP | major | Hand/prop placement and release timing have no current contact test; visible penetration or floating objects would block a finished interaction. Readiness guidance. | engine-config | Set scene and prop anchors, then test grill, eat and log events on the chosen character. If contacts fail, give the artist the clip, time and scene position. Contact quality remains unknown. | Generic contact evidence could help; automatic prop choreography is not established. | Integration risk, not-evaluated; not a confirmed vendor defect. |
Engine status
| Runtime | Evidence level | Technical result | Remaining gate |
|---|---|---|---|
| Unity unspecified | not-evaluated | No current import or playback. | Import, contacts, visual, and build tests. |
| Unreal Engine unspecified | not-evaluated | No current import or playback. | Import, retarget, contacts, and build tests. |
| Godot unspecified | not-evaluated | No current conversion or playback. | Conversion/import and graph tests. |
| Bevy unspecified | not-evaluated | No current handoff or runtime test. | glTF handoff and runtime test. |
Fit and limitations
Combine with Basic Locomotion through a state transition after the controller reaches a campfire anchor. Do not crossfade arbitrary moving locomotion into a seated loop while keeping translation enabled. Cross-pack entry/exit pose, prop placement, collision and character proportions remain untested.
These are proposed integration decisions (inferred), with target-controller, contact and artistic acceptance still not-evaluated.
Changes between AnimSmith versions
AnimSmith 0.14.0 — reran 29 input baselines, 25 per-file declared contracts and one remediation trial using the official release. Added current exact-member generic scenarios and developer/artist actions. AnimSmith 0.10.0 — superseded historical evidence; none of its generated outputs or engine results is relabelled as a fresh run.
Evidence status
Current evidence uses the official release and the canonical readiness ladder; commercial artifacts remain external.
Sources
- Protofactor, Animset: Campfire — product context.
- AnimSmith, CLI reference and game-ready clips — evaluation boundaries.
Animation pack evidence appendix: Protofactor Campfire
Companion report: Protofactor Campfire report
Evidence status: partial — the official evaluator loaded the delivered FBXs and produced current baseline evidence.
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
The canonical readiness ladder remains authoritative.
Evaluation manifest schema: urn:animsmith:skill:animation-pack-evaluation-manifest:1.
Evaluation scope and provenance
| Field | Value |
|---|---|
| Pack/edition | Protofactor Campfire; local constituent revision unknown |
| Vendor/source | Protofactor Animset: Campfire |
| Delivered scope | Authorized local commercial delivery; 29 FBXs |
| Target use | Engine-neutral campfire interaction intake |
| Target engines | Unity, Unreal Engine, Godot, and Bevy; not evaluated |
| Target rigs/packs | No target character or current cross-pack run supplied |
| Source manifest | External scrubbed inventory, SHA-256 122e9fe58cb2c5985d861724a41d709e28cf0f2f48408181e848b69b4d954338 |
| Evaluation manifest | Unavailable: current output binding was not rendered for this retained report format |
| Acquisition/license provenance | Authorized commercial bytes; no transaction or redistribution conclusion |
Evidence coverage
| Surface | Offered/delivered | Evaluated | Findings | Not evaluated and why |
|---|---|---|---|---|
| Animation files | 29 FBXs | 29 | baseline completed | mechanical and contract evidence captured |
| Rigs/export variants | Unknown | 0 | 0 | Not evaluated in this run |
| AnimSmith baseline | 29 | 29 | current findings recorded | inspect, JSON measure, JSON lint, and Markdown lint completed per file |
| Declared contracts | 25 motion-labelled files | 25 | 17 pass; 8 fail per output format | Mechanical contract results are current; historical taxonomy was not reconstructed; current generic scenarios are separately identified; artistic acceptance remains open |
| Offline visual reports | 0 | 0 | 0 | Not evaluated in this run |
| Engine import/playback | 4 runtimes | 0 | 0 | Deferred |
| Blend/mask/retarget | Unknown | 0 | 0 | Measurements completed, but no target rig or blend/mask acceptance test ran |
Claim legend
Current claims are observed-file, observed-animsmith, documentation-stated, or not-evaluated.
Evaluation manifest and taxonomy
Canonical clip-role inventory
| Canonical primary role | Logical motions | Delivered files | Evidence boundary |
|---|---|---|---|
idle-pose | 0 | 0 | Classification was not regenerated or accepted |
continuous-locomotion | 0 | 0 | Classification was not regenerated or accepted |
locomotion-transition | 0 | 0 | Classification was not regenerated or accepted |
airborne | 0 | 0 | Classification was not regenerated or accepted |
traversal | 0 | 0 | Classification was not regenerated or accepted |
action-interaction | 0 | 0 | Classification was not regenerated or accepted |
reaction-death | 0 | 0 | Classification was not regenerated or accepted |
emote-cinematic | 0 | 0 | Classification was not regenerated or accepted |
other-unknown | 0 | 0 | Classification was not regenerated or accepted |
| Total | 0 | 0 | 29 delivered FBXs remain unclassified |
Runtime-set inventory
New evaluator-selected generic scenarios: current source bytes establish the exact members, take names, durations, and measured roots below. Names suggest gameplay roles; topology and semantic intent are hypotheses requiring clip review. These are not reconstructed historical manifests or measured collection-output sets. Every listed member uses Take 001; full identities are in the external selected-set ledger.
| Runtime set | Type | Members/variants | Grouping evidence | Validation status |
|---|---|---|---|---|
| Kneel and sit sequence | transition-chain | Humanoid@StandToKneelCampfire.fbx, Humanoid@IdleKneelCampfire.fbx, Humanoid@KneelToSitCampfire.fbx, Humanoid@IdleSitCampfire.fbx; Take 001 | New evaluator-selected scenario; observed bytes/timing, inferred gameplay roles | Mechanical measurements current; set/engine/visual acceptance open |
| Sit and lie sequence | transition-chain | Humanoid@IdleSitCampfire.fbx, Humanoid@IdleSitToIdleLayDownCampfire.fbx, Humanoid@IdleLayDownCampfire.fbx, Humanoid@IdleLayDownToIdleSitCampfire.fbx; Take 001 | New evaluator-selected scenario; observed bytes/timing, inferred gameplay roles | Mechanical measurements current; set/engine/visual acceptance open |
| Grill interaction sequence | transition-chain | Humanoid@IdleKneelCampfire.fbx, Humanoid@IdleKneelToIdleGrillSkewerCampfire.fbx, Humanoid@IdleGrillSkewerCampfire.fbx, Humanoid@KneelEatSkewerCampfire.fbx; Take 001 | New evaluator-selected scenario; observed bytes/timing, inferred gameplay roles | Mechanical measurements current; set/engine/visual acceptance open |
Exact runtime members
Current source members and measurements for the selected runtime scenarios. Selection is an evaluator hypothesis; source identity and measured values are retained below.
| Set/profile | Role or coordinate | Exact members | Variant/type | Timing or motion | Runtime contract |
|---|---|---|---|---|---|
| Kneel and sit sequence | Candidate member 1 | Humanoid@StandToKneelCampfire.fbx::Take 001 | set_type=transition-chain | duration=4.500 s | loop=unknown; movement=controller; contact=not-evaluated |
| Kneel and sit sequence | Candidate member 2 | Humanoid@IdleKneelCampfire.fbx::Take 001 | set_type=transition-chain | duration=2.167 s | loop=unknown; movement=controller; contact=not-evaluated |
| Kneel and sit sequence | Candidate member 3 | Humanoid@KneelToSitCampfire.fbx::Take 001 | set_type=transition-chain | duration=4.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Kneel and sit sequence | Candidate member 4 | Humanoid@IdleSitCampfire.fbx::Take 001 | set_type=transition-chain | duration=2.667 s | loop=unknown; movement=controller; contact=not-evaluated |
| Sit and lie sequence | Candidate member 1 | Humanoid@IdleSitCampfire.fbx::Take 001 | set_type=transition-chain | duration=2.667 s | loop=unknown; movement=controller; contact=not-evaluated |
| Sit and lie sequence | Candidate member 2 | Humanoid@IdleSitToIdleLayDownCampfire.fbx::Take 001 | set_type=transition-chain | duration=4.000 s | loop=unknown; movement=controller; contact=not-evaluated |
| Sit and lie sequence | Candidate member 3 | Humanoid@IdleLayDownCampfire.fbx::Take 001 | set_type=transition-chain | duration=1.967 s | loop=unknown; movement=controller; contact=not-evaluated |
| Sit and lie sequence | Candidate member 4 | Humanoid@IdleLayDownToIdleSitCampfire.fbx::Take 001 | set_type=transition-chain | duration=2.667 s | loop=unknown; movement=controller; contact=not-evaluated |
| Grill interaction sequence | Candidate member 1 | Humanoid@IdleKneelCampfire.fbx::Take 001 | set_type=transition-chain | duration=2.167 s | loop=unknown; movement=controller; contact=not-evaluated |
| Grill interaction sequence | Candidate member 2 | Humanoid@IdleKneelToIdleGrillSkewerCampfire.fbx::Take 001 | set_type=transition-chain | duration=3.667 s | loop=unknown; movement=controller; contact=not-evaluated |
| Grill interaction sequence | Candidate member 3 | Humanoid@IdleGrillSkewerCampfire.fbx::Take 001 | set_type=transition-chain | duration=3.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Grill interaction sequence | Candidate member 4 | Humanoid@KneelEatSkewerCampfire.fbx::Take 001 | set_type=transition-chain | duration=11.667 s | loop=unknown; movement=controller; contact=not-evaluated |
Pipeline-stage coverage
| Stage | Coverage state | Evidence / remaining gate |
|---|---|---|
| Acquire | partially-evaluated | Authorized local inventory |
| Preserve raw | evaluated-clean | Source unchanged |
| Inspect | evaluated-finding | 29 baseline attempts completed |
| Segment | not-evaluated | No separate segmentation trial selected |
| Root motion | not-evaluated | Measurements completed; no root-motion policy or controller acceptance was evaluated |
| Conform | not-evaluated | No target rig or conformance policy was selected |
| Validate | evaluated-finding | Lint completed for every FBX |
| Optimize | partially-evaluated | One external constant-track candidate was generated; source was unchanged and runtime equivalence remains unproved |
| Export | partially-evaluated | Candidate was written as an external GLB only; no engine handoff was selected |
| Gate/report | partially-evaluated | Current baseline, remediation, and boundary recorded |
Readiness evidence by clip set
| Role or runtime set | File-ready / clip-ready | Set-ready / rig-use | Runtime / acceptance boundary |
|---|---|---|---|
| Entire delivered corpus | evaluated-finding | not-evaluated | not-evaluated |
| Named current generic scenarios above | Exact sources/takes measured; declared lint conditions remain | Candidate topology inferred; no set readiness approval | Target controller, contacts and artistic acceptance not evaluated |
Validation-profile status
| Validation profile | Selection | Result / next evidence |
|---|---|---|
| Marketplace intake | selected — evaluator-selected-generic-scenario | Inventory and current baseline captured |
| Blended locomotion | not-selected | No current locomotion scope |
| Root-motion controller | not-selected | No root-motion controller scope |
| State-machine transitions | selected — evaluator-selected-generic-scenario | Requires contract-qualified clip selection and engine test |
| Layered upper body/weapons | not-selected | No current action scope |
| Traversal/environment | not-selected | No traversal scope |
| Contact actions/interactions | selected — evaluator-selected-generic-scenario | Requires declared contact/prop criteria and engine/visual review |
| Retargeted/customizable characters | not-selected | No target rig supplied |
| Motion matching/search | not-selected | No database scope |
| Networked movement | not-selected | No controller scope |
| Runtime performance | not-selected | No runtime build |
Pack inventory and content evidence
The scrubbed inventory records 114 regular files totaling 188,335,953 bytes; 29 are FBX candidates. Filename labels are not semantic or contact evidence.
Mechanical baseline
| Finding/check | Affected scope | Potential impact | Evidence |
|---|---|---|---|
baseline: constant-track:note | 27 files; 3664 findings | Policy-dependent; inspect the affected take before assigning artist responsibility | observed-animsmith; exact affected files below |
contracts: constant-track:note | 25 files; 3394 findings | Policy-dependent; inspect the affected take before assigning artist responsibility | observed-animsmith; exact affected files below |
contracts: loop-seam-vel:error | 6 files; 6 findings | Policy-dependent; inspect the affected take before assigning artist responsibility | observed-animsmith; exact affected files below |
contracts: loop-seam-rot:error | 8 files; 8 findings | Policy-dependent; inspect the affected take before assigning artist responsibility | observed-animsmith; exact affected files below |
contracts: loop-closure:error | 1 files; 2 findings | Policy-dependent; inspect the affected take before assigning artist responsibility | observed-animsmith; exact affected files below |
Per-file contract results: 17 pass and 8 fail of 25; JSON and Markdown agree. Loop declarations are evaluation hypotheses, not proof that every action should repeat.
loop-seam-vel:error: Humanoid@FlintstonesLightCampfire.fbx, Humanoid@IdleGrillSkewerCampfire.fbx, Humanoid@KneelTossLogCampfire.fbx, Humanoid@LighterLightCampfire.fbx, Humanoid@MatchesLightCampfire.fbx, Humanoid@StickLightCampfire.fbx.
loop-seam-rot:error: Humanoid@FlintstonesLightCampfire.fbx, Humanoid@IdleGrillSkewerCampfire.fbx, Humanoid@IdleKneelCampfire.fbx, Humanoid@IdleKneelToIdleGrillSkewerCampfire.fbx, Humanoid@KneelTossLogCampfire.fbx, Humanoid@LighterLightCampfire.fbx, Humanoid@MatchesLightCampfire.fbx, Humanoid@StickLightCampfire.fbx.
loop-closure:error: Humanoid@IdleKneelToIdleGrillSkewerCampfire.fbx.
AnimSmith remediation evidence
| Source issue | Operation/declarations | Result | Independent verification | Remaining caveat |
|---|---|---|---|---|
prune-constant-tracks trial | Explicit original source/config; transform --prune-constant-tracks | 1 candidates emitted; 0 pass selected output lint | Every candidate inspected, measured, linted, diffed and checked with fix --dry-run; detailed exit records external | Unpromoted; no candidate engine/contact/visual acceptance |
Engine procedures and evidence
| Runtime | Version | Procedure | Observed result | Remaining gate |
|---|---|---|---|---|
| Unity | unspecified | None in current run | not evaluated | Disposable import, visual interaction, contact, and build test |
| Unreal Engine | unspecified | None in current run | not evaluated | Disposable import, retarget, contact, and build test |
| Godot | unspecified | None in current run | not evaluated | Disposable conversion/import and graph test |
| Bevy | unspecified | None in current run | not evaluated | Selected export handoff and runtime test |
Rig, masking, and compatibility evidence
| Pack/rig/set pair | Skeleton/retarget | Scale/axes | Root policy | Timing/blend | Overall evidence |
|---|---|---|---|---|---|
| Campfire/current evaluator | not evaluated | not evaluated | not evaluated | not evaluated | Current baseline evidence |
Limitations and unknowns
- No current engine, visual, contact, retarget, or artistic test ran.
- Commercial sources and derivatives remain external.
Changes between AnimSmith versions
AnimSmith 0.14.0 — reran 29 input baselines, 25 per-file declared contracts and one remediation trial using the official release. Added current exact-member generic scenarios and developer/artist actions. AnimSmith 0.10.0 — superseded historical evidence; none of its generated outputs or engine results is relabelled as a fresh run.
Reproduction
Official evaluator preflight: release archive, archive SHA-256 4ecf79436f9123c779edb004050da3010b44a2b392e3031facb227d7734fc33e; exact member animsmith-v0.14.0-x86_64-unknown-linux-gnu/animsmith, binary SHA-256 c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366; tag v0.14.0, peeled commit e8321ad40be5ef6f162b31f085819c039175c3c9; working-tree state: N/A (official archive). Compiled features: fbx, report. Version and required command help plus representative FBX admission passed before evaluation. Preflight: external:animsmith-0.14.0-report-refresh/preflight.json, SHA-256 7034b5043de54e7b30064230db18605d1f96d0ea1477f7b8c56ce817cc73521a.
Evaluator: official 0.14.0 Linux release, tag commit e8321ad40be5ef6f162b31f085819c039175c3c9; archive SHA-256 4ecf79436f9123c779edb004050da3010b44a2b392e3031facb227d7734fc33e; binary SHA-256 c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366, features fbx, report. Output schema v19; measurements schema v18. Version/help and representative admission passed before corpus work. Sources/configurations were hashed before and after read-only runs.
External evidence prefix: external:animsmith-0.14.0-report-refresh/evaluations/protofactor-campfire/. Commands record exact source/config arguments, exits and raw-output paths. Commercial source and generated motion remain external. Reproduction requires the authorized delivery and captured configuration; generic one-liners below do not substitute for those declarations.
baseline/command-results.json, SHA-2569adacb0d5337653263819ab80646744463c5826945f3efa3d2d30b6c967593db.contracts/command-results.json, SHA-256d98b6436e3401ccea7a3b50aee7b98384df760f9d507f1f1c51cc86cccc4e070.remediation/command-results.json, SHA-256fc78fb24af301fbfe6ddd543649150556fb4face43e1d093c9bb6445da9c89a9.selected-runtime-sets.json, SHA-256ae84f5b67615281775432a0713a3e2d813ae16a6f8e699f4802d039f789fba72.current-summary.json, SHA-256468e0386579f146b795e7b780f119c8b800d7299bfb343f6895e5b710f4b6a5c.
animsmith inspect --config <captured-config> <authorized-source>
animsmith measure --config <captured-config> --format json <authorized-source>
animsmith lint --config <captured-config> --format json <authorized-source>
The exact per-trial transform, slice range, configuration and post-check commands are in the remediation ledger. Current scenario sets are new evaluator choices; historical structured-model migration remains outside this refresh.
Sources
- Protofactor, Animset: Campfire — product context.
- AnimSmith, CLI reference and game-ready clips — evaluator boundary.
Animation pack evaluation: Protofactor Climbing Animset
Technical verdict: Usable with conditions
Evaluation completeness: partial — all source files and declared checks were reviewed; engine playback and visual quality remain untested.
Confidence: medium
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
Detailed evidence: Protofactor Climbing evidence
Technical decision
Prototype decision: Try separate wall and ladder states with controller-owned movement. Configure cyclic policy, wall distance and contacts; source correction depends on reviewed motion.
Camera scope: Full-body humanoid animations; appearance on your target character and camera is untested. Dedicated first-person arms/viewmodel use is unverified.
This animset is one of eight locally evaluated parts of Ultimate Animation Collection. The local asset revision is unknown; the collection report separates evaluated files from the dated vendor listing.
Content in evaluated inventory: Wall movement and ladder movement. Wall directions and ladder up/down are selected; ledge, jump and obstacle actions are outside the selected runtime sets. These are identified motions; gameplay behavior has not been approved.
A conditional traversal source, not a drop-in climbing controller. The selected wall set supplies eight named directions with equal durations; the ladder pair is a separate topology. Use controller-owned surface movement with the selected stationary-root files, or specify how root-motion travel and collision will work. Hand/foot contact, wall distance and ledge transitions remain acceptance gates.
observed-animsmith: the official 0.14.0 evaluator inspected and measured all 77 FBXs; 34/75 declared-contract files pass and 41 fail per output format. The findings below separate source problems, project settings and untested gameplay. Transformed outputs still need gameplay and visual checks.
Capability coverage
Content present
Filename-classified wall movement offers eight directions; ladder up/down forms a separate two-member candidate. The wider inventory also names obstacle and jump actions.
Content gaps and unknowns
Entry, top-out and fall/interrupt state paths are not classified as accepted runtime sets. Wall geometry and hand/foot contact requirements are project dependent.
Evaluation still needed
Test cyclic intent, reversals, surface alignment, collision and contact on actual wall and ladder geometry. Target-engine, IK and artistic acceptance remain open.
Runtime sets and authored motion
Use each row as a separate blend or sequence proposal. The linked appendix names its exact clips, timings and measurements; controller playback and contact quality still need testing.
| Set | Controller use | Adoption decision | Exact members |
|---|---|---|---|
| Wall eight directions | Separate traversal state | Traversal prototype. All ten selected wall/ladder members fail declared-loop contracts; pruning did not clear the WallClimbUp loop findings. Review true cycles and scene contacts (CL-LOOP). | 8 exact members |
| Ladder up and down | Separate traversal state | Ladder prototype with controller travel. Both are among the ten selected motions that fail their declared loop checks; review cyclic intent, seams and rung contacts before repetition. | 2 exact members |
Integration recipe
- Members/topology: Use the wall set in the wall plane, with horizontal/vertical input. Use ladder up/down as a separate state; select obstacle-height and wall-jump actions discretely.
- Timing/synchronization: Establish intended cyclic playback and support-contact timing before choosing synchronization. Equal durations do not prove matching hand/foot support phases. Check neighboring wall directions at intermediate weights and ladder reversals on actual geometry.
- State ownership: The selected root trajectories are stationary. The controller owns wall projection, vertical displacement and collisions. Do not use horizontal-only speed as a measure of a root-motion climb.
- Composition constraints: Keep arm/leg coupling and solve scene contact constraints. Locomotion weapon masks are not validated for climbing.
- Acceptance gate: Test entry, top-out, corner changes, interruption/fall and varying wall/ladder dimensions before treating this as a traversal system.
Technical issue register
Severity describes impact as delivered for the stated use. Residual status is explicit; untested risks are not confirmed artist defects.
| ID | Severity | Problem and impact | Primary owner | Current action | Future AnimSmith potential | Evidence/status |
|---|---|---|---|---|---|---|
| CL-LOOP | major | All ten selected wall/ladder members fail their declared-loop contracts; Humanoid@WallClimbUp.fbx still fails its declared checks after pruning. Cyclic traversal requires stable seams and contacts. Readiness guidance. | artist-author | Review genuine climb loops, correct endpoint pose/velocity and preserve contact timing. Pruning did not remove the measured loop findings. Repeating these motions still needs reviewed source loops and engine contact tests. | Current pruning reduces redundant tracks, not contact or loop defects. | Take 001 and current output lint; observed-animsmith. |
| CL-POLICY | minor | Loop-policy failures on obstacle one-shots can be expected; they must not all be reported as broken loops. Readiness guidance. | engine-config | Use non-looping obstacle actions where intended; require entry/exit continuity instead of wrap continuity. Residual: unresolved policy. | Per-clip declarations exist; project semantics remain explicit. | For example Humanoid@ClimbUp1MeterObstacleUnarmed.FBX; observed-animsmith, one-shot role inferred. |
| CL-CONTACT | major | No current test establishes wall distance, rung spacing or hand/foot lock at blended weights. Readiness guidance. | engine-config | Validate selected sets against target geometry and collision movement. Escalate authored contact defects to the artist with clip/time/geometry reproduction. Residual: unknown, not a demonstrated vendor defect. | Future generic contact diagnostics may help; no automatic climbing controller is claimed. | Scene/contact acceptance not-evaluated. |
Engine status
| Runtime | Evidence level | Technical result | Remaining gate |
|---|---|---|---|
| Unity unspecified | not-evaluated | No current import or traversal playback. | Controller, contact, visual, and build tests. |
| Unreal Engine unspecified | not-evaluated | No current import or traversal playback. | Retarget, IK, contact, and build tests. |
| Godot unspecified | not-evaluated | No current conversion or playback. | Conversion/import and graph tests. |
| Bevy unspecified | not-evaluated | No current handoff or runtime test. | glTF handoff and runtime test. |
Fit and limitations
Basic Locomotion can provide the approach/ground state, with an explicit transition into constrained traversal. Shared naming or rig conventions do not prove top-out/ground pose continuity. A wall loop cannot supply environment detection, capsule placement or a missing transition by itself.
These are proposed integration decisions (inferred), with target-controller, contact and artistic acceptance still not-evaluated.
Changes between AnimSmith versions
AnimSmith 0.14.0 — reran 77 input baselines, 75 per-file declared contracts and one remediation trial using the official release. Added current exact-member generic scenarios and developer/artist actions. AnimSmith 0.10.0 — superseded historical evidence; none of its generated outputs or engine results is relabelled as a fresh run.
Evidence status
Current evidence uses the official release and the canonical readiness ladder; commercial artifacts remain external.
Sources
- Protofactor, Animset: Climbing — product context.
- AnimSmith, CLI reference and game-ready clips — evaluation boundaries.
Animation pack evidence appendix: Protofactor Climbing Animset
Companion report: Protofactor Climbing report
Evidence status: partial — the official evaluator loaded the delivered FBXs and produced current baseline evidence.
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
The canonical readiness ladder remains authoritative.
Evaluation manifest schema: urn:animsmith:skill:animation-pack-evaluation-manifest:1.
Evaluation scope and provenance
| Field | Value |
|---|---|
| Pack/edition | Protofactor Climbing Animset; local constituent revision unknown |
| Vendor/source | Protofactor Animset: Climbing |
| Delivered scope | Authorized local commercial delivery; 77 FBXs |
| Target use | Engine-neutral traversal intake |
| Target engines | Unity, Unreal Engine, Godot, and Bevy; not evaluated |
| Target rigs/packs | No target character or current cross-pack run supplied |
| Source manifest | External scrubbed inventory, SHA-256 9646d3aa9057fbfe58d612642d1a8c6b72039a5c0f6fec8ee60f27d0fb66c12a |
| Evaluation manifest | Unavailable: current output binding was not rendered for this retained report format |
| Acquisition/license provenance | Authorized commercial bytes; no transaction or redistribution conclusion |
Evidence coverage
| Surface | Offered/delivered | Evaluated | Findings | Not evaluated and why |
|---|---|---|---|---|
| Animation files | 77 FBXs | 77 | baseline completed | mechanical and contract evidence captured |
| Rigs/export variants | Unknown | 0 | 0 | Not evaluated in this run |
| AnimSmith baseline | 77 | 77 | current findings recorded | inspect, JSON measure, JSON lint, and Markdown lint completed per file |
| Declared contracts | 75 motion-labelled files | 75 | 34 pass; 41 fail per output format | Mechanical contract results are current; historical taxonomy was not reconstructed; current generic scenarios are separately identified; artistic acceptance remains open |
| Offline visual reports | 0 | 0 | 0 | Not evaluated in this run |
| Engine import/playback | 4 runtimes | 0 | 0 | Deferred |
| Blend/mask/retarget | Unknown | 0 | 0 | Measurements completed, but no target rig or blend/mask acceptance test ran |
Claim legend
Current claims are observed-file, observed-animsmith, documentation-stated, or not-evaluated.
Evaluation manifest and taxonomy
Canonical clip-role inventory
| Canonical primary role | Logical motions | Delivered files | Evidence boundary |
|---|---|---|---|
idle-pose | 0 | 0 | Classification was not regenerated or accepted |
continuous-locomotion | 0 | 0 | Classification was not regenerated or accepted |
locomotion-transition | 0 | 0 | Classification was not regenerated or accepted |
airborne | 0 | 0 | Classification was not regenerated or accepted |
traversal | 0 | 0 | Classification was not regenerated or accepted |
action-interaction | 0 | 0 | Classification was not regenerated or accepted |
reaction-death | 0 | 0 | Classification was not regenerated or accepted |
emote-cinematic | 0 | 0 | Classification was not regenerated or accepted |
other-unknown | 0 | 0 | Classification was not regenerated or accepted |
| Total | 0 | 0 | 77 delivered FBXs remain unclassified |
Runtime-set inventory
New evaluator-selected generic scenarios: current source bytes establish the exact members, take names, durations, and measured roots below. Names suggest gameplay roles; topology and semantic intent are hypotheses requiring clip review. These are not reconstructed historical manifests or measured collection-output sets. Every listed member uses Take 001; full identities are in the external selected-set ledger.
| Runtime set | Type | Members/variants | Grouping evidence | Validation status |
|---|---|---|---|---|
| Wall eight directions | directional-blend | Humanoid@WallClimbUp.fbx, Humanoid@WallClimbUpLeft.fbx, Humanoid@WallClimbLeft.fbx, Humanoid@WallClimbDownLeft.fbx, Humanoid@WallClimbDown.fbx, Humanoid@WallClimbDownRight.fbx, Humanoid@WallClimbRight.fbx, Humanoid@WallClimbUpRight.fbx; Take 001 | New evaluator-selected scenario; observed bytes/timing, inferred gameplay roles | Mechanical measurements current; set/engine/visual acceptance open |
| Ladder up and down | directional-blend | Humanoid@ClimbUpLadder.fbx, Humanoid@ClimbDownLadder.fbx; Take 001 | New evaluator-selected scenario; observed bytes/timing, inferred gameplay roles | Mechanical measurements current; set/engine/visual acceptance open |
Exact runtime members
Current source members and measurements for the selected runtime scenarios. Selection is an evaluator hypothesis; source identity and measured values are retained below.
| Set/profile | Role or coordinate | Exact members | Variant/type | Timing or motion | Runtime contract |
|---|---|---|---|---|---|
| Wall eight directions | Proposed up (0,1) | Humanoid@WallClimbUp.fbx::Take 001 | set_type=directional-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Wall eight directions | Proposed up-left (-1,1) | Humanoid@WallClimbUpLeft.fbx::Take 001 | set_type=directional-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Wall eight directions | Proposed left (-1,0) | Humanoid@WallClimbLeft.fbx::Take 001 | set_type=directional-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Wall eight directions | Proposed down-left (-1,-1) | Humanoid@WallClimbDownLeft.fbx::Take 001 | set_type=directional-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Wall eight directions | Proposed down (0,-1) | Humanoid@WallClimbDown.fbx::Take 001 | set_type=directional-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Wall eight directions | Proposed down-right (1,-1) | Humanoid@WallClimbDownRight.fbx::Take 001 | set_type=directional-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Wall eight directions | Proposed right (1,0) | Humanoid@WallClimbRight.fbx::Take 001 | set_type=directional-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Wall eight directions | Proposed up-right (1,1) | Humanoid@WallClimbUpRight.fbx::Take 001 | set_type=directional-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Ladder up and down | Proposed up (0,1) | Humanoid@ClimbUpLadder.fbx::Take 001 | set_type=directional-blend | duration=1.200 s | loop=unknown; movement=controller; contact=not-evaluated |
| Ladder up and down | Proposed down | Humanoid@ClimbDownLadder.fbx::Take 001 | set_type=directional-blend | duration=1.200 s | loop=unknown; movement=controller; contact=not-evaluated |
Pipeline-stage coverage
| Stage | Coverage state | Evidence / remaining gate |
|---|---|---|
| Acquire | partially-evaluated | Authorized local inventory |
| Preserve raw | evaluated-clean | Source unchanged |
| Inspect | evaluated-finding | 77 baseline attempts completed |
| Segment | not-evaluated | No separate segmentation trial selected |
| Root motion | not-evaluated | Measurements completed; no root-motion policy or controller acceptance was evaluated |
| Conform | not-evaluated | No target rig or conformance policy was selected |
| Validate | evaluated-finding | Lint completed for every FBX |
| Optimize | partially-evaluated | One external constant-track candidate was generated; source was unchanged and runtime equivalence remains unproved |
| Export | partially-evaluated | Candidate was written as an external GLB only; no engine handoff was selected |
| Gate/report | partially-evaluated | Current baseline, remediation, and boundary recorded |
Readiness evidence by clip set
| Role or runtime set | File-ready / clip-ready | Set-ready / rig-use | Runtime / acceptance boundary |
|---|---|---|---|
| Entire delivered corpus | evaluated-finding | not-evaluated | not-evaluated |
| Named current generic scenarios above | Exact sources/takes measured; declared lint conditions remain | Candidate topology inferred; no set readiness approval | Target controller, contacts and artistic acceptance not evaluated |
Validation-profile status
| Validation profile | Selection | Result / next evidence |
|---|---|---|
| Marketplace intake | selected — evaluator-selected-generic-scenario | Inventory and current baseline captured |
| Blended locomotion | not-selected | No current locomotion scope |
| Root-motion controller | selected — evaluator-selected-generic-scenario | Measurements completed; controller trajectory policy and engine acceptance remain |
| State-machine transitions | selected — evaluator-selected-generic-scenario | Requires contract-qualified clip selection and engine test |
| Layered upper body/weapons | not-selected | No current action scope |
| Traversal/environment | selected — evaluator-selected-generic-scenario | Requires traversal and contact review |
| Contact actions/interactions | selected — evaluator-selected-generic-scenario | Requires obstacle/ladder evidence |
| Retargeted/customizable characters | not-selected | No target rig supplied |
| Motion matching/search | not-selected | No database scope |
| Networked movement | not-selected | No controller scope |
| Runtime performance | not-selected | No runtime build |
Pack inventory and content evidence
The scrubbed inventory records 179 regular files totaling 194,505,327 bytes; 77 are FBX candidates. Filename labels are not semantic, contact, or root-motion evidence.
Mechanical baseline
| Finding/check | Affected scope | Potential impact | Evidence |
|---|---|---|---|
baseline: constant-track:note | 77 files; 9011 findings | Policy-dependent; inspect the affected take before assigning artist responsibility | observed-animsmith; exact affected files below |
contracts: constant-track:note | 75 files; 8753 findings | Policy-dependent; inspect the affected take before assigning artist responsibility | observed-animsmith; exact affected files below |
contracts: loop-seam-vel:error | 39 files; 39 findings | Policy-dependent; inspect the affected take before assigning artist responsibility | observed-animsmith; exact affected files below |
contracts: loop-seam-rot:error | 41 files; 41 findings | Policy-dependent; inspect the affected take before assigning artist responsibility | observed-animsmith; exact affected files below |
contracts: loop-closure:error | 22 files; 26 findings | Policy-dependent; inspect the affected take before assigning artist responsibility | observed-animsmith; exact affected files below |
Per-file contract results: 34 pass and 41 fail of 75; JSON and Markdown agree. Loop declarations are evaluation hypotheses, not proof that every action should repeat.
loop-seam-vel:error: Humanoid@ClimbDownLadder.fbx, Humanoid@ClimbDownLadder_RM.fbx, Humanoid@ClimbUp1MeterObstacleUnarmed.FBX, Humanoid@ClimbUp1MeterObstacleUnarmed_RM.FBX, Humanoid@ClimbUp2MetersObstacleUnarmed.FBX, Humanoid@ClimbUp2MetersObstacleUnarmed_RM.FBX, Humanoid@ClimbUpHalfMeterObstacleLeftUnarmed.fbx, Humanoid@ClimbUpHalfMeterObstacleLeftUnarmed_RM.fbx, Humanoid@ClimbUpHalfMeterObstacleRightUnarmed.fbx, Humanoid@ClimbUpHalfMeterObstacleRightUnarmed_RM.fbx, Humanoid@ClimbUpLadder.fbx, Humanoid@ClimbUpLadder_RM.fbx, Humanoid@FallingUnarmed.FBX, Humanoid@IdleLookAroundWallClimb.fbx, Humanoid@IdleWallClimb.fbx, Humanoid@WallClimbDown.fbx, Humanoid@WallClimbDown_RM.fbx, Humanoid@WallClimbDownLeft.fbx, Humanoid@WallClimbDownLeft_RM.fbx, Humanoid@WallClimbDownRight.fbx, Humanoid@WallClimbDownRight_RM.fbx, Humanoid@WallClimbLeft.fbx, Humanoid@WallClimbLeft_RM.fbx, Humanoid@WallClimbRight.fbx, Humanoid@WallClimbRight_RM.fbx, Humanoid@WallClimbUp.fbx, Humanoid@WallClimbUp_RM.fbx, Humanoid@WallClimbUpLeft.fbx, Humanoid@WallClimbUpLeft_RM.fbx, Humanoid@WallClimbUpRight.fbx, Humanoid@WallClimbUpRight_RM.fbx, Humanoid@WallJumpDown.fbx, Humanoid@WallJumpDown_RM.fbx, Humanoid@WallJumpLeft.fbx, Humanoid@WallJumpLeft_RM.fbx, Humanoid@WallJumpRight.fbx, Humanoid@WallJumpRight_RM.fbx, Humanoid@WallJumpUp.fbx, Humanoid@WallJumpUp_RM.fbx.
loop-seam-rot:error: Humanoid@ClimbDownLadder.fbx, Humanoid@ClimbDownLadder_RM.fbx, Humanoid@ClimbUp1MeterObstacleUnarmed.FBX, Humanoid@ClimbUp1MeterObstacleUnarmed_RM.FBX, Humanoid@ClimbUp2MetersObstacleUnarmed.FBX, Humanoid@ClimbUp2MetersObstacleUnarmed_RM.FBX, Humanoid@ClimbUpHalfMeterObstacleLeftUnarmed.fbx, Humanoid@ClimbUpHalfMeterObstacleLeftUnarmed_RM.fbx, Humanoid@ClimbUpHalfMeterObstacleRightUnarmed.fbx, Humanoid@ClimbUpHalfMeterObstacleRightUnarmed_RM.fbx, Humanoid@ClimbUpLadder.fbx, Humanoid@ClimbUpLadder_RM.fbx, Humanoid@FallingUnarmed.FBX, Humanoid@IdleLookAroundWallClimb.fbx, Humanoid@IdlePrepareJumpOppositeWallLeft.fbx, Humanoid@IdlePrepareJumpOppositeWallRight.fbx, Humanoid@IdleWallClimb.fbx, Humanoid@WallClimbDown.fbx, Humanoid@WallClimbDown_RM.fbx, Humanoid@WallClimbDownLeft.fbx, Humanoid@WallClimbDownLeft_RM.fbx, Humanoid@WallClimbDownRight.fbx, Humanoid@WallClimbDownRight_RM.fbx, Humanoid@WallClimbLeft.fbx, Humanoid@WallClimbLeft_RM.fbx, Humanoid@WallClimbRight.fbx, Humanoid@WallClimbRight_RM.fbx, Humanoid@WallClimbUp.fbx, Humanoid@WallClimbUp_RM.fbx, Humanoid@WallClimbUpLeft.fbx, Humanoid@WallClimbUpLeft_RM.fbx, Humanoid@WallClimbUpRight.fbx, Humanoid@WallClimbUpRight_RM.fbx, Humanoid@WallJumpDown.fbx, Humanoid@WallJumpDown_RM.fbx, Humanoid@WallJumpLeft.fbx, Humanoid@WallJumpLeft_RM.fbx, Humanoid@WallJumpRight.fbx, Humanoid@WallJumpRight_RM.fbx, Humanoid@WallJumpUp.fbx, Humanoid@WallJumpUp_RM.fbx.
loop-closure:error: Humanoid@ClimbDownLadder_RM.fbx, Humanoid@ClimbUp1MeterObstacleUnarmed.FBX, Humanoid@ClimbUp1MeterObstacleUnarmed_RM.FBX, Humanoid@ClimbUp2MetersObstacleUnarmed_RM.FBX, Humanoid@ClimbUpHalfMeterObstacleLeftUnarmed.fbx, Humanoid@ClimbUpHalfMeterObstacleLeftUnarmed_RM.fbx, Humanoid@ClimbUpHalfMeterObstacleRightUnarmed.fbx, Humanoid@ClimbUpHalfMeterObstacleRightUnarmed_RM.fbx, Humanoid@ClimbUpLadder_RM.fbx, Humanoid@WallClimbDown_RM.fbx, Humanoid@WallClimbDownLeft_RM.fbx, Humanoid@WallClimbDownRight_RM.fbx, Humanoid@WallClimbLeft.fbx, Humanoid@WallClimbLeft_RM.fbx, Humanoid@WallClimbRight_RM.fbx, Humanoid@WallClimbUp_RM.fbx, Humanoid@WallClimbUpLeft_RM.fbx, Humanoid@WallClimbUpRight_RM.fbx, Humanoid@WallJumpDown_RM.fbx, Humanoid@WallJumpLeft_RM.fbx, Humanoid@WallJumpRight_RM.fbx, Humanoid@WallJumpUp_RM.fbx.
AnimSmith remediation evidence
| Source issue | Operation/declarations | Result | Independent verification | Remaining caveat |
|---|---|---|---|---|
prune-constant-tracks trial | Explicit original source/config; transform --prune-constant-tracks | 1 candidates emitted; 0 pass selected output lint | Every candidate inspected, measured, linted, diffed and checked with fix --dry-run; detailed exit records external | Unpromoted; no candidate engine/contact/visual acceptance |
Engine procedures and evidence
| Runtime | Version | Procedure | Observed result | Remaining gate |
|---|---|---|---|---|
| Unity | unspecified | None in current run | not evaluated | Disposable import, traversal, contact, and visual test |
| Unreal Engine | unspecified | None in current run | not evaluated | Disposable import, retarget, IK, contact, and build test |
| Godot | unspecified | None in current run | not evaluated | Disposable conversion/import and graph test |
| Bevy | unspecified | None in current run | not evaluated | Selected export handoff and runtime test |
Rig, masking, and compatibility evidence
| Pack/rig/set pair | Skeleton/retarget | Scale/axes | Root policy | Timing/blend | Overall evidence |
|---|---|---|---|---|---|
| Climbing/current evaluator | not evaluated | not evaluated | not evaluated | not evaluated | Current baseline evidence |
Limitations and unknowns
- No current engine, visual, contact, retarget, or artistic test ran.
- Commercial sources and derivatives remain external.
Changes between AnimSmith versions
AnimSmith 0.14.0 — reran 77 input baselines, 75 per-file declared contracts and one remediation trial using the official release. Added current exact-member generic scenarios and developer/artist actions. AnimSmith 0.10.0 — superseded historical evidence; none of its generated outputs or engine results is relabelled as a fresh run.
Reproduction
Official evaluator preflight: release archive, archive SHA-256 4ecf79436f9123c779edb004050da3010b44a2b392e3031facb227d7734fc33e; exact member animsmith-v0.14.0-x86_64-unknown-linux-gnu/animsmith, binary SHA-256 c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366; tag v0.14.0, peeled commit e8321ad40be5ef6f162b31f085819c039175c3c9; working-tree state: N/A (official archive). Compiled features: fbx, report. Version and required command help plus representative FBX admission passed before evaluation. Preflight: external:animsmith-0.14.0-report-refresh/preflight.json, SHA-256 7034b5043de54e7b30064230db18605d1f96d0ea1477f7b8c56ce817cc73521a.
Evaluator: official 0.14.0 Linux release, tag commit e8321ad40be5ef6f162b31f085819c039175c3c9; archive SHA-256 4ecf79436f9123c779edb004050da3010b44a2b392e3031facb227d7734fc33e; binary SHA-256 c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366, features fbx, report. Output schema v19; measurements schema v18. Version/help and representative admission passed before corpus work. Sources/configurations were hashed before and after read-only runs.
External evidence prefix: external:animsmith-0.14.0-report-refresh/evaluations/protofactor-climbing/. Commands record exact source/config arguments, exits and raw-output paths. Commercial source and generated motion remain external. Reproduction requires the authorized delivery and captured configuration; generic one-liners below do not substitute for those declarations.
baseline/command-results.json, SHA-256a0b70aa8d224845797d296567e56d430ec4a9b4f18179f1a4ba23c921338c519.contracts/command-results.json, SHA-256c736fb5f2f79b8044d9a2fc51707470eccabeda81a6a9c878fd6b2847d8c2046.remediation/command-results.json, SHA-256dca43e1b41a0561de9cd395f266cf5aeab5cb239a20ae8c407d1753f33c3387f.selected-runtime-sets.json, SHA-256f2a494df4894caff8640995b88aff8a0a7d435f9d231db0f421ede4df182dccf.current-summary.json, SHA-256f047b5c288f4ba004f4635e6319f232b2616f5a0c3ee492ddbef7a527b1155ef.
animsmith inspect --config <captured-config> <authorized-source>
animsmith measure --config <captured-config> --format json <authorized-source>
animsmith lint --config <captured-config> --format json <authorized-source>
The exact per-trial transform, slice range, configuration and post-check commands are in the remediation ledger. Current scenario sets are new evaluator choices; historical structured-model migration remains outside this refresh.
Sources
- Protofactor, Animset: Climbing — product context.
- AnimSmith, CLI reference and game-ready clips — evaluator boundary.
Animation pack evaluation: Protofactor Injured Animset
Technical verdict: Usable with conditions
Evaluation completeness: partial — all source files and declared checks were reviewed; engine playback and visual quality remain untested.
Confidence: medium
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
Detailed evidence: Protofactor Injured evidence
Technical decision
Prototype decision: Try a separate style-local speed state for each injury style. Configure speed and healthy-state transitions; foot contacts and character fit remain unknown.
Camera scope: Full-body humanoid animations; appearance on your target character and camera is untested. Dedicated first-person arms/viewmodel use is unverified.
This animset is one of eight locally evaluated parts of Ultimate Animation Collection. The local asset revision is unknown; the collection report separates evaluated files from the dated vendor listing.
Content in evaluated inventory: Seven idle/walk/run injury styles. Each A–G style has an evaluated three-file candidate; filenames do not establish a severity order or recovery path. These are identified motions; gameplay behavior has not been approved.
Use as seven separate injury-style locomotion candidates. Each proposed style has idle/walk/run members; there is no evidence that A–G are interchangeable directions or severity levels. Keep the selected style coherent, let the kinematic controller own motion, and check walk/run contact timing and transitions to healthy locomotion.
observed-animsmith: the official 0.14.0 evaluator inspected and measured all 72 FBXs; 28/70 declared-contract files pass and 42 fail per output format. The findings below separate source problems, project settings and untested gameplay. Transformed outputs still need gameplay and visual checks.
Capability coverage
Content present
Filename-classified A–G styles each supply idle, walk and run candidates. Each style is a distinct three-member speed-state hypothesis.
Content gaps and unknowns
The letters do not classify severity or an ordered progression. Recovery and healthy-state handoffs have no accepted set classification here; this does not establish absence from the wider source.
Evaluation still needed
Test style choice, speed calibration, planted-foot behavior, cross-style and healthy-state transitions on the target character. Engine, retarget and visual acceptance remain open.
Runtime sets and authored motion
Use each row as a separate blend or sequence proposal. The linked appendix names its exact clips, timings and measurements; controller playback and contact quality still need testing.
| Set | Controller use | Adoption decision | Exact members |
|---|---|---|---|
| Injury style A speed | Style-local idle/walk/run; controller travel | Style-local prototype. RunInjuredA has loop seam findings; review true cycles and foot contacts before speed blending (IN-LOOP). | 3 exact members |
| Injury style B speed | Style-local idle/walk/run; controller travel | Style-local prototype. Selected injury loops mostly fail their declared checks; review this style’s loop intent and contacts before speed blending. | 3 exact members |
| Injury style C speed | Style-local idle/walk/run; controller travel | Style-local prototype. Selected injury loops mostly fail their declared checks; review this style’s loop intent and contacts before speed blending. | 3 exact members |
| Injury style D speed | Style-local idle/walk/run; controller travel | Style-local prototype. Selected injury loops mostly fail their declared checks; review this style’s loop intent and contacts before speed blending. | 3 exact members |
| Injury style E speed | Style-local idle/walk/run; controller travel | Style-local prototype. Selected injury loops mostly fail their declared checks; review this style’s loop intent and contacts before speed blending. | 3 exact members |
| Injury style F speed | Style-local idle/walk/run; controller travel | Style-local prototype. Selected injury loops mostly fail their declared checks; review this style’s loop intent and contacts before speed blending. | 3 exact members |
| Injury style G speed | Style-local idle/walk/run; controller travel | Style-local prototype. Selected injury loops mostly fail their declared checks; review this style’s loop intent and contacts before speed blending. | 3 exact members |
Integration recipe
- Members/topology: Create one idle/walk/run tree per chosen style A–G. Treat style changes as explicit state transitions; do not distribute injury letters around a direction tree.
- Timing/synchronization: Establish intended cyclic playback and support-contact timing before choosing synchronization. Walks measure 1.333 s; runs measure 0.700 or 0.800 s. Match support-foot timing between moving clips; an almost-stationary idle gait phase is not a useful contact anchor.
Humanoid@RunInjuredB.fbxpasses a non-looping declaration, which does not establish it as a clean repeating run. - State ownership: Selected in-place roots are stationary. Tune speed thresholds to stride/contact behavior, not their measured zero root speed. Keep root-motion alternatives under a separate movement policy.
- Composition constraints: Preserve the limp and compensating torso pose. Upper-body masks can erase the injury expression; validate any weapon layer separately.
- Acceptance gate: Test start/stop, velocity changes, healthy-to-injured transitions and interruptions on the target character.
Technical issue register
Severity describes impact as delivered for the stated use. Residual status is explicit; untested risks are not confirmed artist defects.
| ID | Severity | Problem and impact | Primary owner | Current action | Future AnimSmith potential | Evidence/status |
|---|---|---|---|---|---|---|
| IN-LOOP | major | Twenty of the 21 selected idle/walk/run members fail their declared contracts. Humanoid@RunInjuredA.fbx has loop-closure/seam findings; repeated playback and speed blending need review. Readiness guidance. | artist-author | Correct intended loop endpoints and support-foot timing. Fourteen gait-anchor candidates were generated; thirteen still fail declared lint. Residual adoption remains unresolved; no candidates are promoted and thirteen retain contract findings. | Current anchoring changes phase; it is not proof of a clean seam or preserved limp. | Exact selected files, Take 001, current source/output lint; observed-animsmith. |
| IN-STYLE | minor | Mixing lettered styles as if they were directions or scalar severity levels has no established semantic basis. Readiness guidance. | engine-config | Choose a style and keep idle/walk/run coherent; explicitly review any style transition. Residual: project decision, not proven vendor defect. | Explicit set declarations make the choice reproducible. | Names and timing observed; artistic relationship not-evaluated. |
| IN-TRACK | minor | Dense constant tracks may add storage without useful motion. Readiness guidance. | animsmith-current-declared | Pruning reduced tracks in WalkInjuredA, but its loop check still fails. Compare required channels and engine playback before using that output. Unresolved. | Current pruning exists; performance gain remains unmeasured. | Fresh prune output and diff; observed-animsmith. |
Engine status
| Runtime | Evidence level | Technical result | Remaining gate |
|---|---|---|---|
| Unity unspecified | not-evaluated | No current import or playback. | Blend, mask, visual, and build tests. |
| Unreal Engine unspecified | not-evaluated | No current import or playback. | Retarget, graph, and build tests. |
| Godot unspecified | not-evaluated | No current conversion or playback. | Conversion/import and graph tests. |
| Bevy unspecified | not-evaluated | No current handoff or runtime test. | glTF handoff and runtime test. |
Fit and limitations
Combine with Basic Locomotion as a healthy/injured state switch, with speed and stance changes handled deliberately. Combining with weapon packs requires checking whether a grip/aim layer conflicts with the injured torso and supported limb. No cross-pack contact, masking or artistic match is approved.
These are proposed integration decisions (inferred), with target-controller, contact and artistic acceptance still not-evaluated.
Changes between AnimSmith versions
AnimSmith 0.14.0 — reran 72 input baselines, 70 per-file declared contracts and 15 remediation trials using the official release. Added current exact-member generic scenarios and developer/artist actions. AnimSmith 0.10.0 — superseded historical evidence; none of its generated outputs or engine results is relabelled as a fresh run.
Evidence status
Current evidence uses the official release and the canonical readiness ladder; commercial artifacts remain external.
Sources
- Protofactor, Animset: Injured — product context.
- AnimSmith, CLI reference and game-ready clips — evaluation boundaries.
Animation pack evidence appendix: Protofactor Injured Animset
Companion report: Protofactor Injured report
Evidence status: partial — the official evaluator loaded the delivered FBXs and produced current baseline evidence.
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
The canonical readiness ladder remains authoritative.
Evaluation manifest schema: urn:animsmith:skill:animation-pack-evaluation-manifest:1.
Evaluation scope and provenance
| Field | Value |
|---|---|
| Pack/edition | Protofactor Injured Animset; local constituent revision unknown |
| Vendor/source | Protofactor Animset: Injured |
| Delivered scope | Authorized local commercial delivery; 72 FBXs |
| Target use | Engine-neutral injured-state intake |
| Target engines | Unity, Unreal Engine, Godot, and Bevy; not evaluated |
| Target rigs/packs | No target character or current cross-pack run supplied |
| Source manifest | External scrubbed inventory, SHA-256 e60ef65d2964a9eaa497cb6f8ee898542f88a67719625633cfee64ad7816e7cf |
| Evaluation manifest | Unavailable: current output binding was not rendered for this retained report format |
| Acquisition/license provenance | Authorized commercial bytes; no transaction or redistribution conclusion |
Evidence coverage
| Surface | Offered/delivered | Evaluated | Findings | Not evaluated and why |
|---|---|---|---|---|
| Animation files | 72 FBXs | 72 | baseline completed | mechanical and contract evidence captured |
| Rigs/export variants | Unknown | 0 | 0 | Not evaluated in this run |
| AnimSmith baseline | 72 | 72 | current findings recorded | inspect, JSON measure, JSON lint, and Markdown lint completed per file |
| Declared contracts | 70 motion-labelled files | 70 | 28 pass; 42 fail per output format | Mechanical contract results are current; historical taxonomy was not reconstructed; current generic scenarios are separately identified; artistic acceptance remains open |
| Offline visual reports | 0 | 0 | 0 | Not evaluated in this run |
| Engine import/playback | 4 runtimes | 0 | 0 | Deferred |
| Blend/mask/retarget | Unknown | 0 | 0 | Measurements completed, but no target rig or blend/mask acceptance test ran |
Claim legend
Current claims are observed-file, observed-animsmith, documentation-stated, or not-evaluated.
Evaluation manifest and taxonomy
Canonical clip-role inventory
| Canonical primary role | Logical motions | Delivered files | Evidence boundary |
|---|---|---|---|
idle-pose | 0 | 0 | Classification was not regenerated or accepted |
continuous-locomotion | 0 | 0 | Classification was not regenerated or accepted |
locomotion-transition | 0 | 0 | Classification was not regenerated or accepted |
airborne | 0 | 0 | Classification was not regenerated or accepted |
traversal | 0 | 0 | Classification was not regenerated or accepted |
action-interaction | 0 | 0 | Classification was not regenerated or accepted |
reaction-death | 0 | 0 | Classification was not regenerated or accepted |
emote-cinematic | 0 | 0 | Classification was not regenerated or accepted |
other-unknown | 0 | 0 | Classification was not regenerated or accepted |
| Total | 0 | 0 | 72 delivered FBXs remain unclassified |
Runtime-set inventory
New evaluator-selected generic scenarios: current source bytes establish the exact members, take names, durations, and measured roots below. Names suggest gameplay roles; topology and semantic intent are hypotheses requiring clip review. These are not reconstructed historical manifests or measured collection-output sets. Every listed member uses Take 001; full identities are in the external selected-set ledger.
| Runtime set | Type | Members/variants | Grouping evidence | Validation status |
|---|---|---|---|---|
| Injury style A speed | speed-blend | Humanoid@IdleInjuredA.fbx, Humanoid@WalkInjuredA.fbx, Humanoid@RunInjuredA.fbx; Take 001 | New evaluator-selected scenario; observed bytes/timing, inferred gameplay roles | Mechanical measurements current; set/engine/visual acceptance open |
| Injury style B speed | speed-blend | Humanoid@IdleInjuredB.fbx, Humanoid@WalkInjuredB.fbx, Humanoid@RunInjuredB.fbx; Take 001 | New evaluator-selected scenario; observed bytes/timing, inferred gameplay roles | Mechanical measurements current; set/engine/visual acceptance open |
| Injury style C speed | speed-blend | Humanoid@IdleInjuredC.fbx, Humanoid@WalkInjuredC.fbx, Humanoid@RunInjuredC.fbx; Take 001 | New evaluator-selected scenario; observed bytes/timing, inferred gameplay roles | Mechanical measurements current; set/engine/visual acceptance open |
| Injury style D speed | speed-blend | Humanoid@IdleInjuredD.fbx, Humanoid@WalkInjuredD.fbx, Humanoid@RunInjuredD.fbx; Take 001 | New evaluator-selected scenario; observed bytes/timing, inferred gameplay roles | Mechanical measurements current; set/engine/visual acceptance open |
| Injury style E speed | speed-blend | Humanoid@IdleInjuredE.fbx, Humanoid@WalkInjuredE.fbx, Humanoid@RunInjuredE.fbx; Take 001 | New evaluator-selected scenario; observed bytes/timing, inferred gameplay roles | Mechanical measurements current; set/engine/visual acceptance open |
| Injury style F speed | speed-blend | Humanoid@IdleInjuredF.fbx, Humanoid@WalkInjuredF.fbx, Humanoid@RunInjuredF.fbx; Take 001 | New evaluator-selected scenario; observed bytes/timing, inferred gameplay roles | Mechanical measurements current; set/engine/visual acceptance open |
| Injury style G speed | speed-blend | Humanoid@IdleInjuredG.fbx, Humanoid@WalkInjuredG.fbx, Humanoid@RunInjuredG.fbx; Take 001 | New evaluator-selected scenario; observed bytes/timing, inferred gameplay roles | Mechanical measurements current; set/engine/visual acceptance open |
Exact runtime members
Current source members and measurements for the selected runtime scenarios. Selection is an evaluator hypothesis; source identity and measured values are retained below.
| Set/profile | Role or coordinate | Exact members | Variant/type | Timing or motion | Runtime contract |
|---|---|---|---|---|---|
| Injury style A speed | Proposed idle | Humanoid@IdleInjuredA.fbx::Take 001 | set_type=speed-blend | duration=2.000 s | loop=unknown; movement=controller; contact=not-evaluated |
| Injury style A speed | Proposed walk | Humanoid@WalkInjuredA.fbx::Take 001 | set_type=speed-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Injury style A speed | Proposed run | Humanoid@RunInjuredA.fbx::Take 001 | set_type=speed-blend | duration=0.800 s | loop=unknown; movement=controller; contact=not-evaluated |
| Injury style B speed | Proposed idle | Humanoid@IdleInjuredB.fbx::Take 001 | set_type=speed-blend | duration=2.000 s | loop=unknown; movement=controller; contact=not-evaluated |
| Injury style B speed | Proposed walk | Humanoid@WalkInjuredB.fbx::Take 001 | set_type=speed-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Injury style B speed | Proposed run | Humanoid@RunInjuredB.fbx::Take 001 | set_type=speed-blend | duration=0.800 s | loop=unknown; movement=controller; contact=not-evaluated |
| Injury style C speed | Proposed idle | Humanoid@IdleInjuredC.fbx::Take 001 | set_type=speed-blend | duration=2.000 s | loop=unknown; movement=controller; contact=not-evaluated |
| Injury style C speed | Proposed walk | Humanoid@WalkInjuredC.fbx::Take 001 | set_type=speed-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Injury style C speed | Proposed run | Humanoid@RunInjuredC.fbx::Take 001 | set_type=speed-blend | duration=0.700 s | loop=unknown; movement=controller; contact=not-evaluated |
| Injury style D speed | Proposed idle | Humanoid@IdleInjuredD.fbx::Take 001 | set_type=speed-blend | duration=1.667 s | loop=unknown; movement=controller; contact=not-evaluated |
| Injury style D speed | Proposed walk | Humanoid@WalkInjuredD.fbx::Take 001 | set_type=speed-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Injury style D speed | Proposed run | Humanoid@RunInjuredD.fbx::Take 001 | set_type=speed-blend | duration=0.700 s | loop=unknown; movement=controller; contact=not-evaluated |
| Injury style E speed | Proposed idle | Humanoid@IdleInjuredE.fbx::Take 001 | set_type=speed-blend | duration=2.000 s | loop=unknown; movement=controller; contact=not-evaluated |
| Injury style E speed | Proposed walk | Humanoid@WalkInjuredE.fbx::Take 001 | set_type=speed-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Injury style E speed | Proposed run | Humanoid@RunInjuredE.fbx::Take 001 | set_type=speed-blend | duration=0.800 s | loop=unknown; movement=controller; contact=not-evaluated |
| Injury style F speed | Proposed idle | Humanoid@IdleInjuredF.fbx::Take 001 | set_type=speed-blend | duration=2.000 s | loop=unknown; movement=controller; contact=not-evaluated |
| Injury style F speed | Proposed walk | Humanoid@WalkInjuredF.fbx::Take 001 | set_type=speed-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Injury style F speed | Proposed run | Humanoid@RunInjuredF.fbx::Take 001 | set_type=speed-blend | duration=0.800 s | loop=unknown; movement=controller; contact=not-evaluated |
| Injury style G speed | Proposed idle | Humanoid@IdleInjuredG.fbx::Take 001 | set_type=speed-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Injury style G speed | Proposed walk | Humanoid@WalkInjuredG.fbx::Take 001 | set_type=speed-blend | duration=1.333 s | loop=unknown; movement=controller; contact=not-evaluated |
| Injury style G speed | Proposed run | Humanoid@RunInjuredG.fbx::Take 001 | set_type=speed-blend | duration=0.800 s | loop=unknown; movement=controller; contact=not-evaluated |
Pipeline-stage coverage
| Stage | Coverage state | Evidence / remaining gate |
|---|---|---|
| Acquire | partially-evaluated | Authorized local inventory |
| Preserve raw | evaluated-clean | Source unchanged |
| Inspect | evaluated-finding | 72 baseline attempts completed |
| Segment | not-evaluated | No separate segmentation trial selected |
| Root motion | not-evaluated | Measurements completed; no root-motion policy or controller acceptance was evaluated |
| Conform | partially-evaluated | Fourteen declared in-place gait anchors generated external candidates; no target-rig or engine acceptance was run |
| Validate | evaluated-finding | Lint completed for every FBX |
| Optimize | partially-evaluated | One external constant-track candidate was generated; source was unchanged and runtime equivalence remains unproved |
| Export | partially-evaluated | Candidates were written as external GLBs only; no engine handoff was selected |
| Gate/report | partially-evaluated | Current baseline, remediation, and boundary recorded |
Readiness evidence by clip set
| Role or runtime set | File-ready / clip-ready | Set-ready / rig-use | Runtime / acceptance boundary |
|---|---|---|---|
| Entire delivered corpus | evaluated-finding | not-evaluated | not-evaluated |
| Named current generic scenarios above | Exact sources/takes measured; declared lint conditions remain | Candidate topology inferred; no set readiness approval | Target controller, contacts and artistic acceptance not evaluated |
Validation-profile status
| Validation profile | Selection | Result / next evidence |
|---|---|---|
| Marketplace intake | selected — evaluator-selected-generic-scenario | Inventory and current baseline captured |
| Blended locomotion | selected — evaluator-selected-generic-scenario | Measurements completed; selected IP/RM ownership, blend policy, and engine acceptance remain |
| Root-motion controller | selected — evaluator-selected-generic-scenario | Measurements completed; controller trajectory policy and engine acceptance remain |
| State-machine transitions | selected — evaluator-selected-generic-scenario | Requires contract-qualified clip selection and engine test |
| Layered upper body/weapons | not-selected | No current action scope |
| Traversal/environment | not-selected | No traversal scope |
| Contact actions/interactions | not-selected | No current contact scope |
| Retargeted/customizable characters | not-selected | No target rig supplied |
| Motion matching/search | not-selected | No database scope |
| Networked movement | not-selected | No controller scope |
| Runtime performance | not-selected | No runtime build |
Pack inventory and content evidence
The scrubbed inventory records 171 regular files totaling 217,461,232 bytes; 72 are FBX candidates. Filename labels are not semantic, speed, loop, or blend evidence.
Mechanical baseline
| Finding/check | Affected scope | Potential impact | Evidence |
|---|---|---|---|
baseline: constant-track:note | 72 files; 9915 findings | Policy-dependent; inspect the affected take before assigning artist responsibility | observed-animsmith; exact affected files below |
contracts: constant-track:note | 70 files; 9644 findings | Policy-dependent; inspect the affected take before assigning artist responsibility | observed-animsmith; exact affected files below |
contracts: loop-seam-vel:error | 31 files; 31 findings | Policy-dependent; inspect the affected take before assigning artist responsibility | observed-animsmith; exact affected files below |
contracts: loop-seam-rot:error | 42 files; 42 findings | Policy-dependent; inspect the affected take before assigning artist responsibility | observed-animsmith; exact affected files below |
contracts: loop-closure:error | 15 files; 15 findings | Policy-dependent; inspect the affected take before assigning artist responsibility | observed-animsmith; exact affected files below |
Per-file contract results: 28 pass and 42 fail of 70; JSON and Markdown agree. Loop declarations are evaluation hypotheses, not proof that every action should repeat.
loop-seam-vel:error: Humanoid@IdleInjuredA.fbx, Humanoid@IdleInjuredD.fbx, Humanoid@IdleInjuredF.fbx, Humanoid@IdleSitInjuredE.fbx, Humanoid@RunInjuredA.fbx, Humanoid@RunInjuredA_RM.fbx, Humanoid@RunInjuredB_RM.fbx, Humanoid@RunInjuredC.fbx, Humanoid@RunInjuredC_RM.fbx, Humanoid@RunInjuredD.fbx, Humanoid@RunInjuredD_RM.fbx, Humanoid@RunInjuredE.fbx, Humanoid@RunInjuredE_RM.fbx, Humanoid@RunInjuredF.fbx, Humanoid@RunInjuredF_RM.fbx, Humanoid@RunInjuredG.fbx, Humanoid@RunInjuredG_RM.fbx, Humanoid@WalkInjuredA.fbx, Humanoid@WalkInjuredA_RM.fbx, Humanoid@WalkInjuredB.fbx, Humanoid@WalkInjuredB_RM.fbx, Humanoid@WalkInjuredC.fbx, Humanoid@WalkInjuredC_RM.fbx, Humanoid@WalkInjuredD.fbx, Humanoid@WalkInjuredD_RM.fbx, Humanoid@WalkInjuredE.fbx, Humanoid@WalkInjuredE_RM.fbx, Humanoid@WalkInjuredF.fbx, Humanoid@WalkInjuredF_RM.fbx, Humanoid@WalkInjuredG.fbx, Humanoid@WalkInjuredG_RM.fbx.
loop-seam-rot:error: Humanoid@IdleInjuredA.fbx, Humanoid@IdleInjuredB.fbx, Humanoid@IdleInjuredC.fbx, Humanoid@IdleInjuredD.fbx, Humanoid@IdleInjuredE.fbx, Humanoid@IdleInjuredF.fbx, Humanoid@IdleInjuredG.fbx, Humanoid@IdleKneelInjuredA.fbx, Humanoid@IdleKneelInjuredC.fbx, Humanoid@IdleKneelInjuredD.fbx, Humanoid@IdleKneelInjuredE.fbx, Humanoid@IdleSitInjuredB.fbx, Humanoid@IdleSitInjuredD.fbx, Humanoid@IdleSitInjuredE.fbx, Humanoid@IdleSitInjuredF.fbx, Humanoid@RunInjuredA.fbx, Humanoid@RunInjuredA_RM.fbx, Humanoid@RunInjuredB_RM.fbx, Humanoid@RunInjuredC.fbx, Humanoid@RunInjuredC_RM.fbx, Humanoid@RunInjuredD.fbx, Humanoid@RunInjuredD_RM.fbx, Humanoid@RunInjuredE.fbx, Humanoid@RunInjuredE_RM.fbx, Humanoid@RunInjuredF.fbx, Humanoid@RunInjuredF_RM.fbx, Humanoid@RunInjuredG.fbx, Humanoid@RunInjuredG_RM.fbx, Humanoid@WalkInjuredA.fbx, Humanoid@WalkInjuredA_RM.fbx, Humanoid@WalkInjuredB.fbx, Humanoid@WalkInjuredB_RM.fbx, Humanoid@WalkInjuredC.fbx, Humanoid@WalkInjuredC_RM.fbx, Humanoid@WalkInjuredD.fbx, Humanoid@WalkInjuredD_RM.fbx, Humanoid@WalkInjuredE.fbx, Humanoid@WalkInjuredE_RM.fbx, Humanoid@WalkInjuredF.fbx, Humanoid@WalkInjuredF_RM.fbx, Humanoid@WalkInjuredG.fbx, Humanoid@WalkInjuredG_RM.fbx.
loop-closure:error: Humanoid@RunInjuredA.fbx, Humanoid@RunInjuredA_RM.fbx, Humanoid@RunInjuredB_RM.fbx, Humanoid@RunInjuredC_RM.fbx, Humanoid@RunInjuredD_RM.fbx, Humanoid@RunInjuredE_RM.fbx, Humanoid@RunInjuredF_RM.fbx, Humanoid@RunInjuredG_RM.fbx, Humanoid@WalkInjuredA_RM.fbx, Humanoid@WalkInjuredB_RM.fbx, Humanoid@WalkInjuredC_RM.fbx, Humanoid@WalkInjuredD_RM.fbx, Humanoid@WalkInjuredE_RM.fbx, Humanoid@WalkInjuredF_RM.fbx, Humanoid@WalkInjuredG_RM.fbx.
AnimSmith remediation evidence
| Source issue | Operation/declarations | Result | Independent verification | Remaining caveat |
|---|---|---|---|---|
prune-constant-tracks trial | Explicit original source/config; transform --prune-constant-tracks | 1 candidates emitted; 0 pass selected output lint | Every candidate inspected, measured, linted, diffed and checked with fix --dry-run; detailed exit records external | Unpromoted; no candidate engine/contact/visual acceptance |
gait-anchor trial | Explicit original source/config; transform --gait-anchor | 14 candidates emitted; 1 pass selected output lint | Every candidate inspected, measured, linted, diffed and checked with fix --dry-run; detailed exit records external | Unpromoted; no candidate engine/contact/visual acceptance |
Engine procedures and evidence
| Runtime | Version | Procedure | Observed result | Remaining gate |
|---|---|---|---|---|
| Unity | unspecified | None in current run | not evaluated | Disposable import, blend, mask, and visual test |
| Unreal Engine | unspecified | None in current run | not evaluated | Disposable import, retarget, graph, and build test |
| Godot | unspecified | None in current run | not evaluated | Disposable conversion/import and graph test |
| Bevy | unspecified | None in current run | not evaluated | Selected export handoff and runtime test |
Rig, masking, and compatibility evidence
| Pack/rig/set pair | Skeleton/retarget | Scale/axes | Root policy | Timing/blend | Overall evidence |
|---|---|---|---|---|---|
| Injured/current evaluator | not evaluated | not evaluated | not evaluated | not evaluated | Current baseline evidence |
Limitations and unknowns
- No current engine, visual, contact, retarget, or artistic test ran.
- Commercial sources and derivatives remain external.
Changes between AnimSmith versions
AnimSmith 0.14.0 — reran 72 input baselines, 70 per-file declared contracts and 15 remediation trials using the official release. Added current exact-member generic scenarios and developer/artist actions. AnimSmith 0.10.0 — superseded historical evidence; none of its generated outputs or engine results is relabelled as a fresh run.
Reproduction
Official evaluator preflight: release archive, archive SHA-256 4ecf79436f9123c779edb004050da3010b44a2b392e3031facb227d7734fc33e; exact member animsmith-v0.14.0-x86_64-unknown-linux-gnu/animsmith, binary SHA-256 c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366; tag v0.14.0, peeled commit e8321ad40be5ef6f162b31f085819c039175c3c9; working-tree state: N/A (official archive). Compiled features: fbx, report. Version and required command help plus representative FBX admission passed before evaluation. Preflight: external:animsmith-0.14.0-report-refresh/preflight.json, SHA-256 7034b5043de54e7b30064230db18605d1f96d0ea1477f7b8c56ce817cc73521a.
Evaluator: official 0.14.0 Linux release, tag commit e8321ad40be5ef6f162b31f085819c039175c3c9; archive SHA-256 4ecf79436f9123c779edb004050da3010b44a2b392e3031facb227d7734fc33e; binary SHA-256 c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366, features fbx, report. Output schema v19; measurements schema v18. Version/help and representative admission passed before corpus work. Sources/configurations were hashed before and after read-only runs.
External evidence prefix: external:animsmith-0.14.0-report-refresh/evaluations/protofactor-injured/. Commands record exact source/config arguments, exits and raw-output paths. Commercial source and generated motion remain external. Reproduction requires the authorized delivery and captured configuration; generic one-liners below do not substitute for those declarations.
baseline/command-results.json, SHA-256e1c37270f1fd4b496845d2a4bc8748b54008b9d4985e159f307c4f9f1cd99979.contracts/command-results.json, SHA-256f38a687675764b1f6a6bbb78e931aadfec712396b0ebf74390be6392b1aaeb32.remediation/command-results.json, SHA-2567e924d579e7089edac6c5bb3bd71fee5929d675f47d1b61b1797d7c0bb44c025.selected-runtime-sets.json, SHA-256edada9b15d75682712eb5467e105d6df08aa804297ea876e251cd67bd641fd48.current-summary.json, SHA-256d620da6ff1c1b856936ed587cad7a84801461a2a847b3b06ef73290dc76ea1c0.
animsmith inspect --config <captured-config> <authorized-source>
animsmith measure --config <captured-config> --format json <authorized-source>
animsmith lint --config <captured-config> --format json <authorized-source>
The exact per-trial transform, slice range, configuration and post-check commands are in the remediation ledger. Current scenario sets are new evaluator choices; historical structured-model migration remains outside this refresh.
Sources
- Protofactor, Animset: Injured — product context.
- AnimSmith, CLI reference and game-ready clips — evaluator boundary.
Animation pack evaluation: Protofactor One-Handed Melee Animset
Technical verdict: Usable with conditions
Evaluation completeness: partial — source files, declared checks and transform trials were reviewed, with limited Unity source playback; target-character visuals, contacts, retargeting and the game controller remain untested.
Confidence: medium
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
Detailed evidence: Protofactor One-Handed Melee Animset evidence appendix
Technical decision
Prototype decision: Try one in-place or root-motion gait variant at a time after loop-policy review. Continuous gait defects require artist or source correction if confirmed. The 73-bone block/guard animation differs from the common 56-bone rig and needs an isolated retarget/import check. In-place clips leave travel to the controller; root-motion clips carry authored travel.
Camera scope: Full-body humanoid animations; appearance on your target character and camera is untested. Dedicated first-person arms/viewmodel use is unverified.
This animset is one of eight locally evaluated parts of Ultimate Animation Collection. The local asset revision is unknown; the collection report separates evaluated files from the dated vendor listing.
Content in evaluated inventory: Armed walk, run, crouch and equipment/actions. Evaluated filenames include airborne, attacks and reactions; these have no accepted controller topology here. These are identified motions; gameplay behavior has not been approved.
AnimSmith 0.14.0 reads all 113 delivered FBX candidates. The untouched baseline has no errors; it reports 13629 constant-track notes. With the per-file settings used in this evaluation, 23/110 motion files pass and 87 fail, mainly because loop settings need review. These loop and movement settings still need confirmation from the project or vendor; their failures are not all source defects. Set confirmed one-time actions to play once before treating their wrap seams as source defects. For intended repeating gaits, review the loop policy and correct actual source seams.
All 25 transform trials passed file inspection and measurement. All transformed outputs still fail their declared checks. A target-engine diagnostic can play unchanged sources now to reveal seams and contact problems. Before admitting a selected gait to the proposed controller, correct confirmed one-shot settings, establish one gait set that passes its intended checks, and review its full blend, foot contacts, movement and visuals. The transform outputs have not been accepted as replacements.
Capability coverage
Content present
Filename-classified walk, run and crouch combat gaits have separate in-place and root-motion candidates. The evaluated role inventory also includes airborne, actions and reactions.
Content gaps and unknowns
Additive aim, paired interactions and dedicated viewmodel content have no established classification. The 73-bone block/guard animation differs from the common 56-bone family and needs its own import/retarget check.
Evaluation still needed
Redeclare one-shots, resolve continuous gait contracts, and test blend coordinates, root ownership, grip/contact and target-character playback. Named Unity source executions do not accept the full controller.
Runtime sets and authored motion
Use each row as a separate blend or sequence proposal. The linked appendix names its exact clips, timings and measurements; controller playback and contact quality still need testing.
| Set | Controller use | Adoption decision | Exact members |
|---|---|---|---|
crouch-combat-8-way-in-place | Crouch eight directions; controller travel | Crouch prototype with controller travel. Its declared checks still fail. Confirm which motions should loop, correct any genuine seam, and test foot contacts before using the full blend. | 8 exact members |
crouch-combat-8-way-root-motion | Crouch eight directions; animation travel; measured root-motion 0.480–0.730 m/s | Crouch prototype with animation travel. Its declared checks still fail. Confirm which motions should loop, correct any genuine seam, and test foot contacts before using the full blend. | 8 exact members |
run-8-way-in-place | Run eight directions; controller travel | Run prototype with controller travel. Its declared checks still fail. Confirm which motions should loop, correct any genuine seam, and test foot contacts before using the full blend. | 8 exact members |
run-8-way-root-motion | Run eight directions; animation travel; measured root-motion 1.905–2.117 m/s | Run prototype with animation travel. Its declared checks still fail. Confirm which motions should loop, correct any genuine seam, and test foot contacts before using the full blend. | 8 exact members |
walk-combat-8-way-in-place | Walk eight directions; controller travel | Walk prototype with controller travel. Its declared checks still fail. Confirm which motions should loop, correct any genuine seam, and test foot contacts before using the full blend. | 8 exact members |
walk-combat-8-way-root-motion | Walk eight directions; animation travel; measured root-motion 0.491–0.951 m/s | Walk prototype with animation travel. Its declared checks still fail. Confirm which motions should loop, correct any genuine seam, and test foot contacts before using the full blend. | 8 exact members |
The appendix gives timings, travel speeds and phase measurements for each motion. Both eight-file run-8-way alternatives span 0.600–0.667 s; the detailed table reports each variant’s minimum. Use in-place and root-motion as separate movement ownership choices; neither variant has accepted contact synchronization.
Integration recipe
- Members/topology: Select one eight-member in-place or root-motion row from the appendix and map forward/cardinal/diagonal coordinates explicitly.
- Timing/synchronization: Loop only confirmed continuous states, keep named one-shots at
loop=false, and require phase/contact review before synchronized blending. - State ownership: Choose controller translation for in-place clips or animation translation for root-motion clips per state, with one owner for translation, yaw, and collision.
- Composition constraints: Keep weapon combat states namespaced to this pack and require separate mask/socket/IK evidence before layering.
- Acceptance gate: Require clean intended contracts plus target-engine import, full blend-space playback, foot/weapon contact, and artist review.
Technical issue register
| ID | Severity | Problem and impact | Primary owner | Current action | Future AnimSmith potential | Evidence/status |
|---|---|---|---|---|---|---|
| PF1-PHASE | note | The three selected in-place gait sets have measured phase differences (see appendix). Foot-contact timing and intended synchronization are unknown, so the measurements alone do not show a source defect. Guidance. | unknown | Obtain project or vendor loop/contact intent, then sweep the exact blend coordinates against a declared contact tolerance. Use engine phase markers or declared alignment only after intent is established; request artist cleanup only for a confirmed source-motion problem. Residual unresolved. | Current phase measurements inform a declared alignment trial; they do not prove contact or visual acceptance. | observed-animsmith phases; synchronization/contact acceptance not-evaluated. |
| PF1-01 | major | Humanoid@RunForward1hMelee.fbx, Humanoid@RunForward1hMelee_RM.fbx and their eight-direction sets fail the per-file settings used in this evaluation; none of the selected gait sets is ready for a production blend. Guidance | artist-author | Re-export intended repeating gaits with matching endpoint pose and velocity, or document a play-once setting. All eight members must pass their intended checks, then pass visual and contact review across the blend. Unresolved. | AnimSmith can validate and compare candidates, but cannot infer intended contact phase or approve visible motion. | observed-animsmith; current exact-file evidence, high confidence mechanically |
| PF1-02 | moderate | Humanoid@AttackA1hMelee.fbx and other attacks, reactions, parries or emotes are declared as loops. Their wrap-seam findings may reflect the project setting for one-time actions rather than bad source motion. Guidance | engine-config | Set confirmed one-time actions to loop=false; keep looping only where the project or vendor intends it. Recheck the settings and play each transition once. Unresolved. | AnimSmith may suggest a play-once setting, but the project or vendor must confirm intent. | observed-animsmith; declaration defect distinguished from source-animation quality |
Engine status
| Runtime | Evidence level | Technical result | Remaining gate |
|---|---|---|---|
| Unity 6000.5.8f1 | source-controller-feasibility | Fresh import and standalone sampling passed for IdleCombat1hMelee, WalkForwardCombat1hMelee, and WalkForwardCombat1hMelee_RM; basic-idle-to-onehand-idle, sword-shield-idle-to-onehand-idle, and basic-walk-ip-to-onehand-walk-ip produced finite mixer poses with fixed gameplay-owned root; the root-motion walk produced finite 1.215074 owner translation with animation-owned root. | Inspect rendered motion, contacts, deformation, authored AnimatorController behavior, and a player build; do not generalize beyond named sources. |
| Unreal Engine unspecified | not-evaluated | No current import or retarget result. | Import, retarget, root-motion, blend, and visual tests. |
| Godot unspecified | not-evaluated | No current conversion or playback result. | Convert/import and test animation graph, roots, contacts, and visuals. |
| Bevy unspecified | not-evaluated | No current conversion or playback result. | Convert/load and test graph, root ownership, performance, and visuals. |
Fit and limitations
Best fit is a full-body combat prototype whose controller can explicitly select one gait variant and keep pack-local state names. It is a poor fit for immediate production admission, motion matching, layered combat, or retargeted characters without the missing engine and human gates.
The fresh Unity probe establishes finite source playback and the named cross-pack transitions for IdleCombat1hMelee, WalkForwardCombat1hMelee, and WalkForwardCombat1hMelee_RM; it does not establish visual quality, contact, phase, retargeting, or broad pack compatibility. Keep states namespaced and retain explicit root ownership.
Changes between AnimSmith versions
AnimSmith 0.14.0 — revalidated the unchanged retained source inventory, per-file declarations, measured runtime hypotheses, and every previously recommended bounded transform. Current transformed outputs still need gameplay and visual checks. AnimSmith 0.10.0 — retained command ledgers and classification material supplied replay controls; its conclusions are superseded for current behavior.
Evidence status
Current evidence covers 113 physical FBXs and 110 logical individual-motion files with official AnimSmith 0.14.0 and the retained evaluation-manifest schema, now presented in editorial report format 3. A bounded Unity source/controller probe is current; visual/artistic, contact, retarget, authored-controller, and player-build gates remain open. See the readiness ladder and the evidence appendix. Licensed source and generated candidates remain outside Git.
Sources
- Protofactor product context and End User License Agreement — vendor context only; it does not identify the local constituent revision.
Animation pack evidence appendix: Protofactor One-Handed Melee Animset
Companion report: technical evaluation
Evidence status: partial — current mechanical, declaration, runtime-group, and remediation evidence completed; engine and human acceptance did not.
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
The canonical readiness ladder remains authoritative.
Evaluation scope and provenance
| Field | Value |
|---|---|
| Pack/edition | Authorized local Protofactor constituent; revision unknown |
| Vendor/source | Protofactor product context; local revision not established by the current listing |
| Delivered scope | 113 retained FBX candidates; source bytes unchanged during replay |
| Target use | Broad game-engine technical intake; no target controller supplied |
| Target engines | Unity 6000.5.8f1 bounded source/controller probe; Unreal, Godot, and Bevy not evaluated |
| Target rigs/packs | Delivered rigs; Basic Locomotion compatibility not freshly evaluated |
| Source manifest | external:protofactor-one-handed-melee/source-inventory.json; SHA-256 d70f848f52c11918d90f02f460455cc98b20cacda292cc5b7ae71f733027a5a7 |
| Evaluation manifest | Retained urn:animsmith:skill:animation-pack-evaluation-manifest:1; current memberships treated as hypotheses and remeasured |
| Acquisition/license provenance | Authorized local commercial input; no legal conclusion |
Evidence coverage
| Surface | Offered/delivered | Evaluated | Findings | Not evaluated and why |
|---|---|---|---|---|
| Animation files | 113 | 113 | 112 animation-bearing | Engine semantics not implied |
| Rigs/export variants | 113 | 113 | 109 individual-motion/pack files share the 56-bone 2b6fe49d5ae6 skeleton; Humanoid@Blocked1hMelee.fbx is a separate 73-bone variant | Retarget/reference-pose behavior not run |
| AnimSmith baseline | 113 | 113 | All inspect/measure/two lint commands exit 0; No baseline warnings or errors. | N/A |
| Declared contracts | 110 | 110 | 23 pass, 87 fail in each format | Declaration intent needs project/vendor authority |
| Offline visual reports | 0 | 0 | 0 | Not run |
| Engine import/playback | 3 named Unity sources | 3 | Fresh Humanoid import and finite standalone sampling passed | Visual/contact, authored-controller, and player-build behavior not evaluated |
| Blend/mask/retarget | Three named source Playables mixers | Three fixed-root schedules | Finite sampled poses for named cross-pack pairs | No full blend tree, mask, separate target-rig retarget, visual or contact acceptance |
Claim legend
observed-file identifies source structure, observed-animsmith identifies current tool output, inferred identifies bounded hypotheses, and not-evaluated marks open gates.
Evaluation manifest and taxonomy
Current evaluation manifest schema: urn:animsmith:skill:animation-pack-evaluation-manifest:1.
Canonical clip-role inventory
| Canonical primary role | Logical motions | Delivered files | Evidence boundary |
|---|---|---|---|
idle-pose | 6 | 6 | observed-file; evaluator classification, not vendor semantics |
continuous-locomotion | 54 | 54 | observed-file; evaluator classification, not vendor semantics |
locomotion-transition | 0 | 0 | observed-file; evaluator classification, not vendor semantics |
airborne | 5 | 5 | observed-file; evaluator classification, not vendor semantics |
traversal | 0 | 0 | observed-file; evaluator classification, not vendor semantics |
action-interaction | 20 | 20 | observed-file; evaluator classification, not vendor semantics |
reaction-death | 25 | 25 | observed-file; evaluator classification, not vendor semantics |
emote-cinematic | 0 | 0 | observed-file; evaluator classification, not vendor semantics |
other-unknown | 0 | 0 | observed-file; evaluator classification, not vendor semantics |
| Total | 110 | 110 | Current exact-file catalog |
Runtime-set inventory
The untested acceptance below applies to each complete set. Named Unity source samples and mixers ran for a smaller subset; they do not establish full-set playback or visual quality.
| Runtime set | Type | Members/variants | Grouping evidence | Validation status |
|---|---|---|---|---|
crouch-combat-8-way | directional-blend | 16 exact files; 8 IP, 8 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 0/16 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
run-8-way | directional-blend | 16 exact files; 8 IP, 8 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 0/16 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
walk-combat-8-way | directional-blend | 16 exact files; 8 IP, 8 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 0/16 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
hold-forward-speed | speed-blend | 6 exact files; 3 IP, 3 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 0/6 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
draw-combat-put-away | transition-chain | 3 exact files; 3 IP, 0 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 2/3 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
heavy-hit-4-way | other | 8 exact files; 4 IP, 4 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 0/8 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
crouch-combat-8-way-in-place | directional-blend | 8 exact IP files | Selectable variant of the measured crouch-combat-8-way source family | Retained gait contracts non-clean; contact/engine acceptance open |
crouch-combat-8-way-root-motion | directional-blend | 8 exact RM files | Selectable variant of the measured crouch-combat-8-way source family | Retained gait contracts non-clean; contact/engine acceptance open |
run-8-way-in-place | directional-blend | 8 exact IP files | Selectable variant of the measured run-8-way source family | Retained gait contracts non-clean; contact/engine acceptance open |
run-8-way-root-motion | directional-blend | 8 exact RM files | Selectable variant of the measured run-8-way source family | Retained gait contracts non-clean; contact/engine acceptance open |
walk-combat-8-way-in-place | directional-blend | 8 exact IP files | Selectable variant of the measured walk-combat-8-way source family | Retained gait contracts non-clean; contact/engine acceptance open |
walk-combat-8-way-root-motion | directional-blend | 8 exact RM files | Selectable variant of the measured walk-combat-8-way source family | Retained gait contracts non-clean; contact/engine acceptance open |
The exact runtime members below record source members and fresh timing/motion for the decision-driving gait sets. No unavailable membership authority is inferred from prose or filenames.
Grouped gait measurements (retained)
Current source members and measurements for the selected runtime scenarios. Selection is an evaluator hypothesis; source identity and measured values are retained below.
For these multi-member gait rows, duration and RM speed cells are set minima, not per-clip values. The ranges below retain authored variation; individual speed assignments are not reproduced here.
The original grouped gait rows below mix eight in-place and eight root-motion files. The selectable eight-member variants follow this retained source table.
| Set/profile | Role or coordinate | Exact members | Variant/type | Timing or motion | Runtime contract |
|---|---|---|---|---|---|
crouch-combat-8-way | eight-way directional gait | Humanoid@CrouchForward1hMelee.fbx; Humanoid@CrouchForward1hMelee_RM.fbx; Humanoid@CrouchForwardLeft1hMelee.fbx; Humanoid@CrouchForwardLeft1hMelee_RM.fbx; Humanoid@CrouchLeft1hMelee.fbx; Humanoid@CrouchLeft1hMelee_RM.fbx; Humanoid@CrouchBackwardsLeft1hMelee.fbx; Humanoid@CrouchBackwardsLeft1hMelee_RM.fbx; Humanoid@CrouchBackwards1hMelee.fbx; Humanoid@CrouchBackwards1hMelee_RM.fbx; Humanoid@CrouchBackwardsRight1hMelee.fbx; Humanoid@CrouchBackwardsRight1hMelee_RM.fbx; Humanoid@CrouchRight1hMelee.fbx; Humanoid@CrouchRight1hMelee_RM.fbx; Humanoid@CrouchForwardRight1hMelee.fbx; Humanoid@CrouchForwardRight1hMelee_RM.fbx | set_type=directional-blend | duration=1.667 s; rm_speed=0.480 m/s | loop_ip=true; loop_rm=true; sync=not-evaluated |
run-8-way | eight-way directional gait | Humanoid@RunForward1hMelee.fbx; Humanoid@RunForward1hMelee_RM.fbx; Humanoid@RunForwardLeft1hMelee.fbx; Humanoid@RunForwardLeft1hMelee_RM.fbx; Humanoid@RunLeft1hMelee.fbx; Humanoid@RunLeft1hMelee_RM.fbx; Humanoid@RunBackwardsLeft1hMelee.fbx; Humanoid@RunBackwardsLeft1hMelee_RM.fbx; Humanoid@RunBackwards1hMelee.fbx; Humanoid@RunBackwards1hMelee_RM.fbx; Humanoid@RunBackwardsRight1hMelee.fbx; Humanoid@RunBackwardsRight1hMelee_RM.fbx; Humanoid@RunRight1hMelee.fbx; Humanoid@RunRight1hMelee_RM.fbx; Humanoid@RunForwardRight1hMelee.fbx; Humanoid@RunForwardRight1hMelee_RM.fbx | set_type=directional-blend | duration=0.600 s; rm_speed=1.905 m/s | loop_ip=true; loop_rm=true; sync=not-evaluated |
walk-combat-8-way | eight-way directional gait | Humanoid@WalkForwardCombat1hMelee.fbx; Humanoid@WalkForwardCombat1hMelee_RM.fbx; Humanoid@WalkForwardLeftCombat1hMelee.fbx; Humanoid@WalkForwardLeftCombat1hMelee_RM.fbx; Humanoid@WalkLeftCombat1hMelee.fbx; Humanoid@WalkLeftCombat1hMelee_RM.fbx; Humanoid@WalkBackwardsLeftCombat1hMelee.fbx; Humanoid@WalkBackwardsLeftCombat1hMelee_RM.fbx; Humanoid@WalkBackwardsCombat1hMelee.fbx; Humanoid@WalkBackwardsCombat1hMelee_RM.fbx; Humanoid@WalkBackwardsRightCombat1hMelee.fbx; Humanoid@WalkBackwardsRightCombat1hMelee_RM.fbx; Humanoid@WalkRightCombat1hMelee.fbx; Humanoid@WalkRightCombat1hMelee_RM.fbx; Humanoid@WalkForwardRightCombat1hMelee.fbx; Humanoid@WalkForwardRightCombat1hMelee_RM.fbx | set_type=directional-blend | duration=1.333 s; rm_speed=0.491 m/s | loop_ip=true; loop_rm=true; sync=not-evaluated |
The grouped table gives minimum duration and RM speed. Run durations span 0.600–0.667 s. Measured RM speed spans are crouch-combat-8-way 0.480–0.730 m/s (ratio 0.657); run-8-way 1.905–2.117 m/s (ratio 0.900); walk-combat-8-way 0.491–0.951 m/s (ratio 0.516). Preserve authored variation unless the project declares a normalization policy; diagonals and cardinals require the full blend test. Measured source in-place phase spreads (cycles, eight measured members each) are crouch-combat-8-way 0.7136; run-8-way 0.7342; walk-combat-8-way 0.5538. Phase spread here is the minimum covering arc in cycles: sort phases in [0,1), include the wraparound gap, then subtract the largest gap from 1. It is not max_circular_deviation_from_mean; neither measure alone proves support-foot or visual compatibility. Keep sync=not-evaluated until the selected synchronization and contact policy is tested. The current catalog also measures hold-forward-speed, draw-combat-put-away, and heavy-hit-4-way as evaluator-defined hypotheses; they remain candidate groupings.
Exact runtime members
Each row is one selectable eight-member variant. Numeric duration and RM-speed table values are the measured minima, not a uniform value for every member. The retained current clip catalog measures both run-8-way-in-place and run-8-way-root-motion at 0.600–0.667 s across their respective eight files; their combined group has the same duration range. RM-speed ranges remain in the grouped measurement paragraph above. Continuous-loop declarations remain unaccepted.
| Set/profile | Role or coordinate | Exact members | Variant/type | Timing or motion | Runtime contract |
|---|---|---|---|---|---|
crouch-combat-8-way-in-place | eight-way directional gait | Humanoid@CrouchForward1hMelee.fbx; Humanoid@CrouchForwardLeft1hMelee.fbx; Humanoid@CrouchLeft1hMelee.fbx; Humanoid@CrouchBackwardsLeft1hMelee.fbx; Humanoid@CrouchBackwards1hMelee.fbx; Humanoid@CrouchBackwardsRight1hMelee.fbx; Humanoid@CrouchRight1hMelee.fbx; Humanoid@CrouchForwardRight1hMelee.fbx | variant=in-place | duration=1.667 s | loop=true; sync=not-evaluated |
crouch-combat-8-way-root-motion | eight-way directional gait | Humanoid@CrouchForward1hMelee_RM.fbx; Humanoid@CrouchForwardLeft1hMelee_RM.fbx; Humanoid@CrouchLeft1hMelee_RM.fbx; Humanoid@CrouchBackwardsLeft1hMelee_RM.fbx; Humanoid@CrouchBackwards1hMelee_RM.fbx; Humanoid@CrouchBackwardsRight1hMelee_RM.fbx; Humanoid@CrouchRight1hMelee_RM.fbx; Humanoid@CrouchForwardRight1hMelee_RM.fbx | variant=root-motion | duration=1.667 s; rm_speed=0.480 m/s | loop=true; sync=not-evaluated |
run-8-way-in-place | eight-way directional gait | Humanoid@RunForward1hMelee.fbx; Humanoid@RunForwardLeft1hMelee.fbx; Humanoid@RunLeft1hMelee.fbx; Humanoid@RunBackwardsLeft1hMelee.fbx; Humanoid@RunBackwards1hMelee.fbx; Humanoid@RunBackwardsRight1hMelee.fbx; Humanoid@RunRight1hMelee.fbx; Humanoid@RunForwardRight1hMelee.fbx | variant=in-place | duration=0.600 s | loop=true; sync=not-evaluated |
run-8-way-root-motion | eight-way directional gait | Humanoid@RunForward1hMelee_RM.fbx; Humanoid@RunForwardLeft1hMelee_RM.fbx; Humanoid@RunLeft1hMelee_RM.fbx; Humanoid@RunBackwardsLeft1hMelee_RM.fbx; Humanoid@RunBackwards1hMelee_RM.fbx; Humanoid@RunBackwardsRight1hMelee_RM.fbx; Humanoid@RunRight1hMelee_RM.fbx; Humanoid@RunForwardRight1hMelee_RM.fbx | variant=root-motion | duration=0.600 s; rm_speed=1.905 m/s | loop=true; sync=not-evaluated |
walk-combat-8-way-in-place | eight-way directional gait | Humanoid@WalkForwardCombat1hMelee.fbx; Humanoid@WalkForwardLeftCombat1hMelee.fbx; Humanoid@WalkLeftCombat1hMelee.fbx; Humanoid@WalkBackwardsLeftCombat1hMelee.fbx; Humanoid@WalkBackwardsCombat1hMelee.fbx; Humanoid@WalkBackwardsRightCombat1hMelee.fbx; Humanoid@WalkRightCombat1hMelee.fbx; Humanoid@WalkForwardRightCombat1hMelee.fbx | variant=in-place | duration=1.333 s | loop=true; sync=not-evaluated |
walk-combat-8-way-root-motion | eight-way directional gait | Humanoid@WalkForwardCombat1hMelee_RM.fbx; Humanoid@WalkForwardLeftCombat1hMelee_RM.fbx; Humanoid@WalkLeftCombat1hMelee_RM.fbx; Humanoid@WalkBackwardsLeftCombat1hMelee_RM.fbx; Humanoid@WalkBackwardsCombat1hMelee_RM.fbx; Humanoid@WalkBackwardsRightCombat1hMelee_RM.fbx; Humanoid@WalkRightCombat1hMelee_RM.fbx; Humanoid@WalkForwardRightCombat1hMelee_RM.fbx | variant=root-motion | duration=1.333 s; rm_speed=0.491 m/s | loop=true; sync=not-evaluated |
Pipeline-stage coverage
| Stage | Coverage state | Evidence / remaining gate |
|---|---|---|
| Acquire | evaluated-clean | Authorized retained source inventory |
| Preserve raw | evaluated-clean | Replay verified source and config hashes unchanged |
| Inspect | evaluated-finding | Exhaustive current inspect/measure/lint; notes and named warnings retained |
| Segment | partially-evaluated | Exact individual files measured; combined take not semantically segmented |
| Root motion | evaluated-finding | IP/RM measurements captured; controller ownership not evaluated |
| Conform | evaluated-finding | Fresh candidates produced but not adopted |
| Validate | evaluated-finding | Baseline and retained declarations complete; many declarations fail |
| Optimize | not-evaluated | Prune candidates are not production-adopted |
| Export | partially-evaluated | Candidate GLBs exist externally; no engine import acceptance |
| Gate/report | partially-evaluated | Mechanical report complete; engine and human gates open |
Readiness evidence by clip set
| Role or runtime set | File-ready / clip-ready | Set-ready / rig-use | Runtime / acceptance boundary |
|---|---|---|---|
| Individual motions | Current evaluator reads and measures exact files | Declaration-clean subset only | Named Unity source imports/samples ran; visual behavior not evaluated |
| Directional gait hypotheses | Timing, speed, skeleton, and current errors recorded | 0 contract-clean members in each named gait set | Named source samples and mixers only; full blend, phase, controller, and contact gates open |
| Transition/action hypotheses | Exact files measured | One-shot/loop intent requires authority | Named source Playables schedules ran; authored state graph, contact, and artistic gates open |
Validation-profile status
| Validation profile | Selection | Result / next evidence |
|---|---|---|
| Marketplace intake | selected — vendor-intended | Current source inventory and exhaustive mechanical intake complete. |
| Blended locomotion | selected — evaluator-selected-generic-scenario | Named gait hypotheses measured; selected source mixer schedules ran in Unity, while contracts and full blend review remain open. |
| Root-motion controller | selected — observed-pack-capability | RM variants measured; named Unity source root samples ran where applicable, while controller/collision ownership remains untested. |
| State-machine transitions | selected — observed-pack-capability | Transition candidates identified; named source Playables schedules ran, but authored state transitions and interruptions were not tested. |
| Layered upper body/weapons | not-selected | No mask/socket/IK authority or test. |
| Traversal/environment | not-selected | No target scene or traversal contacts. |
| Contact actions/interactions | selected — observed-pack-capability | Combat files present; weapon/body contacts not evaluated. |
| Retargeted/customizable characters | not-selected | No target rig or reference-pose test. |
| Motion matching/search | not-selected | No database construction or query test. |
| Networked movement | not-selected | No networking or reconciliation design. |
| Runtime performance | not-selected | No target build or benchmark. |
Pack inventory and content evidence
The current inventory contains 113 FBXs: 112 animation-bearing files, 110 individual-motion files, and 1 combined take. 109 individual-motion/pack files share the 56-bone 2b6fe49d5ae6 skeleton; Humanoid@Blocked1hMelee.fbx is a separate 73-bone variant. Counts describe physical current inputs, not runtime acceptance.
Current in-place gait phase evidence
Phase spread here is the minimum covering arc in cycles: sort phases in [0,1), include the wraparound gap, then subtract the largest gap from 1. It is not max_circular_deviation_from_mean; neither measure alone proves support-foot or visual compatibility. These are observed-animsmith source measurements for the exact hypotheses above; synchronization/contact acceptance remains open.
| Current hypothesis | Exact source member (Take 001) | Measured phase (cycles) |
|---|---|---|
crouch-combat-8-way | Humanoid@CrouchForward1hMelee.fbx | 0.093914 |
crouch-combat-8-way | Humanoid@CrouchForwardLeft1hMelee.fbx | 0.095760 |
crouch-combat-8-way | Humanoid@CrouchLeft1hMelee.fbx | 0.374620 |
crouch-combat-8-way | Humanoid@CrouchBackwardsLeft1hMelee.fbx | 0.378580 |
crouch-combat-8-way | Humanoid@CrouchBackwards1hMelee.fbx | 0.861504 |
crouch-combat-8-way | Humanoid@CrouchBackwardsRight1hMelee.fbx | 0.916710 |
crouch-combat-8-way | Humanoid@CrouchRight1hMelee.fbx | 0.919138 |
crouch-combat-8-way | Humanoid@CrouchForwardRight1hMelee.fbx | 0.575092 |
run-8-way | Humanoid@RunForward1hMelee.fbx | 0.192225 |
run-8-way | Humanoid@RunForwardLeft1hMelee.fbx | 0.226898 |
run-8-way | Humanoid@RunLeft1hMelee.fbx | 0.299076 |
run-8-way | Humanoid@RunBackwardsLeft1hMelee.fbx | 0.358187 |
run-8-way | Humanoid@RunBackwards1hMelee.fbx | 0.926401 |
run-8-way | Humanoid@RunBackwardsRight1hMelee.fbx | 0.799076 |
run-8-way | Humanoid@RunRight1hMelee.fbx | 0.552283 |
run-8-way | Humanoid@RunForwardRight1hMelee.fbx | 0.675372 |
walk-combat-8-way | Humanoid@WalkForwardCombat1hMelee.fbx | 0.128668 |
walk-combat-8-way | Humanoid@WalkForwardLeftCombat1hMelee.fbx | 0.126990 |
walk-combat-8-way | Humanoid@WalkLeftCombat1hMelee.fbx | 0.276900 |
walk-combat-8-way | Humanoid@WalkBackwardsLeftCombat1hMelee.fbx | 0.837304 |
walk-combat-8-way | Humanoid@WalkBackwardsCombat1hMelee.fbx | 0.796839 |
walk-combat-8-way | Humanoid@WalkBackwardsRightCombat1hMelee.fbx | 0.878098 |
walk-combat-8-way | Humanoid@WalkRightCombat1hMelee.fbx | 0.723103 |
walk-combat-8-way | Humanoid@WalkForwardRightCombat1hMelee.fbx | 0.178871 |
Mechanical baseline
| Finding/check | Affected scope | Potential impact | Evidence |
|---|---|---|---|
| Current readability | 113/113 inputs | Enables mechanical intake | observed-animsmith; all four commands per input exit 0 |
| Constant tracks | 13629 notes across 112 animation-bearing files | Storage/evaluation noise; not a gameplay defect by itself | observed-animsmith; baseline ledger SHA-256 2017c5adce72a3164d891bc0eda281ad0583d6285282aa423b5e47b443a591dd |
| Retained declarations | 87/110 non-clean | Blocks admission under those exact declarations | observed-animsmith; contract ledger SHA-256 57eb976f00807af833e25609b9ca74c019bd321800074ec87855524a84f6bb04 |
| Structural/timing warnings | Named scope above | Requires bounded author/project decision | observed-file and observed-animsmith |
AnimSmith remediation evidence
| Source issue | Operation/declarations | Result | Independent verification | Remaining caveat |
|---|---|---|---|---|
PF1-01 gait continuity | transform --gait-anchor on 24 selected in-place gait files; retained per-file configs | 24 outputs produced; selected inputs have no accumulating root translation/yaw | 24 inspect and measure commands exit 0; output hashes and diffs captured | Post-config lint remains non-clean; no RM trajectory was transformed; visual/contact residual unresolved |
| Constant-track notes | transform --prune-constant-tracks on Humanoid@IdleCombat1hMelee.fbx | Output candidate(s) produced | Inspect, measure, diff, fix dry-run, and config lint captured | 25/25 post-transform config lints exit 1. Candidate adoption and engine/visual acceptance remain open |
Fresh remediation ledger SHA-256 b6e30d9afd4d816057d41e58d7627cb39cc272074b491d1a8be4c625fb5901df. Generated motion stays outside Git.
Engine procedures and evidence
| Runtime | Version | Procedure | Observed result | Remaining gate |
|---|---|---|---|---|
| Unity | 6000.5.8f1 | Fresh named Humanoid source import and standalone sampling: IdleCombat1hMelee, WalkForwardCombat1hMelee, WalkForwardCombat1hMelee_RM; named Playables mixers: basic-idle-to-onehand-idle, sword-shield-idle-to-onehand-idle, basic-walk-ip-to-onehand-walk-ip. See collection source/controller probe. | Bounded source/controller feasibility for listed sources only; the collection’s 26/26 is a shared aggregate, not this pack’s result | Rendered motion, contact, deformation, full blend, retargeting, authored AnimatorController, player build and performance remain untested |
| Unreal Engine | not evaluated | No current procedure executed | not-evaluated | Import, retarget, root, blend, and visual review |
| Godot | not evaluated | No current procedure executed | not-evaluated | Convert/import, graph, root, contact, and visual review |
| Bevy | not evaluated | No current procedure executed | not-evaluated | Convert/load, graph, root, performance, and visual review |
Rig, masking, and compatibility evidence
| Pack/rig/set pair | Skeleton/retarget | Scale/axes | Root policy | Timing/blend | Overall evidence |
|---|---|---|---|---|---|
| One-Handed Melee internal gait sets | Current signatures measured; no retarget | No engine scale/axis test | IP/RM files measured; owner not selected | Current duration/speed/phase recorded; full blend absent | Mechanical only |
| One Handed Melee plus Basic Locomotion | Named Humanoid sources imported on shared SK_Protof-Actor | Unity import scale/axis accepted for named sources only | Gameplay-owned root fixed for IP/idle; animation-owned root finite for tested RM where applicable | Named Playables transitions finite; visual blend not inspected | Bounded source/controller feasibility, not compatibility acceptance |
Limitations and unknowns
- Retained configs are replay controls, not proof that every attack, reaction, parry, or emote should loop.
- Current grouping hypotheses were remeasured from exact files, but vendor/project semantics and the unavailable richer historical membership authority were not reconstructed.
- Generated candidates remain external and unadopted; command success does not lower residual severity.
- Unity evidence is limited to named source import, finite headless poses, explicit root ownership, and named Playables mixers; no rendered visual, contact, retarget, mask, authored-controller, player-build, performance, or artistic acceptance was performed.
Changes between AnimSmith versions
AnimSmith 0.14.0 — current official binary revalidated source hashes, all baseline and declaration commands, named runtime hypotheses, and every retained remediation recipe. AnimSmith 0.10.0 — retained ledgers supplied immutable source/config controls; historical results are superseded for current conclusions.
Reproduction
Official evaluator preflight: release archive, archive SHA-256 4ecf79436f9123c779edb004050da3010b44a2b392e3031facb227d7734fc33e; exact member animsmith-v0.14.0-x86_64-unknown-linux-gnu/animsmith, binary SHA-256 c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366; tag v0.14.0, peeled commit e8321ad40be5ef6f162b31f085819c039175c3c9; working-tree state: N/A (official archive). Compiled features: fbx, report. Version and required command help plus representative FBX admission passed before evaluation. Preflight: external:animsmith-0.14.0-report-refresh/preflight.json, SHA-256 7034b5043de54e7b30064230db18605d1f96d0ea1477f7b8c56ce817cc73521a.
Official archive https://github.com/mmannerm/animsmith/releases/download/v0.14.0/animsmith-v0.14.0-x86_64-unknown-linux-gnu.tar.gz; archive SHA-256 4ecf79436f9123c779edb004050da3010b44a2b392e3031facb227d7734fc33e; member animsmith-v0.14.0-x86_64-unknown-linux-gnu/animsmith; binary SHA-256 c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366; tag v0.14.0; commit e8321ad40be5ef6f162b31f085819c039175c3c9; working tree N/A; features fbx/report.
Preflight verified version/help for required commands and representative GLB/FBX admission before replay; scrubbed locator external:0.14.0-report-refresh/preflight.json, SHA-256 7034b5043de54e7b30064230db18605d1f96d0ea1477f7b8c56ce817cc73521a. The read-only helper replayed the retained exact inspect, measure, and two lint invocations with four workers, recorded argv/exit/stdout/stderr and source/config hashes, and did not mutate inputs. Baseline, contract, and remediation ledger digests are 2017c5adce72a3164d891bc0eda281ad0583d6285282aa423b5e47b443a591dd, 57eb976f00807af833e25609b9ca74c019bd321800074ec87855524a84f6bb04, and b6e30d9afd4d816057d41e58d7627cb39cc272074b491d1a8be4c625fb5901df. Licensed paths, sources, and derivative outputs are excluded from Git and this report.
Fresh Unity evidence: editor 6000.5.8f1, Unity.exe SHA-256 cdc0eeca135394cde79a632aa998aca14b521b58afe8c2750b009aff608be906; probe SHA-256 28cd00897dfd739e2a7bcfa193adab03de3591a1009802946305127b67d1bfb6; result schema animsmith-unity-crosspack-probe-v1, result SHA-256 f067bca91f182b65fab408a1c97b297377a8f361b03d59e809e236aba666652c; log SHA-256 0429bcb9cc7a7492c6637eb799566463ba915243d0a110b4d76cbe75590c8819; 26/26 aggregate required checks passed with 53 nonempty Humanoid bones per sample. This report uses only the named applicable subset above.
Sources
- Protofactor product context and End User License Agreement — context only; local revision remains unknown.
Animation pack evaluation: Protofactor Two-Handed Melee Animset
Technical verdict: Usable with conditions
Evaluation completeness: partial — source files, declared checks and transform trials were reviewed, with limited Unity source playback; target-character visuals, contacts, retargeting and the game controller remain untested.
Confidence: medium
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
Detailed evidence: Protofactor Two-Handed Melee Animset evidence appendix
Technical decision
Prototype decision: Try one in-place or root-motion gait variant at a time after loop-policy review. Continuous gait defects require artist or source correction if confirmed. Two 56-bone block/guard animations differ from the common 58-bone rig and need isolated retarget/import checks. In-place clips leave travel to the controller; root-motion clips carry authored travel.
Camera scope: Full-body humanoid animations; appearance on your target character and camera is untested. Dedicated first-person arms/viewmodel use is unverified.
This animset is one of eight locally evaluated parts of Ultimate Animation Collection. The local asset revision is unknown; the collection report separates evaluated files from the dated vendor listing.
Content in evaluated inventory: Armed walk, run, crouch and equipment/actions. Evaluated filenames include airborne, attacks and reactions; these have no accepted controller topology here. These are identified motions; gameplay behavior has not been approved.
AnimSmith 0.14.0 reads all 123 delivered FBX candidates. The untouched baseline has no errors; it reports 17010 constant-track notes, one duration warning. With the per-file settings used in this evaluation, 13/120 motion files pass and 107 fail, mainly because loop settings need review. These loop and movement settings still need confirmation from the project or vendor; their failures are not all source defects. Set confirmed one-time actions to play once before treating their wrap seams as source defects. For intended repeating gaits, review the loop policy and correct actual source seams. One heavy-hit root-motion file has unequal channel end times.
All 25 transform trials passed file inspection and measurement. All transformed outputs still fail their declared checks. A target-engine diagnostic can play unchanged sources now to reveal seams and contact problems. Before admitting a selected gait to the proposed controller, correct confirmed one-shot settings, establish one gait set that passes its intended checks, and review its full blend, foot contacts, movement and visuals. The transform outputs have not been accepted as replacements.
Capability coverage
Content present
Filename-classified walk, run and crouch combat gaits have separate in-place and root-motion candidates. The evaluated role inventory also includes airborne, actions and reactions.
Action candidates to inspect: The retained runtime-set inventory groups dodge-forward-back (4 files; 2 in-place, 2 root-motion; 0/4 pass the retained declaration), parry-3-way (3 in-place; 0/3), and draw-combat-put-away (3 in-place; 2/3). These are grouping leads, not accepted dodge, parry or equip behavior. The public grouping rows do not bind their individual filenames, take times, interruption windows or contacts; inspect the source and test each transition before building a controller recipe.
Content gaps and unknowns
Additive aim, paired interactions and dedicated viewmodel content have no established classification. The 56-bone Blocked2HandMelee and IdleBlock2HandMelee states differ from the common 58-bone family.
Evaluation still needed
Redeclare one-shots, resolve continuous gait contracts, and test those block-state imports, blend coordinates, root ownership, weapon/foot contact and target-character playback.
Runtime sets and authored motion
Use each row as a separate blend or sequence proposal. The linked appendix names its exact clips, timings and measurements; controller playback and contact quality still need testing.
| Set | Controller use | Adoption decision | Exact members |
|---|---|---|---|
crouch-combat-8-way-in-place | Crouch eight directions; controller travel | Crouch prototype with controller travel. Its declared checks still fail. Confirm which motions should loop, correct any genuine seam, and test foot contacts before using the full blend. | 8 exact members |
crouch-combat-8-way-root-motion | Crouch eight directions; animation travel; measured root-motion 0.642–0.835 m/s | Crouch prototype with animation travel. Its declared checks still fail. Confirm which motions should loop, correct any genuine seam, and test foot contacts before using the full blend. | 8 exact members |
run-combat-8-way-in-place | Run eight directions; controller travel | Run prototype with controller travel. Its declared checks still fail. Confirm which motions should loop, correct any genuine seam, and test foot contacts before using the full blend. | 8 exact members |
run-combat-8-way-root-motion | Run eight directions; animation travel; measured root-motion 2.202–2.690 m/s | Run prototype with animation travel. Its declared checks still fail. Confirm which motions should loop, correct any genuine seam, and test foot contacts before using the full blend. | 8 exact members |
walk-combat-8-way-in-place | Walk eight directions; controller travel | Walk prototype with controller travel. Its declared checks still fail. Confirm which motions should loop, correct any genuine seam, and test foot contacts before using the full blend. | 8 exact members |
walk-combat-8-way-root-motion | Walk eight directions; animation travel; measured root-motion 0.810–1.092 m/s | Walk prototype with animation travel. Its declared checks still fail. Confirm which motions should loop, correct any genuine seam, and test foot contacts before using the full blend. | 8 exact members |
The appendix gives timings, travel speeds and phase measurements for each motion. Both eight-file run-combat-8-way alternatives span 0.533–0.567 s; the detailed table reports each variant’s minimum. Use in-place and root-motion as separate movement ownership choices; neither variant has accepted contact synchronization.
Integration recipe
- Members/topology: Select one eight-member in-place or root-motion row from the appendix and map forward/cardinal/diagonal coordinates explicitly.
- Timing/synchronization: Loop only confirmed continuous states, keep named one-shots at
loop=false, and require phase/contact review before synchronized blending. - State ownership: Choose controller translation for in-place clips or animation translation for root-motion clips per state, with one owner for translation, yaw, and collision.
- Composition constraints: Keep weapon combat states namespaced to this pack and require separate mask/socket/IK evidence before layering.
- Acceptance gate: Require clean intended contracts plus target-engine import, full blend-space playback, foot/weapon contact, and artist review.
Technical issue register
| ID | Severity | Problem and impact | Primary owner | Current action | Future AnimSmith potential | Evidence/status |
|---|---|---|---|---|---|---|
| PF2-PHASE | note | The three selected in-place gait sets have measured phase differences (see appendix). Foot-contact timing and intended synchronization are unknown, so the measurements alone do not show a source defect. Guidance. | unknown | Obtain project or vendor loop/contact intent, then sweep the exact blend coordinates against a declared contact tolerance. Use engine phase markers or declared alignment only after intent is established; request artist cleanup only for a confirmed source-motion problem. Residual unresolved. | Current phase measurements inform a declared alignment trial; they do not prove contact or visual acceptance. | observed-animsmith phases; synchronization/contact acceptance not-evaluated. |
| PF2-01 | major | Humanoid@RunForwardCombat2HandMelee.fbx, Humanoid@RunForwardCombat2HandMelee_RM.fbx and their eight-direction sets fail the per-file settings used in this evaluation; none of the selected gait sets is ready for a production blend. Guidance | artist-author | Re-export intended repeating gaits with matching endpoint pose and velocity, or document a play-once setting. All eight members must pass their intended checks, then pass visual and contact review across the blend. Unresolved. | AnimSmith can validate and compare candidates, but cannot infer intended contact phase or approve visible motion. | observed-animsmith; current exact-file evidence, high confidence mechanically |
| PF2-02 | moderate | Humanoid@AttackA2HandMelee.fbx and other attacks, reactions, parries or emotes are declared as loops. Their wrap-seam findings may reflect the project setting for one-time actions rather than bad source motion. Guidance | engine-config | Set confirmed one-time actions to loop=false; keep looping only where the project or vendor intends it. Recheck the settings and play each transition once. Unresolved. | AnimSmith may suggest a play-once setting, but the project or vendor must confirm intent. | observed-animsmith; declaration defect distinguished from source-animation quality |
| PF2-03 | moderate | Humanoid@GetHitLeftHeavy2HandMelee_RM.fbx has channels ending at 1.700–1.733 s; shorter channels hold their last values through the final interval. Guidance | artist-author | Align channel endpoints or document the hold. Confirm the duration check passes and approve heavy-hit contact and recovery in playback. Unresolved. | Current lint detects the mismatch; safe semantic repair still needs author intent. | observed-animsmith; one warning plus six time-monotonic notes |
Engine status
| Runtime | Evidence level | Technical result | Remaining gate |
|---|---|---|---|
| Unity 6000.5.8f1 | source-controller-feasibility | Fresh import and standalone sampling passed for IdleCombatA2HandMelee, WalkForwardCombat2HandMelee, and WalkForwardCombat2HandMelee_RM; basic-idle-to-twohand-idle, sword-shield-idle-to-twohand-idle, and basic-walk-ip-to-twohand-walk-ip produced finite mixer poses with fixed gameplay-owned root; the root-motion walk produced finite 1.159724 owner translation with animation-owned root. | Inspect rendered motion, contacts, deformation, authored AnimatorController behavior, and a player build; do not generalize beyond named sources. |
| Unreal Engine unspecified | not-evaluated | No current import or retarget result. | Import, retarget, root-motion, blend, and visual tests. |
| Godot unspecified | not-evaluated | No current conversion or playback result. | Convert/import and test animation graph, roots, contacts, and visuals. |
| Bevy unspecified | not-evaluated | No current conversion or playback result. | Convert/load and test graph, root ownership, performance, and visuals. |
Fit and limitations
Best fit is a full-body combat prototype whose controller can explicitly select one gait variant and keep pack-local state names. It is a poor fit for immediate production admission, motion matching, layered combat, or retargeted characters without the missing engine and human gates.
The fresh Unity probe establishes finite source playback and the named cross-pack transitions for IdleCombatA2HandMelee, WalkForwardCombat2HandMelee, and WalkForwardCombat2HandMelee_RM; it does not establish visual quality, contact, phase, retargeting, or broad pack compatibility. Keep states namespaced and retain explicit root ownership.
Changes between AnimSmith versions
AnimSmith 0.14.0 — revalidated the unchanged retained source inventory, per-file declarations, measured runtime hypotheses, and every previously recommended bounded transform. Current transformed outputs still need gameplay and visual checks. AnimSmith 0.10.0 — retained command ledgers and classification material supplied replay controls; its conclusions are superseded for current behavior.
Evidence status
Current evidence covers 123 physical FBXs and 120 logical individual-motion files with official AnimSmith 0.14.0 and the retained evaluation-manifest schema, now presented in editorial report format 3. A bounded Unity source/controller probe is current; visual/artistic, contact, retarget, authored-controller, and player-build gates remain open. See the readiness ladder and the evidence appendix. Licensed source and generated candidates remain outside Git.
Sources
- Protofactor product context and End User License Agreement — vendor context only; it does not identify the local constituent revision.
Animation pack evidence appendix: Protofactor Two-Handed Melee Animset
Companion report: technical evaluation
Evidence status: partial — current mechanical, declaration, runtime-group, and remediation evidence completed; engine and human acceptance did not.
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
The canonical readiness ladder remains authoritative.
Evaluation scope and provenance
| Field | Value |
|---|---|
| Pack/edition | Authorized local Protofactor constituent; revision unknown |
| Vendor/source | Protofactor product context; local revision not established by the current listing |
| Delivered scope | 123 retained FBX candidates; source bytes unchanged during replay |
| Target use | Broad game-engine technical intake; no target controller supplied |
| Target engines | Unity 6000.5.8f1 bounded source/controller probe; Unreal, Godot, and Bevy not evaluated |
| Target rigs/packs | Delivered rigs; Basic Locomotion compatibility not freshly evaluated |
| Source manifest | external:protofactor-two-handed-melee/source-inventory.json; SHA-256 1fc1189eb8ccb299fe26ff51bce96d932b7b2dab5c71a0c6adab477937ad7bc7 |
| Evaluation manifest | Retained urn:animsmith:skill:animation-pack-evaluation-manifest:1; current memberships treated as hypotheses and remeasured |
| Acquisition/license provenance | Authorized local commercial input; no legal conclusion |
Evidence coverage
| Surface | Offered/delivered | Evaluated | Findings | Not evaluated and why |
|---|---|---|---|---|
| Animation files | 123 | 123 | 122 animation-bearing | Engine semantics not implied |
| Rigs/export variants | 123 | 123 | 118 individual-motion files share the 58-bone 3da84463466a skeleton; Humanoid@Blocked2HandMelee.fbx and Humanoid@IdleBlock2HandMelee.fbx use the 56-bone 2b6fe49d5ae6 variant | Retarget/reference-pose behavior not run |
| AnimSmith baseline | 123 | 123 | All inspect/measure/two lint commands exit 0; One duration-sanity warning on Humanoid@GetHitLeftHeavy2HandMelee_RM.fbx; six time-monotonic notes. | N/A |
| Declared contracts | 120 | 120 | 13 pass, 107 fail in each format | Declaration intent needs project/vendor authority |
| Offline visual reports | 0 | 0 | 0 | Not run |
| Engine import/playback | 3 named Unity sources | 3 | Fresh Humanoid import and finite standalone sampling passed | Visual/contact, authored-controller, and player-build behavior not evaluated |
| Blend/mask/retarget | Three named source Playables mixers | Three fixed-root schedules | Finite sampled poses for named cross-pack pairs | No full blend tree, mask, separate target-rig retarget, visual or contact acceptance |
Claim legend
observed-file identifies source structure, observed-animsmith identifies current tool output, inferred identifies bounded hypotheses, and not-evaluated marks open gates.
Evaluation manifest and taxonomy
Current evaluation manifest schema: urn:animsmith:skill:animation-pack-evaluation-manifest:1.
Canonical clip-role inventory
| Canonical primary role | Logical motions | Delivered files | Evidence boundary |
|---|---|---|---|
idle-pose | 8 | 8 | observed-file; evaluator classification, not vendor semantics |
continuous-locomotion | 56 | 56 | observed-file; evaluator classification, not vendor semantics |
locomotion-transition | 0 | 0 | observed-file; evaluator classification, not vendor semantics |
airborne | 5 | 5 | observed-file; evaluator classification, not vendor semantics |
traversal | 0 | 0 | observed-file; evaluator classification, not vendor semantics |
action-interaction | 34 | 34 | observed-file; evaluator classification, not vendor semantics |
reaction-death | 17 | 17 | observed-file; evaluator classification, not vendor semantics |
emote-cinematic | 0 | 0 | observed-file; evaluator classification, not vendor semantics |
other-unknown | 0 | 0 | observed-file; evaluator classification, not vendor semantics |
| Total | 120 | 120 | Current exact-file catalog |
Runtime-set inventory
The untested acceptance below applies to each complete set. Named Unity source samples and mixers ran for a smaller subset; they do not establish full-set playback or visual quality.
| Runtime set | Type | Members/variants | Grouping evidence | Validation status |
|---|---|---|---|---|
crouch-combat-8-way | directional-blend | 16 exact files; 8 IP, 8 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 0/16 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
run-combat-8-way | directional-blend | 16 exact files; 8 IP, 8 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 0/16 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
walk-combat-8-way | directional-blend | 16 exact files; 8 IP, 8 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 0/16 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
normal-forward-speed | speed-blend | 6 exact files; 3 IP, 3 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 0/6 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
draw-combat-put-away | transition-chain | 3 exact files; 3 IP, 0 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 2/3 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
dodge-forward-back | other | 4 exact files; 2 IP, 2 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 0/4 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
parry-3-way | other | 3 exact files; 3 IP, 0 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 0/3 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
crouch-combat-8-way-in-place | directional-blend | 8 exact IP files | Selectable variant of the measured crouch-combat-8-way source family | Retained gait contracts non-clean; contact/engine acceptance open |
crouch-combat-8-way-root-motion | directional-blend | 8 exact RM files | Selectable variant of the measured crouch-combat-8-way source family | Retained gait contracts non-clean; contact/engine acceptance open |
run-combat-8-way-in-place | directional-blend | 8 exact IP files | Selectable variant of the measured run-combat-8-way source family | Retained gait contracts non-clean; contact/engine acceptance open |
run-combat-8-way-root-motion | directional-blend | 8 exact RM files | Selectable variant of the measured run-combat-8-way source family | Retained gait contracts non-clean; contact/engine acceptance open |
walk-combat-8-way-in-place | directional-blend | 8 exact IP files | Selectable variant of the measured walk-combat-8-way source family | Retained gait contracts non-clean; contact/engine acceptance open |
walk-combat-8-way-root-motion | directional-blend | 8 exact RM files | Selectable variant of the measured walk-combat-8-way source family | Retained gait contracts non-clean; contact/engine acceptance open |
The exact runtime members below record source members and fresh timing/motion for the decision-driving gait sets. No unavailable membership authority is inferred from prose or filenames.
Grouped gait measurements (retained)
Current source members and measurements for the selected runtime scenarios. Selection is an evaluator hypothesis; source identity and measured values are retained below.
For these multi-member gait rows, duration and RM speed cells are set minima, not per-clip values. The ranges below retain authored variation; individual speed assignments are not reproduced here.
The original grouped gait rows below mix eight in-place and eight root-motion files. The selectable eight-member variants follow this retained source table.
| Set/profile | Role or coordinate | Exact members | Variant/type | Timing or motion | Runtime contract |
|---|---|---|---|---|---|
crouch-combat-8-way | eight-way directional gait | Humanoid@CrouchForward2HandMelee.fbx; Humanoid@CrouchForward2HandMelee_RM.fbx; Humanoid@CrouchForwardLeft2HandMelee.fbx; Humanoid@CrouchForwardLeft2HandMelee_RM.fbx; Humanoid@CrouchLeft2HandMelee.fbx; Humanoid@CrouchLeft2HandMelee_RM.fbx; Humanoid@CrouchBackwardsLeft2HandMelee.fbx; Humanoid@CrouchBackwardsLeft2HandMelee_RM.fbx; Humanoid@CrouchBackwards2HandMelee.fbx; Humanoid@CrouchBackwards2HandMelee_RM.fbx; Humanoid@CrouchBackwardsRight2HandMelee.fbx; Humanoid@CrouchBackwardsRight2HandMelee_RM.fbx; Humanoid@CrouchRight2HandMelee.fbx; Humanoid@CrouchRight2HandMelee_RM.fbx; Humanoid@CrouchForwardRight2HandMelee.fbx; Humanoid@CrouchForwardRight2HandMelee_RM.fbx | set_type=directional-blend | duration=1.667 s; rm_speed=0.642 m/s | loop_ip=true; loop_rm=true; sync=not-evaluated |
run-combat-8-way | eight-way directional gait | Humanoid@RunForwardCombat2HandMelee.fbx; Humanoid@RunForwardCombat2HandMelee_RM.fbx; Humanoid@RunForwardLeftCombat2HandMelee.fbx; Humanoid@RunForwardLeftCombat2HandMelee_RM.fbx; Humanoid@RunLeftCombat2HandMelee.fbx; Humanoid@RunLeftCombat2HandMelee_RM.fbx; Humanoid@RunBackwardsLeftCombat2HandMelee.fbx; Humanoid@RunBackwardsLeftCombat2HandMelee_RM.fbx; Humanoid@RunBackwardsCombat2HandMelee.fbx; Humanoid@RunBackwardsCombat2HandMelee_RM.fbx; Humanoid@RunBackwardsRightCombat2HandMelee.fbx; Humanoid@RunBackwardsRightCombat2HandMelee_RM.fbx; Humanoid@RunRightCombat2HandMelee.fbx; Humanoid@RunRightCombat2HandMelee_RM.fbx; Humanoid@RunForwardRightCombat2HandMelee.fbx; Humanoid@RunForwardRightCombat2HandMelee_RM.fbx | set_type=directional-blend | duration=0.533 s; rm_speed=2.202 m/s | loop_ip=true; loop_rm=true; sync=not-evaluated |
walk-combat-8-way | eight-way directional gait | Humanoid@WalkForwardCombat2HandMelee.fbx; Humanoid@WalkForwardCombat2HandMelee_RM.fbx; Humanoid@WalkForwardLeftCombat2HandMelee.fbx; Humanoid@WalkForwardLeftCombat2HandMelee_RM.fbx; Humanoid@WalkLeftCombat2HandMelee.fbx; Humanoid@WalkLeftCombat2HandMelee_RM.fbx; Humanoid@WalkBackwardsLeftCombat2HandMelee.fbx; Humanoid@WalkBackwardsLeftCombat2HandMelee_RM.fbx; Humanoid@WalkBackwardsCombat2HandMelee.fbx; Humanoid@WalkBackwardsCombat2HandMelee_RM.fbx; Humanoid@WalkBackwardsRightCombat2HandMelee.fbx; Humanoid@WalkBackwardsRightCombat2HandMelee_RM.fbx; Humanoid@WalkRightCombat2HandMelee.fbx; Humanoid@WalkRightCombat2HandMelee_RM.fbx; Humanoid@WalkForwardRightCombat2HandMelee.fbx; Humanoid@WalkForwardRightCombat2HandMelee_RM.fbx | set_type=directional-blend | duration=1.333 s; rm_speed=0.810 m/s | loop_ip=true; loop_rm=true; sync=not-evaluated |
The grouped table gives minimum duration and RM speed. Run durations span 0.533–0.567 s. Measured RM speed spans are crouch-combat-8-way 0.642–0.835 m/s (ratio 0.769); run-combat-8-way 2.202–2.690 m/s (ratio 0.819); walk-combat-8-way 0.810–1.092 m/s (ratio 0.742). Preserve authored variation unless the project declares a normalization policy; diagonals and cardinals require the full blend test. Measured source in-place phase spreads (cycles, eight measured members each) are crouch-combat-8-way 0.5774; run-combat-8-way 0.6024; walk-combat-8-way 0.7112. Phase spread here is the minimum covering arc in cycles: sort phases in [0,1), include the wraparound gap, then subtract the largest gap from 1. It is not max_circular_deviation_from_mean; neither measure alone proves support-foot or visual compatibility. Keep sync=not-evaluated until the selected synchronization and contact policy is tested. The current catalog also measures normal-forward-speed, draw-combat-put-away, dodge-forward-back, and parry-3-way as evaluator-defined hypotheses; they remain candidate groupings.
Exact runtime members
Each row is one selectable eight-member variant. Numeric duration and RM-speed table values are the measured minima, not a uniform value for every member. The retained current clip catalog measures both run-combat-8-way-in-place and run-combat-8-way-root-motion at 0.533–0.567 s across their respective eight files; their combined group has the same duration range. RM-speed ranges remain in the grouped measurement paragraph above. Continuous-loop declarations remain unaccepted.
| Set/profile | Role or coordinate | Exact members | Variant/type | Timing or motion | Runtime contract |
|---|---|---|---|---|---|
crouch-combat-8-way-in-place | eight-way directional gait | Humanoid@CrouchForward2HandMelee.fbx; Humanoid@CrouchForwardLeft2HandMelee.fbx; Humanoid@CrouchLeft2HandMelee.fbx; Humanoid@CrouchBackwardsLeft2HandMelee.fbx; Humanoid@CrouchBackwards2HandMelee.fbx; Humanoid@CrouchBackwardsRight2HandMelee.fbx; Humanoid@CrouchRight2HandMelee.fbx; Humanoid@CrouchForwardRight2HandMelee.fbx | variant=in-place | duration=1.667 s | loop=true; sync=not-evaluated |
crouch-combat-8-way-root-motion | eight-way directional gait | Humanoid@CrouchForward2HandMelee_RM.fbx; Humanoid@CrouchForwardLeft2HandMelee_RM.fbx; Humanoid@CrouchLeft2HandMelee_RM.fbx; Humanoid@CrouchBackwardsLeft2HandMelee_RM.fbx; Humanoid@CrouchBackwards2HandMelee_RM.fbx; Humanoid@CrouchBackwardsRight2HandMelee_RM.fbx; Humanoid@CrouchRight2HandMelee_RM.fbx; Humanoid@CrouchForwardRight2HandMelee_RM.fbx | variant=root-motion | duration=1.667 s; rm_speed=0.642 m/s | loop=true; sync=not-evaluated |
run-combat-8-way-in-place | eight-way directional gait | Humanoid@RunForwardCombat2HandMelee.fbx; Humanoid@RunForwardLeftCombat2HandMelee.fbx; Humanoid@RunLeftCombat2HandMelee.fbx; Humanoid@RunBackwardsLeftCombat2HandMelee.fbx; Humanoid@RunBackwardsCombat2HandMelee.fbx; Humanoid@RunBackwardsRightCombat2HandMelee.fbx; Humanoid@RunRightCombat2HandMelee.fbx; Humanoid@RunForwardRightCombat2HandMelee.fbx | variant=in-place | duration=0.533 s | loop=true; sync=not-evaluated |
run-combat-8-way-root-motion | eight-way directional gait | Humanoid@RunForwardCombat2HandMelee_RM.fbx; Humanoid@RunForwardLeftCombat2HandMelee_RM.fbx; Humanoid@RunLeftCombat2HandMelee_RM.fbx; Humanoid@RunBackwardsLeftCombat2HandMelee_RM.fbx; Humanoid@RunBackwardsCombat2HandMelee_RM.fbx; Humanoid@RunBackwardsRightCombat2HandMelee_RM.fbx; Humanoid@RunRightCombat2HandMelee_RM.fbx; Humanoid@RunForwardRightCombat2HandMelee_RM.fbx | variant=root-motion | duration=0.533 s; rm_speed=2.202 m/s | loop=true; sync=not-evaluated |
walk-combat-8-way-in-place | eight-way directional gait | Humanoid@WalkForwardCombat2HandMelee.fbx; Humanoid@WalkForwardLeftCombat2HandMelee.fbx; Humanoid@WalkLeftCombat2HandMelee.fbx; Humanoid@WalkBackwardsLeftCombat2HandMelee.fbx; Humanoid@WalkBackwardsCombat2HandMelee.fbx; Humanoid@WalkBackwardsRightCombat2HandMelee.fbx; Humanoid@WalkRightCombat2HandMelee.fbx; Humanoid@WalkForwardRightCombat2HandMelee.fbx | variant=in-place | duration=1.333 s | loop=true; sync=not-evaluated |
walk-combat-8-way-root-motion | eight-way directional gait | Humanoid@WalkForwardCombat2HandMelee_RM.fbx; Humanoid@WalkForwardLeftCombat2HandMelee_RM.fbx; Humanoid@WalkLeftCombat2HandMelee_RM.fbx; Humanoid@WalkBackwardsLeftCombat2HandMelee_RM.fbx; Humanoid@WalkBackwardsCombat2HandMelee_RM.fbx; Humanoid@WalkBackwardsRightCombat2HandMelee_RM.fbx; Humanoid@WalkRightCombat2HandMelee_RM.fbx; Humanoid@WalkForwardRightCombat2HandMelee_RM.fbx | variant=root-motion | duration=1.333 s; rm_speed=0.810 m/s | loop=true; sync=not-evaluated |
Pipeline-stage coverage
| Stage | Coverage state | Evidence / remaining gate |
|---|---|---|
| Acquire | evaluated-clean | Authorized retained source inventory |
| Preserve raw | evaluated-clean | Replay verified source and config hashes unchanged |
| Inspect | evaluated-finding | Exhaustive current inspect/measure/lint; notes and named warnings retained |
| Segment | partially-evaluated | Exact individual files measured; combined take not semantically segmented |
| Root motion | evaluated-finding | IP/RM measurements captured; controller ownership not evaluated |
| Conform | evaluated-finding | Fresh candidates produced but not adopted |
| Validate | evaluated-finding | Baseline and retained declarations complete; many declarations fail |
| Optimize | not-evaluated | Prune candidates are not production-adopted |
| Export | partially-evaluated | Candidate GLBs exist externally; no engine import acceptance |
| Gate/report | partially-evaluated | Mechanical report complete; engine and human gates open |
Readiness evidence by clip set
| Role or runtime set | File-ready / clip-ready | Set-ready / rig-use | Runtime / acceptance boundary |
|---|---|---|---|
| Individual motions | Current evaluator reads and measures exact files | Declaration-clean subset only | Named Unity source imports/samples ran; visual behavior not evaluated |
| Directional gait hypotheses | Timing, speed, skeleton, and current errors recorded | 0 contract-clean members in each named gait set | Named source samples and mixers only; full blend, phase, controller, and contact gates open |
| Transition/action hypotheses | Exact files measured | One-shot/loop intent requires authority | Named source Playables schedules ran; authored state graph, contact, and artistic gates open |
Validation-profile status
| Validation profile | Selection | Result / next evidence |
|---|---|---|
| Marketplace intake | selected — vendor-intended | Current source inventory and exhaustive mechanical intake complete. |
| Blended locomotion | selected — evaluator-selected-generic-scenario | Named gait hypotheses measured; selected source mixer schedules ran in Unity, while contracts and full blend review remain open. |
| Root-motion controller | selected — observed-pack-capability | RM variants measured; named Unity source root samples ran where applicable, while controller/collision ownership remains untested. |
| State-machine transitions | selected — observed-pack-capability | Transition candidates identified; named source Playables schedules ran, but authored state transitions and interruptions were not tested. |
| Layered upper body/weapons | not-selected | No mask/socket/IK authority or test. |
| Traversal/environment | not-selected | No target scene or traversal contacts. |
| Contact actions/interactions | selected — observed-pack-capability | Combat files present; weapon/body contacts not evaluated. |
| Retargeted/customizable characters | not-selected | No target rig or reference-pose test. |
| Motion matching/search | not-selected | No database construction or query test. |
| Networked movement | not-selected | No networking or reconciliation design. |
| Runtime performance | not-selected | No target build or benchmark. |
Pack inventory and content evidence
The current inventory contains 123 FBXs: 122 animation-bearing files, 120 individual-motion files, and 1 combined take. 118 individual-motion files share the 58-bone 3da84463466a skeleton; Humanoid@Blocked2HandMelee.fbx and Humanoid@IdleBlock2HandMelee.fbx use the 56-bone 2b6fe49d5ae6 variant. Counts describe physical current inputs, not runtime acceptance.
Current in-place gait phase evidence
Phase spread here is the minimum covering arc in cycles: sort phases in [0,1), include the wraparound gap, then subtract the largest gap from 1. It is not max_circular_deviation_from_mean; neither measure alone proves support-foot or visual compatibility. These are observed-animsmith source measurements for the exact hypotheses above; synchronization/contact acceptance remains open.
| Current hypothesis | Exact source member (Take 001) | Measured phase (cycles) |
|---|---|---|
crouch-combat-8-way | Humanoid@CrouchForward2HandMelee.fbx | 0.136668 |
crouch-combat-8-way | Humanoid@CrouchForwardLeft2HandMelee.fbx | 0.162019 |
crouch-combat-8-way | Humanoid@CrouchLeft2HandMelee.fbx | 0.213006 |
crouch-combat-8-way | Humanoid@CrouchBackwardsLeft2HandMelee.fbx | 0.783259 |
crouch-combat-8-way | Humanoid@CrouchBackwards2HandMelee.fbx | 0.857985 |
crouch-combat-8-way | Humanoid@CrouchBackwardsRight2HandMelee.fbx | 0.840786 |
crouch-combat-8-way | Humanoid@CrouchRight2HandMelee.fbx | 0.635619 |
crouch-combat-8-way | Humanoid@CrouchForwardRight2HandMelee.fbx | 0.145788 |
run-combat-8-way | Humanoid@RunForwardCombat2HandMelee.fbx | 0.117562 |
run-combat-8-way | Humanoid@RunForwardLeftCombat2HandMelee.fbx | 0.100797 |
run-combat-8-way | Humanoid@RunLeftCombat2HandMelee.fbx | 0.239777 |
run-combat-8-way | Humanoid@RunBackwardsLeftCombat2HandMelee.fbx | 0.792904 |
run-combat-8-way | Humanoid@RunBackwardsCombat2HandMelee.fbx | 0.688387 |
run-combat-8-way | Humanoid@RunBackwardsRightCombat2HandMelee.fbx | 0.710328 |
run-combat-8-way | Humanoid@RunRightCombat2HandMelee.fbx | 0.637374 |
run-combat-8-way | Humanoid@RunForwardRightCombat2HandMelee.fbx | 0.151982 |
walk-combat-8-way | Humanoid@WalkForwardCombat2HandMelee.fbx | 0.139942 |
walk-combat-8-way | Humanoid@WalkForwardLeftCombat2HandMelee.fbx | 0.150506 |
walk-combat-8-way | Humanoid@WalkLeftCombat2HandMelee.fbx | 0.382751 |
walk-combat-8-way | Humanoid@WalkBackwardsLeftCombat2HandMelee.fbx | 0.304546 |
walk-combat-8-way | Humanoid@WalkBackwardsCombat2HandMelee.fbx | 0.851128 |
walk-combat-8-way | Humanoid@WalkBackwardsRightCombat2HandMelee.fbx | 0.694238 |
walk-combat-8-way | Humanoid@WalkRightCombat2HandMelee.fbx | 0.678060 |
walk-combat-8-way | Humanoid@WalkForwardRightCombat2HandMelee.fbx | 0.653921 |
Mechanical baseline
| Finding/check | Affected scope | Potential impact | Evidence |
|---|---|---|---|
| Current readability | 123/123 inputs | Enables mechanical intake | observed-animsmith; all four commands per input exit 0 |
| Constant tracks | 17010 notes across 122 animation-bearing files | Storage/evaluation noise; not a gameplay defect by itself | observed-animsmith; baseline ledger SHA-256 ddb5a9e2e0cdc97bd4b768fcf8700da32f4a22358babd50a101416a14c9737d5 |
| Retained declarations | 107/120 non-clean | Blocks admission under those exact declarations | observed-animsmith; contract ledger SHA-256 0438c30a7e86b46ca0be07b02baf0e7145a22125fc6f6f8f8eacfef2047a5267 |
| Structural/timing warnings | Named scope above | Requires bounded author/project decision | observed-file and observed-animsmith |
AnimSmith remediation evidence
| Source issue | Operation/declarations | Result | Independent verification | Remaining caveat |
|---|---|---|---|---|
PF2-01 gait continuity | transform --gait-anchor on 24 selected in-place gait files; retained per-file configs | 24 outputs produced; selected inputs have no accumulating root translation/yaw | 24 inspect and measure commands exit 0; output hashes and diffs captured | Post-config lint remains non-clean; no RM trajectory was transformed; visual/contact residual unresolved |
| Constant-track notes | transform --prune-constant-tracks on Humanoid@IdleCombatA2HandMelee.fbx | Output candidate(s) produced | Inspect, measure, diff, fix dry-run, and config lint captured | 25/25 post-transform config lints exit 1. Candidate adoption and engine/visual acceptance remain open |
Fresh remediation ledger SHA-256 0f15f4171bf799b5e4bef91251f94dbfaaed8d6f4341a59b05be52f8e1b61430. Generated motion stays outside Git.
Engine procedures and evidence
| Runtime | Version | Procedure | Observed result | Remaining gate |
|---|---|---|---|---|
| Unity | 6000.5.8f1 | Fresh named Humanoid source import and standalone sampling: IdleCombatA2HandMelee, WalkForwardCombat2HandMelee, WalkForwardCombat2HandMelee_RM; named Playables mixers: basic-idle-to-twohand-idle, sword-shield-idle-to-twohand-idle, basic-walk-ip-to-twohand-walk-ip. See collection source/controller probe. | Bounded source/controller feasibility for listed sources only; the collection’s 26/26 is a shared aggregate, not this pack’s result | Rendered motion, contact, deformation, full blend, retargeting, authored AnimatorController, player build and performance remain untested |
| Unreal Engine | not evaluated | No current procedure executed | not-evaluated | Import, retarget, root, blend, and visual review |
| Godot | not evaluated | No current procedure executed | not-evaluated | Convert/import, graph, root, contact, and visual review |
| Bevy | not evaluated | No current procedure executed | not-evaluated | Convert/load, graph, root, performance, and visual review |
Rig, masking, and compatibility evidence
| Pack/rig/set pair | Skeleton/retarget | Scale/axes | Root policy | Timing/blend | Overall evidence |
|---|---|---|---|---|---|
| Two-Handed Melee internal gait sets | Current signatures measured; no retarget | No engine scale/axis test | IP/RM files measured; owner not selected | Current duration/speed/phase recorded; full blend absent | Mechanical only |
| Two Handed Melee plus Basic Locomotion | Named Humanoid sources imported on shared SK_Protof-Actor | Unity import scale/axis accepted for named sources only | Gameplay-owned root fixed for IP/idle; animation-owned root finite for tested RM where applicable | Named Playables transitions finite; visual blend not inspected | Bounded source/controller feasibility, not compatibility acceptance |
Limitations and unknowns
- Retained configs are replay controls, not proof that every attack, reaction, parry, or emote should loop.
- Current grouping hypotheses were remeasured from exact files, but vendor/project semantics and the unavailable richer historical membership authority were not reconstructed.
- Generated candidates remain external and unadopted; command success does not lower residual severity.
- Unity evidence is limited to named source import, finite headless poses, explicit root ownership, and named Playables mixers; no rendered visual, contact, retarget, mask, authored-controller, player-build, performance, or artistic acceptance was performed.
Changes between AnimSmith versions
AnimSmith 0.14.0 — current official binary revalidated source hashes, all baseline and declaration commands, named runtime hypotheses, and every retained remediation recipe. AnimSmith 0.10.0 — retained ledgers supplied immutable source/config controls; historical results are superseded for current conclusions.
Reproduction
Official evaluator preflight: release archive, archive SHA-256 4ecf79436f9123c779edb004050da3010b44a2b392e3031facb227d7734fc33e; exact member animsmith-v0.14.0-x86_64-unknown-linux-gnu/animsmith, binary SHA-256 c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366; tag v0.14.0, peeled commit e8321ad40be5ef6f162b31f085819c039175c3c9; working-tree state: N/A (official archive). Compiled features: fbx, report. Version and required command help plus representative FBX admission passed before evaluation. Preflight: external:animsmith-0.14.0-report-refresh/preflight.json, SHA-256 7034b5043de54e7b30064230db18605d1f96d0ea1477f7b8c56ce817cc73521a.
Official archive https://github.com/mmannerm/animsmith/releases/download/v0.14.0/animsmith-v0.14.0-x86_64-unknown-linux-gnu.tar.gz; archive SHA-256 4ecf79436f9123c779edb004050da3010b44a2b392e3031facb227d7734fc33e; member animsmith-v0.14.0-x86_64-unknown-linux-gnu/animsmith; binary SHA-256 c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366; tag v0.14.0; commit e8321ad40be5ef6f162b31f085819c039175c3c9; working tree N/A; features fbx/report.
Preflight verified version/help for required commands and representative GLB/FBX admission before replay; scrubbed locator external:0.14.0-report-refresh/preflight.json, SHA-256 7034b5043de54e7b30064230db18605d1f96d0ea1477f7b8c56ce817cc73521a. The read-only helper replayed the retained exact inspect, measure, and two lint invocations with four workers, recorded argv/exit/stdout/stderr and source/config hashes, and did not mutate inputs. Baseline, contract, and remediation ledger digests are ddb5a9e2e0cdc97bd4b768fcf8700da32f4a22358babd50a101416a14c9737d5, 0438c30a7e86b46ca0be07b02baf0e7145a22125fc6f6f8f8eacfef2047a5267, and 0f15f4171bf799b5e4bef91251f94dbfaaed8d6f4341a59b05be52f8e1b61430. Licensed paths, sources, and derivative outputs are excluded from Git and this report.
Fresh Unity evidence: editor 6000.5.8f1, Unity.exe SHA-256 cdc0eeca135394cde79a632aa998aca14b521b58afe8c2750b009aff608be906; probe SHA-256 28cd00897dfd739e2a7bcfa193adab03de3591a1009802946305127b67d1bfb6; result schema animsmith-unity-crosspack-probe-v1, result SHA-256 f067bca91f182b65fab408a1c97b297377a8f361b03d59e809e236aba666652c; log SHA-256 0429bcb9cc7a7492c6637eb799566463ba915243d0a110b4d76cbe75590c8819; 26/26 aggregate required checks passed with 53 nonempty Humanoid bones per sample. This report uses only the named applicable subset above.
Sources
- Protofactor product context and End User License Agreement — context only; local revision remains unknown.
Animation pack evaluation: Protofactor Dual Swords Animset
Technical verdict: Usable with conditions
Evaluation completeness: partial — source files, declared checks and transform trials were reviewed, with limited Unity source playback; target-character visuals, contacts, retargeting and the game controller remain untested.
Confidence: medium
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
Detailed evidence: Protofactor Dual Swords Animset evidence appendix
Technical decision
Prototype decision: Try one in-place or root-motion gait variant at a time after loop-policy review. Continuous gait defects require artist or source correction if confirmed; contacts and cross-pack stance changes remain unknown. In-place clips leave travel to the controller; root-motion clips carry authored travel.
Camera scope: Full-body humanoid animations; appearance on your target character and camera is untested. Dedicated first-person arms/viewmodel use is unverified.
This animset is one of eight locally evaluated parts of Ultimate Animation Collection. The local asset revision is unknown; the collection report separates evaluated files from the dated vendor listing.
Content in evaluated inventory: Armed walk, run, crouch and equipment/actions. The evaluated role inventory includes armed gaits, actions and reactions; no airborne motion was classified in it. These have no accepted controller topology here. These are identified motions; gameplay behavior has not been approved.
AnimSmith 0.14.0 reads all 189 delivered FBX candidates. The untouched baseline has no errors; it reports 25426 constant-track notes. With the per-file settings used in this evaluation, 24/186 motion files pass and 162 fail, mainly because loop settings need review. These loop and movement settings still need confirmation from the project or vendor; their failures are not all source defects. Set confirmed one-time actions to play once before treating their wrap seams as source defects. For intended repeating gaits, review the loop policy and correct actual source seams.
All 25 transform trials passed file inspection and measurement. All transformed outputs still fail their declared checks. A target-engine diagnostic can play unchanged sources now to reveal seams and contact problems. Before admitting a selected gait to the proposed controller, correct confirmed one-shot settings, establish one gait set that passes its intended checks, and review its full blend, foot contacts, movement and visuals. The transform outputs have not been accepted as replacements.
Capability coverage
Content present
Filename-classified walk, run and crouch combat gaits have separate in-place and root-motion candidates. The evaluated role inventory includes actions and reactions, but records zero airborne motions; that classification does not establish absence from every delivered file.
Action candidates to inspect: The retained runtime-set inventory groups combo-alternatives (38 files; 19 in-place, 19 root-motion; 1/38 passes the retained declaration), single-attack-alternatives (22 files; 11 of each variant; 0/22), and draw-combat-put-away (3 in-place; 2/3). These are candidate groups, not an accepted combo or equip system. Individual action filenames, timing, contacts and interruption rules are not bound by the public grouping rows; inspect source members before implementing them.
Content gaps and unknowns
Additive aim, paired interactions and dedicated viewmodel content have no established classification. Cross-pack identity and safe co-installation are not established by shared naming.
Evaluation still needed
Redeclare one-shots, resolve continuous gait contracts, and test blend coordinates, root ownership, dual-grip contact and target-character playback. Named Unity executions do not accept the full controller.
Runtime sets and authored motion
Use each row as a separate blend or sequence proposal. The linked appendix names its exact clips, timings and measurements; controller playback and contact quality still need testing.
| Set | Controller use | Adoption decision | Exact members |
|---|---|---|---|
crouch-combat-8-way-in-place | Crouch eight directions; controller travel | Crouch prototype with controller travel. Its declared checks still fail. Confirm which motions should loop, correct any genuine seam, and test foot contacts before using the full blend. | 8 exact members |
crouch-combat-8-way-root-motion | Crouch eight directions; animation travel; measured root-motion 0.730–0.732 m/s | Crouch prototype with animation travel. Its declared checks still fail. Confirm which motions should loop, correct any genuine seam, and test foot contacts before using the full blend. | 8 exact members |
run-combat-8-way-in-place | Run eight directions; controller travel | Run prototype with controller travel. Its declared checks still fail. Confirm which motions should loop, correct any genuine seam, and test foot contacts before using the full blend. | 8 exact members |
run-combat-8-way-root-motion | Run eight directions; animation travel; measured root-motion 2.475–2.500 m/s | Run prototype with animation travel. Its declared checks still fail. Confirm which motions should loop, correct any genuine seam, and test foot contacts before using the full blend. | 8 exact members |
walk-combat-8-way-in-place | Walk eight directions; controller travel | Walk prototype with controller travel. Its declared checks still fail. Confirm which motions should loop, correct any genuine seam, and test foot contacts before using the full blend. | 8 exact members |
walk-combat-8-way-root-motion | Walk eight directions; animation travel; measured root-motion 0.692–0.765 m/s | Walk prototype with animation travel. Its declared checks still fail. Confirm which motions should loop, correct any genuine seam, and test foot contacts before using the full blend. | 8 exact members |
The appendix gives timings, travel speeds and phase measurements for each motion. Use the in-place and root-motion rows as alternatives; neither variant has accepted contact synchronization.
Integration recipe
- Members/topology: Select one eight-member in-place or root-motion row from the appendix and map forward/cardinal/diagonal coordinates explicitly.
- Timing/synchronization: Loop only confirmed continuous states, keep named one-shots at
loop=false, and require phase/contact review before synchronized blending. - State ownership: Choose controller translation for in-place clips or animation translation for root-motion clips per state, with one owner for translation, yaw, and collision.
- Composition constraints: Keep weapon combat states namespaced to this pack and require separate mask/socket/IK evidence before layering.
- Acceptance gate: Require clean intended contracts plus target-engine import, full blend-space playback, foot/weapon contact, and artist review.
Technical issue register
| ID | Severity | Problem and impact | Primary owner | Current action | Future AnimSmith potential | Evidence/status |
|---|---|---|---|---|---|---|
| PFD-PHASE | note | The three selected in-place gait sets have measured phase differences (see appendix). Foot-contact timing and intended synchronization are unknown, so the measurements alone do not show a source defect. Guidance. | unknown | Obtain project or vendor loop/contact intent, then sweep the exact blend coordinates against a declared contact tolerance. Use engine phase markers or declared alignment only after intent is established; request artist cleanup only for a confirmed source-motion problem. Residual unresolved. | Current phase measurements inform a declared alignment trial; they do not prove contact or visual acceptance. | observed-animsmith phases; synchronization/contact acceptance not-evaluated. |
| PFD-01 | major | Humanoid@RunForwardDualSwords.fbx, Humanoid@RunForwardDualSwords_RM.fbx and their eight-direction sets fail the per-file settings used in this evaluation; none of the selected gait sets is ready for a production blend. Guidance | artist-author | Re-export intended repeating gaits with matching endpoint pose and velocity, or document a play-once setting. All eight members must pass their intended checks, then pass visual and contact review across the blend. Unresolved. | AnimSmith can validate and compare candidates, but cannot infer intended contact phase or approve visible motion. | observed-animsmith; current exact-file evidence, high confidence mechanically |
| PFD-02 | moderate | Humanoid@Attack1DualSwords.fbx and other attacks, reactions, parries or emotes are declared as loops. Their wrap-seam findings may reflect the project setting for one-time actions rather than bad source motion. Guidance | engine-config | Set confirmed one-time actions to loop=false; keep looping only where the project or vendor intends it. Recheck the settings and play each transition once. Unresolved. | AnimSmith may suggest a play-once setting, but the project or vendor must confirm intent. | observed-animsmith; declaration defect distinguished from source-animation quality |
Engine status
| Runtime | Evidence level | Technical result | Remaining gate |
|---|---|---|---|
| Unity 6000.5.8f1 | source-controller-feasibility | Fresh import and standalone sampling passed for IdleCombatDualSwords, WalkForwardDualSwords, and WalkForwardDualSwords_RM; basic-idle-to-dual-idle, sword-shield-idle-to-dual-idle, and basic-walk-ip-to-dual-walk-ip produced finite mixer poses with fixed gameplay-owned root; the root-motion walk produced finite 0.968650 owner translation with animation-owned root. | Inspect rendered motion, contacts, deformation, authored AnimatorController behavior, and a player build; do not generalize beyond named sources. |
| Unreal Engine unspecified | not-evaluated | No current import or retarget result. | Import, retarget, root-motion, blend, and visual tests. |
| Godot unspecified | not-evaluated | No current conversion or playback result. | Convert/import and test animation graph, roots, contacts, and visuals. |
| Bevy unspecified | not-evaluated | No current conversion or playback result. | Convert/load and test graph, root ownership, performance, and visuals. |
Fit and limitations
Best fit is a full-body combat prototype whose controller can explicitly select one gait variant and keep pack-local state names. It is a poor fit for immediate production admission, motion matching, layered combat, or retargeted characters without the missing engine and human gates.
The fresh Unity probe establishes finite source playback and the named cross-pack transitions for IdleCombatDualSwords, WalkForwardDualSwords, and WalkForwardDualSwords_RM; it does not establish visual quality, contact, phase, retargeting, or broad pack compatibility. Keep states namespaced and retain explicit root ownership.
Changes between AnimSmith versions
AnimSmith 0.14.0 — revalidated the unchanged retained source inventory, per-file declarations, measured runtime hypotheses, and every previously recommended bounded transform. Current transformed outputs still need gameplay and visual checks. AnimSmith 0.10.0 — retained command ledgers and classification material supplied replay controls; its conclusions are superseded for current behavior.
Evidence status
Current evidence covers 189 physical FBXs and 186 logical individual-motion files with official AnimSmith 0.14.0 and the retained evaluation-manifest schema, now presented in editorial report format 3. A bounded Unity source/controller probe is current; visual/artistic, contact, retarget, authored-controller, and player-build gates remain open. See the readiness ladder and the evidence appendix. Licensed source and generated candidates remain outside Git.
Sources
- Protofactor product context and End User License Agreement — vendor context only; it does not identify the local constituent revision.
Animation pack evidence appendix: Protofactor Dual Swords Animset
Companion report: technical evaluation
Evidence status: partial — current mechanical, declaration, runtime-group, and remediation evidence completed; engine and human acceptance did not.
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
The canonical readiness ladder remains authoritative.
Evaluation scope and provenance
| Field | Value |
|---|---|
| Pack/edition | Authorized local Protofactor constituent; revision unknown |
| Vendor/source | Protofactor product context; local revision not established by the current listing |
| Delivered scope | 189 retained FBX candidates; source bytes unchanged during replay |
| Target use | Broad game-engine technical intake; no target controller supplied |
| Target engines | Unity 6000.5.8f1 bounded source/controller probe; Unreal, Godot, and Bevy not evaluated |
| Target rigs/packs | Delivered rigs; Basic Locomotion compatibility not freshly evaluated |
| Source manifest | external:protofactor-dual-swords/source-inventory.json; SHA-256 6686db08c5d264823473332b0c35aa55e068e1d01595797bfc8710da6ea81a7e |
| Evaluation manifest | Retained urn:animsmith:skill:animation-pack-evaluation-manifest:1; current memberships treated as hypotheses and remeasured |
| Acquisition/license provenance | Authorized local commercial input; no legal conclusion |
Evidence coverage
| Surface | Offered/delivered | Evaluated | Findings | Not evaluated and why |
|---|---|---|---|---|
| Animation files | 189 | 189 | 188 animation-bearing | Engine semantics not implied |
| Rigs/export variants | 189 | 189 | 186 individual-motion files share the 56-bone 2b6fe49d5ae6 skeleton | Retarget/reference-pose behavior not run |
| AnimSmith baseline | 189 | 189 | All inspect/measure/two lint commands exit 0; No baseline warnings or errors. | N/A |
| Declared contracts | 186 | 186 | 24 pass, 162 fail in each format | Declaration intent needs project/vendor authority |
| Offline visual reports | 0 | 0 | 0 | Not run |
| Engine import/playback | 3 named Unity sources | 3 | Fresh Humanoid import and finite standalone sampling passed | Visual/contact, authored-controller, and player-build behavior not evaluated |
| Blend/mask/retarget | Three named source Playables mixers | Three fixed-root schedules | Finite sampled poses for named cross-pack pairs | No full blend tree, mask, separate target-rig retarget, visual or contact acceptance |
Claim legend
observed-file identifies source structure, observed-animsmith identifies current tool output, inferred identifies bounded hypotheses, and not-evaluated marks open gates.
Evaluation manifest and taxonomy
Current evaluation manifest schema: urn:animsmith:skill:animation-pack-evaluation-manifest:1.
Canonical clip-role inventory
| Canonical primary role | Logical motions | Delivered files | Evidence boundary |
|---|---|---|---|
idle-pose | 6 | 6 | observed-file; evaluator classification, not vendor semantics |
continuous-locomotion | 70 | 70 | observed-file; evaluator classification, not vendor semantics |
locomotion-transition | 0 | 0 | observed-file; evaluator classification, not vendor semantics |
airborne | 0 | 0 | observed-file; evaluator classification, not vendor semantics |
traversal | 0 | 0 | observed-file; evaluator classification, not vendor semantics |
action-interaction | 86 | 86 | observed-file; evaluator classification, not vendor semantics |
reaction-death | 24 | 24 | observed-file; evaluator classification, not vendor semantics |
emote-cinematic | 0 | 0 | observed-file; evaluator classification, not vendor semantics |
other-unknown | 0 | 0 | observed-file; evaluator classification, not vendor semantics |
| Total | 186 | 186 | Current exact-file catalog |
Runtime-set inventory
The untested acceptance below applies to each complete set. Named Unity source samples and mixers ran for a smaller subset; they do not establish full-set playback or visual quality.
| Runtime set | Type | Members/variants | Grouping evidence | Validation status |
|---|---|---|---|---|
crouch-combat-8-way | directional-blend | 16 exact files; 8 IP, 8 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 0/16 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
run-combat-8-way | directional-blend | 16 exact files; 8 IP, 8 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 0/16 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
walk-combat-8-way | directional-blend | 16 exact files; 8 IP, 8 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 0/16 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
forward-speed-alternatives | speed-blend | 10 exact files; 5 IP, 5 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 0/10 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
draw-combat-put-away | transition-chain | 3 exact files; 3 IP, 0 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 2/3 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
combo-alternatives | other | 38 exact files; 19 IP, 19 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 1/38 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
single-attack-alternatives | other | 22 exact files; 11 IP, 11 RM | Current exact-file measurements; grouping is an evaluator-defined hypothesis seeded by retained catalog declarations | 0/22 files pass the retained declaration; whole-set engine and visual acceptance not evaluated |
crouch-combat-8-way-in-place | directional-blend | 8 exact IP files | Selectable variant of the measured crouch-combat-8-way source family | Retained gait contracts non-clean; contact/engine acceptance open |
crouch-combat-8-way-root-motion | directional-blend | 8 exact RM files | Selectable variant of the measured crouch-combat-8-way source family | Retained gait contracts non-clean; contact/engine acceptance open |
run-combat-8-way-in-place | directional-blend | 8 exact IP files | Selectable variant of the measured run-combat-8-way source family | Retained gait contracts non-clean; contact/engine acceptance open |
run-combat-8-way-root-motion | directional-blend | 8 exact RM files | Selectable variant of the measured run-combat-8-way source family | Retained gait contracts non-clean; contact/engine acceptance open |
walk-combat-8-way-in-place | directional-blend | 8 exact IP files | Selectable variant of the measured walk-combat-8-way source family | Retained gait contracts non-clean; contact/engine acceptance open |
walk-combat-8-way-root-motion | directional-blend | 8 exact RM files | Selectable variant of the measured walk-combat-8-way source family | Retained gait contracts non-clean; contact/engine acceptance open |
The exact runtime members below record source members and fresh timing/motion for the decision-driving gait sets. No unavailable membership authority is inferred from prose or filenames.
Grouped gait measurements (retained)
Current source members and measurements for the selected runtime scenarios. Selection is an evaluator hypothesis; source identity and measured values are retained below.
For these multi-member gait rows, duration and RM speed cells are set minima, not per-clip values. The ranges below retain authored variation; individual speed assignments are not reproduced here.
The original grouped gait rows below mix eight in-place and eight root-motion files. The selectable eight-member variants follow this retained source table.
| Set/profile | Role or coordinate | Exact members | Variant/type | Timing or motion | Runtime contract |
|---|---|---|---|---|---|
crouch-combat-8-way | eight-way directional gait | Humanoid@CrouchForwardDualSwords.fbx; Humanoid@CrouchForwardDualSwords_RM.fbx; Humanoid@CrouchForwardLeftDualSwords.fbx; Humanoid@CrouchForwardLeftDualSwords_RM.fbx; Humanoid@CrouchLeftDualSwords.fbx; Humanoid@CrouchLeftDualSwords_RM.fbx; Humanoid@CrouchBackwardsLeftDualSwords.fbx; Humanoid@CrouchBackwardsLeftDualSwords_RM.fbx; Humanoid@CrouchBackwardsDualSwords.fbx; Humanoid@CrouchBackwardsDualSwords_RM.fbx; Humanoid@CrouchBackwardsRightDualSwords.fbx; Humanoid@CrouchBackwardsRightDualSwords_RM.fbx; Humanoid@CrouchRightDualSwords.fbx; Humanoid@CrouchRightDualSwords_RM.fbx; Humanoid@CrouchForwardRightDualSwords.fbx; Humanoid@CrouchForwardRightDualSwords_RM.fbx | set_type=directional-blend | duration=1.667 s; rm_speed=0.730 m/s | loop_ip=true; loop_rm=true; sync=not-evaluated |
run-combat-8-way | eight-way directional gait | Humanoid@RunForwardDualSwords.fbx; Humanoid@RunForwardDualSwords_RM.fbx; Humanoid@RunForwardLeftDualSwords.fbx; Humanoid@RunForwardLeftDualSwords_RM.fbx; Humanoid@RunLeftDualSwords.fbx; Humanoid@RunLeftDualSwords_RM.fbx; Humanoid@RunBackwardsLeftDualSwords.fbx; Humanoid@RunBackwardsLeftDualSwords_RM.fbx; Humanoid@RunBackwardsDualSwords.fbx; Humanoid@RunBackwardsDualSwords_RM.fbx; Humanoid@RunBackwardsRightDualSwords.fbx; Humanoid@RunBackwardsRightDualSwords_RM.fbx; Humanoid@RunRightDualSwords.fbx; Humanoid@RunRightDualSwords_RM.fbx; Humanoid@RunForwardRightDualSwords.fbx; Humanoid@RunForwardRightDualSwords_RM.fbx | set_type=directional-blend | duration=0.600 s; rm_speed=2.475 m/s | loop_ip=true; loop_rm=true; sync=not-evaluated |
walk-combat-8-way | eight-way directional gait | Humanoid@WalkForwardDualSwords.fbx; Humanoid@WalkForwardDualSwords_RM.fbx; Humanoid@WalkForwardLeftDualSwords.fbx; Humanoid@WalkForwardLeftDualSwords_RM.fbx; Humanoid@WalkLeftDualSwords.fbx; Humanoid@WalkLeftDualSwords_RM.fbx; Humanoid@WalkBackwardsLeftDualSwords.fbx; Humanoid@WalkBackwardsLeftDualSwords_RM.fbx; Humanoid@WalkBackwardsDualSwords.fbx; Humanoid@WalkBackwardsDualSwords_RM.fbx; Humanoid@WalkBackwardsRightDualSwords.fbx; Humanoid@WalkBackwardsRightDualSwords_RM.fbx; Humanoid@WalkRightDualSwords.fbx; Humanoid@WalkRightDualSwords_RM.fbx; Humanoid@WalkForwardRightDualSwords.fbx; Humanoid@WalkForwardRightDualSwords_RM.fbx | set_type=directional-blend | duration=1.333 s; rm_speed=0.692 m/s | loop_ip=true; loop_rm=true; sync=not-evaluated |
Measured RM speed spans are crouch-combat-8-way 0.730–0.732 m/s (ratio 0.997); run-combat-8-way 2.475–2.500 m/s (ratio 0.990); walk-combat-8-way 0.692–0.765 m/s (ratio 0.905). Preserve authored variation unless the project declares a normalization policy; diagonals and cardinals require the full blend test. Measured source in-place phase spreads (cycles, eight measured members each) are crouch-combat-8-way 0.6184; run-combat-8-way 0.6732; walk-combat-8-way 0.7086. Phase spread here is the minimum covering arc in cycles: sort phases in [0,1), include the wraparound gap, then subtract the largest gap from 1. It is not max_circular_deviation_from_mean; neither measure alone proves support-foot or visual compatibility. Keep sync=not-evaluated until the selected synchronization and contact policy is tested. The current catalog also measures forward-speed-alternatives, draw-combat-put-away, combo-alternatives, and single-attack-alternatives as evaluator-defined hypotheses; they remain candidate groupings.
Exact runtime members
Each row is one selectable eight-member variant. Duration and RM speed ranges are preserved in the grouped measurement paragraph above; numeric table values are the measured minima. Continuous-loop declarations remain unaccepted.
| Set/profile | Role or coordinate | Exact members | Variant/type | Timing or motion | Runtime contract |
|---|---|---|---|---|---|
crouch-combat-8-way-in-place | eight-way directional gait | Humanoid@CrouchForwardDualSwords.fbx; Humanoid@CrouchForwardLeftDualSwords.fbx; Humanoid@CrouchLeftDualSwords.fbx; Humanoid@CrouchBackwardsLeftDualSwords.fbx; Humanoid@CrouchBackwardsDualSwords.fbx; Humanoid@CrouchBackwardsRightDualSwords.fbx; Humanoid@CrouchRightDualSwords.fbx; Humanoid@CrouchForwardRightDualSwords.fbx | variant=in-place | duration=1.667 s | loop=true; sync=not-evaluated |
crouch-combat-8-way-root-motion | eight-way directional gait | Humanoid@CrouchForwardDualSwords_RM.fbx; Humanoid@CrouchForwardLeftDualSwords_RM.fbx; Humanoid@CrouchLeftDualSwords_RM.fbx; Humanoid@CrouchBackwardsLeftDualSwords_RM.fbx; Humanoid@CrouchBackwardsDualSwords_RM.fbx; Humanoid@CrouchBackwardsRightDualSwords_RM.fbx; Humanoid@CrouchRightDualSwords_RM.fbx; Humanoid@CrouchForwardRightDualSwords_RM.fbx | variant=root-motion | duration=1.667 s; rm_speed=0.730 m/s | loop=true; sync=not-evaluated |
run-combat-8-way-in-place | eight-way directional gait | Humanoid@RunForwardDualSwords.fbx; Humanoid@RunForwardLeftDualSwords.fbx; Humanoid@RunLeftDualSwords.fbx; Humanoid@RunBackwardsLeftDualSwords.fbx; Humanoid@RunBackwardsDualSwords.fbx; Humanoid@RunBackwardsRightDualSwords.fbx; Humanoid@RunRightDualSwords.fbx; Humanoid@RunForwardRightDualSwords.fbx | variant=in-place | duration=0.600 s | loop=true; sync=not-evaluated |
run-combat-8-way-root-motion | eight-way directional gait | Humanoid@RunForwardDualSwords_RM.fbx; Humanoid@RunForwardLeftDualSwords_RM.fbx; Humanoid@RunLeftDualSwords_RM.fbx; Humanoid@RunBackwardsLeftDualSwords_RM.fbx; Humanoid@RunBackwardsDualSwords_RM.fbx; Humanoid@RunBackwardsRightDualSwords_RM.fbx; Humanoid@RunRightDualSwords_RM.fbx; Humanoid@RunForwardRightDualSwords_RM.fbx | variant=root-motion | duration=0.600 s; rm_speed=2.475 m/s | loop=true; sync=not-evaluated |
walk-combat-8-way-in-place | eight-way directional gait | Humanoid@WalkForwardDualSwords.fbx; Humanoid@WalkForwardLeftDualSwords.fbx; Humanoid@WalkLeftDualSwords.fbx; Humanoid@WalkBackwardsLeftDualSwords.fbx; Humanoid@WalkBackwardsDualSwords.fbx; Humanoid@WalkBackwardsRightDualSwords.fbx; Humanoid@WalkRightDualSwords.fbx; Humanoid@WalkForwardRightDualSwords.fbx | variant=in-place | duration=1.333 s | loop=true; sync=not-evaluated |
walk-combat-8-way-root-motion | eight-way directional gait | Humanoid@WalkForwardDualSwords_RM.fbx; Humanoid@WalkForwardLeftDualSwords_RM.fbx; Humanoid@WalkLeftDualSwords_RM.fbx; Humanoid@WalkBackwardsLeftDualSwords_RM.fbx; Humanoid@WalkBackwardsDualSwords_RM.fbx; Humanoid@WalkBackwardsRightDualSwords_RM.fbx; Humanoid@WalkRightDualSwords_RM.fbx; Humanoid@WalkForwardRightDualSwords_RM.fbx | variant=root-motion | duration=1.333 s; rm_speed=0.692 m/s | loop=true; sync=not-evaluated |
Pipeline-stage coverage
| Stage | Coverage state | Evidence / remaining gate |
|---|---|---|
| Acquire | evaluated-clean | Authorized retained source inventory |
| Preserve raw | evaluated-clean | Replay verified source and config hashes unchanged |
| Inspect | evaluated-finding | Exhaustive current inspect/measure/lint; notes and named warnings retained |
| Segment | partially-evaluated | Exact individual files measured; combined take not semantically segmented |
| Root motion | evaluated-finding | IP/RM measurements captured; controller ownership not evaluated |
| Conform | evaluated-finding | Fresh candidates produced but not adopted |
| Validate | evaluated-finding | Baseline and retained declarations complete; many declarations fail |
| Optimize | not-evaluated | Prune candidates are not production-adopted |
| Export | partially-evaluated | Candidate GLBs exist externally; no engine import acceptance |
| Gate/report | partially-evaluated | Mechanical report complete; engine and human gates open |
Readiness evidence by clip set
| Role or runtime set | File-ready / clip-ready | Set-ready / rig-use | Runtime / acceptance boundary |
|---|---|---|---|
| Individual motions | Current evaluator reads and measures exact files | Declaration-clean subset only | Named Unity source imports/samples ran; visual behavior not evaluated |
| Directional gait hypotheses | Timing, speed, skeleton, and current errors recorded | 0 contract-clean members in each named gait set | Named source samples and mixers only; full blend, phase, controller, and contact gates open |
| Transition/action hypotheses | Exact files measured | One-shot/loop intent requires authority | Named source Playables schedules ran; authored state graph, contact, and artistic gates open |
Validation-profile status
| Validation profile | Selection | Result / next evidence |
|---|---|---|
| Marketplace intake | selected — vendor-intended | Current source inventory and exhaustive mechanical intake complete. |
| Blended locomotion | selected — evaluator-selected-generic-scenario | Named gait hypotheses measured; selected source mixer schedules ran in Unity, while contracts and full blend review remain open. |
| Root-motion controller | selected — observed-pack-capability | RM variants measured; named Unity source root samples ran where applicable, while controller/collision ownership remains untested. |
| State-machine transitions | selected — observed-pack-capability | Transition candidates identified; named source Playables schedules ran, but authored state transitions and interruptions were not tested. |
| Layered upper body/weapons | not-selected | No mask/socket/IK authority or test. |
| Traversal/environment | not-selected | No target scene or traversal contacts. |
| Contact actions/interactions | selected — observed-pack-capability | Combat files present; weapon/body contacts not evaluated. |
| Retargeted/customizable characters | not-selected | No target rig or reference-pose test. |
| Motion matching/search | not-selected | No database construction or query test. |
| Networked movement | not-selected | No networking or reconciliation design. |
| Runtime performance | not-selected | No target build or benchmark. |
Pack inventory and content evidence
The current inventory contains 189 FBXs: 188 animation-bearing files, 186 individual-motion files, and 1 combined take. 186 individual-motion files share the 56-bone 2b6fe49d5ae6 skeleton. Counts describe physical current inputs, not runtime acceptance.
Current in-place gait phase evidence
Phase spread here is the minimum covering arc in cycles: sort phases in [0,1), include the wraparound gap, then subtract the largest gap from 1. It is not max_circular_deviation_from_mean; neither measure alone proves support-foot or visual compatibility. These are observed-animsmith source measurements for the exact hypotheses above; synchronization/contact acceptance remains open.
| Current hypothesis | Exact source member (Take 001) | Measured phase (cycles) |
|---|---|---|
crouch-combat-8-way | Humanoid@CrouchForwardDualSwords.fbx | 0.266199 |
crouch-combat-8-way | Humanoid@CrouchForwardLeftDualSwords.fbx | 0.244612 |
crouch-combat-8-way | Humanoid@CrouchLeftDualSwords.fbx | 0.325734 |
crouch-combat-8-way | Humanoid@CrouchBackwardsLeftDualSwords.fbx | 0.783295 |
crouch-combat-8-way | Humanoid@CrouchBackwardsDualSwords.fbx | 0.820047 |
crouch-combat-8-way | Humanoid@CrouchBackwardsRightDualSwords.fbx | 0.816853 |
crouch-combat-8-way | Humanoid@CrouchRightDualSwords.fbx | 0.707328 |
crouch-combat-8-way | Humanoid@CrouchForwardRightDualSwords.fbx | 0.186673 |
run-combat-8-way | Humanoid@RunForwardDualSwords.fbx | 0.194700 |
run-combat-8-way | Humanoid@RunForwardLeftDualSwords.fbx | 0.265383 |
run-combat-8-way | Humanoid@RunLeftDualSwords.fbx | 0.298649 |
run-combat-8-way | Humanoid@RunBackwardsLeftDualSwords.fbx | 0.803162 |
run-combat-8-way | Humanoid@RunBackwardsDualSwords.fbx | 0.831072 |
run-combat-8-way | Humanoid@RunBackwardsRightDualSwords.fbx | 0.723860 |
run-combat-8-way | Humanoid@RunRightDualSwords.fbx | 0.625469 |
run-combat-8-way | Humanoid@RunForwardRightDualSwords.fbx | 0.112560 |
walk-combat-8-way | Humanoid@WalkForwardDualSwords.fbx | 0.163464 |
walk-combat-8-way | Humanoid@WalkForwardLeftDualSwords.fbx | 0.142984 |
walk-combat-8-way | Humanoid@WalkLeftDualSwords.fbx | 0.324181 |
walk-combat-8-way | Humanoid@WalkBackwardsLeftDualSwords.fbx | 0.884376 |
walk-combat-8-way | Humanoid@WalkBackwardsDualSwords.fbx | 0.886856 |
walk-combat-8-way | Humanoid@WalkBackwardsRightDualSwords.fbx | 0.882356 |
walk-combat-8-way | Humanoid@WalkRightDualSwords.fbx | 0.615565 |
walk-combat-8-way | Humanoid@WalkForwardRightDualSwords.fbx | 0.134164 |
Mechanical baseline
| Finding/check | Affected scope | Potential impact | Evidence |
|---|---|---|---|
| Current readability | 189/189 inputs | Enables mechanical intake | observed-animsmith; all four commands per input exit 0 |
| Constant tracks | 25426 notes across 188 animation-bearing files | Storage/evaluation noise; not a gameplay defect by itself | observed-animsmith; baseline ledger SHA-256 bc7abef8fb3439801eb2cae1c7a212849af278265a13732e1f31b0ccd5feaa5c |
| Retained declarations | 162/186 non-clean | Blocks admission under those exact declarations | observed-animsmith; contract ledger SHA-256 a57be47f3eb19c0deee6f78ea700c8ecb40f0a983b54c8789e0bbef59e7a7820 |
| Structural/timing warnings | Named scope above | Requires bounded author/project decision | observed-file and observed-animsmith |
AnimSmith remediation evidence
| Source issue | Operation/declarations | Result | Independent verification | Remaining caveat |
|---|---|---|---|---|
PFD-01 gait continuity | transform --gait-anchor on 24 selected in-place gait files; retained per-file configs | 24 outputs produced; selected inputs have no accumulating root translation/yaw | 24 inspect and measure commands exit 0; output hashes and diffs captured | Post-config lint remains non-clean; no RM trajectory was transformed; visual/contact residual unresolved |
| Constant-track notes | transform --prune-constant-tracks on Humanoid@IdleCombatDualSwords.fbx | Output candidate(s) produced | Inspect, measure, diff, fix dry-run, and config lint captured | 25/25 post-transform config lints exit 1. Candidate adoption and engine/visual acceptance remain open |
Fresh remediation ledger SHA-256 b201f90c3f30afd4fbc96c25dee8b99608de4bc3d33998a0a29a7b85e1c19b73. Generated motion stays outside Git.
Engine procedures and evidence
| Runtime | Version | Procedure | Observed result | Remaining gate |
|---|---|---|---|---|
| Unity | 6000.5.8f1 | Fresh named Humanoid source import and standalone sampling: IdleCombatDualSwords, WalkForwardDualSwords, WalkForwardDualSwords_RM; named Playables mixers: basic-idle-to-dual-idle, sword-shield-idle-to-dual-idle, basic-walk-ip-to-dual-walk-ip. See collection source/controller probe. | Bounded source/controller feasibility for listed sources only; the collection’s 26/26 is a shared aggregate, not this pack’s result | Rendered motion, contact, deformation, full blend, retargeting, authored AnimatorController, player build and performance remain untested |
| Unreal Engine | not evaluated | No current procedure executed | not-evaluated | Import, retarget, root, blend, and visual review |
| Godot | not evaluated | No current procedure executed | not-evaluated | Convert/import, graph, root, contact, and visual review |
| Bevy | not evaluated | No current procedure executed | not-evaluated | Convert/load, graph, root, performance, and visual review |
Rig, masking, and compatibility evidence
| Pack/rig/set pair | Skeleton/retarget | Scale/axes | Root policy | Timing/blend | Overall evidence |
|---|---|---|---|---|---|
| Dual Swords internal gait sets | Current signatures measured; no retarget | No engine scale/axis test | IP/RM files measured; owner not selected | Current duration/speed/phase recorded; full blend absent | Mechanical only |
| Dual Swords plus Basic Locomotion | Named Humanoid sources imported on shared SK_Protof-Actor | Unity import scale/axis accepted for named sources only | Gameplay-owned root fixed for IP/idle; animation-owned root finite for tested RM where applicable | Named Playables transitions finite; visual blend not inspected | Bounded source/controller feasibility, not compatibility acceptance |
Limitations and unknowns
- Retained configs are replay controls, not proof that every attack, reaction, parry, or emote should loop.
- Current grouping hypotheses were remeasured from exact files, but vendor/project semantics and the unavailable richer historical membership authority were not reconstructed.
- Generated candidates remain external and unadopted; command success does not lower residual severity.
- Unity evidence is limited to named source import, finite headless poses, explicit root ownership, and named Playables mixers; no rendered visual, contact, retarget, mask, authored-controller, player-build, performance, or artistic acceptance was performed.
Changes between AnimSmith versions
AnimSmith 0.14.0 — current official binary revalidated source hashes, all baseline and declaration commands, named runtime hypotheses, and every retained remediation recipe. AnimSmith 0.10.0 — retained ledgers supplied immutable source/config controls; historical results are superseded for current conclusions.
Reproduction
Official evaluator preflight: release archive, archive SHA-256 4ecf79436f9123c779edb004050da3010b44a2b392e3031facb227d7734fc33e; exact member animsmith-v0.14.0-x86_64-unknown-linux-gnu/animsmith, binary SHA-256 c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366; tag v0.14.0, peeled commit e8321ad40be5ef6f162b31f085819c039175c3c9; working-tree state: N/A (official archive). Compiled features: fbx, report. Version and required command help plus representative FBX admission passed before evaluation. Preflight: external:animsmith-0.14.0-report-refresh/preflight.json, SHA-256 7034b5043de54e7b30064230db18605d1f96d0ea1477f7b8c56ce817cc73521a.
Official archive https://github.com/mmannerm/animsmith/releases/download/v0.14.0/animsmith-v0.14.0-x86_64-unknown-linux-gnu.tar.gz; archive SHA-256 4ecf79436f9123c779edb004050da3010b44a2b392e3031facb227d7734fc33e; member animsmith-v0.14.0-x86_64-unknown-linux-gnu/animsmith; binary SHA-256 c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366; tag v0.14.0; commit e8321ad40be5ef6f162b31f085819c039175c3c9; working tree N/A; features fbx/report.
Preflight verified version/help for required commands and representative GLB/FBX admission before replay; scrubbed locator external:0.14.0-report-refresh/preflight.json, SHA-256 7034b5043de54e7b30064230db18605d1f96d0ea1477f7b8c56ce817cc73521a. The read-only helper replayed the retained exact inspect, measure, and two lint invocations with four workers, recorded argv/exit/stdout/stderr and source/config hashes, and did not mutate inputs. Baseline, contract, and remediation ledger digests are bc7abef8fb3439801eb2cae1c7a212849af278265a13732e1f31b0ccd5feaa5c, a57be47f3eb19c0deee6f78ea700c8ecb40f0a983b54c8789e0bbef59e7a7820, and b201f90c3f30afd4fbc96c25dee8b99608de4bc3d33998a0a29a7b85e1c19b73. Licensed paths, sources, and derivative outputs are excluded from Git and this report.
Fresh Unity evidence: editor 6000.5.8f1, Unity.exe SHA-256 cdc0eeca135394cde79a632aa998aca14b521b58afe8c2750b009aff608be906; probe SHA-256 28cd00897dfd739e2a7bcfa193adab03de3591a1009802946305127b67d1bfb6; result schema animsmith-unity-crosspack-probe-v1, result SHA-256 f067bca91f182b65fab408a1c97b297377a8f361b03d59e809e236aba666652c; log SHA-256 0429bcb9cc7a7492c6637eb799566463ba915243d0a110b4d76cbe75590c8819; 26/26 aggregate required checks passed with 53 nonempty Humanoid bones per sample. This report uses only the named applicable subset above.
Sources
- Protofactor product context and End User License Agreement — context only; local revision remains unknown.
Animation pack evaluation: Mixamo Locomotion Collection
Technical verdict: Restricted use
Evaluation completeness: partial — exhaustive current source mechanics for nine constituents; no current collection binding, vendor mapping, engine, visual, retarget, contact, or cross-pack acceptance.
Confidence: medium
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
Detailed evidence: Evidence appendix
Technical decision
For a controller-driven (kinematic) prototype, choose one local pool by the movement you need first: Basic for three-direction unarmed walking, a walk/run speed pair for a small speed pilot, or Rifle 8-Way for a directional armed run. Their selected members are source-measured candidates, with gameplay contacts, blending, and visual acceptance still open. Start on the target character with one pool and one controller owning movement. The adoption guide compares this route with broader library and custom-authoring pilots.
The unarmed-walk-to-pistol-run pair below illustrates a possible full-body handoff, not a compatibility result. It changes weapon state and gait at once. To diagnose the result, first play each state alone, then test a same-gait speed change and a weapon-state change at a matched movement speed if suitable exact clips can be classified. Only then try the combined handoff; record feet, grip, rest pose, scale, deformation, style, and interruption behavior. If a controlled comparator is unavailable, leave the cause of any failure unknown.
The collection evidence retains exact source and warning totals, skeleton counts, and profile results. The source-only checks do not establish pairwise compatibility or a shared movement policy.
Capability coverage
This is a full-body humanoid controller hypothesis for visible characters. Third-person gameplay-camera appearance and target-character deformation remain visually untested. Dedicated first-person arms/viewmodel content and close-camera suitability were not established. These nine locally evaluated pools are not a verified current vendor bundle or complete Mixamo catalog.
Content present
| Candidate ingredients in the evaluated files | Current use boundary |
|---|---|
| Nine local pools offer different filename-based controller ingredients; one cross-pool unarmed-to-pistol handoff is proposed. Shared names do not establish duplicate files. | Filename-based candidates and current measurements; these do not establish a complete accepted gameplay controller. |
Content gaps and unknowns
The legacy metadata has not been matched reliably to individual clips, and current vendor grouping is unverified. Shared names and measurements have not been checked for duplicate bytes or safe combined use.
Evaluation still needed
- Each pool has a selected controller prototype; the proposed full-body handoff between pools still needs pairwise validation.
- Across those nine constituent scenarios, AnimSmith measured gait phase for all 29 selected members. The constituent reports retain every member value and each set's normalized circular-range spread; synchronization and contact acceptance remain
not-evaluated. - Cross-pool skeleton identity, retargeting, transition, visual/contact, performance and style acceptance remain open.
Choosing a local pool
These nine evaluated local pools have no verified current vendor-bundle membership. Choose a pool for its proposed controller shape, then inspect the constituent report and the individual links below before adopting other named motions. All entries have filename-based content signals, unresolved loop/contact/visual acceptance, and per-file ownership work outside the selected set.
| Pool | Candidate use from current files | Practical difference and open decision |
|---|---|---|
| Basic | Unarmed forward/left/right walk | Directional walking pilot; no selected run or complete gameplay core. |
| Female Basic | Walk/run speed pair | Includes more delivered files than Female Locomotion; extra gameplay roles and value are unclassified. |
| Female Locomotion | Walk/run speed pair | Selected walk/run names, timings and phases match Female Basic in these reports; byte identity and interchangeable use were not checked. |
| Locomotion | Walk/run speed pair | Another unarmed speed pilot with a different reported run phase; practical overlap with the female and male pools is unresolved. |
| Longbow | Four-direction full-body run | Bow-themed run pose; duration and contact alignment need review. |
| Magic | Four-direction full-body run | Magic-themed run pose; duration and contact alignment need review. |
| Male | Walk/standard-run speed pair | Another unarmed speed pilot; the label does not prove target body proportions or retarget suitability. |
| Pistol-Handgun | Forward/back full-body pistol run | Two-direction armed pilot, with a larger measured phase spread. |
| Rifle 8-Way | Eight-direction full-body rifle run | Widest selected run topology; aiming, crouch, jump and death filenames are candidate content, not accepted states. |
Shared filenames, measurements, Mixamo role labels or bone counts do not prove duplicate assets, safe co-installation, one retarget profile, or compatible style. Compare exact source bytes and the target character before replacing one pool with another or combining them.
Runtime sets and authored motion
| Set | Controller use | Adoption decision | Exact members |
|---|---|---|---|
hypothesis/full-body-unarmed-to-pistol | Unarmed walk to pistol run full-body handoff; controller owns XZ translation, yaw and collision | Each constituent can be piloted separately with controller-owned movement. The cross-pool handoff remains an unaccepted proposal: check hierarchy, rest pose, scale, contact and style per exact pair. The 52 ownership findings concern other files across the pools. No AnimSmith remediation trial or accepted combined controller was reported. | Member timings, coordinates and contracts |
The selected handoff is illustrative. A poor combined result alone cannot identify whether gait speed, weapon posture, or cross-pool rig/style caused it. Record the controlled comparisons described above before assigning a repair.
The constituent gait-phase spreads range from 0.0312 to 0.2477 cycles, using normalized circular range (1 - largest cyclic gap across member phases on the unit cycle). Those measurements can guide target-engine tests but do not establish a shared phase policy, compatible support-foot events, or a defect in any source pack.
Integration recipe
- Members/topology: Place the exact unarmed and pistol members from the linked appendix in separate full-body states. Keep this pair as an illustrative two-variable trial.
- Timing/synchronization: Keep source timing. Set and review the crossfade in the target engine; loop intent and gait alignment across pools remain unverified.
- State ownership: Use one kinematic controller for XZ translation, yaw, and collision across the state change.
- Composition constraints: Start with a full-body handoff. Test any upper-body mask, additive motion, shared IK, socket, or retarget profile separately.
- Acceptance gate: First play each state alone; compare speed with weapon state held constant and weapon state at a comparable speed when matching clips exist. Then compare hierarchy, rest pose, and scale and inspect the combined interrupted transition, feet, weapon hands, target deformation, and style from the gameplay camera. Record unknown causes when no controlled comparison exists.
Technical issue register
| ID | Severity | Problem and impact | Primary owner | Current action | Future AnimSmith potential | Evidence/status |
|---|---|---|---|---|---|---|
| MIX-OWN-001 | moderate | Scope: 52 exact paths listed in the nine linked constituent issue rows; reproduce: archive-level animation-owned XZ lint; impact: a package-wide policy can leave intended movement stationary. Guidance: not applicable. | engine-config | Choose ownership per file and state. Accept when declared lint and one target controller apply movement/yaw/collision exactly once for every admitted state. | Generic rewrite is not justified without intent. | observed-animsmith; 52 current findings; unresolved. |
| MIX-XPACK-001 | major | Scope: every proposed cross-pack state/layer boundary; reproduce: no current pairwise hierarchy/rest/scale or runtime transition test exists; impact: a shared controller may pop, deform, slide, misalign weapons, or change style. Guidance: not applicable. | unknown | Select exact pairs, compare skeleton/rest/scale, then run full-body target-engine transitions before considering layers. Accept when technical, contact, deformation, and artistic gates pass per pair. | Deterministic comparison can support evidence but cannot decide artistic compatibility. | not-evaluated; current source mechanics only; unresolved. |
Engine status
| Runtime | Evidence level | Technical result | Remaining gate |
|---|---|---|---|
| Unity unspecified | not-evaluated | No current collection import, controller, or visual test | Test exact pairwise full-body state graph |
| Unreal Engine unspecified | not-evaluated | No current collection import, retarget, or Blend Space test | Test exact IK Rig/Skeleton and state graph |
| Godot unspecified | not-evaluated | No current collection import or AnimationTree test | Test exact Skeleton3D and state graph |
| Bevy unspecified | not-evaluated | No current collection loader or graph test | Test exact animation targets and state graph |
Fit and limitations
Best fit is source intake for projects willing to choose one constituent/set at a time, own movement, and validate full-body state changes. Poor fits include drop-in cross-pack blending, shared root-motion policy, weapon layering, motion matching, or production use without target-engine and artistic review.
The nine constituent reports are authoritative for their fresh source findings and new bounded hypotheses. This rollup does not recreate historical vendor mappings or claim that common role labels/bone counts establish compatibility.
Changes between AnimSmith versions
AnimSmith 0.14.0 — The current evaluation reran 996 commands across 249 FBXs and revalidated that the source inventory stayed unchanged. It proposed constituent controller sets and one cross-pool handoff; clip mapping remains unverified.
AnimSmith 0.10.0 — Retained historical finding totals agree but supply no current collection semantics or engine acceptance. AnimSmith 0.7.0 collection conclusions remain superseded.
Evidence status
The current tests covered nine constituents, 249 FBX files, and 119,754,377 source bytes. Legacy metadata lists 231 motions, but its clip-to-file mapping is unverified. The evidence appendix records source integrity, evaluator revision, command results, and digests; licensed data remains external. See the canonical readiness ladder.
Sources
- The nine linked current constituent reports and fresh external AnimSmith 0.14.0 evidence.
- AnimSmith game-ready clip guidance.
Animation pack evidence appendix: Mixamo Locomotion Collection
Companion report: Technical report
Evidence status: partial — exhaustive source mechanics for nine constituents; no current collection semantics, engine, visual, retarget, contact, or artistic acceptance.
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
This appendix contains scrubbed rollup facts. The canonical readiness ladder remains authoritative. The retained collection manifest schema is urn:animsmith:skill:animation-pack-evaluation-manifest:1; it does not provide current semantic membership authority.
Evaluation scope and provenance
| Field | Value |
|---|---|
| Pack/edition | Mixamo Locomotion Collection; nine locally available constituents; revisions unknown |
| Vendor/source | Vendor identity observed in metadata; current listing URLs not retained |
| Delivered scope | 249 extracted FBXs: 92 in-place-directory and 157 root-motion-directory files |
| Target use | Engine-neutral intake plus new evaluator-proposed controller/state hypotheses |
| Target engines | Not evaluated |
| Target rigs/packs | Nine source pools; no target character or proven pairwise combination |
| Source manifest | Fresh collection inventory SHA-256 bc8543a8e1c7cf439030c07a2aead5db183d610633f637c1979dbce71a4dce66; identical before/after |
| Evaluation manifest | mixamo-locomotion-collection.manifest.json; SHA-256 9601129c4237e5b331fd7de8c6c705c7bb8dcd702a219d31c250a8ce57b77cf1; legacy schema urn:animsmith:skill:animation-pack-evaluation-manifest:1 |
| Acquisition/license provenance | Extracted files locally authorized; original ZIPs and controlling license evidence unavailable; not legal advice |
Evidence coverage
| Surface | Offered/delivered | Evaluated | Findings | Not evaluated and why |
|---|---|---|---|---|
| Physical FBX files | 249 | 249 | 0 baseline errors; 50 warnings | Vendor 231-motion mapping unavailable |
| Rigs/export variants | 249 | 249 | 231 66-bone motion files; 18 68-bone X Bot.fbx; Mixamo roles resolved | Pairwise hierarchy/rest/retarget unavailable |
| AnimSmith baseline | 249 | 249 | 50 warnings; 35405 notes | — |
| Declared contracts | 249 | 249 | 52 ownership errors; 50 warnings | Per-file project intent unavailable |
| Constituent hypotheses | 9 | 9 static proposals | 29 exact selected members with measured gait phase | Synchronization, contact, runtime, and visual acceptance unavailable |
| Cross-pack hypotheses | 1 | 1 static proposal | Full-body handoff only | Pairwise engine/artistic evidence unavailable |
| Engine import/playback | 4 common runtimes | 0 | 0 | No current engine project ran |
Claim legend
Consequential claims are observed-file, observed-animsmith, inferred, or not-evaluated.
Evaluation manifest and taxonomy
Canonical clip-role inventory
| Canonical primary role | Logical motions | Delivered files | Evidence boundary |
|---|---|---|---|
idle-pose | 0 | 0 | Not authoritatively classified |
continuous-locomotion | 0 | 0 | Filename hypotheses stay outside canonical counts |
locomotion-transition | 0 | 0 | Not authoritatively classified |
airborne | 0 | 0 | Not authoritatively classified |
traversal | 0 | 0 | Not authoritatively classified |
action-interaction | 0 | 0 | Not authoritatively classified |
reaction-death | 0 | 0 | Not authoritatively classified |
emote-cinematic | 0 | 0 | Not authoritatively classified |
other-unknown | 249 | 249 | One opaque current source unit per delivered FBX |
| Total | 249 | 249 | No vendor mapping inferred |
Exact runtime members
| Set/profile | Role or coordinate | Exact members | Variant/type | Timing or motion | Runtime contract |
|---|---|---|---|---|---|
hypothesis/full-body-unarmed-to-pistol | source-state | mixamo-basic-locomotion::Basic_Locomotion_Pack_-_in-place/walking.fbx | set_type=transition-chain | duration=1.033 s; frames=32 frames | loop=unknown; transition=full-body; movement=controller-xz-yaw; state=unarmed |
hypothesis/full-body-unarmed-to-pistol | destination-state | mixamo-pistol-handgun-locomotion::Pistol-Handgun_Locomotion_Pack_-_in-place/pistol run.fbx | set_type=transition-chain | duration=0.500 s; frames=16 frames | loop=unknown; transition=full-body; movement=controller-xz-yaw; state=pistol |
The table above preserves the measured member detail from the companion report; its proposed controller still requires target-engine acceptance.
Runtime-set inventory
| Runtime set | Type | Members/variants | Grouping evidence | Validation status |
|---|---|---|---|---|
hypothesis/full-body-unarmed-to-pistol | transition-chain | mixamo-basic-locomotion::Basic_Locomotion_Pack_-_in-place/walking.fbx; mixamo-pistol-handgun-locomotion::Pistol-Handgun_Locomotion_Pack_-_in-place/pistol run.fbx | New evaluator-selected namespaced full-body state proposal; no historical set recovered | not-evaluated pairwise/runtime/artistic acceptance |
Pipeline-stage coverage
| Stage | Coverage state | Evidence / remaining gate |
|---|---|---|
| Acquire | partially-evaluated | Extracted sources present; original ZIP/license unavailable |
| Preserve raw | evaluated-clean | 249-file inventory matched before/after |
| Inspect | evaluated-clean | All sources loaded and resolved Mixamo roles |
| Segment | not-evaluated | Vendor 231-to-249 mapping unavailable |
| Root motion | partially-evaluated | Archive declarations ran; per-file XZ/yaw policy unavailable |
| Conform | not-evaluated | No outputs produced |
| Validate | partially-evaluated | Mechanical/current contracts complete; runtime/artistic gates open |
| Optimize | not-evaluated | Constant-track notes not promoted |
| Export | not-evaluated | No engine-facing export ran |
| Gate/report | partially-evaluated | Source-only reports complete; adoption gates remain |
Readiness evidence by clip set
| Role or runtime set | File-ready / clip-ready | Set-ready / rig-use | Runtime / acceptance boundary |
|---|---|---|---|
| Nine opaque corpora | 0 baseline errors; 50 warnings | Roles resolve; per-file semantics and retarget use unavailable | No current engine/visual/gameplay acceptance |
| Nine constituent hypotheses | Exact members and gait phases measured | New controller proposals only | Synchronization, blend, phase/contact acceptance, transition, and deformation tests required |
hypothesis/full-body-unarmed-to-pistol | Two exact in-place members measured | Pairwise skeleton/rest/scale unproven | Full-body transition plus technical/artistic acceptance required |
Validation-profile status
| Validation profile | Selection | Result / next evidence |
|---|---|---|
| Marketplace intake | selected — observed-pack-capability | Exhaustive source mechanics complete |
| Blended locomotion | selected — evaluator-selected-generic-scenario | Nine static hypotheses and 29 gait-phase measurements; synchronization/contact/runtime acceptance open |
| Root-motion controller | selected — observed-pack-capability | 52 ownership conflicts need per-file policy |
| State-machine transitions | selected — evaluator-selected-generic-scenario | Full-body cross-pack handoff proposed; not tested |
| Layered upper body/weapons | selected — evaluator-selected-generic-scenario | Full-body baseline only; layering remains unavailable |
| Traversal/environment | not-selected | No target environment contract |
| Contact actions/interactions | not-selected | No target contact contract |
| Retargeted/customizable characters | not-selected | No target character/retargeter supplied |
| Motion matching/search | not-selected | No database contract supplied |
| Networked movement | not-selected | No replication/rollback contract supplied |
| Runtime performance | not-selected | No target runtime/hardware supplied |
Pack inventory and content evidence
| Constituent | FBXs | Legacy metadata | Baseline | Declared XZ | New hypothesis |
|---|---|---|---|---|---|
| Mixamo Basic Locomotion | 12 | 10 unlinked metadata motions | 4 warnings / 1712 notes | 4 ownership errors | hypothesis/kinematic-walk-3way; phase spread 0.0318 cycles; sync not-evaluated |
| Mixamo Female Basic Locomotion | 20 | 18 unlinked metadata motions | 5 warnings / 2843 notes | 6 ownership errors | hypothesis/kinematic-speed; phase spread 0.0624 cycles; sync not-evaluated |
| Mixamo Female Locomotion | 18 | 16 unlinked metadata motions | 4 warnings / 2559 notes | 4 ownership errors | hypothesis/kinematic-speed; phase spread 0.0624 cycles; sync not-evaluated |
| Mixamo Locomotion | 20 | 18 unlinked metadata motions | 5 warnings / 2852 notes | 6 ownership errors | hypothesis/kinematic-speed; phase spread 0.0312 cycles; sync not-evaluated |
| Mixamo Longbow Locomotion | 22 | 20 unlinked metadata motions | 3 warnings / 3126 notes | 3 ownership errors | hypothesis/kinematic-run-4way; phase spread 0.1557 cycles; sync not-evaluated |
| Mixamo Magic Locomotion | 27 | 25 unlinked metadata motions | 6 warnings / 3837 notes | 4 ownership errors | hypothesis/kinematic-run-4way; phase spread 0.1926 cycles; sync not-evaluated |
| Mixamo Male Locomotion | 18 | 16 unlinked metadata motions | 5 warnings / 2564 notes | 4 ownership errors | hypothesis/kinematic-speed; phase spread 0.0801 cycles; sync not-evaluated |
| Mixamo Pistol-Handgun Locomotion | 29 | 27 unlinked metadata motions | 7 warnings / 4122 notes | 5 ownership errors | hypothesis/kinematic-run-axis; phase spread 0.2477 cycles; sync not-evaluated |
| Mixamo Rifle 8-Way Locomotion | 83 | 81 unlinked metadata motions | 11 warnings / 11790 notes | 16 ownership errors | hypothesis/kinematic-run-8way; phase spread 0.1731 cycles; sync not-evaluated |
The 231 metadata motion count is not mapped to the 249 files. Exact current paths remain opaque canonical units. New hypotheses use only named current files and do not claim vendor intent or recovered historical membership. All nine spreads use normalized circular range, defined as 1 - largest cyclic gap across member phases on the unit cycle; a spread alone establishes neither support-foot compatibility nor a source defect.
Mechanical baseline
| Finding/check | Affected scope | Potential impact | Evidence |
|---|---|---|---|
| No baseline errors | 249/249 files | File-readable prerequisite only | Current ledger |
duration-sanity | 50 findings | Empty reference takes or endpoint clamp-hold | Exact paths in constituent appendices |
constant-track | 35405 notes | Optimization hint; no visual defect or safe rewrite inferred | Current ledger |
| Skeleton counts | 231 files at 66 bones; 18 X Bot.fbx at 68 | Reference files need classification; common counts do not prove compatibility | Current inspect output |
AnimSmith remediation evidence
| Source issue | Operation/declarations | Result | Independent verification | Remaining caveat |
|---|---|---|---|---|
| MIX-OWN-001 | Archive-level XZ declarations | 52 stationary-root errors | Current per-file lint/measure; no output produced | Per-file intent/controller acceptance unavailable; residual unresolved |
| MIX-XPACK-001 | No transform or retarget run | No candidate produced | Exact source identities only | Pairwise technical/artistic acceptance unavailable; residual unresolved |
Engine procedures and evidence
| Runtime | Version | Procedure | Observed result | Remaining gate |
|---|---|---|---|---|
| Unity | unspecified | Not run | not-evaluated | Exact pairwise full-body state graph |
| Unreal Engine | unspecified | Not run | not-evaluated | Exact retarget/state graph |
| Godot | unspecified | Not run | not-evaluated | Exact Skeleton3D/AnimationTree graph |
| Bevy | unspecified | Not run | not-evaluated | Exact target identity/animation graph |
Rig, masking, and compatibility evidence
| Pack/rig/set pair | Skeleton/retarget | Scale/axes | Root policy | Timing/blend | Overall evidence |
|---|---|---|---|---|---|
| Basic / Pistol proposed handoff | 66-bone counts and Mixamo roles on selected files; hierarchy/rest/deformation not compared | Not evaluated | Controller ownership proposed | Full-body transition not tested | not-evaluated technical/contact/artistic acceptance |
| All other constituent pairs | Common role labels/counts are insufficient | Not evaluated | Per-file ownership unavailable | No pairwise test | unknown |
Limitations and unknowns
- Vendor mapping, loop/phase intent, target character, engines, retargeting, contacts, deformation, performance, networking, and artistic style remain unavailable.
- Cross-pack hypotheses are new evaluator scenarios; no historical membership or compatibility conclusion was reconstructed.
- Original ZIP payloads and controlling license evidence were unavailable; hashing covers extracted sources.
Changes between AnimSmith versions
AnimSmith 0.14.0 — Fresh source-only matrix admitted 249/249 files, revalidated mechanical/declared counts, and added bounded new controller/state hypotheses without claiming runtime acceptance.
AnimSmith 0.10.0 — Retained historical totals do not supply collection semantics. AnimSmith 0.7.0 evidence remains superseded.
Reproduction
Official evaluator preflight: release archive, archive SHA-256 4ecf79436f9123c779edb004050da3010b44a2b392e3031facb227d7734fc33e; exact member animsmith-v0.14.0-x86_64-unknown-linux-gnu/animsmith, binary SHA-256 c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366; tag v0.14.0, peeled commit e8321ad40be5ef6f162b31f085819c039175c3c9; working-tree state: N/A (official archive). Compiled features: fbx, report. Version and required command help plus representative FBX admission passed before evaluation. Preflight: external:animsmith-0.14.0-report-refresh/preflight.json, SHA-256 7034b5043de54e7b30064230db18605d1f96d0ea1477f7b8c56ce817cc73521a.
Official binary SHA-256 c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366, version 0.14.0, source revision e8321ad40be5ef6f162b31f085819c039175c3c9, dirty false, features fbx,report. Version/help/required-command preflight exited 0. The four-worker read-only matrix ran 996 commands with no timeouts: inspect, measure, empty-baseline lint, and variant-declaration lint per FBX. The 52 nonzero exits are the declared-contract errors reported above; every loader/inspect/measure/baseline invocation exited 0.
Source inventory SHA-256 bc8543a8e1c7cf439030c07a2aead5db183d610633f637c1979dbce71a4dce66 covers 249 files / 119,754,377 bytes and matched before/after. Ledger SHA-256 62b0a7b7a1bb47c817f6b005a988077a44dc8d942bb26343a5f7278a15582272; summary 4dd01ee9f1398fa200355c82206c8fd24d80d0dea1f8965aee8b03ba400cfc7c; report data a8d8e1bd3707b58e24f7d08f4ea7038c0f316254fa289e4f6439284dbcbc388f. Full outputs, exact private paths, and licensed sources remain external.
Sources
- Nine linked current constituent pairs plus fresh external source inventory, command ledger, summary, and outputs.
- AnimSmith game-ready clip guidance.
Animation pack evaluation: Mixamo Basic Locomotion
Technical verdict: Restricted use
Evaluation completeness: partial — all 12 extracted FBX files have current mechanical evidence; vendor mapping, runtime, visual, contact, retarget, license, and cross-pack acceptance remain unavailable.
Confidence: medium
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
Detailed evidence: Evidence appendix
Technical decision
For a controller-driven (kinematic) prototype, start with three-direction unarmed walking using the selected members. Their source mechanics were measured, but the proposed blend has no accepted loop, foot-contact, transition, target-character, or target-engine result. Use one controller for movement and yaw; decide ownership separately for any additional root-motion-directory state.
First experiment: Import the selected in-place files on the target character, play them individually, then test their blend through speed or direction changes, starts, stops, and interrupted transitions. Review any endpoint warning on other named files before admission: retain an intentional hold after visual review, or request source repair when the intended endpoint differs. The evidence appendix retains the exact source counts, warning totals, skeleton counts, and profile results. The adoption guide compares this limited pilot with broader library and authoring routes.
Capability coverage
This is a full-body humanoid controller hypothesis for visible characters. Third-person gameplay-camera appearance and target-character deformation remain visually untested. Dedicated first-person arms/viewmodel content and close-camera suitability were not established.
Content present
| Candidate ingredients in the evaluated files | Current use boundary |
|---|---|
| Walk forward and strafe left/right are named and measured. Idle, jump and turns also appear by filename; their controller roles remain unclassified. | Check the named states and their transitions in the target game before treating this as a complete controller. |
Content gaps and unknowns
Other delivered motion leads, filename only: idle.fbx, jump.fbx, left turn 90.fbx, and right turn 90.fbx. These names occur in the inspected source or issue inventory, but their intended controller roles and gameplay behavior have not been classified or tested. Inspect each source and its boundaries before adding a state; see the retained source evidence. A zero canonical role count does not establish that these motions are absent.
The legacy count of 10 motions has no verified mapping to these 12 files. Classify the other named motions before adding them to a controller.
Evaluation still needed
- Loop/phase policy, target-engine graph, contact and visual review, target-character retargeting, performance and cross-pack acceptance remain open.
Runtime sets and authored motion
| Set | Controller use | Adoption decision | Exact members |
|---|---|---|---|
hypothesis/kinematic-walk-3way | Three-direction unarmed walk; controller owns XZ translation, yaw and collision | The three selected in-place walks have no reported mechanical finding. Prototype with controller-owned movement and project-defined speed; configure loop and phase only after contact review. Four other root-motion-directory states need per-file ownership; idle and left turn also have endpoint warnings. No AnimSmith repair was tested. | Member timings, coordinates and contracts |
The selected files are grouped by name and measurement. AnimSmith measured phase (cycles) as forward 0.4841, left 0.4905, and right 0.4587; their measured phase spread is 0.0318 cycles. These phase numbers do not establish matching foot contacts or a usable blend. Loop intent, contact alignment, and gameplay use remain not-evaluated.
Integration recipe
- Members/topology: Use the exact members and coordinates for
hypothesis/kinematic-walk-3wayin the linked appendix as a proposed directional blend. Add other directions or in-place/root-motion alternatives only after checking their own files. - Timing/synchronization: Keep source timing and leave loop settings undecided. Compare normalized phase and visible foot contacts in the target engine before enabling a continuous blend.
- State ownership: These selected in-place files supply pose; one kinematic controller supplies XZ translation, yaw, and collision. Root-motion use, where animation supplies travel, requires separate per-file checks.
- Composition constraints: Begin with full-body states and transitions. Masks, additive motion, IK, sockets, and layering need separate tests.
- Acceptance gate: In the chosen engine, test the selected blend at center and axes, rapid input changes, starts and stops, foot contacts, interrupted transitions, and target-character deformation.
Technical issue register
| ID | Severity | Problem and impact | Primary owner | Current action | Future AnimSmith potential | Evidence/status |
|---|---|---|---|---|---|---|
| MIX-OWN-001 | moderate | Scope: Basic_Locomotion_Pack_-_root-motion/idle.fbx; Basic_Locomotion_Pack_-_root-motion/jump.fbx; Basic_Locomotion_Pack_-_root-motion/left turn 90.fbx; Basic_Locomotion_Pack_-_root-motion/right turn 90.fbx; reproduce: lint each file with the current archive-level animation-owned XZ declaration; impact: applying that policy can leave these states stationary while gameplay expects displacement. Guidance: not applicable. | engine-config | Declare these files controller-owned XZ unless project evidence establishes authored displacement. Accept when per-file declared lint has no ownership error and one target-controller playback applies translation/yaw/collision exactly once. | No generic rewrite is justified because the directory label does not establish per-file intent. | observed-animsmith; 4 current findings; no runtime acceptance; unresolved. |
| MIX-TIME-001 | moderate | Scope: Basic_Locomotion_Pack_-_root-motion/idle.fbx; Basic_Locomotion_Pack_-_root-motion/left turn 90.fbx; reproduce: empty-baseline duration-sanity; impact: shorter channels clamp-hold at the end and can create a brief frozen joint or transition/loop pop. Guidance. | unknown | Inspect the intended endpoint and slowed target-engine boundary playback first. If the hold is intentional and visually acceptable, document and retain it. Otherwise ask the source author to align the shorter channels to the intended endpoint, rerun duration-sanity, and review boundary playback. Accept only when the intended hold or corrected boundary is visually approved and the remaining mechanical warning is recorded accurately. | Deterministic endpoint conformance is plausible only after the intended endpoint and protected channels are declared. | observed-animsmith; 2 current source files; visual intent not evaluated; unresolved. |
| MIX-CONTENT-001 | note | Scope: Basic_Locomotion_Pack_-_in-place/X Bot.fbx; Basic_Locomotion_Pack_-_root-motion/X Bot.fbx; reproduce: empty-baseline duration-sanity reports Take 001 with no tracks; impact: automatic clip registration can expose an empty state, although these may be reference-character files. Guidance: not applicable. | unknown | Classify each file as reference-only or intended animation before import. Accept when reference files are excluded from animation registration or the author supplies the intended tracks. | No tool should invent missing motion. | observed-file and observed-animsmith; semantic purpose unavailable; unresolved. |
Engine status
| Runtime | Evidence level | Technical result | Remaining gate |
|---|---|---|---|
| Unity unspecified | not-evaluated | No current import, graph, playback, or visual evidence | Test the proposed controller and full-body graph on the target character |
| Unreal Engine unspecified | not-evaluated | No current import, retarget, Blend Space, or playback evidence | Test the exact retarget and controller path |
| Godot unspecified | not-evaluated | No current import, AnimationTree, or playback evidence | Test the exact Skeleton3D and controller path |
| Bevy unspecified | not-evaluated | No current loader, graph, or playback evidence | Test exact animation targets and controller composition |
Fit and limitations
Best fit is basic unarmed locomotion prototyping where a developer can own movement, define thresholds, and test the exact target character. It is a poor fit for drop-in root-motion, network, motion-matching, layered-animation, or production-ready claims.
Cross-pack compatibility is unknown. Shared Mixamo role resolution and a common 66-bone count on motion-named files do not prove identical hierarchy, rest pose, scale, retargeting, blending, contacts, or style.
Changes between AnimSmith versions
AnimSmith 0.14.0 — The current evaluation reran 996 inspect, measure, and lint commands across the collection. This constituent admitted 12/12 files; baseline and declared-contract findings were revalidated, and the selected controller set was proposed from current files. Its clip roles remain unverified.
AnimSmith 0.10.0 — Retained historical mechanical counts agree with the current rerun; clip mapping and engine acceptance remain open. AnimSmith 0.7.0 evidence remains superseded.
Evidence status
The current tests covered 12 FBX source files individually. The legacy metadata lists 10 motions, but its clip-to-file mapping is unverified for the 12 files. The evidence appendix records the source integrity check, evaluator revision, command results, and digests. See the canonical readiness ladder.
Sources
- Fresh external AnimSmith 0.14.0 command ledger and summary; licensed source and outputs remain external.
- AnimSmith game-ready clip guidance.
Animation pack evidence appendix: Mixamo Basic Locomotion
Companion report: Technical report
Evidence status: partial — exhaustive current source mechanics and archive-level declarations; no current engine, visual, contact, retarget, license, or artistic acceptance.
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
This appendix contains scrubbed facts only. The canonical readiness ladder remains authoritative. The evaluation manifest schema is urn:animsmith:skill:animation-pack-evaluation-manifest:1; it is retained as opaque historical classification input, not current source-to-vendor mapping authority.
Evaluation scope and provenance
| Field | Value |
|---|---|
| Pack/edition | Mixamo Basic Locomotion; local revision unknown |
| Vendor/source | Vendor identity observed in delivered metadata; listing URL not retained |
| Delivered scope | 12 extracted FBX files: 4 in-place-directory and 8 root-motion-directory files |
| Target use | Engine-neutral intake plus a new evaluator-proposed kinematic-controller scenario |
| Target engines | Not evaluated |
| Target rigs/packs | Mixamo profile resolved; no target character or cross-pack runtime supplied |
| Source manifest | Fresh 249-file collection inventory SHA-256 bc8543a8e1c7cf439030c07a2aead5db183d610633f637c1979dbce71a4dce66; identical before/after evaluation |
| Evaluation manifest | mixamo-basic-locomotion.manifest.json; SHA-256 345923251a496e41610ac2593e04312935db7bbf13cf0a86a5bb045b5e7461f4; legacy schema urn:animsmith:skill:animation-pack-evaluation-manifest:1 |
| Acquisition/license provenance | Extracted files are locally authorized; original ZIPs and controlling license evidence were unavailable; not legal advice |
The legacy metadata count of 10 motions is not linked authoritatively to these 12 files. Current conclusions use exact source paths and fresh measurements only.
Evidence coverage
| Surface | Offered/delivered | Evaluated | Findings | Not evaluated and why |
|---|---|---|---|---|
| Animation/source files | 12 | 12 | 0 baseline errors; 4 warnings | Vendor motion mapping unavailable |
| Rigs/export variants | 2 skeleton counts | 12 | two 68-bone X Bot.fbx; 10 66-bone files; Mixamo roles resolved | Retarget/deformation unavailable |
| AnimSmith baseline | 12 | 12 | 4 warnings; 1712 notes | — |
| Declared contracts | 12 | 12 | 4 ownership errors; 4 warnings | Per-file project intent unavailable |
| Evaluator scenario | 1 | 1 static proposal | 3 members with measured gait phase | Loop, synchronization, contact, engine, and visual acceptance unavailable |
| Engine import/playback | 4 common runtimes | 0 | 0 | No current engine project ran |
| Blend/mask/retarget | 1 proposed set | 0 runtime tests | 0 | Target graph/character unavailable |
Claim legend
Consequential claims are observed-file, observed-animsmith, inferred, or not-evaluated under the assessment taxonomy.
Evaluation manifest and taxonomy
Canonical clip-role inventory
| Canonical primary role | Logical motions | Delivered files | Evidence boundary |
|---|---|---|---|
idle-pose | 0 | 0 | Not authoritatively classified |
continuous-locomotion | 0 | 0 | Filename-based hypotheses stay outside the canonical role count |
locomotion-transition | 0 | 0 | Not authoritatively classified |
airborne | 0 | 0 | Not authoritatively classified |
traversal | 0 | 0 | Not authoritatively classified |
action-interaction | 0 | 0 | Not authoritatively classified |
reaction-death | 0 | 0 | Not authoritatively classified |
emote-cinematic | 0 | 0 | Not authoritatively classified |
other-unknown | 12 | 12 | One opaque current source unit per delivered FBX |
| Total | 12 | 12 | Current exact source paths; no vendor mapping inferred |
Exact runtime members
| Set/profile | Role or coordinate | Exact members | Variant/type | Timing or motion | Runtime contract |
|---|---|---|---|---|---|
hypothesis/kinematic-walk-3way | forward=(0,1) | Basic_Locomotion_Pack_-_in-place/walking.fbx | variant=in-place | duration=1.033 s; frames=32 frames | loop=unknown; sync=not-evaluated; movement=controller-xz-yaw; playback=project-defined |
hypothesis/kinematic-walk-3way | left=(-1,0) | Basic_Locomotion_Pack_-_in-place/left strafe walking.fbx | variant=in-place | duration=1.033 s; frames=32 frames | loop=unknown; sync=not-evaluated; movement=controller-xz-yaw; playback=project-defined |
hypothesis/kinematic-walk-3way | right=(1,0) | Basic_Locomotion_Pack_-_in-place/right strafe walking.fbx | variant=in-place | duration=1.033 s; frames=32 frames | loop=unknown; sync=not-evaluated; movement=controller-xz-yaw; playback=project-defined |
The table above preserves the measured member detail from the companion report; its proposed controller still requires target-engine acceptance.
Runtime-set inventory
| Runtime set | Type | Members/variants | Grouping evidence | Validation status |
|---|---|---|---|---|
hypothesis/kinematic-walk-3way | directional-blend | Basic_Locomotion_Pack_-_in-place/walking.fbx; Basic_Locomotion_Pack_-_in-place/left strafe walking.fbx; Basic_Locomotion_Pack_-_in-place/right strafe walking.fbx; all in-place | new incomplete evaluator hypothesis from current source names; no backward member or vendor/runtime authority; exact durations and frames are in the primary table | not-evaluated runtime acceptance |
Pipeline-stage coverage
| Stage | Coverage state | Evidence / remaining gate |
|---|---|---|
| Acquire | partially-evaluated | Extracted source present; original ZIP/license unavailable |
| Preserve raw | evaluated-clean | Fresh inventory matched byte-for-byte before/after the read-only matrix |
| Inspect | evaluated-clean | All 12 FBXs admitted and resolved the Mixamo profile |
| Segment | not-evaluated | Vendor motion-to-file mapping unavailable |
| Root motion | partially-evaluated | Archive-level XZ declarations ran; per-file ownership and yaw remain project decisions |
| Conform | not-evaluated | No source rewrite was authorized or produced |
| Validate | partially-evaluated | Mechanical and declared checks complete; runtime/artistic gates open |
| Optimize | not-evaluated | Constant-track notes were not promoted to a rewrite |
| Export | not-evaluated | No engine-facing export ran |
| Gate/report | partially-evaluated | Source-only decision report complete; adoption gates remain |
Readiness evidence by clip set
| Role or runtime set | File-ready / clip-ready | Set-ready / rig-use | Runtime / acceptance boundary |
|---|---|---|---|
| Opaque delivered corpus | 0 errors; 4 warnings; all sources readable | Mixamo roles resolved; vendor semantics and retarget use unavailable | No current engine, visual, contact, or gameplay acceptance |
hypothesis/kinematic-walk-3way | Exact members and gait phases measured; loop intent unknown | New evaluator hypothesis; controller ownership proposed | Target-engine blend, phase/contact acceptance, transition, and deformation test required |
Validation-profile status
| Validation profile | Selection | Result / next evidence |
|---|---|---|
| Marketplace intake | selected — observed-pack-capability | Exhaustive source mechanics complete; acquisition/license boundary remains |
| Blended locomotion | selected — evaluator-selected-generic-scenario | Exact static proposal and gait-phase measurements recorded; runtime blend and phase/contact acceptance required |
| Root-motion controller | selected — observed-pack-capability | 4 archive-level ownership conflicts require per-file policy |
| State-machine transitions | selected — evaluator-selected-generic-scenario | Full-body transition policy proposed; no authored/runtime transition tested |
| Layered upper body/weapons | not-selected | No mask/additive contract or layer evidence |
| Traversal/environment | not-selected | No target traversal/environment contract |
| Contact actions/interactions | not-selected | No contact target or gameplay contract |
| Retargeted/customizable characters | not-selected | No target rig or retargeter supplied |
| Motion matching/search | not-selected | No database/annotation contract supplied |
| Networked movement | not-selected | No replication/rollback contract supplied |
| Runtime performance | not-selected | No target hardware/runtime project supplied |
Pack inventory and content evidence
The fresh inventory contains 12 FBX files. Both directory variants include X Bot.fbx with a 68-bone skeleton and an empty Take 001; the 10 motion-named files use 66 bones. All sources resolved the same nine Mixamo roles. This establishes static structure, not identical hierarchy/rest pose or retarget compatibility. Constant-track findings are optimization hints only.
The current scenario is intended to be useful without claiming old authority: hypothesis/kinematic-walk-3way names 3 exact in-place-directory files, records current durations/frame counts, assigns movement to a hypothetical kinematic controller, and leaves loop intent, gait-phase acceptance, transitions, and runtime acceptance open. AnimSmith measured phase (cycles) as forward 0.4841, left 0.4905, and right 0.4587; the normalized circular-range spread is 0.0318 cycles, defined as 1 - largest cyclic gap across member phases on the unit cycle. These are observed-animsmith measurements; sync=not-evaluated, and no contact or synchronization acceptance follows from the spread.
Mechanical baseline
| Finding/check | Affected scope | Potential impact | Evidence |
|---|---|---|---|
| No baseline errors | 12/12 files | File-readable mechanical prerequisite only | observed-animsmith; current ledger |
duration-sanity | 4 findings in 4 files | Empty reference take or shorter channels clamp-held at clip end | Basic_Locomotion_Pack_-_in-place/X Bot.fbx; Basic_Locomotion_Pack_-_root-motion/X Bot.fbx; Basic_Locomotion_Pack_-_root-motion/idle.fbx; Basic_Locomotion_Pack_-_root-motion/left turn 90.fbx |
constant-track | 1712 notes | Possible storage/evaluation overhead; no visual defect or safe rewrite inferred | observed-animsmith |
| Mixamo role resolution | 12/12 files | Enables measurement; does not prove target-character retargeting | observed-animsmith |
AnimSmith remediation evidence
| Source issue | Operation/declarations | Result | Independent verification | Remaining caveat |
|---|---|---|---|---|
| MIX-OWN-001 | Archive-level in-place and animation-owned XZ declarations | 4 stationary-root errors on Basic_Locomotion_Pack_-_root-motion/idle.fbx; Basic_Locomotion_Pack_-_root-motion/jump.fbx; Basic_Locomotion_Pack_-_root-motion/left turn 90.fbx; Basic_Locomotion_Pack_-_root-motion/right turn 90.fbx | Current per-file lint and measurement; no output produced | Per-file intent and controller acceptance unavailable; residual unresolved |
| MIX-TIME-001 | No transform run | Candidate not produced | Current baseline identifies exact channel-end ranges | Artist intent and visual boundary acceptance unavailable; residual unresolved |
| MIX-CONTENT-001 | No transform run | Candidate not produced | Both exact X Bot.fbx files retain 68 bones and an empty take | Reference-vs-animation intent unavailable; residual unresolved |
No command availability, configuration, or generated hypothesis is reported as a source repair.
Engine procedures and evidence
| Runtime | Version | Procedure | Observed result | Remaining gate |
|---|---|---|---|---|
| Unity | unspecified | Not run | not-evaluated | Exact import/controller/blend/visual test |
| Unreal Engine | unspecified | Not run | not-evaluated | Exact import/retarget/Blend Space test |
| Godot | unspecified | Not run | not-evaluated | Exact Skeleton3D/AnimationTree test |
| Bevy | unspecified | Not run | not-evaluated | Exact target identity/graph/controller test |
Rig, masking, and compatibility evidence
| Pack/rig/set pair | Skeleton/retarget | Scale/axes | Root policy | Timing/blend | Overall evidence |
|---|---|---|---|---|---|
hypothesis/kinematic-walk-3way / target character | Mixamo roles resolve; target deformation not tested | Not evaluated | Controller XZ/yaw/collision proposed | Exact durations and gait phases measured; synchronization/blend acceptance not tested | not-evaluated beyond static source evidence |
| This pack / any other Mixamo constituent | Common role labels and 66-bone count observed on motion files | Not evaluated | Per-file ownership unavailable | No pairwise transition/blend test | unknown; no cross-pack claim |
Limitations and unknowns
- Vendor mapping from 10 metadata motions to 12 files is unavailable and was not reconstructed from names.
- Loop intent, gait-phase acceptance, contacts, target character, deformation, engine graph, visual quality, root/yaw policy, and cross-pack behavior remain untested.
- Original ZIP payloads and controlling license evidence were unavailable; current hashing covers extracted source only.
Changes between AnimSmith versions
AnimSmith 0.14.0 — Fresh current-source matrix admitted 12/12 FBXs and revalidated baseline and archive-level declarations. New controller topology is explicitly evaluator-proposed and remains runtime-unaccepted.
AnimSmith 0.10.0 — Retained historical counts happen to agree; no prior semantic membership or engine claim was promoted. AnimSmith 0.7.0 evidence remains superseded.
Reproduction
Official evaluator preflight: release archive, archive SHA-256 4ecf79436f9123c779edb004050da3010b44a2b392e3031facb227d7734fc33e; exact member animsmith-v0.14.0-x86_64-unknown-linux-gnu/animsmith, binary SHA-256 c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366; tag v0.14.0, peeled commit e8321ad40be5ef6f162b31f085819c039175c3c9; working-tree state: N/A (official archive). Compiled features: fbx, report. Version and required command help plus representative FBX admission passed before evaluation. Preflight: external:animsmith-0.14.0-report-refresh/preflight.json, SHA-256 7034b5043de54e7b30064230db18605d1f96d0ea1477f7b8c56ce817cc73521a.
The current official binary SHA-256 is c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366, version 0.14.0, source revision e8321ad40be5ef6f162b31f085819c039175c3c9, dirty false, features fbx,report. Version, top-level help, and inspect, measure, and lint help all exited 0 before the matrix. Representative FBX admission and the exhaustive run then completed.
The read-only four-worker matrix ran inspect, JSON measure, empty-config JSON lint, and variant-declaration JSON lint for every source: 48 commands for this constituent and 996 collection-wide. Source inventory SHA-256 bc8543a8e1c7cf439030c07a2aead5db183d610633f637c1979dbce71a4dce66 covers 249 files / 119,754,377 bytes and matched before/after. Current ledger SHA-256 is 62b0a7b7a1bb47c817f6b005a988077a44dc8d942bb26343a5f7278a15582272; summary 4dd01ee9f1398fa200355c82206c8fd24d80d0dea1f8965aee8b03ba400cfc7c; report data a8d8e1bd3707b58e24f7d08f4ea7038c0f316254fa289e4f6439284dbcbc388f. Baseline config digests are e3b0c442... for the shared empty file or cf70223... for the explicit Mixamo baseline; in-place declaration digest acb638c0...; root-motion declaration digest 567dd18f.... Full digests, private paths, outputs, and licensed sources remain external.
Sources
- Fresh external source inventory, command ledger, current summary, and exact per-command output.
- AnimSmith game-ready clip guidance.
Animation pack evaluation: Mixamo Female Basic Locomotion
Technical verdict: Restricted use
Evaluation completeness: partial — all 20 extracted FBX files have current mechanical evidence; vendor mapping, runtime, visual, contact, retarget, license, and cross-pack acceptance remain unavailable.
Confidence: medium
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
Detailed evidence: Evidence appendix
Technical decision
For a controller-driven (kinematic) prototype, start with a walk/run speed pair from Female Basic using the selected members. Their source mechanics were measured, but the proposed blend has no accepted loop, foot-contact, transition, target-character, or target-engine result. Use one controller for movement and yaw; decide ownership separately for any additional root-motion-directory state.
First experiment: Import the selected in-place files on the target character, play them individually, then test their blend through speed or direction changes, starts, stops, and interrupted transitions. Review any endpoint warning on other named files before admission: retain an intentional hold after visual review, or request source repair when the intended endpoint differs. The evidence appendix retains the exact source counts, warning totals, skeleton counts, and profile results. The adoption guide compares this limited pilot with broader library and authoring routes.
Capability coverage
This is a full-body humanoid controller hypothesis for visible characters. Third-person gameplay-camera appearance and target-character deformation remain visually untested. Dedicated first-person arms/viewmodel content and close-camera suitability were not established.
Content present
| Candidate ingredients in the evaluated files | Current use boundary |
|---|---|
| Walk and run are named and measured. Strafe walking, idle, jump and turns appear by filename; their controller roles remain unclassified. | Check the named states and their transitions in the target game before treating this as a complete controller. |
Content gaps and unknowns
Other delivered motion leads, filename only: left strafe walk.fbx, idle.fbx, jump.fbx, left turn (2).fbx, and right turn.fbx. These names occur in the inspected source or issue inventory, but their intended controller roles and gameplay behavior have not been classified or tested. Inspect each source and its boundaries before adding a state; see the retained source evidence. A zero canonical role count does not establish that these motions are absent.
The legacy count of 18 motions has no verified mapping to these 20 files. Classify the other named motions before adding them to a controller.
Evaluation still needed
- Loop/phase policy, target-engine graph, contact and visual review, target-character retargeting, performance and cross-pack acceptance remain open.
Runtime sets and authored motion
| Set | Controller use | Adoption decision | Exact members |
|---|---|---|---|
hypothesis/kinematic-speed | Walk/run speed blend; controller owns XZ translation, yaw and collision | The selected walk/run pair has no reported mechanical finding. Prototype with controller-owned movement and project-defined speed thresholds. Six other root-motion-directory states need per-file ownership; left strafe walk has a source endpoint warning. No AnimSmith repair was tested. | Member timings, coordinates and contracts |
The selected files are grouped by name and measurement. AnimSmith measured phase (cycles) as walk 0.4913 and run 0.4289; their measured phase spread is 0.0624 cycles. These phase numbers do not establish matching foot contacts or a usable blend. Loop intent, contact alignment, and gameplay use remain not-evaluated.
Integration recipe
- Members/topology: Build a walk/run speed blend from the two linked clips. Set walk and run thresholds from your controller speeds and visible stride matching; stationary roots do not provide those thresholds. Treat other directions and root-motion alternatives as separate, untested sets.
- Timing/synchronization: Keep source timing and leave loop settings undecided. Compare normalized phase and visible foot contacts in the target engine before enabling a continuous blend.
- State ownership: These selected in-place files supply pose; one kinematic controller supplies XZ translation, yaw, and collision. Root-motion use, where animation supplies travel, requires separate per-file checks.
- Composition constraints: Begin with full-body states and transitions. Masks, additive motion, IK, sockets, and layering need separate tests.
- Acceptance gate: In the chosen engine, sweep the walk/run speed range in both directions and test rapid speed changes, starts and stops, foot contacts, interrupted transitions, and target-character deformation.
Technical issue register
| ID | Severity | Problem and impact | Primary owner | Current action | Future AnimSmith potential | Evidence/status |
|---|---|---|---|---|---|---|
| MIX-OWN-001 | moderate | Scope: Female_Basic_Locomotion_Pack_-_root-motion/idle.fbx; Female_Basic_Locomotion_Pack_-_root-motion/jump.fbx; Female_Basic_Locomotion_Pack_-_root-motion/left turn (2).fbx; Female_Basic_Locomotion_Pack_-_root-motion/left turn.fbx; Female_Basic_Locomotion_Pack_-_root-motion/right turn (2).fbx; Female_Basic_Locomotion_Pack_-_root-motion/right turn.fbx; reproduce: lint each file with the current archive-level animation-owned XZ declaration; impact: applying that policy can leave these states stationary while gameplay expects displacement. Guidance: not applicable. | engine-config | Declare these files controller-owned XZ unless project evidence establishes authored displacement. Accept when per-file declared lint has no ownership error and one target-controller playback applies translation/yaw/collision exactly once. | No generic rewrite is justified because the directory label does not establish per-file intent. | observed-animsmith; 6 current findings; no runtime acceptance; unresolved. |
| MIX-TIME-001 | moderate | Scope: Female_Basic_Locomotion_Pack_-_in-place/left strafe walk.fbx; Female_Basic_Locomotion_Pack_-_root-motion/left strafe walk.fbx; Female_Basic_Locomotion_Pack_-_root-motion/left turn (2).fbx; reproduce: empty-baseline duration-sanity; impact: shorter channels clamp-hold at the end and can create a brief frozen joint or transition/loop pop. Guidance. | unknown | Inspect the intended endpoint and slowed target-engine boundary playback first. If the hold is intentional and visually acceptable, document and retain it. Otherwise ask the source author to align the shorter channels to the intended endpoint, rerun duration-sanity, and review boundary playback. Accept only when the intended hold or corrected boundary is visually approved and the remaining mechanical warning is recorded accurately. | Deterministic endpoint conformance is plausible only after the intended endpoint and protected channels are declared. | observed-animsmith; 3 current source files; visual intent not evaluated; unresolved. |
| MIX-CONTENT-001 | note | Scope: Female_Basic_Locomotion_Pack_-_in-place/X Bot.fbx; Female_Basic_Locomotion_Pack_-_root-motion/X Bot.fbx; reproduce: empty-baseline duration-sanity reports Take 001 with no tracks; impact: automatic clip registration can expose an empty state, although these may be reference-character files. Guidance: not applicable. | unknown | Classify each file as reference-only or intended animation before import. Accept when reference files are excluded from animation registration or the author supplies the intended tracks. | No tool should invent missing motion. | observed-file and observed-animsmith; semantic purpose unavailable; unresolved. |
Engine status
| Runtime | Evidence level | Technical result | Remaining gate |
|---|---|---|---|
| Unity unspecified | not-evaluated | No current import, graph, playback, or visual evidence | Test the proposed controller and full-body graph on the target character |
| Unreal Engine unspecified | not-evaluated | No current import, retarget, Blend Space, or playback evidence | Test the exact retarget and controller path |
| Godot unspecified | not-evaluated | No current import, AnimationTree, or playback evidence | Test the exact Skeleton3D and controller path |
| Bevy unspecified | not-evaluated | No current loader, graph, or playback evidence | Test exact animation targets and controller composition |
Fit and limitations
Best fit is basic female locomotion prototyping where a developer can own movement, define thresholds, and test the exact target character. It is a poor fit for drop-in root-motion, network, motion-matching, layered-animation, or production-ready claims.
Cross-pack compatibility is unknown. Shared Mixamo role resolution and a common 66-bone count on motion-named files do not prove identical hierarchy, rest pose, scale, retargeting, blending, contacts, or style.
Changes between AnimSmith versions
AnimSmith 0.14.0 — The current evaluation reran 996 inspect, measure, and lint commands across the collection. This constituent admitted 20/20 files; baseline and declared-contract findings were revalidated, and the selected controller set was proposed from current files. Its clip roles remain unverified.
AnimSmith 0.10.0 — Retained historical mechanical counts agree with the current rerun; clip mapping and engine acceptance remain open. AnimSmith 0.7.0 evidence remains superseded.
Evidence status
The current tests covered 20 FBX source files individually. The legacy metadata lists 18 motions, but its clip-to-file mapping is unverified for the 20 files. The evidence appendix records the source integrity check, evaluator revision, command results, and digests. See the canonical readiness ladder.
Sources
- Fresh external AnimSmith 0.14.0 command ledger and summary; licensed source and outputs remain external.
- AnimSmith game-ready clip guidance.
Animation pack evidence appendix: Mixamo Female Basic Locomotion
Companion report: Technical report
Evidence status: partial — exhaustive current source mechanics and archive-level declarations; no current engine, visual, contact, retarget, license, or artistic acceptance.
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
This appendix contains scrubbed facts only. The canonical readiness ladder remains authoritative. The evaluation manifest schema is urn:animsmith:skill:animation-pack-evaluation-manifest:1; it is retained as opaque historical classification input, not current source-to-vendor mapping authority.
Evaluation scope and provenance
| Field | Value |
|---|---|
| Pack/edition | Mixamo Female Basic Locomotion; local revision unknown |
| Vendor/source | Vendor identity observed in delivered metadata; listing URL not retained |
| Delivered scope | 20 extracted FBX files: 7 in-place-directory and 13 root-motion-directory files |
| Target use | Engine-neutral intake plus a new evaluator-proposed kinematic-controller scenario |
| Target engines | Not evaluated |
| Target rigs/packs | Mixamo profile resolved; no target character or cross-pack runtime supplied |
| Source manifest | Fresh 249-file collection inventory SHA-256 bc8543a8e1c7cf439030c07a2aead5db183d610633f637c1979dbce71a4dce66; identical before/after evaluation |
| Evaluation manifest | mixamo-female-basic-locomotion.manifest.json; SHA-256 aa732d482af4e42d97579c2493084ddc1a20151d03ce22f54e8c73ae6d7cae68; legacy schema urn:animsmith:skill:animation-pack-evaluation-manifest:1 |
| Acquisition/license provenance | Extracted files are locally authorized; original ZIPs and controlling license evidence were unavailable; not legal advice |
The legacy metadata count of 18 motions is not linked authoritatively to these 20 files. Current conclusions use exact source paths and fresh measurements only.
Evidence coverage
| Surface | Offered/delivered | Evaluated | Findings | Not evaluated and why |
|---|---|---|---|---|
| Animation/source files | 20 | 20 | 0 baseline errors; 5 warnings | Vendor motion mapping unavailable |
| Rigs/export variants | 2 skeleton counts | 20 | two 68-bone X Bot.fbx; 18 66-bone files; Mixamo roles resolved | Retarget/deformation unavailable |
| AnimSmith baseline | 20 | 20 | 5 warnings; 2843 notes | — |
| Declared contracts | 20 | 20 | 6 ownership errors; 5 warnings | Per-file project intent unavailable |
| Evaluator scenario | 1 | 1 static proposal | 2 members with measured gait phase | Loop, synchronization, contact, engine, and visual acceptance unavailable |
| Engine import/playback | 4 common runtimes | 0 | 0 | No current engine project ran |
| Blend/mask/retarget | 1 proposed set | 0 runtime tests | 0 | Target graph/character unavailable |
Claim legend
Consequential claims are observed-file, observed-animsmith, inferred, or not-evaluated under the assessment taxonomy.
Evaluation manifest and taxonomy
Canonical clip-role inventory
| Canonical primary role | Logical motions | Delivered files | Evidence boundary |
|---|---|---|---|
idle-pose | 0 | 0 | Not authoritatively classified |
continuous-locomotion | 0 | 0 | Filename-based hypotheses stay outside the canonical role count |
locomotion-transition | 0 | 0 | Not authoritatively classified |
airborne | 0 | 0 | Not authoritatively classified |
traversal | 0 | 0 | Not authoritatively classified |
action-interaction | 0 | 0 | Not authoritatively classified |
reaction-death | 0 | 0 | Not authoritatively classified |
emote-cinematic | 0 | 0 | Not authoritatively classified |
other-unknown | 20 | 20 | One opaque current source unit per delivered FBX |
| Total | 20 | 20 | Current exact source paths; no vendor mapping inferred |
Exact runtime members
| Set/profile | Role or coordinate | Exact members | Variant/type | Timing or motion | Runtime contract |
|---|---|---|---|---|---|
hypothesis/kinematic-speed | walk | Female_Basic_Locomotion_Pack_-_in-place/walking.fbx | variant=in-place | duration=0.967 s; frames=30 frames | loop=unknown; sync=not-evaluated; movement=controller-xz-yaw; playback=project-defined |
hypothesis/kinematic-speed | run | Female_Basic_Locomotion_Pack_-_in-place/running.fbx | variant=in-place | duration=0.700 s; frames=22 frames | loop=unknown; sync=not-evaluated; movement=controller-xz-yaw; playback=project-defined |
The table above preserves the measured member detail from the companion report; its proposed controller still requires target-engine acceptance.
Runtime-set inventory
| Runtime set | Type | Members/variants | Grouping evidence | Validation status |
|---|---|---|---|---|
hypothesis/kinematic-speed | speed-blend | Female_Basic_Locomotion_Pack_-_in-place/walking.fbx; Female_Basic_Locomotion_Pack_-_in-place/running.fbx; all in-place | new evaluator hypothesis from current source names; no vendor/runtime authority; exact durations and frames are in the primary table | not-evaluated runtime acceptance |
Pipeline-stage coverage
| Stage | Coverage state | Evidence / remaining gate |
|---|---|---|
| Acquire | partially-evaluated | Extracted source present; original ZIP/license unavailable |
| Preserve raw | evaluated-clean | Fresh inventory matched byte-for-byte before/after the read-only matrix |
| Inspect | evaluated-clean | All 20 FBXs admitted and resolved the Mixamo profile |
| Segment | not-evaluated | Vendor motion-to-file mapping unavailable |
| Root motion | partially-evaluated | Archive-level XZ declarations ran; per-file ownership and yaw remain project decisions |
| Conform | not-evaluated | No source rewrite was authorized or produced |
| Validate | partially-evaluated | Mechanical and declared checks complete; runtime/artistic gates open |
| Optimize | not-evaluated | Constant-track notes were not promoted to a rewrite |
| Export | not-evaluated | No engine-facing export ran |
| Gate/report | partially-evaluated | Source-only decision report complete; adoption gates remain |
Readiness evidence by clip set
| Role or runtime set | File-ready / clip-ready | Set-ready / rig-use | Runtime / acceptance boundary |
|---|---|---|---|
| Opaque delivered corpus | 0 errors; 5 warnings; all sources readable | Mixamo roles resolved; vendor semantics and retarget use unavailable | No current engine, visual, contact, or gameplay acceptance |
hypothesis/kinematic-speed | Exact members and gait phases measured; loop intent unknown | New evaluator hypothesis; controller ownership proposed | Target-engine blend, phase/contact acceptance, transition, and deformation test required |
Validation-profile status
| Validation profile | Selection | Result / next evidence |
|---|---|---|
| Marketplace intake | selected — observed-pack-capability | Exhaustive source mechanics complete; acquisition/license boundary remains |
| Blended locomotion | selected — evaluator-selected-generic-scenario | Exact static proposal and gait-phase measurements recorded; runtime blend and phase/contact acceptance required |
| Root-motion controller | selected — observed-pack-capability | 6 archive-level ownership conflicts require per-file policy |
| State-machine transitions | selected — evaluator-selected-generic-scenario | Full-body transition policy proposed; no authored/runtime transition tested |
| Layered upper body/weapons | not-selected | No mask/additive contract or layer evidence |
| Traversal/environment | not-selected | No target traversal/environment contract |
| Contact actions/interactions | not-selected | No contact target or gameplay contract |
| Retargeted/customizable characters | not-selected | No target rig or retargeter supplied |
| Motion matching/search | not-selected | No database/annotation contract supplied |
| Networked movement | not-selected | No replication/rollback contract supplied |
| Runtime performance | not-selected | No target hardware/runtime project supplied |
Pack inventory and content evidence
The fresh inventory contains 20 FBX files. Both directory variants include X Bot.fbx with a 68-bone skeleton and an empty Take 001; the 18 motion-named files use 66 bones. All sources resolved the same nine Mixamo roles. This establishes static structure, not identical hierarchy/rest pose or retarget compatibility. Constant-track findings are optimization hints only.
The current scenario is intended to be useful without claiming old authority: hypothesis/kinematic-speed names 2 exact in-place-directory files, records current durations/frame counts, assigns movement to a hypothetical kinematic controller, and leaves loop intent, gait-phase acceptance, transitions, and runtime acceptance open. AnimSmith measured phase (cycles) as walk 0.4913 and run 0.4289; the normalized circular-range spread is 0.0624 cycles, defined as 1 - largest cyclic gap across member phases on the unit cycle. These are observed-animsmith measurements; sync=not-evaluated, and no contact or synchronization acceptance follows from the spread.
Mechanical baseline
| Finding/check | Affected scope | Potential impact | Evidence |
|---|---|---|---|
| No baseline errors | 20/20 files | File-readable mechanical prerequisite only | observed-animsmith; current ledger |
duration-sanity | 5 findings in 5 files | Empty reference take or shorter channels clamp-held at clip end | Female_Basic_Locomotion_Pack_-_in-place/X Bot.fbx; Female_Basic_Locomotion_Pack_-_in-place/left strafe walk.fbx; Female_Basic_Locomotion_Pack_-_root-motion/X Bot.fbx; Female_Basic_Locomotion_Pack_-_root-motion/left strafe walk.fbx; Female_Basic_Locomotion_Pack_-_root-motion/left turn (2).fbx |
constant-track | 2843 notes | Possible storage/evaluation overhead; no visual defect or safe rewrite inferred | observed-animsmith |
| Mixamo role resolution | 20/20 files | Enables measurement; does not prove target-character retargeting | observed-animsmith |
AnimSmith remediation evidence
| Source issue | Operation/declarations | Result | Independent verification | Remaining caveat |
|---|---|---|---|---|
| MIX-OWN-001 | Archive-level in-place and animation-owned XZ declarations | 6 stationary-root errors on Female_Basic_Locomotion_Pack_-_root-motion/idle.fbx; Female_Basic_Locomotion_Pack_-_root-motion/jump.fbx; Female_Basic_Locomotion_Pack_-_root-motion/left turn (2).fbx; Female_Basic_Locomotion_Pack_-_root-motion/left turn.fbx; Female_Basic_Locomotion_Pack_-_root-motion/right turn (2).fbx; Female_Basic_Locomotion_Pack_-_root-motion/right turn.fbx | Current per-file lint and measurement; no output produced | Per-file intent and controller acceptance unavailable; residual unresolved |
| MIX-TIME-001 | No transform run | Candidate not produced | Current baseline identifies exact channel-end ranges | Artist intent and visual boundary acceptance unavailable; residual unresolved |
| MIX-CONTENT-001 | No transform run | Candidate not produced | Both exact X Bot.fbx files retain 68 bones and an empty take | Reference-vs-animation intent unavailable; residual unresolved |
No command availability, configuration, or generated hypothesis is reported as a source repair.
Engine procedures and evidence
| Runtime | Version | Procedure | Observed result | Remaining gate |
|---|---|---|---|---|
| Unity | unspecified | Not run | not-evaluated | Exact import/controller/blend/visual test |
| Unreal Engine | unspecified | Not run | not-evaluated | Exact import/retarget/Blend Space test |
| Godot | unspecified | Not run | not-evaluated | Exact Skeleton3D/AnimationTree test |
| Bevy | unspecified | Not run | not-evaluated | Exact target identity/graph/controller test |
Rig, masking, and compatibility evidence
| Pack/rig/set pair | Skeleton/retarget | Scale/axes | Root policy | Timing/blend | Overall evidence |
|---|---|---|---|---|---|
hypothesis/kinematic-speed / target character | Mixamo roles resolve; target deformation not tested | Not evaluated | Controller XZ/yaw/collision proposed | Exact durations and gait phases measured; synchronization/blend acceptance not tested | not-evaluated beyond static source evidence |
| This pack / any other Mixamo constituent | Common role labels and 66-bone count observed on motion files | Not evaluated | Per-file ownership unavailable | No pairwise transition/blend test | unknown; no cross-pack claim |
Limitations and unknowns
- Vendor mapping from 18 metadata motions to 20 files is unavailable and was not reconstructed from names.
- Loop intent, gait-phase acceptance, contacts, target character, deformation, engine graph, visual quality, root/yaw policy, and cross-pack behavior remain untested.
- Original ZIP payloads and controlling license evidence were unavailable; current hashing covers extracted source only.
Changes between AnimSmith versions
AnimSmith 0.14.0 — Fresh current-source matrix admitted 20/20 FBXs and revalidated baseline and archive-level declarations. New controller topology is explicitly evaluator-proposed and remains runtime-unaccepted.
AnimSmith 0.10.0 — Retained historical counts happen to agree; no prior semantic membership or engine claim was promoted. AnimSmith 0.7.0 evidence remains superseded.
Reproduction
Official evaluator preflight: release archive, archive SHA-256 4ecf79436f9123c779edb004050da3010b44a2b392e3031facb227d7734fc33e; exact member animsmith-v0.14.0-x86_64-unknown-linux-gnu/animsmith, binary SHA-256 c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366; tag v0.14.0, peeled commit e8321ad40be5ef6f162b31f085819c039175c3c9; working-tree state: N/A (official archive). Compiled features: fbx, report. Version and required command help plus representative FBX admission passed before evaluation. Preflight: external:animsmith-0.14.0-report-refresh/preflight.json, SHA-256 7034b5043de54e7b30064230db18605d1f96d0ea1477f7b8c56ce817cc73521a.
The current official binary SHA-256 is c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366, version 0.14.0, source revision e8321ad40be5ef6f162b31f085819c039175c3c9, dirty false, features fbx,report. Version, top-level help, and inspect, measure, and lint help all exited 0 before the matrix. Representative FBX admission and the exhaustive run then completed.
The read-only four-worker matrix ran inspect, JSON measure, empty-config JSON lint, and variant-declaration JSON lint for every source: 80 commands for this constituent and 996 collection-wide. Source inventory SHA-256 bc8543a8e1c7cf439030c07a2aead5db183d610633f637c1979dbce71a4dce66 covers 249 files / 119,754,377 bytes and matched before/after. Current ledger SHA-256 is 62b0a7b7a1bb47c817f6b005a988077a44dc8d942bb26343a5f7278a15582272; summary 4dd01ee9f1398fa200355c82206c8fd24d80d0dea1f8965aee8b03ba400cfc7c; report data a8d8e1bd3707b58e24f7d08f4ea7038c0f316254fa289e4f6439284dbcbc388f. Baseline config digests are e3b0c442... for the shared empty file or cf70223... for the explicit Mixamo baseline; in-place declaration digest acb638c0...; root-motion declaration digest 567dd18f.... Full digests, private paths, outputs, and licensed sources remain external.
Sources
- Fresh external source inventory, command ledger, current summary, and exact per-command output.
- AnimSmith game-ready clip guidance.
Animation pack evaluation: Mixamo Female Locomotion
Technical verdict: Restricted use
Evaluation completeness: partial — all 18 extracted FBX files have current mechanical evidence; vendor mapping, runtime, visual, contact, retarget, license, and cross-pack acceptance remain unavailable.
Confidence: medium
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
Detailed evidence: Evidence appendix
Technical decision
For a controller-driven (kinematic) prototype, start with a walk/run speed pair from Female Locomotion using the selected members. Their source mechanics were measured, but the proposed blend has no accepted loop, foot-contact, transition, target-character, or target-engine result. Use one controller for movement and yaw; decide ownership separately for any additional root-motion-directory state.
First experiment: Import the selected in-place files on the target character, play them individually, then test their blend through speed or direction changes, starts, stops, and interrupted transitions. Review any endpoint warning on other named files before admission: retain an intentional hold after visual review, or request source repair when the intended endpoint differs. The evidence appendix retains the exact source counts, warning totals, skeleton counts, and profile results. The adoption guide compares this limited pilot with broader library and authoring routes.
Capability coverage
This is a full-body humanoid controller hypothesis for visible characters. Third-person gameplay-camera appearance and target-character deformation remain visually untested. Dedicated first-person arms/viewmodel content and close-camera suitability were not established.
Content present
| Candidate ingredients in the evaluated files | Current use boundary |
|---|---|
| Walk and run are named and measured. Strafe walking, idle, jump and turns appear by filename; their controller roles remain unclassified. | Check the named states and their transitions in the target game before treating this as a complete controller. |
Content gaps and unknowns
Other delivered motion leads, filename only: left strafe walk.fbx, idle.fbx, jump.fbx, left turn.fbx, and right turn.fbx. These names occur in the inspected source or issue inventory, but their intended controller roles and gameplay behavior have not been classified or tested. Inspect each source and its boundaries before adding a state; see the retained source evidence. A zero canonical role count does not establish that these motions are absent.
The legacy count of 16 motions has no verified mapping to these 18 files. Classify the other named motions before adding them to a controller.
Evaluation still needed
- Loop/phase policy, target-engine graph, contact and visual review, target-character retargeting, performance and cross-pack acceptance remain open.
Runtime sets and authored motion
| Set | Controller use | Adoption decision | Exact members |
|---|---|---|---|
hypothesis/kinematic-speed | Walk/run speed blend; controller owns XZ translation, yaw and collision | The selected walk/run pair has no reported mechanical finding. Prototype with controller-owned movement and project-defined speed thresholds. Four other root-motion-directory states need per-file ownership; left strafe walk has a source endpoint warning. No AnimSmith repair was tested. | Member timings, coordinates and contracts |
The selected files are grouped by name and measurement. AnimSmith measured phase (cycles) as walk 0.4913 and run 0.4289; their measured phase spread is 0.0624 cycles. These phase numbers do not establish matching foot contacts or a usable blend. Loop intent, contact alignment, and gameplay use remain not-evaluated.
Integration recipe
- Members/topology: Build a walk/run speed blend from the two linked clips. Set walk and run thresholds from your controller speeds and visible stride matching; stationary roots do not provide those thresholds. Treat other directions and root-motion alternatives as separate, untested sets.
- Timing/synchronization: Keep source timing and leave loop settings undecided. Compare normalized phase and visible foot contacts in the target engine before enabling a continuous blend.
- State ownership: These selected in-place files supply pose; one kinematic controller supplies XZ translation, yaw, and collision. Root-motion use, where animation supplies travel, requires separate per-file checks.
- Composition constraints: Begin with full-body states and transitions. Masks, additive motion, IK, sockets, and layering need separate tests.
- Acceptance gate: In the chosen engine, sweep the walk/run speed range in both directions and test rapid speed changes, starts and stops, foot contacts, interrupted transitions, and target-character deformation.
Technical issue register
| ID | Severity | Problem and impact | Primary owner | Current action | Future AnimSmith potential | Evidence/status |
|---|---|---|---|---|---|---|
| MIX-OWN-001 | moderate | Scope: Female_Locomotion_Pack_-_root-motion/idle.fbx; Female_Locomotion_Pack_-_root-motion/jump.fbx; Female_Locomotion_Pack_-_root-motion/left turn.fbx; Female_Locomotion_Pack_-_root-motion/right turn.fbx; reproduce: lint each file with the current archive-level animation-owned XZ declaration; impact: applying that policy can leave these states stationary while gameplay expects displacement. Guidance: not applicable. | engine-config | Declare these files controller-owned XZ unless project evidence establishes authored displacement. Accept when per-file declared lint has no ownership error and one target-controller playback applies translation/yaw/collision exactly once. | No generic rewrite is justified because the directory label does not establish per-file intent. | observed-animsmith; 4 current findings; no runtime acceptance; unresolved. |
| MIX-TIME-001 | moderate | Scope: Female_Locomotion_Pack_-_in-place/left strafe walk.fbx; Female_Locomotion_Pack_-_root-motion/left strafe walk.fbx; reproduce: empty-baseline duration-sanity; impact: shorter channels clamp-hold at the end and can create a brief frozen joint or transition/loop pop. Guidance. | unknown | Inspect the intended endpoint and slowed target-engine boundary playback first. If the hold is intentional and visually acceptable, document and retain it. Otherwise ask the source author to align the shorter channels to the intended endpoint, rerun duration-sanity, and review boundary playback. Accept only when the intended hold or corrected boundary is visually approved and the remaining mechanical warning is recorded accurately. | Deterministic endpoint conformance is plausible only after the intended endpoint and protected channels are declared. | observed-animsmith; 2 current source files; visual intent not evaluated; unresolved. |
| MIX-CONTENT-001 | note | Scope: Female_Locomotion_Pack_-_in-place/X Bot.fbx; Female_Locomotion_Pack_-_root-motion/X Bot.fbx; reproduce: empty-baseline duration-sanity reports Take 001 with no tracks; impact: automatic clip registration can expose an empty state, although these may be reference-character files. Guidance: not applicable. | unknown | Classify each file as reference-only or intended animation before import. Accept when reference files are excluded from animation registration or the author supplies the intended tracks. | No tool should invent missing motion. | observed-file and observed-animsmith; semantic purpose unavailable; unresolved. |
Engine status
| Runtime | Evidence level | Technical result | Remaining gate |
|---|---|---|---|
| Unity unspecified | not-evaluated | No current import, graph, playback, or visual evidence | Test the proposed controller and full-body graph on the target character |
| Unreal Engine unspecified | not-evaluated | No current import, retarget, Blend Space, or playback evidence | Test the exact retarget and controller path |
| Godot unspecified | not-evaluated | No current import, AnimationTree, or playback evidence | Test the exact Skeleton3D and controller path |
| Bevy unspecified | not-evaluated | No current loader, graph, or playback evidence | Test exact animation targets and controller composition |
Fit and limitations
Best fit is female locomotion prototyping where a developer can own movement, define thresholds, and test the exact target character. It is a poor fit for drop-in root-motion, network, motion-matching, layered-animation, or production-ready claims.
Cross-pack compatibility is unknown. Shared Mixamo role resolution and a common 66-bone count on motion-named files do not prove identical hierarchy, rest pose, scale, retargeting, blending, contacts, or style.
Changes between AnimSmith versions
AnimSmith 0.14.0 — The current evaluation reran 996 inspect, measure, and lint commands across the collection. This constituent admitted 18/18 files; baseline and declared-contract findings were revalidated, and the selected controller set was proposed from current files. Its clip roles remain unverified.
AnimSmith 0.10.0 — Retained historical mechanical counts agree with the current rerun; clip mapping and engine acceptance remain open. AnimSmith 0.7.0 evidence remains superseded.
Evidence status
The current tests covered 18 FBX source files individually. The legacy metadata lists 16 motions, but its clip-to-file mapping is unverified for the 18 files. The evidence appendix records the source integrity check, evaluator revision, command results, and digests. See the canonical readiness ladder.
Sources
- Fresh external AnimSmith 0.14.0 command ledger and summary; licensed source and outputs remain external.
- AnimSmith game-ready clip guidance.
Animation pack evidence appendix: Mixamo Female Locomotion
Companion report: Technical report
Evidence status: partial — exhaustive current source mechanics and archive-level declarations; no current engine, visual, contact, retarget, license, or artistic acceptance.
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
This appendix contains scrubbed facts only. The canonical readiness ladder remains authoritative. The evaluation manifest schema is urn:animsmith:skill:animation-pack-evaluation-manifest:1; it is retained as opaque historical classification input, not current source-to-vendor mapping authority.
Evaluation scope and provenance
| Field | Value |
|---|---|
| Pack/edition | Mixamo Female Locomotion; local revision unknown |
| Vendor/source | Vendor identity observed in delivered metadata; listing URL not retained |
| Delivered scope | 18 extracted FBX files: 7 in-place-directory and 11 root-motion-directory files |
| Target use | Engine-neutral intake plus a new evaluator-proposed kinematic-controller scenario |
| Target engines | Not evaluated |
| Target rigs/packs | Mixamo profile resolved; no target character or cross-pack runtime supplied |
| Source manifest | Fresh 249-file collection inventory SHA-256 bc8543a8e1c7cf439030c07a2aead5db183d610633f637c1979dbce71a4dce66; identical before/after evaluation |
| Evaluation manifest | mixamo-female-locomotion.manifest.json; SHA-256 4f0df4fda4c22806c29378f5d7b7a62cd88abc90d03324c16dc3cfdf0b0a3906; legacy schema urn:animsmith:skill:animation-pack-evaluation-manifest:1 |
| Acquisition/license provenance | Extracted files are locally authorized; original ZIPs and controlling license evidence were unavailable; not legal advice |
The legacy metadata count of 16 motions is not linked authoritatively to these 18 files. Current conclusions use exact source paths and fresh measurements only.
Evidence coverage
| Surface | Offered/delivered | Evaluated | Findings | Not evaluated and why |
|---|---|---|---|---|
| Animation/source files | 18 | 18 | 0 baseline errors; 4 warnings | Vendor motion mapping unavailable |
| Rigs/export variants | 2 skeleton counts | 18 | two 68-bone X Bot.fbx; 16 66-bone files; Mixamo roles resolved | Retarget/deformation unavailable |
| AnimSmith baseline | 18 | 18 | 4 warnings; 2559 notes | — |
| Declared contracts | 18 | 18 | 4 ownership errors; 4 warnings | Per-file project intent unavailable |
| Evaluator scenario | 1 | 1 static proposal | 2 members with measured gait phase | Loop, synchronization, contact, engine, and visual acceptance unavailable |
| Engine import/playback | 4 common runtimes | 0 | 0 | No current engine project ran |
| Blend/mask/retarget | 1 proposed set | 0 runtime tests | 0 | Target graph/character unavailable |
Claim legend
Consequential claims are observed-file, observed-animsmith, inferred, or not-evaluated under the assessment taxonomy.
Evaluation manifest and taxonomy
Canonical clip-role inventory
| Canonical primary role | Logical motions | Delivered files | Evidence boundary |
|---|---|---|---|
idle-pose | 0 | 0 | Not authoritatively classified |
continuous-locomotion | 0 | 0 | Filename-based hypotheses stay outside the canonical role count |
locomotion-transition | 0 | 0 | Not authoritatively classified |
airborne | 0 | 0 | Not authoritatively classified |
traversal | 0 | 0 | Not authoritatively classified |
action-interaction | 0 | 0 | Not authoritatively classified |
reaction-death | 0 | 0 | Not authoritatively classified |
emote-cinematic | 0 | 0 | Not authoritatively classified |
other-unknown | 18 | 18 | One opaque current source unit per delivered FBX |
| Total | 18 | 18 | Current exact source paths; no vendor mapping inferred |
Exact runtime members
| Set/profile | Role or coordinate | Exact members | Variant/type | Timing or motion | Runtime contract |
|---|---|---|---|---|---|
hypothesis/kinematic-speed | walk | Female_Locomotion_Pack_-_in-place/walking.fbx | variant=in-place | duration=0.967 s; frames=30 frames | loop=unknown; sync=not-evaluated; movement=controller-xz-yaw; playback=project-defined |
hypothesis/kinematic-speed | run | Female_Locomotion_Pack_-_in-place/running.fbx | variant=in-place | duration=0.700 s; frames=22 frames | loop=unknown; sync=not-evaluated; movement=controller-xz-yaw; playback=project-defined |
The table above preserves the measured member detail from the companion report; its proposed controller still requires target-engine acceptance.
Runtime-set inventory
| Runtime set | Type | Members/variants | Grouping evidence | Validation status |
|---|---|---|---|---|
hypothesis/kinematic-speed | speed-blend | Female_Locomotion_Pack_-_in-place/walking.fbx; Female_Locomotion_Pack_-_in-place/running.fbx; all in-place | new evaluator hypothesis from current source names; no vendor/runtime authority; exact durations and frames are in the primary table | not-evaluated runtime acceptance |
Pipeline-stage coverage
| Stage | Coverage state | Evidence / remaining gate |
|---|---|---|
| Acquire | partially-evaluated | Extracted source present; original ZIP/license unavailable |
| Preserve raw | evaluated-clean | Fresh inventory matched byte-for-byte before/after the read-only matrix |
| Inspect | evaluated-clean | All 18 FBXs admitted and resolved the Mixamo profile |
| Segment | not-evaluated | Vendor motion-to-file mapping unavailable |
| Root motion | partially-evaluated | Archive-level XZ declarations ran; per-file ownership and yaw remain project decisions |
| Conform | not-evaluated | No source rewrite was authorized or produced |
| Validate | partially-evaluated | Mechanical and declared checks complete; runtime/artistic gates open |
| Optimize | not-evaluated | Constant-track notes were not promoted to a rewrite |
| Export | not-evaluated | No engine-facing export ran |
| Gate/report | partially-evaluated | Source-only decision report complete; adoption gates remain |
Readiness evidence by clip set
| Role or runtime set | File-ready / clip-ready | Set-ready / rig-use | Runtime / acceptance boundary |
|---|---|---|---|
| Opaque delivered corpus | 0 errors; 4 warnings; all sources readable | Mixamo roles resolved; vendor semantics and retarget use unavailable | No current engine, visual, contact, or gameplay acceptance |
hypothesis/kinematic-speed | Exact members and gait phases measured; loop intent unknown | New evaluator hypothesis; controller ownership proposed | Target-engine blend, phase/contact acceptance, transition, and deformation test required |
Validation-profile status
| Validation profile | Selection | Result / next evidence |
|---|---|---|
| Marketplace intake | selected — observed-pack-capability | Exhaustive source mechanics complete; acquisition/license boundary remains |
| Blended locomotion | selected — evaluator-selected-generic-scenario | Exact static proposal and gait-phase measurements recorded; runtime blend and phase/contact acceptance required |
| Root-motion controller | selected — observed-pack-capability | 4 archive-level ownership conflicts require per-file policy |
| State-machine transitions | selected — evaluator-selected-generic-scenario | Full-body transition policy proposed; no authored/runtime transition tested |
| Layered upper body/weapons | not-selected | No mask/additive contract or layer evidence |
| Traversal/environment | not-selected | No target traversal/environment contract |
| Contact actions/interactions | not-selected | No contact target or gameplay contract |
| Retargeted/customizable characters | not-selected | No target rig or retargeter supplied |
| Motion matching/search | not-selected | No database/annotation contract supplied |
| Networked movement | not-selected | No replication/rollback contract supplied |
| Runtime performance | not-selected | No target hardware/runtime project supplied |
Pack inventory and content evidence
The fresh inventory contains 18 FBX files. Both directory variants include X Bot.fbx with a 68-bone skeleton and an empty Take 001; the 16 motion-named files use 66 bones. All sources resolved the same nine Mixamo roles. This establishes static structure, not identical hierarchy/rest pose or retarget compatibility. Constant-track findings are optimization hints only.
The current scenario is intended to be useful without claiming old authority: hypothesis/kinematic-speed names 2 exact in-place-directory files, records current durations/frame counts, assigns movement to a hypothetical kinematic controller, and leaves loop intent, gait-phase acceptance, transitions, and runtime acceptance open. AnimSmith measured phase (cycles) as walk 0.4913 and run 0.4289; the normalized circular-range spread is 0.0624 cycles, defined as 1 - largest cyclic gap across member phases on the unit cycle. These are observed-animsmith measurements; sync=not-evaluated, and no contact or synchronization acceptance follows from the spread.
Mechanical baseline
| Finding/check | Affected scope | Potential impact | Evidence |
|---|---|---|---|
| No baseline errors | 18/18 files | File-readable mechanical prerequisite only | observed-animsmith; current ledger |
duration-sanity | 4 findings in 4 files | Empty reference take or shorter channels clamp-held at clip end | Female_Locomotion_Pack_-_in-place/X Bot.fbx; Female_Locomotion_Pack_-_in-place/left strafe walk.fbx; Female_Locomotion_Pack_-_root-motion/X Bot.fbx; Female_Locomotion_Pack_-_root-motion/left strafe walk.fbx |
constant-track | 2559 notes | Possible storage/evaluation overhead; no visual defect or safe rewrite inferred | observed-animsmith |
| Mixamo role resolution | 18/18 files | Enables measurement; does not prove target-character retargeting | observed-animsmith |
AnimSmith remediation evidence
| Source issue | Operation/declarations | Result | Independent verification | Remaining caveat |
|---|---|---|---|---|
| MIX-OWN-001 | Archive-level in-place and animation-owned XZ declarations | 4 stationary-root errors on Female_Locomotion_Pack_-_root-motion/idle.fbx; Female_Locomotion_Pack_-_root-motion/jump.fbx; Female_Locomotion_Pack_-_root-motion/left turn.fbx; Female_Locomotion_Pack_-_root-motion/right turn.fbx | Current per-file lint and measurement; no output produced | Per-file intent and controller acceptance unavailable; residual unresolved |
| MIX-TIME-001 | No transform run | Candidate not produced | Current baseline identifies exact channel-end ranges | Artist intent and visual boundary acceptance unavailable; residual unresolved |
| MIX-CONTENT-001 | No transform run | Candidate not produced | Both exact X Bot.fbx files retain 68 bones and an empty take | Reference-vs-animation intent unavailable; residual unresolved |
No command availability, configuration, or generated hypothesis is reported as a source repair.
Engine procedures and evidence
| Runtime | Version | Procedure | Observed result | Remaining gate |
|---|---|---|---|---|
| Unity | unspecified | Not run | not-evaluated | Exact import/controller/blend/visual test |
| Unreal Engine | unspecified | Not run | not-evaluated | Exact import/retarget/Blend Space test |
| Godot | unspecified | Not run | not-evaluated | Exact Skeleton3D/AnimationTree test |
| Bevy | unspecified | Not run | not-evaluated | Exact target identity/graph/controller test |
Rig, masking, and compatibility evidence
| Pack/rig/set pair | Skeleton/retarget | Scale/axes | Root policy | Timing/blend | Overall evidence |
|---|---|---|---|---|---|
hypothesis/kinematic-speed / target character | Mixamo roles resolve; target deformation not tested | Not evaluated | Controller XZ/yaw/collision proposed | Exact durations and gait phases measured; synchronization/blend acceptance not tested | not-evaluated beyond static source evidence |
| This pack / any other Mixamo constituent | Common role labels and 66-bone count observed on motion files | Not evaluated | Per-file ownership unavailable | No pairwise transition/blend test | unknown; no cross-pack claim |
Limitations and unknowns
- Vendor mapping from 16 metadata motions to 18 files is unavailable and was not reconstructed from names.
- Loop intent, gait-phase acceptance, contacts, target character, deformation, engine graph, visual quality, root/yaw policy, and cross-pack behavior remain untested.
- Original ZIP payloads and controlling license evidence were unavailable; current hashing covers extracted source only.
Changes between AnimSmith versions
AnimSmith 0.14.0 — Fresh current-source matrix admitted 18/18 FBXs and revalidated baseline and archive-level declarations. New controller topology is explicitly evaluator-proposed and remains runtime-unaccepted.
AnimSmith 0.10.0 — Retained historical counts happen to agree; no prior semantic membership or engine claim was promoted. AnimSmith 0.7.0 evidence remains superseded.
Reproduction
Official evaluator preflight: release archive, archive SHA-256 4ecf79436f9123c779edb004050da3010b44a2b392e3031facb227d7734fc33e; exact member animsmith-v0.14.0-x86_64-unknown-linux-gnu/animsmith, binary SHA-256 c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366; tag v0.14.0, peeled commit e8321ad40be5ef6f162b31f085819c039175c3c9; working-tree state: N/A (official archive). Compiled features: fbx, report. Version and required command help plus representative FBX admission passed before evaluation. Preflight: external:animsmith-0.14.0-report-refresh/preflight.json, SHA-256 7034b5043de54e7b30064230db18605d1f96d0ea1477f7b8c56ce817cc73521a.
The current official binary SHA-256 is c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366, version 0.14.0, source revision e8321ad40be5ef6f162b31f085819c039175c3c9, dirty false, features fbx,report. Version, top-level help, and inspect, measure, and lint help all exited 0 before the matrix. Representative FBX admission and the exhaustive run then completed.
The read-only four-worker matrix ran inspect, JSON measure, empty-config JSON lint, and variant-declaration JSON lint for every source: 72 commands for this constituent and 996 collection-wide. Source inventory SHA-256 bc8543a8e1c7cf439030c07a2aead5db183d610633f637c1979dbce71a4dce66 covers 249 files / 119,754,377 bytes and matched before/after. Current ledger SHA-256 is 62b0a7b7a1bb47c817f6b005a988077a44dc8d942bb26343a5f7278a15582272; summary 4dd01ee9f1398fa200355c82206c8fd24d80d0dea1f8965aee8b03ba400cfc7c; report data a8d8e1bd3707b58e24f7d08f4ea7038c0f316254fa289e4f6439284dbcbc388f. Baseline config digests are e3b0c442... for the shared empty file or cf70223... for the explicit Mixamo baseline; in-place declaration digest acb638c0...; root-motion declaration digest 567dd18f.... Full digests, private paths, outputs, and licensed sources remain external.
Sources
- Fresh external source inventory, command ledger, current summary, and exact per-command output.
- AnimSmith game-ready clip guidance.
Animation pack evaluation: Mixamo Locomotion
Technical verdict: Restricted use
Evaluation completeness: partial — all 20 extracted FBX files have current mechanical evidence; vendor mapping, runtime, visual, contact, retarget, license, and cross-pack acceptance remain unavailable.
Confidence: medium
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
Detailed evidence: Evidence appendix
Technical decision
For a controller-driven (kinematic) prototype, start with an unarmed walk/run speed pair using the selected members. Their source mechanics were measured, but the proposed blend has no accepted loop, foot-contact, transition, target-character, or target-engine result. Use one controller for movement and yaw; decide ownership separately for any additional root-motion-directory state.
First experiment: Import the selected in-place files on the target character, play them individually, then test their blend through speed or direction changes, starts, stops, and interrupted transitions. Review any endpoint warning on other named files before admission: retain an intentional hold after visual review, or request source repair when the intended endpoint differs. The evidence appendix retains the exact source counts, warning totals, skeleton counts, and profile results. The adoption guide compares this limited pilot with broader library and authoring routes.
Capability coverage
This is a full-body humanoid controller hypothesis for visible characters. Third-person gameplay-camera appearance and target-character deformation remain visually untested. Dedicated first-person arms/viewmodel content and close-camera suitability were not established.
Content present
| Candidate ingredients in the evaluated files | Current use boundary |
|---|---|
| Walk and run are named and measured. Idle, jump, strafe and turns appear by filename; their controller roles remain unclassified. | Check the named states and their transitions in the target game before treating this as a complete controller. |
Content gaps and unknowns
Other delivered motion leads, filename only: idle.fbx, jump.fbx, left turn 90.fbx, right turn 90.fbx, and right strafe.fbx. These names occur in the inspected source or issue inventory, but their intended controller roles and gameplay behavior have not been classified or tested. Inspect each source and its boundaries before adding a state; see the retained source evidence. A zero canonical role count does not establish that these motions are absent.
The legacy count of 18 motions has no verified mapping to these 20 files. Classify the other named motions before adding them to a controller.
Evaluation still needed
- Loop/phase policy, target-engine graph, contact and visual review, target-character retargeting, performance and cross-pack acceptance remain open.
Runtime sets and authored motion
| Set | Controller use | Adoption decision | Exact members |
|---|---|---|---|
hypothesis/kinematic-speed | Walk/run speed blend; controller owns XZ translation, yaw and collision | The selected walk/run pair has no reported mechanical finding. Prototype with controller-owned movement and project-defined speed thresholds. Six other root-motion-directory states need per-file ownership; idle, left turn 90 and right strafe have source endpoint warnings. No AnimSmith repair was tested. | Member timings, coordinates and contracts |
The selected files are grouped by name and measurement. AnimSmith measured phase (cycles) as walk 0.4841 and run 0.4529; their measured phase spread is 0.0312 cycles. These phase numbers do not establish matching foot contacts or a usable blend. Loop intent, contact alignment, and gameplay use remain not-evaluated.
Integration recipe
- Members/topology: Build a walk/run speed blend from the two linked clips. Set walk and run thresholds from your controller speeds and visible stride matching; stationary roots do not provide those thresholds. Treat other directions and root-motion alternatives as separate, untested sets.
- Timing/synchronization: Keep source timing and leave loop settings undecided. Compare normalized phase and visible foot contacts in the target engine before enabling a continuous blend.
- State ownership: These selected in-place files supply pose; one kinematic controller supplies XZ translation, yaw, and collision. Root-motion use, where animation supplies travel, requires separate per-file checks.
- Composition constraints: Begin with full-body states and transitions. Masks, additive motion, IK, sockets, and layering need separate tests.
- Acceptance gate: In the chosen engine, sweep the walk/run speed range in both directions and test rapid speed changes, starts and stops, foot contacts, interrupted transitions, and target-character deformation.
Technical issue register
| ID | Severity | Problem and impact | Primary owner | Current action | Future AnimSmith potential | Evidence/status |
|---|---|---|---|---|---|---|
| MIX-OWN-001 | moderate | Scope: Locomotion_Pack_-_root-motion/idle.fbx; Locomotion_Pack_-_root-motion/jump.fbx; Locomotion_Pack_-_root-motion/left turn 90.fbx; Locomotion_Pack_-_root-motion/left turn.fbx; Locomotion_Pack_-_root-motion/right turn 90.fbx; Locomotion_Pack_-_root-motion/right turn.fbx; reproduce: lint each file with the current archive-level animation-owned XZ declaration; impact: applying that policy can leave these states stationary while gameplay expects displacement. Guidance: not applicable. | engine-config | Declare these files controller-owned XZ unless project evidence establishes authored displacement. Accept when per-file declared lint has no ownership error and one target-controller playback applies translation/yaw/collision exactly once. | No generic rewrite is justified because the directory label does not establish per-file intent. | observed-animsmith; 6 current findings; no runtime acceptance; unresolved. |
| MIX-TIME-001 | moderate | Scope: Locomotion_Pack_-_root-motion/idle.fbx; Locomotion_Pack_-_root-motion/left turn 90.fbx; Locomotion_Pack_-_root-motion/right strafe.fbx; reproduce: empty-baseline duration-sanity; impact: shorter channels clamp-hold at the end and can create a brief frozen joint or transition/loop pop. Guidance. | unknown | Inspect the intended endpoint and slowed target-engine boundary playback first. If the hold is intentional and visually acceptable, document and retain it. Otherwise ask the source author to align the shorter channels to the intended endpoint, rerun duration-sanity, and review boundary playback. Accept only when the intended hold or corrected boundary is visually approved and the remaining mechanical warning is recorded accurately. | Deterministic endpoint conformance is plausible only after the intended endpoint and protected channels are declared. | observed-animsmith; 3 current source files; visual intent not evaluated; unresolved. |
| MIX-CONTENT-001 | note | Scope: Locomotion_Pack_-_in-place/X Bot.fbx; Locomotion_Pack_-_root-motion/X Bot.fbx; reproduce: empty-baseline duration-sanity reports Take 001 with no tracks; impact: automatic clip registration can expose an empty state, although these may be reference-character files. Guidance: not applicable. | unknown | Classify each file as reference-only or intended animation before import. Accept when reference files are excluded from animation registration or the author supplies the intended tracks. | No tool should invent missing motion. | observed-file and observed-animsmith; semantic purpose unavailable; unresolved. |
Engine status
| Runtime | Evidence level | Technical result | Remaining gate |
|---|---|---|---|
| Unity unspecified | not-evaluated | No current import, graph, playback, or visual evidence | Test the proposed controller and full-body graph on the target character |
| Unreal Engine unspecified | not-evaluated | No current import, retarget, Blend Space, or playback evidence | Test the exact retarget and controller path |
| Godot unspecified | not-evaluated | No current import, AnimationTree, or playback evidence | Test the exact Skeleton3D and controller path |
| Bevy unspecified | not-evaluated | No current loader, graph, or playback evidence | Test exact animation targets and controller composition |
Fit and limitations
Best fit is general unarmed locomotion prototyping where a developer can own movement, define thresholds, and test the exact target character. It is a poor fit for drop-in root-motion, network, motion-matching, layered-animation, or production-ready claims.
Cross-pack compatibility is unknown. Shared Mixamo role resolution and a common 66-bone count on motion-named files do not prove identical hierarchy, rest pose, scale, retargeting, blending, contacts, or style.
Changes between AnimSmith versions
AnimSmith 0.14.0 — The current evaluation reran 996 inspect, measure, and lint commands across the collection. This constituent admitted 20/20 files; baseline and declared-contract findings were revalidated, and the selected controller set was proposed from current files. Its clip roles remain unverified.
AnimSmith 0.10.0 — Retained historical mechanical counts agree with the current rerun; clip mapping and engine acceptance remain open. AnimSmith 0.7.0 evidence remains superseded.
Evidence status
The current tests covered 20 FBX source files individually. The legacy metadata lists 18 motions, but its clip-to-file mapping is unverified for the 20 files. The evidence appendix records the source integrity check, evaluator revision, command results, and digests. See the canonical readiness ladder.
Sources
- Fresh external AnimSmith 0.14.0 command ledger and summary; licensed source and outputs remain external.
- AnimSmith game-ready clip guidance.
Animation pack evidence appendix: Mixamo Locomotion
Companion report: Technical report
Evidence status: partial — exhaustive current source mechanics and archive-level declarations; no current engine, visual, contact, retarget, license, or artistic acceptance.
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
This appendix contains scrubbed facts only. The canonical readiness ladder remains authoritative. The evaluation manifest schema is urn:animsmith:skill:animation-pack-evaluation-manifest:1; it is retained as opaque historical classification input, not current source-to-vendor mapping authority.
Evaluation scope and provenance
| Field | Value |
|---|---|
| Pack/edition | Mixamo Locomotion; local revision unknown |
| Vendor/source | Vendor identity observed in delivered metadata; listing URL not retained |
| Delivered scope | 20 extracted FBX files: 7 in-place-directory and 13 root-motion-directory files |
| Target use | Engine-neutral intake plus a new evaluator-proposed kinematic-controller scenario |
| Target engines | Not evaluated |
| Target rigs/packs | Mixamo profile resolved; no target character or cross-pack runtime supplied |
| Source manifest | Fresh 249-file collection inventory SHA-256 bc8543a8e1c7cf439030c07a2aead5db183d610633f637c1979dbce71a4dce66; identical before/after evaluation |
| Evaluation manifest | mixamo-locomotion.manifest.json; SHA-256 b9478ef47ba6fbf158737c867a2e7e6a253c910c48b0b3c0a77f133950f005e9; legacy schema urn:animsmith:skill:animation-pack-evaluation-manifest:1 |
| Acquisition/license provenance | Extracted files are locally authorized; original ZIPs and controlling license evidence were unavailable; not legal advice |
The legacy metadata count of 18 motions is not linked authoritatively to these 20 files. Current conclusions use exact source paths and fresh measurements only.
Evidence coverage
| Surface | Offered/delivered | Evaluated | Findings | Not evaluated and why |
|---|---|---|---|---|
| Animation/source files | 20 | 20 | 0 baseline errors; 5 warnings | Vendor motion mapping unavailable |
| Rigs/export variants | 2 skeleton counts | 20 | two 68-bone X Bot.fbx; 18 66-bone files; Mixamo roles resolved | Retarget/deformation unavailable |
| AnimSmith baseline | 20 | 20 | 5 warnings; 2852 notes | — |
| Declared contracts | 20 | 20 | 6 ownership errors; 5 warnings | Per-file project intent unavailable |
| Evaluator scenario | 1 | 1 static proposal | 2 members with measured gait phase | Loop, synchronization, contact, engine, and visual acceptance unavailable |
| Engine import/playback | 4 common runtimes | 0 | 0 | No current engine project ran |
| Blend/mask/retarget | 1 proposed set | 0 runtime tests | 0 | Target graph/character unavailable |
Claim legend
Consequential claims are observed-file, observed-animsmith, inferred, or not-evaluated under the assessment taxonomy.
Evaluation manifest and taxonomy
Canonical clip-role inventory
| Canonical primary role | Logical motions | Delivered files | Evidence boundary |
|---|---|---|---|
idle-pose | 0 | 0 | Not authoritatively classified |
continuous-locomotion | 0 | 0 | Filename-based hypotheses stay outside the canonical role count |
locomotion-transition | 0 | 0 | Not authoritatively classified |
airborne | 0 | 0 | Not authoritatively classified |
traversal | 0 | 0 | Not authoritatively classified |
action-interaction | 0 | 0 | Not authoritatively classified |
reaction-death | 0 | 0 | Not authoritatively classified |
emote-cinematic | 0 | 0 | Not authoritatively classified |
other-unknown | 20 | 20 | One opaque current source unit per delivered FBX |
| Total | 20 | 20 | Current exact source paths; no vendor mapping inferred |
Exact runtime members
| Set/profile | Role or coordinate | Exact members | Variant/type | Timing or motion | Runtime contract |
|---|---|---|---|---|---|
hypothesis/kinematic-speed | walk | Locomotion_Pack_-_in-place/walking.fbx | variant=in-place | duration=1.033 s; frames=32 frames | loop=unknown; sync=not-evaluated; movement=controller-xz-yaw; playback=project-defined |
hypothesis/kinematic-speed | run | Locomotion_Pack_-_in-place/running.fbx | variant=in-place | duration=0.700 s; frames=22 frames | loop=unknown; sync=not-evaluated; movement=controller-xz-yaw; playback=project-defined |
The table above preserves the measured member detail from the companion report; its proposed controller still requires target-engine acceptance.
Runtime-set inventory
| Runtime set | Type | Members/variants | Grouping evidence | Validation status |
|---|---|---|---|---|
hypothesis/kinematic-speed | speed-blend | Locomotion_Pack_-_in-place/walking.fbx; Locomotion_Pack_-_in-place/running.fbx; all in-place | new evaluator hypothesis from current source names; no vendor/runtime authority; exact durations and frames are in the primary table | not-evaluated runtime acceptance |
Pipeline-stage coverage
| Stage | Coverage state | Evidence / remaining gate |
|---|---|---|
| Acquire | partially-evaluated | Extracted source present; original ZIP/license unavailable |
| Preserve raw | evaluated-clean | Fresh inventory matched byte-for-byte before/after the read-only matrix |
| Inspect | evaluated-clean | All 20 FBXs admitted and resolved the Mixamo profile |
| Segment | not-evaluated | Vendor motion-to-file mapping unavailable |
| Root motion | partially-evaluated | Archive-level XZ declarations ran; per-file ownership and yaw remain project decisions |
| Conform | not-evaluated | No source rewrite was authorized or produced |
| Validate | partially-evaluated | Mechanical and declared checks complete; runtime/artistic gates open |
| Optimize | not-evaluated | Constant-track notes were not promoted to a rewrite |
| Export | not-evaluated | No engine-facing export ran |
| Gate/report | partially-evaluated | Source-only decision report complete; adoption gates remain |
Readiness evidence by clip set
| Role or runtime set | File-ready / clip-ready | Set-ready / rig-use | Runtime / acceptance boundary |
|---|---|---|---|
| Opaque delivered corpus | 0 errors; 5 warnings; all sources readable | Mixamo roles resolved; vendor semantics and retarget use unavailable | No current engine, visual, contact, or gameplay acceptance |
hypothesis/kinematic-speed | Exact members and gait phases measured; loop intent unknown | New evaluator hypothesis; controller ownership proposed | Target-engine blend, phase/contact acceptance, transition, and deformation test required |
Validation-profile status
| Validation profile | Selection | Result / next evidence |
|---|---|---|
| Marketplace intake | selected — observed-pack-capability | Exhaustive source mechanics complete; acquisition/license boundary remains |
| Blended locomotion | selected — evaluator-selected-generic-scenario | Exact static proposal and gait-phase measurements recorded; runtime blend and phase/contact acceptance required |
| Root-motion controller | selected — observed-pack-capability | 6 archive-level ownership conflicts require per-file policy |
| State-machine transitions | selected — evaluator-selected-generic-scenario | Full-body transition policy proposed; no authored/runtime transition tested |
| Layered upper body/weapons | not-selected | No mask/additive contract or layer evidence |
| Traversal/environment | not-selected | No target traversal/environment contract |
| Contact actions/interactions | not-selected | No contact target or gameplay contract |
| Retargeted/customizable characters | not-selected | No target rig or retargeter supplied |
| Motion matching/search | not-selected | No database/annotation contract supplied |
| Networked movement | not-selected | No replication/rollback contract supplied |
| Runtime performance | not-selected | No target hardware/runtime project supplied |
Pack inventory and content evidence
The fresh inventory contains 20 FBX files. Both directory variants include X Bot.fbx with a 68-bone skeleton and an empty Take 001; the 18 motion-named files use 66 bones. All sources resolved the same nine Mixamo roles. This establishes static structure, not identical hierarchy/rest pose or retarget compatibility. Constant-track findings are optimization hints only.
The current scenario is intended to be useful without claiming old authority: hypothesis/kinematic-speed names 2 exact in-place-directory files, records current durations/frame counts, assigns movement to a hypothetical kinematic controller, and leaves loop intent, gait-phase acceptance, transitions, and runtime acceptance open. AnimSmith measured phase (cycles) as walk 0.4841 and run 0.4529; the normalized circular-range spread is 0.0312 cycles, defined as 1 - largest cyclic gap across member phases on the unit cycle. These are observed-animsmith measurements; sync=not-evaluated, and no contact or synchronization acceptance follows from the spread.
Mechanical baseline
| Finding/check | Affected scope | Potential impact | Evidence |
|---|---|---|---|
| No baseline errors | 20/20 files | File-readable mechanical prerequisite only | observed-animsmith; current ledger |
duration-sanity | 5 findings in 5 files | Empty reference take or shorter channels clamp-held at clip end | Locomotion_Pack_-_in-place/X Bot.fbx; Locomotion_Pack_-_root-motion/X Bot.fbx; Locomotion_Pack_-_root-motion/idle.fbx; Locomotion_Pack_-_root-motion/left turn 90.fbx; Locomotion_Pack_-_root-motion/right strafe.fbx |
constant-track | 2852 notes | Possible storage/evaluation overhead; no visual defect or safe rewrite inferred | observed-animsmith |
| Mixamo role resolution | 20/20 files | Enables measurement; does not prove target-character retargeting | observed-animsmith |
AnimSmith remediation evidence
| Source issue | Operation/declarations | Result | Independent verification | Remaining caveat |
|---|---|---|---|---|
| MIX-OWN-001 | Archive-level in-place and animation-owned XZ declarations | 6 stationary-root errors on Locomotion_Pack_-_root-motion/idle.fbx; Locomotion_Pack_-_root-motion/jump.fbx; Locomotion_Pack_-_root-motion/left turn 90.fbx; Locomotion_Pack_-_root-motion/left turn.fbx; Locomotion_Pack_-_root-motion/right turn 90.fbx; Locomotion_Pack_-_root-motion/right turn.fbx | Current per-file lint and measurement; no output produced | Per-file intent and controller acceptance unavailable; residual unresolved |
| MIX-TIME-001 | No transform run | Candidate not produced | Current baseline identifies exact channel-end ranges | Artist intent and visual boundary acceptance unavailable; residual unresolved |
| MIX-CONTENT-001 | No transform run | Candidate not produced | Both exact X Bot.fbx files retain 68 bones and an empty take | Reference-vs-animation intent unavailable; residual unresolved |
No command availability, configuration, or generated hypothesis is reported as a source repair.
Engine procedures and evidence
| Runtime | Version | Procedure | Observed result | Remaining gate |
|---|---|---|---|---|
| Unity | unspecified | Not run | not-evaluated | Exact import/controller/blend/visual test |
| Unreal Engine | unspecified | Not run | not-evaluated | Exact import/retarget/Blend Space test |
| Godot | unspecified | Not run | not-evaluated | Exact Skeleton3D/AnimationTree test |
| Bevy | unspecified | Not run | not-evaluated | Exact target identity/graph/controller test |
Rig, masking, and compatibility evidence
| Pack/rig/set pair | Skeleton/retarget | Scale/axes | Root policy | Timing/blend | Overall evidence |
|---|---|---|---|---|---|
hypothesis/kinematic-speed / target character | Mixamo roles resolve; target deformation not tested | Not evaluated | Controller XZ/yaw/collision proposed | Exact durations and gait phases measured; synchronization/blend acceptance not tested | not-evaluated beyond static source evidence |
| This pack / any other Mixamo constituent | Common role labels and 66-bone count observed on motion files | Not evaluated | Per-file ownership unavailable | No pairwise transition/blend test | unknown; no cross-pack claim |
Limitations and unknowns
- Vendor mapping from 18 metadata motions to 20 files is unavailable and was not reconstructed from names.
- Loop intent, gait-phase acceptance, contacts, target character, deformation, engine graph, visual quality, root/yaw policy, and cross-pack behavior remain untested.
- Original ZIP payloads and controlling license evidence were unavailable; current hashing covers extracted source only.
Changes between AnimSmith versions
AnimSmith 0.14.0 — Fresh current-source matrix admitted 20/20 FBXs and revalidated baseline and archive-level declarations. New controller topology is explicitly evaluator-proposed and remains runtime-unaccepted.
AnimSmith 0.10.0 — Retained historical counts happen to agree; no prior semantic membership or engine claim was promoted. AnimSmith 0.7.0 evidence remains superseded.
Reproduction
Official evaluator preflight: release archive, archive SHA-256 4ecf79436f9123c779edb004050da3010b44a2b392e3031facb227d7734fc33e; exact member animsmith-v0.14.0-x86_64-unknown-linux-gnu/animsmith, binary SHA-256 c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366; tag v0.14.0, peeled commit e8321ad40be5ef6f162b31f085819c039175c3c9; working-tree state: N/A (official archive). Compiled features: fbx, report. Version and required command help plus representative FBX admission passed before evaluation. Preflight: external:animsmith-0.14.0-report-refresh/preflight.json, SHA-256 7034b5043de54e7b30064230db18605d1f96d0ea1477f7b8c56ce817cc73521a.
The current official binary SHA-256 is c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366, version 0.14.0, source revision e8321ad40be5ef6f162b31f085819c039175c3c9, dirty false, features fbx,report. Version, top-level help, and inspect, measure, and lint help all exited 0 before the matrix. Representative FBX admission and the exhaustive run then completed.
The read-only four-worker matrix ran inspect, JSON measure, empty-config JSON lint, and variant-declaration JSON lint for every source: 80 commands for this constituent and 996 collection-wide. Source inventory SHA-256 bc8543a8e1c7cf439030c07a2aead5db183d610633f637c1979dbce71a4dce66 covers 249 files / 119,754,377 bytes and matched before/after. Current ledger SHA-256 is 62b0a7b7a1bb47c817f6b005a988077a44dc8d942bb26343a5f7278a15582272; summary 4dd01ee9f1398fa200355c82206c8fd24d80d0dea1f8965aee8b03ba400cfc7c; report data a8d8e1bd3707b58e24f7d08f4ea7038c0f316254fa289e4f6439284dbcbc388f. Baseline config digests are e3b0c442... for the shared empty file or cf70223... for the explicit Mixamo baseline; in-place declaration digest acb638c0...; root-motion declaration digest 567dd18f.... Full digests, private paths, outputs, and licensed sources remain external.
Sources
- Fresh external source inventory, command ledger, current summary, and exact per-command output.
- AnimSmith game-ready clip guidance.
Animation pack evaluation: Mixamo Longbow Locomotion
Technical verdict: Restricted use
Evaluation completeness: partial — all 22 extracted FBX files have current mechanical evidence; vendor mapping, runtime, visual, contact, retarget, license, and cross-pack acceptance remain unavailable.
Confidence: medium
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
Detailed evidence: Evidence appendix
Technical decision
For a controller-driven (kinematic) prototype, start with four-direction full-body bow running using the selected members. Their source mechanics were measured, but the proposed blend has no accepted loop, foot-contact, transition, target-character, or target-engine result. Use one controller for movement and yaw; decide ownership separately for any additional root-motion-directory state.
First experiment: Import the selected in-place files on the target character, play them individually, then test their blend through speed or direction changes, starts, stops, and interrupted transitions. Review any endpoint warning on other named files before admission: retain an intentional hold after visual review, or request source repair when the intended endpoint differs. The evidence appendix retains the exact source counts, warning totals, skeleton counts, and profile results. The adoption guide compares this limited pilot with broader library and authoring routes.
Capability coverage
This is a full-body humanoid controller hypothesis for visible characters. Third-person gameplay-camera appearance, target-character deformation, and close-camera grip remain visually untested. Dedicated first-person arms/viewmodel content and first-person weapon framing were not established; the pistol/rifle/bow names do not establish them.
Content present
| Candidate ingredients in the evaluated files | Current use boundary |
|---|---|
| Four standing run directions are named and measured. Standing idle, turn and forward-stop files appear by filename; their controller roles are unclassified and untested. | Check the named states and their transitions in the target game before treating this as a complete controller. |
Content gaps and unknowns
Other delivered motion leads, filename only: standing run forward stop.fbx, standing idle 01.fbx, standing turn 90 left.fbx, and standing turn 90 right.fbx. These names occur in the inspected source or issue inventory, but their intended controller roles and gameplay behavior have not been classified or tested. Inspect each source and its boundaries before adding a state; see the retained source evidence. A zero canonical role count does not establish that these motions are absent.
The legacy count of 20 motions has no verified mapping to these 22 files. Classify the other named motions before adding them to a controller.
Evaluation still needed
- Loop/phase policy, target-engine graph, contact and visual review, target-character retargeting, performance and cross-pack acceptance remain open.
Runtime sets and authored motion
| Set | Controller use | Adoption decision | Exact members |
|---|---|---|---|
hypothesis/kinematic-run-4way | Four-direction full-body bow run; controller owns XZ translation, yaw and collision | The four selected runs support a controller-owned movement prototype. Duration and phase differ between directions; inspect contacts before blending. Three idle/turn files need per-file ownership; the forward-stop file has an endpoint warning. No AnimSmith repair was tested; engine acceptance remains open. | Member timings, coordinates and contracts |
The selected files are grouped by name and measurement. AnimSmith measured phase (cycles) as forward 0.4578, back 0.5651, left 0.6134, and right 0.4589; their measured phase spread is 0.1557 cycles. These phase numbers do not establish matching foot contacts or a usable blend. Loop intent, contact alignment, and gameplay use remain not-evaluated.
Integration recipe
- Members/topology: Use the exact members and coordinates for
hypothesis/kinematic-run-4wayin the linked appendix as a proposed directional blend. Add other directions or in-place/root-motion alternatives only after checking their own files. - Timing/synchronization: Keep source timing and leave loop settings undecided. Compare normalized phase and visible foot contacts in the target engine before enabling a continuous blend.
- State ownership: These selected in-place files supply pose; one kinematic controller supplies XZ translation, yaw, and collision. Root-motion use, where animation supplies travel, requires separate per-file checks.
- Composition constraints: Begin with full-body states and transitions. Masks, additive motion, IK, sockets, and layering need separate tests.
- Acceptance gate: In the chosen engine, test the selected blend at center and axes, rapid input changes, starts and stops, foot contacts, interrupted transitions, and target-character deformation.
Technical issue register
| ID | Severity | Problem and impact | Primary owner | Current action | Future AnimSmith potential | Evidence/status |
|---|---|---|---|---|---|---|
| MIX-PHASE-001 | note | Scope: hypothesis/kinematic-run-4way; reproduce: compare the four measured phases, whose normalized circular-range spread is 0.1557 cycles; impact: if these phases represent incompatible support-foot events, a continuous blend can pulse or foot-skate, but no contact/runtime test establishes a defect. Guidance. | unknown | Align normalized time in the target engine and inspect support-foot contacts at center, axes, and rapid direction changes. Accept when a project-declared contact tolerance passes with no visible pulse or skate; if it fails, establish phase/contact intent before adding engine markers or requesting artist cleanup. | A deterministic alignment operation is plausible only after loop and contact semantics are declared. | observed-animsmith measurement; synchronization/contact acceptance not-evaluated; unresolved. |
| MIX-OWN-001 | moderate | Scope: Longbow_Locomotion_Pack_-_root-motion/standing idle 01.fbx; Longbow_Locomotion_Pack_-_root-motion/standing turn 90 left.fbx; Longbow_Locomotion_Pack_-_root-motion/standing turn 90 right.fbx; reproduce: lint each file with the current archive-level animation-owned XZ declaration; impact: applying that policy can leave these states stationary while gameplay expects displacement. Guidance: not applicable. | engine-config | Declare these files controller-owned XZ unless project evidence establishes authored displacement. Accept when per-file declared lint has no ownership error and one target-controller playback applies translation/yaw/collision exactly once. | No generic rewrite is justified because the directory label does not establish per-file intent. | observed-animsmith; 3 current findings; no runtime acceptance; unresolved. |
| MIX-TIME-001 | moderate | Scope: Longbow_Locomotion_Pack_-_root-motion/standing run forward stop.fbx; reproduce: empty-baseline duration-sanity; impact: shorter channels clamp-hold at the end and can create a brief frozen joint or transition/loop pop. Guidance. | unknown | Inspect the intended endpoint and slowed target-engine boundary playback first. If the hold is intentional and visually acceptable, document and retain it. Otherwise ask the source author to align the shorter channels to the intended endpoint, rerun duration-sanity, and review boundary playback. Accept only when the intended hold or corrected boundary is visually approved and the remaining mechanical warning is recorded accurately. | Deterministic endpoint conformance is plausible only after the intended endpoint and protected channels are declared. | observed-animsmith; 1 current source files; visual intent not evaluated; unresolved. |
| MIX-CONTENT-001 | note | Scope: Longbow_Locomotion_Pack_-_in-place/X Bot.fbx; Longbow_Locomotion_Pack_-_root-motion/X Bot.fbx; reproduce: empty-baseline duration-sanity reports Take 001 with no tracks; impact: automatic clip registration can expose an empty state, although these may be reference-character files. Guidance: not applicable. | unknown | Classify each file as reference-only or intended animation before import. Accept when reference files are excluded from animation registration or the author supplies the intended tracks. | No tool should invent missing motion. | observed-file and observed-animsmith; semantic purpose unavailable; unresolved. |
Engine status
| Runtime | Evidence level | Technical result | Remaining gate |
|---|---|---|---|
| Unity unspecified | not-evaluated | No current import, graph, playback, or visual evidence | Test the proposed controller and full-body graph on the target character |
| Unreal Engine unspecified | not-evaluated | No current import, retarget, Blend Space, or playback evidence | Test the exact retarget and controller path |
| Godot unspecified | not-evaluated | No current import, AnimationTree, or playback evidence | Test the exact Skeleton3D and controller path |
| Bevy unspecified | not-evaluated | No current loader, graph, or playback evidence | Test exact animation targets and controller composition |
Fit and limitations
Best fit is full-body longbow locomotion prototyping where a developer can own movement, define thresholds, and test the exact target character. It is a poor fit for drop-in root-motion, network, motion-matching, layered-animation, or production-ready claims.
Cross-pack compatibility is unknown. Shared Mixamo role resolution and a common 66-bone count on motion-named files do not prove identical hierarchy, rest pose, scale, retargeting, blending, contacts, or style.
Changes between AnimSmith versions
AnimSmith 0.14.0 — The current evaluation reran 996 inspect, measure, and lint commands across the collection. This constituent admitted 22/22 files; baseline and declared-contract findings were revalidated, and the selected controller set was proposed from current files. Its clip roles remain unverified.
AnimSmith 0.10.0 — Retained historical mechanical counts agree with the current rerun; clip mapping and engine acceptance remain open. AnimSmith 0.7.0 evidence remains superseded.
Evidence status
The current tests covered 22 FBX source files individually. The legacy metadata lists 20 motions, but its clip-to-file mapping is unverified for the 22 files. The evidence appendix records the source integrity check, evaluator revision, command results, and digests. See the canonical readiness ladder.
Sources
- Fresh external AnimSmith 0.14.0 command ledger and summary; licensed source and outputs remain external.
- AnimSmith game-ready clip guidance.
Animation pack evidence appendix: Mixamo Longbow Locomotion
Companion report: Technical report
Evidence status: partial — exhaustive current source mechanics and archive-level declarations; no current engine, visual, contact, retarget, license, or artistic acceptance.
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
This appendix contains scrubbed facts only. The canonical readiness ladder remains authoritative. The evaluation manifest schema is urn:animsmith:skill:animation-pack-evaluation-manifest:1; it is retained as opaque historical classification input, not current source-to-vendor mapping authority.
Evaluation scope and provenance
| Field | Value |
|---|---|
| Pack/edition | Mixamo Longbow Locomotion; local revision unknown |
| Vendor/source | Vendor identity observed in delivered metadata; listing URL not retained |
| Delivered scope | 22 extracted FBX files: 9 in-place-directory and 13 root-motion-directory files |
| Target use | Engine-neutral intake plus a new evaluator-proposed kinematic-controller scenario |
| Target engines | Not evaluated |
| Target rigs/packs | Mixamo profile resolved; no target character or cross-pack runtime supplied |
| Source manifest | Fresh 249-file collection inventory SHA-256 bc8543a8e1c7cf439030c07a2aead5db183d610633f637c1979dbce71a4dce66; identical before/after evaluation |
| Evaluation manifest | mixamo-longbow-locomotion.manifest.json; SHA-256 317fa928cb79eef6dd447e936438a9114100d47ae6b8191af1dd53111e137336; legacy schema urn:animsmith:skill:animation-pack-evaluation-manifest:1 |
| Acquisition/license provenance | Extracted files are locally authorized; original ZIPs and controlling license evidence were unavailable; not legal advice |
The legacy metadata count of 20 motions is not linked authoritatively to these 22 files. Current conclusions use exact source paths and fresh measurements only.
Evidence coverage
| Surface | Offered/delivered | Evaluated | Findings | Not evaluated and why |
|---|---|---|---|---|
| Animation/source files | 22 | 22 | 0 baseline errors; 3 warnings | Vendor motion mapping unavailable |
| Rigs/export variants | 2 skeleton counts | 22 | two 68-bone X Bot.fbx; 20 66-bone files; Mixamo roles resolved | Retarget/deformation unavailable |
| AnimSmith baseline | 22 | 22 | 3 warnings; 3126 notes | — |
| Declared contracts | 22 | 22 | 3 ownership errors; 3 warnings | Per-file project intent unavailable |
| Evaluator scenario | 1 | 1 static proposal | 4 members with measured gait phase | Loop, synchronization, contact, engine, and visual acceptance unavailable |
| Engine import/playback | 4 common runtimes | 0 | 0 | No current engine project ran |
| Blend/mask/retarget | 1 proposed set | 0 runtime tests | 0 | Target graph/character unavailable |
Claim legend
Consequential claims are observed-file, observed-animsmith, inferred, or not-evaluated under the assessment taxonomy.
Evaluation manifest and taxonomy
Canonical clip-role inventory
| Canonical primary role | Logical motions | Delivered files | Evidence boundary |
|---|---|---|---|
idle-pose | 0 | 0 | Not authoritatively classified |
continuous-locomotion | 0 | 0 | Filename-based hypotheses stay outside the canonical role count |
locomotion-transition | 0 | 0 | Not authoritatively classified |
airborne | 0 | 0 | Not authoritatively classified |
traversal | 0 | 0 | Not authoritatively classified |
action-interaction | 0 | 0 | Not authoritatively classified |
reaction-death | 0 | 0 | Not authoritatively classified |
emote-cinematic | 0 | 0 | Not authoritatively classified |
other-unknown | 22 | 22 | One opaque current source unit per delivered FBX |
| Total | 22 | 22 | Current exact source paths; no vendor mapping inferred |
Exact runtime members
| Set/profile | Role or coordinate | Exact members | Variant/type | Timing or motion | Runtime contract |
|---|---|---|---|---|---|
hypothesis/kinematic-run-4way | forward=(0,1) | Longbow_Locomotion_Pack_-_in-place/standing run forward.fbx | variant=in-place | duration=0.867 s; frames=27 frames | loop=unknown; sync=not-evaluated; movement=controller-xz-yaw; playback=project-defined |
hypothesis/kinematic-run-4way | back=(0,-1) | Longbow_Locomotion_Pack_-_in-place/standing run back.fbx | variant=in-place | duration=0.667 s; frames=21 frames | loop=unknown; sync=not-evaluated; movement=controller-xz-yaw; playback=project-defined |
hypothesis/kinematic-run-4way | left=(-1,0) | Longbow_Locomotion_Pack_-_in-place/standing run left.fbx | variant=in-place | duration=0.667 s; frames=21 frames | loop=unknown; sync=not-evaluated; movement=controller-xz-yaw; playback=project-defined |
hypothesis/kinematic-run-4way | right=(1,0) | Longbow_Locomotion_Pack_-_in-place/standing run right.fbx | variant=in-place | duration=0.767 s; frames=24 frames | loop=unknown; sync=not-evaluated; movement=controller-xz-yaw; playback=project-defined |
The table above preserves the measured member detail from the companion report; its proposed controller still requires target-engine acceptance.
Runtime-set inventory
| Runtime set | Type | Members/variants | Grouping evidence | Validation status |
|---|---|---|---|---|
hypothesis/kinematic-run-4way | directional-blend | Longbow_Locomotion_Pack_-_in-place/standing run forward.fbx; Longbow_Locomotion_Pack_-_in-place/standing run back.fbx; Longbow_Locomotion_Pack_-_in-place/standing run left.fbx; Longbow_Locomotion_Pack_-_in-place/standing run right.fbx; all in-place | new evaluator hypothesis from current source names; no vendor/runtime authority; exact durations and frames are in the primary table | not-evaluated runtime acceptance |
Pipeline-stage coverage
| Stage | Coverage state | Evidence / remaining gate |
|---|---|---|
| Acquire | partially-evaluated | Extracted source present; original ZIP/license unavailable |
| Preserve raw | evaluated-clean | Fresh inventory matched byte-for-byte before/after the read-only matrix |
| Inspect | evaluated-clean | All 22 FBXs admitted and resolved the Mixamo profile |
| Segment | not-evaluated | Vendor motion-to-file mapping unavailable |
| Root motion | partially-evaluated | Archive-level XZ declarations ran; per-file ownership and yaw remain project decisions |
| Conform | not-evaluated | No source rewrite was authorized or produced |
| Validate | partially-evaluated | Mechanical and declared checks complete; runtime/artistic gates open |
| Optimize | not-evaluated | Constant-track notes were not promoted to a rewrite |
| Export | not-evaluated | No engine-facing export ran |
| Gate/report | partially-evaluated | Source-only decision report complete; adoption gates remain |
Readiness evidence by clip set
| Role or runtime set | File-ready / clip-ready | Set-ready / rig-use | Runtime / acceptance boundary |
|---|---|---|---|
| Opaque delivered corpus | 0 errors; 3 warnings; all sources readable | Mixamo roles resolved; vendor semantics and retarget use unavailable | No current engine, visual, contact, or gameplay acceptance |
hypothesis/kinematic-run-4way | Exact members and gait phases measured; loop intent unknown | New evaluator hypothesis; controller ownership proposed | Target-engine blend, phase/contact acceptance, transition, and deformation test required |
Validation-profile status
| Validation profile | Selection | Result / next evidence |
|---|---|---|
| Marketplace intake | selected — observed-pack-capability | Exhaustive source mechanics complete; acquisition/license boundary remains |
| Blended locomotion | selected — evaluator-selected-generic-scenario | Exact static proposal and gait-phase measurements recorded; runtime blend and phase/contact acceptance required |
| Root-motion controller | selected — observed-pack-capability | 3 archive-level ownership conflicts require per-file policy |
| State-machine transitions | selected — evaluator-selected-generic-scenario | Full-body transition policy proposed; no authored/runtime transition tested |
| Layered upper body/weapons | not-selected | No mask/additive contract or layer evidence |
| Traversal/environment | not-selected | No target traversal/environment contract |
| Contact actions/interactions | not-selected | No contact target or gameplay contract |
| Retargeted/customizable characters | not-selected | No target rig or retargeter supplied |
| Motion matching/search | not-selected | No database/annotation contract supplied |
| Networked movement | not-selected | No replication/rollback contract supplied |
| Runtime performance | not-selected | No target hardware/runtime project supplied |
Pack inventory and content evidence
The fresh inventory contains 22 FBX files. Both directory variants include X Bot.fbx with a 68-bone skeleton and an empty Take 001; the 20 motion-named files use 66 bones. All sources resolved the same nine Mixamo roles. This establishes static structure, not identical hierarchy/rest pose or retarget compatibility. Constant-track findings are optimization hints only.
The current scenario is intended to be useful without claiming old authority: hypothesis/kinematic-run-4way names 4 exact in-place-directory files, records current durations/frame counts, assigns movement to a hypothetical kinematic controller, and leaves loop intent, gait-phase acceptance, transitions, and runtime acceptance open. AnimSmith measured phase (cycles) as forward 0.4578, back 0.5651, left 0.6134, and right 0.4589; the normalized circular-range spread is 0.1557 cycles, defined as 1 - largest cyclic gap across member phases on the unit cycle. These are observed-animsmith measurements; sync=not-evaluated, and no defect, contact acceptance, or synchronization acceptance follows from the spread alone.
Mechanical baseline
| Finding/check | Affected scope | Potential impact | Evidence |
|---|---|---|---|
| No baseline errors | 22/22 files | File-readable mechanical prerequisite only | observed-animsmith; current ledger |
duration-sanity | 3 findings in 3 files | Empty reference take or shorter channels clamp-held at clip end | Longbow_Locomotion_Pack_-_in-place/X Bot.fbx; Longbow_Locomotion_Pack_-_root-motion/X Bot.fbx; Longbow_Locomotion_Pack_-_root-motion/standing run forward stop.fbx |
constant-track | 3126 notes | Possible storage/evaluation overhead; no visual defect or safe rewrite inferred | observed-animsmith |
| Mixamo role resolution | 22/22 files | Enables measurement; does not prove target-character retargeting | observed-animsmith |
AnimSmith remediation evidence
| Source issue | Operation/declarations | Result | Independent verification | Remaining caveat |
|---|---|---|---|---|
| MIX-OWN-001 | Archive-level in-place and animation-owned XZ declarations | 3 stationary-root errors on Longbow_Locomotion_Pack_-_root-motion/standing idle 01.fbx; Longbow_Locomotion_Pack_-_root-motion/standing turn 90 left.fbx; Longbow_Locomotion_Pack_-_root-motion/standing turn 90 right.fbx | Current per-file lint and measurement; no output produced | Per-file intent and controller acceptance unavailable; residual unresolved |
| MIX-TIME-001 | No transform run | Candidate not produced | Current baseline identifies exact channel-end ranges | Artist intent and visual boundary acceptance unavailable; residual unresolved |
| MIX-CONTENT-001 | No transform run | Candidate not produced | Both exact X Bot.fbx files retain 68 bones and an empty take | Reference-vs-animation intent unavailable; residual unresolved |
No command availability, configuration, or generated hypothesis is reported as a source repair.
Engine procedures and evidence
| Runtime | Version | Procedure | Observed result | Remaining gate |
|---|---|---|---|---|
| Unity | unspecified | Not run | not-evaluated | Exact import/controller/blend/visual test |
| Unreal Engine | unspecified | Not run | not-evaluated | Exact import/retarget/Blend Space test |
| Godot | unspecified | Not run | not-evaluated | Exact Skeleton3D/AnimationTree test |
| Bevy | unspecified | Not run | not-evaluated | Exact target identity/graph/controller test |
Rig, masking, and compatibility evidence
| Pack/rig/set pair | Skeleton/retarget | Scale/axes | Root policy | Timing/blend | Overall evidence |
|---|---|---|---|---|---|
hypothesis/kinematic-run-4way / target character | Mixamo roles resolve; target deformation not tested | Not evaluated | Controller XZ/yaw/collision proposed | Exact durations and gait phases measured; synchronization/blend acceptance not tested | not-evaluated beyond static source evidence |
| This pack / any other Mixamo constituent | Common role labels and 66-bone count observed on motion files | Not evaluated | Per-file ownership unavailable | No pairwise transition/blend test | unknown; no cross-pack claim |
Limitations and unknowns
- Vendor mapping from 20 metadata motions to 22 files is unavailable and was not reconstructed from names.
- Loop intent, gait-phase acceptance, contacts, target character, deformation, engine graph, visual quality, root/yaw policy, and cross-pack behavior remain untested.
- Original ZIP payloads and controlling license evidence were unavailable; current hashing covers extracted source only.
Changes between AnimSmith versions
AnimSmith 0.14.0 — Fresh current-source matrix admitted 22/22 FBXs and revalidated baseline and archive-level declarations. New controller topology is explicitly evaluator-proposed and remains runtime-unaccepted.
AnimSmith 0.10.0 — Retained historical counts happen to agree; no prior semantic membership or engine claim was promoted. AnimSmith 0.7.0 evidence remains superseded.
Reproduction
Official evaluator preflight: release archive, archive SHA-256 4ecf79436f9123c779edb004050da3010b44a2b392e3031facb227d7734fc33e; exact member animsmith-v0.14.0-x86_64-unknown-linux-gnu/animsmith, binary SHA-256 c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366; tag v0.14.0, peeled commit e8321ad40be5ef6f162b31f085819c039175c3c9; working-tree state: N/A (official archive). Compiled features: fbx, report. Version and required command help plus representative FBX admission passed before evaluation. Preflight: external:animsmith-0.14.0-report-refresh/preflight.json, SHA-256 7034b5043de54e7b30064230db18605d1f96d0ea1477f7b8c56ce817cc73521a.
The current official binary SHA-256 is c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366, version 0.14.0, source revision e8321ad40be5ef6f162b31f085819c039175c3c9, dirty false, features fbx,report. Version, top-level help, and inspect, measure, and lint help all exited 0 before the matrix. Representative FBX admission and the exhaustive run then completed.
The read-only four-worker matrix ran inspect, JSON measure, empty-config JSON lint, and variant-declaration JSON lint for every source: 88 commands for this constituent and 996 collection-wide. Source inventory SHA-256 bc8543a8e1c7cf439030c07a2aead5db183d610633f637c1979dbce71a4dce66 covers 249 files / 119,754,377 bytes and matched before/after. Current ledger SHA-256 is 62b0a7b7a1bb47c817f6b005a988077a44dc8d942bb26343a5f7278a15582272; summary 4dd01ee9f1398fa200355c82206c8fd24d80d0dea1f8965aee8b03ba400cfc7c; report data a8d8e1bd3707b58e24f7d08f4ea7038c0f316254fa289e4f6439284dbcbc388f. Baseline config digests are e3b0c442... for the shared empty file or cf70223... for the explicit Mixamo baseline; in-place declaration digest acb638c0...; root-motion declaration digest 567dd18f.... Full digests, private paths, outputs, and licensed sources remain external.
Sources
- Fresh external source inventory, command ledger, current summary, and exact per-command output.
- AnimSmith game-ready clip guidance.
Animation pack evaluation: Mixamo Magic Locomotion
Technical verdict: Restricted use
Evaluation completeness: partial — all 27 extracted FBX files have current mechanical evidence; vendor mapping, runtime, visual, contact, retarget, license, and cross-pack acceptance remain unavailable.
Confidence: medium
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
Detailed evidence: Evidence appendix
Technical decision
For a controller-driven (kinematic) prototype, start with four-direction full-body magic running using the selected members. Their source mechanics were measured, but the proposed blend has no accepted loop, foot-contact, transition, target-character, or target-engine result. Use one controller for movement and yaw; decide ownership separately for any additional root-motion-directory state.
First experiment: Import the selected in-place files on the target character, play them individually, then test their blend through speed or direction changes, starts, stops, and interrupted transitions. Review any endpoint warning on other named files before admission: retain an intentional hold after visual review, or request source repair when the intended endpoint differs. The evidence appendix retains the exact source counts, warning totals, skeleton counts, and profile results. The adoption guide compares this limited pilot with broader library and authoring routes.
Capability coverage
This is a full-body humanoid controller hypothesis for visible characters. Third-person gameplay-camera appearance and target-character deformation remain visually untested. Dedicated first-person arms/viewmodel content and close-camera suitability were not established.
Content present
| Candidate ingredients in the evaluated files | Current use boundary |
|---|---|
| Four standing run directions are named and measured. Standing jump, land, idle and turn files appear by filename; their controller roles are unclassified and untested. | Check the named states and their transitions in the target game before treating this as a complete controller. |
Content gaps and unknowns
Other delivered motion leads, filename only: Standing Jump.fbx, Standing Land To Standing Idle.fbx, Standing Turn Left 90.fbx, and Standing Turn Right 90.fbx. These names occur in the inspected source or issue inventory, but their intended controller roles and gameplay behavior have not been classified or tested. Inspect each source and its boundaries before adding a state; see the retained source evidence. A zero canonical role count does not establish that these motions are absent.
The legacy count of 25 motions has no verified mapping to these 27 files. Classify the other named motions before adding them to a controller.
Evaluation still needed
- Loop/phase policy, target-engine graph, contact and visual review, target-character retargeting, performance and cross-pack acceptance remain open.
Runtime sets and authored motion
| Set | Controller use | Adoption decision | Exact members |
|---|---|---|---|
hypothesis/kinematic-run-4way | Four-direction full-body magic run; controller owns XZ translation, yaw and collision | The four selected runs support a controller-owned movement prototype. Duration and phase differ between directions; inspect contacts before blending. Four jump/turn/idle files need per-file ownership; jump, land, turn and idle files have endpoint warnings. No AnimSmith repair was tested; engine acceptance remains open. | Member timings, coordinates and contracts |
The selected files are grouped by name and measurement. AnimSmith measured phase (cycles) as forward 0.4370, back 0.6134, left 0.4670, and right 0.4208; their measured phase spread is 0.1926 cycles. These phase numbers do not establish matching foot contacts or a usable blend. Loop intent, contact alignment, and gameplay use remain not-evaluated.
Integration recipe
- Members/topology: Use the exact members and coordinates for
hypothesis/kinematic-run-4wayin the linked appendix as a proposed directional blend. Add other directions or in-place/root-motion alternatives only after checking their own files. - Timing/synchronization: Keep source timing and leave loop settings undecided. Compare normalized phase and visible foot contacts in the target engine before enabling a continuous blend.
- State ownership: These selected in-place files supply pose; one kinematic controller supplies XZ translation, yaw, and collision. Root-motion use, where animation supplies travel, requires separate per-file checks.
- Composition constraints: Begin with full-body states and transitions. Masks, additive motion, IK, sockets, and layering need separate tests.
- Acceptance gate: In the chosen engine, test the selected blend at center and axes, rapid input changes, starts and stops, foot contacts, interrupted transitions, and target-character deformation.
Technical issue register
| ID | Severity | Problem and impact | Primary owner | Current action | Future AnimSmith potential | Evidence/status |
|---|---|---|---|---|---|---|
| MIX-PHASE-001 | note | Scope: hypothesis/kinematic-run-4way; reproduce: compare the four measured phases, whose normalized circular-range spread is 0.1926 cycles; impact: if these phases represent incompatible support-foot events, a continuous blend can pulse or foot-skate, but no contact/runtime test establishes a defect. Guidance. | unknown | Align normalized time in the target engine and inspect support-foot contacts at center, axes, and rapid direction changes. Accept when a project-declared contact tolerance passes with no visible pulse or skate; if it fails, establish phase/contact intent before adding engine markers or requesting artist cleanup. | A deterministic alignment operation is plausible only after loop and contact semantics are declared. | observed-animsmith measurement; synchronization/contact acceptance not-evaluated; unresolved. |
| MIX-OWN-001 | moderate | Scope: Magic_Locomotion_Pack_-_root-motion/Standing Jump.fbx; Magic_Locomotion_Pack_-_root-motion/Standing Turn Left 90.fbx; Magic_Locomotion_Pack_-_root-motion/Standing Turn Right 90.fbx; Magic_Locomotion_Pack_-_root-motion/standing idle.fbx; reproduce: lint each file with the current archive-level animation-owned XZ declaration; impact: applying that policy can leave these states stationary while gameplay expects displacement. Guidance: not applicable. | engine-config | Declare these files controller-owned XZ unless project evidence establishes authored displacement. Accept when per-file declared lint has no ownership error and one target-controller playback applies translation/yaw/collision exactly once. | No generic rewrite is justified because the directory label does not establish per-file intent. | observed-animsmith; 4 current findings; no runtime acceptance; unresolved. |
| MIX-TIME-001 | moderate | Scope: Magic_Locomotion_Pack_-_root-motion/Standing Jump.fbx; Magic_Locomotion_Pack_-_root-motion/Standing Land To Standing Idle.fbx; Magic_Locomotion_Pack_-_root-motion/Standing Turn Left 90.fbx; Magic_Locomotion_Pack_-_root-motion/standing idle.fbx; reproduce: empty-baseline duration-sanity; impact: shorter channels clamp-hold at the end and can create a brief frozen joint or transition/loop pop. Guidance. | unknown | Inspect the intended endpoint and slowed target-engine boundary playback first. If the hold is intentional and visually acceptable, document and retain it. Otherwise ask the source author to align the shorter channels to the intended endpoint, rerun duration-sanity, and review boundary playback. Accept only when the intended hold or corrected boundary is visually approved and the remaining mechanical warning is recorded accurately. | Deterministic endpoint conformance is plausible only after the intended endpoint and protected channels are declared. | observed-animsmith; 4 current source files; visual intent not evaluated; unresolved. |
| MIX-CONTENT-001 | note | Scope: Magic_Locomotion_Pack_-_in-place/X Bot.fbx; Magic_Locomotion_Pack_-_root-motion/X Bot.fbx; reproduce: empty-baseline duration-sanity reports Take 001 with no tracks; impact: automatic clip registration can expose an empty state, although these may be reference-character files. Guidance: not applicable. | unknown | Classify each file as reference-only or intended animation before import. Accept when reference files are excluded from animation registration or the author supplies the intended tracks. | No tool should invent missing motion. | observed-file and observed-animsmith; semantic purpose unavailable; unresolved. |
Engine status
| Runtime | Evidence level | Technical result | Remaining gate |
|---|---|---|---|
| Unity unspecified | not-evaluated | No current import, graph, playback, or visual evidence | Test the proposed controller and full-body graph on the target character |
| Unreal Engine unspecified | not-evaluated | No current import, retarget, Blend Space, or playback evidence | Test the exact retarget and controller path |
| Godot unspecified | not-evaluated | No current import, AnimationTree, or playback evidence | Test the exact Skeleton3D and controller path |
| Bevy unspecified | not-evaluated | No current loader, graph, or playback evidence | Test exact animation targets and controller composition |
Fit and limitations
Best fit is full-body magic locomotion prototyping where a developer can own movement, define thresholds, and test the exact target character. It is a poor fit for drop-in root-motion, network, motion-matching, layered-animation, or production-ready claims.
Cross-pack compatibility is unknown. Shared Mixamo role resolution and a common 66-bone count on motion-named files do not prove identical hierarchy, rest pose, scale, retargeting, blending, contacts, or style.
Changes between AnimSmith versions
AnimSmith 0.14.0 — The current evaluation reran 996 inspect, measure, and lint commands across the collection. This constituent admitted 27/27 files; baseline and declared-contract findings were revalidated, and the selected controller set was proposed from current files. Its clip roles remain unverified.
AnimSmith 0.10.0 — Retained historical mechanical counts agree with the current rerun; clip mapping and engine acceptance remain open. AnimSmith 0.7.0 evidence remains superseded.
Evidence status
The current tests covered 27 FBX source files individually. The legacy metadata lists 25 motions, but its clip-to-file mapping is unverified for the 27 files. The evidence appendix records the source integrity check, evaluator revision, command results, and digests. See the canonical readiness ladder.
Sources
- Fresh external AnimSmith 0.14.0 command ledger and summary; licensed source and outputs remain external.
- AnimSmith game-ready clip guidance.
Animation pack evidence appendix: Mixamo Magic Locomotion
Companion report: Technical report
Evidence status: partial — exhaustive current source mechanics and archive-level declarations; no current engine, visual, contact, retarget, license, or artistic acceptance.
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
This appendix contains scrubbed facts only. The canonical readiness ladder remains authoritative. The evaluation manifest schema is urn:animsmith:skill:animation-pack-evaluation-manifest:1; it is retained as opaque historical classification input, not current source-to-vendor mapping authority.
Evaluation scope and provenance
| Field | Value |
|---|---|
| Pack/edition | Mixamo Magic Locomotion; local revision unknown |
| Vendor/source | Vendor identity observed in delivered metadata; listing URL not retained |
| Delivered scope | 27 extracted FBX files: 10 in-place-directory and 17 root-motion-directory files |
| Target use | Engine-neutral intake plus a new evaluator-proposed kinematic-controller scenario |
| Target engines | Not evaluated |
| Target rigs/packs | Mixamo profile resolved; no target character or cross-pack runtime supplied |
| Source manifest | Fresh 249-file collection inventory SHA-256 bc8543a8e1c7cf439030c07a2aead5db183d610633f637c1979dbce71a4dce66; identical before/after evaluation |
| Evaluation manifest | mixamo-magic-locomotion.manifest.json; SHA-256 c766dea7a1f0e34d83fcf7f4686f9540f72b514643e28038b81e9ea9a1c8909d; legacy schema urn:animsmith:skill:animation-pack-evaluation-manifest:1 |
| Acquisition/license provenance | Extracted files are locally authorized; original ZIPs and controlling license evidence were unavailable; not legal advice |
The legacy metadata count of 25 motions is not linked authoritatively to these 27 files. Current conclusions use exact source paths and fresh measurements only.
Evidence coverage
| Surface | Offered/delivered | Evaluated | Findings | Not evaluated and why |
|---|---|---|---|---|
| Animation/source files | 27 | 27 | 0 baseline errors; 6 warnings | Vendor motion mapping unavailable |
| Rigs/export variants | 2 skeleton counts | 27 | two 68-bone X Bot.fbx; 25 66-bone files; Mixamo roles resolved | Retarget/deformation unavailable |
| AnimSmith baseline | 27 | 27 | 6 warnings; 3837 notes | — |
| Declared contracts | 27 | 27 | 4 ownership errors; 6 warnings | Per-file project intent unavailable |
| Evaluator scenario | 1 | 1 static proposal | 4 members with measured gait phase | Loop, synchronization, contact, engine, and visual acceptance unavailable |
| Engine import/playback | 4 common runtimes | 0 | 0 | No current engine project ran |
| Blend/mask/retarget | 1 proposed set | 0 runtime tests | 0 | Target graph/character unavailable |
Claim legend
Consequential claims are observed-file, observed-animsmith, inferred, or not-evaluated under the assessment taxonomy.
Evaluation manifest and taxonomy
Canonical clip-role inventory
| Canonical primary role | Logical motions | Delivered files | Evidence boundary |
|---|---|---|---|
idle-pose | 0 | 0 | Not authoritatively classified |
continuous-locomotion | 0 | 0 | Filename-based hypotheses stay outside the canonical role count |
locomotion-transition | 0 | 0 | Not authoritatively classified |
airborne | 0 | 0 | Not authoritatively classified |
traversal | 0 | 0 | Not authoritatively classified |
action-interaction | 0 | 0 | Not authoritatively classified |
reaction-death | 0 | 0 | Not authoritatively classified |
emote-cinematic | 0 | 0 | Not authoritatively classified |
other-unknown | 27 | 27 | One opaque current source unit per delivered FBX |
| Total | 27 | 27 | Current exact source paths; no vendor mapping inferred |
Exact runtime members
| Set/profile | Role or coordinate | Exact members | Variant/type | Timing or motion | Runtime contract |
|---|---|---|---|---|---|
hypothesis/kinematic-run-4way | forward=(0,1) | Magic_Locomotion_Pack_-_in-place/Standing Run Forward.fbx | variant=in-place | duration=0.733 s; frames=23 frames | loop=unknown; sync=not-evaluated; movement=controller-xz-yaw; playback=project-defined |
hypothesis/kinematic-run-4way | back=(0,-1) | Magic_Locomotion_Pack_-_in-place/Standing Run Back.fbx | variant=in-place | duration=0.633 s; frames=20 frames | loop=unknown; sync=not-evaluated; movement=controller-xz-yaw; playback=project-defined |
hypothesis/kinematic-run-4way | left=(-1,0) | Magic_Locomotion_Pack_-_in-place/Standing Run Left.fbx | variant=in-place | duration=0.767 s; frames=24 frames | loop=unknown; sync=not-evaluated; movement=controller-xz-yaw; playback=project-defined |
hypothesis/kinematic-run-4way | right=(1,0) | Magic_Locomotion_Pack_-_in-place/Standing Run Right.fbx | variant=in-place | duration=0.767 s; frames=24 frames | loop=unknown; sync=not-evaluated; movement=controller-xz-yaw; playback=project-defined |
The table above preserves the measured member detail from the companion report; its proposed controller still requires target-engine acceptance.
Runtime-set inventory
| Runtime set | Type | Members/variants | Grouping evidence | Validation status |
|---|---|---|---|---|
hypothesis/kinematic-run-4way | directional-blend | Magic_Locomotion_Pack_-_in-place/Standing Run Forward.fbx; Magic_Locomotion_Pack_-_in-place/Standing Run Back.fbx; Magic_Locomotion_Pack_-_in-place/Standing Run Left.fbx; Magic_Locomotion_Pack_-_in-place/Standing Run Right.fbx; all in-place | new evaluator hypothesis from current source names; no vendor/runtime authority; exact durations and frames are in the primary table | not-evaluated runtime acceptance |
Pipeline-stage coverage
| Stage | Coverage state | Evidence / remaining gate |
|---|---|---|
| Acquire | partially-evaluated | Extracted source present; original ZIP/license unavailable |
| Preserve raw | evaluated-clean | Fresh inventory matched byte-for-byte before/after the read-only matrix |
| Inspect | evaluated-clean | All 27 FBXs admitted and resolved the Mixamo profile |
| Segment | not-evaluated | Vendor motion-to-file mapping unavailable |
| Root motion | partially-evaluated | Archive-level XZ declarations ran; per-file ownership and yaw remain project decisions |
| Conform | not-evaluated | No source rewrite was authorized or produced |
| Validate | partially-evaluated | Mechanical and declared checks complete; runtime/artistic gates open |
| Optimize | not-evaluated | Constant-track notes were not promoted to a rewrite |
| Export | not-evaluated | No engine-facing export ran |
| Gate/report | partially-evaluated | Source-only decision report complete; adoption gates remain |
Readiness evidence by clip set
| Role or runtime set | File-ready / clip-ready | Set-ready / rig-use | Runtime / acceptance boundary |
|---|---|---|---|
| Opaque delivered corpus | 0 errors; 6 warnings; all sources readable | Mixamo roles resolved; vendor semantics and retarget use unavailable | No current engine, visual, contact, or gameplay acceptance |
hypothesis/kinematic-run-4way | Exact members and gait phases measured; loop intent unknown | New evaluator hypothesis; controller ownership proposed | Target-engine blend, phase/contact acceptance, transition, and deformation test required |
Validation-profile status
| Validation profile | Selection | Result / next evidence |
|---|---|---|
| Marketplace intake | selected — observed-pack-capability | Exhaustive source mechanics complete; acquisition/license boundary remains |
| Blended locomotion | selected — evaluator-selected-generic-scenario | Exact static proposal and gait-phase measurements recorded; runtime blend and phase/contact acceptance required |
| Root-motion controller | selected — observed-pack-capability | 4 archive-level ownership conflicts require per-file policy |
| State-machine transitions | selected — evaluator-selected-generic-scenario | Full-body transition policy proposed; no authored/runtime transition tested |
| Layered upper body/weapons | not-selected | No mask/additive contract or layer evidence |
| Traversal/environment | not-selected | No target traversal/environment contract |
| Contact actions/interactions | not-selected | No contact target or gameplay contract |
| Retargeted/customizable characters | not-selected | No target rig or retargeter supplied |
| Motion matching/search | not-selected | No database/annotation contract supplied |
| Networked movement | not-selected | No replication/rollback contract supplied |
| Runtime performance | not-selected | No target hardware/runtime project supplied |
Pack inventory and content evidence
The fresh inventory contains 27 FBX files. Both directory variants include X Bot.fbx with a 68-bone skeleton and an empty Take 001; the 25 motion-named files use 66 bones. All sources resolved the same nine Mixamo roles. This establishes static structure, not identical hierarchy/rest pose or retarget compatibility. Constant-track findings are optimization hints only.
The current scenario is intended to be useful without claiming old authority: hypothesis/kinematic-run-4way names 4 exact in-place-directory files, records current durations/frame counts, assigns movement to a hypothetical kinematic controller, and leaves loop intent, gait-phase acceptance, transitions, and runtime acceptance open. AnimSmith measured phase (cycles) as forward 0.4370, back 0.6134, left 0.4670, and right 0.4208; the normalized circular-range spread is 0.1926 cycles, defined as 1 - largest cyclic gap across member phases on the unit cycle. These are observed-animsmith measurements; sync=not-evaluated, and no defect, contact acceptance, or synchronization acceptance follows from the spread alone.
Mechanical baseline
| Finding/check | Affected scope | Potential impact | Evidence |
|---|---|---|---|
| No baseline errors | 27/27 files | File-readable mechanical prerequisite only | observed-animsmith; current ledger |
duration-sanity | 6 findings in 6 files | Empty reference take or shorter channels clamp-held at clip end | Magic_Locomotion_Pack_-_in-place/X Bot.fbx; Magic_Locomotion_Pack_-_root-motion/Standing Jump.fbx; Magic_Locomotion_Pack_-_root-motion/Standing Land To Standing Idle.fbx; Magic_Locomotion_Pack_-_root-motion/Standing Turn Left 90.fbx; Magic_Locomotion_Pack_-_root-motion/X Bot.fbx; Magic_Locomotion_Pack_-_root-motion/standing idle.fbx |
constant-track | 3837 notes | Possible storage/evaluation overhead; no visual defect or safe rewrite inferred | observed-animsmith |
| Mixamo role resolution | 27/27 files | Enables measurement; does not prove target-character retargeting | observed-animsmith |
AnimSmith remediation evidence
| Source issue | Operation/declarations | Result | Independent verification | Remaining caveat |
|---|---|---|---|---|
| MIX-OWN-001 | Archive-level in-place and animation-owned XZ declarations | 4 stationary-root errors on Magic_Locomotion_Pack_-_root-motion/Standing Jump.fbx; Magic_Locomotion_Pack_-_root-motion/Standing Turn Left 90.fbx; Magic_Locomotion_Pack_-_root-motion/Standing Turn Right 90.fbx; Magic_Locomotion_Pack_-_root-motion/standing idle.fbx | Current per-file lint and measurement; no output produced | Per-file intent and controller acceptance unavailable; residual unresolved |
| MIX-TIME-001 | No transform run | Candidate not produced | Current baseline identifies exact channel-end ranges | Artist intent and visual boundary acceptance unavailable; residual unresolved |
| MIX-CONTENT-001 | No transform run | Candidate not produced | Both exact X Bot.fbx files retain 68 bones and an empty take | Reference-vs-animation intent unavailable; residual unresolved |
No command availability, configuration, or generated hypothesis is reported as a source repair.
Engine procedures and evidence
| Runtime | Version | Procedure | Observed result | Remaining gate |
|---|---|---|---|---|
| Unity | unspecified | Not run | not-evaluated | Exact import/controller/blend/visual test |
| Unreal Engine | unspecified | Not run | not-evaluated | Exact import/retarget/Blend Space test |
| Godot | unspecified | Not run | not-evaluated | Exact Skeleton3D/AnimationTree test |
| Bevy | unspecified | Not run | not-evaluated | Exact target identity/graph/controller test |
Rig, masking, and compatibility evidence
| Pack/rig/set pair | Skeleton/retarget | Scale/axes | Root policy | Timing/blend | Overall evidence |
|---|---|---|---|---|---|
hypothesis/kinematic-run-4way / target character | Mixamo roles resolve; target deformation not tested | Not evaluated | Controller XZ/yaw/collision proposed | Exact durations and gait phases measured; synchronization/blend acceptance not tested | not-evaluated beyond static source evidence |
| This pack / any other Mixamo constituent | Common role labels and 66-bone count observed on motion files | Not evaluated | Per-file ownership unavailable | No pairwise transition/blend test | unknown; no cross-pack claim |
Limitations and unknowns
- Vendor mapping from 25 metadata motions to 27 files is unavailable and was not reconstructed from names.
- Loop intent, gait-phase acceptance, contacts, target character, deformation, engine graph, visual quality, root/yaw policy, and cross-pack behavior remain untested.
- Original ZIP payloads and controlling license evidence were unavailable; current hashing covers extracted source only.
Changes between AnimSmith versions
AnimSmith 0.14.0 — Fresh current-source matrix admitted 27/27 FBXs and revalidated baseline and archive-level declarations. New controller topology is explicitly evaluator-proposed and remains runtime-unaccepted.
AnimSmith 0.10.0 — Retained historical counts happen to agree; no prior semantic membership or engine claim was promoted. AnimSmith 0.7.0 evidence remains superseded.
Reproduction
Official evaluator preflight: release archive, archive SHA-256 4ecf79436f9123c779edb004050da3010b44a2b392e3031facb227d7734fc33e; exact member animsmith-v0.14.0-x86_64-unknown-linux-gnu/animsmith, binary SHA-256 c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366; tag v0.14.0, peeled commit e8321ad40be5ef6f162b31f085819c039175c3c9; working-tree state: N/A (official archive). Compiled features: fbx, report. Version and required command help plus representative FBX admission passed before evaluation. Preflight: external:animsmith-0.14.0-report-refresh/preflight.json, SHA-256 7034b5043de54e7b30064230db18605d1f96d0ea1477f7b8c56ce817cc73521a.
The current official binary SHA-256 is c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366, version 0.14.0, source revision e8321ad40be5ef6f162b31f085819c039175c3c9, dirty false, features fbx,report. Version, top-level help, and inspect, measure, and lint help all exited 0 before the matrix. Representative FBX admission and the exhaustive run then completed.
The read-only four-worker matrix ran inspect, JSON measure, empty-config JSON lint, and variant-declaration JSON lint for every source: 108 commands for this constituent and 996 collection-wide. Source inventory SHA-256 bc8543a8e1c7cf439030c07a2aead5db183d610633f637c1979dbce71a4dce66 covers 249 files / 119,754,377 bytes and matched before/after. Current ledger SHA-256 is 62b0a7b7a1bb47c817f6b005a988077a44dc8d942bb26343a5f7278a15582272; summary 4dd01ee9f1398fa200355c82206c8fd24d80d0dea1f8965aee8b03ba400cfc7c; report data a8d8e1bd3707b58e24f7d08f4ea7038c0f316254fa289e4f6439284dbcbc388f. Baseline config digests are e3b0c442... for the shared empty file or cf70223... for the explicit Mixamo baseline; in-place declaration digest acb638c0...; root-motion declaration digest 567dd18f.... Full digests, private paths, outputs, and licensed sources remain external.
Sources
- Fresh external source inventory, command ledger, current summary, and exact per-command output.
- AnimSmith game-ready clip guidance.
Animation pack evaluation: Mixamo Male Locomotion
Technical verdict: Restricted use
Evaluation completeness: partial — all 18 extracted FBX files have current mechanical evidence; vendor mapping, runtime, visual, contact, retarget, license, and cross-pack acceptance remain unavailable.
Confidence: medium
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
Detailed evidence: Evidence appendix
Technical decision
For a controller-driven (kinematic) prototype, start with a walk/standard-run speed pair using the selected members. Their source mechanics were measured, but the proposed blend has no accepted loop, foot-contact, transition, target-character, or target-engine result. Use one controller for movement and yaw; decide ownership separately for any additional root-motion-directory state.
First experiment: Import the selected in-place files on the target character, play them individually, then test their blend through speed or direction changes, starts, stops, and interrupted transitions. Review any endpoint warning on other named files before admission: retain an intentional hold after visual review, or request source repair when the intended endpoint differs. The evidence appendix retains the exact source counts, warning totals, skeleton counts, and profile results. The adoption guide compares this limited pilot with broader library and authoring routes.
Capability coverage
This is a full-body humanoid controller hypothesis for visible characters. Third-person gameplay-camera appearance and target-character deformation remain visually untested. Dedicated first-person arms/viewmodel content and close-camera suitability were not established.
Content present
| Candidate ingredients in the evaluated files | Current use boundary |
|---|---|
| Walk and standard run are named and measured. Idle, jump and turns appear by filename; their controller roles remain unclassified. | Check the named states and their transitions in the target game before treating this as a complete controller. |
Content gaps and unknowns
Other delivered motion leads, filename only: idle.fbx, jump.fbx, left turn 90.fbx, and right turn 90.fbx. These names occur in the inspected source or issue inventory, but their intended controller roles and gameplay behavior have not been classified or tested. Inspect each source and its boundaries before adding a state; see the retained source evidence. A zero canonical role count does not establish that these motions are absent.
The legacy count of 16 motions has no verified mapping to these 18 files. Classify the other named motions before adding them to a controller.
Evaluation still needed
- Loop/phase policy, target-engine graph, contact and visual review, target-character retargeting, performance and cross-pack acceptance remain open.
Runtime sets and authored motion
| Set | Controller use | Adoption decision | Exact members |
|---|---|---|---|
hypothesis/kinematic-speed | Walk/standard-run speed blend; controller owns XZ translation, yaw and collision | The selected walk/run pair has no reported mechanical finding. Prototype with controller-owned movement and project-defined speed thresholds. Four other root-motion-directory states need per-file ownership. No AnimSmith repair was tested. | Member timings, coordinates and contracts |
The selected files are grouped by name and measurement. AnimSmith measured phase (cycles) as walk 0.4841 and run 0.4040; their measured phase spread is 0.0801 cycles. These phase numbers do not establish matching foot contacts or a usable blend. Loop intent, contact alignment, and gameplay use remain not-evaluated.
Integration recipe
- Members/topology: Build a walk/run speed blend from the two linked clips. Set walk and run thresholds from your controller speeds and visible stride matching; stationary roots do not provide those thresholds. Treat other directions and root-motion alternatives as separate, untested sets.
- Timing/synchronization: Keep source timing and leave loop settings undecided. Compare normalized phase and visible foot contacts in the target engine before enabling a continuous blend.
- State ownership: These selected in-place files supply pose; one kinematic controller supplies XZ translation, yaw, and collision. Root-motion use, where animation supplies travel, requires separate per-file checks.
- Composition constraints: Begin with full-body states and transitions. Masks, additive motion, IK, sockets, and layering need separate tests.
- Acceptance gate: In the chosen engine, sweep the walk/run speed range in both directions and test rapid speed changes, starts and stops, foot contacts, interrupted transitions, and target-character deformation.
Technical issue register
| ID | Severity | Problem and impact | Primary owner | Current action | Future AnimSmith potential | Evidence/status |
|---|---|---|---|---|---|---|
| MIX-OWN-001 | moderate | Scope: Male_Locomotion_Pack_-_root-motion/idle.fbx; Male_Locomotion_Pack_-_root-motion/jump.fbx; Male_Locomotion_Pack_-_root-motion/left turn 90.fbx; Male_Locomotion_Pack_-_root-motion/right turn 90.fbx; reproduce: lint each file with the current archive-level animation-owned XZ declaration; impact: applying that policy can leave these states stationary while gameplay expects displacement. Guidance: not applicable. | engine-config | Declare these files controller-owned XZ unless project evidence establishes authored displacement. Accept when per-file declared lint has no ownership error and one target-controller playback applies translation/yaw/collision exactly once. | No generic rewrite is justified because the directory label does not establish per-file intent. | observed-animsmith; 4 current findings; no runtime acceptance; unresolved. |
| MIX-TIME-001 | moderate | Scope: Male_Locomotion_Pack_-_root-motion/idle.fbx; Male_Locomotion_Pack_-_root-motion/left turn 90.fbx; Male_Locomotion_Pack_-_root-motion/right strafe.fbx; reproduce: empty-baseline duration-sanity; impact: shorter channels clamp-hold at the end and can create a brief frozen joint or transition/loop pop. Guidance. | unknown | Inspect the intended endpoint and slowed target-engine boundary playback first. If the hold is intentional and visually acceptable, document and retain it. Otherwise ask the source author to align the shorter channels to the intended endpoint, rerun duration-sanity, and review boundary playback. Accept only when the intended hold or corrected boundary is visually approved and the remaining mechanical warning is recorded accurately. | Deterministic endpoint conformance is plausible only after the intended endpoint and protected channels are declared. | observed-animsmith; 3 current source files; visual intent not evaluated; unresolved. |
| MIX-CONTENT-001 | note | Scope: Male_Locomotion_Pack_-_in-place/X Bot.fbx; Male_Locomotion_Pack_-_root-motion/X Bot.fbx; reproduce: empty-baseline duration-sanity reports Take 001 with no tracks; impact: automatic clip registration can expose an empty state, although these may be reference-character files. Guidance: not applicable. | unknown | Classify each file as reference-only or intended animation before import. Accept when reference files are excluded from animation registration or the author supplies the intended tracks. | No tool should invent missing motion. | observed-file and observed-animsmith; semantic purpose unavailable; unresolved. |
Engine status
| Runtime | Evidence level | Technical result | Remaining gate |
|---|---|---|---|
| Unity unspecified | not-evaluated | No current import, graph, playback, or visual evidence | Test the proposed controller and full-body graph on the target character |
| Unreal Engine unspecified | not-evaluated | No current import, retarget, Blend Space, or playback evidence | Test the exact retarget and controller path |
| Godot unspecified | not-evaluated | No current import, AnimationTree, or playback evidence | Test the exact Skeleton3D and controller path |
| Bevy unspecified | not-evaluated | No current loader, graph, or playback evidence | Test exact animation targets and controller composition |
Fit and limitations
Best fit is male locomotion prototyping where a developer can own movement, define thresholds, and test the exact target character. It is a poor fit for drop-in root-motion, network, motion-matching, layered-animation, or production-ready claims.
Cross-pack compatibility is unknown. Shared Mixamo role resolution and a common 66-bone count on motion-named files do not prove identical hierarchy, rest pose, scale, retargeting, blending, contacts, or style.
Changes between AnimSmith versions
AnimSmith 0.14.0 — The current evaluation reran 996 inspect, measure, and lint commands across the collection. This constituent admitted 18/18 files; baseline and declared-contract findings were revalidated, and the selected controller set was proposed from current files. Its clip roles remain unverified.
AnimSmith 0.10.0 — Retained historical mechanical counts agree with the current rerun; clip mapping and engine acceptance remain open. AnimSmith 0.7.0 evidence remains superseded.
Evidence status
The current tests covered 18 FBX source files individually. The legacy metadata lists 16 motions, but its clip-to-file mapping is unverified for the 18 files. The evidence appendix records the source integrity check, evaluator revision, command results, and digests. See the canonical readiness ladder.
Sources
- Fresh external AnimSmith 0.14.0 command ledger and summary; licensed source and outputs remain external.
- AnimSmith game-ready clip guidance.
Animation pack evidence appendix: Mixamo Male Locomotion
Companion report: Technical report
Evidence status: partial — exhaustive current source mechanics and archive-level declarations; no current engine, visual, contact, retarget, license, or artistic acceptance.
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
This appendix contains scrubbed facts only. The canonical readiness ladder remains authoritative. The evaluation manifest schema is urn:animsmith:skill:animation-pack-evaluation-manifest:1; it is retained as opaque historical classification input, not current source-to-vendor mapping authority.
Evaluation scope and provenance
| Field | Value |
|---|---|
| Pack/edition | Mixamo Male Locomotion; local revision unknown |
| Vendor/source | Vendor identity observed in delivered metadata; listing URL not retained |
| Delivered scope | 18 extracted FBX files: 7 in-place-directory and 11 root-motion-directory files |
| Target use | Engine-neutral intake plus a new evaluator-proposed kinematic-controller scenario |
| Target engines | Not evaluated |
| Target rigs/packs | Mixamo profile resolved; no target character or cross-pack runtime supplied |
| Source manifest | Fresh 249-file collection inventory SHA-256 bc8543a8e1c7cf439030c07a2aead5db183d610633f637c1979dbce71a4dce66; identical before/after evaluation |
| Evaluation manifest | mixamo-male-locomotion.manifest.json; SHA-256 bebd0e4f450133c8a00b9d2c8bb9cb996a56a8ab43cfc7f2d8b4c07c44a8f288; legacy schema urn:animsmith:skill:animation-pack-evaluation-manifest:1 |
| Acquisition/license provenance | Extracted files are locally authorized; original ZIPs and controlling license evidence were unavailable; not legal advice |
The legacy metadata count of 16 motions is not linked authoritatively to these 18 files. Current conclusions use exact source paths and fresh measurements only.
Evidence coverage
| Surface | Offered/delivered | Evaluated | Findings | Not evaluated and why |
|---|---|---|---|---|
| Animation/source files | 18 | 18 | 0 baseline errors; 5 warnings | Vendor motion mapping unavailable |
| Rigs/export variants | 2 skeleton counts | 18 | two 68-bone X Bot.fbx; 16 66-bone files; Mixamo roles resolved | Retarget/deformation unavailable |
| AnimSmith baseline | 18 | 18 | 5 warnings; 2564 notes | — |
| Declared contracts | 18 | 18 | 4 ownership errors; 5 warnings | Per-file project intent unavailable |
| Evaluator scenario | 1 | 1 static proposal | 2 members with measured gait phase | Loop, synchronization, contact, engine, and visual acceptance unavailable |
| Engine import/playback | 4 common runtimes | 0 | 0 | No current engine project ran |
| Blend/mask/retarget | 1 proposed set | 0 runtime tests | 0 | Target graph/character unavailable |
Claim legend
Consequential claims are observed-file, observed-animsmith, inferred, or not-evaluated under the assessment taxonomy.
Evaluation manifest and taxonomy
Canonical clip-role inventory
| Canonical primary role | Logical motions | Delivered files | Evidence boundary |
|---|---|---|---|
idle-pose | 0 | 0 | Not authoritatively classified |
continuous-locomotion | 0 | 0 | Filename-based hypotheses stay outside the canonical role count |
locomotion-transition | 0 | 0 | Not authoritatively classified |
airborne | 0 | 0 | Not authoritatively classified |
traversal | 0 | 0 | Not authoritatively classified |
action-interaction | 0 | 0 | Not authoritatively classified |
reaction-death | 0 | 0 | Not authoritatively classified |
emote-cinematic | 0 | 0 | Not authoritatively classified |
other-unknown | 18 | 18 | One opaque current source unit per delivered FBX |
| Total | 18 | 18 | Current exact source paths; no vendor mapping inferred |
Exact runtime members
| Set/profile | Role or coordinate | Exact members | Variant/type | Timing or motion | Runtime contract |
|---|---|---|---|---|---|
hypothesis/kinematic-speed | walk | Male_Locomotion_Pack_-_in-place/walking.fbx | variant=in-place | duration=1.033 s; frames=32 frames | loop=unknown; sync=not-evaluated; movement=controller-xz-yaw; playback=project-defined |
hypothesis/kinematic-speed | run | Male_Locomotion_Pack_-_in-place/standard run.fbx | variant=in-place | duration=0.733 s; frames=23 frames | loop=unknown; sync=not-evaluated; movement=controller-xz-yaw; playback=project-defined |
The table above preserves the measured member detail from the companion report; its proposed controller still requires target-engine acceptance.
Runtime-set inventory
| Runtime set | Type | Members/variants | Grouping evidence | Validation status |
|---|---|---|---|---|
hypothesis/kinematic-speed | speed-blend | Male_Locomotion_Pack_-_in-place/walking.fbx; Male_Locomotion_Pack_-_in-place/standard run.fbx; all in-place | new evaluator hypothesis from current source names; no vendor/runtime authority; exact durations and frames are in the primary table | not-evaluated runtime acceptance |
Pipeline-stage coverage
| Stage | Coverage state | Evidence / remaining gate |
|---|---|---|
| Acquire | partially-evaluated | Extracted source present; original ZIP/license unavailable |
| Preserve raw | evaluated-clean | Fresh inventory matched byte-for-byte before/after the read-only matrix |
| Inspect | evaluated-clean | All 18 FBXs admitted and resolved the Mixamo profile |
| Segment | not-evaluated | Vendor motion-to-file mapping unavailable |
| Root motion | partially-evaluated | Archive-level XZ declarations ran; per-file ownership and yaw remain project decisions |
| Conform | not-evaluated | No source rewrite was authorized or produced |
| Validate | partially-evaluated | Mechanical and declared checks complete; runtime/artistic gates open |
| Optimize | not-evaluated | Constant-track notes were not promoted to a rewrite |
| Export | not-evaluated | No engine-facing export ran |
| Gate/report | partially-evaluated | Source-only decision report complete; adoption gates remain |
Readiness evidence by clip set
| Role or runtime set | File-ready / clip-ready | Set-ready / rig-use | Runtime / acceptance boundary |
|---|---|---|---|
| Opaque delivered corpus | 0 errors; 5 warnings; all sources readable | Mixamo roles resolved; vendor semantics and retarget use unavailable | No current engine, visual, contact, or gameplay acceptance |
hypothesis/kinematic-speed | Exact members and gait phases measured; loop intent unknown | New evaluator hypothesis; controller ownership proposed | Target-engine blend, phase/contact acceptance, transition, and deformation test required |
Validation-profile status
| Validation profile | Selection | Result / next evidence |
|---|---|---|
| Marketplace intake | selected — observed-pack-capability | Exhaustive source mechanics complete; acquisition/license boundary remains |
| Blended locomotion | selected — evaluator-selected-generic-scenario | Exact static proposal and gait-phase measurements recorded; runtime blend and phase/contact acceptance required |
| Root-motion controller | selected — observed-pack-capability | 4 archive-level ownership conflicts require per-file policy |
| State-machine transitions | selected — evaluator-selected-generic-scenario | Full-body transition policy proposed; no authored/runtime transition tested |
| Layered upper body/weapons | not-selected | No mask/additive contract or layer evidence |
| Traversal/environment | not-selected | No target traversal/environment contract |
| Contact actions/interactions | not-selected | No contact target or gameplay contract |
| Retargeted/customizable characters | not-selected | No target rig or retargeter supplied |
| Motion matching/search | not-selected | No database/annotation contract supplied |
| Networked movement | not-selected | No replication/rollback contract supplied |
| Runtime performance | not-selected | No target hardware/runtime project supplied |
Pack inventory and content evidence
The fresh inventory contains 18 FBX files. Both directory variants include X Bot.fbx with a 68-bone skeleton and an empty Take 001; the 16 motion-named files use 66 bones. All sources resolved the same nine Mixamo roles. This establishes static structure, not identical hierarchy/rest pose or retarget compatibility. Constant-track findings are optimization hints only.
The current scenario is intended to be useful without claiming old authority: hypothesis/kinematic-speed names 2 exact in-place-directory files, records current durations/frame counts, assigns movement to a hypothetical kinematic controller, and leaves loop intent, gait-phase acceptance, transitions, and runtime acceptance open. AnimSmith measured phase (cycles) as walk 0.4841 and run 0.4040; the normalized circular-range spread is 0.0801 cycles, defined as 1 - largest cyclic gap across member phases on the unit cycle. These are observed-animsmith measurements; sync=not-evaluated, and no contact or synchronization acceptance follows from the spread.
Mechanical baseline
| Finding/check | Affected scope | Potential impact | Evidence |
|---|---|---|---|
| No baseline errors | 18/18 files | File-readable mechanical prerequisite only | observed-animsmith; current ledger |
duration-sanity | 5 findings in 5 files | Empty reference take or shorter channels clamp-held at clip end | Male_Locomotion_Pack_-_in-place/X Bot.fbx; Male_Locomotion_Pack_-_root-motion/X Bot.fbx; Male_Locomotion_Pack_-_root-motion/idle.fbx; Male_Locomotion_Pack_-_root-motion/left turn 90.fbx; Male_Locomotion_Pack_-_root-motion/right strafe.fbx |
constant-track | 2564 notes | Possible storage/evaluation overhead; no visual defect or safe rewrite inferred | observed-animsmith |
| Mixamo role resolution | 18/18 files | Enables measurement; does not prove target-character retargeting | observed-animsmith |
AnimSmith remediation evidence
| Source issue | Operation/declarations | Result | Independent verification | Remaining caveat |
|---|---|---|---|---|
| MIX-OWN-001 | Archive-level in-place and animation-owned XZ declarations | 4 stationary-root errors on Male_Locomotion_Pack_-_root-motion/idle.fbx; Male_Locomotion_Pack_-_root-motion/jump.fbx; Male_Locomotion_Pack_-_root-motion/left turn 90.fbx; Male_Locomotion_Pack_-_root-motion/right turn 90.fbx | Current per-file lint and measurement; no output produced | Per-file intent and controller acceptance unavailable; residual unresolved |
| MIX-TIME-001 | No transform run | Candidate not produced | Current baseline identifies exact channel-end ranges | Artist intent and visual boundary acceptance unavailable; residual unresolved |
| MIX-CONTENT-001 | No transform run | Candidate not produced | Both exact X Bot.fbx files retain 68 bones and an empty take | Reference-vs-animation intent unavailable; residual unresolved |
No command availability, configuration, or generated hypothesis is reported as a source repair.
Engine procedures and evidence
| Runtime | Version | Procedure | Observed result | Remaining gate |
|---|---|---|---|---|
| Unity | unspecified | Not run | not-evaluated | Exact import/controller/blend/visual test |
| Unreal Engine | unspecified | Not run | not-evaluated | Exact import/retarget/Blend Space test |
| Godot | unspecified | Not run | not-evaluated | Exact Skeleton3D/AnimationTree test |
| Bevy | unspecified | Not run | not-evaluated | Exact target identity/graph/controller test |
Rig, masking, and compatibility evidence
| Pack/rig/set pair | Skeleton/retarget | Scale/axes | Root policy | Timing/blend | Overall evidence |
|---|---|---|---|---|---|
hypothesis/kinematic-speed / target character | Mixamo roles resolve; target deformation not tested | Not evaluated | Controller XZ/yaw/collision proposed | Exact durations and gait phases measured; synchronization/blend acceptance not tested | not-evaluated beyond static source evidence |
| This pack / any other Mixamo constituent | Common role labels and 66-bone count observed on motion files | Not evaluated | Per-file ownership unavailable | No pairwise transition/blend test | unknown; no cross-pack claim |
Limitations and unknowns
- Vendor mapping from 16 metadata motions to 18 files is unavailable and was not reconstructed from names.
- Loop intent, gait-phase acceptance, contacts, target character, deformation, engine graph, visual quality, root/yaw policy, and cross-pack behavior remain untested.
- Original ZIP payloads and controlling license evidence were unavailable; current hashing covers extracted source only.
Changes between AnimSmith versions
AnimSmith 0.14.0 — Fresh current-source matrix admitted 18/18 FBXs and revalidated baseline and archive-level declarations. New controller topology is explicitly evaluator-proposed and remains runtime-unaccepted.
AnimSmith 0.10.0 — Retained historical counts happen to agree; no prior semantic membership or engine claim was promoted. AnimSmith 0.7.0 evidence remains superseded.
Reproduction
Official evaluator preflight: release archive, archive SHA-256 4ecf79436f9123c779edb004050da3010b44a2b392e3031facb227d7734fc33e; exact member animsmith-v0.14.0-x86_64-unknown-linux-gnu/animsmith, binary SHA-256 c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366; tag v0.14.0, peeled commit e8321ad40be5ef6f162b31f085819c039175c3c9; working-tree state: N/A (official archive). Compiled features: fbx, report. Version and required command help plus representative FBX admission passed before evaluation. Preflight: external:animsmith-0.14.0-report-refresh/preflight.json, SHA-256 7034b5043de54e7b30064230db18605d1f96d0ea1477f7b8c56ce817cc73521a.
The current official binary SHA-256 is c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366, version 0.14.0, source revision e8321ad40be5ef6f162b31f085819c039175c3c9, dirty false, features fbx,report. Version, top-level help, and inspect, measure, and lint help all exited 0 before the matrix. Representative FBX admission and the exhaustive run then completed.
The read-only four-worker matrix ran inspect, JSON measure, empty-config JSON lint, and variant-declaration JSON lint for every source: 72 commands for this constituent and 996 collection-wide. Source inventory SHA-256 bc8543a8e1c7cf439030c07a2aead5db183d610633f637c1979dbce71a4dce66 covers 249 files / 119,754,377 bytes and matched before/after. Current ledger SHA-256 is 62b0a7b7a1bb47c817f6b005a988077a44dc8d942bb26343a5f7278a15582272; summary 4dd01ee9f1398fa200355c82206c8fd24d80d0dea1f8965aee8b03ba400cfc7c; report data a8d8e1bd3707b58e24f7d08f4ea7038c0f316254fa289e4f6439284dbcbc388f. Baseline config digests are e3b0c442... for the shared empty file or cf70223... for the explicit Mixamo baseline; in-place declaration digest acb638c0...; root-motion declaration digest 567dd18f.... Full digests, private paths, outputs, and licensed sources remain external.
Sources
- Fresh external source inventory, command ledger, current summary, and exact per-command output.
- AnimSmith game-ready clip guidance.
Animation pack evaluation: Mixamo Pistol-Handgun Locomotion
Technical verdict: Restricted use
Evaluation completeness: partial — all 29 extracted FBX files have current mechanical evidence; vendor mapping, runtime, visual, contact, retarget, license, and cross-pack acceptance remain unavailable.
Confidence: medium
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
Detailed evidence: Evidence appendix
Technical decision
For a controller-driven (kinematic) prototype, start with forward/back full-body pistol running using the selected members. Their source mechanics were measured, but the proposed blend has no accepted loop, foot-contact, transition, target-character, or target-engine result. Use one controller for movement and yaw; decide ownership separately for any additional root-motion-directory state.
First experiment: Import the selected in-place files on the target character, play them individually, then test their blend through speed or direction changes, starts, stops, and interrupted transitions. Review any endpoint warning on other named files before admission: retain an intentional hold after visual review, or request source repair when the intended endpoint differs. The evidence appendix retains the exact source counts, warning totals, skeleton counts, and profile results. The adoption guide compares this limited pilot with broader library and authoring routes.
Capability coverage
This is a full-body humanoid controller hypothesis for visible characters. Third-person gameplay-camera appearance, target-character deformation, and close-camera grip remain visually untested. Dedicated first-person arms/viewmodel content and first-person weapon framing were not established; the pistol/rifle/bow names do not establish them.
Content present
| Candidate ingredients in the evaluated files | Current use boundary |
|---|---|
| Forward and backward pistol runs are named and measured. Pistol idle, jump and kneel/stand files appear by filename; those states are not accepted together. | Check the named states and their transitions in the target game before treating this as a complete controller. |
Content gaps and unknowns
Other delivered motion leads, filename only: pistol idle.fbx, pistol jump.fbx, pistol kneel to stand.fbx, and pistol stand to kneel.fbx. These names occur in the inspected source or issue inventory, but their intended controller roles and gameplay behavior have not been classified or tested. Inspect each source and its boundaries before adding a state; see the retained source evidence. A zero canonical role count does not establish that these motions are absent.
The legacy count of 27 motions has no verified mapping to these 29 files. Classify the other named motions before adding them to a controller.
Evaluation still needed
- Loop/phase policy, target-engine graph, contact and visual review, target-character retargeting, performance and cross-pack acceptance remain open.
Runtime sets and authored motion
| Set | Controller use | Adoption decision | Exact members |
|---|---|---|---|
hypothesis/kinematic-run-axis | Forward/back full-body pistol run; controller owns XZ translation, yaw and collision | The selected run pair supports a controller-owned movement prototype. Its phase spread calls for contact review before blending. Five other root-motion-directory states need per-file ownership; pistol idle, jump and kneel/stand files have endpoint warnings. No AnimSmith repair was tested. | Member timings, coordinates and contracts |
The selected files are grouped by name and measurement. AnimSmith measured phase (cycles) as forward 0.3981 and back 0.6458; their measured phase spread is 0.2477 cycles. These phase numbers do not establish matching foot contacts or a usable blend. Loop intent, contact alignment, and gameplay use remain not-evaluated.
Integration recipe
- Members/topology: Use the exact members and coordinates for
hypothesis/kinematic-run-axisin the linked appendix as a proposed directional blend. Add other directions or in-place/root-motion alternatives only after checking their own files. - Timing/synchronization: Keep source timing and leave loop settings undecided. Compare normalized phase and visible foot contacts in the target engine before enabling a continuous blend.
- State ownership: These selected in-place files supply pose; one kinematic controller supplies XZ translation, yaw, and collision. Root-motion use, where animation supplies travel, requires separate per-file checks.
- Composition constraints: Begin with full-body states and transitions. Masks, additive motion, IK, sockets, and layering need separate tests.
- Acceptance gate: In the chosen engine, test the selected blend at center and axes, rapid input changes, starts and stops, foot contacts, interrupted transitions, and target-character deformation.
Technical issue register
| ID | Severity | Problem and impact | Primary owner | Current action | Future AnimSmith potential | Evidence/status |
|---|---|---|---|---|---|---|
| MIX-PHASE-001 | note | Scope: hypothesis/kinematic-run-axis; reproduce: compare the two measured phases, whose normalized circular-range spread is 0.2477 cycles; impact: if these phases represent incompatible support-foot events, a continuous blend can pulse or foot-skate, but no contact/runtime test establishes a defect. Guidance. | unknown | Align normalized time in the target engine and inspect support-foot contacts at center, axes, and rapid direction changes. Accept when a project-declared contact tolerance passes with no visible pulse or skate; if it fails, establish phase/contact intent before adding engine markers or requesting artist cleanup. | A deterministic alignment operation is plausible only after loop and contact semantics are declared. | observed-animsmith measurement; synchronization/contact acceptance not-evaluated; unresolved. |
| MIX-OWN-001 | moderate | Scope: Pistol-Handgun_Locomotion_Pack_-_root-motion/pistol idle.fbx; Pistol-Handgun_Locomotion_Pack_-_root-motion/pistol jump.fbx; Pistol-Handgun_Locomotion_Pack_-_root-motion/pistol kneel to stand.fbx; Pistol-Handgun_Locomotion_Pack_-_root-motion/pistol kneeling idle.fbx; Pistol-Handgun_Locomotion_Pack_-_root-motion/pistol stand to kneel.fbx; reproduce: lint each file with the current archive-level animation-owned XZ declaration; impact: applying that policy can leave these states stationary while gameplay expects displacement. Guidance: not applicable. | engine-config | Declare these files controller-owned XZ unless project evidence establishes authored displacement. Accept when per-file declared lint has no ownership error and one target-controller playback applies translation/yaw/collision exactly once. | No generic rewrite is justified because the directory label does not establish per-file intent. | observed-animsmith; 5 current findings; no runtime acceptance; unresolved. |
| MIX-TIME-001 | moderate | Scope: Pistol-Handgun_Locomotion_Pack_-_root-motion/pistol idle.fbx; Pistol-Handgun_Locomotion_Pack_-_root-motion/pistol jump.fbx; Pistol-Handgun_Locomotion_Pack_-_root-motion/pistol kneel to stand.fbx; Pistol-Handgun_Locomotion_Pack_-_root-motion/pistol kneeling idle.fbx; Pistol-Handgun_Locomotion_Pack_-_root-motion/pistol stand to kneel.fbx; reproduce: empty-baseline duration-sanity; impact: shorter channels clamp-hold at the end and can create a brief frozen joint or transition/loop pop. Guidance. | unknown | Inspect the intended endpoint and slowed target-engine boundary playback first. If the hold is intentional and visually acceptable, document and retain it. Otherwise ask the source author to align the shorter channels to the intended endpoint, rerun duration-sanity, and review boundary playback. Accept only when the intended hold or corrected boundary is visually approved and the remaining mechanical warning is recorded accurately. | Deterministic endpoint conformance is plausible only after the intended endpoint and protected channels are declared. | observed-animsmith; 5 current source files; visual intent not evaluated; unresolved. |
| MIX-CONTENT-001 | note | Scope: Pistol-Handgun_Locomotion_Pack_-_in-place/X Bot.fbx; Pistol-Handgun_Locomotion_Pack_-_root-motion/X Bot.fbx; reproduce: empty-baseline duration-sanity reports Take 001 with no tracks; impact: automatic clip registration can expose an empty state, although these may be reference-character files. Guidance: not applicable. | unknown | Classify each file as reference-only or intended animation before import. Accept when reference files are excluded from animation registration or the author supplies the intended tracks. | No tool should invent missing motion. | observed-file and observed-animsmith; semantic purpose unavailable; unresolved. |
Engine status
| Runtime | Evidence level | Technical result | Remaining gate |
|---|---|---|---|
| Unity unspecified | not-evaluated | No current import, graph, playback, or visual evidence | Test the proposed controller and full-body graph on the target character |
| Unreal Engine unspecified | not-evaluated | No current import, retarget, Blend Space, or playback evidence | Test the exact retarget and controller path |
| Godot unspecified | not-evaluated | No current import, AnimationTree, or playback evidence | Test the exact Skeleton3D and controller path |
| Bevy unspecified | not-evaluated | No current loader, graph, or playback evidence | Test exact animation targets and controller composition |
Fit and limitations
Best fit is full-body pistol locomotion prototyping where a developer can own movement, define thresholds, and test the exact target character. It is a poor fit for drop-in root-motion, network, motion-matching, layered-animation, or production-ready claims.
Cross-pack compatibility is unknown. Shared Mixamo role resolution and a common 66-bone count on motion-named files do not prove identical hierarchy, rest pose, scale, retargeting, blending, contacts, or style.
Changes between AnimSmith versions
AnimSmith 0.14.0 — The current evaluation reran 996 inspect, measure, and lint commands across the collection. This constituent admitted 29/29 files; baseline and declared-contract findings were revalidated, and the selected controller set was proposed from current files. Its clip roles remain unverified.
AnimSmith 0.10.0 — Retained historical mechanical counts agree with the current rerun; clip mapping and engine acceptance remain open. AnimSmith 0.7.0 evidence remains superseded.
Evidence status
The current tests covered 29 FBX source files individually. The legacy metadata lists 27 motions, but its clip-to-file mapping is unverified for the 29 files. The evidence appendix records the source integrity check, evaluator revision, command results, and digests. See the canonical readiness ladder.
Sources
- Fresh external AnimSmith 0.14.0 command ledger and summary; licensed source and outputs remain external.
- AnimSmith game-ready clip guidance.
Animation pack evidence appendix: Mixamo Pistol-Handgun Locomotion
Companion report: Technical report
Evidence status: partial — exhaustive current source mechanics and archive-level declarations; no current engine, visual, contact, retarget, license, or artistic acceptance.
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
This appendix contains scrubbed facts only. The canonical readiness ladder remains authoritative. The evaluation manifest schema is urn:animsmith:skill:animation-pack-evaluation-manifest:1; it is retained as opaque historical classification input, not current source-to-vendor mapping authority.
Evaluation scope and provenance
| Field | Value |
|---|---|
| Pack/edition | Mixamo Pistol-Handgun Locomotion; local revision unknown |
| Vendor/source | Vendor identity observed in delivered metadata; listing URL not retained |
| Delivered scope | 29 extracted FBX files: 8 in-place-directory and 21 root-motion-directory files |
| Target use | Engine-neutral intake plus a new evaluator-proposed kinematic-controller scenario |
| Target engines | Not evaluated |
| Target rigs/packs | Mixamo profile resolved; no target character or cross-pack runtime supplied |
| Source manifest | Fresh 249-file collection inventory SHA-256 bc8543a8e1c7cf439030c07a2aead5db183d610633f637c1979dbce71a4dce66; identical before/after evaluation |
| Evaluation manifest | mixamo-pistol-handgun-locomotion.manifest.json; SHA-256 5fbc91e885d4dbdacf25a67c26833a0c9f6c48b410555b67c5cd0843e7bbd47b; legacy schema urn:animsmith:skill:animation-pack-evaluation-manifest:1 |
| Acquisition/license provenance | Extracted files are locally authorized; original ZIPs and controlling license evidence were unavailable; not legal advice |
The legacy metadata count of 27 motions is not linked authoritatively to these 29 files. Current conclusions use exact source paths and fresh measurements only.
Evidence coverage
| Surface | Offered/delivered | Evaluated | Findings | Not evaluated and why |
|---|---|---|---|---|
| Animation/source files | 29 | 29 | 0 baseline errors; 7 warnings | Vendor motion mapping unavailable |
| Rigs/export variants | 2 skeleton counts | 29 | two 68-bone X Bot.fbx; 27 66-bone files; Mixamo roles resolved | Retarget/deformation unavailable |
| AnimSmith baseline | 29 | 29 | 7 warnings; 4122 notes | — |
| Declared contracts | 29 | 29 | 5 ownership errors; 7 warnings | Per-file project intent unavailable |
| Evaluator scenario | 1 | 1 static proposal | 2 members with measured gait phase | Loop, synchronization, contact, engine, and visual acceptance unavailable |
| Engine import/playback | 4 common runtimes | 0 | 0 | No current engine project ran |
| Blend/mask/retarget | 1 proposed set | 0 runtime tests | 0 | Target graph/character unavailable |
Claim legend
Consequential claims are observed-file, observed-animsmith, inferred, or not-evaluated under the assessment taxonomy.
Evaluation manifest and taxonomy
Canonical clip-role inventory
| Canonical primary role | Logical motions | Delivered files | Evidence boundary |
|---|---|---|---|
idle-pose | 0 | 0 | Not authoritatively classified |
continuous-locomotion | 0 | 0 | Filename-based hypotheses stay outside the canonical role count |
locomotion-transition | 0 | 0 | Not authoritatively classified |
airborne | 0 | 0 | Not authoritatively classified |
traversal | 0 | 0 | Not authoritatively classified |
action-interaction | 0 | 0 | Not authoritatively classified |
reaction-death | 0 | 0 | Not authoritatively classified |
emote-cinematic | 0 | 0 | Not authoritatively classified |
other-unknown | 29 | 29 | One opaque current source unit per delivered FBX |
| Total | 29 | 29 | Current exact source paths; no vendor mapping inferred |
Exact runtime members
| Set/profile | Role or coordinate | Exact members | Variant/type | Timing or motion | Runtime contract |
|---|---|---|---|---|---|
hypothesis/kinematic-run-axis | forward=(0,1) | Pistol-Handgun_Locomotion_Pack_-_in-place/pistol run.fbx | variant=in-place | duration=0.500 s; frames=16 frames | loop=unknown; sync=not-evaluated; movement=controller-xz-yaw; playback=project-defined |
hypothesis/kinematic-run-axis | back=(0,-1) | Pistol-Handgun_Locomotion_Pack_-_in-place/pistol run backward.fbx | variant=in-place | duration=0.533 s; frames=17 frames | loop=unknown; sync=not-evaluated; movement=controller-xz-yaw; playback=project-defined |
The table above preserves the measured member detail from the companion report; its proposed controller still requires target-engine acceptance.
Runtime-set inventory
| Runtime set | Type | Members/variants | Grouping evidence | Validation status |
|---|---|---|---|---|
hypothesis/kinematic-run-axis | directional-blend | Pistol-Handgun_Locomotion_Pack_-_in-place/pistol run.fbx; Pistol-Handgun_Locomotion_Pack_-_in-place/pistol run backward.fbx; all in-place | new evaluator hypothesis from current source names; lateral semantics remain unavailable; exact durations and frames are in the primary table | not-evaluated runtime acceptance |
Pipeline-stage coverage
| Stage | Coverage state | Evidence / remaining gate |
|---|---|---|
| Acquire | partially-evaluated | Extracted source present; original ZIP/license unavailable |
| Preserve raw | evaluated-clean | Fresh inventory matched byte-for-byte before/after the read-only matrix |
| Inspect | evaluated-clean | All 29 FBXs admitted and resolved the Mixamo profile |
| Segment | not-evaluated | Vendor motion-to-file mapping unavailable |
| Root motion | partially-evaluated | Archive-level XZ declarations ran; per-file ownership and yaw remain project decisions |
| Conform | not-evaluated | No source rewrite was authorized or produced |
| Validate | partially-evaluated | Mechanical and declared checks complete; runtime/artistic gates open |
| Optimize | not-evaluated | Constant-track notes were not promoted to a rewrite |
| Export | not-evaluated | No engine-facing export ran |
| Gate/report | partially-evaluated | Source-only decision report complete; adoption gates remain |
Readiness evidence by clip set
| Role or runtime set | File-ready / clip-ready | Set-ready / rig-use | Runtime / acceptance boundary |
|---|---|---|---|
| Opaque delivered corpus | 0 errors; 7 warnings; all sources readable | Mixamo roles resolved; vendor semantics and retarget use unavailable | No current engine, visual, contact, or gameplay acceptance |
hypothesis/kinematic-run-axis | Exact members and gait phases measured; loop intent unknown | New evaluator hypothesis; controller ownership proposed | Target-engine blend, phase/contact acceptance, transition, and deformation test required |
Validation-profile status
| Validation profile | Selection | Result / next evidence |
|---|---|---|
| Marketplace intake | selected — observed-pack-capability | Exhaustive source mechanics complete; acquisition/license boundary remains |
| Blended locomotion | selected — evaluator-selected-generic-scenario | Exact static proposal and gait-phase measurements recorded; runtime blend and phase/contact acceptance required |
| Root-motion controller | selected — observed-pack-capability | 5 archive-level ownership conflicts require per-file policy |
| State-machine transitions | selected — evaluator-selected-generic-scenario | Full-body transition policy proposed; no authored/runtime transition tested |
| Layered upper body/weapons | not-selected | No mask/additive contract or layer evidence |
| Traversal/environment | not-selected | No target traversal/environment contract |
| Contact actions/interactions | not-selected | No contact target or gameplay contract |
| Retargeted/customizable characters | not-selected | No target rig or retargeter supplied |
| Motion matching/search | not-selected | No database/annotation contract supplied |
| Networked movement | not-selected | No replication/rollback contract supplied |
| Runtime performance | not-selected | No target hardware/runtime project supplied |
Pack inventory and content evidence
The fresh inventory contains 29 FBX files. Both directory variants include X Bot.fbx with a 68-bone skeleton and an empty Take 001; the 27 motion-named files use 66 bones. All sources resolved the same nine Mixamo roles. This establishes static structure, not identical hierarchy/rest pose or retarget compatibility. Constant-track findings are optimization hints only.
The current scenario is intended to be useful without claiming old authority: hypothesis/kinematic-run-axis names 2 exact in-place-directory files, records current durations/frame counts, assigns movement to a hypothetical kinematic controller, and leaves loop intent, gait-phase acceptance, transitions, and runtime acceptance open. AnimSmith measured phase (cycles) as forward 0.3981 and back 0.6458; the normalized circular-range spread is 0.2477 cycles, defined as 1 - largest cyclic gap across member phases on the unit cycle. These are observed-animsmith measurements; sync=not-evaluated, and no defect, contact acceptance, or synchronization acceptance follows from the spread alone.
Mechanical baseline
| Finding/check | Affected scope | Potential impact | Evidence |
|---|---|---|---|
| No baseline errors | 29/29 files | File-readable mechanical prerequisite only | observed-animsmith; current ledger |
duration-sanity | 7 findings in 7 files | Empty reference take or shorter channels clamp-held at clip end | Pistol-Handgun_Locomotion_Pack_-_in-place/X Bot.fbx; Pistol-Handgun_Locomotion_Pack_-_root-motion/X Bot.fbx; Pistol-Handgun_Locomotion_Pack_-_root-motion/pistol idle.fbx; Pistol-Handgun_Locomotion_Pack_-_root-motion/pistol jump.fbx; Pistol-Handgun_Locomotion_Pack_-_root-motion/pistol kneel to stand.fbx; Pistol-Handgun_Locomotion_Pack_-_root-motion/pistol kneeling idle.fbx; Pistol-Handgun_Locomotion_Pack_-_root-motion/pistol stand to kneel.fbx |
constant-track | 4122 notes | Possible storage/evaluation overhead; no visual defect or safe rewrite inferred | observed-animsmith |
| Mixamo role resolution | 29/29 files | Enables measurement; does not prove target-character retargeting | observed-animsmith |
AnimSmith remediation evidence
| Source issue | Operation/declarations | Result | Independent verification | Remaining caveat |
|---|---|---|---|---|
| MIX-OWN-001 | Archive-level in-place and animation-owned XZ declarations | 5 stationary-root errors on Pistol-Handgun_Locomotion_Pack_-_root-motion/pistol idle.fbx; Pistol-Handgun_Locomotion_Pack_-_root-motion/pistol jump.fbx; Pistol-Handgun_Locomotion_Pack_-_root-motion/pistol kneel to stand.fbx; Pistol-Handgun_Locomotion_Pack_-_root-motion/pistol kneeling idle.fbx; Pistol-Handgun_Locomotion_Pack_-_root-motion/pistol stand to kneel.fbx | Current per-file lint and measurement; no output produced | Per-file intent and controller acceptance unavailable; residual unresolved |
| MIX-TIME-001 | No transform run | Candidate not produced | Current baseline identifies exact channel-end ranges | Artist intent and visual boundary acceptance unavailable; residual unresolved |
| MIX-CONTENT-001 | No transform run | Candidate not produced | Both exact X Bot.fbx files retain 68 bones and an empty take | Reference-vs-animation intent unavailable; residual unresolved |
No command availability, configuration, or generated hypothesis is reported as a source repair.
Engine procedures and evidence
| Runtime | Version | Procedure | Observed result | Remaining gate |
|---|---|---|---|---|
| Unity | unspecified | Not run | not-evaluated | Exact import/controller/blend/visual test |
| Unreal Engine | unspecified | Not run | not-evaluated | Exact import/retarget/Blend Space test |
| Godot | unspecified | Not run | not-evaluated | Exact Skeleton3D/AnimationTree test |
| Bevy | unspecified | Not run | not-evaluated | Exact target identity/graph/controller test |
Rig, masking, and compatibility evidence
| Pack/rig/set pair | Skeleton/retarget | Scale/axes | Root policy | Timing/blend | Overall evidence |
|---|---|---|---|---|---|
hypothesis/kinematic-run-axis / target character | Mixamo roles resolve; target deformation not tested | Not evaluated | Controller XZ/yaw/collision proposed | Exact durations and gait phases measured; synchronization/blend acceptance not tested | not-evaluated beyond static source evidence |
| This pack / any other Mixamo constituent | Common role labels and 66-bone count observed on motion files | Not evaluated | Per-file ownership unavailable | No pairwise transition/blend test | unknown; no cross-pack claim |
Limitations and unknowns
- Vendor mapping from 27 metadata motions to 29 files is unavailable and was not reconstructed from names.
- Loop intent, gait-phase acceptance, contacts, target character, deformation, engine graph, visual quality, root/yaw policy, and cross-pack behavior remain untested.
- Original ZIP payloads and controlling license evidence were unavailable; current hashing covers extracted source only.
Changes between AnimSmith versions
AnimSmith 0.14.0 — Fresh current-source matrix admitted 29/29 FBXs and revalidated baseline and archive-level declarations. New controller topology is explicitly evaluator-proposed and remains runtime-unaccepted.
AnimSmith 0.10.0 — Retained historical counts happen to agree; no prior semantic membership or engine claim was promoted. AnimSmith 0.7.0 evidence remains superseded.
Reproduction
Official evaluator preflight: release archive, archive SHA-256 4ecf79436f9123c779edb004050da3010b44a2b392e3031facb227d7734fc33e; exact member animsmith-v0.14.0-x86_64-unknown-linux-gnu/animsmith, binary SHA-256 c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366; tag v0.14.0, peeled commit e8321ad40be5ef6f162b31f085819c039175c3c9; working-tree state: N/A (official archive). Compiled features: fbx, report. Version and required command help plus representative FBX admission passed before evaluation. Preflight: external:animsmith-0.14.0-report-refresh/preflight.json, SHA-256 7034b5043de54e7b30064230db18605d1f96d0ea1477f7b8c56ce817cc73521a.
The current official binary SHA-256 is c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366, version 0.14.0, source revision e8321ad40be5ef6f162b31f085819c039175c3c9, dirty false, features fbx,report. Version, top-level help, and inspect, measure, and lint help all exited 0 before the matrix. Representative FBX admission and the exhaustive run then completed.
The read-only four-worker matrix ran inspect, JSON measure, empty-config JSON lint, and variant-declaration JSON lint for every source: 116 commands for this constituent and 996 collection-wide. Source inventory SHA-256 bc8543a8e1c7cf439030c07a2aead5db183d610633f637c1979dbce71a4dce66 covers 249 files / 119,754,377 bytes and matched before/after. Current ledger SHA-256 is 62b0a7b7a1bb47c817f6b005a988077a44dc8d942bb26343a5f7278a15582272; summary 4dd01ee9f1398fa200355c82206c8fd24d80d0dea1f8965aee8b03ba400cfc7c; report data a8d8e1bd3707b58e24f7d08f4ea7038c0f316254fa289e4f6439284dbcbc388f. Baseline config digests are e3b0c442... for the shared empty file or cf70223... for the explicit Mixamo baseline; in-place declaration digest acb638c0...; root-motion declaration digest 567dd18f.... Full digests, private paths, outputs, and licensed sources remain external.
Sources
- Fresh external source inventory, command ledger, current summary, and exact per-command output.
- AnimSmith game-ready clip guidance.
Animation pack evaluation: Mixamo Rifle 8-Way Locomotion
Technical verdict: Restricted use
Evaluation completeness: partial — all 83 extracted FBX files have current mechanical evidence; vendor mapping, runtime, visual, contact, retarget, license, and cross-pack acceptance remain unavailable.
Confidence: medium
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
Detailed evidence: Evidence appendix
Technical decision
For a controller-driven (kinematic) prototype, start with eight-direction full-body rifle running using the selected members. Their source mechanics were measured, but the proposed blend has no accepted loop, foot-contact, transition, target-character, or target-engine result. Use one controller for movement and yaw; decide ownership separately for any additional root-motion-directory state.
First experiment: Import the selected in-place files on the target character, play them individually, then test their blend through speed or direction changes, starts, stops, and interrupted transitions. Review any endpoint warning on other named files before admission: retain an intentional hold after visual review, or request source repair when the intended endpoint differs. The evidence appendix retains the exact source counts, warning totals, skeleton counts, and profile results. The adoption guide compares this limited pilot with broader library and authoring routes.
Capability coverage
This is a full-body humanoid controller hypothesis for visible characters. Third-person gameplay-camera appearance, target-character deformation, and close-camera grip remain visually untested. Dedicated first-person arms/viewmodel content and first-person weapon framing were not established; the pistol/rifle/bow names do not establish them.
Content present
| Candidate ingredients in the evaluated files | Current use boundary |
|---|---|
| Eight run directions are named and measured. Aiming, crouching, jump and death files appear by filename; their shooter-controller integration is untested. | Check the named states and their transitions in the target game before treating this as a complete controller. |
Content gaps and unknowns
Other delivered motion leads, filename only: idle crouching aiming.fbx, jump loop.fbx, walk crouching left.fbx, and death from front headshot.fbx. These names occur in the inspected source or issue inventory, but their intended controller roles and gameplay behavior have not been classified or tested. Inspect each source and its boundaries before adding a state; see the retained source evidence. A zero canonical role count does not establish that these motions are absent.
The legacy count of 81 motions has no verified mapping to these 83 files. Classify the other named motions before adding them to a controller.
Evaluation still needed
- Loop/phase policy, target-engine graph, contact and visual review, target-character retargeting, performance and cross-pack acceptance remain open.
Runtime sets and authored motion
| Set | Controller use | Adoption decision | Exact members |
|---|---|---|---|
hypothesis/kinematic-run-8way | Eight-direction full-body rifle run; controller owns XZ translation, yaw and collision | The selected eight run files support a controller-owned movement prototype. Their phase spread needs contact review before blending. Sixteen other root-motion-directory states need per-file ownership; walk/crouch, death, aiming-idle and jump files have endpoint warnings. No AnimSmith repair was tested. | Member timings, coordinates and contracts |
The selected files are grouped by name and measurement. AnimSmith measured phase (cycles) as forward 0.4720, forward-left 0.4908, left 0.5860, back-left 0.5316, back 0.5311, back-right 0.4792, right 0.4128, and forward-right 0.4446; their measured phase spread is 0.1731 cycles. These phase numbers do not establish matching foot contacts or a usable blend. Loop intent, contact alignment, and gameplay use remain not-evaluated.
Integration recipe
- Members/topology: Use the exact members and coordinates for
hypothesis/kinematic-run-8wayin the linked appendix as a proposed directional blend. Add other directions or in-place/root-motion alternatives only after checking their own files. - Timing/synchronization: Keep source timing and leave loop settings undecided. Compare normalized phase and visible foot contacts in the target engine before enabling a continuous blend.
- State ownership: These selected in-place files supply pose; one kinematic controller supplies XZ translation, yaw, and collision. Root-motion use, where animation supplies travel, requires separate per-file checks.
- Composition constraints: Begin with full-body states and transitions. Masks, additive motion, IK, sockets, and layering need separate tests.
- Acceptance gate: In the chosen engine, test the selected blend at center and axes, rapid input changes, starts and stops, foot contacts, interrupted transitions, and target-character deformation.
Technical issue register
| ID | Severity | Problem and impact | Primary owner | Current action | Future AnimSmith potential | Evidence/status |
|---|---|---|---|---|---|---|
| MIX-PHASE-001 | note | Scope: hypothesis/kinematic-run-8way; reproduce: compare the eight measured phases, whose normalized circular-range spread is 0.1731 cycles; impact: if these phases represent incompatible support-foot events, a continuous blend can pulse or foot-skate, but no contact/runtime test establishes a defect. Guidance. | unknown | Align normalized time in the target engine and inspect support-foot contacts at center, axes, diagonals, and rapid direction changes. Accept when a project-declared contact tolerance passes with no visible pulse or skate; if it fails, establish phase/contact intent before adding engine markers or requesting artist cleanup. | A deterministic alignment operation is plausible only after loop and contact semantics are declared. | observed-animsmith measurement; synchronization/contact acceptance not-evaluated; unresolved. |
| MIX-OWN-001 | moderate | Scope: the 16 root-motion-directory files listed by this issue in the appendix; reproduce: lint each file with the current archive-level animation-owned XZ declaration; impact: applying that policy can leave these states stationary while gameplay expects displacement. Guidance: not applicable. | engine-config | Declare these files controller-owned XZ unless project evidence establishes authored displacement. Accept when per-file declared lint has no ownership error and one target-controller playback applies translation/yaw/collision exactly once. | No generic rewrite is justified because the directory label does not establish per-file intent. | observed-animsmith; 16 current findings; no runtime acceptance; unresolved. |
| MIX-TIME-001 | moderate | Scope: Rifle_8-Way_Locomotion_Pack_-_in-place/walk backward.fbx; Rifle_8-Way_Locomotion_Pack_-_in-place/walk crouching left.fbx; Rifle_8-Way_Locomotion_Pack_-_root-motion/death from back headshot.fbx; Rifle_8-Way_Locomotion_Pack_-_root-motion/death from front headshot.fbx; Rifle_8-Way_Locomotion_Pack_-_root-motion/death from right.fbx; Rifle_8-Way_Locomotion_Pack_-_root-motion/idle crouching aiming.fbx; Rifle_8-Way_Locomotion_Pack_-_root-motion/jump loop.fbx; Rifle_8-Way_Locomotion_Pack_-_root-motion/walk backward.fbx; Rifle_8-Way_Locomotion_Pack_-_root-motion/walk crouching left.fbx; reproduce: empty-baseline duration-sanity; impact: shorter channels clamp-hold at the end and can create a brief frozen joint or transition/loop pop. Guidance. | unknown | Inspect the intended endpoint and slowed target-engine boundary playback first. If the hold is intentional and visually acceptable, document and retain it. Otherwise ask the source author to align the shorter channels to the intended endpoint, rerun duration-sanity, and review boundary playback. Accept only when the intended hold or corrected boundary is visually approved and the remaining mechanical warning is recorded accurately. | Deterministic endpoint conformance is plausible only after the intended endpoint and protected channels are declared. | observed-animsmith; 9 current source files; visual intent not evaluated; unresolved. |
| MIX-CONTENT-001 | note | Scope: Rifle_8-Way_Locomotion_Pack_-_in-place/X Bot.fbx; Rifle_8-Way_Locomotion_Pack_-_root-motion/X Bot.fbx; reproduce: empty-baseline duration-sanity reports Take 001 with no tracks; impact: automatic clip registration can expose an empty state, although these may be reference-character files. Guidance: not applicable. | unknown | Classify each file as reference-only or intended animation before import. Accept when reference files are excluded from animation registration or the author supplies the intended tracks. | No tool should invent missing motion. | observed-file and observed-animsmith; semantic purpose unavailable; unresolved. |
Engine status
| Runtime | Evidence level | Technical result | Remaining gate |
|---|---|---|---|
| Unity unspecified | not-evaluated | No current import, graph, playback, or visual evidence | Test the proposed controller and full-body graph on the target character |
| Unreal Engine unspecified | not-evaluated | No current import, retarget, Blend Space, or playback evidence | Test the exact retarget and controller path |
| Godot unspecified | not-evaluated | No current import, AnimationTree, or playback evidence | Test the exact Skeleton3D and controller path |
| Bevy unspecified | not-evaluated | No current loader, graph, or playback evidence | Test exact animation targets and controller composition |
Fit and limitations
Best fit is full-body rifle locomotion prototyping where a developer can own movement, define thresholds, and test the exact target character. It is a poor fit for drop-in root-motion, network, motion-matching, layered-animation, or production-ready claims.
Cross-pack compatibility is unknown. Shared Mixamo role resolution and a common 66-bone count on motion-named files do not prove identical hierarchy, rest pose, scale, retargeting, blending, contacts, or style.
Changes between AnimSmith versions
AnimSmith 0.14.0 — The current evaluation reran 996 inspect, measure, and lint commands across the collection. This constituent admitted 83/83 files; baseline and declared-contract findings were revalidated, and the selected controller set was proposed from current files. Its clip roles remain unverified.
AnimSmith 0.10.0 — Retained historical mechanical counts agree with the current rerun; clip mapping and engine acceptance remain open. AnimSmith 0.7.0 evidence remains superseded.
Evidence status
The current tests covered 83 FBX source files individually. The legacy metadata lists 81 motions, but its clip-to-file mapping is unverified for the 83 files. The evidence appendix records the source integrity check, evaluator revision, command results, and digests. See the canonical readiness ladder.
Sources
- Fresh external AnimSmith 0.14.0 command ledger and summary; licensed source and outputs remain external.
- AnimSmith game-ready clip guidance.
Animation pack evidence appendix: Mixamo Rifle 8-Way Locomotion
Companion report: Technical report
Evidence status: partial — exhaustive current source mechanics and archive-level declarations; no current engine, visual, contact, retarget, license, or artistic acceptance.
Evaluation date: 2026-09-16
Current evaluator: AnimSmith 0.14.0
Report format: 3
This appendix contains scrubbed facts only. The canonical readiness ladder remains authoritative. The evaluation manifest schema is urn:animsmith:skill:animation-pack-evaluation-manifest:1; it is retained as opaque historical classification input, not current source-to-vendor mapping authority.
Evaluation scope and provenance
| Field | Value |
|---|---|
| Pack/edition | Mixamo Rifle 8-Way Locomotion; local revision unknown |
| Vendor/source | Vendor identity observed in delivered metadata; listing URL not retained |
| Delivered scope | 83 extracted FBX files: 33 in-place-directory and 50 root-motion-directory files |
| Target use | Engine-neutral intake plus a new evaluator-proposed kinematic-controller scenario |
| Target engines | Not evaluated |
| Target rigs/packs | Mixamo profile resolved; no target character or cross-pack runtime supplied |
| Source manifest | Fresh 249-file collection inventory SHA-256 bc8543a8e1c7cf439030c07a2aead5db183d610633f637c1979dbce71a4dce66; identical before/after evaluation |
| Evaluation manifest | mixamo-rifle-8-way-locomotion.manifest.json; SHA-256 5f3a266b00f223047ec6ce5e0bef1e4e93c7d55dafd5bb4fc7ba26fee8e85dc4; legacy schema urn:animsmith:skill:animation-pack-evaluation-manifest:1 |
| Acquisition/license provenance | Extracted files are locally authorized; original ZIPs and controlling license evidence were unavailable; not legal advice |
The legacy metadata count of 81 motions is not linked authoritatively to these 83 files. Current conclusions use exact source paths and fresh measurements only.
Evidence coverage
| Surface | Offered/delivered | Evaluated | Findings | Not evaluated and why |
|---|---|---|---|---|
| Animation/source files | 83 | 83 | 0 baseline errors; 11 warnings | Vendor motion mapping unavailable |
| Rigs/export variants | 2 skeleton counts | 83 | two 68-bone X Bot.fbx; 81 66-bone files; Mixamo roles resolved | Retarget/deformation unavailable |
| AnimSmith baseline | 83 | 83 | 11 warnings; 11790 notes | — |
| Declared contracts | 83 | 83 | 16 ownership errors; 11 warnings | Per-file project intent unavailable |
| Evaluator scenario | 1 | 1 static proposal | 8 members with measured gait phase | Loop, synchronization, contact, engine, and visual acceptance unavailable |
| Engine import/playback | 4 common runtimes | 0 | 0 | No current engine project ran |
| Blend/mask/retarget | 1 proposed set | 0 runtime tests | 0 | Target graph/character unavailable |
Claim legend
Consequential claims are observed-file, observed-animsmith, inferred, or not-evaluated under the assessment taxonomy.
Evaluation manifest and taxonomy
Canonical clip-role inventory
| Canonical primary role | Logical motions | Delivered files | Evidence boundary |
|---|---|---|---|
idle-pose | 0 | 0 | Not authoritatively classified |
continuous-locomotion | 0 | 0 | Filename-based hypotheses stay outside the canonical role count |
locomotion-transition | 0 | 0 | Not authoritatively classified |
airborne | 0 | 0 | Not authoritatively classified |
traversal | 0 | 0 | Not authoritatively classified |
action-interaction | 0 | 0 | Not authoritatively classified |
reaction-death | 0 | 0 | Not authoritatively classified |
emote-cinematic | 0 | 0 | Not authoritatively classified |
other-unknown | 83 | 83 | One opaque current source unit per delivered FBX |
| Total | 83 | 83 | Current exact source paths; no vendor mapping inferred |
Exact runtime members
| Set/profile | Role or coordinate | Exact members | Variant/type | Timing or motion | Runtime contract |
|---|---|---|---|---|---|
hypothesis/kinematic-run-8way | forward=(0,1) | Rifle_8-Way_Locomotion_Pack_-_in-place/run forward.fbx | variant=in-place | duration=0.500 s; frames=28 frames | loop=unknown; sync=not-evaluated; movement=controller-xz-yaw; playback=project-defined |
hypothesis/kinematic-run-8way | forward-left=(-0.707,0.707) | Rifle_8-Way_Locomotion_Pack_-_in-place/run forward left.fbx | variant=in-place | duration=0.500 s; frames=16 frames | loop=unknown; sync=not-evaluated; movement=controller-xz-yaw; playback=project-defined |
hypothesis/kinematic-run-8way | left=(-1,0) | Rifle_8-Way_Locomotion_Pack_-_in-place/run left.fbx | variant=in-place | duration=0.500 s; frames=16 frames | loop=unknown; sync=not-evaluated; movement=controller-xz-yaw; playback=project-defined |
hypothesis/kinematic-run-8way | back-left=(-0.707,-0.707) | Rifle_8-Way_Locomotion_Pack_-_in-place/run backward left.fbx | variant=in-place | duration=0.500 s; frames=20 frames | loop=unknown; sync=not-evaluated; movement=controller-xz-yaw; playback=project-defined |
hypothesis/kinematic-run-8way | back=(0,-1) | Rifle_8-Way_Locomotion_Pack_-_in-place/run backward.fbx | variant=in-place | duration=0.500 s; frames=20 frames | loop=unknown; sync=not-evaluated; movement=controller-xz-yaw; playback=project-defined |
hypothesis/kinematic-run-8way | back-right=(0.707,-0.707) | Rifle_8-Way_Locomotion_Pack_-_in-place/run backward right.fbx | variant=in-place | duration=0.500 s; frames=16 frames | loop=unknown; sync=not-evaluated; movement=controller-xz-yaw; playback=project-defined |
hypothesis/kinematic-run-8way | right=(1,0) | Rifle_8-Way_Locomotion_Pack_-_in-place/run right.fbx | variant=in-place | duration=0.500 s; frames=18 frames | loop=unknown; sync=not-evaluated; movement=controller-xz-yaw; playback=project-defined |
hypothesis/kinematic-run-8way | forward-right=(0.707,0.707) | Rifle_8-Way_Locomotion_Pack_-_in-place/run forward right.fbx | variant=in-place | duration=0.500 s; frames=23 frames | loop=unknown; sync=not-evaluated; movement=controller-xz-yaw; playback=project-defined |
The table above preserves the measured member detail from the companion report; its proposed controller still requires target-engine acceptance.
Runtime-set inventory
| Runtime set | Type | Members/variants | Grouping evidence | Validation status |
|---|---|---|---|---|
hypothesis/kinematic-run-8way | directional-blend | Rifle_8-Way_Locomotion_Pack_-_in-place/run forward.fbx; Rifle_8-Way_Locomotion_Pack_-_in-place/run forward left.fbx; Rifle_8-Way_Locomotion_Pack_-_in-place/run left.fbx; Rifle_8-Way_Locomotion_Pack_-_in-place/run backward left.fbx; Rifle_8-Way_Locomotion_Pack_-_in-place/run backward.fbx; Rifle_8-Way_Locomotion_Pack_-_in-place/run backward right.fbx; Rifle_8-Way_Locomotion_Pack_-_in-place/run right.fbx; Rifle_8-Way_Locomotion_Pack_-_in-place/run forward right.fbx; all in-place | new evaluator hypothesis from current source names; no vendor/runtime authority; exact durations and frames are in the primary table | not-evaluated runtime acceptance |
Pipeline-stage coverage
| Stage | Coverage state | Evidence / remaining gate |
|---|---|---|
| Acquire | partially-evaluated | Extracted source present; original ZIP/license unavailable |
| Preserve raw | evaluated-clean | Fresh inventory matched byte-for-byte before/after the read-only matrix |
| Inspect | evaluated-clean | All 83 FBXs admitted and resolved the Mixamo profile |
| Segment | not-evaluated | Vendor motion-to-file mapping unavailable |
| Root motion | partially-evaluated | Archive-level XZ declarations ran; per-file ownership and yaw remain project decisions |
| Conform | not-evaluated | No source rewrite was authorized or produced |
| Validate | partially-evaluated | Mechanical and declared checks complete; runtime/artistic gates open |
| Optimize | not-evaluated | Constant-track notes were not promoted to a rewrite |
| Export | not-evaluated | No engine-facing export ran |
| Gate/report | partially-evaluated | Source-only decision report complete; adoption gates remain |
Readiness evidence by clip set
| Role or runtime set | File-ready / clip-ready | Set-ready / rig-use | Runtime / acceptance boundary |
|---|---|---|---|
| Opaque delivered corpus | 0 errors; 11 warnings; all sources readable | Mixamo roles resolved; vendor semantics and retarget use unavailable | No current engine, visual, contact, or gameplay acceptance |
hypothesis/kinematic-run-8way | Exact members and gait phases measured; loop intent unknown | New evaluator hypothesis; controller ownership proposed | Target-engine blend, phase/contact acceptance, transition, and deformation test required |
Validation-profile status
| Validation profile | Selection | Result / next evidence |
|---|---|---|
| Marketplace intake | selected — observed-pack-capability | Exhaustive source mechanics complete; acquisition/license boundary remains |
| Blended locomotion | selected — evaluator-selected-generic-scenario | Exact static proposal and gait-phase measurements recorded; runtime blend and phase/contact acceptance required |
| Root-motion controller | selected — observed-pack-capability | 16 archive-level ownership conflicts require per-file policy |
| State-machine transitions | selected — evaluator-selected-generic-scenario | Full-body transition policy proposed; no authored/runtime transition tested |
| Layered upper body/weapons | not-selected | No mask/additive contract or layer evidence |
| Traversal/environment | not-selected | No target traversal/environment contract |
| Contact actions/interactions | not-selected | No contact target or gameplay contract |
| Retargeted/customizable characters | not-selected | No target rig or retargeter supplied |
| Motion matching/search | not-selected | No database/annotation contract supplied |
| Networked movement | not-selected | No replication/rollback contract supplied |
| Runtime performance | not-selected | No target hardware/runtime project supplied |
Pack inventory and content evidence
The fresh inventory contains 83 FBX files. Both directory variants include X Bot.fbx with a 68-bone skeleton and an empty Take 001; the 81 motion-named files use 66 bones. All sources resolved the same nine Mixamo roles. This establishes static structure, not identical hierarchy/rest pose or retarget compatibility. Constant-track findings are optimization hints only.
The current scenario is intended to be useful without claiming old authority: hypothesis/kinematic-run-8way names 8 exact in-place-directory files, records current durations/frame counts, assigns movement to a hypothetical kinematic controller, and leaves loop intent, gait-phase acceptance, transitions, and runtime acceptance open. AnimSmith measured phase (cycles) as forward 0.4720, forward-left 0.4908, left 0.5860, back-left 0.5316, back 0.5311, back-right 0.4792, right 0.4128, and forward-right 0.4446; the normalized circular-range spread is 0.1731 cycles, defined as 1 - largest cyclic gap across member phases on the unit cycle. These are observed-animsmith measurements; sync=not-evaluated, and no defect, contact acceptance, or synchronization acceptance follows from the spread alone.
Mechanical baseline
| Finding/check | Affected scope | Potential impact | Evidence |
|---|---|---|---|
| No baseline errors | 83/83 files | File-readable mechanical prerequisite only | observed-animsmith; current ledger |
duration-sanity | 11 findings in 11 files | Empty reference take or shorter channels clamp-held at clip end | Rifle_8-Way_Locomotion_Pack_-_in-place/X Bot.fbx; Rifle_8-Way_Locomotion_Pack_-_in-place/walk backward.fbx; Rifle_8-Way_Locomotion_Pack_-_in-place/walk crouching left.fbx; Rifle_8-Way_Locomotion_Pack_-_root-motion/X Bot.fbx; Rifle_8-Way_Locomotion_Pack_-_root-motion/death from back headshot.fbx; Rifle_8-Way_Locomotion_Pack_-_root-motion/death from front headshot.fbx; Rifle_8-Way_Locomotion_Pack_-_root-motion/death from right.fbx; Rifle_8-Way_Locomotion_Pack_-_root-motion/idle crouching aiming.fbx; Rifle_8-Way_Locomotion_Pack_-_root-motion/jump loop.fbx; Rifle_8-Way_Locomotion_Pack_-_root-motion/walk backward.fbx; Rifle_8-Way_Locomotion_Pack_-_root-motion/walk crouching left.fbx |
constant-track | 11790 notes | Possible storage/evaluation overhead; no visual defect or safe rewrite inferred | observed-animsmith |
| Mixamo role resolution | 83/83 files | Enables measurement; does not prove target-character retargeting | observed-animsmith |
AnimSmith remediation evidence
| Source issue | Operation/declarations | Result | Independent verification | Remaining caveat |
|---|---|---|---|---|
| MIX-OWN-001 | Archive-level in-place and animation-owned XZ declarations | 16 stationary-root errors on Rifle_8-Way_Locomotion_Pack_-_root-motion/crouching turn 90 left.fbx; Rifle_8-Way_Locomotion_Pack_-_root-motion/crouching turn 90 right.fbx; Rifle_8-Way_Locomotion_Pack_-_root-motion/death from back headshot.fbx; Rifle_8-Way_Locomotion_Pack_-_root-motion/death from front headshot.fbx; Rifle_8-Way_Locomotion_Pack_-_root-motion/death from right.fbx; Rifle_8-Way_Locomotion_Pack_-_root-motion/death from the back.fbx; Rifle_8-Way_Locomotion_Pack_-_root-motion/death from the front.fbx; Rifle_8-Way_Locomotion_Pack_-_root-motion/idle aiming.fbx; Rifle_8-Way_Locomotion_Pack_-_root-motion/idle crouching aiming.fbx; Rifle_8-Way_Locomotion_Pack_-_root-motion/idle crouching.fbx; Rifle_8-Way_Locomotion_Pack_-_root-motion/idle.fbx; Rifle_8-Way_Locomotion_Pack_-_root-motion/jump down.fbx; Rifle_8-Way_Locomotion_Pack_-_root-motion/jump loop.fbx; Rifle_8-Way_Locomotion_Pack_-_root-motion/jump up.fbx; Rifle_8-Way_Locomotion_Pack_-_root-motion/turn 90 left.fbx; Rifle_8-Way_Locomotion_Pack_-_root-motion/turn 90 right.fbx | Current per-file lint and measurement; no output produced | Per-file intent and controller acceptance unavailable; residual unresolved |
| MIX-TIME-001 | No transform run | Candidate not produced | Current baseline identifies exact channel-end ranges | Artist intent and visual boundary acceptance unavailable; residual unresolved |
| MIX-CONTENT-001 | No transform run | Candidate not produced | Both exact X Bot.fbx files retain 68 bones and an empty take | Reference-vs-animation intent unavailable; residual unresolved |
No command availability, configuration, or generated hypothesis is reported as a source repair.
Engine procedures and evidence
| Runtime | Version | Procedure | Observed result | Remaining gate |
|---|---|---|---|---|
| Unity | unspecified | Not run | not-evaluated | Exact import/controller/blend/visual test |
| Unreal Engine | unspecified | Not run | not-evaluated | Exact import/retarget/Blend Space test |
| Godot | unspecified | Not run | not-evaluated | Exact Skeleton3D/AnimationTree test |
| Bevy | unspecified | Not run | not-evaluated | Exact target identity/graph/controller test |
Rig, masking, and compatibility evidence
| Pack/rig/set pair | Skeleton/retarget | Scale/axes | Root policy | Timing/blend | Overall evidence |
|---|---|---|---|---|---|
hypothesis/kinematic-run-8way / target character | Mixamo roles resolve; target deformation not tested | Not evaluated | Controller XZ/yaw/collision proposed | Exact durations and gait phases measured; synchronization/blend acceptance not tested | not-evaluated beyond static source evidence |
| This pack / any other Mixamo constituent | Common role labels and 66-bone count observed on motion files | Not evaluated | Per-file ownership unavailable | No pairwise transition/blend test | unknown; no cross-pack claim |
Limitations and unknowns
- Vendor mapping from 81 metadata motions to 83 files is unavailable and was not reconstructed from names.
- Loop intent, gait-phase acceptance, contacts, target character, deformation, engine graph, visual quality, root/yaw policy, and cross-pack behavior remain untested.
- Original ZIP payloads and controlling license evidence were unavailable; current hashing covers extracted source only.
Changes between AnimSmith versions
AnimSmith 0.14.0 — Fresh current-source matrix admitted 83/83 FBXs and revalidated baseline and archive-level declarations. New controller topology is explicitly evaluator-proposed and remains runtime-unaccepted.
AnimSmith 0.10.0 — Retained historical counts happen to agree; no prior semantic membership or engine claim was promoted. AnimSmith 0.7.0 evidence remains superseded.
Reproduction
Official evaluator preflight: release archive, archive SHA-256 4ecf79436f9123c779edb004050da3010b44a2b392e3031facb227d7734fc33e; exact member animsmith-v0.14.0-x86_64-unknown-linux-gnu/animsmith, binary SHA-256 c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366; tag v0.14.0, peeled commit e8321ad40be5ef6f162b31f085819c039175c3c9; working-tree state: N/A (official archive). Compiled features: fbx, report. Version and required command help plus representative FBX admission passed before evaluation. Preflight: external:animsmith-0.14.0-report-refresh/preflight.json, SHA-256 7034b5043de54e7b30064230db18605d1f96d0ea1477f7b8c56ce817cc73521a.
The current official binary SHA-256 is c2b9649bc74f8a7e5b5d7361a6feaa93941b2327cd7eee82235024bd4f61e366, version 0.14.0, source revision e8321ad40be5ef6f162b31f085819c039175c3c9, dirty false, features fbx,report. Version, top-level help, and inspect, measure, and lint help all exited 0 before the matrix. Representative FBX admission and the exhaustive run then completed.
The read-only four-worker matrix ran inspect, JSON measure, empty-config JSON lint, and variant-declaration JSON lint for every source: 332 commands for this constituent and 996 collection-wide. Source inventory SHA-256 bc8543a8e1c7cf439030c07a2aead5db183d610633f637c1979dbce71a4dce66 covers 249 files / 119,754,377 bytes and matched before/after. Current ledger SHA-256 is 62b0a7b7a1bb47c817f6b005a988077a44dc8d942bb26343a5f7278a15582272; summary 4dd01ee9f1398fa200355c82206c8fd24d80d0dea1f8965aee8b03ba400cfc7c; report data a8d8e1bd3707b58e24f7d08f4ea7038c0f316254fa289e4f6439284dbcbc388f. Baseline config digests are e3b0c442... for the shared empty file or cf70223... for the explicit Mixamo baseline; in-place declaration digest acb638c0...; root-motion declaration digest 567dd18f.... Full digests, private paths, outputs, and licensed sources remain external.
Sources
- Fresh external source inventory, command ledger, current summary, and exact per-command output.
- AnimSmith game-ready clip guidance.
Rust integration
- Embedding guide — Choose library crates and integration boundaries, then follow the embedded gate flow with the runnable
embedexample. - docs.rs API references — Look up exact published Rust API contracts for
animsmith-core,animsmith-gltf,animsmith-fbx,animsmith-engine, andanimsmith-report.
Embedding animsmith in a pipeline
The CLI is one frontend. Asset pipelines that already own their contract format, importer, build graph, and gate can call the same loaders, measurements, and checks in process.
This guide covers integration decisions. Symbol-level contracts belong in rustdoc, the pipeline scenario guide owns the larger raw-to-game-ready process, and the examples cookbook owns runnable CLI transcripts.
Choose the crates
| Crate | Use it for |
|---|---|
animsmith-core | Required embedding boundary: data model, rig roles, config, sampling, measurements, diffs, checks, and findings. No file I/O or format dependency. |
animsmith-gltf | Load glTF/GLB, write glTF/GLB, or apply byte-surgical glTF repairs. |
animsmith-fbx | Load FBX through ufbx; adds a bundled C build. Omit it from glTF-only pipelines. |
animsmith-engine | Resolve one exact built-in consumer/importer profile and fully materialized typed importer settings. No TOML, file I/O, format crate, or engine SDK dependency. |
animsmith-report | Render self-contained HTML from the same sampled grids and findings. |
The animsmith crate is the CLI binary, not a library facade.
[dependencies]
animsmith-core = "0.14"
animsmith-gltf = "0.14"
# Optional:
animsmith-fbx = "0.14"
animsmith-engine = "0.14"
animsmith-report = "0.14"
docs.rs is the canonical reference for published APIs. The stable package
URLs are:
animsmith-core,
animsmith-gltf,
animsmith-fbx,
animsmith-engine, and
animsmith-report. For the current workspace
state, build the same rustdocs locally with just doc.
Rustdoc owns signatures, type invariants, lifetimes, and the Errors and
Panics contracts. In particular, start at the animsmith-core crate root;
it is the compact API map rather than another copy of this guide.
The compiling end-to-end example is
crates/animsmith/examples/embed.rs:
cargo run -p animsmith --example embed
Integration flow
-
Load a source. Use
animsmith_gltf::load_sourceoranimsmith_fbx::load_sourcewhen importer-sensitive evidence matters. The immutable result binds the exact primary-file identity and bounded raw facts plus dependency-closure evidence to one normalizedDocument; borrow them throughdocument(),source_facts(), anddependency_closure(). Callinginto_document()explicitly discards both sidecars. Byte-owning hosts with sibling resources use the format crate's explicit-resource-root byte API. The compatibilityload_source_bytes/load_bytesentry points do not fall back to the process working directory. Theloadandload_bytesentry points still return only aDocument.glTF animation values remain authored; FBX scenes are normalized to metres, right-handed +Y-up coordinates and baked into linear TRS tracks. Raw FBX facts describe parser-projected source declarations and the AnimSmith loader's treatment, not authored key/tangent/interpolation values reconstructed from those baked tracks. Structural failures are loader errors. Semantic defects load and become findings. The same document carries meshes, skins, factor-only materials, and available base-color and normal textures for scene round-trips.
Raw-fact row sets have explicit complete, partial, or unavailable coverage; a partial prefix proves presence only. Resource rows inventory bounded declarations. The separate closure view maps them to the primary identity, one-read identities for safe rooted sidecars, or typed refusal/unavailability. Each row also exposes its kind-derived loader-essential, nonessential, or target-only purpose; that classification is not an engine-import verdict. Only complete declaration coverage with an identity for every row yields a complete closure identity. The result is exact for that versioned resource domain, not a claim that an unsupported extension or unmodelled FBX domain cannot introduce another target-importer dependency.
SourceInfo.pathis diagnostic metadata, not identity or raw-fact authority.External capture is local and bounded: unsafe, absolute, remote, escaping, oversized, and symlink-mediated locators are not opened or reproduced. Aliases of one normalized source-relative key are read and hashed once; equal bytes under different keys remain distinct dependencies. Path-based loading supplies the trusted root from the primary file. Hosts loading captured primary bytes must explicitly supply a trusted root to resolve sidecars; without one, a loader-essential resource is an error and an optional resource leaves conservative closure coverage.
An FBX host that needs auditable scale-boundary facts uses
animsmith_fbx::load_scale_sourceinstead: the returned wrapper retains the document andFbxScaleCapabilityInventoryfrom one parse. The inventory and normalized source-skeleton sidecar enable only the CLI's narrow rest/bind re-encode path whenrest_bind_capability_facts_for_sourceaccepts them; the source-aware form can distinguish known, non-scale-bearing custom properties, resource linkage, and shader/binding-table metadata from an unknown source element. It also admits a BindPose only when it covers every joint of each skin it touches and every exposed converted row is finite, unambiguous, and agrees with the cluster bind or node rest-world matrix the bridge consumes; a source with no Pose remains valid. The same-load source can also prove that enumerated payload omission, influence projection with complete effective coverage, triangulation, and exact-bit welding are conversion-fidelity facts rather than scale-bearing blockers. The inventory retains them, and unknown payload or missing effective geometry/influence coverage still fails closed. The inventory-onlyrest_bind_capability_factsremains conservative where its frozen aggregate cannot make that distinction. Whole-document FBX scaling remains refused.Completemeans the documented ufbx projection covers every representable node/skin identity and joint slot; a missing cluster bone downgrades the sidecar toUnavailableinstead of dropping that slot. Unreadable ordered bind declarations retain no finite prefix. The local rests and bind matrices are adjusted/compensated or derived target- coordinate values, not exact authored FBX members. The inventory explicitly records baked curves, rebuilt payloads, omitted authored face/edge members, uninstanced, non-polygon-only, or zero-face mesh definitions (including stable identities for zero-face omissions), and unavailable raw span proof. Stackless source curves are present but unsupported. Retained mesh definitions and source-skin attachments share the stable ufbx mesh identity even when an earlier source definition emits no normalized primitive. A shared FBX geometry remains one normalized definition with multiple compact node instances, so that stable identity stays unique in measurements. -
Resolve an optional engine profile. Construct an
animsmith_engine::EngineDeclarationwith the exact family, profile revision, engine version, importer, and typed document/selector settings.resolve_staticvalidates every declaration without file I/O; then callStaticResolution::resolve_inputwith the loader-ownedSourceFormatV1and actual clip names. The result contains the immutable facts identity and fully materialized settings identity. Pass that result and the sameLoadedSourcetoproject_prediction_provenance_v1; do not reopen or reconstruct either input. There is no generic/fallback profile, caller-supplied source-unit override, or TOML type in this API. If the host has no engine contract, omit projection and retainNone; core measurements and checks remain engine-neutral. -
Compose one check catalog. Start with
animsmith_core::all_checks()and append one borrowedanimsmith_engine::EngineAddressabilityCheck::new(&source, provenance.as_ref())?. PassNonewhen no profile was resolved so the stable engine-owned record remains not applicable. Validate selection against the core ids plusanimsmith_engine::ENGINE_CHECK_IDS_V1before asset I/O, then callevaluate_checksexactly once for the combined per-file catalog. The borrowed catalog intentionally cannot outlive its same-loadLoadedSourceand provenance.measure_documentdoes not consume either and remains profile-neutral. V1 materializes at most 4,096 actual clip-setting rows; an N+1 document is a typed bounds error rather than a truncated prediction. -
Resolve rig roles. Use
resolve_configured_rolesto apply the same named/auto profile plus inline-override policy as the CLI. Lower-leveldetect_profile,profile::resolve_named, andResolvedRoles::from_namesremain available when a host intentionally owns a different policy. Checks consume roles, never project-specific bone names. -
Build
Config. The CLI's TOML is only one constructor. Deserialize the types from your schema or build them programmatically. Deserialization validates numeric check tolerances and per-clip loop caps immediately.MovementOwnerrepresents independent XZ, Y, and yaw intent in core;Config::expectations_forreturns canonical effective fields after exact/glob layering, whileConfig::loop_continuity_tolerancesresolves the four effective loop pose and seam-velocity caps through the same per-clip, check-setting, and built-in-default precedence used by the checks. The legacyin_placeinput maps only to XZ and is cleared from that effective result. A selector that declares both spellings is rejected byConfig::validate. Programmatic callers must callConfig::validatebefore passing a directly constructed config to measurement-only APIs;evaluate_checksperforms the same validation before inspecting or running the check catalog and returns invalid configuration as a typedEvaluationError::InvalidConfiguration.Config::runtime_nodesis the shared attachment/socket/IK selector policy; the legacyrest-world-scalecheck field is normalized into it only when the shared field is absent. Supplying both is a typed conflict. This added public field is an intentional pre-1.0 struct-literal break: exhaustive literals must addruntime_nodesor use..Config::default(). -
Create one
MetricGrids. Share it by reference withmeasure_document,CheckCtx::new,evaluate_checks, and optional report rendering so each clip is sampled once.animsmith_report::rendertakes oneReportInputscarrying those grids, the roles, the evaluations and the sameConfigthe checks ran under; the configuration is what lets it draw a declared gait group's members against each other, since a coherent group emits no finding naming them. -
Map results into the host.
Findingcarries a stable check id, severity, optional clip/bone/time, measured and expected values, and a message. The host decides whether warnings fail its gate.
Call evaluate_checks with the full catalog and a CheckSelection. It
returns one CheckEvaluation per
catalog check, including disabled, unselected, not-applicable, partial, and
not-evaluated work. CoverageGap::code and EvaluationScope::code are the
machine fields; never reconstruct coverage by parsing a message. Content
findings are nested under their owning check and coverage gaps are never
encoded as findings.
Role resolution remains an explicit frontend step. Use
resolve_configured_roles, CheckCtx, and Config::rig rustdocs for the
exact profile, override, and unresolved-role contracts.
Compose the outputs you need
An embedded gate does not need to reproduce every CLI output. It can emit
clip measurements, add static asset measurements, run findings, render HTML, or
combine those results with host-owned checks. Share the same MetricGrids
within the limits documented by its rustdoc so those consumers judge one
sampled representation.
The MetricGrids, measure_document, and measure_assets rustdocs own cache
thread-safety, sampling, static-domain scope, and identity details.
Primitive::additional_influence_sets and the corresponding mesh measurement
records expose secondary glTF skin-attribute presence only. Mesh records also
preserve whether either side was unpaired on an individual primitive; they do
not retain or evaluate secondary per-vertex payloads.
For glTF/GLB, asset measurements additionally provide source-order material,
texture, and image inventory records plus explicit
material_resource_coverage. Complete glTF/GLB coverage is scoped to the five
documented core bindings, in order: base_color, normal,
metallic_roughness, occlusion, and emissive. It does not cover
extension-defined texture slots. These are loader observations, not a portable
image-management API: a host must handle "unavailable" coverage from other
loaders and must not infer writer preservation, image acceptance, repair,
resizing, transcode, color-space policy, or engine-import behavior from them.
Use the typed image metadata to inspect declared MIME separately from the
byte-detected container and decoded color/channel data; unavailable images
carry a reason instead of decoded metadata.
When the host needs to exchange the same JSON as the CLI, construct
MeasurementContract, MeasureFileReport/LintFileReport, and
MeasureEnvelope/LintEnvelope from animsmith-core::contract. That module
owns both immutable URNs and derives the lint/measure summary from the supplied
records.
It also exposes the typed MeasurementReportInput subset and
MeasurementReportFile records for consumers that need to validate and recover
every file's exact display path and full measurement contract from a current
measure or lint report. The core boundary preserves file order and accepts
empty or multi-file reports; each consumer owns any cardinality rule for its
workflow. MeasurementReportInput::file_count lets an adapter retain the raw
record count before into_files consumes and validates the report, while
MeasurementReportError::File carries a typed MeasurementFileError and an
index (also available through file_index) without adding consumer-specific
prose.
The compiling example emits a full schema-valid lint envelope; embedded
producers do not need to copy private CLI structs or hard-code protocol
identities. Host-specific sidecars remain appropriate when CLI interoperability
is not needed.
Gate and stability contracts
The CLI convention is a useful default for an embedded gate:
- no error findings and no required-unavailable prediction facets: success (warnings and ordinary coverage gaps may remain visible);
- any
Severity::Error: content rejection; - any
required_prediction_unavailablefacet: prediction-evidence rejection, regardless of severity or allow-list policy; - loader/config/I/O error: operator failure, kept separate from findings.
Missing prerequisites are typed coverage gaps, and disabled/unselected checks
remain visible without executing. Severity overrides apply only to content
findings. Ordinary coverage gaps are nonblocking by default; the embedding host
owns any stricter required-check or release-lane policy. To match the CLI's
finding threshold, allow-list, and unsuppressible prediction policy, call
animsmith_core::lint_requires_failure on the completed evaluations rather
than deciding from findings alone.
Built-in scope and gap codes may be emitted only by the checks declared in the
core evidence-code authority; CheckEvaluation::evaluated returns a typed
error when a different check claims one. Embedded checks retain an open
vocabulary by using namespaced custom codes such as acme:input_unavailable.
The public built-in-code slices are available to consumers that need to
enumerate or allow-list animsmith-owned codes.
For the current pre-1.0 API, prefer the crate-root flow: loader → role
resolution → Config → MetricGrids → measurements/checks → findings. The
durable automation contracts are deliberately narrower than the pre-1.0 Rust
API:
- built-in check ids used by config and findings;
- CLI exit codes and the versioned JSON envelope, when the host interoperates with the CLI.
The animsmith-core crate root owns the full
API status contract. Rust symbols, model and
transform types, metric formulas, and diff thresholds may still be refined
before 1.0. Match #[non_exhaustive] result types with a fallback arm. The
Check trait supports experiments with custom checks, including opt-in checks
that override enabled_by_default; an explicit "note", "warn", or "error"
setting activates one. There is no stable plugin registry yet; wrapping
animsmith findings with host-owned checks keeps that boundary explicit.
Migrating an existing pipeline
For a command-by-command migration plan, use the pipeline scenarios for marketplace intake, mocap cleanup, outsourced acceptance, CI gating, and raw/generated artifact storage, then use the cookbook for exact commands.
For the library cutover itself:
- Capture accepted measurements from the old pipeline as golden values.
- Run the CLI and embedded path from the same
Configuntil their findings agree. - Compare old and new measurement maps with
diff_measurements; judge motion deltas rather than binary file differences. - Keep project-specific sidecars, hashes, provenance, and storage policy in the host pipeline.
Foot-cycle map planning
animsmith_core::plan_foot_cycle_parameterization_v1 is the pure planning
boundary for a manifest-declared in-place gait ring. A host supplies the strict
typed parameterization, its exact byte identity, the already validated
collection manifest and exact byte identity, plus canonical contact fragments
and independently measured typed Root/Hips in-place evidence bound to each
fragment's exact artifact, dependency closure, and selected collection take in declared
member/path order. The planner validates manifest clip witnesses,
the known stance-detector provenance and exact common detector threshold,
bounded aggregate canonical-fragment work, alternating bilateral support
topology, positional boundary correspondence, and every declared segment-slope bound.
The strict declaration also carries a required exact proof policy: finite
maximum gait-phase spread and contact-boundary phase error in [0, 0.5], plus
a finite non-negative minimum left/right amplitude. The plan retains that
policy together with the identity of the exact parameterization bytes; it does
not obtain defaults from config or merge values across members.
It returns one exact ContactTransformBindingV1 and duration-preserving
ContactTransformOperationV1::TimeWarp per member.
After the host has independently bound the selected loaded clip to that exact
member plan and bound every track bone index to its selected, validated
skeleton, animsmith_core::time_warp_clip_v1 can build a pure cloned clip
candidate. The map is source-to-output: LINEAR authored keys move forward and
interior map knots are sampled with AnimSmith's normal runtime semantics; STEP
tracks move only their authored breakpoints. Unsafe nonconstant CUBICSPLINE
tracks and binary32 time collisions refuse the whole candidate. A successful
candidate still carries no emitted-artifact identity or reread proof.
animsmith_core::time_warp_rows_v1 yields one FootCycleClipWarpRowV1 per
emitted key — authored, a knot of its own, or the two coalesced — and
FootCycleClipWarpKnotV1 carries the one rule that decides when a control
point and an authored key are the same emitted instant, so an embedder proving
a candidate independently can ask those questions instead of restating them.
A row says which key exists and where in the order, never what it stores: the
knot it names carries its control point's index, so a proof narrows every
stored time itself instead of trusting the producer's resolution.
animsmith_core::preflight_time_warp_clip_v1 runs the same validation and
returns exact candidate name/track/key/value/storage-byte counts plus a
conservative V1 inspection-work charge without allocating the candidate,
allowing embedders to enforce their own checked aggregate batch budget before
retaining multiple outputs.
The planner does not open paths. The CLI crate has a private
adapter that validates the closed bounded TOML grammar, resolves the manifest
and parameterization path sets with symlink/canonical and
platform-observable hardlink alias refusal, validates the pure runtime-set
binding before host source access, reloads only its exact member-reachable
sources/configs, retains their normalized config semantics and byte identities
for proof without rereading controls, derives the typed in-place root evidence,
cross-checks plans against exact selected takes, and prepares bounded clip
candidates. Root evidence retains signed endpoint X/Z and signed accumulated
yaw even though the unchanged in-place gate applies horizontal hypot(X, Z)
and absolute yaw. The prepared collection also retains exact aggregate source
metric pose-cell and sample-evaluation totals for the shared
source/output proof budget. It also runs the closed operation-aware stance-detector extension
handler, but retains contact transformation as a typed continuation: only a
serializer supplies a truthful fresh output artifact identity and dependency
closure. The private in-memory continuation now serializes every candidate
as a strict self-contained GLB, rereads the exact bytes, and independently
proves the transformed clips and contacts as one all-or-nothing batch.
The public CLI then bounded-serializes member and aggregate evidence and
publishes the complete fixed generation through one no-replace directory
rename. Embedders must not
treat a successful plan or prepared candidate as an output-artifact or
gameplay-acceptance proof. See the
collection contract
for the declaration and exact topology grammar.
The format-edge candidate writer is also a private continuation, not a
producer: animsmith_gltf::write::preflight_glb_bytes records one exact
in-memory projection receipt and write_glb_bytes reruns it before retaining
bytes. StrictFootCycleV1 is deliberately narrower than legacy conversion and
refuses data it cannot represent exactly enough for the foot-cycle candidate
contract, including source scene collapse, incomplete skin bindings, primary
skin-slot reinterpretation, unsupported primitive/texture/material domains and
non-empty complete source material-resource sidecars,
and unrepresentable complete source-node or source-skin facts. The receipt
binds emitted JSON/BIN projection bytes, not deliberately excluded source-only
evidence. It preserves no
source-only names, stable source indices, or image-inspection/provenance
sidecars as output facts. An exact complete-empty material resource graph is
admitted only when the writer-facing material list is also empty. The
collection proof clones each prepared source document, replaces only its
selected clip, preflights all candidate bytes and shared metric-grid work
before writing, rereads the exact GLB bytes, requires a complete closure with
no external resources (embedded-buffer references may target Primary),
finishes the contact transform with the reread identities, and independently
checks duration, map/contact correspondence, gait, signed root trajectory,
and configured loop pose/velocity tolerances. The CLI-only transaction owns
generation staging/publication; the format crate does not. Neither path
claims game-engine acceptance.
Scale plan and proof contracts
The scale workflow is the operator-facing guide. Normative algebra, tolerances, refusal boundaries, and ownership live in DESIGN.md Appendix D; the output reference owns the evidence wire contract.
For an embedded producer:
- Preflight the exact source format with
animsmith_gltf::preflight_scale_source, then project it withanimsmith_gltf::capability_factsintoScaleCapabilityFacts. - Call
plan_scaleonce with an explicitScaleOperation. The opaque plan owns the operation parameters and a numeric-free structural ledger. - Apply that same plan to the exact source representation. For glTF/GLB,
rewrite_scale_plancross-checks raw hierarchy and container identities against the plan before changing JSON or accessor bytes. - Reload the emitted artifact, wrap the document with
ScaleCandidate::from_document, and callprove_scalewith the original source and same plan. - Run the format artifact proof as well. Core proof covers normalized semantic claims; artifact proof covers exact raw preservation, container structure, declared bounds, aliasing, deterministic bytes, and the actual write set.
- Publish only the proved artifact and matching evidence as one coordinated pair, preserving the CLI's rollback and documented process-crash semantics.
Those producer steps apply directly to glTF/GLB. With the default FBX feature,
the narrow rest-bind producer first requires a complete normalized FBX
inventory, stages a private GLB, and then runs the same raw-GLB plan/rewrite/
proof transaction; whole-document remains unsupported for FBX. The FBX
inventory is not authority to claim raw FBX spans, object properties, curve
keys, material/texture assignments, or vertex identity were preserved. The
source-aware gate may admit bounded texture/video declarations and discarded
user-defined properties while retaining their raw facts and closure evidence;
all incomplete or unknown scale-bearing domains still refuse with the exact
failed fact or counter.
ScaleCandidate grants no authority: proof independently validates the
source, plan inventory, candidate structure, and numeric claims. A frontend
must not substitute a normalized writer for exact-source rewrite when the
operation promises preservation of unknown or format-only payload.
ScalePlan::ledger() exposes read-only field, payload-shape, source-topology,
and proof-claim views for adapters. These rows name structural ownership and
identity, not resolved multipliers or expected numeric values. Writer and proof
may share this numeric-free binding vocabulary, but each must derive its own
numeric expectations. Under unavailable source coverage, best-effort raw locals
remain compatibility output rather than replay identity.
Each ScaleProof claim is a read-only ScaleProofResidual carrying its
maximum and comparison count together. evaluated() distinguishes a measured
zero from an obligation that walked nothing. The fixed
ScaleTolerancePolicy::APPENDIX_D_V6 identity also owns the sampled-work
budget; proof refuses over-budget input rather than proving a subset.
The glTF convenience functions rewrite_linear_units and
rewrite_rest_bind compile and delegate to the same plan-taking adapter.
Use the plan-taking boundary when the host already owns the plan. The format
frontend retains raw capability, hierarchy, component, type/count/range,
alias/overlap, and complement checks that core cannot express.
See the public rustdoc for exact selectors, row variants, errors, and proof fields. Calibration populations and historical timing notes are intentionally separate in scale-calibration.md.
What the libraries do not own
- Parsing the host's contract files into
Config. - Hashing assets, tracking provenance, or deciding staleness.
- Choosing raw/generated artifact paths or retention policy.
- Artistic retargeting, contact cleanup, or motion editing.
Those decisions surround animsmith in the raw-to-game-ready pipeline; they are not API responsibilities.
docs.rs API references
This reference is published outside the AnimSmith Pages site.
Project and contributing
- CONTRIBUTING.md — Work on animsmith itself — contribution flow.
- DEVELOPMENT.md — Set up a checkout and run the local verification commands.
- RELEASING.md — Cut a release — the release-plz flow, the manual 0.1.0 bootstrap, and the published-doc-link policy.
- SUPPORT.md — Get help or file a bug.
- SECURITY.md — Report a vulnerability privately.
- DESIGN.md — Follow the architecture, check-catalog rationale, and roadmap.
- Scale proof calibration — Review the implementation-owned calibration sweep, provenance magnitudes, historical policy measurements, and reproducible command behind
appendix-d-v6. - research/ — Read the dated research notes that inform the roadmap.
Contributing
Thanks for improving animsmith. This project is pre-1.0, so small contract changes can still happen, but every user-visible change should be explicit, tested, and documented.
Pull Request Flow
All changes land through pull requests. Keep each PR focused on one behavioral or documentation goal, and prefer follow-up issues over expanding a PR after review has started.
Before coding, state the user outcome, invariants, non-goals, and simplest maintainable end state if the change were designed today. Inspect related issues and existing authorities before adding a new concept. Focus means one coherent outcome, not the smallest diff; prefer a larger coherent refactor over preserving duplicate authority.
Use this lifecycle for non-trivial changes:
- Start from current
main. - Implement the change with behavioral tests when behavior changes.
- Update docs in the same PR whenever public behavior, commands, output, feature flags, or supported workflows change.
- Run the required local gates from DEVELOPMENT.md.
- Open a draft PR with a description of the behavior change, the chosen design, verification performed, and known out-of-scope work.
- Address review findings and run the project audit checklist for substantial changes.
- Leave the PR for a maintainer merge decision.
Trivial documentation-only changes can use a shorter path, but they still need a PR, a conventional title, and enough verification to show the rendered links and affected files are correct.
Conventional Commits
Every PR title and every non-merge commit that lands on main must use
Conventional Commits. CI enforces the accepted types from
.commitlintrc.yml:
feat fix perf revert chore ci docs style refactor test build
Use docs: for documentation-only changes. Use feat:, fix:, or
perf: only when the commit should affect release notes and version
calculation. Release automation groups the merged conventional commits
into the changelog.
Agent-authored commits also need the agent attribution trailer required by that agent's local instructions.
MSRV Policy
The minimum supported Rust version is a floor, not a ratchet. It moves
only when something in the tree actually requires a newer compiler —
normally a dependency raising its own rust-version, occasionally a
language feature the code has a concrete reason to adopt. It is never
raised merely because a newer stable exists.
The ceiling on that movement is six months: a bump must not require a compiler released within the last six months, so consumers who pin toolchains always have a two-release window to catch up. Six months is stated in months rather than as N-minus-releases because months are the unit downstream users experience, and the rule then survives a change in Rust's release cadence.
Day-to-day development still happens on current stable, and CI checks stable on Linux, macOS, and Windows. The MSRV is what the published crates promise, not what maintainers run.
workspace.package.rust-version in the root Cargo.toml is the single
source of truth. Nothing else states the number independently: the CI
MSRV job reads it out of the manifest, and two gates hold the prose to
it (see the Toolchain section of DEVELOPMENT.md). A
bump is therefore a one-line manifest edit plus whatever prose the gates
flag.
Because an MSRV bump is a compatibility change for downstream crates, it warrants a minor version rather than a patch, and a changelog entry saying so.
Workflows must not pin dtolnay/rust-toolchain to a version-shaped ref
such as @1.88. Dependabot reads those as action tags and bumps them,
which silently rewrites the Rust version — it once proposed @1.100, a
nightly number, for the MSRV job. Use a branch ref (@stable,
@nightly) or a SHA pin with an explicit toolchain: input;
scripts/check-github-community-files.sh enforces this.
Documentation Freshness
The PR description must call out documentation impact. If a change
affects user-visible behavior or public contracts, update the relevant
docs in the same PR or link a follow-up issue labeled type:docs.
Documentation impact includes:
- CLI commands, flags, exit codes, or examples.
- Machine-readable JSON output or schema ids.
- Public Rust symbols, crate features, loader boundaries, or README claims.
- Task guides, tutorials, example projects, and current version or project-status claims.
- Check ids, severities, thresholds, config keys, or measurement semantics.
- Release, support, security, or contributor workflows.
Review the complete affected stakeholder journey, not only files named
by the issue or touched by the implementation. Search the root and crate
READMEs, docs/, examples/, rustdoc, and release/process docs for
superseded commands, identifiers, schemas, versions, examples, and
status claims. Preserve clearly historical references.
Mechanical freshness checks cover the parts that are cheap and reliable:
schema URL consistency, GitHub community files and PR-template coverage,
crate README/package inclusion, docs.rs manifest metadata, and rustdoc
missing-docs enforcement. Semantic docs freshness remains a review
requirement: update docs in the same PR as the behavior change, or link a
focused type:docs issue.
During pre-1.0 development, published package READMEs intentionally link
deeper repository docs to latest main with
github.com/mmannerm/animsmith/blob/main/... URLs. If a release later
needs version-pinned README links, update RELEASING.md
and the package-readiness gate in the same PR.
Do not duplicate durable process rules across multiple files. This file owns contributor process. DEVELOPMENT.md owns local setup and verification commands. Agent files may add agent-specific deltas, but should link back here for the shared process.
Audit Expectations
Run the project audit checklist before asking for a merge on substantial changes: new checks, subcommands, measurement semantics, output contracts, dependency additions, write/fix path changes, or broad docs restructures.
Current-base gate
Immediately before final audit and readiness, fetch and record the review base:
origin/main for a normal PR, or the declared parent branch for an explicitly
approved stack while its parent is open. Record the candidate HEAD and fetched
review-base SHA, then prove that base is an ancestor of HEAD. If the base has
advanced, rebase onto it or otherwise integrate it, then repeat the final diff
review, local gates, exact-head CI, and audits. Any change to HEAD or the
recorded review-base SHA invalidates that readiness evidence. After a stacked
parent merges, retarget the PR to main, rebase onto current origin/main, and
repeat this gate.
Substantial agent-authored changes also require one reciprocal cross-model CLI audit: Codex invokes Claude, and Claude invokes Codex. The versioned audit workflow owns reviewer selection, model/effort, session reuse, attribution, and exact-head evidence rules.
The audit should check:
- Simplicity: the design is scoped to the issue and avoids needless abstractions.
- Tests: behavior changes have focused coverage and the local gates match CI expectations.
- Invariants: crate boundaries and dependency rules still match DESIGN.md.
- Documentation: README files, docs, and PR text stay fresh.
Follow-up work found during audit should become GitHub issues rather
than TODO comments. Search existing issues first, then file a focused
issue with the right type:* and priority:* labels.
Labels And Milestones
Use type:* labels to describe the work area:
type:bugfor incorrect behavior.type:featurefor new capabilities or enhancements.type:docsfor documentation, examples, and tutorial gaps.type:refactorfor behavior-preserving code structure changes.type:chorefor maintenance work.
Use priority:high, priority:medium, or priority:low when priority
is known. Put pre-release work in the active release milestone unless a
maintainer chooses another milestone.
Merge Policy
Maintainers merge PRs after review, verification, and any required audit
are complete. animsmith uses merge commits so every branch commit lands
on main with its own conventional subject; do not rely on squash merge
to repair commit history after review.
Development
This file is the canonical local setup and verification guide for a source checkout. Contributor process lives in CONTRIBUTING.md; release operations live in RELEASING.md; architecture and crate boundaries live in DESIGN.md.
Toolchain
animsmith uses the Rust 2024 edition and MSRV 1.88, declared in the
workspace Cargo.toml. CI checks stable Rust on Linux, macOS, and
Windows, plus the MSRV on Linux.
Develop on current stable; the MSRV is the floor the published crates promise, not the compiler maintainers run. When that floor may move is governed by the MSRV policy in CONTRIBUTING.md.
workspace.package.rust-version is the single source of truth for the
number. The CI MSRV job reads it out of the manifest rather than pinning
a second copy, and two gates hold the prose to it: this file's MSRV
sentence via scripts/check-github-community-files.sh, and every crate
README and rustdoc header via crates/animsmith/tests/msrv_docs.rs. A
bump is therefore a one-line manifest edit plus whatever prose those
gates flag.
That same shell gate also rejects a version-shaped
dtolnay/rust-toolchain@1.88 ref in any workflow, because Dependabot
rewrites those as if they were action tags.
Install the local tools used by the gates:
$ just install-rust-tools
That installs sccache, cargo-deny, typos-cli, and cargo-llvm-cov
if they are missing. Cargo still works with stock defaults.
The animation-pack skill gate also uses Python 3, PyYAML 6.x, and JSON Schema 4.x to validate skill metadata and the closed evaluation-model schema:
$ python3 -m pip install "PyYAML>=6,<7" "jsonschema>=4.18,<5"
sccache
Local sccache is optional. To configure a user-level Cargo
rustc-wrapper for faster repeated builds across worktrees:
$ just configure-sccache
Use RUSTC_WRAPPER= on an individual command when you intentionally want
to bypass sccache.
CI uses GitHub Actions caching and Swatinem/rust-cache; it does not
depend on a private runner cache.
Common Commands
$ just build
$ just test
$ just doc
$ just animation-pack-skill
$ just gates
just gates is the local PR gate and should be green on each candidate head
before pushing a non-trivial PR. It runs formatting, clippy, workspace tests,
golden skip marker verification, dependency checks, schema-id verification,
GitHub community-file checks, spell checking, rustdoc with missing public docs
denied, no-default-features CLI tests and builds, the retained release CLI
proof, package readiness checks, and the animation-pack skill's behavioral and
published-report validation.
This author-side pre-push gate is distinct from the later PR audit. Once the
same commit is pushed, audit agents reuse the captured local result and the PR's
required checks; they do not rerun just gates independently.
The corresponding CI workflows also validate the same expectations on a clean checkout. Coverage and the security scanners (Scorecard, CodeQL) run only in CI and are informational, so they are not part of the local gate (see below).
Pull requests also receive one separate checks / animation-pack result from
the reusable checks workflow. It runs the same animation-pack validator suite
once on the exact pull-request head, equivalent to the local
just animation-pack-skill recipe, covering the versioned skill validators and
every maintained report/appendix pair. Audit agents should retain and reuse
that exact-head result instead of rerunning the suite independently. The
check uses only repository-safe fixtures; licensed animation packs remain
outside the repository and CI.
no-default-features
The default CLI build includes FBX support through ufbx and the HTML
report feature. The --no-default-features build must keep working as a
pure-Rust glTF-only binary:
$ cargo test -p animsmith --test cli_contract --no-default-features --target-dir target/no-default-features
$ cargo test -p animsmith --test foot_cycle_cli --no-default-features --target-dir target/no-default-features
$ cargo test -p animsmith --test scale_cli --no-default-features --target-dir target/no-default-features
$ cargo test -p animsmith --test transition_pose_cli --no-default-features --target-dir target/no-default-features
$ cargo build -p animsmith --no-default-features --target-dir target/no-default-features
$ cargo build -p animsmith --release --no-default-features --target-dir target/no-default-features
--target-dir is not optional bookkeeping. Without it these builds land on
target/debug/animsmith, target/release/animsmith, and target/doc, and
whichever feature set ran last is what stays there; see Evaluation Artifacts
below. just github-community enforces the pairing: in justfile,
.github/workflows/checks.yml, and scripts/*.sh, a non-comment line naming
--no-default-features must name --target-dir target/no-default-features on
that same line.
In that build, glTF inspect, measure, lint, transform, fix, scale, diff,
generate addressability, and collection transform-foot-cycle stay
available, as do evaluate-transition-poses and
collection evaluate-transition-poses. HTML reports require the report
feature; FBX input, convert, and assemble require the fbx feature.
scale and collection transform-foot-cycle are the minimal build's
evidence-emitting producers, so their shared atomic artifact/evidence and
generation publication helpers live in crates/animsmith/src/publish.rs
rather than inside the feature-gated assembly module. Both scale_cli and
foot_cycle_cli are part of the no-default-features gate.
Evaluation Artifacts
A checkout holds more than one CLI, and they do not have the same
capabilities. Every --no-default-features command carries
--target-dir target/no-default-features, so no feature-variant build writes
to the conventional paths:
| Path | Compiled features | Use |
|---|---|---|
target/release/animsmith | fbx, report, proven by probing | external evaluation, after a green just gates |
target/debug/animsmith | default: no feature-variant build writes here | development and the local skill gates |
target/no-default-features/{debug,release}/animsmith | none | proving the minimal build still works |
| a published release archive | fbx, report | external evaluation, verified per RELEASING.md |
The first row is the only one this repository proves rather than arranges, and
just release-cli is what proves it. It builds nothing, so it judges the
artifacts a run leaves behind: both binaries must admit glTF, the retained
target/release/animsmith must also admit the self-authored FBX fixture and
expose report, and the isolated minimal binary must refuse those two. It then
prints a provenance record to keep beside an evaluation's results — the binary
path, its SHA-256, its --version line, the capabilities the probes proved,
git rev-parse HEAD, and git describe --tags --dirty. That last field reads
no tag reachable in a shallow or tag-less checkout, which is what CI clones;
it is not degraded to a bare hash, because a hash there would read like a
release name while naming none.
just gates runs it as its last line and the CI test job as that job's last
step, so what it attests to is what the completed run retained. The isolation
itself is held in place separately, by just github-community: across
justfile, .github/workflows/checks.yml, and scripts/*.sh it requires the
two flags to appear together on one line, and it requires the probe to be the
last step of the gates recipe and of the workflow's test job, so nothing
runs afterwards that could replace the artifact it just attested to.
Cross-platform determinism
Committed example assets and documentation visuals are byte-compared, and CI runs that comparison on Linux, macOS, and Windows, so every number that reaches those bytes has to be identical on x86-64 and arm64.
Model-space sampling is where that bites. glam is pinned to its
scalar-math and libm features in the workspace Cargo.toml, so the FK
that builds a pose grid uses scalar Rust f32 arithmetic and the libm
crate's transcendentals rather than SIMD intrinsics and the platform's own
libm. Rust never contracts a * b + c into a fused multiply-add, so
scalar IEEE-754 arithmetic gives bit-identical results everywhere; glam's
NEON path on arm64 does fuse, and its SSE2 path on x86-64 does not, which
moves the last bits of every matrix product. A committed report embeds the
sampled grid as base64, so those last bits are the file.
Fixtures whose motion is translation-only never saw this — adding exact
values is bit-identical on any IEEE platform. The first committed report
whose model positions come from a rotation (clip-dirty.report.html) is
what surfaced it, as a macOS-only failure of
committed_visuals_match_the_generator_output.
Prefer fixing determinism at the source over loosening a byte comparison: the comparison is what makes a committed picture evidence.
The one thing that byte comparison cannot answer is what changed across a
release: the glTF writer stamps animsmith <version> into asset.generator,
so every committed example asset changes when the version does.
PAYLOAD_IDENTITIES in crates/animsmith/tests/examples_cookbook.rs pins
each asset's content around that stamp, so a release refresh leaves the pins
alone and a changed animation does not.
Golden Tests
Golden tests include an env-gated reference test against licensed assets
and CI-visible FBX mesh/skin/clip coverage from self-authored checked-in
fixtures. The reference test skips cleanly when ANIMSMITH_GOLDEN_GLB
is unset and prints the grep-able marker ANIMSMITH_GOLDEN_SKIP; CI and
just gates assert that marker is present.
$ ANIMSMITH_GOLDEN_GLB=/path/to/reference-character.glb just golden
ANIMSMITH_GOLDEN_GLB is a developer-local, one-time reference input. Never
commit it or expose it through CI downloads, secrets, caches, logs, or
artifacts.
The committed-fixture rule applies repository-wide, not only under
testdata/: use synthetic/self-authored assets, CC0 assets, or other assets
whose license explicitly permits repository inclusion and CI
use/redistribution. Record provenance and license evidence for every
non-synthetic fixture. Commercial and other redistribution-restricted assets,
including excerpts and motion-bearing derivatives, stay outside the repository
and CI.
The same boundary governs what tooling may publish about a licensed clip.
Published evidence is limited to summary numbers, non-recoverable digests,
scrubbed labels, and single-series metric charts — one or two scalar series
per clip, such as foot height or root speed. Per-bone pose data is a
motion-bearing derivative and is never published, and that includes the HTML
report's sampled pose grid: the grid is the model-space position of every
bone on every judged frame, so a full report of a licensed clip carries the
clip. The artifact form that satisfies this rule is
animsmith report --evidence-only, which is what may be attached to an
issue, published, or sent to a vendor.
Documentation Builds
Use just doc for rustdoc warnings-as-errors and missing-docs
enforcement. It renders workspace docs and the CLI crate without default
features with -D warnings -D missing_docs, so all six publishable crates
keep documented public surfaces.
When editing public docs, cargo test --workspace validates Markdown
link targets and #anchors in the gated doc set (the docs_links
test); review GitHub forms and rendering by inspection. The root
README.md is also the crates.io front page for the animsmith CLI
crate, so keep its links absolute and keep CLI-user content first.
The release_version_docs workspace test also keeps the current dependency
snippets in the published READMEs and embedding guide aligned with the current
tool.version examples. It accepts the manifest version or exactly its next
patch or minor during pre-dispatch documentation staging; on a generated
release-plz-* branch, every current version claim must exactly match the
bumped workspace manifest. Historical changelog, bootstrap, and roadmap
versions are deliberately outside that inventory.
Those claims are written rather than hand-edited:
$ cargo run -p animsmith --example stage_release_docs
$ cargo run -p animsmith --example stage_release_docs -- --version <next>
With no argument the target is [workspace.package] version; --version
stages the next patch or minor before the release workflow is dispatched, and
any other version is refused. The inventory and the reader that locates each
claim live in animsmith-testkit's docs_versions module, which both the
example and the test drive: validating is asking whether staging would report
a change, so the tool rewrites exactly the spans the gate reads and a
version-shaped string elsewhere on the same page is not a current-version
claim. A second test holds that inventory to the tracked Markdown, so a new
snippet cannot be added without entering it. Re-running the example changes
nothing, and the gate — not the example — is what fails a stale checkout.
GitHub Pages preview
The Pages site uses the mdBook version pinned in .mdbook-version. Install it
locally before using the Pages commands:
$ cargo install mdbook --version "$(tr -d '[:space:]' < .mdbook-version)" --locked
just docs-check stages tracked repository files outside the checkout (in a
deterministic sibling directory named after the checkout by default), generates
the book, builds it, parser-validates the staged Markdown destinations, and
rejects rendered local links without targets in the built artifact.
just docs-serve stages the same source and serves it locally. Set
ANIMSMITH_DOCS_STAGE to choose another external staging directory; it must not
overlap the checkout and must not be committed.
Navigation comes from the Category column of docs/README.md. A cell is either
Part or Part › Group: parts become mdBook part titles in first-appearance
order, and a group becomes a generated chapter at _generated/groups/<slug>.md
listing its member rows with their Use it to… text. Staging fails when a part
or a group is split by another one, when the same group name appears under two
parts, when two group names collapse to one slug, or when a group name has no
slug characters, so the sidebar always follows the table's order. The row
pointing at docs/reports/README.md nests its report and evidence pairs below
itself. That index’s Current reports table is the canonical pair catalog;
its optional Browse by collection table groups those pairs into collection
chapters. When present, collection membership must cover the catalog exactly:
unknown, duplicate, or missing members fail staging. Without that section,
staging keeps a flat list of report pairs. Chapters with children start collapsed
([output.html.fold] at level 0).
Staging also resolves the media a page embeds: an <img> naming a tracked
drawing under docs/visuals becomes that drawing's own markup, so it reads the
page's theme tokens instead of guessing light or dark from the operating
system, and an <iframe src> becomes a site-absolute path, which mdBook never
rewrites and which the aggregated print page therefore needs. The repository
Markdown keeps the plain references GitHub renders.
Tracked docs/site files stage as mdBook's theme/ override directory instead
of as book source, so the stylesheet, fonts, and favicons style the site without
becoming pages. docs/site/animsmith.css is required and wired as
additional-css; staging refuses a checkout that does not track it rather than
publishing an unstyled book. docs/site/redirects.toml is configuration rather
than a page: each "/route.html" = "relative/target.html" entry becomes an
[output.html.redirect] route, and the build fails when a redirect target is
missing from the artifact. Built README.md chapters receive compatibility
routes because mdBook renders them as index.html. Generated links to
repository source are pinned to the selected release tag at the release root and
to main below /dev/.
Spell Checking
just gates runs typos over source,
comments, and docs. Run it alone with:
$ just typos
Domain jargon that reads as a misspelling (and binary DCC fixtures that
embed vendor strings) is allow-listed in _typos.toml. Add
new project terms there rather than rewording correct code.
Coverage
Line and region coverage come from
cargo-llvm-cov (source-based
LLVM instrumentation). Generate a local HTML report under
target/llvm-cov/html:
$ just coverage
CI runs the same tool and uploads the lcov report to Codecov, which renders
the README badge and per-PR diff coverage. Coverage is informational and
never blocks a merge; the codecov.yml project and patch statuses are set
to informational. Enabling the repository on Codecov and adding the
CODECOV_TOKEN secret is a one-time maintainer step (see
.github/workflows/coverage.yml).
Security And Supply-Chain Scans
Beyond cargo audit and cargo deny (the audit workflow), CI runs three
informational security scans that report to the GitHub Security tab and are
not wired into branch protection:
- OpenSSF Scorecard grades repository security posture weekly and backs the README badge.
- CodeQL performs static analysis of the Rust sources on
mainpushes and weekly, keeping results in the Security tab without adding another full-workspace compile to every PR. - Dependabot (
.github/dependabot.yml) opens weekly PRs to bump Cargo dependencies and pinned Action versions. Minor and patch Cargo bumps and Action updates are grouped into one PR each; major Cargo bumps open individually so each breaking upgrade is reviewed on its own.
Package Readiness
The package inventory gate protects the publishable crate contents intended for crates.io:
$ just package-inventory
It verifies each publishable manifest's docs.rs metadata and README
choice, checks that package inventories include the README, manifest,
and source entry point, and fully verifies animsmith-core with
cargo package. The shared include list carries workspace license and
notice files. The dependent crates cannot run full cargo package
verification until the matching animsmith-* dependency versions have
been published to crates.io, so the local gate uses
cargo package --list for them and the release flow publishes in
dependency order.
When the matching internal dependency versions already exist in the registry, also dry-run package assembly for any affected dependent crate:
$ cargo package -p animsmith-gltf
The release binary workflow packages CLI archives and detects the CLI
release tag through shared scripts (scripts/package-release-binary.py,
scripts/select-cli-release-tag.sh) so both are exercised locally rather
than only in CI:
$ just release-packaging
Bevy Readback Lock
The isolated exact-Bevy probe under tools/bevy-readback is excluded from
this workspace and owns its own Cargo.lock. That lock is the single
repository-owned authority for the resolved graph the probe observes: the
probe hashes it with include_bytes!, and the engine's strict reader
rejects any readback whose recorded lock identity differs.
The engine cannot include a file from outside its published crate, so it
carries that identity in crates/animsmith-engine/src/bevy_readback_lock.txt:
two lines, the byte count then the lowercase SHA-256, read with include_str!
and parsed at compile time into the crate's two public constants. Do not edit
that file by hand; rewrite it from the lock:
$ just bevy-readback-lock-refresh
just bevy-readback-lock (part of just gates) renders those same two lines
from the committed lock and fails when they differ from the committed file,
printing the identity the lock renders, the refresh command, and a diff. It
also checks that the lock pins animsmith-core and animsmith-engine at the
workspace version and every Bevy 0.19 release crate at 0.19.0. Its self-test
covers the identity-file comparison and the refresh that repairs it, on
temporary copies; the version and Bevy-patch pins are exercised by the opt-in
matrix below, which has a lock fixture for them.
The opt-in compile and runtime matrix for the probe itself needs Rust
1.95.0 and is not part of CI:
$ just bevy-readback-test
Releasing
Releases are automated with release-plz. You
never hand-edit versions: when a maintainer manually dispatches the release
workflow, release-plz opens or updates a release PR that bumps the shared
workspace version, propagates the internal animsmith-* dependency versions,
and updates CHANGELOG.md. Merging that PR tags the release, publishes the
GitHub Release, and publishes the workspace crates intended for crates.io in
dependency order.
The workflow is .github/workflows/release-plz.yml; its behaviour is
configured by release-plz.toml. The changelog uses release-plz's
default Keep-a-Changelog format, derived from the Conventional Commit
history (accepted types live in .commitlintrc.yml).
Repository prerequisite
In GitHub, enable Settings → Actions → General → Workflow
permissions → Allow GitHub Actions to create and approve pull requests.
The release-pr job's pull-requests: write permission is necessary but does
not override that repository setting. If the setting is disabled,
release-plz can push its release branch but PR creation fails with HTTP 403,
leaving the branch without a pull request.
Per-release flow (steady state)
During release freeze, default new work to the next milestone. Promote only reproducible correctness, safety, public-contract, or documentation blockers, plus genuinely small mutation-strengthening tests; record why shipping is riskier than changing.
-
Merge feature/fix PRs to
mainas usual (Conventional Commits). -
Perform one release-wide documentation-freshness sweep over the root and crate READMEs,
docs/,examples/, and current version/status claims. Preserve clearly historical references. Stage the intended release line in the dependency snippets and currenttool.versionexamples before dispatch:cargo run -p animsmith --example stage_release_docs -- --version <next>That rewrites every claim the
release_version_docsworkspace test reads, and nothing else: it edits only the located spans, so a historical version on the same page stays. The test accepts exactly the next patch or minor whilemainstill carries the previous manifest version, and the example refuses any other version. On the generatedrelease-plz-*branch the same test requires every current version claim to equal the bumped workspace manifest. Both deliberately ignoreCHANGELOG.md, this completed bootstrap, and roadmap history. The dispatch job checks that generated branch itself in strict mode before succeeding, because GitHub does not initially trigger PR workflows for a PR created with the defaultGITHUB_TOKEN. -
When
mainis ready to release, manually dispatch the release workflow:gh workflow run release-plz.yml --ref mainThe workflow first runs the shared checks, then release-plz opens or updates the release PR. It computes the next version from the commits since the last release — one shared version across the publishable crates (
version_group), so the whole workspace moves together — and writes the changelog. Ordinary pushes tomainnever create or update this PR. The release-PR job temporarily installs three exact, commit-scoped Git replacements so release-plz can inspect a short historical branch whose manifests did not parse. The preparation refuses any object-identity drift or pre-existing replacement, preserves commit identities and messages, and is removed even when release-plz fails. It is local to that job: the push-triggered publication path always reads canonical repository history. If another change merges tomainwhile the release PR is open, dispatch the workflow again before merging so the version and changelog include it. -
Review that PR. If step 2 staged nothing, or staged a version release-plz did not pick, restate the documentation from the bumped manifest on the release PR branch itself and commit what it writes:
cargo run -p animsmith --example stage_release_docs cargo test -p animsmith --test release_version_docsWith no argument the example targets
[workspace.package] version, so on a correctly staged branch it reports no changes. The test is the fail-closed check either way: on arelease-plz-*branch it fails unless every current version claim equals the bumped manifest.The glTF writer records the package version in
asset.generator, so regenerate and commit the version-stamped example assets from the release PR branch before merging:cargo run -p animsmith --example gen_example_assets cargo test -p animsmith --test examples_cookbookThat refresh moves the stamp and nothing else, so the
PAYLOAD_IDENTITIESpins incrates/animsmith/tests/examples_cookbook.rsmust come through it untouched: the test above digests each asset around itsasset.generatorstring, and a pin that moves means the regenerated animation changed rather than only its version. Do not edit those literals as part of a release.The committed documentation visuals under
docs/visuals/are rendered from those same assets, so regenerate and commit them in the same step:cargo run -p animsmith --example gen_docs_visuals cargo test -p animsmith --test docs_visualsThe isolated Bevy readback probe locks
animsmith-coreandanimsmith-engineat the workspace version, and the engine states that lock's size and SHA-256 incrates/animsmith-engine/src/bevy_readback_lock.txt. Move both package versions intools/bevy-readback/Cargo.lockto the release, rewrite that file from the lock, and confirm the lock gate agrees:just bevy-readback-lock-refresh just bevy-readback-lock cargo test -p animsmith-engineDo not hand-edit the identity file:
just bevy-readback-lockrenders those two lines again from the committed lock and fails on any difference.The cookbook drift guard fails if the committed bytes do not match the release version. The example assets, documentation visuals, and Bevy lock are the three mechanical regenerations this step retains. Payload identities protect the example assets and exact-artifact assembly fixture independently of the package version; the visuals and Bevy lock/identity remain exact regenerated artifacts checked by their own gates. There are no manual assembly digest literals to refresh and no counterfactual assembly rebuild to perform.
When you merge the PR, the
releasejob runs on the resulting push tomain, tags the release, creates the GitHub Release, and publishes every crate to crates.io in dependency order (animsmith-core→-gltf/-fbx/-engine/-report→animsmith). The follow-onrelease_binariesjob callsrelease-binaries.yml, builds CLI archives from the tag, and uploads the archives plus matching.sha256files to that GitHub Release. Therelease_pagesjob dispatchesdocs-pages.ymlonmainin the same run, moving the Pages root to the tag just published; see GitHub Pages documentation.
Supported CLI archive targets live in release-targets.json.
scripts/release-targets.py renders the generated workflow matrix block in
.github/workflows/release-binaries.yml and the install table in
docs/cli.md. After changing release targets, run
scripts/release-targets.py write and then just release-packaging so the
generated docs and workflow matrix stay in sync.
Post-release verification
Treat workflow success as the start of verification, not the final claim. Before closing the milestone:
-
Confirm the push-triggered
release-plz.ymlrun completed successfully at the release-PR merge commit, including every platform build and the release asset upload. Confirm the release tag resolves to the frozen, audited release PR head; with merge commits this is normally the release commit, not the merge commit itself. -
Verify every publishable package exists at the expected version on crates.io and is not yanked. The crates.io API may return HTTP 403 to an anonymous default client, so use a descriptive
curl -Auser agent when querying it. -
Compare the GitHub Release assets with
release-targets.json: expect one archive and one.sha256file per target. Download them, runsha256sum -c, inspect.tar.gzcontents withtar -tzf, and inspect.zipcontents withpython3 -m zipfile -lso verification does not depend onunzipbeing installed. Before closing the milestone, run--versionfrom at least one archive that is native to the verification host, confirm the output names the expected release version, and retain that comparison. If no release target is native to that host, perform the same execution, expected-version comparison, and evidence retention on a fresh VM or runner for one supported target. Ifgh release downloadleaves the destination empty, fetch each release asset by id throughgh api -H 'Accept: application/octet-stream' repos/<owner>/<repo>/releases/assets/<id>instead. -
Check docs.rs separately. A crate appearing in the docs.rs queue means it was accepted, not rendered; report it as queued until the versioned pages or build record resolve successfully.
-
Confirm the Pages deployment followed the release. The
release_pagesjob dispatchesdocs-pages.yml, so the composing run appears in Actions as Pages documentation triggered byworkflow_dispatch. Check the published routing record rather than the run alone:curl -fsSL https://mmannerm.github.io/animsmith/BUILD-INFO.txtIt must report
Release root: vX.Y.Zfor the tag just published andDevelopment subtree: main. If it still names the previous tag, run the manual recovery dispatch from GitHub Pages documentation and re-check. -
Re-query the live bug list and release milestone. Close the milestone only when it has zero open issues, then update the roadmap ledger with the shipped date and bounded scope.
crates.io publishing uses
Trusted Publishing (GitHub
OIDC): the release job holds id-token: write and release-plz mints a
short-lived token itself — there is no long-lived CARGO_REGISTRY_TOKEN.
The publish step is idempotent: a re-run skips versions already on the registry. If publishing fails partway through, use Re-run failed jobs on that original push-triggered workflow run. A new manual dispatch creates or updates the next release PR; it does not retry publishing.
Version-bump policy
Configured in release-plz.toml:
featbumps the minor,fix/perfthe patch, even on0.x(features_always_increment_minor = true, porting the oldcliff.tomlbump rule).- Breaking changes follow semver — on
0.xthat is a minor bump (0.1.0→0.2.0), not a major. This is the one place the oldcliff.tomldiffered: it forced breaking → major (→ 1.0.0), but release-plz has no equivalent setting. If you want to go to1.0.0, bump the version explicitly in the release PR. - Only
feat/fix/perf/revertappear in the changelog and release notes;chore/ci/docs/style/refactor/test/buildand merge commits are skipped ([changelog].commit_parsers) — they still count toward whether a release is warranted.
Schema identities
Output and measurement schemas use immutable protocol URNs, independent of
package releases. Do not rewrite $id during a release. A breaking contract
gets a new URN and schema file. scripts/check-schema-id.sh checks that each
schema, the CLI, and docs/output.md reference the same identities; repository
links are only retrieval locations.
Published README and docs links
The crate READMEs are included in the crates published to crates.io.
During pre-1.0 development, those READMEs intentionally link deeper
repository docs to latest main with
github.com/mmannerm/animsmith/blob/main/... or /tree/main/... URLs.
That means an older published crate can send readers to newer source
docs. For now this is accepted so reference docs stay simple while the
API is still settling; the machine-readable JSON schema remains protected
separately by scripts/check-schema-id.sh.
GitHub Pages documentation
GitHub Pages publishes two deliberately different snapshots through the
official artifact deployment workflow in .github/workflows/docs-pages.yml.
The site root, https://mmannerm.github.io/animsmith/, is rebuilt from the
latest published release tag. https://mmannerm.github.io/animsmith/dev/ is
rebuilt from protected main at the same deployment, so contributors can link
to unreleased work without relabeling it as released documentation.
The workflow source is the default branch; pull requests only retain a rendered
preview artifact and never deploy. The release root is selected from GitHub's
latest published release, rather than from an assumed version prefix. Its
mdBook executable and its scripts/build-docs-site.py both come from that
selected tag. Composition preserves its chapter content and adds only a
report-version navigation notice linking older evaluations to current main.
The stable /evaluations/ route also leads to the current report index.
The /dev/ subtree separately
uses the default branch's own pin and build script. If no published release
exists, do not enable Pages deployment: publish the first release first, then
dispatch the workflow as shown below.
A successful release publication dispatches that workflow itself: the
release_pages job in .github/workflows/release-plz.yml runs
gh workflow run docs-pages.yml --ref main --repo "${GITHUB_REPOSITORY}" with
the repository GITHUB_TOKEN as soon as release-plz reports a published
release. That job checks nothing out, so gh cannot infer the repository and
the flag is explicit; the recovery command below is the same dispatch run from
a clone, where --repo is unnecessary. The dispatch is required, not
decorative. GitHub creates no workflow run for an event produced with the
repository GITHUB_TOKEN, so the workflow's own release: published trigger
stays silent for an automated release — it still covers a release published by
hand — while workflow_dispatch is one of the two documented exceptions to
that recursion guard. The push that merges the release PR also runs the
workflow, but it runs before the release exists and would leave the root on the
previous tag; the shared pages-<ref> concurrency group cancels that earlier
run when the post-publication dispatch starts.
Recover a missing or stale deployment with the same dispatch from a clone of
the repository — after a failed release_pages job, or a release published by
hand:
gh workflow run docs-pages.yml --ref main
scripts/check-pages-release-trigger.py, run by
scripts/check-github-community-files.sh, fails the build if that dispatch,
its actions: write permission, or the workflow_dispatch trigger it targets
disappears, or if deployment stops being gated on an eligible published
release.
docs/README.md is the canonical task index and the site's only routing
authority. Its Category column — Part or Part › Group — generates the
mdBook parts, group chapters, and their order during clean staging; do not add
a hand-maintained SUMMARY.md, generated HTML, or symlink-based publication
tree. docs.rs remains the Rust API reference and must remain prominent in the
Pages index and README. Animation-pack reports are nested from the canonical
table in docs/reports/README.md, and built README.md chapters receive
compatibility routes for mdBook's README.html rewrites. Generated
source-reference routes are pinned to the selected release tag at the release
root and to main below /dev/.
The site's presentation is tracked rather than configured at deploy time.
docs/site/ holds the theme override mdBook applies — stylesheet, fonts, and
favicons — staged outside the published source so nothing in it becomes a page,
and the current build script refuses a checkout that does not track its
stylesheet. Because each release root is built by its own tag's script, release
tags predating docs/site/ still publish with mdBook's default theme and their
own navigation; do not patch a tag to add either. Retiring or renaming a
published route requires its docs/site/redirects.toml entry in the same
change, so an existing external link keeps resolving, and a redirect whose
target is missing fails the build instead of shipping a dead route. Run
just docs-check before changing any Pages source; DEVELOPMENT.md,
"GitHub Pages preview", owns the local commands and what they verify.
If a future release needs version-pinned README links, update the
READMEs, this section, and scripts/check-package-inventory.sh in the
same release-oriented PR. Do not add release-time rewriting without a
mechanical check that proves the packaged README links and the release
tag agree.
Bootstrapping a newly added crate
Trusted Publishing cannot create a crates.io package. Generate the release PR, freeze its exact head, and run the full release gates before bootstrapping a new workspace crate. Do not change the release PR after any package from that head has been published.
If the new crate verifies against the internal dependency versions already on
crates.io, manually publish only the new crate with a token carrying
publish-new. If it consumes internal APIs introduced by the same release,
first manually publish the required existing workspace crates from the frozen
release head in dependency order with publish-update, then publish the new
crate with publish-new. Always let Cargo perform its ordinary package
verification; --no-verify is not a bootstrap mechanism.
Add the repository's release-plz.yml Trusted Publisher to the new package,
then merge the unchanged release PR. release-plz skips the exact versions
already published from that head and publishes the remaining workspace crates
in dependency order. Record every manual package publish, the exact release-PR
commit, and the Trusted Publisher setup in the release audit.
If the release PR must change after a manual publish, stop and reconcile the already public immutable package rather than pretending the replacement head has the same bytes.
One-time bootstrap
This repository starts its public release history from a clean slate: the
pre-publication development tags (v0.1.0–v0.7.0, never on crates.io)
were deleted on 2026-07-04, so the first crates.io publish, the first
GitHub Release, and CHANGELOG.md all begin together at the 0.1.0 in
Cargo.toml.
The entire first 0.1.0 release is manual — crates.io publish, the
v0.1.0 tag, the GitHub Release, and the initial CHANGELOG.md. Two
constraints force this, and they compose:
- Trusted Publishing cannot publish a crate that does not yet exist, so the first crates.io publish of each crate must use a token.
- release-plz
releaseonly acts on unpublished packages. Once0.1.0is on crates.io it will (correctly) no-op — so it will not create the firstv0.1.0tag/Release for you. The manualv0.1.0tag is also the baseline release-plz needs to compute the next version.
So automation begins at 0.2.0; 0.1.0 is done by hand, once:
-
cargo loginwith a token from https://crates.io/settings/tokens (scope:publish-new+publish-update). -
Generate and commit the changelog first — before publishing.
release-plz updatecompares the local crates against the registry to find unreleased changes, so it only produces the0.1.0changelog while the crates are still unpublished. Run it now (its Keep-a-Changelog format matches every later release):release-plz update # writes CHANGELOG.md git add CHANGELOG.md && git commit -m "chore(release): 0.1.0" git push -
Publish the workspace from that release commit, in dependency order (each dependent crate resolves against the crate just published):
for crate in animsmith-core animsmith-gltf animsmith-fbx animsmith-report animsmith; do cargo publish -p "$crate" doneanimsmith-coreshould passcargo publish --dry-runfirst; the dependent crates can only fully verify once theiranimsmith-*dependencies exist in the index. -
After each crate is accepted, docs.rs queues its documentation. Check each crate's landing page, source listing, and feature metadata. The library crates (
animsmith-core,animsmith-gltf,animsmith-fbx,animsmith-engine, andanimsmith-report) must also have successful rustdoc builds; their manifests setdocumentationlinks and[package.metadata.docs.rs]so pure-Rust crates get Linux/macOS/Windows pages, while the C-dependentanimsmith-fbxuses the Linux default target. The publishedanimsmithpackage is intentionally bin-only: docs.rs has no library target to document and itscargo rustdoc --libfailure is an accepted, mechanically guarded exemption. Verify its docs.rs landing/source/features pages only. -
On crates.io, for each publishable crate: Settings → Trusted Publishing → add publisher — repository
mmannerm/animsmith, workflowrelease-plz.yml, no environment. -
Tag the release commit and publish the GitHub Release from the
0.1.0changelog section (release-plz won't create it —0.1.0is already published — so the notes are extracted from that same section):git tag v0.1.0 && git push origin v0.1.0 gh release create v0.1.0 --title v0.1.0 \ --notes-file <(awk '/^## \[0\.1\.0\]/{f=1;next} /^## \[/{f=0} f' CHANGELOG.md)Then dispatch the binary packaging workflow against
mainso the manually created release gets the same archives and checksums as later automated releases:gh workflow run release-binaries.yml --ref main -f tag=v0.1.0 -
Arm the release automation. Both the manually dispatched
release-prjob and the push-triggeredreleasejob are gated onvars.RELEASE_PLZ_ARMED, so the whole flow stays inert until this is set — no release PRs and no publish attempts before the manual0.1.0above:gh variable set RELEASE_PLZ_ARMED --body true
After the bootstrap, every subsequent release (0.2.0+) starts with the
manual workflow dispatch and then goes through the release-plz PR flow above —
no manual cargo publish, no manual version edits, one repo-level vX.Y.Z
tag and Release per version.
Known caveat: CI on the release PR
PRs opened with the default GITHUB_TOKEN do not trigger
on: pull_request workflows, so the release-plz PR will not get its own
full shared-check matrix of its own. The manual dispatch runs that matrix
against pre-bump main, then checks out the exact generated branch and runs
the focused release-version documentation guard in strict mode. The generated
lockfile, changelog, and assets still require review, and the post-merge
checks job runs the full matrix again before publishing. If branch protection
also requires a full-matrix check attached to the release PR itself, give the
release-pr job a PAT or GitHub App token via the release-plz token input
instead of secrets.GITHUB_TOKEN.
Support
animsmith uses GitHub issues for project support. GitHub Discussions are not enabled for this repository.
Questions
For usage questions, open a question issue:
https://github.com/mmannerm/animsmith/issues/new
Include the command you ran, the asset format, the animsmith version, and what you expected to happen.
Bugs
Use the bug report template:
https://github.com/mmannerm/animsmith/issues/new?template=bug_report.yml
Please include a minimal public or synthetic asset when possible. Do not attach licensed assets unless you have permission to share them.
Documentation And Examples
Use the documentation-gap template for missing, stale, or confusing docs:
https://github.com/mmannerm/animsmith/issues/new?template=documentation_gap.yml
Example and tutorial gaps belong there too; the issue should say what asset type, command, or embedding workflow the example needs to cover.
Feature Requests
Use the feature request template:
https://github.com/mmannerm/animsmith/issues/new?template=feature_request.yml
Describe the game-animation workflow you want to support and whether it belongs in the CLI, a Rust crate, the report, or documentation.
Security
Do not report vulnerabilities in public issues. Use the private reporting path in SECURITY.md.
Security Policy
animsmith is a pre-1.0 Rust CLI and library workspace. Security fixes target the current development line and the latest published crates once the project is on crates.io.
| Version | Supported |
|---|---|
current main | yes |
| latest crates.io release | yes, after first publish |
| older pre-1.0 releases | no, unless a maintainer explicitly says otherwise |
Reporting A Vulnerability
Please report suspected vulnerabilities through GitHub private vulnerability reporting:
https://github.com/mmannerm/animsmith/security/advisories/new
Do not open a public issue for security-sensitive reports. Include:
- Affected crate or CLI command.
- A minimal reproducer or input description.
- Expected impact.
- Whether the report depends on untrusted animation input, build-time behavior, or generated HTML reports.
Maintainers will triage the report, discuss remediation privately, and publish an advisory if the issue has user impact.
Scope
Security-sensitive areas include parser hardening for glTF/GLB and FBX input, generated report output, dependency advisories, and behavior that could crash or hang CI on untrusted assets.
Correctness bugs, false positives, and missing animation checks should be filed as normal GitHub issues unless they expose a security impact.
animsmith — design & requirements
Status: pre-1.0 publishing design. Intended to keep the public crate and CLI surface aligned while the project is still willing to make breaking changes. Origin: extracted from a private game project's animation pipeline (design session 2026-07-03); that project — "the incubating project" below — is the first consumer, not the scope.
1. Mission & positioning
animsmith is a linter for skeletal animation clips. It answers the question every game team answers by hand today: does this clip have game-engine-friendly characteristics? — does the loop actually close, does the walk cycle's declared speed match its root motion, do the feet slide during stance, is the rig conformant, are the quaternions sane.
The gap it fills. Nothing open-source does game-semantics clip validation:
- Khronos glTF-Validator checks spec conformance (accessor validity, NaN, quaternion norms at the container level) — it has no concept of a loop, a gait, or root motion.
- ozz-animation has a motion-extraction sample (a good root-motion measurement reference) but no lint pipeline.
- Academic metric code (foot-skate ratio, jitter, penetration) lives in ML-evaluation repos, not artist tools.
- Engine importers (Unreal Interchange + Data Validation, Unity AssetPostprocessor, Godot import sidecars) give teams a place to hang custom checks, but the checks themselves are always studio-custom and re-derived from scratch.
animsmith packages those checks as a standalone Rust library + CLI: glTF/GLB native, FBX ingested via ufbx, engine-agnostic core, machine-readable output, and a self-contained HTML report with a 3D preview.
What it is not (scope guardrails):
- Not a spec validator. Run glTF-Validator for container conformance; animsmith assumes a parseable file and judges its content.
- Not an art exporter.
convert(FBX→glTF) exists so clips can enter the lint pipeline directly from a DCC export; it promises animation and skinning fidelity, not material/shading fidelity. - A transformer for pipeline-mechanical operations only (scope
widened 2026-07-03; see Appendix A). In scope:
fixfor lossless mechanical repairs (quaternion unit normalization and hemisphere normalization), frame-range slice/trim + hold-extend, fail-closed in-place gait-anchor rotation, opt-in pruning of provably constant multi-key tracks, and format conversion including a full mesh/skin FBX→glTF path (a maintained replacement for the archived FBX2glTF). Out of scope stays artistic transformation: retargeting, motion editing, procedural animation — that is DCC work. The rule of thumb: animsmith may rewrite a clip only in ways whose correctness its own checks can verify. - Gait anchoring is an explicitly in-place operation. The core boundary
requires a movement policy; the shipped CLI and assembly switches select
only
InPlace. Before any channel is cyclically reordered, AnimSmith samples the resolved Root role (falling back to Hips) and refuses missing/non-finite evidence, horizontal accumulation above 1 cm, or yaw accumulation above 1°. Every nonconstant channel the operation would rotate must contain exactly one key at each declared whole-frame sample over[0, duration], at the exact representable f32key / fpstime and exact period endpoint. Sparse, differently framed, duplicate-time, or off-grid evidence refuses; duplicate(bone, property)channels, including constant channels, also refuse before sampling. A phase shift must bijectively permute authored values rather than synthesize values at omitted frames. The verifier samples those exact admitted f32 key times, and mutation is an integer-index permutation rather than a second floating-point resample. Constant channels need no grid because cyclic reordering cannot change them; their key times cannot influence the declared period or shift. Before allocating a pose grid, the public core boundary validates all track cardinalities, targets, finite values, all resolved metric-role indices, and the complete acyclic parents-before-children skeleton. Both declared-frame and maximum-authored-key work must independently satisfy the inclusive boundsdeclared frames × skeleton bones <= 1,000,000,declared frames × tracks <= 1,000,000, andmaximum authored keys × skeleton bones <= 1,000,000; every verifier and metric sample uses the one bounded declared grid. The 1 cm endpoint-displacement and 1° accumulated-yaw caps are applied directly, without any sampled-step allowance an interior outlier could inflate. Yaw is derived from model-space f32 quaternions as binary64 headings. At sample zero, the verifier selects the local+Z,+Y, or+Xbasis axis with the greatest finite horizontal projection, breaking exact ties in that order, and retains that one axis for the entire proof. This admits equivalent source-axis conventions without switching witnesses later to hide yaw; a selected axis that later loses its horizontal projection refuses. Full-turn crossings are counted and the first heading is subtracted from the final unwrapped heading, so the result has no segment-count-dependent summation error. Only four f32 successors at each inclusive cap cover authored endpoint translation/quaternion quantization; this gait-local rule does not widen other checks. Standalone transform output is transactional too: per-clip success lines and the write summary remain buffered until every selected clip and the artifact write succeed. This admits tightly closed cyclic pelvis/root sway while refusing authored travel and turns. Root-motion phase offsets, root-motion extraction, and trajectory-preserving cyclic rebasing remain runtime or separately designed operations; gait anchoring must not improvise them. - Not a runtime. It models how engines sample animation; it does not play games.
2. Users & use cases
- Artist inner loop —
animsmith lint export.fbxseconds after a DCC export, before any engine import or bake. Catches "the loop pops", "wrong rig", "cm instead of m" while the DCC session is still open. This is the highest-value loop: the alternative is discovering the problem after the slowest step of the pipeline. - CI gate —
animsmith lint --format jsonin CI on committed assets; stable JSON schema, exit codes, per-check severity config, baseline file for adopting teams with a dirty back catalog. - Pipeline library — engine pipelines embed
animsmith-coreand build check sets programmatically. First consumer: the incubating project's asset gate replaces ~1000 LOC of measurement Python with library calls. - PR-review artifact —
animsmith report clip.glb -o report.htmlproduces a single offline HTML file with 3D skeleton playback and metric charts; attach it to a PR or CI artifacts so a reviewer can see the seam pop the numbers describe.
3. CLI surface
animsmith lint <file...> [--config animsmith.toml] [--select ids] [--deny warn] [--format text|json]
animsmith measure <file...> --format json # measurements only, no judgment
animsmith inspect <file> # clips, durations, tracks, bones, detected rig profile
animsmith report <file> -o report.html [--clip name]
animsmith report <before> -o comparison.html --compare-after <after> --before-clip <name> --after-clip <name>
animsmith transform <file> -o <out.glb> [--clip name] [--slice START:END] [--hold-extend SECONDS] [--gait-anchor] [--drop-duplicate-loop-endpoint] [--prune-constant-tracks]
animsmith fix <file> (-o <out.glb>|--in-place|--dry-run) [--repair id[,id]]
animsmith convert <in.fbx|in.glb|in.gltf> -o <out.glb> [--material-texture-recipe recipe.toml] [--animation-only|--bake-static-mesh-transforms] [--format text|json]
animsmith assemble <recipe.toml> -o <out.glb> --evidence <out.json>
animsmith diff <A> <B> [--format text|json] # A/B: assets or one-file output-v13 measure/lint JSON
-
lint= measure + judge against config.measureis lint minus judgment — both emit the independently versioned measurement contract that other pipelines can pin. -
Exit codes:
0no failing findings and no required-unavailable engine prediction facets (warnings, notes, and ordinary nonblocking coverage gaps may remain),1at least one error-severity finding, anyrequired_prediction_unavailablefacet, pending repairs underfix --dry-run, or any producer refusal established as a fact about source asset bytes,2operator/tool error (unreadable declared file, bad config or recipe syntax, unsafe/unwritable path, or publication failure).--deny-warningspromotes warnings to errors; severity and--allowdo not suppress an emitted required-unavailable facet. -
Producer outcome authority is typed before prose exists.
convertandassemblecarryPublished,Rejected, andOperatorthrough one shared boundary. Loader, selector, transform, proof, and representability failures reached after source bytes were read are asset refusals; invocation, recipe-validation, path, file-read, temporary-file, serialization, and publication failures are operator errors. Mixed error enums (notably the material-texture recipe) are matched by variant at the call site. No exit decision parses an error string. JSON refusals use the separately immutableurn:animsmith:schema:producer-refusal:1record; existing conversion, assembly, output, and scale evidence identities remain unchanged. Text refusals are one escaped stderr diagnostic carrying the same stable kind. Every refusal precedes publication, preserving a prior assembly pair and a prior convert artifact; this does not claim that convert's direct writer can roll back an operator I/O failure. -
Stdout is reporting, not outcome authority. Every JSON, text, and Markdown stdout write, including parser-rendered help and version text, uses a fallible checked write followed by a best-effort checked stderr diagnosis. A closed pipe or other write failure never panics or replaces the outcome already established by a stdout-bearing path: shipped success paths retain
0, while finding and refusal paths retain1. Operator errors occur before stdout reporting, remain stderr-only, and retain2. This deliberately includes output-centric commands such asinspect: exit0states that inspection completed, not that an external consumer accepted every byte. Assigning a new outcome to text alone would make exit semantics depend on presentation format and would relabel real findings or completed publication as operator errors. JSON serialization failure remains an operator error because it means no truthful record could be formed; failure to deliver already-rendered bytes is only a reporting failure. Multi-part human output is attempted as one checked stream where one command can produce multiple records; in particular, all selectedfixrepair reports and all parts of one conversion summary yield at most one stdout-failure diagnostic. Parser output remains clap-owned: the checked boundary calls clap's fallible writer directly so its configured automatic/forced ANSI styling is preserved instead of being stripped while materializing a plain string. -
Human-readable command results are assembled by pure functions in the CLI's renderer module. Command dispatch keeps execution, file writes, and exit policy in
main.rsand passes structured values to the renderer; the renderer is the single owner of escaping untrusted text for terminal-safe presentation. JSON result serialization remains a separate, unchanged path. -
transform --prune-constant-tracksis an opt-in, candidate-first mechanical edit: it shares theconstant-tracktolerances (vector components1e-4, sign-invariant rotations1e-3radians), verifies the resulting sampled local TRS and model-space position/rotation, and reports every original track index it removed or retained. It runs after selected transforms and never substitutes for DCC curve cleanup or key reduction. Per-clipanimates_bonesnames protect motion evidence;[rig] required_bonesremains a skeleton-presence-only contract. -
Inputs:
.glb,.gltf(+ external buffers),.fbx(via thefbxfeature, default-on in the released binary). -
Malformation policy: structural malformation — keyframe/value count mismatch, zero-key channels, absolute or escaping external buffer URIs, non-forest node graphs (cycles or a node with two parents), or a primitive accessor whose dense or sparse byte extent its declared buffer view or resolved buffer bytes cannot satisfy, an integer
TEXCOORD_0orWEIGHTS_0accessor that omitsnormalized: true, or an animation sampler accessor whose declared element does not match its property-selected reader — is rejected at load. Forconvertandassemble, a rejection derived from readable source bytes is an asset refusal (exit 1); an unreadable primary file or a missing/unreadable declared external resource is still an operator error (exit 2). Commands outside that producer boundary retain their historical operator-error load policy. Run glTF-Validator for structural details. Recovering a non-forest graph would force an arbitrary parent choice or silently drop a cyclic subtree, so the loader rejects rather than repairs (decision recorded for #92). Likewise, substituting an empty position/index vector for a short accessor would make checks report on geometry the file did not author. Primitive shortfalls therefore use a locatedPrimitiveAccessorLayoutrefusal; inverse-bind shortfalls remain typed source evidence because that domain already models unavailable bind declarations (decision recorded for #329). Sampler input isSCALAR/FLOAT; translation and scale output areVEC3/FLOAT; rotation output retains all five glTF quaternion encodings (BYTE,UNSIGNED_BYTE,SHORT,UNSIGNED_SHORT,FLOAT) asVEC4, and morph-weight output retains those same component encodings asSCALAR. Declared lookalikes are never reinterpreted merely because their byte size happens to match the reader's element (decision recorded for #327). The same fail-closed rule keeps invariant-9 honest for integer UVs and weights: normalizedUNSIGNED_BYTE/UNSIGNED_SHORTvalues are decoded to their declared float range, andFLOATvalues pass through, but an unnormalized integer accessor is refused before decoding. Thegltfreader rescales those integers whether or not the flag is set, and the loader has no finding channel in which to disclose that unauthorized reinterpretation; allowing it would makemeasurereport derived values as authored data (decision recorded for #328). Semantic defects — NaN times or values, non-unit quaternions, hemisphere flips, seam pops — load fine and are judged by the checks; sampling is panic-free under them by construction. -
fixintentionally requires either-o/--outputor--in-placefor writes;--dry-runis the check mode — it inspects only and exits1when repairs are pending, mirroringlint. Repairs are addressed by stable ids; every repair must be safe, lossless, and idempotent. Repair taxonomy (risk-tier groups) is deliberately deferred until a repair exists that doesn't meet that bar. -
fixstays byte-surgical — a product requirement, not an accident (decision recorded for #33). It patches only the offending animation bytes in the original container and copies everything else through verbatim, so meshes, skins, materials, and textures survive a repair bit-for-bit. Folding hemisphere/norm repair into a coretransformand re-emitting through the unifiedDocumentwriter was considered and rejected: the model writer re-emits and reorders accessors, so it is not byte-identical and would rewrite bytesfixmust leave untouched. TheDocumentround-trip is the right tool forconvert/transform; in-placefixis not a round-trip. -
convertandassembleare compiled only with thefbxfeature.--no-default-featuresremains a glTF-only pure-Rust CLI with validation, transform, fix, and diff commands intact;reportis controlled separately by thereportfeature. -
convert --bake-static-mesh-transformsis an opt-in static geometry operation. It accumulates rest hierarchy transforms into positions and inverse-transpose normalized normals, then writes canonical identity-root geometry. It retains topology, UVs, and the model-supported material and embedded base-color, normal, metallic-roughness, and occlusion-texture data. It fails closed for any animation track, skin signal, uninstanced or shared mesh definition, malformed/non-finite data, singular or near-singular transform, or reflection; it neither bakes skinning nor guesses animated or reflected semantics. It conflicts with--animation-only, leaves default conversion unchanged, and is deterministic for repeated same-platform input/options. -
convert --material-texture-recipesupplies exact named BaseColor, normal, metallic-roughness, and occlusion mappings for a conversion. It conflicts with--animation-only, is compatible with static baking, and leaves the ordinary linked/embedded texture path untouched when omitted. Its versioned recipe and provenance contracts pin path containment, image processing, and encoder behavior. -
assembleis the versioned generic boundary for a skinned base plus clips spread across separate files or master timelines. It permits only explicit, mechanically verifiable operations: exact-name skeleton remap, exact mesh selection, clip slicing/endpoint/hold/gait operations, named channel removal, rest-track completion, quaternion cleanup, opt-in constant-track pruning after all other transforms while protecting effective-clipanimates_bonesnames, fail-closed exact-name node-subtree removal after animation transforms, bind-consistent skin canonicalization, material recipes, and deterministic GLB/evidence emission. Node-subtree removal refuses any surviving track, mesh-instance, skin-joint, or complete source-skin reference; it remaps the surviving hierarchy but does not reparent descendants or garbage-collect mesh, material, or texture resources. Character-assembly recipe/evidence v3 introduced that node-removal contract; v4 retains it unchanged while adding optional rest/bind scale integration. Selectors resolve by exact unique name against the post-canonicalization base; their descendant union is planned before completion, excluded from completion targets, and projected after clip processing and constant-track pruning.animates_bonesmay therefore retain a protected track and force refusal, while[rig] required_bonesremains lint presence policy rather than an assembly selector. Accepted non-empty projection clears optional source-native skeleton identity, because it cannot remain a complete account after authored deletion. Because mesh-bearing nodes are refused, removal cannot newly orphan asset resources; only the earlier explicit mesh-instance selection performs resource pruning. Evidence lists every removed node once in original parent-before-child order, distinguishes direct selectors from descendants, and retains original node and parent indices. Archive extraction, gameplay naming and acceptance policy, cache/generation policy, and publication remain with the consumer.
4. Repository & crate layout
One public repo, one cargo workspace, five published crates (plus one
publish = false dev crate, animsmith-testkit). The split is driven by
two hard constraints: the core must be consumable with zero C compilation
and minimal deps; FBX support pulls in a C build step most library
consumers must never pay for.
animsmith/
├── Cargo.toml # workspace, edition 2024
├── LICENSE-MIT / LICENSE-APACHE
├── THIRD-PARTY.md # ufbx (MIT OR PDDL-1.0), vendored viewer assets
├── crates/
│ ├── animsmith-core/ # data model, sampling/FK, metrics, diffs, checks, config, findings
│ ├── animsmith-gltf/ # glTF/GLB → core model; GLB writer for `convert`
│ ├── animsmith-fbx/ # ufbx wrapper → core model; isolates the C build
│ ├── animsmith-report/ # self-contained HTML report generation
│ ├── animsmith/ # CLI binary (features: fbx, report — default on)
│ └── animsmith-testkit/ # publish=false: fixture builders, documentation-source readers, and CLI process helpers shared by tests + the example generators
├── assets/viewer/ # viewer JS/CSS, inlined via include_str!
├── fuzz/ # cargo-fuzz targets for the untrusted-input loaders
└── testdata/ # CC0 rigs + procedurally corrupted fixtures
- animsmith-core: deps
glam(the de-facto Rust game-math crate — do not hand-roll mat4/quat as the Python did),serde,serde_json,serde_jcsfor strict bounded core-owned RFC 8785 canonical JSON contracts,sha2, andthiserror. Its JCS use remains engine-, format-, filesystem-, and network-agnostic. It owns strict versioned JSON contract validation but no file-format knowledge or filesystem/network I/O. This is what embedding pipelines link. - animsmith-gltf: the
gltfcrate with trimmed features (no image decoding); owns GLB emission viagltf-json. - animsmith-fbx:
ufbx(official bindings, v0.11.x, actively maintained; bundles the single-file C library viacc— no system deps). A separate crate rather than a feature flag so the C toolchain requirement is structurally isolated. - animsmith (CLI):
clap,serde_json,toml.--no-default-featuresyields a pure-Rust glTF-only build. - Toolchain: stable Rust, edition 2024, MSRV pinned in CI. License: MIT OR Apache-2.0. All crate names verified free on crates.io (2026-07-03).
- fuzz/: a nightly-only cargo-fuzz workspace (detached from the main
workspace) with libFuzzer targets for the three entry points that ingest
untrusted files —
animsmith_gltf::load,FixSession::apply_to_path(..., Repair::QuatFlip), andanimsmith_fbx::load. These are the executable check on invariant-1 ("untrusted input must never panic or OOM"): targets run in release mode to match the shipped CLI's panic semantics, with AddressSanitizer on. A weekly CI job (fuzz.yml) runs each target for 60s off a checked-in seed corpus; minimized crashers are committed underfuzz/seeds/as regression fixtures, each also pinned by a unit test inanimsmith-gltf's hardening suite. Continuous/long-running fuzzing (OSS-Fuzz) is deferred.
5. Core data model
Two representations of a loaded file, because checks genuinely need both:
Raw layer — what the file says. The document keeps the skeleton hierarchy
and rest pose, authored clips and tracks, optional scene assets, and source
metadata together. Mechanical checks use this layer for defects such as NaNs,
quaternion flips, key density, and constant tracks. Exact Rust types and fields
belong to the animsmith-core model rustdocs; build them with just doc or use
the package README's stable docs.rs link.
assets (meshes, skins, PBR materials, and embedded base-color, normal,
metallic-roughness, and occlusion textures) is the scene-asset half of
the document. Both the FBX and glTF loaders populate it from a single
load (there is no separate assets-carrying entry point — the two
loaders share one shape); it is empty when the input carries no scene
assets. The check catalog ignores it — checks judge animation — but it
rides the one load/write round-trip, so transform and convert
preserve geometry rather than silently dropping it, and measure
reports versioned static scene measurements from it (#16): geometry-definition
boxes in primitive coordinates, node-instance boxes in default/rest node-world
coordinates, and per-scene unions. These are not runtime-visible bounds:
animation, skin deformation, morph deformation, and runtime placement are
excluded. The output contract records unavailable static node bounds and scene
partial coverage rather than serializing non-finite values.
The glTF loader also retains metadata-only presence for secondary
JOINTS_n/WEIGHTS_n attributes. The measurement contract aggregates those
independent sides per mesh definition so unsupported or unpaired sets are
observable without changing the primary four-influence semantics. Secondary
per-vertex payloads, repair, and writer preservation remain outside the core
skinning model.
The scene-asset model keeps source mesh definitions separate from their
mesh-bearing node instances and retains declared scene roots plus the optional
default-scene index. That identity is measurement evidence: duplicate names do
not collapse, shared definitions are reported once, and nodes outside every
declared scene remain observable. The current glTF writer still normalizes an
emitted document to its existing single generated scene; preserving authored
multi-scene membership across transform/convert is not part of the static
measurement contract.
validate_document_shape is the model-owned snapshot check for the structural
invariants full-shape strict operations such as scale share: finite
parent-before-child rest topology, source identity/projection topology,
clip/track storage, and scene instance/inverse-bind references. Document
remains publicly mutable, so a
successful check is not a durable token; strict operations revalidate every
input at every public boundary. This is deliberately not universal model
validity. Measurement can retain finite instance evidence from hierarchy
branches unrelated to a malformed chain, sampling remains panic-free and
tolerant of hostile standalone tracks, and static baking validates only the
fields its static operation consumes. Scale's
negative-weight, finite-position, capability, closure, affine, parity, and
proof rules remain operation-specific.
Ingestion is triangle-list only — the target inputs are skinned game
rigs, and the model and writer carry no primitive-topology field. A
non-TRIANGLES glTF primitive (points, lines, strips, fans) is skipped
rather than misread as a triangle list; other topologies and their
retriangulation are out of scope for now.
Sampled layer — what a game runtime sees. A PoseGrid built by a
ClipSampler: uniform time grid over [0, duration] (resolution = max
channel key count, or explicit fps), glTF-spec interpolation semantics
(lerp for T/S, shortest-path slerp for R with negation on dot < 0, STEP
hold, cubic-spline Hermite), clamp at ends. For clips declared loop,
the wrap pair is (last frame, frame 0) — the seam definition every loop
check shares. FK accumulates every skeleton node's local TRS to model
space, including each root node's own transform. Metrics that need a
body-relative frame derive it from resolved roles such as hips and feet.
The metric grid is computed once per clip and shared across checks,
measurements, and reports through the lazy MetricGrids owner.
Rig profiles — checks reference semantic roles rather than bone names.
Profiles bind those roles to source-rig names; the exact public role set,
binding types, and matcher fallback belong to the animsmith-core profile
rustdocs.
Built-in profiles ship for mixamo (mixamorig:Hips…), ue-mannequin
(pelvis, foot_l…), and humanoid (humanoid_ Pelvis,
humanoid_ L Foot…), plus auto-detection that scores every profile by
resolved-role coverage and reports the winner in inspect. A check whose
required roles don't resolve records a typed coverage gap — never a false
finding. This is the single design rule that makes the tool useful outside
its birthplace: tolerance data and bone names are config; the math is not.
The runner owns selection, activation, and severity policy. Each check exposes
a cheap applicability predicate and one evaluation method. The evaluation
returns content findings separately from completed scopes and typed coverage
gaps. Severity overrides therefore cannot turn missing evidence into a false
error. gait-group, for example, can complete member-existence validation
while reporting phase coherence as a gap. severity = "off" disables a check
without hiding its applicability record. A check may also declare an opt-in
default; assigning "note", "warn", or "error" enables it while retaining the
same independent configuration record.
The built-in evidence-code declarations are the single authority for each scope or gap code's machine identity, meaning, and allowed emitting check ids. The evaluation boundary rejects a built-in code emitted by an undeclared check, and the output-reference gate derives its exact inventory, meanings, and emitters from those same declarations. Embedded checks remain open-ended: they use namespaced custom codes rather than extending a closed enum.
Checks implement one trait and emit structured findings:
#![allow(unused)] fn main() { pub trait Check { fn id(&self) -> &'static str; // "loop-seam", "quat-flip", … fn enabled_by_default(&self) -> bool; // true unless policy is opt-in fn applicability(&self, ctx: &CheckCtx) -> Applicability; fn evaluate(&self, ctx: &CheckCtx) -> CheckOutput; } pub struct Finding { pub check_id: &'static str, pub severity: Severity, // Note | Warning | Error pub clip: Option<String>, pub bone: Option<String>, pub time_s: Option<f32>, pub measured: Option<Value>, pub expected: Option<Value>, pub message: String, } }
The structured fields (not just a message string) are what make diff,
the JSON schema, and the HTML report cheap.
6. Check catalog
Tiers are shipping priority. "Prior art" = a proven implementation exists in the incubating project's pipeline to port, with real-world numbers to golden-test against.
P0 — mechanical + the two killer semantic checks (v0 core)
| id | what it checks | needs | config | prior art |
|---|---|---|---|---|
nan | NaN/Inf in key times or values | raw | — | new (trivial) |
time-monotonic | non-increasing/duplicate key times; first key ≫ 0 | raw | epsilon | new |
quat-norm | rotation keys with |q|−1 beyond tolerance | raw | eps (1e-3) | new |
quat-flip | adjacent keys with dot < 0 (long-way slerp in engines that don't neighborhood-correct) | raw | severity | new |
duration-sanity | zero/degenerate duration; channels within one clip ending at different times; frame count non-integral at declared fps | raw + meta | expected fps list, pinned duration | reference contract duration_s pin |
scale-keys | interpolation-aware temporal scale variation beyond tolerance | raw trajectory | severity | new |
non-uniform-scale | component inequality anywhere on the interpolation-aware scale trajectory | raw trajectory | severity | new |
constant-nonunit-scale | constant non-unit scale channel or single-key pin | raw trajectory | opt-in severity (off by default) | new |
constant-track | redundant multi-key representation whose interpolation-aware trajectory never moves | raw trajectory | eps | new |
frozen-bone | required bone's max angular deviation from first frame below floor | grid + roles/meta | min_rotation_deg | reference contract rotation floor + measured rotation ranges |
duplicate-loop-endpoint | strict authored-key duplicate closing endpoint in a declared loop; warning/default-on | common finite strictly increasing timeline, valid cardinality, matching endpoints, interior motion + loop declaration | — | mechanical subset of #22's endpoint classifier plus open-cycle transform |
loop-closure | maximum per-bone model-space last→first position and shortest-path rotation delta | grid + loop declaration | global max_position_delta_m / max_rotation_delta_deg; clip max_loop_position_delta_m / max_loop_rotation_delta_deg | consumer-neutral authoring-loop requirement |
loop-seam | last→first position wrap discontinuity of feet-relative-to-hips, normalized by the local neighbour per-frame step, with a stride floor so stationary clips skip | grid + Hips/feet/toe roles | max_ratio, min_stride_step_m | locomotion_metrics.py — port verbatim |
loop-seam-vel | maximum per-bone model-space difference between the in-clip velocities entering and leaving the wrap | grid + loop declaration | global max_velocity_delta_mps; clip max_loop_velocity_delta_mps | consumer-neutral authoring-loop requirement |
loop-seam-rot | maximum per-bone shortest-path model-space angular-velocity difference between the in-clip steps entering and leaving the wrap | grid + loop declaration | max_angular_velocity_delta_degps | consumer-neutral authoring-loop requirement |
root-motion-speed | horizontal root/hips displacement ÷ duration vs declared speed_mps; flags stray speed pins on non-locomotion clips | grid + Root/Hips | pinned speed + tolerance (reference gate: 15%) | reference bake |
missing-bones | declared-required animated bones absent; tracks targeting nodes outside the skeleton | raw + meta | bone/role list | reference contract animates_bones |
required-bones | declared required rig bones absent from the skeleton; static sockets and IK targets are valid | skeleton + rig config | [rig] required_bones | consumer-neutral structural rig contract |
naming | clip names vs convention pattern | meta | regex/glob | new |
units-sanity | hips rest height wildly outside human scale (the cm-vs-m export classic) | skeleton + profile | height band | new |
P1 — locomotion semantics (the first-of-kind tier)
| id | what it checks | prior art |
|---|---|---|
gait-phase / gait-group | stride-phase anchor from the fundamental-harmonic trough of the left-minus-right foot-height signal; an exactly zero-amplitude signal has no phase subject, while positive signals are governed by the configured inclusive lr_amplitude confidence floor; circular phase spread across a declared clip ring (directional-blend coherence) | reference metrics module + gait-group contract — port verbatim |
in-place | classify in-place vs root-motion (net + per-frame root displacement) and compare against the clip's declared expectation | new; trivial on the grid |
foot-slide | detect stance (foot height + near-zero vertical velocity), measure horizontal foot velocity during stance in the travel-cancelled frame | new; hardest check — ships opt-in until corpus-tuned |
bind-pose | rest pose vs first frame delta (clip authored against wrong bind); T-pose/A-pose classification; node-TRS rest disagreeing with IBM-derived rest (the disagreement is itself a finding) | reference sidecar already derives rest from IBMs |
axis-conventions | character forward/up at rest vs declared axes; root orientation drift over a loop | reference contract axis vocabulary |
key-density | keys/sec far above the clip fps (unbaked-curve bloat) or far below (starved track) | new |
P2 — corpus/cross-clip
Cross-clip skeleton & rest-pose consistency across a directory;
ground-penetration of feet/toes; mirrored-pair symmetry (walk_left vs
walk_right); additive-clip suitability; compression-noise metrics
(per-track jerk); morph-target weight ranges; SARIF output.
7. Configuration
TOML (animsmith.toml, or --config); Rust-ecosystem norm and
diff-friendly in asset repos:
[rig]
profile = "mixamo" # or "auto", or an inline role map:
# A presence-only structural contract; these do not need animation tracks.
required_bones = ["root", "weapon_socket", "ik_hand_l"]
# [rig.roles]
# hips = "humanoid_ Pelvis"
# left_foot = "humanoid_ L Foot"
[checks.loop-seam]
severity = "error" # off | note | warn | error
max_ratio = 1.5
[checks.loop-closure]
max_position_delta_m = 0.01
max_rotation_delta_deg = 1.0
[checks.loop-seam-vel]
max_velocity_delta_mps = 0.1
[checks.loop-seam-rot]
max_angular_velocity_delta_degps = 5.0
[checks.quat-flip]
severity = "warn"
[clips."run_*"] # glob; exact > glob, later entries win ties
loop = true
movement_owner_xz = "animation"
movement_owner_y = "gameplay"
movement_owner_yaw = "animation"
max_loop_position_delta_m = 0.04 # finite per-family override; global is fallback
max_loop_rotation_delta_deg = 2.0
max_loop_velocity_delta_mps = 0.2
speed_mps = { value = 3.1, tolerance = 0.25 }
fps = 30
[clips.run_forward] # exact fields overlay matching globs
max_loop_rotation_delta_deg = 0.5
[gait_groups.run-ring]
clips = ["run_forward", "run_back", "run_left", "run_right"]
max_gait_phase_spread = 0.08
min_lr_amplitude_m = 0.05
CLI flags override file config (--select, --allow, --deny).
Engine-agnosticism rule: the TOML file is merely one constructor of
a CheckSet. Embedding pipelines build check sets
programmatically through the library API and keep their own contract
formats, hashing, and tolerance semantics on their side. animsmith never
learns an embedder's contract schema.
8. Output formats
- Text (default): findings grouped per clip, measured-vs-expected on
one line, colored;
--quietfor CI summaries. - JSON (
--format json): current output v19, identified byurn:animsmith:schema:output:19. Lint emits one result per catalog check and represents selection, configuration, applicability, evaluation coverage, content findings, completed scopes, and typed gaps independently. Measure and lint share a nested, independently versioned measurement contract. The current contract inventories glTF material definitions, the five corebase_color,normal,metallic_roughness,occlusion, andemissivetexture slots, texture-to-image identity, and decoded image metadata in source order. Complete coverage is scoped to that documented core domain; extension-defined texture slots are not implied. Measurements-v17 makes per-bone loop-continuity unavailability explicit without suppressing finite sibling rows. It retains measurements-v16's inventories each mesh's primitives in source order: nullable source material, total and finite decodedPOSITIONrows, and finite-only primitive AABB and centroid. Indexed geometry counts each stored position once rather than index references. Unsupported nonempty images may carry at most 16 bytes of lowercaseleading_magic_hex; it is evidence only and never a guessed container format. The contract makes absent loader support explicit as unavailable coverage and separates a source-declared MIME type from byte-detected container and decoded image facts. This remains measurement evidence, not an image acceptance, repair, resize, transcode, color-space, writer-preservation, or recipe-authority policy. Measurements v15 adds the canonical set of surviving non-empty local TRS channels keyed by skeleton index, plus engine-neutral sampled Root/Hips trajectory evidence. Root is selected whenever resolved; Hips is only a fallback when Root is unresolved. Translation and yaw retain independent availability, with endpoint X/Z/Y displacement, sampled horizontal travel and vertical extrema, and fixed-basis net/unwrapped/travel yaw in normalized right-handed +Y-up metre space. These are shared uniform-grid regression observations, not continuous-curve extrema or transform-correctness proof. Output v10 added the registry-independent engine-prediction provenance and scoped-facet substrate described in Appendix E. Output v11 retained it and added per-role resolution outcome and match-policy provenance. Output v12 introduced bounded V2 provenance/settings/prediction evidence and catalog-allocated facet summaries; output v17 retained that evidence while advancing its nested measurements to v16 and adding V6 root-motion provenance. Output v19 retains the immutable prediction graphs against a fail-closed measurements-v16 projection while publishing the complete measurements-v18 evidence. Output-v9, measurements-v15, output-v12, and collection-output-v4 remain immutable historical contracts. Measurements v14 gives every clip fact that is not applicable to every clip (loop continuity, loop endpoint mode, frame grid, loop seam ratio, gait and its phase, and root-motion speed) a required sibling<field>_availabilitystatus ofmeasured,not_applicable, orunavailable, so a consumer can distinguish a legitimately missing subject from an applicable metric whose derivation failed and must fail closed. It also retains v13's exact raw inverse-bind matrices and affine and conditioning evidence before deriving their inverses, and v12's corrected linear-transform observations through shared f64 affine facts; v13 and earlier contracts remain immutable historical evidence.convert --format jsoninstead emits the separately versionedurn:animsmith:schema:conversion-evidence:2producer-evidence contract: requested options, written-artifact counts, optional static mesh bake entries with source/output identities and applied world transforms, and deterministic material-texture recipe provenance when requested. v1 remains historical; the current CLI emits v2 exclusively. CLI exit status derives from content severity (warnings block only with--deny-warnings) plus required-unavailable engine-prediction facets; ordinary coverage gaps remain nonblocking evidence. The current output-v19 envelope types and immutable identities live inanimsmith-coreso CLI and embedded producers serialize the same reporting contract. Static-bake evidence is also a public core type; the conversion envelope remains a CLI producer contract. - Future serializers: no game-industry standard exists for skeletal animation lint results. Keep native JSON as the source of truth, then add serializers where downstream tools expect them: SARIF for code scanning, GitLab Code Quality/CodeClimate for MR widgets, JUnit XML for CI test dashboards, and CSV/HTML for humans.
diff A B: compares measurement maps per metric with fixed public per-metric significance thresholds, independent of configured lint caps; prints deltas; exits 1 on significant movement. Primary use: "did this DCC re-export change anything that matters?"
9. HTML report (the visual preview)
animsmith report clip.glb -o report.html → one self-contained offline
HTML file (CI-artifact- and PR-attachment-friendly; no CDN, no install).
The explicit comparison spelling is a separate presentation producer: it preflights named/indexed skeleton correspondence, selected metric-grid work, and input text before either side's checks run; writes atomically only to an output distinct from both inputs; and uses one shared coordinate/path scale. Both judged poses are drawn through that one camera: side by side by default, and at the reader's option overlaid in a single pane with the after skeleton dashed over the before one at the same two selected frames. The overlay is a second reading of the embedded grids — no second copy of the pose data, no refit, and no new authority. Its phase synchronization labels both source times and never claims a retime. Findings, coverage, prediction provenance, and immutable identities remain side-specific, so a missing finding cannot read as acceptance.
Key design choice: no three.js, no <model-viewer>.
<model-viewer>can play a GLB but exposes no skeleton/per-frame API — it cannot draw bone lines, foot trails, or sync to charts. Fails the requirement outright.- three.js (~650KB inlined + GLTFLoader) works, but it would re-sample the animation in JS, and its loop/slerp behavior may disagree subtly with what the linter measured — the preview could contradict the findings it illustrates. It is also an update treadmill.
- The decisive observation: the report never needs to sample animation
in JS. The Rust side already computed the
PoseGrid— model-space joint positions for every frame the checks judged. Embed that.
The source viewer is a hand-written WebGL2 skeleton renderer:
bones as line segments, joint dots, root-motion and foot trails, orbit
camera, play/scrub transport. It renders exactly the frames the checks
measured — when loop-seam flags the wrap at frame N, the viewer scrubs
to that frame N. Determinism is the feature.
- Embedded data: pose grids as base64 Float32Array in
<script type="application/json">blocks (~60 bones × 3 floats × 300 frames ≈ 290KB base64 per clip; f16 quantization is the escape hatch if reports grow). The source GLB embedded once as a download button. - Charts: Rust-generated inline SVG — root-motion top-down path, foot
heights, L−R gait signal with the fitted fundamental, per-bone
seam-delta bars — with a small shared JS playhead syncing a cursor line
across all charts and the 3D view. A declared
gait_groupsblend ring adds one cross-clip figure per group: every member's L−R foot height on one unshifted normalized source-phase axis, each measured stride anchor marked at its own phase, and the group'smax_gait_phase_spreaddrawn as a band around their circular mean. The members are never aligned to their anchors, because that would erase the disagreement the figure is evidence for, and no member is nominated as the reference the others are judged against — the check judges the set's spread and names none. - Findings panel: each finding links to its clip + time; clicking scrubs the viewer.
- Two clips side by side: the
withcontrol (and thewith=NAMEfragment key) plays a second clip of the same document beside the selected one, as two scissored halves of the one canvas drawn through one camera fitted to both clips' bounds. The transport drives the selected clip, and every half — its pose, its chart playhead and its path dot — is placed at the frame nearest that phase in its own grid, with both source times labelled. Both viewers place their two timelines through that one shared mapping and label it with one shared disclosure sentence. Like the comparison's shared phase it is a presentation mapping between two timelines, not a retime: the source panes select the samples the checks judged and never resample authored clip time. - Illustrative local blend: an optional third pane in the single-document report interpolates dense Rust-sampled local TRS, then runs full affine FK. Its continuous weight uses T/S lerp and shortest-hemisphere quaternion nlerp in binary64; exact endpoint weights reuse the source positions. A private binary64 Rust reference supplies synthetic conformance fixtures. No authored key sampler, rest-pose duplicate, all-pairs table, core measurement or engine runtime claim is introduced. The existing shared camera/orbit/zoom remains; interior poses may extend beyond its initial source bounds. The added local authority has a 32 MiB aggregate raw-byte cap plus clip, bone and metadata limits. Aggregate excess omits all local streams; invalid clips omit their own stream. Neither changes source findings, source playback or the public infallible render API. Evidence-only embeds neither positions nor locals. The output guide owns the detailed math, limits, omission and presentation contract.
- A skinned-mesh view (vendored three.js,
--report full) is a P2 option the crate layout leaves room for; it is presentation polish, not v1.
10. FBX ingestion (animsmith-fbx + convert)
- Library: the official
ufbxRust bindings (v0.11.x, actively maintained; the same C foundation the incubating pipeline already trusted, so behavior is already trusted in the incubating pipeline). - Normalization at load via
LoadOpts: target axes = glTF convention (right-handed, +Y up, −Z forward),target_unit_meters = 1.0(FBX defaults to cm), transform-adjust space conversion (don't rewrite geometry), helper-node handling for 3ds Max geometric transforms. The core model only ever sees glTF-convention data regardless of source. - Animation extraction uses ufbx's
bake_anim— it evaluates anim stacks/layers, cubic/TCB curves, pre/post-rotation, and inherit-scale modes into resampled TRS keys (rate from the FBX TimeMode, overridable). Each anim stack (take) becomes one coreClip. This sidesteps the entire FBX-curve-semantics swamp. - Scale inventory without enablement: the same successful parse produces
a deterministic ufbx-side status for every current Appendix D.4 domain and a
documented normalized source-node/source-skin projection when every declared
skin slot can be represented. A missing cluster bone downgrades that generic
projection to unavailable instead of silently dropping the slot. It
explicitly names coordinate/inherit normalization, baked and lost curve state, generated
normals/helper nodes, cluster-derived bind provenance, four-influence
truncation/renormalization, rejected negative/non-finite/unrepresentable
influences, triangulation/welding, omitted face/edge payloads, uninstanced
mesh definitions, unsupported source data, and ufbx source identities. Only
successfully projected cluster binds count as bone-convenience overwrites.
One unreadable cluster makes its ordered bind declaration
atomically unreadable; finite prefixes are not retained under shifted slots.
Stackless authored curves, point/line faces, and attached zero-face mesh
definitions are present-but-unsupported rather than absent or rebuilt; the
zero-face definitions retain an exact count and stable source identity.
Retained normalized meshes keep one definition per stable ufbx mesh identity;
shared source geometry produces multiple node instances that reference the
same compact normalized definition. They keep their stable ufbx typed
identity so source-skin attachment joins cannot shift
when an earlier source mesh emits no primitive.
Its core capability projection remains unsupported for ordinary operations;
narrow FBX
rest-bindinstead stages and proves a private GLB under the dedicated v5 contract.whole-documentremains unsupported, and no raw FBX span or artifact-preservation claim is made. convertemits glTF 2.0 GLB: nodes + skin (computed IBMs) + one animation per clip with at least one writable track; mesh + weights when present;--animation-onlyto strip mesh. The glTF writer returns counts derived from the emitted artifact, which bothconvertandtransformuse for their summaries and to report source clips omitted because they have no writable tracks. Explicitly not an art exporter — no material fidelity promise. Bothconvertandtransformshare oneload→writeround-trip overDocument(assets included), so geometry survives a transform pass and--animation-onlyclears it uniformly across input formats (it is the only lever that drops geometry).--bake-static-mesh-transformsis the separate opt-in path for static, unskinned, singly-instanced geometry: it removes the effective rest hierarchy transform by baking positions and normals under an identity root and reports each applied transform in conversion evidence. Unsupported animated, skinned, malformed/non-finite, singular/near-singular, or reflected input is rejected before output.- FBX pitfalls double as checks when linting
.fbxdirectly: source unit ≠ 1m (warn even though we convert), Z-up source, namespace-prefixed bone names (profile matcher strips), default "Take 001" naming (feedsnaming), baked-key explosion (feedskey-density), non-uniform inherited scale.
11. Roadmap
- M0 — walking skeleton. Repo bootstrap: workspace, dual license, CI
(fmt/clippy/test on Linux/macOS/Windows), CC0 test fixtures. Core model
- sampler/FK;
animsmith-gltf;inspectandmeasure --format json; the mechanical P0 checks (nan→constant-track).
- sampler/FK;
- M1 — reference parity. Rig profiles, TOML config, per-clip
expectations; port
loop-seam,frozen-bone,root-motion-speed,gait-phase/gait-group— golden-tested against the reference implementation's verified production numbers;lintwith exit codes + stable JSON; adopt the reference project's mutation-test discipline (corrupt one field, assert the finding names exactly that field). the incubating project's measure port lands here: its sidecar tool becomes a thin wrapper overanimsmith-core+animsmith-gltf(the embedder keeps its sidecar schema and hashing);locomotion_metrics.pyand the animation half ofmeasured_sidecar.pyare deleted. - M2 — report, FBX, diff → v0.1.0 on crates.io. HTML report; FBX
ingestion +
convert;diff. The v0.1 bar for "usable by an unaffiliated team": README quickstart works on a raw Mixamo-style GLB with zero config (profile auto-detect), built-inmixamo+ue-mannequinprofiles, sampleanimsmith.toml, versioned JSON schema doc, no incubator vocabulary anywhere in the public API. - M3 — the hard semantics.
foot-slide(stance detection),in-place,bind-pose/axis-conventions, rotational/velocity loop seams,--deny-warnings, baseline/suppression file for teams adopting with a dirty back catalog.
12. Risks & open questions
- Foot-contact detection robustness (M3) is the only research-grade
item — thresholds vary with rig scale and style. Ship opt-in at
warnuntil tuned on a corpus. - Test-asset licensing: Mixamo clips cannot be redistributed. Use CC0 rigged clips (Quaternius/KayKit/self-authored) + procedurally corrupted fixtures; budget M0 time for this.
- Sampling-semantics fidelity: engines differ subtly (cubic handling, sub-frame wrap). The doc pins the model — "glTF-spec interpolation on a uniform grid, wrap = (last, 0)" — and exposes grid fps as config, accepting it is a model of runtimes, not all of them.
- ufbx 0.x churn and thin docs.rs coverage: pin exact versions,
treat the C library's docs as canonical, keep
animsmith-fbxthin and swappable. - Rest-pose truth: node TRS and inverse-bind matrices can disagree.
Rule: IBM-derived rest is authoritative when a skin exists, node TRS
otherwise, and disagreement beyond tolerance is a
bind-posefinding. - Report size on long/many clips: f16 quantization + per-clip lazy JSON blocks if it bites.
- Scope pressure toward transformation will come ("you detected the
seam pop — just fix it"). The linter-first line is the identity; only
convert,transform, and mechanical/losslessfixoperations may mutate data.
Appendix A — naming decision record
Two decisions, both 2026-07-03 (the project was renamed the same day it was built, before anything was published):
-
animlintwas chosen first, on a linter-first scope with clip transformation explicitly out of scope. Rejected then:gltf-lint(glTF is the carrier, not the domain; permanently confusable with Khronos glTF-Validator's spec-conformance role; wrong the moment FBX input landed),animkit/clipkit/clipforge(existing projects),gaitkeeper(minor collisions; overemphasizes locomotion). -
Renamed to
animsmiththe same day, when transformation became first-class: the incubating pipeline needs hemisphere normalization, frame-range slicing, hold-extend, and gait-anchor rotation sooner than later, and its archived-FBX2glTF conversion step wants a maintained replacement. The naming record had said the lint-first name "only breaks if clip transformation becomes first-class" — that fork flipped, so the name followed before the first crates.io publish made it permanent.animsmithand all sibling crate names verified free on crates.io with zero GitHub repository hits. Lint remains the flagship subcommand.
Appendix B — prior-art map (first consumer)
The measurement algorithms, config vocabulary, and testing discipline
were extracted from a private game project's asset pipeline — the
uniform-grid sampler, the local-neighbour loop-seam denominator with
its stride floor, the L−R fundamental-harmonic gait anchor, the
root-motion speed gate, the Pinned{value, tolerance} expectation
shape, the Finding/severity/exit-code conventions, and the
mutation-test style (corrupt one field, assert the finding names it)
are all faithful ports, golden-tested against that pipeline's shipped
numbers. Its measurement scripts are deleted as the project migrates
onto the animsmith library — the standing proof that the public API is
sufficient for a real bake pipeline.
Appendix C — result-contract ownership decisions
These decisions record result-contract ownership after output v3 finalization:
-
The versioned envelope belongs to
animsmith-core, not the CLI binary. The CLI still supplies its build identity and frontend policy, butMeasurementContract, the command-specificMeasureFileReportandLintFileReportrecords, command-specific envelopes, summaries, and URNs are shared library types. This lets embedded producers emit the exact schema without copying private structs or protocol strings while making an invalid measure/lint record shape unrepresentable. -
Nested findings keep their
check_id. AFindingis also a standalone embedded result and the record consumed by text, Markdown, and HTML presentations. Keeping it self-describing avoids a second wire-only finding projection and supports extracting findings without retaining their parent check record. Output v3 therefore accepts the small nested redundancy, andevaluate_checksrejects a child id that disagrees with its parent rather than serializing ambiguous ownership. -
Read-side validation is consumer-neutral.
animsmith-corevalidates output and nested measurement identities and recovers every file's display path plus complete clip and mesh measurements in source order. It does not impose a file-count rule or prescribe CLI remediation. Thedifffrontend owns its single-file policy and operator guidance; embedded consumers may accept empty or multi-file reports according to their own workflows.
Appendix D — decision record: skinned rest/bind scale canonicalization
Status (2026-08-20): implemented for self-contained glTF/GLB, the narrow
FBX rest/bind re-encode path, and character assembly recipe/evidence v7. The shared
core plan, typed ledger, exact-source writers, independent core and artifact
proofs, atomic CLI publisher, and immutable scale-evidence v4 producer are the
current contract. Character assembly v7 can opt into rest/bind scale for
glTF/GLB and inventory-complete FBX inputs with an exact root name and factor
after validating a versioned basis for the base and every separate clip;
recipe/evidence v1 through v6 remain unchanged.
Whole-document conversion supports
raw glTF POSITION morph deltas while preserving static JSON weights as
numeric values and animated weight accessor payloads byte-exactly; other morph
semantics and every rest/bind morph payload remain refused. FBX
whole-document scaling remains deferred.
D.1 Problem and two distinct operations
A skinned mesh can already have the intended physical dimensions while its
joint hierarchy carries a compensating uniform scale. For example, a root can
contribute 0.01, descendant translations can be authored 100 times larger,
and inverse-bind matrices can carry a compensating linear magnitude of 100.
Skin deformation is then visually correct, but a rigid object parented to a
joint inherits the unwanted 0.01 scale.
That case must not be confused with a document whose entire linear unit is wrong. The following are separate, explicitly selected operations:
- Whole-document linear-unit conversion changes physical size. The caller
declares a finite factor
q > 0; every represented length is converted byq. It is appropriate only when the source was authored in a different linear unit. - Rest/bind hierarchy reparameterization preserves already-correct world geometry. It removes one compensating inherited scale from a restricted skinned hierarchy, rebases local rest and animation translations, and regenerates inverse binds.
Neither operation may infer its factor or applicability from mesh bounds, character height, joint lengths, inverse-bind magnitude, filename, or an asset category. Measurements from #267 are diagnostic evidence, not conversion authority. A caller must name the operation and declare or accept the exact factor that the transform plan validates.
The declared unit-conversion transform is a finite positive scalar only; it is not an axis, rotation, translation, shear, or reflection operation. The initial reparameterization source class is finite, orientation-preserving, positive uniform rest-world scale. Non-uniform scale, shear, reflection, singular or near-singular matrices, non-finite values, and mixed effective factors in the selected domain fail before any output is written.
Classification and proof share one versioned tolerance policy and compute in
f64, narrowing only at the writer model boundary. The current policy identity
is appendix-d-v6. The three finite widened axis lengths are sorted in
ascending order before their arithmetic mean is accumulated and divided by
three. This canonical association rule makes the common factor independent of
authored axis order, including when the sum lies on an equal-axis boundary.
Relative orthogonality, equal-axis, and common-factor tolerance is 1e-5; an
axis is unequal when
abs(length - average) > 1e-5 * max(average, length), relative to the longer
of the two and to nothing else; a determinant is singular when
abs(det) <= 1e-6 * product(axis_lengths); scalar/vector comparison uses
abs_error <= 1e-6 + 1e-5 * max(abs(before), abs(after)); and shortest-path
rotation residual is at most 1e-5 radians. Every bound is inclusive: a
residual exactly equal to its bound is accepted. Exact float equality is
forbidden. The implementation records every threshold and observed maximum
residual in evidence, so noisy values such as 100.000015 can be accepted for
an explicit, reviewable reason rather than by implementation accident.
This is a pre-1.0 policy-identity cutover: the former public
ScaleTolerancePolicy::APPENDIX_D_V5 associated constant is removed rather
than retained as an alias for the new axis-order-independent meaning. Evidence
records therefore use appendix-d-v6. The historical v5 calibration and
revision history live in the
scale proof calibration notes; the recurrence v5
introduced and v6 retains is specified below as part of the current policy.
The f32-rounding term, per obligation. The scalar band above is stated
relative to the quantity being compared. For five of proof's obligations
that quantity is not the magnitude the arithmetic ran on, and a rotation can
separate the two without limit: a bound component near zero on a mesh four
thousand units across, a near-identity W * B whose translation column is the
difference of two terms of magnitude abs(W), or a world translation whose
parent chain cancelled two terms larger still. The compared number is
then small and the absolute rounding error it carries is large, so a purely
relative band derives the tolerance from the small number and the error from
the big one — and plan_scale accepts a plan whose candidate prove_scale
then refuses. Sweeping a rotating rig at a declared factor of 3190, that
happened on 86 % of rotations before this policy, with residuals up to
9.8e-4 against a 6.1e-5 band.
appendix-d-v6 therefore retains the further quantity f32_rounding_ulps = 4, and the five obligations that compare f32-rounded arithmetic use
abs_error <= 1e-6 + 1e-5 * max(abs(before), abs(after))
+ f32_rounding_ulps * magnitude * 2^-23
where magnitude is named per obligation below. The added term is absolute
in magnitude, never relative to the compared quantity, which is what makes
it incapable of loosening a comparison whose operands already are its own
magnitude: there it contributes 4 * 2^-23 = 4.77e-7 of them, twenty times
below the 1e-5 the relative band already allows. The 2^-23 is
f32::EPSILON, because every one of these residuals is the difference of two
f32 quantities even though the subtraction itself is done in f64.
-
Skinned bounds takes
magnitudeas the largest per-vertex arithmetic provenance over every contributing vertex — read off the candidate, and maxed against the source's own rebased by the conversion factor:candidate.max(q * source), for the reason given after this list. Each influence has three stages whose cancellation a later stage cannot recover:- the magnitude the
W * B * ptransform runs on, which ismax over i of sum over k of abs((W * B)_ik) * abs(p_k)withpextended by the homogeneous1thattransform_point3sums the translation column in with — the productabs(W * B) * abs(p)and not either factor alone — because the weighted sum over slots that follows can cancel those terms against each other; - the magnitude the contributing slot's
W * Bcomposition ran on, which covers the cancellation that composition performs; and - the magnitude that slot's joint world translation was itself accumulated from along its parent chain, which covers the cancellation performed one composition earlier.
For one influence, the base is the maximum of those three stages. A vertex combines those influence bases with the same binary64 weighted average of the stored non-negative binary32 weights as its skinned point:
sum(w_i * base_i) / sum(w_i). The accumulator then takes the maximum vertex base. All three stages are load-bearing, and each dominates the others by an unbounded ratio on some rig. The skinned points alone miss a joint placed far from the geometry it carries — a1000-unit local translation under a3190root puts the joint3.2e6from the origin while its vertices skin to within one unit of it, and the bound inherits1.6e-1of error from a9.7e-1extreme. Dropping the slot-composition stage makes the calibration sweep refuse correct candidates in fifty-six of its seventy-two pre-v4 cells, at up to62.6ulps of the base that remains; dropping the transform stage outright refuses in thirty-four, and narrowing it toabs(p)alone refuses in fifteen at up to47.7.Stage 1 has been wrong twice, in the same place. It first read the skinned point's magnitude and claimed that covered the transform; the correction read the vertex position
abs(p)and claimed the same. Both name one term of a product of two, and each was exact only on the rigs that happened to be built.- The skinned point coincides with the transform only while no two slots
oppose. Two slots whose composed
W * Bdiffer by a half turn send a vertex1000units out to a blended origin, so the result reads0while every term summed to produce it carried that vertex's own ulp; put the joints near the origin as well and stages 2 and 3 read1. The residual is then6.1e-5— one ulp of1000— against a1.5e-6band, a demand of503ulps, rising to65_527atabs(p) = 1e5. abs(p)coincides with the transform only whileabs(W * B)is1, and the one rig that reached the cancellation composedW * Bfrom a pure rotation, where it is1by construction. Give those same two slots a uniform scale ofkand the sum still cancels while each term carries the rounding of ak * abs(p)-magnitude transform: atk = 1024andabs(p) = 65536the residual is4.0against a0.0313band, a demand of512ulps, and the rig refuses frommin(k, abs(p)) > 8upward with the shortfall growing asmin(k, abs(p)).
Both need only that the bind pose is not the rest pose, which is ordinary content: with
W * Bthe identity on every slot there is nothing for a blend to cancel and nothing but1forabs(W * B)to be, which is why a section's worth of fixtures built on analytic inverse binds could express neither case.two_slots_whose_composed_binds_cancel_a_vertex_still_prove_its_boundspins the cancellation andtwo_slots_with_a_scaled_composition_cancel_a_vertex_and_still_prove_its_boundspins the second factor; both build their binds independently of the rest world for that reason, and the calibration sweep carries composed slots atabs(W * B) = {1e-3, 1, 1e3}so a third factor of the same kind is visible to a measurement rather than only to a reviewer.The blended point is not another stage. Shared scale-input validation refuses every finite negative primary skin weight before planning. The implementation widens the stored binary32 weights, transformed-point numerator, and denominator to binary64, divides once, and narrows the final point to binary32. This prevents a lone subnormal contribution from disappearing before division, several large finite weights from overflowing their denominator, and rounded binary32 coefficients whose sum exceeds one from overflowing an otherwise finite convex blend at
f32::MAX. Bounds residuals are compared per axis, so each blended component is already bounded by the same binary64 weighted transform operands. v3's L2 length could be up tosqrt(3)larger while naming no additional per-axis arithmetic; v4 removes it.This validation decision removes two formerly unbounded affine cases. With weights
1.0and-0.99999, a positive near-zero denominator could amplify opposed transformed points to1.997e8; with identical transformed points, both numerator and denominator could cancel while the accumulation error was still divided by the near-zero sum. No finite ULP count could cover the latter generally. The two #336 counterexample rigs remain as fixtures and pinNegativeSkinWeightat the shared planning boundary instead of forcing tolerance machinery to describe invalid input.Weighting the provenance is equally load-bearing in the other direction. A stage at magnitude
1e20with weight1e-20contributes about1, not1e20; an unweighted max would let an influence with essentially no effect buy the whole distant stage's tolerance. Separate named fixtures isolate the transform and slot-composition halves, a proof-level fixture measures the old and v4 detection floors, and the calibration has an explicit profile that gives each vertex's larger production influence base the smaller weight.The proof-level floor is an adjacent-binary32 bracket on the same candidate-only weight mutation in both policies. A frozen v3 evaluator first refuses at candidate weight bits
0x3a7a1be4(the preceding0x3a7a1be3is accepted), where the Bounds residual is9.5320709005312e13. v4 searches both directions around the authored1e-20weight; its smaller refused residual is2.4400651454925537e-6, with the lower bracket0x1e3c6f0arefused /0x1e3c6f0baccepted and the upper bracket0x1e3d5b21accepted /0x1e3d5b22refused. That is a3.906e19xrecovery in this adversarial rig. It is a measured detection floor for this fixture, not a universal precision claim, and the exact bits and residuals are pinned in the test.magnitudeis deliberately not read off the bound corner being compared. A per-axis extreme is contributed by whichever vertex happened to be furthest along that axis, so three vertices at(3000, .001, .002),(.001, 3000, .003)and(.002, .003, 3000)build a corner of magnitude2.4e-3out of vertices of magnitude3000. A corner-derived band is a million times tighter than the error the corner carries, and — read the other way, as a base for the relative term — a corner-derived tolerance admits4.2e-2on a component of magnitude5.98, which is0.71 %relative error and no longer a rounding allowance at all. - the magnitude the
-
The skin equation and the inverse binds of an unaffected instance take
magnitudeas the largest quantity any entry of the compared product was summed from:max over (i, j) of sum over k of abs(a_ik) * abs(b_kj). ForW * Bthat is6.4e3on the rig above, not the near-identity product's1.0: the residual there is1.8e-4against the1.1e-5band the product magnitude buys, a16xshortfall, and2.50ulps of the operand magnitude after it. The skin equation maxes that against the same parent-chain magnitude skinned bounds takes, becauseabs(W) * abs(B)reads the already composedWand so cannot see terms the chain that produced it already cancelled. A joint whose local offset points back along its parent's world translation leavesabs(W) * abs(B)at1.0while the residual it must admit is6.25e-2—524288ulps of that base, and0.08of the chain's. The skin equation reads that quantity off the candidate and maxes it against the source's rebased by the factor, exactly as skinned bounds does, and for the same reason. It is also notmatrix_magnitude(a) * matrix_magnitude(b), which replaces the sum overkwith a product of two independent maxima and reads7.6e6where the arithmetic ran on6.4e3— a tolerance from that would accept a matrix that is entirely wrong, and the over-acceptance calibration fixture refuses exactly that case.For an unaffected instance's binds the two operands are the effective matrix and the declared factor, and scaling a column cancels nothing, so the term is inert there: a correctly built candidate leaves the effective bind unchanged and therefore has exactly zero residual. It is stated rather than omitted so that the policy quantity means one thing across every obligation that compares
f32matrices. -
Rest translation and sampled trajectory compare a node's world translation between the two documents, and take
magnitudeas that node's parent-chain magnitude alone — the same quantity the two above max against, read off the rest pose and off the sampled pose respectively. There is no product to sum here: the comparison is between two composed world translations, and the only way one of them can be small while its error is large is the chain cancellation.The root carries zero provenance because its world is a copied local. For child
i, with parent worldW_p, local matrixL_i, and inherited provenanceC_parent, the shipped binary64 recurrence iss_i,r = sum over k in {0,1,2} f64(abs(W_p[r,k])) * f64(abs(L_i[k,3])) p_i,r = f64(abs(W_p[r,3])) m_i,r = s_i,r + min(max(p_i,r, f32::MIN_POSITIVE), s_i,r / 2^-23) m_i = max over spatial rows r of m_i,r C_i = C_parent + m_iThe first term provisions the local three-product dot. The second provisions the final addition by paying the smaller of one ulp at the carried parent's scale and the whole new contribution being absorbed. The minimum-normal floor covers subnormal product rounding without imposing a fixed charge when
s_i,r = 0. Only the three spatial output rows participate; affine row 3 is the exact homogeneous coordinate. Rest and sampled pose construction share this recurrence. Its calibration populations and revision history are recorded separately in the scale proof calibration notes. The policy-neutral matrix rewrite, residual, and provenance kernels live incrates/animsmith-core/src/scale/numeric.rs; operation selection, tolerance decisions, reference expectations, connector products, and proof-owned expected values remain outside that leaf.This scalar is not a universal componentwise forward-error proof: it does not separately propagate error in the inherited linear block before that block acts on a later translation. The shipped claim is this additive recurrence and its checked empirical envelope, not an unbounded-depth analytic guarantee. At the later fixed stages, SkinMatrix maxes the accumulated chain against the
W * Boperand magnitude and Bounds feeds that slot base through the weight-proportional blend described above. A fully analytic matrix-error propagation would be a different model and policy contract.The chain is the candidate's, not the source's and not the max of the two. Whole-document conversion scales every translation by the factor and leaves every linear part alone, so the two documents' chains are that factor apart — subject to the candidate's
f32narrowing of the factor, since the build scales byfactor as f32while the proof rebases by thef64factor, a relative difference of at most2^-24(1.49e-8atq = 0.1, whosef32is0.10000000149011612) that this very rounding term covers many times over — and the source's rounding is rebased by the same factor before it is compared, so the residual scales with the candidate's chain at either end of the factor range, and under rest/bind the two chains are equal outright. A max over the two sides is therefore never needed, and under a shrinking conversion it is strictly worse: at a factor of0.01the source chain is100xthe candidate's, so the max freezes the band at the source rig's size while the candidate it is spent on keeps shrinking, and the smallest genuinely wrong candidate either obligation refuses stops tracking the factor at all. On the cancelling-chain rig that costs89xof discriminating power at0.01and648xat1e-4.a_shrinking_conversion_holds_rest_translation_to_the_candidate_s_own_chainanda_shrinking_conversion_holds_trajectory_to_the_candidate_s_own_chainpin the tightening; the two..._holds_..._to_the_candidate_sidefixtures pin the growing direction, where the candidate's chain is also the larger.
Transform-only affine is not in this rounding class. It compares a probe point transformed through the complete expected and actual world affines as a vector L2 residual against a vector-magnitude base, so its comparison base is already the magnitude its arithmetic ran on and it takes no rounding term.
Overflow is not NaN. A skinned position that leaves the f32 range is
reported as InvalidSkinnedPrimitive { reason: "skinned_magnitude_overflow" },
separately from the "non_finite_result" reported for NaN. Both fail closed,
and neither has a fixed preflight magnitude limit: skinning accumulates a dot
product per axis, so overflow depends on orientation and operand magnitudes,
not only on the magnitude of the result.
The magnitude calculations follow the same finite-domain rule. Bounds are
compared per axis and never square a finite blended point into an L2 length
that could overflow above sqrt(f32::MAX). The parent-chain
abs(W_parent) * abs(t_local) sums are accumulated in binary64. Composition
and column operand magnitudes are summed in f32 lanes and recomputed in
binary64 whenever that sum leaves the f32 range, so an operand magnitude is
never reported as inf while the composed result remains finite. These are
current appendix-d-v6 proof and refusal semantics, not calibration
measurements.
Why the source side is rebased. Every residual above is |a - q * b| for
the candidate quantity a and the source quantity b, so the source operand
enters the comparison multiplied by the factor and its rounding is multiplied
by the factor with it: a source quantity accurate to k ulps of its own
magnitude contributes q * k ulps of that magnitude to the residual. A base
that reads the source side unrebased — which max(source, candidate) did for
skinned bounds and the skin equation — therefore states the source's error in
the wrong units by a factor of q. Under a shrinking conversion that is the
whole defect, and it is loose by exactly 1/q: the band freezes at the source
rig's size while the candidate it is spent on keeps shrinking. Measured on the
far-joint rig, the smallest inverse-bind shift the skin equation refuses goes
from 1.9e-3 under the max to 1.3e-5 at 0.01 and 1.3e-7 at 1e-4; the
smallest bounds error refused goes from 4.8e-1 to 4.8e-3 and 4.9e-5.
a_shrinking_conversion_rebases_the_skin_matrix_magnitude_by_the_factor and
a_shrinking_conversion_rebases_the_bounds_magnitude_by_the_factor pin both.
Unlike the parent chain this does not reduce to the candidate's magnitude
alone, because these two magnitudes are products: only the terms carrying a
translation are a factor apart, and both sides retain an unscaled O(1) floor
from the composition's linear block and from the exact 1.0 the homogeneous
row contributes to every chain. Where that floor dominates the source,
q * source exceeds the candidate's magnitude under a growing conversion by up
to the whole factor. Production retains the max because it is the operand
bound justified by the comparison; no smaller candidate-only or min base is
part of the policy contract.
An earlier calibration tried to classify those smaller bases as equivalent by
dividing an obligation-wide maximum residual by a separately selected maximum
slot base. Those maxima can belong to different slots: after calibration began
recording each production comparison's own residual and provenance, the
shipped SkinMatrix demand was 2.213 while that mispairing still printed
2.084. The alternative-base figures and equivalence claim were removed
rather than preserved as false mutation evidence.
Calibration evidence. The checked-in release calibration, its deterministic populations, current reference demand tables, historical policy comparisons, and machine-local cost measurements live in the separate scale proof calibration notes. Those notes measure this policy; the normative thresholds, provenance definitions, and refusal boundaries remain in this decision record.
Normative residual norm for the unit-scale postcondition. The postcondition
"unit composed scale for every affected node" is measured per axis, as
max(abs(scale_axis - 1)) over the three axes — an L-infinity norm, not an L2
norm over the three axes together. This is normative. Per-axis is what the
claim means, it is how the equal-axis check already measures, and it is the
same dimensionless relative quantity the scalar common-factor check compares,
so the input band and the postcondition are directly commensurable.
How the common-factor band relates to it. The common-factor band is the
normative input contract, and the unit-scale postcondition bound is derived
from it rather than declared independently. For an affected node i with
observed rest-world factor s_i, a domain common factor s_0, and a declared
factor s_declared, the candidate's composed scale on axis k of node i is
axis_ik / s_declared. Three independently bounded relative comparisons stand
between those two numbers, and an affected chain composes all three:
- the declared-factor match binds
s_0tos_declaredwithinc = 1e-5; - the mixed-factor check binds every other affected node's
s_itos_0within the samec; and - the equal-axis check binds each individual axis length
axis_iktos_iwithin the samec.
The third band is the easiest to lose sight of and cannot be dropped: s_i is
the average of node i's three rest-world axis lengths, while the
postcondition measures an individual axis, so an axis is permitted a further
band away from the very number the mixed-factor check compared. A rig whose
root is off-factor, whose leaf is off-common-factor, and whose leaf axes are
non-uniform within the equal-axis band loads all three at once.
Each comparison is stated relative to max of its operands, so each
contributes at most c / (1 - c) when re-expressed relative to the smaller
one, and the composed worst case is (1 - c)^-3 - 1 = 3.00006e-5. The policy
therefore sets the postcondition bound to four common-factor bands — 4e-5 —
rounded up to the next power of two, 2^-14 = 6.103515625e-5. Three of those
bands are the analytic composition above; the fourth is headroom for the f32
world-matrix composition and decomposition that produces the measured composed
scale. 2^-14 is also 512 * 2^-23, and so lies on the binary32 mantissa grid
the measurement lives on, which is what makes the inclusive "at most" above
reachable rather than vacuous for this obligation.
Three bands rounded up (2^-15 = 3.0517578125e-5) would leave that analytic
worst case only four binary32 ulps of room — 5.17e-7, or 4.34 * 2^-23.
That is a rounding artefact, not headroom, and it would make the reserved
float-headroom band above a claim rather than a fact. The fourth band buys the
claim honestly, and it does not blunt the obligation: every build defect this
postcondition exists to catch — a dropped rebase, a factor applied twice, a
stale no-op candidate — is at least 1e-3, so 6.1e-5 still leaves better
than a 16x detection margin.
No floor on the comparison base. The common-factor and mixed-factor
comparisons are abs(a - b) <= c * max(abs(a), abs(b)), with nothing else
inside the max. Flooring that base — at 1.0, at the 1e-6 absolute scalar
term, or at anything else — turns the band into an absolute tolerance below the
floor, which is a relative band of floor * c / abs(s) that widens without
limit as the declared factor shrinks. It therefore breaks the closure property
below floor * c / 2^-14; at a 1e-6 floor that is
1e-11 * 16384 = 1.6384e-7 (the crossing point is inversely proportional to
the postcondition bound, so the 2^-15 bound an earlier revision declared put
it at twice that, 3.2768e-7), and at a declared factor of 1e-9 such a band
admits 1e-2 relative error, 1000x the declared policy. The degenerate
a == b == 0 case needs no floor either: it compares 0 <= 0.
What the derivation guarantees is the closure property: any plan the
common-factor band accepts yields a candidate whose every affected node
satisfies the unit-scale postcondition — at every declared factor the policy
admits, not merely near unit magnitude — with a full band to spare
(6.1035e-5 - 3.00006e-5 = 3.1035e-5). The guarantee is stated for the single
closed affected domain this record admits — one common factor, one declared
factor, hence exactly the three composed bands above. Admitting a second
independently classified factor in one plan would compose a fourth band and
require a new policy identity, not a wider bound at this one.
Sampling budget. The policy also bounds the total sampled proof work a
document may demand, as sample_time_count * per_sample_work_units against a
budget of 4e8 work units. The per-sample cost is every pass the sampled
obligations actually make, and each is charged once per document side, since
proof walks the source and the candidate:
2 * bone_count
+ sum over affected skinned instances of
[skin_and_bounds] *
(3 * len(skin_joints)
+ 2 * (vertices over every primitive of its mesh))
Every sampled obligation poses both skeletons, hence the bone term. Only the
source bone count is measured, which is sound because proof rejects a
candidate whose bone count differs (bone_count_mismatch) before it charges
anything: the candidate document is caller-supplied, so an unchecked candidate
skeleton is proof work nothing has billed. The slot term is charged explicitly,
per instance: nothing bounds the instance count, and nothing bounds
skin_joints, which may repeat a joint, so slot work bears no relation to the
bone count and must not be folded into it. The vertex term charges each vertex
once, while the f32-rounding stage of the skinning walk runs once per non-zero
influence. A four-influence vertex therefore performs that stage four times for
the same vertex charge; the budget is the fixed released work proxy calibrated
for the documented populations, not an exact instruction counter.
sample_time_count counts key times
and cubic-segment interior times, because both are evaluated. Exceeding the
budget is a typed refusal raised before the first sample time is evaluated;
proof never silently samples a subset. The comparison is >, so the budget is
a ceiling a document may reach, matching the inclusive "at most" every other
policy quantity is stated with. The budget is part of the policy identity for
the same reason every tolerance is: it is recorded in evidence, and two
evidence records carrying the same policy id must describe the same amount of
checking.
The calibration notes record the historical timing shapes and measurements that established this charge as a linear proxy for real work. Those seconds are machine observations, not a normative wall-time guarantee.
The value has to clear real assets as well as bound bad ones: a 200-bone rig
with a 100k-vertex skinned mesh costs 201_000 units per sample time, so a
30-second clip at 30 fps costs 180_900_000. A budget that refuses that
refuses a plausible production asset, with no way for a caller to opt into the
work.
Historical implementation cost and baseline comparisons are retained in the scale proof calibration notes, not in this normative work-budget definition.
D.2 Algebra and rewrite rules
Let L_i(t) and W_i(t) be node i's local and composed world matrices, G
be a skinned primitive's geometry-bind-to-world matrix, and B_i be its
inverse bind. Valid input satisfies
W_i(rest) * B_i = G
for every joint slot. Matrices below act on column vectors.
Whole-document linear-unit conversion
For the caller-declared factor q, define U = scale(q). Coordinates change
basis as M' = U M U^-1. For a uniform U, this multiplies an affine matrix's
translation by q while leaving its dimensionless linear part unchanged.
Therefore:
- multiply every node-local rest translation by
q; - multiply every animation translation value and both glTF cubic-spline
translation tangents by
q; key times remain seconds; - multiply mesh
POSITIONvalues and morphPOSITIONdeltas byq; - conjugate every per-skin inverse bind as
B_i' = U B_i U^-1; - multiply every supported camera, light, collision, or extension length by
qthrough a field-specific handler; and - leave rotations, normals under positive uniform conversion, UVs, weights, times, morph weights, and rest/animation scale channels unchanged.
glTF LINEAR and STEP samplers have values only; they do not store tangent
elements to rewrite. “Translation tangents” throughout this record therefore
means the explicit in/out elements of a CUBICSPLINE translation sampler.
Root-motion positions and velocities are not separate stored fields: they are
recomputed from the converted translation tracks. Distances and velocities
therefore change by q; durations do not. Animation scale is dimensionless
and is never multiplied as though it were a distance.
This operation preserves topology and semantics while intentionally producing
world positions q times the source positions. It is not a repair for an
already-correct mesh with a compensating skeleton scale.
Rest/bind hierarchy reparameterization
For the initial supported class, every affected joint has a rest-world linear
part within tolerance of s R_i, where s > 0 is one common factor and R_i
is a proper rotation. The affected domain is a closed connected hierarchy:
the scaled ancestor, all selected skin joints and paths between them, and all
descendant nodes whose attachment transform would otherwise inherit s.
A strict source-node connector with no normalized bone may occur on a path
between two projected nodes when its only modeled contribution is its authored
static local rest, which must be finite and affine. Planning composes that
local in source order; the connector does not become an affected normalized
node, its authored local is preserved exactly, and it owns exact-preservation
and projected-successor bridge obligations rather than a numeric residual.
The selected root and every selected skin joint must still project to
normalized bones. An
unprojected descendant or attachment that would itself need rewriting, a
second effective factor, or an instance outside the closure still rejects the
plan.
The compiled rest/bind plan records this source topology once, keyed by raw
source-node identity. A projected row also records its normalized BoneId,
authoritative raw parent, nearest projected parent, role, and the incoming
connector tail from which the ordered span is derived. A connector topology
row records its source and parent identities; field rows state its exact
preservation, and the projected successor identifies ownership of the bridged
rewrite. Derived indexes may accelerate lookup, but they are not additional
topology authorities. This distinction matters to embedders: source-node
indices select and preserve raw structure, while BoneId identifies
normalized rewrite and residual domains.
The same global table also retains OutsideDomain rows, including every
source row for whole-document conversion, so raw parent/projection identity is
never inferred from absence.
For affected node i, define the constant basis correction
C_i = scale(1 / s_i), where the initial contract admits s_i = s inside the
domain and s_i = 1 at its parent boundary. The desired world matrix is
W_i'(t) = W_i(t) * C_i
and the corresponding local matrix is
L_i'(t) = C_parent^-1 * L_i(t) * C_i .
The same rule remains valid across a preserved static connector, but it does
not reduce to skipping that connector or blindly applying the projected
parent's scalar to the next raw local. Let H be the ordered product of
unprojected connector locals between projected parent p and projected node
i, and let L_i be i's authored local after that connector span. Since
H is preserved, the projected successor's authored local must become
L_i' = H^-1 * C_p^-1 * H * L_i * C_i .
Then W_p' * H * L_i' = W_p * H * L_i * C_i, as required. A translated
connector makes the conjugation's additive translation term observable, so a
non-identity connector fixture is required; an identity-only fixture cannot
distinguish composition from omission. This is a static-rest rule only:
SourceNodeAsset carries no raw animation channel for an unprojected row, so
support for a frontend that drops an animated connector requires a separate
source-animation inventory and rewrite contract.
Because every admitted C is a positive uniform scale, the result remains
representable as TRS. On a direct projected edge (H = I) — including the
normalized Bone local into which a loader has already folded a connector —
the node-local translation is multiplied by the parent's s_parent, rotation
is unchanged, and local scale is multiplied by the dimensionless ratio
s_parent / s_i. For a raw projected successor after a preserved connector,
the linear part receives that same ratio but its translation is instead
determined by H^-1 * C_p^-1 * H * L_i * C_i; a translated H contributes
an additive term, so the simple translation multiplier must not be applied to
that raw local. The connector product, inverse, additive term, complete
bridged translation sum, and bridged linear ratio application are evaluated
in f64, then each final source-local component is narrowed once to the
f32 model boundary. Direct projected edges retain their established f32
arithmetic and association. Planning likewise keeps each admitted connector
span widened until its projected endpoint, so a built candidate that retains
Complete source projection remains a consumable factor-one planner input even
when the preserved connector-only product exceeds f32. Translation
animation values and both cubic translation
tangents on normalized nodes receive the existing parent-basis multiplier.
Scale animation on those normalized nodes is the same local-scale component
over time, so every stored VEC3 scale element receives s_parent / s_i too:
the selected closure root receives 1 / s, a strict affected descendant
receives s / s = 1, and an unaffected node receives 1. This is a topology
rule, not a constant-track predicate: LINEAR, STEP, and every value plus
in/out tangent of CUBICSPLINE are rebased. Rotation tracks and key times
remain unchanged.
A node on a direct projected edge that declares matrix rather than TRS
members takes the same rule in component form. Writing L_i' out in glTF's
column-major order, the nine linear entries (0,1,2, 4,5,6, 8,9,10) are
multiplied by s_parent / s_i and the three translation entries
(12, 13, 14) by s_parent. Components
3, 7, 11, 15 are written back exactly as authored, which is the correct
rebase only because the §D.4 gate has already proved them exactly
(0, 0, 0, 1): the basis change multiplies 3, 7, 11 by 1 / s_i, a no-op
on an exact zero and on nothing else, and leaves 15 alone at any value, so
what the gate buys there is not the arithmetic but the premise — a matrix
is the node's whole transform, with no projective divide this record models,
only when 15 is exactly one. §D.3 case 4 records why that gate compares
exactly rather than within tolerance. A raw projected successor after a
preserved connector is governed by the full H product above, not this
component shortcut.
Mesh positions, morph deltas, and normals remain unchanged because their world geometry is already correct. Each inverse bind is regenerated from the unchanged geometry bind:
B_i' = inverse(W_i'(rest)) * G = C_i^-1 * B_i .
Consequently W_i'(t) * B_i' = W_i(t) * B_i: the complete skin palette,
not merely joint origins, is analytically preserved.
Both bone convenience inverse binds and every per-instance skin-slot array must be updated; the single bone-level value is never authority for a multi-skin document. A raw format has one bind store per skin and no per-node bind, so the frontend rewrites each affected skin's stored matrices with per-slot factors and proves through the reloaded document that every per-instance array and bone convenience value is correspondingly correct. The output deliberately changes the affected nodes' composed scale to one while preserving their world translations and orientations, animation trajectories, skinned vertices, and bounds.
Conflicting raw accessor claims reject. Unlike whole-document conversion,
whose converted uses all take the same q, rest/bind derives a typed claim for
every raw accessor use, not only a scale-bearing write. A claim records the
accessor's expected component shape and its multiplier. Translation outputs,
scale outputs, and inverse-bind slots carry their topology- or slot-derived
multiplier. Factor-one preservation claims cover mesh attributes and indices,
sampler inputs, rotation and weights outputs, and sampler outputs that no
channel selects. The latter are still sampler-referenced payloads, not unused
bytes that the raw proof may overlook.
One accessor index must have compatible component shape and one multiplier
across all of those claims. Thus a translation or scale output reached from the
closure root and from a child, from an affected and unaffected node, or an
accessor used both as mesh POSITION and a translation or scale output can
reject; so can a same-index type conflict even when both factors are one. One
inverse-bind store shared by skins whose joints straddle the closure is the
same problem. Splitting the payload is not a remedy, because it changes array
lengths and destroys the identities the proof pins.
Distinct accessor indices are checked too: if their resolved byte ranges
overlap and either claim rewrites bytes, the operation refuses rather than
silently corrupt a preserved neighbour. Direct-image bufferView consumers
are not accessors and have their own separate image-overlap refusal. Within
the source domain admitted by the common conservative capability preflight,
uses whose claims agree impose no additional rest/bind restriction however
many reach one accessor. The preflight can still refuse a
scale-bearing/dimensionless alias or overlapping raw range before this ledger
runs. A declared factor of one makes every rest/bind multiplier one, so this
ledger's factor disagreement cannot fire on a no-op; it does not broaden the
preflight's supported raw domain.
D.3 Worked synthetic cases
The implementation fixtures use literal analytic values rather than values generated by the transform under test.
- Unit skeleton. A root and child both have unit scale, the child rest
translation is
(0, 1, 0), and the IBM is rigid. Reparameterization with an expected factor of one is a deterministic no-op. Requesting any other factor rejects as a source-fact mismatch. A whole-document conversion by0.01instead produces a0.01 mchild offset and mesh while leaving scale channels at one. - Compensated inherited scale. A root has scale
0.01; its child has local translation(0, 100, 0)and local scale one. Their composed child position is(0, 1, 0), every selected joint has effective scale0.01, and the corresponding IBM linear columns have magnitude 100. The reparameterized root and child have unit composed scale, the child local translation is(0, 1, 0), the IBM is rigid, and mesh positions and the child world position stay byte-for-byte or tolerance-equivalent as defined by the proof. A transform-only child at local offset(1, 0, 0)is rebased to(0.01, 0, 0), preserving its world origin while its composed linear scale changes from0.01to one. Transforming a further off-origin point through that child's complete affine distinguishes the result from a no-op. Checking onlyjoint_translation + joint_rotation * offsetis invalid because it drops the very scale channel the fixture must prove. Unskinned geometry in the affected closure is initially rejected rather than silently resized; supporting it would require a separate geometry and instancing rule. - Separate clips. A compatible clip declares the same named parent
topology, rest orientations, helper layout, and
0.01translation basis; its child translation value and cubic tangents are rebased by0.01before remapping. A name-identical clip with a unit parent basis, different parent rotation, different helper layout, or a mixed factor fails compatibility before any keys are copied. - Rejected affines. Independent fixtures introduce
(0.01, 0.02, 0.01)scale, a shear term, a negative determinant, a zero/near-zero determinant,NaN/infinity, and one joint with an effective factor different from the common factor. Each must produce a stable typed rejection, no artifact, and no partial evidence publication. Two kinds of near-valid noise are treated differently, by design. Values that reach the tolerance-bearing affine classification — a100.000015IBM column, or a rest scale axis skewed withinequal_axis— are accepted when their measured residual is inside the declared tolerance, and that residual is recorded. Structural facts checked at the §D.4 capability gate are compared exactly: a nodematrixwhose last row is not exactly(0, 0, 0, 1)is aNonAffineNodeMatrixrefusal, somatrix[15] = 1.0000001is rejected before any tolerance is consulted. The exactness is deliberate on three grounds: it matches glTF-Validator, whoseisTrsDecomposable(lib/src/utils.dart) tests those same four components with exact!=before it applies any tolerance and reportsNODE_MATRIX_NON_TRS; composing two affine matrices produces an exactly zero bottom row in floating point (0*x + 0*y + 0*z + 1*0), so no real composition pipeline emits noise there; and the §D.2 rewrite rules rely on it — the reparameterization's node-matrixrebase writes components3, 7, 11, 15back as authored, so a toleratedmatrix[3] = 1e-12would be published unconverted where the basis change owed it1 / s_i, and a toleratedmatrix[15] = 1.0000001would be rebased as though the node were affine. Relaxing it is a change to the §D.4 contract, not a tolerance widening; §D.1's ban on exact float equality binds the comparisons that tolerance policy governs — classification and proof — not this gate.
D.4 Modeled domains and preservation coverage
The transform plan owns an explicit capability manifest. A field absent from
Document is not evidence that the source lacks that field; preflight must
inspect the raw input or a loader-supplied complete inventory before mutation.
The plan compiles every modeled container into a typed field ledger. Each row
identifies the target and component, then states either a structural semantic
rewrite kind or PreserveExact; absence is not used to mean preservation. A
row does not carry a resolved factor, expected value, or connector product:
candidate construction and proof independently derive those numbers from the
operation and validated topology. PreserveExact is typed builder write-set
ownership: the core builder does not write that field. It is not, by itself, a
bit-equality rule for normalized derived state presented to prove_scale.
Container rows carry the counts and shapes needed to describe every element
without storing one row per vertex, key, tangent, or matrix component. Candidate
construction consumes this ledger, so each authoritative modeled production
write discharges one rewrite row and every rewrite row must be discharged.
Whole-document conversion retains one compatibility-only exception: raw locals
kept beside Unavailable source coverage are still converted best-effort, as
before, but that coverage explicitly makes their identities non-authoritative,
so they produce neither ledger rows nor proof/replay claims. Factor-one rows remain
explicit where identity, aliasing, and write-set coverage depend on them.
Separate typed payload-shape rows retain document, clip, track, instance, mesh,
and primitive identity and cardinality, including empty containers, without
freezing key times or other numeric values. Exact replay therefore detects
added, removed, reordered, or retargeted payload that happens not to add a
field row or obligation.
| Domain | Current model boundary | Unit conversion | Reparameterization |
|---|---|---|---|
| Rest hierarchy | Bone::rest; source-node TRS/matrix and parent topology form a canonical typed source table | Scale translations; conjugate retained matrices; preserve topology explicitly | Rebase affected normalized/projected locals; exactly preserve typed static-connector rows; bridge only the projected successor; preserve orientations and topology explicitly |
| Translation animation | Values and cubic tangent triplets are retained for normalized nodes | Multiply values and both tangents by q | Multiply by the affected parent-basis factor; infer no raw animation support for an unprojected connector |
| Rotation and scale animation | Retained as dimensionless values | Leave unchanged | Rotation unchanged; rebase each scale VEC3 by s_parent / s_i, including cubic tangents |
| Root motion and velocity | Derived from translation tracks | Convert tracks, then recompute | Preserve sampled trajectory and derived velocity |
| Base mesh geometry | Base POSITION and normals are retained | Scale positions; normals unchanged | Leave positions and normals unchanged |
| Morphs | Not retained by the normalized core; raw glTF ownership inventories target accessor identities and weight locations | Raw glTF only: scale dense f32 POSITION deltas once per unique accessor; preserve static JSON weights as numeric values and animated weight accessor payloads byte-exactly; reject other semantics and unsafe aliases/layouts | Reject: rest/bind morph preservation is not proven |
| Skin binds | Per-instance IBMs plus a lossy bone convenience value | Conjugate every per-skin matrix | Regenerate every per-skin matrix from output joints and unchanged G |
| Cameras/lights | Node transform only; typed fields are not modeled | Reject until all length fields have handlers | Reject when attached to the affected domain until preservation is proven |
| Collision/custom data | No semantic model for extras or extensions | Reject unless a registered handler covers every length | Reject when affected unless exact preservation is proven |
| Other vertex/source data | Several attributes, non-triangle modes, and extension payloads are not writer-preserved | Reject on the normalized-model route | Reject on the normalized-model route |
| Out-of-contract node transforms | A matrix beside a TRS member, or a matrix whose last row is not exactly (0, 0, 0, 1) — compared exactly at the gate, never within tolerance (§D.3 case 4) — parses but is not glTF 2.0 | Reject: which transform the author meant is unknowable, and U M U^-1 leaves 3, 7, 11 unconverted where the basis change owes them 1 / q, while a 15 other than one is invariant under the rewrite and so would be published still asserting a projective divide (§D.2) | Reject for the same reason, with 1 / s_i for 1 / q |
| Animation targeting a matrix node | glTF 2.0 requires an animated node to use TRS; a typed reader can otherwise decompose matrix and lose that source distinction | Reject at raw preflight | Reject at raw preflight |
| Shared raw accessor payloads | Every accessor use has a component-shape and typed field disposition; preserved uses remain explicit factor-one claims | Convert once per unique accessor: every converted use takes q | Reject same-index type/factor conflicts and overlapping distinct accessors when either range is rewritten (§D.2) |
| Unreferenced accessor payloads | Retained only in the raw source container, including dense bases and sparse index/value payloads | Preserve exactly; reject when any owned byte span overlaps a rewritten accessor | Preserve exactly; reject when any owned byte span overlaps a rewritten accessor |
| Image payload aliases | An image reads a bufferView directly and never becomes an accessor | Reject when its bytes overlap a rewritten accessor | Reject when its bytes overlap a rewritten accessor |
The narrow normalized FBX bridge applies the collision/custom row at the operation boundary rather than treating every parser-observed declaration as scale-bearing. User-defined properties are explicitly discarded before the private GLB is written, and parser-known texture/video linkage is represented by bounded resource facts and dependency-closure evidence rather than a transform field. The source-aware gate may therefore admit those two known classes while retaining their inventory/evidence rows; rooted capture keeps supported texture assignments in the normalized document and private GLB. The same proof boundary admits exactly ufbx's marker, LOD-group, stereo-camera, camera-switcher, and display-layer typed lists: those records supply no hierarchy transform, skin bind, animation track, or geometry to the normalized bridge. Display layers contain only node membership and editor visibility/freeze/color state. Shader and binding-table records are material-evaluation metadata and are admitted on the same basis. BindPose is different rest/bind-domain evidence: the same-load gate admits it only when it covers every joint of each skin it touches and every converted row is finite, unambiguous, and agrees under the fixed Appendix D scalar tolerance with the converted cluster bind or node rest-world matrix the bridge already consumes. A source without a Pose remains valid; incomplete, ambiguous, non-finite, or mismatching BindPoses fail closed as distinct residual kinds. All admitted rows remain counted in the raw unmodeled-element aggregate. Every residual unmodeled typed list fails closed, and its refusal retains the exact nonzero kind counts. This remains a normalized re-encoding rather than a raw FBX object-property, marker/camera/display-layer behavior, material, or texture-assignment preservation claim. Extensions, incomplete construct/resource coverage, and every unsupported transform, geometry, bind, or animation fact still reject. The inventory-only API remains conservative because its frozen v5 aggregate cannot distinguish these discharged classes from another unmodeled source element.
The connector exception is deliberately narrower than general helper-node support. The source projection can attest an unprojected row's identity, parent, and authored local rest, but not a raw animation channel targeting that row. A frontend that omits such a channel from the normalized model has not preserved it and cannot cite the static connector rule as evidence that it is safe to rewrite.
The current glTF writer rebuilds nodes as TRS, emits only modeled triangle
attributes, creates skin/holder structures, and does not preserve arbitrary
source JSON. The FBX loader mechanically inventories every current row above,
including the unverifiable unreferenced-accessor analogue, and exposes a
documented normalized ufbx source-node/source-skin sidecar when every declared
joint slot is representable. A missing cluster bone downgrades the generic
sidecar to unavailable, and one unreadable converted bind makes its whole
ordered declaration unreadable with no retained prefix. Complete coverage here
means every adjusted/compensated ufbx node and skin has stable identity and
projected values; it does not relabel those values as raw authored FBX
transform or payload evidence. The inventory also records
that it bakes takes, normalizes coordinate and inheritance semantics, generates
some missing data, truncates/renormalizes influences, counts rejected invalid
or unrepresentable influences, triangulates, welds, and cannot prove raw
payload spans. Consequently neither current
load-Document-write route qualifies as a
preservation-proof frontend for these operations without the raw capability
preflight and explicitly bounded writer work. Unknown extensions or extras,
unsupported morph semantics or unsafe morph storage, cameras, lights, collision metadata, non-triangle primitives,
unmodeled vertex attributes, secondary influence payloads, malformed or
missing inverse binds, node transforms outside the glTF 2.0 contract, and image
payloads sharing bytes with a rewritten accessor fail closed. The FBX
inventory is evidence of why the current frontend remains refused, not an
enablement signal. A later FBX producer may enable only an operation whose
complete inventory/write/proof boundary it satisfies; it must state that FBX
curves were baked, not preserved as authored curves.
Source validity is decided once, at the preflight, and not re-derived per operation. The preflight's byte-disjointness inspection therefore ranges every accessor, whether or not a mesh, skin, or animation sampler references it, plus every image view. An unreferenced accessor contributes its dense base when it has one and the packed spans of its sparse indices and values; those bytes are preserved source rather than expendable payload. A referenced sparse accessor is still refused outright before disjointness matters, and extension-defined consumers are unreachable while every extension is itself a refusal. Every unreferenced span may sit adjacent to or alias other preserved data, but any overlap with a rewritten accessor refuses with both owners located. Each operation keeps its own guard as defence in depth, since the guard is what must hold if the gate is ever relaxed, but it shares the gate's classifier rather than re-deriving one.
The core typed ledger is also the semantic authority for both raw glTF scale
writers. Before binding raw fields, the adapter replays the plan's complete
structural inventory against the current normalized document; numeric-only
replay remains valid, while a stale payload, field, topology, or obligation
inventory refuses. One numeric-free glTF binding then projects canonical
source topology and field dispositions onto raw node members, animation
outputs, mesh payloads, and skin slots. The binding independently walks
/nodes/*/children and refuses disagreement with canonical topology before
writing bytes. Writer and artifact proof share those validated identities and
component-group names, but independently select components and derive numeric
multipliers; neither consumes the other's expected values, matrices, factor
helpers, or domain model. The core ledger never replaces raw claims over
scenes, materials, default scene, references, unknown payload, aliasing, byte
ranges, or container bytes.
D.5 Separate-clip compatibility
Exact bone-name matching is necessary but insufficient. Before transforming or remapping a separately supplied clip, the producer compares a versioned skeleton-basis record containing:
- named parent topology and target node paths;
- finite local rest matrices, rest orientations, and helper-node layout;
- declared/normalized coordinate convention;
- effective uniform scale at every translation-track target;
- selected reparameterization domain and factor; and
- loader/tool identity plus the exact input-byte digest.
Compatibility requires topology and orientation agreement within their declared rules and the analytically expected translation-basis relationship. Different rest translation bases, parent scale, parent rotation, helper layout, or effective factor reject before remapping. A digest identifies the evidence record but does not replace tolerance-aware semantic comparison.
Character-assembly recipe/evidence v4 implements the base record and
comparator; v5 composes it with canonicalization, grounding, and node removal.
V6 admits the narrow normalized/baked FBX projection while leaving both older
contracts immutable. It captures the exact base and clip bytes once, performs
the format-specific capability preflight and plan before remapping, and rebases
each compatible clip's translation values and both CUBICSPLINE translation
tangents in the source basis. Evidence pins every input digest and basis
fingerprint together with the compatibility result; v6 additionally binds the
input container, projection kind, full FBX inventory, and private staged-GLB
identity. V1 through v3 remain immutable and v3 rejects the v4 block as unknown.
V7 generalizes the core compatibility basis around selector identity derived
for each accepted plan and source document.
Indexed mode retains v6's exact format-local indices, while named mode compares
the exact resolved root and ordered selected-skin joint names plus semantic
source/helper parent paths. Thus independently captured inputs may use
different source-array indices without weakening topology, rest, orientation,
or factor checks.
D.6 Proof, evidence, publication, and rollback
The candidate is serialized before it is proved, and the proof runs once, on the document reloaded from the exact artifact bytes — never on a separately built in-memory candidate, which would prove a document the rewriter did not emit. That single pass has two layers over the same reload: the normalized-document layer, which produces the residuals below, and the artifact layer, which re-derives directly from the container bytes every claim the normalized model cannot represent. Publication carries the proof to the file by digest, not by a second proof: the bytes are staged, read back, and refused unless their SHA-256 equals that of the proved bytes. A second proof would be redundant, the proof being deterministic over byte-identical input.
The artifact layer's claims are stated over raw source bytes — preservation of every raw JSON location and every buffer byte outside the rewritten set, array and index identity, honest reporting of the rewritten locations, container framing, bounds consistency, and single-step narrowing — and presuppose a rewriter that edits the source container in place. A frontend without one, which rebuilds the file from the normalized model as an FBX writer would, cannot make these claims and is not thereby exempt from the obligation they discharge: that declared-unaffected payloads are unchanged. Such a frontend owes that obligation by the other route — a complete loader-supplied inventory of every §D.4 domain — and may enable an operation only over the domains that inventory covers.
The exact raw-JSON-preservation refusal is diagnostic evidence as well as a refusal: the current scale-evidence v4 record carries up to the first 16 deterministic differences, each as a pointer and an added/removed/changed direction, plus the omitted remainder; the full count is the retained length plus that remainder. The field is null when the exact-preservation walk did not supply locations for a refusal; it is not an alternate spelling of the capability preflight's violations.
For the glTF producers, that raw complement explicitly includes scene
membership, default-scene selection, materials, and their references: neither
scale operation owns a write location in those payloads, so the artifact layer
proves their raw JSON values exactly unchanged. The normalized core proof does
not also deep-compare assets.scenes or assets.materials; those model values
cannot replace the raw fields and references the container proof owns. A
rebuilding frontend therefore cannot cite the core proof for those domains
and must discharge them through the complete inventory route above before
enabling an operation.
The proof evaluates rest, every key time, and analytic or sufficiently bounded interior times for cubic segments. For reparameterization it must prove, within versioned tolerances:
- equal world joint translations and orientations before/after;
- equal sampled animated trajectories, root motion, and derived velocities;
- equal skinned vertex positions and bounds for analytic one-joint and multi-joint fixtures plus deterministic sampled production evidence;
- unchanged mesh/material/skin identity and declared unaffected payloads —
including that each candidate mesh instance draws the same mesh, binds the
same skin joints, hangs off the same node, and names the same source node as
its source counterpart, none of which either operation rewrites, and that a
skin outside the affected closure keeps each slot's effective inverse bind.
Identity here covers placement, not only what an instance is: the node is
what positions an unskinned prop, and the source node is what resolves the
skin whose absent bind accessor stands for the format-defined identity
default. Both are compared positionally, so the instance list must also
agree in length and order. Candidate proof also compares normalized parent
topology and source-projection coverage exactly for every operation. When
coverage is
Complete, the raw-node parent/bone map is exact too; rows underUnavailablecoverage are non-authoritative, produce no raw topology or field rows, and are not compared. Neither operation rewrites any authoritative identity. For rest/bind, the compiled ledger includes anExactUnchangedWorldRestobligation for every bone in the exact unaffected complement and proof compares each complete composed world-rest affine exactly. The discriminating case is an independent sibling or leaf outside the closure's ancestry; an unaffected ancestor was already constrained transitively by an affected descendant's world-rest residual. This is an exact semantic placement invariant, not a tolerance or a claim that equal matrices prove every stored local field was retained; exact local ownership is a separate field-ledger obligation. Whole-document conversion has no unaffected complement, but its typed global topology obligation remains in force. Before either operation builds or proves, it recompiles the supplied source and requires exact equality of the operation variant, canonical source topology, payload and instance inventory, field dispositions, and derived obligation rows. Added, removed, reordered, or retargeted positional payloads (including stable-order source skins, attachments, tracks, meshes, instances, slots, or evidence) therefore make the plan stale even when the former aggregate boolean inventory would have been unchanged. Canonical source-node and connector topology is keyed by raw source identity, so merely reordering its backing rows is not a topology change. Numeric values may still change when that exact inventory and ledger remain equal; replay does not freeze the planned source snapshot. The shared input/candidate checks, source-skin structural rows, canonical source topology and rest/bind domain derivation, world-pose and inverse-bind readers, and one-time affected-skin classification live incrates/animsmith-core/src/scale/validation.rs. Operation selection, both planners, typed field/payload/obligation ledger compilation, clip-evidence declaration, and complete numeric-value-free replay live in privatecrates/animsmith-core/src/scale/planning.rs. Planning consumes the shared validation/topology boundary and remains the single owner of the compiled plan vocabulary; validation does not depend back on planning or reconstruct a second plan vocabulary. The opaque candidate wrapper, feature-gated analytic reference builder, and writer-owned direct/connector source-local rebase arithmetic live in privatecrates/animsmith-core/src/scale/reference.rs. That module consumes the compiled plan and shared validation/numeric leaves; proof derives its own connector products, expected source locals, and scale-track boundary rather than importing the reference writer's expectations. Those proof-owned derivations, exact field discharge, sampled work and budget accounting, semantic residuals, and skin/bounds checks live in privatecrates/animsmith-core/src/scale/proof.rs. Its paired residual recorder is nested underproof::residual, so only proof can mutate a residual maximum and its comparison count; the public read-only values continue to resolve through theanimsmith_core::scalefacade. For an admitted connector span, typed obligations require every connector local to remain bit-exact and independently derive the projected successor's expected bridged source local from canonical source topology. The connector owns no normalized residual, but a writer and proof cannot agree on a wrong connector rebase by sharing the writer's product calculation. Rewritten direct rewritten source-local components compare under theappendix-d-v6scalar tolerance, rather than bit equality: a raw adapter may apply the same factor before its onef32narrowing while the normalized source view was already narrowed. Preserved source-local components retain their exact-bit obligation. A connector-bridged projected successor also remains bit-exact: that core-only connector path does not cross a raw frontend narrowing boundary, and its exact connector-projection contract is unchanged. Operation-fixed rewrite and tolerance policy fields are not source inventory. Numeric affine/factor classification is not repeated there, so proof's normalized-skeleton witness remains independent of planning's raw-projection witness; a structurally stale plan still cannot omit a newly added bone, connector, or payload from every proof walk. The unaffected-bind comparison is over each slot's effective bind, in the resolution order the model defines: per-instance array, then the bone convenience value, then the format-defined identity only when a complete attached source skin declares its inverse-bind accessorAbsent. When both sides resolve, the effective matrices are compared even if their stored representations differ. Exactly one side resolving is missing proof evidence; neither side resolving is reported as out of the proof's scope, never as proven — a document carrying an unrelated skin with no bind evidence and no licensed identity default is not an operation this record touches, and must not be refused for lacking evidence about it; - unit composed scale for every affected node, measured per axis against the derived bound of §D.1; and
- the analytically expected full world affine of a transform-only attached child: preserved origin/orientation and unit linear scale, so a no-op cannot pass.
For whole-document conversion, the corresponding length facts must differ by
exactly the declared factor within tolerance while dimensionless facts remain
equal. The raw glTF artifact proof independently walks admitted morph
POSITION accessors, requires one conversion per unique accessor (including
base/morph and cross-target aliases), compares every static JSON morph-weight
numeric value, and compares every animated weight accessor byte exactly. Both
operations prove finite output, the skin equation, deterministic artifact
bytes, and deterministic evidence bytes.
Every obligation above is derived from the evidence it needs. The typed
ledger contains a row only when the validated planned inventory carries the
payload that row reads — a transform-only attachment in the closure for a
full-affine probe, an affected translation track for key-time checks, an
affected cubic segment for interior samples, any affected track for sampled
trajectories, a skinned instance touching the closure for one shared skinned
evidence group, and a non-empty affected closure for the inseparable
rest/unit-scale group. Skin-matrix and bounds results remain separately
reported projections of that one skinned group; neither can be independently
armed, and unit scale cannot exist without the affected-rest obligation.
A row whose evidence disappears from a replayed source changes the recompiled
ledger and yields typed PlanDocumentMismatch, never a zero residual. A
counterpart or value missing inside an inventory-matched walk is typed
MissingProofEvidence. The residuals are the evidence record, so a record
stating "residual 0.0" for a claim nothing checked is false rather than
missing. This matters most for the transform-only obligation, which is the
claim ensuring a no-op cannot pass — a guarantee an empty probe loop reporting
zero would not provide.
Per-element track values, base mesh positions, and inverse binds outside the
affected closure are ordinary typed obligations rather than three special
ungated loops. Their rows are derived for every corresponding planned
container. For normalized Bone::rest, inverse binds, tracks, and meshes,
proof discharges ownership through the existing versioned residual or exact
semantic obligations, independently deriving rewritten expectations from the
operation. A normalized candidate may therefore be non-bit-identical while
remaining inside the declared tolerance; field-row ownership neither replaces
nor tightens that accepted set. Rewritten and preserved track/mesh comparisons
retain their released maxima and counts. Core bit equality applies only to
authored SourceNodeRest locals that every admitted producer copies, including
preserved connector locals. Exact structural obligations such as topology,
connector projection, and unchanged world rest own no numeric residual. Raw
byte-exact preservation of other fields — including signed zero and quaternion
sign — remains the artifact proof's PR-B responsibility. No separately mutable
boolean controls any of these walks.
Proof cost is bounded, not merely expected to be small. Source and candidate
world matrices are derived once per sample time and shared across the
trajectory, skin, and bounds obligations, and the skin and bounds obligations
share one walk over the affected instances and their vertices. The remaining
work is sample_time_count * per_sample_work_units with the per-sample cost
spelled out in §D.1 — bones, skin slots, and skinned vertices, each charged
once per document side — which the tolerance policy of §D.1 caps. A document
above that cap is refused with a
typed error naming the policy identity, the sample count, the per-sample cost,
the computed work, and the budget. It is never proved against a truncated
sample set: a bounded proof of a subset would publish evidence for claims it
did not check. Because the budget lives in the policy identity, it is not a
per-run flag, and the producer's fixed-policy rule below covers it.
The glTF/GLB producer emits immutable scale-evidence v4 containing the operation kind, declared and observed factors, tolerance policy and residuals, affected node/skin identities, raw capability manifest, input/output byte digests and counts, proof sample coverage/results, tool identity, and rejection reason. It is written as an atomic artifact/evidence publication pair. Failure leaves no new output; an existing pair is restored. The original input is retained so rollback is selecting the prior artifact, not attempting an inverse float rewrite.
Narrow FBX rest/bind path. With the ufbx feature, scale rest-bind may
accept an .fbx input only when its complete inventory proves the normalized
rest/bind subset: adjusted metre/Y-up coordinates, compensated inheritance,
complete finite bind provenance, unchanged four-slot influences, and no
unsupported scale-bearing semantic D.4 row. Same-load source facts may prove
that user-defined properties, bounded texture/video declarations, or exact
marker, LOD-group, stereo-camera, camera-switcher, and display-layer typed
lists are known, non-scale-bearing inputs to this normalized bridge. Display
layers contain only node membership and editor visibility/freeze/color state.
Shader and binding-table metadata is equally non-bearing. BindPose rows are
admitted only after the same-load converted matrices reconcile with the
cluster/node matrices the bridge consumes; no Pose is required. Other unmodeled typed lists remain
fail-closed and are reported by exact nonzero kind counts. The same-load
boundary may also classify an exhaustive set of conversion-fidelity facts as
scale-invariant: omitted authored vertex/face/edge metadata, influence
truncation/rejection/renormalization with complete effective coverage,
triangulation, and exact-bit welding. The frozen inventory continues to report
those losses and its detached projection remains conservative. Missing
effective influences, omitted point/line or mesh definitions, unsupported
skinning/deformers, incomplete binds or normals, and unclassified payload stay
fail-closed. The same-load
FbxScaleSource retains the raw facts and dependency closure, while v5 keeps
the frozen inventory projection rather than adding either sidecar to its wire
shape. The three raw-span rows are deliberately
unverifiable because FBX is not rewritten. The frontend serializes the
normalized document to a private GLB, maps the selected root and ordered skin
joint topology by exact unique normalized names, rewrites that GLB with the
existing raw glTF writer, reloads and semantically proves the exact candidate
once, read-back-digest checks it, and atomically publishes the resulting GLB
with immutable scale-evidence v5. V5 records the complete FBX inventory and
the private staged-GLB identity plus the nested raw-GLB proof; it makes no raw
FBX span, object-property, authored-curve, material, or texture-assignment
preservation claim, nor a marker/camera/display-layer behavior claim; supported
linked texture bytes are nevertheless captured before staging so admission cannot
silently remove them. Refusals name the exact incomplete coverage domain,
semantic row, inventory counter, or residual unmodeled kind.
Two observed factors, and the divergence between them. For rest/bind
reparameterization, "observed factor" names two independent witnesses of the
same quantity, measured from deliberately different state: planning classifies
the raw source projection's node-local rests composed through the raw parent
chain, and proof reads the normalized skeleton's bone rests composed through
its own parent chain. For whole-document conversion, both fields are the
declared factor by definition and their divergence is exactly zero; that
operation does not classify scale from either hierarchy.
The raw composition includes every preserved static connector local. The
normalized witness reads the corresponding folded transform through the next
projected bone; the connector itself has no normalized identity and therefore
owns no residual obligation. After semantic residuals succeed, proof's typed
field-row discharge separately checks exact connector preservation and the
analytic bridged source local at each projected successor. This preserves the
released residual-error precedence while keeping both the connector's
preserved authored components and its bridged projected successor bit-exact.
Direct rewritten raw rows outside a connector bridge use the published scalar
tolerance at the frontend narrowing boundary described in the compiled-ledger
contract above.
Strict scale-input shape validation requires the two chains to agree — under
Complete source-skeleton coverage, which this operation requires anyway, a
projection that contradicts its own skeleton is refused before either witness
is taken — but nothing reconciles the two readings: each is still composed
from its own stored state, and that independence is the reason both witnesses
exist. The record therefore carries both, plus the relative divergence
|planned - proved| / max(|planned|, |proved|) between them explicitly, so a
consumer neither mistakes one witness for the other nor has to derive the
relationship from two separate policy fields.
The expected ceiling on that divergence is the sum of two bands the policy
already declares: the common-factor band plus the postcondition unit-scale
residual (1e-5 + 2^-14 = 7.103515625e-5 under appendix-d-v6). Planning
binds its witness to the declared factor within the first band or refuses;
and for a candidate this operation built from the source under proof, that
candidate's composed root scale is the proof witness divided by the declared
factor, so the unit-scale postcondition binds the proof witness to the
declared factor within the second. The two bands are not stated the same way,
and the sum is a ceiling only up to that difference: planning's is relative to
the max of its two operands, exactly as the divergence is, while the
postcondition's is an absolute L∞ deviation from 1 on the candidate's
composed scale — so it bounds |proved − declared| as a fraction of the
declared factor rather than as a fraction of max(planned, proved).
The divergence is reported, not enforced. The second step above holds for a candidate this operation built from the source it is proved against — which proof deliberately does not require — and it costs the binary32 rounding of the rebase on the way, so the sum is the ceiling the design guarantees rather than a bound proved to the last ulp; refusing at it would refuse documents whose two witnesses each honour their own band. A divergence beyond it is not evidence that the two parent chains disagree — those are validated to agree, above — but that the state each witness reads differs: most often the raw node-local rests against the normalized bone rests, two separately stored descriptions of one rest pose. How far apart those are is a fact about the input, not a residual this proof owns. Recording both witnesses and their divergence introduces no band of its own and no new policy identity.
Migration is opt-in. assemble.canonicalize_skin in recipe v3 remains the
existing unanimated bind-geometry operation with identity source-to-metre
conversion; it does not gain rest-scale rewriting, and old recipes are not
silently reinterpreted. Recipe/evidence v4 adds the optional, explicit
rest_bind_scale operation and basis-compatibility evidence. Consumers that invert scaled
inverse binds with the rigid-only shortcut -A^T t can observe a change by
s^2; that exposes a pre-existing consumer error because the shortcut is valid
only for an orthonormal linear part, not a geometry change made by this
operation.
D.7 CLI, configuration, and public API
The CLI uses a required operation subcommand, not one flag whose meaning depends on the input:
animsmith scale whole-document INPUT.glb -o OUTPUT.glb \
--factor 0.01 --evidence OUTPUT.scale.json
animsmith scale rest-bind INPUT.glb -o OUTPUT.glb \
--source-skin-index 0 --source-root-node-index 3 \
--expected-factor 0.01 --evidence OUTPUT.scale.json
All numeric and source-identity arguments shown are required; there is no
inferred factor, implicit first skin/root, or in-place mode. Artifact and
evidence paths must be distinct from each other and the input. The narrow FBX
path additionally refuses either destination when it names a retained safe
dependency key, and refuses publication entirely when that key was rejected as
symlink-mediated or the dependency closure stopped before enumerating every
key. Initial support is self-contained glTF/GLB for both
operations, plus the narrow inventory-complete FBX rest-bind path that emits
a .glb and v5 evidence.
The command uses the fixed tolerance-policy version recorded in evidence rather
than accepting per-run tolerance flags. A future policy change requires a new
policy identity and compatibility review.
The single-document producer has no animsmith.toml key and no separate plan
file: mutation must not become an incidental effect of a lint configuration.
Assembly recipe v7 exposes the same rest/bind operation through an optional
[rest_bind_scale] block whose exact root_node_name and factor are required.
The name rejects leading or trailing whitespace instead of normalizing it.
For every captured base and clip input, that name must resolve to exactly one
normalized source node and exactly one non-empty source skin whose every joint
is that node or its descendant. The resolved per-input source indices feed the
unchanged core operation and private staging bridge; no cross-file source-array
index is inferred or reused. It accepts glTF/GLB plus the narrow FBX inventory
admitted by the
standalone rest/bind boundary, validates every base and clip basis before
applying the operation or remapping keys, and records the declared name,
resolved per-input name/indices, and normalized/baked FBX projection rather
than claiming raw FBX curve or span preservation. Recipe v6 remains immutable
with its explicit source indices, recipe v5 remains glTF-only, and recipe v3
has no such block and continues to reject it as unknown.
The public animsmith-core shape is a non-exhaustive ScaleOperation with
distinct WholeDocumentLinearUnits { factor } and
RestBindUniformScale { source_skin_index, source_root_node_index, expected_factor } variants, carried by a ScaleRequest. Pure planning returns
either an opaque, non-exhaustive ScalePlan (operation variant, affected
closure, canonical topology, typed field rows, tolerance policy, and derived
obligations) or a typed ScaleError; proof returns a
ScaleProof with one read-only ScaleProofResidual per semantic claim. Each
value carries the maximum and number of comparisons together, so a residual
nothing walked is distinguishable from a measured zero (issue #319) and an
adapter cannot combine one claim's count with another claim's maximum (issue
#323).
Planning and proof take format-neutral node, track, bind, and capability facts.
They do not accept paths, glTF/ufbx types, config parsers, or publication
policy. The format frontend owns raw inventory and exact source rewriting; the
CLI owns atomic artifact/evidence publication. Applying a plan builds a
candidate and never mutates the caller's source on failure.
animsmith-core::scale implements this contract (issue #281): the
ScaleOperation/ScaleRequest planning entry point, the typed ScalePlan
and ScaleError, the no-authority candidate handoff, and paired ScaleProof
residual values. Any change to the operation variants, required selectors, or failure
boundary is a design change rather than an implementation detail.
ScalePlan exposes its compiled ledger only through non-exhaustive read-only
field, payload-shape, topology, and obligation row views; callers cannot
use those row values to construct or mutate a plan with an incoherent
operation, topology, field inventory, or obligation set. Plan
equality includes that exact structural ledger. The former public
ScaleDomainRewrites and ScaleProofObligations boolean bags and their plan
accessors are removed in this pre-1.0 API break: operation-fixed facts and
evidence gates have one typed authority rather than independently selectable
booleans. The CLI privately projects the same five domain_rewrites booleans
from the operation variant's frozen mapping, so payload presence and
factor-one rows cannot change their meaning. Immutable scale-evidence
v1/v2/v3, ScaleProof serialization, appendix-d-v6, tolerances, calibration
semantics, and policy pins do not change. The internal ledger is not serialized
into evidence.
ScalePlan and ScaleProof each carry the observed factor beside the
declared one, as §D.6's evidence contract requires (issue #290). The declared
factor is what the build applies and what every proof expectation is stated
in; the observed factor is the rest-world uniform factor measured at the
scaled root, which the declared-factor band admits without requiring equality.
Proof re-measures it from the source it is handed rather than reading it off
the plan, so evidence does not depend on planning having recorded it. It is
reported, not checked: the input band is planning's obligation and the
postcondition derived from it in §D.1 is proof's. For a whole-document
conversion the observed factor is the declared one, because §D.1 gives that
operation's factor no measurable source counterpart at all.
animsmith-gltf owns the glTF/GLB frontend of that contract:
preflight_scale_source (issue #280) captures the raw inventory,
capability_facts / rewrite_linear_units / prove_rewritten_artifact
(issue #282) implement the whole-document conversion of §D.2 directly on the
source's own JSON and buffer bytes, and rewrite_rest_bind /
prove_rewritten_rest_bind (issue #283) implement the rest/bind
reparameterization the same way. Because that route produces a candidate
core did not build, ScaleCandidate::from_document exists so the reloaded
artifact can reach prove_scale; the type asserts nothing that prove_scale
does not independently re-derive. Core exposes no production candidate
builder: its analytic reference constructor is available only through the
non-default fixtures feature for tests and calibration.
rewrite_rest_bind is the selector-taking convenience boundary; it compiles a
plan and delegates to the same plan-taking raw adapter used by
rewrite_scale_plan. The latter validates the plan against the supplied
source before any write, so the CLI can carry one authoritative compiled plan
through rewrite and proof without admitting stale-plan replay.
GltfScaleArtifact reports which operation produced it
beside the declared factor, because the two operations rewrite different
domains and a factor alone does not distinguish them. Beyond the shared
plan/proof rejections, the frontend owns three refusals the format-neutral
layer cannot state: the disagreeing-multiplier refusal of §D.2, and two
agreement checks over the raw node hierarchy, the loader's source-node
projection, and the normalized skeleton's parent links. animsmith-core
requires the last two of those three to describe the same tree — that is the
document-shape validation of §D.6 — but the raw /nodes/*/children arrays
live in JSON that never becomes a Document field, so bringing the third
description into the comparison is only possible here.
D.8 Implementation ownership and deferred integrations
Putting all semantics in assembly was rejected: it would hide a reusable
single-document operation and preserve assembly's currently insufficient
separate-clip proof. A standalone command with unrelated duplicate math was
also rejected. The chosen end state is one shared core transform-plan and proof
layer with distinct, explicit frontends: the shipped single-document producer
and character-assembly recipe/evidence v7 integration. The existing
canonicalize_skinned_bind_pose remains the narrower unanimated bind-geometry
foundation; it is not silently widened or renamed into this contract.
Shared affine classification, distinct acceptance contracts. The core uses
one typed, symmetric f64 positive-uniform Mat3 classifier for both Appendix
D and skinned bind-pose canonicalization. Appendix D retains
appendix-d-v6's 1e-5 equal-axis/orthogonality and 1e-6
relative-determinant thresholds. The canonicalization input
source_to_meters_y_up retains its numeric 1e-4 equal-axis/orthogonality
thresholds and exact-zero determinant threshold; these are compatibility with
the existing assembly/canonicalization contract, not a second scale-policy
version.
Canonicalization does deliberately adopt the shared classifier's semantics:
axis equality is relative to the three-axis average and longer operand rather
than to the first column, derived arithmetic is f64, and singularity is
classified before non-uniformity or shear. Thus a unit basis with one axis at
1.00015 is accepted while 1.00016 is refused, and an input that is both
exactly singular and sheared now uses the existing
reflection_or_singular reason rather than non_uniform_or_sheared. The
machine-readable vocabulary is unchanged; these symmetric boundaries and
precedence replace two independent algorithms with one explicit typed policy.
The classifier runs after canonicalization's separate existing
zero-translation/fourth-column w_axis ~= Vec4::W gate and its first-axis
<= 1e-4 zero_scale reason. That zero-scale gate reads the same widened
f64 axis length as the classifier, so a finite large basis cannot overflow a
binary32 length intermediate and be mislabeled as zero scale.
Near the shear boundary, the symmetric average base and binary64 dot products
can also refuse inputs that the former first-column/binary32 check accepted.
Measurement shares facts, not the positive-only verdict. Measurements v12
derives axis lengths, their canonical ascending-order mean, determinant,
axis-length product, and XY/XZ/YZ dot products from the same crate-private
widened f64 fact record. It also uses the shared symmetric
mean/longer-operand equal-axis predicate, which removes v11's privileged-X
result and reconciles the finite
diag(1, 1, 1.000012) fixture. Measurement still asks a different question:
it keeps finite partial evidence for singular, reflected, sheared, and
non-uniform inputs; normalizes each orthogonality comparison by that axis
pair's length product; and classifies in singular, reflected, sheared,
unit/uniform, then non-uniform precedence. Calling the positive-uniform
classifier directly would discard those descriptive facts and impose the
operation's different precedence. Skin-bind summary factors are sorted before
their mean is derived and every factor is compared to that mean, so authored
joint order cannot choose consistent_uniform versus mixed_uniform or the
reported common factor. These corrected numeric semantics require the nested
measurements-v12 identity. The JSON shape remains unchanged, but the outer
identity advances to output-v6 because each immutable output schema statically
pins its nested measurement URN; measurements v11 and output-v5 remain
immutable historical evidence. In general, changing a nested identity that an
immutable outer schema references requires a new outer identity too; historical
outer schemas are never retargeted or broadened in place.
Measurements v13 makes inverse-bind derivation auditable and fail-closed. Each
per-joint bind observation retains the exact finite raw inverse-bind matrix for
its accessor slot. Before publishing an inverse, the source bottom row must be
within absolute 1e-6 of affine [0, 0, 0, 1]; this admits several binary32
round trips around one but refuses projective translation columns. The linear
3x3 reports the scale-free reciprocal infinity-norm condition number
1 / (norm_inf(A) * norm_inf(inverse(A))). Exactly singular sources and
finite sources at or below 1e-6 use distinct non_invertible and
ill_conditioned unavailable reasons. This avoids the prior scale-bearing,
exact-zero determinant decision: a uniformly small but well-conditioned matrix
is assessed by its error amplification rather than its determinant magnitude.
The new nested shape advances the immutable outer envelope to output v7;
measurements v12 and output v6 remain historical.
Measurements v14 gives every clip fact that is not applicable to every clip —
loop continuity, loop endpoint mode, frame grid, loop seam ratio, gait (and
its own phase), and root-motion speed — a required sibling
<field>_availability status of measured, not_applicable, or
unavailable. A bare optional value cannot distinguish "this clip has no
subject for the fact" (not_applicable, a legitimate absence) from "the
subject applies, but derivation failed" (unavailable, which a consumer must
fail closed on rather than silently treat as passing); those two absences now
compare distinctly under diff as well. The value field is present if and
only if its status is measured. The new nested shape advances the immutable
outer envelope to output v8; measurements v13 and output v7 remain
historical.
Measurements v15 inventories each clip's surviving non-empty local TRS channel set by skeleton index and adds normalized model-space root-trajectory regression evidence. A resolved Root always wins; Hips is a typed fallback only when Root is unresolved. The record retains independent translation and yaw availability, signed endpoint displacement, sampled horizontal travel and vertical extrema, and fixed-witness net/unwrapped/travel yaw. All trajectory path/extrema/winding values come from the shared inclusive uniform metric grid and therefore do not prove continuous-curve extrema or authored transform preservation. The new nested shape advances the immutable outer envelope to output v9; measurements v14 and output v8 remain historical.
The implementation status is:
- shipped: raw glTF/GLB capability preflight, the shared core plan/ledger and proof, preservation-safe whole-document and rest/bind exact-source writers, artifact proof, and atomic CLI publication with scale-evidence v4;
- shipped: issue #286's conservative FBX rest/bind re-encode path with the full ufbx inventory, exact staged-GLB rewrite/reload proof, v5 evidence, and atomic GLB/evidence publication; whole-document FBX scaling remains refused;
- shipped: glTF/GLB and inventory-complete normalized/baked FBX assembly integration in recipe/evidence v6, including the versioned basis fingerprint/comparator, pre-remap clip rebasing, exact input digests, compatibility evidence, and proof over the exact staged artifact;
- shipped: recipe/evidence v7 exact-name rest/bind selection, with a unique source-node and fully-governed-skin resolution for every captured input and explicit declared/resolved selector evidence (issue #459);
- shipped: issue #286-A's explicit conservative ufbx-side status for every current D.4 domain and documented normalized source-skeleton projection;
- shipped: raw glTF whole-document scaling for dense
f32morphPOSITIONdeltas with numeric-value preservation for static JSON weights and byte-exact preservation for animated weight accessors (issue #298); rest/bind morphs and a normalized core morph model remain out of scope.
The live implementation sequence is owned by tracker issue #344 and roadmap issue #165, not by a numbered future-slice list in this decision record. Work beyond this positive-uniform, fail-closed decision remains ADR-gated.
Appendix E — decision record: engine profiles
This appendix is the accepted design outcome of issue #150. It defines the consumer-model boundary required by the 0.4.0 engine-feedback milestone; it does not claim that the configuration, checks, or generators described here already ship.
E.1 Decision and ownership
An engine profile is a versioned set of externally verified importer or runtime facts. It answers how a named consumer handles a supported input. It is not any of the following:
- a rig profile, which resolves semantic roles to bones;
- project intent, such as whether a clip should be in-place, looping, or animation-driven root motion;
- a severity or artistic-quality preset;
- evidence that a particular engine import actually ran; or
- authority to rewrite an asset.
Those distinctions are structural. [rig] continues to own role resolution,
[runtime_nodes] owns the shared selection of project-declared attachment,
socket, IK, collision, and other runtime-facing nodes, [clips] owns per-clip
intent plus explicitly nested clip-scoped importer choices, [checks] owns
activation, severity, and project tolerances, engine readback owns observed
import results, and Appendix D plus each transform's own proof contract owns
rewriting. An engine profile may describe a hard importer constraint or
interpretation, but it must not silently change the math, thresholds, or
findings of an existing consumer-neutral check.
The issue's original suggestion that profiles own loop/contact thresholds is therefore rejected. An engine may expose a loop toggle or a compression setting; it does not know a project's acceptable seam, contact, or slide error. Generated guidance may propose a documented starting value, but the effective value remains explicit project policy.
E.2 Selection and built-ins
The first configuration surface is deliberately small:
[engine]
profile = "unity-generic"
profile_revision = 2
engine_version = "6000.3"
importer = "fbx-model-importer"
[engine.settings]
animation_type = "generic"
avatar_setup = "create_from_this_model"
import_animation = true
root_motion_source = "Reference/Root"
[clips."locomotion_*"]
movement_owner_xz = "animation"
movement_owner_y = "gameplay"
movement_owner_yaw = "animation"
[clips."locomotion_*".engine_settings]
root_rotation = "extract"
root_position_y = "bake"
root_position_xz = "extract"
[clips.idle]
movement_owner_xz = "gameplay"
movement_owner_y = "gameplay"
movement_owner_yaw = "gameplay"
[clips.idle.engine_settings]
root_rotation = "bake"
root_position_y = "bake"
root_position_xz = "bake"
[runtime_nodes]
selectors = ["weapon_socket", "ik_*", "collision_anchor"]
[runtime_nodes].selectors is project policy, not an importer fact. Exact
names and * globs resolve with the deterministic miss and ambiguity behavior
already established by issue #268. The existing
[checks.rest-world-scale].node_selectors field remains accepted as a
compatibility alias and is normalized into this shared selection before
evaluation. Declaring both forms is a typed configuration error rather than a
precedence rule. rest-world-scale and the selected-node facet of
engine-unit-scale consume the same resolved set; one check never reads the
other check's private settings. An absent selector field and an explicit empty
selector list both mean no declared policy, preserving the legacy check's
behavior. Only that state makes the selected-node engine facet genuinely not
applicable. If a
declared selector misses or is ambiguous, rest-world-scale retains its
existing engine-neutral coverage behavior, while an enabled, otherwise
applicable engine-unit-scale facet emits required_prediction_unavailable
and follows E.4's exit policy. Component/root and mesh-node unit-mapping rules
may still apply independently.
An absent [engine] section means today's engine-neutral behavior. There is no
generic engine profile, no auto-detection, no nearest-version match, and no
fallback profile. Family, profile revision, engine version, and importer route
are all required when [engine] is present. An unknown or unsupported tuple,
unknown setting, or setting that is invalid for that importer is a typed
configuration error. A caller that still wants ordinary engine-neutral linting
removes [engine]; it does not receive a partial prediction under a different
consumer contract.
Every profile setting descriptor declares its exact value domain, applicability,
and either document or clip scope. Document-scoped importer choices live only
under [engine.settings]. Clip-scoped importer choices live only under the
matching [clips.<selector>.engine_settings] table and resolve through the
existing clip-selector rule (exact name over matching globs, later matching
globs winning ties). For the Unity Generic FBX profile, the Motion/Root Motion
Node choice is an exact source-transform path that applies to all imported
clips and is therefore document-scoped as root_motion_source. It is
explicitly not applicable to the Unity Humanoid profile, whose importer uses
Avatar/body semantics. Rotation and position bake/extract choices remain
clip-scoped for both Unity profiles. [clips.<selector>] movement-owner fields remain project intent;
the sibling engine_settings table records the importer state that a prediction
must compare with that intent. A setting at the wrong scope is a typed
configuration error, not an override.
Each applicable V1 setting is classified as required without a verified default; values in the example are not defaults. Adding a documented default requires a later profile revision and settings-contract revision rather than a silent registry edit. Omitting a required setting is a typed configuration error; it is never guessed, treated as an implementation default, or deferred to a coverage gap. The resolved settings record therefore contains one exact document value or per-clip value for every applicable setting.
The registry contains the preserved revision-1 tuples plus the current revision-2 Unity Generic, Godot, Unreal, and Bevy tuples and the revision-3 Bevy tuple:
| family | revision | engine version | importer | AnimSmith input boundary |
|---|---|---|---|---|
unity-generic | 2 | 6000.3 | fbx-model-importer | FBX |
unity-humanoid | 1 | 6000.3 | fbx-model-importer | FBX |
unreal | 1 | 5.8 | fbx-importer | FBX |
unreal | 2 | 5.8 | fbx-importer | FBX |
godot | 1 | 4.7 | resource-importer-scene | glTF JSON, GLB, FBX |
godot | 2 | 4.7 | resource-importer-scene | glTF JSON, GLB |
bevy | 1 | 0.19.0 | gltf-asset-loader | glTF JSON, GLB |
bevy | 2 | 0.19.0 | gltf-asset-loader | glTF JSON, GLB |
bevy | 3 | 0.19.0 | gltf-asset-loader | glTF JSON, GLB |
These are exact versions, not ranges. The Godot row is AnimSmith's bounded V1
profile boundary rather than a claim that Godot cannot import other formats.
The preserved Unity revision-1 advice profiles expose document booleans
convert_units and bake_axis_conversion, plus per-clip root_rotation,
root_position_y, and root_position_xz values in the closed bake | extract
domain. Current Unity Generic revision 2 instead exposes the frozen document
values animation_type = "generic", avatar_setup = "create_from_this_model",
import_animation = true, and the exact document-scoped
root_motion_source, plus those three per-clip root controls. Only Generic
also exposes root_motion_source; it is not applicable to Humanoid. Unreal,
Godot, and Bevy revision 1 expose no V1 setting vocabulary, so every supplied
key is rejected. Bevy revision 2 has a separate closed setting vocabulary for exact
loader coordinate toggles, mesh loading, extension-handler environment,
animation feature, and animation loading. It materializes version-pinned
defaults and retains each value's explicit/default origin.
Bevy revision 3 preserves the earlier tuples and adds only the narrow
animation/channel gate-support fact. It resolves the compiled
bevy_animation feature and load_animations with explicit/default origins;
the compiled feature gate takes precedence. Its raw same-load inventory binds
animation coverage independently from each animation's channel coverage.
Extensions, other animation constructs, and positive runtime survival remain
outside this revision; the latter requires an actual Bevy readback.
The separately versioned bevy-gltf-addressability-rules:1 bundle paired with
this immutable profile owns the richer scene, skin, named-map, and target-path
projection; it does not mutate profile revisions 1--3.
The two Unity ids are import modes, not skeleton-name maps. Likewise,
unreal, godot, and bevy do not imply a mannequin, humanoid bone map, or
other rig profile. glTF is not an engine profile: its specification defines
format semantics but deliberately does not define looping, playback, import
options, or other runtime policy. glTF 2.0 compatibility remains a loader and
consumer-neutral check boundary. Bevy has separate supported-extension,
asset-addressability, and animation-target behavior.
Each built-in resolves its exact (family, profile_revision, engine_version, importer) tuple to an immutable fact-bundle identity such as
urn:animsmith:engine-profile:unity-generic:2. Its data record contains exact
engine version and importer values, accepted input formats, source
and target coordinate bases, importer/runtime facts, allowed setting
vocabulary, and primary-source references with a verification date. For every
claimed input format it must also enumerate, rather than leave implicit:
- handedness, up/forward axes, and the selected importer's axis-conversion mapping;
- each applicable clip-boundary predicate (including whole-frame constraints) or an explicit statement that none is known; and
- supported, dropped, and reinterpreted channels, extensions, and animation constructs, with an explicit unsupported or unknown state where evidence is incomplete.
Changing any such fact requires a new profile revision and normal release
notes; a historical result never silently resolves through the current
revision. [engine.settings] selects only choices the immutable importer facts
already expose; it cannot override those facts.
Facts use explicit known, unknown, and genuinely not-applicable states. The
accepted-format fact is the sole input-format authority. The facts digest is
the core InputIdentity of a versioned, fixed-field-order, length-prefixed
UTF-8 encoding of the full tuple, URN, every fact state and value (including
accepted formats), every setting descriptor/domain/scope/applicability and
default status, verification metadata, and source references sorted by stable
id. Source references bind stable fact/setting ids to one target version, URL,
and verification date; live documentation bytes are never digest input. The
settings digest uses the same encoding over the full resolved identity and
fully materialized document settings plus actual clip names/settings in lexical
order, never TOML spelling, selector patterns, or map insertion order.
Version one does not accept arbitrary engine-fact overrides in TOML. A user cannot safely make an importer support a channel or reinterpret root motion by assertion. Embedders may supply namespaced custom checks and keep their own consumer contracts, as they do today, without extending the built-in profile registry.
Crate ownership is fixed before implementation. A new format-neutral
animsmith-engine library owns the built-in fact registry, strict tuple
resolution, prediction-rule adapters, and the standalone advisory/readback
payloads. It depends on animsmith-core, but not on format libraries, TOML, a
filesystem, or engine SDKs. animsmith-core continues to own consumer-neutral
documents, measurements, checks, shared source-evidence value types, every
measure/lint envelope and URN, and a registry-independent wire projection for
profile identity, rule basis, prediction state, and shared provenance. The
engine library populates that core-owned projection; core never depends on the
engine registry or its concrete profile types. The glTF and FBX libraries
populate the common raw-source projection while retaining their format-specific
parsing details. The animsmith CLI owns strict TOML mapping and orchestration;
animsmith-report renders the resulting public contracts. This preserves
Appendix C's envelope ownership, prevents a core/engine dependency cycle, and
gives embedders the same registry and resolver as the CLI.
Format loaders bind that projection to the normalized document in an opaque,
immutable LoadedSource. Its consumer surface is deliberately limited to
document(&self), source_facts(&self), dependency_closure(&self), and
into_document(self): callers cannot mutate a document while continuing to
present its source facts or captured closure as current, and consuming the
document explicitly discards both sidecars. The facts view borrows the
canonical Document.assets.source_skeleton projection rather than copying
source-node or skin authority. Existing document-only loader entry points
remain compatibility conveniences that consume this wrapper.
The V1 projection is bound to the exact primary-file InputIdentity and an
explicit source format established by the loader; SourceInfo.path and its
free-form format label are neither identity nor fact authority. Row sets carry
independent complete, partial, or unavailable coverage. Rows retained
under partial coverage prove positive presence only; absence is established
only by complete coverage. Observation availability, provenance, and AnimSmith
loader disposition remain orthogonal and never imply target-engine support.
V1 bounds projection work to 65,536 enumerable rows, 4,096 clips/takes, 4,096
resource-reference declarations, 4,096 UTF-8 bytes per retained source string,
8 MiB of retained source strings in total, and traversal depth 128. At N+1 the
loader keeps the deterministic source-order prefix (lexical order only where a
parser exposes a map without source order), marks the affected domain partial
with projection_budget_exceeded, and stops new work in that domain. A legacy
load that succeeds does not become a failure merely because this sidecar is
partial. These are projection limits, not claims that every existing parser,
resource read, image payload, or bake allocation is globally bounded.
Raw resource facts stop at bounded declaration identity, kind, and a safely
retained source-relative spelling. They do not themselves open, normalize,
deduplicate, hash, or map referenced content. Unsafe absolute, remote,
data-payload, or escaping spellings are classified without being reproduced in
the new facts surface, its debug/serialization views, or its errors. Legacy
diagnostic Document::source.path remains outside that projection.
The separate dependency-closure v1 sidecar is exact only over that versioned
declaration domain. Format loaders construct it during the same load that
consumes the resource bytes; a post-load reopen cannot establish the identity
of bytes already parsed. Embedded/data/BIN/view-backed declarations map to the
primary identity. Safe relative declarations map through a normalized
source-relative key to one captured InputIdentity; aliases of one key are
opened and hashed once, while different keys remain distinct even when their
bytes match. The source-order reference map and key-sorted distinct-resource
table form a domain-separated canonical closure identity. Only complete raw
declaration coverage with an identity for every declaration produces a
complete closure identity. Unsupported extensions or unmodelled FBX
resource-bearing domains keep the result conservative rather than turning a
known subset into a whole-document or target-importer claim.
Each reference also records a purpose derived from its source kind: loader-essential buffers, nonessential images/textures, or target-only video/cache declarations. This is source/loader evidence, not a prediction of whether a selected engine profile will import the resource.
Closure capture has its own immutable v1 budget: at most 4,096 declarations, 1,024 distinct external keys, 4,096 UTF-8 bytes for each source spelling and normalized key, 128 path components per spelling, 8 MiB of aggregate normalization input, 64 MiB read and hashed per external resource, 256 MiB in aggregate, and one bounded non-quadratic deduplication probe per declaration. The loader checks N+1 before allocation or opening, retains the deterministic prefix and work counters, and stops the affected capture. Absolute, escaping, out-of-root, remote, malformed, oversized, and symlink-mediated locators are typed refusals and are never opened or reproduced. A trusted resource root comes from path-based loading or an explicit byte-loader API; byte loading never falls back to the process working directory. Missing loader-essential bytes remain a load/operator error, while unavailable nonessential or target-only bytes may leave typed partial closure. No captured resource is recursively parsed for more declarations. The limits bound this closure walk; they do not certify the primary parser or a concurrently mutating trusted filesystem.
E.3 Rules, checks, and precedence
Engine rules consume engine-neutral measurements plus a dedicated read-only
raw-source-facts projection; they do not recompute those facts differently per
engine. The normalized Document alone is insufficient because importer-
sensitive extensions, authored FBX curves and units, take boundaries, and
other declarations may already have been normalized or discarded. The raw
projection preserves such facts and their availability without exposing a
scale operation's private capability ledger as the general engine API.
That projection is the one shared provenance-and-coverage model for raw source evidence, not a parallel per-feature copy. Loaders reuse its identity, availability, and provenance primitives wherever scale planning needs the same source observation. A scale capability inventory and proof ledger remain operation-specific derived consumers because they additionally encode a requested rewrite's closure and proof obligations; engine rules do not create a third representation of the underlying source fact.
The format-neutral core also owns urn:animsmith:exact-source-timing:1, a
bounded same-load sidecar attached to a loaded source. It records generic exact
time units, frame periods, selected clip ranges, and typed availability without
format names, parser types, or format constants. A format adapter performs its
own projection into that vocabulary: the FBX adapter alone owns KTime constants,
integer period arithmetic, and ufbx resolved-property selection. Engine rules
consume the generic sidecar through RawSourceFactsV2 and prediction provenance
v3; neither the V2 outer identity nor the historical readers are widened.
A rule declares all of its preconditions: the full resolved profile identity,
input format and raw-source coverage, required measurement fields, rig roles
where applicable, and every explicit project intent or importer setting. A
required engine-prediction facet with missing evidence records scoped
required_prediction_unavailable rather than a passing finding, guessed
value, or generic engine-neutral coverage gap.
New engine prediction checks are grouped by stable concern rather than copied once per engine. The initial families are:
engine-clip-boundaryfor ranges, whole-frame requirements, and slicing;engine-root-motionfor importer extraction/bake interpretation;engine-track-supportfor channels, extensions, and data an importer drops or reinterprets;engine-unit-scalefor explicit unit conversion and resulting transform scale; andengine-addressabilityfor scenes, animations, targets, and runtime labels.
The current engine-root-motion slice is the exact Unity Generic revision-2 /
6000.3 / fbx-model-importer FBX tuple. Its V6 provenance binds the V5
same-load source evidence to a bounded raw transform-path inventory and the
normalized per-clip movement-owner intent. The rule is applicable only when
that exact profile is resolved and at least one axis is explicitly declared.
For each declared horizontal_xz, vertical_y, or yaw axis, it compares
movement_owner_xz, movement_owner_y, or movement_owner_yaw with
root_position_xz, root_position_y, or root_rotation. bake is
baked_into_pose; extract is stored_as_root_motion. Gameplay ownership
matches baking, animation ownership matches extraction, and a mismatch emits
an ordinary error finding bound to the available prediction facet.
The rule has one explicit lifecycle: all available axis facets are complete,
a mixture of available and required-unavailable facets is partial, and all
required-unavailable work is not_evaluated. Missing or ambiguous path
resolution, a path that does not identify the explicitly resolved Root role,
incomplete raw-path or project-intent coverage, settings overflow, duplicate
clip names, or unavailable axis-specific translation/yaw evidence emits
required_prediction_unavailable; this state is not a content finding and
cannot be suppressed by --allow. The rule never falls back to Hips, even
though the consumer-neutral root-trajectory measurement still uses Hips when
Root is unresolved. Measurement availability is required, but numeric
translation/yaw magnitude is not a threshold and does not affect routing.
Generic's root_motion_source is a closed, case-sensitive, byte-exact relative
path. / is the only separator and has no escape syntax. Empty segments,
leading/trailing or doubled separators, ./.., backslashes, Unicode control
or format characters are rejected. The bounds are 1,024 UTF-8 bytes per
segment, 4,096 bytes per path, and 256 segments. The FBX adapter projects
source identities, names, and parent chains from the same input bytes with a
raw-preserving ufbx load. The implicit ufbx root and generated geometry/scale
helpers remain rows but cannot match. Complete inventory coverage is required
to prove NoMatch; partial or unavailable coverage yields CoverageIncomplete.
This is source evidence only: AnimSmith does not execute Unity, read back an
imported asset, play runtime clips, or certify engine behavior. Repository and
CI fixtures for this slice are self-authored synthetic FBX files only.
The selected profile chooses applicable rules within those check ids. The
file-scoped provenance retains the full resolved profile and source identity;
each check retains prediction facets keyed by the existing evaluation scope,
with the exact fact, setting, policy, and measurement basis for clean and
finding-bearing work alike. Findings bind to an available facet rather than
introducing another applied-rule id. Checks do not use unity-*, unreal-*,
and similar duplicate families.
The bounded 0.4.0 production slice is the exact Bevy revision 1 / 0.19.0 /
gltf-asset-loader animation-index selector rule. With complete nonempty glTF
or GLB source-animation inventory, engine-addressability emits one available
facet per source row whose subject is Bevy's Animation{source_clip_index}
display label. A source name is only metadata: absent and duplicate names do
not change the index selector. Partial or unavailable inventory emits one
required-unavailable inventory facet and no retained-prefix predictions, so
the 4,096-row raw-source and file-facet caps compose without an N+1 facet.
The resolved-settings/provenance v1 contract separately caps actual clip rows
at 4,096. A 4,097-clip document is therefore a bounded operator error before
prediction, not a silently truncated settings inventory; #485 owns any future
overflow evidence and shared multi-rule facet-budget policy.
This predicts the canonical selector convention only. It does not claim a
successful Bevy load, runtime asset existence, animation-target survival, or
graph wiring, and the index is not stable across source-order edits. Richer
scene/name/target addressability remains a separate inventory/adapter concern.
The current additional production slice is the exact Unreal revision 1 / 5.8 /
fbx-importer whole-end-frame rule. For every retained FBX animation stack,
the FBX adapter projects ufbx's selected complete range pair and exact KTime
period into the generic exact-source-timing sidecar. engine-clip-boundary
tests only whether the absolute selected end coordinate is on that lattice.
Incomplete inventory and unavailable declarations or ranges yield scoped
required-unavailable facets; decimal FPS, inferred frame numbers, import-range
selection, resampling, compression, root motion, and all other importer policy
remain out of scope.
The corresponding 0.4.0 standalone producer is
generate addressability. Its V1 root is separate from output-v11 and contains
one engine-neutral glTF/GLB animation inventory plus a nullable exact-Bevy
adapter. animsmith-engine owns this format/profile-specific envelope and its
strict reader; core remains limited to the generic source, closure, prediction,
and evaluation contracts it consumes. The neutral identity covers the primary
input, full existing dependency closure, and bounded raw animation/channel
observations only; tool metadata and the adapter cannot change it. The exact
Bevy adapter embeds the same prediction provenance and unchanged
engine-addressability evaluation,
so it creates neither a second selector rule nor a second check lifecycle.
Absent and repeated source names remain metadata. Scene/default-scene, skin,
target path or UUID, named-map-winner, and extension-handling claims require
new raw facts and a later profile revision and are outside this V1.
Issue #488 adds those rich facts as a separate immutable producer contract,
urn:animsmith:schema:gltf-addressability:2; the historical V1 root and
readers remain unchanged. V2 preserves the V1 animation inventory and adds
bounded same-load raw glTF scene, node, skin, node-to-skin attachment,
scene-path, default-scene, named-map, and unique animation-target evidence.
Coverage is independent for every domain: complete empty coverage proves
absence, while a retained prefix or unavailable domain never does. Rows retain
source-array indices, authored names, parent/child order, scene roots, ordered
skin joints, explicit skin.skeleton, and the dependency-closure identity.
The explicit skeleton member remains source evidence only because Bevy ignores
it; V2 makes no inferred-root claim and does not claim scene-instantiated
SkinnedMesh attachment.
The optional exact-Bevy adapter is paired with the existing one
engine-addressability evaluation and reuses the exact Animation{i}
primitive, without creating a second check lifecycle. Its separately
versioned rule bundle accompanies immutable Bevy profile revision 3 and pins
tag v0.19.0, commit
c6f634ca9f406d68ba5109d921247b654cb42c10, bevy_gltf 0.19.0, locked
gltf 1.4.1, and the label, loader, node-path, target-id, feature, and root
Cargo.lock sources.
Scene{i} is emitted for every declared scene; Gltf.default_scene is only a
route to an existing scene handle, with no DefaultScene label and no
fabricated Scene0. Skin{i}/InverseBindMatrices is eager for every source
skin, including unreferenced skins, using Bevy's identity fallback for an
absent accessor. Skin{i} is created when any source node references the skin
during the all-source-node construction pass. Source indices, not collected
vector order, are identity. Named maps are separate, source-order
last-write-wins projections; skin winners follow lazy first-reference order.
Target rows are per unique source animation target node and retain their
animation/channel contributors. Authored names or GltfNode{source_index}
fallbacks form the path, excluding the scene world-root. A path/UUID is
published only with complete hierarchy, reachability, closure, feature,
settings, and collision evidence. Unreachable or multiply reachable targets,
duplicate paths, UUID collisions, disabled/missing bevy_animation, disabled
load_animations, missing target pointer width, and incomplete evidence are
typed required-unavailable states. Exact target IDs require explicitly pinned
32- or 64-bit pointer width because Bevy hashes segment lengths with the target
pointer width's little-endian encoding; the host width is never inferred.
The projection carries explicit target_coverage separate from retained
target rows. It is complete, including for an empty target domain, or is
required-unavailable when raw node/scene/path or animation/channel evidence is
incomplete, and with target_domain_truncated at the 4,096-target row
ceiling. Other new-rich-projection bound failures use
projection_bounds_exceeded. Each new scene/node/skin/attachment/path/target/
map domain is bounded at 4,096 rows, with aggregate structural references at
65,536 and dynamic projection text at 1 MiB; the sealed V1 animation inventory
and CheckEvaluation scopes retain their own bounds. Names and path segments
are capped at 1,024 UTF-8 bytes, paths at 4,096 bytes and 256 segments, and
staged reports at 256 MiB. Overflow retains an independent canonical prefix;
strict readback rejects N+1 collections and contradictory states. A bounded second parse of already captured primary bytes is permitted
within the same loader invocation; reopening the primary or dependencies is
not. V2 is prediction evidence only and never certifies runtime loading,
scene spawning, target survival, graph wiring, or playback. Required-
unavailable prediction retains exit 1; malformed tuple/config/input remains
exit 2.
The second bounded 0.4.0 standalone producer is generate import-advice.
animsmith-engine owns its independent V1 envelope, canonical identity, and
strict reader. It embeds the same exact prediction-provenance record but is
not a check evaluation: it projects fully materialized profile settings into
document/per-clip importer properties beside same-load source identity,
dependency closure, source-to-normalized clip linkage, explicit loop/movement
intent, and normalized measurement availability. Unity 6000.3 Generic and
Humanoid revision 1 can project Convert Units, Bake Axis Conversion, Generic
Root Motion Source, and the three bake/extract root controls. Frozen Unreal
5.8 and Godot 4.7 revision 1 have no setting descriptors and therefore emit a
typed refusal rather than a guessed preset. Authored frame coordinates,
sampling policy, root-motion prediction, and unit-conversion inference remain
out of scope until their own exact source/profile authorities exist.
Revision 2 of the producer is an independent immutable contract,
urn:animsmith:schema:engine-import-advice:2. It uses output-v15's V4
prediction-provenance and prediction-basis types by reference and carries one
optional native projection. available requires that projection and omits
refusal_reason when the same-load dependency closure is complete; partial or
unavailable closure requires refused, dependency_closure_incomplete, and
no projection. Unknown tuples, missing settings, and unsupported formats are
configuration errors, so neither those errors nor incomplete closure can be
mistaken for an importer preset.
The only V2 advice profiles are exact: Godot 2 / 4.7 /
resource-importer-scene accepts glTF JSON and GLB and projects
animation/fps (1..120, verified default 30) plus animation/trimming
(verified default false); Unreal 2 / 5.8 / fbx-importer accepts FBX and
requires explicit sample_rate from default_30, source_determined, or
custom_hz(1..48000). The projection retains explicit/default origin and no
other settings. It is a parameter projection from same-load evidence, not
engine execution, imported-asset readback, runtime/gameplay certification, or
project-file mutation.
[checks.<id>] remains the only authority for severity and explicit
enable/disable overrides. Existing checks such as loop-closure, in-place,
foot-slide, and root-motion-speed keep their current behavior under every
engine profile.
The shipped narrow slice is Bevy revision 3 / 0.19.0 /
gltf-asset-loader engine-track-support. It binds a bounded raw animation
inventory from the same load, with animation coverage and independent
per-animation channel coverage. A complete inventory can produce only
negative gate outcomes for rows dropped by the compiled bevy_animation
feature or by load_animations; a dropped row is not a content finding. The
compiled feature gate has precedence over the load setting. A complete-empty
inventory is not applicable. Partial or unavailable coverage emits exactly one
subjectless, unsuppressible required_prediction_unavailable inventory facet
and no retained-prefix prediction, including the N+1 boundary. If both gates
allow loading, the result is a stable required-unavailable runtime-survival
state. Extensions, other constructs, and positive runtime survival are not
modeled by this slice. V5 provenance and output-v19 are current for this Bevy
slice; V4/revision-2 and output-v15 remain preserved and readable. The Unity
Generic root-motion slice uses V6 provenance in output-v19, while output v9
and all other historical readers remain immutable.
Configuration precedence is consequently:
- loader and measurement evidence establish what the input contains;
- the resolved engine profile supplies immutable verified consumer facts;
- resolved document and per-clip importer settings state the configured
consumer behavior, while
[clips]fields state the intended use; [checks]supplies activation, severity, and tolerances; and- the runner evaluates each applicable facet or records its typed required-unavailable prediction state.
No later layer may fabricate evidence missing from an earlier one.
E.4 Reproducible output and fail-closed coverage
measure remains engine-profile-independent: selecting an engine profile or
its importer settings does not change measurements. Existing measurement
inputs from [clips] and engine-neutral check policy retain their current
semantics. lint and generated-advice outputs that use a profile record, at
minimum:
- requested family, profile revision, engine version, and importer;
- resolved immutable profile URN and engine-facts digest;
- input format, raw-source coverage, and fully materialized document and per-clip importer settings;
- measurement and outer-output schema identities consumed;
- each check id plus facet scope, exact fact/policy/measurement basis, and primary-source reference set, including clean evaluations; and
- scoped required-unavailable reasons for prediction evidence that is missing, distinct from ordinary engine-neutral coverage gaps.
Output v17 publishes this record; output v9 remains immutable historical
evidence. Under Appendix C, animsmith-core owns the outer lint envelope, its
URN, and the
registry-independent prediction/provenance
wire types. animsmith-engine constructs those wire records through a one-way
adapter without making core depend on the engine crate. The exact JSON shape is
fixed by the v10 contract and permits a consumer to distinguish
a measured fact, a profile prediction, generated advice, and an engine
readback. Text and HTML views render that model rather than maintaining separate
conclusions.
Prediction, advice, and readback contracts reuse one versioned provenance
header for input dependency-closure identity, profile tuple and facts digest,
settings digest, schema identities, and source references. The primary-file
InputIdentity remains present, but it is not sufficient closure identity for
a text glTF or any source with external dependencies. The header also carries
a deterministic source-reference-to-resource map and the exact byte count and
digest of every distinct external resource declared or consumed by the loader
or target importer, keyed by a source-relative declaration rather than a host
absolute path. It records closure coverage as complete, partial, or
unavailable. If an applicable rule, suggestion, or readback comparison depends
on a resource whose identity cannot be established, that work is unavailable
rather than silently bound to primary-file bytes alone.
The three contracts' top-level schema identities and typed payloads remain separate because their authorities differ: a shared header must not let advice or an observation deserialize as measured or predicted evidence. This centralizes common identity plumbing without coupling the contracts' independent compatibility rules.
Profile selection never turns unavailable evidence into not_applicable.
not_applicable is reserved for a rule whose subject genuinely does not exist
under the resolved profile and project intent. An engine prediction is
required when the resolved profile and project intent make its rule
applicable, the run's CheckSelection selects its check, and the check remains
enabled after [checks] configuration. An unselected check remains visible as
unselected and is not required; an explicitly disabled check remains visible
as disabled. Once a check is selected and enabled, severity does not change
whether evidence is required. A required facet with schema-valid unavailable
measurement evidence or incomplete raw-source coverage emits typed
required_prediction_unavailable and makes lint exit 1. Malformed or
non-finite present measurement data remains a contract/operator error and exit
2; it is not prediction unavailability. The facet is not rewritten as a
content finding, and existing engine-neutral coverage gaps
remain nonblocking. Advice generation refuses the affected suggestion without
changing lint status. Unknown profile, version, importer, or setting
selections fail earlier as configuration errors; they are never downgraded to
coverage. Output v10 records the requirement state so embedded
consumers can apply the same policy without parsing CLI exit behavior.
E.5 Root motion, units, and scale
Profiles keep these independent:
- the source/interchange linear unit captured from normative format semantics or an explicit declaration in the source itself;
- the target engine's native distance unit;
- the exact source-to-target distance mapping and whether the importer preserves physical dimensions;
- source and target handedness, up/forward axes, and the importer's selected axis-conversion behavior;
- the resulting component/root, mesh-node, selected runtime-facing joint, and attachment transform scales; and
- the project's movement-ownership policy.
For example, a centimetre-native engine does not imply a component scale of
100, and a metre-native format does not prove that a source hierarchy has
unit effective scale. Rules consume the coordinate-domain and selected-node
measurements designed in issues #267 and #268. They may recommend an importer
setting only when its documented behavior accounts for animation
translations, bones, sockets, attachments, IK targets, and collision anchors.
Version one has no TOML or profile override for source units. A loader records
the format-defined or source-declared unit and its provenance in the shared raw
projection; if that evidence is absent, an applicable unit prediction is
unavailable under E.4 rather than inferred from bounds, character height,
filenames, importer defaults, or a caller assertion.
They never infer units from bounds, character height, filenames, inverse-bind
magnitudes, or an observed persistent object scale.
Animation scale channels are dimensionless and never become length-bearing
merely because the target engine uses a different world unit.
The contract can therefore state, independently, that one source metre maps
to 100 target centimetre units, physical dimensions are preserved, and the
expected imported component/root, mesh-node, and selected-joint transform
scales are dimensionless 1.0. A mismatch in any one of those claims cannot
be hidden by satisfying another.
Issue #269's accepted Appendix D result is incorporated here: whole-document unit conversion and rest/bind hierarchy reparameterization are distinct operations with separate positive-uniform, closure, and proof contracts. Profile selection neither authorizes nor freezes an automatic normalization path. It may diagnose the need for, or advise a separately requested, Appendix D operation only when that operation's own source coverage and proof requirements are satisfied.
Root motion is handled the same way. The profile describes which source or
projection a configured importer uses and what a bake/extract option means.
The artifact supplies engine-neutral horizontal displacement, signed vertical
displacement plus non-collapsing vertical excursion/extrema, and yaw evidence.
[clips.<selector>] independently declares
movement_owner_xz, movement_owner_y, and movement_owner_yaw as either
"gameplay" (the entity/controller owns world motion and the importer bakes
that component into pose) or "animation" (extracted root motion owns world
motion). Issue #466 owns these engine-neutral clip-intent fields. The existing
in_place field remains a compatibility alias only for
horizontal ownership (true means gameplay, false means animation); declaring
both spellings in one selector entry is a typed configuration error. Each valid
entry is normalized before the existing field-by-field exact/glob overlay, so
an alias in one layer and the canonical field in another remain valid. A missing
axis declaration makes only the intent-dependent rule for that axis not
applicable; it is never inferred from another axis. A filename such as _RM,
small translation magnitude, or the profile alone cannot declare intent.
Conversion between root motion and in-place remains ADR-gated and is not
authorized by this profile design.
E.6 Advice, manifests, and readback
Generated import settings use a separate immutable advisory-sidecar contract. They bind the complete input dependency-closure identity, full profile identity and facts digest, selected importer settings, measurement/check identities, and every suggestion or refusal. They must say when upstream canonicalization is required instead of presenting an unsafe importer bake as equivalent. They never affect lint exit status or claim to be measured evidence or the engine's actual imported state.
The 0.4.0 generation boundary is one input document. The engine-neutral glTF inventory may publish source indices, names, targets, and the complete input dependency-closure identity. A versioned Bevy adapter may derive typed labels and supported-extension predictions from that inventory; those fields are not mislabeled as engine-version-agnostic. Cross-file clip identity, runtime sets, collection manifests, and collection-wide presets consume the file-scoped contract from issue #409 in the 0.5.0 milestone rather than inventing a second identity here.
Engine readback has another immutable contract that records the exact engine
build, project/import-settings digest, observed outputs, and warnings. Smoke
tests compare a versioned prediction with that captured readback; successful
parsing alone is not proof that an expected clip, target, or setting survived
import. Engine SDKs, editor installations, licenses, and runtime harnesses stay
outside animsmith-core. CI fixtures must be synthetic, self-authored, or
redistribution-safe. Commercial packs may supply local, one-time validation
evidence, but are never repository fixtures, workflow downloads, caches, or
uploaded artifacts.
E.7 Primary-source and version discipline
Engine behavior is mutable external state. Every built-in rule cites a primary engine or specification source, records the verified target version and date, and is rechecked when that target or rule changes. Marketplace observations and local import probes are useful counterexamples, not authority for a general engine claim.
The design was reconciled on 2026-08-20 against these current primary sources:
- Unity 6000.3 Animation Clip, Root Motion, and model-import manuals: https://docs.unity3d.com/6000.3/Documentation/Manual/class-AnimationClip.html, https://docs.unity3d.com/6000.3/Documentation/Manual/RootMotion.html, and https://docs.unity3d.com/6000.3/Documentation/Manual/FBXImporter-Model.html;
- Unreal Engine 5.8 Animation Sequence and FBX import documentation: https://dev.epicgames.com/documentation/en-us/unreal-engine/animation-sequences-in-unreal-engine?application_version=5.8 and https://dev.epicgames.com/documentation/en-us/unreal-engine/fbx-import-options-reference-in-unreal-engine?application_version=5.8;
- Godot 4.7 advanced 3D import and scene-importer documentation: https://docs.godotengine.org/en/4.7/tutorials/assets_pipeline/importing_3d_scenes/advanced_import_settings.html and https://docs.godotengine.org/en/4.7/classes/class_resourceimporterscene.html;
- Bevy 0.19
GltfAssetLabel,AnimationTargetId, and animation API rustdoc: https://docs.rs/bevy/0.19.0/bevy/gltf/enum.GltfAssetLabel.html and https://docs.rs/bevy/0.19.0/bevy/animation/struct.AnimationTargetId.html; and - the glTF 2.0 specification: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html.
Unversioned latest documentation is not retained as profile evidence.
E.8 Implementation sequence and non-goals
The dependency order is:
- this decision record;
- coordinated engine-neutral measurement work for per-property coverage and horizontal/vertical root trajectory plus yaw (#402 and #408);
- independent per-axis clip movement-ownership intent (#466), required before root-motion prediction but not by the measurement/profile substrate;
- the dedicated raw importer-sensitive source-facts projection (#463);
- strict engine-profile registry, config, and resolution types without prediction behavior (#464);
- bounded, rooted dependency-closure capture from the raw resource domain (#475);
- reproducible output-v17 provenance for the resolved target, facts digest, required-prediction state, and per-check basis (#465 and #482);
- per-concern prediction rules, split from umbrella issue #154 into bounded engine/rule slices where their input facts differ, including the Unity Generic V6 root-motion path;
- single-document preset advice and separate glTF-inventory/Bevy-adapter generation (#155 and #156);
- prediction-versus-readback feasibility and harness decisions (#151); and
- per-engine guides generated from the accepted rules and evidence (#157).
The two measurement tickets share schema, diff, rendering, and contract surfaces, so they require one version plan and sequential implementation rather than sibling branches. They do not depend on profile selection. Bevy graph- template demand (#163) remains gated by the manifest and a demonstrated consumer; the profile design does not turn it into an implementation commitment.
This decision does not add engine dependencies, custom TOML profile definitions, automatic unit or root-motion conversion, retargeting, compression simulation, artistic thresholds, cross-file collection policy, or claims of visual, deformation, artistic, gameplay, or runtime correctness. Those require their own evidence and, where they rewrite motion, a separate accepted design and proof boundary.
Appendix F — decision record: file-scoped clip identity and collections
Issue #409 decides the boundary used when logical clips and runtime
relationships span physical source files. The decision is intentionally a
protocol decision followed by bounded implementation slices. It does not make
the existing multi-file spelling of lint collection-aware.
F.1 Separate collection authority
AnimSmith provides two independently versioned contracts:
urn:animsmith:schema:collection-manifest:1, a strict TOML declaration; andurn:animsmith:schema:collection-output:5, deterministic JSON evidence.
The historical urn:animsmith:schema:collection-output:1 and
urn:animsmith:schema:collection-output:2 contracts remain immutable. V2
derives its exact shape, budgets, and reader rules from V1, changing only the
nested ordinary output envelope identity from output v10 to output v11. The
historical urn:animsmith:schema:collection-output:3 derives from V2 and adds one
bounded dependency_closure state to every source. A complete state retains the
loader's exact DependencyClosureIdentityV1; partial and unavailable states
retain a nonempty, sorted, unique sequence from the seven closed
DependencyClosureCoverageReasonV1 values. The current collection-output:11
embeds the current output-v19 lint envelope with measurements-v18 while
preserving collection-output-v8 and its nested output-v16, and
collection-output-v4 with its nested output-v12/measurements-v15, as immutable
historical evidence. Only complete closure coverage can
establish a source, logical clip, or runtime-set member. This keeps primary
input identity, optional manifest digest pin, and complete loader-input
identity separate.
They are consumed and produced by an explicit command:
animsmith collection lint COLLECTION.toml --format json
The command name, input identity, and output identity are distinct from
ordinary animsmith lint FILE.... Existing lint and measure invocations remain
document-local and continue to emit output-v13 with measurements-v16. A file
extension, the presence of multiple inputs, or repeated embedded take names
never switches commands implicitly.
The collection manifest is the sole AnimSmith authority for:
- durable logical clip identifiers;
- physical source and take bindings;
- optional source digest pins; and
- cross-file runtime-set identity and membership.
It is not a second authority for rig roles, per-clip expectations, check
severity, or check tolerances. Those remain in an explicitly selected existing
animsmith.toml config basis and retain their ordinary per-document selector
semantics. The collection manifest may select a complete config basis for a
source; it cannot override individual config fields.
An assembly recipe is not a collection manifest. Assembly is a mutating producer that chooses a base, projects source domains, and publishes one artifact. Collection lint preserves each physical source and evaluates relationships without renaming, merging, or rewriting it. Likewise, the animation-pack evaluation manifest remains a skill-level evidence and taxonomy format. It may supply retained validation evidence, but it is not accepted as the CLI protocol.
The repository-owned structured evaluation model is a separate skill-level
authority. animation-pack-evaluation:1 remains frozen to independently
validated collection-output:2. Current evaluations use the additive
animation-pack-evaluation:2 contract and bind the exact raw
collection-output:11 digest and byte count plus its manifest identity. V2
retains every scrubbed source state, including complete dependency-closure
identity or the full typed partial/unavailable reason set, and proves exact
clip source/take and ordered runtime-set relationships against that V11 input.
The V2 validation boundary requires the explicitly selected checkout-matched
AnimSmith binary and invokes a hidden validation-only collection subcommand
that calls the one authoritative V11 strict reader before any projection.
Schema validity plus a matching raw digest is not sufficient, and there is no
second Python semantic mirror.
Clip duration and speed preserve exact measured, unavailable, or not-applicable
availability rather than collapsing the latter two categories.
Unavailable rows are mandatory typed soft failures, not grounds to shrink the
model to surviving records. A separate projection sidecar was rejected because
it would duplicate clip/set authority and create a third versioned contract;
V2 is the single report authority, while the exact hashed V11 document remains
the mechanical evidence binding. Neither model is accepted as a CLI protocol.
F.2 Logical and physical clip identity
Every collection clip has one user-declared logical id and one physical binding. Neither is sufficient alone.
The logical id is the durable public reference used by runtime sets and future sidecar schemas. V1 ids are portable, lowercase ASCII, slash-separated tokens:
com.example.pack/locomotion/walk-forward-in-place
Each token starts with [a-z0-9] and then contains only [a-z0-9._-]; empty,
. and .. tokens are invalid. An id has at least two tokens and at most 255
bytes. The namespace is a collision-avoidance convention, not an ownership or
compatibility claim. It is opaque to AnimSmith: filenames, directories, take
names, vendor metadata, and clip behavior never generate or rewrite it.
The physical binding contains:
- one manifest-local source key;
- a zero-based source-local take index; and
- the exact expected embedded take name at that index.
Index and name are both required. Name alone is ambiguous when many files or
multiple takes use Take 001; index alone would silently accept reordered or
renamed source content. The source record supplies the safe root-relative
locator, observed primary InputIdentity (SHA-256 and bytes), optional
expected SHA-256, complete or typed-incomplete dependency-closure state, and
config basis. A source rename does not change the logical id, but
the manifest must be edited explicitly and output records the changed physical
locator. A digest pin is a binding assertion, not the logical identity.
One physical (source, take_index) may have only one logical id. Two ids bound
to the same physical take are an ambiguous duplicate and invalidate the
manifest. One source may legitimately bind several distinct take indices.
Distinct source locators with equal content digests remain distinct physical
sources; content equality does not create an alias or compatibility claim.
F.3 Manifest V1 shape and composition
The normative implementation issue freezes the exact JSON Schema and Rust wire types, but V1 has this authority shape:
schema = "urn:animsmith:schema:collection-manifest:1"
schema_version = 1
collection_id = "com.example.pack"
input_root = "assets"
[[sources]]
key = "walk-forward"
path = "locomotion/walk_forward.fbx"
config = "configs/locomotion.toml"
expected_sha256 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
[[clips]]
id = "com.example.pack/locomotion/walk-forward-in-place"
source = "walk-forward"
take_index = 0
take_name = "Take 001"
[[runtime_sets]]
id = "com.example.pack/sets/walk-ring"
kind = "directional-blend"
members = [
"com.example.pack/locomotion/walk-forward-in-place",
"com.example.pack/locomotion/walk-left-in-place",
]
The schema/version pair, collection id, nonempty source/clip arrays, and every
field shown for clips are required. input_root, source config, and
expected_sha256 are optional. An absent config means Config::default();
there is no ambient config discovery. A selected config path applies to the
whole loaded source document. The same source cannot select different configs
for different takes.
The collection id and source keys use one token from the lowercase ASCII
logical-id grammar (no /) and are each limited to 255 bytes. Source keys are
manifest-local plumbing rather than public clip identity. Every clip and
runtime-set id must start with the exact collection id followed by /; this
makes the declared collection namespace an enforced prefix without treating it
as an ownership claim. Unknown fields, duplicate ids/keys, duplicate
source/take bindings, malformed digests, dangling source references, dangling
or repeated set members, and sets with fewer than two members are operator
errors.
Runtime-set ids use the logical-id grammar. V1 set kinds are membership vocabulary only:
gait-group;sync-group;directional-blend;speed-blend;transition-chain;mask-composition;retarget-group;paired-interaction; andmotion-database.
The closed list prevents typo-created semantics. A kind does not itself claim that its members blend, retarget, synchronize, or play correctly. V1 preserves declared member order because some relationship consumers assign coordinates or roles by declaration order. Output sorts source, clip, and set records by their stable ids, while each set retains its declared member order.
V1 intentionally carries no generic policy map and no untyped TOML payload. Check-specific coordinates, tolerances, or controller policies require a typed, versioned contract owned by that check. They must not be smuggled into collection V1 as open data merely to avoid a schema revision.
The parser and wire reader are bounded before allocation. The implementation
slice must freeze limits for manifest bytes, source rows, clip rows, runtime
sets, aggregate memberships, identifier bytes, and output bytes, with exact
N/N+1 tests and checked arithmetic. Safe path spelling reuses the immutable
dependency-closure V1 key limits: 4,096 UTF-8 bytes and 128 components.
F.4 Rooted access and immutable sources
The manifest directory is the control root. input_root, when present, is a
relative directory below it; source paths resolve below that input root. When
it is absent, source paths resolve below the manifest directory itself, never
the process working directory or another ambient root. Config paths always
resolve below the manifest directory. All declared paths use / as the
separator and are retained as UTF-8 evidence labels, never as host absolute
paths.
The CLI/frontend owns filesystem access. It reuses
DependencyResourceKeyV1 with ParserRelativePath for lexical validation and
uses a rooted resolver equivalent to the assembly/material boundaries. It
rejects, before source execution:
- empty,
.,.., absolute, drive-qualified, UNC, remote, or backslash paths; - NUL, control characters, oversized keys, and excessive components;
- a symbolic-link component or final symbolic link;
- a non-directory input root, non-regular config target, or existing non-regular source target; and
- a canonical target outside its declared root.
Case and Unicode are preserved exactly; AnimSmith does not case-fold or Unicode normalize a declaration into another identity. Lexical duplicates and aliases the host resolver exposes as one canonical target are rejected. V1 does not claim portable hard-link identity beyond what the host safely exposes.
The source is opened read-only, measured, and never renamed or rewritten. Observed identity is derived from the bytes actually analyzed. An expected digest mismatch is evidence about a readable binding, not permission to use the bytes as if they matched. Root containment and symlink refusal prevent ordinary path escapes; they do not claim to defeat a privileged actor concurrently mutating the filesystem between checks and reads. That portable TOCTOU limit is retained in the public security notes.
F.5 Existing config and document-local checks
Collection execution loads each source with exactly one selected existing
config basis. [clips."<selector>"], [gait_groups], and [sync_groups]
continue to resolve embedded clip names inside that one document. Reaching a
config through a collection does not make its selectors match logical ids and
does not reinterpret a previously valid single-document project.
Document-local gait/sync checks may run as part of the nested source analysis. Collection runtime sets are separate records keyed by logical ids. A shared name does not merge the two domains, and membership is never inferred from a document-local group, filename, directory, or evaluation taxonomy.
Core remains format- and filesystem-neutral. It may own validated bounded logical/source/take/set values and deterministic evidence types. TOML parsing, rooted access, loader dispatch, and command exit routing remain in the CLI frontend. Format loaders continue to own their raw-source and dependency closure projections.
F.6 Collection output and completeness
collection-output:1 is a historic strict envelope, not ordinary output v12. It contains:
- the manifest schema identity, observed manifest SHA-256/bytes, collection id, tool version, and frozen collection budget identity;
- one source record with its safe declared locator, observed or unavailable source identity, expected-digest result, config state/path/digest, loader state, and nested ordinary per-document result when available;
- one clip record with logical id, source reference, take-index/name witness, binding state, and exact nested measurement/check references;
- one runtime-set record with id, kind, every declared member, each resolution state, an explicit evaluation lifecycle, coverage gaps, findings, and any later check-specific measured payload; and
- deterministic totals and terminal work counters needed to prove bounds.
Nested ordinary results retain their published schema identities and strict readers. They are not copied into a nearly equivalent collection-only shape. Host absolute paths and raw host I/O messages are never emitted. A source row uses its safe declared locator; stable typed states carry failures.
Collection-output V1 additionally carries one existing ClipMeasurements
value per established logical clip, selected from a duplicate-safe
normalized-index projection only after the raw source take index and exact
authored name map to that normalized index. This additive in-memory projection
does not change measurements-v15: ordinary output remains name-keyed. When two
authored names remain equal after loader normalization, their indexed
measurements remain distinct while a name-addressed check reference is typed
unavailable; no first/last match is guessed. Runtime-set V1 completeness
depends on the binding and indexed measurement, not that optional check
reference, and its decision remains not_evaluated until a later typed set
check owns a conclusion.
The normalized name is bounded at 4,101 bytes: the 4,096-byte authored-name
maximum plus # and the largest duplicate ordinal possible under the
4,096-clip manifest cap. Available nested measurement keys retain output-v10's
4,096-byte bound.
When that derived key exceeds output-v10's immutable 4,096-byte text bound, V1
retains physical binding and indexed measurements but marks the nested document
and name-addressed check reference nested_output_unavailable, then exits 1;
it never embeds a schema-invalid ordinary envelope.
The immutable output budget permits 1 GiB per primary source, 16 GiB of
aggregate primary-source reads, and 256 MiB of serialized/readback JSON. It
reuses the manifest V1 source, clip, runtime-set, aggregate-membership, and
aggregate-work limits. Primary reads and strict output readback stop at N+1;
checked work counters retain the terminal observation needed to distinguish an
exact limit from exhaustion.
Producer serialization also writes through a 256 MiB N+1 sink before any full
envelope can be allocated, and its self-reported byte count converges over the
finite decimal widths permitted by that same cap.
After an aggregate primary-read N+1 witness, later declared sources are retained
as aggregate_exhausted without being opened, so the terminal counter cannot
advance beyond that single witness.
The manifest digest binds the exact input bytes. Reordering otherwise equivalent TOML therefore changes that input identity, while the decoded source, clip, and set record arrays remain canonically ordered and byte-identical. The contract does not erase source-manifest changes merely to make two different inputs share a full output digest.
A set may report a complete conclusion only when every declared member needed by that conclusion resolved and supplied the required evidence. Missing, rejected, mismatched, quarantined, or unmeasurable members remain visible. The evaluator must not shrink a set and report a clean result over the survivors. Member ordering in output is deterministic and every group-level measurement names its exact member basis.
Logical ids, equal source digests, common skeleton names, or membership in one set prove none of retargeting, blend, artistic, deformation, engine, gameplay, or cross-pack compatibility. Those claims require their own declared policy and evidence.
F.7 Failure and exit policy
The collection command introduces one explicit aggregate-data exception to the
ordinary lint operator-error rule. The exception is scoped to a manifest that
has already passed all control-plane validation:
- exit
2with no collection envelope for an unreadable or malformed manifest, unsupported schema identity, budget violation, duplicate/unsafe declaration, existing non-regular source target, missing/unreadable/malformed selected config, serialization failure, or other control/tool error; - exit
1with a schema-complete collection envelope when a lexically safe declared source is missing or unreadable, when readable bytes are unsupported or malformed, when a digest/take binding mismatches, or when another required collection member cannot be established; and - otherwise apply the existing lint severity and required-unavailable rules to
available nested/check evidence, with exit
0only when none requires1.
Missing/unreadable bytes and readable-but-rejected bytes have distinct typed
states even though both make a required collection incomplete. Unsafe paths and
configs are never downgraded to data availability. This preserves all declared
members for batch automation without silently changing ordinary lint FILE...,
which retains exit 2 and no output for an unreadable primary file.
An unavailable measurement inside a successfully loaded member follows the
owning check's existing coverage/required policy; collection membership alone
does not promote every nonblocking coverage gap to exit 1. A source that
cannot be loaded at all is different: the declared physical clip identity was
not established, so the collection result is incomplete and exits 1.
F.8 Retained evidence exercise
The retained, externally generated animation-pack evidence behind #409 contains the required marketplace shape: many physical files repeat one embedded take name, while meaningful logical motions and runtime sets live outside those takes. The public eight-pack rollup reports 895 individual files, 582 logical motions, and 90 runtime-set records without publishing licensed source bytes.
The mapping exercise is lossless for the foundation facts:
- retained per-file locator and digest facts map to source rows;
- retained logical-motion identities map to namespaced clip ids;
- selected source-local takes map to the index-plus-name witness;
- retained runtime-set membership maps to logical-id member lists; and
- evaluator roles, confidence, capability profiles, workflow stages, report prose, and engine observations deliberately remain outside this contract.
This shows why the evaluation manifest must not be reused wholesale. It proves the collection identity and membership need while keeping evaluation taxonomy and product conclusions at their existing skill/report authority.
The retained license-safe spike fixture is
crates/animsmith/testdata/collection-spike.
Its self-authored, motion-free glTF files cover two distinct sources with the
same bytes and embedded Take 001, one two-take source, cross-file gait and
sync sets, an explicit config basis, and duplicate-member, missing-member, and
escaping-source failures. The preservation table pins each locator, byte count,
digest, logical id, and index-plus-name witness without treating that retained
fixture as output-schema authority.
Repository tests use only synthetic/self-authored or explicitly redistribution-safe multi-file assets. Licensed pack bytes and motion-bearing derivatives remain outside source, CI, caches, logs, and public artifacts.
F.9 Implementation sequence and non-goals
The 0.5.0 order is:
- this accepted decision record and bounded issue decomposition (#409);
- strict collection-manifest V1 types, budgets, logical/physical identity validation, and rooted resolver with license-safe fixtures (#545);
- explicit
collection lint, deterministic collection-output V1, nested per-document execution, aggregate failure routing, and strict reader tests (#546); - contact-sidecar and transition-family schema spikes (#147 and #148) using the logical clip-reference vocabulary without generation code;
- truthful gait-phase statistic publication (#504), after separately choosing the existing maximum circular deviation from mean or a deliberate changed threshold statistic; and
- root-speed/stride set evidence (#411), split so raw evidence and any typed controller policy do not expand collection V1 into an open policy map.
The parser/resolver and execution/output work are separate PRs because rooted I/O review and public output/exit review have different failure surfaces. Both must land in 0.5.0; closing the ADR alone does not deliver the milestone's collection foundation.
This decision does not add collection report HTML, generated transitions, engine graph output, archive extraction, filename-based intent inference, source renaming/merging, motion rewriting, cross-pack compatibility claims, or a generic untyped runtime policy language. The strict contact-fragment sidecar producer specified in F.10 is the narrow exception; host-side merge and all runtime interpretation remain later consumers or separately accepted contracts.
F.10 Contact-fragment V1 (#147)
Issue #147 freezes the interchange contract for contact facts. In 0.6.0,
issue #152 delivers its narrow strict producer: animsmith generate contact-fragment and
animsmith collection generate-contact-fragment sample stance support and
atomically publish one source-bound fragment. The collection command reloads
its selected manifest source rather than consuming collection-output evidence.
Neither issue adds host-side sidecar merge or a runtime event system. Its
independently versioned identity is:
urn:animsmith:schema:contact-fragment:1
The fragment is an importable envelope, not a second independently edited
authority beside a host's measured sidecar. A V1 envelope contains schema,
schema_version, producer (tool and tool-version), artifact (the exact
primary source bytes' existing InputIdentity wire {sha256, bytes}),
dependency_closure_identity (the complete existing
DependencyClosureIdentityV1,
serialized through that same {sha256, bytes} wire), one clip reference, a
measured positive duration_s, and an ordered events array. The sha256
field makes both the algorithm and digest explicit; this contract does not
introduce an {algorithm, digest, bytes} shape. Both identities are mandatory.
The closure identity binds the primary source and every dependency in the
loader's complete versioned dependency domain; partial or unavailable closure
coverage refuses fragment generation. This is the identity only, not the full
dependency_closure record used by output v10. The captured closure's
primary_input must equal artifact; the producer and any consumer checking
staleness validate that relationship against the complete captured closure.
Unknown fields are rejected by the strict core reader implemented in 0.6. A mismatch in either
identity makes the fragment stale rather than authorizing a consumer to use it.
AnimSmith implements the format-neutral strict reader and bounded
canonicalization/identity seam in animsmith-core, plus the strict producer
above. The producer loads one exact direct take or reloads one exact
manifest-selected take, requires complete finite bilateral stance evidence,
and publishes the canonical fragment as a single durable sidecar. It does not
implement contact transforms, host-side merge, runtime scheduling, engine
mapping, physical-contact inference, or gameplay semantics; those consumer
boundaries remain separately deferred.
V1 deliberately requires the complete modeled closure, including declared dependencies such as textures that may not affect contact calculations. It can therefore refuse generation when an unrelated dependency is unavailable. This conservative policy avoids a second format-specific dependency-relevance authority; narrowing it requires a separately versioned contract.
The clip reference is a tagged scope rather than a filename or animation-array index:
collectioncarries the #409 logical clip id together with its physicalsource, take-index, and exact take-name witness. The witness lets a host detect a manifest edit or take reorder even when a logical id was retained.documentcarries the exact embedded clip/take name and the primary-plus- dependency-closure binding that scopes that name. If the document exposes duplicate names or cannot establish an unambiguous source-local take identity, the reference is unavailable and the fragment is refused; no first/last animation match is guessed.
An event has a stable event_id, an engine-neutral role and phase, and
either a point time or a window {start, end}. Times are normalized to the
closed interval [0, 1]; start <= end is required for a window. A point or
window is never identified by its array position. Optional confidence and
explicitly versioned extension data are non-normative additions; confidence,
when present, is finite and lies in [0, 1]. Unity, Unreal, Bevy, and
gameplay-specific event types do not belong in V1. An
event_id is unique within the clip and remains stable when the same fact is
regenerated. It is opaque to consumers, is not an engine handle, and is not
derived from array position.
V1's closed role vocabulary is left_foot, right_foot, left_hand,
right_hand, left_toe, right_toe, left_knee, right_knee, left_elbow,
right_elbow, root, prop, and body. Its closed phase vocabulary is
begin, end, and marker. A producer that needs another semantic role or
phase must propose a versioned contract extension; it must not smuggle an
engine enum or arbitrary string into V1.
Canonical bytes use the complete RFC 8785 JSON Canonicalization Scheme
(JCS), including its object-member
sorting, string escaping, and number serialization rules. Every value,
including an opaque extension payload, must be JCS-canonicalizable or the
fragment is refused; an extension does not supply a private key-ordering rule.
The reader accepts decoded IEEE-754 numbers only within the safe magnitude
[-9007199254740991, 9007199254740991], including identity byte counts and
extension-payload numbers. Lexical decimal aliases are therefore the same
decoded JCS value, not distinct V1 facts. Extension payloads are reparsed from
their bounded JCS bytes before storage, so canonical serialization followed by
strict reread preserves the validated fragment value.
Before JCS serialization, a mixed point/window event sort key is exactly
(start, kind_rank, end_key, role, phase, event_id): a point sets
start = time, kind_rank = 0, and the end sentinel end_key = null, which
sorts before every numeric window end; a window uses its declared start, has
kind_rank = 1, and sets end_key = end. Thus a
point and a window at the same start have a deterministic order, including
when their numeric endpoints are otherwise equal. Every string comparison in
that tuple, including the opaque event_id, is lexicographic by unsigned
UTF-16 code units exactly as RFC 8785 orders object property names; no Unicode
normalization is applied. Extension arrays retain
their declared order. Seconds and frame numbers, if
present for display, are derived values and never identity or comparison
coordinates. The strict core reader rejects unknown fields in the
core envelope and events, duplicate event ids, non-finite values, out-of-range
times, and ambiguous references before accepting the fragment. These rules
make a regenerated fragment reviewable without making event detection part of
this contract.
V1 is bounded before retention or canonicalization. A contact-fragment source
is at most 8,388,608 bytes of UTF-8 JSON with at most 32 nested arrays/objects,
4,096 events, and 256 extensions. Every authored string is at most 4,096 UTF-8
bytes; identifiers (including event_id, producer fields, schema ids, source
keys, and logical ids) are additionally at most 255 bytes, except the exact
64-lowercase-hex SHA-256 spelling. One extension payload is at most 262,144 JCS
bytes and 16 levels deep. The complete canonical fragment is also at most
8,388,608 JCS bytes. A contact-transform-result source and its complete
canonical result are each at most 16,777,216 bytes; it contains at most 4,096
control points and 4,096 event outcomes and embeds a contact fragment obeying
the preceding limits. Container depth is the number of object/array containers
on the path from the root to a value, inclusive: the top-level envelope object
has depth 1. An extension payload object participates in that full-envelope
count and is also depth 1 for its separate 16-level payload limit. A strict
reader accepts each exact maximum and rejects the first byte, element, string
byte, or nesting level above it while decoding, before retaining that excess
value or allocating an unbounded canonical buffer. Canonical byte limits are
enforced with a bounded JCS sink.
The published JSON Schema's maxLength limits are Unicode-code-point limits;
the reader's UTF-8 byte limits are deliberately stricter for non-ASCII text.
Extensions, when present, are an array of strict envelopes. Each envelope has
exactly schema, schema_version, and payload; schema is the extension's
versioned identity, schema_version is a positive integer, and payload is a
JSON object validated by that extension's schema. The core reader rejects
unknown envelope fields and preserves a well-formed unsupported extension as
opaque data; a consumer that requires its semantics may reject the fragment
with a typed unsupported-extension result. Core fields remain closed and an
extension cannot redefine them or add an engine event type.
A minimal collection-scoped envelope is illustrative of the normative shape (the two input identities are not second clip digests):
{
"schema": "urn:animsmith:schema:contact-fragment:1",
"schema_version": 1,
"producer": {"tool": "animsmith", "version": "0.6.0"},
"artifact": {"sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "bytes": 123456},
"dependency_closure_identity": {"sha256": "1111111111111111111111111111111111111111111111111111111111111111", "bytes": 456},
"clip": {"scope": "collection", "logical_id": "com.example.pack/locomotion/walk-forward-in-place", "source": "walk-forward", "take_index": 0, "take_name": "Take 001"},
"duration_s": 1.2,
"events": [
{"event_id": "left-foot/0", "role": "left_foot", "phase": "marker", "time": 0.23, "confidence": 0.92},
{"event_id": "right-foot/0", "role": "right_foot", "phase": "begin", "window": {"start": 0.71, "end": 0.79}}
],
"extensions": [{"schema": "urn:example:contact-quality:1", "schema_version": 1, "payload": {"quality": "high"}}]
}
The standalone document clip variant replaces the collection fields with
clip_name and retains both envelope input identities. A host may deterministically
merge this fragment into its existing sidecar, but the host remains the sole
authority for that file's unrelated fields.
Trim, slice, resample, and time-warp are contract-preserving only when the
contact fragment is transformed in the same operation. The minimal transform
result has identity
urn:animsmith:schema:contact-transform-result:1 and fields schema,
schema_version, operation, input, outcome, event_outcomes, and,
only when outcome = "transformed", output; a refusal additionally has a
refusal object containing exactly a stable code and message for display.
The strict tagged operation object has exactly these structurally
representable V1 shapes:
{"kind":"trim","version":1,"interval":{"start":a,"end":b}};{"kind":"slice","version":1,"interval":{"start":a,"end":b}};{"kind":"resample","version":1,"mapping":"identity"}; and{"kind":"time_warp","version":1,"output_duration_s":d,"control_points":[{"input_time":0,"output_time":0}, ...]}.
The control-point array is ordered, finite, starts at (0, 0), ends at
(1, 1), and has strictly increasing input and output coordinates. The mapping
is piecewise linear: an exact input knot maps to its declared output knot; for
adjacent knots (x0, y0) and (x1, y1), an input t strictly between them
maps to y0 + ((t - x0) / (x1 - x0)) * (y1 - y0). Point times and both window
endpoints use that same mapping. Structural decoding retains a known V1
operation even when its finite numeric domain or ordering is invalid, so a
refusal can echo the request. An unknown kind, version, field, mapping token,
missing field, or malformed field type is a strict request/reader error and
produces no V1 transform result or event outcomes. input
has exactly
{artifact:{sha256,bytes},dependency_closure_identity:{sha256,bytes},fragment:{sha256,bytes}}
and refers to
the separately supplied input fragment; the input fragment is not duplicated
inline. A successful output has exactly
{artifact:{sha256,bytes},dependency_closure_identity:{sha256,bytes},fragment:{sha256,bytes},contact_fragment};
its
inline contact_fragment is the complete transformed
urn:animsmith:schema:contact-fragment:1 value. The output fragment
identity must equal the canonical serialized inline fragment's {sha256, bytes}, and the output artifact identity must equal the inline fragment's
artifact binding. The output dependency_closure_identity must equal the
inline fragment's closure binding and must be freshly captured for the output
artifact rather than copied from the input. Its captured closure's
primary_input must equal the output artifact. The output is absent, never a
null placeholder, on refusal.
V1 operation mappings are exact: trim and slice carry a finite retained
interval [a, b] with 0 <= a < b <= 1 and map an in-range t to
(t - a) / (b - a); resample carries the identity mapping on normalized
time; and time_warp carries the piecewise-linear control points above. A point outside
a trim/slice interval has outcome outside and is omitted only from a
successful output fragment. For a point, t < a || t > b is outside and both
endpoints are retained. A window is wholly outside exactly when
end < a || start > b, and is fully contained exactly when
a <= start && end <= b; equality at either retained endpoint is contained.
Every other window crosses a boundary and has outcome refused with code
partial_window, refusing the entire operation—no partial window is silently
clamped.
Preserving an opaque extension through an artifact transform cannot prove
that extension-owned times or event references remain valid. A transformer may
succeed only when it recognizes every extension schema/version and implements
that extension's operation-specific transform contract. Otherwise it refuses
the whole operation before event inventory with top-level code
unsupported_extension, an empty event_outcomes, and no output fragment.
The core transform context therefore carries the handler-produced extension
outputs, in exact input order and with the same schema/version at every
position. It never treats a caller-declared support set or an automatic opaque
payload copy as proof that an extension was transformed.
The inline output fragment's duration_s is exact: trim and slice use
input_duration_s * (b - a), resample preserves input_duration_s, and
time_warp requires a finite positive output_duration_s field in its
operation object, which the output fragment must equal. These duration rules
apply even when every event is outside the retained interval. If the rounded
trim/slice duration is not finite and positive, the operation refuses before
event inventory with invalid_value and an empty event_outcomes list.
Every V1 time and duration number is a finite IEEE 754 binary64 value.
Arithmetic uses round-to-nearest, ties-to-even after each operation in the
following order; fused multiply-add and extended-precision intermediates are
not permitted. Write rn(x) for that rounding. Trim/slice first computes
span = rn(b - a), then mapped = rn(rn(t - a) / span) and
output_duration_s = rn(input_duration_s * span). Between time-warp knots it
computes dx = rn(x1 - x0), alpha = rn(rn(t - x0) / dx),
dy = rn(y1 - y0), and mapped = rn(y0 + rn(alpha * dy)); an exact knot
bypasses interpolation and returns its declared output_time. Resample returns
the decoded input value unchanged. Any non-finite intermediate is
invalid_value. These binary64 results are the numbers supplied to JCS.
After input binding and fragment identity validation, event_outcomes contains
exactly one object per input event, in the fragment's canonical input order.
Each object has exactly event_id and outcome, plus value only when
outcome = "transformed" (the exact mapped point or window) and code only
when outcome = "refused". A pre-inventory binding, fragment-identity,
operation-validation, or extension-support refusal uses an empty
event_outcomes list. Global success means
outcome = "transformed", an output is present, and every event outcome is
transformed or outside; global refusal means outcome = "refused", a
typed top-level refusal is present, and output is absent. A refusal discovered
after inventory has at least one refused event outcome. partial_window means
the exact crossing predicate above; invalid_mapping means a structurally
known operation has an out-of-domain interval, invalid endpoint/order, or
non-monotonic control points; invalid_binding means the supplied fragment,
primary artifact, or dependency-closure identity does not match current input;
and invalid_value means valid finite inputs produced a non-finite arithmetic
intermediate or a rounded trim/slice duration outside the fragment's finite
positive domain. Unsupported extension transformation uses
unsupported_extension as specified above. A malformed fragment/result or
duplicate, missing, or unknown event-outcome identity is a strict reader error,
not another refusal result. The result records the operation, bindings, and
outcomes. Core implements the exact mapping and independently rederives a read
result against its separately supplied input fragment and external transform
context. That context carries the current input and output artifacts, their
complete captured closures, the expected output producer, and any
operation-specific transformed extension outputs. Core validates that each
closure's primary input is its corresponding artifact before deriving the
identity recorded in a successful result; detector policy, asset mutation, and
publication remain outside this contract.
AnimSmith owns the strict sampled stance-support contact facts and these identity/time/transform semantics. The host owns final file layout and merge, unrelated measurements and provenance, runtime scheduling, and engine-native mapping. The delivered contact-fragment producer does not perform contact transforms, infer gameplay meaning, validate foot placement, or claim engine, artistic, or gameplay correctness. Host-side merge and runtime behavior remain deferred.
F.10A Skeleton compatibility V1 (#158)
Issue #158 adds a strict structural seam for declared source/target skeleton comparison without implementing retargeting, mesh rewrite, engine-avatar generation, masking, contacts, or gameplay proof. Its immutable JSON result identity is:
urn:animsmith:schema:skeleton-compatibility:1
The public command is
animsmith skeleton compare SOURCE TARGET --correspondence FILE --format json|text.
FILE is a separate strict TOML authority with independent identity
urn:animsmith:skeleton-correspondence:1. It binds the exact source and
target primary input identities, explicit selected roots and finite selected
node-name sets on both sides, either exact-name or one-to-one explicit
correspondence, and every numeric tolerance. Unknown fields, duplicate names
or targets, malformed or stale identities, ambiguous selectors, non-finite
tolerances, oversize control input, or source/target primary inputs above the
existing 64 MiB bound are operator errors that emit no result.
The result records exact source and target primary identities, complete
dependency-closure identities when same-load capture is complete, the exact
correspondence bytes, canonical selected-skeleton provenance for each side,
deterministic row order, and separate non-scoring evidence facets for source
skin membership, inverse-bind accessors, and deformation model coverage.
Topology/rest rows are the only scoring surface in V1. compatible means
every required row was available and within tolerance; partial retains mixed
available and unavailable required evidence even when another retained field
mismatches; otherwise any required mismatch is incompatible; and
not_evaluated means no correspondence row was evaluable. Missing source/target
names and parent mismatch remain explicit typed rows rather than being
collapsed into an aggregate score.
The seam compares only the declared selector members. It does not infer a
skeleton subtree from attachments, scene reachability, or skin membership.
Parent correspondence and normalized child length walk through unselected
intermediate nodes to the nearest selected ancestor, so they describe the
declared projection and do not claim complete raw-hierarchy equality.
selected_skeleton_identity is derived provenance, not a copied authored
profile: AnimSmith serializes a canonical, name-ordered projection of each
selected node's measured structural evidence and the declared selector, then
records that exact identity. Local and rest-world transform deltas reuse the
existing normalized source-skeleton measurements contract; world-space
comparison stays unavailable when the loaded source evidence does not prove a
finite right-handed unit-orthonormal rest-world basis.
Non-pass rows and unavailable evidence sides carry a typed remediation surface and likely remedy class. This routes work to correspondence, the selected skeleton authority, selected skin/bind evidence, or a measurement boundary without guessing whether a project integrator, artist, or vendor is to blame. The human text view renders those fields together with exact identities, matching authority, tolerances, row deltas, and facet details.
F.10B Foot-cycle parameterization planner V1 (#18)
Issue #18's first post-result implementation slice adds the separate strict
urn:animsmith:schema:foot-cycle-parameterization:1 TOML declaration and pure
format-neutral map planner. The declaration binds exact manifest bytes, one
existing ordered gait-group, one explicit reference, one safe fragment path
per member, the future generation directory, and finite positive segment-slope
bounds whose inclusive interval contains identity slope 1.0. It also requires
a closed [proof] table whose finite maximum gait-phase spread and contact
boundary phase error each lie in [0, 0.5], and whose finite minimum
left/right amplitude is non-negative. These values are bound by the exact
parameterization bytes and retained in the plan, with no defaults, config
lookup, inference, or cross-member merge. The planner
accepts only canonical collection-scoped fragments with
the exact known stance-detector V1 provenance and independently measured typed
Root/Hips evidence bound to the same exact artifact, dependency closure, and
collection source/take witness. Missing, ambiguous, malformed, or non-finite root evidence
refuses. It retains signed endpoint X/Z displacement and signed accumulated
unwrapped yaw, while the unchanged admission applies binary64 horizontal
hypot(X, Z) at 0.01 m and absolute yaw at 1 degree; this pure slice does not
derive those source measurements. It partitions positive windows and markers
by side, requires one marker per physical window and strictly disjoint
same-side runs, and preserves those physical rows. A distinct same-side first
run beginning at 0 and last run ending at 1 coalesce into one logical
cyclic stance whose artificial seam edges do not enter correspondence; a full
[0,1] stance refuses. Opposite-side overlap and touch are valid. Logical
left/right counts must be equal and nonzero, with alternating onsets around the
cycle. The planner builds the true logical boundaries in deterministic cyclic
chronological order, rotates correspondence (not phases) to a true left onset,
and requires one identical boundary signature across members. It pairs
boundaries positionally and maps them to the reference's authored phases.
Simultaneous boundary groups must agree on both source and reference instants;
identical map pairs collapse before (0,0) and (1,1) are included and the
retained 4,096-knot cap is checked. It returns bounded duration-preserving
time_warp operations with exact contact input bindings. All members must use one exact detector contact
height because that policy determines the compared boundaries. The whole ring
admits at most 16,384 contact events and 32 MiB of canonical fragment bytes
before topology retention and repeated canonicalization, and marker/window
validation is ordered rather than cross-product work. Only the correspondence
topology is circularly normalized: phase zero is not rotated, so incompatible
wrap/non-wrap correspondence or any other mapping that cannot remain strictly
monotone and endpoint preserving refuses. This slice does not load or mutate
animation assets, transform fragments, derive its required typed in-place
Root/Hips evidence, serialize candidates, prove output, or publish the
collection generation.
F.10C Foot-cycle clip candidate V1 (#18)
The next independent slice is the pure format-neutral application of one
planned member map to an already selected Clip. time_warp_clip_v1 validates
track-local clip/track shape again, requires the plan's exact duration-preserving
V1 operation, and returns a cloned candidate without mutating its input. This
Clip-only seam has no skeleton input, so its host must already have bound every
track bone index to the selected, validated skeleton.
Normalized control points map source time to output time: LINEAR tracks retain
all mapped authored keys and add interior map-knot samples with the existing
runtime sampling semantics, while STEP tracks map only authored breakpoints.
Binary64 calculations may narrow to finite binary32 track times, but distinct
source or output instants that collide after narrowing refuse rather than being
silently collapsed. A map knot and an authored key that are the same instant in
that emitted binary32 domain — equal, or one representable place apart, since
nothing is representable between them — are one emitted key at the authored
time and value, carrying the control point's own output.
FootCycleClipWarpKnotV1 is the single definition of that question, and
time_warp_rows_v1 merges a track's authored keys with its knots into the key
sequence a candidate stores. A knot-bearing row carries that knot — its
control-point index and that point's two narrowed times — and no stored value,
so the independent ClipMap proof in the CLI crate consumes the rows for the
sequence and reads only the index, narrowing the control point itself rather
than spelling coincidence a second way. What the proof takes from core is which
keys exist and in what order; every number is its own, including a knot's two
times, which it narrows again from the control point the row names rather than
reading the producer's resolution of them. Tests freeze the row sequence and
perturb producer times by one binary32 place in both directions: the proof
rejects changed stored outputs and inserted-knot samples while still accepting
the independently correct candidate. A coalesced row retains its authored
value, so its source-time metadata is unused after row selection; perturbing
that metadata alone remains accepted, while resampling a changed value is
rejected. That holds for every authored key the map has a control point for, including a track's
own first and last: the span test is inclusive of one place on either side, so
which side of a rounding step the reconstructed instant lands on does not
decide the outcome. A key the knot is bit-equal to binds before a key it is
merely beside, and coalescing only ever re-times an authored key — it never
removes one. The single exception is an interior point that is the same
instant as a source endpoint, 0 or the duration: it contributes nothing,
because the exact endpoint rows define the output there and a candidate retains
those two instants as themselves. Identity maps return a structurally identical
admissible clip.
CUBICSPLINE remains deliberately conservative: one-key tracks are retained; multi-key tracks are retained only when every stored key value is bit-exact equal and all stored input/output tangents are zero. Any other cubic track refuses the whole candidate. Finite quaternion keys must also have a finite positive binary32 squared length before any runtime sampling. Fixed checked caps refuse N+1 retained name bytes, tracks, authored keys/values, generated keys, and aggregate inspection work before candidate allocation. Candidate storage has a derived public upper bound: retained cubic storage is already bounded by authored values, while LINEAR/STEP emit one value per bounded generated key. This slice does not prove that the plan belongs to the loaded artifact, transform contact fragments, serialize or reread an output, or publish a generation directory. Those bindings and atomic transaction steps remain later #18 work.
F.10D Foot-cycle source preparation V1 (#18)
The CLI crate's next private seam binds the two prior pure boundaries to exact local source bytes without adding a command. It bounded-reads the manifest and parameterization once, resolves the exact member-reachable manifest source/config subset in canonical manifest source-key order plus every parameterization contact/output locator under its explicit declaration root, and rejects symlinks, canonical collisions, and platform-observable hardlink aliases before parsing any motion source. The future output leaf must be absent under an existing symlink-free parent. Pure manifest/runtime-set binding is validated first. Per-source and aggregate byte caps then cover only the exact member-reachable source subset, retained in manifest order; unrelated manifest sources do not gate this collection-scoped operation. Every reachable source and its exact config basis are loaded. Both the normalized config semantics and the explicit config byte identity are retained for later proof without a control-file reread. Any declared digest pin is enforced, while complete dependency-closure identities, complete raw take inventories, and exact index/name-to-normalized-clip witnesses are mandatory.
Each normalized reachable document passes the core's strict structural shape
validation before it can reach sampling or candidate work. The adapter then
preflights every selected member's frames * bones pose cells and frames * tracks sampling work, including the complete invocation totals, before
constructing the first MetricGrids grid. It uses configured role resolution
and root_trajectory_metrics as the existing authority. Horizontal
admission is the binary64 hypot of retained signed X/Z endpoint components
and yaw admission is the absolute value of retained signed accumulated
unwrapped yaw. Missing, ambiguous, non-finite,
or over-threshold evidence remains typed and makes the core planner refuse. The
prepared collection retains the exact aggregate source pose-cell and
sample-evaluation totals for a later shared source/output proof budget. The
adapter cross-checks every plan's member, source artifact, closure, canonical
fragment, selected take, duration, and root binding before building the pure
clip candidate. Any later member failure discards all candidates; preparation
does no filesystem mutation.
Contact output remains intentionally unfinished at this boundary. Core exposes
one operation-aware handler that validates and reconstructs the exact closed
stance-support detector payload for time_warp; the adapter retains that
handler output together with the exact input fragment, artifact, closure, and
operation. It cannot truthfully construct the output artifact identity or
output dependency closure until a complete candidate document is serialized.
The typed continuation accepts those later exact identities and only then calls
transform_contact_fragment_v1. This slice therefore performs no
serialization, generated-artifact hashing, reread/proof, generation staging,
publication, or CLI exposure.
F.10E Bounded candidate GLB projection V1 (#18)
The next private format-edge seam is an in-memory GLB writer, not a collection producer. It first makes an allocation-free strict admission pass over the normalized document: checked row, name-byte, and traversal-work ceilings bound the modeled writer arrays, scalar binary components, validation lookups, source sidecars, and copied texture bytes before it constructs writer-owned JSON. It then counts the single shared projection with checked JSON/BIN/GLB framing arithmetic and returns an immutable receipt containing padded chunk sizes, framing size, and JSON/BIN digests. The receipt binds that emitted projection, not deliberately excluded source-only provenance. Retaining output reruns the projection and accepts only the same receipt, reserving exact output capacities before copying bytes.
The legacy path remains its established permissive conversion projection. The
new StrictFootCycleV1 policy instead fails closed on any normalized fact that
the writer cannot preserve: omitted clips/tracks, primitive or texture domains,
non-explicit skinned inverse binds, invalid primary skin slots, incompatible
skinned/unskinned mesh reuse, meshless materials, scene membership collapse,
or source-local rest and source-skin root facts without an exact normalized
representation. Strict output attaches a skinned mesh to its normalized node,
rather than applying legacy's generated-holder normalization. Non-empty
complete source material-resource sidecars refuse: V1 has no source
texture/image identity or inspection-fact projection. An exact complete-empty
resource graph is admitted only when the writer-facing material list is also
empty. Source-only names, stable
source indices, and image-inspection/provenance evidence remain deliberately
outside the output projection; they are not presented as candidate facts.
This seam does not bind a prepared collection member to its source, serialize
contact transforms, reread/prove output, stage files, publish a directory, or
add a CLI command.
F.10F Foot-cycle serialized-candidate proof V1 (#18)
The next private transaction consumes one complete prepared collection without
adding filesystem or command behavior. For every declared member it clones the
member's retained source document and replaces only the selected clip with the
prepared candidate. It validates the document, runs StrictFootCycleV1
preflight, sums exact framed GLB sizes through one checked 256 MiB candidate
ceiling, and admits the combined source-plus-output metric-grid pose-cell and
sample-evaluation work before the first candidate write. All candidate writes
finish before any readback; every exact byte vector is then reread through the
ordinary glTF loader. Any refusal drops the local vectors and returns no partial
batch.
The reread artifact identity must equal the exact retained bytes. Its dependency
closure must be complete and bound to that primary identity, with no external
resources and no reference target other than Primary; embedded BIN-buffer
references therefore remain valid self-contained evidence. Only then does the
transaction finish the prepared contact transform with the reread artifact,
closure, and independently redetected producer identity.
Proof does not treat the existing checks as its verdict. It independently
revalidates the finite strictly increasing endpoint-preserving map and exact
LINEAR/STEP/cubic candidate relationship, including the constant-value,
zero-tangent multi-key CUBICSPLINE restriction. One already-budgeted output grid
per member supplies fresh contact topology/boundaries, gait phase/amplitude,
signed Root/Hips trajectory, and per-bone loop pose plus linear/angular seam
velocity. Contact and gait use the declaration's required proof fields; root
facts are compared to retained signed source facts under the fixed 0.01 m and
1 degree V1 limits; loop facts use the exact captured source config's effective
per-clip/check/default tolerance resolution. The result retains exact artifacts,
closures, completed transforms, independently detected contacts, proof facts,
phase spread, and separate source/output/combined work totals for the later
evidence and publication transaction. Its contact proof independently repeats
the per-side marker/disjointness validation, seam coalescing, logical-count and
cyclic-onset checks, and deterministic true-boundary ordering rather than
calling planner internals. Before its circular numeric error comparison, proof also
requires the expected and independently detected (side, edge) signature in
absolute phase-zero chronological order to match, so a complete left/right
role swap remains a topology failure at every numeric error tolerance. It
defines no public evidence schema,
filesystem layout, publication, or CLI behavior.
F.10G Foot-cycle generation producer V1 (#18)
The final CLI-private transaction exposes exactly
collection transform-foot-cycle COLLECTION.toml --parameterization FOOT-CYCLE.toml --format json; the required JSON-only format has no default
and the parameterization is the sole destination authority. The glTF-only
build retains this command, while FBX admission remains feature gated. Every
successful source becomes a self-contained strict GLB.
For N members the fixed generation has checked 3N+1 cardinality and exact
declared order: each members/NNNNNN/ contains artifact.glb,
contact-fragment.json, and evidence.json, followed by the root
aggregate-evidence.json. The member evidence V1 contract is
urn:animsmith:schema:foot-cycle-member-evidence:1; it binds exact control
inputs, source artifact/config/complete closure, output artifact/complete
closure, transformed and independently redetected contacts, canonical map
identity, policy, proof facts, and deterministic relative paths. Aggregate
evidence V1 is urn:animsmith:schema:foot-cycle-aggregate-evidence:1; it binds
the ordered member summaries and member-evidence identities,
phase spread, component and exact total bytes, retained GLB bytes, and
source/output/combined proof work. Strict bounded readers and published JSON
Schemas close both records.
All GLB, fragment, member-evidence, and aggregate-evidence bytes share one 256
MiB retained/staged ceiling; each component retains its narrower contract cap.
Checked arithmetic admits the exact file count and byte total, including the
aggregate record's own converged serialized size, before the first filesystem
write. One publish_generation call stages and flushes the complete fixed set
under a sibling directory, then performs one platform no-replace directory
rename. An existing or racing destination wins unchanged. This is atomic
visibility of a new local generation, not replacement, power-loss, network
filesystem, or crash-cleanup atomicity.
Success emits the exact aggregate file bytes to stdout only after publication;
a broken stream is best-effort diagnosed and retains exit 0. Valid asset,
capability, refinement, contact-transform, or proof refusal publishes nothing,
emits one producer-refusal:1 record, and exits 1. Malformed or stale control
bindings, unsafe/aliased paths, I/O, serialization, publication, and race
failures publish nothing, keep stdout empty, and exit 2. The producer never
rereads mutable manifest, parameterization, config, or contact controls after
their exact capture and never substitutes the writer's candidate model for the
independent serialized readback proof.
F.11 Transition-family declaration V1 (#148)
Issue #148 freezes declarations for transition families without implementing transition checks, reports, inferred graph edges, or runtime generation. Its independent identity is:
urn:animsmith:schema:transition-family:1
The document-local tables are admitted by the CLI's normal strict config loader, which retains the whole-config exact input identity alongside the normalized declaration. The JSON-only document command and manifest-bound collection command now evaluate that authority. The collection form binds its exact manifest before source work and reloads only manifest-witnessed takes.
The format-neutral core now owns the strict SkeletonBasisV1 identity, both
document and collection transition-pose evaluators, and their shared result contract
urn:animsmith:schema:transition-pose-evaluation:1. It admits a mutable
loaded document's skeleton and selected T/R tracks, plans bounded endpoint
work before sampling, and returns an
immutable complete/finding/not-evaluated result. The collection adapter
reloads only manifest-witnessed sources; neither adapter adds a lint check.
The document adapter is the
JSON-only animsmith evaluate-transition-poses INPUT --format json command:
it loads one document through the normal public loader/config path, binds the
loaded exact raw document identity, and emits the core result unchanged. It
uses the exact zero-byte TOML sequence as the defined omitted-config
declaration source, so an omitted config and an explicitly empty config have
the same exact and normalized declaration identities. The collection adapter
preserves every declared family member and makes an unavailable or unusable
source a whole-family incomplete result rather than evaluating a subset.
Its scope-neutral subject_input binds the exact raw document in this
document evaluator; the collection adapter binds its manifest subject under
the same result schema. The normalized declaration scope determines
which subject meaning applies. For document scope, the public evaluator accepts
the loader's same-load DependencyClosureV1, takes subject_input only from
its primary_input, and retains its complete
subject_dependency_closure_identity. Every document member retains the same
primary identity as source_input and the same complete closure identity as
source_dependency_closure_identity. Complete coverage and a present
DependencyClosureIdentityV1 are both required before any configured family
can be complete; partial, unavailable, or identity-less coverage makes every
configured family incomplete/not_evaluated with
dependency_closure_incomplete. The no_configured_families result is the
deliberate exception because it performs no source evaluation and remains a
complete pass even when closure capture is unavailable.
Collection scope does not fabricate a dependency closure for its manifest:
subject_input is the manifest's primary identity, while each available
member independently retains its raw source_input and complete
source_dependency_closure_identity. An unavailable source may retain a null
source_input when no primary bytes were obtained. One missing or incomplete
member closure makes the entire family dependency_closure_incomplete, but
other available members keep their closure identities as positive evidence;
the family is never evaluated as a survivor subset. Thus unchanged primary
text glTF bytes cannot mask changed external animation-buffer bytes.
Before sampling, the evaluator directly verifies every declared index/name
witness, admits at most 4,096 document clips for its one-pass global
duplicate-name proof, 4,096 bones, and 8 MiB of basis-name text. A document
above the clip cap with no direct witness contradiction yields normal
input_limit family rows without scanning its full name domain. Each selected
clip first admits at most three raw flat track rows per bone (12,288 maximum),
then bounds the consumed translation/rotation domain to two channels per bone.
Scale values, shape, and duplicates remain semantically ignored, but raw scale
rows count toward that first resource admission because the loader stores all
properties in one vector. Across the invocation it inspects at most the V1
aggregate-comparison bound of 16,777,216 selected time/value elements. A
valid-witness input exceeding one of these admission limits retains every
family as incomplete/not_evaluated with input_limit;
unrelated assets and unselected clips are outside this evaluator boundary.
Before sampling it serializes the full pair-free authority, including subject
and per-member dependency-closure identities, and subtracts that from a
conservative detailed JCS reservation, producing result_limit rather than
cloning a representation that must later be discarded as over-cap.
The declaration has a tagged scope, a stable family_id, an explicit
ordered members array, a boundary (entry, exit, or both), a typed
basis, and typed named tolerances. A family has at least two members.
family_id is unique within its owner and is never inferred from filenames,
directories, embedded names, or member order. Unknown fields and duplicate
members are invalid.
Document-local family ids are one lowercase-ASCII token, 1–255 bytes, starting
with [a-z0-9] and continuing with [a-z0-9._-]. The table key is the id and
there is no duplicate family_id field. TOML must quote the key whenever it
contains punctuation or a dot (the canonical spelling quotes it always), for
example [transition_families."walk_to_run"] or
[transition_families."combat.entry.v1"]. Collection family ids retain
Appendix F's slash-qualified logical-id grammar and collection-id prefix.
The two scopes are intentionally different authorities:
documentdeclarations are placed in the existinganimsmith.tomlconfig basis under[transition_families."<family_id>"]. The table key itself is the family id; it is a reusable config declaration and carries no artifact digest. The document evaluator binds the exact primary documentInputIdentityand complete dependency-closure identity in its output, while members use exact embedded take-index/name witnesses. A repeated or loader-ambiguous take name is a strict resolution failure.collectiondeclarations use a separate declaration envelope, not an extension of collection-manifest V1 and not another path/member authority. The envelope binds the exact collection-manifestInputIdentity{sha256, bytes}, collection id, and one or more family records. Each record references a declared logical clip id plus its correspondingsource, take-index, and take-name witness.
A declaration cannot mix scopes, use a filesystem path, use an animation-array
index as identity, or import a member from another document or collection.
Missing, duplicate, cross-scope, and ambiguous members are strict resolution
failures. stale_digest is a collection-only failure: it means the envelope's
manifest InputIdentity no longer matches the declaration. A source digest
pin, when present in that manifest, is enforced by manifest resolution and a
mismatch makes the affected member unavailable; it is not a second identity
carried by this declaration. A reusable document-local config has no
stale-digest state at parse time; the document evaluator binds the exact primary
document InputIdentity plus its complete dependency-closure identity and
reports its source/take resolution in its output. A family
is not silently reduced to the members that happen to resolve, and a
collection family is not copied into a document-local config. The existing [clips], [gait_groups], and
[sync_groups] sections retain their document-local embedded-name semantics;
this declaration does not replace, reinterpret, or create a second authority
for them. The document-local table and collection envelope share the same
transition-family:1 family-record semantics; only their ownership and
placement differ.
V1's basis is explicit and engine-neutral: translation is metres in the
declared skeleton-local basis, rotation is degrees in the same basis, and time
is normalized clip time in [0, 1]. The named tolerance record is closed and
unit-bearing: translation_m, rotation_deg, and time_normalized, each a
finite non-negative value. boundary selects whether the evaluator
considers entry, exit, or both; it does not itself perform a comparison.
Additional policy fields require a separately versioned contract, not a
generic TOML map or untyped extension payload.
Both declaration placements are bounded before retention or canonicalization. The exact declaration source is at most 8,388,608 bytes and 16 nested tables/arrays, with at most 4,096 families, 4,096 members in one family, and 16,384 members across all families. Authored strings are at most 4,096 UTF-8 bytes, with the 255-byte identifier limits above applied more strictly. The normalized JSON envelope is at most 8,388,608 JCS bytes and has the same nesting limit. Depth counts each table/array or JSON object/array on the root-to-value path, inclusive; the root table or envelope object is depth
- The strict reader accepts each exact maximum and rejects the first byte, element, string byte, aggregate member, or nesting level above it while decoding, before retaining that excess value or allocating an unbounded canonical buffer. Canonical byte limits are enforced with a bounded JCS sink.
For example, a collection-owned declaration can be written as the following typed TOML-like value. The logical id and source/take witnesses must agree with the collection manifest; they are repeated here to make a stale or reordered member explicit rather than silently resolving by name:
schema = "urn:animsmith:schema:transition-family:1"
schema_version = 1
scope = "collection"
collection_id = "com.example.pack"
manifest_input_identity = { sha256 = "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789", bytes = 9876 }
[[families]]
family_id = "com.example.pack/transitions/walk-to-run"
boundary = "both"
[families.basis]
translation = "skeleton-local-metres"
rotation = "skeleton-local-degrees"
time = "normalized-clip"
[families.tolerances]
translation_m = 0.05
rotation_deg = 5.0
time_normalized = 0.02
[[families.members]]
logical_id = "com.example.pack/locomotion/walk-forward-in-place"
source = "walk-forward"
take_index = 0
take_name = "Take 001"
[[families.members]]
logical_id = "com.example.pack/locomotion/run-forward-in-place"
source = "run-forward"
take_index = 0
take_name = "Take 001"
The document-local placement uses the existing config basis and the same family-record fields:
[transition_families."walk_to_run"]
schema = "urn:animsmith:schema:transition-family:1"
schema_version = 1
scope = "document"
boundary = "entry"
[transition_families."walk_to_run".basis]
translation = "skeleton-local-metres"
rotation = "skeleton-local-degrees"
time = "normalized-clip"
[transition_families."walk_to_run".tolerances]
translation_m = 0.05
rotation_deg = 5.0
time_normalized = 0.02
[[transition_families."walk_to_run".members]]
take_index = 0
take_name = "Walk"
[[transition_families."walk_to_run".members]]
take_index = 1
take_name = "Run"
After strict validation, the normalized JSON representation has exactly one of the following two closed envelope shapes. The collection form is:
{
"schema": "urn:animsmith:schema:transition-family:1",
"schema_version": 1,
"scope": "collection",
"collection_id": "com.example.pack",
"manifest_input_identity": {"sha256": "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789", "bytes": 9876},
"families": [{
"family_id": "com.example.pack/transitions/walk-to-run",
"boundary": "both",
"basis": {"translation": "skeleton-local-metres", "rotation": "skeleton-local-degrees", "time": "normalized-clip"},
"tolerances": {"translation_m": 0.05, "rotation_deg": 5.0, "time_normalized": 0.02},
"members": [
{"logical_id": "com.example.pack/locomotion/walk-forward-in-place", "source": "walk-forward", "take_index": 0, "take_name": "Take 001"},
{"logical_id": "com.example.pack/locomotion/run-forward-in-place", "source": "run-forward", "take_index": 0, "take_name": "Take 001"}
]
}]
}
The document form lifts the repeated table contract fields into one envelope;
the table key becomes family_id:
{
"schema": "urn:animsmith:schema:transition-family:1",
"schema_version": 1,
"scope": "document",
"families": [{
"family_id": "walk_to_run",
"boundary": "entry",
"basis": {"translation": "skeleton-local-metres", "rotation": "skeleton-local-degrees", "time": "normalized-clip"},
"tolerances": {"translation_m": 0.05, "rotation_deg": 5.0, "time_normalized": 0.02},
"members": [{"take_index": 0, "take_name": "Walk"}, {"take_index": 1, "take_name": "Run"}]
}]
}
No owner or source-input identity is injected into the document form. A V1
evaluation record binds three distinct facts: the exact declaration-source
InputIdentity (the whole config for document scope or the declaration TOML
for collection scope), the normalized declaration InputIdentity computed
over the JCS bytes above, and the evaluated document or collection-manifest
InputIdentity. The collection form additionally retains its embedded exact
manifest binding. These identities are never interchangeable.
The strict reader rejects malformed/duplicate declaration members, and the document evaluator rejects missing or ambiguous document witnesses before it emits a result. The collection adapter additionally rejects stale or cross-scope members. The collection envelope's manifest digest/bytes binding makes a manifest reorder or edit stale even when its collection id and logical ids are unchanged.
Canonical serialization first sorts decoded family declarations by their
stable family_id and preserves declared member order, then serializes that
typed JSON representation with RFC 8785 JCS. The exact source TOML
InputIdentity remains a separate binding and is never replaced by the
normalized JSON identity. The strict reader validates owner/scope, member
syntax, basis, units, finite tolerances, and canonicalization before a
declaration is accepted. The document and collection commands produce V1
findings/reports; required gameplay metadata, inferred edges, state machines,
blend trees, and runtime transition generation remain follow-up work.
F.12 Directional-speed policy/evaluation V1 (#552)
The directional-speed policy is a separate, manifest-bound declaration for directional-blend runtime sets. It is not a field in collection-manifest V1, does not revise collection-output V2, and does not infer membership from filenames, paths, engine controller types, or other host metadata. The typed contract and strict TOML reader feed the evaluator and its JSON-only CLI consumer; no new manifest or collection-output authority is introduced.
The schema identity is
urn:animsmith:schema:collection-directional-speed-policy:1, with
schema_version = 1. Its wire envelope has exactly these top-level fields:
schema, schema_version, manifest, runtime_set_id, source_basis,
diagonal_behavior, direction_tolerance_deg, mode, the mode-specific
fields, and members. manifest has exactly the collection-manifest V1
schema/version, collection_id, and input = { sha256, bytes }; that input
identity is the exact manifest identity, not a digest of normalized values.
runtime_set_id is a logical id that must bind to a manifest runtime set of
kind directional-blend.
The explicit source_basis contains x and z, each a semantic 2-D
orientation witness for the raw collection-output V3 +X/+Z endpoint
displacement. Their magnitudes are nonsemantic; the evaluator uses unit axes
for heading comparison. Every component is finite and has absolute value
at most 1,000,000. Each axis is nonzero. Perpendicularity is judged by the
normalized dot product
abs(dot(x / ||x||, z / ||z||)) <= 1e-9.
The normalized calculation uses hypot lengths and component-wise division,
so its decision is independent of axis scale and does not square tiny values
or overflow large bounded values. The 1e-9 cosine threshold is the frozen
COLLECTION_DIRECTIONAL_SPEED_POLICY_V1_MAX_AXIS_COSINE bound. It rejects
near-parallel and diagonal axes, including tiny vectors, while accepting
orthogonal subnormal-scale axes when their finite representation has a
nonzero length.
members is an ordered sequence. Each member has exactly one logical id
and one semantic coordinate = [x, z]; it may additionally carry
speed_mps only for authored mode or expected_ratio only for ratios mode.
Coordinates are finite, bounded by the same component limit, nonzero by
component value, and unique by exact finite bit identity after canonicalizing
both signed zeros to one zero key. The reader preserves declared order. A
consumer binds the policy only when the manifest identity, runtime-set id,
runtime-set kind, and complete member sequence all match exactly; missing,
extra, or reordered members are binding errors.
The closed diagonal_behavior tokens are preserve and normalize. The
existing speed_mps and expected_ratio fields are unit-input/base targets.
For a semantic coordinate c, preserve defines gain g(c) = hypot(c) and
normalize defines gain g(c) = 1. Therefore uniform expected speed is
base * g(c), authored expected speed is the member base speed_mps * g(c),
and ratios compare
expected_measured_ratio_i_to_ref = declared_expected_ratio_i * g(c_i) / g(c_ref), with the reference declaration fixed at 1.0. This gain does not
affect direction comparison. The declaration carries finite
non-negative direction_tolerance_deg, bounded inclusively by 180 degrees.
The closed speed modes are:
uniform: requires finite non-negative boundeduniform_speed_mpsandspeed_tolerance_mps; member speed and ratio fields are forbidden.authored: requires finite non-negative boundedspeed_tolerance_mpsand exactly one finite non-negative boundedspeed_mpsper member; uniform and ratio fields are forbidden.ratios: requires finite non-negative boundedratio_tolerance, areference_member, and exactly one finite non-negative boundedexpected_ratioper member. The reference member must be present and its expected ratio must be exactly1.0; speed fields are forbidden.
All scalar bounds are inclusive. Unknown fields, duplicate TOML keys,
unknown mode or diagonal tokens, missing or mode-inapplicable fields,
malformed lowercase SHA-256 identities, invalid manifest schema/version,
non-finite numbers, out-of-range components, invalid bases, invalid member
coordinates, duplicate member ids, and duplicate coordinates are strict
control errors. The source reader is capped at 8,388,608 bytes and 4,096
members; each exact limit is accepted and the first excess member is rejected
before it is retained. The reader first decodes only schema and
schema_version so unsupported declarations receive stable unsupported-schema
or unsupported-version classifications; it then performs the complete
deny-unknown-fields decode and typed invariant validation. This two-pass
lifecycle is classification, not a second policy authority.
There is no evaluator inference or fallback. The pure, format-neutral
collection-directional-speed-evaluation:1 result and typed adapter consume
already strictly decoded collection-output V3 evidence; they do not revise V3.
The immutable result binds the raw policy TOML
and evidence JSON InputIdentity values as well as the manifest, and retains
every member's coordinate, raw root-travel values, projected heading,
comparison values, pass/violation state, lifecycle, and gaps.
For complete evidence, finite endpoint X/Z displacement is normalized before
the unit source X/Z axes form its semantic heading. The declared coordinate is
also normalized; the included angle is atan2(abs(cross), dot) in degrees.
Speed uses published speed_mps, never travel. Every tolerance is inclusive,
so a finding requires strict excess. A zero endpoint, zero ratio reference, or
unrepresentable derived product/ratio yields a complete typed not-evaluated
result (zero_net_displacement, zero_reference_speed, or numeric_range)
without a subset or fabricated finding. Preserve uses hypot(coordinate) as
the expected-speed gain; normalize uses one, and the ratio reference remains
exactly one. Findings retain manifest order, direction before speed/ratio.
animsmith collection evaluate-directional-speed --policy POLICY.toml --evidence COLLECTION-OUTPUT.json --format json captures policy bytes under the 8 MiB
cap and evidence bytes under the 256 MiB cap before retaining them, invokes
the strict readers/adapter/evaluator, and serializes the result once to stdout.
Invalid, stale, wrong-kind, unreadable, malformed, or over-budget control
inputs exit 2 with no result. Incomplete/not-evaluable evidence and policy
findings emit the result and exit 1; only a complete passing policy exits 0.
There is no filesystem publication, controller generation, retiming, stride
rewriting, or universal speed rule.
Scale proof calibration
This document owns the reproducible calibration populations, measured demand,
historical policy comparisons, and machine-local timing notes behind
ScaleTolerancePolicy::APPENDIX_D_V6. The normative operation, tolerance,
provenance, work-budget, proof, and publication contracts remain in
DESIGN.md Appendix D.
The calibration is evidence for the fixed policy, not another policy authority. Changing an accepted bound, work budget, magnitude definition, sampling rule, or proof comparison requires a new policy identity and corresponding normative design change; editing the measurements here cannot change runtime behavior.
Rounding calibration sweep
4 is measured, not assumed, and the measurement is checked in:
calibrate_f32_rounding_ulps in
crates/animsmith-core/src/scale/tests.rs. Run it with
cargo test -p animsmith-core --release --lib \
calibrate_f32_rounding_ulps -- --ignored --nocapture
It builds and proves 360_000 correct candidates over 144 cells: the cross
product of nine operations (rest/bind at root scale 3190, and whole-document
conversion at {1e-4, 0.01, 0.1, 1.5, 7.3, 100, 3190, 1e6} — both directions
of the factor), four slot compositions (analytic binds, where abs(W * B) is
1, and composed slots at abs(W * B) = {1e-3, 1, 1e3}), two blends (two
slots that oppose on the swept vertex and cancel it to the origin under
balanced weights, and two independent slots that do not), and two explicit
weight profiles. The balanced profile uses [0.5, 0.5]. In the mismatched
profile, every vertex whose two production influence bases differ gives the
larger base a log-uniform weight in [1e-20, 1e-2] and the smaller base weight
1. The measured population contains 274_670 such vertices — 74_085 with slot
0 larger and 200_585 with slot 1 larger — and the sweep asserts strong nonzero
floors for both orientations rather than merely counting profile labels. Each
cell draws 2500 candidates. Joint locals and vertex positions are drawn
log-uniformly over six and eight decades in random directions, every joint
carries a random rotation, and half of every cell's trials carry a parent chain
that cancels. The generator is a written-out SplitMix64 whose seed is derived
from each cell's own coordinates — conversion, composition, blend and weight
profile — rather than from its position in the loop, so a cell's population
does not move when a neighbouring cell is added, removed or reordered.
The bit stream is identical on every machine and every run; the rigs drawn
from it are not quite. The draws run f64::powf for the magnitude decades,
f64::sin/cos for the directions, and glam's f32 sine and cosine for the
rotations, none of which any platform is required to round identically. Two
machines can therefore differ in the last ulp of a joint local and in the last
printed digit of the worst demand. The table below records the reference run
for this policy revision; the checked test asserts platform-tolerant
non-silence floors and the strict < 4 safety ceiling rather than pretending
those libm-dependent last digits are normative.
| quantity | worst over the population |
|---|---|
| skinned bounds | 2.768 |
| the skin equation | 2.213 |
| rest translation | 2.583 |
| refused correct candidates | 0 of 360_000 |
The quantity is residual / (magnitude * 2^-23): the raw ulp count, not net
of the scalar band that is paid first. It therefore overstates what the count
is actually asked for, by the whole scalar band, and a worst case under 4
measured this way is a worst case under 4 however the two terms are split. An
earlier revision of this section reported the net demand instead, from
populations that were never checked in and so could not be re-derived; the
figures above replace them.
The 144-cell phase remains intentionally shallow. A separate deep-chain phase
proves 80 correct animated candidates at depths
8, 16, 32, 64, 128, 192, 256, 512. At every
depth it covers rest/bind and the eight whole-document conversions; the
whole-document cases use the literal 170-degree chain, while rest/bind uses
an exact half turn because repeating the approximate quaternion eventually
leaves the operation's supported affine-input class. Eight additional
whole-document cases use a literal step that closes a 192-link ring. In test
builds the central production comparison records each raw ULP demand at the
exact point its residual and provenance meet. Both shallow and deep calibration
fold those private maxima; the deep phase also folds production-owned counts.
The released proof layout and runtime path are unchanged, and calibration no
longer rebuilds a parallel pose/slot/bounds walk or divides unrelated
obligation-wide maxima.
| deep-chain quantity | worst over 80 cases | comparisons |
|---|---|---|
| rest translation | 0.715 | 12_488 |
| sampled trajectory | 0.715 | 24_976 |
| the skin equation | 0.143 | 240 |
| skinned bounds | 0.149 | 1_440 |
| refused correct candidates | 0 | — |
The harness retains and prints a separate record for every declared depth, rather than folding the individual demands immediately into the aggregate above. The depth-192 row also includes the eight ring cases; every other row contains rest/bind plus the eight whole-document conversions.
| depth | rest | trajectory | skin equation | bounds | comparisons (rest / trajectory / skin / bounds) |
|---|---|---|---|---|---|
8 | 0.578 | 0.578 | 0.143 | 0.149 | 81 / 162 / 27 / 162 |
16 | 0.578 | 0.578 | 0.067 | 0.073 | 153 / 306 / 27 / 162 |
32 | 0.578 | 0.578 | 0.076 | 0.078 | 297 / 594 / 27 / 162 |
64 | 0.578 | 0.578 | 0.063 | 0.063 | 585 / 1_170 / 27 / 162 |
128 | 0.578 | 0.578 | 0.037 | 0.039 | 1_161 / 2_322 / 27 / 162 |
192 | 0.715 | 0.715 | 0.034 | 0.033 | 3_281 / 6_562 / 51 / 306 |
256 | 0.578 | 0.578 | 0.034 | 0.034 | 2_313 / 4_626 / 27 / 162 |
512 | 0.578 | 0.578 | 0.019 | 0.019 | 4_617 / 9_234 / 27 / 162 |
An unaffected instance's binds demand 0, and always will: a correctly built
candidate leaves both effective matrices equal, including when one side stores
an explicit identity and the other uses the format-defined identity default.
4 is the next power of two above every figure above, measured over these
declared populations. It is not an analytic bound. An earlier revision
reasoned from one four-term matrix inner product; v4 exposed the missing
dimension because one composition happens per link. Its maximum over links
stayed flat while coherent rounding accumulated with depth, and correctly
built candidates began refusing at depth 180.
Historical v5 calibration of the recurrence retained by v6. The v5 policy
replaced the depth-flat maximum with the additive provenance recurrence that
v6 still ships. Its exact formula, scope, and fixed-stage semantics have one
normative owner in DESIGN.md Appendix D.
This note retains the evidence for that choice. A 512-link zero-translation
chain adds nothing below a translated root; the corresponding chain of
underflowed nonzero translations carries only its vanishing contribution
instead of charging the million-unit parent 512 times. An uneven chain accrues
its actual per-link bases rather than depth * max, and the literal 192-link
ring plus the 170-degree chain prove through depth 512. The checked-in shallow
and deep calibration and the isolated floors below delimit the empirical
envelope retained by v6.
A residual above the count is evidence about the magnitude before it is
evidence about the count. Three revisions have now found the magnitude wrong
rather than the count too small — the skinned extent alone, which missed the
W * B composition; abs(W) * abs(B) alone, which missed what the parent
chain had already cancelled; and abs(p) alone, which missed the other factor
of the transform it was named for — and in all three the measured excess was
hundreds or hundreds of thousands of ulps, not a factor of two. Each was
invisible because the calibration population could not express the shape:
no rig in it composed a slot to anything but the identity. That is what the
checked-in sweep is for, and why its cells name the shapes rather than only
the magnitudes.
The cost is real and it is not bounded. The four chain-dominant fixtures
search their declared positive-binary32 intervals in the normal suite, sample
4097 evenly spaced bit coordinates as a gross non-monotonicity guard, and pin
the adjacent accepted/refused transition found by bisection plus the exact
refused residual/tolerance bits. This is not an exhaustive monotonicity proof.
The four searches remain comfortably sub-second in the debug reference run,
so a second ignored-only search path would add structure without buying
practical test time. The v4 values are frozen historical measurements from the
exact merged commit 0a253228dc2d557a9030cfd72f2b15326f4853bd, independently
reproduced during audit with the same fixtures and mutation axes. The v5 suite
compares its production endpoints with those recorded bits; it does not carry
a second implementation of the retired policy.
| obligation / mutation | v4 refused transition | v5 adjacent accepted / refused | v5 refused-endpoint observed / tolerance |
|---|---|---|---|
| RestTranslation, unskinned rest x | 3.1875 (0x404c0000) | 4.5625 / 4.5625005 (0x40920000 / 0x40920001) | 4.5686544 / 4.5633805 |
| Trajectory, both unskinned key x values | 3.1875 (0x404c0000) | 4.5625 / 4.5625005 (0x40920000 / 0x40920001) | 4.5686544 / 4.5633805 |
| SkinMatrix, cancelling-joint inverse-bind x | 3.5713947 (0x406491bb) | 5.3570914 / 5.357092 (0x40ab6d4b / 0x40ab6d4c) | 4.5633788 / 4.5633783 |
| Bounds, chain-dominant point y | 2.9826486 (0x403ee3b7) | 4.503774 / 4.5037746 (0x40901eeb / 0x40901eec) | 4.5633788 / 4.5633786 |
Those are fixture-local transitions, not global smallest defects. The typed refused endpoint isolates the named obligation: the rest/trajectory bone is not skinned, the inverse-bind mutation reaches SkinMatrix before Bounds, and the million-unit x coordinate gives MeshPosition a wider band than the Bounds y mutation. Reverting the recurrence to v4's maximum reproduces the recorded v4 boundary and fails the strict v5 comparison.
The general cost remains unbounded relative to the compared quantity.
4 * 2^-23 is 4.77e-7 of the magnitude the arithmetic ran on,
which is 4.77e-7 of the compared quantity only when the two coincide and is
otherwise 4.77e-7 * (operand magnitude / compared magnitude) of it. That
ratio has no upper bound, and it is exactly the ratio the term exists to
cover, so the term and the compared quantity diverge precisely as far as the
operand/result cancellation documented above.
Concretely, on the far-joint rig of
a_joint_far_from_the_geometry_it_carries_still_proves_its_bounds — joints
3.2e6 from the origin carrying geometry within one unit of themselves, so
W * B is near-identity — the term is 4.44 against a compared magnitude of
1.0, and the largest inverse-bind x shift still accepted is 4.09375
units — the smallest refused is the next binary32 above it. A regenerated bind
wrong by four units is accepted there. The bracket is pinned by
the_far_joint_rig_admits_a_four_unit_bind_shift_and_refuses_the_next_one_up,
because a floor quoted in prose and nowhere held to drifts: an earlier
revision of this section stated 4.09, which is on the accepted side of the
real floor and so described a bracket that does not exist. As a rule: for a rig
whose joints sit k times further from the origin than the geometry they
carry, SkinMatrix and Bounds lose discriminating power in proportion to
k.
Folding the parent chain into the magnitude does not move that number: on the
far-joint rig abs(W) * abs(B) already reads 6.4e6 against the chain's
3.2e6, so the max is unchanged and the floor is still 4.09375 units.
The chain widens the magnitude only where a chain
actually cancelled, always in proportion to what it cancelled, and leaves it
untouched everywhere else. Buying the same admissions by raising the count
instead would have cost the whole factor on every slot, including the ones
that lost nothing, which is why the correction is to the magnitude and the
count stays at 4. That is the general rule this section applies each time:
a_parent_chain_whose_translations_cancel_still_proves_its_skin needs
524288 ulps of the base without the chain and 0.08 of it with, and no
count between those two is a policy anyone could defend.
This is a property of the f32 inputs, not of the policy, and precision
does not fix it. The dominant error is not the composition's own rounding.
It is the stored inverse bind's translation column — accurate only to its own
ulp — amplified by W's linear part into a product that cancellation has made
near-identity. Composing W * B in f64 from the same f32 stored values
was measured over a 30_000-candidate rest/bind population: it moves the worst skin
residual from 2.50 to 2.06 ulps and the worst bounds residual from 1.68
to 0.90, an improvement of under a factor of two, and leaves the worst
residual at 86 % of the compared product's own magnitude against a 1e-5
relative band. On the far-joint rig it moves the floor from 4.09375 units to
about 4.16 at the same four ulps, or about 3.16 at the three ulps that
residual would then permit — those two are approximate, being measurements of
a composition this tree does not perform and so not pinned by any test here. The term covers a quantization the file itself
already performed, and no amount of proof-side precision can undo it.
What the term does buy is the direction that matters: no obligation is held to
a band tighter than the rounding its own arithmetic genuinely incurs, which
none of them was ever sound doing. The converse — that each obligation refuses
every error larger than its rounding — is a stronger claim than anything here
establishes. What is pinned is one adjacent bracket per named mutation axis,
including the far-joint inverse-bind bracket at 4.09375 accepted and the next
binary32 value refused.
Transform-only rounding and overflow/refusal semantics are current policy, not
calibration conclusions, and therefore live only in
DESIGN.md Appendix D.
Their checked evidence includes
a_rig_whose_skinned_extent_passes_the_square_root_of_f32_max_still_proves,
a_rig_whose_composition_operands_overflow_f32_still_proves, and
a_parent_chain_whose_operand_sums_overflow_f32_still_proves.
Sampling-budget timing history
4e8 is a wall-time ceiling expressed in work units. What that ceiling costs
in seconds is a property of the machine, not of this design. Historical v3
measurements were linear in the charge across four doublings of the budget
in both shapes, which is the evidence that the charge is a proxy for real work
rather than an arbitrary count. Both shapes are fully specified here, so a
reader can rebuild them and measure their own machine: the
slot-dominated one is 200 instances of a 99-joint skin list with one vertex
each, and the vertex-dominated one is a single instance of that skin list with
a 10_000-vertex primitive, each with as many sample times as 4e8 admits, and
every vertex in both carries exactly one non-zero influence.
The released arithmetic also gives reproducible sizing landmarks independent
of machine timing. Appendix D's 200-bone, 100k-vertex example costs 201_000
units per sample, so 4e8 admits 1,990 samples; the same rig with 10k vertices
costs 21_000 and admits 19,047. A 100k-sample clip on the first rig asks for
2.01e10 units and is refused. Its 30-second, 30-fps example costs
180_900_000, leaving about 2.2x headroom, or roughly 66 seconds at 30 fps
for that exact shape. These are consequences of the released formula, not
wall-time measurements.
The one-influence condition above is part of the shape and not an incidental
detail. The budget charges per vertex, but the f32-rounding term's stage-1
work runs once per non-zero influence — the skinning loop continues on a
zero weight — so a
rig whose vertices carry four influences pays that stage four times per vertex
against the same charge. The seconds below therefore describe a one-influence
shape and understate a four-influence one; the work units are identical for
both. Appendix D §D.1 names that vertex-versus-influence discrepancy as part of
the normative work-budget definition so a rebuilder does not have to infer it
from this historical timing population.
On one developer machine under v3 the slot-dominated shape at the ceiling
measured 6.7s and the vertex-dominated one 3.3s, a ratio of 2.0. Neither
number is a bound this design guarantees or a v4 measurement, and an earlier
revision of this section
claimed one — "stays inside five seconds" — against a measurement taken on
different hardware and a different tree. The ratio moved too: before
f32_rounding_ulps the same machine measured 7.1s and 2.0s, a ratio of
3.5. The absolute seconds are what a reader cannot check; the linearity and
the ordering are what they can.
Historical v3 cost of the f32-rounding term. Deriving each obligation's
magnitude was per-slot and per-vertex work that the tree before
appendix-d-v3 did not do, and the vertex-dominated shape is where it showed.
Two baselines are quoted here and they are not the same baseline, so both are
named. Against no rounding term at all — the appendix-d-v2 tree — on one
machine, over twelve runs of each shape and taking minima, that shape went from
2.02s to 3.31s, +64 %, for the same 1_932_084 skin and 117_096 bounds
comparisons.
Against the term with stage 1 read as abs(p) — the form this design
carried mid-revision, before stage 1 was corrected to the transform's operand
product — the correction alone costs +27 %. Measured in one session on the
same shape and machine, minimum of five runs: 2.57s with no stage-1 term at
all, 2.87s reading position.length(), 3.64s reading
column_operand_magnitude. That is +12 % for having any stage-1 term,
+27 % for correcting it, and +42 % for both together. The absolute seconds
differ from the paragraph above because they are a different session of the
same shape on the same machine; only ratios within one session are
comparable.
Within the first v3 baseline, the cost was dominated by taking the length of
each skinned position, 390 million of them at the ceiling. Widening that
length unconditionally to f64 cost +83 % instead; making the f64 square
a fallback behind a finite f32 one recovers about a quarter of the
regression and none of the correctness, since the fallback is still what
admits an extent past sqrt(f32::MAX). The remaining +64 % is the f32
square root itself, which is work this obligation did not previously do at
all.
The slot-dominated shape did not regress in that v3 session: 7.10s before
the term and 6.70s after. Folding the parent chain into the magnitude added
nothing measurable on
either shape — one Mat4 * Vec4 per bone per document side per sample time,
accumulated in the walk that already composes the world matrices, against work
that is per slot and per vertex.
Those timings are retained as history, not carried forward as v4 evidence. v4 removes the per-vertex L2/square-root stage and widens its weighted numerator and denominator to binary64, so the old cost attribution and which shape is slowest may have changed. The budget is nevertheless unchanged: it bounds the same proof passes and cardinalities, still admits the production-sized example in Appendix D, and remains a conservative resource limit. This design makes no v4 wall-time or shape-ordering claim without a v4 benchmark.
Game-Ready Animation Clips: Research Notes for Animsmith
Date: 2026-07-09
Scope note: this is a dated research artifact, not the current animsmith
product contract. The user-facing explainer of what animsmith checks and
why is docs/game-ready-clips.md. Before
converting any recommendation into an issue, reconcile it against
DESIGN.md, the shipped CLI docs, the stable check ids, and any newer
documentation that has merged since this note was written.
Executive Summary
A game-ready animation clip is not just an animation that looks good in a DCC tool. It is a clip whose data, rig assumptions, root motion behavior, loop boundaries, contact quality, metadata, compression tolerance, and import settings are predictable enough for a real-time engine and a production pipeline.
The engine documentation points to the same practical contract from different angles:
- Unity treats clips as isolated units of motion, then exposes import-time decisions for clip ranges, looping, root transform rotation and position, compression, custom curves, events, masks, and retargeting warnings.
- Unreal treats an Animation Sequence as skeletal transform data tied to a Skeleton, with import choices for frame range, sample rate, skeleton matching, custom attributes, root motion, notifies/curves, and compression.
- Godot exposes importer-level decisions for animation optimization, saving animations as separate files, timeline slicing, and retargeting through bone maps/rest-pose alignment.
- Bevy is glTF-forward and code-driven: clips commonly arrive as named glTF animations, are loaded through asset labels, and become
AnimationClips driven byAnimationPlayer,AnimationGraph, transitions, events, masks, and target IDs. - glTF constrains animation data at the format level: node hierarchies must be valid trees, animated nodes must use TRS rather than matrix transforms, times and accessors must be valid, and interpolation must be supported correctly.
That suggests animsmith's core value proposition:
Animsmith should be a repeatable animation-readiness gate and repair assistant: it turns ambiguous "this animation feels broken in the engine" feedback into measurable checks, actionable reports, CI-friendly failures, and safe transforms for common engine import workflows.
The best first product surface is not a full DCC replacement. It is a clip audit, measurement, normalization, and reporting tool for teams that receive raw mocap, marketplace assets, outsourced FBX files, glTF characters, or DCC exports and need them to become engine-safe assets.
Current Animsmith Reconciliation
This research was written from engine and pipeline needs inward. The existing animsmith implementation already covers part of that surface. The useful planning question is therefore not "what should animsmith build from scratch?" but "which gaps remain after mapping the research against the current product?"
Shipped or already documented capabilities include:
animsmith lintas the CI-friendly audit command, with project config, stable exit codes, text output, and JSON output.animsmith measurefor raw measurements without judgment.- Versioned JSON output with
schema_version. - A self-contained HTML
reportcommand. - Stable kebab-case check ids such as
time-monotonic,nan,quat-norm,quat-flip,duration-sanity,scale-keys,non-uniform-scale,constant-nonunit-scale,constant-track,loop-seam,gait-group,root-motion-speed,missing-bones,frozen-bone,in-place,foot-slide,fps, andbind-pose. - Safe mechanical repairs through
fix, including quaternion normalization and hemisphere normalization. - Mechanical transforms such as slicing, hold extension, gait anchoring, and fps-aware transforms.
difffor comparing meaningful metric changes across asset revisions.
Genuinely new or underdeveloped areas surfaced by this research include:
- Engine profiles distinct from rig profiles: Unity, Unreal, Godot, Bevy, and generic glTF/runtime behavior.
- Engine import prediction and optional engine readback/smoke tests.
- Bevy-specific asset addressability:
GltfAssetLabelmanifests, named animation inventories, target-id reports, and RON animation graph templates. - Event, curve, notify, contact, and gameplay-window metadata checks.
- Duplicate loop-endpoint detection and safe removal.
- Blend entry/exit pose checks against declared transition families.
- Retarget bone-map, bone-length, and rest-pose risk diagnostics.
- Runtime-facing sidecars, including contact events and engine import manifests.
- Markdown report output, if a lightweight non-HTML artifact is useful.
The check-like names later in this document are conceptual categories, not proposed public ids. Existing shipped check ids are public contract: renaming or replacing them with dotted taxonomy names would require a separate pre-1.0 design decision.
The transform ideas also need to respect the current design guardrail: animsmith may rewrite clips only in ways whose correctness its own checks can verify. Retargeting, rest-pose rewriting, procedural foot locking, motion warping, stride normalization, and other artistic motion edits should be treated as ADR-level scope proposals unless they can be reduced to mechanical, measurable, reversible operations.
What "Game-Ready" Means
An animation clip is game-ready when it satisfies four contracts at once.
1. Runtime Contract
The engine can import, store, sample, blend, compress, retarget, and play the clip without surprises.
This includes:
- Valid file structure and buffers.
- Supported animation channels and interpolation modes.
- Predictable sample rate or key distribution.
- Finite values, normalized rotations, sane scale, and no hidden shears.
- Reasonable key counts and memory footprint.
- No unnecessary animated scale or zero-value curves unless intentionally used.
2. Character Controller Contract
The clip's spatial behavior matches the gameplay system that will drive it.
This includes:
- Clear in-place vs root-motion classification.
- Expected root bone, root node, or motion extraction source.
- Horizontal, vertical, and rotational root motion treated consistently.
- No foot sliding during planted phases beyond project tolerance.
- No ground penetration or unintended floating.
- Movement distance, stride, speed, and phase aligned with locomotion design.
3. Retargeting Contract
The clip can be used on the intended rigs without silent degradation.
This includes:
- Compatible skeleton hierarchy, bone names, and bone count policy.
- Known rest pose, bind pose, unit scale, and orientation.
- Bone lengths and default rotations within tolerance.
- Humanoid/quadruped/creature profile mapping where applicable.
- No tracks that the target engine will discard without the user noticing.
4. Pipeline Contract
Artists, technical animators, engineers, and build systems can understand the asset state.
This includes:
- Stable naming and clip slicing.
- Actionable warnings, not vague pass/fail output.
- JSON/HTML/Markdown reports for CI and reviews.
- Profile-specific thresholds per engine/project.
- A clear path from "failed" to "fix in DCC" or "auto-transform safely".
Engine Observations
Unity
Unity's Animation tab makes the production concerns explicit. The Manual describes Animation Clips as the smallest building blocks of Unity animation and lets imported FBX data become selectable clips. The importer includes asset-level controls such as import animation, baking IK/simulation to FK keyframes, resampling Euler curves to quaternions, and animation compression. It also exposes clip-level controls for frame ranges, Loop Time, Loop Pose, Cycle Offset, root transform rotation, root transform position Y, root transform position XZ, curves, events, and Avatar masks.
Root motion is treated as a first-class import decision. Unity computes a root transform projection from the body transform for humanoid clips, and lets the user decide whether rotation, vertical position, and horizontal position are baked into the pose or stored as root motion. Unity also warns that import messages can indicate that source data was discarded or that retargeted animation may not exactly match the source.
Implication for animsmith:
- Checks should report whether a clip is an in-place candidate, root-motion candidate, or mixed/ambiguous.
- Loop validation should not only compare first and last local poses. It should compare root-space pose continuity, root displacement continuity, and phase/contact continuity.
- Reports should distinguish "engine will import" from "engine will drop or reinterpret data".
- Compression readiness should be measurable before import: key counts, redundant tracks, high-frequency jitter, rotation/position/scale error estimates.
Primary references:
- Unity Manual, Animation tab: https://docs.unity3d.com/Manual/class-AnimationClip.html
- Unity Manual, How Root Motion works: https://docs.unity3d.com/Manual/RootMotion.html
Unreal Engine
Unreal's FBX animation pipeline says the FBX pipeline is the route for getting skeletal animations from DCC tools into Unreal, and notes that the UE FBX importer uses FBX 2020.2. The Animation Sequence docs define an Animation Sequence as keyframes for a Skeletal Mesh's Skeleton, with position, rotation, and scale sampled over time. Sequences are tied to Skeletons, which allows sharing across meshes using the same Skeleton.
Unreal's import settings expose the same pipeline concerns in engine terms: animation length, frame import range, sample rate, skeleton selection, importing custom attributes, bone tracks, redundant-key removal, and preservation of local transforms. Unreal also flags a practical issue: animations with non-whole end frame values do not import correctly. Root motion requires a skeleton with a root bone and root-bone motion data, then must be enabled per Animation Sequence or Montage. Unreal also separates root motion extraction modes and documents a performance cost when root motion requires animation graph work on the game thread.
Implication for animsmith:
- An Unreal profile should check FBX version where detectable, whole-frame end ranges, root bone presence, root-bone motion, skeleton name/hierarchy compatibility, track scale usage, and custom attribute/event curves.
- Root motion checks should include "root motion exists but is not safely extractable" and "root motion exists but looks like accidental drift".
- Performance/readiness checks should classify clips by compression risk and redundant track count.
Primary references:
- Unreal Engine, FBX Animation Pipeline: https://dev.epicgames.com/documentation/en-us/unreal-engine/fbx-animation-pipeline-in-unreal-engine
- Unreal Engine, Animation Sequences: https://dev.epicgames.com/documentation/en-us/unreal-engine/animation-sequences-in-unreal-engine
- Unreal Engine, Root Motion: https://dev.epicgames.com/documentation/en-us/unreal-engine/root-motion-in-unreal-engine
Godot
Godot's 3D scene import docs expose two relevant concepts for animsmith. First, the advanced import settings include animation optimization, saving animations to external files, and slicing one source timeline into multiple named animations with start/end frames and loop flags. Second, Godot's retargeting documentation emphasizes that bone names alone are insufficient: bone rest transforms and hierarchy matter, and models exported from different DCC tools can have different rest rotations.
Implication for animsmith:
- A Godot profile should validate that a single source timeline can be sliced into named clips and that those slices have frame-accurate boundaries.
- Retarget-readiness checks should include rest-pose/bone-rest diagnostics, not just bone-name mapping.
- Reports should include "will share animation correctly" vs "imports but retargeting will likely distort".
Primary references:
- Godot, Advanced Import Settings, Animation options: https://docs.godotengine.org/en/stable/tutorials/assets_pipeline/importing_3d_scenes/advanced_import_settings.html
- Godot, Retargeting 3D Skeletons: https://docs.godotengine.org/en/stable/tutorials/assets_pipeline/retargeting_3d_skeletons.html
Bevy
Bevy's animation path is different from Unity, Unreal, and Godot because the main engine-facing workflow is not an editor import inspector. In Bevy 0.19, the glTF plugin loads glTF 2.0 assets, exposes specific parts through GltfAssetLabel, and creates Bevy-side representations such as scenes, nodes, skins, animations, and named animations. The animation crate defines AnimationClip as curves mapped to AnimationTargetIds, AnimationPlayer as the playback controller, AnimationGraph as the blending surface, plus transitions, repeat modes, masks, and animation events.
The official examples show the practical contract: a skinned glTF scene can spawn an animation player, clips can be selected by numeric or named glTF animation labels, and an AnimationGraphHandle must be attached to drive playback. Bevy also supports serialized animation graphs in RON and runtime graph construction. That means Bevy-readiness is partly data quality and partly asset-addressability: the clip must be valid glTF, but it also needs stable names/indices, usable target IDs, predictable scene hierarchy, and graph/mask/event metadata that Rust systems can consume.
The glTF loader docs also matter for asset policy. Bevy documents supported and unsupported Khronos extensions; for animation-related planning, KHR_animation_pointer is not supported in the referenced Bevy 0.19 docs. The docs also warn that misspelled labels can be ignored when using string labels, recommending typed GltfAssetLabel for correctness. That makes name stability and manifest generation useful animsmith features for Bevy teams.
Implication for animsmith:
- A Bevy profile should start from strict glTF validation, then add Bevy-specific checks for named animations, asset labels, scene/default scene availability, skins, animation target IDs, and graph-readiness.
- Reports should include Bevy code-facing identifiers: scene labels, animation labels/indices, named animation keys, target IDs, skeleton root candidates, and whether an
AnimationPlayer/graph setup can be inferred from the asset. - Animsmith should warn when clips depend on unsupported glTF extensions or data paths that Bevy will not load into ordinary
AnimationClipcurves. - For Bevy projects, sidecars are especially valuable: generated RON animation graph templates, Rust-friendly metadata JSON, animation event/contact sidecars, and import manifests can reduce fragile hardcoded indices.
Primary references:
- Bevy docs.rs,
bevy::animation: https://docs.rs/bevy/latest/bevy/animation/index.html - Bevy docs.rs,
bevy::gltf: https://docs.rs/bevy/latest/bevy/gltf/index.html - Bevy example, Animated Mesh: https://bevy.org/examples/animation/animated-mesh/
- Bevy example, Animation Graph: https://bevy.org/examples/animation/animation-graph/
- Bevy example, Animated Mesh Control: https://bevy.org/examples/animation/animated-mesh-control/
glTF
glTF gives animsmith a precise low-level validation target. A glTF node hierarchy must be a set of disjoint strict trees, with no cycles and at most one parent per node. Animated nodes must use translation, rotation, and scale properties rather than matrix transforms. TRS is composed in T * R * S order. Animation interpolation modes define how values are evaluated between keyframes, with special handling for quaternion rotation interpolation.
Implication for animsmith:
- glTF validation should include structural checks before semantic animation checks.
- Animated matrix transforms should be reported as a format-level incompatibility.
- Rotation tracks should be checked for normalized quaternions and interpolation-safe continuity.
- Accessor bounds, finite values, monotonic timestamps, and channel target validity should be hard errors.
Primary reference:
- Khronos glTF 2.0 Specification: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html
What Should Be Verified
This section is written as a check catalog animsmith can gradually implement.
Format and Importability
Verify:
- File parses cleanly.
- All referenced buffers/images/accessors exist.
- Node hierarchy has no cycles.
- Animation channels point to valid nodes and supported target paths.
- Animated transforms use supported TRS representation.
- Timestamps are monotonic and finite.
- Rotation values are valid quaternions.
- No NaN, infinity, denormal-heavy data, or extreme coordinate values.
- Declared duration matches actual key times.
- File extensions, embedded/external buffers, and path references are portable.
Why it matters:
Bad file structure creates hard import failures. Slightly malformed animation data can create engine-specific behavior that is hard to diagnose after import.
Animsmith output:
- Hard errors for broken references and unsupported animated transform representation.
- Warnings for unusual but technically legal data, such as animated scale on many bones.
- A "portable asset" score for pathing and external dependency stability.
Skeleton and Rig Compatibility
Verify:
- Expected root bone or root node exists.
- Skeleton hierarchy matches project profile.
- Bone names match a configured naming map or known profile.
- Bone parent-child relationships match target skeleton.
- Rest pose and bind pose are available and consistent.
- Bone lengths and default rotations are within tolerance.
- No duplicate bone names where the engine/profile requires uniqueness.
- No unnecessary mesh nodes inside bone hierarchy for profiles that treat those specially.
- No unsupported helper/control bones unless explicitly ignored.
Why it matters:
Retargeting often fails as "almost correct". The clip imports, but shoulders twist, feet rotate, hips drift, or scale changes leak into the pose. Engine docs consistently make skeleton compatibility central to sharing animations.
Animsmith output:
- Bone map report: matched, missing, duplicate, extra, parent mismatch, rest-pose mismatch.
- Retarget risk level: low, medium, high.
- DCC-oriented fix hints: rename, map, remove helper tracks, rebake rest pose, export skeleton only.
Clip Segmentation, Naming, and Timing
Verify:
- Clip has stable name, action name, or slice definition.
- Start/end frames are explicit and whole-frame when the target profile requires it.
- Duration is above a useful minimum.
- Start/end frame selection does not duplicate a pose in a way that causes a visible hitch.
- Source timeline can be split into named clips without overlap mistakes.
- Sample rate is expected or intentionally variable-keyed.
- For looping clips, loop flag/policy is present or inferable.
Why it matters:
Many "bad clips" are bad cuts. A walk loop that includes both frame 0 and the repeated final frame may pause. An attack clip with a fractional end frame may import wrong in Unreal. A single mocap timeline without slices is not directly usable by designers.
Animsmith output:
- Clip table: name, source take, start, end, duration, frame count, sample rate.
- Slice validation: gaps, overlaps, fractional boundaries, duplicate endpoint risk.
- Suggested split definitions for DCC/export/import tools.
Root Motion and In-Place Policy
Verify:
- Root track exists when required.
- Root node/bone is unambiguous.
- Root translation and rotation are intentional, not tiny drift.
- Horizontal XZ root motion matches gameplay expectations.
- Vertical Y root motion is either baked into pose or extracted according to profile.
- Root rotation is either baked into pose or extracted according to profile.
- In-place clips have near-zero root displacement and near-zero accumulated drift.
- Root-motion clips have coherent trajectory, speed, and final displacement.
- Root path does not jump unexpectedly at clip boundaries.
- Root lock policy can be expressed for target engine.
Why it matters:
Root motion is where animation and gameplay meet. Unity, Unreal, and Godot all expose controls that decide whether motion moves the character object, stays in the pose, or is discarded. The same raw data can be correct for one gameplay model and wrong for another.
Animsmith output:
- Classification: in-place, root-motion, vertical-motion, rotational-root, ambiguous, broken.
- Metrics: displacement, facing delta, average speed, peak speed, vertical travel, path curvature.
- Policy diff: what changes if the clip is imported as Unity in-place, Unity root-motion, Unreal root-motion, or generic glTF playback.
Loop Quality
Verify:
- First and last poses match within tolerance after accounting for root-space projection.
- Root displacement per cycle is expected.
- Velocity at loop boundary is continuous.
- Foot/contact phase is continuous.
- Upper-body additive layers do not pop at wrap.
- Quaternion continuity does not flip at wrap.
- Loop contains a complete gait cycle where expected.
- Loop has no hidden endpoint hold.
Why it matters:
Engine loop toggles do not make a bad loop good. They repeat the data. A good loop must preserve pose, velocity, root-space relation, and contacts.
Animsmith output:
- Loop score with component deltas: root position, root rotation, skeleton pose, key contact bones, velocity.
- Suggested trim offsets, such as "remove duplicated final frame" or "start at opposite foot contact".
- Optional transform: distribute small loop-pose correction across the clip, similar in spirit to an import-time loop-pose adjustment.
Contact, Grounding, and Foot Slide
Verify:
- Feet, toes, hands, weapon tips, or configured contact bones respect contact windows.
- Planted foot horizontal velocity remains below threshold.
- Planted foot vertical position is near floor or contact plane.
- Feet do not penetrate the ground beyond tolerance.
- Feet do not float during expected support phase.
- Contact events align with visual contact frames.
- Stride length and cadence are plausible for the character scale.
- Character root speed matches observed foot motion.
Why it matters:
Foot slide is one of the fastest ways for a game animation to feel ungrounded. It also exposes root-motion/in-place mismatch, scale mismatch, bad retargeting, and bad trimming.
Animsmith output:
- Foot-slide report per foot/toe/contact bone.
- Contact timeline and inferred support phases.
- Warnings for "root speed inconsistent with stride" and "floor plane appears offset".
- Optional event generation: footstep left/right markers and contact windows.
Blend and State-Machine Friendliness
Verify:
- Entry/exit poses are compatible with expected transitions.
- Idle/start/stop/turn clips have appropriate neutral or authored transition frames.
- Locomotion clips expose phase for blend trees or motion matching.
- Directional clips use consistent forward axis and speed labels.
- Additive clips have a correct reference pose/frame.
- Attack/ability clips expose anticipation, active, recovery, and cancel windows if configured.
- Facial/upper-body overlays do not animate lower-body tracks unless intended.
Why it matters:
A single clip can look good in isolation and still be awkward in a state machine. Designers and animation engineers need phase, event, and transition metadata, not just poses.
Animsmith output:
- Phase metrics for loops.
- Entry/exit pose deltas against configured transition families.
- Metadata coverage report: events, curves, notifies, gameplay windows.
- Suggested tags: locomotion, idle, start, stop, turn, additive, attack, hit-react.
Runtime Performance and Compression
Verify:
- Total key count and key density.
- Per-track key count.
- Redundant keys.
- Tracks with constant values.
- Animated scale track count.
- Curves with all-zero values.
- High-frequency jitter that compression will preserve or amplify.
- Estimated error under key reduction.
- Memory footprint estimate before and after cleanup.
Why it matters:
Unity and Unreal both expose key reduction/compression as part of normal animation import. Compression is not just an engine-side detail; bad raw data makes compression less effective and more visually risky.
Animsmith output:
- Key-count budget pass/fail.
- Redundant track report.
- Compression-risk score.
- Suggested safe transforms: drop constant scale tracks, remove zero curves, reduce keys under configured tolerance, resample only where needed.
Metadata, Events, Curves, and Gameplay Signals
Verify:
- Clip has required events for its type.
- Gameplay windows are present and ordered.
- Curves use expected names and value ranges.
- Footstep/contact events align with inferred contact frames.
- Root motion/in-place tags match measured behavior.
- Custom attributes are supported by target profile.
- Events are not outside clip range after trimming.
Why it matters:
Gameplay often depends on animation metadata: footsteps, hit frames, invulnerability windows, VFX triggers, audio, IK weights, look-at masks, turn curves, stride warping, and blend weights. Raw assets usually do not arrive with this information in a project-specific form.
Animsmith output:
- Metadata completeness report.
- Event alignment warnings.
- Exportable event/curve sidecars for engines that cannot preserve the source representation.
Asset Addressability and Runtime Wiring
Verify:
- Each clip can be addressed by stable name, index, label, or manifest entry.
- Named animations and numeric animation indices match the target runtime's expected paths.
- Scene/default scene labels are present for runtimes that spawn animated scenes from asset labels.
- Animation target identifiers can be mapped back to bones/entities after import.
- Animation graph, blend tree, mask, or state-machine inputs can refer to the intended clips without hardcoded guesswork.
- Event/contact sidecars use stable clip IDs and frame/time coordinates after trimming or resampling.
- Required engine/runtime features are supported by the asset loader, such as glTF extensions and animation channel types.
Why it matters:
This is especially important for Bevy and custom runtimes. A clip can be valid glTF and still be awkward to use if the project has to rely on fragile numeric indices, unnamed clips, missing default scenes, unsupported extensions, or target IDs that are hard to connect to gameplay code.
Animsmith output:
- Bevy/glTF asset manifest: scenes, animations, named animations, skins, target IDs, root candidates, and labels.
- Runtime wiring warnings: missing default scene, unnamed animation, unsupported extension, ambiguous animation target, graph/mask metadata missing.
- Optional generated templates: Bevy
GltfAssetLabelmanifest, RON animation graph skeleton, and event/contact sidecar.
Common Asset Process: Raw to Game-Ready
A practical game animation pipeline often looks like this:
-
Acquire source asset
Sources include mocap capture, marketplace packs, outsourced DCC files, Mixamo-style libraries, procedural generation, animation retarget output, or internal Maya/Blender exports.
-
Normalize and preserve source
Keep raw source immutable. Record source author, license, DCC package, export settings, units, skeleton profile, and intended target engine.
-
Parse and inspect
Validate file structure, skeleton, clips, track counts, root motion, sample rates, durations, and metadata. This is where animsmith should operate first.
-
Segment clips
Split source takes or long timelines into named gameplay clips. Trim endpoints. Decide loop flags. Remove accidental duplicate final frames. Record start/end frames in versioned config.
-
Decide root motion policy
For each clip, choose in-place, root-motion XZ, vertical root motion, rotational root motion, or baked pose. This should be explicit in config and checked by animsmith.
-
Retarget and conform
Map to project skeleton, apply rest-pose corrections, remove helper/control tracks, normalize scale, and rebake transforms where needed.
-
Validate motion semantics
Check loop continuity, foot contacts, root trajectory, stride, phase, floor contact, event windows, and blend entry/exit.
-
Optimize
Remove redundant keys/tracks, compress or prepare for engine compression, remove unsupported curves, and resample only when it improves portability.
-
Export engine-facing artifacts
Export FBX, glTF/GLB, engine-specific sidecars, event JSON, import presets, or generated clips.
-
Engine import smoke test
Import into Unity/Unreal/Godot where possible, capture warnings, compare engine-readback metadata, and verify the clip still matches measured expectations.
- CI gate and report
Run checks in continuous integration. Attach HTML/Markdown reports to asset review. Track metric diffs across revisions.
How Animsmith Can Help
Animsmith should position itself as a buildable, scriptable counterpart to engine import inspectors. Engines already show many of the problems, but they show them late, inside editor workflows, and often after engine-specific conversion has already happened. Animsmith can catch and explain those problems before import.
Core Value Proposition
Animsmith helps teams answer:
- Will this clip import into my target engine?
- Will the engine reinterpret, discard, or compress data in a way that changes motion?
- Is this clip in-place or root-motion, and does that match the gameplay contract?
- Is this loop actually seamless?
- Are the feet grounded and contacts usable?
- Can this clip retarget to our skeleton?
- Are the events, curves, and gameplay windows present?
- Did this asset revision make the clip better or worse?
Stakeholder Value
For gameplay engineers:
- Fewer movement/controller mismatches.
- Measurable root motion, speed, stride, and phase.
- CI gates for animation regressions.
- JSON output that can feed gameplay tooling.
For animation engineers and technical animators:
- Clear rig, retarget, root, loop, contact, and compression diagnostics.
- Batch validation across libraries.
- Repeatable thresholds per character, engine, and clip family.
- A safer way to prepare clips for blend trees, state machines, and motion matching.
For artists and animators:
- Actionable reports instead of vague "import looked wrong" feedback.
- Visualizable metrics: root path, foot contact, loop error, pose deltas.
- Fix hints that map to DCC actions: trim frames, freeze scale, bake keys, rename bones, remove helper controls, fix rest pose.
For producers and asset pipeline owners:
- Reduced review churn.
- Better outsourced asset acceptance criteria.
- A machine-readable definition of "done".
- Library-wide inventory and quality metrics.
Recommended Animsmith Requirements
Product Requirements
Shipped: clip audit command
animsmith lint <asset>already runs structural, skeleton, clip, root-motion, loop, contact, and performance checks.- It supports project config, stable exit codes, text output, and JSON output.
- Future work should extend this surface with engine profiles rather
than introduce a parallel
checkcommand.
P0/P1: Engine profiles
- Built-in profiles:
generic,unity-generic,unity-humanoid,unreal,godot,bevy,gltf-runtime. - Profiles define root policy, unit/axis expectations, skeleton profile, clip boundary rules, loop thresholds, contact bones, and unsupported track behavior.
Shipped: metrics command
animsmith measurealready reports raw measurements without judgment.- Future work should add missing engine- and metadata-oriented metrics: asset labels, event windows, transition pose deltas, duplicate endpoint hints, import-risk fields, and Bevy target-id summaries.
Shipped/P1: report formats
- The HTML
reportcommand already provides a human-readable artifact. - JSON output is already versioned and should remain the machine-readable source of truth.
- Markdown output would be new and useful for CI comments, issue filing, and lightweight design reviews.
- Any report should keep exposing:
- "What failed"
- "Why it matters in engines"
- "How to fix"
- "Which profile threshold was used"
- "Raw metric values"
Shipped/P1: Configuration
animsmith.tomlalready provides project config for rig profiles, checks, clips, thresholds, and expectations.- Future config work should add engine-profile settings for:
- skeleton profiles
- root bones/nodes
- contact bones
- floor plane
- clip families
- thresholds
- required metadata/events
- engine profile
P1: Transform command
- Safe transforms:
- split/slice clips
- trim frames
- remove duplicate endpoint frame
- normalize quaternion signs
- drop redundant constant tracks when the result is provably equivalent
- remove all-zero curves when the target profile permits it
- generate footstep/contact events
Transforms that alter authored motion need a separate design decision: root-motion/in-place conversion, retargeting, rest-pose rewriting, floor alignment, key reduction with error tolerance, motion warping, procedural foot locking, and stride normalization are valuable ideas but exceed the current "mechanical and check-verifiable" bar unless narrowed.
P1: Diff command
- Compare raw vs transformed clip and asset revision vs previous revision.
- Report motion deltas, loop score changes, key count changes, foot-slide changes, and retarget risk changes.
P1: Engine-import preset generation
- Generate suggested Unity/Unreal/Godot import settings, Bevy asset manifests, or sidecar instructions:
- loop flags
- root motion node/bone
- sample rate
- clip ranges
- compression tolerances
- event/curve metadata
- Bevy
GltfAssetLabelpaths and optional animation graph templates
P2: Visual report
- Web/HTML viewer with:
- root trajectory
- contact timeline
- per-bone track summary
- loop boundary pose delta
- foot slide heatmap
- before/after transform comparison
P2: Engine smoke tests
- Optional integration to import assets into Unity, Unreal, Godot, or a Bevy runtime harness and capture import warnings.
- Compare engine-side imported clip metadata to animsmith's predictions.
P3: DCC integrations
- Blender/Maya scripts or plugins that open animsmith reports at the failing frame/bone.
- Export profile templates.
- One-click "validate before export".
Check Requirements
The following are capability categories for planning. They should map onto existing stable ids where those exist, not replace them.
Shipped or partially shipped categories:
format.parseformat.referencesanimation.time_monotonicanimation.finite_valuesanimation.quaternion_validityanimation.channel_target_validityskeleton.root_existsskeleton.hierarchy_profileskeleton.rest_pose_presenceclip.durationclip.boundariesclip.whole_frame_endfor Unreal profileroot.classificationroot.drift_in_placeroot.motion_presenceloop.pose_deltaloop.root_deltaloop.velocity_deltacontact.foot_slidecontact.floor_penetrationperformance.key_countperformance.constant_tracksperformance.animated_scale
New or underdeveloped categories:
retarget.bone_map_qualityretarget.rest_pose_deltaretarget.bone_length_deltacompression.estimated_errorcompression.jitter_riskblend.entry_exit_pose_deltaphase.gait_cycle_consistencymetadata.event_alignmentengine.unity_import_predictionengine.unreal_import_predictionengine.godot_import_predictionengine.bevy_import_predictionbevy.gltf_asset_labelsbevy.animation_target_idsbevy.animation_graph_readinessmetadata.required_events
Transform Requirements
Safe transforms should be explicit, reversible where practical, and reported as patches to the animation data.
Initial transforms:
- Trim clip to frame/time range.
- Split source timeline into named clips.
- Remove duplicated loop endpoint.
- Normalize quaternion signs to avoid interpolation flips.
- Drop constant tracks below tolerance.
- Drop all-zero curves not required by profile.
- Resample to target sample rate.
- Bake or extract root XZ/Y/rotation according to profile.
- Floor-align root or contact plane where clearly configured.
- Generate contact events from foot/toe analysis.
High-risk transforms should require explicit opt-in:
- Retargeting.
- Rest-pose rewriting.
- Loop-pose correction distribution.
- Key reduction with nonzero error tolerance.
- Procedural foot locking.
- Motion warping or stride normalization.
Documentation Animsmith Should Cover
Documentation Landing Page
Explain animsmith in one sentence:
Animsmith validates, measures, reports, and prepares skeletal animation clips for game-engine import and runtime use.
Then show three concrete examples:
- "Check whether a walk cycle is a good in-place loop for Unity."
- "Verify whether an FBX attack animation has a valid Unreal root bone, whole-frame end, and gameplay events."
- "Generate a Bevy-friendly glTF animation manifest with stable labels, contact events, and graph-ready clip metadata."
Concepts Guide: What Is Game-Ready?
Cover:
- Game-ready vs visually good.
- Clip, take, sequence, action, slice.
- In-place vs root motion.
- Root bone vs root node vs body/root transform.
- Loop quality.
- Contact and foot slide.
- Retargeting readiness.
- Compression readiness.
- Events, curves, notifies, and gameplay windows.
Engine Profile Guides
Create separate pages:
- Unity profile
- Unreal profile
- Godot profile
- Bevy profile
- glTF profile
- Generic runtime profile
Each page should include:
- What the engine expects.
- What animsmith checks.
- Common failures.
- How to configure thresholds.
- How to fix failures in DCC or engine import settings.
Artist-Facing Failure Catalog
For every warning/error, document:
- Symptom in engine.
- What animsmith measured.
- Common cause.
- Fix in Blender/Maya/MotionBuilder where possible.
- Fix by animsmith transform where safe.
- When to ask an animation engineer.
Examples:
- Foot slides during planted phase.
- Loop pops at wrap.
- Root drifts in an in-place idle.
- Attack moves mesh away from capsule.
- Clip imports but retargeted shoulders twist.
- Scale tracks are discarded or cause compression issues.
- Animation has fractional end frame.
- Source timeline contains multiple unnamed clips.
Engineer-Facing Reference
Cover:
- CLI commands.
- Config schema.
- Engine profile schema.
- JSON report schema.
- Exit codes.
- CI examples.
- Batch mode.
- Diff mode.
- Embedding/library API.
Pipeline Guide
Show recommended workflows:
- Marketplace pack intake.
- Mocap cleanup gate.
- Outsourced asset acceptance.
- Character skeleton migration.
- Pre-commit asset checks.
- CI checks on animation asset changes.
- Raw vs transformed artifact storage.
Roadmap Recommendations
First Milestone: Trustworthy Audit
Goal: animsmith becomes useful before it mutates files.
Deliver:
- Engine profiles.
- Check catalog.
- JSON and Markdown/HTML reports.
- Metrics inventory.
- Root/in-place classification.
- Loop and foot-slide checks.
- Configurable skeleton/contact profiles.
- CI-ready exit codes.
Second Milestone: Safe Preparation
Goal: animsmith can fix common mechanical issues whose correctness it can verify.
Deliver:
- Clip splitting/trimming.
- Duplicate endpoint removal.
- Provably equivalent track pruning.
- Quaternion normalization.
- Fps-aware resampling with measured before/after diff.
- Contact event generation.
- Diff report for every transform.
Third Milestone: Engine Feedback Loop
Goal: animsmith predicts and verifies real engine import behavior.
Deliver:
- Unity/Unreal/Godot import setting generation.
- Bevy asset manifest and animation graph template generation.
- Optional headless/editor import smoke tests.
- Optional Bevy runtime smoke-test harness.
- Import warning capture.
- Engine-readback comparison.
- Profile-specific docs generated from check metadata.
Fourth Milestone: Retargeting Diagnostics and Authoring Assistance
Goal: animsmith helps technical animators diagnose retargeting and library quality problems without silently becoming a DCC retargeter.
Deliver:
- Bone map validation.
- Rest-pose diagnostics.
- Retarget risk scoring.
- Motion library inventory.
- Batch reports and dashboards.
- DCC helper plugins.
Actual retargeting, rest-pose rewriting, and authored-motion repair would need an explicit design-record update before becoming product scope.
Product Positioning
Animsmith should not be positioned as "another converter" or "another animation editor". The stronger position is:
Animsmith is a content quality gate for animation clips. It gives animation teams the same repeatable, reviewable, CI-friendly confidence that code teams expect from tests and linters.
The pitch differs by audience:
- Artists: "See exactly why the clip fails and which frame/bone needs attention."
- Technical animators: "Codify engine import rules and retargeting assumptions once, then batch-apply them."
- Gameplay engineers: "Stop debugging capsule/root/foot-slide problems after import."
- Producers: "Turn outsourced animation acceptance into measurable requirements."
- Tool engineers: "Use a library and JSON schema rather than scraping engine import logs."
Suggested Issue Backlog
New issues worth filing after dedupe:
- Add duplicate endpoint detection and removal.
- Add engine profile config model distinct from rig profiles.
- Add engine import prediction for Unity/Unreal/Godot/Bevy profiles.
- Add Unity/Unreal/Godot/Bevy profile docs.
- Add Bevy glTF asset-label and named-animation manifest generation.
- Add Bevy animation graph template generation.
- Add generated contact events sidecar.
- Add gameplay event/window metadata checks.
- Add blend entry/exit pose-delta checks against transition families.
- Add retarget bone-map and bone-length diagnostics.
- Add Markdown report output.
- Add engine import preset generation.
- Add headless import smoke test adapters.
- Add Bevy runtime smoke-test harness.
- Add compression error estimator.
- Add batch library dashboard.
Ideas that need ADR or design-record updates before issue filing:
- Root-motion to in-place conversion, or in-place to authored trajectory.
- Retargeting and rest-pose rewriting.
- Procedural foot locking, motion warping, or stride normalization.
- Key reduction with nonzero visual/motion error tolerance.
Appendix: Source Notes
- Unity's Animation tab documents import-level controls for animation clips, compression, clip ranges, looping, root transform controls, curves, events, masks, root motion node source, import messages, and retargeting warnings.
- Unity's Root Motion guide documents body/root transform behavior, baking rotation/Y/XZ into pose, root transform projection, gravityWeight implications, and loop pose behavior.
- Unreal's FBX Animation Pipeline documents the FBX import path, FBX 2020.2 requirement, animation export/import expectations, skeleton-only animation export, and skeleton selection.
- Unreal's Animation Sequences guide documents skeletal keyframe data, skeleton sharing, import options, whole-frame end frame issue, editing, sharing/retargeting, and compression.
- Unreal's Root Motion guide documents root bone requirements, root motion extraction, root lock options, debug visualization, and runtime/performance implications.
- Godot's Advanced Import Settings document animation optimizer, saving animations to file, and timeline slices.
- Godot's Retargeting 3D Skeletons guide documents bone rest, bone map, skeleton profile, and rest-pose alignment concerns.
- Bevy's animation docs describe
AnimationClip,AnimationPlayer,AnimationTargetId,AnimationGraph, transitions, repeat modes, masks, and events as the runtime animation surface. - Bevy's glTF docs describe loading glTF 2.0 assets, using typed
GltfAssetLabels for scenes and animations, named asset access, coordinate conversion utilities, skins, and supported/unsupported Khronos extensions. - Bevy's animation examples show skinned glTF playback, named animation selection, animation graph blending, serialized graph assets, and attaching an animation graph handle for runtime playback.
- Khronos glTF 2.0 documents hierarchy, TRS animation constraints, animation interpolation behavior, skins, accessors, and binary data rules.
All pages
Every page AnimSmith publishes, one row each — each page owns one job. The site's front door, which routes the two reader journeys and the ten-minute path, is at https://mmannerm.github.io/animsmith/.
The Category column names the documentation site's part and optional
Part › Group, so this table and the site sidebar carry the same
structure in the same order.
| Document | Use it to… | Category |
|---|---|---|
| Why animsmith | Decide whether animsmith fits your team — what it is, why it exists, and what it is worth by role. The canonical home of the positioning case. | Start |
| What game-ready means | Read the staged readiness ladder — what "game-ready" is evidence for at each level, who owns that level, and what one lint run does and does not settle. | Start |
| Install | Get the binary — prebuilt archive, cargo install, or the pure-Rust glTF-only build — and confirm it runs. | Start |
| First lint in 60 seconds | See a finding, repair what is mechanically safe, and watch a declared contract catch a popped loop, on two committed sample clips. | Start |
| Your first report | Turn findings into skeleton playback, charts and one shareable HTML file, including the before/after comparison and the evidence-only form. | Start |
| Mixamo tutorial | Take a real Mixamo download end-to-end — download, convert, inspect, lint, fix, and grow a contract config with the built-in mixamo rig profile. | Start |
| Symptom index | Start from what you see in the engine and route it to the page, check, repair and config that address it. | Symptoms |
| The pose flickers, spins, or explodes | Repair the rotation representation itself: non-unit keys, hemisphere flips, non-finite values, and what fix restores losslessly. | Symptoms |
| The clip is the wrong length or freezes at the end | Find the channel that stopped early or the export range that drifted, and pin the duration and frame grid the clip owes you. | Symptoms |
| The loop pops | Fix a looping clip that jumps or hitches at the wrap: what the seam checks measure, the before/after evidence, and the loop contract. | Symptoms |
| The character glides or runs in place | Settle whether gameplay or the animation owns horizontal movement, and hold the clip's measured travel to that decision. | Symptoms |
| Feet skate when clips blend | Hold a directional set to one stride phase and one timing surface, and know which member is out. | Symptoms |
| Feet slide within a clip | Fix a planted foot that skates during stance: the sampled stance intervals, the declared speed, and who owns the repair. | Symptoms |
| A limb is T-posed, or a bone never moves | Separate a bone that is absent from one that is keyed but frozen, and both from a clip authored against another bind. | Symptoms |
| Files disagree about skeleton or clip identity | Keep the exact (file, clip) identity of a pack instead of trusting a repeated display name, and decide what a retarget still owes you. | Symptoms |
| The file is bloated, or the retargeter chokes | Decide what is redundant exported data and what is authored scale, then remove only the part a transform can prove. | Symptoms |
| For artists: from export to handoff | Take an authored export from immutable source through evidence-backed candidate handoff, without treating a safe mechanical edit as artistic approval. | Workflows |
| For game developers: from pack to engine gate | Take a pack or collection from inventory through an exact engine profile and an engine-observed gate; includes the complete Bevy 0.19.0 path. | Workflows |
| Declaring the contract: who writes what | Settle who owns each animsmith.toml surface — which parts an animator declares alone, which are the game's call, and where each value comes from — with one complete minimal config. | Workflows |
| Pipeline scenario guide | Plan a raw-to-game-ready asset process — marketplace intake, mocap cleanup, outsourced acceptance, CI gating, and artifact storage. | Workflows |
| Unity 6000.3 profile guide | Configure the exact Unity Generic/Humanoid profiles, importer advice, root-motion choices, and scale boundary. | Workflows › Engine profiles |
| Unreal Engine 5.8 profile guide | Map FBX animation, Skeleton, frame, unit, root-motion, and scale concerns to AnimSmith evidence without inventing importer settings. | Workflows › Engine profiles |
| Godot 4.7 profile guide | Plan scene import, retargeting, animation slicing, root scale, and the current profile's explicit prediction boundary. | Workflows › Engine profiles |
| Bevy 0.19.0 profile guide | Generate exact Animation{i} selector evidence and keep runtime loading, graph, target, and scale responsibilities explicit. | Workflows › Engine profiles |
| glTF and generic runtime guide | Use the engine-neutral contract for custom runtimes, glTF units, source identity, scale repair, and downstream validation. | Workflows › Engine profiles |
| Examples cookbook | Do the work, copy-paste style — gate exports in CI, repair a broken export, trim or re-anchor a clip, encode a project contract config, migrate FBX/Mixamo exports, embed the checks in Rust. | Workflows |
| Static asset workflow guide | Diagnose bounds and transform domains, preserve normal maps, bake supported static placement, attach explicit textures, and understand what still needs engine validation. | Workflows › Advanced workflows |
| Scaling glTF safely | Choose whole-document unit conversion or rest/bind reparameterization, understand the exact-source rewrite/proof transaction, and interpret its support boundary. | Workflows › Advanced workflows |
| Material texture recipes | Attach explicit BaseColor, normal, metallic-roughness, and occlusion images during conversion with deterministic resizing and provenance evidence. | Workflows › Advanced workflows |
| Multi-source character assembly | Combine an authoritative skinned base with exact takes and timeline windows from separate inputs, producing one deterministic GLB plus evidence. | Workflows › Advanced workflows |
| Collection contract extensions | Read the contact-fragment, foot-cycle planner, and transition-family contracts, including strict manifest and contact-evidence bindings. | Workflows › Advanced workflows |
| Configuration reference | Complete animsmith.toml tables, keys, defaults, precedence, validation, globs, rig profiles, and engine settings. | More › Reference |
| Built-in check reference | Look up every registered built-in check: current IDs, default findings, prerequisites, config keys, gap semantics, and remediation boundaries. | More › Reference |
| CLI reference | Look up a command, flag, or exit code. | More › Reference |
| Machine-readable output | Parse versioned --format json reports, glTF animation-addressability inventories, engine import advice, and producer evidence in a pipeline, validated by the JSON Schema under schemas/. | More › Reference |
| Choose animations for your game | Compare evaluated Protofactor and Mixamo content, consider custom authoring, and choose a bounded gameplay trial. | More › Pack evaluations |
| Animation-pack evaluation reports | Review evidence-backed marketplace-pack assessments and the reporting convention for constituent packs and collection-level cross-pack summaries. | More › Pack evaluations |
| Embedding guide | Choose library crates and integration boundaries, then follow the embedded gate flow with the runnable embed example. | More › Rust integration |
| docs.rs API references | Look up exact published Rust API contracts for animsmith-core, animsmith-gltf, animsmith-fbx, animsmith-engine, and animsmith-report. | More › Rust integration |
| CONTRIBUTING.md | Work on animsmith itself — contribution flow. | More › Project and contributing |
| DEVELOPMENT.md | Set up a checkout and run the local verification commands. | More › Project and contributing |
| RELEASING.md | Cut a release — the release-plz flow, the manual 0.1.0 bootstrap, and the published-doc-link policy. | More › Project and contributing |
| SUPPORT.md | Get help or file a bug. | More › Project and contributing |
| SECURITY.md | Report a vulnerability privately. | More › Project and contributing |
| DESIGN.md | Follow the architecture, check-catalog rationale, and roadmap. | More › Project and contributing |
| Scale proof calibration | Review the implementation-owned calibration sweep, provenance magnitudes, historical policy measurements, and reproducible command behind appendix-d-v6. | More › Project and contributing |
| research/ | Read the dated research notes that inform the roadmap. | More › Project and contributing |
| All pages | Find any page by the job it owns — this table, which is also where the site's navigation comes from. | More |