Skip to content

Ivan/feat/planning-trajectory - #3352

Draft
leshy wants to merge 119 commits into
mainfrom
ivan/feat/trajectory_ctrl
Draft

Ivan/feat/planning-trajectory#3352
leshy wants to merge 119 commits into
mainfrom
ivan/feat/trajectory_ctrl

Conversation

@leshy

@leshy leshy commented Aug 4, 2026

Copy link
Copy Markdown
Member

No description provided.

leshy added 30 commits August 2, 2026 15:04
Vendor the motion2 referee (worlds, gold oracle, judge, scoring, rust
candidate crate) from ivan/feat/body_obstacle @ d7c1b7c88 into
dimos/navigation/motion/autoresearch/ as its canonical home, made fully
standalone: the package runs on numpy + scipy + pydantic alone, with
relative imports throughout, so a plain directory copy works anywhere.

Two substitutions close the dependency knots:

- types.py replaces dimos.msgs (Vector3/Quaternion/Pose/PoseStamped/
  Path/PointCloud2/SolidPrimitive) with minimal classes whose numeric
  formulas are copied op-for-op from the originals.
- geometry.py vendors the pure-geometry slice of motion/obstacle.py
  (CollisionShape, DistanceField, scored_clearance, station_poses,
  turn_mask, sweep_samples, near_field_diff, ...) with
  BodyAwareAvoidanceConfig reparented to a plain-pydantic
  AvoidanceConfig. Re-vendor deliberately if obstacle.py's scoring
  math ever changes (the source branch is not merged here).

Dropped relative to motion2: planners/core.py and the core-keyed test
suites (they need the production motion stack, which is not on this
branch); default --planner is now target. sim2d.py is renamed sim.py.
The pyo3 module name dimos_motion2_target and python.rs plan()
signature are frozen interface and unchanged.

Verified against the pinned referee at d7c1b7c88: 56-world battery
(--gen 40 --seed 0), per-world scored/truth/veto/flicker/consist
columns BIT-IDENTICAL for both --planner target (gold 0.9624,
consistency 0.8041, dq 0) and --planner gold (gold 1.0, dq 0, no
self-veto). Speed pillar differs only by machine load, as expected.
…ssion test

- __main__.py is now the canonical entry (python -m ...autoresearch): it
  pins OPENBLAS/OMP/MKL/NUMEXPR/VECLIB pools to 1 thread BEFORE numpy
  loads. avoid_ms is time.process_time(), which sums CPU across all
  threads, and OpenBLAS's spinning pool was being charged to the
  candidate whenever a spin window overlapped a timed plan() call —
  measured on this battery, pinning takes identical-code score spread
  from 0.334 to 0.0069.

- --jobs N: battery fan-out over worker processes, each pinned to its
  own core, each measuring its own per-process CPU time — so parallel
  workers cannot contaminate each other's timings (threads in one
  process would). The parent warms both pickle caches serially, then
  spawns workers cache-read-only. 56-world battery: ~20 s at --jobs 8
  vs ~2.5 min serial; gold/consistency bit-identical to serial.

- scenarios.py cache contract: AUTORESEARCH_CACHE_DIR redirects both
  pickle caches (shared cache dir for lab worktrees),
  AUTORESEARCH_CACHE_RO suppresses writes, and all cache writes are
  atomic (tmp + os.replace) so a crash can never leave a torn pickle.

- --json: one JSON document with per-world records (score + verdict
  fields, non-finite floats nulled) plus the summary — machine-readable
  for eval harnesses. Default output stays byte-compatible.

- --build: build + install the rust candidate first (maturin develop
  --release), so "score the latest planner" is one command.

- test_gold.py: for every curated world plus generated seeds 0/28/30,
  --planner gold must never be vetoed by its own judge and scores ~1.0
  against itself — the regression test for the densify_states
  truncation bug fixed at d7c1b7c88 (gen028 self-veto).

- README.md: quick-start for build/score/view/tests, the timing model,
  and vendoring provenance.

Verified: serial battery per-world columns remain bit-identical to the
d7c1b7c88 baseline; --jobs 8 matches serial on all deterministic
columns; 19 gold tests pass; --view writes a valid sim2d.rrd.
Replace the brute-force seed planner with the autoresearch lab's best
candidate: the exp_0008 + exp_0009 + exp_0010 merge with the two-tier
yaw publication gate (lab worktree exp_0016, base commit d911a39; the
merged planner.rs was uncommitted on the lab branch due to an evo
bookkeeping accident, hence the worktree provenance). rustfmt applied;
two clippy allows added on test fns; no functional edits.

What it adds over the seed, cumulatively earned across the run:
lazy demand-driven distance field + per-(yaw,cell) clearance, CSR point
buckets, precomputed gait tables, 16-byte A* heap nodes, goal-anchored
shortcut smoothing, start-pose-invariant working area + exact grid
index arithmetic, a demand-driven backward 2D Dijkstra differential
heuristic, ceil-based fine yaw publication, and a two-tier yaw gate
that publishes coarse yaw only where measured chord clearance affords
the swept-box window.

Battery (56 worlds, --gen 40 --seed 0) vs the seed planner it replaces:
referee 104.4 -> 106.7/111, gold 0.9624 -> 0.9629, consistency
0.8041 -> 0.9473, speed pillar 0.12 -> 0.89 (at the 20 ms budget),
dq 0, gen028's rotation-cylinder veto cleared. gold/consistency match
the lab's recorded exp_0016 numbers exactly.

Also: crate tests from the lab land as tests/invariants.rs — behavioral
black-box properties (bit-determinism, no cross-call memoization,
sealed-world refusal, thin walls never hopped, open-world routing).
They are developer aids, deliberately NOT wired into any harness gate:
the benchmark treats the crate as a black box and gates behavior
through the referee. lib.rs's header now states the CPU-time threading
rule and the judge-constant coupling of the yaw gate.
…loops

python -m dimos.navigation.motion.autoresearch.export <dest> scaffolds a
complete, self-contained research lab: the referee copied as referee/
(relative imports make the package name-independent), the current rust
planner seeded as candidate/, the bench/ gate harness, generated locks,
warmed caches, a uv venv, and a git repo -- ready for /evo:discover.
gates.json carries the benchmark command, the recommended gates with
epoch-3 thresholds, and the seeded baseline, so a lab agent registers
them mechanically.

The harness is the proven one from the source lab (autoresearch-planner
run_0000), adapted for standalone operation:

- trust chain unchanged in shape: tracked bench/trust.lock -> .evo/
  {bench_guard.py, harness.lock, referee.lock} -> every harness byte.
- referee pinning goes content-hash: referee.lock pins every referee/
  file by sha256 (+ stray-file ban), the live co_code of judge/
  score_world/summarize/se2_path, and the scoring constants by value --
  the old lab's git-head pin has no meaning without a dimos checkout.
- check_rules.py keeps the full anti-cheat ban list (fs/env/net/pyo3-
  reach/subprocess/cross-call state; threading deliberately permitted
  under CPU-time scoring) with frozen-surface hashes generated at
  export into bench/frozen.json.
- fitness weights move to bench/fitness.py with the retune procedure
  in its docstring (they were retuned twice in the source lab).
- bench/run drops DIMOS_REPO: lab-root walk-up (EVO_LAB_ROOT override),
  the lab venv, PYTHONPATH staging of the .so, baked-in BLAS pinning,
  and AUTORESEARCH_CACHE_DIR pointing at the shared .evo/cache.
- caches: the gold cache ships from the source package (plain dict
  pickle, key-compatible; warmth probed at export), world caches are
  REBUILT under the lab import name (they pickle Scenario module paths).
- policy: no cargo-test gates. candidate/tests/invariants.rs ships as
  optional crate tests; behavior is gated through the referee.

Templates are byte-copied, lintable files under export/templates/
(excluded from repo ruff/mypy -- lab conventions, not repo code); all
export-varying data lives in generated files.

Self-test: fast tier (9 tests, ~1 s, CI-safe) exports with build/venv/
warm skipped and proves the tree, standalone import, the trust chain,
and four tamper scenarios (referee edit, stray file beside the judge,
harness edit, banned construct in the candidate). Full smoke behind
AUTORESEARCH_FULL_EXPORT=1.

Verified end-to-end on a real export: uv sync, gold cache warm (3 ms
probe), locks green, cargo build, 2-world battery fitness 25.0/26,
./eval curated 108.13/111 dq 0, ext_invariants + parity gates pass --
with no dimos checkout involved anywhere in the lab.
referee.lock pins live co_code hashes and CPython bytecode changes
across minor versions, so a lab uv-synced under a different python
would fail ext_invariants for what is really an interpreter mismatch.
Write .python-version from the exporting interpreter so uv provisions
the same minor everywhere (surfaced deploying to a python-3.13 host).
Standalone flat-ground MuJoCo Go2 for the sim-to-real work. Deliberately does
not build on unitree/mujoco_connection or the existing sim stack -- those are
reference only.

  model.py    menagerie unitree_go2/scene.xml; Unitree<->MuJoCo motor
              permutation resolved by name, not by assumed index order
  replay.py   re-runs the on-board law tau = kp(q_des-q) + kd(dq_des-dq) +
              tau_ff against simulated joint state, scores vs recorded lowstate
  __main__.py CLI

Timestep matches the recordings exactly (0.002 s = 500 Hz), so it is one
command per sim step with no resampling. Actuators are torque with gear=1,
so ctrl is N.m directly.

Ships two recordings with the exact networks that produced them, as the
ml-trajectory-research LFS archive:

  unitree_himloco01           freewalk (HIMLoco)  <-> freewalk_mcf.bin
  unitree_v11_gait_height01   v11-final           <-> v11_final.bin

FINDINGS.md records why joint-level replay does not work on this data: across
every recording, lowcmd/policy_lowcmd carry q_des=0, kd=0, kp=1.0, tau_ff=40.0
constant over all twelve joints -- template defaults the publisher never fills.
lowstate.dq and .tau_est are identically zero and ~25% of lowstate.q rows are
outside physical joint range. Verified four ways (dimos codec, hand CDR parse,
both channels, three files), so the harness is kept but comparison should be
made at body-pose level against vive_pose until the publisher is fixed.
Wall-clock paced replay in mujoco.viewer.launch_passive, --speed to scale.
Same control loop as the headless path.
…dependency

policy.py reads the "FREE" v1 blob directly -- speed-banded HIMLoco experts
plus shared normalization -- and runs the HIM forward pass in numpy. Verified
against MNN on all three bands: max |ours - MNN| = 1.1e-4, well under the
2e-3 fp32 floor the exporter validates at.

walk.py drives that policy on the flat menagerie scene, commanded either by a
constant or by a recording's control_log (zero-order held), so a run is
directly comparable to its vive_pose. Sanity: vx=0.5 -> 0.536 achieved,
upright; turn-in-place stays put.

    --policy <bin>   drive from control_log instead of replaying lowcmd
    --view           interactive MuJoCo window

Two values the blob does not carry are pinned here as constants with the cfg
keys they came from: CONTROL_DT (cfg "dt") and TORQUE_LIMITS
(cfg "torque_limits").

MNN is added as a dep so the port can be re-checked against the original, but
nothing on the runtime path imports it.
Drop the cd, the $D shorthand and the direnv exec prefix -- direnv is
already loaded, so the commands should be copy-pasteable as-is.
… the sim

A translucent mocap box follows the recorded pose while the policy walks, so
sim and hardware can be compared by eye before any error metric exists.

Two Vive conventions were read off the data, not assumed (test_vive.py pins
the reasoning):

  quaternion is wxyz  -- under that reading the tracker z axis holds
                         0.997 +/- 0.003 alignment with world z over a run;
                         xyzw gives 0.725 +/- 0.279, impossible for a walker
  frame is z-up       -- 0.09 m of z range against 1.0 and 1.6 m in x and y

Where the tracker sits relative to base_link is still unknown. It is mounted
roughly 15 cm above the body, so the default offset is (0, 0, -0.15) with
--tracker-z to adjust; the in-plane component is not modelled. The track is
anchored at t=0, which cancels the unknown room origin and mounting rotation
but not the lever arm -- that shows up as soon as the body turns, which is
exactly what makes a wrong offset visible.
…the sim

The ghost looked mirrored because it was. Two mounting facts, both read off
the data:

  tracker is inverted   R[2,2] = -0.997 (himloco01), -0.996 (v11) while the
                        robot is upright throughout -- so a base offset that
                        should point down in world is +z in tracker frame
  yaw is ~94 deg        robot forward lands along the tracker's +y, not +x

The yaw is fitted sim-free, two independent ways that agree: circular mean of
travel direction under a pure +vx command gives 93.6 deg (concentration 0.88,
n=2347), and maximizing cos(body velocity, commanded direction) gives 94.0 at
0.840 on a clean unimodal curve.

Fitting it against the simulator does not work, and the failure is worth
recording: the policy rollout diverges from the real robot within a second or
two, so a sim-vs-ghost displacement score on 2 s windows spans only 0.79-1.09 m
against ~1 m of travel, and its argmin lands at 285 deg -- about 180 deg wrong.
The mount is a property of the recording and has to be fitted from it.

Also corrects the ghost box, which was 2x too wide: the trunk collision from
the official URDF is 0.3762 x 0.0935 x 0.114 at origin 0 0 0, so `base` sits at
the trunk's geometric centre, level with the hips. menagerie inherits that body
one-to-one, so MuJoCo qpos[0:3] is exactly the URDF `base` frame -- there is no
mapping to do, and no `base_link` (the root link is called `base`).
…te claim

The simulator always begins standing: walk() resets to the menagerie `home`
keyframe and overwrites the leg joints with the policy's default_pose. Nothing
in a recording can change that, because there are no joint angles in one --
so the first seconds of himloco01, where the tracker climbs from 0.166 m to a
steady 0.229-0.245 m as the robot stands up, cannot be compared against it.
--start anchors the ghost and the command schedule past that.

Corrects an earlier claim in FINDINGS that ~75% of lowstate.q rows were usable.
That came from a |q| < 4 rad filter, which is far too loose. Against the real
mechanical limits (calf is [-2.72, -0.84], always negative) the recorded calf
angles are in range in 0.0% of rows on himloco01 and 0.1% on v11, with mean
+0.007 and a +/-14 rad span. lowstate.q is not joint angles.

Also fixes a variable shadowed between an index and a rotation matrix in
base_track, caught by mypy.
… fixed

Trajectory error cannot be the objective. Perturbing only the initial joint
angles and replaying the same commands diverges by 136 cm at 12 s for a 3 deg
perturbation and 58 cm for a 17 deg one -- non-monotonic, i.e. chaotic. Against
that floor the sim-vs-real windowed error is 1.5x at 0.5-2 s and 0.88x at 10 s,
so a long-horizon trajectory score carries no information about physics.

metrics.py computes what does survive: speed, speed gain, yaw rate gain, body
height mean/std, gait frequency, plus chaos_spread() to measure each
statistic's own noise across perturbed rollouts. Nothing should be fitted
against a statistic whose sim-real difference does not clearly exceed it.

First results over 40 s of himloco01: speed (0.410 sim vs 0.389 real) and speed
gain (0.780 vs 0.759) agree to within the noise -- the policy's translational
response transfers. height_std is the one clear discriminator so far, the
simulated body bobbing 50% more than the real one at 3x the noise floor.

Two estimator bugs, both mine, both recorded in FINDINGS:

  sample-window filtering  differentiating a 253 Hz recording and a 50 Hz
                           rollout with the same 25-sample window smooths them
                           by 0.1 s and 0.5 s respectively; with Vive dt jitter
                           as large as the interval itself this reported the
                           robot walking at 3.87 m/s
  mean of ratios           mean(achieved/commanded) explodes near zero command
                           and cancels across sign flips, reporting the
                           simulator turning backwards; now a least-squares
                           slope through the origin

vive.read_vive_pose now clocks off the payload's t_host: monotonic, worst gap
15 ms against 92 ms for log_time, while the payload's own ts goes backwards at
91 points.

Still not trustworthy and marked as such: yaw_rate_gain (correct on a constant
command, wrong against the fast-alternating recorded schedule -- needs lag
compensation) and gait_hz (locking onto drift at 0.6 Hz instead of the ~2 Hz
gait). height_mean is not comparable at all while the tracker offset is a guess.
It died with "IndexError: index -1 is out of bounds for axis 0 with size 0"
from deep inside the summary print, because a start beyond the last command
makes the duration negative and the rollout empty. Validate up front and name
the actual span, which is the number the user needs.
…imators

--start was not reaching the commands. A patch adding the offset to cmd_at()
silently failed to apply while the same offset did land on the ghost lookup, so
the simulator ran the first seconds of a recording and was scored against a
ghost six seconds later. Every --start number reported before this was wrong.
The tell: applied commands had vx=0 where the schedule said vx=0.422. Pinned by
test_start_offset_reaches_the_command_schedule.

gait_hz was locking onto the robot drifting around the room, reporting 0.58 Hz.
Now high-passed by subtracting a 1 s moving average, Hann-windowed, and searched
from 1.0 Hz. No ground truth exists to calibrate against -- HIMLoco free_walk
has no clocked gait, and the only explicit rate in the fleet is 1.5 Hz on an
experimental trot-clock policy -- so this is plausibility, not calibration.
(50 Hz is the control rate, himloco.rs:185, confirming walk.CONTROL_DT.)

Command gains now find the policy-to-body lag by cross-correlation and regress
at that lag, reporting it too. The lag is itself the strongest discriminator
found so far.

With commands aligned the picture is coherent and matches the viewer: the
simulated gait runs at 3.30 Hz against 1.75 Hz real, bobs 33% higher, and
answers a turn command in 0.07 s against 0.49 s while turning less per unit
command. Translation still transfers -- speed 0.441 vs 0.389 and speed_gain
0.887 vs 0.839, both within about twice the chaos noise.

Fitting targets, by SNR: yaw_lag 14.0, yaw_rate_gain 6.7, gait_hz 3.4,
height_std 2.8.
… sweep

gait_hz is window-dependent: the same configuration reads 1.54, 1.52, 3.30 and
1.51 Hz on 15/25/40/45 s windows. The 3.30 was an outlier -- a harmonic winning
one FFT -- and it is what the previous commit's "simulated gait is twice as
fast" headline rested on. Read across windows sim sits near 1.5 Hz and the real
robot wanders 1.1-1.75; they are not clearly different. gait_hz is now marked
unusable as a fitting target until estimated by autocorrelation or a median
across sub-windows.

Stable and discriminating, still: height_std, yaw_rate_gain, yaw_lag, with
speed and speed_gain agreeing between sim and hardware.

First sweep of leg-joint physics against the real targets shows no single
parameter closes the gap -- raising armature pulls gait_hz toward real but
pushes height_std away and collapses speed past 0.1; damping costs speed for
little gain. The menagerie defaults are the best row tested. That is the honest
case for a multi-objective search, and also evidence that whatever looks wrong
in the viewer is not simple leg-joint under-damping.
…cs search

gait_hz now takes the first peak of the height autocorrelation instead of an
FFT peak. It reads 3.33/3.23/3.33/3.33 Hz on 15/25/40/45 s windows against
1.52-1.69 real, where the FFT swung between fundamental and harmonic. That
un-retracts the earlier "simulated gait is twice as fast" finding: the claim
was right, the estimator was not.

evaluate.py scores one configuration -- runs the policy under the recorded
commands, summarizes both sides, and divides each statistic by its own noise
floor so a term cannot be won by driving something the simulator cannot
resolve. search.py drives it with Optuna's CMA-ES sampler, the right choice for
a continuous, low-dimensional, noisy objective (TPE would suit categorical or
conditional spaces, which this is not).

Measuring the noise floor once and reusing it takes a trial from 7.3 s to 1.6 s,
so 200 trials is about five minutes. It also keeps losses comparable: recomputed
per trial, a trial could win by getting noisier.

30 trials say joint friction is the missing physics. frictionloss 0.2 -> 1.23
(6x) with armature and damping essentially unmoved takes gait_hz from 3.333 to
1.562 against 1.695 real (SNR 5.2 -> 0.4) and height_std from 0.033 to 0.026
against 0.023 (SNR 4.9 -> 1.5). Loss 4.97 -> 4.27. Consistent with real
gearboxes; menagerie's 0.2 N*m models a much freer joint than the robot has.

yaw_lag does not move and cannot: 0.04-0.06 s in sim against 0.46 s on hardware
is command transport and filtering, not a leg-joint property. It is the largest
remaining loss term and needs an explicit command delay, otherwise the next
search will drag every other parameter around trying to compensate for it.
Ivan's alternative -- that the real robot is simply heavier and slower to swing
round -- tests cleanly and comes out negative. Tripling the trunk's rotational
inertia moves sim yaw_lag not at all (0.06 -> 0.06 against 0.46 real), while a
0.5 s command delay takes it to 0.55. The policy is a closed loop at 50 Hz: it
feels a heavier body on the first tick and compensates, but cannot compensate
for a command it has not received. The measured cross-correlation is a sharp
single peak (0.88 at 0.50 s, against 0.66 at zero lag) -- the signature of a
shift rather than a filter. Inertia is not useless, x3 improves total loss
4.97 -> 4.43; it just does not explain this.

walk() gains command_delay, and the search space gains command_delay plus trunk
mass/inertia scaling so the two hypotheses compete on the data.

80 trials: loss 4.97 -> 2.39, with yaw_lag SNR 10.0 -> 1.0, height_std
4.9 -> 1.0, yaw_rate_gain 4.3 -> 1.7. The search settled on command_delay
0.451 s with no knowledge of the cross-correlation that independently measured
0.46-0.50 s, and left trunk_inertia_scale at 1.039 -- unchanged, rejecting the
inertia hypothesis on its own.

Caveats recorded in FINDINGS: frictionloss fell from 1.23 to 0.307 once delay
was available, so the earlier friction figure was partly standing in for the
missing delay. And gait_hz is now the worst term (SNR 4.9) where the
physics-only search had driven it to 0.4 by leaning on friction, with speed_lag
regressing 0.7 -> 3.0. The two optima disagree, so the scalar loss is trading
gait accuracy against the rest; next step is weighting or a multi-objective
study with a Pareto front.
…e groups

NSGA-II over gait / translation / rotation, each the RMS of its statistics' SNR.
Seven separate objectives would leave nearly every trial non-dominated; these
three are the ones that actually trade.

150 trials, 24 non-dominated. corr(gait, rotation) = -0.42 across the front:
they genuinely pull against each other, which is what the scalar loss was
averaging away. The physics-only search sat at the gait end (gait 0.4, rotation
10) and the delay search near the rotation end (rotation ~1, gait 4.9) -- both
were real points on this curve reported as if they were the answer.

Most of the space is smeared across its full range, as expected when the
optimum is a curve rather than a point. Two parameters stand out: armature
wants ~7x the menagerie default (median 0.071 against 0.01, where the scalar
search had said 0.0158), and command_delay's median of 0.369 s spans the 0.46 s
measured independently by cross-correlation.

Best balanced point, minimizing the worst objective rather than the sum:
gait 2.41, translation 2.05, rotation 2.71 -- every term under 2.8 noise floors
against a baseline where yaw_lag alone was 10.

The read on sim-to-real: no point in this six-parameter space matches gait and
rotation together, which points at a missing mechanism (actuator bandwidth,
contact behaviour) rather than a mis-tuned constant. The front is also the test
for whether adding one helps -- a better model should collapse the curve toward
the origin rather than slide along it.

Two tests earned their place while writing this: the objective groups must
partition the comparable statistics, or a statistic goes silently unoptimized;
and log scaling is only allowed where a range spans a decade, which caught
trunk_mass_scale set to log over 0.6-2.0.
…e lag

A MuJoCo motor delivers the requested torque on the same step; a real BLDC
through a gearbox reaches it over a few milliseconds of current-loop
bandwidth. actuator_step() applies that lag, tau=0 reproducing the ideal
actuator exactly.

The Pareto front said no scalar on an existing term could match gait and
rotation together, which pointed at a missing mechanism rather than a
mis-tuned constant. This is the first candidate. A quick probe is promising:
5 ms takes gait_hz from 3.33 to 1.52 against 1.70 real and height_std from
0.033 to 0.029 against 0.023, loss 4.97 -> 4.53. Past ~30 ms it destabilises
the gait entirely, which is the expected shape for a bandwidth limit.

Added to the search space as actuator_tau over 0-50 ms. Whether it is a real
improvement is a question for the front: a better model should collapse the
curve toward the origin rather than slide along it.
Same 150 trials, same objectives, only actuator_tau added:

  min distance to origin   3.82 -> 2.87
  best sum                 6.01 -> 4.49
  best worst-objective     2.71 -> 2.20
  best gait                1.05 -> 0.67
  best translation         0.44 -> 0.22
  best rotation            0.81 -> 0.94

