What this is: The story of why our simulated drone kept clipping walls with its side even though its forward sensor said “all clear,” and how we built a proximity safety layer that watches sideways too.
Why it’s here: It captures a tricky lesson — a single forward-facing sensor is blind to walls beside the aircraft — and the geometry trick we used to fix it, plus the surprise discovery that our wall-retreat maneuver was itself knocking the drone over.
Date: 2026-06-10 Ticket: Proximity safety layer — lateral clearance and retreat fix
Glossary
- Proximity safety layer — a guard that constantly watches how close the drone is to obstacles and quietly slows or stops it before a collision. Think of it like the parking sensors in a car that beep louder as you near a wall, except here it actually eases off the throttle for you.
- Lateral retreat — backing the drone away from an obstacle that is beside it, not directly ahead. Like pulling your car sideways out of a too-tight parking spot instead of just reversing straight.
- Safety filter — a layer that sits between “what the drone wants to do” and “what the motors actually do,” trimming the command so it never crosses a danger line. Like a speed governor on a truck: you can press the pedal, but it won’t let you exceed the limit.
- ToF sensor — a “time-of-flight” range sensor. It fires a beam, times how long the reflection takes to come back, and converts that into a distance. We simulate six of them around the drone, each a single narrow beam.
- Projected clearance — the real perpendicular gap to a wall when the sensor is aimed at an angle. If a sensor points 60° off to the side, the wall isn’t as far away as the raw reading suggests; a bit of triangle math gives the true sideways distance.
1. The problem: the drone was blind on its sides
The first clue came from the reinforcement-learning team’s data. Across one run, all 36 crashes happened at almost exactly the same spot — about 0.23 m from a wall — and never in the open middle of the arena. That kind of consistency is rarely luck; it points at a single repeatable cause.
The cause turned out to be a diagonal side-clip. The drone approaches a wall at an angle. Its front sensor (a single narrow beam pointing straight ahead) looks down the corridor and reads the far wall as a comfortable ~2.0 m away — so as far as the front sensor is concerned, everything is fine. Meanwhile the side of the aircraft is grazing the near wall. The forward-only proximity check simply cannot see walls that are beside the drone.
I reproduced it in my own smoke test: flying head-on, the front reading stayed pinned at 2.0 m the whole way, while the closest-point-on-the-perimeter reading collapsed from 0.46 m down to 0.06 m — that was the side wall, invisible to the front beam.
2. How the fix evolved
First attempt — a forward arc. Instead of trusting only the single front beam, combine the front beam with its two nearest neighbors and use whichever sees the closest obstacle. In the smoke test this genuinely helped during a forward move — the drone automatically shortened a 1.40 m hop to 0.85 m when the arc caught a side wall, and it stayed level (tilt under 9°) with no tumble. But the overall test still failed, because a different mechanism was knocking the drone over (more on that below).
Second attempt — projected lateral clearance. This is the version we kept. Rather than a flat “take the smallest of three beams,” it uses geometry. The side sensors point about 60° off-axis, so a little trigonometry converts each angled reading into the true perpendicular gap to the side wall:
perpendicular gap = sensor reading × sin(60°)
That perpendicular gap is, in effect, the usable width of the corridor on each side. It is more honest than a flat minimum because it answers the real question — “how much room do I have beside me?” — instead of “what’s the nearest raw number?”
3. The real culprit: the retreat maneuver itself
Here is where the investigation took a turn. The thing actually flipping the drone was not the forward move at all. It was a separate, pre-maneuver retreat step — the bit of logic that is supposed to back the drone off when it finds itself too close to a wall before it even starts its next action.
The sequence looked like this: the drone noticed it was too close, triggered a retreat, and instead of calmly backing away it pitched over hard — from a gentle 7° lean to a violent 72° — jammed itself against the wall, and from then on every subsequent action just hammered the same broken retreat while the drone lay stuck and unrecoverable.
Two things were wrong with that old retreat:
- It aimed at the wrong opening. It steered toward whichever single sensor reported the most free space. But that is the same single-beam blindness from §1 — the “most open” beam can point straight at an invisible diagonal or side wall.
- It threw away altitude control. To retreat aggressively it suppressed the drone’s normal position- and altitude-holding and pushed a raw velocity command right next to the wall. With nothing holding the height, the drone sagged, tipped, and tumbled.
4. The fixes
Part A — retreat away from the danger, not toward “open space.” Flip the logic: find the sensor reading the closest obstacle, and steer 180° away from it. We also kept the drone’s current heading frame in the math (the channels are heading-relative), which the original spec had glossed over.
Part B — a background lateral layer. During every forward move, continuously compute the perpendicular side gap from the two side beams. If it drops below a hard stop distance, declare “arrived” and stop cleanly. Between the stop distance and a comfortable distance, scale the speed down smoothly so the drone eases off as the corridor narrows, taking the minimum of the front-limited speed and the side-limited speed. This layer is always on — no special trigger needed — and the existing front-sensor braking pulse stays in place.
5. Smoke-test validation
The smoke test flies a live stack through a reposition, five head-on approaches, three rotations next to a wall, and four diagonal approaches. The first runs failed, but the data isolated the cause cleanly.
5.1 Isolating the cause
| action | travel (m) | nearest gap (m) | tilt | verdict |
|---|---|---|---|---|
| head-on #0 | 1.40 | 2.00 | 7.6° | flies fine |
| head-on #1 | 1.40 | 1.48 | 8.0° | flies fine |
| head-on #2 | 1.08 | 0.27 | 7.0° | lateral layer worked — auto-shortened 1.5→1.08, stopped at the wall, no tumble |
| head-on #3 | 0.00 | 0.06 | 72.3° | pre-maneuver retreat → tumble + height loss |
So the forward + lateral layer works: tilt stayed under 8°, travel auto-shortened, the drone parked safely at the wall. The thing that flipped it was specifically the pre-maneuver retreat — the drone entered retreat at 7° tilt and 1.8 m altitude, and came out at 72° tilt and 0.44 m. It lost both its footing and its height.
The root cause confirmed: the old retreat ran a raw velocity command with zero vertical velocity and maintenance suppressed, right against the wall. Zero vertical velocity is not enough on its own without active height-hold, so the drone sank, leaned, and tumbled. Not a direction problem (Part A already fixed that) and not a sensor problem — purely a velocity-retreat with no altitude hold too close to a wall.
5.2 Carrot-style retreat — retreat fixed
The fix for the retreat was to stop using raw velocity and instead aim the drone at a gentle position target with altitude hold — the same calm approach we use for repositioning.
| before (raw velocity) | after (position target + altitude hold) |
|---|---|
| head-on #3 retreat → tilt 72°, height 1.8→0.44 | head-on #3 retreat → tilt 1.5°, height holds |
| whole run unrecoverable | rotation case: retreat (tilt 2.6°) → two clean rotations at 0.7° / 0.5° |
Retreat-before-maneuver now behaves: the drone reaches a wall, softly drifts back, and turns cleanly.
But the drone still touched walls in two situations:
- Marginal touches — the lateral layer braked, but the drone came to rest at 0.25–0.26 m, just inside our 0.30 m “touch” threshold, with no tumble. A margin tune (raising the stop distance and the front braking distance) addresses this.
- Diagonal corner-clip — on a sharp diagonal approach, a corner wall only appeared on the front sensor at 0.19 m. The front beam was blind to it behind the 2.0 m sensor range, and the wall sat in the ~60° gap between two sensor beams. This is a genuine limit of having only six beams spaced 60° apart: a wall can hide in the gap.
A note for context: in a real training run, forward travel is driven by an accumulated occupancy map (the drone stops inside mapped grid cells), not by raw sensor readings. The standalone smoke test, using only raw front-minus-margin, is therefore harsher than reality. The ToF safety layer is a secondary net underneath the occupancy map.
5.3 Crab fix and margin tune
Aleks spotted on video that the drone seemed to drift sideways by ~15°. Track analysis showed it was not drift — it was a crab. During repositioning the drone held its final target heading for the whole translation, so its body was turned ~74° away from the direction it was actually moving (flying sideways). The heading itself was accurate (14.8° vs the 15° requested); only the body orientation looked wrong.
The crab fix: point the nose along the direction of travel during the move, fly straight to the point, then snap to the spawn heading at the very end. The track confirmed it — the drone faced the target, flew straight, then squared up to 15°, with reposition tilt down to 2.8° and no crab. We also raised the lateral stop margin, and added a ground-truth track logger to the smoke test so every run leaves a trajectory we can replay.
Where things stand after all the fixes:
- Fixed: recovery death-loop, reposition lunge and crab, off-axis oscillation, retreat tumble, and lateral side-clip.
- Improving: marginal touch is now 0.28 m (up from 0.25 m — the margin helped, still just under 0.30 m, but with no tumble).
- Still open: the diagonal corner-clip persists. A wall sitting at roughly 30° off-course can fall into the gap between the 0° and 60° beams, where the front beam is blind beyond 2.0 m. This cannot be cured with margins or geometry — we proved that across three smoke runs.
6. Open question: the diagonal sensor gap
The diagonal clip is the one remaining issue, and it is architectural — six beams 60° apart will always have blind wedges between them. The options under consideration:
- Occupancy mapping already handles it in real runs. Real forward travel uses the accumulated map, which remembers the wall even when no beam currently sees it. The next step is to validate on the occupancy-wired path rather than the raw smoke test.
- A dedicated dense safety scanner in simulation — a 360° sensor used only by the safety layer. It would see the true nearest wall and close the gap entirely, without touching the six narrow beams the learning agent observes, so training inputs stay identical. Clean and durable, but it adds a sensor and the plumbing to carry its data.
- Native autopilot proximity — feed the six range readings to ArduPilot through its obstacle-distance interface and let the autopilot’s built-in avoidance handle it. This would remove the single-beam blindness from both the forward move and the retreat at once.
- Shorter forward hops — take smaller steps so the drone re-senses more often and overshoots less into a blind wedge. Cheap, but it changes how far each step travels.
The carrot-style retreat and altitude hold are worth keeping regardless — that was a clear, standalone win. The diagonal gap is the next decision to make.