MilitaryFlightRecords coerced a missing course to 0 (`courseDeg || 0`)
and a missing speed to 0 (`speedMps ?? 0`) before stickyNumber, so the
hold-last-known-good merge never ran. When an adsb.lol row arrived
without `track` or `gs`, the aircraft's icon swung to north and the
position history got a 0 kt / 0 degree fix, so dead reckoning stopped
or turned it north. The civil FlightRecords holds the last value for
the same input.
Pass the raw values to stickyNumber, with 0 as the fallback only until a
value is known, as before. A grounded row without a speed still reads
0, so a parked aircraft doesn't coast at its landing speed.
OpenSky's state-vector spec allows null longitude/latitude, and every live
/states/all response carries such rows (about 50 of 7,800). The normaliser
rejects them, so admitRecords reported complete: false on effectively every
poll. While a snapshot is incomplete, FlightRecords.absence() retains any
aircraft received in the last five minutes, which meant the stale fade,
MISSING_POLL_LIMIT and the landed fast-cull never ran: vanished and landed
aircraft stayed on the globe at full opacity for up to five minutes.
admitRecords now takes an optional unpositionedId(row). A rejected row it
names is a contact without a position: its id is returned in retainIds and
the row does not count against completeness. OpenSky opts in for rows with an
ICAO24 address and strictly null longitude and latitude; the id comes from
the same helper the normaliser uses. Rows with no usable id, out-of-range or
half-null coordinates, truncated rows and positioned duplicates still make
the snapshot incomplete, and a nonempty feed with no positioned rows is
still malformed.
absence() retains exactly the retainIds aircraft under the existing
five-minute bound, except likely-landed ones, which keep the landed limit.
Everything else ages out normally. Military readsb admission does not opt
in and is unchanged.
fetchPinnedRadioResponse built `new Response(stream, { status })` inside
the raw https response callback, which runs outside the surrounding
promise. A Radio Browser mirror answering 204, 205 or 304 (null-body
statuses), or any status outside 200-599, made the Response constructor
throw. Nothing caught it, so the whole dev/preview server process exited
and every layer went down, not only radio. The catalog's mirror failover
never saw the error.
Mirror the guard local-receivers.js already uses: null-body statuses get
a null body, statuses outside 200-599 are rejected, and anything thrown
in the callback rejects the promise so the existing error handling and
mirror failover run.