Fix the ADS-B truth path for ADSBHub-fed sources - #73
Merged
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.alt_barofallback whenalt_geomis absent.baro_ratefallback whengeom_rateis 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
extrapolatePositionread onlyalt_geom, so an aircraft without one was placed at 0 ft. Zero is falsy,bistatic.jsrejects it, andgeometry.jsturns that rejection into a delay of exactly 0: a truth target that looks present and is useless.bistatic.jsalready hadalt_geom ?? alt_baro, but it could never fire, becauseextrapolatePositionhad already replaced the missing value with 0 and handed it over labelledalt_geom. The fallback belongs where the field is first read.SBS/BaseStation carries no geometric altitude, so
adsb.retina.fmservesalt_geomfor 0 of 43 aircraft andalt_barofor 43 of 43.What the approximation costs.
alt_barois pressure altitude,alt_geomis 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'sbistatic.jsand jn1's real geometry, in 150 m 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.fmrefreshes 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_ratereaches this code from neither source: SBS has no such field, and the tar1090 converter does not pass adsb.lol's through.vel_upwas therefore always 0, so a climbing aircraft got the doppler of a level one and its projected altitude never moved.baro_rateis 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.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
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_unitsandtest_locationstill pass.Deploying
This is an
api/change, so it needs the build-frontend workflow;build-blah2does 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