u/formiat

▲ 23 r/ROS

[Project update] The drone now flies the building in total darkness on the light it carries, and decides by itself what to do when that light flickers, fails, runs out or goes (ROS 2 Jazzy, PX4 SITL, Gazebo Harmonic, CUDA MPPI)

(GIF: a deep flicker of the carried light 200 s into the flight. Left is Gazebo, the world as it is, lit only by the drone. Right is RViz: pink is the obstacle memory, grey is darkness the camera looked into and could not see, cyan the current stereo and time-of-flight returns, orange the route, white the drone's own position estimate, green the simulator's truth. LIGHT is what the test set the light to; SEES is the share of its sensing range the drone's own frame grants. Both fall and recover together.)

Previous update, three weeks ago: the stack flying the same building with no map, no GNSS, no magnetometer and no lidar, two cameras doing both the seeing and the localizing. I ended that post saying it had never been tested against bad seeing: the world was lit by a uniform fill and the cameras had no light to lose. This release is that test.

Everything is MIT, simulation-first, and none of it is validated for a real aircraft.

Release: https://github.com/formiat/px4-ros2-drone-nav/releases/tag/v0.5.0

Video of the whole flight, uncut, in real time: https://youtu.be/WlpePGmt19A

What changed in the world

The location's light is off for good. The vehicle carries a spot light (25 m range, quadratic falloff) in front of its stereo pair, and the evaluation drives that light through three profiles:

  • moderate, the norm of every flight now: short outages every ten to thirty seconds, dark never longer than two seconds, each ramping down and up over a second;
  • severe: outages growing from the twentieth second to 4.5 s of dark every 8 s, and staying that bad;
  • lost: the light gone from the forty-fifth second.

Separately, the light can carry a charge in seconds, and the camera stream can lose frames or deliver them late. The sensors are the same as in v0.4.0: a forward stereo pair (1280 x 960, 120 degrees, 0.20 m baseline, 7.5 Hz) and two 8 x 8 time-of-flight sensors looking up and down (2.8 m), which work in the dark; the position and heading come from a stereo MSCKF on those images.

What the vehicle does about it

All of it is decided onboard from what the camera sees, nothing reads the test's light setting.

The braking contract reads the light. The frame's light headroom and the share of it that matched anything scale the range the forward sensor is trusted for; below 0.35 the frame is blind and the vehicle holds where it stood until it sees again. In the moderate flicker that is a slowdown every few seconds and nothing more.

Darkness is not unknown. This is the one idea I would keep from the whole item. Unexplored space, which nobody has looked at, stays traversable at no penalty, otherwise the planner never leaves the start. But space the camera looked into for 2.5 s and could not see is marked as observed unobservable: occupied evidence without free space, a prohibition like a wall, cleared when the vehicle sees through it again. The memory tells the two apart and RViz draws the dark grey beside the pink of real surfaces. Without this the vehicle flew into the dark at 2 m/s over a goal it had seen free while the light still reached it.

The light is judged by the camera. One outage longer than 3.5 s, or dark for 60 % of the last 120 s, and the light is unreliable: the mission monitor replaces the goal with the start through the same channel any goal enters by, and the vehicle flies home along the trail it flew out on, through the same failing light. In the recorded flight it turns at 76 s and is home 0.9 m from the pad.

The battery is weighed against the way home, all flight long: the charge must cover 1.1 times the estimate of the way back, which is 1.5 times the path flown over the mean speed plus 20 s. With 240 s of light the recorded flight turns at 72 s with 140 s left against 128 estimated, and is home with about 80 s to spare. A flight launched without a battery never gives its goal up for one.

The light gone. Past 1 s unaided the estimator keeps publishing a sound pose for 10 s more, declared as dead reckoning: its height held to the barometer's change, its horizontal velocity bounded by the rotors' drag. Three seconds into that the offboard descends the vehicle level at 0.5 m/s in the autopilot's velocity mode, holding no position (a position held on a dead-reckoned estimate tipped the landed vehicle over), and lands. An estimator that sees again first takes the flight back. In the recorded flight the light goes at 51 s, the word on the overlay changes to DEAD RECKONING, then LEVEL DESCENT, and LANDED at 59 s; the white estimate and the green truth part in those seconds, which is the drift the landing cuts short.

The acceptance series

Five point-to-point flights on one commit through the dark under the moderate flicker, each inspected before the next, speeds on simulation time, positions in the simulator's truth:

  • 400 to 520 m of path at 1.58 to 1.77 m/s (r1205 to r1210)
  • the true position 0.79 to 1.79 m from the goal at its acknowledgement (the capture radius is 2.0 m)
  • no contact in any of them

That is slower than the 1.82 to 1.99 m/s the same flights made a day earlier. The difference is one law added on purpose, below.

Three findings that cost me flights

A floor touched in flight was a landing. The crash judge counted a level, slow contact of the body with a floor as a landing whenever it came. A flight sank onto the floor of the start area in flight, at 0.11 m/s, and the check passed it. It had kept 0.3 m above the floor by its own estimate; the estimate was 0.5 m high. Two changes: a floor contact is a landing only while the offboard's blind descent is going on, otherwise it is a crash; and the clearance law now measures the body's clearance where the estimate puts it and 0.5 m lower and higher, the least of the three, because the camera estimator's height runs 0.3 to 0.6 m from the truth over a flight with or without dead reckoning. That half metre is the price in the speeds above.

The ground's friction read as 21 m/s. A vehicle landed blind on a slope of 12 degrees stood level and whole, and four seconds later its estimate had run 100 m away and the autopilot had reset its position three times. The rotor drag model, which bounds the velocity in the dark by reading the accelerometer across the rotor axis, was reading the ground's friction holding the tilted body (2.3 m/s^2, constant for as long as it lay there) and solving it for a velocity. A reading beyond the drag of any flight is now left out. On flat floors the land detector fires and the autopilot disarms within seconds; on a slope it never fired, because the estimate it reads was still "descending".

The autopilot left offboard before my hold's landing. When the planner's heartbeat is lost the offboard holds position and lands after 5 s; the branch returned before publishing OffboardControlMode, so PX4 left offboard on its own loss timeout first. Never hit in a flight, found by reading the code against the real-vehicle safety list, fixed in a line.

Limitations, honestly

  • The blind landing lands where the drone has drifted to. Nothing on the vehicle sees sideways in the dark, and the memory is exact only relative to the drifting estimate. Twelve blind landings in the dark in this release: nine stood; one met a ledge with a rotor; two came down level on uneven ground and tipped four seconds later. Kept as a known risk, recorded in the debt register with the numbers.
  • The return on a goal proven unreachable (a topological proof over the memory, item 19) is implemented and unit-tested, but has never fired in a flight: every return flown was the light's or the battery's. Its flight test is item 18's smoke scenario, which is not built yet.
  • The darkness marking keeps the memory's rule of never forgetting by time: what the dark marked stays until seen through again.
  • The flights are recorded at a real-time factor of 0.6 for the screen capture (the simulator does not hold 1.0 beside three windows), and every frame of the video is re-timed to the simulation clock, so it plays in real time; the acceptance flights above ran at 1.0.
  • Everything is Gazebo: the light, the noise, the IMU. Nothing of this has seen a real sensor.

Questions

  • Is there a standard name for "observed unobservable" as a map state? I have seen it in smoke papers as a sensor-level flag, not as something the planner reads as occupancy.
  • How do people land a multirotor blind on dead reckoning without a downward-looking sensor that works in the dark? A short-range ToF is what I have; it sees the floor, not the slope.
u/formiat — 5 days ago
▲ 52 r/ROS

[Project update] The drone now flies the same building with no map, no GNSS, no magnetometer and no lidar: two cameras do both the seeing and the localizing (ROS 2 Jazzy, PX4 SITL, Gazebo Harmonic, CUDA MPPI)

(GIF: an industrial room, 58 m short of the goal. Left Gazebo, right RViz: pink is the obstacle memory, the cyan fan is the current stereo depth return, orange is the route, the magenta sphere is the goal. The drone crawls at 0.2 m/s, runs up to 2.5, and is back at 0.1 before the next corner: the speed law bounds it by what the cameras have actually resolved.)

Previous update, two weeks ago: the stack flying a 3D urban location with no map. Back then it had a 3D lidar and PX4's GNSS-based EKF, and I ended that post with three things I said were next: lidar-inertial localization without GNSS, a larger and more varied city, and eventually vision-only perception without lidar. Two of them are done. The third I dropped on purpose, and I will say why below.

Everything is MIT, simulation-first, and none of it is validated for a real aircraft.

Release: https://github.com/formiat/px4-ros2-drone-nav/releases/tag/v0.4.0

Video of the whole flight, cameras only: https://www.youtube.com/watch?v=OUuAj2WNKzs

What it does now

Same location (the DARPA SubT "Urban Circuit Practice 01" world by Open Robotics, CC BY 4.0), same mission: a start, a goal, no map. What the vehicle carries is different. The default airframe now has no lidar at all:

  • a forward stereo pair, 1280 x 960, 120 degrees, 0.20 m baseline, 7.5 Hz;
  • two 8 x 8 time-of-flight sensors looking up and down, 2.8 m.

Semi-global matching turns the pair into the same beam observations the lidar used to produce, so the obstacle memory, the D* Lite planner, the CUDA MPPI optimizer and the execution core run unchanged above the sensor boundary. Confident depth is 6.4 m forward.

And the same pair localizes the vehicle. A stereo MSCKF (multi-state constraint Kalman filter) written on Eigen alone, with a Lucas-Kanade feature tracker in front of it, feeds PX4's external-odometry interface in place of GNSS and the magnetometer. EKF2_GPS_CTRL 0, EKF2_MAG_TYPE 5, EKF2_EV_CTRL 11, EKF2_HGT_REF 3. Nothing the autopilot fuses comes from the simulator's truth.

So the vehicle flies a multi-storey building on two cameras and an IMU, with no prior map, no satellite and no compass.

The acceptance series

Ten flights on one commit, nothing changed between them, each inspected before the next. Mean speed is path over the time from mission readiness to the successful result, so every hold, stop and replan counts against it.

Cameras, no GNSS (the repository defaults):

Flight Path Duration Mean speed Truth at the goal Route availability
r579 465.6 m 295.6 s 1.58 m/s 1.42 m 98.3 %
r580 629.4 m 350.9 s 1.79 m/s 1.14 m 97.9 %
r581 442.2 m 276.6 s 1.60 m/s 1.37 m 98.3 %
r582 434.1 m 272.1 s 1.60 m/s 0.52 m 99.1 %
r583 410.0 m 276.5 s 1.48 m/s 0.47 m 99.1 %

3D lidar, no GNSS, same commit:

Flight Path Duration Mean speed Truth at the goal
r584 417.0 m 156.3 s 2.67 m/s 0.48 m
r585 387.1 m 154.5 s 2.51 m/s 0.55 m
r586 424.0 m 167.9 s 2.53 m/s 0.85 m
r587 424.6 m 175.2 s 2.42 m/s 0.45 m
r588 398.0 m 150.2 s 2.65 m/s 0.66 m

No crash and no contact in any of the ten.

"Truth at the goal" is the new column and the point of this release. The mission monitor judges arrival by the autopilot's estimate, and an odometry drifts by metres, so a vehicle can "arrive" in its own coordinates while standing somewhere else. The mission check now fails a flight unless the true position from Gazebo is inside the 2.0 m capture radius when the goal is acknowledged. The truth is read by the check and nowhere in the control loop.

The estimator's own drift over those flights: 0.13 to 1.24 m from the true pose at the end of 409 to 631 m of path, and at most 0.31 to 0.88 m over any 100 m. It costs 0.53 of a core, a frame takes 44 to 60 ms at the median, and the onboard processes together use 4.7 to 5.0 cores on cameras against 3.6 on the lidar.

Four findings that cost me flights

Each of these was a series, a failure, a root cause and a law, not a tuned constant.

1. PX4's timesync resets when the simulation runs slower than the wall clock. SITL is lockstep, so the autopilot's clock is the simulation clock, but uXRCE-DDS synchronises it against the agent's wall clock. At a real-time factor under 1 the two drift apart, PX4 resets the synchronisation every 15 s or so, and each reset emits one boot-relative stamp and steps the offset by 0.6 to 1.1 s. My stack reads that as an epoch reset and goes about a second without an authoritative state: 17 times in one camera flight. One of those seconds happened on a route turn and cost me a crash. Setting UXRCE_DDS_SYNCT 0 and mapping the autopilot's stamps with the identity removed it completely (17 reacquisitions per flight to 0). If you fly offboard in Gazebo below real time, this is worth checking.

2. EKF2_EV_NOISE for an odometry is not the estimator's accuracy, it is what one update moves the pose by. I first gave PX4 the estimator's own variances, then a fixed 0.1 m. In a fast turn the filter gated 13 of 19 features, its pose moved 0.4 m and back within two frames, PX4's five-sigma gate at 0.5 m threw the odometry away, the vehicle dead-reckoned 1.8 s of a 5 m/s dash and reset its position by 1.19 m. My controller closes the navigation for good on a reset beyond 0.33 m, so the mission was over. Consecutive frames move the pose by up to 0.22 m, so the noise is 0.3 m now and the gate sits at 1.5 m. Note the other half: an odometry's honest variance of its position in the world grows without bound (first-estimate Jacobians never let features shrink it), and an autopilot that has no other position would stop believing the only one it is given.

3. Telling an MSCKF the gyroscope is noisier than it is makes the heading walk. No camera observes the heading, so between frames it is the gyroscope's. I had the noise density at 1.0e-3 rad/s/sqrt(Hz), eighteen times the sensor's, and the noise of every visual update walked the unobservable direction: 2 to 4 degrees over a flight, which is 1.6 to 2.1 m at a goal 62 m from the start. Two flights were acknowledged 1.97 and 2.31 m from their goal in truth. Integrated alone that gyroscope loses 0.06 degrees over the same flight. With its own noise, a bias walk of 2.0e-6 and the initial bias known to its scatter at rest, the heading ends within a degree and the position 0.25 to 0.89 m from the truth on six recorded flights. Tightening the bias alone did nothing (2.50 m to 2.19 m).

4. The accelerometer's noise density is not the sensor's either. A tilt error of 0.2 degrees leaks 0.03 m/s^2 of gravity into the horizontal acceleration. With the sensor's 0.02 the filter believed the IMU over the pair, refused a quarter of the features and measured every displacement 1.6 percent short. At 0.2 it refuses 2 percent and the displacement ratio is 0.998.

There was also a defect older than this work, which a probe at 15 Hz exposed by losing the vehicle: a planning tick could last 2.5 s. Beside a wall the envelope touches, a scan's returns lie a centimetre apart, and every one inside the envelope was walked against the vehicle's departure chain at every body position of every segment: 487 ms for one path validation. Thinning the latest scan to the nearest return per 0.05 m cell and walking the departure once per sweep interval brought it to 15 ms and the longest tick to 226 ms.

Method, because it is most of the work

Everything above came from recording flights and replaying them offline, not from watching the vehicle fly. There is a recorder that writes every frame of both cameras, both IMU streams and the true pose on one clock, and an offline replay of the tracker and the filter over those recordings. Estimator parameters are set on recordings and only confirmed in flight, because flights are stochastic.

Two examples of what that buys. The frame stamp leads the IMU clock by 4 ms: I found it by sweeping the offset and watching the share of gated features, which has a clean minimum at +4.1 ms on one recording and +3.8 ms on another, and doubles 10 ms either side. And the estimator runs at the matcher's 7.5 Hz because every second frame (3.75 Hz) flies the same recording just as well, so rendering the pair at 15 or 30 Hz, which costs the simulation its real time, buys nothing.

The larger city I said was next, and did not build

The earlier post promised a larger and more varied location. I dropped it. The imported SubT worlds are already what that milestone asked for: a multi-storey building complex with shafts, corridors and openings at different altitudes, with one source of truth for what is rendered, what collides and what the planner's artifacts are built from. Building a generated city on top of that would have been a second environment to maintain and a worse one to validate against. The generated grid city that existed earlier was removed from the repository instead, along with the interception missions and the airborne radar that lived in it.

Limitations, honestly

  • Cameras are slower: 1.48 to 1.79 m/s against 2.42 to 2.67 on the lidar, because confident stereo depth is 6.4 m against the lidar's 35, and the speed the vehicle may fly is what it can stop from inside the range the sensor is guaranteed to have resolved.
  • The odometry drifts without bound: 0.1 to 0.4 percent of the path. The worst accepted flight stood 1.42 m from its goal in truth against a 2.0 m radius, so a mission several times longer will exceed it. The remedy is long-lived points in the filter's state or relocalization against a map, and it is a deep rework rather than a tuning pass.
  • One location, one start and goal pair, simulation only.
  • A faced motion whose elevation falls between the pair's vertical field and a time-of-flight cone (52.4 to 67.5 degrees) is seen by no sensor and is flown at 1 m/s.
  • No reference visual-inertial system has been run on my recordings yet. OpenVINS without ROS wants Ceres and Boost.Filesystem, which my container does not carry, and then a runner for my format; I stopped there.
  • The cooperative multi-vehicle missions have not been flown on cameras or without GNSS.

Questions

  • For those running a VIO in the loop with an autopilot: what do you give the filter as external-vision noise, and do you gate the odometry at all? The failure above says the gate has to be wide enough for the estimator's own jumps, which feels like it defeats the purpose of having one.
  • How do you decide a mission is short enough for an unbounded odometry? I now have a goal-in-truth check that fails the flight, but the honest answer is that it would start failing on a mission three times as long.
  • Anyone flying a stereo pair as both the perception and the localization sensor? Everything I read keeps them on separate sensors, and I deliberately kept them independent in code (the estimator never reads the map it helps build), but I would like to know what breaks first at scale.
u/formiat — 24 days ago
▲ 5 r/ROS

[Project] I Built a Multi-Drone Urban Interception Simulation with ROS 2, PX4, Gazebo, and CUDA MPPI

[Project] I Built a Multi-Drone Urban Interception Simulation with ROS 2, PX4, Gazebo, and CUDA MPPI

TL;DR

I am building a simulation-first autonomous drone navigation and interception stack for dense urban environments. It combines ROS 2 Jazzy, PX4 SITL, Gazebo Harmonic, a global lattice planner, and a local CUDA MPPI controller.

The same navigation system can fly with a known 3D map or rely on local lidar and accumulated obstacle memory. On top of it, I implemented radar-only target tracking, predictive interception, three-interceptor-versus-one-attacker and two-interceptor-versus-two-attacker missions, adaptive target assignment, and physically verified mission outcomes.

This is a research simulation and engineering testbed, not a production weapon system or software intended for real aircraft.

Videos

These are public demos from the project:

I also have separate recordings of a drone traversing a 3D passage and the earlier one-interceptor-versus-one-attacker mission.

In the interception videos, red represents an attacker and yellow represents an interceptor in the Gazebo tactical view. An interception is counted only when Gazebo physical positions show that an interceptor and attacker came within 5 meters.

What I Wanted to Explore

The project started as point-to-point navigation through a Manhattan-style city, but the larger goal became testing how several autonomous systems fit together:

  • global navigation through dense buildings;
  • fast local trajectory generation with vehicle dynamics;
  • operation with and without a preloaded map;
  • traversal of constrained three-dimensional openings;
  • pursuit of a moving target from limited measurements;
  • adaptive allocation of several interceptors to several targets;
  • reproducible mission adjudication based on simulated physics.

The city currently contains a 5 x 8 building grid and several physical air channels: straight, L-shaped, and T-junction structures. Gazebo uses the geometry for rendering and contact physics, while the static planner receives artifacts generated from the same canonical world definition.

Simulation Stack

The environment runs in Docker and uses:

  • ROS 2 Jazzy for component orchestration, messages, diagnostics, and TF;
  • PX4 SITL for every vehicle's flight stack;
  • Gazebo Harmonic for world physics, lidar, and authoritative vehicle poses;
  • CUDA for MPPI rollout simulation;
  • RViz for routes, local horizons, radar targets, obstacle memory, and planner diagnostics.

Every drone has its own PX4 instance, ROS namespace, planner state, obstacle memory, and offboard controller. The planners run concurrently, but resource allocation is bounded across the whole mission so that adding drones does not simply create an unlimited number of competing CPU workers.

Navigation Architecture

The navigation pipeline has two levels.

The global planner produces a route around city-scale topology. Static mode uses a 3D lattice plus generated constrained channel edges. A channel is not a scripted maneuver or mandatory waypoint: the planner compares ordinary routes with explicit start -> entry -> channel -> exit -> goal candidates and selects the better validated topology.

The local controller is CUDA MPPI. On every planning tick it perturbs a warm started control sequence, simulates thousands of dynamic trajectories, queries clearance and collision data, selects the best eligible risk class, and publishes a short timestamped execution horizon to the PX4 offboard node.

Raw occupied cells are the only hard environmental prohibition. Collision checks use the drone's swept oriented 3D footprint, including its horizontal radius and upper and lower body extents. ESDF distance bands affect risk and ranking, but they do not create artificial forbidden regions around buildings.

This distinction matters. Excessive hard inflation can make a valid detour or narrow physical opening mathematically unreachable. Here, physical geometry is hard; clearance preference is a cost.

Static and No-Static Navigation

In static mode, the planner loads canonical 3D occupancy and a fingerprinted, precomputed, chunked ESDF. Only chunks intersecting the active planning region are decoded into a dense local field. This gives the planner long lookahead, full building topology, higher speed limits, and explicit knowledge of the 3D channels.

In no-static mode, the drone starts without that world model. It projects lidar using timestamped full-6DoF acquisition poses, accumulates raw obstacle memory, and builds a local 2D ESDF around the vehicle. Unknown space remains traversable, but observed distance and braking constraints reduce speed. Persistent frontier search can accept temporary zero or negative progress toward the goal, which is necessary for leaving corners and taking real detours instead of repeatedly selecting the shortest dead end.

The no-static perception model is intentionally still 2D, so the generated 3D channel semantics are currently available only in static mode.

Radar-Only Interception

An interceptor does not receive the attacker's absolute ground-truth position, velocity, route, or destination. Gazebo truth is restricted to two simulation boundaries: the mission referee and radar simulators.

The interceptor-facing radar message contains only:

  • range;
  • azimuth;
  • elevation;
  • relative radial velocity.

Each interceptor has an independent variable-dt Cartesian tracker. The first detection provides relative position but not a complete velocity vector. Later measurements estimate the full target velocity, and the tracker coasts between scans.

When the target is occluded, radar cadence follows a deterministic correlated random walk between 0.1 and 3 seconds. If the planner confirms swept line of sight to the current target estimate, the radar immediately enters a 20 Hz tracking mode without a range limit.

Guidance solves a constant-velocity interception problem rather than chasing the attacker's current position. It compensates for measurement age, predicts a future meeting point, and continuously updates the objective. If the full lead point is hidden behind a building but the current target remains visible, the planner shortens the prediction horizon toward the current target instead of abandoning direct interception. If the target itself disappears, control atomically returns to a fresh global route for the current objective.

The attackers and interceptors currently use the same speed policy. A successful capture therefore depends on geometry, prediction, and assignment rather than a built-in speed advantage.

Multi-Drone Missions

The original point-to-point mission remains available. The interception work progressed through a 1v1 demonstration to two current predefined scenarios:

  • three interceptors versus one attacker;
  • two interceptors versus two attackers.

The implementation itself is generic over an N x M scenario specification. In the 2v2 mission, each radar scan contains a detection for every active attacker. A typed assignment coordinator estimates constant-velocity intercept costs and computes a deterministic minimum-cost allocation. It tries to cover distinct attackers when valid tracks permit it.

Assignments are not permanent. They can change when geometry changes or a vehicle disappears, but a material and sustained improvement is required before switching. This hysteresis prevents two interceptors from continuously swapping targets because of small cost fluctuations. Interceptors do not exchange routes or telemetry with one another, and radio-network impairments are not simulated.

When an attacker is captured, reaches its destination, or crashes, it is removed from future assignment immediately. Surviving interceptors are reassigned to the remaining active attackers.

Physical Outcomes and Test Semantics

Mission outcome and vehicle death are separate contracts.

If an interceptor and attacker come within 5 meters, the referee publishes typed destruction events for that pair. Both PX4 instances are disarmed, and the result is settled only after physical proximity and disarm confirmations are present.

If an attacker reaches its goal first, that outcome is latched immediately. The remaining interceptors stop pursuing it, brake, and enter confirmed position hold. Goal arrival does not disarm a vehicle. A later inertial approach cannot rewrite which terminal event happened first.

A collision with a building is never treated as an acceptable mission outcome: it makes a headless validation run technically fail, regardless of whether the vehicle was an attacker or interceptor. Inter-drone collision avoidance is not implemented yet, so interceptor-to-interceptor collisions are currently treated as collateral damage and the mission continues while another interceptor is alive.

Performance and Parallelism

The local MPPI rollouts and reductions run on CUDA, while independent global topology searches use a bounded CPU worker pool. Multi-vehicle planners share a ROS component process and one CUDA primary context, but retain independent CUDA streams, buffers, routes, and warm starts. Static ESDF chunks use shared decoded storage, and no-static mapping transports revisioned dirty chunks instead of copying the complete map for every update.

The maximum MPPI budget is 8,192 rollouts. Open static flight can use 6,144, and confirmed direct interception can use 4,096; uncertain navigation and obstacle handling retain the full budget.

On my 16-logical-CPU workstation with an RTX 3060, recent four-vehicle no-static runs had roughly 7-8 ms p95 planning ticks and normally stayed inside the 20 ms target. Static mode improved substantially after precomputed ESDF and cache work, but its p95 remained around 60 ms in representative runs. These are workload- and-machine-specific measurements, not general benchmark claims.

One useful negative result was a fused vehicle-by-rollout CUDA backend. It sounded ideal, but the live average batch contained only about 1.5 vehicles. Waiting to form batches increased latency: static GPU p50 went from 22.8 to 45.9 ms, and no-static from 3.45 to 6.07 ms. Independent streams were faster and more reliable for this asynchronous workload. More parallelism is not automatically better when synchronization and memory bandwidth dominate.

Architectural Lessons

Several rules emerged from debugging the system:

  1. Simulation ground truth must have an explicit boundary. Guidance should not accidentally gain access to data that the simulated sensor does not provide.
  2. Route activation must be atomic and revisioned. A stale route for an older moving target is worse than a brief controlled fallback.
  3. Physical collision, risk preference, vehicle death, and mission outcome are different concepts and should use different typed contracts.
  4. Persistent search state is more valuable than repeatedly restarting a search whenever a sensor snapshot changes.
  5. Diagnostics must not sit on the control-critical path. Full records are rate-limited and buffered, while the execution horizon is published first.
  6. Parallelization should be accepted only when live timing and mission results improve, not because a design looks more parallel on paper.

Current Limitations

  • Radar values are ideal: there is no measurement noise, false detection, interference, or transport latency. Only scan cadence varies.
  • The tracker and interception prediction currently assume approximately constant target velocity between updates.
  • Target assignment is centralized, and communication failures are not modeled.
  • There is no cooperative inter-drone collision avoidance.
  • No-static perception is 2D and cannot yet detect and traverse the 3D channels.
  • Static multi-drone MPPI still misses the desired 50 Hz deadline under heavy load on my current hardware.
  • Attackers are not dynamically respawned; every mission is a finite episode.
  • The project is simulation-first and has not been validated on physical drones.

Roadmap

The next directions I am considering are:

  • radar noise, latency, missed detections, and a stronger multi-target tracker;
  • repeated mission episodes with dynamically spawned attackers;
  • cooperative civilian drone traffic with negotiated collision avoidance;
  • more complex 3D passages with changing altitude profiles;
  • 3D lidar passage detection and traversal without a static map;
  • moving selected components toward hardware-in-the-loop and eventually carefully controlled real-world experiments.

Repository and Running It

Repository: github.com/formiat/px4-ros2-drone-nav

The supported workflow uses Docker. A CUDA-capable NVIDIA GPU is expected for the production MPPI path. The main commands are:

./scripts/build.sh
./scripts/test.sh
./scripts/sim_gui.sh
./scripts/sim_intercept_gui.sh
./scripts/sim_multi_intercept_gui.sh

Set ENABLE_STATIC_MAP=false for the lidar-memory mode. Equivalent headless scripts are available for regression runs, and the repository includes more detailed architecture, performance, diagnostics, and world-generation docs.

Questions for the Community

  • Which metrics would you use to evaluate interception quality beyond binary success: time-to-capture, distance from the attacker's goal, energy, route efficiency, or something else?
  • Which radar imperfections would produce the most useful next step without turning the project into a full sensor-physics simulator?
  • Which multi-target assignment methods would be most interesting to compare against deterministic minimum-cost allocation?
  • What urban scenarios would expose the most meaningful weaknesses in the current planner and tracker?
u/formiat — 2 months ago