The standing controller works and is worth reading. The walker is not, and is published as a documented failure rather than a result.
It travels 27 m at 1.35 m/s and stays upright on 8 of 9 seeds, and in stills it looks like a walk. In motion it does not: measured over fifteen seconds it vibrates its joints at half their range every frame, its trunk rises and falls 276 mm where a real walk moves about 40, and a quarter of the distance travelled is the feet sliding along the ground rather than pushing off it. Those three numbers are what "it looks buggy" means, and no amount of distance makes it a walk.
It is not a physics problem, and the standing controller is the evidence — same body, same solver, same contacts, standing fifteen seconds against 260 N shoves. A 3D engine would not help: it adds lateral balance, an entirely new failure mode, on top of an unsolved sagittal one.
What it needs is action smoothing plus explicit bounce and slip penalties, and then enough training to relearn under a lagged actuator — an attempt at 220 generations regressed to 1 of 6, which is the expected cost of changing the actuator, not evidence against the fix. That work is not done here.
Read this repo for the rig, the assertions, and the failure analysis below. Do not read it for a walk.
A biped that learned to stand, and a walk that did not come off.
Aplomb is French for uprightness, and English for keeping your poise when shoved. Seven rigid bodies, six hinges, and a neural net choosing joint torques sixty times a second. Nothing here is animated: it is upright only while the net keeps pushing correctly against the ground.
Four frames of one stride. Every joint angle in them is the rigid-body simulation; the mesh is machin-pantin's, skinned to a skeleton read straight off the bodies.
Same figure, same seed, same shoves. Left: the evolved controller. Right: no controller at all.
Box2D via machin-physics, evolved with tinybrain, in machin (MFL).
./build.sh # bin/aplomb (2D debug view) and bin/pantin3d (the mesh)
./build.sh test # headless assertions, no window
./build.sh train 900 72 --walk # evolve a walker (~18 min)
./build.sh train 250 64 # evolve a stander (~4 min)
./build.sh race # the learned controller vs the animation
./bin/pantin3d # the walker, wearing pantin's mesh
./bin/pantin3d --bones # the skeleton the mesh is following
./bin/aplomb # the stander, being shoved (boxes)
./bin/aplomb --limp # the same figure with no controller
Walking, by imitation. A 20 → 18 → 6 net walks 27 m in twenty seconds — 1.35 m/s against the reference's 1.4 — still upright on 5 of 6 held-out starts, from 700 generations at population 64.
It is imitating machin-lowpoly's
procedural gait: tools/refwalk.src samples that walk once into a table of
sagittal joint angles, and the net is rewarded for tracking it while the
physics decides whether the figure stays up. The animation supplies the
style; the physics supplies the reality. The reference knows what a walk
looks like and nothing about balance or contact; the controller knows nothing
about walking and only how to push. Neither half does the job alone.
Standing. A separate 16 → 18 → 6 net, evolved for 250 generations against the worst of four shove sequences, stands the full fifteen seconds on 6 of 6 held-out seeds, in 231 seconds. Untrained, the same body falls in 37 steps — six tenths of a second.
They are two controllers, not one: disturbance rejection and locomotion are different problems, and asking for both at once produced a figure that braced rather than walked. The walker trains with the shoves off.
A gait cannot be judged by its fitness — the first version scored well and was
garbage. These six numbers decide it, and distance is the least useful:
| lunge | track-in-place | imitation | |
|---|---|---|---|
| step alternations, 15 s | 0 | — | 43 |
| double support | 0% | — | 9% |
| foot slip | 5.04 m | — | 2.64 m |
| torso lowest | 0.81 m | — | 0.95 m |
| distance | 17.9 m | 0.27 m | 27 m |
Zero step alternations means the feet never swapped — it was not stepping at all, and no amount of distance makes that a walk.
The first attempt failed for a reason that was not the reward: the knee limit
had the sign inverted. A joint angle here is child - parent and a knee
flexes negative, but the limit was [0.0, 2.30] — 132° of hyperextension and
zero flexion. The body was stiff-legged by construction, so the bound it
found was the best available to it, and that was read as a reward-shaping
problem for a whole 18-minute run. The limits now come from the reference's own
measured ranges.
Then the task term had to be right, and both obvious forms fail. Rewarding distance heavily paid about 0.61 a step against a tracking term worth under 0.2, so lunging paid three times better than walking. Rewarding it lightly paid nothing, so the best strategy became tracking the reference while standing still — 1002 fitness for 0.27 metres. A speed target has no such extreme: standing still and lunging are both far from 1.4 m/s and both score near zero.
And before blaming the controller: ./bin/pantin3d --ref poses the body
straight from the table with no physics at all. If that does not look like
a walk the reference is wrong, and a controller faithfully tracking a garbled
reference is indistinguishable from one that failed to learn.
This repo exists next to machin-pantin, which is the same family's other answer to making a figure move.
| machin-pantin | machin-aplomb | |
|---|---|---|
| joints get | positions, computed | torques, chosen by a net |
| upright because | it is drawn that way | it pushes against the ground |
| shove it | nothing happens | it falls, or catches itself |
| knows how to walk because | the author did | it worked it out |
Animation is cheap, exact, and cannot be surprised. Learned physics is expensive, approximate, and cannot be faked.
A learned controller will find any cheat the body offers, so the body is pinned down first. The suite runs before training and is about the simulation, not the net:
- A limp biped must fall over. If it stands by itself the joint limits are doing the standing and there is nothing left to learn.
- Driving its own joints must buy no horizontal momentum. A joint torque is an equal and opposite pair; applying it to one body alone is a reactionless drive, free momentum from nowhere, and evolution finds that within a few dozen generations and then hovers. Measured airborne in zero gravity: 3.9e-06 kg·m/s on a 77 kg figure after sixty steps of full torque.
- The two legs must not shove each other apart. They share a plane, so they go in a negative Box2D collision group.
- No sensor may read absolute position. A controller given its world x learns where it started rather than how to stand, and passes every training seed while falling over anywhere else.
- The same seed must score identically, or evolution selects for luck.
The torso weighed a hundred and eighty grams. Box2D is two-dimensional, so mass is area × density, and three-dimensional intuition for density (~1, like water) gives a figure that a 260 N shove launches at ninety metres a second. Everything downstream looked broken for the wrong reason. Densities are now per unit area and the figure comes to 77 kg.
Two tests were wrong before the code was. One teleported a jointed figure to get it airborne, which violates every joint at once and measures the solver's recovery rather than the drive. The other measured a shove over four hundred steps and took the peak — by which time the biped had collapsed and was being dragged along the ground as a tangle. It reported twenty metres a second and proved nothing.
And momentum is Σmv, not Σv. The conservation test summed velocities, which is not conserved when the segments have different masses, and read as the drive leaking momentum when it was not. Box2D exposes no mass accessor, so the masses are computed from area × density where the bodies are built.
b2BodyId and b2WorldId are cstructs, and a user struct with a field of
that type compiles to C that passes an int where the handle is expected.
Every use of b2BodyId in machin-physics is a []b2BodyId for this reason.
So the biped keeps its bodies in a slice indexed by constants, and the
simulation keeps its world in a one-element slice. Both read fine; the
point is that the obvious spelling does not compile and the error is a wall of
C type mismatches rather than anything about MFL.
The other repo in this family for making a figure move is machin-pantin, which plays machin-lowpoly's procedural gait. So the animation gets to drive this body too: the gait supplies target joint angles, a PD servo per joint turns them into torque, and it runs on the same seeds against the same shoves.
./build.sh race
seed neuro gait
5 stood fell t=58
13 stood fell t=58
...
NEURO 8/8 mean fitness 2304.6
GAIT 0/8 mean fitness 108.3
The animation cannot stand up, and not because the servo is weak. A swept grid of gains (kp 3…120 × kd 0.4…4.0, a fortyfold range) never keeps the figure up for more than about a second:
gait survival vs stiffness (seed 41, of 900 steps)
kp=3:26 kp=16:28 kp=70:58 kp=120:46
That is the whole finding, and it is a structural one rather than a tuning one. A pose is not a policy. Holding fixed joint angles, however stiffly, makes the figure a rigid body pivoting about its ankles — there is no path from "the torso is leaning" to "push differently with the ankle", because the animation never had a torso-lean input. Stiffening the servo makes it a more rigid thing that falls over just as surely.
It is also not an argument that animation is bad. It is an argument that these two do different jobs: the gait knows what a walk looks like and has no idea what to do when pushed; the net has never seen a walk and knows only how to not fall. The interesting system is probably both.
- The walk is deprecated — see the notice at the top. It vibrates, pogos 276 mm and skates a quarter of its distance. One held-out start in six also falls outright, at about a second in.
- A reference-angle bug found late and left unfixed here: the reference ankle carries a constant −25.4° offset, because machin-lowpoly's ankle-to-ball runs down and forward while aplomb's foot box is built flat. Feeding the raw table tells the figure to point its toes on every frame. The fix is to subtract the reference body's own rest pose on load; it is described here rather than applied, because the committed champion was trained without it and shipping both together would be incoherent.
- "PD-track the reference" is not a test of the physics. It fails at every gain, and that is expected: open-loop replay of a gait on a physical biped is unstable without balance feedback. It was used here as a decisive check and it is not one.
- It is imitating, not inventing. The gait looks right because the reference is right; what the net contributes is making it survive contact and gravity. A walk discovered from nothing is a different and much harder claim, and this repo does not make it.
- Roughly half the cycle registers as airborne by the foot-height test, where a real walk has no flight phase at all. Part of that is the test — a pitched foot lifts the box centre — and part is a real slight bounce.
- The mesh is 3D and the simulation is not. The physics is planar, so the
legs are placed at ±a hip half-width for display, and the arms — which a
sagittal biped does not have — are hung from the chest and swung against the
opposite hip. Those two things are the only parts of the picture the
simulation did not compute, and they are marked as such in
55_pose3d.src. - The gait baseline is a pose, not a walk. It holds a standing posture through PD servos; a fairer opponent would be a hand-written balance controller (an ankle strategy), which is the cartpole-vs-LQR analogue and is the next baseline worth building.
- 2D, sagittal plane. 3D humanoid locomotion is research-scale and would not converge at this size.
- The stance is wide and a little splayed. It is what the fitness asked for; a narrower stance would need a term that says so.


