Animation
Every player pawn that wears a humanoid .dh-avatar is animated by the engine’s built-in locomotion. It uses no clips. A pose is made every frame from what the pawn is doing:
| Motion | What drives it |
|---|---|
| Idle | Time: a slow breath in the chest and a weight shift in the hips. |
| Walk and run | Velocity against the body’s facing (the 2D blend) and speed (idle → walk → run). |
| Crouch | The eye height. The hips sink and sit back over the planted feet, the legs bend to reach them, and the trunk leans forward. It layers over everything else, so a crouched pawn walks crouched. |
| Jump | Vertical speed. The feet tuck at take-off, stay tucked through the rise, reach down while falling, and the hips absorb the landing in proportion to the impact. A crouch held in the air pulls the legs up instead of sinking the hips, as a Source crouch-jump does, and no foot target is ever allowed above, or closer than a minimum fold to, its hip. In a sustained fall the arms flail: a windmill in antiphase on a clock of its own, with the shoulders lifting, the elbows working and the feet kicking a little, easing in after a short delay so an ordinary jump never reaches it. |
| Noclip float | Time and flight speed. The legs drift slowly and out of step, as if treading water, the arms drift, and the body leans into the flight. |
States cross-fade. Nothing pops between standing, moving, jumping and flying.
Feet stay where they are put
Section titled “Feet stay where they are put”A foot is planted, not swung. The stride phase advances by the distance the body covers divided by the stride length, on the pawn’s stride clock (below). A planted foot is carried back under the body by exactly what the body did that frame, so it holds its place in the world. A swinging foot eases to the next plant, half a stance ahead along the direction of travel. The leg reaches its foot with a two-bone solve, and the hips drop just enough that no leg is pulled straight. Stepping sideways spreads the feet so they pass without crossing. A pawn that stops finishes only the step already in the air: that foot lands within client.anim.locomotionSettleSeconds, at its rest rather than half a stance ahead, and a planted foot is never lifted to start another. A planted foot never slides: one left farther than client.anim.locomotionSettleTolerance from its rest takes a short settle step home instead (locomotionSettleStepSeconds, lifted locomotionSettleStepLift), after the foot in the air has landed and one foot at a time, so the feet are never both in the air. The step plants once, so it plays one footstep through the same path (a pawn with no legs to draw takes none). The toe-off and heel-strike roll scales with the gait, so a foot at idle stays level.
One clock for the legs and the footsteps
Section titled “One clock for the legs and the footsteps”The stride lives in a GaitClock, not in the pose. It owns the phase, the cadence and stride it advances by, how a start sets off and a stop settles, and the moment each foot lifts and plants. The pose reads it and draws the feet it says; it keeps no phase of its own. The footsteps play on the same plants (see Audio), so the foot you see land and the step you hear are one event.
Every pawn has a clock, whatever it wears. A humanoid avatar, an avatar with no legs to animate, the hull box and the stand-in shown while an avatar loads all tick the same clock at the same cadence for the same speed, so all of them are heard walking. A legged rig adds one thing, and it does not move the beat: it lifts a foot early when the foot has been carried as far behind as it reaches. A pawn with no legged rig reaches with client.anim.gaitStandInLegLength, which bounds the stance and nothing else.
The clock ticks whether or not the body is drawn, so your own footsteps play in first person with the body hidden, and whether or not client.anim.locomotionEnabled is on.
The 2D blend
Section titled “The 2D blend”The direction of travel, measured against the body’s facing, blends four anchors: forward, left, back and right. The weights are linear in angle, so a diagonal is its own mix of its two neighbors. Backward has its own stride and lean; it is not a mirrored forward. Speed blends idle, walk and run, and the cadence rises through client.anim.locomotionSprintSpeed.
Where it runs
Section titled “Where it runs”The pose order is stride clock → animation → springs → eyes → draw, and every step goes through one shared per-pawn path in ClientEntityPoses (AdvanceGait, then AdvanceAnimation). The desktop client, the iOS shell and every other host get the same picture with no code of their own.
- Your own pawn moves by prediction: velocity, grounded, landed, noclip and the crouch, stated by the shared local-body submission.
- Everyone else moves by where they are drawn from frame to frame and by their replicated
Grounded,NoclipandFlyingflags. Nothing new crosses the wire. - The crouch comes from the eye height for both: how far it sits between the standing eye and the crouched one. The server already smooths the eye through the transition, so the body sinks exactly as fast as the camera does. A state with no eye height reads as standing.
- Editor bone edits layer on top of the animation. Each delta acts on the bone’s animated transform exactly as it acts on the rest pose.
An avatar whose rig maps no hips and no two whole legs (upper leg, lower leg and foot on each side) stands in its bind pose, as before. Its stride clock still ticks, so it still has footsteps.
Tuning
Section titled “Tuning”Every number is a local preference under client.anim.locomotion* and applies on the next frame. The units are m/s, strides per second, degrees, seconds, and leg lengths for every distance on the body, so one setting reads the same on a small avatar and a tall one.
| Preference | Default | Meaning |
|---|---|---|
client.anim.locomotionEnabled | true | Off leaves every pawn in its bind pose. The stride clock and the footsteps keep running. |
client.anim.gaitStandInLegLength | 0.85 m | Leg length, at scale one, the stride clock reaches with for a pawn with no legged avatar. |
client.anim.locomotionWalkSpeed / RunSpeed / SprintSpeed | 1.6 / 5.08 / 10.16 m/s | Where the walk and the run are complete, and where the cadence tops out. |
client.anim.locomotionStartCadence / WalkCadence / RunCadence / SprintCadence | 0.6 / 0.9 / 1.4 / 1.8 | Strides per second at those speeds. |
client.anim.locomotionWalkStance / RunStance | 0.62 / 0.36 | Share of a stride each foot is planted. |
client.anim.locomotionStanceReach | 0.65 | Farthest a planted foot travels under the body. |
client.anim.locomotionSideReach | 0.5 | Share of that reach when stepping sideways. |
client.anim.locomotionWalkLift / RunLift | 0.12 / 0.42 | How high a swinging foot lifts. |
client.anim.locomotionFootRoll | 25° | Toe-off and heel-strike pitch at a full gait; none at idle. |
client.anim.locomotionStepClearance | 0.08 | Gap the feet keep stepping sideways. |
client.anim.locomotionSettleSeconds / SettleTolerance | 0.2 s / 0.2 | How long a stop takes to land the foot in the air, and how far from rest (leg lengths) a planted foot may stand before it steps home. |
client.anim.locomotionSettleStepSeconds / SettleStepLift | 0.16 s / 0.06 | How long the settle step home takes, and how high it lifts the foot (leg lengths). |
client.anim.locomotionSideStride / BackStride | 0.5 / 0.8 | Stride length sideways and backward, as a share of forward. |
client.anim.locomotionForwardLean / BackLean / SideLean | 10° / 5° / 5° | Lean into the run at the run speed. |
client.anim.locomotionWalkBob / RunBob / RunCrouch | 0.02 / 0.035 / 0.04 | Hip rise over a walking leg, dip over a running one, and a lower run. |
client.anim.locomotionPelvisTwist / ChestCounter | 12° / 1.3 | Pelvis yaw per leg length of foot split, and how much of it the chest turns back. |
client.anim.locomotionArmSwing / WalkElbow / RunElbow / ArmOut | 38° / 18° / 85° / 8° | Arm swing per leg length of split, elbow bends, and arms out from the sides. |
client.anim.locomotionHeadSteady | 0.6 | Share of the trunk’s pitch the head turns back. |
client.anim.locomotionIdleKnee | 0.015 | Hips sit lower at rest so the knees are soft. |
client.anim.locomotionCrouchHipDrop / CrouchHipBack / CrouchLean | 0.7 / 0.3 / 35° | How far the hips sink and sit back at a full crouch, and the trunk’s lean over them. |
client.anim.locomotionCrouchAirTuck | 0.25 | How much higher a full crouch pulls the feet in the air (the hips sink only on the ground, fading out with the ground weight). |
client.anim.locomotionMinFootBelowHip / MinLegFold | 0.15 / 0.3 | The least a foot target may sit under its hip and keep from it, so a tucked knee never folds behind the body. |
client.anim.locomotionBreathPeriod / BreathAngle | 4 s / 1.5° | The breath. |
client.anim.locomotionSwayPeriod / SwayAmount | 7 s / 0.012 | The weight shift. |
client.anim.locomotionVelocitySmoothing / FadeSeconds | 0.16 s / 0.18 s | How quickly the gait follows the velocity, and the ground/air/flight cross-fade. |
client.anim.locomotionGaitEase | 1 | How softly idle, walk and run hand over: 0 is linear in speed, 1 a smoothstep. |
client.anim.locomotionJumpSpeed | 5 m/s | Vertical speed that counts as a full rise or fall. |
client.anim.locomotionTakeoffTuck / TuckSeconds / ApexTuck / FallTuck | 0.38 / 0.12 s / 0.22 / 0.04 | The feet’s tuck through the jump. |
client.anim.locomotionFlailFallSpeed / FlailDelay / FlailRamp / FlailRelease | 3 m/s / 0.3 s / 0.25 s / 0.3 s | The free-fall flail: the downward speed that counts as falling, how long a fall lasts before the flail starts, how long it takes to ease in, and how long to ease out once the fall ends (slow, because the landing cross-fade is already taking the arms away). An ordinary jump’s descent never reaches it. |
client.anim.locomotionFlailFrequency / FlailArmSwing / FlailArmRaise / FlailElbow / FlailLegKick | 1.5 Hz / 40° / 25° / 30° / 0.08 | The flail’s windmill rate on its own clock (never the stride’s), the arms’ swing, the shoulders’ lift, the elbows’ extra bend and the feet’s fore-aft kick against the arms. |
client.anim.locomotionLandDrop / LandMaxDrop / LandRecover | 0.035 / 0.22 / 0.35 s | Hip drop per m/s of impact, its limit, and the recovery. |
client.anim.locomotionFloatPeriod / FloatKick / FloatLegPhase / FloatTuck | 2.6 s / 0.14 / 0.37 / 0.22 | The floating legs’ drift. |
client.anim.locomotionFloatLean / FloatLeanSpeed / FloatTrail | 35° / 12 m/s / 0.2 | Lean into the flight, and the feet trailing. |
client.anim.locomotionFloatArmOut / FloatArmDrift | 28° / 10° | The floating arms. |
Photographing it
Section titled “Photographing it”DigitalHeaven.Engine.Host --render <dir> --renderAnim --avatar <barcode> [--set client.render.msaa=1]This walks one pawn through the same PlayerPawnVisual a live client draws with, at a fixed 60 Hz step. Its springs are on. The capture follows the pawn with its own camera and writes a strip of PNGs plus one CSV per strip: <avatar>_anim_<strip>_<nn>.png and <avatar>_anim_<strip>.csv.
idle: eight frames over one breath.walk_<direction>andrun_<direction>for all eight directions, plussprint_forward: six frames across one stride, chosen by the stride’s own phase.blend_run_000toblend_run_090: the run at 15° steps from forward to left, all at the same phase. They show the 2D blend is continuous.startStop: standing, a run, a stop and the settling step.jumpandjumpRun: take-off, tuck, rise, apex, fall, landing and recovery.crouchJump: the standing jump with the crouch held.crouchandcrouch_walk: standing, sinking into a crouch and holding it; then one crouched stride at the walk speed.floatStillandfloatMove: noclip hovering, and flying forward up to the noclip speed.
The CSV records every step: the phase, the stride in meters, the speed, all blend weights, each foot’s height, whether it is planted, and the foot skate, which is how far a planted foot has slid since it was put down, in millimeters. The log states the worst skate per strip.
With MSAA off, two runs are byte-identical, the same pin verify-render-determinism.bat uses for its spawn half. The design is in Engine/design-notes/locomotion.md.
Videos
Section titled “Videos”DigitalHeaven.Engine.Host --render <dir> --map core:maps/flat-map --renderAnim --animVideo --avatar <barcode> [--animClip idle,walk,...] [--animFps 60] [--width 1280 --height 720] [--set client.render.msaa=1]bun Engine/scripts/encode-anim-video.ts <dir>/animVideo [outDir]--animVideo renders every frame of a set of clips instead of the strips, at a fixed rate: --animFps, 60 unless given, which is also the step the pawn is advanced by. It is the same pawn the strips walk, through the same PlayerPawnVisual, with its springs, and with its eyes, which are offered the camera as a face to look at. The camera trails the pawn from a fixed side, so a turn reads as a turn. Each clip lands in <dir>/animVideo/<avatar>/<clip>/ as 00000.png onward, with trace.csv (the strip CSV, one row per frame) and clip.json (the rate, the frame count and what the clip shows).
idle: six seconds standing.walk: a walk around a circle while facing one way, so all eight directions and the blends between them show.strafe: facing the camera, a walk left, right, back and forward again.run: a run, a 180° turn, a sprint back, a turn and a stop, seen from the side.startStop: standing, a run and a stop, from the side.jump: a standing jump, then a jump out of a run, from the side.noclip: a rise off the floor, a hover, a flight forward, a turn back and a hover.showcase: all of the above back to back as one take.
--animClip picks clips by name; a run renders about eight frames a second at 1280x720, so the showcase is one run and the rest another. core:maps/flat-map keeps walls and props out of a moving camera’s way. With MSAA off, two runs write the same bytes.
encode-anim-video.ts turns every clip folder under the path it is given into an H.264 MP4 at the rate in its clip.json, yuv420p at CRF 16, so it plays anywhere, a phone included. The engine never runs ffmpeg; the script does, from PATH or $FFMPEG.