Skip to content

Validate real receivers with ADS-B and isolated blind MLAT replay - #512

Draft
jehanazad wants to merge 15 commits into
mainfrom
feat/real-node-validation
Draft

jehanazad wants to merge 15 commits into
mainfrom
feat/real-node-validation

Conversation

@jehanazad

@jehanazad jehanazad commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

Physical receivers were not consistently using fetched ADS-B references: the seeding provider omitted the external cache, v1 adsb_hex labels were ignored by claiming/archive paths, and shared aircraft identities could combine real and simulated claims. Repeated reference normalization per detection also created an ingest bottleneck once real traffic arrived.

This change adds optional regional polling of a first-party readsb service, preserves observation clocks/provenance, resolves indexed node identities, rejects mixed-world claim groups, and separates real/simulated solve funnels. Reference snapshots are reused once per frame; a representative 20-identity/300-reference benchmark fell from ~102 ms to ~5 ms for reference lookup. MLAT/TIS-B-derived positions are excluded; incomplete observations cannot become zero-valued kinematic references. Feed-origin other/unknown is retained separately from explicitly direct ADS-B.

The opt-in private capture and replay tools support reproducible blind tracking/association/solving. Aircraft labels and positions are joined only after estimation; reports retain failed attempts, false identity pairings, horizontal/vertical error, and captured detection opportunity denominators. Node reports provide residuals, delivery lag and observed coverage cells. Calibration/evaluation overlap is refused. Synthetic snapshots skip real-only remote catalogues. SINGLE_NODE_GEO_INTERVAL_S exposes the single-node fit cadence (default 10 seconds), and both HTTP routes now count all timestamped frames discarded on queue saturation. Noise, history, unknown-beam, optimizer-budget, uncertainty, track-exclusivity and fixed-layer experiments are offline options. See docs/real-node-validation.md for the workflow and limitations.

Depends on the geolocator diagnostics in offworldlabs/retina-geolocator#26 and the per-pass trust computation in offworldlabs/retina-analytics#35 (both pinned commits included).

Validation:

  • Backend CI on final commit 28e8270: 4,293 passed, 2 skipped; full CI matrix green. The 124 focused arc/track/feed checks and earlier 87 reference/ingest/cadence checks also passed.
  • Geolocator: 119 tests passed, including convergence/uncertainty diagnostics.
  • Analytics: 521 tests passed. The representative reputation pass benchmark improves from 1,104 ms to 46 ms.
  • Write-time reference validation avoids repeated per-frame checks; the 1,000-record snapshot benchmark improves from 10.8 ms to 2.5 ms versus the intermediate implementation.
  • All pre-commit checks passed.
  • Test service deployed with real ADS-B input and bounded private capture. The initial saturation recorded 100 drops; this is a minimum because HTTP queue losses were previously uncounted. Recurring sustained-load backlogs required synthetic-fleet drain pauses. The original 50 synthetic frames/s load remained unsustainable. The test fleet was reduced to 25 frames/s without reducing real input; the final three-hour sampled interval stayed below 453 queued frames, with no counted ingest drops. This is a test-load mitigation, not proof of original-load capacity.

Known-lane publications are ADS-B-assisted. Blind replay acceptance is reported separately and is not proof of accurate position: two-node association ambiguity remains a material limitation. Seven frozen later windows contain 51,299 real frames, with no missing late frames in the capture-membership audit. On the explicitly direct ADS-B subset, the baseline accepted 28/388 eligible attempts (14 within 5 km); the conservative tuned rule accepted 11/453 (10 within 5 km, one 71.7 km outlier), plus 61 accepted conflicting-identity candidates. Fixed-layer sensitivity accepted 212/444 eligible attempts (155 within 5 km) but also 457 conflicting-identity candidates. Repeated fits are not independent aircraft trials. Experimental blind gates remain offline; greater numerical acceptance did not establish reliable blind publication.

The final operational fixes align archive health with its actual hourly cadence and replace 32 bisection steps per display-arc point with the exact ground-plane ellipse intersection. The latter preserved point counts/clipping in 600 invented geometries (17,509 points; maximum difference below 0.00006 m) and reduced a 3,000-arc CPU benchmark from 2.799 to 0.316 seconds. It does not change MLAT estimation, the measurement-only arc definition, or separate public receiver geometry. The 124 focused arc/track/feed tests and all pre-commit gates passed.

