Skip to content

Fix the ADS-B truth path for ADSBHub-fed sources - #73

Merged
Purple10101 merged 3 commits into
mainfrom
fix/adsb-truth-alt-baro-fallback
Sep 27, 2026
Merged

Purple10101 merged 3 commits into
mainfrom
fix/adsb-truth-alt-baro-fallback

Conversation

@Purple10101

@Purple10101 Purple10101 commented Sep 26, 2026 •

Copy link
Copy Markdown

What

Three changes to the ADS-B truth path, all needed before a node can read truth from an ADSBHub-fed source such as adsb.retina.fm.

  1. alt_baro fallback when alt_geom is absent.
  2. Extrapolation window 5 s → 10 s, as a named constant.
  3. baro_rate fallback when geom_rate is absent.

All three are the same shape: blah2 read only the GNSS-derived field, and ADSBHub's SBS feed carries none of them.

1. Altitude fallback

extrapolatePosition read only alt_geom, so an aircraft without one was placed at 0 ft. Zero is falsy, bistatic.js rejects it, and geometry.js turns that rejection into a delay of exactly 0: a truth target that looks present and is useless.

bistatic.js already had alt_geom ?? alt_baro, but it could never fire, because extrapolatePosition had already replaced the missing value with 0 and handed it over labelled alt_geom. The fallback belongs where the field is first read.

SBS/BaseStation carries no geometric altitude, so adsb.retina.fm serves alt_geom for 0 of 43 aircraft and alt_baro for 43 of 43.

What the approximation costs. alt_baro is pressure altitude, alt_geom is height above the WGS-84 ellipsoid. Measured across 202 aircraft carrying both: median difference +750 ft, p90 1350 ft, and systematically positive rather than scattered. Projected onto bistatic delay with this repo's bistatic.js and jn1's real geometry, in 150 m bins:

ground range elevation error at +500 ft at +1500 ft
5 km 64.9° 287 m (1.9 bins) 862 m (5.7 bins)
25 km 23.1° 124 m (0.8 bins) 375 m (2.5 bins)
100 km 6.1° 34 m (0.2 bins) 104 m (0.7 bins)
400 km 1.5° 17 m (0.1 bins) 53 m (0.4 bins)

It scales with sin(elevation): sub-bin beyond ~50 km, a few bins for near-overhead targets.

2. Wider extrapolation window

The 5 s threshold was chosen when every source refreshed positions about once a second. adsb.retina.fm refreshes in whole-population batches every 8.6 s, so each aircraft's age sweeps a 1-9 s sawtooth and the whole population crosses 5 s together.

It is a confidence cutoff on a constant-velocity model, not a property of the data, so widening it costs projection error rather than correctness. Worst realistic case, a standard-rate turn held for the whole window: 131 m at 5 s, 520 m at 10 s, about three range bins here, and near zero for an airliner in cruise.

3. Vertical rate fallback

geom_rate reaches this code from neither source: SBS has no such field, and the tar1090 converter does not pass adsb.lol's through. vel_up was therefore always 0, so a climbing aircraft got the doppler of a level one and its projected altitude never moved. baro_rate is carried by both feeds and already passed by the converter.

This one is correctness, not capability. 122 of 193 live aircraft were climbing or descending by more than 100 ft/min, and including their vertical rate moved the doppler by a median of 0.19 Hz, at most 1.38 Hz. Against 2 Hz bins at a 0.5 s CPI that is under one bin even at worst, because these targets sit at low elevation. It would matter for a near-overhead climber; this sample had none.

Measured effect

Live data through this pipeline with jonathan-node-1's real rx/tx geometry (WGBY-TV), usable truth targets per poll over 10 polls:

adsb.retina.fm api.adsb.lol
before 0 every poll 38
altitude fallback only mean 10.7, zero on 7/10 40.7
plus wider window mean 53.2, zero on 0/10 60.0

adsb.lol gains 47% as a side effect, because its sparser aircraft were crossing the 5 s cutoff too. That was available at any point and is independent of adsb-service.

Testing

node api/test_alt_baro_fallback.js     # 14 checks, all pass

