Files
gods-eye-view/src/data/trackedReadout.test.mjs
T
f4fcf267c6 fix(osm): stop using public Overpass by default (#648) (#742)
Traffic roads come from OpenFreeMap vector tiles, or TomTom flow tiles with a
key (Hybrid by default). Military areas use OpenFreeMap polygons with a bundled
worldwide name index; wide views show named points. ALPR reads an hourly
OpenStreetMap extract. Cockpit place context and country, state and US county
outlines use bundled Natural Earth and US Census data. Overpass is used only
when an operator configures their own endpoint.

Also: camera arrivals no longer land below ground, keyless terrain retries on
429, bounded relation geometry for configured Overpass, one shared
OpenStreetMap credit, and separate tile and API transports for embedders.

Co-authored-by: theGaryLarson <[email protected]>
Co-authored-by: Milan Khanal <[email protected]>
2026-09-28 19:07:57 -05:00

307 lines
13 KiB
JavaScript

import { readLayerSource } from '../testSupport/readLayerSource.mjs';
import test from 'node:test';
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import {
_setTrackedOverlayHostForTest,
cachedTrackedDisplayPosition,
cachedTrackedVisualPosition,
createTrackedOverlayEntry,
destroyTrackedReadout,
getActiveTrackedReadoutId,
initTrackedReadout,
refreshTrackedReadout,
trackedLabelModelFromText,
} from './trackedReadout.js';
function makeCesiumEvent() {
const listeners = new Set();
return {
addEventListener(callback) {
listeners.add(callback);
return () => listeners.delete(callback);
},
raise() { for (const callback of [...listeners]) callback(); },
get size() { return listeners.size; },
};
}
function makeHostRecorder() {
const calls = [];
return {
calls,
host: {
setEntries(sourceId, entries, options) { calls.push({ op: 'set', sourceId, entries, options }); },
setVisible(sourceId, visible) { calls.push({ op: 'visible', sourceId, visible }); },
clearSource(sourceId) { calls.push({ op: 'clear', sourceId }); },
},
};
}
test('tracked label text becomes an explicit title/details/accent model', () => {
assert.deepEqual(
trackedLabelModelFromText('UAL123 · FL350 · 451 kts', '#39d0ff'),
{ title: 'UAL123', details: ['FL350 · 451 kts'], accent: '#39d0ff' },
);
assert.deepEqual(
trackedLabelModelFromText('RCH123\nC-17 · 01-0186\nUSAF · FL310', '#ffd166'),
{
title: 'RCH123',
details: ['C-17 · 01-0186', 'USAF · FL310'],
accent: '#ffd166',
},
);
});
test('tracked position source reads only gevDisplayPosition and never entity.position', () => {
const cached = { x: 1, y: 2, z: 3 };
let propertyReads = 0;
const entity = {
gevDisplayPosition: () => cached,
position: {
getValue() { propertyReads += 1; throw new Error('fresh position read is forbidden'); },
},
};
assert.equal(cachedTrackedDisplayPosition(entity), cached);
assert.equal(propertyReads, 0);
assert.equal(cachedTrackedDisplayPosition({ position: entity.position }), null);
assert.equal(propertyReads, 0);
});
test('the tracked card anchors to the visual position without repurposing the display accessor', () => {
// A grounded aircraft's 3D model rides a ground snap while its billboard stays
// at the reported (buried) altitude — ~100 m apart. The card must follow the
// model you can see, while `gevDisplayPosition` keeps returning the cached
// dead-reckoned value the follow camera settled on (anti-jitter contract).
const display = { x: 1, y: 2, z: 3 };
const visual = { x: 1, y: 2, z: 103 };
let displayReads = 0;
const entity = {
gevTrackedId: 'flights:aaa077',
gevDisplayPosition: () => { displayReads += 1; return display; },
gevVisualPosition: () => visual,
gevLabelModel: { title: 'SWA143', details: ['ON GROUND'], accent: '#6be8ff' },
};
assert.equal(cachedTrackedVisualPosition(entity), visual);
assert.equal(displayReads, 0, 'the visual accessor short-circuits the display read');
assert.equal(cachedTrackedDisplayPosition(entity), display,
'the display accessor is untouched and still reports the dead-reckoned position');
assert.equal(createTrackedOverlayEntry(entity).position(), visual,
'the published tracked entry anchors to the visual position');
// Layers with no 3D visual, and any layer before its model is ready, are
// unchanged: the accessor falls back to the display position.
const spriteOnly = { gevDisplayPosition: () => display };
assert.equal(cachedTrackedVisualPosition(spriteOnly), display);
assert.equal(cachedTrackedVisualPosition({ ...spriteOnly, gevVisualPosition: () => null }), display,
'a null visual position falls back rather than blanking the card');
assert.equal(
cachedTrackedVisualPosition({
gevDisplayPosition: () => display,
gevVisualPosition: () => { throw new Error('model gone'); },
}),
display,
'a throwing visual accessor falls back instead of dropping the card',
);
assert.equal(cachedTrackedVisualPosition(null), null);
});
test('tracked entry factory pins the production protected-lane policy', () => {
const display = { x: 4, y: 5, z: 6 };
const entry = createTrackedOverlayEntry({
gevTrackedId: 'satellites:25544',
gevDisplayPosition: () => display,
gevLabelModel: { title: 'ISS', details: ['420 km · NORAD 25544'], accent: '#ffd84d' },
});
assert.equal(entry.id, 'satellites:25544');
assert.equal(entry.position(), display);
assert.equal(entry.protected, true);
assert.equal(entry.paintLane, 'tracked');
assert.equal(entry.collisionGroup, 'ambient-card');
assert.equal(entry.edgeFade, 'keyhole');
});
test('selected camera readout carries tactical animation and fixed badge clearance', () => {
const entry = createTrackedOverlayEntry({
id: 'alpr:42', gevDisplayPosition: () => ({ x: 1, y: 2, z: 3 }),
gevLabelModel: {
title: 'ALPR-0042', details: ['OSM MAPPED'], accent: '#ff6474',
cardStyle: 'tactical', selected: true, leaderStyle: 'elbow',
leaderAnimationMs: 440, leaderAnimationStartedAt: 100, leaderDrawRatio: 0.68,
anchorRadiusPx: 30, anchorRadiusScale: null,
},
});
assert.equal(entry.cardStyle, 'tactical');
assert.equal(entry.selected, true);
assert.equal(entry.leaderStyle, 'elbow');
assert.equal(entry.leaderAnimationMs, 440);
assert.equal(entry.leaderAnimationStartedAt, 100);
assert.equal(entry.leaderDrawRatio, 0.68);
assert.equal(entry.anchorRadiusPx, 30);
assert.equal(entry.anchorRadiusScale, null);
});
test('tracked entity publishes a protected host entry backed by the frame cache', () => {
const originalWindow = globalThis.window;
const fakeWindow = new EventTarget();
const changed = makeCesiumEvent();
const display = { x: 10, y: 20, z: 30 };
const entity = {
id: 'generated',
gevTrackedId: 'flights:abc123',
gevDisplayPosition: () => display,
gevLabelModel: { title: 'UAL123', details: ['FL350 · 451 kts'], accent: '#39d0ff' },
};
const viewer = { trackedEntity: entity, trackedEntityChanged: changed };
const recorder = makeHostRecorder();
globalThis.window = fakeWindow;
_setTrackedOverlayHostForTest(recorder.host);
try {
initTrackedReadout(viewer);
const publication = recorder.calls.find(({ op }) => op === 'set');
assert.ok(publication);
assert.equal(publication.sourceId, 'tracked');
assert.equal(publication.entries.length, 1);
const entry = publication.entries[0];
assert.equal(entry.id, 'flights:abc123');
assert.equal(entry.variant, 'tracked');
assert.equal(entry.tracked, true);
assert.equal(entry.protected, true);
assert.equal(entry.paintLane, 'tracked');
assert.equal(entry.collisionGroup, 'ambient-card');
assert.equal(entry.position(), display);
assert.equal(publication.options.collisionCapacity, 0);
assert.equal(publication.options.hideInCockpit, true);
assert.equal(getActiveTrackedReadoutId(), 'flights:abc123');
entity.gevLabelModel = { ...entity.gevLabelModel, details: ['FL360 · 455 kts'] };
refreshTrackedReadout(entity);
assert.deepEqual(recorder.calls.filter(({ op }) => op === 'set').at(-1).entries[0].details, [
'FL360 · 455 kts',
]);
viewer.trackedEntity = null;
changed.raise();
assert.equal(getActiveTrackedReadoutId(), null);
assert.equal(recorder.calls.at(-1).op, 'clear');
} finally {
destroyTrackedReadout();
_setTrackedOverlayHostForTest();
globalThis.window = originalWindow;
}
});
test('selection lifecycle leaves installations to their own entry and retains ALPR cards', () => {
const originalWindow = globalThis.window;
const fakeWindow = new EventTarget();
const changed = makeCesiumEvent();
const viewer = { trackedEntity: null, trackedEntityChanged: changed };
const recorder = makeHostRecorder();
const installation = {
gevTrackedId: 'installations:fort-test',
gevDisplayPosition: () => ({ x: 1, y: 2, z: 3 }),
gevLabelModel: { title: 'FORT TEST', details: ['AIRFIELD'], accent: '#5aa9ff' },
};
globalThis.window = fakeWindow;
_setTrackedOverlayHostForTest(recorder.host);
try {
initTrackedReadout(viewer);
const setsBefore = recorder.calls.filter(({ op }) => op === 'set').length;
fakeWindow.dispatchEvent(new CustomEvent('gev:entity-selected', {
detail: { layerId: 'ais-live-vessels', entity: installation },
}));
assert.equal(recorder.calls.filter(({ op }) => op === 'set').length, setsBefore);
fakeWindow.dispatchEvent(new CustomEvent('gev:entity-selected', {
detail: { layerId: 'military-installations', entity: installation },
}));
assert.equal(getActiveTrackedReadoutId(), null);
assert.equal(recorder.calls.filter(({ op }) => op === 'set').length, setsBefore, 'installation selection cannot create a second overlay');
// ALPR cameras are static context too: a click publishes the same card.
const camera = {
gevTrackedId: 'alpr:42',
gevDisplayPosition: () => ({ x: 4, y: 5, z: 6 }),
gevLabelModel: { title: 'FLOCK SAFETY ALPR', details: ['CITY PD'], accent: '#ff66c4' },
};
fakeWindow.dispatchEvent(new CustomEvent('gev:entity-selected', {
detail: { layerId: 'alpr-cameras', entity: camera },
}));
assert.equal(getActiveTrackedReadoutId(), 'alpr:42');
assert.equal(recorder.calls.filter(({ op }) => op === 'set').at(-1).entries[0].title, 'FLOCK SAFETY ALPR');
fakeWindow.dispatchEvent(new CustomEvent('gev:entity-selected', {
detail: { layerId: 'ais-live-vessels', entity: installation },
}));
assert.equal(getActiveTrackedReadoutId(), null, 'sibling selection clears an installation card');
fakeWindow.dispatchEvent(new CustomEvent('gev:entity-selected', {
detail: { layerId: 'military-installations', entity: installation },
}));
fakeWindow.dispatchEvent(new CustomEvent('gev:entity-selection-cleared', {
detail: { layerId: 'military-installations' },
}));
assert.equal(getActiveTrackedReadoutId(), null);
assert.ok(recorder.calls.some(({ op }) => op === 'clear'));
const clearsBeforeDestroy = recorder.calls.filter(({ op }) => op === 'clear').length;
destroyTrackedReadout();
assert.ok(recorder.calls.filter(({ op }) => op === 'clear').length > clearsBeforeDestroy);
assert.equal(changed.size, 0, 'trackedEntityChanged listener was removed');
assert.ok(recorder.calls.some(({ op, visible }) => op === 'visible' && visible === false));
} finally {
destroyTrackedReadout();
_setTrackedOverlayHostForTest();
globalThis.window = originalWindow;
}
});
test('trackedReadout cannot resurrect a dedicated canvas or render listener', async () => {
const source = await readFile(new URL('./trackedReadout.js', import.meta.url), 'utf8');
for (const forbidden of [
'document.createElement',
"createElement('canvas')",
'postRender.addEventListener',
'worldToWindowCoordinates',
"id = 'tracked-readout'",
'keyholeLabelAlpha',
]) {
assert.equal(source.includes(forbidden), false, `dedicated renderer token returned: ${forbidden}`);
}
});
test('tracking layers write gevLabelModel and expose only their cached display positions', async () => {
const files = await Promise.all([
'flights.js',
'militaryFlights.js',
'satellites.js',
'militaryInstallations.js',
].map(async (name) => [name, readLayerSource(new URL(`./${name}`, import.meta.url))]));
const sources = Object.fromEntries(files);
for (const [name, source] of files) {
assert.ok(source.includes('.gevLabelModel ='), `${name} writes the explicit model directly`);
assert.ok(source.includes('.gevDisplayPosition ='), `${name} exposes a display-position cache`);
}
assert.match(sources['flights.js'], /gevDisplayPosition\s*=\s*parts\.motion\._trackedDisplayCached/);
assert.match(sources['militaryFlights.js'], /gevDisplayPosition\s*=\s*parts\.motion\._trackedDisplayCached/);
assert.ok(sources['satellites.js'].includes('gevDisplayPosition = _trackedDisplayCached'));
assert.equal(sources['flights.js'].includes('_trackedEntity.label.text'), false);
assert.equal(sources['militaryFlights.js'].includes('_trackedEntity.label.text'), false);
assert.equal(sources['satellites.js'].includes('_trackedEntity.label.text'), false);
});
test('civilian and military trail heads use the lower-centre model anchor and weak-texture tint', async () => {
const files = await Promise.all(['flights.js', 'militaryFlights.js'].map(async (name) => (
[name, readLayerSource(new URL(`./${name}`, import.meta.url))]
)));
for (const [name, source] of files) {
assert.ok(
(/const\s*head\s*=\s*(?:parts\.motion\.)?_trackedTrailCached\(\s*,?\s*\)\s*\|\|\s*(?:parts\.motion\.)?_trackedDisplayPosition\(\s*(?:flightState\.)?_trackedIcao,?\s*\);/).test(source),
`${name} trail head uses the dedicated lower-centre model anchor`,
);
assert.ok(source.includes('const MODEL_COLOR_BLEND_AMOUNT = 0.94;'),
`${name} keeps diffuse texture contribution weak through code-side MIX`);
}
});