diff --git a/aerpaw/controller.coderprj b/aerpaw/controller.coderprj
index 821668c..8fe2fe0 100644
--- a/aerpaw/controller.coderprj
+++ b/aerpaw/controller.coderprj
@@ -708,7 +708,7 @@
true
- 2026-04-01T20:44:04
+ 2026-04-01T21:59:22
diff --git a/aerpaw/controller.m b/aerpaw/controller.m
index f71fa14..fba8fab 100644
--- a/aerpaw/controller.m
+++ b/aerpaw/controller.m
@@ -7,7 +7,8 @@ coder.extrinsic('disp', 'readScenarioCsv');
% Maximum clients supported (one initial position per UAV)
MAX_CLIENTS = 4;
-MAX_TARGETS = MAX_CLIENTS;
+% Two waypoints per UAV: altitude-staggered transit + final position
+MAX_TARGETS = MAX_CLIENTS * 2;
% Allocate targets array (MAX_TARGETS x 3)
targets = zeros(MAX_TARGETS, 3);
@@ -33,6 +34,28 @@ else
numWaypoints = totalLoaded / int32(numClients);
end
+% In the compiled path, inject altitude-staggered transit waypoints so UAVs
+% are vertically separated while flying horizontally to their start positions.
+% ArduPilot reaches target altitude before horizontal movement, so UAV i is at
+% altitude (TRANSIT_ALT_BASE + (i-1)*TRANSIT_ALT_STEP) throughout its transit,
+% preventing collisions regardless of horizontal path geometry.
+% TRANSIT_ALT_STEP must exceed 2 * max(collisionRadius).
+% Waypoint 1: each UAV flies to (finalX, finalY) at its unique transit altitude.
+% Waypoint 2: each UAV adjusts to its actual target altitude.
+if ~coder.target('MATLAB')
+ TRANSIT_ALT_BASE = 25.0; % must match drone.takeoff() altitude in uav_runner.py
+ TRANSIT_ALT_STEP = 12.1; % vertical separation per UAV (m); must exceed 2*collisionRadius
+ for ii = double(totalLoaded):-1:1
+ transitRow = (ii - 1) * 2 + 1;
+ finalRow = (ii - 1) * 2 + 2;
+ finalPos = targets(ii, :);
+ transitAlt = TRANSIT_ALT_BASE + (ii - 1) * TRANSIT_ALT_STEP;
+ targets(finalRow, :) = finalPos;
+ targets(transitRow, :) = [finalPos(1), finalPos(2), transitAlt];
+ end
+ numWaypoints = int32(2);
+end
+
% Load guidance scenario from CSV (parameters for guidance_step)
NUM_SCENARIO_PARAMS = 45;
MAX_OBSTACLES_CTRL = int32(8);