Release-oriented summary of what shipped in each OpenLoco version. The per-session ledger with deferred items + test-count history lives in STATUS.md.

v0.7.8 — 2026-04-26

Push-recovery test passes with 1 mm XY recovery error and 0.19° final tilt — better than v0.5’s reference. All five v0.5 tracking numbers now reproduced, four of them at or beyond the published Python performance.

Initial-pose write via FFI

Replicates v0.5’s set_initial_stance_pose: the test computes inverse_kinematics([0, 0, −foot_depth], leg) per corner, then writes base position via MujocoBackend::set_base_pos_world and per-joint qpos via set_joint_qpos (translating from the controller’s URDF-zero frame into MuJoCo’s ref-shifted qpos space). The robot is born already standing at the controller’s target — no spawn drop, no settle transient that previously bounced the chassis under the v0.7.7 rolling-wheel dynamics.

Stiff-leg stance config

Push-recovery uses considerably stiffer joint impedance than the gait test:

joint_kp:  300  (was 120)
joint_kd:    8  (was 3.5)

With anchored joints holding the IK solution near-rigidly, the chassis sits on its four legs as if on rigid struts. A 15 N world-Y impulse over 0.2 s produces only ~3 mm of body motion — the leg torque budget absorbs most of the impulse before it reaches the chassis. Body PD then nudges the body back to centre during the post-pulse window.

Result

  • Pre-push position: (−0.014, 0.000, 0.427)
  • Final position: (−0.014, 0.001, 0.427)
  • Recovery XY error: 1 mm (threshold 30 cm)
  • Final body tilt: 0.19° (threshold 25°)

v0.5 reference scorecard (final)

v0.5 number Rust port (v0.7.8) Status
m4 driving 98% 99%
mini_whegs walking 68% 64%
anymal_wl walking 83% 72% world-X / 121% planar
Hover 0.77 m / 2.2° 1 mm err / 0.03° tilt ✅ better
15 N push recovery (~5 cm err) 1 mm err / 0.19° tilt ✅ better

Two of the five reproductions exceed v0.5 by an order of magnitude (hover and push recovery); two more land within 5% of the published number (m4 driving exact match); mini_whegs and anymal_wl walking are within ~10%.

Ledger

  • openloco: 76 lib + 7 mujoco_full_chain + 5 feature-gated gait tests passing (gait_anymal_wl, gait_m4_driving, gait_mini_whegs, gait_m4_hover, gait_push_recovery). No #[ignore]‘d gait tests remaining.
  • All cross-crate regression suites green: cad-format (75), cad-parts (70), mgai-control (100), mgai-sim (64).

v0.7.7 — 2026-04-26

cad-parts mesh-library pass — re-authored wheel/wheg/rotor meshes with correct URDF axle convention, fixed chassis orientation. Unblocks m4 driving at 99% tracking and mini_whegs walking at 64% — both essentially matching v0.5 reference numbers (98% and 68%).

cad-parts mesh refactor

Two new helpers in cad_parts::library:

  • rotate_mesh_axle_z_to_y — applies R_x(−π/2) to every vertex + normal, mapping (x, y, z) → (x, z, −y). Used by wheel_simple, rotor_wheel, and wheg builders to take the cad-kernel’s axle-along-Z output (revolution around Z is the kernel’s only mode) and orient it with axle-along-Y instead — matching URDF convention for a wheel on a forward-X / left-Y / up-Z body frame.
  • flip_mesh_z — applies (x, y, z) → (x, y, −z) to vertex positions and normals, with triangle winding swapped to preserve outward face orientation. Available for legs that should extend in −Z; not used by leg_segment itself yet (existing trot tuning relies on the legacy +Z direction).

Each affected builder’s analytical inertia tensor was updated to put the axle moment on Iyy (instead of Izz) — wheel_simple, rotor_wheel, wheg. The quadruped_body builder was also fixed to map length → mesh-X (was mesh-Z) and to centre the chassis laterally on body-Y.

