MOVEMENT.md — how characters move, and why¶
kke::Locomotion (engine/include/kke/Locomotion.h) is the movement layer
between input actions and RigidWorld's character controller. Its rules
come from PointDown's controller, "mechanics museum" and parkour videos
(https://www.youtube.com/@PointDown). They're Godot videos, but the
principles are engine-independent. This file keeps the notes and says
where each principle lives in the code.
Tested in tests/test_locomotion.cpp (12 tests, including the whole
kke_demo parkour lane at 60 and at 15 fps). You can play it in kke_demo:
walk to the lane at x = 20, or run KKE_DEMO_AUTOPILOT=1 ./kke_demo to watch
it run the lane by itself. The Synty scenes (SCENES.md) are real-art trails:
SceneTrails.* runs them headless when the packs are present.
The frame¶
PointDown's controller core (Advanced controller core, E3BxMgmP4m0) splits a character into input → model → presentation:
- Input:
InputModule/InputMapgather actions (move,sprint,walk,crouch,jump). The game never reads keys. - Area awareness runs next, before any state logic, and is the only
code that asks the world questions (
Locomotion::probe). - Translation, per state: the current state turns actions into a move.
jumpon the ground becomes a vault if a thin, hip-high obstacle is ahead, a climb if there's a top with room, and a jump otherwise (Designing AAA parkour system, veOpS2S8Lco: "input actions are not move names"). - Update: the state moves the capsule.
- Presentation: the Animator picks poses from the state. Animations are pose providers: they never decide where the capsule goes (Root motion once and for all, _PHRm2EqfX0).
Area awareness: dynamic, no markup¶
PointDown compares static awareness (trigger volumes placed by a designer)
with dynamic awareness (ray casts that read the level). Dynamic costs more
code but lets a level designer drop in any mesh (Designing AAA parkour
system; Ledge actions, IMSFMmekFxg). We use dynamic awareness because
the Synty levels have hundreds of props nobody will mark up. probe()
costs about 15–30 ray casts and 2–4 capsule tests, and runs only on the
frame "go up" is pressed, or every frame while in the air.
- Face: rays forward at knee, hip and chest height, from the middle and both shoulders (a centre ray slips through the seam between two fence panels). Anything lower is a stair the controller steps up by itself.
- Top: one ray down, just past the face, from above the highest top this sensor can reach, and a second one further in when the rim is bevelled (rocks). A ray that starts inside geometry means the obstacle is too tall.
- Depth: rays down across the top until it drops away. Thin is a fence (vault); a platform is a climb.
- Room: capsule tests for a tucked body over the top, and for a standing body at the landing or on the top.
- Sensors per state (PointDown's "ray slice" resources): walking looks 0.7 m ahead and reaches 1.9 m, sprinting looks 1.6 m ahead and reaches 2.2 m, and the air sensor reaches 2.1 m. A 2.1 m ledge is only climbable from a sprint.
Floor: three tiers, not two¶
From Designing AAA parkour system: on the floor; up to 25 cm above the floor, which still counts as walking (snap down, no fall animation on a kerb or a stair edge going down); and in the air. That removes the fall/land stutter on small drops. Coyote time (0.12 s) and a jump buffer (0.15 s, PointDown's "queued input") come from the same idea: don't make the player hit a one-frame window.
Turning (Smoother turn movement, MM1, ysKxT3q4tA8)¶
- Speed and direction are separate. Input rotates the direction at a limited rate (600°/s running, 320°/s sprinting) instead of replacing the velocity. The head doesn't jump 40 cm in one frame when you mash A/D.
- Sharp turns slow you down to 60% while the turn lasts ("creatures that value their lives drop speed in turns").
- A 180° reversal keeps its side. Near 180° the sign of the angle flips on float noise, and the turn would change sides every frame; the last turn direction wins inside ±12° of 180°.
- The animation follows the measured speed: the blend space gets how far the feet actually moved, not the requested speed. Legs slow down in turns and stop against walls. It is measured over the time physics simulated since the last frame (physics steps at 60 Hz, frames don't), so a frame between two physics steps doesn't read as standing still (BUG-054).
Jumping and the air (Jump & Fall control, MM2, vPFAh2T8Ipg)¶
- Air control is a small acceleration (5 m/s²), added to the flight and capped at the take-off speed (at least 1.6 m/s for standing jumps). It corrects a jump; it doesn't fly the character.
- Facing is stored separately from velocity. When a wall deflects the flight, the body still faces where you meant to go.
- Take-off is a glue window. The last grounded frame may re-aim the run at the input, so a jump straight after a turn goes where you asked.
- Ledge grab in the air: rising slowly or falling, steering into a wall whose top is within reach turns into a climb when "go up" is held, or when the top is less than 1.5 m above the feet. Otherwise it hangs.
Ledge hang and shimmy (Ledge actions, IMSFMmekFxg)¶
- Grab: in the air, steering into a wall whose top is 1.5-2.35 m above the feet (and "go up" not held) hangs from it. That includes edges the probe can't stand on, like a thin wall's top. The capsule is pulled in over 0.15 s to 2.05 m below the top, square to the wall.
- Shimmy: the sideways part of the input moves along the edge at 1.1 m/s. Every step re-finds the wall (at chest height) and the top (just in from the face). Where either is gone, the top steps by more than 15 cm, or the capsule wouldn't fit, the character stops: it never shimmies off the end or through a side wall. The wall normal is re-read each step, so gently curved walls work.
- Corners: where the edge ends, an outside corner (the wall turns away) or an inside corner (a wall ahead) with a top at the same height is followed: the character moves around it over 0.3 s and hangs from the new face. The held input keeps going the same way along the ledge, even though it no longer points along the new wall. A top that steps up or down is a real end.
- Jump back: "go up" while pushing away from the wall jumps off it (3.5 m/s out, 80% of a jump up), facing away, with no re-grab for 0.4 s.
- Climb up: "go up" runs the normal checked climb from the hang (so a thin wall with no room on top can be hung from but not climbed).
- Let go: crouch. A small push off the wall, no coyote jump, and no re-grab for 0.4 s.
- Animation: no hang clip in the UAL sets, so kke_demo uses the fall
pose slowed down with hand IK on the edge; a pack with
Hang_Idleis picked up by name.KKE_DEMO_HANG=1 ./kke_demoruns a jump, hang, shimmy around the wall's end and a jump back at the lane's 3 m wall by itself. - Tests:
Locomotion.JumpAtAHighWallHangsFromTheTop,ShimmyAlongTheEdgeStopsWhereItEnds,ClimbUpFromAHang,CrouchLetsGoAndDoesNotRegrab,HangsFromAThinWallItCannotStandOn,ShimmyAlongTheEdgeAndAroundTheCorner,ShimmyStopsWhereTheTopStepsUp,ShimmyIntoAnInsideCorner,JumpBackOffAHang.
Ledge leaps (Ledge actions, IMSFMmekFxg)¶
- Sideways: hanging, "go up" with the stick pushed along the wall leaps to the next edge that way. The search walks along the wall past the end of the current edge (the part shimmying covers is skipped) and takes the nearest edge within 2.2 m that is up to 1.3 m higher or 1 m lower. It lands a body width in from that edge's end, so the hands aren't on a corner.
- Up: with the stick into the wall (or no stick), "go up" climbs if there is room on top. If there isn't (a thin wall, a sill), it leaps up to an edge above on the same wall, up to 1.3 m higher.
- The target is found and checked before the leap, including room for the body there and at the middle of the arc. The body moves kinematically for 0.45 s: evenly along the ground, on a parabola in height that peaks about 0.3 m above the higher end (PointDown's ledge-leap parabola). It hangs on arrival. Hand IK reaches for the new edge in the last 40%.
- No target (nothing in reach, no room) means no leap. A sideways "go up" then climbs if it can, as before.
- Tests:
Locomotion.LeapsSidewaysAcrossAGapToAHigherEdge,LeapsUpToAnEdgeAbove.
Wall run¶
- Start: in the air, going at least 4 m/s, still holding the run, with a vertical wall at the hips and head within 0.45 m of the capsule's side, and running along it rather than into it (the wall is within 60 degrees of parallel). At least 0.35 m off the ground, so a wall beside a kerb doesn't count.
- On the wall: the body moves kinematically along the wall at the entry speed, with the wall's normal re-read each step (gently curved walls work). Height follows a lighter gravity (7 m/s² instead of 9.8), starting with 70% of the take-off's vertical speed, so a run arcs up about a metre and comes down. It lasts at most 1.1 s.
- Ends: "go up" is a wall jump (4.5 m/s off the wall, 85% of a jump up, 80% of the run kept). Letting go of the stick or crouching drops off. Where the wall ends, the runner flies on with the momentum. Something in the way stops the run. Landing on the floor keeps the run going on the ground. The same wall can't catch you again for 0.35 s, but another wall can, so wall-to-wall chains work.
- Animation: UAL2's
WallRun_L_Loop/WallRun_R_Loopby wall side (Locomotion::wallRunSide()). The camera's shoulder moves to the open side during the run, because over the wall shoulder the spring arm would pull in behind the head. - Tests:
Locomotion.JumpingAlongAWallRunsOnIt,WallJumpKicksOffTheWall,SlowJumpNextToAWallIsNoWallRun. - kke_demo: the trick course at x = 26 has a 12 m wall to run along, a
row of thin pillars with 1 m gaps and rising tops for leaps, and a thin
wall under a beam for the leap up.
KKE_DEMO_TRICKS=1 ./kke_demoplays through all of them.
Vault and climb (Parkour ep3, rhzwhJPb-jQ; Ledge actions)¶
- The capsule becomes kinematic for the move (
RigidWorld:: setCharacterKinematic). It follows a path that was checked for room before the move started, so there's no fight with the solver halfway over. - Correction window (first 0.2 s): the body turns square to the obstacle, whatever angle you came in at.
- Vault path: PointDown's ledge-leap parabola (
leapParabola). It is solved through the start and the landing, with a peak that clears both edges of the top. Horizontal speed stays constant, so a speed vault keeps its momentum (90% of the entry speed). - Climb path: up the wall to the edge (hands on top), then over it onto the top.
- At the end the momentum goes back to the controller, and the character is on the floor that the probe found (no "stand on thin air" snap).
Free climbing (kke::ClimbWall, kke::Climber; games/climb_race)¶
Ledge hang is for edges you run into; free climbing is a whole rock face where you choose every hold.
- The wall (
ClimbWall::generate(seed)) is a height field over x, y: bands of lean (an easy start, a forced overhang in the middle, a slab at the top), fbm relief and buttresses. Ledges are boxes sticking out of the rock. Holds (jug, crimp, sloper, edge) are placed along a drawn line that is always climbable, then filled in around it. Same seed, same mountain. - Reach is
ClimbWall::reachDistance: sideways and up count fully, in and out of the rock a quarter. Routes, the climber and the bot all use it. - The climber (
kke::Climber) is pure logic, with no physics. The capsule is kinematic atfeet()while climbing and goes back tokke::Locomotionon a fall or at the top. The hands pick holds: a bumper reaches precisely (1.55 m), a released trigger lunges up to 2.45 m, and both together snatch quickly. The body hangs under the hands and the feet find holds under the hips. Stamina is the one resource. - The body is IK only: the hang clip slowed down, with two-bone IK
putting each hand and foot exactly where
Climbersays. - Tests:
tests/test_climb_wall.cppcovers 25 seeds climbable, reaches, lunges, loose holds, stamina and the bot to the top.
Animations¶
The Universal Animation Library "Standard" set in assets/animations/ has
no vault or climb clips (its 43 clips are locomotion, jump, crouch,
combat, sitting, swimming and interaction). Volume 2 (UAL2.fbx, CC0,
same skeleton, git-ignored like the first) has them: put it next to
UAL1_Standard.fbx and kke_demo plays
SafetyVaultfor vaults (one hand on the top, legs swung to the side),ClimbUp_1mfor climbs up walls under 1.6 m,ClimbUp_2mabove that.
Those clips are made in place: the pelvis rises up to 0.8 m and comes back
down at the end. Locomotion already moves the capsule up and over, so
AnimationSet::removeLift takes the vertical travel out of them (the
forward and sideways body motion stays). And they aren't played by the
clock: Animator::setProgress poses them at Locomotion's progress through
the move, so a vault that takes 0.4 s or 0.8 s (it depends on the entry
speed) still puts the hand down on the edge. Other players see the same
clips: the network state carries the obstacle height during the move.
Without volume 2 the stand-ins are used: the tucked jump pose for the vault, the take-off reach and a crouch step for the climb. There is no hang or shimmy clip in either volume (the hang uses the slowed fall pose).
After the Animator, two small modifiers run in a fixed order
(engine/include/kke/AnimRig.h, PointDown's SkeletonModifier3D idea):
- Feet on the ground. Each foot keeps its animated lift above the ground under it, the hips drop when one foot has to go lower than the capsule's floor (stairs, slopes, rock), and analytic two-bone IK bends the legs. Each foot also tilts to lie along the ground under it (the ray's normal, at most 30 degrees), keeping the animated foot angle on top of that. Smoothed over frames, and off in the air.
- Hands on the edge. During the first part of a vault or climb, the hands go to the top edge the probe found, shoulder-width apart, with the elbows bending out and back. It stands in for the hand plant the stand-in clips don't have.
Any Synty character can wear the UAL clips. Bones pair up by name
(UAL and Synty both follow the Unreal mannequin, give or take case and
Synty's indexFinger/finger), and each paired bone copies the source's
rotation change from its rest pose, turned by the difference in facing
(UAL faces -Z, Synty +Z); the pelvis travel scales with leg length. The
match logs which bones stayed at rest (Synty's eyes, eyebrows and toes).
Importing animated 3D characters (a0_JVEY7sbY) puts it as a contract: a
clip only means something for the skeleton it was made for, so a
mismatch should be visible, not silent. Try it in kke_demo's Character
panel, or KKE_CHARACTER=SK_Character_Father_01 ./kke_demo.
Root motion is available as data: AnimationSet::extractRootMotion
moves a bone's horizontal travel out of the clips into a track, the clip
plays in place, and Animator::rootMotion() reports the travel each
update for the game to apply. Locomotion clips don't use it (the
controller moves the capsule, and the blend space follows the measured
speed); it's for authored moves such as a real vault clip.
Not done yet (next)¶
- CCD for longer chains (IK fundamentals with CCD, 8pX6LeZdpOo); the legs and arms use the analytic two-bone solve.
- Animation layering (upper body over locomotion; Animation layering pipelines, Fsa2wxyQvzM, blocked below).
- Rest-pose matching for retargeting between skeletons whose rest poses differ a lot (A-pose vs T-pose arms); UAL and Synty are close enough.
Transcripts¶
Fetched 2026-09-26 as auto-captions. YouTube blocks this server after about a dozen requests, so these videos didn't come through: God Tier 3D Character Controller (qIf5YQ8qJng), Detect climbable ledges (yxWxHfjNpa4), Animation layering pipelines (Fsa2wxyQvzM), Post-start melee attack redirection (WGZ-QG-0cpw), Use professional AAA practices (FgO5edghqRE). Importing animated 3D characters (a0_JVEY7sbY) came through on a retry. Their principles overlap with the videos above; retrying from another network would complete the set.