Platform Note: MAVLink Only

[Relay video — coming soon]
Filmed on our workshop 3× X500 V2 fleet. Overwatch software runs identically on operational Freefly Astro Max, Sky-Hopper, and Quantum Systems deployments.

Fleet relay in Overwatch is a MAVLink-only capability. The mechanics described in this post — pre-launch dispatch, waypoint-resume handoff, closed-loop Fleet Advisor tuning, runtime collision avoidance — all require a live ground-to-drone command channel. AirSDK drones are sealed after takeoff: once the mission archive is uploaded and the drone is armed, there is no further dialogue until it lands. That is fine for the single-drone SAR track, but it cannot support relay.

Fleet patrol mode therefore runs on PX4/MAVLink airframes. The reference rig in our workshop is three Holybro X500 V2 kits on the bench. Operational deployments run the same ground station against larger industrial platforms — Freefly Astro Max, Sky-Hopper, Quantum Systems Trinity Pro — with longer endurance, hot-swap batteries, and IP-rated enclosures. The X500 fleet is our test harness, not the product.

The Relay Problem

The naive way to run a multi-drone patrol is: drone A flies until its battery is low, lands, and drone B takes off. The coverage gap is whatever it takes to ground A, swap or charge, launch B, fly out to the patrol start, and begin the route. Measure that honestly on a 100 m AOI with a 1.5 km perimeter: 90 seconds for approach and land, 30 seconds for the battery swap to be registered, 60 seconds for pre-flight and takeoff, plus three to eight minutes of transit depending on how far the first waypoint is from the base. Coverage gaps of five to ten minutes per handoff are typical. For a 24/7 patrol running eight handoffs per day per drone, that is 40–80 minutes of unwatched airspace every day per sector.

That is the gap Overwatch’s relay engine is designed to close. In practice, coverage gaps on our bench tests run between 0 and 8 seconds — measured as the interval during which no drone in the sector is actively patrolling the route. The mechanism has three parts: pre-launch dispatch, waypoint-resume handoff, and battery-aware scheduling.

Pre-Launch Dispatch and Waypoint Resume

The core mechanic is that the standby drone launches before the active drone lands. When the active drone’s battery crosses the swap threshold (default 20%, configurable 15–30%), the orchestrator dispatches the standby. The active keeps patrolling until the standby physically arrives at the handoff waypoint on the route. Only then does the active peel off and transit to base.

The handoff is a waypoint-resume. The route is a sequence of N waypoints indexed 0..N-1. Each active drone carries a monotonically-increasing waypointIdx that is updated every orchestrator tick (200 ms) based on its current position along the route. When the standby receives the handoff command, it is given the incoming drone’s waypointIdx, flies direct-to that index, and resumes patrol from there. The active drone’s final job is to finish its current inter-waypoint leg; it does not turn for home until the standby has confirmed arrival at the handoff waypoint.

The critical rule: waypoint index is monotonic per relay cycle. A handed-off drone never restarts from waypoint 0. A 1,500 m perimeter patrol is a 1,500 m perimeter patrol regardless of how many handoffs occur during a 24-hour patrol window.

In pseudocode, the per-sector handoff state machine looks like:

// tick runs every 200ms
for each sector in sectors:
    active = sector.activeDrone
    standby = sector.standbyDrone

    // advance active along route using speed-based waypoint progress
    active.waypointIdx = advanceWaypoint(active, dtSeconds, speedFactor)

    if active.batteryPct <= swapThresholdPct and standby.state == 'standby':
        // pre-launch dispatch — standby takes off while active keeps patrolling
        handoffIdx = active.waypointIdx + lookaheadWps
        dispatchIdx = wps[handoffIdx - overlapWps]   // see overlap section
        standby.state = 'transit'
        standby.targetWp = dispatchIdx
        emit('dispatchStandby', { sector, handoffIdx, dispatchIdx })

    if standby.state == 'transit' and distTo(standby, wps[standby.targetWp]) < 5m:
        // handoff moment — standby assumes patrol, active transitions to returning
        standby.waypointIdx = active.waypointIdx
        standby.state = 'patrol'
        active.state = 'returning'
        emit('handoffComplete', { sector, waypointIdx: standby.waypointIdx })

The active drone never flies the handoff waypoint twice. The standby never restarts the route. The patrol is continuous in ground-coverage terms: at every tick, at least one drone in the sector is actively traversing the route (give or take a worst-case few seconds during the final leg alignment).

The Battery-Aware Math

The scheduler has to decide when to dispatch, not just what to do at handoff. Dispatch too early and you waste standby battery loitering; dispatch too late and the active drone lands dry mid-sector. Fleet Advisor (the closed-loop decision engine, rebranded as Overwatch AI) runs this calculation every 30 seconds using a local physics model.

