# Animation: a first character that moves

The way in to animation in this engine: import a model, give it an animator, play its clips, switch
between them, blend, loop, react to events, and rig a model that came with no skeleton. Every
example here is run by a test (`crates/editor/tests/animation_guide.rs` reads them out of this file),
so what you copy works. The full reference is `docs/API.md` ("Animation — ..." sections); this page
is the order to learn it in.

The examples use the mannequin from the third-person recipe,
`fixtures/recipes/third-person-character/scenes/models/mannequin.gltf`: a stand-in character on a
two-joint rig with the clips `Idle`, `Run`, `Jump`, `Fall` and `Land`. Copy it into your project's
`scenes/models/`, or use your own model and its clip names.

## 1. Import a model

In the editor, **Import** on the toolbar (or drop the file on the window); an agent calls
`asset.import` with the file's path. Either way the file is copied beside the scene and an asset
entry is written that says what the model can do:

<!-- guide: asset-entry -->
```json
"mannequin": { "kind": "glb", "path": "assets/mannequin.gltf", "rig": "custom",
  "clips": ["Idle", "Run", "Jump", "Fall", "Land"],
  "clipDurations": { "Idle": 2, "Run": 0.6, "Jump": 0.5, "Fall": 0.8, "Land": 0.4 },
  "skeleton": { "name": "rig", "joints": 2, "bones": ["hips", "chest"] } }
```

