Skip to content

Overhaul ADS-B pipeline: dead reckoning, signal-gap handling, stale-track rendering#117

Merged
d3mocide merged 1 commit into
mainfrom
claude/adsb-pipeline-improvements-lzas4b
Jul 6, 2026
Merged

Overhaul ADS-B pipeline: dead reckoning, signal-gap handling, stale-track rendering#117
d3mocide merged 1 commit into
mainfrom
claude/adsb-pipeline-improvements-lzas4b

Conversation

@d3mocide

@d3mocide d3mocide commented Jul 6, 2026

Copy link
Copy Markdown
Owner

What

End-to-end overhaul of the ADS-B pipeline (local SDR BEAST feed + OpenSky supplement) targeting the three reported pain points: dead reckoning, odd signal gaps, and frontend rendering of stale tracks.

Server-side dead reckoning (poller/normalizers/beast_decoder.py)

  • Stale positions are now projected forward along the last known velocity via project_position() (which existed in beast_math.py but was never called). DF17 TC19 velocity messages usually keep decoding through CPR position gaps, so the projected track stays close to truth.
  • Bounded by new ADSB_DEAD_RECKON_MAX_SECONDS (default 60 s); the stale threshold is now configurable via ADSB_POSITION_STALE_SECONDS (default 10 s). Altitude projects with the vertical rate.
  • DR positions are flagged position_dr (plus position_age_s) and are estimates only: they never enter the trail ring buffer, DB observation history, or geofence entry/exit checks (guarded both at the publish call and inside db.write_entity_observation).

Signal-gap fixes

  • Cross-source CPR seeding: OpenSky and ultrafeeder positions seed the decoder's Tier-2 local CPR reference (seed_reference()), so a single odd/even frame resolves a position the moment an aircraft (re-)enters SDR range — previously a cold lock waited up to ~60 s for a fresh even+odd pair. Local fixes stay authoritative; seeds apply only when the decoder's own fix is missing or stale.
  • The registry tick loop now refreshes decoder entities on every snapshot tick, not only when new BEAST frames arrive — a total feed outage no longer freezes the published snapshot while DR is active.
  • The CPR heading-consistency guard is bounded to gaps < 30 s; across longer gaps an aircraft may legitimately have turned, and the guard could previously reject the first good fix after a gap indefinitely.
  • normalize_tar1090 fixes: alt_baro: "ground" no longer leaks the string into altitude and now correctly sets on_ground status (readsb has no on_ground key); seen_pos is honored (drop > 60 s, flag stale > 10 s); non-numeric track/gs/baro_rate are nulled.
  • Redis-hydrated aircraft (position but no fix timestamp) now report position_stale until a real fix arrives, matching the hydration docstring's intent.

Frontend rendering

  • PVB keeps projecting server-dead-reckoned tracks between reports — previously stale tracks froze (projectedSpeed = 0), then snapped to the next fix. positionDr joins the report key so DR↔fix transitions re-blend smoothly.
  • Stale/dead-reckoned local aircraft render dimmed (like OpenSky supplements) so estimated positions are visually distinct from live fixes.
  • Entity/Track types now carry position_stale / position_dr / position_age_s.

Relationship to PR #115

The raw-byte DF/length fast path from #115 (Jules) is folded into this branch verbatim (with credit) since this overhaul rewrites the surrounding ingest() lines — merging this PR supersedes the beast_decoder.py half of #115. The .jules/bolt.md log addition in #115 is untouched here, so #115 can still be merged for that file (expect a trivial conflict in beast_decoder.py if it merges second).

Testing

  • 13 new decoder tests: fast-path rejection before any pyModeS call, dead-reckoning bounds (fresh/stale/capped/no-velocity/hydrated, altitude projection), and seed-reference arbitration.
  • 9 new tar1090 normalization tests (ground handling, seen_pos staleness, non-numeric fields).
  • Full poller suite: 155 passed. tsc --noEmit: clean. docker compose config: valid.

🤖 Generated with Claude Code

https://claude.ai/code/session_01G6ZwvPTHAZ76MxF6swwryH


Generated by Claude Code

…endering

Server side (poller):
- Dead-reckon stale BEAST positions forward along the last known velocity
  (bounded by ADSB_DEAD_RECKON_MAX_SECONDS, default 60s) so tracks keep
  moving through CPR gaps instead of freezing; altitude projects with the
  vertical rate. DR positions are flagged position_dr, never enter trails,
  DB observations, or geofence checks.
- Seed the decoder's Tier-2 CPR reference from OpenSky/ultrafeeder positions
  so a single odd/even frame resolves position on (re-)entry into SDR range
  instead of waiting up to ~60s for a fresh even+odd pair.
- Refresh decoder entities on every snapshot tick so DR advances during a
  total feed outage; bound the CPR heading-consistency guard to <30s gaps so
  it can't reject the first good fix after a long gap.
- Fold in the raw-byte DF/length fast path from PR #115 (credit: Jules).
- normalize_tar1090: handle alt_baro=="ground" (no more "ground" string in
  altitude, correct on_ground status), honor seen_pos for stale/ancient
  positions, null non-numeric track/gs/baro_rate.
- Redis-hydrated aircraft now report position_stale until a real fix arrives.

Frontend:
- PVB keeps projecting dead-reckoned tracks between reports (previously
  stale tracks froze then snapped); positionDr joins the report key.
- Stale/DR local aircraft render dimmed like OpenSky supplements.
- Entity/Track types carry position_stale/position_dr/position_age_s.

Tests: 13 new decoder tests (fast path, DR bounds, seed arbitration),
9 new tar1090 tests. Full poller suite passes (155), tsc clean.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01G6ZwvPTHAZ76MxF6swwryH
@d3mocide
d3mocide marked this pull request as ready for review July 6, 2026 00:24
@d3mocide
d3mocide merged commit 5c939a7 into main Jul 6, 2026
6 checks passed
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.

2 participants