mapping pipeline: map-build orchestrator - #113
Open
MJohnson459 wants to merge 1 commit into
Open
Conversation
Stage 2 of the mapping pipeline as one command. `pixi run map-build --bag <bag>` solves the bag in lockstep under the committed build params, assembles the grid and posegraph into the layout `save-map` writes, declutters, segments rooms, validates, scores against a baseline revision and packs the registry's bundle, with a build report beside it. No robot, no live SLAM session. On the 2026-08-02 flat bag it reproduces the hand-built map's loop drift — 0.0992 m against 0.098 — in 21 s of solve for a 21-minute bag, unattended. `docs/tuning/2026-09-01-map-build.md` and the banked artifacts beside it. Four things are load-bearing. The declutter step is `sites.promote_cleaned` itself rather than a copy, so a built map and a saved one are the same pass over the same pixels (which is why those two helpers stopped being private). A solve that serialized no posegraph fails the build: a revision without one navigates and cannot be extended, and the frame — with every zone taught in it — is gone. `bundle.validate` errors stop the build; a metric regression rides on the candidate as review evidence. And both sides of that diff are read back from a revision's own served `map.png` at the thresholds its own `map.yaml` declares — the replay leg's metrics describe the *raw* solve, so scoring the candidate from the leg and the baseline from disk compared two different artifacts and reported the candidate's speckle as five times the baseline's when the served maps agree to a thousandth. `unknown_frac` left the diff table for the same class of reason (a 4-px-wider canvas read 2.2% worse on it while covering 0.4 m² more floor); `angular_support_deg` was already out, being confounded by coverage. Two smaller decisions. The map pair declares `free_thresh` 0.100, not `map_saver`'s 0.196 — which is the unknown shade's own value to five decimals, so it decides "unknown" against "free space the planner may drive through" on a rounding. And the replay harness now records the grid's origin yaw: it has always been zero, but a build assembling a `map.yaml` must not assume it, since a dropped origin yaw moves every zone on the floor and leaves the map looking perfectly good. Three of the design's steps are deliberately absent, each reported on the candidate rather than left silent. Alignment needs an estimator that can say which of two solves is squarer; the one in the tree called four of the seven banked 2026-08-02 solves square when they were 3.5-5.6 deg off, and a re-solve is not a rigid rotation, so the step is undecidable rather than merely ungated (task 615). Birth-alignment stays an operator's `--frame X Y YAW`, recorded in the revision's meta. Vocabulary carry-forward is task 345. Upload needs a build identity (task 344), so the bundle is emitted locally.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stage 2 of the mapping pipeline (
docs/design/mapping-pipeline.md, work item 4)as one command.
pixi run map-build -- --bag <bag>takes a recorded mapping bagand emits a validated candidate map revision, the gzipped bundle the registry
accepts, and a build report — with no robot, no live SLAM session and no map
needing to exist first.
What it does
mote_simulation/tools/map_build/(runbook in its README), besidebag_replayfor that harness's reason: workstation-only, excluded from the robot sync. The
chain is solve → assemble → declutter → segment → validate + score → package.
bag_replay.replay.run_onein-process rather thanre-implementing it, so the DDS isolation, stack launch, lockstep
acceptance-chain feed and teardown stay in one place.
save-mapwrites,since there is exactly one shape of revision and the registry knows it.
sites.promote_cleaneditself, not a copy — which is whythat helper and
clean_map_pngstopped being private.merge_into_zonesgainedsite/floor/platform_idkwargs so a build can stamp a revision for afloor it was told about on the command line.
bundle.validateerrors mean nothing is emitted, asdoes a solve that serialized no posegraph (such a revision navigates and
cannot be extended — the frame, and every zone taught in it, is gone).
--baseline, printed asreview evidence.
Verification
Run against the bag the acceptance names,
20260802_142539, with the promotedhome/ground/20260802T203339as baseline. Report, maps andbuild.jsonarebanked in
docs/tuning/2026-09-01-map-build/; the write-up isdocs/tuning/2026-09-01-map-build.md.millimetre, and it is the one metric here that measures the solve.
directions each, and the flat's angled wing reproduced as a second orthogonal
frame 18.5° off the dominant one carrying 0.219 of the energy — the figure
the 2026-08-25 report found.
declutter thresholds rather than the solve: the hand build stripped more
short structure at
peak_rel_threshold0.55 than task 337's prominence-pickeddefault does. Reported, not gated.
--bagto candidate.mote_bringup+mote_taskssuites 468 passed / 12skipped;
bag_replay32 passed; pre-commit clean.Two defects the acceptance run found in this tool
Both would have told a reviewer to reject a good map.
The candidate was scored from the raw solve, the baseline from disk. The
replay leg carries map metrics, but they describe the image before the declutter
pass, while a stored revision only ever keeps the cleaned one. The first run
reported the candidate's speckle as five times the baseline's when the two
served maps agree to a thousandth. Both sides now read back a revision's own
map.pngat the thresholds its ownmap.yamldeclares.unknown_fracmoves with the bounding box. A candidate 4 px wider than thebaseline read 2.2% worse on it while covering 0.4 m² more floor. It left the
diff table;
explored_area_m2carries the same signal in metres.angular_support_degwas already out, confounded by coverage. The rule both areinstances of: a diff row is a claim that the metric ranks candidates.
Two smaller decisions
The map pair declares
free_thresh0.100 rather thanmap_saver's 0.196,which is the unknown shade's own occupancy value to five decimals and so decides
"unknown" against "free space the planner may drive through" on a rounding
(filed as #621 for the revisions already saved that way). And
replayer.pynowrecords the grid's origin yaw — always zero in practice, recorded because a
build assembling a
map.yamlmust not assume it: a dropped origin yaw movesevery zone on the floor and leaves the map looking perfectly good.
Three of the design's steps are deliberately absent
Each is reported on the candidate rather than left silent.
Alignment. The step measures the wall rotation, re-solves with it injected
and keeps the better map — which needs an estimator that can say which is
better. The one in the tree called four of the seven banked 2026-08-02 solves
square when they were 3.5–5.6° off, and a re-solve is not a rigid rotation (the
same −3.0° injection moved three solves of one bag by +0.1°, −4.3° and −5.8°),
so the step is undecidable rather than merely ungated. That is task 615, and
the design (#111, landed after this task was written) already says the step is
blocked on it. Birth-alignment stays an operator's
--frame X Y YAW, recordedin the revision's meta, which is how the 2026-08-02 map is reproduced above.
The acceptance's "walls ≤ 0.5° off axis" therefore cannot be evaluated and
was not:
docs/tuning/2026-09-01-alignment-residual.mdestablished that thismap has no single wall grid to half a degree. The build measures wall structure
and prints it. Everything else in the acceptance is met.
Vocabulary carry-forward is task 345: the build emits the segmenter's
room_NNplaceholders and reports what the baseline floor's places were called.Upload needs a build identity (task 344) — the registry accepts candidate
uploads only from enrolled robots — so the bundle is emitted locally and the
report says what will send it.
Every revision's
meta.yamlnames its exact inputs: bag sha256 over bytes andfile names, params sha256, frame, feed, harness commit.
Also confirmed in passing, and not this tool's: the nine room proposals are
visibly tilted ~2° off their walls (task 349, on the building it was reported
against), and the Pi's
home/groundfloor has the straymaps/mapdirectory oftask 300.
Branch
mapping-map-build, commitc310d81. Not pushed.