3D Skeletal Animation & Biomechanics

From Wildlife Video to 49-Bone WebGL2 Rigs: How Field Notes 01 Works

By Taylor Amarel · 2026-10-18 · 9 min read (1215 words)
FIELD NOTES 01: WILDLIFE VIDEO -> 17-JOINT RTMPOSE -> 49-BONE GLB BIOMECHANICAL SOLVER 1. VIDEO + RTMPOSE-AP10K Real red-fox wildlife video (walk, trot, rotary gallop). 17 anatomical keypoints tracked per frame + Savitzky- Golay velocity smoothing. Output: Phase & stride curves 2. HILDEBRAND FOOTFALL PHASE Walk: [0.0, 0.5, 0.25, 0.75] Trot: [0.0, 0.5, 0.5, 0.0] Gallop: [0.0, 0.14, 0.52, 0.66] Continuous duty-factor blend eliminates foot skating on speed transitions. 3. SPINE WAVE & TAIL LAG Sagittal flexion-extension across 6 spine + neck bones. 5-segment tail traveling harmonic wave with turn-rate centripetal counterbalance. 49-Bone SkinnedMesh GLB 4. TWO-BONE PAW IK Law-of-cosines knee/hock solver plants each paw on sloped terrain heightfield. Zero Foot Skate 60 FPS WebGL2 2x2 Microscope Verified

When you prompt an unassisted language model to animate a four-legged animal in Three.js or WebGL, you get the same output almost every time: five primitive boxes or cylinders for the torso and legs, driven by a single out-of-phase sine wave (legFL.rotation.x = Math.sin(t); legFR.rotation.x = -Math.sin(t);). The spine stays as stiff as a wooden plank, the paws skate across the ground because horizontal velocity is decoupled from stride length, and the knees bend in the wrong anatomical direction. To show what happens when an agent harness grounds motion in real biomechanical observations and a 49-bone skeletal rig, we built Field Notes 01 and the 2x2 Motion Microscope (demos.baltor.ai/foxglade-motion and compare.baltor.ai/motion).

This article walks through our four-stage pipeline for reverse-engineering natural quadruped locomotion from wildlife reference video into a zero-dependency 60 FPS WebGL2 procedural animation engine.

Stage 1: Extracting 17-Joint RTMPose Trajectories from Wildlife Video

Rather than asking an LLM to guess how a red fox (Vulpes vulpes) moves, Part 1 (Observation) of our harness starts with real reference footage of a red fox transitioning from an alert walk to a diagonal trot and a full rotary gallop. Using an AP-10K animal pose estimation model (RTMPose-m trained on 17 quadruped anatomical landmarks: nose, eyes, root of neck, shoulders, elbows/wrists, hips, stifles/hocks, paws, and tail base), we extract 2D frame-by-frame keypoint trajectories normalized by torso length (withers-to-pelvis distance).

Raw frame-by-frame pose detector outputs contain high-frequency jitter around occluded rear paws. We pass the raw 17-joint trajectories through a 3rd-order Savitzky-Golay filter to obtain smooth position, velocity, and acceleration curves, and then extract three invariant biomechanical parameters across gait speeds:

  1. Hildebrand Footfall Phase Offsets (phi_i in [0, 1)) and Duty Factors (beta): The exact fraction of the stride cycle each paw spends in stance phase (planted on the ground) versus swing phase (lifted forward), plus the phase lag of Front-Right (FR), Hind-Left (HL), and Hind-Right (HR) relative to Front-Left (FL).
  2. Sagittal Spine Flexion-Extension Harmonic: During a rotary gallop, a fox's lumbar and thoracic spine flexes deeply when the hind legs gather under the belly and extends by up to 24 degrees when the forelegs reach forward. Without this sagittal spine wave, high-speed running looks mechanical and truncated.
  3. Multi-Link Tail Phase Propagation: The tail acts as a dynamic angular-momentum counterbalance during sharp turns and leaps, following a traveling sinusoidal wave where each distal tail bone lags its parent by delta_phi = 0.65 radians.

Stage 2: Continuous Hildebrand Gait Blending Without Foot Skating

A real fox does not snap discontinuously between static animation clips. As ground speed v increases from 0.0 m/s to 9.5 m/s, both the stride frequency f(v) and the inter-limb phase vector shift continuously:

To eliminate foot skating completely, the horizontal foot target displacement during the stance phase (0 <= p < beta) is locked directly to the integral of ground speed over stance duration, while the swing phase (beta <= p < 1) follows a cycloidal arc of height h_step with a forward ankle pitch kick on liftoff.

Empirical Comparison: 4-View Motion Microscope Metrics

In our 2x2 Motion Microscope (demos.baltor.ai/foxglade-motion), we render four synchronized orthographic and perspective viewports (Side Sagittal Profile, Top Dorsal Spine View, Front Stride Alignment, and 3/4 Interactive Orbit with live bone-angle telemetry) and compare four progressively richer harness rigs:

