Animation Systems — Deep Engineering Guide
Animation makes characters feel alive. This skill covers the skeleton/skinning pipeline, animation state machines and blending, root motion, IK, retargeting and the performance budgets that keep hundreds of characters animating on 16 ms budgets.
1. The Animation Pipeline
AnimClip (per-bone keyframes)
→ AnimInstance (time, pose)
→ blending (state machine, layers)
→ skeleton-relative posed bones
→ skinning (GPU: vertex skin via bone matrices)
→ render
The hot path is per-bone matrix generation + skinning — the two budget walls for crowds (see §8).
2. Skeleton & Bone Hierarchy
struct Bone { int parent; Mat4 bindPoseInv; /* inverse bind */ const char* name; };
struct Skeleton { vector<Bone> bones; /* sorted so parent < child */ };
- Bones store inverse bind pose (bindPoseInv): skinning needs model→bone in the bind pose (constant per skeleton).
- Precompute per-bone
bindPoseInv * worldParent can vectorize.
- Bone order: sorted parents-first so the loop is sequential (cache-friendly; see job/memory skills).
3. The Animation Clip & Compressed Storage
| Representation |
Size/frame |
Quality |
| Raw (Vec3 quats float) |
~ 52 B/bone |
exact |
| Compressed quats (16-bit + smallest-three) |
~ 10 B/bone |
great |
| Keyframe-Lerp (only on change) |
keys compressed |
tuned |
- Store local pose (per-bone, parent-relative) — the evaluator blends local, then multiplies down the chain once.
- Interleave: a clip = array of bone channels; load once to cache.
- Keyframe dedup: stationary bones (fingers) hold 1–2 keys; explode memory by 10×.
- 16-bit compression (quantized quats) is the standard; S18Q8-style for gross.
- Deterministic: same clip + same t → same pose (see
compression ref).
4. Blending & The State Machine
4.1 The AnimStateMachine (ASM)
states: Idle, Walk, Run, Jump, Attack_A...
transitions: {from, to, triggers, blendTime, condition}
- Blend parameters (e.g., MoveSpeed → Walk/Run weight) or trigger transitions (Attack).
- Blend atomic poses, not clips: a "Walk" state is a pose (after clips/layers), blended toward Run via
lerp(pose) or quat nlerp.
- Transition evaluator:
blend w ramps 0→1 over blendTime (frame-rate independent — state.time/transition.duration).
4.2 Layer Mixing (Upper Body)
base layer: whole-body locomotion
masked layer: upper-body (aim, pickup, minor)
combine: mix per-bone by mask weights (blend-by-mask)
The mask = per-bone weights (torso 1.0, hips 0.2...) — cache offsets.
5. Root Motion & Movement
- Root bone = the actor transform. Root motion = the clip's translation/rotation on the root:
- extracted, applied to the actor position, or
- ignored (locomotion only; velocity comes from the controller).
- Extract carefully: a walk clip with root displacement = 0.8 m/tick; the animator drives the placement.
- Blend root motion through the same blend weights (else foot-slide).
6. IK (Inverse Kinematics)
| IK use |
Method |
Cost |
| Foot placement |
2-bone (leg) analytic |
cheap |
| Head/hand target |
2-bone + reach |
cheap |
| Full-body align (grippers) |
CCD/FABRIK |
heavier |
- Solve after the ASM, on a few bones; keep the rest from ASM.
- Foot IK: sample ground height under the ankle, rotate the leg→IK chain, blend 0–50%.
- Never run full-body IK per character when not needed (only interacting/grounded-anim).
7. Retargeting (One Skeleton, Many Rigs)
- The state machine + clips authored on a reference skeleton; each character maps bones by semantic name (hips, spine_01...).
- Inverse retarget (author once, apply to all): a mapper table
{referenceBone → actorBone, weight} + scale handling.
- Root/bone lengths differ: retarget applies local pose, not world (keep proportions in the rigs).
8. Performance Budgeting (The Crowd Wall)
Per-character cost budget (skinned):
| Metric |
Target/char (100 chars) |
| CPU pose eval (LOD0) |
~0.1–0.3 ms |
| Skinning GPU |
~0.05–0.2 ms |
| skin vertices |
3–8k quads LOD0 |
| animation memory (decompressed) |
LOD0 ~1 MB/char-anim-set |
- LOD: distant characters → half poses (24 bones) → single-skin → no-ASM (a "idle texture" / impostor).
- Skin on GPU (vertex shader matrices), pose on CPU (cheap per-bone); lifting skinned verts to GPU = your budget lever.
- Histogram "bones skinned / second" in profiler is the first tuning hand crank.
9. Determinism (Netcode & Replay)
- The ASM must be deterministic:
state.time advances by fixed or sim-driven dt; blends use the same polygon (no wall-clock).
- Serialize the state (currentState, time, parameters), not the pose — replay reconstructs.
10. References
references/skeleton-and-skinning.md — bone chain, bind pose, local evaluation, GPU skin matrix math
references/animation-blending.md — blend spaces, masks, state machine transitions, continuous blends
references/clip-compression.md — keyframe dedup, quantization, streaming clips, deterministic decode
references/root-motion-and-ik.md — root extraction, foot IK, 2-bone/CCD solving, budgets
references/retargeting.md — bone maps, scale handles, pose-local rules, merchant/furry-safe rules
references/performance-budgeting.md — bone counts, LOD ladder, impostors, profiler histogram
1---2name: animation-systems3description: Expert character animation systems — skeletons, skinning, state machines, blending, root motion, IK, retargeting and animation performance budgeting.4---56# Animation Systems — Deep Engineering Guide78Animation makes characters feel alive. This skill covers the skeleton/skinning pipeline, animation state machines and blending, root motion, IK, retargeting and the performance budgets that keep hundreds of characters animating on 16 ms budgets.910## 1. The Animation Pipeline1112```13AnimClip (per-bone keyframes)14 → AnimInstance (time, pose)15 → blending (state machine, layers)16 → skeleton-relative posed bones17 → skinning (GPU: vertex skin via bone matrices)18 → render19```2021The hot path is **per-bone matrix generation + skinning** — the two budget walls for crowds (see §8).2223## 2. Skeleton & Bone Hierarchy2425```cpp26struct Bone { int parent; Mat4 bindPoseInv; /* inverse bind */ const char* name; };27struct Skeleton { vector<Bone> bones; /* sorted so parent < child */ };28```29- Bones store **inverse bind pose** (bindPoseInv): skinning needs model→bone *in the bind pose* (constant per skeleton).30- Precompute per-bone `bindPoseInv * worldParent` can vectorize.31- Bone order: sorted parents-first so the loop is sequential (cache-friendly; see job/memory skills).3233## 3. The Animation Clip & Compressed Storage3435| Representation | Size/frame | Quality |36|----------------|-----------|---------|37| Raw (Vec3 quats float) | ~ 52 B/bone | exact |38| Compressed quats (16-bit + smallest-three) | ~ 10 B/bone | great |39| Keyframe-Lerp (only on change) | keys compressed | tuned |4041- Store *local* pose (per-bone, parent-relative) — the evaluator blends local, then multiplies down the chain once.42- Interleave: a clip = array of bone channels; load once to cache.43- **Keyframe dedup**: stationary bones (fingers) hold 1–2 keys; explode memory by 10×.44- 16-bit compression (quantized quats) is the standard; S18Q8-style for gross.45- Deterministic: same clip + same t → same pose (see `compression` ref).4647## 4. Blending & The State Machine4849### 4.1 The AnimStateMachine (ASM)5051```52states: Idle, Walk, Run, Jump, Attack_A...53transitions: {from, to, triggers, blendTime, condition}54```5556- Blend *parameters* (e.g., MoveSpeed → Walk/Run weight) or *trigger* transitions (Attack).57- Blend **atomic poses**, not clips: a "Walk" state is a *pose* (after clips/layers), blended toward Run via `lerp(pose)` or `quat nlerp`.58- Transition evaluator: `blend w` ramps 0→1 over `blendTime` (frame-rate independent — `state.time/transition.duration`).5960### 4.2 Layer Mixing (Upper Body)6162```63base layer: whole-body locomotion64masked layer: upper-body (aim, pickup, minor)65combine: mix per-bone by mask weights (blend-by-mask)66```67The mask = per-bone weights (torso 1.0, hips 0.2...) — cache offsets.6869## 5. Root Motion & Movement7071- Root bone = the actor transform. **Root motion** = the clip's translation/rotation on the root:72 - extracted, applied to the actor position, or73 - ignored (locomotion only; velocity comes from the controller).74- Extract carefully: a walk clip with root displacement = 0.8 m/tick; the animator drives the placement.75- Blend root motion through the same blend weights (else foot-slide).7677## 6. IK (Inverse Kinematics)7879| IK use | Method | Cost |80|--------|--------|------|81| Foot placement | 2-bone (leg) analytic | cheap |82| Head/hand target | 2-bone + reach | cheap |83| Full-body align (grippers) | CCD/FABRIK | heavier |8485- Solve *after* the ASM, on a few bones; keep the rest from ASM.86- Foot IK: sample ground height under the ankle, rotate the leg→IK chain, blend 0–50%.87- Never run full-body IK per character when not needed (only interacting/grounded-anim).8889## 7. Retargeting (One Skeleton, Many Rigs)9091- The state machine + clips authored on a *reference* skeleton; each character maps *bones by semantic name* (hips, spine_01...).92- **Inverse retarget** (author once, apply to all): a mapper table `{referenceBone → actorBone, weight}` + scale handling.93- Root/bone *lengths* differ: retarget applies local pose, not world (keep proportions in the rigs).9495## 8. Performance Budgeting (The Crowd Wall)9697Per-character cost budget (skinned):9899| Metric | Target/char (100 chars) |100|--------|-------------------------|101| CPU pose eval (LOD0) | ~0.1–0.3 ms |102| Skinning GPU | ~0.05–0.2 ms |103| skin vertices | 3–8k quads LOD0 |104| animation memory (decompressed) | LOD0 ~1 MB/char-anim-set |105106- **LOD**: distant characters → half poses (24 bones) → single-skin → no-ASM (a "idle texture" / impostor).107- Skin on GPU (vertex shader matrices), pose on CPU (cheap per-bone); lifting skinned verts to GPU = your budget lever.108- Histogram "bones skinned / second" in profiler is the first tuning hand crank.109110## 9. Determinism (Netcode & Replay)111112- The ASM must be **deterministic**: `state.time` advances by fixed or sim-driven dt; blends use the same polygon (no wall-clock).113- Serialize the *state* (currentState, time, parameters), not the pose — replay reconstructs.114115## 10. References116117- `references/skeleton-and-skinning.md` — bone chain, bind pose, local evaluation, GPU skin matrix math118- `references/animation-blending.md` — blend spaces, masks, state machine transitions, continuous blends119- `references/clip-compression.md` — keyframe dedup, quantization, streaming clips, deterministic decode120- `references/root-motion-and-ik.md` — root extraction, foot IK, 2-bone/CCD solving, budgets121- `references/retargeting.md` — bone maps, scale handles, pose-local rules, merchant/furry-safe rules122- `references/performance-budgeting.md` — bone counts, LOD ladder, impostors, profiler histogram