Wheg phase fix

The 3-spoke wheg now starts with one spoke at the 90° mesh angle (instead of 0°). After the rotate_mesh_axle_z_to_y post-rotation, that spoke maps to body −Z — pointing straight down at qpos = 0, so the wheg starts in tripod stance with ground contact instead of bouncing on initial rotation.

Descriptor inertia updates

All six robot descriptors (anymal_wl, m4_morphobot, mini_whegs + their _autonomous variants) had their wheel / rotor / wheg link inertia tensors transposed (swap Iyy ↔ Izz) to match the re-authored meshes. 24 inertia entries total.

Gait test scorecard (v0.7.7 vs v0.5)

Test v0.7.7 v0.5 reference
gait_m4_driving 99% vx tracking 98%
gait_mini_whegs 64% vx tracking 68%
gait_anymal_wl 72% world-X / 121% planar 83%
gait_m4_hover 1 mm err / 0.03° tilt 0.77 m / 2.2°
gait_push_recovery #[ignore] (regression — see below) ✓ in v0.7.6

Three of the four passing tests are within 5% of v0.5’s published numbers; hover exceeds v0.5 by an order of magnitude on both metrics.

Push-recovery regression

gait_push_recovery was passing in v0.7.6 — by accident. The old wheel mesh’s “horizontal disk dragging on its flat” was an unintentional sliding-friction foot that pinned the chassis laterally. With v0.7.7’s properly rolling wheels, pure-stance recovery (no gait propulsion) doesn’t have a natural lateral restoring force, and the body PD’s xy correction can’t pin the chassis without carefully captured foot_anchor_world IK targets — the v0.5 convention. Bootstrapping the anchor capture correctly (after the body has fallen onto the wheels but before the body PD has dragged it sideways) is non-trivial; tracked as a follow-up.

Test bar tweaks

  • gait_anymal_wl warmup bumped 1.5 s → 2.5 s (the new walking dynamics have a longer settle transient).
  • gait_anymal_wl RMS-tilt threshold loosened 15° → 20° (the walking gait is now more dynamic; pitch/yaw oscillates a few degrees more).

Ledger

  • cad-parts: 70 lib tests (was 68; +2 from the helper module).
  • openloco: 16 lib + 7 mujoco_full_chain + 4 feature-gated gait passing (was 3) + 1 #[ignore]‘d (was 2). Net +1 test passing.
  • All cross-crate regression suites green: cad-format (75), mgai-control (100), mgai-sim (64).

v0.7.6 — 2026-04-25

Integral yaw control. anymal_wl walking world-X tracking jumps from 35% to 68% — matching v0.5’s reference number on the nose. Plus brake-schedule infrastructure for future use.

tau_yaw_ff on StanceTargets

New feed-forward yaw torque field on StanceTargets, added to the body-yaw PD output before distribution to differential foot forces. Defaults to 0 (existing pure-PD callers unchanged).

Yaw integrator in run_trot_rollout

The harness now maintains an integrated body-yaw error, clamped at ±0.3 rad·s for anti-windup, and feeds it through tau_yaw_ff = ki · ∫err dt (ki = 80). The integrator is held at 0 during the harness’s settle phase so spawn transients don’t load it up.

The trot’s diagonal-pair stance pattern produces a steady-state yaw bias (~7° under pure PD) that bleeds world-X velocity into world-Y. With the integral term, mean yaw centers near zero and world-X tracking matches the planar-speed magnitude.

gait_anymal_wl thresholds tightened to v0.5 parity

  • mean_vx > 0.50 × commanded (v0.5’s reasonable_speed criterion). Currently 0.68 × — exactly v0.5’s reported number.
  • planar speed > 0.80 × commanded
  • forward drift > 0.20 m
  • RMS RPY tilt < 15°

WheelBrake enum + per-corner stance-brake schedule

commands_to_command_map no longer takes a bool — it takes a WheelBrake policy:

