---
title: "Animation (Mecanim)"
slug: /workflows/animation/
markdown: /workflows/animation.md
section: art-audio-animation
tier: 2
summary: "Clip timing, Write Defaults, bindings that die silently, IK and rigs."
sources: ['unity/animation.md']
canonical: https://unityworkflowlibrary.dev/workflows/animation/
read_first: /read-first.md
---

> Read first, on every prompt: [non-destructive rules](/read-first.md).

# Animation (Mecanim)

> Clip timing, Write Defaults, bindings that die silently, IK and rigs.

Non-DOTS animation: what a clip's length actually means on screen, Write Defaults, controller bindings that die silently, and particle effects that never age past frame 0.

- **ANIMATION-BAKE-PRESERVES-EULER-ORDER ** - Parts separating during
  a baked animation can be a rotation-decoding fault even when every position
  key matches. Preserve the source Euler rotation order when sampling; XYZ
  curves are not equivalent to `Quaternion.Euler`'s ZXY composition. In Unity
  6000.5, copying serialized `m_EulerCurves[].curve.m_RotationOrder` alone was
  not durable: a later unrelated `AnimationUtility.SetEditorCurve` reset it.
  Bake evaluated orientations as quaternion tracks with continuous signs,
  including constant Idle/rest clips, and remove competing Euler tracks.
  Compare native `AnimationClip.SampleAnimation` poses and rigid mesh points
  in their parent part's frame, both at keys and between them; resampling
  still has interpolation error. Recheck dependent grips and sight calibration
  in Play. Observed: wrong order displaced a rigid part 23-27 cm and rotated
  Idle by 5 degrees; native comparisons exposed both while scalar-key checks
  passed. The reference evaluator must not reuse the bake's decoder.

- **Write Defaults ON is often the ONLY writer of a prefab's authored default — flipping it OFF
  permanently breaks the pose, and disabling the Animator freezes it mid-blend.** Two paired Mecanim
  footguns that both look like free perf wins on an idle Animator: (1) an "empty" default state
  (`m_Motion: {fileID: 0}`) with `m_WriteDefaultValues: 1` is what writes the prefab's authored
  values back every frame — if a clip animates `m_LocalScale` to 0.477 but the prefab default is
  0.7, WD:1 is the only thing restoring 0.7, so WD:0 leaves every instance permanently shrunk after
  the first play. Before flipping WD, diff the binding SETS of every clip in the layer: any binding
  present in one clip and absent from another is a value that will stick. (2) `SetActive(false)` /
  `animator.enabled = false` on a controller whose prefab has `m_WriteDefaultValuesOnDisable: 0`
  freezes the current pose instead of resetting it — gate it from an `OnStateExit` and you freeze
  mid-cross-fade (exit time is a fraction of the clip, not the end), leaving half-faded graphics
  rasterising forever. Also: **`m_CullingMode` is inert on a Renderer-less hierarchy** — Animator
  culling keys off `Renderer` visibility, so a Canvas/UI-only object always animates regardless.
  And triggers set on a disabled Animator are silently lost, so any gating needs the enable to
  precede the `SetTrigger`. (Verified on a mobile card game `Pass-Animation.prefab:278/:288` +
  `SeatPass_CTRL.controller:204` while auditing an iPad frame-rate drop — all three "obvious"
  optimisations on five idle per-seat Animators were net-negative.)

- **ANIMFBX-TAKE-SWAP-UNBINDS-CONTROLLER — overwriting an animation FBX with a DIFFERENT source
  take silently NULLs every AnimatorController state that referenced it.** A clip is a sub-asset
  whose identity follows the imported TAKE, not the file path or the clip's display name, so
  re-importing the same path with a new take (a different mocap action, a re-export from another
  source clip) leaves `state.motion == null` on every state — with **no error, no warning, no
  missing-reference marker** — and the unit just stands in bind pose at play. Re-buying/re-exporting
  the SAME take masks the trap entirely (bindings survive), which is why it surfaces only on the
  swap. After ANY animation-source swap: re-point the controller states in the same slice and
  assert each `state.motion != null` programmatically — do not eyeball the Animator window, an
  empty motion slot reads as an ordinary state. Same class as the missing-script stub trap
  (the assets serialization page): the
  data is intact, the REFERENCE is what died. (a mobile strategy game, swapping a demon's clip set
  to different library actions.)

- **ANIM-RETIME-CLIP — "make the clip longer" is three edits, and two of them are invisible if you
  skip them.** Retiming an `.anim` by hand (scaling every `time:` so the clip fills a target
  duration) is only correct if you also:
  (1) **divide `inSlope`/`outSlope` by the same factor** — tangents are value-units per *second*,
  so leaving them alone silently distorts every ease and overshoot even though the keys land where
  you meant; `inWeight`/`outWeight` are normalised fractions of their segment and must **not** be
  touched; and
  (2) **apply the identical transform to `m_EditorCurves` / `m_EulerEditorCurves`** — those mirror
  the runtime curves for the Animation window, so skipping them makes the editor draw something
  different from what plays.
  Snap scaled times back onto the clip's own `m_SampleRate` grid (`round(t*fps)/fps`) or the keys
  land off-frame and the clip becomes miserable to hand-edit later; stretching (factor > 1) can
  never collide two on-grid keys, shrinking can, so assert it.

- **…and clip length is NOT on-screen duration.** A state whose transition has `m_HasExitTime: 1`
  with `m_ExitTime < 1` starts blending out at that *fraction* of the clip, so a longer clip is
  cut proportionally longer and the authored tail (a pop-out, a settle) never fully plays —
  lengthening the clip alone changes nothing visible. Fix the clip and the `m_ExitTime` together.
  Corollary: raising `m_ExitTime` to 1 on a clip with `m_LoopTime: 1` makes it **wrap and replay
  its intro** underneath the exit blend — clear `m_LoopTime` on one-shots so the last frame holds
  instead. Measure duration as `clipLength * exitTime + transitionDuration`, never `clip.length`.
  (a mobile card game, stretching five emote clips to 3s: the exit transitions were firing at
  77–89%, so the retime would have been entirely invisible on its own.)

- **ONE-SHOTS-ARE-EDGE-TRIGGERS — a one-shot effect fires once, on the rising edge of a replicated flag or
  counter, with the baseline primed on first observation.** Source: learned in shipped-project VFX work (networked one-shot effects); the rule applies to any one-shot effect or event, not only VFX. Status: locked (empirical). Fire one-shot effects (an
  impact burst, a per-tick damage puff, a pickup or cure flash) once, when a replicated flag goes from false to
  true or a counter increments, and record the current value as the baseline the first time the object is observed,
  so late joiners and respawned or pooled objects do not replay old bursts. Ongoing states (burning, stunned, a zone
  active) are plain replicated bools or 0..1 values that drive looping visuals, never one-shots. Why: a client that
  compares against a default baseline sees every existing flag or counter as "just changed" when it first spawns the
  object, and a one-shot driven by a level instead of an edge repeats while the level holds. Check: a late-joining
  client and a pool-returned object show no stray burst; holding the state for 10 s plays exactly one burst. Pairs
  with PARTICLE-SHOW-IS-EDGE-TRIGGERED below, the local half of the same rule.

