Conversation
Every node's published `empirical_coverage.polygon` is evidence-only, built from calibration points that come from ADS-B hexes bound to tracks. That is right for real hardware — a real node's declared beam azimuth and width are unsurveyed configuration, and drawing them would claim coverage nobody measured — but wrong for the simulator fleet. The simulator emits a detection only for an aircraft inside the node's declared cone, so for a synthetic node the cone IS the detection area by definition, and the evidence is the unreliable half: roughly a third of those hex binds are to the wrong aircraft. Measured on test 2026-09-13, synth-GVL-SCAT-0032 (42 deg beam) held 3,037 calibration points of which 47% lay outside its wedge (34% ignoring the two edge bins), 55 out-of-wedge bins had opened, and the published polygon covered 71 of 72 bearings. Every synthetic node on the test map draws as a disc. So register_node_blocking passes declared_geometry_is_truth=is_synthetic_node to the analytics registration, and the library publishes declared_wedge_polygon() for those nodes instead (retina-analytics 6df7c3d, pinned here). Real nodes are untouched. Both kinds now name their rule in `empirical_coverage.polygon_source`, so no consumer has to guess which shape it was served. is_synthetic_node is imported inside the function: tcp_handler imports this module at import time, the same cycle analytics_refresh and core.state already step around the same way. test_ingest_event_loop's registration stubs take **kwargs — they stand in for the analytics registry, which now receives one more keyword. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The site popup quotes measured coverage and nothing else — the declared beam azimuth, width and range were deliberately removed from it, because most nodes' aim was never surveyed and beside a calibration-point count they read as measurements. That stays true for real nodes. A synthetic node is now served its declared cone as its detection area (the simulator emits a detection only inside it, so it is the detection area by definition), and the backend says which rule it used in `empirical_coverage.polygon_source`. NodeInfo carries it through as `empirical_polygon_source`, and the popup line moves into a pure `coverageLine(node)` helper in nodeSites.ts so the wording is unit-tested rather than buried in JSX: the declared beam is quoted only when the source reads "declared", and "learned" is treated as measured, because it is. Missing on a payload from an older server, where it defaults to "evidence" — the conservative reading, and what every node published then. CoverageLayer needs no change: it draws whatever polygon it is served. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Claude finished @jehanazad's task in 6m 18s —— View job Review of PR #367
Reviewed the diff between 1.
from services.tcp_handler import is_synthetic_node
...
declared_geometry_is_truth=is_synthetic_node(node_id),But
The new tests in 2. Stale comments now contradict the code they document — The Everything else — the |
Summary
Synthetic nodes on the live map were drawing "empirical" detection areas far larger than their beams (every synthetic node near Greenville showed a near-disc for a 42° beam). This makes the backend tell the analytics library which nodes' declared geometry is ground truth — synthetic nodes, by the existing
is_synthetic_nodeprefix rule — so those nodes are served their declared cone, while real nodes keep the evidence-only polygon from #358. The map popup says which it is showing.Depends on offworldlabs/retina-analytics#32 (submodule pin
6df7c3d), which itself is stacked on retina-analytics#28 — that one is still open althoughmainalready pins its head. Merge order: analytics #28, analytics #32, then this.Why the polygons were too big
The simulator emits a detection only for an aircraft inside the node's declared cone, so for a synthetic node the wedge is the detection area. The calibration feed (
track_gates→services/calibration.py) records the ADS-B fix of whichever hex a track bound to, and a steady share of those binds are the wrong aircraft. From the test container's persisted bins (2026-09-13): one 42° node had 3,037 calibration points, 47 % outside its wedge (34 % excluding the two edge bins), 55 out-of-wedge bins open, polygon on 71 of 72 bearings; an eight-node sample ranged 5–41 % fully-outside points, and even the 5 % node opened 15+ stray bearings becauseFOV_OPEN_MIN_POINTSis 3. The evidence-only publisher was faithfully drawing mis-attributed evidence.Changes
services/node_registration.register_node_blockingpassesdeclared_geometry_is_truth=is_synthetic_node(node_id)(function-local import;tcp_handlerimports this module).tests/test_ingest_event_loop.pystubs take**kwargs.6df7c3d:declared_wedge_polygon, the flag, andempirical_coverage.polygon_source("declared"/"evidence"/"learned").NodeInfo.empirical_polygon_source(defaults to"evidence"on an older payload); the popup coverage line moved into a unit-testedcoverageLine()inmap/nodeSites.ts: "Coverage: declared beam (synthetic node), reach ≤ N km" for declared nodes, the existing measured / not-yet-measured lines otherwise.CoverageLayerdraws whatever polygon is served, unchanged.docs/pipeline.md§7 (real-node paragraph kept, synthetic-node paragraph added with the measurement);docs/arc-display.mdcorrected where it said the beam wedge is never drawn.Test coverage
tests/test_node_registration_flag.py(8 new):synth-*→polygon_source == "declared"with a polygon at zero points; allow-listedret…id →"evidence", no polygon until points; the payload carriespolygon_source;public_location.translate_polygonshifts the declared wedge rigidly and leaves no vertex at the true receiver.PYTHONPATH: coverage gate reached at 85.4 %. Two pre-existing, load-sensitive flakes appeared across five runs and pass in isolation:test_mlat_history::test_nearest_gt_survives_concurrent_appends(hang under coverage's collector lock, reproduced on the unmodified tree) andtest_known_lane::TestMaybeRunPass::test_interval_gate_holds_between_passes(a leaked solver worker holds_PASS_LOCK). Neither file is touched here.coverageLine).pre-commit run --all-filesandtests/test_no_real_identities.pypass.SimulationWorld._aircraft_in_detection_coneon 25,919 / 25,921 grid points; both misses are on the boundary.Review notes
node_id,beam_*fields, the solver beam gate and arc clipping remain as in map: one marker per site, node_ref labels, measured coverage only #358's follow-up list.🤖 Generated with Claude Code