mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-10 21:32:23 +00:00
Merge pull request #870 from waar19/perf/fleet-tick-skip-hidden
perf(flights): skip dead-reckoning contacts that stay behind the horizon
This commit is contained in:
7 files changed
+228
-1
No files matched your search
@@ -0,0 +1,4 @@
|
|||||||
|
- Reduce dead-reckoning work for flights already hidden behind the horizon.
|
||||||
|
Hidden contacts update on a staggered one-in-13-ticks rotation (about once a
|
||||||
|
second) while visible contacts and contacts with fleet models retain the full
|
||||||
|
update path (Wilmer Alzate, #870).
|
||||||
@@ -5350,7 +5350,7 @@ silently demoting every later lookup for the session.
|
|||||||
|
|
||||||
### Motion & Symbology Correctness (June 10, 2026)
|
### 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 whole fleet dead-reckons at ~12Hz (1m² write gating); aircraft get a 3-poll grace period (faded icon) before removal. When a position epoch pauses, both layers coast for at least 60 seconds of contact grace with an absolute five-minute ceiling. 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. When a position epoch pauses, both layers coast for at least 60 seconds of contact grace with an absolute five-minute ceiling. 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.”
|
||||||
- **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.
|
- **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.
|
- **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**: 838-sat core catalog (stations/visual/GPS/GLONASS/Galileo/GEO), 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.
|
- **Satellites**: 838-sat core catalog (stations/visual/GPS/GLONASS/Galileo/GEO), 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.
|
||||||
|
|||||||
@@ -347,6 +347,7 @@
|
|||||||
"src/layers/flights/tracking.js",
|
"src/layers/flights/tracking.js",
|
||||||
"src/layers/flights/rendering.js",
|
"src/layers/flights/rendering.js",
|
||||||
"src/layers/flights/testing.js",
|
"src/layers/flights/testing.js",
|
||||||
|
"src/layers/flights/hiddenContactRotation.test.mjs",
|
||||||
"src/layers/flights/evidence.js",
|
"src/layers/flights/evidence.js",
|
||||||
"src/layers/flights/ingestion.js",
|
"src/layers/flights/ingestion.js",
|
||||||
"src/layers/flights/queries.js",
|
"src/layers/flights/queries.js",
|
||||||
|
|||||||
@@ -0,0 +1,163 @@
|
|||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import * as Cesium from 'cesium';
|
||||||
|
import { createFlightState } from './state.js';
|
||||||
|
import { createRendering } from './rendering.js';
|
||||||
|
import {
|
||||||
|
FLEET_DR_INTERVAL_MS,
|
||||||
|
HIDDEN_CONTACT_DR_INTERVAL_MS,
|
||||||
|
HIDDEN_CONTACT_DR_TICKS,
|
||||||
|
hiddenContactDrPhase,
|
||||||
|
} from './policy.js';
|
||||||
|
|
||||||
|
// Contacts hidden behind the horizon re-reckon on one fleet tick per rotation
|
||||||
|
// instead of every tick. Their positions still feed hidden-inclusive proximity
|
||||||
|
// queries, so the rotation must reach every contact about once a second, and
|
||||||
|
// it must spread them across ticks so no single tick carries the whole fleet.
|
||||||
|
|
||||||
|
const icao = (n) => n.toString(16).padStart(6, '0');
|
||||||
|
|
||||||
|
test('the rotation revisits each hidden contact about once a second', () => {
|
||||||
|
const periodMs = HIDDEN_CONTACT_DR_TICKS * FLEET_DR_INTERVAL_MS;
|
||||||
|
assert.ok(HIDDEN_CONTACT_DR_TICKS > 1);
|
||||||
|
assert.ok(
|
||||||
|
Math.abs(periodMs - HIDDEN_CONTACT_DR_INTERVAL_MS) <= FLEET_DR_INTERVAL_MS,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a contact keeps the same slot, inside the rotation', () => {
|
||||||
|
for (let n = 0; n < 500; n++) {
|
||||||
|
const phase = hiddenContactDrPhase(icao(n * 7919));
|
||||||
|
assert.ok(Number.isInteger(phase));
|
||||||
|
assert.ok(phase >= 0 && phase < HIDDEN_CONTACT_DR_TICKS);
|
||||||
|
assert.equal(hiddenContactDrPhase(icao(n * 7919)), phase);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a worldwide fleet is spread evenly enough that no tick carries a burst', () => {
|
||||||
|
const fleet = 13000;
|
||||||
|
const perSlot = new Array(HIDDEN_CONTACT_DR_TICKS).fill(0);
|
||||||
|
// Real ICAO24 addresses cluster by national block; sample several blocks.
|
||||||
|
for (let n = 0; n < fleet; n++)
|
||||||
|
perSlot[hiddenContactDrPhase(icao(0x0a0000 + n * 53))]++;
|
||||||
|
const fair = fleet / HIDDEN_CONTACT_DR_TICKS;
|
||||||
|
for (const count of perSlot) assert.ok(count < fair * 1.5, `${count} > 1.5×`);
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Real fleet tick and horizon culling, with motion and presentation injected. */
|
||||||
|
function fleetFixture() {
|
||||||
|
let now = 1000;
|
||||||
|
let reckonings = 0;
|
||||||
|
const services = {
|
||||||
|
groundSnap: { createGroundSnap: () => ({ clear() {} }) },
|
||||||
|
militaryRegistry: { isMilitaryIcao: () => false },
|
||||||
|
aircraftPresentation: { isTr3b: () => false },
|
||||||
|
camera: {},
|
||||||
|
focus: {
|
||||||
|
focusNowMs: () => now,
|
||||||
|
getFocusTarget: () => null,
|
||||||
|
nearFarScalarValueAtDistance: () => 1,
|
||||||
|
advanceProjectedSpriteFocus: () => ({ factor: 1 }),
|
||||||
|
},
|
||||||
|
recession: {
|
||||||
|
applyAircraftBillboardTreatment: () => ({
|
||||||
|
factors: { scale: 1 },
|
||||||
|
alpha: 1,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const flightState = createFlightState({ services });
|
||||||
|
const camera = {
|
||||||
|
positionWC: Cesium.Cartesian3.fromDegrees(0, 0, 100),
|
||||||
|
positionCartographic: { height: 100 },
|
||||||
|
heading: 0,
|
||||||
|
pitch: 0,
|
||||||
|
roll: 0,
|
||||||
|
frustum: {
|
||||||
|
computeCullingVolume: () => ({
|
||||||
|
computeVisibility: () => Cesium.Intersect.INSIDE,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const id = 'abc123';
|
||||||
|
// 111 km away at ground level is beyond a 100 m camera's horizon, but
|
||||||
|
// inside the model keep radius. No WebGL scene is needed for the culler.
|
||||||
|
const billboard = {
|
||||||
|
show: false,
|
||||||
|
position: Cesium.Cartesian3.fromDegrees(1, 0, 0),
|
||||||
|
};
|
||||||
|
flightState._viewer = { camera, scene: {} };
|
||||||
|
flightState._billboardCollection = { show: true };
|
||||||
|
flightState._billboards.set(id, billboard);
|
||||||
|
flightState.records.data.set(id, {});
|
||||||
|
flightState._models3dEnabled = false;
|
||||||
|
// Far cockpit contacts avoid glyph rasterization, keeping the fixture DOM-free.
|
||||||
|
flightState._cockpitContactMode = true;
|
||||||
|
const parts = {
|
||||||
|
tracking: { _refreshCockpitNearContacts() {} },
|
||||||
|
motion: {
|
||||||
|
_deadReckon() {
|
||||||
|
reckonings++;
|
||||||
|
return billboard.position;
|
||||||
|
},
|
||||||
|
_floorGroundedDisplayPosition(id, info, position) {
|
||||||
|
return position;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const rendering = createRendering({ flightState, services, parts });
|
||||||
|
return {
|
||||||
|
flightState,
|
||||||
|
camera,
|
||||||
|
billboard,
|
||||||
|
id,
|
||||||
|
reckonings: () => reckonings,
|
||||||
|
tick() {
|
||||||
|
now += FLEET_DR_INTERVAL_MS;
|
||||||
|
rendering._fleetTick();
|
||||||
|
},
|
||||||
|
offTurn() {
|
||||||
|
flightState._fleetTickSeq =
|
||||||
|
(hiddenContactDrPhase(id) + 1) % HIDDEN_CONTACT_DR_TICKS;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test('the fleet tick dead-reckons a hidden contact once per complete rotation', () => {
|
||||||
|
const f = fleetFixture();
|
||||||
|
for (let i = 0; i < HIDDEN_CONTACT_DR_TICKS; i++) f.tick();
|
||||||
|
assert.equal(f.reckonings(), 1);
|
||||||
|
assert.equal(f.billboard.show, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a camera reveal bypasses the hidden rotation on the same fleet tick', () => {
|
||||||
|
const f = fleetFixture();
|
||||||
|
f.offTurn();
|
||||||
|
f.tick();
|
||||||
|
assert.equal(f.reckonings(), 0);
|
||||||
|
f.camera.positionWC = Cesium.Cartesian3.fromDegrees(1, 0, 100);
|
||||||
|
f.offTurn();
|
||||||
|
f.tick();
|
||||||
|
assert.equal(f.reckonings(), 1);
|
||||||
|
assert.equal(f.billboard.show, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a hidden contact with a retained fleet model is never rotation-skipped', () => {
|
||||||
|
const f = fleetFixture();
|
||||||
|
f.flightState._models3dEnabled = true;
|
||||||
|
const model = { ready: true, show: true };
|
||||||
|
f.flightState._models.set(f.id, model);
|
||||||
|
f.offTurn();
|
||||||
|
f.tick();
|
||||||
|
assert.equal(f.reckonings(), 1);
|
||||||
|
assert.equal(
|
||||||
|
f.flightState._models.get(f.id),
|
||||||
|
model,
|
||||||
|
'model remains in keep range',
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
model.show,
|
||||||
|
false,
|
||||||
|
'the full pass hides its model beyond the horizon',
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -198,6 +198,37 @@ export const FLEET_DR_INTERVAL_MS = 80;
|
|||||||
|
|
||||||
export const ROTATION_REFRESH_MS = 1000;
|
export const ROTATION_REFRESH_MS = 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @constant {number} Dead-reckoning cadence (ms) for contacts hidden behind the
|
||||||
|
* horizon. The worldwide feed carries ~13k contacts and a zoomed-in view shows
|
||||||
|
* well under 1% of them; the rest still need positions for hidden-inclusive
|
||||||
|
* proximity queries, but ~1 s old (≈250 m for an airliner) is well inside the
|
||||||
|
* 30 s poll's own uncertainty. Each hidden contact is re-reckoned on one tick in
|
||||||
|
* every HIDDEN_CONTACT_DR_TICKS, staggered by id so no tick carries them all.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const HIDDEN_CONTACT_DR_INTERVAL_MS = 1000;
|
||||||
|
|
||||||
|
export const HIDDEN_CONTACT_DR_TICKS = Math.max(
|
||||||
|
1,
|
||||||
|
Math.round(HIDDEN_CONTACT_DR_INTERVAL_MS / FLEET_DR_INTERVAL_MS),
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stable slot in [0, HIDDEN_CONTACT_DR_TICKS) for a contact id, so each hidden
|
||||||
|
* contact re-reckons on the same tick of every rotation.
|
||||||
|
* @param {string} id Contact id (ICAO24).
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function hiddenContactDrPhase(id) {
|
||||||
|
const text = String(id);
|
||||||
|
let hash = 0;
|
||||||
|
for (let i = 0; i < text.length; i++)
|
||||||
|
hash = (hash * 31 + text.charCodeAt(i)) >>> 0;
|
||||||
|
return hash % HIDDEN_CONTACT_DR_TICKS;
|
||||||
|
}
|
||||||
|
|
||||||
/** Max course slew (deg/s) — well above real turns (≤4°/s), hides fix-boundary
|
/** Max course slew (deg/s) — well above real turns (≤4°/s), hides fix-boundary
|
||||||
* steps. Scaled down toward COURSE_MIN_DPS at low speed (courseSlewCapDps). */
|
* steps. Scaled down toward COURSE_MIN_DPS at low speed (courseSlewCapDps). */
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ import {
|
|||||||
COURSE_SLEW_DT_MAX_SEC,
|
COURSE_SLEW_DT_MAX_SEC,
|
||||||
ROTATION_REFRESH_MS,
|
ROTATION_REFRESH_MS,
|
||||||
COURSE_MAX_DPS,
|
COURSE_MAX_DPS,
|
||||||
|
HIDDEN_CONTACT_DR_TICKS,
|
||||||
|
hiddenContactDrPhase,
|
||||||
} from './policy.js';
|
} from './policy.js';
|
||||||
import { AIRCRAFT_ENVIRONMENT_MAP_OPTIONS } from '../../data/aircraftModelLighting.js';
|
import { AIRCRAFT_ENVIRONMENT_MAP_OPTIONS } from '../../data/aircraftModelLighting.js';
|
||||||
|
|
||||||
@@ -839,6 +841,12 @@ export function createRendering({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The contact's hidden-rotation slot, cached on its billboard. */
|
||||||
|
function _hiddenDrPhase(bb, icao24) {
|
||||||
|
bb._gevHiddenDrPhase ??= hiddenContactDrPhase(icao24);
|
||||||
|
return bb._gevHiddenDrPhase;
|
||||||
|
}
|
||||||
|
|
||||||
function _fleetTick() {
|
function _fleetTick() {
|
||||||
if (
|
if (
|
||||||
!flightState._viewer ||
|
!flightState._viewer ||
|
||||||
@@ -965,9 +973,25 @@ export function createRendering({
|
|||||||
for (const icao of toRelease) _releaseModel(icao);
|
for (const icao of toRelease) _releaseModel(icao);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hiddenDrPhase = flightState._fleetTickSeq++ % HIDDEN_CONTACT_DR_TICKS;
|
||||||
|
|
||||||
for (const [icao24, bb] of flightState._billboards) {
|
for (const [icao24, bb] of flightState._billboards) {
|
||||||
if (icao24 === flightState._trackedIcao) continue; // tracked entity owns its own motion
|
if (icao24 === flightState._trackedIcao) continue; // tracked entity owns its own motion
|
||||||
|
|
||||||
|
// A contact still behind the horizon at its last position would only be
|
||||||
|
// re-reckoned to be hidden again, so off its turn it keeps that position
|
||||||
|
// (see HIDDEN_CONTACT_DR_TICKS). Anything the stale position puts in view,
|
||||||
|
// and any contact with a fleet model, takes the full pass below at once.
|
||||||
|
if (
|
||||||
|
!bb.show &&
|
||||||
|
_hiddenDrPhase(bb, icao24) !== hiddenDrPhase &&
|
||||||
|
!flightState._models.has(icao24) &&
|
||||||
|
!occluder.isPointVisible(
|
||||||
|
flightState._cullPositions.get(icao24) || bb.position,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
continue;
|
||||||
|
|
||||||
const info = flightState.records.data.get(icao24);
|
const info = flightState.records.data.get(icao24);
|
||||||
|
|
||||||
const dr = parts.motion._deadReckon(
|
const dr = parts.motion._deadReckon(
|
||||||
|
|||||||
@@ -204,6 +204,10 @@ export function createFlightState({ source, services }) {
|
|||||||
|
|
||||||
flightState._lastFleetTickMs = 0;
|
flightState._lastFleetTickMs = 0;
|
||||||
|
|
||||||
|
/** @type {number} Fleet ticks run; selects which hidden contacts re-reckon */
|
||||||
|
|
||||||
|
flightState._fleetTickSeq = 0;
|
||||||
|
|
||||||
/** @type {string} Camera pose signature at the last rotation pass */
|
/** @type {string} Camera pose signature at the last rotation pass */
|
||||||
|
|
||||||
flightState._lastCamPoseSig = '';
|
flightState._lastCamPoseSig = '';
|
||||||
|
|||||||
Reference in new issue
Block a user