- **PARTICLE-SHOW-IS-EDGE-TRIGGERED — a per-frame "show this effect here" path calls only the
  MOVE, never the RESTART; `Clear+Play` every frame pins every particle at age 0, and any
  size/color-over-lifetime curve starting at 0 then renders it permanently INVISIBLE.** The
  shape that burns: a hover/aim/preview follower whose update does
  `fx.transform.position = p; fx.Play;` (or a presenter method that internally does
  `system.Clear(true); system.Play(true)`), because "make sure it's playing" reads harmless.
  It is not — the effect is re-seeded every frame, so its particles never age past frame 0.
  With a Size-over-Lifetime curve that ramps 0 → 1 over the first ~10% of life (the norm on
  vendor ground rings, shockwaves, decals) the particle is drawn at scale 0 and the effect is
  simply never visible; with a fade-in alpha ramp it is fully transparent. Worse on a
  **single-burst** emitter (rateOverTime 0 + one burst at t=0, the standard authoring for a
  ground ring): there is exactly one particle to kill, so the whole effect vanishes rather
  than flickering — and it looks identical to "the prefab isn't wired", sending you to hunt a
  null ref that does not exist. Correct shape: split MOVE from START. Per frame set the
  transform/scale/radius only; call the restart on the **hidden → shown edge**, and again only
  when a property that is applied AT EMIT changes (`main.startColor`, `startSize` — an
  already-alive particle never picks those up, which is why a green/red validity tint DOES
  need a restart while a position change does not). Track the shown/variant state in the
  caller that owns visibility. Diagnose by reading the emitter: `rateOverTime == 0` +
  `m_Bursts` `cycleCount: 1` means one particle, and a `SizeModule`/`ColorModule` curve whose
  `time: 0` key has `value: 0` proves an age-0 particle cannot render. (Seen in a mobile strategy game: the Fire Rain aim ring never appeared — `ShowAimAt` called `presenter.Play`
  every Update while the ring was one burst particle with size 0 → 1 over the first 11%.)

- **PARTICLE-SIMULATE-PAUSES-THE-CLOCK .** `ParticleSystem.Simulate` advances particles
  and then pauses the system. A tail/`IsAlive` test that waits afterward can diagnose its own
  frozen fixture as a pool leak. Prefer actual Play-frame aging, or explicitly resume playback
  after fast-forwarding; compare against an independent instance with the same authored settings.
  Record `time`, `isPaused`, `isPlaying`, emission and particle counts as well as `IsAlive(true)`.
  Measured: both tested and reference systems held zero particles, `IsAlive=true`, `isPaused=true`
  at time 2 for 37 seconds. A real-clock replacement completed ten variants over 202 frames
  with 12,636 checks; that result does not establish offscreen-culling behavior.

- **PARTICLE-VELOCITY-AXES-SHARE-CURVE-MODE .** Author compatible MinMaxCurve modes on all enabled velocity-over-lifetime axes, including zero axes. If the varying axis uses two constants, use explicit two-constant zero on the other axes. Fix both the producer and saved output, read back that exact enabled module, then verify ordinary Play has no velocity-mode errors. Measured: x/y/z modes 0/3/0 produced errors; 3/3/3 retained upward motion for 42 seconds with zero errors. This observation concerns velocity over lifetime, not every particle module.

- **EYE-FOLLOW-PASSES-THE-BREATH .** A first-person camera that rides the animated eye
  height through a settle filter tuned against the walk bob lets the idle breath straight through:
  the breath is the SLOW signal and a low-pass keeps slow signals. Measured: a 0.35 s settle took a
  1.9-3.2 cm walk/run bob at 2.5-3.3 Hz down to under 0.4 cm, but passed a 2.5 cm, 2.7 s idle breath
  at about 2 cm. Put a still band (hysteresis dead zone) BEFORE the settle, sized to half the idle's
  peak-to-peak: 1.5 cm for camera height; 3.5 cm for any body-under-the-lens slide, because the same
  breath swayed the head 6 cm fore and aft and slid the feet to follow it. Test both halves: a
  sine breath inside the band moves the eye under 1 mm over 10 s, and a 40 cm crouch still arrives
  within the band. Size the settle test to the settle: at 0.35 s the crouch is 94% there at 1 s.
  (Amended 0.5.71: build the band smooth and the settle as a spring - see EYE-FOLLOW-IS-A-SPRING-NOT-A-CLAMP.)

- **EYE-FOLLOW-IS-A-SPRING-NOT-A-CLAMP .** A first-person lens that followed the animated eyes
  through a hard still band, a first-order ease and a hard lag clamp felt "rigid and jumpy" in Play: each
  one is a velocity step. The band releases a step when the gap leaves it, and the clamp snaps the lens
  onto the eyes' speed when it engages. Measured with the shipped function at 144 fps: a 30 cm landing
  drop gave 324 m/s2 and a 50 cm animated stance change 274 m/s2, while a steady run was already smooth
  (1.7 m/s2). The jerk is in the transitions, so a gait-only test misses it. Fix: a smooth band
  (`gap - band * tanh(gap / band)`) into an exact critically damped step (stable at 10 fps), with the
  frequency raised by `1 + (drop / dropLag)^2` only while the eyes are below the lens, so a drop cannot show
  the neck. Same inputs gave 190 and 100 m/s2 (a rise 4.8) with the same run smoothing. Test the
  transitions against the old function called directly, not a hand copy of it.

- **CAMERA-OFFSET-LEADS-THE-WEAPON-TARGET .** A first-person weapon target is placed BEFORE the
  animation evaluates and reads last frame's lens offset, while the camera takes this frame's offset
  after. Any camera-only motion added there (walk bob, landing dip) therefore swims the gun on screen by one
  frame's change: 0.09 cm a frame at 144 fps and 0.21 cm at 60 fps for a 1.2 cm sprint bob. Step authored
  offsets before the weapon target is placed and add this frame's change to it, so gun and camera move
  together. Only the part that needs the evaluated pose (following the animated eyes) may lag a frame;
  measure that lag per frame. It reached 2.4 cm a frame on a hard landing at 144 fps and 5.6 cm at 60 fps.

- **REVERSED-CLIP-IS-THE-OPPOSITE-DIRECTION .** A missing locomotion direction is filled by
  the opposite direction's clip played backwards, not by blending its neighbours. Blending is the
  trap it looks like the cheap fix: two perpendicular strides mixed at 45 degrees move the planted
  foot at about cos(45) = 71% of ground speed, so the feet skate, and the blend also inherits any
  crossing step in the neighbour clips - shipped once, the developer's verdict was that the corners were
  worse than the crossed originals they replaced. A run reversed keeps its own uncrossed legs and
  its foot-plant timing, and the human gait is near enough symmetric that it reads as the opposite
  corner: measured, the reversed forward corners travelled within 5 degrees of the exact opposite
  heading at the same speed, crossed in 0 of 120 frames, and their planted foot slid 0.35-0.39 of
  ground speed against 0.32 for the forward corner the developer had approved. It does not fix a SIDE
  direction: a side clip reversed is still the same side.
  Reverse by rebuilding the curves, not by a negative speed on a state (a blend tree child cannot
  hold one, and the asset stays honest): for each `AnimationUtility.GetCurveBindings` curve, walk
  the keys backwards into `new Keyframe(length - t, value, -outTangent, -inTangent, outWeight,
  inWeight)` with `weightedMode` In and Out swapped, then copy the clip settings over so loop time
  and cycle offset survive. Bake it to a project `.anim` (a gitignored vendor clip cannot be a
  binding) and give the authoring surface the button that does it, so the next direction costs a
  click. Verify a filled direction by driving the view without Play: crossed frames, foot gap and
  planted-foot slide as a share of ground speed, read against the direction the developer already
  approved - the slide metric carries a baseline, so only the comparison means anything.

