> ## Documentation Index
> Fetch the complete documentation index at: https://docs.eucaengine.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Animation

> Load a glTF clip, play and blend it on an entity, and attach a state machine or montage — driving the skeletal-animation model that a render host skins on screen, on a local server.

Euca's animation system (`euca-animation`) attaches an **animator to an entity**: it plays a clip by
index, blends between states with an `AnimStateMachine`, or overlays a one-shot `MontagePlayer`. Clips
themselves come from glTF files loaded into an `AnimationLibrary`. The pose model — animators, state
machines, montages — is headless and mutates entities synchronously; what needs a host are the two
*inputs*: a real glTF **file** to load clips from, and the **`AnimationLibrary`** resource to store
them in. This page walks the model end to end; every command is real and runnable against a
[local server](/quickstart), with output captured from one.

## Load a clip — and meet two gates

`/animation/load` reads a glTF file and adds its clips and skeleton to the `AnimationLibrary`. There
are two gates, in order. First, the **file must exist** — the minimal server has no character assets,
so a typical path fails at load:

```bash theme={null}
curl -s -X POST http://localhost:3917/animation/load -H 'Content-Type: application/json' \
  -d '{"path":"assets/characters/hero.glb"}'
# {"ok":false,"error":"Failed to load glTF 'assets/characters/hero.glb': No such file or directory (os error 2)"}

curl -s -X POST http://localhost:3917/animation/load -H 'Content-Type: application/json' -d '{}'
# {"ok":false,"error":"Missing 'path' to glTF file"}
```

Second — even with a valid file — the clips need somewhere to land: the **`AnimationLibrary`**
resource. The minimal server installs only events and the rules system, so the store step returns
`{"ok":false,"error":"AnimationLibrary resource not initialized"}`, and the clip list stays empty:

```bash theme={null}
curl -s http://localhost:3917/animation/list
# {"clips":[],"count":0}
```

On a host that ships a rigged `.glb` **and** registers an `AnimationLibrary`, the same load returns
`{"ok":true,"meshes":1,"skeleton":true,"animations":["idle","run"]}`, and `/animation/list` enumerates
each clip's index, name, duration, and channel count.

## Play, blend, and overlay on an entity

The animator side is pure component logic — it doesn't need the library to *attach*, so it works on the
bare server against any spawned entity. Spawn one:

```bash theme={null}
curl -s -X POST http://localhost:3917/spawn -H 'Content-Type: application/json' -d '{"health":100}'
# {"entity_id":2,"entity_generation":0}
```

**Play a clip by index.** `clip` is a numeric index into the library (default `0`), with `speed` and
`loop`. The animator is attached immediately:

```bash theme={null}
curl -s -X POST http://localhost:3917/animation/play -H 'Content-Type: application/json' \
  -d '{"entity_id":2,"clip":0,"speed":1.0,"loop":true}'
# {"ok":true,"message":"Playing animation clip 0 on entity 2"}
```

**Attach a state machine.** Named states each map to a clip index; the engine crossfades between them:

```bash theme={null}
curl -s -X POST http://localhost:3917/animation/state-machine -H 'Content-Type: application/json' \
  -d '{"entity_id":2,"initial_state":0,"states":[{"name":"idle","clip":0},{"name":"run","clip":1,"speed":1.5}]}'
# {"ok":true,"message":"State machine set on entity 2"}
```

**Overlay a montage.** A one-shot blended overlay with blend-in/out ramps (and an optional bone mask):

```bash theme={null}
curl -s -X POST http://localhost:3917/animation/montage -H 'Content-Type: application/json' \
  -d '{"entity_id":2,"clip":2,"clip_duration":0.8,"blend_in":0.1,"blend_out":0.2}'
# {"ok":true,"message":"Montage (clip 2) triggered on entity 2"}
```

**Stop.** Pauses the entity's animator:

```bash theme={null}
curl -s -X POST http://localhost:3917/animation/stop -H 'Content-Type: application/json' \
  -d '{"entity_id":2}'
# {"ok":true,"message":"Stopped animation on entity 2"}
```

A missing target on any of these returns `{"ok":false,"message":"Entity 999 not found"}`.

## Behavior and gotchas

The things you only find out by running it:

* **Loading has two distinct failure modes.** A missing/bad path fails at the glTF read
  (`No such file or directory`); a valid file with no `AnimationLibrary` registered fails at the store
  (`AnimationLibrary resource not initialized`). The clip list is empty in both cases — they are not
  the same gate.
* **`clip` is a numeric index, not a clip name.** `/animation/play`, the state-machine states, and the
  montage all reference clips by their integer position in the library, not by string. Index `0` is the
  default for `play`.
* **Attaching an animator needs only an entity, not the library.** `play`, `state-machine`, `montage`,
  and `stop` all succeed on the bare server (they insert components on an entity) — the clip they point
  at simply doesn't resolve to pose data until a real library is loaded and a render host skins it.
* **No `/step` to attach.** The component is inserted on the call; the per-tick `animation_evaluate`
  pipeline (state machine → sample → crossfade → montage → root motion) runs when the host steps and
  renders.
* **`montage` is idempotent on re-trigger.** A second montage on an entity that already has a
  `MontagePlayer` re-plays into the existing player rather than stacking a new one.

## Endpoints

| Method | Path                       | Description                                                        |
| ------ | -------------------------- | ------------------------------------------------------------------ |
| `POST` | `/animation/load`          | Load a glTF file's clips + skeleton into the `AnimationLibrary`    |
| `POST` | `/animation/play`          | Attach a `SkeletalAnimator` (clip index, speed, loop) to an entity |
| `POST` | `/animation/stop`          | Pause an entity's animator                                         |
| `GET`  | `/animation/list`          | Every loaded clip with index, name, duration, and channel count    |
| `POST` | `/animation/state-machine` | Attach an `AnimStateMachine` (named states → clip indices)         |
| `POST` | `/animation/montage`       | Play a blended one-shot montage overlay (with optional bone mask)  |

## Status

* **Shipped & headless** — the animator, state-machine, and montage component model (all verified on the
  bare server), plus crossfade blending, blend spaces, two-bone and FABRIK IK, look-at constraints, root
  motion, and animation events.
* **Host-dependent** — clip *loading* needs both a real glTF file **and** a registered
  `AnimationLibrary`; the minimal example server has neither, so `/animation/load` fails and
  `/animation/list` is empty. Producing the on-screen skinned mesh from the evaluated `BoneTransforms`
  is the render host's job.
* **Render caveat** — skinned characters currently **receive** shadows but **do not cast** them:
  the shadow depth pass draws only the static cascade batches, so a posed skinned mesh isn't written
  into the shadow map. Proper skinned shadow casting needs GPU compute skinning and is not yet wired.

<Card title="Animation endpoints" icon="film" href="/api-reference/introduction" horizontal>
  Load, play, state-machine, and montage endpoints, with request and response schemas.
</Card>
