fix(status): preserve cached aircraft age and independent refresh errors

This commit is contained in:
Sameh Khamis committed 2026-09-12 17:12:34 -07:00
1 parent 8eb6d09105
commit dae5f45426
9 files changed
+174 -14

No files matched your search

+2 -2
View File
@@ -81,7 +81,7 @@ export function layerFeedState(stats = {}) {
if (
(presentedError || state.unavailable === true || state.available === false)
&& !hasPriorData
&& !['zoom-in', 'empty', 'idle'].includes(status)
&& !GUIDANCE_STATUSES.includes(status)
) {
return 'unavailable';
}
@@ -90,7 +90,7 @@ export function layerFeedState(stats = {}) {
// operation, not feed faults. One honesty carve-out: layers keep their
// rendered records through the guidance state, so a genuinely stale cache
// still reads STALE; a guidance prompt alone never reads DEGRADED.
if (['zoom-in', 'empty', 'idle'].includes(status)) {
if (GUIDANCE_STATUSES.includes(status)) {
return state.stale ? 'stale' : 'nominal';
}
if (
+4 -2
View File
@@ -2833,7 +2833,9 @@ const militaryFlightsLayer = {
_retryAt = 0;
_lastError = null;
const currentIcaos = new Set();
const receiptNowMs = Date.now();
const cacheAgeMs = Number(response.headers?.get?.('x-ads-b-cache-age-ms'));
const receiptNowMs = Date.now() - (Number.isFinite(cacheAgeMs) && cacheAgeMs > 0 ? cacheAgeMs : 0);
_backoff = response.headers?.get?.('x-ads-b-cache') === 'STALE';
// Field-test fix (RS46): coarse floor cells to warm for the below-ground
// clamp — collected during the loop (low airborne contacts only),
// batch-resolved once after it. Never a fetch inside the loop.
@@ -3215,7 +3217,7 @@ const militaryFlightsLayer = {
registerMilitaryIcaos(currentIcaos);
_count = _billboards.size;
_lastUpdate = Date.now();
_lastUpdate = receiptNowMs;
_lastTrackingRefreshOutcome = {
epoch: trackingRefreshEpoch,
status: 'accepted',
+43
View File
@@ -219,6 +219,49 @@ test('military poll refreshes tracked callsign/altitude/kts and marks a missed p
}
});
test('cached military snapshots retain their observation time and expose stale fallback', async (t) => {
let now = 1_800_000_000_000;
const receivedAt = now;
const history = [];
const viewer = { camera: { positionCartographic: null }, scene: {} };
_setTrackedMilitaryRefreshStateForTest({
icao24: 'ae01ce',
entity: { gevLabelModel: { title: 'RCH451', details: [] } },
billboard: { position: Cesium.Cartesian3.fromDegrees(-97, 31, 8000), show: false },
billboardCollection: { show: false, remove() {} },
viewer,
history,
meta: { rawLat: 31, rawLon: -97, onGround: false },
});
let cache = 'MISS';
t.mock.method(Date, 'now', () => now);
t.mock.method(globalThis, 'fetch', async () => Response.json({
ac: [{ hex: 'ae01ce', lon: -97, lat: 31, alt_baro: 28000,
track: 95, gs: 400, seen: 1, seen_pos: 2, flight: 'RCH451' }],
}, { headers: {
'X-ADS-B-Cache': cache,
...(cache === 'MISS' ? {} : { 'X-ADS-B-Cache-Age-Ms': String(now - receivedAt) }),
} }));
await militaryFlightsLayer.update(viewer);
assert.equal(history.length, 1);
assert.equal(history[0].epochMs, receivedAt - 2000);
for (const [status, age] of [['HIT', 5000], ['STALE', 45000]]) {
cache = status;
now = receivedAt + age;
await militaryFlightsLayer.update(viewer);
assert.equal(history.length, 1, 'replayed positions cannot create a new fix');
assert.equal(militaryFlightsLayer.getStats().lastUpdate, receivedAt);
assert.equal(militaryFlightsLayer.getStats().stale, status === 'STALE');
assert.equal(militaryFlightsLayer.getStats().error, null);
}
cache = 'MISS';
now += 15000;
await militaryFlightsLayer.update(viewer);
assert.equal(history.length, 2, 'fresh data resumes position updates');
assert.equal(militaryFlightsLayer.getStats().lastUpdate, now);
assert.equal(militaryFlightsLayer.getStats().stale, false);
});
test('real military track path creates no native label and publishes every cached host line', () => {
const icao24 = 'ae01ce';
const position = Cesium.Cartesian3.fromDegrees(-97.03, 31.05, 8_534.4);