The model is straightforward. Usable flight time for a cycle is:

usable_flight_s = endurance_s * (100 - swap_threshold_pct) / 100

That assumes the drone returns to base before dropping below swapThresholdPct. The dispatch moment is the point at which standby transit time plus a 60 s safety margin equals the remaining usable flight time of the active drone:

dispatch_when:
    time_remaining_s = active.batteryPct_over_swap * endurance_s / 100
    transit_s        = dist(base, wps[handoffIdx]) / transit_speed_mps
    margin_s         = 60
    dispatch_now if time_remaining_s <= transit_s + margin_s

Transit and return flight both drain battery at 1.4× patrol rate — higher ground speed means higher power draw on both the outbound dispatch leg and the incoming return. The physics model accounts for this directly so transit battery cost is not double-counted or ignored.

Wind corrections matter. Above a 3 m/s headwind, battery drain climbs roughly 10% per additional m/s on transit legs. Fleet Advisor reads the local weather estimate (Open-Meteo Marine API, cached 15 minutes) and adjusts the dispatch threshold accordingly. At 8 m/s wind, a drone dispatched at 20% might not make the handoff waypoint — so the Safety Gate forces a conservative dispatch earlier. At 10 m/s sustained, the entire mission goes into wind-abort RTH regardless of what Fleet Advisor proposes.

The safety story is important here: Fleet Advisor proposes parameter changes, but every proposal is validated by an independent Safety Gate before it reaches the orchestrator. criticalThresholdPct (force-return, default 10%) is never modifiable by the advisor. swapThresholdPct is clamped to criticalThresholdPct + 5% minimum. Transit speed is clamped at 2.0× structural. If the cloud-refined decision violates any of these, the local decision is used instead.

Relay Overlap Without Backwards Teleports

Operators often want the incoming drone to start patrolling a little before the handoff point, so there is a brief window where both drones are on the route. That redundancy catches detection misses and gives a clean visual handoff. Overwatch exposes this as a Relay Overlap slider, 0–30% of the inter-handoff leg length.

The obvious wrong implementation: have the standby arrive at the handoff waypoint, then have the active drone teleport backwards to continue patrolling the overlap segment. Nobody flies like that. The right approach is to dispatch the standby to an earlier waypoint:

overlapWps   = round(overlapPct * wps_per_cycle)
dispatchIdx  = wps[handoffIdx - overlapWps]
standby.targetWp = dispatchIdx

The active drone keeps patrolling forward through handoffIdx - overlapWps to handoffIdx. The standby, meanwhile, is flying direct-to dispatchIdx. Assume overlapWps = 4 and the route is 150 m per segment: for roughly 600 m of patrol, both drones are covering the same stretch of ground at staggered altitudes. When the standby hits dispatchIdx, it starts patrolling forward, and the active hits handoffIdx at about the same moment and peels off for base. Overlap emerges naturally from the dispatch geometry — no teleports, no route replay.

Scaling to Sectors: 1 / 2 / 4 / 8 / 16

A 100 km² AOI is not going to be covered by one drone on one loop. Overwatch partitions the operator-drawn AOI polygon into sub-sectors and runs one relay fleet per sector in parallel. The count is selected automatically from fleet size, AOI area, and battery endurance: sectorMode = 'auto' by default. The operator does not need to know sectors exist to get them.

Partitioning is recursive equal-area bisection. Each step: find the longer bounding-box axis, binary-search the cut position along that axis until the two halves have equal area within a tolerance, then clip both halves against the parent polygon using Sutherland-Hodgman. Recurse until the target sector count (1, 2, 4, 8, or 16) is reached. Sector counts stay powers of two so the tree is balanced.

The continuous-coverage math per sector is a cycle-time ratio:

usable_flight_s = endurance_s * (100 - swap_threshold_pct) / 100
cycle_time_s    = usable_flight_s + charge_time_s
drones_per_sector = ceil(cycle_time_s / usable_flight_s)

Plugging in real hardware numbers for the three platforms we test against:

  • ANAFI UKR XLR — 70 min flight, 5 h charge, 20% swap ⇒ usable 56 min, cycle 356 min ⇒ 7 drones per sector for continuous coverage.
  • ANAFI UKR Standard — 38 min flight, 2.5 h charge, 20% swap ⇒ usable 30 min, cycle 180 min ⇒ 6 drones per sector.
  • Holybro X500 V2 (workshop) — 20 min flight, 1 h charge, 20% swap ⇒ usable 16 min, cycle 76 min ⇒ 5 drones per sector.

We then add one for redundancy — a fleet running at the exact minimum has no slack for a weather abort, a failed pre-flight, or a charger fault. A four-sector ANAFI XLR deployment for continuous 24/7 coverage is therefore 4 × 8 = 32 drones. That is the honest number and it is what the Fleet Sufficiency Check banner shows in the planner.

