ruiciro.devfield notesworkgithub

October 22, 2026

Angles survive a ×0.01

Ruiciro RiveraSenior AI engineer
& game developer

The robot from the last post is in Unreal now. The approved look came first. Then the hard half: Manny’s animations retargeted onto a character with spring legs, a post-process rig that compresses those springs, and a test that walks, runs and jumps it. That gate was signed today. Along the way three checks reported a pass while the thing they guarded was broken, and none of them was wrong about what it measured.

The robot’s boots from the side, same walk frame twice. Left, post-process rig off: both boots tilted and sunk into the floor. Right, rig on: both boots flat on top of the floor.

Angles survive a ×0.01

The first retarget had a check, written before it ran: the retargeted idle had to leave the robot’s arm within 3° of its rest angle. It came out 1.8° off, so it passed. In play-in-editor the robot collapsed into a ball.

Every child bone in the retargeted animations was 100 times too short. calf_l sat 0.123 from its parent where the skeleton has 12.3 cm. The cause was in the export. Blender kept the armature at scale 1 and the bones in metres, and the FBX exporter wrote the conversion as a scale of 100 on the armature node. Unreal folded that node into the root bone, so the skeleton’s reference pose had a root at scale 100 and every child in metres. The mesh looked right because the root multiplied everything back. The retargeted animations wrote the root at scale 1.

An angle between two bones doesn’t change when every translation is multiplied by 0.01. The check measured a quantity the bug left alone, so it was going to pass whatever the translations were.

The export now bakes centimetres into the file and refuses to export if the rest pose no longer matches the bind mesh. The new check reads translations: every bone except root and pelvis within 0.05 cm of its reference, at five points of each animation. Before fixing anything, it ran on the broken animations:

translations   max deviation 26.31 cm   FAIL

After the fix, 0.000 cm on all 20. The pelvis height on its own would have passed on the broken set too, which is why it isn’t the check.

Nothing moved

The spring compression is driven by a curve the post-process rig reads. The measurement sets the value on an actor in the editor, waits, reads the posed mesh and checks pelvis drop, planted feet and coil spacing.

The editor doesn’t re-evaluate an animation graph unless the component is told to update in the editor. Without that flag the value changes, the curve the rig reads stays at 0, and the mesh is the rest pose every time. The rest pose has planted feet and evenly spaced coils, so those checks passed. They were reading a robot nobody had touched.

The script now reads back the curve the rig actually received and fails unless it equals the value it set. Each case is also read twice, a second apart, and must not move between reads: there is no call that forces an animation update, so a stable second read is the evidence that the pose was evaluated.

Yesterday’s JSON

The heavier scripts run as editor commandlets that write their results to a JSON file. During the foot work, a run of the retarget script came back with the same numbers as the day before. It looked like a change that did nothing.

The script had never run. A relative -script path is resolved against the engine’s binaries folder:

LogPython: Error: Could not load Python file
  '.../UE_5.8/Engine/Binaries/Win64/Tools/build_retarget.py'
  (resolved from 'Tools/build_retarget.py')

Yesterday’s JSON was still in Saved/, and the agent read it as the result. What gave it away was a field the new code writes and the file didn’t have.

The runner now passes absolute paths and deletes every output file before each run. A run that doesn’t happen leaves no file, and a missing file is an error.

Watch it fail first

The three have the same shape. The check was real and measured what it said. What it measured couldn’t change under the bug: angles under a uniform scale, a rest pose that was never re-posed, a file that outlived the run that should have replaced it. An earlier experiment had the same thing in a smaller form: two exports “gave the same FBX” because they had the same size. The files differed in 274 places.

The rule that came out of this is a narrower version of the one in The tool said true: before trusting a pass, run the check against the broken version and watch it say no to that specific bug. Write the criterion down before the run. If there’s no broken version to hand, break one on purpose. The translation check earned its place at 26.31 cm, FAIL. The angle check never had to.

Eight centimetres under the floor

The locomotion test was built that way. Its criteria were written before the first run: walk and run speeds, the speed the animation thinks it’s at, the jump rising and landing, and the lowest vertex of the posed mesh against the floor. Speeds passed to the decimal. The feet failed: 8.34 cm under the floor walking, 8.84 running, 2.43 after landing.

Measured in the retargeted animations alone, the walking sole went down to −10.71 cm and its highest point in any frame was −0.07. The whole cycle was underground. The retarget copies Manny’s leg rotations onto legs that are straight springs, splayed out from the hips, with an ankle 20 cm above a large boot.

Four fixes didn’t hold up:

  • The template’s foot IK node. An A/B with three robots on the floor: with the node, the left boot turned toe-up and the right one went under the floor. 130 of the 161 bones that rig expects don’t exist on this character.
  • A limb IK solver per leg in the retarget. The walking sole went from −10.71 to −7.39 cm, but more frames ended under the floor and the shins changed length by up to 0.93 cm.
  • The foot as its own chain, inheriting less of Manny’s rotation. The blend happens in the foot’s local space, so at zero the boot stays rigid on a tilted shin. Worst sole −11.65, −13.75, −14.66, −15.31 cm as the inheritance went down.
  • Full-body IK on the legs. The sole came up to −4.57 cm and the knees folded to 141–169°. Frames past the knee limit went from 291 to 725.

The same walk frame on three robots: the left and middle ones with the foot IK node, legs and boots twisted; the right one without it, standing straight.

A measurement settled it: compute the sole as if the boot were perfectly flat, at the same ankle position. Walking still came out 5.26 cm under, landing 9.72. The boot’s tilt was about half of the walking problem and almost none of the landing. The ankle itself was too low, so nothing that only rotated the foot could pass.

The spring was already the answer

The rig already knew how to make a leg shorter. Compression slides the hip end of each spring along its own axis, and the coil helpers move 1/3, 1/2 and 2/3 of the distance so the coils stay evenly spaced, up to 39% of the spring’s length. That mechanism had passed its own measurements the day before.

The fix is a pass in the same post-process rig, before compression. For each foot: trace down from the ankle to the floor. Flatten the boot against the surface it hit while keeping the direction it points. If the sole is still under the floor, raise the foot by the difference. The compression then plants the foot on that raised point instead of the animated one, so the lift is one more compression of the spring, with the same coil spacing and the same 39% cap.

Two measurements decided it. With the feet on the floor nothing changes, and the compression measurement came back identical to the saved one, field by field, with no difference over 0.001. In play-in-editor, lowest vertex against the floor: idle 2.12 cm above it, walking 0.00, running 0.37, landing 0.00.

The robot in play-in-editor, four frames from behind: standing, walking, running and just after landing, boots on the floor in all four.

The 0.37 is the spot where the robot steps onto a raised piece of the test level while the floor is traced under its centre. The animations are still Manny’s and they’re filler. What this gate asked for was a character that walks, runs and jumps without breaking, and that is what it does now.


Ruiciro Rivera — Senior AI engineer, AI enthusiast, and builder of worlds. By day I build production LLM systems; by night I ship my own products with Claude — and, occasionally, a video game. Find me on LinkedIn and GitHub.

← back