- **SQUARE-HIPS-CAN-STILL-CROSS-THE-FEET (second half superseded by).** Hips square to the look do not make a strafe
  clip usable: a commercial pack's strafe-run back corners kept the hips within 10 degrees yet held
  the right foot left of the left for 26 of 49 samples, by up to 41 cm. Measure the mean of
  (rightFoot - leftFoot) along the hip axis over one cycle, in hip widths (divide the dot by the hip
  vector's squared length): 13 clean walk/jog clips read 1.2 to 2.7, the crossing ones below 0.
  Make the controller builder refuse a walk or jog under 0.5, next to its hips-turn guard. Measure in
  edit mode with `clip.SampleAnimation`; a PlayableGraph evaluated outside Play leaves every bone
  still. Do NOT leave the direction unbound for the tree to blend - see REVERSED-CLIP-IS-THE-
  OPPOSITE-DIRECTION, which replaces that half of this rule.

- **SUB-EMITTER-PLAYS-ON-ITS-OWN .** A sub-emitter is still a ParticleSystem with its own
  Play On Awake and Looping. With both on it was found playing by itself while its parent read
  stopped, firing its own burst every `duration` at the sub-emitter's origin with no parent particle
  to trigger it. Author sub-emitters with Play On Awake off, and never instantiate a system you do not
  play: a vendor weather module in GPU-graph mode still instantiated its legacy particle rain and
  stopped it. Check `isPlaying` on every sub-emitter, not only the parent. Measured: parent
  `isStopped`, `time` 0; its collision sub-emitter `isPlaying`, `time` advancing, one 0.1 m particle
  per 0.6 s at its origin 25 m over the camera - a pulsing dot straight overhead that follows the player.

- **MECANIM-OVERRIDE-LAYER-WD — an EMPTY full-weight override layer with Write Defaults ON writes
  the default pose over the base and freezes the whole rig** (sibling of ANIM-WRITE-DEFAULTS, the
  opposite direction). One locomotion driver per Animator — two stack silently. `CullUpdateTransforms`
  (the correct default) does not pose off-screen bones, so verify that the composited pose CHANGES,
  not that parameters are live — a handoff "passed" on live params while nothing moved. Discarded
  root motion + the default humanoid root reference gives a rigid stride-frequency counter-sway:
  Bake Into Pose / Based Upon Original. (a survival game)

- **ANIM-HUMANOID-FINGER-BINDINGS — `HumanTrait.MuscleName` display names are not always clip binding names.**
  Unity Humanoid finger channels such as `Right Index 1 Stretched` and `Right Index Spread`
  serialize as `RightHand.Index.1 Stretched` and `RightHand.Index.Spread` (with the analogous
  `LeftHand` forms). When baking curves, map only the 40 finger channels to the serialized
  hand/finger paths and preserve the other muscle names. Verify the resulting bone pose or
  serialized bindings; a human-motion flag or valid Avatar does not prove the fingers are
  driving the intended bones. (Observed against a valid Unity Humanoid clip; final geometry
  acceptance remains a separate verification.)

- **ANIM-FIRSTPERSON-HEAD-COVERAGE — first-person head visibility must cover every renderer type.**
  A visibility or preview pass that handles only `SkinnedMeshRenderer` leaves rigid head
  attachments or descendants visible, while a skinned-only inventory preview omits rigid
  wearables. Traverse the authored head hierarchy and inventory attachments for both
  `SkinnedMeshRenderer` and `MeshRenderer`/rigid renderer descendants, then verify the actual
  camera image and geometry; source coverage alone does not establish a clean view.

- **POOLED-PARTICLE-SCALE-INCLUDES-DESCENDANTS — when a pooled effect's root owns its overall
  scale, author every descendant ParticleSystem with `main.scalingMode = Hierarchy`.** `Local`
  ignores ancestor scale; `Shape` scales the emitter shape without scaling particle size/motion.
  Include inactive children and sub-emitters in both desktop/mobile variants. Preserve authored
  child transforms; multiplying `startSize` as well creates a second scale owner. Playback must
  forward the authored scale to the pooled root instead of resetting it to 1. Verify two materially
  different root scales using live particles or rendered extents, not a Transform read-back:
  a root at 0.1 can still emit full-size child shards.

- **ANIM-MANUAL-GRAPH-OWNS-INPUT .** When manually evaluating a vendor animation
  graph, advance its dependent input interpolation exactly once before evaluation. FPSAF
  UserInputController.SetValue can write an interpolation target without changing the
  consumed float until Update runs. EditMode/manual probes must reproduce that scheduling;
  otherwise an apparent pose-layer or mask defect may be a stale input value.

- **ANIM-OPTIC-FIT-INCLUDES-REACH .** Align the physical sight's position and rotation,
  not an assumed forward axis, and test authored eye relief against real limb reach in every
  stance and perspective. Correct ADS math can still leave the support grip unreachable.
  Verify actual camera clearance too. If the graph overwrites an Animator-root offset, use
  an authored parent outside the Animator hierarchy and compensate the same translation in
  the aim target; never move authoritative collision or aim merely to fit a presentation mesh.
  Check the resulting feet/body placement, reload release/reattachment and graph reactivation.

- **ANIM-CONTROLLER-ARTIFACT-OUTLIVES-ITS-YAML .** An AnimatorController is TWO things:
  the editor graph (states, blend trees, motion references) and a compiled blob the runtime
  actually plays. They can disagree. A branch switch or an interrupted import can leave a stale
  compiled artifact while the `.controller` file on disk stays byte-correct, so every review that
  reads the YAML, walks `layer.stateMachine.states`, or resolves `blendTree.children[i].motion`
  reports a perfectly healthy controller — and the character still animates from nothing.
  The symptom is a pose that is CONSTANT across every state: with no clip playing, a humanoid
  falls to the avatar's zero pose, which puts the hips near the animator origin instead of at
  hip height. Measured on a 1.9 m body: hips 1.07 m low, feet 0.77 m below the character root,
  head down at chest height, while the model's rest pose and every clip's `RootT.y` (0.92–0.96)
  were correct. It reads as a sunk or mangled model, so it is misdiagnosed as a rig, avatar,
  mesh or IK defect.
  Detect it with ONE number: `controller.animationClips.Length` against the clips reachable by
  walking the editor graph. Equal is healthy; fewer is a stale artifact (measured: 3 vs 22).
  Bisect before blaming the rig — swap controllers across two rigs. A controller that breaks
  every rig is the defect; a rig that breaks under every controller is the rig. Here the
  suspected rig animated correctly under a sibling controller, which cleared it in one step.
  `ImportAsset` with `ForceUpdate` does NOT repair it — the source hash is unchanged, so the bad
  artifact is reused. Force a real rebuild: re-assign each blend tree's `children` to itself,
  `SetDirty`, `SaveAssets`, then reimport. The fix writes NOTHING to the asset file, so there is
  no diff to commit and no way to spot it in review — say so in the report, and prefer
  regenerating the controller through its builder when one exists.

## EYE-FRAME-RIDES-THE-PELVIS-NOT-THE-HEAD

A first-person eye is usually a socket at a fixed height above the feet, and the weapon and its
hand IK are posed in that eye's frame. The animated skeleton does not hold still under it. A walk
cycle drops the whole torso, so the gun stays at the authored height while the shoulders leave,
and the arms stretch upward to keep hold of it. Measured on one humanoid: pelvis resting at
0.874 m fell to a 0.612-0.747 m band while walking, a 0.19 m mean drop plus a 0.135 m cyclic bob,
against an aim target that held its authored height to 0.000 m. The eye also sat 0.118 m above
the avatar's own eye bones, so the gun rode at skull-crown height in every pose.

It reads as an IK, grip or arm-length defect because the arms are what visibly break. The measurement
that names it is the aim target's vertical RANGE over a walk cycle: exactly zero while the body's is
not is the whole finding.

**Follow the PELVIS, never the head or neck.** A look layer distributes pitch across spine, neck and
head, so those bones move with the camera and following them feeds the view back into itself. Measured
across a 160 degree pitch sweep on a still body: pelvis 1.1 mm, chest 17 mm, shoulder 180 mm, neck
210 mm, head 290 mm. The pelvis carries the full locomotion bob and none of the look.

Take the offset against a rest height LEARNED WHERE THE BODY IS STILL, per stance. A stance's own
authored eye height already carries the difference between standing and crouching; measuring against
one global rest double-counts it and drives the eye through the floor (measured: crouch eye 0.950
authored, 0.372 delivered). Gate the whole thing on third person - a first-person eye IS the frame the
weapon is authored in, and it must not chase the body it is looking out of.

## POST-GRAPH-SKELETON-SURGERY-UNDOES-IN-GRAPH-IK

A pose that is finished by moving bones AFTER the PlayableGraph has evaluated silently breaks every
IK the graph just solved. The classic pair is a foot-placement solver that gives the pelvis back
some height so an unreachable leg can reach, and a weapon-grip hand IK that runs as the graph's last
layer. The hand IK seats both hands on a weapon anchored to the CAMERA; the pelvis move then carries
the whole upper body — shoulders, elbows, hands — off a gun that never moved.

**The measurement that names it is a dot product, not an eyeball.** Both hands are displaced by one
IDENTICAL vector, and that vector is the negative of the post-graph offset:
`dot(normalize(handError), normalize(pelvisOffset)) == -1.000`, magnitudes equal to four decimals.
Identical on both sides is the whole finding — it rules out grips, arm length, clip content and the
IK solver itself, all of which would differ per hand. Measured on one humanoid: 0.070 / 0.106 /
0.186 m of hand error at 0 / 2.5 / 5 m/s, each equal to that frame's pelvis drop.

It reads as a one-hand problem because the support hand on a fore-end shows a shift that the grip
hand hides, so "the left hand is broken" is the usual report. Believe the numbers, not the side.

**Two fixes, and the tempting one is wrong.** Pre-compensating the in-graph IK target by the offset
needs a PER-BODY channel, and a layer settings object is a shared asset — a per-character value
written there leaks across every body using that weapon. Instead let whatever performs the surgery
own its own consequence: capture each end effector's world pose BEFORE the move, and put it back
after. Re-solve with the limb's OWN CURRENT BEND as the hint (`middle.position - upper.position`) so
the joint keeps the plane the animation chose; a hint copied from the leg solver inverts elbows,
because knees and elbows fold opposite ways. Two-bone IK rotates only upper and middle, so the end
bone inherits a changed rotation — restore the captured rotation explicitly or the hand twists on
the grip.

Gate the re-seat on a non-zero offset so a still body's arms are never touched, and put the drag
distance on the authoring readout: a number that tracks the pelvis offset is the regression test.

## LEG-HINT-NEVER-POINTS-BEHIND-THE-TOES

The own-bend hint above (`middle.position - upper.position`) is safe for a hand re-seated a few
centimetres. It is NOT safe for a leg whose target moves ahead of the knee line. A two-bone solver
projects the hint onto the plane perpendicular to hip->target; raise a heel-strike foot onto a step
with the hips dropped and that projection points BEHIND the leg, so the knee folds backwards for the
frame the foot lands. Measured on a 30 cm stair flight: one backward knee per step-up, worst 0.25 m
behind the hip-ankle line. Build the hint from the animated bend perpendicular to hip->ankle, remove
any part of it that points behind the sole's forward direction, then add a small lean toward the toes
(10% of the thigh length was enough). Same measurement after: 1 frame in 342, 0.03 m. The regression
test is one leg with a target ahead of the knee, asserting the knee ends ahead of the hip->foot line.

A second cause hid behind it: feet planted on the plane the character capsule rides. When stairs are
ramp colliders for the capsule, that plane runs through the nosings, lifting the lead foot by up to one
riser and tilting it with the slope. Probe each foot's own ground (heel and toe rays through a filter
that excludes the ramp, keep the higher hit) and let the capsule keep the ramp.

## SOLE-HEADING-COMES-FROM-THE-TOE-BONE

A foot solver that stores each sole's forward in ankle space, measured as the body's forward on the saved
prefab pose, inherits that pose. Save a character with a foot splayed and the heading is off by the splay:
measured on four humanoid rigs, right soles sat 52-92 degrees and left soles 6-44 degrees off the toe bone.
Every reader of the heading then fails quietly. The knee hint leaning toward the toes pulled both knees
2.5-4.7 cm inward at idle and up to 8 cm in a walk, while the ankle spacing (owned by target placement)
stayed the clip's, so it read as a narrow animation or an IK tolerance. Measure the heading toward the foot's
own toe bone, projected on the body up. Ship a validator that reports each body's error in degrees and a
repair that re-measures past a small tolerance (3 degrees), and test a rig whose saved pose splays one foot.
Triage: measure the stance with the solver on and off; ankles equal and knees not points at the hint.

## BONE-NAMES-MATCH-ON-THE-SUFFIX-NEVER-ON-A-SUBSTRING

Auto-wiring a rig by searching bone names for a keyword produces false positives that look like
data. Two measured on one vendor pack of nine quadruped rigs:

- `Contains("ear")` matched every `L Forearm` and `R Forearm`, reporting ear bones on species that
  have none.
- Rigs prefix every bone with the species name, so on the bear rig - `BEAR_ Pelvis`, `BEAR_ Spine` -
  the same search matched all 38 bones. The count read as a richly rigged head and was noise.