The sector planner prefers fewer sectors with continuous coverage over more sectors with coverage gaps. If a 16-sector partition would leave each sub-fleet one drone short, the recommender drops to 8 sectors instead and reports mode: 'optimal'. If even a single-sector patrol exceeds the drone’s usable flight distance, the recommender raises the sector count to meet the endurance floor. The mode field on the recommendation tells the operator whether they are in optimal, discontinuous (patrol gaps will exist), aoi-too-big, or single-drone territory.

Patrol type is per-sector. For 'perimeter', each sector gets an arc of the original outer boundary — not the internal cut lines — so the fleet patrols only the AOI edge. For 'area', each sub-polygon is inset inward by a configurable buffer and a lawnmower grid is generated inside. The buffer exists so adjacent sector grids never share ground.

Collision Avoidance: Four Layers

With 32 drones sharing airspace, collision avoidance is not optional. Overwatch runs four layers in depth:

Layer 1 — intra-sector altitude stagger. Within one sector, each drone gets a unique assigned altitude offset, 5 m apart. Two drones patrolling the same route at the same time (during overlap) never share altitude.

Layer 2 — per-sector transit altitude bands. Each sector gets a unique transit-altitude band, also 5 m spaced. A returning drone from sector 3 does not fly at the same altitude as a dispatching drone from sector 4, even if their paths cross the base.

Layer 3 — takeoff stagger. In multi-sector patrols, sector N’s first drone launches at elapsedS >= N * 5 s. Adjacent bays never clear the ground simultaneously. This is a hard constraint on fleet startup, not a soft recommendation.

Layer 4 — runtime proximity sweep. Every 200 ms tick, the orchestrator scans all airborne-drone pairs. If any two come within 20 m horizontally and 4 m vertically, the lower-priority drone climbs 6 m. Priority ordering is patrol > returning > transit > standby, so a drone actively patrolling a route is never the one that moves. Every avoidance triggers a collisionAvoided event in the audit log.

The sweep is a safety net, not the primary mechanism. In well-configured patrols, layers 1–3 keep the fleet deconflicted and layer 4 rarely fires. During weather events, parameter-tuning races, or manual operator interventions, layer 4 is what keeps the fleet airworthy.

What the Operator Actually Sees

None of the above appears on the operator’s screen in normal operation. The alert engine is exception-only: patrol continuity, handoff success, and routine dispatch are shown on the fleet cards as state changes but do not raise alerts. What does raise alerts:

  • Battery critical on any drone (below criticalThresholdPct)
  • Handoff failure (standby arrival delayed beyond dispatch margin)
  • Wind critical (≥ 8 m/s, conservative parameters forced)
  • Wind abort (≥ 10 m/s, mission-wide RTH)
  • GPS degraded beyond VIO continuity window
  • Collision avoidance event (any layer-4 bump)
  • Detection event (person/vessel/vehicle found)
  • Fleet Advisor decision with reason code

The operator’s screen stays quiet during a 24-hour patrol. Silence is the correct state.

From Test Bench to Operational

On our workshop X500 V2 fleet, we measure handoff coverage gaps between 0 and 8 seconds, with the median at 2 s. Dispatch decisions converge within one 30 s Fleet Advisor loop. Runtime collision-avoidance bumps fire during deliberate stress tests (forced convergence on the base point) but not during nominal patrols. Battery physics predictions match measured drain within about 4% on the 1.4× transit coefficient and about 7% on the wind curve — residuals we attribute to prop wear and individual battery age rather than model error.

In operational deployment on Freefly Astro Max, the same software runs against a 45 min endurance airframe with hot-swap batteries, which collapses charge time from 60 minutes to the 90 seconds it takes an operator to pull and insert a pack. That changes the cycle math dramatically: drones_per_sector drops to 2 for continuous coverage. Sky-Hopper and Quantum Systems Trinity Pro change the numbers again in their own directions. The relay mechanics are identical. Only the constants change.

That is the point of building relay on a physics-first scheduler rather than a hard-coded state machine: the same orchestrator runs across ANAFI UKR XLR, Holybro X500, Freefly Astro Max, Sky-Hopper, and Quantum Systems Trinity Pro without modification. New airframe, new constants in the drone spec, same relay.

Try It

The simulator runs the entire relay engine against scripted scenarios — Curacao coastal patrol, multi-sector AOI, forced wind events, handoff stress tests — with the sim-speed selector compressing a 24-hour patrol into 14 minutes. Every number in this post can be reproduced in the simulator by adjusting fleet size, AOI, swap threshold, and wind. For operational deployment on your own MAVLink fleet, see pricing or contact us to book a live demo.