Merge pull request #1096 from Rohilalala/fix/military-sticky-track

fix(military): hold the last track and speed when a poll omits them
This commit is contained in:
Sameh Khamis authored and GitHub committed 2026-10-16 14:43:59 -07:00
commit bb00a13b03
4 files changed
+51 -5

No files matched your search

@@ -0,0 +1,3 @@
- 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 (Aditya, @Rohilalala, #1096).
+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);
});