> ## 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.

# Character animation wiring skill for AI agents

> Wire a rigged character end to end: real clips and bones, locomotion states, method tracks, root motion. A Summer Engine skill for AI agents.

`character-animation-wiring` 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 a rigged character end to end: real clips and bones, locomotion states, method tracks, root motion.

| | |
| - | - |
| 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

* A rigged GLB or Meshy rig with generated clips is in the scene and needs idle/walk/run wiring with blend times
* Footstep or attack-frame events, head tracking, still poses, blend-shape facial keys, or root motion must be hooked up
* The user says the character slides while walking, or the GLB has animations but nothing plays

## Install

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

```bash theme={null}
npx -y summer-engine@latest skills install character-animation-wiring --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

The game-critical path: a rigged, animated character lands in the scene — from `assets.generate_3d({ ..., rig: true })` + `assets.generate_motion`, or an imported GLB — and nothing moves yet. This skill wires it end to end: inspect what actually arrived, build locomotion, fire gameplay events off animation frames, add poses and head tracking, key the face, and handle root motion. Then **prove it in a playtest** — a wired tree that was never played is a claim, not a result.

Two lanes throughout:

* **ctx lane (animation-tier engines):** the animation ctx helpers on `summer_run_script` — `anim_state_machine`, `animate_method`, `bone_pose`, `look_at_modifier`, plus the `animate()` v2 extensions. One script per step, owner handled, failures come back as report entries.
* **raw lane (any engine):** the same wiring through plain GDScript in `summer_run_script` — the Animation/AnimationTree classes are fully script-bound, just verbose. On an older engine a missing ctx helper is a plain `Invalid call to method ...` script error; fall back to the raw lane, which works everywhere.

Frozen animation-tier signatures (see `scene-scripting` for the full stdlib):

```gdscript theme={null}
anim_state_machine(target: Node, spec: Dictionary, player: AnimationPlayer = null) -> AnimationTree
    # spec: { states: {name: clip_name}, transitions: [[from, to, {auto?: bool,
    #   blend_s?: float}], ...], start: name }
animate_method(node: Node, calls: Array, anim_name: String = "",
               loop: bool = false, player: AnimationPlayer = null) -> AnimationPlayer
    # calls: [[time_s, method_name, args_array], ...] -> one method-call track
bone_pose(skeleton: Node, bone: String, pose: Dictionary) -> bool
    # pose keys position/rotation/scale; unknown bone -> false + report listing bone names
look_at_modifier(node: Node, target: Node, props: Dictionary = {}) -> Node
    # LookAtModifier3D under the skeleton/node, owned
animate(...) v2: keys entries also accept {time, value, interpolation: "nearest"|"linear"|"cubic"};
    # property may target bones ("<skeleton_node>:<bone_name>/position|rotation|scale")
    # and blend shapes ("blend_shapes/<name>")
```

## When to use this skill

* A rigged GLB with clips is instanced and the character T-poses or slides.
* "Make the goblin idle, walk when moving, run when sprinting."
* Footstep sounds / attack hit-frames need to fire at exact animation times.
* A corpse/statue/mannequin needs a still pose; an NPC head should track the player.
* The character visibly moonwalks — the clip translates the root but the body stays put (root motion).

## When NOT to use this skill

* No clips yet — generate first via `generate-motion` (rigged Meshy target) or import them.
* Continuous walk↔run blending by speed, upper-body attack overlays, OneShot hit reactions — that graph design lives in `animation-tree`; this skill's state machine is the discrete idle/walk/run backbone.
* Foot IK, additive lean, ragdoll — `procedural-animation`.
* Full viseme lipsync from audio — `facial-and-lipsync` (this skill covers the blend-shape keying mechanism it builds on).

## Step 1 — Inspect what actually came in

Never wire against assumed clip names or bone names. Imports differ: the AnimationPlayer may be nested inside the instance, clips may be namespaced (`walk` vs `Walking_Woman` vs `mixamo_com`), bone names vary by rig source. The instance's internals are not owned by the edited scene, so pass `owned: false` when searching:

```gdscript theme={null}
func run(ctx):
    var character = ctx.find("Goblin")
    if character == null:
        ctx.report("error", "Goblin not found — check summer_get_scene_tree")
        return
    for p in character.find_children("*", "AnimationPlayer", true, false):
        ctx.report("player:" + str(character.get_path_to(p)), p.get_animation_list())
    for s in character.find_children("*", "Skeleton3D", true, false):
        var bones := []
        for i in s.get_bone_count():
            bones.append(s.get_bone_name(i))
        ctx.report("skeleton:" + str(character.get_path_to(s)), bones)
    for m in character.find_children("*", "MeshInstance3D", true, false):
        if m.mesh and m.mesh.get_blend_shape_count() > 0:
            var shapes := []
            for i in m.mesh.get_blend_shape_count():
                shapes.append(m.mesh.get_blend_shape_name(i))
            ctx.report("blend_shapes:" + str(character.get_path_to(m)), shapes)
