Ivan/feat/motion - #3572
Draft
leshy wants to merge 15 commits into
Draft
Conversation
The conformance test reads a wrapper's ports off `In`/`Out` type hints only, so a port that both subscribes and publishes reads as absent and the manifest that declares it fails: `motion_planner.tf is not a port of ...MotionPlannerNative`. tf is that port. It rides in the manifest's `inputs` table because the bake graph has no io kind, and the wrapper types it `IO[TFMessage]` because the rust `#[tf]` does both. Counting `IO` is what makes the two agree.
Ported from ivan/feat/trajectory_ctrl onto current main. What comes over is the shipped code and the proof it works: the SE(2) local planner (the evolved rust crate) with its referee -- worlds, gold oracle, judge, invariance tests, all pure numpy -- the follower's per-track laws in python and bit-exact rust, the shared domain the two agree on, and the adapter that makes both dimos modules. What stays behind is everything that trains, searches or simulates: the matched Go2 MuJoCo env, the closed-loop executor referee that drives it, and both evo labs. README's "What is not here" says where to go and what to re-measure there. That cut leaves three loose ends, tied off rather than papered over: - the plant's command slew was read out of simulation/walk.py, which is a sim constant the shipped law is held to. It moves to embodiment.py, next to the body table, and test_hinted.py holds python and rust to it there. - adapter/planner.py borrowed the referee's path_clearance; the adapter already has the same function, in array form. One copy now. - the "ml" registry entry named a candidate that does not exist here. Against main rather than against the branch: dimos.memory2 is dimos.memory, GO2ZenohConfig takes mid360_mount presets instead of a raw rpy triple, and this Plot has no axis labels. mypy and the fast suite are green.
The motion host is four modules, not two: the follower's twist has to reach the robot past teleop, and both baked modules wait on a tf leg that used to come over wifi from the laptop. - CmdVelMux (movement_manager/): teleop preempts nav on cmd_vel and a watchdog zeros it when nav dies, python twin and rust native. Pairs with MovementManager, which keeps the click relay; both see tele_cmd_vel. - Go2Tf (go2/tf/): GO2Zenoh's mount tree and odom edge in rust, so base_link <- mid360_link no longer depends on the laptop being up. tf_parity.json is derived from the python and asserted by both sides. go2_tf came over reading a raw rpy triple, because that is what GO2ZenohConfig took on the branch it was written on. Main has mount PRESETS instead, so the parity test pins the default to ATHENS rather than to GO2ZenohConfig's default (SF): the mount is per-rig, and what has to agree is the two halves of one rig. Both Cargo.locks are regenerated against main's dimos-module, whose dependency set has moved on from the branch these crates were written against.
Three blueprints, added beside the existing ones rather than replacing them -- nav, nav-baked and htc are untouched. - go2-zenoh-motion: MLS keeps the global graph but its path moves to planner_path and becomes a carrot source; the local planner replans ~5 m along it over the raycaster's local map and the follower tracks that plan on the hinted track, with the clearance array recomputed from the same map. - go2-zenoh-motion-blind: the same graph, hint withheld. The law recovers the required precision from the path's own stamps, so an A/B isolates the law. - go2-zenoh-motion-local: planner, follower and mux lifted onto the robot as one baked host; what stays is expensive (raycaster), global (MLS) or attached to the operator (rerun, clicks, teleop). SPEEDS ARE SIM-CALIBRATED, and the comment above the stack says so: both envelopes were measured against the freewalk blob in the matched MuJoCo env, not against the gait the robot runs. Dial the ceiling down here before trusting either at speed. One thing the branch had that this does not: region_radius_m=5.0 on the raycaster. Its emitted window is sized to a percentile of the last ten sweeps, so it breathes -- 68 % of the voxel churn the local planner saw was the window moving, not the world -- and the plan flips with it. The fix threads a fixed radius through the raycaster's rust, which is not planner-or-follower, so it stays on ivan/feat/trajectory_ctrl until someone wants it on its own terms. The comment in _motion_raycaster carries the whole story.
… in CI Two halves of the same rule, both already the repo's, neither of which came over with the port: a test that needs a maturin extension says so with `importorskip`, and CI builds the extension so the test actually runs. Without the first, a checkout that has not run `maturin develop` collects a `ModuleNotFoundError` out of test_grid_invariance and test_rust_parity instead of a skip -- which is how the whole motion package reads as broken every time `uv sync` prunes the two editable installs. `test_commitment` splits finer: the rust candidate is skipped, the python port spec runs either way. Without the second, the skip is the last word and nothing upstream ever compares python to rust. The PyO3 job now builds both motion crates next to ray_tracing and mls_planner, and runs the two tests whose whole point is the extension.
`dimos bake --target aarch64-unknown-linux-gnu.2.31` needs three things a plain nixpkgs shell does not have: a rustc carrying the aarch64 std (nixpkgs' does not, and without it every cross build dies with "can't find crate for core"), zig, and cargo-zigbuild -- which is what makes the `.2.31` glibc pin possible at all. deployment_plan.md already promised all three; now the flake delivers them, via rust-overlay pinned to 1.97.1 so the toolchain does not drift under contributors when the overlay moves. Two deployment docs were describing a topology that is no longer the one in the blueprint, and both would have cost a robot session: - the quick start told the laptop to run `go2-zenoh-motion` against a live baked host, which is two planners, two followers and two muxes on the same topics. It is `-local` that leaves those three to the robot. - the unit's comment still explained a listen port on 7448 with the laptop dialing both. The unit's own environment has no DIMOS_ZENOH_LISTEN: go2web is the router, the host is a client of it, and it listens on nothing.
Two bugs the first robot run found, both mine, both in the same three edges. `go2-zenoh-motion-local` left GO2Zenoh publishing tf from the laptop while go2_tf publishes it from the robot. Two publishers of one static tree is not redundancy: base_link jumps between them at their combined rate. The robot is the one that should win -- that is the whole reason go2_tf is baked into the host -- so the laptop's tf is remapped onto a dead channel. And they disagreed about where the lidar is. go2_tf defaults to the ATHENS mount (-60, 0, -90); main's blueprint constant is "SF" (0, 60, 0). Whichever tick arrived last moved the body. The motion stacks now name their rig once, in MOTION_MID360_MOUNT, with the constraint written down: it has to equal go2_tf's default on any robot that runs the baked host. nav/htc keep MID360_MOUNT.
The port carried a field-debugging kit that nothing on the runtime path imports: `adapter/diagnose.py` and its tests, `precision.py`, `replay.py`, and `planner/referee/measure_margin.py` (which bakes a constant that is already baked, and reaches into diagnose to do it). Good tools, and a reviewer of a planner has no reason to read 2.8k lines of them. They stay on `ivan/feat/trajectory_ctrl` and can come back as their own change. Same for four writeups nothing cites: a dated diagnosis, a replay-provenance proposal, the 3D revision sketch, and the envelope sweep's result dump. `native_module_recipe.md` goes too -- how to write a native module is a dimos topic, not a motion one, and `dimos/cli/bake/` is the code it describes. `planner/revision.md` STAYS. Seventeen comments in shipped code cite it for why a number is what it is -- the body box, the lattice pitch, the commitment margin -- so deleting it would leave those dangling, which is the opposite of trimming. `diagnostics.py` and `viz.py` stay too: the follower, the planner and the blueprint import them. And .gitattributes now marks what is machine-generated. The five Cargo.locks and the evolved planner crate are 15.1k of the diff that nobody reads line by line; labelled, a review renders the ~19k that was actually written.
The port brought `data/.lfs/ml-trajectory-research.tar.gz` along because the adapter's recording tools read it. Those tools are gone, nothing on this branch calls `get_data` any more -- and the archive carries `freewalk_mcf.bin` and `v11_final.bin`, walking policies lifted out of the robot's own binaries, which are not ours to publish. The pointer is out of every commit on this branch, not just deleted in a later one, so pushing uploads no LFS object. The gait calibration constants stay: they are measurements OF that policy, not the policy, and the comment now says where the blob is not.
leshy
force-pushed
the
ivan/feat/motion
branch
from
August 20, 2026 18:36
ab9a40b to
902937b
Compare
What is left is what someone runs on a robot. The planner's referee -- the worlds, the SE(2) gold oracle, the judge, its six invariance tests and the `--score` runner over them -- is a benchmark, not a module, and it belongs with the sim and the labs on `ivan/feat/trajectory_ctrl`. `gold.py` was not purely judge-side: the shipped candidate imported three helpers out of it (`states_of`, `densify_states`, `pose_stamped`). Those move to `planners/base.py`, where the interface they serve already lives, and the slow reference oracle leaves with the rest. What still holds the planner honest here: the crate's own five behavioural invariants (open world routes, sealed box refuses, thin wall not hopped, deterministic across calls, no cross-call memoization), the in-src property tests beside them, and `adapter/test_planner.py` for the module wiring. All of it runs in CI already -- the rust job discovers every crate with a Cargo.lock and runs `cargo test --workspace --all-features`. The control side is untouched: 240-case python-vs-rust parity, the profile dialect, the envelope.
Deploying the host on a Go2 found three things wrong with the deployment artifacts, all fixed on the robot during that session and only now written back here, which is the wrong order and exactly the config skew the plan warns about. - The blob must be ONE LINE. `read_launch_config` reads a single line, so the repo's `pretty-format-json` hook -- which reformatted this file the moment it was committed -- crash-looped the host with "EOF while parsing an object at line 1 column 1". The file is now excluded from that hook, with the reason. - The unit's DIMOS_ZENOH_CONNECT / _MODE / _SCOUTING did nothing. The rust module reads only DIMOS_TRANSPORT from the environment; session settings come from a `session` block on the launch line, and without one the host opens zenoh's defaults -- a peer with multicast scouting, which a router does not forward to. The block is in the config now and the dead env is gone. - The per-module `topics` maps were restating the wiring the bake bakes into the binary (stdin topics are overrides only), so they are dropped, and the blob gained `--emit-config`'s `graph` stamp -- which makes a config baked for another graph a startup error instead of a host wired to keys nobody uses. 2418 bytes over 124 lines becomes 1492 on one, and the only value in it that is not a class default is still `max_speed` 0.7.
leshy
force-pushed
the
ivan/feat/motion
branch
from
August 20, 2026 19:01
a14857d to
f310960
Compare
The PyO3 job still listed `planner/referee/test_grid_invariance.py`, which left with the benchmark two commits ago -- pytest would have failed the job on a path that no longer exists. The planner's rust invariants are covered anyway: the rust job discovers every crate with a Cargo.lock and runs `cargo test --workspace --all-features`, which builds and runs `planner/rust/tests/invariants.rs`.
… history Two things the first robot run showed, both real. THE BOXES ARE WIDER THAN THE ROBOT because they are the measured leg swing, not the 0.31 m trunk: a forward edge is tested against 0.416 m of width, a rotation against the 0.593 m all-gait union, each plus the 0.05 m clearance floor. So a straight run wanted a 0.516 m gap and a turn-in-place 0.693 m, which refuses passages the robot walks through. `body_dilate_m` grows every planning box by that much PER SIDE, negative to shrink, and reaches the union and the per-heading rows alike -- python, `Embodiment.dilated`, and rust, `emb::dilated`, same formula. The follower takes the same value: its room hint has to price the body the plan was made for, or the governor creeps through gaps the plan called fine. At the -0.03 the go2 stacks now run, a straight gap of 0.456 m admits a route. THE PLAN WAS PUBLISHED AT odom z = 0, which on a lidar-odometry stack is wherever the LIO frame started -- about a body height above the floor. Every drawn body box floated over the robot. Both modules already track the ground plane for the obstacle model; the path carries it now. Only a viewer reads the z, every consumer is planar. And the viewer drew the all-gait UNION at every waypoint -- the turn-in-place shape, ~0.18 m wider than what a forward edge has to fit -- which made every corridor look impassable. It draws the straight-drift box at the dilation in force, which is 0.759 x 0.356 against the old 0.883 x 0.593. Riding along, because it is the same files: the comments stop narrating how the code was arrived at. Experiment ids, lab branch names, score deltas, dated recordings and ablation tallies are gone from the laws, the domain and both crates -- what a number IS and why it has to be that stays, 286 lines of how it was found does not. `planner/revision.md` goes with them; every comment that cited it now says the thing itself.
They carried their own RayTracingVoxelMap config -- emit_every 10 against the nav stack's 1, global_emit_every 100 against 50 -- chosen when the local planner was replanning off every emitted window. It buys the planner nothing here and it costs the operator the picture: at one frame in ten the emitted cylinder sits still between updates and steps in visible jumps, and at one global map in a hundred there is nothing around it, so the local region reads as a hard circle rather than a moving frontier. The stacks take `ray_tracing_config` now, same as nav, and the raycaster is back to being a module this branch does not touch at all. The window still BREATHES -- it is sized to a percentile of what the last sweeps saw, so its radius tracks the room rather than the robot. `region_radius_m`, which pins it, is ours and stays on ivan/feat/trajectory_ctrl with the rest of the raycaster work.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.