Coverage investigation follow-up: add the last accepted evidence timestamp to the analytics payload and map popup, identify sample counts as retained (200 per bearing bin), and label the percentile polygon as a typical observed footprint rather than a hard detection limit. The browser refresh regression confirms geometry/timestamps update even when counts are unchanged. Coverage storage now acknowledges accepted writes so rejected points do not inflate recording counters; the pinned analytics change also aligns display and admission range caps. The live sampled polygons were updating: two busy receivers changed geometry while their counts stayed fixed. The cap mismatch was not clipping those sampled receivers. Unknown-beam reference-assignment censoring remains documented; blind solves are not used to grow coverage. Validation: 521 analytics tests, 124 focused backend calibration/claim/timestamp tests, 26 map tests, dashboard typecheck/lint/build and pre-commit gates passed.

@claude

This comment has been minimized.

@claude

This comment has been minimized.

@claude

This comment has been minimized.

@claude

This comment has been minimized.

@claude

This comment has been minimized.

@claude

This comment has been minimized.

@claude

This comment has been minimized.

@claude

This comment has been minimized.

@claude

This comment has been minimized.

@claude

This comment has been minimized.

@claude

claude Bot commented Sep 20, 2026 •

Copy link
Copy Markdown

Claude finished @jehanazad's task in 1m 56s —— View job


Code Review: Validate real receivers with ADS-B and isolated blind MLAT replay

Todo list

  • Gather context (PR body, changed files, CLAUDE.md, prior review)
  • Diff latest commit (28e8270, "Exercise timestamp calibration tests against registered coverage storage") against previously-reviewed 8b0ae08
  • Re-check prior open findings against current HEAD
  • Check CI status
  • Post findings

New commit 28e8270 — test-only fix, no correctness issues

Touches only backend/tests/test_adsb_capture_timestamps.py and backend/tests/test_adsb_freshness_regressions.py (+10/-2, no production code).

This is a follow-up to the 8b0ae08 change reviewed previously, where record_adsb_calibration/record_claim_calibration started propagating the analytics library's real accept/reject verdict from record_calibration_point(...) instead of always returning success. That verdict only comes back True when the node has a registered EmpiricalCoverageState (see test_calibration.py::TestFanOut::test_missing_coverage_state_is_not_counted_as_a_recorded_point). Both tests touched here call record_adsb_calibration(["node-1"], ...) but never registered "node-1" with state.node_analytics — so post-8b0ae08 they were asserting recorded == 1 against a node with no coverage backing, which (per the library's own contract) should return 0, not 1.

This commit fixes that by:

  • test_adsb_capture_timestamps.py:150-162 — registers an EmpiricalCoverageState(47.9, 16.0, max_range_km=50) for "node-1" via monkeypatch.setitem(state.node_analytics.empirical_coverages, "node-1", coverage), then asserts coverage.n_points == 1 after recording, in addition to the existing recorded == 1 check.
  • test_adsb_freshness_regressions.py:99-127 — same pattern for TestCalibrationSkewStaysOnOneClock::test_a_node_whose_clock_is_off_still_calibrates.

Both changes correctly exercise the real acceptance path end-to-end (recorded count and actual storage), rather than just asserting a return value that could pass without anything having been stored. monkeypatch.setitem scopes the fixture registration to the test, consistent with how the rest of the suite avoids cross-test state leakage on state.node_analytics.

No issues found in this commit.

Prior findings — status unchanged

The four previously-resolved substantive findings (truth-snapshot loss on a radar-less tick, private capture ordering vs. signature verification, replay crashes on bad fc/missing node config, real-node Path 1 claims dropped for incomplete tags) remain resolved as of f5af62e/a63be2e, untouched by this commit. The three low-severity cleanup items remain open and non-blocking (none touched by 28e8270):

  • finite() (backend/services/adsb_truth.py:26) still duplicates is_num() (backend/config/constants.py:26).
  • The redundant node_world lookup in known_claiming.py is unchanged.
  • seeding_references's node_records parameter (backend/services/adsb_truth.py:169) is still only ever called with {} from backend/core/state.py:312, so the "node"-sourced branch stays dead in practice.

Other notes

  • CI for 28e8270 (run 35479773746) was still in progress at review time; all prior commits on this branch were green.
  • No changes under /v1/nodes or backend/services/node_config.py in this commit, so contracts/nodes-v1.openapi.yaml correctly doesn't need regeneration.
  • The libs/retina-analytics submodule content isn't checked out in this sandbox (and network access to fetch it is unavailable here), so the EmpiricalCoverageState/record_calibration_point contract was verified from its usage and existing tests in this repo (test_calibration.py), not from the library source directly.
  • Did not run the backend test suite or pre-commit run --all-files locally (no backend .venv provisioned, and this sandbox declines to run arbitrary scripts non-interactively); relying on CI and the PR author's reported test results for execution.

Summary: This commit is a small, correct test fix that closes a gap left by the previous commit's behavior change — it now verifies calibration points are actually stored, not just that a truthy count is returned. No correctness or security issues found. Nothing here blocks merge; only the pre-existing CI result (in progress) and the previously-noted low-severity cleanups remain open.
· Branch

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