Skip to content

feat(route): multi-waypoint route on the map - #4979

Closed
dmrpereira wants to merge 5 commits into
dimensionalOS:mainfrom
dmrpereira:david/feat/multi-waypoint-route
Closed

dmrpereira wants to merge 5 commits into
dimensionalOS:mainfrom
dmrpereira:david/feat/multi-waypoint-route

Conversation

@dmrpereira

Copy link
Copy Markdown

Summary

Plan a route of up to 20 waypoints by clicking the map in dimos-viewer, then drive it from a companion Route window. The robot visits the waypoints in order, passes through intermediate ones without stopping, and stops at the last. New modules and one new blueprint only; everything is additive (constitution VIII).

  • RouteFollower (dimos/navigation/route_follower/) interposes on the viewer's map clicks, owns the route, feeds the existing planner one goal at a time on goal_request, and detects completion, failure, operator stop and outside interruption (stop_movement / goal_request from teleop, agents, patrol).
  • RouteControl is a pygame Route window (add-waypoints switch, Start/Stop/Clear, per-row remove, status/target/message lines) speaking two JSON string streams, with a 1 Hz keepalive and a heartbeat that holds the route when the window goes silent.
  • unitree-go2-route composes the Go2 smart stack with both modules, remaps the shared streams (contract: specs/001-multi-waypoint-route/contracts/route-streams.md) and merges the route overlay (numbered coloured waypoints + route line) into the Go2 rerun config.
  • pygame added to the unitree extra; docs at docs/capabilities/navigation/route.md.

Safety (constitution III)

Edits never move the robot; only Start does. Stop reuses the shared stop_movement stream and always works. Any outside goal or stop ends the route as interrupted. Window loss holds the robot after the current leg. Idle zero-twist teleop bursts are filtered before interrupting.

Test plan

  • uv run pytest dimos/navigation/route_follower dimos/robot/unitree/go2/blueprints/smart: 106 passed (state machine, message documents, overlay, module behaviour incl. watchdog/interruption/failure, window behaviour headless, blueprint wiring incl. remappings and overlay merge).
  • uv run pytest dimos/core dimos/navigation dimos/robot: 1208 passed (2 pre-existing environmental failures in test_git_cache.py on this machine, unrelated).
  • uv run mypy dimos/: only 2 pre-existing missing-stub errors in untouched files.
  • Worker deployment smoke in simulation: all 10 modules deploy, including the pickled overlay config.

Pending manual validation (fill in before merge)

  • Quickstart S1 to S12 and replay steps (specs/001-multi-waypoint-route/quickstart.md) in simulation and replay.
  • SC-002 measured minimum speed between waypoints: TBD
  • SC-004 measured Stop-to-stationary time: TBD
  • Hardware stages A to F on Go2 PRO with a person at the manual emergency stop (specs/001-multi-waypoint-route/quickstart.md §4): TBD
  • Confirm the 3 s window-silence held rule feels right on the robot (rule itself is operator-confirmed).

Notes

  • The Route window forces the X11 SDL video driver (XWayland needed on Wayland-only desktops).
  • Registry regenerated via pytest dimos/robot/test_all_blueprints_generation.py.
  • LFS upload was skipped on this push (GIT_LFS_SKIP_PUSH=1); the branch adds no LFS-tracked files.

Route and Waypoint state machine (data-model rules), the route_cmd and
route_state documents, and the Rerun overlay converters for waypoints and
the route line.
RouteFollower interposes on map clicks, feeds the planner one goal at a
time with pass-through waypoints, and detects completion, failure, stop
and outside interruption. RouteControl is the pygame Route window with a
heartbeat keepalive.
Composes the Go2 smart stack with RouteFollower and RouteControl, remaps
the shared streams, and merges the route overlays into the Go2 rerun
config. Adds pygame to the unitree extra and the registry entries.
Spec, plan, research, data model, stream contract, quickstart and task
list for the multi-waypoint route feature.
@github-actions github-actions Bot added the first-time-contributor PR opened by an author who had not previously committed to this repository label Nov 3, 2026
@greptile-apps

greptile-apps Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 2/5

[Medium impact] The PR adds an opt-in route workflow for Go2 navigation.

Do not merge until the waypoint handoff, held-route completion, and idle-input cancellation defects are corrected. The stale window notice is non-blocking.