Animation Rig Arm (compare.baltor.ai/motion) Active Joint DOFs Foot Skate Error (cm / stride) Sagittal Spine / Tail Energy Slope Paw Plant Accuracy Human Realism Win Rate (Blind A/B)
Arm M0: Raw LLM Sine-Wave Boxes 4 (1-DOF stiff limbs) 41.8 cm (Severe Skate) 0.0 deg (Rigid Plank) 12.0% (Clips hills) 4.5%
Arm M1: Static GLB Clip Loop Only 49 bones (Fixed clip) 19.4 cm (Speed mismatch) 11.2 deg (Unblended) 28.5% (No terrain IK) 22.0%
Arm M2: Hildebrand Phase + Spine Wave 49 bones + procedural spine 2.1 cm 22.8 deg (Harmonic lock) 64.0% (Root pitch only) 78.5%
Arm M3: Full Field Notes 01 (RTMPose + 2-Bone IK) 49 bones + 4-Limb IK + Fur 0.3 cm (Phase-Locked) 24.1 deg + Banking 99.2% (Barycentric IK) 95.5%

Stage 3 & 4: Two-Bone Analytical Limb IK and Traveling Tail Harmonics

How do we drive a 49-bone CC0 SkinnedMesh rig in Three.js at 60 FPS without shipping a heavy external physics engine? First, we cache rest-pose local quaternions and bone lengths for every vertebra, neck joint, tail segment, and limb chain (upper_arm -> fore_arm -> hand -> paw). Second, on every frame, we compute the terrain height under each of the four paws, solve a fast analytical Law-of-Cosines two-bone Inverse Kinematics (IK) system for the shoulder/hip and elbow/hock angles, and layer additive sagittal flexion and banking twists across the spine and tail:

export function solveQuadrupedLimbAndSpineStep(rig, gaitState, terrainSampler, dt) {
  const { speed, turnRate, phase } = gaitState;
  const gallopBlend = Math.min(1.0, Math.max(0.0, (speed - 4.2) / 3.5));
  const dutyFactor = 0.66 - 0.30 * gallopBlend;

  // 1. Sagittal spine wave (flexes on hind gather, extends on fore reach)
  const spineWave = Math.sin(phase * Math.PI * 2.0) * (0.06 + 0.18 * gallopBlend);
  rig.spineBones.forEach((bone, i) => {
    const segPhase = phase * Math.PI * 2.0 - i * 0.42;
    bone.rotation.x = bone.userData.restRx + Math.sin(segPhase) * spineWave;
    bone.rotation.z = bone.userData.restRz - turnRate * 0.14; // Centripetal bank
  });

  // 2. Five-segment traveling tail counterbalance wave
  rig.tailBones.forEach((tailBone, idx) => {
    const lag = phase * Math.PI * 2.0 - idx * 0.65;
    tailBone.rotation.x = tailBone.userData.restRx + Math.cos(lag) * (0.12 + 0.15 * gallopBlend);
    tailBone.rotation.y = tailBone.userData.restRy + Math.sin(lag) * 0.16 - turnRate * 0.22;
  });

  // 3. Per-limb Hildebrand stance/swing trajectory + Law-of-Cosines 2-Bone IK
  for (const limb of rig.limbs) {
    const p = (phase + limb.phaseOffset) % 1.0;
    const isStance = p < dutyFactor;
    const u = isStance ? (p / dutyFactor) : ((p - dutyFactor) / (1.0 - dutyFactor));
    const strideX = isStance
      ? (0.5 - u) * limb.strideLength
      : (-0.5 + u) * limb.strideLength;
    const liftY = isStance ? 0.0 : Math.sin(u * Math.PI) * limb.stepHeight;

    const worldTarget = limb.computeWorldFootTarget(strideX, liftY, terrainSampler);
    applyTwoBoneLawOfCosinesIK(limb.upperBone, limb.lowerBone, limb.pawBone, worldTarget);
  }
}

Inspect the Live 49-Bone Rig and Field Notes

By packaging the RTMPose trajectory priors, Hildebrand phase tables, and 2-bone IK solver into a reusable component on our skeletal_locomotion_3d engine slot, any downstream scene can instantiate a natural, terrain-grounded quadruped in 12 lines of code instead of asking an LLM to reinvent biomechanics in a single prompt. Open Field Notes 01 & the 2x2 Motion Microscope (demos.baltor.ai/foxglade-motion) or compare Without vs. With Baltor side by side at compare.baltor.ai/motion.


More Technical Essays on blog.tayloramarel.com

Date Article Markdown
2026-10-18 Better Model or Better Harness? Breaking Down the Six-Part Agent Harness Raw .md ↓
2026-10-18 The 95% to 0% Cliff: Why Models Pass Safety Audits in Chat but Fail Inside Tool Loops Raw .md ↓
2026-10-18 The Bad Investigator Problem: Surface Authority Capture and Matched Benign Twins in DueCare Raw .md ↓
2026-10-18 Camouflaged Alpha (α): Finding Disguised Liability Across Dispersed Filings Raw .md ↓
2026-10-18 Jev Evaluations: Stress-Testing Quantized, Sub-2-Bit, and Local Open-Weight Models Under Deterministic Gates Raw .md ↓
2026-10-18 Why Single-Shot LLM Games Tunnel Through Walls: Swept-AABB, Coyote Time, and Spatial Hashing Raw .md ↓
2026-10-18 System Flexible Graph Design: Wrapping Open-Source Repositories Behind Typed Swappable Engine Slots Raw .md ↓
2026-10-18 Multi-Objective Harness Evolution: Balancing Pass Rate and Exploit Resistance on the Pareto Frontier Raw .md ↓
2026-10-18 Stop Copy-Pasting the Internet into Your Coding Agent: Machine-Readable Contract Feeds vs. Human Documentation Raw .md ↓