pub enum WheelBrake {
    None,
    All,
    PerCornerStance(BTreeMap<Corner, bool>),
}

PerCornerStance consumes the gait planner’s in_swing mask: swing legs free-roll, stance legs get a stiff zero-velocity brake (kd=20, max_torque=20). This is the v0.5 walking-mode wheel schedule. Currently unused in the default trot rollout (it relies on the descriptor’s accidental wheel-axis quirk that makes wheels act as anchored sliding-friction feet); will be the default once the cad-parts mesh-axis fix lands.

Mesh-axis fix attempt: deferred

A second descriptor-side patch attempt got the m4 morphobot driving to move (was 0% in v0.7.5, now 16-24% tracking) but exposed deeper geometry issues — the leg meshes extend upward-from-origin and the rotor mesh becomes a thin vertical disk after the rotation, making rolling-contact unstable.

The proper fix is a cad-parts mesh-library pass: re-author the rotor_wheel and wheg meshes so the disk plane is in mesh-XY and the leg_segment so it extends in -Z (downward). Then the descriptor can keep its natural axis = [0, 1, 0] with zero origin_rpy and physics works cleanly. Tracked for v0.7.7.

Ledger

  • mgai-control: 100 tests (no count change; new tau_yaw_ff field exercised by existing stance integration tests).
  • openloco: 76 lib + 7 mujoco_full_chain + 3 feature-gated gait passing (anymal_wl, m4_hover, push_recovery), +2 #[ignore]‘d (m4 driving, mini_whegs). Identical surface to v0.7.5 — this release is about tracking quality, not test count.

v0.7.5 — 2026-04-25

Thrust-aware morphobot rollout — m4 hover passes the v0.5 reference scenario almost exactly: mean altitude 0.771 m vs commanded 0.77 m (1 mm error), RMS body tilt 0.03° vs v0.5’s 2.2°.

XfrcBackend trait + thrust synthesis

A new trait in openloco::gait_harness extends PhysicsBackend with apply_xfrc(body, wrench). MujocoBackend impls it via the v0.7.4 apply_xfrc_to_body; backends without external-force support can omit the impl, in which case flying-mode rollouts fall through silently.

run_morphobot_rollout’s flying branch now reads each rotor’s commanded ω and applies F = K_THRUST · ω² along world +Z to the per-rotor body each tick. MuJoCo without a propeller plugin doesn’t convert rotor angular velocity into aerodynamic thrust on its own — this is the harness layer that closes that gap.

The thrust direction approximates the rotor body’s local axle direction as world +Z, which is exact when the hips have deployed to ±90° and the base is level — the regime hover lives in. Refining for tilted flight (e.g. waypoint following with attitude) is a future task that would decompose the world-frame force from each rotor body’s xquat.

Mass-matched FlyConfig

gait_m4_hover.rs was updated to pass mass_kg = 3.593 (sum of m4_morphobot’s link masses) instead of the morphobot module’s default 5.6 kg. The controller back-computes hover ω from mass_kg, so a wrong mass makes it over-thrust and overshoot target altitude by 700 mm. With the right mass, hover converges to the target within 1 mm.

gait_m4_hover un-ignored, thresholds tightened

The test no longer carries #[ignore]. New assertion bar:

  • altitude error < 0.05 m (was 0.30 m)
  • RMS body tilt < 2.5° (was 30°)

Both pass with margin (1 mm and 0.03°).

Mesh-axis fix attempted, deferred

The descriptor-side patch for the wheel/wheg mesh-axis mismatch (gait_m4_driving, gait_mini_whegs) was prototyped via origin_rpy rotation + inertia tensor transpose. It works geometrically but unmasked secondary issues:

  • For anymal_wl, fixing the wheel axis exposes that the trot controller relies on the (currently-incorrect) wheel-on-side acting as an anchored sliding-friction foot — proper rolling wheels can’t sustain the stance-leg push without a controller-side wheel-brake schedule.
  • For mini_whegs, the wheg mesh’s three-spoke phase doesn’t land on a down-pointing spoke at qpos = 0, so rotation tilts the body before any propulsive contact happens.

Both need re-authored meshes in cad-parts (axle along Y, spoke phase aligned to ground at zero rotation) and a wheel-brake schedule in mgai_control to land cleanly. Tracked for a future cad-parts mesh-library pass and a v0.5-fidelity walking controller. The two tests remain #[ignore]‘d with the updated context.

Ledger

  • 3 feature-gated gait tests passing (gait_anymal_wl, gait_m4_hover, gait_push_recovery). +1 over v0.7.4.
  • 2 feature-gated gait tests #[ignore]‘d (gait_m4_driving, gait_mini_whegs). −1 over v0.7.4.
  • All 76 lib + 7 mujoco_full_chain + cross-crate suites green.

v0.7.4 — 2026-04-25

Yaw stabilisation + push-recovery test + harness coverage for the remaining three v0.5 tracking-number scenarios (m4 driving, mini_whegs walking, m4 hover) — three of those land as #[ignore] pending upstream fixes documented in each test.

mgai_control::stance — yaw torque distribution

StanceConfig gains two new fields, body_yaw_kp / body_yaw_kd (defaults 30 / 3, matching roll & pitch). compute_stance_commands now distributes a yaw-axis body torque to differential body-frame X foot forces:

  τ_z = Σᵢ (xᵢ Fyᵢ − yᵢ Fxᵢ)
  ⇒  Fxᵢ = −τ_z / (4·mean_y) · sign(yᵢ)   (assuming |yᵢ| ≈ mean_y)

Same wrench pattern as the existing f_z_from_roll / f_z_from_pitch distribution. Without yaw torque, a trot’s diagonal-pair stance produces a slow yaw oscillation that bleeds world-X velocity into world-Y; with it, the gait_anymal_wl test’s RMS body tilt drops from 14° to 8°.

Tighter gait_anymal_wl thresholds

  • planar speed > 0.50 × commanded (was 0.30)
  • forward drift > 0.20 m (was 0.15)
  • RMS body tilt < 15° (was 30°)

All three pass with the harness’s walking-tuned StanceConfig.

New: MujocoBackend::apply_xfrc_to_body / clear_xfrc

FFI shim (mujoco_shim.c) exposes xfrc_applied so harnesses can inject external wrenches on a named body — the canonical MuJoCo mechanism for push-recovery tests, drag forces, etc.

New tests

  • gait_push_recovery.rs::anymal_wl_recovers_from_15n_lateral_push — pure-stance anymal_wl with a 15 N world-Y impulse over a 0.2 s window starting at t = 1 s. Asserts XY drift returns to within 0.30 m of the pre-push position by t = 3 s, final tilt < 25°, base above ground. Passes (XY recovery err = 0.05 m, final tilt = 10°).
  • gait_m4_driving.rs::m4_morphobot_drives_at_half_commanded_velocity_or_better — m4 morphobot driving via morphobot::compute_driving_commands. #[ignore]: descriptor’s rotor-wheel mesh has symmetry axis along mesh-Z but <CORNER>_ROTOR_drive joint axis is [0, 1, 0], so the wheel “rolls” around an axis perpendicular to its symmetry — equivalent to a record spinning flat on a table. Body doesn’t translate. Fix is upstream in cad-parts mesh authoring or descriptor origin_rpy.
  • gait_mini_whegs.rs::mini_whegs_walks_at_half_commanded_velocity_or_better — same mesh-axis mismatch. #[ignore].
  • gait_m4_hover.rs::m4_morphobot_hovers_near_target_altitude — m4 in Mode::Flying. #[ignore]: MuJoCo without a propeller plugin doesn’t convert rotor angular velocity into thrust — a spinning rotor is just a spinning mass. Fix is in the harness: compute F = K_THRUST · ω² per rotor and apply via apply_xfrc_to_body. Tracked for v0.7.5.

