---
title: "animation"
description: "Observe what the engine is animating: every body it is posing, the clips driving each one and where their playheads are…"
section: "API Reference"
slug: "api-animation"
canonical: "https://origozero.ai/docs/api-animation"
updated: "2026-10-03T23:56:05.388453548+00:00"
tags: ["api", "reference"]
---

# animation

Observe what the engine is animating: every body it is posing, the clips driving each one and where their playheads are, how many of a body's bones a clip's retarget reached, and — for a body that is not moving — the one reason why, from a closed set.

```lua
require("@builtin::modules.api.engine.animation") -- animation (also available as global 'animation')
```

The `animation` namespace: 29 functions.

## globals/animation/animating {#globals-animation-animating}

```lua
animation.animating() -> { AnimationBody }
```

The bodies the engine measured a changing pose on — what is animating
right now.

**Returns** `{ AnimationBody }` — An array of `AnimationBody`.

```lua
for _, b in animation.animating() do print(b.entity, b.clips[1] and b.clips[1].name) end
```

## globals/animation/bodies {#globals-animation-bodies}

```lua
animation.bodies() -> { AnimationBody }
```

Every body the engine holds animation state for.

**Returns** `{ AnimationBody }` — An array of `AnimationBody`.

```lua
for _, b in animation.bodies() do print(b.entity, b.matched .. "/" .. b.total) end
```

## globals/animation/body {#globals-animation-body}

```lua
animation.body(entityId: string | EntityRef) -> AnimationBody?
```

The report for one body, or nil when the engine holds no animation
state for it. Accepts the body itself or any ancestor of it, so a character
root answers for the skinned body underneath it.

**Parameters**

- `entityId` `string | EntityRef` — The entity's stable id, or an EntityRef.

**Returns** `AnimationBody?` — An `AnimationBody`, or nil.

```lua
local b = animation.body(hero.id); print(b and b.reason)
```

## globals/animation/clips {#globals-animation-clips}

```lua
animation.clips(entityId: string | EntityRef) -> { AnimationClip }
```

The clips contributing to a body's pose right now, with their playheads
and their retarget coverage.

**Parameters**

- `entityId` `string | EntityRef` — The entity's stable id, or an EntityRef.

**Returns** `{ AnimationClip }` — An array of `AnimationClip`.

```lua
for _, c in animation.clips(hero.id) do print(c.name, c.time, c.matched) end
```

## globals/animation/coverage {#globals-animation-coverage}

```lua
animation.coverage(entityId: string | EntityRef) -> (number, number)
```

How many of a body's bones the clips driving it actually reach.
Returns `(matched, total)`. A clip that retargets onto nothing reads
`(0, 50)` while its playhead advances; a partial retarget reads its own
count, so 3 of 50 is as visible as none.

**Parameters**

- `entityId` `string | EntityRef` — The entity's stable id, or an EntityRef.

**Returns** `(number, number)` — `(matched, total)`.

```lua
local m, t = animation.coverage(hero.id); print(m .. "/" .. t)
```

## globals/animation/declare {#globals-animation-declare}

```lua
animation.declare(entityId: string, facts: { [string]: any })
```

Publish what an animator is running on a body, so the observation names
its clips, playheads and retarget coverage beside the pose the engine
measures. The shipped animators declare through `AnimGraph:publish`; a
custom animator calls this itself, once per frame it runs.

**Parameters**

- `entityId` `string` — The body the animator drives.
- `facts` `{ [string]: any }` — `{ driver, bound, playing, outputKind, failure, clips }`, where
each clip is `{ name, nodeKind, time, duration, playing, finished, looping,
weight, matched, total, unmatched }`.

```lua
animation.declare(body.id, { driver = "MyAnimator", playing = true, clips = {} })
```

## globals/animation/forget {#globals-animation-forget}

```lua
animation.forget(entityId: string)
```

Drop the declaration and the pose evidence the engine holds for one
body. An animator calls this when it releases a body, so the observation
reports the body as undriven from the next frame.

**Parameters**

- `entityId` `string` — The body to drop.

```lua
animation.forget(body.id)
```

## globals/animation/observe {#globals-animation-observe}

```lua
animation.observe() -> AnimationObservation
```

Report what the engine is posing right now and why a body is not
moving. One read covering every body the engine holds animation state for,
each with the pose evidence the engine measured on its armature beside the
clips the animator driving it declared. Answers in edit mode as well as
play mode.

**Returns** `AnimationObservation` — An `AnimationObservation`.

```lua
local a = animation.observe(); print(a.animatingCount, a.riggedBodyCount)
for _, b in animation.observe().bodies do print(b.entity, b.animating, b.reason) end
```

## globals/animation/whyStill {#globals-animation-whystill}

```lua
animation.whyStill(entityId: string | EntityRef) -> (string?, string?)
```

Why the body on an entity is not animating. Returns nil when it IS
animating, and otherwise one of `deactivated`, `noRiggedSkeleton`,
`noGraph`, `clipUnreadable`, `noOutputNode`, `retargetPending`, `retargetMatchedNoRoles`,
`stopped`, `finished`, `paused`, `poseNotApplied`, `poseUnchanged` — the
nearest cause, so the answer names the thing to change. A second return
carries the animator's own words when it could not build a graph.

An entity the engine holds no animation state for is answered from the
entity itself, in the same order the engine resolves a body it does hold:
one carrying no rigged `Skeleton` is `noRiggedSkeleton`, and a rigged one
nothing drives is `noGraph`. An id no entity carries is neither — the
reason is nil and the detail says so.

**Parameters**

- `entityId` `string | EntityRef` — The entity's stable id, or an EntityRef.

**Returns** `(string?, string?)` — `(reason, detail)`.

```lua
local why, detail = animation.whyStill(hero.id); if why then print(why, detail) end
```

## modules/animation/animating {#modules-animation-animating}

```lua
animating(): { AnimationBody }
```

The bodies the engine measured a changing pose on — what is animating
right now.

```lua
for _, b in animation.animating() do print(b.entity, b.clips[1] and b.clips[1].name) end
```

## modules/animation/bodies {#modules-animation-bodies}

```lua
bodies(): { AnimationBody }
```

Every body the engine holds animation state for.

```lua
for _, b in animation.bodies() do print(b.entity, b.matched .. "/" .. b.total) end
```

## modules/animation/body {#modules-animation-body}

```lua
body(entityId: string | EntityRef): AnimationBody?
```

The report for one body, or nil when the engine holds no animation
state for it. Accepts the body itself or any ancestor of it, so a character
root answers for the skinned body underneath it.

**Parameters**

- `entityId` `string | EntityRef` — The entity's stable id, or an EntityRef.

```lua
local b = animation.body(hero.id); print(b and b.reason)
```

## modules/animation/clips {#modules-animation-clips}

```lua
clips(entityId: string | EntityRef): { AnimationClip }
```

The clips contributing to a body's pose right now, with their playheads
and their retarget coverage.

**Parameters**

- `entityId` `string | EntityRef` — The entity's stable id, or an EntityRef.

```lua
for _, c in animation.clips(hero.id) do print(c.name, c.time, c.matched) end
```

## modules/animation/coverage {#modules-animation-coverage}

```lua
coverage(entityId: string | EntityRef): (number, number)
```

How many of a body's bones the clips driving it actually reach.
Returns `(matched, total)`. A clip that retargets onto nothing reads
`(0, 50)` while its playhead advances; a partial retarget reads its own
count, so 3 of 50 is as visible as none.

**Parameters**

- `entityId` `string | EntityRef` — The entity's stable id, or an EntityRef.

```lua
local m, t = animation.coverage(hero.id); print(m .. "/" .. t)
```

## modules/animation/declare {#modules-animation-declare}

```lua
declare(entityId: string, facts: { [string]: any })
```

Publish what an animator is running on a body, so the observation names
its clips, playheads and retarget coverage beside the pose the engine
measures. The shipped animators declare through `AnimGraph:publish`; a
custom animator calls this itself, once per frame it runs.

**Parameters**

- `entityId` `string` — The body the animator drives.
- `facts` `{ [string]: any }` — `{ driver, bound, playing, outputKind, failure, clips }`, where
each clip is `{ name, nodeKind, time, duration, playing, finished, looping,
weight, matched, total, unmatched }`.

```lua
animation.declare(body.id, { driver = "MyAnimator", playing = true, clips = {} })
```

## modules/animation/forget {#modules-animation-forget}

```lua
forget(entityId: string)
```

Drop the declaration and the pose evidence the engine holds for one
body. An animator calls this when it releases a body, so the observation
reports the body as undriven from the next frame.

**Parameters**

- `entityId` `string` — The body to drop.

```lua
animation.forget(body.id)
```

## modules/animation/observe {#modules-animation-observe}

```lua
observe(): AnimationObservation
```

Report what the engine is posing right now and why a body is not
moving. One read covering every body the engine holds animation state for,
each with the pose evidence the engine measured on its armature beside the
clips the animator driving it declared. Answers in edit mode as well as
play mode.

```lua
local a = animation.observe(); print(a.animatingCount, a.riggedBodyCount)
for _, b in animation.observe().bodies do print(b.entity, b.animating, b.reason) end
```

## modules/animation/whyStill {#modules-animation-whystill}

```lua
whyStill(entityId: string | EntityRef): (string?, string?)
```

Why the body on an entity is not animating. Returns nil when it IS
animating, and otherwise one of `deactivated`, `noRiggedSkeleton`,
`noGraph`, `clipUnreadable`, `noOutputNode`, `retargetPending`, `retargetMatchedNoRoles`,
`stopped`, `finished`, `paused`, `poseNotApplied`, `poseUnchanged` — the
nearest cause, so the answer names the thing to change. A second return
carries the animator's own words when it could not build a graph.

An entity the engine holds no animation state for is answered from the
entity itself, in the same order the engine resolves a body it does hold:
one carrying no rigged `Skeleton` is `noRiggedSkeleton`, and a rigged one
nothing drives is `noGraph`. An id no entity carries is neither — the
reason is nil and the detail says so.

**Parameters**

- `entityId` `string | EntityRef` — The entity's stable id, or an EntityRef.

```lua
local why, detail = animation.whyStill(hero.id); if why then print(why, detail) end
```

## tools/animation/observe {#tools-animation-observe}

```lua
animation.observe(target?: (string | EntityRef)) -> AnimObserveResult
```

What the engine is posing right now, one line per body: whether its pose is actually changing, the clip driving it with its playhead, how many of its bones that clip reaches, and — when it is not moving — the one reason why. Call it with no arguments for every body in the scene, or pass an entity to narrow it to that one (a character root resolves down to the skinned body under it).

**Parameters**

- `target` `(string | EntityRef)` _(optional)_

**Returns** `AnimObserveResult`

```lua
"hero"
```

## tools/animation/play {#tools-animation-play}

```lua
animation.play(animation: string | AssetRef<animation>, entityArg: string | EntityRef, opts?: AnimPlayOpts) -> AnimPlayResult
```

Play an animation clip on an entity's skinned body for a test window, then clean up. Resolves `animation` by identity or name and `entity` by id or name, finds the SkinnedModel to drive, and plays it through the canonical AnimGraph — the same path ClipPlayer uses. Returns `{ ok = false, error }` when the clip, the entity, or a SkinnedModel under it cannot be found. When the body already runs an animation the clip overrides it for the window and the original resumes untouched on cleanup.

**Parameters**

- `animation` `string | AssetRef<animation>`
- `entityArg` `string | EntityRef`
- `opts` `AnimPlayOpts` _(optional)_

**Returns** `AnimPlayResult`

```lua
"A_Walk_F_Masc", "hero"
"Idle", "hero", { offset = "5s", loop = true, duration = 8 }
```

## typed/builtin//modules/api/engine/animation/animation/animating {#typed-builtin-modules-api-engine-animation-animation-animating}

```lua
animation.animating() -> { AnimationBody }
```

The bodies the engine measured a changing pose on — what is animating
right now.

**Returns** `{ AnimationBody }` — An array of `AnimationBody`.

```lua
for _, b in animation.animating() do print(b.entity, b.clips[1] and b.clips[1].name) end
```

## typed/builtin//modules/api/engine/animation/animation/bodies {#typed-builtin-modules-api-engine-animation-animation-bodies}

```lua
animation.bodies() -> { AnimationBody }
```

Every body the engine holds animation state for.

**Returns** `{ AnimationBody }` — An array of `AnimationBody`.

```lua
for _, b in animation.bodies() do print(b.entity, b.matched .. "/" .. b.total) end
```

## typed/builtin//modules/api/engine/animation/animation/body {#typed-builtin-modules-api-engine-animation-animation-body}

```lua
animation.body(entityId: string | EntityRef) -> AnimationBody?
```

The report for one body, or nil when the engine holds no animation
state for it. Accepts the body itself or any ancestor of it, so a character
root answers for the skinned body underneath it.

**Parameters**

- `entityId` `string | EntityRef` — The entity's stable id, or an EntityRef.

**Returns** `AnimationBody?` — An `AnimationBody`, or nil.