Assertion-based, exits non-zero on failure. Each group was verified to fail with its change reverted and the tests retained: 3 fail without the altitude fallback, 5 without the vertical-rate fallback.

test_bistatic, test_tar1090_extrapolation, test_timestamp_units and test_location still pass.

Deploying

This is an api/ change, so it needs the build-frontend workflow; build-blah2 does not build blah2-api.

Must land before or with offworldlabs/retina-node#44, or nodes pointed at adsb-service get an empty overlay. Pairs with offworldlabs/tar1090-node#20.

🤖 Generated with Claude Code

Purple10101 and others added 3 commits September 26, 2026 11:07
extrapolatePosition read only alt_geom, so an aircraft without one was placed
at 0 ft. Zero is falsy, bistatic.js rejects it, and geometry.js turns that
rejection into a delay of exactly 0: a truth target that looks present and is
useless.

That silently discarded a large share of the sky, and all of it from any
source fed by ADSBHub. SBS/BaseStation carries no geometric altitude at all,
so adsb.retina.fm serves alt_geom for 0 of 43 aircraft while serving alt_baro
for 43 of 43. Measured against live data with jonathan-node-1's real rx/tx
geometry, the truth path produced 0 usable targets from it; with this change,
36, delay 58.8-440.6 km, against api.adsb.lol's 39 at the same moment.

bistatic.js already had `alt_geom ?? alt_baro`, but it could never fire:
extrapolatePosition had already replaced the missing value with 0 and handed
it over labelled alt_geom. The fallback belongs where the field is first read.

Barometric and geometric altitude differ by tens to hundreds of feet, which is
negligible against a bistatic delay measured over 100+ km; the new test pins
that difference under 1 km. alt_baro is the string "ground" for surface
movements and must not be added to a number, which is also covered.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
extrapolatePosition refused any position older than 5 s. That threshold was
chosen when every ADS-B source refreshed positions about once a second, so it
only ever rejected genuinely dead data.

ADSBHub-fed sources do not behave that way. adsb.retina.fm refreshes positions
in whole-population batches every 8.6 s, so each aircraft's age sweeps a 1-9 s
sawtooth and the whole population crosses 5 s together. Measured over 10 polls
against the live service with jonathan-node-1's geometry, a 5 s window yielded
zero usable truth targets on 7 of them. At 10 s the sawtooth fits inside the
window: 53.2 targets on average, and zero on none of 10 polls.

api.adsb.lol also gains, from 40.7 usable targets to 60.0, because its sparser
aircraft were crossing 5 s too.

The threshold is a confidence cutoff on a constant-velocity model, not a
property of the data, so widening it costs projection error rather than
correctness. A straight-line projection is only wrong while an aircraft
manoeuvres; the worst realistic case, a standard-rate turn held for the whole
window, moves from 131 m at 5 s to 520 m at 10 s, about three range bins at
this fs of 2 MHz, and near zero for an airliner in cruise. The new test pins
that bound.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
extrapolatePosition and the doppler velocity both read only geom_rate, which
reaches this code from neither source: ADSBHub's SBS feed has no such field,
and the tar1090 proxy's converter does not pass adsb.lol's through. vel_up was
therefore always 0, so a climbing or descending aircraft was given the doppler
of a level one, and its projected altitude never moved.

baro_rate is the pressure-derived equivalent, is carried by both feeds, and is
already passed by the converter, so the same fallback that fixed altitude fixes
this. The two differ by a few percent, far less than the ~750 ft measured gap
between the two altitudes.

Be clear about the size of this: it is correctness, not capability. Measured
against both live services with jonathan-node-1's geometry, 122 of 193 aircraft
were climbing or descending by more than 100 ft/min, and including their
vertical rate moved the computed doppler by a median of 0.19 Hz and at most
1.38 Hz. Against 2 Hz doppler bins at a 0.5 s CPI that is under one bin even in
the worst case, because these targets sit at low elevation where vertical
motion barely projects onto the bistatic range rate. It would matter for a
near-overhead climber, of which this sample had none.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Purple10101
Purple10101 merged commit e8f07fb into main Sep 27, 2026
2 checks passed
@Purple10101
Purple10101 deleted the fix/adsb-truth-alt-baro-fallback branch September 27, 2026 08:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant