From 7b328232edcb5f3c621b314bfc33fd02d1506042 Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Tue, 6 Oct 2026 11:46:34 -0700 Subject: [PATCH 1/3] fix(flights): start new aircraft at their delayed display position The fleet renders behind real time, so a newly seen aircraft is displayed at its fix projected back to that delayed time. Its billboard was created at the raw fix instead, so it appeared ahead of where the next fleet tick put it and then jumped back about one render delay of travel before moving forward. --- src/data/flights.test.mjs | 71 ++++++++++++++++++++++++++ src/layers/flights/snapshotRenderer.js | 8 ++- 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/src/data/flights.test.mjs b/src/data/flights.test.mjs index abb047384..6f2544034 100644 --- a/src/data/flights.test.mjs +++ b/src/data/flights.test.mjs @@ -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: [] } }; diff --git a/src/layers/flights/snapshotRenderer.js b/src/layers/flights/snapshotRenderer.js index 636e691fd..369fb4af2 100644 --- a/src/layers/flights/snapshotRenderer.js +++ b/src/layers/flights/snapshotRenderer.js @@ -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, From d9737d94f213826bce63ecf7d249faa812c2844f Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Tue, 6 Oct 2026 12:01:56 -0700 Subject: [PATCH 2/3] fix(military): start new contacts at their delayed display position The military fleet also renders behind real time and projects a fresh contact's first fix back to that delayed time, but its billboard was created at the raw fix. A new contact therefore appeared ahead of where the next fleet tick put it and jumped back. The snapshot renderer now receives the layer's motion and creates the billboard at the dead-reckoned delayed position. --- src/layers/military/index.js | 1 + src/layers/military/ownership.test.mjs | 98 +++++++++++++++++++++++++ src/layers/military/snapshotRenderer.js | 9 ++- 3 files changed, 107 insertions(+), 1 deletion(-) diff --git a/src/layers/military/index.js b/src/layers/military/index.js index 283c9b7f8..53a1a25b4 100644 --- a/src/layers/military/index.js +++ b/src/layers/military/index.js @@ -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({ diff --git a/src/layers/military/ownership.test.mjs b/src/layers/military/ownership.test.mjs index 167900b7c..41081c137 100644 --- a/src/layers/military/ownership.test.mjs +++ b/src/layers/military/ownership.test.mjs @@ -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`, + ); +}); diff --git a/src/layers/military/snapshotRenderer.js b/src/layers/military/snapshotRenderer.js index d36576cf7..58eb57972 100644 --- a/src/layers/military/snapshotRenderer.js +++ b/src/layers/military/snapshotRenderer.js @@ -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, From bff921ccb378e443b585f625df652aa19fcd362e Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Tue, 6 Oct 2026 13:42:37 -0700 Subject: [PATCH 3/3] fix(flights): start a new grounded aircraft on the floor under its delayed position The fleet draws a grounded contact at its delayed position lifted onto the floor under it, but a new billboard was created at the delayed position without that lift. A taxiing aircraft whose reported fix sat lower than the ground under its delayed position started on the fix's floor and then rose on the next fleet tick. Creation now applies the same floor the fleet pass does. --- src/data/flights.test.mjs | 1248 +++++++++++++++++++----- src/layers/flights/snapshotRenderer.js | 16 +- 2 files changed, 1018 insertions(+), 246 deletions(-) diff --git a/src/data/flights.test.mjs b/src/data/flights.test.mjs index 6f2544034..b09dc7014 100644 --- a/src/data/flights.test.mjs +++ b/src/data/flights.test.mjs @@ -18,8 +18,10 @@ import flightsLayer, { } from './flights.js'; import { setMilitaryLayerActive } from './militaryRegistry.js'; import { - GROUND_FLOOR_LIFT_M, reportMeshFloorCell, - setMeshFloorPreferred, _clearMeshFloorCellsForTest, + GROUND_FLOOR_LIFT_M, + reportMeshFloorCell, + setMeshFloorPreferred, + _clearMeshFloorCellsForTest, } from './groundFloor.js'; import { _setTrackedOverlayHostForTest, @@ -33,7 +35,10 @@ test('share-Follow absence requires an accepted OpenSky snapshot', async () => { (await flightsLayer.resolveTrackingRestoreTarget('abc123')).status, 'source-unavailable', ); - _setFlightTrackingRefreshOutcomeForTest({ status: 'accepted', ids: ['different'] }); + _setFlightTrackingRefreshOutcomeForTest({ + status: 'accepted', + ids: ['different'], + }); const missing = await flightsLayer.resolveTrackingRestoreTarget('abc123'); assert.equal(missing.status, 'missing'); assert.equal(missing.reason, 'target-absent-from-snapshot'); @@ -55,7 +60,10 @@ const FULL_INFO = { }; test('flights analyst record: full record maps every contract field', () => { - const r = mapAnalystRecord('a1b2c3', FULL_INFO, { military: false, routeOk: true }); + const r = mapAnalystRecord('a1b2c3', FULL_INFO, { + military: false, + routeOk: true, + }); assert.deepEqual(r, { id: 'SWA696', icao24: 'a1b2c3', @@ -90,7 +98,8 @@ test('flights analyst record: empty info yields nulls, never NaN/undefined', () assert.equal(r.military, false); for (const [key, value] of Object.entries(r)) { assert.notEqual(value, undefined, `${key} must not be undefined`); - if (typeof value === 'number') assert.ok(Number.isFinite(value), `${key} must not be NaN`); + if (typeof value === 'number') + assert.ok(Number.isFinite(value), `${key} must not be NaN`); } }); @@ -98,14 +107,31 @@ test('flights analyst record: no callsign falls back to registration, then icao2 const r = mapAnalystRecord('abc123', { ...FULL_INFO, callsign: ' ' }); assert.equal(r.id, 'abc123'); // FULL_INFO carries no registration assert.equal(r.callsign, null); - assert.equal(mapAnalystRecord('abc123', { ...FULL_INFO, callsign: '', registration: 'N123AB' }).id, 'N123AB'); - assert.equal(mapAnalystRecord('abc123', { callsign: '', registration: ' ' }).id, 'abc123'); + assert.equal( + mapAnalystRecord('abc123', { + ...FULL_INFO, + callsign: '', + registration: 'N123AB', + }).id, + 'N123AB', + ); + assert.equal( + mapAnalystRecord('abc123', { callsign: '', registration: ' ' }).id, + 'abc123', + ); }); test('flights analyst record: NaN kinematics become null, military flag passes through', () => { - const r = mapAnalystRecord('ae01ce', { - ...FULL_INFO, velocity: NaN, true_track: undefined, verticalRate: null, - }, { military: true }); + const r = mapAnalystRecord( + 'ae01ce', + { + ...FULL_INFO, + velocity: NaN, + true_track: undefined, + verticalRate: null, + }, + { military: true }, + ); assert.equal(r.speedMps, null); assert.equal(r.heading, null); assert.equal(r.verticalRateMps, null); @@ -120,17 +146,25 @@ test('flights analyst record: output is JSON-safe (no Cesium types)', () => { test('flights first update forwards caller cancellation into the feed request', async () => { const realFetch = globalThis.fetch; let observedSignal = null; - globalThis.fetch = (_url, options = {}) => new Promise((_resolve, reject) => { - observedSignal = options.signal; - options.signal?.addEventListener('abort', () => { - reject(new DOMException('aborted', 'AbortError')); - }, { once: true }); - }); + globalThis.fetch = (_url, options = {}) => + new Promise((_resolve, reject) => { + observedSignal = options.signal; + options.signal?.addEventListener( + 'abort', + () => { + reject(new DOMException('aborted', 'AbortError')); + }, + { once: true }, + ); + }); try { const controller = new AbortController(); - const work = flightsLayer.update({ camera: { positionCartographic: null }, scene: {} }, { - signal: controller.signal, - }); + const work = flightsLayer.update( + { camera: { positionCartographic: null }, scene: {} }, + { + signal: controller.signal, + }, + ); await Promise.resolve(); assert.ok(observedSignal); controller.abort(); @@ -155,14 +189,27 @@ test('nonempty OpenSky payload with zero usable rows cannot prove share target a ok: true, status: 200, headers: { get: () => null }, - json: async () => ({ states: [null, {}, ['abc123', null, null, null, null, null, null]] }), + json: async () => ({ + states: [null, {}, ['abc123', null, null, null, null, null, null]], + }), }); try { - await flightsLayer.update({ camera: { positionCartographic: null }, scene: {} }); - const resolution = await flightsLayer.resolveTrackingRestoreTarget('abc123'); + await flightsLayer.update({ + camera: { positionCartographic: null }, + scene: {}, + }); + const resolution = + await flightsLayer.resolveTrackingRestoreTarget('abc123'); assert.equal(resolution.status, 'source-unavailable'); - assert.match(flightsLayer.getStats().error, /Malformed OpenSky aircraft rows/); - assert.equal(flightsLayer.getAnalystRecords().length, 1, 'warm aircraft data is preserved'); + assert.match( + flightsLayer.getStats().error, + /Malformed OpenSky aircraft rows/, + ); + assert.equal( + flightsLayer.getAnalystRecords().length, + 1, + 'warm aircraft data is preserved', + ); } finally { globalThis.fetch = realFetch; } @@ -187,7 +234,10 @@ test('a newly seen aircraft appears at its delayed position, not ahead of it', a _setTrackedFlightRefreshStateForTest({ icao24: 'seed00', entity: null, - billboard: { show: false, position: Cesium.Cartesian3.fromDegrees(0, 0, 0) }, + billboard: { + show: false, + position: Cesium.Cartesian3.fromDegrees(0, 0, 0), + }, billboardCollection, viewer, meta: { rawLat: 0, rawLon: 0, onGround: false }, @@ -209,11 +259,28 @@ test('a newly seen aircraft appears at its delayed position, not ahead of it', a 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, - ]], + states: [ + [ + icao24, + 'NEW123 ', + 'United States', + nowSec - 2, + nowSec - 2, + fixLon, + fixLat, + 10_668, + false, + speedMps, + 90, + 0, + null, + 10_700, + null, + null, + null, + 5, + ], + ], }), }; }; @@ -229,7 +296,10 @@ test('a newly seen aircraft appears at its delayed position, not ahead of it', a ); // 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( + 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`, @@ -277,13 +347,31 @@ test('flights poll refreshes tracked callsign/FL/kts and marks a missed poll STA if (!String(url).startsWith('/api/flights')) { return { ok: true, status: 200, json: async () => ({ ac: [] }) }; } - const states = openskyPoll++ === 0 - ? [[ - icao24, 'DAL123 ', 'United States', nowSec, nowSec, - -97.6, 30.3, 10_668, false, 250, 95, 5, null, 10_700, - null, null, null, 5, - ]] - : []; + const states = + openskyPoll++ === 0 + ? [ + [ + icao24, + 'DAL123 ', + 'United States', + nowSec, + nowSec, + -97.6, + 30.3, + 10_668, + false, + 250, + 95, + 5, + null, + 10_700, + null, + null, + null, + 5, + ], + ] + : []; return { ok: true, status: 200, @@ -322,14 +410,18 @@ test('real civil track path creates no native label and publishes every cached h trackedEntityChanged, clock: { currentTime: now }, camera: { - cancelFlight() { cancelledFlights += 1; }, + cancelFlight() { + cancelledFlights += 1; + }, position: new Cesium.Cartesian3(0, -10_000, 7_000), positionWC: new Cesium.Cartesian3(0, -10_000, 7_000), direction: Cesium.Cartesian3.UNIT_Y, transform: Cesium.Matrix4.IDENTITY, viewMatrix: Cesium.Matrix4.IDENTITY, frustum: { projectionMatrix: Cesium.Matrix4.IDENTITY }, - lookAtTransform() { appliedFrames += 1; }, + lookAtTransform() { + appliedFrames += 1; + }, }, scene: { canvas: { clientWidth: 1600, clientHeight: 900 }, @@ -368,9 +460,12 @@ test('real civil track path creates no native label and publishes every cached h const realFetch = globalThis.fetch; globalThis.window = new EventTarget(); const selectionEvents = []; - globalThis.window.addEventListener('gev:awareness-subject-selected', (event) => { - selectionEvents.push(event.detail); - }); + globalThis.window.addEventListener( + 'gev:awareness-subject-selected', + (event) => { + selectionEvents.push(event.detail); + }, + ); globalThis.fetch = async () => ({ ok: false }); _setTrackedOverlayHostForTest(host); try { @@ -382,13 +477,15 @@ test('real civil track path creates no native label and publishes every cached h billboardCollection: { show: true, remove() {} }, viewer, tracked: false, - history: [{ - time: now, - epochMs: Date.now(), - position, - velocity: 250, - track: 95, - }], + history: [ + { + time: now, + epochMs: Date.now(), + position, + velocity: 250, + track: 95, + }, + ], meta: { callsign: 'N12345', altitude: 10_668, @@ -411,11 +508,19 @@ test('real civil track path creates no native label and publishes every cached h }, }); - assert.equal(flightsLayer.trackById(icao24, { origin: 'programmatic' }), true); + assert.equal( + flightsLayer.trackById(icao24, { origin: 'programmatic' }), + true, + ); const entity = viewer.trackedEntity; - assert.ok(entity instanceof Cesium.Entity, 'trackById must create the real Cesium entity'); + assert.ok( + entity instanceof Cesium.Entity, + 'trackById must create the real Cesium entity', + ); assert.equal(entity.label, undefined); - assert.ok(entities.values.every((candidate) => candidate.label === undefined)); + assert.ok( + entities.values.every((candidate) => candidate.label === undefined), + ); assert.deepEqual(entity.gevLabelModel, { title: 'N12345 · FL350 · 486 kts', details: ['TEST AIR · A320', 'AUS → LAX'], @@ -425,17 +530,44 @@ test('real civil track path creates no native label and publishes every cached h const initialAppliedFrames = appliedFrames; const initialCancelledFlights = cancelledFlights; assert.equal(flightsLayer.trackById(icao24, { origin: 'user' }), true); - assert.equal(cancelledFlights, initialCancelledFlights, 'ordinary repeated tracking stays camera-idempotent'); - assert.equal(selectionEvents.at(-1)?.origin, 'user', 'same-target selection upgrades durable authority'); + assert.equal( + cancelledFlights, + initialCancelledFlights, + 'ordinary repeated tracking stays camera-idempotent', + ); + assert.equal( + selectionEvents.at(-1)?.origin, + 'user', + 'same-target selection upgrades durable authority', + ); assert.equal(entity.gevSelectionOrigin, 'user'); assert.equal(flightsLayer.refocusTrackedById('different-flight'), false); - assert.equal(flightsLayer.refocusTrackedById(icao24, { origin: 'voice' }), true); - assert.equal(selectionEvents.at(-1)?.origin, 'voice', 'same-target refocus forwards explicit authority'); + assert.equal( + flightsLayer.refocusTrackedById(icao24, { origin: 'voice' }), + true, + ); + assert.equal( + selectionEvents.at(-1)?.origin, + 'voice', + 'same-target refocus forwards explicit authority', + ); assert.equal(flightsLayer.refocusTrackedById(icao24), true); viewer.scene.preUpdate.raiseEvent(); - assert.equal(viewer.trackedEntity, entity, 'refocus must retain the exact tracked entity'); - assert.equal(appliedFrames, initialAppliedFrames + 1, 'repeated refocus keeps one camera-frame owner'); - assert.equal(cancelledFlights, initialCancelledFlights + 2, 'only explicit refocus requests cancel camera flights'); + assert.equal( + viewer.trackedEntity, + entity, + 'refocus must retain the exact tracked entity', + ); + assert.equal( + appliedFrames, + initialAppliedFrames + 1, + 'repeated refocus keeps one camera-frame owner', + ); + assert.equal( + cancelledFlights, + initialCancelledFlights + 2, + 'only explicit refocus requests cancel camera flights', + ); const publication = publications.at(-1); assert.equal(publication.sourceId, 'tracked'); const entry = publication.entries[0]; @@ -446,7 +578,11 @@ test('real civil track path creates no native label and publishes every cached h const display = entity.position.getValue(now); assert.ok(display, 'tracked position callback must seed its frame cache'); - assert.equal(entry.position(), display, 'host must reuse the exact cached display Cartesian'); + assert.equal( + entry.position(), + display, + 'host must reuse the exact cached display Cartesian', + ); flightsLayer.setParams( { selectedFlightsTrackingId: 'late001' }, @@ -454,10 +590,17 @@ test('real civil track path creates no native label and publishes every cached h ); assert.equal(_pendingFlightTrackingRestoreForTest(), 'late001'); assert.equal(flightsLayer.trackById(icao24, { origin: 'user' }), true); - assert.equal(_pendingFlightTrackingRestoreForTest(), null, 'new user selection cancels stale restore'); + assert.equal( + _pendingFlightTrackingRestoreForTest(), + null, + 'new user selection cancels stale restore', + ); _addFlightTrackingCandidateForTest({ icao24: 'late001', - billboard: { ...billboard, position: Cesium.Cartesian3.fromDegrees(-97.5, 30.4, 9_000) }, + billboard: { + ...billboard, + position: Cesium.Cartesian3.fromDegrees(-97.5, 30.4, 9_000), + }, meta: { ...FULL_INFO, callsign: 'LATE1', rawLat: 30.4, rawLon: -97.5 }, history: [], }); @@ -471,10 +614,17 @@ test('real civil track path creates no native label and publishes every cached h ); assert.equal(_pendingFlightTrackingRestoreForTest(), 'late002'); flightsLayer.stopTracking({ origin: 'user' }); - assert.equal(_pendingFlightTrackingRestoreForTest(), null, 'explicit clear cancels stale restore'); + assert.equal( + _pendingFlightTrackingRestoreForTest(), + null, + 'explicit clear cancels stale restore', + ); _addFlightTrackingCandidateForTest({ icao24: 'late002', - billboard: { ...billboard, position: Cesium.Cartesian3.fromDegrees(-97.4, 30.5, 8_000) }, + billboard: { + ...billboard, + position: Cesium.Cartesian3.fromDegrees(-97.4, 30.5, 8_000), + }, meta: { ...FULL_INFO, callsign: 'LATE2', rawLat: 30.5, rawLon: -97.4 }, history: [], }); @@ -494,7 +644,10 @@ test('real civil track path creates no native label and publishes every cached h ); _addFlightTrackingCandidateForTest({ icao24: 'late003', - billboard: { ...billboard, position: Cesium.Cartesian3.fromDegrees(-97.3, 30.6, 7_000) }, + billboard: { + ...billboard, + position: Cesium.Cartesian3.fromDegrees(-97.3, 30.6, 7_000), + }, meta: { ...FULL_INFO, callsign: 'LATE3', rawLat: 30.6, rawLon: -97.3 }, history: [], }); @@ -508,7 +661,10 @@ test('real civil track path creates no native label and publishes every cached h assert.equal(_pendingFlightTrackingRestoreForTest(), 'late004'); _addFlightTrackingCandidateForTest({ icao24: 'late004', - billboard: { ...billboard, position: Cesium.Cartesian3.fromDegrees(-97.2, 30.7, 6_000) }, + billboard: { + ...billboard, + position: Cesium.Cartesian3.fromDegrees(-97.2, 30.7, 6_000), + }, meta: { ...FULL_INFO, callsign: 'LATE4', rawLat: 30.7, rawLon: -97.2 }, history: [], }); @@ -519,7 +675,10 @@ test('real civil track path creates no native label and publishes every cached h { selectedFlightsTrackingId: 'late005' }, { origin: 'share-restore' }, ); - assert.equal(flightsLayer.trackById(icao24, { origin: 'programmatic' }), true); + assert.equal( + flightsLayer.trackById(icao24, { origin: 'programmatic' }), + true, + ); assert.equal( _pendingFlightTrackingRestoreForTest(), 'late005', @@ -527,7 +686,10 @@ test('real civil track path creates no native label and publishes every cached h ); _addFlightTrackingCandidateForTest({ icao24: 'late005', - billboard: { ...billboard, position: Cesium.Cartesian3.fromDegrees(-97.1, 30.8, 5_000) }, + billboard: { + ...billboard, + position: Cesium.Cartesian3.fromDegrees(-97.1, 30.8, 5_000), + }, meta: { ...FULL_INFO, callsign: 'LATE5', rawLat: 30.8, rawLon: -97.1 }, history: [], }); @@ -562,7 +724,11 @@ function seedLabelContact({ callsign, registration, tracked = false }) { _setTrackedFlightRefreshStateForTest({ icao24: LABEL_ICAO, entity: null, - billboard: { position: LABEL_POSITION, color: Cesium.Color.WHITE, show: true }, + billboard: { + position: LABEL_POSITION, + color: Cesium.Color.WHITE, + show: true, + }, billboardCollection: { show: true, remove() {} }, viewer: { camera: { positionCartographic: null }, scene: {} }, tracked, @@ -620,7 +786,11 @@ test('civil label chain: a blank callsign falls back to the registration, not th const labels = labelsFor({ callsign: ' ', registration: 'N123AB ' }); for (const [surface, value] of Object.entries(labels)) { if (surface === '_identity') continue; - assert.equal(value, 'N123AB', `${surface} must show the registration, not ${LABEL_ICAO}`); + assert.equal( + value, + 'N123AB', + `${surface} must show the registration, not ${LABEL_ICAO}`, + ); } }); @@ -629,7 +799,11 @@ test('civil label chain: no callsign and no registration still reads as the ICAO const labels = labelsFor({ callsign: null, registration }); for (const [surface, value] of Object.entries(labels)) { if (surface === '_identity') continue; - assert.equal(value, LABEL_ICAO, `${surface} must fall through to the hex`); + assert.equal( + value, + LABEL_ICAO, + `${surface} must fall through to the hex`, + ); } } }); @@ -653,7 +827,11 @@ test('civil label chain: identity stays icao24 while the label moves', () => { ]) { const { _identity } = labelsFor(fixture); for (const [key, value] of Object.entries(_identity)) { - assert.equal(value, LABEL_ICAO, `${key} identity must remain the ICAO hex`); + assert.equal( + value, + LABEL_ICAO, + `${key} identity must remain the ICAO hex`, + ); } } }); @@ -700,7 +878,11 @@ test('civil label chain: the awareness selection EVENT uses the canonical chain' `selection event label for ${JSON.stringify(fixture)}`, ); // Identity must NOT move with the label. - assert.equal(detail.id, LABEL_ICAO, 'selection event identity stays the ICAO hex'); + assert.equal( + detail.id, + LABEL_ICAO, + 'selection event identity stays the ICAO hex', + ); assert.equal(detail.layerId, 'flights'); } }); @@ -711,7 +893,11 @@ test('civil tracked readout: a callsign-less enriched contact reads as its regis _setTrackedFlightRefreshStateForTest({ icao24: LABEL_ICAO, entity, - billboard: { position: LABEL_POSITION, color: Cesium.Color.WHITE, show: false }, + billboard: { + position: LABEL_POSITION, + color: Cesium.Color.WHITE, + show: false, + }, billboardCollection: { show: false, remove() {} }, viewer: { camera: { positionCartographic: null }, scene: {} }, meta: { @@ -732,10 +918,16 @@ test('civil tracked readout: a callsign-less enriched contact reads as its regis const realFetch = globalThis.fetch; globalThis.fetch = async () => ({ - ok: true, status: 200, headers: { get: () => null }, json: async () => ({ time: 0, states: [] }), + ok: true, + status: 200, + headers: { get: () => null }, + json: async () => ({ time: 0, states: [] }), }); try { - await flightsLayer.update({ camera: { positionCartographic: null }, scene: {} }); + await flightsLayer.update({ + camera: { positionCartographic: null }, + scene: {}, + }); assert.match(entity.gevLabelModel.title, /^N123AB\b/); assert.doesNotMatch( [entity.gevLabelModel.title, ...entity.gevLabelModel.details].join(' · '), @@ -815,7 +1007,104 @@ test('military suppression exempts the contact this layer is still restoring', ( // contacts only, never when a 3D model owns the visual (T7 one-shot). // --------------------------------------------------------------------------- -const _floorCarto = (pos) => Cesium.Cartographic.fromCartesian(pos, Cesium.Ellipsoid.WGS84); +const _floorCarto = (pos) => + Cesium.Cartographic.fromCartesian(pos, Cesium.Ellipsoid.WGS84); + +test('a newly seen taxiing aircraft starts on the floor under its delayed position', async () => { + // A grounded contact is drawn at its delayed position lifted onto the floor + // under it. Creating the billboard without that lift started it on the + // floor of its reported fix, then the next fleet tick raised it. + _clearDisplayFloorStateForTest(); + _clearMeshFloorCellsForTest(); + setMeshFloorPreferred(true); + 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 = 'taxi01'; + const fixLon = -97.66; + const fixLat = 30.2; + const speedMps = 10; + // The reported fix sits on a 100 m floor; the apron it taxied from is higher. + reportMeshFloorCell(fixLat, fixLon, 100); + for (let k = 1; k <= 40; k += 1) { + reportMeshFloorCell(fixLat, fixLon - k * 0.0001, 131.5); + } + 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, + 'TAXI01 ', + 'United States', + nowSec - 2, + nowSec - 2, + fixLon, + fixLat, + null, + true, + speedMps, + 90, + 0, + null, + null, + 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 = _floorCarto(billboard.position); + assert.ok( + Cesium.Math.toDegrees(shown.longitude) < fixLon, + 'drawn behind the fix', + ); + assert.ok( + Math.abs(shown.height - (131.5 + GROUND_FLOOR_LIFT_M)) < 0.05, + `height ${shown.height} should be the delayed position's floor, 131.5 + lift`, + ); + } finally { + globalThis.fetch = realFetch; + _clearMeshFloorCellsForTest(); + _clearDisplayFloorStateForTest(); + } +}); test('display floor: a grounded contact that drifted onto a higher cell is lifted', () => { _clearDisplayFloorStateForTest(); @@ -824,10 +1113,16 @@ test('display floor: a grounded contact that drifted onto a higher cell is lifte // Fix cell floor was 124.7; the display has taxied into a 140.2 m cell. reportMeshFloorCell(30.2004, -97.6604, 140.2); const pos = Cesium.Cartesian3.fromDegrees(-97.6604, 30.2004, 124.7); - const out = _floorGroundedDisplayPositionForTest({ onGround: true }, pos, false); + const out = _floorGroundedDisplayPositionForTest( + { onGround: true }, + pos, + false, + ); const c = _floorCarto(out); - assert.ok(Math.abs(c.height - (140.2 + GROUND_FLOOR_LIFT_M)) < 0.05, - `height ${c.height} should be 140.2 + lift`); + assert.ok( + Math.abs(c.height - (140.2 + GROUND_FLOOR_LIFT_M)) < 0.05, + `height ${c.height} should be 140.2 + lift`, + ); // Lifted straight up — the drifted lat/lon is preserved exactly. assert.ok(Math.abs(Cesium.Math.toDegrees(c.latitude) - 30.2004) < 1e-6); assert.ok(Math.abs(Cesium.Math.toDegrees(c.longitude) + 97.6604) < 1e-6); @@ -839,7 +1134,11 @@ test('display floor: an AIRBORNE contact over the same cell is never lifted', () setMeshFloorPreferred(true); reportMeshFloorCell(30.2004, -97.6604, 140.2); const pos = Cesium.Cartesian3.fromDegrees(-97.6604, 30.2004, 124.7); - const out = _floorGroundedDisplayPositionForTest({ onGround: false }, pos, false); + const out = _floorGroundedDisplayPositionForTest( + { onGround: false }, + pos, + false, + ); assert.equal(out, pos, 'airborne display positions pass through untouched'); }); @@ -849,8 +1148,16 @@ test('display floor: T7 — a model-owned grounded contact keeps its billboard d setMeshFloorPreferred(true); reportMeshFloorCell(30.2004, -97.6604, 140.2); const pos = Cesium.Cartesian3.fromDegrees(-97.6604, 30.2004, 124.7); - const out = _floorGroundedDisplayPositionForTest({ onGround: true }, pos, true); - assert.equal(out, pos, 'moving it would drag groundSnap past its 50 m re-sample threshold'); + const out = _floorGroundedDisplayPositionForTest( + { onGround: true }, + pos, + true, + ); + assert.equal( + out, + pos, + 'moving it would drag groundSnap past its 50 m re-sample threshold', + ); }); test('display floor: a contact already above the floor is passed through unchanged', () => { @@ -859,7 +1166,11 @@ test('display floor: a contact already above the floor is passed through unchang setMeshFloorPreferred(true); reportMeshFloorCell(30.2004, -97.6604, 124.7); const pos = Cesium.Cartesian3.fromDegrees(-97.6604, 30.2004, 140.2); - const out = _floorGroundedDisplayPositionForTest({ onGround: true }, pos, false); + const out = _floorGroundedDisplayPositionForTest( + { onGround: true }, + pos, + false, + ); assert.equal(out, pos, 'no rebuild when nothing needs lifting'); }); @@ -868,7 +1179,11 @@ test('display floor: a cold display cell leaves the contact where it is', () => _clearMeshFloorCellsForTest(); setMeshFloorPreferred(true); const pos = Cesium.Cartesian3.fromDegrees(-97.6604, 30.2004, 124.7); - const out = _floorGroundedDisplayPositionForTest({ onGround: true }, pos, false); + const out = _floorGroundedDisplayPositionForTest( + { onGround: true }, + pos, + false, + ); assert.equal(out, pos, 'no floor data → no clamp (never invent a surface)'); }); @@ -879,7 +1194,11 @@ test('display floor: mesh cells are ignored outside the google-3d regime', () => reportMeshFloorCell(30.2004, -97.6604, 140.2); setMeshFloorPreferred(false); // globe stack: the DEM IS the rendered terrain const pos = Cesium.Cartesian3.fromDegrees(-97.6604, 30.2004, 124.7); - const out = _floorGroundedDisplayPositionForTest({ onGround: true }, pos, false); + const out = _floorGroundedDisplayPositionForTest( + { onGround: true }, + pos, + false, + ); assert.equal(out, pos); setMeshFloorPreferred(true); }); @@ -895,14 +1214,23 @@ test('display floor: jitter across a cell edge does not alternate the floor', () reportMeshFloorCell(30.2014, -97.6604, 146); const at = (lat) => { const out = _floorGroundedDisplayPositionForTest( - { onGround: true }, Cesium.Cartesian3.fromDegrees(-97.6604, lat, 120), false, 'jitter', + { onGround: true }, + Cesium.Cartesian3.fromDegrees(-97.6604, lat, 120), + false, + 'jitter', ); return _floorCarto(out).height; }; const settled = at(30.2004); // squarely inside the low cell - assert.ok(Math.abs(settled - (140 + GROUND_FLOOR_LIFT_M)) < 0.05, `settled ${settled}`); + assert.ok( + Math.abs(settled - (140 + GROUND_FLOOR_LIFT_M)) < 0.05, + `settled ${settled}`, + ); // 30.2006 belongs to the HIGH cell but is only ~11 m past the boundary. - assert.ok(Math.abs(at(30.2006) - settled) < 0.05, 'held the low cell across the edge'); + assert.ok( + Math.abs(at(30.2006) - settled) < 0.05, + 'held the low cell across the edge', + ); assert.ok(Math.abs(at(30.2004) - settled) < 0.05, 'and back again — no pop'); }); @@ -912,12 +1240,21 @@ test('display floor: a real crossing does adopt the next cell', () => { setMeshFloorPreferred(true); reportMeshFloorCell(30.2004, -97.6604, 140); reportMeshFloorCell(30.2014, -97.6604, 146); - const at = (lat) => _floorCarto(_floorGroundedDisplayPositionForTest( - { onGround: true }, Cesium.Cartesian3.fromDegrees(-97.6604, lat, 120), false, 'crossing', - )).height; + const at = (lat) => + _floorCarto( + _floorGroundedDisplayPositionForTest( + { onGround: true }, + Cesium.Cartesian3.fromDegrees(-97.6604, lat, 120), + false, + 'crossing', + ), + ).height; at(30.2004); const crossed = at(30.2014); // properly into the next cell - assert.ok(Math.abs(crossed - (146 + GROUND_FLOOR_LIFT_M)) < 0.05, `crossed ${crossed}`); + assert.ok( + Math.abs(crossed - (146 + GROUND_FLOOR_LIFT_M)) < 0.05, + `crossed ${crossed}`, + ); }); // --- F7: no rebuild when nothing changed ----------------------------------- @@ -932,10 +1269,28 @@ test('display floor: a clamped stationary contact reuses its previous output', ( setMeshFloorPreferred(true); reportMeshFloorCell(30.2004, -97.6604, 140); const pos = Cesium.Cartesian3.fromDegrees(-97.6604, 30.2004, 120); - const first = _floorGroundedDisplayPositionForTest({ onGround: true }, pos, false, 'parked'); - const second = _floorGroundedDisplayPositionForTest({ onGround: true }, pos, false, 'parked'); - assert.notEqual(first, pos, 'it really is clamped (not the pass-through case)'); - assert.equal(second, first, 'same input + same floor ⇒ the very same object, no rebuild'); + const first = _floorGroundedDisplayPositionForTest( + { onGround: true }, + pos, + false, + 'parked', + ); + const second = _floorGroundedDisplayPositionForTest( + { onGround: true }, + pos, + false, + 'parked', + ); + assert.notEqual( + first, + pos, + 'it really is clamped (not the pass-through case)', + ); + assert.equal( + second, + first, + 'same input + same floor ⇒ the very same object, no rebuild', + ); }); test('display floor: the cached output is dropped when the floor changes under it', () => { @@ -944,14 +1299,31 @@ test('display floor: the cached output is dropped when the floor changes under i setMeshFloorPreferred(true); reportMeshFloorCell(30.2004, -97.6604, 140); const pos = Cesium.Cartesian3.fromDegrees(-97.6604, 30.2004, 120); - const before = _floorCarto(_floorGroundedDisplayPositionForTest({ onGround: true }, pos, false, 'warming')).height; + const before = _floorCarto( + _floorGroundedDisplayPositionForTest( + { onGround: true }, + pos, + false, + 'warming', + ), + ).height; // A late mesh sample raises this cell (a fresh module state stands in for the // one-shot latch, which never overwrites). _clearMeshFloorCellsForTest(); reportMeshFloorCell(30.2004, -97.6604, 175); - const after = _floorCarto(_floorGroundedDisplayPositionForTest({ onGround: true }, pos, false, 'warming')).height; + const after = _floorCarto( + _floorGroundedDisplayPositionForTest( + { onGround: true }, + pos, + false, + 'warming', + ), + ).height; assert.ok(Math.abs(before - 141.5) < 0.05, `before ${before}`); - assert.ok(Math.abs(after - 176.5) < 0.05, `after ${after} — a stale cache would still read 141.5`); + assert.ok( + Math.abs(after - 176.5) < 0.05, + `after ${after} — a stale cache would still read 141.5`, + ); }); // --- F8: hold the last known floor through a floor-data gap ---------------- @@ -971,18 +1343,28 @@ test('display floor: a cold cell HOLDS the last floor that resolved for this con reportMeshFloorCell(30.2004, -97.6604, 140.2); // On the warm cell: clamped onto the floor as usual. const warm = _floorGroundedDisplayPositionForTest( - { onGround: true }, Cesium.Cartesian3.fromDegrees(-97.6604, 30.2004, 124.7), false, 'held', + { onGround: true }, + Cesium.Cartesian3.fromDegrees(-97.6604, 30.2004, 124.7), + false, + 'held', + ); + assert.ok( + Math.abs(_floorCarto(warm).height - (140.2 + GROUND_FLOOR_LIFT_M)) < 0.05, ); - assert.ok(Math.abs(_floorCarto(warm).height - (140.2 + GROUND_FLOOR_LIFT_M)) < 0.05); // Taxis ~550 m onto a cell nothing has resolved, still at the geoid-ish // height the poll path handed it. Pre-fix this passed straight through — // buried. const cold = _floorGroundedDisplayPositionForTest( - { onGround: true }, Cesium.Cartesian3.fromDegrees(-97.6604, 30.2054, 124.7), false, 'held', + { onGround: true }, + Cesium.Cartesian3.fromDegrees(-97.6604, 30.2054, 124.7), + false, + 'held', ); const h = _floorCarto(cold).height; - assert.ok(Math.abs(h - (140.2 + GROUND_FLOOR_LIFT_M)) < 0.05, - `held ${h} — a cold cell must hold 140.2 + lift, never the 124.7 it arrived with`); + assert.ok( + Math.abs(h - (140.2 + GROUND_FLOOR_LIFT_M)) < 0.05, + `held ${h} — a cold cell must hold 140.2 + lift, never the 124.7 it arrived with`, + ); }); test('display floor: with NO prior anywhere the contact is left exactly where it is', () => { @@ -990,8 +1372,17 @@ test('display floor: with NO prior anywhere the contact is left exactly where it _clearMeshFloorCellsForTest(); setMeshFloorPreferred(true); const pos = Cesium.Cartesian3.fromDegrees(-97.6604, 30.2004, 124.7); - const out = _floorGroundedDisplayPositionForTest({ onGround: true }, pos, false, 'fresh'); - assert.equal(out, pos, 'nothing measured anywhere ⇒ nothing invented — same object, no clamp'); + const out = _floorGroundedDisplayPositionForTest( + { onGround: true }, + pos, + false, + 'fresh', + ); + assert.equal( + out, + pos, + 'nothing measured anywhere ⇒ nothing invented — same object, no clamp', + ); }); test('display floor: a fresh contact on a cold cell adopts a resolved NEIGHBOUR', () => { @@ -1004,11 +1395,16 @@ test('display floor: a fresh contact on a cold cell adopts a resolved NEIGHBOUR' reportMeshFloorCell(30.2014, -97.6604, 141.0); reportMeshFloorCell(30.1994, -97.6604, 152.0); const out = _floorGroundedDisplayPositionForTest( - { onGround: true }, Cesium.Cartesian3.fromDegrees(-97.6604, 30.2004, 124.7), false, 'neighbour', + { onGround: true }, + Cesium.Cartesian3.fromDegrees(-97.6604, 30.2004, 124.7), + false, + 'neighbour', ); const h = _floorCarto(out).height; - assert.ok(Math.abs(h - (141.0 + GROUND_FLOOR_LIFT_M)) < 0.05, - `neighbour hold ${h} — expected the lower of the two resolved neighbours`); + assert.ok( + Math.abs(h - (141.0 + GROUND_FLOOR_LIFT_M)) < 0.05, + `neighbour hold ${h} — expected the lower of the two resolved neighbours`, + ); }); // Drives a stationary contact at the production 80 ms fleet cadence and reports @@ -1016,16 +1412,28 @@ test('display floor: a fresh contact on a cold cell adopts a resolved NEIGHBOUR' // `mutate` runs before the tick at that index, so a target can be moved // mid-approach. function _driveEase(id, pos, fromMs, ticks, mutate = () => {}) { - let previous = _floorCarto(_floorGroundedDisplayPositionForTest( - { onGround: true }, pos, false, id, fromMs, - )).height; + let previous = _floorCarto( + _floorGroundedDisplayPositionForTest( + { onGround: true }, + pos, + false, + id, + fromMs, + ), + ).height; const opened = previous; let maxStepM = 0; for (let i = 1; i <= ticks; i += 1) { mutate(i); - const h = _floorCarto(_floorGroundedDisplayPositionForTest( - { onGround: true }, pos, false, id, fromMs + i * 80, - )).height; + const h = _floorCarto( + _floorGroundedDisplayPositionForTest( + { onGround: true }, + pos, + false, + id, + fromMs + i * 80, + ), + ).height; maxStepM = Math.max(maxStepM, previous - h); previous = h; } @@ -1044,10 +1452,18 @@ test('display floor: a hold released onto a LOWER floor eases down, never snaps' if (i === 1) reportMeshFloorCell(30.2004, -97.6604, 160.0); // its own cell resolves lower }); const totalDropM = r.opened - (160.0 + GROUND_FLOOR_LIFT_M); - assert.ok(Math.abs(r.opened - (200.0 + GROUND_FLOOR_LIFT_M)) < 0.05, `opened at ${r.opened}`); - assert.ok(r.maxStepM <= totalDropM * 0.25, - `largest single-tick drop ${r.maxStepM.toFixed(1)} m of ${totalDropM.toFixed(1)} m — a snap is the whole drop at once`); - assert.ok(Math.abs(r.settled - (160.0 + GROUND_FLOOR_LIFT_M)) < 0.05, `settled at ${r.settled}`); + assert.ok( + Math.abs(r.opened - (200.0 + GROUND_FLOOR_LIFT_M)) < 0.05, + `opened at ${r.opened}`, + ); + assert.ok( + r.maxStepM <= totalDropM * 0.25, + `largest single-tick drop ${r.maxStepM.toFixed(1)} m of ${totalDropM.toFixed(1)} m — a snap is the whole drop at once`, + ); + assert.ok( + Math.abs(r.settled - (160.0 + GROUND_FLOOR_LIFT_M)) < 0.05, + `settled at ${r.settled}`, + ); }); test('display floor: a hold released onto a HIGHER floor rises immediately', () => { @@ -1055,17 +1471,26 @@ test('display floor: a hold released onto a HIGHER floor rises immediately', () _clearMeshFloorCellsForTest(); setMeshFloorPreferred(true); const pos = Cesium.Cartesian3.fromDegrees(-97.6604, 30.2004, 124.7); - const at = (ms) => _floorCarto(_floorGroundedDisplayPositionForTest( - { onGround: true }, pos, false, 'rise', ms, - )).height; + const at = (ms) => + _floorCarto( + _floorGroundedDisplayPositionForTest( + { onGround: true }, + pos, + false, + 'rise', + ms, + ), + ).height; reportMeshFloorCell(30.2014, -97.6604, 160.0); reportMeshFloorCell(30.1994, -97.6604, 190.0); assert.ok(Math.abs(at(1000) - (160.0 + GROUND_FLOOR_LIFT_M)) < 0.05); reportMeshFloorCell(30.2004, -97.6604, 205.0); // No easing UP: an eased rise is time spent under the mesh, which is the // whole failure this path exists to prevent. - assert.ok(Math.abs(at(1001) - (205.0 + GROUND_FLOOR_LIFT_M)) < 0.05, - 'the next tick is already on the higher floor'); + assert.ok( + Math.abs(at(1001) - (205.0 + GROUND_FLOOR_LIFT_M)) < 0.05, + 'the next tick is already on the higher floor', + ); }); // --- F9: the hold is a live state machine, not a one-shot ------------------ @@ -1079,17 +1504,24 @@ test('display floor: a STATIONARY unresolved contact still adopts a floor that a _clearMeshFloorCellsForTest(); setMeshFloorPreferred(true); const pos = Cesium.Cartesian3.fromDegrees(-97.6604, 30.2004, 124.7); - const at = (ms) => _floorGroundedDisplayPositionForTest( - { onGround: true }, pos, false, 'parked-cold', ms, - ); + const at = (ms) => + _floorGroundedDisplayPositionForTest( + { onGround: true }, + pos, + false, + 'parked-cold', + ms, + ); assert.equal(at(1000), pos, 'nothing anywhere yet — passes through'); // The poll's warm batch lands the cells NEXT DOOR (this contact's own cell // is still cold, so only the hold chain can see them). reportMeshFloorCell(30.2014, -97.6604, 138.0); reportMeshFloorCell(30.1994, -97.6604, 160.0); const adopted = _floorCarto(at(1600)).height; // past the probe throttle - assert.ok(Math.abs(adopted - (138.0 + GROUND_FLOOR_LIFT_M)) < 0.05, - `stationary contact ${adopted} — a memoized unresolved answer would still read 124.7`); + assert.ok( + Math.abs(adopted - (138.0 + GROUND_FLOOR_LIFT_M)) < 0.05, + `stationary contact ${adopted} — a memoized unresolved answer would still read 124.7`, + ); }); test('display floor: a stationary contact FOLLOWS the neighbourhood as it fills in', () => { @@ -1097,9 +1529,16 @@ test('display floor: a stationary contact FOLLOWS the neighbourhood as it fills _clearMeshFloorCellsForTest(); setMeshFloorPreferred(true); const pos = Cesium.Cartesian3.fromDegrees(-97.6604, 30.2004, 100); - const at = (ms) => _floorCarto(_floorGroundedDisplayPositionForTest( - { onGround: true }, pos, false, 'upgrade', ms, - )).height; + const at = (ms) => + _floorCarto( + _floorGroundedDisplayPositionForTest( + { onGround: true }, + pos, + false, + 'upgrade', + ms, + ), + ).height; reportMeshFloorCell(30.2014, -97.6604, 200.0); reportMeshFloorCell(30.1994, -97.6604, 240.0); assert.ok(Math.abs(at(1000) - (200.0 + GROUND_FLOOR_LIFT_M)) < 0.05); @@ -1109,8 +1548,10 @@ test('display floor: a stationary contact FOLLOWS the neighbourhood as it fills reportMeshFloorCell(30.2004, -97.6614, 120.0); let h = 0; for (let ms = 1600; ms <= 6000; ms += 80) h = at(ms); - assert.ok(Math.abs(h - (120.0 + GROUND_FLOOR_LIFT_M)) < 0.05, - `followed to ${h} — a borrowed floor must not outlive a better one`); + assert.ok( + Math.abs(h - (120.0 + GROUND_FLOOR_LIFT_M)) < 0.05, + `followed to ${h} — a borrowed floor must not outlive a better one`, + ); }); test('display floor: an on_ground FLAP mid-takeoff-roll never dips below the runway', () => { @@ -1124,23 +1565,35 @@ test('display floor: an on_ground FLAP mid-takeoff-roll never dips below the run // the geoid. Deleting the hold on the airborne poll made that switch visible: // the contact came back grounded with no prior, outrunning its own floor // cells at 23 m/s, and sat under the runway (owner sighting, VIR138M). - const GROUND = -28.5, GEOID = -32.5, LON = -73.78; + const GROUND = -28.5, + GEOID = -32.5, + LON = -73.78; reportMeshFloorCell(40.64, LON, GROUND); // only the cell it STARTED on is warm let lat = 40.64; const grounded = []; for (let i = 0; i <= 22; i += 1) { const onGround = i !== 10; // one airborne poll mid-roll const out = _floorGroundedDisplayPositionForTest( - { onGround }, Cesium.Cartesian3.fromDegrees(LON, lat, GEOID), false, 'VIR138M', 1000 + i * 80, + { onGround }, + Cesium.Cartesian3.fromDegrees(LON, lat, GEOID), + false, + 'VIR138M', + 1000 + i * 80, ); if (onGround) grounded.push(_floorCarto(out).height); lat += 0.00021; // ~23 m per tick at 45 kt } const dipped = grounded.filter((h) => h < GROUND - 0.05); - assert.deepEqual(dipped, [], - `${dipped.length} of ${grounded.length} grounded ticks rendered below the runway`); - assert.ok(Math.abs(grounded[grounded.length - 1] - (GROUND + GROUND_FLOOR_LIFT_M)) < 0.05, - 'and it is still standing on the floor it held, not on nothing'); + assert.deepEqual( + dipped, + [], + `${dipped.length} of ${grounded.length} grounded ticks rendered below the runway`, + ); + assert.ok( + Math.abs(grounded[grounded.length - 1] - (GROUND + GROUND_FLOOR_LIFT_M)) < + 0.05, + 'and it is still standing on the floor it held, not on nothing', + ); }); test('display floor: the rehydration seed EXPIRES on its own age, with NO calls in between', () => { @@ -1150,20 +1603,45 @@ test('display floor: the rehydration seed EXPIRES on its own age, with NO calls const field = Cesium.Cartesian3.fromDegrees(-97.6604, 30.2004, 100); reportMeshFloorCell(30.2014, -97.6604, 200.0); reportMeshFloorCell(30.1994, -97.6604, 240.0); - assert.ok(Math.abs(_floorCarto(_floorGroundedDisplayPositionForTest( - { onGround: true }, field, false, 'longhaul', 1000, - )).height - (200.0 + GROUND_FLOOR_LIFT_M)) < 0.05, 'holding 200 m before departure'); + assert.ok( + Math.abs( + _floorCarto( + _floorGroundedDisplayPositionForTest( + { onGround: true }, + field, + false, + 'longhaul', + 1000, + ), + ).height - + (200.0 + GROUND_FLOOR_LIFT_M), + ) < 0.05, + 'holding 200 m before departure', + ); // ONE airborne tick parks the seed, and then this contact is not seen again // for 198 s: off the poll on a cruise, outside the corridor radius, tab // hidden. Nothing runs in between, so the retire path never gets a second // chance to expire it — the age has to be judged where it comes BACK. // An earlier cut checked expiry only on the retire path and cleared // `retiredMs` before validating it, so this reused the 200 m floor. - _floorGroundedDisplayPositionForTest({ onGround: false }, field, false, 'longhaul', 2000); + _floorGroundedDisplayPositionForTest( + { onGround: false }, + field, + false, + 'longhaul', + 2000, + ); _clearMeshFloorCellsForTest(); // a different airport: nothing warm here const back = Cesium.Cartesian3.fromDegrees(-97.6604, 30.2004, 100); assert.equal( - _floorGroundedDisplayPositionForTest({ onGround: true }, back, false, 'longhaul', 200000), back, + _floorGroundedDisplayPositionForTest( + { onGround: true }, + back, + false, + 'longhaul', + 200000, + ), + back, 'an expired seed is gone — no floor is invented from a 198-second-old measurement', ); }); @@ -1175,15 +1653,40 @@ test('display floor: the seed expires the same way when the contact keeps report const field = Cesium.Cartesian3.fromDegrees(-97.6604, 30.2004, 100); reportMeshFloorCell(30.2014, -97.6604, 200.0); reportMeshFloorCell(30.1994, -97.6604, 240.0); - _floorGroundedDisplayPositionForTest({ onGround: true }, field, false, 'cruise', 1000); + _floorGroundedDisplayPositionForTest( + { onGround: true }, + field, + false, + 'cruise', + 1000, + ); // The other half of the same clock: an airborne contact that IS still being // ticked. The window is the window either way. - _floorGroundedDisplayPositionForTest({ onGround: false }, field, false, 'cruise', 2000); - _floorGroundedDisplayPositionForTest({ onGround: false }, field, false, 'cruise', 200000); + _floorGroundedDisplayPositionForTest( + { onGround: false }, + field, + false, + 'cruise', + 2000, + ); + _floorGroundedDisplayPositionForTest( + { onGround: false }, + field, + false, + 'cruise', + 200000, + ); _clearMeshFloorCellsForTest(); const back = Cesium.Cartesian3.fromDegrees(-97.6604, 30.2004, 100); assert.equal( - _floorGroundedDisplayPositionForTest({ onGround: true }, back, false, 'cruise', 201000), back, + _floorGroundedDisplayPositionForTest( + { onGround: true }, + back, + false, + 'cruise', + 201000, + ), + back, 'no memory of the departure field survives the grace window', ); }); @@ -1199,10 +1702,28 @@ test('display floor: a rehydrated seed ranks BELOW fresh neighbour evidence', () setMeshFloorPreferred(true); const departed = Cesium.Cartesian3.fromDegrees(-97.6604, 30.2004, 50); reportMeshFloorCell(30.2004, -97.6604, 200.0); - assert.ok(Math.abs(_floorCarto(_floorGroundedDisplayPositionForTest( - { onGround: true }, departed, false, 'short-hop', 1000, - )).height - (200.0 + GROUND_FLOOR_LIFT_M)) < 0.05, 'standing on its own 200 m cell'); - _floorGroundedDisplayPositionForTest({ onGround: false }, departed, false, 'short-hop', 2000); + assert.ok( + Math.abs( + _floorCarto( + _floorGroundedDisplayPositionForTest( + { onGround: true }, + departed, + false, + 'short-hop', + 1000, + ), + ).height - + (200.0 + GROUND_FLOOR_LIFT_M), + ) < 0.05, + 'standing on its own 200 m cell', + ); + _floorGroundedDisplayPositionForTest( + { onGround: false }, + departed, + false, + 'short-hop', + 2000, + ); // Re-grounds 0.56 km away — INSIDE HELD_FLOOR_MAX_DRIFT_KM, so the drift // bound does not save us here; the tier order has to. Its own cell is cold, // but two adjacent cells have since resolved, and they are 100 m of ground. @@ -1210,11 +1731,19 @@ test('display floor: a rehydrated seed ranks BELOW fresh neighbour evidence', () reportMeshFloorCell(30.2044, -97.6604, 100.0); reportMeshFloorCell(30.2064, -97.6604, 105.0); const moved = Cesium.Cartesian3.fromDegrees(-97.6604, 30.2054, 50); - const rendered = _floorCarto(_floorGroundedDisplayPositionForTest( - { onGround: true }, moved, false, 'short-hop', 3000, - )).height; - assert.ok(Math.abs(rendered - (100.0 + GROUND_FLOOR_LIFT_M)) < 0.05, - `rendered at ${rendered} — a seed that outranked the neighbourhood read 201.5, 100 m in the air`); + const rendered = _floorCarto( + _floorGroundedDisplayPositionForTest( + { onGround: true }, + moved, + false, + 'short-hop', + 3000, + ), + ).height; + assert.ok( + Math.abs(rendered - (100.0 + GROUND_FLOOR_LIFT_M)) < 0.05, + `rendered at ${rendered} — a seed that outranked the neighbourhood read 201.5, 100 m in the air`, + ); }); test('display floor: with nothing fresh to contradict it, the seed still answers', () => { @@ -1226,14 +1755,34 @@ test('display floor: with nothing fresh to contradict it, the seed still answers // warmed either. Demoting the seed must not mean discarding it. const pos = Cesium.Cartesian3.fromDegrees(-97.6604, 30.2004, 50); reportMeshFloorCell(30.2004, -97.6604, 200.0); - _floorGroundedDisplayPositionForTest({ onGround: true }, pos, false, 'flap', 1000); - _floorGroundedDisplayPositionForTest({ onGround: false }, pos, false, 'flap', 2000); + _floorGroundedDisplayPositionForTest( + { onGround: true }, + pos, + false, + 'flap', + 1000, + ); + _floorGroundedDisplayPositionForTest( + { onGround: false }, + pos, + false, + 'flap', + 2000, + ); _clearMeshFloorCellsForTest(); // the whole neighbourhood goes cold - const held = _floorCarto(_floorGroundedDisplayPositionForTest( - { onGround: true }, Cesium.Cartesian3.fromDegrees(-97.6604, 30.2014, 50), false, 'flap', 3000, - )).height; - assert.ok(Math.abs(held - (200.0 + GROUND_FLOOR_LIFT_M)) < 0.05, - `held ${held} — with no measurement anywhere the seed is the best thing available`); + const held = _floorCarto( + _floorGroundedDisplayPositionForTest( + { onGround: true }, + Cesium.Cartesian3.fromDegrees(-97.6604, 30.2014, 50), + false, + 'flap', + 3000, + ), + ).height; + assert.ok( + Math.abs(held - (200.0 + GROUND_FLOOR_LIFT_M)) < 0.05, + `held ${held} — with no measurement anywhere the seed is the best thing available`, + ); }); test('display floor: re-measuring its own cell makes a seeded contact authoritative again', () => { @@ -1242,24 +1791,56 @@ test('display floor: re-measuring its own cell makes a seeded contact authoritat setMeshFloorPreferred(true); const pad = Cesium.Cartesian3.fromDegrees(-97.6604, 30.2004, 100); reportMeshFloorCell(30.2004, -97.6604, 300.0); - _floorGroundedDisplayPositionForTest({ onGround: true }, pad, false, 'relatch', 1000); - _floorGroundedDisplayPositionForTest({ onGround: false }, pad, false, 'relatch', 2000); + _floorGroundedDisplayPositionForTest( + { onGround: true }, + pad, + false, + 'relatch', + 1000, + ); + _floorGroundedDisplayPositionForTest( + { onGround: false }, + pad, + false, + 'relatch', + 2000, + ); // Re-grounds on its own, still-warm 300 m cell: that is a live reading, so the // demotion is over and the ordinary tier order applies again. - assert.ok(Math.abs(_floorCarto(_floorGroundedDisplayPositionForTest( - { onGround: true }, pad, false, 'relatch', 3000, - )).height - (300.0 + GROUND_FLOOR_LIFT_M)) < 0.05, 'back on its own measured cell'); + assert.ok( + Math.abs( + _floorCarto( + _floorGroundedDisplayPositionForTest( + { onGround: true }, + pad, + false, + 'relatch', + 3000, + ), + ).height - + (300.0 + GROUND_FLOOR_LIFT_M), + ) < 0.05, + 'back on its own measured cell', + ); // It now taxis 220 m onto a cold cell whose two adjacent cells read 100 m. An // own floor it measured while standing here outranks a borrowed one — the // seed flag has to have been cleared by that reading, or the contact drops // 200 m onto someone else's ground. reportMeshFloorCell(30.2014, -97.6604, 100.0); reportMeshFloorCell(30.2034, -97.6604, 100.0); - const taxied = _floorCarto(_floorGroundedDisplayPositionForTest( - { onGround: true }, Cesium.Cartesian3.fromDegrees(-97.6604, 30.2024, 100), false, 'relatch', 4000, - )).height; - assert.ok(Math.abs(taxied - (300.0 + GROUND_FLOOR_LIFT_M)) < 0.05, - `taxied at ${taxied} — a still-seeded contact would have taken the 100 m neighbours`); + const taxied = _floorCarto( + _floorGroundedDisplayPositionForTest( + { onGround: true }, + Cesium.Cartesian3.fromDegrees(-97.6604, 30.2024, 100), + false, + 'relatch', + 4000, + ), + ).height; + assert.ok( + Math.abs(taxied - (300.0 + GROUND_FLOOR_LIFT_M)) < 0.05, + `taxied at ${taxied} — a still-seeded contact would have taken the 100 m neighbours`, + ); }); test('display floor: taking off and landing elsewhere does NOT inherit the old field floor', () => { @@ -1269,25 +1850,62 @@ test('display floor: taking off and landing elsewhere does NOT inherit the old f const field = Cesium.Cartesian3.fromDegrees(-97.6604, 30.2004, 124.7); reportMeshFloorCell(30.2004, -97.6604, 200.0); // 1. grounded on a 200 m floor. - assert.ok(Math.abs(_floorCarto(_floorGroundedDisplayPositionForTest( - { onGround: true }, field, false, 'trip', 1000, - )).height - (200.0 + GROUND_FLOOR_LIFT_M)) < 0.05); + assert.ok( + Math.abs( + _floorCarto( + _floorGroundedDisplayPositionForTest( + { onGround: true }, + field, + false, + 'trip', + 1000, + ), + ).height - + (200.0 + GROUND_FLOOR_LIFT_M), + ) < 0.05, + ); // 2. it taxis ~330 m onto ground nothing has resolved, so 200 m is now being // HELD — this is the state that must not survive the trip. const heldAt = Cesium.Cartesian3.fromDegrees(-97.6604, 30.2034, 124.7); - assert.ok(Math.abs(_floorCarto(_floorGroundedDisplayPositionForTest( - { onGround: true }, heldAt, false, 'trip', 1500, - )).height - (200.0 + GROUND_FLOOR_LIFT_M)) < 0.05, 'the hold is active before takeoff'); + assert.ok( + Math.abs( + _floorCarto( + _floorGroundedDisplayPositionForTest( + { onGround: true }, + heldAt, + false, + 'trip', + 1500, + ), + ).height - + (200.0 + GROUND_FLOOR_LIFT_M), + ) < 0.05, + 'the hold is active before takeoff', + ); // 3. airborne — the hold belongs to the grounded-billboard regime, not to the // contact, so leaving that regime must retire it. - _floorGroundedDisplayPositionForTest({ onGround: false }, heldAt, false, 'trip', 2000); + _floorGroundedDisplayPositionForTest( + { onGround: false }, + heldAt, + false, + 'trip', + 2000, + ); // 4. lands at another field on a real 100 m floor. reportMeshFloorCell(31.5004, -97.6604, 100.0); - const landed = _floorCarto(_floorGroundedDisplayPositionForTest( - { onGround: true }, Cesium.Cartesian3.fromDegrees(-97.6604, 31.5004, 60), false, 'trip', 3000, - )).height; - assert.ok(Math.abs(landed - (100.0 + GROUND_FLOOR_LIFT_M)) < 0.05, - `landed at ${landed} — a stale hold would arrive at 201.5 and ease down from it`); + const landed = _floorCarto( + _floorGroundedDisplayPositionForTest( + { onGround: true }, + Cesium.Cartesian3.fromDegrees(-97.6604, 31.5004, 60), + false, + 'trip', + 3000, + ), + ).height; + assert.ok( + Math.abs(landed - (100.0 + GROUND_FLOOR_LIFT_M)) < 0.05, + `landed at ${landed} — a stale hold would arrive at 201.5 and ease down from it`, + ); }); test('display floor: a model-owned interval retires the hold too', () => { @@ -1296,21 +1914,47 @@ test('display floor: a model-owned interval retires the hold too', () => { setMeshFloorPreferred(true); const pos = Cesium.Cartesian3.fromDegrees(-97.6604, 30.2004, 124.7); reportMeshFloorCell(30.2004, -97.6604, 200.0); - _floorGroundedDisplayPositionForTest({ onGround: true }, pos, false, 'handoff', 1000); + _floorGroundedDisplayPositionForTest( + { onGround: true }, + pos, + false, + 'handoff', + 1000, + ); // It moves onto unresolved ground, so 200 m is being HELD. const heldAt = Cesium.Cartesian3.fromDegrees(-97.6604, 30.2034, 124.7); - _floorGroundedDisplayPositionForTest({ onGround: true }, heldAt, false, 'handoff', 1500); + _floorGroundedDisplayPositionForTest( + { onGround: true }, + heldAt, + false, + 'handoff', + 1500, + ); // T7: the 3D model takes the visual. Our state is retired (nothing visual is // touched — groundSnap's one-shot input is untouched either way). - _floorGroundedDisplayPositionForTest({ onGround: true }, heldAt, true, 'handoff', 2000); + _floorGroundedDisplayPositionForTest( + { onGround: true }, + heldAt, + true, + 'handoff', + 2000, + ); // The billboard resumes over ground that has since resolved LOWER. A stale // heldActive would read this as a hold release and ease; it is a fresh start. reportMeshFloorCell(30.2034, -97.6604, 150.0); - const back = _floorCarto(_floorGroundedDisplayPositionForTest( - { onGround: true }, heldAt, false, 'handoff', 3000, - )).height; - assert.ok(Math.abs(back - (150.0 + GROUND_FLOOR_LIFT_M)) < 0.05, - `resumed at ${back} — no ease, because there was no hold to release`); + const back = _floorCarto( + _floorGroundedDisplayPositionForTest( + { onGround: true }, + heldAt, + false, + 'handoff', + 3000, + ), + ).height; + assert.ok( + Math.abs(back - (150.0 + GROUND_FLOOR_LIFT_M)) < 0.05, + `resumed at ${back} — no ease, because there was no hold to release`, + ); }); test('display floor: a model handoff back onto a COLD cell takes the neighbourhood, not the seed', () => { @@ -1323,18 +1967,44 @@ test('display floor: a model handoff back onto a COLD cell takes the neighbourho // a floor it measured 330 m ago. Two adjacent cells have resolved at 150 m. const pos = Cesium.Cartesian3.fromDegrees(-97.6604, 30.2004, 124.7); reportMeshFloorCell(30.2004, -97.6604, 200.0); - _floorGroundedDisplayPositionForTest({ onGround: true }, pos, false, 'cold-handoff', 1000); + _floorGroundedDisplayPositionForTest( + { onGround: true }, + pos, + false, + 'cold-handoff', + 1000, + ); const heldAt = Cesium.Cartesian3.fromDegrees(-97.6604, 30.2034, 124.7); - _floorGroundedDisplayPositionForTest({ onGround: true }, heldAt, false, 'cold-handoff', 1500); - _floorGroundedDisplayPositionForTest({ onGround: true }, heldAt, true, 'cold-handoff', 2000); + _floorGroundedDisplayPositionForTest( + { onGround: true }, + heldAt, + false, + 'cold-handoff', + 1500, + ); + _floorGroundedDisplayPositionForTest( + { onGround: true }, + heldAt, + true, + 'cold-handoff', + 2000, + ); _clearMeshFloorCellsForTest(); reportMeshFloorCell(30.2024, -97.6604, 150.0); reportMeshFloorCell(30.2044, -97.6604, 155.0); - const back = _floorCarto(_floorGroundedDisplayPositionForTest( - { onGround: true }, heldAt, false, 'cold-handoff', 3000, - )).height; - assert.ok(Math.abs(back - (150.0 + GROUND_FLOOR_LIFT_M)) < 0.05, - `resumed at ${back} — a seed that outranked the neighbourhood would resume at 201.5`); + const back = _floorCarto( + _floorGroundedDisplayPositionForTest( + { onGround: true }, + heldAt, + false, + 'cold-handoff', + 3000, + ), + ).height; + assert.ok( + Math.abs(back - (150.0 + GROUND_FLOOR_LIFT_M)) < 0.05, + `resumed at ${back} — a seed that outranked the neighbourhood would resume at 201.5`, + ); }); // --- F10: no contact waits on another -------------------------------------- @@ -1357,15 +2027,28 @@ test('display floor: every grounded contact is floored on the tick it asks', () } const heights = []; for (let i = 0; i < N; i += 1) { - heights.push(_floorCarto(_floorGroundedDisplayPositionForTest( - { onGround: true }, Cesium.Cartesian3.fromDegrees(-97.66, latOf(i), 100), false, `q${i}`, 1000, - )).height); + heights.push( + _floorCarto( + _floorGroundedDisplayPositionForTest( + { onGround: true }, + Cesium.Cartesian3.fromDegrees(-97.66, latOf(i), 100), + false, + `q${i}`, + 1000, + ), + ).height, + ); } const unfloored = heights - .map((h, i) => (Math.abs(h - (300 + i + GROUND_FLOOR_LIFT_M)) < 0.05 ? null : i)) + .map((h, i) => + Math.abs(h - (300 + i + GROUND_FLOOR_LIFT_M)) < 0.05 ? null : i, + ) .filter((i) => i != null); - assert.deepEqual(unfloored, [], - 'a global budget would leave the tail of the fleet on the geoid for later ticks'); + assert.deepEqual( + unfloored, + [], + 'a global budget would leave the tail of the fleet on the geoid for later ticks', + ); }); test('display floor: a re-probe that finds a LOWER neighbour eases down, never snaps', () => { @@ -1382,9 +2065,14 @@ test('display floor: a re-probe that finds a LOWER neighbour eases down, never s if (i === 8) reportMeshFloorCell(30.2004, -97.6614, 120.0); // past the 500 ms probe throttle }); const totalDropM = r.opened - target; - assert.ok(r.maxStepM <= totalDropM * 0.25, - `largest single-tick drop ${r.maxStepM.toFixed(1)} m of ${totalDropM.toFixed(1)} m`); - assert.ok(Math.abs(r.settled - target) < 0.05, `settled at ${r.settled}, expected ${target}`); + assert.ok( + r.maxStepM <= totalDropM * 0.25, + `largest single-tick drop ${r.maxStepM.toFixed(1)} m of ${totalDropM.toFixed(1)} m`, + ); + assert.ok( + Math.abs(r.settled - target) < 0.05, + `settled at ${r.settled}, expected ${target}`, + ); }); test('display floor: a SECOND lower re-latch mid-approach retargets without a jump', () => { @@ -1404,9 +2092,14 @@ test('display floor: a SECOND lower re-latch mid-approach retargets without a ju if (i === 16) reportMeshFloorCell(30.2004, -97.6594, 100.0); // -> 100 m, mid-approach }); const totalDropM = r.opened - target; - assert.ok(r.maxStepM <= totalDropM * 0.25, - `largest single-tick drop ${r.maxStepM.toFixed(1)} m of ${totalDropM.toFixed(1)} m — the retarget must not be a seam`); - assert.ok(Math.abs(r.settled - target) < 0.05, `settled at ${r.settled}, expected ${target}`); + assert.ok( + r.maxStepM <= totalDropM * 0.25, + `largest single-tick drop ${r.maxStepM.toFixed(1)} m of ${totalDropM.toFixed(1)} m — the retarget must not be a seam`, + ); + assert.ok( + Math.abs(r.settled - target) < 0.05, + `settled at ${r.settled}, expected ${target}`, + ); }); test('display floor: a DELAYED tick cannot close more of the gap than a prompt one', () => { @@ -1414,9 +2107,16 @@ test('display floor: a DELAYED tick cannot close more of the gap than a prompt o _clearMeshFloorCellsForTest(); setMeshFloorPreferred(true); const pos = Cesium.Cartesian3.fromDegrees(-97.6604, 30.2004, 100); - const at = (ms) => _floorCarto(_floorGroundedDisplayPositionForTest( - { onGround: true }, pos, false, 'stall', ms, - )).height; + const at = (ms) => + _floorCarto( + _floorGroundedDisplayPositionForTest( + { onGround: true }, + pos, + false, + 'stall', + ms, + ), + ).height; reportMeshFloorCell(30.2014, -97.6604, 400.0); reportMeshFloorCell(30.1994, -97.6604, 440.0); // borrowed floor = 400 m const opened = at(1000); @@ -1435,8 +2135,10 @@ test('display floor: a DELAYED tick cannot close more of the gap than a prompt o maxStepM = Math.max(maxStepM, previous - h); previous = h; } - assert.ok(maxStepM <= totalDropM * 0.25, - `largest step ${maxStepM.toFixed(1)} m of ${totalDropM.toFixed(1)} m across a stalled schedule`); + assert.ok( + maxStepM <= totalDropM * 0.25, + `largest step ${maxStepM.toFixed(1)} m of ${totalDropM.toFixed(1)} m across a stalled schedule`, + ); assert.ok(previous < opened, 'and it is still making progress downward'); }); @@ -1447,9 +2149,16 @@ test('display floor: a floor rising MID-APPROACH is taken whole, on that tick', const pos = Cesium.Cartesian3.fromDegrees(-97.6604, 30.2004, 100); reportMeshFloorCell(30.2014, -97.6604, 400.0); reportMeshFloorCell(30.1994, -97.6604, 440.0); // borrowed floor = 400 m - const at = (ms) => _floorCarto(_floorGroundedDisplayPositionForTest( - { onGround: true }, pos, false, 'riseMid', ms, - )).height; + const at = (ms) => + _floorCarto( + _floorGroundedDisplayPositionForTest( + { onGround: true }, + pos, + false, + 'riseMid', + ms, + ), + ).height; at(1000); reportMeshFloorCell(30.2004, -97.6604, 150.0); // own cell resolves far below let h = 0; @@ -1460,8 +2169,10 @@ test('display floor: a floor rising MID-APPROACH is taken whole, on that tick', _clearMeshFloorCellsForTest(); reportMeshFloorCell(30.2004, -97.6604, 500.0); const risen = at(1000 + 5 * 80); - assert.ok(Math.abs(risen - (500.0 + GROUND_FLOOR_LIFT_M)) < 0.05, - `rose to ${risen} — an eased rise is time spent under the mesh`); + assert.ok( + Math.abs(risen - (500.0 + GROUND_FLOOR_LIFT_M)) < 0.05, + `rose to ${risen} — an eased rise is time spent under the mesh`, + ); }); test('display floor: a held floor is dropped once the contact leaves the ground it describes', () => { @@ -1469,18 +2180,38 @@ test('display floor: a held floor is dropped once the contact leaves the ground _clearMeshFloorCellsForTest(); setMeshFloorPreferred(true); reportMeshFloorCell(30.2004, -97.6604, 200.0); - const at = (lat, ms) => _floorGroundedDisplayPositionForTest( - { onGround: true }, Cesium.Cartesian3.fromDegrees(-97.6604, lat, 124.7), false, 'drift', ms, + const at = (lat, ms) => + _floorGroundedDisplayPositionForTest( + { onGround: true }, + Cesium.Cartesian3.fromDegrees(-97.6604, lat, 124.7), + false, + 'drift', + ms, + ); + assert.ok( + Math.abs( + _floorCarto(at(30.2004, 1000)).height - (200.0 + GROUND_FLOOR_LIFT_M), + ) < 0.05, ); - assert.ok(Math.abs(_floorCarto(at(30.2004, 1000)).height - (200.0 + GROUND_FLOOR_LIFT_M)) < 0.05); // ~670 m on: cold cell, but that is inside one poll's rollout. Hold stands. - assert.ok(Math.abs(_floorCarto(at(30.2064, 2000)).height - (200.0 + GROUND_FLOOR_LIFT_M)) < 0.05, - 'inside the drift bound the held floor still describes the ground'); + assert.ok( + Math.abs( + _floorCarto(at(30.2064, 2000)).height - (200.0 + GROUND_FLOOR_LIFT_M), + ) < 0.05, + 'inside the drift bound the held floor still describes the ground', + ); // ~3.3 km on: the held value is no longer a measurement of anywhere this // contact has been, so it is dropped rather than stretched. const far = Cesium.Cartesian3.fromDegrees(-97.6604, 30.2304, 124.7); assert.equal( - _floorGroundedDisplayPositionForTest({ onGround: true }, far, false, 'drift', 3000), far, + _floorGroundedDisplayPositionForTest( + { onGround: true }, + far, + false, + 'drift', + 3000, + ), + far, 'past the drift bound: back to no clamp, not a stretched one', ); }); @@ -1494,12 +2225,21 @@ test('display floor: an ordinary cell-to-cell floor DROP keeps its existing timi // untouched by this round. reportMeshFloorCell(30.2004, -97.6604, 200.0); reportMeshFloorCell(30.2044, -97.6604, 160.0); - const at = (lat, ms) => _floorCarto(_floorGroundedDisplayPositionForTest( - { onGround: true }, Cesium.Cartesian3.fromDegrees(-97.6604, lat, 124.7), false, 'plain', ms, - )).height; + const at = (lat, ms) => + _floorCarto( + _floorGroundedDisplayPositionForTest( + { onGround: true }, + Cesium.Cartesian3.fromDegrees(-97.6604, lat, 124.7), + false, + 'plain', + ms, + ), + ).height; assert.ok(Math.abs(at(30.2004, 1000) - (200.0 + GROUND_FLOOR_LIFT_M)) < 0.05); - assert.ok(Math.abs(at(30.2044, 1050) - (160.0 + GROUND_FLOOR_LIFT_M)) < 0.05, - 'the very next tick is on the new cell — no ease was introduced here'); + assert.ok( + Math.abs(at(30.2044, 1050) - (160.0 + GROUND_FLOOR_LIFT_M)) < 0.05, + 'the very next tick is on the new cell — no ease was introduced here', + ); }); test('display floor: the hold never applies to an airborne contact or a modelled one', () => { @@ -1511,11 +2251,23 @@ test('display floor: the hold never applies to an airborne contact or a modelled _floorGroundedDisplayPositionForTest({ onGround: true }, on, false, 'gates'); const cold = Cesium.Cartesian3.fromDegrees(-97.6704, 30.2104, 124.7); assert.equal( - _floorGroundedDisplayPositionForTest({ onGround: false }, cold, false, 'gates'), cold, + _floorGroundedDisplayPositionForTest( + { onGround: false }, + cold, + false, + 'gates', + ), + cold, 'airborne heights belong to the fix-time clamp, held floor or not', ); assert.equal( - _floorGroundedDisplayPositionForTest({ onGround: true }, cold, true, 'gates'), cold, + _floorGroundedDisplayPositionForTest( + { onGround: true }, + cold, + true, + 'gates', + ), + cold, 'T7: a model-owned contact keeps its billboard datum, held floor or not', ); }); @@ -1526,11 +2278,21 @@ test('display floor: two contacts on the same cell get their own outputs', () => setMeshFloorPreferred(true); reportMeshFloorCell(30.2004, -97.6604, 140); const a = _floorGroundedDisplayPositionForTest( - { onGround: true }, Cesium.Cartesian3.fromDegrees(-97.6604, 30.2004, 120), false, 'A', + { onGround: true }, + Cesium.Cartesian3.fromDegrees(-97.6604, 30.2004, 120), + false, + 'A', ); const b = _floorGroundedDisplayPositionForTest( - { onGround: true }, Cesium.Cartesian3.fromDegrees(-97.66041, 30.20041, 121), false, 'B', + { onGround: true }, + Cesium.Cartesian3.fromDegrees(-97.66041, 30.20041, 121), + false, + 'B', + ); + assert.notEqual( + a, + b, + 'a shared scratch would hand both contacts the same object', ); - assert.notEqual(a, b, 'a shared scratch would hand both contacts the same object'); assert.ok(Math.abs(_floorCarto(a).height - _floorCarto(b).height) < 0.05); }); diff --git a/src/layers/flights/snapshotRenderer.js b/src/layers/flights/snapshotRenderer.js index 369fb4af2..f6e74b125 100644 --- a/src/layers/flights/snapshotRenderer.js +++ b/src/layers/flights/snapshotRenderer.js @@ -181,10 +181,20 @@ export function createFlightSnapshotRenderer({ } 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. + // projected back to that delayed time, and a grounded contact is then + // lifted onto the floor under that delayed position. Creating it at + // the raw fix drew it ahead until the next fleet tick snapped it back; + // skipping the floor started a taxiing contact low and then raised it. + const delayed = motion._deadReckon(icao24, new Cesium.Cartesian3()); const displayed = - motion._deadReckon(icao24, new Cesium.Cartesian3()) || position; + (delayed && + motion._floorGroundedDisplayPosition( + icao24, + flightState.records.data.get(icao24) || meta, + delayed, + rendering._modelOwnsVisual(icao24), + )) || + position; const bb = flightState._billboardCollection.add({ position: displayed, image: aircraftIcon(rendering._iconKind(icao24, meta.klass)),