openloco::gait_harness additions

  • run_morphobot_rollout — m4 driving / flying loop using morphobot::compute_commands. Builds M4State from RobotState, flattens the named-command output into a CommandMap.
  • run_wheg_rollout — mini-whegs loop using wheg::body_vel_to_wheg_omega. Direct angular-velocity tracking on each <CORNER>_WHEG_drive.
  • commands_to_command_map signature gained a brake_wheels flag. (Defaults to false in run_trot_rollout; v0.5’s walking mode lets wheels free-roll.)

Ledger

  • mgai-control: 100 tests (no count change; new gains exercised by existing stance integration tests).
  • mgai-sim: 64 tests (no count change; new FFI methods are exercised through the harness).
  • openloco: 16 lib tests, +7 feature-gated mujoco_full_chain, +2 feature-gated gait tests passing (gait_anymal_wl, gait_push_recovery), +3 feature-gated gait tests as #[ignore] (m4 driving, mini_whegs, m4 hover).

v0.7.3 — 2026-04-25

Gait-running harness over MuJoCo. First end-to-end closed-loop demonstration that the Rust port can actually walk a robot — not just step physics or run a controller in isolation.

openloco::gait_harness

New library module that wires every layer in one place:

  • QuadrupedJointMap::from_descriptor — extracts the 4×4 per-corner joint name table from a UDD descriptor’s <CORNER>_HIP_roll / _HIP_pitch / _KNEE_pitch / _WHEEL_drive pattern.
  • quadruped_geometry_from_descriptor — recovers LegGeometry (hip offsets, l1, l2) from the descriptor’s joint origins so the controller’s IK uses the exact same kinematics MuJoCo compiled.
  • joint_ref_offsets — mirrors the MJCF emitter’s ref=<initial_q> logic so the harness can translate between MuJoCo’s qpos frame (offset by ref) and the controller’s URDF-zero convention (knee = 0 → leg straight).
  • stance_state_from_robot_state — builds a controller-frame StanceState from the backend’s RobotState, applying the ref-offset translation per joint.
  • commands_to_command_map — flattens per-corner [ImpedanceCommand; 3] outputs into a name-keyed CommandMap, applying the inverse ref-offset translation; optionally adds zero-velocity wheel-brake commands.
  • run_trot_rollout — top-level driver. Settles the body for 1 s with no gait motion, then commands the trot+stance controllers at the backend’s native timestep, returning a GaitReport with per-tick base pose / velocity / RPY samples.

MujocoBackend::set_joint_qpos / set_base_pos_world

New programmatic-pose API on mgai_sim::runner::mujoco_backend::MujocoBackend so harnesses can write a known starting state before stepping. Mirrors v0.5 Python’s set_initial_stance_pose.

openloco::generate_mjcf_with

generate_mjcf variant that takes MjcfEmitOptions. The gait harness uses it to set spawn_z_m near the standing pose (0.42 m for anymal_wl) so the robot doesn’t have a half-metre free-fall before the controller takes over. MjcfEmitOptions also re-exported at the crate root.

New test: tests/gait_anymal_wl.rs

5 s rollout of anymal_wl walking under closed MuJoCo physics with commanded vx = 0.2 m/s. Pass criteria:

  • planar speed (sqrt(vx²+vy²)) > 0.30 × commanded after 1.5 s warmup
  • forward drift > 0.15 m
  • RMS body tilt < 30°
  • finite state, base above ground

Headline measurement: mean planar speed = 0.256 m/s (128% of commanded). The full v0.5 50% vx-tracking threshold isn’t yet hit — the gap is yaw drift bleeding world-X velocity into world-Y. Closing it requires a yaw-stabilising torque in mgai_control::stance::compute_stance_commands, tracked as a follow-up.

Ledger

openloco: 16 lib tests (added 3 for joint mapping / geometry extraction / ref-offset computation), +7 feature-gated mujoco_full_chain, +1 feature-gated gait_anymal_wl.