- `Contains("tail")` matched `Ponytail1`, inflating tail chains by one on several species.

Match on the segment after the last separator, case-insensitively, against an exact expected name or
an expected-name-plus-index. Vendor packs built on one skeleton template share a suffix convention -
`Pelvis`, `Spine`/`SpineN`, `Neck`/`NeckN`, `Head`, `Tail`/`TailN`, `L|R Clavicle -> UpperArm ->
Forearm -> Hand`, `L|R Thigh -> Calf -> Foot` - behind a per-asset prefix, so suffix matching
auto-wires the whole pack while substring matching silently invents chains.

Verify a chain exists before building a feature on it. A feature authored against bones the models
do not have ships as a tuning surface that changes nothing.

## QUADRUPED-HIND-LEGS-ARE-THREE-SEGMENT

A digitigrade hind leg is `Thigh -> Calf -> hock -> Foot`, four joints and three segments, and
vendor rigs name the hock explicitly (`HorseLink` in 3ds Max Biped exports). A two-bone IK solver -
the one a biped character already has - bends such a leg backwards at the hock.

Plantigrade species in the same pack (bear, rabbit measured) carry no hock and DO solve with two
bones, so a pack is mixed and the solver must branch per species rather than per pack. Budget foot
planting on a quadruped separately from spine, neck and tail work: those are chain slerps driven by
values the client already has, while feet need a different solver and correct per-species bindings,
where a wrong binding reads as a broken leg rather than as nothing happening.

## POST-ANIMATOR-BONE-OFFSET-NEEDS-A-VISIBILITY-GATE

A procedural layer that finishes a pose by multiplying an offset onto bone rotations AFTER the
Animator has evaluated is correct only while the Animator keeps rewriting those bones. The clip
writes absolute local rotations every frame, so `bone.rotation = offset * bone.rotation` is wiped
and re-applied cleanly - until the Animator stops writing, and then the SAME offset compounds onto
its own output every frame until the body folds in on itself.

`AnimatorCullingMode.CullUpdateTransforms` - the correct default for a crowd, because it keeps the
state machine running off-screen so a queued trigger still plays - is exactly the mode that stops the
transform writes. **Gate the offset on the same question the culling asked: renderer visibility.**
Any renderer visible means the Animator posed the skeleton this frame; none visible means it did not,
and the offset must not be applied at all. `Animator` exposes no "did I write this frame" flag, so
`Renderer.isVisible` is the test, and it is the same one culling keys off.

The failure is silent and slow: it needs the object to leave the camera, so it never reproduces in
the inspector on a selected object, and the body is already deformed by the time anything looks.

**A test harness reproduces it by accident.** Calling the apply path N times inside one frame to
"let the easing settle" is N multiplications with no Animator pass between them - measured 139
degrees of spine rotation from a solver whose authored cap was 14. One call per Animator evaluation
is the contract; to measure a settled pose, call once with a large delta time, and save and restore
the whole chain's rotations around each probe so successive probes are independent.

**A clamp reads as a dead feature.** A look-at with a yaw limit returns exactly zero for a target
past that limit - deliberately, because a head that strains past its own shoulder is worse than one
that lets go. A probe that happens to place the target just outside the limit (measured: 63.4 degrees
against a 62 degree limit) gets 0.0 on every side and looks precisely like a layer that was never
wired. Log the limit alongside the result, and probe inside it before concluding anything.

- **BLENDTREE-2D-DOES-NOT-NORMALISE-ITS-AXES — a Freeform Cartesian / Freeform Directional tree
  weighs children by plain euclidean distance in raw parameter units, so two axes with different
  magnitudes silently collapse into the larger one.** The shape that burns: a locomotion tree whose
  X is a yaw rate in degrees per second (spanning ±45) and whose Y is a ground speed in metres per
  second (spanning 0–2). Distance is then ~95% X, the speed axis is very nearly ignored, and the
  fastest gait blends into the slowest turn — the animal plays a walking corner at a gallop. Nothing
  warns: the tree is valid, every child is reachable, the inspector's diamond preview *looks* right
  because the editor draws the two axes at equal screen width, which is precisely the lie. Any two
  quantities with different units are suspect — degrees vs metres, a normalised −1..1 against a raw
  speed, a health fraction against a timer in seconds. **Put both axes in the same units before
  placing a child**: scale one so its full-scale value sits as far from the origin as the other's
  does, and feed the tree a SECOND parameter carrying that scaled coordinate rather than rescaling
  the first — the original stays readable in the Animator window and usable by state transitions,
  which need a real threshold, not a blend coordinate. Also: `useAutomaticThresholds` is 1D-only and
  is inert here, so it never rescues the placement. Sanity-check by asking what a child at
  (fullAxisX, 0) and a child at (0, fullAxisY) are worth at the sample halfway between them — if it
  is not close to an even split, the axes are not commensurate. (Found adding a turn axis
  to a quadruped locomotion tree, before it ever ran.)

- **MECANIM-ANDS-CONDITIONS-SO-AN-OR-IS-N-TRANSITIONS.** Every condition inside one
  `AnimatorStateTransition` is ANDed; there is no OR operator anywhere in the UI or the API. "Leave
  Idle when the body is moving **or** turning" is therefore two transitions out of Idle with one
  condition each, while the return edge — "settle only when it is neither" — is a single transition
  carrying all of them, since that direction genuinely is an AND. Getting this backwards yields a
  state you can enter and never leave, or one that leaves on the first condition alone. Build the
  truth table before writing the transitions; the asymmetry between the two directions is the part
  that is easy to miss when the forward edge is written first and the return edge copied from it.

- **PRESENTATION-SPRING-STEPS-EXACTLY - a per-frame Euler spring's swing depends on the frame rate.** A hit
  flinch built as `velocity += (-k*angle - c*velocity)*dt; angle += velocity*dt`, kicked with a velocity sized to
  peak at an authored angle, peaked at about half that angle at 40 fps (damping 22 x 0.025 s removed 55% of the
  kick on the first step), about 70% at 60 fps and near full only at high rates, so a body flinched harder on a
  faster PC. Step a presentation spring with its exact transition over the frame: for unit mass, stiffness k,
  damping c, with d = c/2 and f = sqrt(k - d^2), `x' = e^(-d t)[x (C + d S) + v S]`,
  `v' = e^(-d t)[v (C - d S) - k x S]` where C = cos(f t), S = sin(f t)/f (cosh/sinh over f when overdamped, 1 and
  t at critical). Solve the four coefficients once per frame per (k, c) and apply them to every spring sharing
  them: no allocation, no substep loop. Measured after: the same hit peaked at 10.69 / 10.92 / 10.92 degrees at
  30 / 60 / 120 fps (the 30 fps frame lands beside the peak). Prove it by pinning `Time.captureFramerate` (the frame
  time the code steps) alongside `Application.targetFrameRate`; a slow Editor renders its own rate regardless. A
  screenshot taken at the peak stalls that frame, so read spring peaks without captures.

## MEASURE-THE-HIPS-TURN-BEFORE-MASKING-THE-HIPS-OFF

An upper-body layer that leaves the hips on locomotion (so the walk and the feet survive) plays only
the clip's spine and arm motion RELATIVE to the hips. An attack whose power comes from turning the
whole body keeps none of that turn. Measured on a commercial pack's heavy sword combo: the hips heading
swept 360 deg from windup to hit. With the hips held, the weapon dipped 0.95 m under the eye and then
snapped 0.67 m back up in one 33 ms key. An overhead attack from the same pack (hips within 56 deg)
never moved more than 0.29 m per key. A strike window picked as "the fastest stretch of the baked path"
chose the snap, not the cut, and the server's hit landed 0.3 s early.

- Before choosing a clip for a hips-masked layer, sample the hips heading (from the thigh line) across
  it. A span past ~90 deg in the windup means the motion lives in the turn: pick another clip or keep
  the hips.
- A path baked from a clip with authored phase ranges (windup, hit) takes its strike window from those
  ranges, never from speed.

## EDIT-MODE-GRAPH-POSES-IN-A-PREVIEW-SCENE

Sampling a humanoid clip outside Play through a manual PlayableGraph (`DirectorUpdateMode.Manual`,
`SetTime`, `Evaluate(0f)`) posed nothing on an instance hidden in the open scene: every frame read the
bind pose, with no error. The same graph posed every bone once the instance lived in a preview scene
(`EditorSceneManager.NewPreviewScene`) with `animator.cullingMode = AnimatorCullingMode.AlwaysAnimate`.
Measured on 45 commercial-pack clips x 53 bones at 30 Hz.

- Sample in a preview scene with culling off, and close the scene in a `finally`.
- Before trusting a sample set, check that two frames differ. Identical frames mean the rig was never
  evaluated, not that the clip is still.
- If a graph evaluated outside Play leaves every bone still (SQUARE-HIPS-CAN-STILL-CROSS-THE-FEET, this
  file), check the instance's scene and culling mode before falling back to `clip.SampleAnimation`.

## MECANIM-STOPPED-HOLD-OFFSET

A "hold the last frame" state (the death clip again at speed 0) showed the clip's FIRST frame: dead
animals stood back up once the death clip ended. `AnimatorState.cycleOffset` does not apply to a
non-looping clip, so the stopped copy sat at normalized 0. Measured on three quadruped death clips:
bone mean height 0.594 m (standing) in the hold before the fix, 0.057-0.143 m (lying) after.

- Place the stopped copy with `AnimatorStateTransition.offset` (0.99) on the transition into it, and
  enter it from code with `Animator.Play(hash, layer, 0.99f)`. One constant feeds both.
- Hold at 0.99, never 1.0: normalized 1.0 wraps a LOOPING clip back to frame 0, and one of the three
  death clips was imported looping.
- Assert the entry offset in the controller bake or validator (reject below 0.9), so a rebuild cannot
  drop it silently.
- `SetActive(false -> true)` restarts the Animator in its default state with default parameters. A view
  that sends parameters only on change never re-sends them: reset its cache in `OnEnable` and jump back
  to the hold. The hold must survive a hide/show in the live check, not only the first entry.
- Measure the symptom: log state, normalized time and bone mean height through clip end and a reshow.
  A state name reading "Hold" proves nothing about the pose.

Latios MecanimV2 has its own hold rule (MECANIM-HOLD, the latios anim page); this one is Unity's
built-in Animator.

## POSE-SPIKE-IS-LOCATED-AT-RENDER-TIME

A one-frame hand pop on a stance change read 27 cm in bones sampled from `EditorApplication.update`, which
could be sampling order rather than the drawn pose. Two steps turn a spike into a located link:

- Sample the same row at `RenderPipelineManager.beginCameraRendering` for the gameplay camera, once per
  frame, and unsubscribe at the end. When the update-time and render-time rows agree, the pop is on screen.
- A hand position in camera space cannot tell a root or capsule snap from an IK miss. Log the root, the eye
  socket, the camera and the chest world positions, plus `Animator.IsInTransition(0)`, on the same drawn
  frame. Measured: the hand stepped 44 cm while root, camera and chest moved under 5 cm, on the base
  layer's first crossfade frame, so the step was in the hand target chain (weapon bone or aim target),
  not in the feet. An observed copy showed the same step one frame later.

Then log each link of the hand chain (IK target, the bone it rides, that bone's own target) in camera space
before changing code.

## ACTION-FADE-SWINGS-THE-ELBOW-POLE

A support arm snapped back at the end of a reload although both hand tracks ended exactly on the hold grip. The
hand stayed on the gun (0 cm in gun space) through the fade; the elbow did not. An aim layer blended its elbow pole
targets by the action weight, so the 0.08 s action fade swung the elbow 20-29 cm (rifle) and 4-11 cm (pistol) onto
the hold's IK pole in about five frames.

- Measure the elbow (every joint a pole or hint drives), not only the hand, across an action's blend-out.
- List every layer that reads the action weight; the hand track is only one of them.
- Size the blend-out per weapon to that swing (here 0.40 s rifle, 0.25 s pistol) and keep the old value as the
  default for weapons nobody asked about.

## PROP-IS-A-BONE-WITHOUT-A-RENDERER

A loose round floated 35 cm under a rifle in every phase and no renderer or bounds audit found it: the round is a
bone of the gun's skinned mesh with no Renderer of its own, and the gun renderer's bounds were unusable
(158 x 18 x 259 m). First-person packs also park hand-carried props at rest away from the gun, and an Idle or Fire
clip built from a reload's first frame inherits that pose.

- Audit props per clip by bone: scale and position against the gun body through the clip, then capture at the bone.
- A prop the clip never carries into the gun is hidden in every frame (scale 0 for the whole clip); a carried prop
  shows only inside its in-hand window.
- Lock it with a test that samples the scale curve through every clip copied from the reload's first frame.

## LAYER-FLAG-SWITCHES-ITS-INPUT

While aiming, the hands turned and the chest followed about 0.8 s later (slow 30 deg turn, seconds to 90 %: gun
1.88, chest 2.70). It looked like network lag. Cause: the look layer's `useTurnOffset` flag switches its yaw source
from the mouse input to a turn-offset input, and the driver wrote 0 there, so the spine never yawed and only the
body's turn-in-place deadband moved the chest. With the flag off in memory the chest matched the gun (1.88 s).

- Before blaming the network or a solver, read which input property each layer flag selects and log what the
  driver writes into it.
- Fix at the writer: feed the owed twist into the selected input, scaled by the state that wants it (aim weight),
  and keep the flag for the states that want the delay.
- Measure seconds to 90 % per link (camera, gun, chest, root, remote copy) on a scripted turn below and above the
  deadband.

## FULL-BODY-FP-HAND-SITS-AT-ARM-REACH

Every first-person tool carry that showed the fist straightened the arm: elbow 173 deg with the grip 35 cm ahead of
the eye, 150 deg at 33 cm. Relaxing the elbow dropped the hand out of the frame. A full-body rig seen from its own
eye keeps the shoulder about 19 cm behind and 28 cm under the eye, so at a gameplay FOV of 50 a hand in view sits at
the arm's full reach. At that FOV a visible fist also pushed a 0.45 m tool's head off the top (head viewport y 1.02).

- Pick one: move the grip up the haft so the fist and the head both fit (IK-TARGET-IN-VIEW-IS-NOT-THE-HAND-IN-VIEW); keep the fist
  just under the frame corner with the head in view (grip viewport y -0.34, head 0.65-0.78), which a developer holding
  a reference with the fist in view rejected; or draw the arms with their own view-model FOV.
- Log the elbow bend and the hand's viewport position with each carry trial; a hand on screen with the elbow over
  165 deg is this, not a bad pose.

## POSE-FROM-A-REFERENCE-FRAME

Two rounds of first-person carry trials were rejected  Each rest was designed from direction vectors and elbow
angles with nothing to match, so every trial optimised a proxy. Laying the haft into the screen or turning the edge
forward read as "sideways". The first pass built against a reference frame matched it on two tools and was written.

- Before posing, pull an idle frame of the game the developer names. With no browser, a video's storyboard works: the
  watch page's `playerStoryboardSpecRenderer` spec gives sprite sheets (level L3 = 320x180 frames, 3x3 per sheet)
  that `curl` fetches. Image search and wiki fetches returned paywalls or unrelated hits.
- Read three numbers off the frame: the head's and the grip's viewport positions and the blade's direction. Build
  the rest from directions, never from hand-typed Euler angles: `rotation = LookRotation(edge projected off the
  haft, haft)`, `position = grip point - rotation * grip offset`.
- Judge by captures side by side with the frame, not by a logged point. A tool's configured reach point can sit
  past its visible head (0.65 m against about 0.4 m), which moved a correct placement.
- Before trialling, confirm the field moves the pose: a clip-driven carry ignores rest fields, and four identical
  captures are that, not a bad value.

## IK-TARGET-IN-VIEW-IS-NOT-THE-HAND-IN-VIEW

A developer rejected a first-person tool rest that kept the fist just under the frame corner, with a
reference frame showing the fist bottom right. The trial logs had judged the fist by the grip target, and that is a
poor proxy: the wrist is solved toward a target, and a target past arm reach, or an arm another layer still owns,
leaves the hand short while the tool stays at the target. Measured: target viewport y 0.04, hand bone -0.35.

- Log the hand bone's viewport (`Animator.GetBoneTransform(HumanBodyBones.RightHand)`) beside the target's with
  every trial, and judge the fist at the knuckle midpoint, not the bone (HAND-BONE-IS-THE-WRIST).
- To fit the fist and the tool's head in one frame at a narrow FOV, move the grip offset up the haft (0.12-0.20 m
  on a 0.45-0.6 m tool) and keep the target inside reach: hand viewport (0.93-0.98, -0.06 to 0.01) with the head
  top right.
- If the hand bone does not move while the target does, first compute the IK wrist target and its distance from
  the shoulder: a target past reach looks the same. Only a bone short of an in-reach target means another layer
  owns the arm; read that layer's weight before switching it off ( corrected a layer blamed at weight 0).

## HAND-BONE-IS-THE-WRIST

A handoff blamed an animation layer for a first-person hand that "never reached the grip" and blocked a pose trial on
it. Replaying the logged poses offline showed the layer at weight 0: the computed wrist target met the
logged hand bone within 0.7-2.6 cm wherever it was inside reach, and missed by 8-10 cm only where it sat past the arm.
The humanoid hand bone is the wrist; with a knuckles-up grip it sits 5-7 cm under the visible fist.

- Judge the fist at the midpoint of the index and little proximal bones, never at `HumanBodyBones.RightHand`.
- Before naming a cause for a hand short of its grip, compute the IK wrist target from the logged pose (grip point
  plus the palm-frame wrist offset) and log shoulder-to-target against the arm's length (upper + lower bone).
- A layer is the cause only when the bone misses an in-reach target; read its weight, do not infer it.

## FIT-REACH-AND-VIEW-TOGETHER

A first-person melee rest and swing were authored 0.08-0.19 m past arm reach, so the elbow locked straight and the
developer called the arms stiff. Pulling the pivot into reach with the tilt kept put the fist below the
frame and the head above it (viewport y -0.05 / 1.01): at 0.3 m from the eye a 50 deg vertical view spans 0.28 m,
the same as the fist-to-head length of a hand tool.

- Keep every key's wrist target within about 0.50 m of the shoulder (arm about 0.52 m), including the interpolated
  path between keys, and check the whole swing, not only the rest.
- Fit the pivot together with a lean of the haft into depth (20-37 deg measured) so the head recedes into the frame;
  sliding the pivot alone cannot hold both ends in view.
- Fit with a constrained optimizer (SLSQP with the reach and in-front limits as inequalities, multi-started over the
  lean). Nelder-Mead on a quadratic reach penalty stalled on the penalty wall and returned the start pose.
- If the melee reach is derived from the posed strike, an in-reach strike shortens it (0.2-0.35 m measured): decide
  the reach explicitly before writing the keys, since it changes a server outcome.

## ACTION-FADE-RUNS-ON-PRESENTATION-TIME

A server-timed reload's arm pose jumped in at its start: action weight 0 to .36-1.0 in one frame, support elbow 7-29 cm,
on all 24 captured reload starts. The vendor mixer computes its fade-in as (clip time - start time) / blend-in on the
clip's own clock, and the driver slaved that clock to the server's reload progress, which held for 2-3 frames and then
jumped 0.13-0.2 s. The fade was over before it began.

- A clip whose time follows a network or simulation clock gets its weight envelope on presentation time: count elapsed
  frame seconds from the play call and write the weight onto the newest mixer input playing that clip.
- Give the entry the same seconds as the exit, and log the action weight per frame at the action's first 5 frames.

## WEIGHT-GATED-DELTA-IS-ZERO-AT-THE-GATE

Every gun stepped 0.6-2.5 cm on the frame its reload ended, after a smooth blend-out. The aim layer applied the action's
weapon delta whole while the action weight was relevant and dropped it at the relevance threshold, and that delta also
held the hold pose's drift since it was captured.

- Anything switched by a weight-relevance gate must already be near zero at the gate: scale it by the weight
  (`Lerp(identity, delta, weight)`), never add it whole behind the gate.
- Check by logging the driven bone's per-frame step across the frame the weight crosses the threshold.

## FP-CLIP-HAND-CUTS-OFF-SCREEN

On a full-body first-person rig the support hand teleported 51 cm in one frame during a reload, for the developer and for
observers. The vendor's first-person clip takes that hand out of the camera frame for a speedloader and cuts it back
in two samples; an arms-only view model never shows the cut, a full body does. The loose prop teleported 120 cm too.

- When baking a vendor first-person clip onto a full body, scan every hand track for steps over 8 cm per 60 Hz sample
  and bridge each with an eased reach of about 0.25 s ending at the cut's far side.
- Keep the loose prop hidden until the hand arrives, and lock the bake with a test on the maximum per-sample step.

## SIMULATE-SEEKS-PARTICLES-NOT-SCRIPTS

