fix(military): hold the last track and speed when a poll omits them

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.
This commit is contained in:
Aditya committed 2026-10-16 16:52:22 +05:30
1 parent d4492d8f8c
commit 557d3bb41e
4 files changed
+52 -5

No files matched your search

+4
View File
@@ -1,5 +1,9 @@
# Changelog # Changelog
- Military aircraft keep their last heading and speed when an adsb.lol row omits
`track` or `gs`, instead of turning north and stopping; a grounded row without
a speed still reads 0.
- Add Barcelona's bike share to the GBFS registry: Bicing (543 stations, the - Add Barcelona's bike share to the GBFS registry: Bicing (543 stations, the
city's PBSC system) and AMBici (237 e-bike stations across the metropolitan city's PBSC system) and AMBici (237 e-bike stations across the metropolitan
area, run by nextbike). Both are keyless GBFS 2.3 feeds listed in area, run by nextbike). Both are keyless GBFS 2.3 feeds listed in
+3 -1
View File
@@ -584,7 +584,9 @@ pinning, first-position grace, source time and sea-surface placement are unchang
Military flights also separate portable records and source acquisition from Military flights also separate portable records and source acquisition from
Cesium snapshot application. The metadata owner retains aviation feet, source Cesium snapshot application. The metadata owner retains aviation feet, source
fix times, sticky fields and partial-feed retention. Rendering retains ground fix times, sticky fields and partial-feed retention. A row without track or
speed holds the last known value, as civil records do; a grounded row without
speed reads 0. Rendering retains ground
model ownership, motion history, military registration and tracking cleanup. model ownership, motion history, military registration and tracking cleanup.
Civil flights separate source acquisition, portable record reconciliation and Civil flights separate source acquisition, portable record reconciliation and
+9 -4
View File
@@ -39,8 +39,6 @@ export class MilitaryFlightRecords {
? prevMeta.altitudeFt * 0.3048 ? prevMeta.altitudeFt * 0.3048
: 0 : 0
: 3048); : 3048);
const track = aircraft.courseDeg || 0;
const speedMps = aircraft.speedMps ?? 0;
const verticalRateMps = aircraft.verticalRateMps; const verticalRateMps = aircraft.verticalRateMps;
const callsign = aircraft.callsign; const callsign = aircraft.callsign;
const type = aircraft.typeCode; const type = aircraft.typeCode;
@@ -148,8 +146,15 @@ export class MilitaryFlightRecords {
// altitudeFt/baro). // altitudeFt/baro).
geoAltitudeM, geoAltitudeM,
renderAltitudeM, renderAltitudeM,
speedMps: stickyNumber(speedMps, prevMeta?.speedMps, null), // A poll that omits track/speed holds the last values, like the civil
track: stickyNumber(track, prevMeta?.track, null), // records; 0 only until one is known. A grounded row without a speed
// reads 0, so a parked plane never coasts at its landing speed.
speedMps: stickyNumber(
aircraft.speedMps,
onGround ? 0 : prevMeta?.speedMps,
0,
),
track: stickyNumber(aircraft.courseDeg, prevMeta?.track, 0),
// Analyst seam (additive): sticky like the other kinematics. // Analyst seam (additive): sticky like the other kinematics.
verticalRateMps: stickyNumber( verticalRateMps: stickyNumber(
verticalRateMps, verticalRateMps,
+36
View File
@@ -114,3 +114,39 @@ test('military emergency status follows the latest poll instead of sticking', ()
); );
assert.equal(next.meta.emergency, null); assert.equal(next.meta.emergency, null);
}); });
test('military records hold the last track and speed when a poll omits them', () => {
const records = store();
records.receive(observation({ courseDeg: 270, speedMps: 200 }), context());
const missing = records.receive(
observation({ courseDeg: null, speedMps: null }),
context(),
);
assert.equal(missing.meta.track, 270);
assert.equal(missing.meta.speedMps, 200);
const north = records.receive(
observation({ courseDeg: 0, speedMps: 0 }),
context(),
);
assert.equal(north.meta.track, 0);
assert.equal(north.meta.speedMps, 0);
const unseen = store().receive(
observation({ courseDeg: undefined, speedMps: undefined }),
context(),
);
assert.equal(unseen.meta.track, 0);
assert.equal(unseen.meta.speedMps, 0);
const landing = store();
landing.receive(observation({ courseDeg: 90, speedMps: 70 }), context());
const parked = landing.receive(
observation({
onGround: true,
baroAltitudeM: null,
courseDeg: null,
speedMps: null,
}),
context(),
);
assert.equal(parked.meta.speedMps, 0);
assert.equal(parked.meta.track, 90);
});