Skip to content
Back to skills

Procedural Animation

ASecurity

Runtime bone changes on top of clips: head look-at, foot IK, hand grabs a prop, lean, recoil.

  • 77 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 16, 2026
ai-agentsgoshellreactnodeapiperformance

Works with

  • claude code
  • cursor
  • cli
  • api
  • mcp

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned October 16, 2026

npx -y skills add SummerEngine/summer --skill procedural-animation --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Procedural Animation?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Procedural Animation
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/summerengine-procedural-animation-summer/badge)](https://www.skillsdirectory.com/skills/summerengine-procedural-animation-summer)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: procedural-animation
description: "Runtime bone changes on top of clips: head look-at, foot IK, hand grabs a prop, lean, recoil."
license: MIT
compatibility: [Cursor, Claude Code, Windsurf, Codex]
category: animation
user-invocable: false
allowed-tools: Read Grep Edit Write summer_get_scene_tree summer_inspect_node summer_inspect_resource summer_run_script summer_add_node summer_set_prop summer_set_resource_property summer_save_scene summer_get_script_errors
paths: ["**/*.gd", "**/*.tscn", "**/*.tres"]
---

# Procedural Animation — Bones Driven by Code

Generated clips cover most of a character's motion. The rest — the eye contact when an NPC speaks, feet that plant on slopes, hands that wrap a sword grip, the lean into a sprint, the recoil from a hit on the left shoulder — is procedural. In Summer Engine this is done via the `SkeletonModifier3D` family attached to the `Skeleton3D`, plus additive blend layers on the `AnimationTree`. None of it is generative; this skill is a set of recipes.

Limit: Summer Engine's current IK is good enough for look-at, foot-snap,
and simple two-bone arm/leg chains. It is not a full-body IK system with weight
redistribution. For combat-grappling or contact-heavy interactions, hand-author
the contact frame and accept some clipping.

## When to use this skill

- Feet clip into / float over uneven terrain.
- NPC head doesn't track the player during conversation — feels dead-eyed.
- Character's hand doesn't actually hold the sword — pen-through.
- Body doesn't lean into turns / aiming.
- Hit reactions need to come from the hit *direction* (recoil left when struck left), not just one canned animation.
- Ragdoll on death.

## When NOT to use this skill

- The clip already covers it. Don't IK what mocap already nailed — adds CPU cost and rounding error.
- The character is far from the camera (LOD2+). Procedural cost outpaces visual benefit. Disable modifiers past 15m.
- You need facial animation. That's `facial-and-lipsync`.
- You haven't generated base clips yet. Skeleton modifiers compose ON TOP of clips; without a base pose they'll stack on T-pose and look wrong.

## The three patterns

Almost every procedural request maps to one of these three. Pick the right one before writing code.

### Pattern A — SkeletonModifier3D (post-clip bone overrides)

Attached as a child of `Skeleton3D`. Runs after the AnimationTree writes the pose, modifies specific bones in-place. Includes `LookAtModifier3D` and `SkeletonIK3D`. Cheap, deterministic, no animation graph dependency.

### Pattern B — Additive AnimationNodeAdd2 layer in the tree

A clip recorded as a *delta from base pose* (e.g., a "lean left" clip that's 0 at center, fully leaned at +1) added on top of locomotion. Driven by writing `parameters/Add/blend_amount` from script. Use for stylistic overlays — turning lean, aiming offset, breathing.

### Pattern C — PhysicalBoneSimulator3D (ragdoll)

The skeleton hands off to physics on death. Bones become `PhysicalBone3D` rigid bodies that fall under gravity and collide with the world. One-way: once ragdoll fires, you can't easily blend back to clip-driven without snapping.

## Pattern A recipes

### A1 — Head look-at the player

The most common NPC liveliness fix.

**ctx lane (animation-tier engines):** one `summer_run_script` call creates the owned modifier and sets the props — prefer it over the 10-call CRUD chain below:

```gdscript
func run(ctx):
    var skel = ctx.find("NPC").find_children("*", "Skeleton3D", true, false)[0]
    var mod = ctx.look_at_modifier(skel, ctx.find("Player"), {
        "bone_name": "Head", "forward_axis": 2, "primary_rotation_axis": 1,
        "use_secondary_rotation": true, "use_angle_limitation": true,
        "symmetry_limitation": true, "primary_limit_angle": 1.4, "secondary_limit_angle": 1.0,
    })
```

Unknown prop names land in the `prop_warnings` report entry instead of failing silently — read it. On an older engine the helper is a plain script error; fall back to plain GDScript in the same tool (confirm the bone names first with `summer_inspect_node`):

```
summer_run_script(scenePath="res://main.tscn", source="""
func run(ctx):
    var skel = ctx.find("NPC").find_children("*", "Skeleton3D", true, false)[0]
    var look = LookAtModifier3D.new()
    look.name = "HeadLookAt"
    look.bone_name = "Head"
    look.forward_axis = 2                 # +Z forward (Meshy default)
    look.primary_rotation_axis = 1        # Y-up
    look.use_secondary_rotation = true
    look.use_angle_limitation = true      # OFF by default: nothing clamps until you set this
    look.symmetry_limitation = true       # one angle drives both directions
    look.primary_limit_angle = 1.4        # ~80 degrees, prevents the Exorcist
    look.secondary_limit_angle = 1.0
    skel.add_child(look)
    ctx.set_owner_recursive(look)
    ctx.save_scene()
""")
```

There is no `symmetry_limit` and no `secondary_rotation_axis` property on
`LookAtModifier3D` — the real names are the ones above, and the secondary
rotation axis is derived, not set. Confirmed against the shipped 4.6.1 binary;
`ClassDB.class_get_property_list("LookAtModifier3D")` is the check if you're
unsure of a name.

Drive the target each frame:

```gdscript
@onready var head_look: LookAtModifier3D = $Skeleton3D/HeadLookAt

func _process(_delta: float) -> void:
    var player := get_tree().get_first_node_in_group("player")
    if player == null:
        head_look.influence = 0.0
        return
    var to_player := player.global_position - global_position
    var dist := to_player.length()
    # Disable look-at past 8m — feels uncanny at long range.
    head_look.target_node = player.get_path() if dist < 8.0 else NodePath("")
    head_look.influence = clamp(1.0 - (dist - 6.0) / 2.0, 0.0, 1.0)   # fade out 6→8m
```

`use_angle_limitation` is the key setting, and it defaults to **false** — a stock `LookAtModifier3D` does not clamp at all, so the head can rotate 180° and the model looks possessed. Turn it on and set `primary_limit_angle` to ~80° (1.4 rad), which matches a human's neck-only range; for a "whole-body turn" use it with the spine chain (next recipe). `primary_limit_angle` defaults to 2π, i.e. unlimited.

### A2 — Spine + head chain (look-at with body turn)

Multiple `LookAtModifier3D` nodes on the same chain — one for `Spine1` (`primary_limit_angle` 0.4 rad), one for `Spine2` (0.4 rad), one for `Head` (1.0 rad), each with `use_angle_limitation = true`. Total reach: ~110°, distributed naturally. Without the spine contribution, big angles look like the head detaches.

### A3 — Foot IK (slopes & uneven terrain)

The clip has the foot at Y=0; on a slope the ground is at Y=0.15. Without IK,
the foot floats. Use `SkeletonIK3D` on each two-bone leg chain:

```gdscript
@onready var skel: Skeleton3D = $Skeleton3D
@onready var ik_left: SkeletonIK3D = $Skeleton3D/IKLeft
@onready var ik_right: SkeletonIK3D = $Skeleton3D/IKRight
@onready var ray: RayCast3D = $FootRay   # cast straight down

func _physics_process(_delta: float) -> void:
    _solve_foot(ik_left, "LeftFoot")
    _solve_foot(ik_right, "RightFoot")

func _solve_foot(ik: SkeletonIK3D, bone_name: String) -> void:
    var bone_idx := skel.find_bone(bone_name)
    var foot_world := skel.get_bone_global_pose(bone_idx).origin
    ray.global_position = foot_world + Vector3.UP * 0.5
    ray.target_position = Vector3.DOWN * 1.0
    ray.force_raycast_update()
    if ray.is_colliding():
        var ground := ray.get_collision_point()
        if ground.y > foot_world.y - 0.05:   # only lift, never drop into the ground
            ik.target_node = NodePath("../IKTarget" + bone_name)
            (get_node("Skeleton3D/IKTarget" + bone_name) as Node3D).global_position = ground + Vector3.UP * 0.05
            ik.start()
        else:
            ik.stop()
    else:
        ik.stop()
```

The 0.5m up-offset on the ray prevents self-occlusion. The "only lift, never drop" rule matters: letting IK push feet into the ground breaks knee bends.

Limits: this works for stairs and gentle slopes. On 45°+ slopes, use a pelvis lowering pass first (drop the root by `min(left_offset, right_offset)`) so the body squats and the legs don't over-extend.

### A4 — Hand grab on prop (sword grip, rifle stock)

`SkeletonIK3D` on the arm chain (Shoulder → UpperArm → LowerArm → Hand), with `target_node` set to a Marker3D parented to the prop. The clip drives the body; IK pins the hand to the grip. For a rifle held two-handed, do the right hand from clip and IK the left hand to a foregrip Marker3D — locks them together regardless of recoil.

## Pattern B — Additive layer

In the AnimationTree, wrap Locomotion in an `AnimationNodeAdd2`:

```
[parent: AnimationNodeStateMachine.Locomotion]
  AnimationNodeAdd2
    in: BlendSpace1D (idle/walk/run)   # the base
    add: AnimationNodeAnimation         # an additive clip — must be authored as delta
```

Drive `parameters/Locomotion/Add/blend_amount` (0..1) from script. Use cases: turn-lean (lean amount = clamp(turn_rate, -1, 1)), aim offset (lean while aiming up/down), breathing-while-idle. The lean clip must be authored as a delta from the base pose, or the pose is applied twice.

## Pattern C — Ragdoll on death

```gdscript
@onready var sim: PhysicalBoneSimulator3D = $Skeleton3D/PhysicalBoneSimulator

func die(impulse_world: Vector3) -> void:
    $AnimationTree.active = false
    sim.physical_bones_start_simulation()
    var pelvis := $Skeleton3D/PhysicalBoneSimulator/PelvisPhysicalBone as PhysicalBone3D
    pelvis.apply_central_impulse(impulse_world * 4.0)
```

You need PhysicalBone3D children matching every major bone (set up once via the Skeleton3D context menu → "Create Physical Skeleton"). The impulse parameter sells the death — without directional impulse the body just collapses straight down.

## Confirmation gates

- **Before adding modifiers:** state which bones, which axes. Bone-name typos are a top failure.
- **Before driving from script:** confirm influence will fade out at distance and the target node is correct.
- **Before enabling ragdoll:** confirm physical bones exist; without them, simulation is a no-op.

## Reference card

### Bone names (Meshy humanoid skeleton, exact strings)

`Hips`, `Spine`, `Spine1`, `Spine2`, `Neck`, `Head`, `LeftShoulder`, `LeftArm`, `LeftForeArm`, `LeftHand`, `RightShoulder`, `RightArm`, `RightForeArm`, `RightHand`, `LeftUpLeg`, `LeftLeg`, `LeftFoot`, `LeftToeBase`, `RightUpLeg`, `RightLeg`, `RightFoot`, `RightToeBase`. (Meshy uses Mixamo-compatible naming with no `mixamorig:` prefix.)

### Axis conventions

| Setting | Value (Meshy default) |
|---|---|
| Forward axis (head/spine) | Z+ (`forward_axis`) |
| Up axis | Y+ |
| Right axis | X+ |
| Foot down | Y- |

### Influence fade-out distances (tuned defaults)

| Modifier | Disable past |
|---|---|
| Head look-at | 8m |
| Foot IK | 15m (snap-to-ground only matters when player can see) |
| Hand-on-prop IK | always on (cheap, breaks if disabled) |
| Additive lean | always on |
| Ragdoll | one-shot, never disabled |

### Pitfalls

- **LookAt rotates wildly / no clamp at all.** `use_angle_limitation` is false by default and `primary_limit_angle` defaults to 2π. Set both. ~80° (1.4 rad) for a head-only modifier; ~25° (0.4 rad) for a single spine bone.
- **IK pops on the first frame.** Solver hasn't been initialized. Call `ik.start()` after the AnimationTree's first tick, not in `_ready()` — the bone pose is identity until the tree runs.
- **Foot IK pushes the body up on stairs but the camera doesn't follow.** Camera is parented to the root, not the head. Either parent the camera to a chest bone via `BoneAttachment3D`, or accept that the camera doesn't bob with foot IK.
- **Additive layer doubles the clip.** The "additive" clip wasn't authored as a delta from the base pose. Re-author it as a delta.
- **Ragdoll just collapses, no spread.** Missing per-bone impulse + missing collision shapes on hands/feet PhysicalBone3D children. Auto-generate via Skeleton3D inspector → "Create Physical Skeleton" with the "Add collisions" option.
- **Modifiers run before the AnimationTree, not after.** Modifier order is set by tree-position in the scene. SkeletonModifier3D nodes must be CHILDREN of Skeleton3D and appear AFTER any nodes that drive the pose. AnimationTree itself doesn't sit under Skeleton3D — it sits at the character root and writes into Skeleton3D's pose every frame.
- **Look-at influence flickers at fade boundary.** Use `lerp(current_influence, target, 5 * delta)` instead of an instant assignment. Avoids the sub-pixel oscillation when the player walks the threshold.

### Performance budget

- Each `LookAtModifier3D`: ~5 µs/frame. Cheap; have many.
- Each `SkeletonIK3D` two-bone solve: ~30 µs/frame. ~30 active solves per frame on mid-range desktop is fine; budget more conservatively on Steam Deck.
- Ragdoll with full physical skeleton: ~200 µs/frame *while simulating*. Cap concurrent ragdolls at 5–8 before culling oldest.

## Anti-patterns

- Writing `skel.set_bone_pose_position(...)` in `_process`. Bypasses the AnimationTree, fights it next frame, results in jitter. Use modifiers instead; they run after the tree. (Edit-time STILL poses — a corpse, a statue — are the exception: no tree is running, so `ctx.bone_pose(skel, bone, {position/rotation/scale})` on animation-tier engines, or `set_bone_pose_*` in a one-off `summer_run_script`, is exactly right. See `character-animation-wiring`.)
- Putting IK targets in worldspace and forgetting they don't follow the character. Parent IK targets under the character root or bone — IK target is in the modifier's local space.
- Procedural look-at without a fade-out at distance. Distant NPCs all snap to player every frame, looks like a hivemind.
- Foot IK on flying / floating characters. Disable when `is_on_floor() == false`.

## Edge cases

- **Character is mounted (riding a horse).** Disable foot IK on the rider; ride animation already sells the contact. Enable upper-body additive sway driven by horse acceleration.
- **First-person hands.** Same modifier set, but only the arm chain is visible — disable head/spine modifiers, keep hand-on-prop IK.
- **Character is a quadruped.** All four legs need foot IK. The bone names are different (`FrontLeftLeg`, `BackRightLeg`, etc.); inspect first.
- **Network-synced ragdoll.** Don't simulate it on every client and don't sync bones with `MultiplayerSynchronizer`. In a Summer multiplayer game, send the few values that drive the ragdoll (the impulse, the moment it starts) as State or an Event and let each client simulate locally; see `multiplayer-state`.

## Fallback (no MCP)

Add modifiers in Summer Engine under Skeleton3D and set bone names from the
Inspector dropdown, which autocompletes from the rig. Hand-write the GDScript
driving them. Foot raycasts can be set up visually with a RayCast3D child.

## Handoff

- For the end-to-end rigged-character wiring these layers sit on top of (locomotion, method-track events, root motion), `character-animation-wiring`.
- For the AnimationTree these procedural layers compose with, `animation-tree`.
- For the source clips that procedural overlays modify, `generate-motion`.
- For face / lipsync (a different modifier family — BlendShapes, not bones), `facial-and-lipsync`.
- For NPC behavior triggering ragdoll on death, `design-npc`.
- For first-person aim sway / ADS, `fps-controller`.

## See also

- Upstream API reference matching the current Summer technical base:
  `SkeletonModifier3D`, `LookAtModifier3D`, `SkeletonIK3D`,
  `PhysicalBoneSimulator3D`.

<!-- shell-commands: generated from the operation catalog by npm run generate:registry; do not edit by hand -->
## 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@4.3.0` 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_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_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` |
<!-- /shell-commands -->

Files in this skill

  • SKILL.md16.4 KB
  • resource.yaml881 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.