The number I chased first, and what it hid
I spent weeks getting one number down, and for a while it was the only number I cared about: 0.9% drift. In the demo I built, a synthetic drone flies an 80-second, 245.3 m mission. GPS stays clean until t=18 s, then the aircraft crosses into a simulated R-330Zh electronic-warfare bubble and the signal is gone. My tightly-coupled VIO EKF hands off from GPS to vision with no mode change the operator can see, and it keeps tracking. Dead-reckoning on the raw IMU blows out to 30.95% drift and a 75.92 m final error over that same path. My integrity-aware version holds to 0.92%, a 2.26 m final error. I was proud of that gap. I told people about that gap.
GPS has been denied since t=18 s. The estimated track still sits on ground truth, integrity reads GREEN, and the scoreboard shows the gap I was chasing: 30.95% drift for dead-reckoning against 0.92% for the integrity-aware run.
What I had not yet understood is that a good drift number is the easy half of the problem, and in GPS-denied airspace it is not the half that gets a drone killed.
The test that embarrassed my own baseline
I ran a moving convoy through the scene expecting a small wobble, and instead I watched a textbook failure happen live in my own code. From t=30 s to t=43 s a convoy crosses a low-texture clearing and sprays the frame with moving "landmark" features. Stock VIO, the honest baseline with dynamic-feature masking turned off, locks onto the trucks, decides the drone is the thing standing still, and flies off. It ends the run at 28.79% drift and a 32.95 m ATE, a worse trajectory error than dead-reckoning with no vision at all. This is the documented ORB-SLAM3 failure, the one where the estimator tracks the truck and thinks the drone is parked, and I got to see it break my own baseline in real time.
The convoy crossing. Semantic masking has rejected 10 moving features this frame, so my estimator never locks onto the trucks. Integrity honestly dips to AMBER (few static features left) while the track holds. Stock VIO, meanwhile, is already 11 m off.
My version rejected the convoy's moving features and held the line, and the integrity state honestly dipped to AMBER because there genuinely were few static features to trust. That was the first time I felt the real shape of the problem. The danger in this domain is not a position that is a little wrong. It is a position that is confidently, articulately wrong, delivered with a tidy covariance, while the drone flies into a wall.
The tunnel where a clean number was a lie
I hit the moment that changed the whole architecture in a dark tunnel, and I remember not trusting my own screen. From t=52 s the drone enters a feature-poor tunnel and the usable visual features collapse toward zero. My estimator, the good one, kept printing a position with a plausible covariance. It looked fine. It was not fine. There was nothing observable in the scene for any estimator to anchor to, so whatever it reported was a guess wearing the costume of a measurement. That is when it landed for me: I could not make the estimator safe by making the estimator better. A better estimator produces a more convincing wrong answer, not a more honest one.
The tunnel. Usable features have hit zero, so the monitor flips RED, declares the position untrustworthy, and commands return-to-home at t=52.5 s. The system does not bluff a fix. It abstains.
So I stopped trying to make the estimator admit its own blindness, because I no longer believed it could. You do not let a witness certify their own testimony. The question "what is this position?" and the question "can this position be trusted right now?" are different questions, and I had been asking the second one of the exact machinery that had every incentive to answer "yes."
Why the thing that decides trust lives outside the estimator
I pulled trust out of the estimator entirely, and that decision is the part of this system I would defend hardest. The Navigation Integrity Monitor is separate code on purpose. It watches three signals with hard thresholds: usable feature count (GREEN at 8 or more, RED below 1), position sigma (GREEN under 0.6 m, RED over 1.5 m), and vision NIS, the chi-square innovation-consistency check (GREEN under 7.0, RED over 30.0). When RED sustains for half a second, the failsafe latches and the drone is commanded home. In the headline run it fired at t=52.5 s, exactly when the tunnel went unobservable. Across that mission the monitor flagged the genuinely-unobservable tunnel 100% of the time and false-alarmed on the healthy GPS-denied leg 0.1% of the time, and it held GREEN for 56.3% of the denied flight. It abstains when it should and stays quiet when it should not.
I want to be precise about what decides, because it matters to me: the gate is deterministic Python, not a model's opinion. The only large language model anywhere near this system drafts the human-readable report afterward, and it sits entirely outside the flight loop. Agents advise. Code decides. Trust must not depend on the thing being trusted, and the cheapest way I know to guarantee that is to compute it somewhere the estimator cannot reach.
What LiDAR taught me about writing an honest report
I almost shipped the tunnel as an unsolvable case, and then I added simulated LiDAR and had to decide how honest to be about it. With a simulated tightly-coupled LiDAR range constraint switched on, the same tunnel becomes observable, integrity holds, no failsafe fires, and the run comes in at 0.01% drift with 78.9% of the flight at GREEN. It is a better-looking number than my headline. It is also a simulated range constraint, not a real LiDAR factor, and it buys that recovery with 250 to 400 g of payload and 8 to 12 W of power on a real airframe. So the one-click Flight Integrity Report says all of that: the accuracy table, the abstention logic, and an explicit scope disclosure of what is stubbed and what is deferred. I would rather a reviewer catch the caveat in my own export than in their own testing. You can run the whole thing yourself at veriprajna.com/demos/gps-denied-drone-autonomy.
The exported report for the LiDAR-on run. It records the recovery (0.01% drift, 78.9% GREEN, failsafe n/a because the scene was observable) alongside the scope disclosure. The point of the receipt is that the caveats travel with the numbers.
Everything here is one seeded, synthetic mission (SEED 20260619), with ground truth generated independently of every estimator. There is no real drone, no real flight log, and no real emitter, and the R-330Zh is a named scenario, not a sensed signal. The 16,000 EKF updates were measured on my machine; the 30 to 45 FPS on a Jetson Orin NX is a target-hardware spec I have not measured. The metres will move on real hardware in real air. The structure will not.
The question I keep coming back to
The industry is in a race to make the estimator better, and I understand the appeal, because I spent weeks in that race chasing a drift number I was proud of. But GPS jamming and spoofing have become routine in contested airspace, according to widespread public reporting, and in that airspace the failure that kills a drone is not a slightly-worse position. It is a confident-but-wrong one, and a better estimator does not fix that. It sharpens it. So the line of code I am proudest of is not the one that hit 0.9% drift. It is the one that looks at an unobservable scene and says the unglamorous thing: I cannot trust this, turn around. Which line in your own autonomy stack is actually allowed to say that?