Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 32 additions & 19 deletions retina_simulation/orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,37 @@ def build_ground_truth_payload(aircraft_summaries: list[dict]) -> list[dict]:
return payload_aircraft


def build_adsb_push_payload(aircraft_summaries: list[dict]) -> list[dict]:
"""Remap world aircraft summaries to the server ADS-B push schema.

Transponder-equipped aircraft only. This push IS the simulated ADS-B
broadcast, and a dark target by definition emits none — pushing it with
its object id standing in for the hex minted a fake transponder per dark
aircraft on the server, so every dark solve keyed mn-adsb-* and the dark
lane stayed permanently empty. Dark aircraft still reach the server
through the ground-truth push, where the object id is the intended key
(build_ground_truth_payload above).
"""
payload_aircraft = []
for ac in aircraft_summaries:
hex_code = ac.get("adsb_hex") or ""
if not hex_code:
continue
speed_ms = ac.get("speed_ms", 0)
payload_aircraft.append(
{
"hex": hex_code,
"flight": "",
"lat": round(ac["lat"], 5),
"lon": round(ac["lon"], 5),
"alt_baro": round(ac["alt_km"] * 1000 / 0.3048),
"gs": round(speed_ms * 1.94384, 1),
"track": round(ac.get("heading", 0), 1),
}
)
return payload_aircraft


async def _push_ground_truth_live(
orchestrator: FleetOrchestrator,
base_url: str,
Expand Down Expand Up @@ -971,25 +1002,7 @@ async def _push_adsb_live(

try:
aircraft_raw = orchestrator.world.get_aircraft_summary()
payload_aircraft = []
for ac in aircraft_raw:
# Push ALL aircraft — ADS-B and dark/drone/anomalous alike.
# ADS-B aircraft use their transponder hex; others use object_id.
hex_code = ac.get("adsb_hex") or ac.get("id", "")
if not hex_code:
continue
speed_ms = ac.get("speed_ms", 0)
payload_aircraft.append(
{
"hex": hex_code,
"flight": "",
"lat": round(ac["lat"], 5),
"lon": round(ac["lon"], 5),
"alt_baro": round(ac["alt_km"] * 1000 / 0.3048),
"gs": round(speed_ms * 1.94384, 1),
"track": round(ac.get("heading", 0), 1),
}
)
payload_aircraft = build_adsb_push_payload(aircraft_raw)

if not payload_aircraft:
continue
Expand Down
56 changes: 56 additions & 0 deletions tests/test_adsb_push_payload.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""ADS-B push payload schema (build_adsb_push_payload).

The push is the simulated ADS-B broadcast, so it must carry transponder
aircraft only. The old inline loop substituted the object id for a missing
hex, which minted a fake transponder per dark aircraft on the server — every
dark solve then keyed mn-adsb-* and the server's dark lane stayed empty.
"""

from retina_simulation.orchestrator import build_adsb_push_payload


def _summary(**overrides) -> dict:
base = {
"id": "obj-0001",
"lat": 34.85,
"lon": -82.4,
"alt_km": 9.5,
"heading": 270.0,
"speed_ms": 230.0,
"has_adsb": True,
"is_anomalous": False,
"object_type": "aircraft",
"adsb_hex": "a1b2c3",
"adsb_callsign": "ABC1234",
"anomaly_event": None,
}
base.update(overrides)
return base


class TestBuildAdsbPushPayload:
def test_transponder_aircraft_pushed_under_its_hex(self):
out = build_adsb_push_payload([_summary()])
assert len(out) == 1
entry = out[0]
assert entry["hex"] == "a1b2c3"
assert entry["lat"] == 34.85
assert entry["alt_baro"] == round(9500 / 0.3048)
assert entry["gs"] == round(230.0 * 1.94384, 1)
assert entry["track"] == 270.0

def test_dark_aircraft_not_pushed(self):
"""The regression: an aircraft with no adsb_hex must not appear at
all — above all not under its object id."""
out = build_adsb_push_payload([_summary(has_adsb=False, adsb_hex=None, adsb_callsign=None)])
assert out == []

def test_mixed_fleet_keeps_only_transponder_aircraft(self):
out = build_adsb_push_payload(
[
_summary(),
_summary(id="obj-0002", has_adsb=False, adsb_hex=None),
_summary(id="obj-0003", adsb_hex="d4e5f6"),
]
)
assert [e["hex"] for e in out] == ["a1b2c3", "d4e5f6"]
Loading