The curve moved toward the origin rather than sliding along it -- every
aggregate improved together and two of three objectives got a better
attainable minimum. That was the agreed test for a real model improvement
against a new way to trade one error for another.

The strongest evidence is what the search did not choose. actuator_tau is
searched over 0-50 ms, so an ideal actuator is available for free, and not one
of the twenty Pareto-optimal trials sits at zero: min 0.0094, median 0.0219,
max 0.0453 s. A spurious knob would leave some optimal points at the identity
value. Ten to forty-five milliseconds is also the right order for a BLDC
current loop through a gearbox.

Best balanced point is now gait 0.67, translation 2.20, rotation 2.06 against
2.41 / 2.05 / 2.71 before -- gait inside one noise floor, nothing above 2.2,
where the untouched baseline had yaw_lag alone at 10.

Rotation remains the worst term and did not move. Delay and actuator lag both
model when torque arrives; nothing yet models what the foot does once it lands.
The menagerie feet use condim=1, i.e. frictionless point contacts that generate
no tangential force at all, which for a turning quadruped is the obvious next
suspect.
FINDINGS had grown to 767 lines across sixteen appended sections, several of
which documented intermediate states that later work superseded -- a gait_hz
retraction that was itself retracted, a friction figure that changed once
command delay existed. Readable as a diary, not as a place to find out where
the project stands.

Rewritten to 200 lines organised by what a reader actually needs: current
status and best configuration up front, then what the recordings contain, what
is calibrated, why the judge is distributional, and the two mechanisms found.

The bulk of the new document is proposals. Foot contact is the standing
hypothesis for the remaining rotation error -- the menagerie feet use condim=1,
frictionless point contacts that generate no tangential force, which is a
strange way to model a robot that turns by shearing its feet against the
ground. Then pinning the tracker translation (a ruler beats a weakly
conditioned fit), validating on the held-out v11 recording, and what to capture
next time.

Superseded narrative is dropped, but the estimator mistakes are kept as a short
"traps" list. Every one of them produced a plausible, finite, wrong number, and
they are the most reusable thing here.

README now points at it rather than duplicating.
Three changes, each killing a wrong number the judge was fitting:

- Shared epoch: vive t_host and control_log log_time are the same clock,
  but each stream was zeroed at its own first message, pairing every real
  pose with a command 0.31 s in its future (4.4 s on v11). The fitted
  "command delay" of 0.317 s was this artifact to within 4 ms; genuine
  epoch-corrected turn latency is ~0.17 s.

- Sensor-space height: inverting the guessed tracker offset put 11.4 mm
  of lever-arm swing into the "ground truth" z (the tracker itself only
  bobs 5.6 mm). Real keeps raw tracker z; sim mounts a virtual tracker
  with the same guess, so the guess cancels. Adds offset-immune
  pitch_std/roll_std statistics; height_std is detrended.

- Hardware command slew: go2web ramps operator commands per-axis before
  the policy sees them (VEL_DV 0.05/0.04/0.10 per 20 ms tick). Known
  constants, not a fitted knob; walk() now applies the same ramp. This
  alone puts sim yaw_lag at 0.170 vs real 0.170 with default physics --
  the axis-dependent lag no uniform delay could fit.

Also: foot_friction / foot_friction_torsional search dimensions (feet
are condim=6 priority=1, so friction values are the open question, not
contact dimensionality), command_delay range shrunk to genuine-transport
scale, pitch/roll join the gait objective group.
…oco01

The 300-trial NSGA-II front collapsed from 36 points to effectively one:
gait 0.23 / translation 0.85 / rotation 0.16 in default-floor noise
units, every statistic sub-noise, and the fitted parameters physical
(armature at spec, trunk +33% mass for the tracker payload, 23 ms
transport delay). Rewrites FINDINGS around the matched state: three
mechanisms, sensor-space judge, and next steps led by held-out v11
validation and a joint-data capture.
--view/--ghost previously always ran default menagerie physics -- the
configuration measured to oscillate ~2.5x harder than the real robot --
because --physics only wired into --eval. The Pareto winner is now a
named preset (FITTED_* in evaluate.py); --fitted applies it in both view
and eval, and --physics/--command-delay/--actuator-tau pass through to
the viewer for A/B against it.

Also verified two execution-fidelity details against the go2web
executor: the action path matches tick() exactly (clip, last_action =
clipped raw, target = act*act_scale + act_mean, blob kp/kd), and
MuJoCo free-joint qvel[3:6] is body-frame, matching the IMU gyro the
policy was trained on.
…knobs

Track now records joint angles and foot heights (clearance()) every
control tick, and _physics grows trunk_com_x / leg_mass_scale, so leg
behavior is measurable instead of eyeball-only.

