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

# Animation tree skill for AI agents

> Wire AnimationTree state machines and blend trees: locomotion blends, attack interrupts, hit reactions. A Summer Engine skill for AI agents.

`animation-tree` is a skill from the [Summer skill library](/mcp/skills): a guide your AI agent reads when the task matches, so it builds the good version on the first try. Wire AnimationTree state machines and blend trees: locomotion blends, attack interrupts, hit reactions.

| | |
| - | - |
| Status | Stable |
| Domains | Animation, 3D models |
| Version | `1.0.1` |
| Engine version | `>=4.6` |
| Recommended set | No, installed by `--all` or by name |

## When your agent uses it

* The character has clips that must play in response to gameplay — idle/walk/run blends, attacks that interrupt locomotion, hit reactions
* A character keeps T-posing

## Install

`summer setup` installs every skill for your agent. To add only this one:

```bash theme={null}
npx -y summer-engine@latest skills install animation-tree --agent claude-code
```

Swap `claude-code` for your agent, such as `codex` or `cursor`. See [Skills](/mcp/skills#quick-start) for every option.

## The skill

This skill generates nothing. It composes existing clips on an `AnimationPlayer` into an `AnimationTree` with a `StateMachine` for distinct states (idle / attack / hit / dead) and `BlendSpace` nodes for continuous parameters (walk-run blend by speed). Wired wrong, you get T-poses, snapping, or clips that cut each other off.

The canonical Summer Engine stack:

```
Character (CharacterBody3D)
├── Skeleton3D (from the rigged GLB)
├── AnimationPlayer            # holds the raw clips (idle, walk, run, attack_sword, ...)
└── AnimationTree              # references AnimationPlayer, drives behavior
```

Before adding the tree, prove direct `AnimationPlayer.play()` visibly moves the rig. The engine may import animation GLBs as `AnimationLibrary` resources or as `PackedScene`s with embedded `AnimationPlayer`s. If a copied clip appears in the list but the mesh T-poses, its tracks probably target a different root/skeleton path. Use the embedded player that came with the matching GLB, or remap/copy tracks only after confirming they target the active `Skeleton3D`. An inactive AnimationTree is also a no-op, so always set `AnimationTree.active = true`.

## When to use this skill

* Character has 3+ clips and switching between them via `AnimationPlayer.play("walk")` is causing pops, T-poses, or "starts halfway through".
* Need a walk → run blend that's continuous, not boolean.
* Need attack to interrupt locomotion but only on the upper body, while feet keep walking.
* Need hit-react one-shot that returns to whatever state was playing before.
* Building any non-trivial NPC or player character past the prototype phase.

## When NOT to use this skill

* Single-clip looping background prop (banner waving, water surface). Just `AnimationPlayer.play("loop")` is fine.
* Cinematic with hand-authored timing. Use `AnimationPlayer` directly with `play_with_capture`; AnimationTree is overkill.
* Character has zero clips — generate first via `generate-motion`.
* Procedural-only animation (no clips, all IK). That's `procedural-animation`.

## Canonical state machine

This is the production-default for a humanoid character that can move, attack, take a hit, and die. Start here, prune what you don't need, extend with named states for specials.

```
                     ┌─────────┐
              ┌─────►│  Dead   │  (terminal — no exit)
              │      └─────────┘
              │           ▲
              │           │ on_death
   ┌──────────┴───────────┴──────────┐
   │              Hit                │  (one-shot, returns to prev)
   └──────────▲──────────────────────┘
              │ on_damaged                  ┌──────────┐
   ┌──────────┴──────────┐                  │  Attack  │  (one-shot)
   │     Locomotion      │ on_attack ──────►└──────────┘
   │   (BlendSpace1D)    │ ◄────── on_attack_done
   │   idle ↔ walk ↔ run │
   └─────────────────────┘
```

* **Locomotion** is a single AnimationNodeBlendSpace1D parameterized by `move_speed` (0..max). Three points on the line: idle (0), walk (\~3), run (\~6). Crossfades for free.
* **Attack** is an AnimationNodeOneShot wrapping `attack_sword` (or whichever clip). Fires from the script via `parameters/attack/request = ONE_SHOT_REQUEST_FIRE`. Auto-returns to Locomotion when the clip ends.
* **Hit** same pattern, higher priority — interrupts attack. Fires on `damaged` signal.
* **Dead** is a transition-target with no exit. Trigger via `state_machine.travel("dead")`.

## The fast lane: script the wiring (preferred)

Before walking the CRUD steps below, check the ctx lane — one `summer_run_script` call replaces the add-node/set-prop chain AND the hand-written `.tres`:

* **Animation-tier engines:** `ctx.anim_state_machine(character, {states: {...}, transitions: [[from, to, {blend_s: 0.2}], ...], start: "idle"})` gets-or-creates the AnimationTree, wires it to the player, builds the state machine, sets `active = true`. Unknown clip names come back as a report entry listing the player's REAL clips — never wire against guessed names. Full recipe (inspect clips/bones first, method-track events, root motion, playtest verification): `character-animation-wiring`.
* **Any engine (raw fallback):** the same classes are fully script-bound — `AnimationNodeStateMachine.add_node(name, AnimationNodeAnimation)`, `add_transition(from, to, AnimationNodeStateMachineTransition)` (one transition resource each, `xfade_time` on it), then `tree.anim_player = tree.get_path_to(player)`, `tree.tree_root = sm`, `tree.active = true`, and `ctx.set_owner_recursive(tree)`. The raw script is quoted in `character-animation-wiring`.

Scope: `anim_state_machine` builds clip states + transitions only. The canonical machine below — BlendSpace1D locomotion, OneShot attack/hit — still needs the `.tres` (or raw-script) lane for those node types. Discrete idle/walk/run backbones don't.

## Steps via MCP

### 1. Inspect the current character

```
summer_get_scene_tree
summer_inspect_node "./World/Goblin"
```

Confirm there's a `Skeleton3D` and an `AnimationPlayer` with at least `idle` + `walk` + `run` populated. If clips are missing, route to `generate-motion`.

### 2. Add the AnimationTree

```
summer_add_node(scenePath: "res://main.tscn", parent: "./World/Goblin", type: "AnimationTree", name: "AnimationTree")
summer_set_prop(scenePath: "res://main.tscn", path: "./World/Goblin/AnimationTree", key: "anim_player", value: "../AnimationPlayer")
summer_set_prop(scenePath: "res://main.tscn", path: "./World/Goblin/AnimationTree", key: "active", value: "true")
```

`scenePath` is a **required** argument on every scene-mutating tool
(`summer_add_node`, `summer_set_prop`, `summer_set_resource_property`,
`summer_connect_signal`, `summer_save_scene`, `summer_instantiate_scene`,
`summer_remove_node`, `summer_replace_node`). Omit it and the call is rejected
before it reaches the engine. Get the path from `summer_get_project_context`.

`anim_player` must be a relative NodePath; `../AnimationPlayer` from a sibling. Don't use absolute paths — breaks scene reuse.

### 3. Build the tree resource

The `tree_root` is a single `AnimationNodeStateMachine`. Inside it, a `Locomotion` node which is itself an `AnimationNodeBlendSpace1D`, plus `Attack`, `Hit`, `Dead` nodes.

Three ways to build it, in preference order: (1) a `summer_run_script` GDScript
constructing the node graph via the script-bound API — transactional, no
hand-parsed text format (see the fast-lane section above; BlendSpace1D and
OneShot nodes are `AnimationNodeBlendSpace1D.new()` + `add_blend_point(...)`
and `AnimationNodeOneShot.new()` with `fadein_time`/`fadeout_time`, composed
with the same `add_node`/`add_transition` calls); (2) hand-writing the `.tres`
below and importing it; (3) `summer_set_resource_property` node-by-node — the
most verbose, use only for touch-ups. The `.tres` skeleton, for the read/write
fallback lane:

```
[gd_resource type="AnimationNodeStateMachine" load_steps=8 format=3]

[sub_resource type="AnimationNodeBlendSpace1D" id="locomotion"]
blend_point_0/node = SubResource("idle_clip")
blend_point_0/pos = 0.0
blend_point_1/node = SubResource("walk_clip")
blend_point_1/pos = 3.0
blend_point_2/node = SubResource("run_clip")
blend_point_2/pos = 6.0
min_space = 0.0
max_space = 6.0

[sub_resource type="AnimationNodeOneShot" id="attack_oneshot"]
fadein_time = 0.05
fadeout_time = 0.10
mix_mode = 0   # 0=BLEND, 1=ADD

[sub_resource type="AnimationNodeOneShot" id="hit_oneshot"]
fadein_time = 0.0    # snap in — hit reactions are immediate
fadeout_time = 0.15

# ... AnimationNodeAnimation sub_resources point at clip names ...

[resource]
states/Locomotion/node = SubResource("locomotion")
states/Attack/node = SubResource("attack_oneshot")
states/Hit/node = SubResource("hit_oneshot")
states/Dead/node = SubResource("dead_clip")
transitions = [
  "Locomotion", "Attack",   SubResource("trans_to_attack"),
  "Attack",     "Locomotion", SubResource("trans_back"),
  "Locomotion", "Hit",      SubResource("trans_to_hit"),
  "Attack",     "Hit",      SubResource("trans_to_hit"),
  "Hit",        "Locomotion", SubResource("trans_back"),
  "Locomotion", "Dead",     SubResource("trans_to_dead"),
  "Attack",     "Dead",     SubResource("trans_to_dead"),
  "Hit",        "Dead",     SubResource("trans_to_dead")
]
graph_offset = Vector2(0, 0)
```

Each `AnimationNodeStateMachineTransition` carries `xfade_time` (0.15 default), `switch_mode` (Immediate / Sync / At End), and `advance_mode` (Auto / Enabled / Disabled).

Ask before writing the `.tres`: "May I write `res://characters/goblin/animation_tree.tres` with the canonical 4-state machine + Locomotion blendspace?"

### 4. Drive it from the controller script

```gdscript theme={null}
@onready var tree: AnimationTree = $AnimationTree
@onready var sm: AnimationNodeStateMachinePlayback = tree["parameters/playback"]

func _physics_process(delta: float) -> void:
    var horiz_speed: float = Vector2(velocity.x, velocity.z).length()
    tree.set("parameters/Locomotion/blend_position", horiz_speed)

func attack() -> void:
    tree.set("parameters/Attack/request", AnimationNodeOneShot.ONE_SHOT_REQUEST_FIRE)

func take_hit() -> void:
    tree.set("parameters/Hit/request", AnimationNodeOneShot.ONE_SHOT_REQUEST_FIRE)

func die() -> void:
    sm.travel("Dead")
```

`tree.set(...)` writes a parameter path. `sm.travel(...)` switches state.

### 5. Connect signals

```
summer_connect_signal(
  scenePath: "res://main.tscn",
  emitter: "./World/Goblin",
  signal: "damaged",
  receiver: "./World/Goblin",
  method: "take_hit"
)
```

The parameters are `emitter` and `receiver` — not `source`/`target`, not
`from`/`to`. Those spellings fail schema validation.

If the goblin has an HP component emitting `died`, connect that to `die`.

### 6. Save & verify

```
summer_save_scene(scenePath: "res://main.tscn")
summer_get_script_errors(path: "res://scripts/goblin.gd")
```

`summer_get_script_errors` checks **one** file and requires `path`; there is no
"check everything" form. For a project-wide sweep use `summer_get_diagnostics`.

Then `summer_play` and watch — the goblin should idle in place, blend up to walk/run as it moves, fire attack on input/AI, snap to hit on damage, end in Dead.

## Confirmation gates

* **Before writing the `.tres`:** show the state list and transition table.
* **Before connecting signals:** show source → target → method.
* **Before pressing play:** state which behaviors should be visible (idle plays, walk crossfades, attack interrupts).

## Reference card

### Parameter paths cheat-sheet

| Goal | Path |
| - | - |
| Set blendspace1D position | `parameters/Locomotion/blend_position` |
| Fire a OneShot | `parameters/<NodeName>/request` (set to `ONE_SHOT_REQUEST_FIRE`) |
| Abort a OneShot | `parameters/<NodeName>/request = ONE_SHOT_REQUEST_ABORT` |
| Read OneShot active state | `parameters/<NodeName>/active` |
| Get StateMachine playback | `parameters/playback` (returns `AnimationNodeStateMachinePlayback`) |
| Switch state | `state_machine.travel("StateName")` |
| Read current state | `state_machine.get_current_node()` |
| Force state without transition | `state_machine.start("StateName")` |

### Transition `switch_mode` decision

* **Immediate (0):** snap to new state at xfade. Use for hit reactions, deaths.
* **Sync (1):** match current play position. Use for walk ↔ run (avoids foot-pop mid-stride).
* **At End (2):** finish current clip first. Use for attack-string combos (`attack_1` → `attack_2` only at end of `attack_1`).

### Pitfalls

* **Character T-poses on play.** `AnimationTree.active = true` was missed. Set it in `_ready()` or via inspector.
* **Locomotion blendspace stuck on idle even when moving.** The `blend_position` parameter isn't being written — confirm `tree.set("parameters/Locomotion/blend_position", speed)` runs every frame in `_physics_process`. Also confirm the path matches your state name exactly (case-sensitive).
* **Walk-to-run pops.** Transition `switch_mode` is `Immediate`. Set to `Sync` so the cycle phase matches across the crossfade.
* **Attack one-shot fires every frame as long as input is held.** Use `Input.is_action_just_pressed`, not `is_action_pressed`. Or store a `_can_attack` flag with cooldown.
* **Hit reaction interrupts itself when hit twice in 100ms.** Set `fadein_time = 0.0` and gate from script: `if sm.get_current_node() == "Hit": return`.
* **Attack plays but locomotion is invisible (lower body T-pose).** OneShot's
  `mix_mode` is `BLEND` (full body). For upper-body-only attacks, set
  `mix_mode = ADD` and use an additive clip, or drive the lower body from a
  second `AnimationTree` branch and blend by bone with an
  `AnimationNodeBlend2` plus filters. `SkeletonModification3D` is an old
  upstream class name and `ClassDB.class_exists("SkeletonModification3D")`
  returns false on the current Summer build.
* **Dead state replays from start every time HP \< 0.** Add a guard in the controller: `if is_dead: return`.

### One-shot vs StateMachine — which to use

* **OneShot:** the action is a *temporary override* of an underlying state (attack while running, hit-react while attacking). The base keeps playing.
* **StateMachine state:** the action is a *replacement* (dead, stunned, ragdoll). Nothing else plays.
* Action combat is usually OneShot for attacks + a StateMachine state for "dead". Don't model attacks as StateMachine states unless you need precise transition control between attack-string variants.

### BlendSpace1D vs BlendSpace2D

* 1D for speed-only locomotion (idle / walk / run).
* 2D for omnidirectional movement (idle / forward / strafe-left / strafe-right / back). Eight or nine clips arranged on a unit circle. Necessary for top-down or strafing third-person; overkill for FPS hands.

## Anti-patterns

* Skipping AnimationTree and calling `AnimationPlayer.play("walk")` from script. Works for one clip; breaks the moment you need a crossfade.
* Modeling every attack as a StateMachine state. The transition matrix explodes (N states × N transitions). Use one OneShot per attack and switch its inner clip via `parameters/Attack/animation`.
* Writing the `.tres` by hand without using sub\_resource IDs — the engine loses the references. Always use `[sub_resource id="..."]` blocks.
* Driving `blend_position` from raw input (`Input.get_axis(...)`). Drive from actual velocity — that way the blend matches the body's real motion, including knockback and slope-slide.

## Edge cases

* **Player rotates 180° instantly while sprinting.** The blendspace doesn't know about turning — locomotion looks like the character moonwalks for one frame. Add a small additive turn-lean clip layered on top, or add a brief "turn" state with switch\_mode=AtEnd.
* **Animation library uses root motion.** Set `root_motion_track` on the AnimationTree (path to the root bone), then in `_physics_process` use `tree.get_root_motion_position()` and apply to the CharacterBody3D. Don't combine with input-driven velocity — pick one.
* **Character has no AnimationPlayer.** Add one first. AnimationTree without anim\_player target is a no-op.

## Fallback (no MCP)

Open the scene in Summer Engine, add an `AnimationTree` node, set `Anim Player`
to the AnimationPlayer, set `Active = on`, double-click `Tree Root`, and edit
visually. Drag `AnimationNodeStateMachine` as the root, drop nodes inside, draw
transitions by Shift-dragging, and save the scene. The visual editor produces
the same `.tres`; it is slower but discoverable.

## Handoff

* For NPC behavior firing these states (decide-when-to-attack), hand off to `design-npc`.
* For first-person hand animations on the player rig, the same pattern applies — hand off to `fps-controller` for the state inputs.
* For procedural overlays on top of the tree (look-at the player while idling), hand off to `procedural-animation`.
* For lipsync layered on the face during dialogue states, hand off to `facial-and-lipsync`.

## See also

* `character-animation-wiring` — the end-to-end rigged-GLB path: inspect real clip/bone names, `ctx.anim_state_machine`, method tracks, root motion, playtest verification.
* `generate-motion` — produce the clips this tree references.
* `retarget` — share one tree across many characters.

## From a shell

No Summer MCP tools in this session? Run each one as a command; when `summer --version` is missing or below 4.0, use `npx -y summer-engine@latest` in place of `summer`. Fields are flags in kebab-case (`--scene-path res://main.tscn`) or one `--input '<json>'`, and `summer ops describe <op>` prints the exact input. The `summer-cli` skill has the rest.

| In this skill | Command |
| - | - |
| `summer_add_node` | `summer engine add-node` |
| `summer_connect_signal` | `summer engine connect-signal` |
| `summer_get_diagnostics` | `summer engine get-diagnostics` |
| `summer_get_project_context` | `summer engine get-project-context` |
| `summer_get_scene_tree` | `summer engine get-scene-tree` |
| `summer_get_script_errors` | `summer engine get-script-errors` |
| `summer_inspect_node` | `summer engine inspect-node` |
| `summer_inspect_resource` | `summer engine inspect-resource` |
| `summer_instantiate_scene` | `summer engine instantiate-scene` |
| `summer_play` | `summer engine play` |
| `summer_remove_node` | `summer engine remove-node` |
| `summer_replace_node` | `summer engine replace-node` |
| `summer_run_script` | `summer engine run-script` |
| `summer_save_scene` | `summer engine save-scene` |
| `summer_set_prop` | `summer engine set-prop` |
| `summer_set_resource_property` | `summer engine set-resource-property` |
| `summer_stop` | `summer engine stop` |

## Related skills

* [`character-animation-wiring`](/skills/character-animation-wiring): Wire a rigged character end to end: real clips and bones, locomotion states, method tracks, root motion.
* [`design-npc`](/skills/design-npc): Design enemy, NPC, boss or companion behavior: perception, state machine, telegraphs, script stub.
* [`facial-and-lipsync`](/skills/facial-and-lipsync): Make a character's mouth move with a voice-over: phonemes, viseme blendshapes, facial expressions.
* [`generate-motion`](/skills/generate-motion): Attach a library animation clip (idle, walk, run, attack) to a rigged character via AnimationPlayer.
* [`procedural-animation`](/skills/procedural-animation): Runtime bone changes on top of clips: head look-at, foot IK, hand grabs a prop, lean, recoil.
* [`retarget`](/skills/retarget): Apply animation clips from one rigged character to another: one clip library, many models.
* [`fps-controller`](/skills/fps-controller): First-person controller: WASD, mouse look, jump, coyote time, jump buffering, air control.

***

Source: [`library/skills/animation-tree/SKILL.md`](https://github.com/SummerEngine/summer/blob/main/library/skills/animation-tree/SKILL.md), rendered from `summer-engine@4.3.0` on npm.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.