- **`rig`** is `custom` for a model with a skin (a skeleton its vertices follow) and `none` for one
  without. Only a skinned model, or one with morph targets, has anything for an animator to move.
  A model with no skeleton can be given one: see [Rig a model that has none](#7-rig-a-model-that-has-none).
- **`clips`** are the names you write in the animator. They are the file's own animation names,
  spelt exactly.
- The import also checks the model stands up +Y and faces -Z, the engine's forward. If it does not,
  it says so (`rest-pose-orientation`) with the fix: the editor's Import sets the asset's
  `forward` for a character authored facing +Z (glTF's own front, which most are); an agent sets
  `"forward"` from the warning's `fix.forward`. Without it the character runs backwards.
- A model 100 times too big is usually in centimetres: import it with a unit scale of 0.01.

## 2. Make it move with no script

Give the entity an **animator** whose states are named after what the character controller does:
`idle`, `moving`, `jump` and `land`. The controller raises those four events as it starts and stops
moving, leaves the ground and lands, and the engine switches the animator to the state of the same
name when it has one. Nothing else to write:

<!-- guide: zero-script -->
```json
"components": [
  { "type": "mesh", "asset": "mannequin" },
  { "type": "collider", "shape": "capsule", "radius": 0.35, "height": 1.8 },
  { "type": "rigidbody", "kind": "kinematic" },
  { "type": "controller", "kind": "player", "speed": 6, "jump": 1.5 },
  { "type": "animator", "asset": "mannequin", "initial": "idle",
    "states": [
      { "name": "idle",   "clip": "Idle", "loop": true,  "transitions": [] },
      { "name": "moving", "clip": "Run",  "loop": true,  "transitions": [] },
      { "name": "jump",   "clip": "Jump", "loop": false, "transitions": [] },
      { "name": "land",   "clip": "Land", "loop": false, "transitions": [], "once": { "then": "idle" } } ] } ]
```

Press Play and push the stick (or WASD): `moving` plays `Run`, and letting go goes back to `idle`.
A controller needs the scene's player `spawn`, which a new project's scene already has.

- A **state** plays one `clip` (or one blend tree, below). `loop: true` repeats it; `loop: false`
  plays it once and holds the last frame.
- **`initial`** is the state it starts in.
- **`once`** makes a state a one-shot that hands over by itself when its clip ends: `land` plays
  `Land` once and goes back to `idle`.
- The switch between states is a **crossfade**, 0.2 s unless a transition says otherwise (section 4).

## 3. Drive it from your own behaviour

Most games decide the state themselves. A behaviour gets the animator as `ctx.animator`:
`set(state)` switches to a state, `play(name)` plays a state or a clip **once** and then hands back,
and `state` reads the one playing. Setting the state every tick is what takes over from the
controller's own switching in section 2 (it only switches where your script did not this tick).

<!-- guide: own-behaviour-scene -->
```json
"components": [
  { "type": "mesh", "asset": "mannequin" },
  { "type": "collider", "shape": "capsule", "radius": 0.35, "height": 1.8 },
  { "type": "rigidbody", "kind": "kinematic" },
  { "type": "controller", "kind": "player", "speed": 6, "jump": 1.5 },
  { "type": "animator", "asset": "mannequin", "initial": "idle",
    "states": [
      { "name": "idle", "clip": "Idle", "loop": true, "transitions": [] },
      { "name": "run",  "clip": "Run",  "loop": true, "transitions": [] } ] },
  { "type": "script", "module": "behaviors/hero" } ]
```

`scenes/behaviors/hero.ts`:

<!-- guide: own-behaviour -->
```ts
import type { Behavior } from "@argus/api"

const hero: Behavior = {
  update(ctx) {
    const v = ctx.controller!.velocity
    const speed = Math.hypot(v[0], v[2])
    ctx.animator!.set(speed > 0.5 ? "run" : "idle")
    // Once, then back to run or idle. `interrupt: []`: the set() above cannot cut it short.
    if (ctx.input!.pressed("jump")) ctx.animator!.play("Jump", { interrupt: [] })
  },
}
export default hero
```

`play` takes options: `then` (the state to go to afterwards), `blend` (the crossfade in),
`interrupt` (the states that may cut it short; any by default, so a `set` every tick would end it
at once, and `[]` lets nothing) and `event` (the bus event raised when it ends, `animation-end` by
default; see section 6).

## 4. Transitions, and what fires them

A **transition** on a state says "on this event, go to that state, crossfading over this many
seconds". Transitions are run by the built-in **`animator` behaviour**, so the entity needs it as a
script component. It hears the controller's four events by itself; any other event name goes in its
`events` prop, and then any behaviour can fire it with `ctx.events.emit`:

<!-- guide: transitions -->
```json
"components": [
  { "type": "mesh", "asset": "mannequin" },
  { "type": "animator", "asset": "mannequin", "initial": "idle",
    "states": [
      { "name": "idle",  "clip": "Idle", "loop": true, "transitions": [{ "on": "alarm", "to": "panic", "blend": 0.3 }] },
      { "name": "panic", "clip": "Run",  "loop": true, "transitions": [{ "on": "all-clear", "to": "idle", "blend": 0.5 }] } ] },
  { "type": "script", "module": "animator", "props": { "events": ["alarm", "all-clear"] } } ]
```

<!-- guide: emit -->
```ts
ctx.events.emit("alarm") // from any behaviour: every animator listening for "alarm" takes it
```

The rules, all of them:

- **`on`** is the name of a bus event. The `animator` behaviour hears `moving`, `idle`, `jump` and
  `land` with no prop, and other names only when they are in its `events` list.
- **Without the `animator` script component no transition is ever taken.** The scene still loads
  and the state just sits there; the load says `animator-transition-unheard`, naming the events and
  the line to add.
- **`blend`** is the crossfade time into the target, in seconds. `ctx.animator.set` uses it too when
  a transition reaches the state, so a transition kept only for its blend can say `"on": ""`.
- A transition is taken from the state playing now only. `to` must name a state
  (`animator-transition-undefined` otherwise, and the scene is refused).

So there are three ways a state changes, and they combine: the controller's events onto states of
the same name (section 2), transitions through the `animator` behaviour, and `set`/`play` from your
own behaviour, which wins on the tick it is called.

## 5. Blend by a number: walk to run

A state can play a **blend tree** instead of one clip: it mixes clips by a parameter. Here the
parameter is bound to the controller's speed, so the engine writes it every tick and there is no
script at all:

<!-- guide: blend-tree -->
```json
"components": [
  { "type": "mesh", "asset": "mannequin" },
  { "type": "collider", "shape": "capsule", "radius": 0.35, "height": 1.8 },
  { "type": "rigidbody", "kind": "kinematic" },
  { "type": "controller", "kind": "player", "speed": 6, "jump": 1.5 },
  { "type": "animator", "asset": "mannequin", "initial": "locomotion",
    "parameters": [{ "name": "speed", "value": 0, "source": "speed", "damp": 0.15 }],
    "states": [
      { "name": "locomotion", "loop": true, "transitions": [],
        "blend": { "kind": "linear", "parameters": ["speed"], "points": [
          { "clip": "Idle", "at": [0] }, { "clip": "Run", "at": [6] } ] } } ] } ]
```

- **`parameters`** declares every number a tree reads. `source` binds one to a fact the controller
  measures (`speed`, `strafeX`, `strafeY`, `verticalSpeed`, `grounded`, and the `input…` ones);
  leave `source` off and a behaviour writes it with `ctx.animator.param("speed", 3)`. `damp` eases it
  over that many seconds so the pose does not pop.
- **`points`** put each clip at a value; between two points the two clips are mixed, past the ends
  the end clip plays. Clips inside a tree stay in step.
- With a real walk, jog and sprint, put each point at the speed its clip was authored at
  (`GET /feel?section=clips` reads it); the third-person recipe's README shows the tree.

## 6. Clips that loop, play once, and say when

`loop: true` repeats a clip; `loop: false` plays it once and holds. A **one-shot** (`once` on the
state, or `ctx.animator.play`) plays once and hands over by itself, and raises a bus event when it
ends. **Events** mark moments inside a clip: `at` is a fraction of the clip, 0 to 1, and each is
raised on the tick the clip passes it, with `{ entity, clip, at }` as its data:

<!-- guide: events-scene -->
```json
"components": [
  { "type": "mesh", "asset": "mannequin" },
  { "type": "animator", "asset": "mannequin", "initial": "run",
    "states": [
      { "name": "run",  "clip": "Run",  "loop": true,  "transitions": [] },
      { "name": "trip", "clip": "Fall", "loop": false, "transitions": [],
        "once": { "then": "run", "event": "got-up" } } ],
    "events": [
      { "clip": "Run", "at": 0.25, "event": "footstep" },
      { "clip": "Run", "at": 0.75, "event": "footstep" } ] },
  { "type": "script", "module": "behaviors/runner" } ]
```

`scenes/behaviors/runner.ts`:

<!-- guide: events -->
```ts
import type { Behavior } from "@argus/api"

const runner: Behavior = {
  init(ctx) {
    ctx.state.steps = 0
  },
  onEvent(ctx, ev) {
    const data = ev.data as { entity?: string }
    if (data?.entity !== ctx.entity.id) return // one listener hears every character's events
    if (ev.name === "footstep") {
      ctx.state.steps = (ctx.state.steps as number) + 1
      if (ctx.state.steps === 6) ctx.animator!.play("trip") // the one-shot above
    }
    if (ev.name === "got-up") ctx.log("back on its feet")
  },
}
export default runner
```

- Events sit on the animator, not on a state: a clip's footsteps are the same whichever state plays
  it, inside a blend tree too (where the heaviest clip speaks).
- An event with `until` is a **window** that opens and closes (`data.phase` `begin`/`end`), for an
  attack's active frames; `ctx.animator.window(name)` says whether it is open. See `docs/API.md`,
  "Windows".

**Check a clip before you use it.** The asset gate's `motion` command reads a skinned GLB's clips the way the
engine plays them and judges each from its motion alone, before anyone has to watch it. It reports one JSON line:
`pass`, the rig (height, feet, whether it is a canonical humanoid) and, per clip, its numbers and checks:

- `motion-loop-seam`: a clip that must loop (`--loop`, or `--intent idle`, `walk` or `run`) meets itself at the wrap,
  by the same test as the runtime's `animator-loop-seam` warning.
- `motion-foot-slide`: a planted foot stays put (or, in a clip that walks in place, moves with the floor).
- `motion-spike`: no joint pops out and back in one frame or snaps by a large angle, and the root does not jump.
- `motion-bone-coverage`: a walk or run on the canonical humanoid animates every bone the built-in clips need.
- `motion-bad-keys`, `motion-root-drift` (an idle that wanders off), `motion-speed` (with `--speed`, the clip's
  ground speed against the speed you tagged), `motion-length` and `motion-size`; warnings for the hips' height, a
  foot under the floor and a rig that is not a person's height in metres.

Lengths are judged as a share of the rig's height, so a file in centimetres is held to the same standard as one in
metres. Every limit is provisional until real clips have set it. Each reject ends with a `Next:` saying what to fix.

## 7. Rig a model that has none

Characters from an AI model generator or a scan are usually one rigid mesh: no skin, nothing to
animate. Tick **Give it a simple body rig** in the Import modal (an agent passes `bodyRig: true` to
`asset.import`). The copy gets five joints up its middle and twenty-six body clips: `Idle`, `LeanLeft`,
`LeanRight`, `Punch`, `Throw`, `ThrowBack`, `PointLeft`, `PointRight`, `Cheer`, and the reactions
`HighFive`, `HangLeft`, `HangRight`, `HitReact`, `LeanBack`, `FistPump`, `Sag`, `Slump`,
`LookAround`, `Fidget`, `HoldOn`, `GripLeanLeft` and `GripLeanRight` (leans for a rider whose hands
are on bars: the chest rolls and the hands stay near where they rest), `HighFiveLeft`, `SagHold` (a
breathing loop to play after `Sag`), and `GripSag` and `GripSagHold` (the same with the hands kept on
the bars). Your file is not changed. Then animate it like any other:

<!-- guide: body-rig -->
```json
"components": [
  { "type": "mesh", "asset": "statue" },
  { "type": "animator", "asset": "statue", "initial": "idle",
    "states": [
      { "name": "idle",  "clip": "Idle",  "loop": true,  "transitions": [] },
      { "name": "cheer", "clip": "Cheer", "loop": false, "transitions": [], "once": { "then": "idle" } } ] } ]
```

A body has no arms to wave, so the clips are leans, twists and hops. A model that already has a
skeleton is refused the option, and so is one with compressed geometry.

## 8. Models that move parts, not a skeleton

A door on a hinge, a fan, a chest lid: many models are animated by moving their **parts** (glTF
nodes) rather than a skeleton. They play through the same animator. Import gives such a model an
animator that plays its clips, and its asset entry says `"rig": "none"` with the clips it has. Each
part rides its node, and states, one-shots, events and speed work as in the sections above, with
the parts' node names standing in for bones (blend trees and layers too: `docs/API.md`, "Node
animation"). Here a door imported as `door`, whose clip `Open` swings it on its hinge; the state
plays it once and holds it open:

<!-- guide: node-animation -->
```json
"components": [
  { "type": "mesh", "asset": "door" },
  { "type": "animator", "asset": "door", "initial": "open",
    "states": [{ "name": "open", "clip": "Open", "loop": false, "transitions": [] }] } ]
```

- Only an entity with the animator draws the parts moving. Without one the model stands still, and
  colliders, picking and the navmesh always read it at rest.
- **A skinned character can carry parts too.** A part outside the skin that a clip moves, or that
  carries a mesh of its own with no skin (a sword in a hand, a lid on a backpack), rides the
  skeleton and plays its own clips.
- A clip that turns only the node a *skinned* mesh hangs on still moves nothing: a skinned mesh
  follows its bones, not its node. The import says so (`node-animation`); skin those parts in your
  modelling tool, give them a mesh of their own, or move the entity from a behaviour.

## 9. Common mistakes

| You did | What you see | The fix |
|---|---|---|
| Transitions with no `animator` script component, or an event missing from its `events` | the state never changes; `animator-transition-unheard` at load | add `{"type": "script", "module": "animator", "props": {"events": [...]}}`, as the message says |
| A clip name spelt differently from the asset's `clips` | the model holds its rest pose; `animator-clip-missing` at load, `animator-invalid` from the critic | copy the name from the asset entry's `clips` |
| `ctx.animator.set("Run")` with the clip's name instead of the state's | nothing changes; `animator-unknown` at runtime, counted on `GET /feel` | `set` takes a state name; `play` takes a state or a clip |
| `ctx.animator.param("sped", 1)` | throws `AnimatorParamError` (`animator-param-unknown`) | declare it in `parameters`, spelt the same |
| An event `at: 12` (a frame, or seconds) | the scene is refused: `animator-event-invalid` | `at` is a fraction 0 to 1; for a frame number write `frame` |
| `loop: true` on a one-shot | the scene is refused: `animator-once-invalid` | a one-shot has `loop: false` |
| An animator on a model with no skin | it never moves; `has no skin; nothing to animate` at load | import it again with the body rig (section 7) |
| A character that runs backwards or lies down | `rest-pose-orientation` at import | set the asset's `forward` (or the import's up axis) from the warning's `fix` |