Findings from the front-leg investigation (sim visibly high-steps its
front legs while every base statistic matches): the recording DOES carry
joint-space commands after all -- policy/lowcmd (2466 msgs, 44 Hz,
kp=40/kd=1) is the executor's commanded targets; only rt/lowcmd is
zeroed. FK on those targets: real front clearance p95 6.5 cm vs sim
9.6 cm, rear identical -- and sim commanded == sim executed, so the
plant is exonerated: the policy itself asks for the extra lift under
sim observations. Falsified as causes: contact solimp, torque limits,
com shift, leg mass, joint friction/damping, dq smoothing (each null
or worse). Next: leg-space statistics in the judge scored against
policy/lowcmd, and let the search fit the plant that makes the policy
choose the real gait.
read_policy_lowcmd() decodes the executor's commanded joint targets
(the recording's only joint-space ground truth) onto the shared epoch;
front_lift / rear_lift are p95 FK foot clearance of commanded targets,
compared command-to-command so the unrecorded real leg state and the
instability of open-loop replay never enter. Track records sim targets;
Report and the noise floor carry the leg statistics; the search gets a
fourth 'legs' objective plus trunk_com_x / leg_mass_scale dimensions,
and the damping floor drops to 0.05 (the old front leaned on 0.2).

At default physics the new statistic reads front_lift 0.217 sim vs
0.065 real (SNR 25) -- the visible front-leg prancing, now measurable.
… 0.065

The 400-trial four-objective search's min-max point holds gait 1.06 /
translation 0.98 / rotation 0.45 / legs 0.81 at once: commanded front
foot lift 0.060 vs 0.065 real (the base-only fit commanded 0.217), rear
0.039 vs 0.035, base statistics intact. Parameters became physically
coherent -- com +4.4 cm forward where the lidar and head sit, torsional
friction 0.030 vs the shipped 0.02, trunk +20% payload. FITTED_* and
FINDINGS updated; the base-only episode is recorded as the "a judge
only constrains what it can see" trap.
…stats

Three gaps Ivan's viewing session exposed:

- _physics never patched load_with_ghost, so --view --ghost ran stock
  physics under --fitted -- the "almost falling" was the unfitted model.
  Both loaders are patched now, with a regression test.
- The judge scored only t=6-26 of a 48 s recording; unscored time is
  unconstrained time. seconds=None (now the default everywhere) scores
  the entire run.
- tilt_p99 joins the gait group: the stability tail, so a config that
  occasionally almost falls pays even when its oscillation statistics
  look right. Real reference: p99 8.6 deg, max 16.7.
- Leg statistics widen from two clearance scalars to thigh/calf command
  spans per front/rear pair plus the command-space gait frequency --
  using the joint-angle streams instead of collapsing them.
…e, basin limit

The fitted config is visually confirmed on the robot's recording; the
follow-up full-run search found no dominating point, and re-scoring the
same config across processes showed the 4-seed noise floor drifting
(BLAS-order chaos), so single-recording fitting has hit its resolution
limit. The discriminator from here is the held-out v11 recording.
leshy added 5 commits August 4, 2026 19:35
Left untracked by 949ca87 -- the mode knob shipped without the tests
covering its global-config default, env-var path and rejection of an
unknown mode.
Discovery was expressible only as "how far does multicast reach"
(`zenoh_interface`, `zenoh_scouting`) -- there was no way to say whether
it runs at all. That is the knob the router topology needs, and the one
an A/B of the mesh needs, so it had to stop being a code edit.

Defaults are byte-identical to before: zenoh's own defaults for both keys
are true (zenoh-config-1.9.0 defaults.rs), and both sides now insert true,
so nothing changes for anyone who sets nothing. In particular gossip stays
on, which is what 9a746fa deliberately restored.

Both halves in one commit. `zenoh_mode` shipped python-first and silently
never reached native modules, which are exactly the processes that matter
on the robot; not repeating that.

Named for what they gate rather than for scouting: `zenoh_scouting` already
means "widen multicast to every interface", so a second knob saying
"scouting" would have read as the same thing.
The router work kept tripping over facts that are not in any doc and are
not what you would guess: a router forwards to CLIENTS only, so standing
one up changes nothing until the sessions behind it stop being peers;
routing/peer/mode=linkstate is not a key zenoh 1.9 accepts, so there is no
alternative; and cost is per LINK, not per subscriber, which is the whole
reason the topology matters on a wifi-attached robot.

Everything in it was measured against the pinned version rather than taken
from upstream docs, including the two that argue against intuition: a
client stays single-linked whether gossip is on or off, and a peer talks
to a client through a router perfectly well.
The planner and follower resolve odometry into base_link off tf, and that
tf came from GO2Zenoh on the LAPTOP -- so the robot was crossing wifi to
learn its own mount geometry, which is the one thing it cannot get wrong
and the one thing it already knows. Bake this alongside them and
`dimos/tf` stops being an external input:

  dimos bake motion_planner trajectory_follower cmd_vel_mux go2_tf --dry-run
  Internal connections:
    dimos/tf/tf2_msgs.TFMessage  out go2_tf.tf
                                 in  motion_planner.tf
                                 in  trajectory_follower.tf

A transcription of GO2Zenoh's transforms() and _publish_tf, so it is
locked to them by a fixture the PYTHON generates and both sides assert
against at 1e-12 -- rotations compared by their action on the basis
vectors, so q and -q cannot fake a pass. An 11-mutation battery says the
lock bites: negated euler terms, swapped quaternion product order, a
dropped to_radians, axis swaps, and both halves of the two-parents bug
are each caught. Reordering the edges is correctly NOT caught, which is
why the tests look edges up by the frames they join rather than by index.

Nothing yet stops GO2Zenoh publishing the same tf, so running both
double-publishes identical geometry. Gating that is the integration step
and is deliberately not in this commit.
@leshy leshy changed the title Ivan/feat/trajectory ctrl Ivan/feat/planning-trajectory Aug 4, 2026
@mintlify

mintlify Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
dimensional 🟢 Ready View Preview Aug 4, 2026, 7:41 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@codecov

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

❌ 53 Tests Failed:

Tests completed Failed Passed Skipped
3868 53 3815 175
View the top 3 failed test(s) by shortest run time
dimos.navigation.motion.control.referee.test_episode::test_boxed_in_refuses
Stack Traces | 0.001s run time
@pytest.fixture(scope="module")
    def policy() -> FreePolicy:
>       return FreePolicy.load(get_data("ml-trajectory-research/freewalk_mcf.bin"))


.../control/referee/test_episode.py:34: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/utils/data.py:304: in get_data
    archive_path = _decompress_archive(_pull_lfs_archive(archive_name))
        archive_name = 'ml-trajectory-research'
        data_dir   = PosixPath('.../dimos/dimos/data')
        file_path  = PosixPath('.../dimos/dimos/data/ml-trajectory-research/freewalk_mcf.bin')
        name       = 'ml-trajectory-research/freewalk_mcf.bin'
        nested_path = PosixPath('freewalk_mcf.bin')
        path_parts = ('ml-trajectory-research', 'freewalk_mcf.bin')
dimos/utils/data.py:248: in _pull_lfs_archive
    _lfs_pull(file_path, repo_root)
        file_path  = PosixPath('.../dimos/dimos/data/.lfs/ml-trajectory-research.tar.gz')
        filename   = 'ml-trajectory-research'
        repo_root  = PosixPath('.../work/dimos/dimos')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

file_path = PosixPath('.../dimos/dimos/data/.lfs/ml-trajectory-research.tar.gz')
repo_root = PosixPath('.../work/dimos/dimos')

    def _lfs_pull(file_path: Path, repo_root: Path, *, retries: int = 2) -> None:
        relative_path = file_path.relative_to(repo_root)
    
        env = os.environ.copy()
        env["GIT_LFS_FORCE_PROGRESS"] = "1"
    
        last_err: subprocess.CalledProcessError | None = None
        for attempt in range(1, retries + 2):  # retries + 1 total attempts
            try:
                subprocess.run(
                    ["git", "lfs", "pull", "--include", str(relative_path)],
                    cwd=repo_root,
                    check=True,
                    env=env,
                )
                return
            except subprocess.CalledProcessError as e:
                last_err = e
                if attempt <= retries:
                    time.sleep(attempt)  # 1s, 2s backoff
    
>       raise RuntimeError(
            f"Failed to pull LFS file {file_path} after {retries + 1} attempts: {last_err}"
        )
E       RuntimeError: Failed to pull LFS file .../dimos/dimos/data/.lfs/ml-trajectory-research.tar.gz after 3 attempts: Command '['git', 'lfs', 'pull', '--include', 'data/.lfs/ml-trajectory-research.tar.gz']' returned non-zero exit status 1.

attempt    = 3
env        = {'ACCEPT_EULA': 'Y', 'ACTIONS_ID_TOKEN_REQUEST_TOKEN': 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjM4ODI2YjE3LTZhMzAtNWY5Yi1iMTY5LT...-version=2.0', 'ACTIONS_ORCHESTRATION_ID': '2c87c586-97f7-4ca2-b528-e6e17a5b0b97.tests.ubuntu-24_04-arm_3_14_fal', ...}
file_path  = PosixPath('.../dimos/dimos/data/.lfs/ml-trajectory-research.tar.gz')
last_err   = CalledProcessError(1, ['git', 'lfs', 'pull', '--include', 'data/.lfs/ml-trajectory-research.tar.gz'])
relative_path = PosixPath('data/.lfs/ml-trajectory-research.tar.gz')
repo_root  = PosixPath('.../work/dimos/dimos')
retries    = 2

dimos/utils/data.py:216: RuntimeError
dimos.navigation.motion.control.referee.test_episode::test_commands_respect_hardware_slew
Stack Traces | 0.001s run time
@pytest.fixture(scope="module")
    def policy() -> FreePolicy:
>       return FreePolicy.load(get_data("ml-trajectory-research/freewalk_mcf.bin"))


.../control/referee/test_episode.py:34: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/utils/data.py:304: in get_data
    archive_path = _decompress_archive(_pull_lfs_archive(archive_name))
        archive_name = 'ml-trajectory-research'
        data_dir   = PosixPath('.../dimos/dimos/data')
        file_path  = PosixPath('.../dimos/dimos/data/ml-trajectory-research/freewalk_mcf.bin')
        name       = 'ml-trajectory-research/freewalk_mcf.bin'
        nested_path = PosixPath('freewalk_mcf.bin')
        path_parts = ('ml-trajectory-research', 'freewalk_mcf.bin')
dimos/utils/data.py:248: in _pull_lfs_archive
    _lfs_pull(file_path, repo_root)
        file_path  = PosixPath('.../dimos/dimos/data/.lfs/ml-trajectory-research.tar.gz')
        filename   = 'ml-trajectory-research'
        repo_root  = PosixPath('.../work/dimos/dimos')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

file_path = PosixPath('.../dimos/dimos/data/.lfs/ml-trajectory-research.tar.gz')
repo_root = PosixPath('.../work/dimos/dimos')

    def _lfs_pull(file_path: Path, repo_root: Path, *, retries: int = 2) -> None:
        relative_path = file_path.relative_to(repo_root)
    
        env = os.environ.copy()
        env["GIT_LFS_FORCE_PROGRESS"] = "1"
    
        last_err: subprocess.CalledProcessError | None = None
        for attempt in range(1, retries + 2):  # retries + 1 total attempts
            try:
                subprocess.run(
                    ["git", "lfs", "pull", "--include", str(relative_path)],
                    cwd=repo_root,
                    check=True,
                    env=env,
                )
                return
            except subprocess.CalledProcessError as e:
                last_err = e
                if attempt <= retries:
                    time.sleep(attempt)  # 1s, 2s backoff
    
>       raise RuntimeError(
            f"Failed to pull LFS file {file_path} after {retries + 1} attempts: {last_err}"
        )
E       RuntimeError: Failed to pull LFS file .../dimos/dimos/data/.lfs/ml-trajectory-research.tar.gz after 3 attempts: Command '['git', 'lfs', 'pull', '--include', 'data/.lfs/ml-trajectory-research.tar.gz']' returned non-zero exit status 1.

attempt    = 3
env        = {'ACCEPT_EULA': 'Y', 'ACTIONS_ID_TOKEN_REQUEST_TOKEN': 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjM4ODI2YjE3LTZhMzAtNWY5Yi1iMTY5LT...-version=2.0', 'ACTIONS_ORCHESTRATION_ID': '2c87c586-97f7-4ca2-b528-e6e17a5b0b97.tests.ubuntu-24_04-arm_3_14_fal', ...}
file_path  = PosixPath('.../dimos/dimos/data/.lfs/ml-trajectory-research.tar.gz')
last_err   = CalledProcessError(1, ['git', 'lfs', 'pull', '--include', 'data/.lfs/ml-trajectory-research.tar.gz'])
relative_path = PosixPath('data/.lfs/ml-trajectory-research.tar.gz')
repo_root  = PosixPath('.../work/dimos/dimos')
retries    = 2

dimos/utils/data.py:216: RuntimeError
dimos.navigation.motion.control.referee.test_episode::test_corridor_replan_battery_serial
Stack Traces | 0.001s run time
@pytest.fixture(scope="module")
    def policy() -> FreePolicy:
>       return FreePolicy.load(get_data("ml-trajectory-research/freewalk_mcf.bin"))


.../control/referee/test_episode.py:34: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/utils/data.py:304: in get_data
    archive_path = _decompress_archive(_pull_lfs_archive(archive_name))
        archive_name = 'ml-trajectory-research'
        data_dir   = PosixPath('.../dimos/dimos/data')
        file_path  = PosixPath('.../dimos/dimos/data/ml-trajectory-research/freewalk_mcf.bin')
        name       = 'ml-trajectory-research/freewalk_mcf.bin'
        nested_path = PosixPath('freewalk_mcf.bin')
        path_parts = ('ml-trajectory-research', 'freewalk_mcf.bin')
dimos/utils/data.py:248: in _pull_lfs_archive
    _lfs_pull(file_path, repo_root)
        file_path  = PosixPath('.../dimos/dimos/data/.lfs/ml-trajectory-research.tar.gz')
        filename   = 'ml-trajectory-research'
        repo_root  = PosixPath('.../work/dimos/dimos')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

file_path = PosixPath('.../dimos/dimos/data/.lfs/ml-trajectory-research.tar.gz')
repo_root = PosixPath('.../work/dimos/dimos')

    def _lfs_pull(file_path: Path, repo_root: Path, *, retries: int = 2) -> None:
        relative_path = file_path.relative_to(repo_root)
    
        env = os.environ.copy()
        env["GIT_LFS_FORCE_PROGRESS"] = "1"
    
        last_err: subprocess.CalledProcessError | None = None
        for attempt in range(1, retries + 2):  # retries + 1 total attempts
            try:
                subprocess.run(
                    ["git", "lfs", "pull", "--include", str(relative_path)],
                    cwd=repo_root,
                    check=True,
                    env=env,
                )
                return
            except subprocess.CalledProcessError as e:
                last_err = e
                if attempt <= retries:
                    time.sleep(attempt)  # 1s, 2s backoff
    
>       raise RuntimeError(
            f"Failed to pull LFS file {file_path} after {retries + 1} attempts: {last_err}"
        )
E       RuntimeError: Failed to pull LFS file .../dimos/dimos/data/.lfs/ml-trajectory-research.tar.gz after 3 attempts: Command '['git', 'lfs', 'pull', '--include', 'data/.lfs/ml-trajectory-research.tar.gz']' returned non-zero exit status 1.

attempt    = 3
env        = {'ACCEPT_EULA': 'Y', 'ACTIONS_ID_TOKEN_REQUEST_TOKEN': 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjM4ODI2YjE3LTZhMzAtNWY5Yi1iMTY5LT...-version=2.0', 'ACTIONS_ORCHESTRATION_ID': '2c87c586-97f7-4ca2-b528-e6e17a5b0b97.tests.ubuntu-24_04-arm_3_14_fal', ...}
file_path  = PosixPath('.../dimos/dimos/data/.lfs/ml-trajectory-research.tar.gz')
last_err   = CalledProcessError(1, ['git', 'lfs', 'pull', '--include', 'data/.lfs/ml-trajectory-research.tar.gz'])
relative_path = PosixPath('data/.lfs/ml-trajectory-research.tar.gz')
repo_root  = PosixPath('.../work/dimos/dimos')
retries    = 2

dimos/utils/data.py:216: RuntimeError
dimos.navigation.motion.control.referee.test_world::test_spawn_at_start_no_contact
Stack Traces | 0.001s run time
def test_spawn_at_start_no_contact() -> None:
>       model, data = world.load_world(DOOR_SIDE)


.../control/referee/test_world.py:54: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../control/referee/world.py:68: in load_world
    spec = mujoco.MjSpec.from_file(str(go2_model.scene_path(menagerie)))
        menagerie  = None
        physics    = None
        sc         = Scenario(name='door_side', boxes=[Box(cx=2.0, cy=1.28, sx=0.15, sy=1.6, yaw=0.0, height=0.6), Box(cx=2.0, cy=-1.28, sx..., precision=0.05, strafe=1.8, reverse=1.5, yaw_w=0.25), note='starts beside the door, 0.5 m off the wall, facing away')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

menagerie = None

    def scene_path(menagerie: Path | None = None) -> Path:
        """Path to the flat-ground go2 scene (menagerie ``unitree_go2/scene.xml``)."""
        root = menagerie or Path(os.environ.get("MUJOCO_MENAGERIE", DEFAULT_MENAGERIE))
        scene = root / "unitree_go2" / "scene.xml"
        if not scene.is_file():
>           raise FileNotFoundError(
                f"go2 scene not found at {scene}. Point MUJOCO_MENAGERIE at a "
                "mujoco_menagerie checkout."
            )
E           FileNotFoundError: go2 scene not found at .../menagerie/unitree_go2/scene.xml. Point MUJOCO_MENAGERIE at a mujoco_menagerie checkout.

menagerie  = None
root       = PosixPath('.../deep/mujoco/menagerie')
scene      = PosixPath('.../deep/mujoco/menagerie/unitree_go2/scene.xml')

.../motion/simulation/model.py:66: FileNotFoundError
dimos.navigation.motion.control.referee.test_world::test_teleport_into_wall_contacts
Stack Traces | 0.001s run time
def test_teleport_into_wall_contacts() -> None:
        sc = CORRIDOR
>       model, data = world.load_world(sc)

sc         = Scenario(name='corridor', boxes=[Box(cx=2.0, cy=0.6, sx=2.0, sy=0.3, yaw=0.0, height=0.6), Box(cx=2.0, cy=-0.6, sx=2.0..., center_off=-0.01, comfort=0.4, precision=0.05, strafe=1.8, reverse=1.5, yaw_w=0.25), note='0.9 m gap: thread center')

.../control/referee/test_world.py:66: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../control/referee/world.py:68: in load_world
    spec = mujoco.MjSpec.from_file(str(go2_model.scene_path(menagerie)))
        menagerie  = None
        physics    = None
        sc         = Scenario(name='corridor', boxes=[Box(cx=2.0, cy=0.6, sx=2.0, sy=0.3, yaw=0.0, height=0.6), Box(cx=2.0, cy=-0.6, sx=2.0..., center_off=-0.01, comfort=0.4, precision=0.05, strafe=1.8, reverse=1.5, yaw_w=0.25), note='0.9 m gap: thread center')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

menagerie = None

    def scene_path(menagerie: Path | None = None) -> Path:
        """Path to the flat-ground go2 scene (menagerie ``unitree_go2/scene.xml``)."""
        root = menagerie or Path(os.environ.get("MUJOCO_MENAGERIE", DEFAULT_MENAGERIE))
        scene = root / "unitree_go2" / "scene.xml"
        if not scene.is_file():
>           raise FileNotFoundError(
                f"go2 scene not found at {scene}. Point MUJOCO_MENAGERIE at a "
                "mujoco_menagerie checkout."
            )
E           FileNotFoundError: go2 scene not found at .../menagerie/unitree_go2/scene.xml. Point MUJOCO_MENAGERIE at a mujoco_menagerie checkout.

menagerie  = None
root       = PosixPath('.../deep/mujoco/menagerie')
scene      = PosixPath('.../deep/mujoco/menagerie/unitree_go2/scene.xml')

.../motion/simulation/model.py:66: FileNotFoundError
dimos.navigation.motion.control.test_rust_parity::test_ceilings_to_clearance_matches_python
Stack Traces | 0.001s run time
def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
>           import dimos_motion2_tc
E           ModuleNotFoundError: No module named 'dimos_motion2_tc'


.../motion/control/controller.py:167: ModuleNotFoundError

The above exception was the direct cause of the following exception:

    def test_ceilings_to_clearance_matches_python() -> None:
        """The dialect's inverse leg, the half the follower module runs.
    
        A hinted follower with no cloud of its own has no clearance array, and the
        hinted law takes no stamps -- so the stamps are decoded to ceilings and
        bent back into the clearance that produced them. Both legs are wire
        constants, so a divergence here would silently re-price every waypoint of
        a plan the robot is already following.
        """
>       rs = load_extension()


.../motion/control/test_rust_parity.py:370: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
            import dimos_motion2_tc
        except ImportError as e:
>           raise ImportError(f"dimos_motion2_tc is not built; run: {BUILD_CMD}") from e
E           ImportError: dimos_motion2_tc is not built; run: uv run maturin develop --uv --release -m .../control/rust/Cargo.toml


.../motion/control/controller.py:169: ImportError
dimos.navigation.motion.control.test_rust_parity::test_encode_precision_matches_python
Stack Traces | 0.001s run time
def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
>           import dimos_motion2_tc
E           ModuleNotFoundError: No module named 'dimos_motion2_tc'


.../motion/control/controller.py:167: ModuleNotFoundError

The above exception was the direct cause of the following exception:

    def test_encode_precision_matches_python() -> None:
        """The dialect's producer side, the half the planner module runs.
    
        Same sweep as the laws, because the encoder sees the same paths: fan
        clusters price by yaw, degenerate paths must not raise, and a
        wrong-length annotation has to be ignored identically on both sides or a
        stamped path would decode to a speed the planner never intended.
        """
>       rs = load_extension()


.../motion/control/test_rust_parity.py:344: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
            import dimos_motion2_tc
        except ImportError as e:
>           raise ImportError(f"dimos_motion2_tc is not built; run: {BUILD_CMD}") from e
E           ImportError: dimos_motion2_tc is not built; run: uv run maturin develop --uv --release -m .../control/rust/Cargo.toml


.../motion/control/controller.py:169: ImportError
dimos.navigation.motion.simulation.test_evaluate::test_leg_stats_rise_when_the_front_thigh_lifts
Stack Traces | 0.001s run time
def test_leg_stats_rise_when_the_front_thigh_lifts():
        """FK sanity: raising front thigh targets must raise front_lift only."""
        from dimos.navigation.motion.simulation.evaluate import leg_stats
    
        t = np.arange(50) * 0.02
        stand = np.tile([0.0, 0.9, -1.8] * 4, (50, 1))
        lifted = stand.copy()
        lifted[:, [1, 4]] -= 0.5  # front thighs swing forward/up (FL, FR)
>       a, b = leg_stats(t, stand), leg_stats(t, lifted)

leg_stats  = <function leg_stats at 0xff36b4e00ea0>
lifted     = array([[          0,         0.4,        -1.8,           0,         0.4,        -1.8,           0,         0.9,       ...           0,         0.4,        -1.8,           0,         0.9,        -1.8,           0,         0.9,        -1.8]])
stand      = array([[          0,         0.9,        -1.8,           0,         0.9,        -1.8,           0,         0.9,       ...           0,         0.9,        -1.8,           0,         0.9,        -1.8,           0,         0.9,        -1.8]])
t          = array([          0,        0.02,        0.04,        0.06,        0.08,         0.1,        0.12,        0.14,        ...  0.82,        0.84,        0.86,        0.88,         0.9,        0.92,        0.94,
              0.96,        0.98])

.../motion/simulation/test_evaluate.py:161: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../motion/simulation/evaluate.py:128: in leg_stats
    c = commanded_clearance(targets)
        span       = <function leg_stats.<locals>.span at 0xff369ea039c0>
        t          = array([          0,        0.02,        0.04,        0.06,        0.08,         0.1,        0.12,        0.14,        ...  0.82,        0.84,        0.86,        0.88,         0.9,        0.92,        0.94,
              0.96,        0.98])
        targets    = array([[          0,         0.9,        -1.8,           0,         0.9,        -1.8,           0,         0.9,       ...           0,         0.9,        -1.8,           0,         0.9,        -1.8,           0,         0.9,        -1.8]])
.../motion/simulation/evaluate.py:99: in commanded_clearance
    model, data = go2_model.load()
        base_z     = 0.318
        targets    = array([[          0,         0.9,        -1.8,           0,         0.9,        -1.8,           0,         0.9,       ...           0,         0.9,        -1.8,           0,         0.9,        -1.8,           0,         0.9,        -1.8]])
.../motion/simulation/model.py:74: in load
    model = mujoco.MjModel.from_xml_path(str(scene_path(menagerie)))
        menagerie  = None
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

menagerie = None

    def scene_path(menagerie: Path | None = None) -> Path:
        """Path to the flat-ground go2 scene (menagerie ``unitree_go2/scene.xml``)."""
        root = menagerie or Path(os.environ.get("MUJOCO_MENAGERIE", DEFAULT_MENAGERIE))
        scene = root / "unitree_go2" / "scene.xml"
        if not scene.is_file():
>           raise FileNotFoundError(
                f"go2 scene not found at {scene}. Point MUJOCO_MENAGERIE at a "
                "mujoco_menagerie checkout."
            )
E           FileNotFoundError: go2 scene not found at .../menagerie/unitree_go2/scene.xml. Point MUJOCO_MENAGERIE at a mujoco_menagerie checkout.

menagerie  = None
root       = PosixPath('.../deep/mujoco/menagerie')
scene      = PosixPath('.../deep/mujoco/menagerie/unitree_go2/scene.xml')

.../motion/simulation/model.py:66: FileNotFoundError
dimos.navigation.motion.simulation.test_evaluate::test_physics_override_applies_and_restores
Stack Traces | 0.001s run time
def test_physics_override_applies_and_restores():
        original = go2_model.load
>       model, _ = go2_model.load()

original   = <function load at 0xff36b4e0e5c0>

.../motion/simulation/test_evaluate.py:106: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../motion/simulation/model.py:74: in load
    model = mujoco.MjModel.from_xml_path(str(scene_path(menagerie)))
        menagerie  = None
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

menagerie = None

    def scene_path(menagerie: Path | None = None) -> Path:
        """Path to the flat-ground go2 scene (menagerie ``unitree_go2/scene.xml``)."""
        root = menagerie or Path(os.environ.get("MUJOCO_MENAGERIE", DEFAULT_MENAGERIE))
        scene = root / "unitree_go2" / "scene.xml"
        if not scene.is_file():
>           raise FileNotFoundError(
                f"go2 scene not found at {scene}. Point MUJOCO_MENAGERIE at a "
                "mujoco_menagerie checkout."
            )
E           FileNotFoundError: go2 scene not found at .../menagerie/unitree_go2/scene.xml. Point MUJOCO_MENAGERIE at a mujoco_menagerie checkout.

menagerie  = None
root       = PosixPath('.../deep/mujoco/menagerie')
scene      = PosixPath('.../deep/mujoco/menagerie/unitree_go2/scene.xml')

.../motion/simulation/model.py:66: FileNotFoundError
dimos.navigation.motion.simulation.test_evaluate::test_physics_override_reaches_the_ghost_loader
Stack Traces | 0.001s run time
def test_physics_override_reaches_the_ghost_loader():
        """--view --ghost builds through load_with_ghost, which once went
        unpatched: --fitted showed stock physics whenever the ghost was on."""
        with _physics({"armature": 0.07}):
>           model, _ = go2_model.load_with_ghost()


.../motion/simulation/test_evaluate.py:193: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../motion/simulation/evaluate.py:254: in patched_ghost
    model, data = original_ghost(menagerie, rgba)
        menagerie  = None
        original_ghost = <function load_with_ghost at 0xff36b4e0e840>
        overrides  = {'armature': 0.07}
        rgba       = (0.2, 1.0, 0.2, 0.35)
.../motion/simulation/model.py:127: in load_with_ghost
    spec = mujoco.MjSpec.from_file(str(scene_path(menagerie)))
        menagerie  = None
        rgba       = (0.2, 1.0, 0.2, 0.35)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

menagerie = None

    def scene_path(menagerie: Path | None = None) -> Path:
        """Path to the flat-ground go2 scene (menagerie ``unitree_go2/scene.xml``)."""
        root = menagerie or Path(os.environ.get("MUJOCO_MENAGERIE", DEFAULT_MENAGERIE))
        scene = root / "unitree_go2" / "scene.xml"
        if not scene.is_file():
>           raise FileNotFoundError(
                f"go2 scene not found at {scene}. Point MUJOCO_MENAGERIE at a "
                "mujoco_menagerie checkout."
            )
E           FileNotFoundError: go2 scene not found at .../menagerie/unitree_go2/scene.xml. Point MUJOCO_MENAGERIE at a mujoco_menagerie checkout.

menagerie  = None
root       = PosixPath('.../deep/mujoco/menagerie')
scene      = PosixPath('.../deep/mujoco/menagerie/unitree_go2/scene.xml')

.../motion/simulation/model.py:66: FileNotFoundError
dimos.navigation.motion.simulation.test_evaluate::test_physics_override_sets_foot_friction
Stack Traces | 0.001s run time
def test_physics_override_sets_foot_friction():
        import mujoco
    
        with _physics({"foot_friction": 1.1, "foot_friction_torsional": 0.09}):
>           model, _ = go2_model.load()

mujoco     = <module 'mujoco' from '.../dimos/dimos/.venv/lib/python3.12.../site-packages/mujoco/__init__.py'>

.../motion/simulation/test_evaluate.py:145: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../motion/simulation/evaluate.py:244: in patched
    model, data = original(menagerie)
        menagerie  = None
        original   = <function load at 0xff36b4e0e5c0>
        overrides  = {'foot_friction': 1.1, 'foot_friction_torsional': 0.09}
.../motion/simulation/model.py:74: in load
    model = mujoco.MjModel.from_xml_path(str(scene_path(menagerie)))
        menagerie  = None
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

menagerie = None

    def scene_path(menagerie: Path | None = None) -> Path:
        """Path to the flat-ground go2 scene (menagerie ``unitree_go2/scene.xml``)."""
        root = menagerie or Path(os.environ.get("MUJOCO_MENAGERIE", DEFAULT_MENAGERIE))
        scene = root / "unitree_go2" / "scene.xml"
        if not scene.is_file():
>           raise FileNotFoundError(
                f"go2 scene not found at {scene}. Point MUJOCO_MENAGERIE at a "
                "mujoco_menagerie checkout."
            )
E           FileNotFoundError: go2 scene not found at .../menagerie/unitree_go2/scene.xml. Point MUJOCO_MENAGERIE at a mujoco_menagerie checkout.

menagerie  = None
root       = PosixPath('.../deep/mujoco/menagerie')
scene      = PosixPath('.../deep/mujoco/menagerie/unitree_go2/scene.xml')

.../motion/simulation/model.py:66: FileNotFoundError
dimos.navigation.motion.simulation.test_metrics::test_command_slew_ramps_a_step_at_the_hardware_rate
Stack Traces | 0.001s run time
def test_command_slew_ramps_a_step_at_the_hardware_rate():
        """The policy must see the robot's ramped command, not the operator step:
        vyaw moves at most 0.10 rad/s per 20 ms tick (go2web VEL_DV_VYAW)."""
        from dimos.navigation.motion.simulation import walk as walk_mod
    
        class FakePolicy:
            hist, act_dim, obs_per_frame = 1, 12, 45
            default_pose = np.zeros(12)
            kp, kd = np.full(12, 40.0), np.full(12, 1.0)
    
            def normalize(self, raw):
                return raw
    
            def act(self, _p_obs, _cmd):
                return np.zeros(12), self.default_pose
    
        t = np.arange(0.0, 4.0, 0.1)
        cmd = np.stack([np.zeros_like(t), np.zeros_like(t), np.where(t < 2.0, -1.0, 1.0)], 1)
>       track = walk_mod.walk(FakePolicy(), schedule=(t, cmd), seconds=4.0)

FakePolicy = <class 'motion.simulation.test_metrics.test_command_slew_ramps_a_step_at_the_hardware_rate.<locals>.FakePolicy'>
cmd        = array([[          0,           0,          -1],
       [          0,           0,          -1],
       [          0,  ...      0,           1],
       [          0,           0,           1],
       [          0,           0,           1]])
t          = array([          0,         0.1,         0.2,         0.3,         0.4,         0.5,         0.6,         0.7,        ...,         3.1,         3.2,         3.3,         3.4,         3.5,         3.6,         3.7,         3.8,         3.9])
walk_mod   = <module 'dimos.navigation.motion.simulation.walk' from '.../motion/simulation/walk.py'>

.../motion/simulation/test_metrics.py:264: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../motion/simulation/walk.py:270: in walk
    model, data = go2_model.load(menagerie)
        actuator_tau = 0.0
        command    = None
        command_delay = 0.0
        duration   = 4.0
        ghost      = None
        heights    = None
        menagerie  = None
        policy     = <motion.simulation.test_metrics.test_command_slew_ramps_a_step_at_the_hardware_rate.<locals>.FakePolicy object at 0xff369e507020>
        sched_cmd  = array([[          0,           0,          -1],
       [          0,           0,          -1],
       [          0,  ...      0,           1],
       [          0,           0,           1],
       [          0,           0,           1]])
        sched_t    = array([          0,         0.1,         0.2,         0.3,         0.4,         0.5,         0.6,         0.7,        ...,         3.1,         3.2,         3.3,         3.4,         3.5,         3.6,         3.7,         3.8,         3.9])
        schedule   = (array([          0,         0.1,         0.2,         0.3,         0.4,         0.5,         0.6,         0.7,       ...     0,           1],
       [          0,           0,           1],
       [          0,           0,           1]]))
        seconds    = 4.0
        settle     = 0.5
        slew       = True
        span       = 3.9000000000000004
        speed      = 1.0
        start      = 0.0
        view       = False
.../motion/simulation/model.py:74: in load
    model = mujoco.MjModel.from_xml_path(str(scene_path(menagerie)))
        menagerie  = None
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

menagerie = None

    def scene_path(menagerie: Path | None = None) -> Path:
        """Path to the flat-ground go2 scene (menagerie ``unitree_go2/scene.xml``)."""
        root = menagerie or Path(os.environ.get("MUJOCO_MENAGERIE", DEFAULT_MENAGERIE))
        scene = root / "unitree_go2" / "scene.xml"
        if not scene.is_file():
>           raise FileNotFoundError(
                f"go2 scene not found at {scene}. Point MUJOCO_MENAGERIE at a "
                "mujoco_menagerie checkout."
            )
E           FileNotFoundError: go2 scene not found at .../menagerie/unitree_go2/scene.xml. Point MUJOCO_MENAGERIE at a mujoco_menagerie checkout.

menagerie  = None
root       = PosixPath('.../deep/mujoco/menagerie')
scene      = PosixPath('.../deep/mujoco/menagerie/unitree_go2/scene.xml')

.../motion/simulation/model.py:66: FileNotFoundError
dimos.navigation.motion.simulation.test_metrics::test_gait_height_schedule_reaches_obs_channel_45
Stack Traces | 0.001s run time
def test_gait_height_schedule_reaches_obs_channel_45():
        """A 46-channel policy must see the commanded height at index 45, held at
        nominal before the first entry; a 45-channel policy must see nothing --
        the frame stays 45 wide."""
        from dimos.navigation.motion.simulation import walk as walk_mod
    
        class FakePolicy:
            hist, act_dim, obs_per_frame = 1, 12, 46
            default_pose = np.zeros(12)
            kp, kd = np.full(12, 40.0), np.full(12, 1.0)
    
            def __init__(self):
                self.frames = []
    
            def normalize(self, raw):
                self.frames.append(raw.copy())
                return raw
    
            def act(self, _p_obs, _cmd):
                return np.zeros(12), self.default_pose
    
        t = np.arange(0.0, 4.0, 0.1)
        cmd = np.stack([np.zeros_like(t)] * 3, 1)
        heights = (np.array([2.0]), np.array([0.15]))
        policy = FakePolicy()
>       track = walk_mod.walk(policy, schedule=(t, cmd), heights=heights, seconds=4.0)

FakePolicy = <class 'motion.simulation.test_metrics.test_gait_height_schedule_reaches_obs_channel_45.<locals>.FakePolicy'>
cmd        = array([[          0,           0,           0],
       [          0,           0,           0],
       [          0,  ...      0,           0],
       [          0,           0,           0],
       [          0,           0,           0]])
heights    = (array([          2]), array([       0.15]))
policy     = <motion.simulation.test_metrics.test_gait_height_schedule_reaches_obs_channel_45.<locals>.FakePolicy object at 0xff369e504b30>
t          = array([          0,         0.1,         0.2,         0.3,         0.4,         0.5,         0.6,         0.7,        ...,         3.1,         3.2,         3.3,         3.4,         3.5,         3.6,         3.7,         3.8,         3.9])
walk_mod   = <module 'dimos.navigation.motion.simulation.walk' from '.../motion/simulation/walk.py'>

.../motion/simulation/test_metrics.py:303: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../motion/simulation/walk.py:270: in walk
    model, data = go2_model.load(menagerie)
        actuator_tau = 0.0
        command    = None
        command_delay = 0.0
        duration   = 4.0
        ghost      = None
        heights    = (array([          2]), array([       0.15]))
        menagerie  = None
        policy     = <motion.simulation.test_metrics.test_gait_height_schedule_reaches_obs_channel_45.<locals>.FakePolicy object at 0xff369e504b30>
        sched_cmd  = array([[          0,           0,           0],
       [          0,           0,           0],
       [          0,  ...      0,           0],
       [          0,           0,           0],
       [          0,           0,           0]])
        sched_t    = array([          0,         0.1,         0.2,         0.3,         0.4,         0.5,         0.6,         0.7,        ...,         3.1,         3.2,         3.3,         3.4,         3.5,         3.6,         3.7,         3.8,         3.9])
        schedule   = (array([          0,         0.1,         0.2,         0.3,         0.4,         0.5,         0.6,         0.7,       ...     0,           0],
       [          0,           0,           0],
       [          0,           0,           0]]))
        seconds    = 4.0
        settle     = 0.5
        slew       = True
        span       = 3.9000000000000004
        speed      = 1.0
        start      = 0.0
        view       = False
.../motion/simulation/model.py:74: in load
    model = mujoco.MjModel.from_xml_path(str(scene_path(menagerie)))
        menagerie  = None
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

menagerie = None

    def scene_path(menagerie: Path | None = None) -> Path:
        """Path to the flat-ground go2 scene (menagerie ``unitree_go2/scene.xml``)."""
        root = menagerie or Path(os.environ.get("MUJOCO_MENAGERIE", DEFAULT_MENAGERIE))
        scene = root / "unitree_go2" / "scene.xml"
        if not scene.is_file():
>           raise FileNotFoundError(
                f"go2 scene not found at {scene}. Point MUJOCO_MENAGERIE at a "
                "mujoco_menagerie checkout."
            )
E           FileNotFoundError: go2 scene not found at .../menagerie/unitree_go2/scene.xml. Point MUJOCO_MENAGERIE at a mujoco_menagerie checkout.

menagerie  = None
root       = PosixPath('.../deep/mujoco/menagerie')
scene      = PosixPath('.../deep/mujoco/menagerie/unitree_go2/scene.xml')

.../motion/simulation/model.py:66: FileNotFoundError
dimos.navigation.motion.simulation.test_metrics::test_start_offset_reaches_the_command_schedule
Stack Traces | 0.001s run time
def test_start_offset_reaches_the_command_schedule():
        """Regression: --start must shift the commands, not only the ghost.
    
        A failed patch once left cmd_at() reading from t while the ghost read from
        t + start, so every comparison drove the simulator with the first seconds
        of a run and scored it against a ghost six seconds later. It looked
        plausible -- the robot walked -- and silently invalidated every number.
        """
        from dimos.navigation.motion.simulation import walk as walk_mod
    
        class FakePolicy:
            hist, act_dim, obs_per_frame = 1, 12, 45
            default_pose = np.zeros(12)
            kp, kd = np.full(12, 40.0), np.full(12, 1.0)
    
            def normalize(self, raw):
                return raw
    
            def act(self, _p_obs, _cmd):
                return np.zeros(12), self.default_pose
    
        t = np.arange(0.0, 10.0, 0.1)
        cmd = np.stack([np.zeros_like(t), np.zeros_like(t), t], 1)  # vyaw ramps with time
>       track = walk_mod.walk(FakePolicy(), schedule=(t, cmd), seconds=2.0, start=5.0)

FakePolicy = <class 'motion.simulation.test_metrics.test_start_offset_reaches_the_command_schedule.<locals>.FakePolicy'>
cmd        = array([[          0,           0,           0],
       [          0,           0,         0.1],
       [          0,  ...      0,         9.7],
       [          0,           0,         9.8],
       [          0,           0,         9.9]])
t          = array([          0,         0.1,         0.2,         0.3,         0.4,         0.5,         0.6,         0.7,        ...   9.1,         9.2,         9.3,         9.4,         9.5,
               9.6,         9.7,         9.8,         9.9])
walk_mod   = <module 'dimos.navigation.motion.simulation.walk' from '.../motion/simulation/walk.py'>

.../motion/simulation/test_metrics.py:229: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../motion/simulation/walk.py:270: in walk
    model, data = go2_model.load(menagerie)
        actuator_tau = 0.0
        command    = None
        command_delay = 0.0
        duration   = 2.0
        ghost      = None
        heights    = None
        menagerie  = None
        policy     = <motion.simulation.test_metrics.test_start_offset_reaches_the_command_schedule.<locals>.FakePolicy object at 0xff369e491e50>
        sched_cmd  = array([[          0,           0,           0],
       [          0,           0,         0.1],
       [          0,  ...      0,         9.7],
       [          0,           0,         9.8],
       [          0,           0,         9.9]])
        sched_t    = array([          0,         0.1,         0.2,         0.3,         0.4,         0.5,         0.6,         0.7,        ...   9.1,         9.2,         9.3,         9.4,         9.5,
               9.6,         9.7,         9.8,         9.9])
        schedule   = (array([          0,         0.1,         0.2,         0.3,         0.4,         0.5,         0.6,         0.7,       ...     0,         9.7],
       [          0,           0,         9.8],
       [          0,           0,         9.9]]))
        seconds    = 2.0
        settle     = 0.5
        slew       = True
        span       = 9.9
        speed      = 1.0
        start      = 5.0
        view       = False
.../motion/simulation/model.py:74: in load
    model = mujoco.MjModel.from_xml_path(str(scene_path(menagerie)))
        menagerie  = None
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

menagerie = None

    def scene_path(menagerie: Path | None = None) -> Path:
        """Path to the flat-ground go2 scene (menagerie ``unitree_go2/scene.xml``)."""
        root = menagerie or Path(os.environ.get("MUJOCO_MENAGERIE", DEFAULT_MENAGERIE))
        scene = root / "unitree_go2" / "scene.xml"
        if not scene.is_file():
>           raise FileNotFoundError(
                f"go2 scene not found at {scene}. Point MUJOCO_MENAGERIE at a "
                "mujoco_menagerie checkout."
            )
E           FileNotFoundError: go2 scene not found at .../menagerie/unitree_go2/scene.xml. Point MUJOCO_MENAGERIE at a mujoco_menagerie checkout.

menagerie  = None
root       = PosixPath('.../deep/mujoco/menagerie')
scene      = PosixPath('.../deep/mujoco/menagerie/unitree_go2/scene.xml')

.../motion/simulation/model.py:66: FileNotFoundError
dimos.navigation.motion.control.referee.test_world::test_walls_match_scenario_geometry
Stack Traces | 0.002s run time
def test_walls_match_scenario_geometry() -> None:
>       model, data = world.load_world(CORRIDOR)


.../control/referee/test_world.py:40: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../control/referee/world.py:68: in load_world
    spec = mujoco.MjSpec.from_file(str(go2_model.scene_path(menagerie)))
        menagerie  = None
        physics    = None
        sc         = Scenario(name='corridor', boxes=[Box(cx=2.0, cy=0.6, sx=2.0, sy=0.3, yaw=0.0, height=0.6), Box(cx=2.0, cy=-0.6, sx=2.0..., center_off=-0.01, comfort=0.4, precision=0.05, strafe=1.8, reverse=1.5, yaw_w=0.25), note='0.9 m gap: thread center')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

menagerie = None

    def scene_path(menagerie: Path | None = None) -> Path:
        """Path to the flat-ground go2 scene (menagerie ``unitree_go2/scene.xml``)."""
        root = menagerie or Path(os.environ.get("MUJOCO_MENAGERIE", DEFAULT_MENAGERIE))
        scene = root / "unitree_go2" / "scene.xml"
        if not scene.is_file():
>           raise FileNotFoundError(
                f"go2 scene not found at {scene}. Point MUJOCO_MENAGERIE at a "
                "mujoco_menagerie checkout."
            )
E           FileNotFoundError: go2 scene not found at .../menagerie/unitree_go2/scene.xml. Point MUJOCO_MENAGERIE at a mujoco_menagerie checkout.

menagerie  = None
root       = PosixPath('.../deep/mujoco/menagerie')
scene      = PosixPath('.../deep/mujoco/menagerie/unitree_go2/scene.xml')

.../motion/simulation/model.py:66: FileNotFoundError
dimos.navigation.motion.control.test_rust_parity::test_path_clearance_matches_scipy
Stack Traces | 0.002s run time
def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
>           import dimos_motion2_tc
E           ModuleNotFoundError: No module named 'dimos_motion2_tc'


.../motion/control/controller.py:167: ModuleNotFoundError

The above exception was the direct cause of the following exception:

    def test_path_clearance_matches_scipy() -> None:
        """The room hint, against the cKDTree the python uses.
    
        Only the DISTANCE crosses this boundary, never which point produced it,
        so the rust is free to use a grid where the python uses a KD-tree: an
        exact nearest-neighbour distance is unique even when the nearest point is
        not. Agreement here is therefore exact, not approximate.
        """
>       rs = load_extension()


.../motion/control/test_rust_parity.py:396: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
            import dimos_motion2_tc
        except ImportError as e:
>           raise ImportError(f"dimos_motion2_tc is not built; run: {BUILD_CMD}") from e
E           ImportError: dimos_motion2_tc is not built; run: uv run maturin develop --uv --release -m .../control/rust/Cargo.toml


.../motion/control/controller.py:169: ImportError
dimos.navigation.motion.control.test_rust_parity::test_registry_and_build_hint
Stack Traces | 0.002s run time
def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
>           import dimos_motion2_tc
E           ModuleNotFoundError: No module named 'dimos_motion2_tc'


.../motion/control/controller.py:167: ModuleNotFoundError

The above exception was the direct cause of the following exception:

    def test_registry_and_build_hint() -> None:
        from dimos.navigation.motion.control.controller import REGISTRY, load
    
        for name, (_, make_rs) in LAWS.items():
            assert REGISTRY[f"{name}-rs"].endswith(":make_rust")
            assert load(f"{name}-rs") is make_rs
>           assert isinstance(load(f"{name}-rs")().config, ControllerConfig)

REGISTRY   = {'blind': 'dimos.navigation.motion.control.laws.blind:make', 'blind-rs': 'dimos.navigation.motion.control.laws.blind:m...navigation.motion.control.laws.hinted:make', 'hinted-rs': 'dimos.navigation.motion.control.laws.hinted:make_rust', ...}
_          = <function make at 0xffe9e3d3cc20>
load       = <function load at 0xffe9e3c32840>
make_rs    = <function make_rust at 0xffe9e3d3ccc0>
name       = 'seed'

.../motion/control/test_rust_parity.py:270: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../control/laws/seed.py:46: in make_rust
    return RustPursuitController(config)
        config     = None
.../control/laws/seed.py:143: in __init__
    self._mod: Any = load_extension()
        config     = None
        self       = <dimos.navigation.motion.control.laws.seed.RustPursuitController object at 0xffe9d8544f50>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
            import dimos_motion2_tc
        except ImportError as e:
>           raise ImportError(f"dimos_motion2_tc is not built; run: {BUILD_CMD}") from e
E           ImportError: dimos_motion2_tc is not built; run: uv run maturin develop --uv --release -m .../control/rust/Cargo.toml


.../motion/control/controller.py:169: ImportError
dimos.navigation.motion.simulation.test_vive::test_base_track_anchors_first_sample_at_origin
Stack Traces | 0.002s run time
tmp_path = PosixPath('.../pytest-0/popen-gw0/test_base_track_anchors_first_0')

    def test_base_track_anchors_first_sample_at_origin(tmp_path):
        from dimos.navigation.motion.simulation.vive import base_track
    
        pos = [[5.0, 5.0, 1.0], [5.5, 5.0, 1.0], [6.0, 5.0, 1.0]]
        quat = [[1.0, 0, 0, 0]] * 3
>       t, p, q = base_track(_write(tmp_path, pos, quat), tracker_offset=np.zeros(3), mount=np.eye(3))

base_track = <function base_track at 0xff36b4e011c0>
pos        = [[5.0, 5.0, 1.0], [5.5, 5.0, 1.0], [6.0, 5.0, 1.0]]
quat       = [[1.0, 0, 0, 0], [1.0, 0, 0, 0], [1.0, 0, 0, 0]]
tmp_path   = PosixPath('.../pytest-0/popen-gw0/test_base_track_anchors_first_0')

.../motion/simulation/test_vive.py:108: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

tmp_path = PosixPath('.../pytest-0/popen-gw0/test_base_track_anchors_first_0')
pos = [[5.0, 5.0, 1.0], [5.5, 5.0, 1.0], [6.0, 5.0, 1.0]]
quat = [[1.0, 0, 0, 0], [1.0, 0, 0, 0], [1.0, 0, 0, 0]]

    def _write(tmp_path, pos, quat, *, walk_at=None, t_host_shift=None, heights_at=()):
        """A minimal mcap carrying vive/pose (and optionally control_log entries)."""
        import json
    
>       from mcap.writer import Writer
E       ModuleNotFoundError: No module named 'mcap'

heights_at = ()
json       = <module 'json' from '.../usr/lib/python3.12/json/__init__.py'>
pos        = [[5.0, 5.0, 1.0], [5.5, 5.0, 1.0], [6.0, 5.0, 1.0]]
quat       = [[1.0, 0, 0, 0], [1.0, 0, 0, 0], [1.0, 0, 0, 0]]
t_host_shift = None
tmp_path   = PosixPath('.../pytest-0/popen-gw0/test_base_track_anchors_first_0')
walk_at    = None

.../motion/simulation/test_vive.py:77: ModuleNotFoundError
dimos.navigation.motion.simulation.test_vive::test_read_gait_height_shares_the_epoch
Stack Traces | 0.002s run time
tmp_path = PosixPath('.../pytest-0/popen-gw0/test_read_gait_height_shares_t0')

    def test_read_gait_height_shares_the_epoch(tmp_path):
        """Height commands must land on the same clock as everything else: t=0 at
        the first walk command, not at the first slider touch."""
        from dimos.navigation.motion.simulation.walk import read_gait_height
    
>       path = _write(
            tmp_path,
            [[0.0, 0, 0]] * 2,
            [[1.0, 0, 0, 0]] * 2,
            walk_at=0.02,
            heights_at=[(0.01, 0.2), (0.05, 0.33)],
        )

read_gait_height = <function read_gait_height at 0xff36b4e00ae0>
tmp_path   = PosixPath('.../pytest-0/popen-gw0/test_read_gait_height_shares_t0')

.../motion/simulation/test_vive.py:234: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

tmp_path = PosixPath('.../pytest-0/popen-gw0/test_read_gait_height_shares_t0')
pos = [[0.0, 0, 0], [0.0, 0, 0]], quat = [[1.0, 0, 0, 0], [1.0, 0, 0, 0]]

    def _write(tmp_path, pos, quat, *, walk_at=None, t_host_shift=None, heights_at=()):
        """A minimal mcap carrying vive/pose (and optionally control_log entries)."""
        import json
    
>       from mcap.writer import Writer
E       ModuleNotFoundError: No module named 'mcap'

heights_at = [(0.01, 0.2), (0.05, 0.33)]
json       = <module 'json' from '.../usr/lib/python3.12/json/__init__.py'>
pos        = [[0.0, 0, 0], [0.0, 0, 0]]
quat       = [[1.0, 0, 0, 0], [1.0, 0, 0, 0]]
t_host_shift = None
tmp_path   = PosixPath('.../pytest-0/popen-gw0/test_read_gait_height_shares_t0')
walk_at    = 0.02

.../motion/simulation/test_vive.py:77: ModuleNotFoundError
dimos.navigation.motion.simulation.test_vive::test_read_policy_lowcmd_remaps_and_shares_the_epoch
Stack Traces | 0.002s run time
tmp_path = PosixPath('.../pytest-0/popen-gw0/test_read_policy_lowcmd_remaps0')

    def test_read_policy_lowcmd_remaps_and_shares_the_epoch(tmp_path):
        """SDK motor order in, training order out; t=0 at the first walk command."""
        import json
    
>       from mcap.writer import Writer
E       ModuleNotFoundError: No module named 'mcap'

json       = <module 'json' from '.../usr/lib/python3.12/json/__init__.py'>
tmp_path   = PosixPath('.../pytest-0/popen-gw0/test_read_policy_lowcmd_remaps0')

.../motion/simulation/test_vive.py:205: ModuleNotFoundError
dimos.navigation.motion.simulation.test_vive::test_tracker_offset_is_a_lever_arm_not_a_shift
Stack Traces | 0.002s run time
tmp_path = PosixPath('.../pytest-0/popen-gw0/test_tracker_offset_is_a_lever0')

    def test_tracker_offset_is_a_lever_arm_not_a_shift(tmp_path):
        """A pure offset cancels while the body holds still, and bites once it turns."""
        from dimos.navigation.motion.simulation.vive import base_track
    
        yaw90 = [0.7071068, 0.0, 0.0, 0.7071068]
>       path = _write(tmp_path, [[0.0, 0, 2.0], [0.0, 0, 2.0]], [[1.0, 0, 0, 0], yaw90])

base_track = <function base_track at 0xff36b4e011c0>
tmp_path   = PosixPath('.../pytest-0/popen-gw0/test_tracker_offset_is_a_lever0')
yaw90      = [0.7071068, 0.0, 0.0, 0.7071068]

.../motion/simulation/test_vive.py:122: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

tmp_path = PosixPath('.../pytest-0/popen-gw0/test_tracker_offset_is_a_lever0')
pos = [[0.0, 0, 2.0], [0.0, 0, 2.0]]
quat = [[1.0, 0, 0, 0], [0.7071068, 0.0, 0.0, 0.7071068]]

    def _write(tmp_path, pos, quat, *, walk_at=None, t_host_shift=None, heights_at=()):
        """A minimal mcap carrying vive/pose (and optionally control_log entries)."""
        import json
    
>       from mcap.writer import Writer
E       ModuleNotFoundError: No module named 'mcap'

heights_at = ()
json       = <module 'json' from '.../usr/lib/python3.12/json/__init__.py'>
pos        = [[0.0, 0, 2.0], [0.0, 0, 2.0]]
quat       = [[1.0, 0, 0, 0], [0.7071068, 0.0, 0.0, 0.7071068]]
t_host_shift = None
tmp_path   = PosixPath('.../pytest-0/popen-gw0/test_tracker_offset_is_a_lever0')
walk_at    = None

.../motion/simulation/test_vive.py:77: ModuleNotFoundError
dimos.navigation.motion.simulation.test_vive::test_vive_epoch_survives_a_t_host_clock_offset
Stack Traces | 0.002s run time
tmp_path = PosixPath('.../pytest-0/popen-gw0/test_vive_epoch_survives_a_t_h0')

    def test_vive_epoch_survives_a_t_host_clock_offset(tmp_path):
        """t_host provides the sample spacing but must not shift the epoch: the
        mean t_host-to-log_time difference rebases it onto the command clock."""
        from dimos.navigation.motion.simulation.vive import read_vive_pose
    
        pos = [[0.0, 0, 0]] * 5
        quat = [[1.0, 0, 0, 0]] * 5
>       t, _p, _q = read_vive_pose(_write(tmp_path, pos, quat, walk_at=0.02, t_host_shift=-5.0))

pos        = [[0.0, 0, 0], [0.0, 0, 0], [0.0, 0, 0], [0.0, 0, 0], [0.0, 0, 0]]
quat       = [[1.0, 0, 0, 0], [1.0, 0, 0, 0], [1.0, 0, 0, 0], [1.0, 0, 0, 0], [1.0, 0, 0, 0]]
read_vive_pose = <function read_vive_pose at 0xff36b4e01120>
tmp_path   = PosixPath('.../pytest-0/popen-gw0/test_vive_epoch_survives_a_t_h0')

.../motion/simulation/test_vive.py:160: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

tmp_path = PosixPath('.../pytest-0/popen-gw0/test_vive_epoch_survives_a_t_h0')
pos = [[0.0, 0, 0], [0.0, 0, 0], [0.0, 0, 0], [0.0, 0, 0], [0.0, 0, 0]]
quat = [[1.0, 0, 0, 0], [1.0, 0, 0, 0], [1.0, 0, 0, 0], [1.0, 0, 0, 0], [1.0, 0, 0, 0]]

    def _write(tmp_path, pos, quat, *, walk_at=None, t_host_shift=None, heights_at=()):
        """A minimal mcap carrying vive/pose (and optionally control_log entries)."""
        import json
    
>       from mcap.writer import Writer
E       ModuleNotFoundError: No module named 'mcap'

heights_at = ()
json       = <module 'json' from '.../usr/lib/python3.12/json/__init__.py'>
pos        = [[0.0, 0, 0], [0.0, 0, 0], [0.0, 0, 0], [0.0, 0, 0], [0.0, 0, 0]]
quat       = [[1.0, 0, 0, 0], [1.0, 0, 0, 0], [1.0, 0, 0, 0], [1.0, 0, 0, 0], [1.0, 0, 0, 0]]
t_host_shift = -5.0
tmp_path   = PosixPath('.../pytest-0/popen-gw0/test_vive_epoch_survives_a_t_h0')
walk_at    = 0.02

.../motion/simulation/test_vive.py:77: ModuleNotFoundError
dimos.navigation.motion.simulation.test_vive::test_vive_time_is_zeroed_at_the_first_walk_command
Stack Traces | 0.002s run time
tmp_path = PosixPath('.../pytest-0/popen-gw0/test_vive_time_is_zeroed_at_th0')

    def test_vive_time_is_zeroed_at_the_first_walk_command(tmp_path):
        """Both streams must share an epoch — zeroing each at its own first message
        made himloco01's vive samples 0.313 s early, which a search then "fitted"
        as 0.317 s of command delay."""
        from dimos.navigation.motion.simulation.vive import read_vive_pose
    
        pos = [[0.0, 0, 0]] * 5
        quat = [[1.0, 0, 0, 0]] * 5
>       t, _p, _q = read_vive_pose(_write(tmp_path, pos, quat, walk_at=0.02))

pos        = [[0.0, 0, 0], [0.0, 0, 0], [0.0, 0, 0], [0.0, 0, 0], [0.0, 0, 0]]
quat       = [[1.0, 0, 0, 0], [1.0, 0, 0, 0], [1.0, 0, 0, 0], [1.0, 0, 0, 0], [1.0, 0, 0, 0]]
read_vive_pose = <function read_vive_pose at 0xff36b4e01120>
tmp_path   = PosixPath('.../pytest-0/popen-gw0/test_vive_time_is_zeroed_at_th0')

.../motion/simulation/test_vive.py:149: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

tmp_path = PosixPath('.../pytest-0/popen-gw0/test_vive_time_is_zeroed_at_th0')
pos = [[0.0, 0, 0], [0.0, 0, 0], [0.0, 0, 0], [0.0, 0, 0], [0.0, 0, 0]]
quat = [[1.0, 0, 0, 0], [1.0, 0, 0, 0], [1.0, 0, 0, 0], [1.0, 0, 0, 0], [1.0, 0, 0, 0]]

    def _write(tmp_path, pos, quat, *, walk_at=None, t_host_shift=None, heights_at=()):
        """A minimal mcap carrying vive/pose (and optionally control_log entries)."""
        import json
    
>       from mcap.writer import Writer
E       ModuleNotFoundError: No module named 'mcap'

heights_at = ()
json       = <module 'json' from '.../usr/lib/python3.12/json/__init__.py'>
pos        = [[0.0, 0, 0], [0.0, 0, 0], [0.0, 0, 0], [0.0, 0, 0], [0.0, 0, 0]]
quat       = [[1.0, 0, 0, 0], [1.0, 0, 0, 0], [1.0, 0, 0, 0], [1.0, 0, 0, 0], [1.0, 0, 0, 0]]
t_host_shift = None
tmp_path   = PosixPath('.../pytest-0/popen-gw0/test_vive_time_is_zeroed_at_th0')
walk_at    = 0.02

.../motion/simulation/test_vive.py:77: ModuleNotFoundError
dimos.navigation.motion.simulation.test_vive::test_vive_time_without_commands_starts_at_zero
Stack Traces | 0.002s run time
tmp_path = PosixPath('.../pytest-0/popen-gw0/test_vive_time_without_command0')

    def test_vive_time_without_commands_starts_at_zero(tmp_path):
        from dimos.navigation.motion.simulation.vive import read_vive_pose
    
>       t, _p, _q = read_vive_pose(_write(tmp_path, [[0.0, 0, 0]] * 3, [[1.0, 0, 0, 0]] * 3))

read_vive_pose = <function read_vive_pose at 0xff36b4e01120>
tmp_path   = PosixPath('.../pytest-0/popen-gw0/test_vive_time_without_command0')

.../motion/simulation/test_vive.py:167: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

tmp_path = PosixPath('.../pytest-0/popen-gw0/test_vive_time_without_command0')
pos = [[0.0, 0, 0], [0.0, 0, 0], [0.0, 0, 0]]
quat = [[1.0, 0, 0, 0], [1.0, 0, 0, 0], [1.0, 0, 0, 0]]

    def _write(tmp_path, pos, quat, *, walk_at=None, t_host_shift=None, heights_at=()):
        """A minimal mcap carrying vive/pose (and optionally control_log entries)."""
        import json
    
>       from mcap.writer import Writer
E       ModuleNotFoundError: No module named 'mcap'

heights_at = ()
json       = <module 'json' from '.../usr/lib/python3.12/json/__init__.py'>
pos        = [[0.0, 0, 0], [0.0, 0, 0], [0.0, 0, 0]]
quat       = [[1.0, 0, 0, 0], [1.0, 0, 0, 0], [1.0, 0, 0, 0]]
t_host_shift = None
tmp_path   = PosixPath('.../pytest-0/popen-gw0/test_vive_time_without_command0')
walk_at    = None

.../motion/simulation/test_vive.py:77: ModuleNotFoundError
dimos.navigation.motion.control.referee.test_world::test_fitted_physics_applies
Stack Traces | 0.003s run time
def test_fitted_physics_applies() -> None:
>       stock, _ = world.load_world(CORRIDOR)


.../control/referee/test_world.py:75: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../control/referee/world.py:68: in load_world
    spec = mujoco.MjSpec.from_file(str(go2_model.scene_path(menagerie)))
        menagerie  = None
        physics    = None
        sc         = Scenario(name='corridor', boxes=[Box(cx=2.0, cy=0.6, sx=2.0, sy=0.3, yaw=0.0, height=0.6), Box(cx=2.0, cy=-0.6, sx=2.0..., center_off=-0.01, comfort=0.4, precision=0.05, strafe=1.8, reverse=1.5, yaw_w=0.25), note='0.9 m gap: thread center')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

menagerie = None

    def scene_path(menagerie: Path | None = None) -> Path:
        """Path to the flat-ground go2 scene (menagerie ``unitree_go2/scene.xml``)."""
        root = menagerie or Path(os.environ.get("MUJOCO_MENAGERIE", DEFAULT_MENAGERIE))
        scene = root / "unitree_go2" / "scene.xml"
        if not scene.is_file():
>           raise FileNotFoundError(
                f"go2 scene not found at {scene}. Point MUJOCO_MENAGERIE at a "
                "mujoco_menagerie checkout."
            )
E           FileNotFoundError: go2 scene not found at .../menagerie/unitree_go2/scene.xml. Point MUJOCO_MENAGERIE at a mujoco_menagerie checkout.

menagerie  = None
root       = PosixPath('.../deep/mujoco/menagerie')
scene      = PosixPath('.../deep/mujoco/menagerie/unitree_go2/scene.xml')

.../motion/simulation/model.py:66: FileNotFoundError
dimos.navigation.motion.simulation.test_vive::test_anchor_pos_offsets_the_whole_track
Stack Traces | 0.003s run time
tmp_path = PosixPath('.../pytest-0/popen-gw0/test_anchor_pos_offsets_the_wh0')

    def test_anchor_pos_offsets_the_whole_track(tmp_path):
        from dimos.navigation.motion.simulation.vive import base_track
    
>       path = _write(tmp_path, [[0.0, 0, 0], [1.0, 0, 0]], [[1.0, 0, 0, 0]] * 2)

base_track = <function base_track at 0xff36b4e011c0>
tmp_path   = PosixPath('.../pytest-0/popen-gw0/test_anchor_pos_offsets_the_wh0')

.../motion/simulation/test_vive.py:133: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

tmp_path = PosixPath('.../pytest-0/popen-gw0/test_anchor_pos_offsets_the_wh0')
pos = [[0.0, 0, 0], [1.0, 0, 0]], quat = [[1.0, 0, 0, 0], [1.0, 0, 0, 0]]

    def _write(tmp_path, pos, quat, *, walk_at=None, t_host_shift=None, heights_at=()):
        """A minimal mcap carrying vive/pose (and optionally control_log entries)."""
        import json
    
>       from mcap.writer import Writer
E       ModuleNotFoundError: No module named 'mcap'

heights_at = ()
json       = <module 'json' from '.../usr/lib/python3.12/json/__init__.py'>
pos        = [[0.0, 0, 0], [1.0, 0, 0]]
quat       = [[1.0, 0, 0, 0], [1.0, 0, 0, 0]]
t_host_shift = None
tmp_path   = PosixPath('.../pytest-0/popen-gw0/test_anchor_pos_offsets_the_wh0')
walk_at    = None

.../motion/simulation/test_vive.py:77: ModuleNotFoundError
dimos.navigation.motion.simulation.test_vive::test_read_gait_height_is_empty_without_height_commands
Stack Traces | 0.003s run time
tmp_path = PosixPath('.../pytest-0/popen-gw0/test_read_gait_height_is_empty0')

    def test_read_gait_height_is_empty_without_height_commands(tmp_path):
        from dimos.navigation.motion.simulation.walk import read_gait_height
    
>       t, h = read_gait_height(_write(tmp_path, [[0.0, 0, 0]] * 2, [[1.0, 0, 0, 0]] * 2, walk_at=0.0))

read_gait_height = <function read_gait_height at 0xff36b4e00ae0>
tmp_path   = PosixPath('.../pytest-0/popen-gw0/test_read_gait_height_is_empty0')

.../motion/simulation/test_vive.py:249: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

tmp_path = PosixPath('.../pytest-0/popen-gw0/test_read_gait_height_is_empty0')
pos = [[0.0, 0, 0], [0.0, 0, 0]], quat = [[1.0, 0, 0, 0], [1.0, 0, 0, 0]]

    def _write(tmp_path, pos, quat, *, walk_at=None, t_host_shift=None, heights_at=()):
        """A minimal mcap carrying vive/pose (and optionally control_log entries)."""
        import json
    
>       from mcap.writer import Writer
E       ModuleNotFoundError: No module named 'mcap'

heights_at = ()
json       = <module 'json' from '.../usr/lib/python3.12/json/__init__.py'>
pos        = [[0.0, 0, 0], [0.0, 0, 0]]
quat       = [[1.0, 0, 0, 0], [1.0, 0, 0, 0]]
t_host_shift = None
tmp_path   = PosixPath('.../pytest-0/popen-gw0/test_read_gait_height_is_empty0')
walk_at    = 0.0

.../motion/simulation/test_vive.py:77: ModuleNotFoundError
dimos.navigation.motion.simulation.test_vive::test_sensor_z_keeps_the_raw_tracker_height
Stack Traces | 0.003s run time
tmp_path = PosixPath('.../pytest-0/popen-gw0/test_sensor_z_keeps_the_raw_tr0')

    def test_sensor_z_keeps_the_raw_tracker_height(tmp_path):
        """With sensor_z the z column must bypass both the lever arm and the anchor."""
        from dimos.navigation.motion.simulation.vive import base_track
    
        pos = [[5.0, 5.0, 1.4], [5.5, 5.0, 1.5]]
        quat = [[1.0, 0, 0, 0]] * 2
        _t, p, _q = base_track(
>           _write(tmp_path, pos, quat),
            tracker_offset=np.array([0.0, 0.0, -0.2]),
            mount=np.eye(3),
            anchor_pos=np.array([0.0, 0.0, 0.27]),
            sensor_z=True,
        )

base_track = <function base_track at 0xff36b4e011c0>
pos        = [[5.0, 5.0, 1.4], [5.5, 5.0, 1.5]]
quat       = [[1.0, 0, 0, 0], [1.0, 0, 0, 0]]
tmp_path   = PosixPath('.../pytest-0/popen-gw0/test_sensor_z_keeps_the_raw_tr0')

.../motion/simulation/test_vive.py:178: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

tmp_path = PosixPath('.../pytest-0/popen-gw0/test_sensor_z_keeps_the_raw_tr0')
pos = [[5.0, 5.0, 1.4], [5.5, 5.0, 1.5]]
quat = [[1.0, 0, 0, 0], [1.0, 0, 0, 0]]

    def _write(tmp_path, pos, quat, *, walk_at=None, t_host_shift=None, heights_at=()):
        """A minimal mcap carrying vive/pose (and optionally control_log entries)."""
        import json
    
>       from mcap.writer import Writer
E       ModuleNotFoundError: No module named 'mcap'

heights_at = ()
json       = <module 'json' from '.../usr/lib/python3.12/json/__init__.py'>
pos        = [[5.0, 5.0, 1.4], [5.5, 5.0, 1.5]]
quat       = [[1.0, 0, 0, 0], [1.0, 0, 0, 0]]
t_host_shift = None
tmp_path   = PosixPath('.../pytest-0/popen-gw0/test_sensor_z_keeps_the_raw_tr0')
walk_at    = None

.../motion/simulation/test_vive.py:77: ModuleNotFoundError
dimos.navigation.motion.control.test_rust_parity::test_wrap_boundaries[-1.5707963267948966]
Stack Traces | 0.004s run time
def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
>           import dimos_motion2_tc
E           ModuleNotFoundError: No module named 'dimos_motion2_tc'


.../motion/control/controller.py:167: ModuleNotFoundError

The above exception was the direct cause of the following exception:

yaw = -1.5707963267948966

    @pytest.mark.parametrize(
        "yaw", [-math.pi, -math.pi / 2, 0.0, math.pi / 2, math.pi, math.pi - 1e-12]
    )
    def test_wrap_boundaries(yaw: float) -> None:
        """+-pi is where a `%`-based wrap diverges from IEEE remainder."""
        path = _path([(0.0, 0.0, math.pi), (0.0, 0.0, -math.pi + 0.2), (1.0, 0.0, -math.pi + 0.2)])
        for law in sorted(LAWS):
>           a, b = _twists(law, ControllerConfig(), _pose(0.0, 0.0, yaw), path, None)

law        = 'blind'
path       = <dimos.msgs.nav_msgs.Path.Path object at 0xffe9d86ebd70>
yaw        = -1.5707963267948966

.../motion/control/test_rust_parity.py:260: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../motion/control/test_rust_parity.py:213: in _twists
    rs = make_rs(cfg).update(pose, path, 0.0, clearance)
        cfg        = ControllerConfig(lookahead=0.35, max_speed=0.5, max_yaw_rate=1.4, k_pos=2.0, k_yaw=2.0, fan_yaw_per_m=3.0, fan_yaw_don... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
        clearance  = None
        law        = 'blind'
        make_py    = <function make at 0xffe9e3deb9c0>
        make_rs    = <function make_rust at 0xffe9e3deba60>
        path       = <dimos.msgs.nav_msgs.Path.Path object at 0xffe9d86ebd70>
        pose       = Pose(position=Vector([          0           0           0]), orientation=Quaternion(0.000000, 0.000000, -0.707107, 0.707107))
        py         = Twist(linear=Vector([ 3.7597e-17       0.614           0]), angular=Vector([          0           0        -1.4]))
.../control/laws/blind.py:62: in make_rust
    return RustBlindPursuitController(config)
        config     = ControllerConfig(lookahead=0.35, max_speed=0.5, max_yaw_rate=1.4, k_pos=2.0, k_yaw=2.0, fan_yaw_per_m=3.0, fan_yaw_don... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
.../control/laws/blind.py:272: in __init__
    self._mod: Any = load_extension()
        config     = ControllerConfig(lookahead=0.35, max_speed=0.5, max_yaw_rate=1.4, k_pos=2.0, k_yaw=2.0, fan_yaw_per_m=3.0, fan_yaw_don... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
        self       = <dimos.navigation.motion.control.laws.blind.RustBlindPursuitController object at 0xffe9d86eb2f0>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
            import dimos_motion2_tc
        except ImportError as e:
>           raise ImportError(f"dimos_motion2_tc is not built; run: {BUILD_CMD}") from e
E           ImportError: dimos_motion2_tc is not built; run: uv run maturin develop --uv --release -m .../control/rust/Cargo.toml


.../motion/control/controller.py:169: ImportError
dimos.navigation.motion.control.test_rust_parity::test_wrap_boundaries[-3.141592653589793]
Stack Traces | 0.004s run time
def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
>           import dimos_motion2_tc
E           ModuleNotFoundError: No module named 'dimos_motion2_tc'


.../motion/control/controller.py:167: ModuleNotFoundError

The above exception was the direct cause of the following exception:

yaw = -3.141592653589793

    @pytest.mark.parametrize(
        "yaw", [-math.pi, -math.pi / 2, 0.0, math.pi / 2, math.pi, math.pi - 1e-12]
    )
    def test_wrap_boundaries(yaw: float) -> None:
        """+-pi is where a `%`-based wrap diverges from IEEE remainder."""
        path = _path([(0.0, 0.0, math.pi), (0.0, 0.0, -math.pi + 0.2), (1.0, 0.0, -math.pi + 0.2)])
        for law in sorted(LAWS):
>           a, b = _twists(law, ControllerConfig(), _pose(0.0, 0.0, yaw), path, None)

law        = 'blind'
path       = <dimos.msgs.nav_msgs.Path.Path object at 0xffe9d866d9a0>
yaw        = -3.141592653589793

.../motion/control/test_rust_parity.py:260: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../motion/control/test_rust_parity.py:213: in _twists
    rs = make_rs(cfg).update(pose, path, 0.0, clearance)
        cfg        = ControllerConfig(lookahead=0.35, max_speed=0.5, max_yaw_rate=1.4, k_pos=2.0, k_yaw=2.0, fan_yaw_per_m=3.0, fan_yaw_don... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
        clearance  = None
        law        = 'blind'
        make_py    = <function make at 0xffe9e3deb9c0>
        make_rs    = <function make_rust at 0xffe9e3deba60>
        path       = <dimos.msgs.nav_msgs.Path.Path object at 0xffe9d866d9a0>
        pose       = Pose(position=Vector([          0           0           0]), orientation=Quaternion(0.000000, 0.000000, -1.000000, 0.000000))
        py         = Twist(linear=Vector([     -0.614  7.5193e-17           0]), angular=Vector([          0           0         0.4]))
.../control/laws/blind.py:62: in make_rust
    return RustBlindPursuitController(config)
        config     = ControllerConfig(lookahead=0.35, max_speed=0.5, max_yaw_rate=1.4, k_pos=2.0, k_yaw=2.0, fan_yaw_per_m=3.0, fan_yaw_don... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
.../control/laws/blind.py:272: in __init__
    self._mod: Any = load_extension()
        config     = ControllerConfig(lookahead=0.35, max_speed=0.5, max_yaw_rate=1.4, k_pos=2.0, k_yaw=2.0, fan_yaw_per_m=3.0, fan_yaw_don... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
        self       = <dimos.navigation.motion.control.laws.blind.RustBlindPursuitController object at 0xffe9d866f410>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
            import dimos_motion2_tc
        except ImportError as e:
>           raise ImportError(f"dimos_motion2_tc is not built; run: {BUILD_CMD}") from e
E           ImportError: dimos_motion2_tc is not built; run: uv run maturin develop --uv --release -m .../control/rust/Cargo.toml


.../motion/control/controller.py:169: ImportError
dimos.navigation.motion.control.test_rust_parity::test_wrap_boundaries[0.0]
Stack Traces | 0.004s run time
def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
>           import dimos_motion2_tc
E           ModuleNotFoundError: No module named 'dimos_motion2_tc'


.../motion/control/controller.py:167: ModuleNotFoundError

The above exception was the direct cause of the following exception:

yaw = 0.0

    @pytest.mark.parametrize(
        "yaw", [-math.pi, -math.pi / 2, 0.0, math.pi / 2, math.pi, math.pi - 1e-12]
    )
    def test_wrap_boundaries(yaw: float) -> None:
        """+-pi is where a `%`-based wrap diverges from IEEE remainder."""
        path = _path([(0.0, 0.0, math.pi), (0.0, 0.0, -math.pi + 0.2), (1.0, 0.0, -math.pi + 0.2)])
        for law in sorted(LAWS):
>           a, b = _twists(law, ControllerConfig(), _pose(0.0, 0.0, yaw), path, None)

law        = 'blind'
path       = <dimos.msgs.nav_msgs.Path.Path object at 0xffe9d86f0d10>
yaw        = 0.0

.../motion/control/test_rust_parity.py:260: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../motion/control/test_rust_parity.py:213: in _twists
    rs = make_rs(cfg).update(pose, path, 0.0, clearance)
        cfg        = ControllerConfig(lookahead=0.35, max_speed=0.5, max_yaw_rate=1.4, k_pos=2.0, k_yaw=2.0, fan_yaw_per_m=3.0, fan_yaw_don... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
        clearance  = None
        law        = 'blind'
        make_py    = <function make at 0xffe9e3deb9c0>
        make_rs    = <function make_rust at 0xffe9e3deba60>
        path       = <dimos.msgs.nav_msgs.Path.Path object at 0xffe9d86f0d10>
        pose       = Pose(position=Vector([          0           0           0]), orientation=Quaternion(0.000000, 0.000000, 0.000000, 1.000000))
        py         = Twist(linear=Vector([      0.614           0           0]), angular=Vector([          0           0        -1.4]))
.../control/laws/blind.py:62: in make_rust
    return RustBlindPursuitController(config)
        config     = ControllerConfig(lookahead=0.35, max_speed=0.5, max_yaw_rate=1.4, k_pos=2.0, k_yaw=2.0, fan_yaw_per_m=3.0, fan_yaw_don... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
.../control/laws/blind.py:272: in __init__
    self._mod: Any = load_extension()
        config     = ControllerConfig(lookahead=0.35, max_speed=0.5, max_yaw_rate=1.4, k_pos=2.0, k_yaw=2.0, fan_yaw_per_m=3.0, fan_yaw_don... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
        self       = <dimos.navigation.motion.control.laws.blind.RustBlindPursuitController object at 0xffe9d86f0f80>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
            import dimos_motion2_tc
        except ImportError as e:
>           raise ImportError(f"dimos_motion2_tc is not built; run: {BUILD_CMD}") from e
E           ImportError: dimos_motion2_tc is not built; run: uv run maturin develop --uv --release -m .../control/rust/Cargo.toml


.../motion/control/controller.py:169: ImportError
dimos.navigation.motion.control.test_rust_parity::test_wrap_boundaries[1.5707963267948966]
Stack Traces | 0.004s run time
def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
>           import dimos_motion2_tc
E           ModuleNotFoundError: No module named 'dimos_motion2_tc'


.../motion/control/controller.py:167: ModuleNotFoundError

The above exception was the direct cause of the following exception:

yaw = 1.5707963267948966

    @pytest.mark.parametrize(
        "yaw", [-math.pi, -math.pi / 2, 0.0, math.pi / 2, math.pi, math.pi - 1e-12]
    )
    def test_wrap_boundaries(yaw: float) -> None:
        """+-pi is where a `%`-based wrap diverges from IEEE remainder."""
        path = _path([(0.0, 0.0, math.pi), (0.0, 0.0, -math.pi + 0.2), (1.0, 0.0, -math.pi + 0.2)])
        for law in sorted(LAWS):
>           a, b = _twists(law, ControllerConfig(), _pose(0.0, 0.0, yaw), path, None)

law        = 'blind'
path       = <dimos.msgs.nav_msgs.Path.Path object at 0xffe9d856eb10>
yaw        = 1.5707963267948966

.../motion/control/test_rust_parity.py:260: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../motion/control/test_rust_parity.py:213: in _twists
    rs = make_rs(cfg).update(pose, path, 0.0, clearance)
        cfg        = ControllerConfig(lookahead=0.35, max_speed=0.5, max_yaw_rate=1.4, k_pos=2.0, k_yaw=2.0, fan_yaw_per_m=3.0, fan_yaw_don... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
        clearance  = None
        law        = 'blind'
        make_py    = <function make at 0xffe9e3deb9c0>
        make_rs    = <function make_rust at 0xffe9e3deba60>
        path       = <dimos.msgs.nav_msgs.Path.Path object at 0xffe9d856eb10>
        pose       = Pose(position=Vector([          0           0           0]), orientation=Quaternion(0.000000, 0.000000, 0.707107, 0.707107))
        py         = Twist(linear=Vector([          0           0           0]), angular=Vector([          0           0         1.4]))
.../control/laws/blind.py:62: in make_rust
    return RustBlindPursuitController(config)
        config     = ControllerConfig(lookahead=0.35, max_speed=0.5, max_yaw_rate=1.4, k_pos=2.0, k_yaw=2.0, fan_yaw_per_m=3.0, fan_yaw_don... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
.../control/laws/blind.py:272: in __init__
    self._mod: Any = load_extension()
        config     = ControllerConfig(lookahead=0.35, max_speed=0.5, max_yaw_rate=1.4, k_pos=2.0, k_yaw=2.0, fan_yaw_per_m=3.0, fan_yaw_don... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
        self       = <dimos.navigation.motion.control.laws.blind.RustBlindPursuitController object at 0xffe9d8616d50>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
            import dimos_motion2_tc
        except ImportError as e:
>           raise ImportError(f"dimos_motion2_tc is not built; run: {BUILD_CMD}") from e
E           ImportError: dimos_motion2_tc is not built; run: uv run maturin develop --uv --release -m .../control/rust/Cargo.toml


.../motion/control/controller.py:169: ImportError
dimos.navigation.motion.control.test_rust_parity::test_wrap_boundaries[3.141592653588793]
Stack Traces | 0.004s run time
def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
>           import dimos_motion2_tc
E           ModuleNotFoundError: No module named 'dimos_motion2_tc'


.../motion/control/controller.py:167: ModuleNotFoundError

The above exception was the direct cause of the following exception:

yaw = 3.141592653588793

    @pytest.mark.parametrize(
        "yaw", [-math.pi, -math.pi / 2, 0.0, math.pi / 2, math.pi, math.pi - 1e-12]
    )
    def test_wrap_boundaries(yaw: float) -> None:
        """+-pi is where a `%`-based wrap diverges from IEEE remainder."""
        path = _path([(0.0, 0.0, math.pi), (0.0, 0.0, -math.pi + 0.2), (1.0, 0.0, -math.pi + 0.2)])
        for law in sorted(LAWS):
>           a, b = _twists(law, ControllerConfig(), _pose(0.0, 0.0, yaw), path, None)

law        = 'blind'
path       = <dimos.msgs.nav_msgs.Path.Path object at 0xffe9d86e9550>
yaw        = 3.141592653588793

.../motion/control/test_rust_parity.py:260: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../motion/control/test_rust_parity.py:213: in _twists
    rs = make_rs(cfg).update(pose, path, 0.0, clearance)
        cfg        = ControllerConfig(lookahead=0.35, max_speed=0.5, max_yaw_rate=1.4, k_pos=2.0, k_yaw=2.0, fan_yaw_per_m=3.0, fan_yaw_don... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
        clearance  = None
        law        = 'blind'
        make_py    = <function make at 0xffe9e3deb9c0>
        make_rs    = <function make_rust at 0xffe9e3deba60>
        path       = <dimos.msgs.nav_msgs.Path.Path object at 0xffe9d86e9550>
        pose       = Pose(position=Vector([          0           0           0]), orientation=Quaternion(0.000000, 0.000000, 1.000000, 0.000000))
        py         = Twist(linear=Vector([     -0.614 -6.1413e-13           0]), angular=Vector([          0           0         0.4]))
.../control/laws/blind.py:62: in make_rust
    return RustBlindPursuitController(config)
        config     = ControllerConfig(lookahead=0.35, max_speed=0.5, max_yaw_rate=1.4, k_pos=2.0, k_yaw=2.0, fan_yaw_per_m=3.0, fan_yaw_don... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
.../control/laws/blind.py:272: in __init__
    self._mod: Any = load_extension()
        config     = ControllerConfig(lookahead=0.35, max_speed=0.5, max_yaw_rate=1.4, k_pos=2.0, k_yaw=2.0, fan_yaw_per_m=3.0, fan_yaw_don... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
        self       = <dimos.navigation.motion.control.laws.blind.RustBlindPursuitController object at 0xffe9d86e96d0>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
            import dimos_motion2_tc
        except ImportError as e:
>           raise ImportError(f"dimos_motion2_tc is not built; run: {BUILD_CMD}") from e
E           ImportError: dimos_motion2_tc is not built; run: uv run maturin develop --uv --release -m .../control/rust/Cargo.toml


.../motion/control/controller.py:169: ImportError
dimos.navigation.motion.control.test_rust_parity::test_wrap_boundaries[3.141592653589793]
Stack Traces | 0.004s run time
def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
>           import dimos_motion2_tc
E           ModuleNotFoundError: No module named 'dimos_motion2_tc'


.../motion/control/controller.py:167: ModuleNotFoundError

The above exception was the direct cause of the following exception:

yaw = 3.141592653589793

    @pytest.mark.parametrize(
        "yaw", [-math.pi, -math.pi / 2, 0.0, math.pi / 2, math.pi, math.pi - 1e-12]
    )
    def test_wrap_boundaries(yaw: float) -> None:
        """+-pi is where a `%`-based wrap diverges from IEEE remainder."""
        path = _path([(0.0, 0.0, math.pi), (0.0, 0.0, -math.pi + 0.2), (1.0, 0.0, -math.pi + 0.2)])
        for law in sorted(LAWS):
>           a, b = _twists(law, ControllerConfig(), _pose(0.0, 0.0, yaw), path, None)

law        = 'blind'
path       = <dimos.msgs.nav_msgs.Path.Path object at 0xffe9d86df530>
yaw        = 3.141592653589793

.../motion/control/test_rust_parity.py:260: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../motion/control/test_rust_parity.py:213: in _twists
    rs = make_rs(cfg).update(pose, path, 0.0, clearance)
        cfg        = ControllerConfig(lookahead=0.35, max_speed=0.5, max_yaw_rate=1.4, k_pos=2.0, k_yaw=2.0, fan_yaw_per_m=3.0, fan_yaw_don... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
        clearance  = None
        law        = 'blind'
        make_py    = <function make at 0xffe9e3deb9c0>
        make_rs    = <function make_rust at 0xffe9e3deba60>
        path       = <dimos.msgs.nav_msgs.Path.Path object at 0xffe9d86df530>
        pose       = Pose(position=Vector([          0           0           0]), orientation=Quaternion(0.000000, 0.000000, 1.000000, 0.000000))
        py         = Twist(linear=Vector([     -0.614 -7.5193e-17           0]), angular=Vector([          0           0         0.4]))
.../control/laws/blind.py:62: in make_rust
    return RustBlindPursuitController(config)
        config     = ControllerConfig(lookahead=0.35, max_speed=0.5, max_yaw_rate=1.4, k_pos=2.0, k_yaw=2.0, fan_yaw_per_m=3.0, fan_yaw_don... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
.../control/laws/blind.py:272: in __init__
    self._mod: Any = load_extension()
        config     = ControllerConfig(lookahead=0.35, max_speed=0.5, max_yaw_rate=1.4, k_pos=2.0, k_yaw=2.0, fan_yaw_per_m=3.0, fan_yaw_don... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
        self       = <dimos.navigation.motion.control.laws.blind.RustBlindPursuitController object at 0xffe9d86ddeb0>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
            import dimos_motion2_tc
        except ImportError as e:
>           raise ImportError(f"dimos_motion2_tc is not built; run: {BUILD_CMD}") from e
E           ImportError: dimos_motion2_tc is not built; run: uv run maturin develop --uv --release -m .../control/rust/Cargo.toml


.../motion/control/controller.py:169: ImportError
dimos.navigation.motion.control.test_hinted::test_rate_limit_is_the_plants_own_slew
Stack Traces | 0.006s run time
def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
>           import dimos_motion2_tc
E           ModuleNotFoundError: No module named 'dimos_motion2_tc'


.../motion/control/controller.py:167: ModuleNotFoundError

The above exception was the direct cause of the following exception:

    def test_rate_limit_is_the_plants_own_slew() -> None:
        """The limiter models the plant, so it has to be derived from the plant.
    
        ``COMMAND_SLEW`` is per control tick; the law carries a RATE so it stays
        correct at any control period. If the sim's slew or rate ever moves, this
        fails rather than leaving the law quietly modelling the old plant.
        """
        want = tuple(np.asarray(COMMAND_SLEW, dtype=float) / CONTROL_DT)
        assert hinted.CMD_SLEW_PER_S == want
>       assert tuple(load_extension().CMD_SLEW_PER_S) == want

want       = (np.float64(2.5), np.float64(2.0), np.float64(5.0))

.../motion/control/test_hinted.py:56: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
            import dimos_motion2_tc
        except ImportError as e:
>           raise ImportError(f"dimos_motion2_tc is not built; run: {BUILD_CMD}") from e
E           ImportError: dimos_motion2_tc is not built; run: uv run maturin develop --uv --release -m .../control/rust/Cargo.toml


.../motion/control/controller.py:169: ImportError
dimos.navigation.motion.control.test_rust_parity::test_parity_headroom[hinted]
Stack Traces | 0.007s run time
def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
>           import dimos_motion2_tc
E           ModuleNotFoundError: No module named 'dimos_motion2_tc'


.../motion/control/controller.py:167: ModuleNotFoundError

The above exception was the direct cause of the following exception:

law = 'hinted'

    @pytest.mark.parametrize("law", sorted(LAWS))
    def test_parity_headroom(law: str) -> None:
        """Report the real spread: it must sit far under the asserted tolerance."""
        worst = 0.0
        for case in _cases():
>           a, b = _raw_twists(law, *case)

case       = (ControllerConfig(lookahead=0.7801258587377804, max_speed=0.943690524773741, max_yaw_rate=2.889676241199347, k_pos=2.0...n=Quaternion(0.000000, 0.000000, -0.000985, 1.000000)), <dimos.msgs.nav_msgs.Path.Path object at 0xffe9d866c950>, None)
law        = 'hinted'
worst      = 0.0

.../motion/control/test_rust_parity.py:245: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../motion/control/test_rust_parity.py:197: in _raw_twists
    ext = load_extension()
        cfg        = ControllerConfig(lookahead=0.7801258587377804, max_speed=0.943690524773741, max_yaw_rate=2.889676241199347, k_pos=2.09... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
        clearance  = None
        law        = 'hinted'
        path       = <dimos.msgs.nav_msgs.Path.Path object at 0xffe9d866c950>
        pose       = Pose(position=Vector([   0.036546       2.884           0]), orientation=Quaternion(0.000000, 0.000000, -0.000985, 1.000000))
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
            import dimos_motion2_tc
        except ImportError as e:
>           raise ImportError(f"dimos_motion2_tc is not built; run: {BUILD_CMD}") from e
E           ImportError: dimos_motion2_tc is not built; run: uv run maturin develop --uv --release -m .../control/rust/Cargo.toml


.../motion/control/controller.py:169: ImportError
dimos.navigation.motion.control.test_rust_parity::test_rust_matches_python[hinted]
Stack Traces | 0.007s run time
def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
>           import dimos_motion2_tc
E           ModuleNotFoundError: No module named 'dimos_motion2_tc'


.../motion/control/controller.py:167: ModuleNotFoundError

The above exception was the direct cause of the following exception:

law = 'hinted'

    @pytest.mark.parametrize("law", sorted(LAWS))
    def test_rust_matches_python(law: str) -> None:
        seen = {"fan": 0, "governed": 0, "clamped": 0, "held": 0}
        for k, case in enumerate(_cases()):
>           a, b = _raw_twists(law, *case)

case       = (ControllerConfig(lookahead=0.7801258587377804, max_speed=0.943690524773741, max_yaw_rate=2.889676241199347, k_pos=2.0...n=Quaternion(0.000000, 0.000000, -0.000985, 1.000000)), <dimos.msgs.nav_msgs.Path.Path object at 0xffe9d86dc6e0>, None)
k          = 0
law        = 'hinted'
seen       = {'clamped': 0, 'fan': 0, 'governed': 0, 'held': 0}

.../motion/control/test_rust_parity.py:224: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../motion/control/test_rust_parity.py:197: in _raw_twists
    ext = load_extension()
        cfg        = ControllerConfig(lookahead=0.7801258587377804, max_speed=0.943690524773741, max_yaw_rate=2.889676241199347, k_pos=2.09... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
        clearance  = None
        law        = 'hinted'
        path       = <dimos.msgs.nav_msgs.Path.Path object at 0xffe9d86dc6e0>
        pose       = Pose(position=Vector([   0.036546       2.884           0]), orientation=Quaternion(0.000000, 0.000000, -0.000985, 1.000000))
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
            import dimos_motion2_tc
        except ImportError as e:
>           raise ImportError(f"dimos_motion2_tc is not built; run: {BUILD_CMD}") from e
E           ImportError: dimos_motion2_tc is not built; run: uv run maturin develop --uv --release -m .../control/rust/Cargo.toml


.../motion/control/controller.py:169: ImportError
dimos.navigation.motion.control.test_rust_parity::test_parity_headroom[blind]
Stack Traces | 0.008s run time
def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
>           import dimos_motion2_tc
E           ModuleNotFoundError: No module named 'dimos_motion2_tc'


.../motion/control/controller.py:167: ModuleNotFoundError

The above exception was the direct cause of the following exception:

law = 'blind'

    @pytest.mark.parametrize("law", sorted(LAWS))
    def test_parity_headroom(law: str) -> None:
        """Report the real spread: it must sit far under the asserted tolerance."""
        worst = 0.0
        for case in _cases():
>           a, b = _raw_twists(law, *case)

case       = (ControllerConfig(lookahead=0.7801258587377804, max_speed=0.943690524773741, max_yaw_rate=2.889676241199347, k_pos=2.0...n=Quaternion(0.000000, 0.000000, -0.000985, 1.000000)), <dimos.msgs.nav_msgs.Path.Path object at 0xffe9d86df500>, None)
law        = 'blind'
worst      = 0.0

.../motion/control/test_rust_parity.py:245: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../motion/control/test_rust_parity.py:196: in _raw_twists
    return _twists(law, cfg, pose, path, clearance)
        cfg        = ControllerConfig(lookahead=0.7801258587377804, max_speed=0.943690524773741, max_yaw_rate=2.889676241199347, k_pos=2.09... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
        clearance  = None
        law        = 'blind'
        path       = <dimos.msgs.nav_msgs.Path.Path object at 0xffe9d86df500>
        pose       = Pose(position=Vector([   0.036546       2.884           0]), orientation=Quaternion(0.000000, 0.000000, -0.000985, 1.000000))
.../motion/control/test_rust_parity.py:213: in _twists
    rs = make_rs(cfg).update(pose, path, 0.0, clearance)
        cfg        = ControllerConfig(lookahead=0.7801258587377804, max_speed=0.943690524773741, max_yaw_rate=2.889676241199347, k_pos=2.09... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
        clearance  = None
        law        = 'blind'
        make_py    = <function make at 0xffe9e3deb9c0>
        make_rs    = <function make_rust at 0xffe9e3deba60>
        path       = <dimos.msgs.nav_msgs.Path.Path object at 0xffe9d86df500>
        pose       = Pose(position=Vector([   0.036546       2.884           0]), orientation=Quaternion(0.000000, 0.000000, -0.000985, 1.000000))
        py         = Twist(linear=Vector([   -0.88954    -0.82182           0]), angular=Vector([          0           0     -2.8897]))
.../control/laws/blind.py:62: in make_rust
    return RustBlindPursuitController(config)
        config     = ControllerConfig(lookahead=0.7801258587377804, max_speed=0.943690524773741, max_yaw_rate=2.889676241199347, k_pos=2.09... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
.../control/laws/blind.py:272: in __init__
    self._mod: Any = load_extension()
        config     = ControllerConfig(lookahead=0.7801258587377804, max_speed=0.943690524773741, max_yaw_rate=2.889676241199347, k_pos=2.09... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
        self       = <dimos.navigation.motion.control.laws.blind.RustBlindPursuitController object at 0xffe9d86dc4a0>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
            import dimos_motion2_tc
        except ImportError as e:
>           raise ImportError(f"dimos_motion2_tc is not built; run: {BUILD_CMD}") from e
E           ImportError: dimos_motion2_tc is not built; run: uv run maturin develop --uv --release -m .../control/rust/Cargo.toml


.../motion/control/controller.py:169: ImportError
dimos.navigation.motion.control.test_rust_parity::test_parity_headroom[seed]
Stack Traces | 0.008s run time
def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
>           import dimos_motion2_tc
E           ModuleNotFoundError: No module named 'dimos_motion2_tc'


.../motion/control/controller.py:167: ModuleNotFoundError

The above exception was the direct cause of the following exception:

law = 'seed'

    @pytest.mark.parametrize("law", sorted(LAWS))
    def test_parity_headroom(law: str) -> None:
        """Report the real spread: it must sit far under the asserted tolerance."""
        worst = 0.0
        for case in _cases():
>           a, b = _raw_twists(law, *case)

case       = (ControllerConfig(lookahead=0.7801258587377804, max_speed=0.943690524773741, max_yaw_rate=2.889676241199347, k_pos=2.0...n=Quaternion(0.000000, 0.000000, -0.000985, 1.000000)), <dimos.msgs.nav_msgs.Path.Path object at 0xffe9d8545160>, None)
law        = 'seed'
worst      = 0.0

.../motion/control/test_rust_parity.py:245: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../motion/control/test_rust_parity.py:196: in _raw_twists
    return _twists(law, cfg, pose, path, clearance)
        cfg        = ControllerConfig(lookahead=0.7801258587377804, max_speed=0.943690524773741, max_yaw_rate=2.889676241199347, k_pos=2.09... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
        clearance  = None
        law        = 'seed'
        path       = <dimos.msgs.nav_msgs.Path.Path object at 0xffe9d8545160>
        pose       = Pose(position=Vector([   0.036546       2.884           0]), orientation=Quaternion(0.000000, 0.000000, -0.000985, 1.000000))
.../motion/control/test_rust_parity.py:213: in _twists
    rs = make_rs(cfg).update(pose, path, 0.0, clearance)
        cfg        = ControllerConfig(lookahead=0.7801258587377804, max_speed=0.943690524773741, max_yaw_rate=2.889676241199347, k_pos=2.09... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
        clearance  = None
        law        = 'seed'
        make_py    = <function make at 0xffe9e3d3cc20>
        make_rs    = <function make_rust at 0xffe9e3d3ccc0>
        path       = <dimos.msgs.nav_msgs.Path.Path object at 0xffe9d8545160>
        pose       = Pose(position=Vector([   0.036546       2.884           0]), orientation=Quaternion(0.000000, 0.000000, -0.000985, 1.000000))
        py         = Twist(linear=Vector([   -0.69315    -0.64039           0]), angular=Vector([          0           0     -2.8897]))
.../control/laws/seed.py:46: in make_rust
    return RustPursuitController(config)
        config     = ControllerConfig(lookahead=0.7801258587377804, max_speed=0.943690524773741, max_yaw_rate=2.889676241199347, k_pos=2.09... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
.../control/laws/seed.py:143: in __init__
    self._mod: Any = load_extension()
        config     = ControllerConfig(lookahead=0.7801258587377804, max_speed=0.943690524773741, max_yaw_rate=2.889676241199347, k_pos=2.09... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
        self       = <dimos.navigation.motion.control.laws.seed.RustPursuitController object at 0xffe9d85450a0>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
            import dimos_motion2_tc
        except ImportError as e:
>           raise ImportError(f"dimos_motion2_tc is not built; run: {BUILD_CMD}") from e
E           ImportError: dimos_motion2_tc is not built; run: uv run maturin develop --uv --release -m .../control/rust/Cargo.toml


.../motion/control/controller.py:169: ImportError
dimos.navigation.motion.control.test_rust_parity::test_rust_matches_python[seed]
Stack Traces | 0.008s run time
def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
>           import dimos_motion2_tc
E           ModuleNotFoundError: No module named 'dimos_motion2_tc'


.../motion/control/controller.py:167: ModuleNotFoundError

The above exception was the direct cause of the following exception:

law = 'seed'

    @pytest.mark.parametrize("law", sorted(LAWS))
    def test_rust_matches_python(law: str) -> None:
        seen = {"fan": 0, "governed": 0, "clamped": 0, "held": 0}
        for k, case in enumerate(_cases()):
>           a, b = _raw_twists(law, *case)

case       = (ControllerConfig(lookahead=0.7801258587377804, max_speed=0.943690524773741, max_yaw_rate=2.889676241199347, k_pos=2.0...n=Quaternion(0.000000, 0.000000, -0.000985, 1.000000)), <dimos.msgs.nav_msgs.Path.Path object at 0xffe9d8544dd0>, None)
k          = 0
law        = 'seed'
seen       = {'clamped': 0, 'fan': 0, 'governed': 0, 'held': 0}

.../motion/control/test_rust_parity.py:224: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../motion/control/test_rust_parity.py:196: in _raw_twists
    return _twists(law, cfg, pose, path, clearance)
        cfg        = ControllerConfig(lookahead=0.7801258587377804, max_speed=0.943690524773741, max_yaw_rate=2.889676241199347, k_pos=2.09... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
        clearance  = None
        law        = 'seed'
        path       = <dimos.msgs.nav_msgs.Path.Path object at 0xffe9d8544dd0>
        pose       = Pose(position=Vector([   0.036546       2.884           0]), orientation=Quaternion(0.000000, 0.000000, -0.000985, 1.000000))
.../motion/control/test_rust_parity.py:213: in _twists
    rs = make_rs(cfg).update(pose, path, 0.0, clearance)
        cfg        = ControllerConfig(lookahead=0.7801258587377804, max_speed=0.943690524773741, max_yaw_rate=2.889676241199347, k_pos=2.09... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
        clearance  = None
        law        = 'seed'
        make_py    = <function make at 0xffe9e3d3cc20>
        make_rs    = <function make_rust at 0xffe9e3d3ccc0>
        path       = <dimos.msgs.nav_msgs.Path.Path object at 0xffe9d8544dd0>
        pose       = Pose(position=Vector([   0.036546       2.884           0]), orientation=Quaternion(0.000000, 0.000000, -0.000985, 1.000000))
        py         = Twist(linear=Vector([   -0.69315    -0.64039           0]), angular=Vector([          0           0     -2.8897]))
.../control/laws/seed.py:46: in make_rust
    return RustPursuitController(config)
        config     = ControllerConfig(lookahead=0.7801258587377804, max_speed=0.943690524773741, max_yaw_rate=2.889676241199347, k_pos=2.09... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
.../control/laws/seed.py:143: in __init__
    self._mod: Any = load_extension()
        config     = ControllerConfig(lookahead=0.7801258587377804, max_speed=0.943690524773741, max_yaw_rate=2.889676241199347, k_pos=2.09... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
        self       = <dimos.navigation.motion.control.laws.seed.RustPursuitController object at 0xffe9d8544bc0>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
            import dimos_motion2_tc
        except ImportError as e:
>           raise ImportError(f"dimos_motion2_tc is not built; run: {BUILD_CMD}") from e
E           ImportError: dimos_motion2_tc is not built; run: uv run maturin develop --uv --release -m .../control/rust/Cargo.toml


.../motion/control/controller.py:169: ImportError
dimos.navigation.motion.control.test_rust_parity::test_rust_matches_python[blind]
Stack Traces | 0.009s run time
def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
>           import dimos_motion2_tc
E           ModuleNotFoundError: No module named 'dimos_motion2_tc'


.../motion/control/controller.py:167: ModuleNotFoundError

The above exception was the direct cause of the following exception:

law = 'blind'

    @pytest.mark.parametrize("law", sorted(LAWS))
    def test_rust_matches_python(law: str) -> None:
        seen = {"fan": 0, "governed": 0, "clamped": 0, "held": 0}
        for k, case in enumerate(_cases()):
>           a, b = _raw_twists(law, *case)

case       = (ControllerConfig(lookahead=0.7801258587377804, max_speed=0.943690524773741, max_yaw_rate=2.889676241199347, k_pos=2.0...n=Quaternion(0.000000, 0.000000, -0.000985, 1.000000)), <dimos.msgs.nav_msgs.Path.Path object at 0xffe9d87f2810>, None)
k          = 0
law        = 'blind'
seen       = {'clamped': 0, 'fan': 0, 'governed': 0, 'held': 0}

.../motion/control/test_rust_parity.py:224: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../motion/control/test_rust_parity.py:196: in _raw_twists
    return _twists(law, cfg, pose, path, clearance)
        cfg        = ControllerConfig(lookahead=0.7801258587377804, max_speed=0.943690524773741, max_yaw_rate=2.889676241199347, k_pos=2.09... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
        clearance  = None
        law        = 'blind'
        path       = <dimos.msgs.nav_msgs.Path.Path object at 0xffe9d87f2810>
        pose       = Pose(position=Vector([   0.036546       2.884           0]), orientation=Quaternion(0.000000, 0.000000, -0.000985, 1.000000))
.../motion/control/test_rust_parity.py:213: in _twists
    rs = make_rs(cfg).update(pose, path, 0.0, clearance)
        cfg        = ControllerConfig(lookahead=0.7801258587377804, max_speed=0.943690524773741, max_yaw_rate=2.889676241199347, k_pos=2.09... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
        clearance  = None
        law        = 'blind'
        make_py    = <function make at 0xffe9e3deb9c0>
        make_rs    = <function make_rust at 0xffe9e3deba60>
        path       = <dimos.msgs.nav_msgs.Path.Path object at 0xffe9d87f2810>
        pose       = Pose(position=Vector([   0.036546       2.884           0]), orientation=Quaternion(0.000000, 0.000000, -0.000985, 1.000000))
        py         = Twist(linear=Vector([   -0.88954    -0.82182           0]), angular=Vector([          0           0     -2.8897]))
.../control/laws/blind.py:62: in make_rust
    return RustBlindPursuitController(config)
        config     = ControllerConfig(lookahead=0.7801258587377804, max_speed=0.943690524773741, max_yaw_rate=2.889676241199347, k_pos=2.09... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
.../control/laws/blind.py:272: in __init__
    self._mod: Any = load_extension()
        config     = ControllerConfig(lookahead=0.7801258587377804, max_speed=0.943690524773741, max_yaw_rate=2.889676241199347, k_pos=2.09... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
        self       = <dimos.navigation.motion.control.laws.blind.RustBlindPursuitController object at 0xffe9d86171a0>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
            import dimos_motion2_tc
        except ImportError as e:
>           raise ImportError(f"dimos_motion2_tc is not built; run: {BUILD_CMD}") from e
E           ImportError: dimos_motion2_tc is not built; run: uv run maturin develop --uv --release -m .../control/rust/Cargo.toml


.../motion/control/controller.py:169: ImportError
dimos.navigation.motion.control.test_rust_parity::test_reset_clears_every_tick_of_history[hinted]
Stack Traces | 0.017s run time
def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
>           import dimos_motion2_tc
E           ModuleNotFoundError: No module named 'dimos_motion2_tc'


.../motion/control/controller.py:167: ModuleNotFoundError

The above exception was the direct cause of the following exception:

law = 'hinted'

    @pytest.mark.parametrize("law", sorted(STATEFUL))
    def test_reset_clears_every_tick_of_history(law: str) -> None:
        """reset() must make a used law indistinguishable from a fresh one."""
        make_py, make_rs = LAWS[law]
        cfg, pose, path, clearance = next(c for c in _cases() if len(c[2].poses) > 3)
        other = next(c for c in _cases() if len(c[2].poses) > 3 and c[2] is not path)
        for make in (make_py, make_rs):
>           fresh, used = make(cfg), make(cfg)

_          = 4
a          = Twist(linear=Vector([      -0.05       -0.04           0]), angular=Vector([          0           0        -0.1]))
b          = Twist(linear=Vector([      -0.05       -0.04           0]), angular=Vector([          0           0        -0.1]))
cfg        = ControllerConfig(lookahead=0.7801258587377804, max_speed=0.943690524773741, max_yaw_rate=2.889676241199347, k_pos=2.09... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
clearance  = None
fresh      = <dimos.navigation.motion.control.laws.hinted.HintedController object at 0xffe9d93c1c10>
law        = 'hinted'
make       = <function make_rust at 0xffe9e3de9760>
make_py    = <function make at 0xffe9e3de96c0>
make_rs    = <function make_rust at 0xffe9e3de9760>
other      = (ControllerConfig(lookahead=0.7801258587377804, max_speed=0.943690524773741, max_yaw_rate=2.889676241199347, k_pos=2.0...n=Quaternion(0.000000, 0.000000, -0.000985, 1.000000)), <dimos.msgs.nav_msgs.Path.Path object at 0xffe9d93c1ca0>, None)
path       = <dimos.msgs.nav_msgs.Path.Path object at 0xffe9d8810cb0>
pose       = Pose(position=Vector([   0.036546       2.884           0]), orientation=Quaternion(0.000000, 0.000000, -0.000985, 1.000000))
used       = <dimos.navigation.motion.control.laws.hinted.HintedController object at 0xffe9d93c1c70>

.../motion/control/test_rust_parity.py:327: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../control/laws/hinted.py:95: in make_rust
    return RustHintedController(cfg or config())
        cfg        = ControllerConfig(lookahead=0.7801258587377804, max_speed=0.943690524773741, max_yaw_rate=2.889676241199347, k_pos=2.09... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
.../control/laws/hinted.py:418: in __init__
    mod: Any = load_extension()
        cfg        = ControllerConfig(lookahead=0.7801258587377804, max_speed=0.943690524773741, max_yaw_rate=2.889676241199347, k_pos=2.09... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
        self       = <dimos.navigation.motion.control.laws.hinted.RustHintedController object at 0xffe9d93c27b0>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
            import dimos_motion2_tc
        except ImportError as e:
>           raise ImportError(f"dimos_motion2_tc is not built; run: {BUILD_CMD}") from e
E           ImportError: dimos_motion2_tc is not built; run: uv run maturin develop --uv --release -m .../control/rust/Cargo.toml


.../motion/control/controller.py:169: ImportError
dimos.codebase_checks.test_no_init_files::test_no_init_files
Stack Traces | 0.023s run time
def test_no_init_files():
        dimos_dir = DIMOS_PROJECT_ROOT / "dimos"
        init_files = sorted(dimos_dir.rglob("__init__.py"))
        # The root dimos/__init__.py is allowed for the porcelain lazy import.
        init_files = [f for f in init_files if f != dimos_dir / "__init__.py"]
        if init_files:
            listing = "\n".join(f"  - {f.relative_to(dimos_dir)}" for f in init_files)
>           raise AssertionError(
                f"Found __init__.py files in dimos/:\n{listing}\n\n"
                "__init__.py files are not allowed because they lead to unnecessary "
                "extraneous imports. Everything should be imported straight from the "
                "source module."
            )
E           AssertionError: Found __init__.py files in dimos/:
E             - cli/bake/__init__.py
E             - navigation/motion/__init__.py
E             - .../motion/adapter/__init__.py
E             - .../motion/control/__init__.py
E             - .../control/laws/__init__.py
E             - .../motion/planner/__init__.py
E             - .../motion/simulation/__init__.py
E           
E           __init__.py files are not allowed because they lead to unnecessary extraneous imports. Everything should be imported straight from the source module.

dimos_dir  = PosixPath('.../dimos/dimos/dimos')
init_files = [PosixPath('.../dimos/dimos/dimos/cli/bake/__init__.py'), PosixPath('.../dimos/dimos/dimos...ontrol/laws/__init__.py'), PosixPath('.../dimos/dimos/dimos/.../motion/planner/__init__.py'), ...]
listing    = '  - cli/bake/__init__.py\n  - navigation/motion/__init__.py\n  - .../motion/adapter/__init__.py\n  - navigatio....../control/laws/__init__.py\n  - .../motion/planner/__init__.py\n  - .../motion/simulation/__init__.py'

dimos/codebase_checks/test_no_init_files.py:25: AssertionError
dimos.codebase_checks.test_no_sections::test_no_section_markers
Stack Traces | 0.777s run time
def test_no_section_markers():
        """
        Fail if any file contains section-style comment markers.
    
        If a file is too complicated to be understood without sections, then the
        sections should be files. We don't need "subfiles".
        """
        violations = find_section_markers()
        if violations:
            report_lines = [
                f"Found {len(violations)} section marker(s). "
                "If a file is too complicated to be understood without sections, "
                'then the sections should be files. We don\'t need "subfiles".',
                "",
            ]
            for path, lineno, text in violations:
                report_lines.append(f"  {path}:{lineno}: {text.strip()}")
>           raise AssertionError("\n".join(report_lines))
E           AssertionError: Found 5 section marker(s). If a file is too complicated to be understood without sections, then the sections should be files. We don't need "subfiles".
E           
E             .../export/templates/_evo_inline.py:138: # === USAGE EXAMPLE (copy + adapt) ===
E             .../export/templates/battery.py:205: # --- pillars, as the referee scored them ---
E             .../export/templates/battery.py:211: # --- the raw measurements behind each pillar ---
E             .../motion/control/controller.py:65: # --- gait calibration, read by the blind law only (laws/blind.py) ---
E             .../motion/control/controller.py:74: # --- read by the hinted law only (laws/hinted.py) ---

lineno     = 74
path       = '.../motion/control/controller.py'
report_lines = ['Found 5 section marker(s). If a file is too complicated to be understood without sections, then the sections should ....../motion/control/controller.py:65: # --- gait calibration, read by the blind law only (laws/blind.py) ---', ...]
text       = '    # --- read by the hinted law only (laws/hinted.py) ---'
violations = [('.../export/templates/_evo_inline.py', 138, '# === USAGE EXAMPLE (copy + a...), ('.../motion/control/controller.py', 74, '    # --- read by the hinted law only (laws/hinted.py) ---')]

dimos/codebase_checks/test_no_sections.py:145: AssertionError
dimos.codebase_checks.test_no_dunder_new::test_no_dunder_new
Stack Traces | 1.91s run time
def test_no_dunder_new() -> None:
        """Fail if any test file calls `__new__` to bypass `__init__`."""
        dimos_dir = DIMOS_PROJECT_ROOT / "dimos"
        hits = find_dunder_new_calls()
        if hits:
            listing = "\n".join(
                f"  - {p.relative_to(dimos_dir)}:{lineno}: {line.strip()}" for p, lineno, line in hits
            )
>           raise AssertionError(
                f"Found __new__ call(s) in test files:\n{listing}\n\n"
                "Tests must construct objects with the real constructor: __init__ is "
                "code under test too, and an object assembled by hand silently rots "
                "when the constructor changes. If __init__ does heavy work, mock the "
                "collaborators it needs instead of skipping it. Only if that is truly "
                "impossible, add the call to the WHITELIST in "
                "dimos/codebase_checks/test_no_dunder_new.py."
            )
E           AssertionError: Found __new__ call(s) in test files:
E             - .../motion/adapter/test_follower.py:76: follower = object.__new__(TrajectoryFollower)
E             - .../motion/adapter/test_planner.py:72: planner = object.__new__(MotionPlanner)
E             - .../motion/adapter/test_planner.py:131: planner = object.__new__(MotionPlanner)
E             - .../motion/adapter/test_planner.py:181: planner = object.__new__(MotionPlanner)
E           
E           Tests must construct objects with the real constructor: __init__ is code under test too, and an object assembled by hand silently rots when the constructor changes. If __init__ does heavy work, mock the collaborators it needs instead of skipping it. Only if that is truly impossible, add the call to the WHITELIST in dimos/codebase_checks/test_no_dunder_new.py.

dimos_dir  = PosixPath('.../dimos/dimos/dimos')
hits       = [(PosixPath('.../dimos/dimos/dimos/.../motion/adapter/test_follower.py'), 76, '    follower = obj...ork/dimos/dimos/dimos/.../motion/adapter/test_planner.py'), 181, '    planner = object.__new__(MotionPlanner)')]
listing    = '  - .../motion/adapter/test_follower.py:76: follower = object.__new__(TrajectoryFollower)\n  - navigation/moti...ect.__new__(MotionPlanner)\n  - .../motion/adapter/test_planner.py:181: planner = object.__new__(MotionPlanner)'

dimos/codebase_checks/test_no_dunder_new.py:67: AssertionError
dimos.navigation.motion.control.test_rust_parity::test_corridor_reaches_goal_rust
Stack Traces | 3.17s run time
def test_corridor_reaches_goal_rust() -> None:
        """Closed-loop smoke: the extension drives a real episode to the goal."""
        from dimos.navigation.motion.control.referee.episode import EpisodeConfig, run_episode
        from dimos.navigation.motion.planner.referee.scenarios import SCENARIOS
        from dimos.navigation.motion.simulation.policy import FreePolicy
        from dimos.utils.data import get_data
    
>       policy = FreePolicy.load(get_data("ml-trajectory-research/freewalk_mcf.bin"))

EpisodeConfig = <class 'dimos.navigation.motion.control.referee.episode.EpisodeConfig'>
FreePolicy = <class 'dimos.navigation.motion.simulation.policy.FreePolicy'>
SCENARIOS  = [Scenario(name='empty', boxes=[], goal=(4.0, 0.0), start=(0.0, 0.0, 0.0), expect='clear', emb=Embodiment(tag='go2', le...ision=0.05, strafe=1.8, reverse=1.5, yaw_w=0.25), note='starts beside the door, 0.5 m off the wall, facing away'), ...]
get_data   = <function get_data at 0xffeaa3c84e00>
run_episode = <function run_episode at 0xffe9e3d451c0>

.../motion/control/test_rust_parity.py:282: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/utils/data.py:304: in get_data
    archive_path = _decompress_archive(_pull_lfs_archive(archive_name))
        archive_name = 'ml-trajectory-research'
        data_dir   = PosixPath('.../dimos/dimos/data')
        file_path  = PosixPath('.../dimos/dimos/data/ml-trajectory-research/freewalk_mcf.bin')
        name       = 'ml-trajectory-research/freewalk_mcf.bin'
        nested_path = PosixPath('freewalk_mcf.bin')
        path_parts = ('ml-trajectory-research', 'freewalk_mcf.bin')
dimos/utils/data.py:248: in _pull_lfs_archive
    _lfs_pull(file_path, repo_root)
        file_path  = PosixPath('.../dimos/dimos/data/.lfs/ml-trajectory-research.tar.gz')
        filename   = 'ml-trajectory-research'
        repo_root  = PosixPath('.../work/dimos/dimos')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

file_path = PosixPath('.../dimos/dimos/data/.lfs/ml-trajectory-research.tar.gz')
repo_root = PosixPath('.../work/dimos/dimos')

    def _lfs_pull(file_path: Path, repo_root: Path, *, retries: int = 2) -> None:
        relative_path = file_path.relative_to(repo_root)
    
        env = os.environ.copy()
        env["GIT_LFS_FORCE_PROGRESS"] = "1"
    
        last_err: subprocess.CalledProcessError | None = None
        for attempt in range(1, retries + 2):  # retries + 1 total attempts
            try:
                subprocess.run(
                    ["git", "lfs", "pull", "--include", str(relative_path)],
                    cwd=repo_root,
                    check=True,
                    env=env,
                )
                return
            except subprocess.CalledProcessError as e:
                last_err = e
                if attempt <= retries:
                    time.sleep(attempt)  # 1s, 2s backoff
    
>       raise RuntimeError(
            f"Failed to pull LFS file {file_path} after {retries + 1} attempts: {last_err}"
        )
E       RuntimeError: Failed to pull LFS file .../dimos/dimos/data/.lfs/ml-trajectory-research.tar.gz after 3 attempts: Command '['git', 'lfs', 'pull', '--include', 'data/.lfs/ml-trajectory-research.tar.gz']' returned non-zero exit status 1.

attempt    = 3
env        = {'ACCEPT_EULA': 'Y', 'ACTIONS_ID_TOKEN_REQUEST_TOKEN': 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjM4ODI2YjE3LTZhMzAtNWY5Yi1iMTY5LT...-version=2.0', 'ACTIONS_ORCHESTRATION_ID': '2c87c586-97f7-4ca2-b528-e6e17a5b0b97.tests.ubuntu-24_04-arm_3_14_fal', ...}
file_path  = PosixPath('.../dimos/dimos/data/.lfs/ml-trajectory-research.tar.gz')
last_err   = CalledProcessError(1, ['git', 'lfs', 'pull', '--include', 'data/.lfs/ml-trajectory-research.tar.gz'])
relative_path = PosixPath('data/.lfs/ml-trajectory-research.tar.gz')
repo_root  = PosixPath('.../work/dimos/dimos')
retries    = 2

dimos/utils/data.py:216: RuntimeError
dimos.navigation.motion.control.referee.test_episode::test_corridor_reaches_goal_plan_once
Stack Traces | 3.23s run time
@pytest.fixture(scope="module")
    def policy() -> FreePolicy:
>       return FreePolicy.load(get_data("ml-trajectory-research/freewalk_mcf.bin"))


.../control/referee/test_episode.py:34: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/utils/data.py:304: in get_data
    archive_path = _decompress_archive(_pull_lfs_archive(archive_name))
        archive_name = 'ml-trajectory-research'
        data_dir   = PosixPath('.../dimos/dimos/data')
        file_path  = PosixPath('.../dimos/dimos/data/ml-trajectory-research/freewalk_mcf.bin')
        name       = 'ml-trajectory-research/freewalk_mcf.bin'
        nested_path = PosixPath('freewalk_mcf.bin')
        path_parts = ('ml-trajectory-research', 'freewalk_mcf.bin')
dimos/utils/data.py:248: in _pull_lfs_archive
    _lfs_pull(file_path, repo_root)
        file_path  = PosixPath('.../dimos/dimos/data/.lfs/ml-trajectory-research.tar.gz')
        filename   = 'ml-trajectory-research'
        repo_root  = PosixPath('.../work/dimos/dimos')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

file_path = PosixPath('.../dimos/dimos/data/.lfs/ml-trajectory-research.tar.gz')
repo_root = PosixPath('.../work/dimos/dimos')

    def _lfs_pull(file_path: Path, repo_root: Path, *, retries: int = 2) -> None:
        relative_path = file_path.relative_to(repo_root)
    
        env = os.environ.copy()
        env["GIT_LFS_FORCE_PROGRESS"] = "1"
    
        last_err: subprocess.CalledProcessError | None = None
        for attempt in range(1, retries + 2):  # retries + 1 total attempts
            try:
                subprocess.run(
                    ["git", "lfs", "pull", "--include", str(relative_path)],
                    cwd=repo_root,
                    check=True,
                    env=env,
                )
                return
            except subprocess.CalledProcessError as e:
                last_err = e
                if attempt <= retries:
                    time.sleep(attempt)  # 1s, 2s backoff
    
>       raise RuntimeError(
            f"Failed to pull LFS file {file_path} after {retries + 1} attempts: {last_err}"
        )
E       RuntimeError: Failed to pull LFS file .../dimos/dimos/data/.lfs/ml-trajectory-research.tar.gz after 3 attempts: Command '['git', 'lfs', 'pull', '--include', 'data/.lfs/ml-trajectory-research.tar.gz']' returned non-zero exit status 1.

attempt    = 3
env        = {'ACCEPT_EULA': 'Y', 'ACTIONS_ID_TOKEN_REQUEST_TOKEN': 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjM4ODI2YjE3LTZhMzAtNWY5Yi1iMTY5LT...-version=2.0', 'ACTIONS_ORCHESTRATION_ID': '2c87c586-97f7-4ca2-b528-e6e17a5b0b97.tests.ubuntu-24_04-arm_3_14_fal', ...}
file_path  = PosixPath('.../dimos/dimos/data/.lfs/ml-trajectory-research.tar.gz')
last_err   = CalledProcessError(1, ['git', 'lfs', 'pull', '--include', 'data/.lfs/ml-trajectory-research.tar.gz'])
relative_path = PosixPath('data/.lfs/ml-trajectory-research.tar.gz')
repo_root  = PosixPath('.../work/dimos/dimos')
retries    = 2

dimos/utils/data.py:216: RuntimeError
dimos.navigation.motion.control.test_rust_parity::test_stateful_parity_over_a_sequence[hinted]
Stack Traces | 5.09s run time
def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
>           import dimos_motion2_tc
E           ModuleNotFoundError: No module named 'dimos_motion2_tc'


.../motion/control/controller.py:167: ModuleNotFoundError

The above exception was the direct cause of the following exception:

law = 'hinted'

    @pytest.mark.parametrize("law", sorted(STATEFUL))
    def test_stateful_parity_over_a_sequence(law: str) -> None:
        """A law with memory has to agree tick after tick, not just once.
    
        One instance each, fed the whole sweep in order at a realistic 50 Hz clock:
        a rate limiter that diverged by an ulp on tick one would carry that ulp
        forward, so this is the assertion that actually binds it. The tick times
        deliberately include a stall longer than the limiter's MAX_TICK and a
        repeated timestamp, both of which its dt fallback has to handle the same
        way on either side.
        """
        make_py, make_rs = LAWS[law]
        cases = list(_cases())
>       py_law, rs_law = make_py(cases[0][0]), make_rs(cases[0][0])

cases      = [(ControllerConfig(lookahead=0.7801258587377804, max_speed=0.943690524773741, max_yaw_rate=2.889676241199347, k_pos=2.....67347,     0.57842,      0.6568,     0.39223,     0.66726,     0.32084,     0.61523,     0.79548,     0.60311])), ...]
law        = 'hinted'
make_py    = <function make at 0xffe9e3de96c0>
make_rs    = <function make_rust at 0xffe9e3de9760>

.../motion/control/test_rust_parity.py:302: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../control/laws/hinted.py:95: in make_rust
    return RustHintedController(cfg or config())
        cfg        = ControllerConfig(lookahead=0.7801258587377804, max_speed=0.943690524773741, max_yaw_rate=2.889676241199347, k_pos=2.09... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
.../control/laws/hinted.py:418: in __init__
    mod: Any = load_extension()
        cfg        = ControllerConfig(lookahead=0.7801258587377804, max_speed=0.943690524773741, max_yaw_rate=2.889676241199347, k_pos=2.09... tangent_preview=0.15, escape_clearance=0.1, escape_preview=1.0, escape_speed=0.75, brake_accel=0.8, brake_margin=0.15)
        self       = <dimos.navigation.motion.control.laws.hinted.RustHintedController object at 0xffe9d86dca40>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def load_extension() -> Any:
        """The built rust extension, or an ImportError naming the build command."""
        try:
            import dimos_motion2_tc
        except ImportError as e:
>           raise ImportError(f"dimos_motion2_tc is not built; run: {BUILD_CMD}") from e
E           ImportError: dimos_motion2_tc is not built; run: uv run maturin develop --uv --release -m .../control/rust/Cargo.toml


.../motion/control/controller.py:169: ImportError
dimos.robot.test_all_blueprints_generation::test_all_blueprints_is_current
Stack Traces | 6.25s run time
def test_all_blueprints_is_current() -> None:
        root = DIMOS_PROJECT_ROOT / "dimos"
        all_blueprints, all_modules = _scan_for_blueprints(root)
    
        common = set(all_blueprints.keys()) & set(all_modules.keys())
        assert not common, (
            f"Names must be unique across blueprints and modules, "
            f"but these appear in both: {sorted(common)}"
        )
    
        generated_content = _generate_all_blueprints_content(all_blueprints, all_modules)
    
        file_path = root / "robot" / "all_blueprints.py"
    
        if "CI" in os.environ:
            if not file_path.exists():
                pytest.fail(f"all_blueprints.py does not exist at {file_path}")
    
            current_content = file_path.read_text()
            if current_content != generated_content:
                diff = difflib.unified_diff(
                    current_content.splitlines(keepends=True),
                    generated_content.splitlines(keepends=True),
                    fromfile="all_blueprints.py (current)",
                    tofile="all_blueprints.py (generated)",
                )
                diff_str = "".join(diff)
>               pytest.fail(
                    f"all_blueprints.py is out of date. Run "
                    f"`pytest dimos/robot/test_all_blueprints_generation.py` locally to update.\n\n"
                    f"Diff:\n{diff_str}"
                )
E               Failed: all_blueprints.py is out of date. Run `pytest dimos/robot/test_all_blueprints_generation.py` locally to update.
E               
E               Diff:
E               --- all_blueprints.py (current)
E               +++ all_blueprints.py (generated)
E               @@ -256,7 +256,6 @@
E                    "osm-skill": "dimos.agents.skills.osm.OsmSkill",
E                    "path-follower": "dimos.navigation.cmu_nav.modules.path_follower.path_follower.PathFollower",
E                    "path-following-coordinator": "dimos.control.path_following_coordinator.PathFollowingCoordinator",
E               -    "path-heading": "dimos.navigation.path_heading.module.PathHeading",
E                    "patrolling-module": "dimos.navigation.patrolling.module.PatrollingModule",
E                    "perceive-loop-skill": "dimos.perception.experimental.perceive_loop_skill.PerceiveLoopSkill",
E                    "person-follow-skill-container": "dimos.agents.skills.person_follow.PersonFollowSkillContainer",

all_blueprints = {'a1z-planner-coordinator': 'dimos.robot.manipulators.a1z.blueprints.basic:a1z_planner_coordinator', 'alfred-nav': 'di...rs.a1z.blueprints.basic:coordinator_a1z', 'coordinator-basic': 'dimos.control.blueprints.basic:coordinator_basic', ...}
all_modules = {'alfred-high-level': 'dimos.robot.diy.alfred.effector_high_level.AlfredHighLevel', 'arm-command-module': 'dimos.teleo...t_extensions.ArmTeleopModule', 'b-box-navigation-module': 'dimos.navigation.bbox_navigation.BBoxNavigationModule', ...}
common     = set()
current_content = '# Copyright 2025-2026 Dimensional Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the "License");\n# you m...sted.blueprints.cloudflare.WristCamera",\n    "zed-camera": "dimos.hardware.sensors.camera.zed.camera.ZEDCamera",\n}\n'
diff       = <generator object unified_diff at 0xffe9da064380>
diff_str   = '--- all_blueprints.py (current)\n+++ all_blueprints.py (generated)\n@@ -256,7 +256,6 @@\n     "osm-skill": "dimos.age...veLoopSkill",\n     "person-follow-skill-container": "dimos.agents.skills.person_follow.PersonFollowSkillContainer",\n'
file_path  = PosixPath('.../dimos/robot/all_blueprints.py')
generated_content = '# Copyright 2025-2026 Dimensional Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the "License");\n# you m...sted.blueprints.cloudflare.WristCamera",\n    "zed-camera": "dimos.hardware.sensors.camera.zed.camera.ZEDCamera",\n}\n'
root       = PosixPath('.../dimos/dimos/dimos')

dimos/robot/test_all_blueprints_generation.py:76: Failed
dimos.codebase_checks.test_no_underscore_assign::test_no_underscore_assignment
Stack Traces | 7.4s run time
def test_no_underscore_assignment():
        """Fail if any file assigns to a bare `_`."""
        dimos_dir = DIMOS_PROJECT_ROOT / "dimos"
        hits = find_underscore_assignments()
        if hits:
            listing = "\n".join(f"  - {p.relative_to(dimos_dir)}:{lineno}" for p, lineno in hits)
>           raise AssertionError(
                f"Found assignment(s) to `_` in dimos/:\n{listing}\n\n"
                "Assigning to `_` is not allowed: it hides an unused variable instead "
                "of removing it. Delete the variable. If you only need the "
                "expression's side effect, evaluate it directly with a call "
                "(`obj.method()`, `getattr(obj, 'attr')`) or log it; a bare attribute "
                "access needs `# noqa: B018`. Tuple unpacking (`a, _ = f()`) is fine "
                "and not flagged by this rule."
            )
E           AssertionError: Found assignment(s) to `_` in dimos/:
E             - .../export/templates/battery.py:114
E           
E           Assigning to `_` is not allowed: it hides an unused variable instead of removing it. Delete the variable. If you only need the expression's side effect, evaluate it directly with a call (`obj.method()`, `getattr(obj, 'attr')`) or log it; a bare attribute access needs `# noqa: B018`. Tuple unpacking (`a, _ = f()`) is fine and not flagged by this rule.

dimos_dir  = PosixPath('.../dimos/dimos/dimos')
hits       = [(PosixPath('.../dimos/dimos/dimos/.../export/templates/battery.py'), 114)]
listing    = '  - .../export/templates/battery.py:114'

dimos/codebase_checks/test_no_underscore_assign.py:55: AssertionError
dimos.agents.skills.test_navigation::test_go_to_semantic_location
Stack Traces | 67.9s run time
dimos.protocol.rpc.rpc_utils.RemoteError: [Remote builtins.RuntimeError] Failed to fetch tools from MCP server http://localhost:24388/mcp

Remote traceback:
Traceback (most recent call last):
  File ".../protocol/rpc/pubsubrpc.py", line 280, in execute_and_respond
    response = f(*args[0], **args[1])
               ^^^^^^^^^^^^^^^^^^^^^^
  File ".../protocol/rpc/spec.py", line 116, in override_f
    return getattr(module, fname)(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../agents/mcp/mcp_client.py", line 229, in on_system_modules
    tools = self._fetch_tools()
            ^^^^^^^^^^^^^^^^^^^
  File ".../agents/mcp/mcp_client.py", line 152, in _fetch_tools
    raise RuntimeError(
RuntimeError: Failed to fetch tools from MCP server http://localhost:24388/mcp


The above exception was the direct cause of the following exception:

agent_setup = <function agent_setup.<locals>.fn at 0xff369fe63ec0>

    def test_go_to_semantic_location(agent_setup) -> None:
>       history = agent_setup(
            blueprints=[
                FakeCamera.blueprint(),
                FakeOdom.blueprint(),
                MockedSemanticNavSkill.blueprint(),
                *_STUB_BLUEPRINTS,
            ],
            messages=[HumanMessage("Go to the bookshelf. Use the navigate_with_text tool.")],
        )

agent_setup = <function agent_setup.<locals>.fn at 0xff369fe63ec0>

.../agents/skills/test_navigation.py:151: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/agents/conftest.py:90: in fn
    coordinator = ModuleCoordinator.build(blueprint)
        agent_kwargs = {'mcp_server_url': 'http://localhost:24388/mcp', 'model_fixture': '.../agents/fixtures/test_go_to_semantic_location.json', 'system_prompt': None}
        agent_transport = <dimos.core.transport.pLCMTransport object at 0xff369e9ee000>
        blueprint  = Blueprint(blueprints=(BlueprintAtom(kwargs={}, module=<class 'dimos.agents.skills.test_navigation.FakeCamera'>, stream...lobal_config_overrides=mappingproxy({}), remapping_map=mappingproxy({}), requirement_checks=(), configurator_checks=())
        blueprints = [Blueprint(blueprints=(BlueprintAtom(kwargs={}, module=<class 'dimos.agents.skills.test_navigation.FakeCamera'>, strea...obal_config_overrides=mappingproxy({}), remapping_map=mappingproxy({}), requirement_checks=(), configurator_checks=())]
        coordinator = None
        finished_event = <threading.Event at 0xff369e9ee0c0: unset>
        finished_transport = <dimos.core.transport.pLCMTransport object at 0xff369e9edee0>
        fixture    = None
        fixture_path = PosixPath('.../agents/fixtures/test_go_to_semantic_location.json')
        history    = []
        lcm_url    = 'udpm://239.255.76.67:12088?ttl=0'
        mcp_url    = 'http://localhost:24388/mcp'
        messages   = [HumanMessage(content='Go to the bookshelf. Use the navigate_with_text tool.', additional_kwargs={}, response_metadata={})]
        on_message = <function agent_setup.<locals>.fn.<locals>.on_message at 0xff369fe62980>
        recording  = False
        request    = <SubRequest 'agent_setup' for <Function test_go_to_semantic_location>>
        system_prompt = None
        transports = [<dimos.core.transport.pLCMTransport object at 0xff369e9ee000>, <dimos.core.transport.pLCMTransport object at 0xff369e9edee0>]
        unsubs     = [<function LCMPubSubBase.subscribe.<locals>.unsubscribe at 0xff369fe60040>, <function LCMPubSubBase.subscribe.<locals>.unsubscribe at 0xff369fe61580>]
.../core/coordination/module_coordinator.py:368: in build
    coordinator.start_all_modules()
        blueprint  = Blueprint(blueprints=(BlueprintAtom(kwargs={}, module=<class 'dimos.agents.skills.test_navigation.FakeCamera'>, stream...lobal_config_overrides=mappingproxy({}), remapping_map=mappingproxy({}), requirement_checks=(), configurator_checks=())
        cls        = <class 'dimos.core.coordination.module_coordinator.ModuleCoordinator'>
        coordinator = <dimos.core.coordination.module_coordinator.ModuleCoordinator object at 0xff369e9ef950>
        global_values = {}
        module_kwargs = {}
        parsed_config = None
        transport_overrides = {}
        transports = {}
.../core/coordination/module_coordinator.py:259: in start_all_modules
    self._send_on_system_modules()
        modules    = [<dimos.core.rpc_client.RPCClient object at 0xff369e4590d0>, <dimos.core.rpc_client.RPCClient object at 0xff369e433590...s.core.rpc_client.RPCClient object at 0xff369e4596a0>, <dimos.core.rpc_client.RPCClient object at 0xff369e459a60>, ...]
        self       = <dimos.core.coordination.module_coordinator.ModuleCoordinator object at 0xff369e9ef950>
.../core/coordination/module_coordinator.py:299: in _send_on_system_modules
    module.on_system_modules(modules)
        module     = <dimos.core.rpc_client.RPCClient object at 0xff369e45ac30>
        modules    = [<dimos.core.rpc_client.RPCClient object at 0xff369e4590d0>, <dimos.core.rpc_client.RPCClient object at 0xff369e433590...s.core.rpc_client.RPCClient object at 0xff369e4596a0>, <dimos.core.rpc_client.RPCClient object at 0xff369e459a60>, ...]
        self       = <dimos.core.coordination.module_coordinator.ModuleCoordinator object at 0xff369e9ef950>
dimos/core/rpc_client.py:93: in __call__
    result, unsub_fn = self._rpc.call_sync(
        args       = ([<dimos.core.rpc_client.RPCClient object at 0xff369e4590d0>, <dimos.core.rpc_client.RPCClient object at 0xff369e43359...core.rpc_client.RPCClient object at 0xff369e4596a0>, <dimos.core.rpc_client.RPCClient object at 0xff369e459a60>, ...],)
        kwargs     = {}
        self       = <dimos.core.rpc_client.RpcCall object at 0xff369e961dc0>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <dimos.protocol.rpc.pubsubrpc.LCMRPC object at 0xff369e433710>
name = 'McpClient/on_system_modules'
arguments = (([<dimos.core.rpc_client.RPCClient object at 0xff369e4590d0>, <dimos.core.rpc_client.RPCClient object at 0xff369e4335...rpc_client.RPCClient object at 0xff369e4596a0>, <dimos.core.rpc_client.RPCClient object at 0xff369e459a60>, ...],), {})
rpc_timeout = 120.0

    def call_sync(
        self, name: str, arguments: Args, rpc_timeout: float | None = None
    ) -> tuple[Any, Callable[[], None]]:
        if rpc_timeout is None:
            method = name.rsplit("/", 1)[-1]
            rpc_timeout = self.rpc_timeouts.get(name) or self.rpc_timeouts.get(
                method, self.default_rpc_timeout
            )
        event = threading.Event()
    
        def receive_value(val) -> None:  # type: ignore[no-untyped-def]
            event.result = val  # type: ignore[attr-defined]  # attach to event
            event.set()
    
        unsub_fn = self.call(name, arguments, receive_value)
        if not event.wait(rpc_timeout):
            # Retries register new callbacks. Remove this expired callback so
            # repeated timeouts do not accumulate entries in the shared response map.
            unsub_fn()
            raise TimeoutError(f"RPC call to '{name}' timed out after {rpc_timeout} seconds")
    
        # Check if the result is an exception and raise it
        result = event.result  # type: ignore[attr-defined]
        if isinstance(result, BaseException):
>           raise result
E           RuntimeError: Failed to fetch tools from MCP server http://localhost:24388/mcp

arguments  = (([<dimos.core.rpc_client.RPCClient object at 0xff369e4590d0>, <dimos.core.rpc_client.RPCClient object at 0xff369e4335...rpc_client.RPCClient object at 0xff369e4596a0>, <dimos.core.rpc_client.RPCClient object at 0xff369e459a60>, ...],), {})
event      = <threading.Event at 0xff36b45ac6e0: set>
method     = 'on_system_modules'
name       = 'McpClient/on_system_modules'
receive_value = <function RPCClient.call_sync.<locals>.receive_value at 0xff36a404e5c0>
result     = RuntimeError('Failed to fetch tools from MCP server http://localhost:24388/mcp')
rpc_timeout = 120.0
self       = <dimos.protocol.rpc.pubsubrpc.LCMRPC object at 0xff369e433710>
unsub_fn   = <function PubSubRPCMixin.call_cb.<locals>.unsubscribe_callback at 0xff369fe207c0>

.../protocol/rpc/spec.py:88: RuntimeError
dimos.agents.skills.test_navigation::test_stop_movement
Stack Traces | 71s run time
dimos.protocol.rpc.rpc_utils.RemoteError: [Remote builtins.RuntimeError] Failed to fetch tools from MCP server http://localhost:24388/mcp

Remote traceback:
Traceback (most recent call last):
  File ".../protocol/rpc/pubsubrpc.py", line 280, in execute_and_respond
    response = f(*args[0], **args[1])
               ^^^^^^^^^^^^^^^^^^^^^^
  File ".../protocol/rpc/spec.py", line 116, in override_f
    return getattr(module, fname)(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../agents/mcp/mcp_client.py", line 229, in on_system_modules
    tools = self._fetch_tools()
            ^^^^^^^^^^^^^^^^^^^
  File ".../agents/mcp/mcp_client.py", line 152, in _fetch_tools
    raise RuntimeError(
RuntimeError: Failed to fetch tools from MCP server http://localhost:24388/mcp


The above exception was the direct cause of the following exception:

agent_setup = <function agent_setup.<locals>.fn at 0xff369fe2e200>

    def test_stop_movement(agent_setup) -> None:
>       history = agent_setup(
            blueprints=[
                FakeCamera.blueprint(),
                FakeOdom.blueprint(),
                MockedStopNavSkill.blueprint(),
                *_STUB_BLUEPRINTS,
            ],
            messages=[HumanMessage("Stop moving. Use the stop_movement tool.")],
        )

agent_setup = <function agent_setup.<locals>.fn at 0xff369fe2e200>

.../agents/skills/test_navigation.py:121: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/agents/conftest.py:90: in fn
    coordinator = ModuleCoordinator.build(blueprint)
        agent_kwargs = {'mcp_server_url': 'http://localhost:24388/mcp', 'model_fixture': '.../agents/fixtures/test_stop_movement.json', 'system_prompt': None}
        agent_transport = <dimos.core.transport.pLCMTransport object at 0xff369d6544d0>
        blueprint  = Blueprint(blueprints=(BlueprintAtom(kwargs={}, module=<class 'dimos.agents.skills.test_navigation.FakeCamera'>, stream...lobal_config_overrides=mappingproxy({}), remapping_map=mappingproxy({}), requirement_checks=(), configurator_checks=())
        blueprints = [Blueprint(blueprints=(BlueprintAtom(kwargs={}, module=<class 'dimos.agents.skills.test_navigation.FakeCamera'>, strea...obal_config_overrides=mappingproxy({}), remapping_map=mappingproxy({}), requirement_checks=(), configurator_checks=())]
        coordinator = None
        finished_event = <threading.Event at 0xff369d6550a0: unset>
        finished_transport = <dimos.core.transport.pLCMTransport object at 0xff369d656c60>
        fixture    = None
        fixture_path = PosixPath('.../agents/fixtures/test_stop_movement.json')
        history    = []
        lcm_url    = 'udpm://239.255.76.67:12088?ttl=0'
        mcp_url    = 'http://localhost:24388/mcp'
        messages   = [HumanMessage(content='Stop moving. Use the stop_movement tool.', additional_kwargs={}, response_metadata={})]
        on_message = <function agent_setup.<locals>.fn.<locals>.on_message at 0xff3719ca0f40>
        recording  = False
        request    = <SubRequest 'agent_setup' for <Function test_stop_movement>>
        system_prompt = None
        transports = [<dimos.core.transport.pLCMTransport object at 0xff369d6544d0>, <dimos.core.transport.pLCMTransport object at 0xff369d656c60>]
        unsubs     = [<function LCMPubSubBase.subscribe.<locals>.unsubscribe at 0xff369fe2e160>, <function LCMPubSubBase.subscribe.<locals>.unsubscribe at 0xff369fe2efc0>]
.../core/coordination/module_coordinator.py:368: in build
    coordinator.start_all_modules()
        blueprint  = Blueprint(blueprints=(BlueprintAtom(kwargs={}, module=<class 'dimos.agents.skills.test_navigation.FakeCamera'>, stream...lobal_config_overrides=mappingproxy({}), remapping_map=mappingproxy({}), requirement_checks=(), configurator_checks=())
        cls        = <class 'dimos.core.coordination.module_coordinator.ModuleCoordinator'>
        coordinator = <dimos.core.coordination.module_coordinator.ModuleCoordinator object at 0xff369d6556a0>
        global_values = {}
        module_kwargs = {}
        parsed_config = None
        transport_overrides = {}
        transports = {}
.../core/coordination/module_coordinator.py:259: in start_all_modules
    self._send_on_system_modules()
        modules    = [<dimos.core.rpc_client.RPCClient object at 0xff369d6c42f0>, <dimos.core.rpc_client.RPCClient object at 0xff369d6c4fb0...s.core.rpc_client.RPCClient object at 0xff369d6c6bd0>, <dimos.core.rpc_client.RPCClient object at 0xff369d6c5730>, ...]
        self       = <dimos.core.coordination.module_coordinator.ModuleCoordinator object at 0xff369d6556a0>
.../core/coordination/module_coordinator.py:299: in _send_on_system_modules
    module.on_system_modules(modules)
        module     = <dimos.core.rpc_client.RPCClient object at 0xff369d6c44d0>
        modules    = [<dimos.core.rpc_client.RPCClient object at 0xff369d6c42f0>, <dimos.core.rpc_client.RPCClient object at 0xff369d6c4fb0...s.core.rpc_client.RPCClient object at 0xff369d6c6bd0>, <dimos.core.rpc_client.RPCClient object at 0xff369d6c5730>, ...]
        self       = <dimos.core.coordination.module_coordinator.ModuleCoordinator object at 0xff369d6556a0>
dimos/core/rpc_client.py:93: in __call__
    result, unsub_fn = self._rpc.call_sync(
        args       = ([<dimos.core.rpc_client.RPCClient object at 0xff369d6c42f0>, <dimos.core.rpc_client.RPCClient object at 0xff369d6c4fb...core.rpc_client.RPCClient object at 0xff369d6c6bd0>, <dimos.core.rpc_client.RPCClient object at 0xff369d6c5730>, ...],)
        kwargs     = {}
        self       = <dimos.core.rpc_client.RpcCall object at 0xff369d6c79b0>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <dimos.protocol.rpc.pubsubrpc.LCMRPC object at 0xff369d6952b0>
name = 'McpClient/on_system_modules'
arguments = (([<dimos.core.rpc_client.RPCClient object at 0xff369d6c42f0>, <dimos.core.rpc_client.RPCClient object at 0xff369d6c4f...rpc_client.RPCClient object at 0xff369d6c6bd0>, <dimos.core.rpc_client.RPCClient object at 0xff369d6c5730>, ...],), {})
rpc_timeout = 120.0

    def call_sync(
        self, name: str, arguments: Args, rpc_timeout: float | None = None
    ) -> tuple[Any, Callable[[], None]]:
        if rpc_timeout is None:
            method = name.rsplit("/", 1)[-1]
            rpc_timeout = self.rpc_timeouts.get(name) or self.rpc_timeouts.get(
                method, self.default_rpc_timeout
            )
        event = threading.Event()
    
        def receive_value(val) -> None:  # type: ignore[no-untyped-def]
            event.result = val  # type: ignore[attr-defined]  # attach to event
            event.set()
    
        unsub_fn = self.call(name, arguments, receive_value)
        if not event.wait(rpc_timeout):
            # Retries register new callbacks. Remove this expired callback so
            # repeated timeouts do not accumulate entries in the shared response map.
            unsub_fn()
            raise TimeoutError(f"RPC call to '{name}' timed out after {rpc_timeout} seconds")
    
        # Check if the result is an exception and raise it
        result = event.result  # type: ignore[attr-defined]
        if isinstance(result, BaseException):
>           raise result
E           RuntimeError: Failed to fetch tools from MCP server http://localhost:24388/mcp

arguments  = (([<dimos.core.rpc_client.RPCClient object at 0xff369d6c42f0>, <dimos.core.rpc_client.RPCClient object at 0xff369d6c4f...rpc_client.RPCClient object at 0xff369d6c6bd0>, <dimos.core.rpc_client.RPCClient object at 0xff369d6c5730>, ...],), {})
event      = <threading.Event at 0xff369d622bd0: set>
method     = 'on_system_modules'
name       = 'McpClient/on_system_modules'
receive_value = <function RPCClient.call_sync.<locals>.receive_value at 0xff369fe2d3a0>
result     = RuntimeError('Failed to fetch tools from MCP server http://localhost:24388/mcp')
rpc_timeout = 120.0
self       = <dimos.protocol.rpc.pubsubrpc.LCMRPC object at 0xff369d6952b0>
unsub_fn   = <function PubSubRPCMixin.call_cb.<locals>.unsubscribe_callback at 0xff36a4123600>

.../protocol/rpc/spec.py:88: RuntimeError
dimos.agents.skills.test_navigation::test_start_exploration
Stack Traces | 72.8s run time
dimos.protocol.rpc.rpc_utils.RemoteError: [Remote builtins.RuntimeError] Failed to fetch tools from MCP server http://localhost:24388/mcp

Remote traceback:
Traceback (most recent call last):
  File ".../protocol/rpc/pubsubrpc.py", line 280, in execute_and_respond
    response = f(*args[0], **args[1])
               ^^^^^^^^^^^^^^^^^^^^^^
  File ".../protocol/rpc/spec.py", line 116, in override_f
    return getattr(module, fname)(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../agents/mcp/mcp_client.py", line 229, in on_system_modules
    tools = self._fetch_tools()
            ^^^^^^^^^^^^^^^^^^^
  File ".../agents/mcp/mcp_client.py", line 152, in _fetch_tools
    raise RuntimeError(
RuntimeError: Failed to fetch tools from MCP server http://localhost:24388/mcp


The above exception was the direct cause of the following exception:

agent_setup = <function agent_setup.<locals>.fn at 0xff36a4071080>

    def test_start_exploration(agent_setup) -> None:
>       history = agent_setup(
            blueprints=[
                FakeCamera.blueprint(),
                FakeOdom.blueprint(),
                MockedExploreNavSkill.blueprint(),
                *_STUB_BLUEPRINTS,
            ],
            messages=[
                HumanMessage("Take a look around for 10 seconds. Use the start_exploration tool.")
            ],
        )

agent_setup = <function agent_setup.<locals>.fn at 0xff36a4071080>

.../agents/skills/test_navigation.py:135: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/agents/conftest.py:90: in fn
    coordinator = ModuleCoordinator.build(blueprint)
        agent_kwargs = {'mcp_server_url': 'http://localhost:24388/mcp', 'model_fixture': '.../agents/fixtures/test_start_exploration.json', 'system_prompt': None}
        agent_transport = <dimos.core.transport.pLCMTransport object at 0xff369ea0efc0>
        blueprint  = Blueprint(blueprints=(BlueprintAtom(kwargs={}, module=<class 'dimos.agents.skills.test_navigation.FakeCamera'>, stream...lobal_config_overrides=mappingproxy({}), remapping_map=mappingproxy({}), requirement_checks=(), configurator_checks=())
        blueprints = [Blueprint(blueprints=(BlueprintAtom(kwargs={}, module=<class 'dimos.agents.skills.test_navigation.FakeCamera'>, strea...obal_config_overrides=mappingproxy({}), remapping_map=mappingproxy({}), requirement_checks=(), configurator_checks=())]
        coordinator = None
        finished_event = <threading.Event at 0xff369ea0eff0: unset>
        finished_transport = <dimos.core.transport.pLCMTransport object at 0xff369ea0d8b0>
        fixture    = None
        fixture_path = PosixPath('.../agents/fixtures/test_start_exploration.json')
        history    = []
        lcm_url    = 'udpm://239.255.76.67:12088?ttl=0'
        mcp_url    = 'http://localhost:24388/mcp'
        messages   = [HumanMessage(content='Take a look around for 10 seconds. Use the start_exploration tool.', additional_kwargs={}, response_metadata={})]
        on_message = <function agent_setup.<locals>.fn.<locals>.on_message at 0xff36a40711c0>
        recording  = False
        request    = <SubRequest 'agent_setup' for <Function test_start_exploration>>
        system_prompt = None
        transports = [<dimos.core.transport.pLCMTransport object at 0xff369ea0efc0>, <dimos.core.transport.pLCMTransport object at 0xff369ea0d8b0>]
        unsubs     = [<function LCMPubSubBase.subscribe.<locals>.unsubscribe at 0xff36a4070540>, <function LCMPubSubBase.subscribe.<locals>.unsubscribe at 0xff36a4072b60>]
.../core/coordination/module_coordinator.py:368: in build
    coordinator.start_all_modules()
        blueprint  = Blueprint(blueprints=(BlueprintAtom(kwargs={}, module=<class 'dimos.agents.skills.test_navigation.FakeCamera'>, stream...lobal_config_overrides=mappingproxy({}), remapping_map=mappingproxy({}), requirement_checks=(), configurator_checks=())
        cls        = <class 'dimos.core.coordination.module_coordinator.ModuleCoordinator'>
        coordinator = <dimos.core.coordination.module_coordinator.ModuleCoordinator object at 0xff369ea0e390>
        global_values = {}
        module_kwargs = {}
        parsed_config = None
        transport_overrides = {}
        transports = {}
.../core/coordination/module_coordinator.py:259: in start_all_modules
    self._send_on_system_modules()
        modules    = [<dimos.core.rpc_client.RPCClient object at 0xff369d70a180>, <dimos.core.rpc_client.RPCClient object at 0xff369d70a330...s.core.rpc_client.RPCClient object at 0xff369d70ac60>, <dimos.core.rpc_client.RPCClient object at 0xff369d70a2d0>, ...]
        self       = <dimos.core.coordination.module_coordinator.ModuleCoordinator object at 0xff369ea0e390>
.../core/coordination/module_coordinator.py:299: in _send_on_system_modules
    module.on_system_modules(modules)
        module     = <dimos.core.rpc_client.RPCClient object at 0xff369d709970>
        modules    = [<dimos.core.rpc_client.RPCClient object at 0xff369d70a180>, <dimos.core.rpc_client.RPCClient object at 0xff369d70a330...s.core.rpc_client.RPCClient object at 0xff369d70ac60>, <dimos.core.rpc_client.RPCClient object at 0xff369d70a2d0>, ...]
        self       = <dimos.core.coordination.module_coordinator.ModuleCoordinator object at 0xff369ea0e390>
dimos/core/rpc_client.py:93: in __call__
    result, unsub_fn = self._rpc.call_sync(
        args       = ([<dimos.core.rpc_client.RPCClient object at 0xff369d70a180>, <dimos.core.rpc_client.RPCClient object at 0xff369d70a33...core.rpc_client.RPCClient object at 0xff369d70ac60>, <dimos.core.rpc_client.RPCClient object at 0xff369d70a2d0>, ...],)
        kwargs     = {}
        self       = <dimos.core.rpc_client.RpcCall object at 0xff369d628b00>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <dimos.protocol.rpc.pubsubrpc.LCMRPC object at 0xff369e9bd580>
name = 'McpClient/on_system_modules'
arguments = (([<dimos.core.rpc_client.RPCClient object at 0xff369d70a180>, <dimos.core.rpc_client.RPCClient object at 0xff369d70a3...rpc_client.RPCClient object at 0xff369d70ac60>, <dimos.core.rpc_client.RPCClient object at 0xff369d70a2d0>, ...],), {})
rpc_timeout = 120.0

    def call_sync(
        self, name: str, arguments: Args, rpc_timeout: float | None = None
    ) -> tuple[Any, Callable[[], None]]:
        if rpc_timeout is None:
            method = name.rsplit("/", 1)[-1]
            rpc_timeout = self.rpc_timeouts.get(name) or self.rpc_timeouts.get(
                method, self.default_rpc_timeout
            )
        event = threading.Event()
    
        def receive_value(val) -> None:  # type: ignore[no-untyped-def]
            event.result = val  # type: ignore[attr-defined]  # attach to event
            event.set()
    
        unsub_fn = self.call(name, arguments, receive_value)
        if not event.wait(rpc_timeout):
            # Retries register new callbacks. Remove this expired callback so
            # repeated timeouts do not accumulate entries in the shared response map.
            unsub_fn()
            raise TimeoutError(f"RPC call to '{name}' timed out after {rpc_timeout} seconds")
    
        # Check if the result is an exception and raise it
        result = event.result  # type: ignore[attr-defined]
        if isinstance(result, BaseException):
>           raise result
E           RuntimeError: Failed to fetch tools from MCP server http://localhost:24388/mcp

arguments  = (([<dimos.core.rpc_client.RPCClient object at 0xff369d70a180>, <dimos.core.rpc_client.RPCClient object at 0xff369d70a3...rpc_client.RPCClient object at 0xff369d70ac60>, <dimos.core.rpc_client.RPCClient object at 0xff369d70a2d0>, ...],), {})
event      = <threading.Event at 0xff369e9dce00: set>
method     = 'on_system_modules'
name       = 'McpClient/on_system_modules'
receive_value = <function RPCClient.call_sync.<locals>.receive_value at 0xff36a4070f40>
result     = RuntimeError('Failed to fetch tools from MCP server http://localhost:24388/mcp')
rpc_timeout = 120.0
self       = <dimos.protocol.rpc.pubsubrpc.LCMRPC object at 0xff369e9bd580>
unsub_fn   = <function PubSubRPCMixin.call_cb.<locals>.unsubscribe_callback at 0xff36a404e700>

.../protocol/rpc/spec.py:88: RuntimeError

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

leshy added 21 commits August 4, 2026 23:15
An offline post-mortem for a recording of the live motion graph: voxel churn
in the local map (crop-boundary vs interior), when the published plan flipped,
the planner re-run on the recorded inputs with a one-input-at-a-time ablation,
and the age of what each tick planned on.

On 20260805-033007 it says the cloud is the unstable input, not the planner:
the replay reproduces the recorded plans to 0.13 m, is bit-identical run twice,
and 97% of every flip's magnitude follows the cloud alone. The map itself turns
over 10% per frame, two thirds of that because the emitted window breathes
2.5..5.9 m -- and the planner reads a slab 0.33 m above the floor.
A navigation recording's raycaster map, frozen into a world the sim and the
referee can both run. The map flickers -- ~10% of its voxels come and go frame
to frame -- so the static union is stability filtered: a voxel survives only if
it was seen in at least half the frames spanning its own first-to-last
sighting. On the example run that keeps 44562 of 75206 voxels.

recorded_world.py extracts to a small npz (voxels, floor, recorded start pose,
recorded global path and goal, and the body-band footprint as merged
rectangles), builds the fitted Go2 scene with the world as greedy-merged static
boxes (17523 voxels -> 4209 geoms), and crops the world around a pose into a
local_map-shaped cloud for closed-loop perception later.

The robot maps its own legs, so whatever the recorded body swept is carved back
out with a margin -- without it the spawn pose starts inside a wall built from
its own returns and every oracle refuses at step one.

scenarios.recorded() turns the npz into an ordinary Scenario, so --recorded
adds it to the referee battery and to closed-loop episodes next to the curated
and generated worlds. Labeled "safe", never "clear": reality gets no
expectation handed to it.
The target planner slices the cloud at an ABSOLUTE z of 0.05..0.45, which is
the body's band only if the map's z origin is the ground. On the go2's LIO
stack it is not: odometry starts at the sensor, so the origin sits at base
height and the band actually reads 0.33..0.73 above the floor -- blind to the
bottom third of every obstacle and steering off table tops.

cloud_z_offset cannot fix that. It is a knife edge, not a dial: at +0.29 the
floor's own voxel slab lands in the band and every tick refuses. So the floor
is now measured per tick from the cloud under the robot (adapter/floor.py, the
estimator replay.py already had), sanity bounded against what tf says the base
height above ground is, and the ground slab is dropped before the band is
taken. Without the tf prior nothing moves: a low quantile of the cloud alone is
only the floor if the floor is in the cloud, and it says so once in the log.

The margin is two voxel layers, not one. A floor whose true height sits near a
voxel boundary quantises into both layers either side of it; at one voxel the
robot is inside the band on every tick of 20260805-033007, at two on 7% of them.

Anchoring is an adapter-level correction, so the referee's own planners/ path
is untouched and its scores cannot move; on a world whose floor is already at
zero it shifts nothing.

diagnose's replay does the same anchoring off the same prior, with --no-anchor
for the band as it was.
region_percentile sizes the emitted cylinder to the last ten sweeps' point
distances, so it follows what the sensor happened to see rather than what the
map holds. On 20260805-033007 the radius swung 2.53 -> 5.88 m, up to 2.36 m in
one frame, and 68 % of every voxel that appeared or disappeared between local
maps was that window moving -- not the world. Each collapse deletes thousands
of voxels the local planner was routing around and each expansion invents them
back, which is a plan flip at the emit rate.

region_radius_m pins it. Zero keeps the percentile, so nothing that relies on
the old sizing changes; the go2 zenoh motion stacks take 5 m, which covers the
5 m carrot with room for the search's padding and sits inside what the sweeps
actually fill.

The property the tests pin is the one the planner needs: the same map cropped
by a near batch's window and by a far batch's window is the same set of voxels.
The planner ticks at 5 Hz over a 1 Hz map, so four ticks in five re-solve a
world that has not moved. The diagnosis measures what that buys: between maps
the plan is steady to 0.15 m, across a map boundary it moves 0.85 m and flips
ten times as often. The four extra searches are work whose only output is
jitter, and the follower does not need them -- it tracks the path it already
holds as the robot moves.

So the gate is (local_map arrival, global route CHANGE). A route counts as
changed when its waypoints moved, not when MLS republished the same one at
1 Hz -- which is also when the episode gets reset now, instead of every second.

A hold is not gated. It is a statement about the clock, and nothing arriving is
precisely the case it fires on; a stale spell also forgets what was planned, so
the first live tick plans again.

On 20260805-033007 that is 107 published plans instead of 410 -- 74 % less
search -- with flips per minute going 18.2 -> 12.3 alongside the floor
anchoring. diagnose --gate replays the tick sequence the gate would keep.
ff4df4e anchored the PLANNER's obstacle band to the floor and left the
follower reading the raw absolute Z_BAND off the same map, so the speed
governor and the precision profile stamped into the path it is tracking
measured two different slices of one room -- on the go2's LIO stack, 0.33 m
apart. The hint the hinted law governs on came from a slab over the robot's
head-room while the plan was priced at body height.

So the follower anchors too, through the same estimator and the same tf
prior: FloorAnchor now holds what the planner grew for fix 1 (the cached
mount leg, the base height, the warn-once) and both modules own one, with
the same three knobs -- floor_anchor, lidar_height, ground_margin_m -- and
the same degrade: no prior, no anchoring, the band stays where it was.

The rust twins move in step. anchored_cloud drops out of planner.rs into
floor.rs where it serves both, and the follower's odometry handler grows
the tf plumbing the planner's has (resolve_iso + the cached leg, floor_prior
on the snapshot). measure_room is the extracted _clearance_for recompute,
free so it can be exercised with no transport.

On a floor already at zero the anchoring only drops the ground slab, which
was never in the band -- so the referee's sim worlds, and the scores taken
on them, cannot move. The recording in the diagnosis carries no nav_cmd_vel
and cannot score the follower; its planner numbers replay unchanged.
planner/ and control/ now have the same three parts: a runner that picks a
candidate, a referee that scores it, and research/ where candidates come from.
One referee per side is what makes the shipped law, an autoresearch lab's and
a learned one comparable at all.

planner/autoresearch/ was never a lab -- it is the referee, and the name
collision is why the actual control lab (motion-tc-autoresearch, which wrote
laws/{blind,hinted}.py) was invisible from in here. So:

  planner/autoresearch/        -> planner/referee/      (worlds, gold, judge)
  planner/autoresearch/rust/   -> planner/rust/         (production crate)
  planner/autoresearch/export/ -> planner/research/auto/export/
  control/{episode,judge,battery,world,probe_walk_slip}.py
                               -> control/referee/

referee/ stays one copyable unit on the planner side -- every import inside it
is relative and it depends on numpy+scipy+pydantic only, which is what lets
export/ vendor it into a lab and what `python -m referee` there relies on.
Both __main__.py files stay: the runner at the package root, and the referee's
own inside the copyable unit. Each pins the BLAS pools itself, because that
only works before numpy is first imported.

research/*/README.md carries what was previously folklore: where each lab is,
what it produced, and how a result lands. The labs stay outside this repo --
an autoresearch loop needs write access to its candidate and none to the
referee scoring it, and the export hash-pins are what make that checkable.
test_layering.py enforces the one-way import rule.

Behaviour is unchanged: corridor still scores 115.32, 261 motion tests pass,
mypy clean, and the full export smoke rebuilds a lab end to end.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant