Add waypoint_flight system test judged by a standalone track checker - #378
Conversation
New end-to-end acceptance test for planner integration/swaps: takeoff -> ordered waypoint route -> land, per (sim, num_robots, iter). - tests/system/test_waypoint_flight.py (mark: waypoint_flight): after takeoff, sends the route to the local planner's NavigateTask action as a nav_msgs/Path and captures odometry throughout; reuses the flight-cycle workers from test_fixed_trajectory.py (chain guard, takeoff/land, odom CSV capture). - tests/waypoint_checker.py: standalone stdlib-only judge — the odometry track must pass within --waypoint-tolerance of every waypoint IN ORDER, each within --waypoint-timeout of the previous arrival. Success is defined purely on the odometry track (not the action result), so swapping the global or local planner leaves the judgment unchanged; the checker also runs outside the harness on any ros2 `topic echo --csv` odometry dump. - Waypoints are relative to the robot pose at dispatch (x forward along heading, z up), so routes are spawn/sim agnostic. Default: 10 m square at takeoff altitude. - New pytest options: --waypoints, --waypoint-tolerance, --waypoint-timeout; mark registered in pytest.ini; docs in tests/README.md and AGENTS.md; VERSION 0.19.0-alpha.9 + CHANGELOG. Metrics recorded per robot: waypoint_success, waypoints_reached, navigate_action_success, route_time_sim_s, worst_closest_approach_m. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Validated end-to-end against Isaac Sim + the stock stack (4/4 phases pass in 3m20s; corners cut 3.75/5.13 m, final goal error 0.63 m). Fixes found by flying: - Path header frame: an empty frame_id crashed droan_gl (uncaught tf2::InvalidArgumentException in its plan TF transform); the goal now carries the frame from the odometry snapshot (fallback "map"). - Dense plan dispatch: sparse poses get corner-skipped by the local planner's distance-walking look-ahead; the route is now interpolated at 1 m from the current pose (mirrors real global-planner output). - Route/tolerance semantics: the stack's contract is "reach the goal precisely, follow the corridor loosely" (droan_gl cost = deviation - path_distance cuts corners ~4-7 m). Split tolerances: intermediate corridor 15 m, final goal 2.5 m (new --goal-tolerance; NavigateTask's 1.5 m + tracking lag). Default route is now an open 30 m square — NavigateTask succeeds on distance to the FINAL pose, so closed loops succeed instantly without flying (documented). - Settle capture: the action succeeds on the tracking point, which leads the drone by up to the look-ahead distance (~10 m); capture now continues until the drone is stationary (max 30 s) so the goal approach is recorded. New metric: final_goal_error_m. - waypoint_checker: closest_approach now reports the true minimum over the remaining track instead of the tolerance-boundary crossing (arrival stays first-crossing, ordering semantics unchanged). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Validation: full flight in Isaac Sim (stock stack) ✅Ran
Getting to a pass took 5 instrumented flights; each failure was informative and drove a calibration fix (all in
🤖 Generated with Claude Code |
Validated on both sim backends with the identical default config (open 30 m square climbing to ~20 m AGL): - Isaac Sim: corners 5.67/5.72 m, final goal 0.28 m, 4/4 phases - ms-airsim (Blocks): corners 5.93/5.67 m, final goal 0.89 m, 4/4 At the old takeoff-altitude route the drone collided with a Blocks obstacle (disparity was streaming, so DROAN had perception — the corner-cut diagonals leave the forward stereo's coverage). This test judges route-following, not obstacle avoidance, so the default route flies above the clutter; documented in the option help and README. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Validation part 2: ms-airsim (Blocks) + Isaac re-confirmation ✅Both backends now pass 4/4 phases on the identical default configuration (
The cross-backend agreement of the corner-cut depth (~5.7 m both sims) is a nice incidental datapoint for the identical-autonomy-code claim — same planner behavior, different physics/render backends. Findings from ms-airsim validation
🤖 Generated with Claude Code |
Captured mid-route during the validated flights: Isaac Sim viewport with the drone on the square route, ms-airsim Blocks with the drone clearing the obstacle field (collision count 0), and the Foxglove GCS dashboard showing the planned path, expanded obstacle voxels, robot task panel, and live stereo feed. Embedded in the waypoint section of tests/README.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Screenshots from the validation flights 📸Captured mid-route during the passing validation runs (now also embedded in Isaac Sim — drone on the waypoint route: ms-airsim (Blocks) — clearing the obstacle field at ~20 m AGL (collision count 0, top-left): Foxglove GCS dashboard during the route — planned path + expanded obstacle voxels in the 3D panel, Robot Tasks panel, live stereo feed: 🤖 Generated with Claude Code |



Summary
Adds an end-to-end ordered-waypoint navigation test to the system test harness — the standard acceptance check for "I integrated/swapped a planner, does the drone actually fly the route?"
tests/system/test_waypoint_flight.py(mark:waypoint_flight): 4-phase chain per (sim, num_robots, iteration) — PX4 ready → takeoff → send route to the local planner'sNavigateTaskaction (/robot_N/tasks/navigate) as anav_msgs/Path→ land. Reuses the flight-cycle workers fromtest_fixed_trajectory.py(chain guard, takeoff/land, odom CSV capture); landing always runs after a successful takeoff.tests/waypoint_checker.py: standalone, stdlib-only judge. Pass iff the odometry track passes within--waypoint-tolerance(default 1.5 m) of every waypoint in order, each within--waypoint-timeout(default 120 s, odometry clock) of the previous arrival. Prints a JSON verdict (per-waypoint reached / closest approach / elapsed), exit 0/1.Design notes
navigate_action_success) but not asserted.ros2 topic echo --csvodometry dump), so it can judge waypoint flight on non-AirStack ROS 2 systems as well — this also makes it reusable as the automated judge for the ICRA'27 paper's agent-evaluation study (Sec. VI-C), which is the immediate motivation for landing it indevelop.Recorded metrics (per robot)
waypoint_success,waypoints_reached,navigate_action_success,route_time_sim_s,worst_closest_approach_m,ready_duration_sys_sNew pytest options
--waypoints10,0,0; 10,10,0; 0,10,0; 0,0,0--waypoint-tolerance1.5--waypoint-timeout120Docs added to
tests/README.mdand the AGENTS.md test table; mark registered inpytest.ini; VERSION bumped to0.19.0-alpha.9with a CHANGELOG entry.Test plan
pytest --collect-onlycollects 16 tests (4 phases × 4 default env params) with all cross-module imports resolving🤖 Generated with Claude Code