AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Character Animation Wiring

skill-summerengine-summer-character-animation-wiring · by SummerEngine

Wire a rigged, animated character end to end — inspect real clips and bones, locomotion state machine, method-track events, poses, blend shapes, root motion.

No reviews yet
0 installs
5 views
0.0% view→install

Install

$ agentstack add skill-summerengine-summer-character-animation-wiring

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-summerengine-summer-character-animation-wiring)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
12d ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Character Animation Wiring? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Character Animation Wiring — Rigged GLB to Living Character

The game-critical path: a rigged, animated character lands in the scene — from summer_generate_3d({options:{rig:true}}) + summer_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_scriptanim_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):

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 (":/position|rotation|scale")
    # and blend shapes ("blend_shapes/")

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:

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:

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

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

    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/" property paths (value tracks, weights 0..1) against the MeshInstance3D that owns the shapes — names come from Step 1's report:

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

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, verbatim the calls that matter:

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: — exactly the nodes you meant (AnimationTree added, nothing vanished).
  4. summer_save_scene, then behavior: summer_playsummer_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.

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.