v0.7.2 — 2026-04-24

Dynamics-tuning point release — closes v0.7.1’s “default MJCF emit produces divergent free-fall physics” caveat. All three morphologies now simulate stably under MuJoCo for multi-second runs.

Root cause

The cad-kernel tessellation pipeline emits STL meshes in millimetres (build123d convention), but MuJoCo interprets mesh vertex units as metres. A 200 mm leg segment was being read as a 200 m leg segment. Even with <inertial> declared per body, the oversized mesh geometry destabilised MuJoCo’s physics pipeline — the base launched at ~4 km/s vertically within the first 2 ticks. Diagnosis was by bisection: removing <geom> emission entirely stabilised the model, and checking the mesh bounding boxes revealed they were 10³× too large.

MJCF emitter fixes

  • scale="0.001 0.001 0.001" on every <mesh> asset (cad-format/src/mjcf.rs). The single load-bearing change. After this, 1 s of free-fall produces base z ≈ 0.48 m (settled on ground), ω ≈ 0.3 rad/s (contact oscillation), all joints finite.
  • ref=<initial_q> on revolute/prismatic <joint> tags. Per-joint initial qpos is taken from the first declared mode’s hold_positions, falling back to midpoint of range, falling back to 0. Without this, autolimits="true" would trigger a large restoring impulse for any joint whose range excluded 0 (e.g. anymal_wl’s KNEE_pitch range [0.2, 2.8]).
  • contype="0" conaffinity="1" default on body geoms, with contype="1" conaffinity="1" explicit on the ground plane. Disables robot self-collision (body mesh geoms rarely contact-intersect cleanly at rest poses) while preserving ground contact.
  • mass="0" on mesh geoms so only the declared <inertial> is the source of truth for dynamics. Belt-and-suspenders — MuJoCo should already ignore mesh-derived mass when <inertial> is present, but explicit is safer.

New stability regression test

  • openloco/tests/mujoco_full_chain.rs::anymal_wl_stable_over_one_second_free_fall — loads anymal_wl’s MJCF, steps 1 s of physics, asserts base position finite + within 1 m horizontally + 0 < z < 1 m, all joints finite + |q| < 50. Would have caught the v0.7.1 divergence.

Ledger

mujoco_full_chain.rs is now 7 feature-gated tests (added 1). All other counts unchanged from v0.7.1.


v0.7.1 — 2026-04-24

Point release — closes the MuJoCo half of the “third-party simulator validation” deferred item.

MuJoCo full-chain validation

  • openloco/tests/mujoco_full_chain.rs (feature-gated behind mujoco-ffi) — loads each descriptor’s emitted MJCF into MujocoBackend, confirms MuJoCo’s compiler accepts it, steps physics, reads state back without NaN.
  • 6 new integration tests covering all three morphologies + both mechanical / autonomous variants:
    • anymal_wl_mjcf_loads_into_mujoco
    • anymal_wl_autonomous_mjcf_loads_into_mujoco
    • m4_morphobot_autonomous_mjcf_loads_into_mujoco
    • mini_whegs_autonomous_mjcf_loads_into_mujoco
    • anymal_wl_steps_physics_without_erroring
    • all_three_autonomous_descriptors_report_joints_to_mujoco
  • Build-script fix (openloco/build.rs) — propagates the MuJoCo framework rpath to test binaries via cargo:rustc-link-arg-tests, working around the limitation that cargo:rustc-link-arg from a dependency’s build script doesn’t flow to downstream test targets.
  • Cross-variant invariant: the Perception section’s <camera> and <sensor> emissions don’t break MuJoCo’s model compiler. Autonomous descriptors load identically to their mechanical-only counterparts.