```

Read the reports. Every later step quotes these exact strings. If the clip list is empty, the GLB imported without animations (or they landed as a separate AnimationLibrary asset) — route back to `generate-motion` / `retarget` before wiring anything.

## Step 2 — Locomotion state machine (ctx lane)

`ctx.anim_state_machine` gets-or-creates the AnimationTree, wires it to the found (or given) AnimationPlayer, builds the AnimationNodeStateMachine, and sets `active = true`:

```gdscript theme={null}
func run(ctx):
    var character = ctx.find("Goblin")
    var tree = ctx.anim_state_machine(character, {
        "states": {"idle": "idle", "walk": "walk", "run": "run"},
        "transitions": [
            ["idle", "walk", {"blend_s": 0.2}],
            ["walk", "idle", {"blend_s": 0.2}],
            ["walk", "run",  {"blend_s": 0.15}],
            ["run",  "walk", {"blend_s": 0.15}],
        ],
        "start": "idle",
    })
    if tree == null:
        return          # the report names the failure — read it
    ctx.report("tree", str(tree.get_path()))
```

State names are yours; **clip names must be the exact strings from Step 1**. An unknown clip name produces a report entry listing the player's actual clips — never silent. Fix the spelling from that list; do not guess again.

Drive it from the controller script (host-edits the `.gd`):

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

func _physics_process(_delta: float) -> void:
    var speed := Vector2(velocity.x, velocity.z).length()
    if speed < 0.1: sm.travel("idle")
    elif speed < 4.0: sm.travel("walk")
    else: sm.travel("run")
```

Thresholded `travel()` is the discrete backbone. When the user wants a continuous speed blend or attack/hit overlays, extend the tree per `animation-tree` — those node types are beyond the `anim_state_machine` spec dict.

## Step 3 — Method tracks: footsteps and attack frames

