mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 02:14:01 +00:00
Merge pull request #951 from bilawalsidhu/fix/live-aircraft-initial-position
fix: start new live aircraft at their delayed display position
This commit is contained in:
5 files changed
+185
-2
No files matched your search
@@ -168,6 +168,77 @@ test('nonempty OpenSky payload with zero usable rows cannot prove share target a
|
||||
}
|
||||
});
|
||||
|
||||
test('a newly seen aircraft appears at its delayed position, not ahead of it', async () => {
|
||||
// The fleet renders RENDER_DELAY_SEC behind real time, so a fresh contact's
|
||||
// first displayed position is its fix projected back to that delayed time.
|
||||
// Creating the billboard at the raw fix drew it ahead until the next fleet
|
||||
// tick, which then jumped it back about one delay of travel.
|
||||
const added = [];
|
||||
const billboardCollection = {
|
||||
show: false,
|
||||
add(options) {
|
||||
const billboard = { ...options };
|
||||
added.push(billboard);
|
||||
return billboard;
|
||||
},
|
||||
remove() {},
|
||||
};
|
||||
const viewer = { camera: { positionCartographic: null }, scene: {} };
|
||||
_setTrackedFlightRefreshStateForTest({
|
||||
icao24: 'seed00',
|
||||
entity: null,
|
||||
billboard: { show: false, position: Cesium.Cartesian3.fromDegrees(0, 0, 0) },
|
||||
billboardCollection,
|
||||
viewer,
|
||||
meta: { rawLat: 0, rawLon: 0, onGround: false },
|
||||
tracked: false,
|
||||
});
|
||||
const icao24 = 'fresh1';
|
||||
const fixLon = -97.6;
|
||||
const fixLat = 30.3;
|
||||
const speedMps = 250;
|
||||
const nowSec = Math.floor(Date.now() / 1000);
|
||||
const realFetch = globalThis.fetch;
|
||||
globalThis.fetch = async (url) => {
|
||||
if (!String(url).startsWith('/api/flights')) {
|
||||
return { ok: true, status: 200, json: async () => ({ ac: [] }) };
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
headers: { get: () => null },
|
||||
json: async () => ({
|
||||
time: nowSec,
|
||||
states: [[
|
||||
icao24, 'NEW123 ', 'United States', nowSec - 2, nowSec - 2,
|
||||
fixLon, fixLat, 10_668, false, speedMps, 90, 0, null, 10_700,
|
||||
null, null, null, 5,
|
||||
]],
|
||||
}),
|
||||
};
|
||||
};
|
||||
try {
|
||||
await flightsLayer.update(viewer);
|
||||
const billboard = added.find((entry) => entry.id === icao24);
|
||||
assert.ok(billboard, 'the new contact gets a billboard');
|
||||
const shown = Cesium.Cartographic.fromCartesian(billboard.position);
|
||||
const fix = Cesium.Cartesian3.fromDegrees(fixLon, fixLat, 10_700);
|
||||
const behindM = Cesium.Cartesian3.distance(
|
||||
Cesium.Cartesian3.fromRadians(shown.longitude, shown.latitude, 10_700),
|
||||
fix,
|
||||
);
|
||||
// Eastbound, so the delayed position is west of the fix by the travel
|
||||
// between the delayed time and the fix (about 28 s).
|
||||
assert.ok(Cesium.Math.toDegrees(shown.longitude) < fixLon, 'drawn behind the fix');
|
||||
assert.ok(
|
||||
behindM > speedMps * 25 && behindM < speedMps * 31,
|
||||
`expected about ${speedMps * 28} m behind the fix, got ${Math.round(behindM)} m`,
|
||||
);
|
||||
} finally {
|
||||
globalThis.fetch = realFetch;
|
||||
}
|
||||
});
|
||||
|
||||
test('flights poll refreshes tracked callsign/FL/kts and marks a missed poll STALE', async () => {
|
||||
const icao24 = 'a1b2c3';
|
||||
const entity = { gevLabelModel: { title: 'OLD', details: [] } };
|
||||
|
||||
@@ -179,8 +179,14 @@ export function createFlightSnapshotRenderer({
|
||||
// as the enrichment path — the class's GLB/scale may have changed.
|
||||
if (prevMeta?.klass !== meta.klass) rendering._syncModelToClass(icao24);
|
||||
} else {
|
||||
// Start where the fleet pass will draw it: the fleet renders behind
|
||||
// real time, so a fresh contact's displayed position is its fix
|
||||
// projected back to that delayed time. Creating it at the raw fix drew
|
||||
// it ahead until the next fleet tick snapped it back.
|
||||
const displayed =
|
||||
motion._deadReckon(icao24, new Cesium.Cartesian3()) || position;
|
||||
const bb = flightState._billboardCollection.add({
|
||||
position,
|
||||
position: displayed,
|
||||
image: aircraftIcon(rendering._iconKind(icao24, meta.klass)),
|
||||
width: isTracked ? 24 : 20,
|
||||
height: isTracked ? 24 : 20,
|
||||
|
||||
@@ -33,6 +33,7 @@ export function createMilitaryFlightLayer({
|
||||
militaryRegistry: services.militaryRegistry,
|
||||
rendering: parts.rendering,
|
||||
tracking: parts.tracking,
|
||||
motion: parts.motion,
|
||||
queries: parts.queries,
|
||||
});
|
||||
parts.ingestion = createIngestion({
|
||||
|
||||
@@ -113,3 +113,101 @@ test('a normalized source can retain its stale reason without changing standalon
|
||||
assert.equal(layer.getStats().error, null);
|
||||
assert.equal(layer.getStats().stale, true);
|
||||
});
|
||||
|
||||
test('a newly seen military contact appears at its delayed position, not ahead of it', async () => {
|
||||
// The military fleet renders RENDER_DELAY_SEC behind real time, so a fresh
|
||||
// contact's first displayed position is its fix projected back to that
|
||||
// delayed time. Creating the billboard at the raw fix drew it ahead until
|
||||
// the next fleet tick jumped it back.
|
||||
const Cesium = await import('cesium');
|
||||
const { RENDER_DELAY_SEC } = await import('./policy.js');
|
||||
// The application's own scene services, as src/app/layers/militaryFlights.js
|
||||
// composes them; only the surface samplers and registry are stubbed.
|
||||
const supplied = {
|
||||
picking: await import('../../data/pickRegistry.js'),
|
||||
sprites: await import('../../data/spriteOrder.js'),
|
||||
trails: await import('../../data/trailRenderer.js'),
|
||||
aircraftPresentation: await import('../../data/tr3bRegistry.js'),
|
||||
camera: await import('../../data/trackedCamera.js'),
|
||||
labels: await import('../../data/detectionDraw.js'),
|
||||
geoid: await import('../../data/geoid.js'),
|
||||
focus: await import('../../data/focusDeemphasis.js'),
|
||||
readout: await import('../../data/trackedReadout.js'),
|
||||
context: await import('../../data/contextStore.js'),
|
||||
render: await import('../../renderGovernor.js'),
|
||||
recession: await import('../../data/aircraftRecession.js'),
|
||||
groundFloor: await import('../../data/groundFloor.js'),
|
||||
groundSnap: await import('../../data/groundSnap.js'),
|
||||
meshFloor: { sampleMeshFloorCells: () => {} },
|
||||
militaryRegistry: { registerMilitaryIcaos: () => {} },
|
||||
};
|
||||
const fixLon = -97.6;
|
||||
const fixLat = 30.3;
|
||||
const speedMps = 200;
|
||||
const fixAgeMs = 2_000;
|
||||
const nowMs = Date.now();
|
||||
const layer = createMilitaryFlightLayer({
|
||||
services: supplied,
|
||||
source: {
|
||||
label: 'Fixture aircraft',
|
||||
async getSnapshot() {
|
||||
return {
|
||||
source: 'Fixture aircraft',
|
||||
complete: true,
|
||||
observedAtMs: nowMs,
|
||||
records: [
|
||||
{
|
||||
id: 'ae0001',
|
||||
latitude: fixLat,
|
||||
longitude: fixLon,
|
||||
onGround: false,
|
||||
baroAltitudeM: 9_000,
|
||||
courseDeg: 90,
|
||||
speedMps,
|
||||
positionTimeMs: nowMs - fixAgeMs,
|
||||
contactTimeMs: nowMs - fixAgeMs,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
const added = [];
|
||||
layer.testing._setTrackedMilitaryRefreshStateForTest({
|
||||
icao24: 'seed00',
|
||||
entity: null,
|
||||
meta: { rawLat: 0, rawLon: 0, onGround: false },
|
||||
billboard: {
|
||||
show: false,
|
||||
position: Cesium.Cartesian3.fromDegrees(0, 0, 0),
|
||||
},
|
||||
billboardCollection: {
|
||||
show: false,
|
||||
add(options) {
|
||||
const billboard = { ...options };
|
||||
added.push(billboard);
|
||||
return billboard;
|
||||
},
|
||||
remove() {},
|
||||
},
|
||||
viewer: { camera: { positionCartographic: null }, scene: {} },
|
||||
tracked: false,
|
||||
});
|
||||
await layer.update({ camera: { positionCartographic: null }, scene: {} });
|
||||
const billboard = added.find((entry) => entry.id === 'ae0001');
|
||||
assert.ok(billboard, 'the new contact gets a billboard');
|
||||
const shown = Cesium.Cartographic.fromCartesian(billboard.position);
|
||||
const behindM = Cesium.Cartesian3.distance(
|
||||
Cesium.Cartesian3.fromRadians(shown.longitude, shown.latitude, 0),
|
||||
Cesium.Cartesian3.fromDegrees(fixLon, fixLat, 0),
|
||||
);
|
||||
const expectedM = speedMps * (RENDER_DELAY_SEC - fixAgeMs / 1000);
|
||||
assert.ok(
|
||||
Cesium.Math.toDegrees(shown.longitude) < fixLon,
|
||||
'drawn behind the fix',
|
||||
);
|
||||
assert.ok(
|
||||
Math.abs(behindM - expectedM) < speedMps * 3,
|
||||
`expected about ${Math.round(expectedM)} m behind the fix, got ${Math.round(behindM)} m`,
|
||||
);
|
||||
});
|
||||
@@ -23,6 +23,7 @@ export function createMilitarySnapshotRenderer({
|
||||
militaryRegistry,
|
||||
rendering,
|
||||
tracking,
|
||||
motion,
|
||||
queries,
|
||||
}) {
|
||||
const { warmGroundFloor, cachedGroundFloor, GROUND_FLOOR_LIFT_M } =
|
||||
@@ -142,8 +143,14 @@ export function createMilitarySnapshotRenderer({
|
||||
// Round 5: depth policy is uniform (always depth-test-free, see
|
||||
// _groundDepthDistance) — nothing to flip on landing/takeoff.
|
||||
} else {
|
||||
// Start where the fleet pass will draw it: the fleet renders behind
|
||||
// real time, so a fresh contact's displayed position is its fix
|
||||
// projected back to that delayed time. Creating it at the raw fix drew
|
||||
// it ahead until the next fleet tick snapped it back.
|
||||
const displayed =
|
||||
motion._deadReckon(icao24, new Cesium.Cartesian3()) || position;
|
||||
const bb = flightState._billboardCollection.add({
|
||||
position,
|
||||
position: displayed,
|
||||
image: aircraftIcon(rendering._iconKind(icao24, meta.klass)),
|
||||
width: isTracked ? 24 : 20,
|
||||
height: isTracked ? 24 : 20,
|
||||
|
||||
Reference in new issue
Block a user