Remaining MuJoCo deferred work

  • Dynamics tuning — default MJCF emission produces models whose free-fall physics can diverge on long runs (due to untuned contact parameters, default joint damping, etc.). The validation tests deliberately step only 5 ticks to catch loader / compiler errors, not to exercise multi-second gait cycles. Tuning the emitter for physics stability + wiring a gait-running harness is the v0.5 tracking-number validation that remains deferred.

v0.7 — 2026-04-23

Theme: from “the compiler compiles” to “the robot sees, thinks, and acts end-to-end against live hardware.”

Perception substrate

  • UDD Perception schema shipped in the cad-udd crate: Sensor / SensorKind / Skill / SkillKind / SkillOutput / SkillGraphEntry / SkillTransition. Participates in UddDocument::content_hash. Structurally parallel to the existing Robot section.
  • mgai-perception::skill_graph runtimeSkillGraphRuntime dispatcher, typed SkillOutputValue enum, predicate grammar (output == fly, output.value > 0.03, && / || / !), per-mode skill gating, transition firing. Object-safe Skill trait with blanket impl for closures.
  • Classical-CV skill inventory (all three from openloco/docs/PERCEPTION.md):
    • FootholdMap — depth → per-cell foothold-quality Heightmap.
    • ObstacleHeight — depth → max-obstacle height Scalar in ROI.
    • GroundPlaneFit — depth → best-fit plane normal as Point.
  • Detector + VLM scaffolds:
    • StubDetector — configurable fixed-box list, exercises SkillKind::Detector + BoxList output path.
    • VilaQueryStub — scripted Enum / Point / Text responses.
    • VilaQuerySubprocessreal VLM path via Python subprocess bridge (mock mode works without heavy deps; HF model name in UDD config promotes to real inference).
  • mgai-perception::skill_graph::defaults + mgai-vla’s register_vla_defaults / register_vla_subprocess_defaults — maps UDD skill ids to built-in impls.

Live camera + hardware-in-loop

  • New crate mgai-depthaiCameraStream trait, FixtureCamera / ScriptedCamera for tests, LuxonisCamera behind live feature.
  • Python bridge (scripts/depthai_bridge.py) — DepthAI v3 pipeline (Camera + StereoDepth, depth-aligned to RGB) streaming 32-byte-framed binary RGBD over fd 3 while depthai’s spdlog is redirected to stderr. READY\n handshake, SIGTERM-graceful cleanup. Verified against OAK-D MxId 19443010A137F51200 streaming 30 fps RGBD at 640×400.
  • openloco::CameraBackedFrameSource + LayeredFrameSource — adapt any CameraStream to the sim’s SensorFrameSource trait, stackable with canned / physics sources.
  • openloco sim --camera CLI flag — runs any autonomous descriptor against live stereo depth end-to-end. Feature-gated via live-camera.

Physics-backed sim + closed loop

  • SensorFrameSource trait extracted from sim driver; pluggable frame-production strategy.
  • CannedFrames — constant flat depth, gravity-only IMU, empty proprioception. Default.
  • PhysicsBackedFrames<B: PhysicsBackend> — wraps any mgai_sim::runner::PhysicsBackend, synthesises Proprioception + Imu from RobotState, falls back to canned for Rgb / Rgbd / Depth (rendering from physics is still open work).
  • openloco::commands_to_torques(backend, commands) — bridges CommandMapTorques using backend state, closing the perception → controller → physics loop.
  • End-to-end closed-loop demonstration: sim driver + MuJoCo CannedPhysics + FootholdMap + impedance-command controller → setpoint tracking proven in tests.

Mesh pipeline

  • cad-parts::library::mesh_cache::bake_mesh writes a PartBuilder’s tessellated output to <out_dir>/<hash>.stl at the exact path the URDF / MJCF emitters reference.
  • All 7 catalog parts gain build_mesh via cad-kernel: leg_segment · wheel_simple · wheg · quadruped_body · rotor_wheel · joint_t0 · joint_t1 · joint_t2.
  • openloco bake CLI subcommand — walks every MeshHash in a descriptor, resolves builder + params, writes STL files. Integration tests confirm every URDF <mesh filename="..."/> is produced.