Gameplay events that must land on an animation frame (footstep audio at heel-strike, damage at the blade's contact frame) belong in a method-call track, not a timer:

```gdscript theme={null}
func run(ctx):
    var character = ctx.find("Goblin")
    ctx.animate_method(character, [
        [0.32, "play_footstep", []],
        [0.84, "play_footstep", []],
    ], "walk_events", true)          # loop matches the walk clip's loop
    ctx.animate_method(character, [
        [0.45, "deal_attack_damage", [25]],
    ], "attack_events")
```

The methods must exist on the target node's script — write them there first and check with `summer_get_script_errors`. Play the event clip alongside its motion clip (same timings) from the same tree. **Method tracks fire during playback in the running game; a successful script result proves the track exists, not that the method fires** — verification is a playtest where the footstep actually sounds.

## Step 4 — Still poses and head tracking

A corpse over a railing, a statue mid-swing, a mannequin — edit-time poses, no clip needed:

```gdscript theme={null}
func run(ctx):
    var skel = ctx.find("Goblin").find_children("*", "Skeleton3D", true, false)[0]
    if not ctx.bone_pose(skel, "RightArm", {"rotation": Quaternion(Vector3.FORWARD, 1.2)}):
        return          # false -> the report lists the skeleton's real bone names (capped 64)
    ctx.bone_pose(skel, "Head", {"rotation": Quaternion(Vector3.RIGHT, -0.4)})
```

Head tracking — one call creates the owned `LookAtModifier3D` under the skeleton:

```gdscript theme={null}
    var mod = ctx.look_at_modifier(skel, ctx.find("Player"), {"bone_name": "Head"})
```

Unknown props land in `prop_warnings` — read them. Angle limits, influence fade-out by distance, and spine-chain distribution are the difference between alive and possessed: tune per `procedural-animation` (A1/A2).

## Step 5 — Facial keys via blend shapes

`animate()` accepts `"blend_shapes/<name>"` property paths (value tracks, weights 0..1) against the MeshInstance3D that owns the shapes — names come from Step 1's report:

```gdscript theme={null}
func run(ctx):
    var head = ctx.find("Goblin").find_children("*", "MeshInstance3D", true, false)[0]
    ctx.animate(head, "blend_shapes/jawOpen", [
        {"time": 0.0, "value": 0.0, "interpolation": "linear"},
        {"time": 0.15, "value": 1.0},
        {"time": 0.4, "value": 0.0},
    ], "roar")
    ctx.animate(head, "blend_shapes/browDown_L", [[0.0, 0.0], [0.2, 1.0]], "roar")  # same clip — track appended
```

That is the mechanism; a full audio-synced viseme timeline is `facial-and-lipsync`. Bone-track keyframes work the same way through `animate()` v2 — `ctx.animate(character, "Skeleton3D:Head/rotation", keys, "nod")` creates a proper bone rotation track (the helper owns the quaternion conversion; never hand-build quaternion tracks).

## Step 6 — Root motion

Meshy/mocap locomotion clips translate the root bone forward (Meshy `run` moves \~5m per cycle). Two valid setups — **pick one, never both**:

1. **In-place movement (default):** code drives `velocity`, the clip should NOT translate the root. If the character lunges forward and snaps back every loop, the clip has baked root translation — strip it or switch to setup 2.
2. **Root motion:** the clip drives movement. Requires, on the AnimationTree: `root_motion_track` set to the root bone's track path (e.g. `"Skeleton3D:Hips"` — the skeleton node path from Step 1, colon, the root bone name), then in `_physics_process` apply `tree.get_root_motion_position()` / `get_root_motion_rotation()` to the CharacterBody3D instead of input-driven velocity. A `RootMotionView` node visualizes the extracted motion in the editor (editor-only helper; it is invisible in the shipped game).

Symptoms map: sliding feet = setup 1 with speed thresholds mismatched to the clip's stride; lunge-and-snap = baked root translation under setup 1; character animates but never moves = setup 2 without the `get_root_motion_*` application code.

Root motion CANNOT be judged from the editor state. Playtest it: `summer_play`, then `summer_get_runtime_tree` + `summer_inspect_runtime_node` on the character — its live `global_position` must advance while walking — plus `summer_screenshot target:"game"` for the pixels. Claim only what those reads show.

## Raw lane — the same wiring on older engines

Every class above is script-bound; `ctx.anim_state_machine` is convenience, not capability. The raw locomotion wiring:

```gdscript theme={null}
func run(ctx):
    var character = ctx.get_scene_root().find_child("Goblin", true, false)
    var player: AnimationPlayer = character.find_children("*", "AnimationPlayer", true, false)[0]

    var sm := AnimationNodeStateMachine.new()
    for clip in ["idle", "walk", "run"]:          # exact names from Step 1
        var anim_node := AnimationNodeAnimation.new()
        anim_node.animation = clip
        sm.add_node(clip, anim_node)
    for pair in [["idle", "walk", 0.2], ["walk", "idle", 0.2], ["walk", "run", 0.15], ["run", "walk", 0.15]]:
        var t := AnimationNodeStateMachineTransition.new()   # one resource PER transition
        t.xfade_time = pair[2]
        sm.add_transition(pair[0], pair[1], t)

    var tree := AnimationTree.new()
    tree.name = "AnimationTree"
    character.add_child(tree)
    ctx.set_owner_recursive(tree)                 # BEFORE save, or it vanishes
    tree.anim_player = tree.get_path_to(player)   # relative NodePath
    tree.tree_root = sm
    tree.active = true
    ctx.report("tree", str(tree.get_path()))
```

Method tracks raw: `anim.add_track(Animation.TYPE_METHOD)` + `track_set_path(idx, path_to_node)` + `track_insert_key(idx, t, {"method": "play_footstep", "args": []})`, then `player.get_animation_library("").add_animation(name, anim)` (get-or-create the library — `add_animation_library("", AnimationLibrary.new())` when missing; this library step is the one agents get wrong). Still poses raw: `skel.set_bone_pose_rotation(skel.find_bone("Head"), quat)`. Verify unfamiliar members with `summer_api_docs` first — never guess.

## The verification loop (every step)

1. `summer_world_snapshot` before; keep the `snapshot_id`.
2. Run the script; read `errors`, `reports`, `prop_warnings`, `rolled_back`.
3. `summer_snapshot_diff from_id:<id>` — exactly the nodes you meant (AnimationTree added, nothing vanished).
4. `summer_save_scene`, then behavior: `summer_play` → `summer_get_runtime_tree` / `summer_inspect_runtime_node` for live state, `summer_screenshot target:"game"` for pixels → `summer_stop`.
5. Claim only what the capture and the runtime reads show. "The script succeeded" is not "the character walks." Full discipline: `verifying-scenes`.

## Red Flags — STOP

| Red flag | Reality |
| - | - |
| Wiring clip names you never read from the engine | Step 1 exists because imports rename things. Inspect, then quote. |
| `find_children(...)` returns nothing inside the instance | You left `owned` at its default `true`. Instance internals are unowned — pass `false`. |
| Retrying `anim_state_machine` with another guessed clip name | The failure report lists the player's actual clips. Use one of those. |
| Timer-based footsteps/damage synced "by eye" | Method tracks are frame-accurate and survive clip retimes. |
| `set_bone_pose_*` every frame from `_process` for tracking | That fights the tree. Runtime tracking is `look_at_modifier` / modifiers; `bone_pose` is for edit-time stills. |
| Root motion AND input velocity applied together | Double movement. Pick one lane (Step 6). |
| Raw lane: `add_child` without `ctx.set_owner_recursive(tree)` | The saved scene silently loses the AnimationTree. |
| "Locomotion works" without a playtest | Run it. Read the runtime tree. Look at the frame. Silence is not evidence. |

## Handoff

* Blend spaces, OneShot attacks/hit-reacts, upper-body filters — `animation-tree`.
* Foot IK, additive lean, ragdoll — `procedural-animation`.
* Audio-synced lipsync on the blend shapes — `facial-and-lipsync`.
* Clips missing or on the wrong rig — `generate-motion`, `retarget`.
* NPC behavior deciding when to travel/fire states — `design-npc`.

## See also

* `scene-scripting` — the ctx stdlib contract, owner rules, budgets, undo/rollback.
* `verifying-scenes` — snapshot/diff/screenshot/runtime-read discipline this skill leans on.

## 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 |
| - | - |
| `assets.generate_3d` | `summer assets generate-3d` |
| `assets.generate_motion` | `summer assets generate-motion` |
| `summer_api_docs` | `summer docs engine-api` |
| `summer_get_runtime_tree` | `summer engine get-runtime-tree` |
| `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_runtime_node` | `summer engine inspect-runtime-node` |
| `summer_play` | `summer engine play` |
| `summer_run_script` | `summer engine run-script` |
| `summer_save_scene` | `summer engine save-scene` |
| `summer_screenshot` | `summer engine screenshot` |
| `summer_snapshot_diff` | `summer engine snapshot-diff` |
| `summer_stop` | `summer engine stop` |
| `summer_world_snapshot` | `summer engine world-snapshot` |

## Related skills

* [`animation-tree`](/skills/animation-tree): Wire AnimationTree state machines and blend trees: locomotion blends, attack interrupts, hit reactions.
* [`generate-motion`](/skills/generate-motion): Attach a library animation clip (idle, walk, run, attack) to a rigged character via AnimationPlayer.
* [`retarget`](/skills/retarget): Apply animation clips from one rigged character to another: one clip library, many models.
* [`procedural-animation`](/skills/procedural-animation): Runtime bone changes on top of clips: head look-at, foot IK, hand grabs a prop, lean, recoil.
* [`facial-and-lipsync`](/skills/facial-and-lipsync): Make a character's mouth move with a voice-over: phonemes, viseme blendshapes, facial expressions.
* [`design-npc`](/skills/design-npc): Design enemy, NPC, boss or companion behavior: perception, state machine, telegraphs, script stub.
* [`scene-scripting`](/skills/scene-scripting): Build scenes, levels, HUDs and wiring with one editor GDScript (summer\_run\_script) instead of call chains.
* [`verifying-scenes`](/skills/verifying-scenes): Prove scene work landed: snapshot, diff, screenshots, runtime reads, 3D audits, shot sheets and zooms.

***

Source: [`library/skills/character-animation-wiring/SKILL.md`](https://github.com/SummerEngine/summer/blob/main/library/skills/character-animation-wiring/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.