Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.

SurfaceWhat it declaresWho owns the valueWhere the value comes fromMinimal example
[rig] profile, [rig.roles]Which bones are hips, feet, toes and rootArtist alone — a fact of the exported skeletonanimsmith inspect prints the profile it resolved[rig]
profile = "mixamo"
[rig] required_bonesBones that must exist even without keysTogether — the game names them, the export carries themThe 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 capsThat the clip is a cycle, and how far its endpoints may driftArtist alone for loop; the caps are a project bar set togetherThe take was authored as a cycle; the caps start at the defaults[clips.walk]
loop = true
[clips."<name>"] duration_s, fpsThe length and frame grid the clip owesTogether — an export setting against a gameplay expectationThe DCC export range, confirmed by animsmith measure[clips.walk]
fps = 30.0
[clips."<name>"] movement_owner_xz, movement_owner_y, movement_owner_yawWhether gameplay or the animation moves the character on each axisGame developer alone — a character-controller decisionThe engine project: whichever is actually driving movement[clips.walk]
movement_owner_xz = "gameplay"
[clips."<name>"] speed_mpsThe travel speed a root-motion clip promisesTogether — the controller states it, the clip holds itanimsmith measure reports the travel; the pin is the agreed value[clips.run]
speed_mps = { value = 3.5, tolerance = 0.2 }
[clips."<name>"] animates_bonesBones this take must actually move, not merely carryTogether — gameplay needs the motion, the rig produces itThe 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 timeTogether — the technical animator declares the ring and its phase tolerance, the game developer confirms the runtime blend setThe 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] selectorsAttachment nodes whose rest-world scale the runtime relies onTogether — the game names the socket, the export scales itThe 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 togetherGame developer alone — the shipping project's own tupleThe engine project: version, importer, import settings[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
[checks.<id>] severityWhat blocks the gate, what only reports, what is offTogether — it is the project's quality barA 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 familyTogether — the pack owner states what the delivery contains, the game developer binds it to runtime setsThe delivery itself; see collection contractsSee 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.