- `ParticleSystem.Simulate(t)` advances particles only. A light, material or transform animated by a script on the
  effect (an effect pack's light-animation component) stays at its t = 0 value, so a seeked still shows the burst
  without its light. Seek the script's own clock too (its `Animate(t)`, by reflection if it is not public), and read
  PARTICLE-SIMULATE-PAUSES-THE-CLOCK above before timing anything after a `Simulate`.
- The same scripts explain lights saved at intensity 0 that look dead: the script ramps them after every `OnEnable`,
  and a pooled view that toggles `SetActive` re-runs it on each play. Judge a light by what drives it (a retained
  script field that references it, or an Animator), never by its serialized value; see
  DRIVEN-VALUE-IS-NOT-THE-SOURCE-OF-TRUTH in the scene flow page. A converter that strips "dead" lights tests
  for a driver first.
- Stills without a live event: instantiate the view as a `DontSave` scratch object in Edit mode, `Play`,
  `Simulate(t)` the root systems, seek the scripts, and render a scratch camera. Author in Edit mode: an editor
  authoring tool that refuses Play mode can still save a source edit before its check throws.

## SHAKE-IS-VERIFIED-BY-POSE-LOG

- Stills cannot show camera motion. Verify a shake or kick numerically: add a listener after the camera owner's (for
  Cinemachine, `CinemachineCore.CameraUpdatedEvent`), log camera pose and `Time.timeScale` every frame, and remove it
  before stopping Play. Pass: the authored amplitude and decay, then a return to the exact base pose, which proves
  nothing accumulates.

## CONTACT-LANDING-RESPECTS-ARM-REACH

A first-person chop landed onto the aimed surface by turning the posed strike about the eye and sliding it along the
look ray up to a landing shift. That moved the IK pivot with no regard for the shoulder: on a surface 0.8-1.2 m away
the wrist target sat 0.78-1.19 m from the shoulder (arm about 0.52 m), and live the elbow stayed locked at 173 deg
through the whole swing, for old and newly fitted keys alike.

- Bound any contact landing by the arm's reach. A surface past the landed head takes the hit at the natural impact
  with no slide out; only a nearer surface pulls the strike back.
- Never raise the landing shift to buy reach; a longer slide stretches the arm further.
- Gate: log shoulder-to-wrist at the contact frame beside the arm length, not only the head's reach.

## FIT-THE-EDGE-NOT-ONLY-THE-VIEW

The developer reported every held melee tool "rotated the wrong way completely": at a wall the haft stood
upright and the top of the head met it, not the edge. The key fit had scored only fist/head viewport and wrist reach,
so the facing was free, and the optimizer pointed the strike's haft straight ahead with the edge down (a chop into a
log on the floor). The landing then moved only the head's position, never its edge.

- Constrain the strike's edge direction (forward-down) and the haft's diagonal in the fit, beside reach and view.
- Build the windup as the strike turned back about the haft x edge axis (55-120 deg measured) so the edge leads the
  head's path at impact (edge . path 0.90-0.99).
- At landing, roll the strike about its own haft (reach-neutral) to put the edge into the aimed surface.
- Gate: capture the edge at contact on a wall and on a floor target; reach and viewport numbers cannot see facing.

## PICK-THE-RIG-NEAREST-THE-CAMERA

A first-person probe logged a rest wrist of 0.51 m and a fist at viewport (4.7, -5.5) for two different pose variants.
It had picked the Animator by name while two character rigs were live, so every number came from the other body.

- In a live multi-rig session select the rig whose hand bone is nearest the camera and log how many rigs matched.
- A viewport outside 0..1 for a fist the capture shows on screen means the probe read the wrong rig.

## BIPED-QUADRUPED-HIPS-ARE-THE-THIGHS-PARENT

- A quadruped on a Biped-named rig hangs its hind legs off the first spine bone (`Spine`), not off `Pelvis`/`Hips`.
  Bending that bone as a trunk joint yaws the whole skeleton: measured 13 degrees of body heading ahead of travel
  in every corner, read by the developer as "spins in place".
- Resolve the hips as the common parent of the hind thighs at bake time and store it as its own reference. Give
  it the rear counter-rotation (the hind body trails the path tangent) and bend only the joints ahead of it.
  Never pick the hips by bone name.

## SPINE-BENDS-BY-PATH-CURVATURE

- A travelling body's spine bend authored as a share of a reference turn rate over-bends fast wide corners and
  under-bends slow tight ones, because the same yaw rate is a different path at each speed.
- A body lying on its path bends by curvature (yaw rate / speed, 1/m) times its length: give each joint
  curvature x its span measured once from the bind pose, capped per species. Keep the effort-share bend only for
  turning on the spot, where speed is zero and curvature is undefined. Let the neck lead into the corner.

## APPROVED-POSE-BOUNDS-A-REFIT

A first-person gun hold the developer had called good was replaced by a grid fit scored only against a reference game's
screen targets (muzzle and hands in viewport boxes). The pick passed every measured box and sat 5.6 cm lower; the
developer's verdict was that the gun was now too low, and the old hold was right.

- A pose the developer approved is a constraint, not a starting point. Record its values (and a capture) before a fit, and
  add its distance to the fit's score so a pick far from it has to earn the move.
- Show the developer a before/after capture before baking over an approved pose; a passing measurement is not approval.
- When the developer rejects the new pose, restore the approved values from version control (`git show <commit>^:<asset>`)
  rather than authoring a third pose.

## MEASUREMENT-NEVER-GATES-ITS-OWN-REFRESH

A first-person weapon stayed in its wall-pulled pose after the wall was gone. Its muzzle-to-wall reach was
re-measured only while the weapon was not pulled, and the pull came from that reach, so the first blocked
reading froze the pull.

- Never skip a measurement on the state it drives: an effect that switches off its own input latches the first
  bad reading for as long as it stays on.
- Measure every frame from an input the effect does not move (the pre-effect pose, the unpulled frame), and
  cover the release with a test that steps blocked -> clear -> blocked.

## ANIMATOR-REWRITES-A-BOUND-TRANSFORM

A weapon's wall-clearance probe never fired for one gun: its aim target, written by a script every frame, read back a
constant pose (yaw 217.7 degrees) after the Animator evaluated, so the probe cast 0.9 m behind the eye. Another gun
passed only because its stale pose happened to face forward.

- A transform bound to the Animator stream (`BindStreamTransform`, which rig frameworks call for every rig element)
  is written back from the stream on each evaluation. A script value written before evaluation is gone afterwards.
- Jobs that read the transform before evaluation are fine. Anything that reads it later keeps the written value in
  a field and reads that. To find it, log the transform before and after evaluation: a post-evaluation value that
  holds constant while its input moves is the write-back.

## RIG-LOOKUP-MISS-RETURNS-BONE-ZERO

Three generated character bodies passed every load and play step while 448 of their weapon-layer bone references
were wrong: blank pelvis, layers still built against the source rig, indices from another skeleton.

- A by-name bone lookup that returns a default element on a miss (a rig asset's `GetElementByName`) hands back
  index 0, the root, and an `OnValidate` rebind writes that index into the asset. Nothing throws.
- A rig validator compares the returned element's NAME with the name asked for, then the index, and fails on a
  blank or `None` bone. Run it in tests and in the authoring tool.
