mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-07 03:44:00 +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)
|
||||
|
||||
- **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.
|
||||
- **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.
|
||||
|
||||
@@ -347,6 +347,7 @@
|
||||
"src/layers/flights/tracking.js",
|
||||
"src/layers/flights/rendering.js",
|
||||
"src/layers/flights/testing.js",
|
||||
"src/layers/flights/hiddenContactRotation.test.mjs",
|
||||
"src/layers/flights/evidence.js",
|
||||
"src/layers/flights/ingestion.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;
|
||||
|
||||
/**
|
||||
* @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
|
||||
* steps. Scaled down toward COURSE_MIN_DPS at low speed (courseSlewCapDps). */
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@ import {
|
||||
COURSE_SLEW_DT_MAX_SEC,
|
||||
ROTATION_REFRESH_MS,
|
||||
COURSE_MAX_DPS,
|
||||
HIDDEN_CONTACT_DR_TICKS,
|
||||
hiddenContactDrPhase,
|
||||
} from './policy.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() {
|
||||
if (
|
||||
!flightState._viewer ||
|
||||
@@ -965,9 +973,25 @@ export function createRendering({
|
||||
for (const icao of toRelease) _releaseModel(icao);
|
||||
}
|
||||
|
||||
const hiddenDrPhase = flightState._fleetTickSeq++ % HIDDEN_CONTACT_DR_TICKS;
|
||||
|
||||
for (const [icao24, bb] of flightState._billboards) {
|
||||
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 dr = parts.motion._deadReckon(
|
||||
|
||||
@@ -204,6 +204,10 @@ export function createFlightState({ source, services }) {
|
||||
|
||||
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 */
|
||||
|
||||
flightState._lastCamPoseSig = '';
|
||||
|
||||
Reference in new issue
Block a user