```lua
local b = animation.body(hero.id); print(b and b.reason)
```

## typed/builtin//modules/api/engine/animation/animation/clips {#typed-builtin-modules-api-engine-animation-animation-clips}

```lua
animation.clips(entityId: string | EntityRef) -> { AnimationClip }
```

The clips contributing to a body's pose right now, with their playheads
and their retarget coverage.

**Parameters**

- `entityId` `string | EntityRef` — The entity's stable id, or an EntityRef.

**Returns** `{ AnimationClip }` — An array of `AnimationClip`.

```lua
for _, c in animation.clips(hero.id) do print(c.name, c.time, c.matched) end
```

## typed/builtin//modules/api/engine/animation/animation/coverage {#typed-builtin-modules-api-engine-animation-animation-coverage}

```lua
animation.coverage(entityId: string | EntityRef) -> (number, number)
```

How many of a body's bones the clips driving it actually reach.
Returns `(matched, total)`. A clip that retargets onto nothing reads
`(0, 50)` while its playhead advances; a partial retarget reads its own
count, so 3 of 50 is as visible as none.

**Parameters**

- `entityId` `string | EntityRef` — The entity's stable id, or an EntityRef.

**Returns** `(number, number)` — `(matched, total)`.

```lua
local m, t = animation.coverage(hero.id); print(m .. "/" .. t)
```

## typed/builtin//modules/api/engine/animation/animation/declare {#typed-builtin-modules-api-engine-animation-animation-declare}

```lua
animation.declare(entityId: string, facts: { [string]: any })
```

Publish what an animator is running on a body, so the observation names
its clips, playheads and retarget coverage beside the pose the engine
measures. The shipped animators declare through `AnimGraph:publish`; a
custom animator calls this itself, once per frame it runs.

**Parameters**

- `entityId` `string` — The body the animator drives.
- `facts` `{ [string]: any }` — `{ driver, bound, playing, outputKind, failure, clips }`, where
each clip is `{ name, nodeKind, time, duration, playing, finished, looping,
weight, matched, total, unmatched }`.

```lua
animation.declare(body.id, { driver = "MyAnimator", playing = true, clips = {} })
```

## typed/builtin//modules/api/engine/animation/animation/forget {#typed-builtin-modules-api-engine-animation-animation-forget}

```lua
animation.forget(entityId: string)
```

Drop the declaration and the pose evidence the engine holds for one
body. An animator calls this when it releases a body, so the observation
reports the body as undriven from the next frame.

**Parameters**

- `entityId` `string` — The body to drop.

```lua
animation.forget(body.id)
```

## typed/builtin//modules/api/engine/animation/animation/observe {#typed-builtin-modules-api-engine-animation-animation-observe}

```lua
animation.observe() -> AnimationObservation
```

Report what the engine is posing right now and why a body is not
moving. One read covering every body the engine holds animation state for,
each with the pose evidence the engine measured on its armature beside the
clips the animator driving it declared. Answers in edit mode as well as
play mode.

**Returns** `AnimationObservation` — An `AnimationObservation`.

```lua
local a = animation.observe(); print(a.animatingCount, a.riggedBodyCount)
for _, b in animation.observe().bodies do print(b.entity, b.animating, b.reason) end
```

## typed/builtin//modules/api/engine/animation/animation/whyStill {#typed-builtin-modules-api-engine-animation-animation-whystill}

```lua
animation.whyStill(entityId: string | EntityRef) -> (string?, string?)
```

Why the body on an entity is not animating. Returns nil when it IS
animating, and otherwise one of `deactivated`, `noRiggedSkeleton`,
`noGraph`, `clipUnreadable`, `noOutputNode`, `retargetPending`, `retargetMatchedNoRoles`,
`stopped`, `finished`, `paused`, `poseNotApplied`, `poseUnchanged` — the
nearest cause, so the answer names the thing to change. A second return
carries the animator's own words when it could not build a graph.

An entity the engine holds no animation state for is answered from the
entity itself, in the same order the engine resolves a body it does hold:
one carrying no rigged `Skeleton` is `noRiggedSkeleton`, and a rigged one
nothing drives is `noGraph`. An id no entity carries is neither — the
reason is nil and the detail says so.

**Parameters**

- `entityId` `string | EntityRef` — The entity's stable id, or an EntityRef.

**Returns** `(string?, string?)` — `(reason, detail)`.

```lua
local why, detail = animation.whyStill(hero.id); if why then print(why, detail) end
```