Findings

  1. P1 Waypoint handoffs send stop commands ▶
  2. P1 Held routes skip final legs ▶
  3. P1 Idle stops still cancel navigation ▶
  4. P2 Close notices hide later failures ▶

Summary

Adds map-based routes with up to 20 waypoints, a companion pygame Route window, map overlays, and the unitree-go2-route blueprint.

Three navigation problems need correction before merging: intermediate handoffs emit stop commands, held routes can falsely complete an unvisited final leg, and idle zero input can still cancel and fail a route. Explicit viewer Stop can also be misreported as a waypoint failure. A separate, non-blocking window issue leaves a stale close notice over later failure explanations.

dmrpereira explicitly marked simulation measurements, replay checks, and staged hardware validation as pending. Those acknowledged items are not separate findings.

  • Go2 operators can plan and run a route through up to 20 map points.
  • A companion Route window lets operators control and track the route.
  • The new Go2 route blueprint shows route progress on the map.

Reviews (1) · Last reviewed commit: "specs: multi-waypoint route feature" · Reviewed by Greptile

Comment on lines +276 to +278
self._route.mark_current_reached()
if status is RouteStatus.RUNNING:
self._issue_current_goal_locked()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Waypoint handoffs send stop commands

Each intermediate waypoint publishes a fresh goal through the planner's cancel-and-restart interface. The actual planner emits two synchronous zero Twist commands at each handoff: one while cancelling the old path and another while starting its replacement. This sends stop commands between waypoints instead of preserving continuous pass-through motion.

This needs correction before merging. Use a motion-preserving path handoff for intermediate waypoints and cover it with a connected planner regression test. Physical stopping distance and speed were not measured.

Artifacts

Executed planner handoff harness

  • The authored Python harness exercises real route and planner implementations with synthetic inputs and captures velocity messages and stacks, making the candidate reproducible without hardware.

Commands for the control and handoff runs

  • The executed shell script runs both cases and records commands, working directories, exit codes, and source ranges, providing the exact reproduction procedure.

Planner output without intermediate goals

  • The single-final-goal control received the same odometry as the route case and recorded no zero Twists in either observation window, establishing the comparison baseline.

Planner output during intermediate waypoint handoffs

  • The three-waypoint route recorded replacement goals and two synchronous zero Twists at each intermediate handoff, confirming the candidate at the command boundary.

Relevant source ranges and unchanged production check

  • Executed source-extraction commands captured waypoint advancement, planner cancellation, zero-Twist publication, blueprint wiring, and the pass-through requirement, linking the runtime evidence to unchanged production code.

Initial control attempt with grid-aligned odometry

  • The initial control fixture entered final rotation rather than producing forward motion and exited 1, so it was not used as the final comparison.

Initial route attempt with grid-aligned odometry

  • The initial route fixture also failed its forward-motion prerequisite and exited 1, documenting why the fixture was adjusted.

Threaded handoff attempt requiring motion recovery

  • The intermediate handoff emitted the candidate's zero Twists but encountered a local-planner lifecycle error before recovering motion, motivating the final command-boundary assertions.

View artifacts

TREX Ran code and verified through TREX

Comment on lines +154 to +157
if self._route.status in (RouteStatus.RUNNING, RouteStatus.HELD):
index = self._route.current_index
if index is not None and index == len(self._route.waypoints):
self._route.complete()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Held routes skip final legs

A held two-waypoint route can report completion without visiting waypoint 2. While the robot finishes waypoint 1, pass-radius processing advances current_index to 2 without publishing its goal. The planner's subsequent arrival result for waypoint 1 then satisfies this final-index check and completes the route. Start refuses to resume the unvisited leg because the route is already completed.

This needs correction before merging. Track the goal actually issued to the planner separately from the next waypoint, and apply arrival results only to that issued goal.

Artifacts

Targeted held-route reproduction source

  • Executed this assertion-based script against real RouteFollower and Route code in two conditions, isolating the first-goal arrival callback as the trigger.

Commands and output-capture source

  • Executed this runner to capture both reproduction commands and the production diff check with working directories and exit codes, making the evidence reproducible.

Held route resumes without the first-goal arrival callback

  • Ran the control scenario without the arrival callback and then sent Start, showing waypoint 2 is published and remains resumable.

First-goal arrival falsely completes the held route

  • Ran the same scenario with the first goal's True arrival callback and then sent Start, showing waypoint 2 falsely marked reached and resume refused.

Production files remain unchanged

  • Executed git diff --exit-code for the RouteFollower and Route source files, confirming no production edits were made.

View artifacts

TREX Ran code and verified through TREX

Comment on lines +174 to +175
if self._idle_twist_recent_locked(now):
return

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Idle stops still cancel navigation

Ignoring stop_seen after a recent zero twist does not prevent that input from cancelling navigation. MovementManager still cancels the planner, which emits goal_reached(False). When the follower has observed the zero twist before the stop notification, this filter leaves the route running until the cancellation result marks its waypoint failed. Idle zero traffic can therefore abort a route despite the intended filtering.

The viewer's explicit Stop publishes the same zero twist and can also be reported as a waypoint failure rather than an interruption. This needs correction before merging: filter idle input before planner cancellation, preserve explicit Stop intent, and correlate cancellation results so either delivery order is handled correctly.

Artifacts

Connected reproduction script

  • Executed actual navigation components over a controllable queued transport to expose the zero-stop cancellation behavior.

Executable validation commands

  • Runs both connected comparisons and narrow existing tests while capturing commands, exit codes, and observed output.

Connected results with the zero-stop filter disabled

  • Ran ten connected cases with only the early return removed in memory, showing interruption for stop-first delivery and failure for cancellation-result-first delivery.

Connected results with the current zero-stop filter

  • Ran the same ten cases against unchanged current code, showing that both an observed zero Twist and explicit viewer Stop can mark the waypoint failed.

Narrow existing test results

  • Executed nine relevant route, movement-manager, and viewer Stop tests, all of which passed despite the connected defect.

Production source and revision capture

  • Captured numbered source, hashes, revision, and tracked diff status to identify the tested implementation and verify no tracked production changes.

Comparison results and script hashes

  • Extracted each case's observed route state, waypoint state, planner goal state, and cancellation result from the executed captures.

Initial harness scheduling error

  • Preserves the initial failed run where the control handler's changed callback name prevented the intended delivery ordering; the harness was corrected before both successful comparison runs.

View artifacts

TREX Ran code and verified through TREX

Comment on lines +174 to +176
if stop_enabled(self._state_doc):
self._notice = NOTICE_STOP_ROUTE_FIRST
return

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Close notices hide later failures

A refused close leaves _notice set, and subsequent route-state updates never clear it. If the waypoint later fails, the message line keeps displaying “stop the route first” instead of the waypoint-failure explanation because message_text() always prefers the notice. The status and waypoint row still show failure, so this is a non-blocking feedback issue, but the stale instruction hides the reason.

Clear the close notice when the route leaves the active state or a new route message makes it obsolete.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Artifacts

▶ Waypoint failure without a close attempt

  • Recorded the live pygame surface in Chromium while the route transitioned from running to failed without a close attempt—the waypoint-failure explanation remains visible.

▶ Waypoint failure after a refused close

  • Recorded the live pygame surface through running, refused close, and failure—the obsolete stop instruction replaces the failure explanation.

Native pygame screenshot without a close attempt

  • Saved the actual pygame display surface after the baseline failure—the message line renders the waypoint-failure explanation.

Native pygame screenshot after refused close and failure

  • Saved the actual pygame display surface after the refused-close sequence—the failed state retains the stale stop instruction.

Executable pygame reproduction

  • Executed this harness against the unchanged RouteControl implementation to deliver state updates, post the close event, assert rendered messages, and save native screenshots—the candidate is reproducible.

Executed Chromium capture script

  • Executed this Playwright script to record both live pygame sequences and check their observed messages—the comparison uses real rendered pixels rather than a reconstructed UI.

Executed reproduction commands

  • Ran this shell script under Xvfb to start the pygame harness and Chromium captures—both processes completed successfully.

Execution output for both rendered sequences

  • Captured the command, working directory, exit code, incoming states, and actual drawn lines—the failure explanation is present in state but suppressed after refused close.

Unchanged-source and video verification output

  • Ran git diff, git status, ffprobe, and artifact listing commands—the tracked source is unchanged and both recordings have valid durations.

View artifacts

TREX Ran code and verified through TREX

@dmrpereira

Copy link
Copy Markdown
Author

Closing per request.

@dmrpereira dmrpereira closed this Nov 3, 2026
@dmrpereira
dmrpereira deleted the david/feat/multi-waypoint-route branch November 3, 2026 07:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

first-time-contributor PR opened by an author who had not previously committed to this repository

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant