mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-10 21:32:23 +00:00
Merge pull request #1656 from bilawalsidhu/sameh/signal-lost-followups
fix(flights): a stalled feed dims the tracked aircraft as STALE
This commit is contained in:
6 files changed
+52
-9
No files matched your search
@@ -0,0 +1,3 @@
|
||||
- A tracked aircraft is dimmed as STALE while the whole flight feed is
|
||||
stalled, as it is after a missed update, instead of staying at full
|
||||
brightness (Sameh Khamis, @samehkhamis, #1656).
|
||||
@@ -5793,7 +5793,7 @@ silently demoting every later lookup for the session.
|
||||
|
||||
### Motion & Symbology Correctness (June 10, 2026)
|
||||
|
||||
- **Flights (commercial + military)** render one poll interval behind real time (30s/15s) and interpolate between two known feed-stamped fixes (OpenSky `time_position`; adsb.lol `receipt − seen_pos`). The display latency is an intentional product decision — do not "fix" it away. The fleet dead-reckons at ~12Hz (1m² write gating); a contact already hidden behind the horizon whose last position still is re-reckons on a staggered one-in-13-ticks rotation (about once a second) instead, so the worldwide feed's ~13k contacts no longer cost a full pass when well under 1% are on screen. Anything that position puts in view, and any contact with a fleet model, takes the full pass immediately, and hidden-inclusive proximity queries read positions at most about a second old; aircraft get a 3-poll grace period (faded icon) before removal, except the tracked aircraft, which is kept until 10 minutes after its newest fix. When a position epoch pauses, both layers coast for five minutes on the last speed and heading (at most 1,500 kt), then stop dimmed as SIGNAL LOST; contacts on or near known ground hold instead. Source backoff marks each contact and the cockpit `STALE`; the cockpit then holds the exact layer position instead of continuing inertial flight. Repeated-position kinematic changes create a forward-only synthetic fix rather than mutating history, and grounded history is lifted only when no owned 3D model already controls its datum. A nominally successful worldwide OpenSky response whose own snapshot epoch is more than two minutes old prefers the existing 250-nm viewport-scoped adsb.lol fallback and labels the source/coverage accordingly when that upstream is available. If the fallback is also unavailable, the layer's freshness/error fields use the source epoch—never the cache receipt time—so the UI reports an old snapshot rather than “just now.”
|
||||
- **Flights (commercial + military)** render one poll interval behind real time (30s/15s) and interpolate between two known feed-stamped fixes (OpenSky `time_position`; adsb.lol `receipt − seen_pos`). The display latency is an intentional product decision — do not "fix" it away. The fleet dead-reckons at ~12Hz (1m² write gating); a contact already hidden behind the horizon whose last position still is re-reckons on a staggered one-in-13-ticks rotation (about once a second) instead, so the worldwide feed's ~13k contacts no longer cost a full pass when well under 1% are on screen. Anything that position puts in view, and any contact with a fleet model, takes the full pass immediately, and hidden-inclusive proximity queries read positions at most about a second old; aircraft get a 3-poll grace period (faded icon) before removal, except the tracked aircraft, which is kept until 10 minutes after its newest fix. When a position epoch pauses, both layers coast for five minutes on the last speed and heading (at most 1,500 kt), then stop dimmed as SIGNAL LOST; contacts on or near known ground hold instead. Source backoff marks each contact and the cockpit `STALE` and dims the tracked aircraft, as a missed update does; the cockpit then holds the exact layer position instead of continuing inertial flight. Repeated-position kinematic changes create a forward-only synthetic fix rather than mutating history, and grounded history is lifted only when no owned 3D model already controls its datum. A nominally successful worldwide OpenSky response whose own snapshot epoch is more than two minutes old prefers the existing 250-nm viewport-scoped adsb.lol fallback and labels the source/coverage accordingly when that upstream is available. If the fallback is also unavailable, the layer's freshness/error fields use the source epoch—never the cache receipt time—so the UI reports an old snapshot rather than “just now.”
|
||||
- **World-space headings at every angle** (`src/data/iconOrientation.js`): aircraft/vessel icon rotation uses the camera right/up basis per tick (alignedAxis always ZERO), which is exact at screen center and for orthographic/nadir views and remains stable through >180° tracked orbits. Perspective rays vary across the viewport, so off-center contacts at oblique pitch can diverge from an exact finite-difference window projection; a regression test pins that known regime, and field evidence decides whether to adopt exact projection with the basis method as fallback. Fleet rotations refresh on camera-pose change; tracked entities per frame. Billboards are horizon-culled via a shared EllipsoidalOccluder.
|
||||
- **Military/OpenSky reconciliation** (`src/data/militaryRegistry.js`): known-military ICAOs render amber in the flights layer (60s self-poll of the cached mil endpoint when the military layer is off) and are suppressed there while the military layer renders them.
|
||||
- **Satellites**: core catalog from CelesTrak's stations, visual, GPS, GLONASS, Galileo, GEO, resource, weather, planet, SAR and science groups, tracking lands ~726km out via `viewFrom` (tracked entity owns a point graphic so the tracking camera engages), rings realign via primitive modelMatrix (no per-second rebuild flicker), optional `setParams({catalog:'dense'})` Starlink mode.
|
||||
|
||||
@@ -859,8 +859,8 @@ export function createTracking({
|
||||
* - SIGNAL LOST · <time since its last report>, dimmed: its coast has ended
|
||||
* (or it holds at its last report after a missed update) and no newer
|
||||
* report has come.
|
||||
* - STALE, dimmed: it has missed an update and still coasts.
|
||||
* - STALE, not dimmed: the whole feed is backing off.
|
||||
* - STALE, dimmed: it has missed an update and still coasts, or the whole
|
||||
* feed is backing off.
|
||||
* @param {string} icao24
|
||||
* @returns {{text: string, dimmed: boolean}}
|
||||
*/
|
||||
@@ -902,8 +902,8 @@ export function createTracking({
|
||||
: `${formatNumber(Math.floor(sec / 60), { useGrouping: false })} ${unitLabel('min')}`;
|
||||
return { text: t('SIGNAL LOST · {time}', { time }), dimmed: true };
|
||||
}
|
||||
if (missed) return { text: t('STALE'), dimmed: true };
|
||||
if (backoff) return { text: t('STALE'), dimmed: false };
|
||||
// A missed update or a stalled feed: no newer position is coming in.
|
||||
if (missed || backoff) return { text: t('STALE'), dimmed: true };
|
||||
return { text: '', dimmed: false };
|
||||
}
|
||||
|
||||
|
||||
@@ -791,6 +791,26 @@ test('a repeated old fix counts as a missed update for the tracked plane', () =>
|
||||
}
|
||||
});
|
||||
|
||||
test('a stalled feed dims the tracked plane as STALE', () => {
|
||||
const { fixes, info } = outOfCoverage();
|
||||
const h = harness({ fixes, info, nowMs: pastReport(-5) });
|
||||
try {
|
||||
h.tracking._startTrail(ICAO);
|
||||
h.run(pastReport(-2));
|
||||
// The whole feed is backing off: no poll counts a miss, and no newer
|
||||
// position is coming in.
|
||||
h.flightState.feed._backoff = true;
|
||||
assert.deepEqual(h.tracking._trackedStaleCue(ICAO), {
|
||||
text: 'STALE',
|
||||
dimmed: true,
|
||||
});
|
||||
assert.equal(h.tracking._trackedIconColor().alpha, STALE_ALPHA);
|
||||
assert.equal(h.flightState.records.missingPolls.has(ICAO), false);
|
||||
} finally {
|
||||
h.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('a report arriving after the plane has stopped: it eases in with no spur, and the trail keeps everything up to the stop', () => {
|
||||
const { fixes, info } = outOfCoverage();
|
||||
info.lastContactEpochMs = fixes.at(-1).epochMs;
|
||||
|
||||
@@ -230,8 +230,8 @@ export function createTracking({
|
||||
* - SIGNAL LOST · <time since its last report>, dimmed: its coast has ended
|
||||
* (or it holds at its last report after a missed update) and no newer
|
||||
* report has come.
|
||||
* - STALE, dimmed: it has missed an update and still coasts.
|
||||
* - STALE, not dimmed: the whole feed is backing off.
|
||||
* - STALE, dimmed: it has missed an update and still coasts, or the whole
|
||||
* feed is backing off.
|
||||
* @param {string} icao24
|
||||
* @returns {{text: string, dimmed: boolean}}
|
||||
*/
|
||||
@@ -273,8 +273,8 @@ export function createTracking({
|
||||
: `${formatNumber(Math.floor(sec / 60), { useGrouping: false })} ${unitLabel('min')}`;
|
||||
return { text: t('SIGNAL LOST · {time}', { time }), dimmed: true };
|
||||
}
|
||||
if (missed) return { text: t('STALE'), dimmed: true };
|
||||
if (backoff) return { text: t('STALE'), dimmed: false };
|
||||
// A missed update or a stalled feed: no newer position is coming in.
|
||||
if (missed || backoff) return { text: t('STALE'), dimmed: true };
|
||||
return { text: '', dimmed: false };
|
||||
}
|
||||
|
||||
|
||||
@@ -642,6 +642,26 @@ test('military: a repeated old fix counts as a missed update for the tracked pla
|
||||
}
|
||||
});
|
||||
|
||||
test('military: a stalled feed dims the tracked plane as STALE', () => {
|
||||
const { fixes, info } = outOfCoverage();
|
||||
const h = harness({ fixes, info, nowMs: pastReport(-5) });
|
||||
try {
|
||||
h.tracking._startTrail(ICAO);
|
||||
h.run(pastReport(-2));
|
||||
// The whole feed is backing off: no poll counts a miss, and no newer
|
||||
// position is coming in.
|
||||
h.flightState.feed._backoff = true;
|
||||
assert.deepEqual(h.tracking._trackedStaleCue(ICAO), {
|
||||
text: 'STALE',
|
||||
dimmed: true,
|
||||
});
|
||||
assert.equal(h.tracking._trackedIconColor().alpha, STALE_ALPHA);
|
||||
assert.equal(h.flightState.records.missingPolls.has(ICAO), false);
|
||||
} finally {
|
||||
h.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('military: a report arriving after the plane has stopped: it eases in with no spur, and the trail keeps everything up to the stop', () => {
|
||||
const { fixes, info } = outOfCoverage();
|
||||
info.lastContactEpochMs = fixes.at(-1).epochMs;
|
||||
|
||||
Reference in new issue
Block a user