Add up the movementgives a position, but it drifts
Normally
GPS fixes the driftseveral times a second
Jammed
No GPS to fix itthe drift keeps growing
Every number in this deck is from ArduPilot's simulator (Copter-4.6.3 SITL), not real flights. Tags: measured (sim)estimatenot measured. Press N for speaker notes.
System 1 of 6 · reference
GPS working
The normal case. Nothing is jammed.
In
GPS + motion sensorsplus height and compass
Step
ArduPilot's filter blends themits built-in EKF3
Step
GPS corrects the driftso errors never build up
Out
Positionthe reference track
0.06 m off at the endmeasured (sim) median, 24 flights
Jam events removed: tests/harness/runner.py:108-109 · miss: Pathfinder-wt-fasttests/docs/night/2026-10-13-fast-run-2.md:20 · run names: sim/tools/naming.py:14-46
System 2 of 6 · worst case
Motion sensors only
Called dead reckoning. Nothing ever corrects it.
In
Motion sensorsaccelerometer + gyro
Start
True speed at jam timea generous head start
Step
Add up push → speedevery sensor reading
Step
Add up speed → positionerrors grow with time squared
Out
Positionnever corrected
9.2 km off at the endmeasured (sim) median, 24 flights
training/src/pathfinder_eval/baselines.py:70-85, swapped in at tests/harness/runner.py:236-241 · miss: fast-run-2.md:21
System 3 of 6 · ArduPilot alone
ArduPilot, emergency rules off
It keeps flying, and its position slowly slides away.
In
Motion, height, compassGPS until jammed
Jam
GPS fix is lostno outside correction
Step
Filter guesses from motionits built-in EKF3
Step
Emergency rules switched offso we can measure the slide
Out
Positiondrifting
9.2 km off at the endmeasured (sim) median, 24 flights
z_{\text{AI}} the network's speed-through-the-air estimate
\nu its error, assumed random
R_{\text{AI}} how noisy the filter assumes it is: 4.8 m/s, counted as one reading per 29.4 s
Adds a learned speedometer, trusted only a little.
In
Same sensors + motor speedfrom the autopilot
Steps 1-3
GPS check, predict, dragas in physics only
Step 4
ML speedometerlast 1 s of sensors → speed
Step 5
Mix in, lightly≈ one reading per 29 s counts
Out
Position to ArduPilotplus a 95% safety circle
156 m off at the endmeasured (sim) median, 24 flights
Flown in proof Q3 run 3, 24 flights, and not merged: Pathfinder (physics + drift-correcting AI) was 24.59 m off at 2 min vs 3.99 m for physics only measured (sim); Pathfinder (physics + speed AI + drift-correcting AI) 24.14 m measured (sim). In offline replay it had won, 47.5 vs 78.4 m estimate.
Model loaded: tests/harness/runner.py:84-86 · inputs training/src/pathfinder_ml/spec.py:13-27 · network model.py:36-62 · retune crates/pathfinder-core/src/adapter.rs:1-6 · weight learned.rs:290-327, Pathfinder/docs/night/2026-10-13-ml-alternatives.md:22-28 · miss fast-run-2.md:25
The math · physics only
Physics only: the math
Pathfinder (physics only) · jam detection, drag model, learned wind
\hat p=\textstyle\int v\,dt,\quad v=C(q)\,u+w,\quad \bar f_{xy}=-k\,u-k_q\,u\lvert u\rvert,\quad \text{GPS used only if } d<6
1 · Predict200/s, every IMU sample
a=C(q)\,(\tilde f-b_a)+g\,e_z
p\leftarrow p+v\,\Delta t+\tfrac12 a\,\Delta t^2,\quad v\leftarrow v+a\,\Delta t
P\leftarrow F P F^\top+Q
Accelerometer f̃ minus its learned bias ba, turned to north-east-down, plus gravity g, moves speed and position. P is the uncertainty of all 17 error states.
Δ: fix minus estimate, north-east. σh: the receiver's own accuracy. mpv: position and velocity disagree. d ≥ 3 is suspect; 2 s later rejected; 10 s clean to re-trust.
Σnet, the network's own confidence, is switched off (s = 0). Floor rf = 4.8 m/s RMS. Errors stay correlated for τ, so R is inflated ×294. ρ: trust schedule, off.
Two heads, 5-10 sfix drag's residual; learn drag R and wind qw
training/src/pathfinder_ml/spec.py:13-27, model.py:36-62, :81, train.py:100 · data and v1-v4: Pathfinder/docs/night/2026-10-13-ml-alternatives.md:17-41, :90-118 · A + B: D-ML-ALT-2 "Build A + B in one model"
The AI · from our results
AI: what it buys, what it costs
Pathfinder (physics only) · jam detection, drag model, learned wind
\hat p=\textstyle\int v\,dt,\quad v=C(q)\,u+w,\quad \bar f_{xy}=-k\,u-k_q\,u\lvert u\rvert,\quad \text{GPS used only if } d<6
Pathfinder (physics + speed AI) · jam detection, drag model, learned wind, plus a neural-network speed estimate
3.8×closer than ArduPilot alone, by itself (547 vs 2,059 m at 2 min) measured (sim)
8%closer at 1 minute measured (sim)
0new sensors: uses motor outputs already sent code
Cons
12-20%worse at 2 and 5 minutes; AC-02 fails measured (sim)
~100×noisier than drag (4.8 vs ≈0.03 m/s) estimate
200flights: no gain past this measured (sim)
×2000its own confidence was off; thrown away measured (sim)
2 of 2flights landed when trusted more (D-68) measured (sim)
Table: Pathfinder-wt-fasttests/docs/night/2026-10-13-fast-run-2.md:11, :18-25 (24 held-out jammed flights) · ML alone and ArduPilot: ml-alternatives.md:38-40 · ×100, 200, ×2000, D-68: ml-alternatives.md:26-31, :43-52 · runs repeat within ~10%: fast-run-2.md:41
One cycle · the whole loop
One cycle in the air
Each motion reading, every 5 ms, moves the loop.
Motion 200/s crates/pathfinderd/src/cli.rs:3 · predict per reading navigator.rs:107-109 (≤ 50 ms step config.rs:57) · drag 0.1 s config.rs:56, navigator.rs:113-119 · ML 50 samples/s, 1 s window, new guess each 0.1 s models/velocity-v3.meta.json · GPS 5/s daemon.rs:29 · out 20/s daemon.rs:17 · run time: open item docs/PLAN.md:117 not measured · loop from deck slide "One cycle in the air", docs/pitch/deck.html
One cycle · zoom in
200 milliseconds, zoomed in
Each mark is one run of that step.
Height 20/s daemon.rs:30 · compass 10/s daemon.rs:23 · sensor rates above from the code; the alignment of marks within the 200 ms is a drawing, not a recording · run time per step not measured (docs/PLAN.md:117)
Tonight's results
Pathfinder: metres. ArduPilot: kilometres.
How far off after the jam. Each gridline: ten times more.
Motion sensors onlyArduPilotPathfinder
Pathfinder (physics only) · jam detection, drag model, learned wind
\hat p=\textstyle\int v\,dt,\quad v=C(q)\,u+w,\quad \bar f_{xy}=-k\,u-k_q\,u\lvert u\rvert,\quad \text{GPS used only if } d<6
Pathfinder (physics + speed AI) · jam detection, drag model, learned wind, plus a neural-network speed estimate
A · Correct the drag modelLearn only what physics gets wrong, from 5-10 s of data.
✓ Chosen
B · Learn how much to trustTrust drag more in calm air; let wind change faster in gusts.
✕ Not now
C · Reinforcement learningBest case ties "physics only"; needs days of simulator flights.
Expected: A wins at 2 min, B cuts 5-min drift 10-25%. estimate
Options: Pathfinder/docs/night/2026-10-13-ml-alternatives.md:71-170 · decisions D-ML-ALT-1 "Develop an alternative ML model in parallel and compare" and D-ML-ALT-2 "Build A + B in one model": BaseClient/docs/Tasks/IN_PROGRESS/drone-nav/DNAV-1-gps-denied-navigation/DNAV-1-gps-denied-navigation.md:31-33
Error budget · physics only
Wind and drag cause most drift; heading barely matters
64 replays: wind plus drag explain about 27 of 34 m at 5 min.
Tap the chart to open it full size.
measured (sim) Median total 2.0 / 4.9 / 18.6 m at 1 / 2 / 5 min. Wind numbers are a fit estimate.
Chart: docs/night/2026-10-13-error-budget.svg · tool from fb9a2ae ACC-STEP-1 "Error budget: which source causes the drift"
Learning in flight · per aircraft
Tuned to each aircraft, while GPS is good
The aircraft type goes in; the rest adjusts in flight.
WindLearned while GPS is good, held after the jam; navigator.rs:145 (built, on main)built
Speed AILast layer refitted while GPS is good, stops by 180 s; adapter.rs:1 (built, on main)built
Drag scaleLearned while GPS is good; air_learn.rs:1, preset experimental-learn-air: 159.6 vs 80.4 m at 2 min (offline estimate)lost, off
Aircraftquad, hexa or octa is a parameter: spec.py:25, --frame in harness and pathfinderd; hexa never trained: 204 m at 2 min (measured (sim))built
Fine-tuneDrift-correcting AI fine-tuned in flight; a model per real aircraftplanned
inHIGHRES_IMU at 200 Hz, SCALED_IMU, ATTITUDE, GPS_RAW_INT, SCALED_PRESSURE
outODOMETRY at 20 Hz as EKF3 external nav, or GPS_INPUT as a second GPS
pickconfig/presets.json: 11 presets, default best. Pick with --preset, PATHFINDER_PRESET or the Control Room Setup picker. Every switch off = byte-identical main.
runControl Room, localhost:8790: Fly, re-fly, train, test, replay and present from one browser page.