I tried to prove
SLAM was lying.
It called my bluff.
GPS does not work underground, so robots and rescue teams use dead reckoning and LiDAR SLAM instead. Those fail, which is fine, everything fails. The problem is that they fail quietly. I built Cairn to catch that, measured a number that made the whole case, and then found out I had invented the number myself.
Watch the video ↗SEC. 01
Underground, the dot lies confidently
GPS does not work underground. Not "works badly", not "reduced accuracy". The signal arrives weaker than the noise floor after fifty metres of rock, and no amount of software conjures information out of a signal that never arrived.
So you use inertial sensors, LiDAR SLAM, ultra-wideband beacons, wheel odometry. All fine. All of them fail. The problem is how.
A SLAM system in a long featureless tunnel does not throw an exception. It does not turn red. It keeps producing a position, at the same rate, with the same confident little covariance attached, while the robot drifts away from where the map thinks it is.
The DARPA Subterranean Challenge teams documented this in the driest possible language: visual-inertial SLAM would lose tracking and enter unrecoverable states. Read that twice. The system did not know it was lost. Above ground it is not much better: in a public-safety test at the Washington Monument led by Arlington County's fire department, NextNav reported that phone GPS horizontal coordinates became wildly inconsistent near one dense structure.
That was next to one building, in the open. Now put fifty metres of rock overhead, while a rescue team drives toward a dot on a screen. That is not a user experience problem.
SEC. 02
The problem was solved thirty years ago, elsewhere
Aircraft GNSS receivers run RAIM: Receiver Autonomous Integrity Monitoring. A very serious name for a very sensible idea. It does not improve accuracy. It computes a protection level, a bound on your error, and it raises an alarm the moment it can no longer promise you are inside that bound.
Aviation decided, correctly, that a pilot needs to know when the instrument has stopped being trustworthy more urgently than they need the instrument to be slightly better.
So: build the error bar. I called it Cairn, after the stacks of stones people use to mark a route where the ground gives them nothing else. It does not move you forward. It tells you that the way you are going is the way you think you are going.
I remain very pleased with the name. Everything after this point went less smoothly.
SEC. 03
Step one: prove the problem exists
You cannot sell a smoke detector to people who do not believe in fire. So the first experiment was simple: take a real underground dataset, take the positioning system's own self-reported uncertainty, and count how often true error escaped the bound it claimed.
The metric is missed detection rate. The fraction of epochs where the system was wrong and still said NOMINAL. Confidently wrong. The dangerous kind. It sits alongside false alarm rate and availability, but it is the only one of the three that gets people hurt.
I ran it on a CERBERUS SubT sequence from the Louisville Mega Cavern. A legged robot, real tunnels, surveyed ground truth. 15.08 percent.
One epoch in six. I was thrilled. That number was a slide, a business, a reason for someone to reply to an email.
SEC. 04
Step two: destroy my own result
Then a small, irritating voice pointed out something. The CERBERUS trajectory exports do not carry covariance. So where did the uncertainty come from that I was calling overconfident?
I made it up.
Not maliciously. I picked a sigma of 5 cm as a realistic value for a LiDAR-inertial system, plugged it in, and measured how often reality escaped it. Which means 15.08 percent was never a measurement of the system being overconfident. It was a measurement of my assumption being too tight.
Any reviewer finds this in thirty seconds, so I swept it: sigma from 1 cm to 1 metre, missed detection at every point. Near sigma-star, about 6.94 cm, missed detection drops below 0.1 percent with a protection level around 0.258 m on a run with roughly 0.35 m of error. Tight. Useful. A well-tuned static uncertainty was already fine.
The thesis I had been building on was dead. There is a specific feeling when this happens. It is not quite disappointment, it is more like walking confidently into a glass door in front of people.
The old CP-7 tables that used the assumed 5 cm are still in the repository, marked as context only, pointed at from the methodology doc. They are not cited as results anywhere.
SEC. 05
Step three: find the better thesis underneath
Here is the thing about that sigma sweep. I found sigma-star by fitting it against ground truth over the entire run. That option does not exist in a mine.
In the field you take sigma from a spec sheet or a bench calibration, and you have no way to know whether you picked 5 cm or 6.94 cm. One gives you 15 percent missed detections and the other gives you 0.1 percent, and you cannot tell which one you chose.
The problem was never that static covariance is overconfident. The problem is that you cannot tune for a failure you have not seen yet.
That reframing is what saved the project. It also changed the experiment: stop testing on clean data, start injecting faults. Bias. Dropout. Slow accumulating drift, which is the one that actually kills people, because it is the failure with no moment of obvious breakage.
SEC. 06
Never, and a number I had to take back
| Monitor | Alarm | Error when it noticed |
|---|---|---|
| Naive self-reported covariance | never | reached 20.5 m, still NOMINAL |
| Cross-source consistency | 12 s claim withdrawn | clean run with no drift crosses at 12.9 s |
Naive covariance monitoring essentially never noticed. Error climbed to twenty and a half metres while the system reported NOMINAL the entire way. But it can't alarm by construction, so that is a weaker win than it looks.
I used to say cross-source consistency caught the drift twelve seconds after onset, at fifty-four centimetres of error. I'm withdrawing that. Run the same check on the clean recording with no drift at all, and it crosses the same alarm threshold 12.9 seconds after the same onset. The timing number never showed drift detection.
What does hold up: across 8 onset points, the check is in alarm 91 to 98 percent of the time
after onset with the injected drift, and 5 to 12 percent without it. Two caveats. The injector
re-aims the drift along every 10 Hz step, so it jumps around; a smooth drift in one fixed
direction looks the same as clean. And the three sources in this test all came from one
CompSLAM trajectory (the current pose, a constant velocity prediction, the pose from 1 s
earlier), not three independent sensors. The README now says this, and the no-drift check is
in the repo as scripts/smooth_drift_check.py so anyone can rerun it.
The solution-separation side holds up too, on synthetic independent sources: configured family-wise false-alarm probability of 0.05 across 2000 trials returned an empirical false-alarm rate of 5.05 percent, and an injected LiDAR bias was correctly isolated. The configured rate and the observed rate agree, which is the least glamorous kind of good news and the kind I trust most.
SEC. 07
Step four: break the good result too
Cross-source consistency works by checking whether independent sensors agree. Disagreement is the signal. Which raises the obvious question: what happens when they are all wrong together?
Shared map. Shared clock. Shared degenerate geometry. Correlated corruption. Missed detection hit 73 percent: when it was wrong, it said all good almost three times out of four. After the error started, the alarm was on only 9 percent of the time. The consistency check collapses almost completely, because every source is telling the same lie and consistency checking cannot see a unanimous lie by construction.
The README names this weakness in its limitations, and the 73 percent number is written in four separate docs: limitations, methodology, concepts, and how it works.
There is a version of this project where I quietly leave that out. That version demos better and is worth much less, because the first person to find it themselves stops believing anything else I have written.
Solution separation is necessary, not sufficient. That sentence is the honest summary of the entire method and it is in the repo in those words.
SEC. 08
Where it landed
Cairn is public, Apache 2.0, with tests, hash-pinned dependencies, a reproduce-everything script, a security policy, and a slightly humiliating limitations page. Each check takes about half a millisecond on my laptop. Not yet measured on a wearable. It consumes pose estimates from any source and emits three things: a protection level, an integrity state, and how long since anyone independently corroborated the position.
It does not compute position. That is deliberate and it is the whole design.
The bundled quickstart is synthetic, generated by a committed script at a fixed seed, about 700 poses at 10 Hz with an injected drift segment. That is so a fresh clone can reproduce a detection in under fifteen minutes without downloading the multi-gigabyte dataset. The full CERBERUS sequences are In Copyright, Non-Commercial, so they are never committed; there is a prepare script and a DOI instead.
SEC. 09
What broke, and what I would change
My headline was an artifact of my own assumption. The 15.08 percent measured how tight I had guessed, not how overconfident the system was. Covered above. It cost me the framing I had built the project on and it is the most useful thing that happened.
Every fault in this log was injected by me. I wrote the drift. I chose its magnitude and its onset. I then demonstrated that my detector catches the drift I designed, which is a legitimate demonstration that the mechanism works and completely insufficient as evidence it works in a real mine. What I have is one dataset and a fault generator. What I need is a sequence where the failure was not put there by me.
The exports carry no covariance, so the self-report is a stand-in. CERBERUS TUM trajectories ship without a filter matrix. The claimed diagonal sigma is a realistic overconfident LIO prior, not the literal CompSLAM covariance. Change sigma and the missed detection rate moves a lot, which is exactly the point of section 05, and it also means every number here is conditional on that choice.
Corridor localizability is inferred, not decoded. The trajectory-only bundles do not open the multi-gigabyte LiDAR bag. A precomputed ICP-Hessian corridor template is aligned with travel direction instead: straight reads as corridor, turn reads as corner. Synthetic cloud unit tests exercise the real Hessian detector, but full-bag scan replay is not done.
Umeyama alignment absorbs a global SE(3) bias before scoring. Standard for ATE-style comparison, and not a substitute for aligned frames on a live robot.
The sigma sweep result is provisional. One sequence is not a finding. Calling static covariance adequate or inadequate needs multi-sequence evaluation and a matched false-alarm-rate ROC, and until those exist the honest label is provisional rather than a product claim.
This is research scaffolding, not a certified integrity monitor. No SIL or ASIL claim, no formal proof of missed-detection bounds, no warranty for field use. Single-fault, position-only solution separation. Multi-fault isolation and full SE(3) fusion are not implemented.
If you work with underground positioning and have data where something went genuinely wrong, I would like to hear from you. Especially if it makes the detector look bad. Especially then.
Run the experiment that kills your idea. Run it early, before you have told anyone, and run it properly rather than in a way designed to survive. Better a glass door in private than a glass door in front of an audience.