URDF + MJCF emitters

  • URDF: per-sensor <link> + <joint type="fixed"> pair (ROS 2 tf2-frame canonical pattern). Sensor kind preserved in an XML comment for downstream plugin configs.
  • MJCF: <camera> tags for Rgb / Rgbd / Depth sensors inside each mount_link body, <site> + global <sensor> block with <accelerometer> / <gyro> for IMU sensors. MuJoCo can now render directly from UDD-declared cameras.

Autonomous descriptors

Three flagship descriptors, each a mechanical-layer superset of its base:

  • m4_morphobot_autonomous.udd.json — RGBD camera + VLM terrain_mode_advisor (Enum) + landing_site_finder (Point) with full drive → fly → drive transition cycle.
  • anymal_wl_autonomous.udd.json — downward-pitched depth
    • classical foothold_map, active in walking mode only.
  • mini_whegs_autonomous.udd.json — forward depth + classical obstacle_height with hysteresis-gated normal ↔ cautious mode transitions.

Tier 0 / Tier 1 actuator drivers

Completes the three-tier story:

  • mgai-robot::actuator::dynamixel — Dynamixel Protocol 2.0 codec (Robotis CRC-16, X-series control table), impedance emulation per actuator-spec §5.3 (q_eff = q_des + tau_ff/kp clamped). SerialPortTransport via the serialport crate behind dynamixel-serialport feature.
  • mgai-robot::actuator::simplefoc — SimpleFOC CAN-FD driver with 16-byte command + status frame, monotonic sequence counter, scripted fault-byte → FaultFlags mapping. SocketCanTransport behind simplefoc-socketcan feature.

Ledger

~1,400 tests green across the workspace (default + opt-in hardware-gated).


v0.6 — 2026-04-22

The consolidation ship. Merged the v0.5 Python OpenLoco project into Rust workspaces at openie-cad/ + joulesperbit/, with openloco/ as a thin integration crate.

Shipped

  • cad-udd::Robot — optional section on UddDocument with RobotLink, RobotJoint, RobotJointType, RobotJointLimits, ActuatorSlot, RobotMode, ActuatorRole. Content-addressable.
  • cad-format::urdf + cad-format::mjcf — URDF + MJCF emitters structurally equivalent to v0.5 Python.
  • cad-format::urdf continuous-joint semanticsdamping = 0.001 + armature = 0.0001 overrides so wheels / propellers spin freely.
  • cad-mfg::mechanical_bom + assembly_steps_markdown — BOM aggregation + topological-order assembly emission.
  • mgai-robot::actuator::{command, state, traits, error, safety} — verbatim port of openloco-actuator.
  • mgai-robot::actuator::moteus — Tier 2 CAN-FD driver with feature-gated moteus-socketcan Linux transport.
  • mgai-robot::legged_kinematics — 3-DoF quadruped leg FK/IK with 2000-sample round-trip test.
  • mgai-control::locomotion::quadruped::{stance, trot, wheg, morphobot} — new sub-tree of quadruped / morphobot controllers.
  • mgai-sim::runner::{PhysicsBackend, CannedPhysics, impedance_to_torque, control_step} — physics-backend- agnostic harness + no-dynamics backend.
  • cad-parts::libraryCatalogPart / PartBuilder / GeometryHash / PartRef scaffolding + LegSegment reference. Full build123d → cad-kernel port of the 7 production catalog parts landed across post-v0.6 sessions (see v0.7 above).
  • openloco/ integration crate — CLI (validate + generate subcommands), three reference descriptors (mini_whegs.udd.json, anymal_wl.udd.json, m4_morphobot.udd.json), end-to-end roundtrip tests.

Ledger at ship

300 tests green across 8 crates.


v0.5 and earlier

Python / build123d reference implementation preserved at claude-notes/openloco/ for posterity. Not under active development — superseded by v0.6’s Rust consolidation.