mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
An ESLint no-unused-vars scan over src, server, scripts, tools, build and pinokio, repeated until it is stable, reported these. Runtime modules keep every call and drop only the unread binding: - src/ui/cockpitInput.js: the unread result of enter()/exit(). - src/layers/launches/rendering.js: the unread entity returned by entities.add(). - src/layers/radio/rendering.js: the unread map key in the horizon scan, which now iterates the map's values. QA scripts and tools drop an unused helper, two unused in-page module imports, unused counters and destructured fields, and an unread wait result. Tests drop source files read into variables that no assertion uses, redundant StyleManager imports (the shell-source helper already imports it), and unused destructured fields. The one remaining report is the `_` loop variable in src/tools/schema.js, which counts iterations and is intentional.
962 lines
45 KiB
JavaScript
962 lines
45 KiB
JavaScript
import { readLayerSource } from '../testSupport/readLayerSource.mjs';
|
||
// src/data/trackedModelRegime.test.mjs
|
||
//
|
||
// Zoom-driven 2D↔3D for the TRACKED contact (owner directive 2026-08-19).
|
||
//
|
||
// Two things are pinned here, and both are behavioural rather than cosmetic:
|
||
//
|
||
// 1. The POLICY — thresholds and hysteresis — as pure math in
|
||
// trackedModelRegime.js. The enter ceiling is the owner's playtested swap
|
||
// distance, deliberately much NEARER than the fleet ceiling, and the exit
|
||
// ceiling is deliberately higher than the enter ceiling. A regression that
|
||
// collapsed the two thresholds back into one would silently reintroduce
|
||
// billboard↔model flapping for a camera orbiting the boundary, which no
|
||
// unit test would otherwise catch.
|
||
//
|
||
// 2. The WIRING in both flight layers — that the tracked contact takes its
|
||
// model with the fleet `models3d` toggle OFF (the whole point of the
|
||
// feature), that cockpit and TR-3B suppression survived the rewrite, and
|
||
// that deselecting drops the regime.
|
||
import test from 'node:test';
|
||
import assert from 'node:assert/strict';
|
||
import {
|
||
trackedModelZoomActive,
|
||
TRACKED_MODEL_ENTER_ALT_M,
|
||
TRACKED_MODEL_EXIT_ALT_M,
|
||
FLEET_MODEL_ALT_CEIL_M,
|
||
TRACKED_MODEL_EXIT_RATIO,
|
||
} from './trackedModelRegime.js';
|
||
import * as Cesium from 'cesium';
|
||
import flightsLayer, {
|
||
_setTrackedFlightRefreshStateForTest,
|
||
_trackedModelRegimeActiveForTest as flightsRegimeActive,
|
||
_updateTrackedModelForTest as flightsDriveTrackedModel,
|
||
_trackedBillboardColorForTest as flightsTrackedBillboardColor,
|
||
_driveFleetModelHandoffForTest as flightsDriveFleetModel,
|
||
_ensureFleetModelForTest as flightsEnsureFleetModel,
|
||
} from './flights.js';
|
||
import militaryFlightsLayer, {
|
||
_setTrackedMilitaryRefreshStateForTest,
|
||
_trackedModelRegimeActiveForTest as militaryRegimeActive,
|
||
_updateTrackedModelForTest as militaryDriveTrackedModel,
|
||
_trackedBillboardColorForTest as militaryTrackedBillboardColor,
|
||
_driveFleetModelHandoffForTest as militaryDriveFleetModel,
|
||
_ensureFleetModelForTest as militaryEnsureFleetModel,
|
||
} from './militaryFlights.js';
|
||
import { clearTr3bRegistry, setTr3b } from './tr3bRegistry.js';
|
||
import { reportMeshFloorCell, _clearMeshFloorCellsForTest } from './groundFloor.js';
|
||
|
||
// Sample altitudes are expressed RELATIVE to the band rather than as absolute
|
||
// offsets. The thresholds have already been retuned once (1_000_000 → 150_000
|
||
// on the owner's playtest), and the fixed ±50 km / ±100 km offsets that were
|
||
// fine against a 150 km-wide band silently landed on the WRONG SIDE of the exit
|
||
// ceiling against a 22.5 km one. Deriving them keeps the next retune honest.
|
||
const BAND_WIDTH_M = TRACKED_MODEL_EXIT_ALT_M - TRACKED_MODEL_ENTER_ALT_M;
|
||
/** Strictly between enter and exit: latched → holds, fresh → does not. */
|
||
const INSIDE_BAND_M = TRACKED_MODEL_ENTER_ALT_M + BAND_WIDTH_M / 2;
|
||
/** Unambiguously past the exit ceiling. */
|
||
const FAR_OUTSIDE_M = TRACKED_MODEL_EXIT_ALT_M * 2;
|
||
/** Unambiguously below the enter ceiling. */
|
||
const WELL_INSIDE_M = TRACKED_MODEL_ENTER_ALT_M / 2;
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 1. Policy: thresholds + hysteresis math
|
||
// ---------------------------------------------------------------------------
|
||
|
||
test('the tracked 3D takeover sits at the owner-playtested swap distance', () => {
|
||
// Owner playtest 2026-08-20: an earlier 1_000_000 m ceiling "pops to 3D far
|
||
// too early" — 2D still reads correctly at ~600_000 m and the swap belongs at
|
||
// ~150_000 m. These are the numbers the operator judged by eye, so they are
|
||
// pinned literally rather than derived from anything.
|
||
assert.equal(TRACKED_MODEL_ENTER_ALT_M, 150_000);
|
||
assert.equal(trackedModelZoomActive(600_000, false), false,
|
||
'the owner explicitly called 2D correct at ~600 km — it must not model there');
|
||
assert.equal(trackedModelZoomActive(140_000, false), true,
|
||
'inside the ruled swap distance the model owns the visual');
|
||
});
|
||
|
||
test('the tracked contact swaps NEARER than the fleet — a recorded inversion, not a bug', () => {
|
||
// Consequence of the owner's number, spelled out so it cannot be "tidied
|
||
// away": with the DISPLAY-rail 3D toggle ON, camera altitudes between the
|
||
// tracked ceiling and the fleet ceiling draw surrounding contacts as models
|
||
// while the SELECTED one is still a billboard. The fleet pass skips the
|
||
// tracked icao, so nothing double-draws — this is purely an ordering
|
||
// difference. Aligning them is a fleet-side decision, out of scope here.
|
||
assert.equal(FLEET_MODEL_ALT_CEIL_M, 800_000,
|
||
'mirror of MODEL_ALT_CEIL_M in both flight layers');
|
||
assert.ok(TRACKED_MODEL_ENTER_ALT_M < FLEET_MODEL_ALT_CEIL_M,
|
||
'the tracked contact deliberately enters 3D closer in than the fleet does');
|
||
assert.equal(trackedModelZoomActive(FLEET_MODEL_ALT_CEIL_M - 1, false), false,
|
||
'just inside the fleet ceiling the tracked contact is still 2D');
|
||
});
|
||
|
||
test('enter and exit thresholds are ASYMMETRIC — the anti-flap band', () => {
|
||
assert.equal(TRACKED_MODEL_EXIT_RATIO, 1.15);
|
||
assert.equal(TRACKED_MODEL_EXIT_ALT_M, 172_500);
|
||
assert.equal(TRACKED_MODEL_EXIT_ALT_M, TRACKED_MODEL_ENTER_ALT_M * TRACKED_MODEL_EXIT_RATIO,
|
||
'the exit ceiling stays derived from enter, so retuning enter carries the band with it');
|
||
assert.ok(TRACKED_MODEL_EXIT_ALT_M > TRACKED_MODEL_ENTER_ALT_M,
|
||
'a single shared threshold is exactly the flapping bug this prevents');
|
||
});
|
||
|
||
test('the regime enters at the enter ceiling and leaves only past the exit ceiling', () => {
|
||
// Coming in from far out: nothing below the enter ceiling → still 2D.
|
||
assert.equal(trackedModelZoomActive(TRACKED_MODEL_EXIT_ALT_M + 1, false), false);
|
||
assert.equal(trackedModelZoomActive(TRACKED_MODEL_ENTER_ALT_M + 1, false), false);
|
||
assert.equal(trackedModelZoomActive(TRACKED_MODEL_ENTER_ALT_M, false), false,
|
||
'the ceiling itself is still outside — the regime is strictly below it');
|
||
assert.equal(trackedModelZoomActive(TRACKED_MODEL_ENTER_ALT_M - 1, false), true);
|
||
|
||
// Already inside: the model holds the visual all the way out to the exit ceiling.
|
||
assert.equal(trackedModelZoomActive(TRACKED_MODEL_ENTER_ALT_M + 1, true), true,
|
||
'crossing back over the ENTER ceiling must not hand back — that is the flap');
|
||
assert.equal(trackedModelZoomActive(TRACKED_MODEL_EXIT_ALT_M - 1, true), true);
|
||
assert.equal(trackedModelZoomActive(TRACKED_MODEL_EXIT_ALT_M, true), false);
|
||
assert.equal(trackedModelZoomActive(TRACKED_MODEL_EXIT_ALT_M + 1, true), false);
|
||
});
|
||
|
||
test('an orbit sitting AT the boundary never flaps', () => {
|
||
// Simulate a camera loitering on the enter ceiling with orbital
|
||
// wobble. Under a single threshold this alternates every sample.
|
||
let active = trackedModelZoomActive(TRACKED_MODEL_ENTER_ALT_M - BAND_WIDTH_M / 4, false);
|
||
assert.equal(active, true, 'the orbit begins inside the regime');
|
||
let transitions = 0;
|
||
for (let i = 0; i < 40; i++) {
|
||
const wobble = (i % 2 === 0 ? 1 : -1) * (BAND_WIDTH_M / 8);
|
||
const next = trackedModelZoomActive(TRACKED_MODEL_ENTER_ALT_M + wobble, active);
|
||
if (next !== active) transitions++;
|
||
active = next;
|
||
}
|
||
assert.equal(transitions, 0, 'the hysteresis band absorbs boundary wobble entirely');
|
||
assert.equal(active, true);
|
||
});
|
||
|
||
test('a missing camera height reads as infinitely far out, never as "zoomed in"', () => {
|
||
for (const height of [undefined, null, NaN, Infinity]) {
|
||
assert.equal(trackedModelZoomActive(height, false), false);
|
||
assert.equal(trackedModelZoomActive(height, true), false,
|
||
'a torn-down viewer must not latch the model on');
|
||
}
|
||
});
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 2. Wiring: both flight layers
|
||
// ---------------------------------------------------------------------------
|
||
|
||
const ICAO = 'abc123';
|
||
|
||
/** `stopTracking()` is the real deselect path, and it announces the clear on
|
||
* `window`. Give it somewhere to land so the test drives production code
|
||
* rather than a stand-in. */
|
||
if (typeof globalThis.window === 'undefined') {
|
||
globalThis.window = { dispatchEvent() { return true; }, addEventListener() {}, removeEventListener() {} };
|
||
}
|
||
|
||
/** A viewer stub whose camera altitude the test can move between evaluations. */
|
||
function viewerAtHeight(height, { tilesLoaded = true, sampleHeight = () => undefined } = {}) {
|
||
const tileset = { show: true, tilesLoaded };
|
||
return {
|
||
camera: {
|
||
positionCartographic: { height },
|
||
positionWC: Cesium.Cartesian3.fromDegrees(-97.71, 30.21, 5000),
|
||
frustum: { fovy: Cesium.Math.toRadians(60) },
|
||
},
|
||
scene: {
|
||
canvas: { clientHeight: 900 },
|
||
frameState: { frameNumber: 1 },
|
||
primitives: { length: 1, get() { return tileset; } },
|
||
sampleHeight,
|
||
},
|
||
tileset,
|
||
trackedEntity: undefined,
|
||
entities: { remove() {} },
|
||
};
|
||
}
|
||
|
||
/** A billboard with a real position, so the tracked-model driver has somewhere
|
||
* to put a model when it gets one (dead reckoning yields nothing without a
|
||
* fix history, and the driver falls back to this). */
|
||
function contactBillboard() {
|
||
return {
|
||
position: Cesium.Cartesian3.fromDegrees(-97.71, 30.21, 10_668),
|
||
color: null,
|
||
show: true,
|
||
rotation: 0,
|
||
};
|
||
}
|
||
|
||
/** Records what the driver hands to the scene, so a test can assert that a
|
||
* failing loader never put a model up. */
|
||
function recordingModelCollection() {
|
||
const added = [];
|
||
return { added, add(m) { added.push(m); }, remove() {}, isDestroyed() { return false; } };
|
||
}
|
||
|
||
const LAYERS = [
|
||
{
|
||
name: 'flights',
|
||
layer: flightsLayer,
|
||
regimeActive: flightsRegimeActive,
|
||
driveTrackedModel: flightsDriveTrackedModel,
|
||
driveFleetModel: flightsDriveFleetModel,
|
||
ensureFleetModel: flightsEnsureFleetModel,
|
||
trackedBillboardColor: flightsTrackedBillboardColor,
|
||
bellyM: 6.719,
|
||
seed({ viewer, tracked = true, icao24 = ICAO, modelCollection = null, onGround = false, models = [] }) {
|
||
const billboard = contactBillboard();
|
||
_setTrackedFlightRefreshStateForTest({
|
||
icao24,
|
||
entity: null,
|
||
billboard,
|
||
billboardCollection: { show: true, remove() {} },
|
||
viewer,
|
||
tracked,
|
||
models,
|
||
modelCollection,
|
||
meta: { callsign: 'TEST123 ', altitude: 10_668, klass: 'airliner', onGround },
|
||
});
|
||
return billboard;
|
||
},
|
||
},
|
||
{
|
||
name: 'militaryFlights',
|
||
layer: militaryFlightsLayer,
|
||
regimeActive: militaryRegimeActive,
|
||
driveTrackedModel: militaryDriveTrackedModel,
|
||
driveFleetModel: militaryDriveFleetModel,
|
||
ensureFleetModel: militaryEnsureFleetModel,
|
||
trackedBillboardColor: militaryTrackedBillboardColor,
|
||
bellyM: 5.631 * 0.8,
|
||
seed({ viewer, tracked = true, icao24 = ICAO, modelCollection = null, onGround = false, models = [] }) {
|
||
const billboard = contactBillboard();
|
||
_setTrackedMilitaryRefreshStateForTest({
|
||
icao24,
|
||
entity: null,
|
||
billboard,
|
||
billboardCollection: { show: true, remove() {} },
|
||
viewer,
|
||
tracked,
|
||
models,
|
||
modelCollection,
|
||
meta: { callsign: 'TEST123 ', altitudeFt: 35_000, klass: 'fastjet', onGround },
|
||
});
|
||
return billboard;
|
||
},
|
||
},
|
||
];
|
||
|
||
for (const fixture of LAYERS) {
|
||
test(`${fixture.name}: production fleet loader admits a HIDDEN primitive and fails back to 2D`, async () => {
|
||
clearTr3bRegistry();
|
||
fixture.layer.stopTracking();
|
||
fixture.layer.setParams({ models3d: true });
|
||
|
||
const realFrom = Cesium.Model.fromGltfAsync;
|
||
const viewer = viewerAtHeight(30_000, { tilesLoaded: false });
|
||
const position = Cesium.Cartesian3.fromDegrees(-97.71, 30.21, 10_668);
|
||
const loadedCollection = recordingModelCollection();
|
||
const loadingModel = {
|
||
modelMatrix: Cesium.Matrix4.clone(Cesium.Matrix4.IDENTITY),
|
||
ready: false,
|
||
show: true,
|
||
scale: 1,
|
||
destroy() {},
|
||
};
|
||
|
||
try {
|
||
const loadingIcao = fixture.name === 'flights' ? 'a0f201' : 'b0f201';
|
||
Cesium.Model.fromGltfAsync = () => Promise.resolve(loadingModel);
|
||
const billboard = fixture.seed({
|
||
viewer,
|
||
tracked: false,
|
||
icao24: loadingIcao,
|
||
onGround: true,
|
||
modelCollection: loadedCollection,
|
||
});
|
||
const admitted = await fixture.ensureFleetModel(loadingIcao);
|
||
assert.equal(admitted, loadingModel, 'the production fleet loader admits the resolved primitive');
|
||
assert.equal(loadedCollection.added.length, 1, 'the production loader adds it once');
|
||
assert.equal(loadingModel.ready, false);
|
||
// Cesium 1.138's Model.update has no `show` guard — PrimitiveCollection
|
||
// updates every member unconditionally and `_ready` flips in afterRender —
|
||
// so hiding an admitted primitive costs its load nothing, and it is the
|
||
// only thing that keeps Cesium's default show=true from claiming the
|
||
// visual at the identity matrix before the first tick places it.
|
||
assert.equal(loadingModel.show, false, 'admission hides the primitive until it is placed');
|
||
assert.equal(loadingModel.scale, 1, 'and never touches its scale to do it');
|
||
assert.equal(billboard.show, true, 'the 2D billboard owns the cold loading state');
|
||
|
||
const nearby = fixture.layer.getNearby(billboard.position, 1_000, 50)
|
||
.find((object) => object.icao24 === loadingIcao);
|
||
assert.ok(nearby, 'fleet proximity retains the billboard-owned loading contact');
|
||
assert.ok(Cesium.Cartesian3.equalsEpsilon(nearby.position, billboard.position, 0, 1e-6),
|
||
'fleet proximity stays at the billboard rather than the unplaced model');
|
||
|
||
fixture.driveFleetModel({ icao24: loadingIcao, position, course: 92 });
|
||
assert.equal(loadingModel.show, false);
|
||
assert.equal(loadingModel.scale, 1);
|
||
assert.equal(billboard.show, true, 'cold terrain preserves the same visible 2D owner');
|
||
|
||
const failedIcao = fixture.name === 'flights' ? 'a0f202' : 'b0f202';
|
||
const failedCollection = recordingModelCollection();
|
||
Cesium.Model.fromGltfAsync = () => Promise.reject(new Error('simulated fleet GLB failure'));
|
||
const failedBillboard = fixture.seed({
|
||
viewer,
|
||
tracked: false,
|
||
icao24: failedIcao,
|
||
onGround: true,
|
||
modelCollection: failedCollection,
|
||
});
|
||
const failedModel = await fixture.ensureFleetModel(failedIcao);
|
||
assert.equal(failedModel, null, 'a rejected fleet GLB is never admitted');
|
||
assert.equal(failedCollection.added.length, 0);
|
||
assert.equal(failedBillboard.show, true,
|
||
'fleet load rejection leaves the 2D owner visible');
|
||
assert.ok(fixture.layer.getNearby(failedBillboard.position, 1_000, 50)
|
||
.some((object) => object.icao24 === failedIcao),
|
||
'fleet load rejection leaves the contact in the billboard proximity cohort');
|
||
} finally {
|
||
Cesium.Model.fromGltfAsync = realFrom;
|
||
fixture.layer.setParams({ models3d: false });
|
||
fixture.layer.stopTracking();
|
||
}
|
||
});
|
||
|
||
test(`${fixture.name}: grounded fleet handoff waits through terrain load and keeps one visual owner`, () => {
|
||
clearTr3bRegistry();
|
||
fixture.layer.stopTracking();
|
||
|
||
const realNow = Date.now;
|
||
let now = 4_000_000;
|
||
Date.now = () => now;
|
||
let sampleCalls = 0;
|
||
const sampledHeight = 187.5;
|
||
const viewer = viewerAtHeight(30_000, {
|
||
tilesLoaded: false,
|
||
sampleHeight: () => { sampleCalls++; return sampledHeight; },
|
||
});
|
||
const icao24 = fixture.name === 'flights' ? 'a0f101' : 'b0f101';
|
||
const model = {
|
||
modelMatrix: Cesium.Matrix4.clone(Cesium.Matrix4.IDENTITY),
|
||
ready: true,
|
||
show: true,
|
||
};
|
||
const position = Cesium.Cartesian3.fromDegrees(-97.71, 30.21, 10_668);
|
||
|
||
try {
|
||
const billboard = fixture.seed({
|
||
viewer,
|
||
tracked: false,
|
||
icao24,
|
||
onGround: true,
|
||
models: [[icao24, model]],
|
||
});
|
||
billboard.show = false;
|
||
|
||
let ownsVisual = fixture.driveFleetModel({ icao24, position, course: 92 });
|
||
assert.equal(ownsVisual, false, 'cold terrain cannot transfer ownership to 3D');
|
||
assert.equal(model.show, false, 'the ready primitive is hidden until ground resolves');
|
||
assert.equal(billboard.show, true, 'the 2D contact returns in the same handoff turn');
|
||
assert.ok(model.show || billboard.show, 'the contact never has both visuals hidden');
|
||
assert.equal(sampleCalls, 0, 'cold tiles do not force sampleHeight');
|
||
|
||
now += 1000;
|
||
viewer.tileset.tilesLoaded = true;
|
||
ownsVisual = fixture.driveFleetModel({ icao24, position, course: 92 });
|
||
assert.equal(ownsVisual, false, 'the miss backoff remains in force before two seconds');
|
||
assert.equal(sampleCalls, 0, 'backoff prevents an early synchronous sample');
|
||
assert.ok(model.show || billboard.show, 'backoff still leaves a visible owner');
|
||
|
||
now += 1500;
|
||
ownsVisual = fixture.driveFleetModel({ icao24, position, course: 92 });
|
||
assert.equal(ownsVisual, true, 'the model takes over after a valid terrain sample');
|
||
assert.equal(sampleCalls, 1, 'the settled terrain is sampled exactly once');
|
||
assert.equal(model.show, true);
|
||
assert.equal(billboard.show, false);
|
||
assert.ok(model.show || billboard.show, 'successful handoff keeps a visible owner');
|
||
const placed = Cesium.Matrix4.getTranslation(model.modelMatrix, new Cesium.Cartesian3());
|
||
const placedHeight = Cesium.Cartographic.fromCartesian(placed).height;
|
||
assert.ok(Math.abs(placedHeight - (sampledHeight + fixture.bellyM)) < 0.001,
|
||
`placement uses the exact ${fixture.bellyM} m asset-measured belly offset`);
|
||
|
||
viewer.tileset.tilesLoaded = false;
|
||
const datumShifted = Cesium.Cartesian3.fromDegrees(-97.71, 30.21, -500);
|
||
ownsVisual = fixture.driveFleetModel({ icao24, position: datumShifted, course: 92 });
|
||
assert.equal(ownsVisual, true,
|
||
'a poll-time vertical datum change does not invalidate a stationary snap');
|
||
assert.equal(sampleCalls, 1, 'the height-normalized cache does not resample vertically');
|
||
assert.ok(model.show || billboard.show, 'datum transition keeps a visible owner');
|
||
|
||
// Taxi past the 50 m resample threshold while the tiles are gone. The old
|
||
// snap stops being the direct answer, but it is still a MEASUREMENT of
|
||
// ground this contact was standing on ~96 m ago, so it is held: hiding the
|
||
// model here would pop a taxiing aircraft from 3D to 2D for a whole retry
|
||
// backoff (2–30 s), which is the one thing the gate must not introduce.
|
||
const taxied = Cesium.Cartesian3.fromDegrees(-97.709, 30.21, -500);
|
||
ownsVisual = fixture.driveFleetModel({ icao24, position: taxied, course: 92 });
|
||
assert.equal(ownsVisual, true,
|
||
'a taxi-invalidated snap is HELD through the resample outage, not dropped');
|
||
assert.equal(model.show, true, 'no 3D→2D pop while the resample is backing off');
|
||
assert.equal(billboard.show, false);
|
||
assert.equal(sampleCalls, 1, 'and the hold costs no extra synchronous sample');
|
||
const held = Cesium.Matrix4.getTranslation(model.modelMatrix, new Cesium.Cartesian3());
|
||
assert.ok(Math.abs(Cesium.Cartographic.fromCartesian(held).height
|
||
- (sampledHeight + fixture.bellyM)) < 0.001,
|
||
'the hold places the model on the last MEASURED floor, not on the feed altitude');
|
||
|
||
// Past the drift bound the memory stops describing anywhere this contact
|
||
// has been, so it is dropped rather than stretched and the gate takes over.
|
||
const farTaxied = Cesium.Cartesian3.fromDegrees(-97.707, 30.21, -500); // ~289 m out
|
||
ownsVisual = fixture.driveFleetModel({ icao24, position: farTaxied, course: 92 });
|
||
assert.equal(ownsVisual, false,
|
||
'past HELD_SNAP_MAX_DRIFT_M the hold is released and the model is withheld');
|
||
assert.equal(model.show, false);
|
||
assert.equal(billboard.show, true);
|
||
assert.ok(model.show || billboard.show, 'release returns atomically to 2D');
|
||
|
||
const failedIcao = fixture.name === 'flights' ? 'a0f102' : 'b0f102';
|
||
const failedBillboard = fixture.seed({
|
||
viewer,
|
||
tracked: false,
|
||
icao24: failedIcao,
|
||
onGround: true,
|
||
models: [],
|
||
});
|
||
failedBillboard.show = false;
|
||
ownsVisual = fixture.driveFleetModel({
|
||
icao24: failedIcao,
|
||
position,
|
||
course: 92,
|
||
});
|
||
assert.equal(ownsVisual, false, 'a missing/failed model never claims ownership');
|
||
assert.equal(failedBillboard.show, true, 'load failure keeps the billboard visible');
|
||
} finally {
|
||
Date.now = realNow;
|
||
fixture.layer.stopTracking();
|
||
}
|
||
});
|
||
|
||
test(`${fixture.name}: a held snap is dropped when measured ground contradicts it`, () => {
|
||
// Adversarial review 2026-08-23, reproduced verbatim. The drift bound asks
|
||
// "how far has the contact travelled", which is only a PROXY for "does this
|
||
// value still describe the ground here" — and 202 m of taxi onto a surface
|
||
// 30 m higher fits inside it. Because a miss PRESERVES the hold, nothing in
|
||
// the retry path corrects that: the model stays buried for as long as the
|
||
// resample keeps missing, which on the OSM fallback is forever.
|
||
//
|
||
// A at 120 m → taxi 202 m to B, whose measured skin is 150 m → resample
|
||
// misses. The held 120 m must NOT be used to place a model at B.
|
||
clearTr3bRegistry();
|
||
fixture.layer.stopTracking();
|
||
fixture.layer.setParams({ models3d: false });
|
||
_clearMeshFloorCellsForTest();
|
||
|
||
const realNow = Date.now;
|
||
let now = 5_000_000;
|
||
Date.now = () => now;
|
||
let sampleCalls = 0;
|
||
let sampled = 120;
|
||
const viewer = viewerAtHeight(30_000, {
|
||
tilesLoaded: true,
|
||
sampleHeight: () => { sampleCalls++; return sampled; },
|
||
});
|
||
const icao24 = fixture.name === 'flights' ? 'a0f103' : 'b0f103';
|
||
const model = {
|
||
modelMatrix: Cesium.Matrix4.clone(Cesium.Matrix4.IDENTITY),
|
||
ready: true,
|
||
show: true,
|
||
};
|
||
/** Height (m) the model's matrix currently places it at. */
|
||
const placedHeightM = () => Cesium.Cartographic.fromCartesian(
|
||
Cesium.Matrix4.getTranslation(model.modelMatrix, new Cesium.Cartesian3()),
|
||
).height;
|
||
|
||
const LAT = 30.21;
|
||
const A_LON = -97.71;
|
||
// ~202 m east at this latitude: past MOVE_INVALIDATE_M (50 m) so the snap is
|
||
// demoted, and INSIDE HELD_SNAP_MAX_DRIFT_M (250 m) so the bound alone does
|
||
// not catch it — that is the whole point of the repro.
|
||
const B_LON = -97.7079;
|
||
const B_GROUND_M = 150;
|
||
|
||
try {
|
||
const billboard = fixture.seed({
|
||
viewer,
|
||
tracked: false,
|
||
icao24,
|
||
onGround: true,
|
||
models: [[icao24, model]],
|
||
});
|
||
billboard.show = false;
|
||
|
||
const atA = Cesium.Cartesian3.fromDegrees(A_LON, LAT, 0);
|
||
const atB = Cesium.Cartesian3.fromDegrees(B_LON, LAT, 0);
|
||
const taxiM = Cesium.Cartesian3.distance(
|
||
Cesium.Ellipsoid.WGS84.scaleToGeodeticSurface(atA, new Cesium.Cartesian3()),
|
||
Cesium.Ellipsoid.WGS84.scaleToGeodeticSurface(atB, new Cesium.Cartesian3()),
|
||
);
|
||
assert.ok(taxiM > 50 && taxiM < 250,
|
||
`the repro must sit INSIDE the drift bound to be a repro (taxi ${taxiM.toFixed(1)} m)`);
|
||
|
||
// A: the snap resolves and the model takes the visual.
|
||
let ownsVisual = fixture.driveFleetModel({ icao24, position: atA, course: 92 });
|
||
assert.equal(ownsVisual, true, 'a measured snap at A hands the visual to the model');
|
||
assert.equal(sampleCalls, 1);
|
||
assert.ok(Math.abs(placedHeightM() - (sampled + fixture.bellyM)) < 0.001,
|
||
'the model stands on the measured 120 m at A');
|
||
|
||
// Something measures B's surface — the mesh-floor cells the billboard
|
||
// chain and meshFloorSampler share. This is the fresh evidence.
|
||
reportMeshFloorCell(LAT, B_LON, B_GROUND_M);
|
||
|
||
// B, with the resample missing: tiles gone, so no sample can land.
|
||
viewer.tileset.tilesLoaded = false;
|
||
now += 60_000;
|
||
ownsVisual = fixture.driveFleetModel({ icao24, position: atB, course: 92 });
|
||
assert.equal(sampleCalls, 1, 'the missing resample fires no new sample');
|
||
assert.equal(ownsVisual, false,
|
||
'a held measurement contradicted by measured ground must not place the model');
|
||
assert.equal(model.show, false, 'nothing is drawn 30 m under the apron');
|
||
assert.equal(billboard.show, true, 'the floored 2D billboard is the honest fallback');
|
||
assert.ok(model.show || billboard.show, 'the release is atomic — never both hidden');
|
||
|
||
// "If the contact remains at B, misses can preserve that burial forever."
|
||
// They cannot: the hold is gone, so every later miss re-answers COLD.
|
||
for (let i = 0; i < 5; i++) {
|
||
now += 60_000;
|
||
assert.equal(fixture.driveFleetModel({ icao24, position: atB, course: 92 }), false,
|
||
'repeated misses must not resurrect the dropped hold');
|
||
assert.equal(model.show, false);
|
||
}
|
||
|
||
// And when evidence does arrive, it is B's ground the model stands on.
|
||
sampled = B_GROUND_M;
|
||
viewer.tileset.tilesLoaded = true;
|
||
now += 60_000;
|
||
ownsVisual = fixture.driveFleetModel({ icao24, position: atB, course: 92 });
|
||
assert.equal(ownsVisual, true, 'a fresh sample at B restores the model');
|
||
assert.equal(sampleCalls, 2);
|
||
assert.ok(Math.abs(placedHeightM() - (B_GROUND_M + fixture.bellyM)) < 0.001,
|
||
"the restored model stands on B's 150 m, not on the stale 120 m");
|
||
|
||
// The rule is ONE-SIDED, and this is the half that keeps it useful. Roll
|
||
// on to C, where the measured cell reads BELOW the held value, and let the
|
||
// resample miss again. A cell under a real sample is the floor chain's
|
||
// expected error — cells latch once over ~111 m, borrowed neighbours lean
|
||
// lowest, the display clamp only ever raises — so it is not evidence of
|
||
// burial and must NOT cost the contact its model. A two-sided rule fails
|
||
// here, and did: on the track-regression rig a planted cell sat 66.7 m
|
||
// below a real sample at the same spot.
|
||
const C_LON = -97.7064; // ~144 m further on, still inside the drift bound
|
||
const atC = Cesium.Cartesian3.fromDegrees(C_LON, LAT, 0);
|
||
const rollM = Cesium.Cartesian3.distance(
|
||
Cesium.Ellipsoid.WGS84.scaleToGeodeticSurface(atB, new Cesium.Cartesian3()),
|
||
Cesium.Ellipsoid.WGS84.scaleToGeodeticSurface(atC, new Cesium.Cartesian3()),
|
||
);
|
||
assert.ok(rollM > 50 && rollM < 250,
|
||
`the downhill leg must also sit inside the bound (roll ${rollM.toFixed(1)} m)`);
|
||
reportMeshFloorCell(LAT, C_LON, B_GROUND_M - 50); // measured LOWER than the hold
|
||
viewer.tileset.tilesLoaded = false;
|
||
now += 60_000;
|
||
ownsVisual = fixture.driveFleetModel({ icao24, position: atC, course: 92 });
|
||
assert.equal(ownsVisual, true,
|
||
'a cell reading BELOW the hold is the floor chain under-reading, not burial — the hold stands');
|
||
assert.equal(model.show, true, 'the contact keeps its model through a downhill taxi');
|
||
assert.ok(Math.abs(placedHeightM() - (B_GROUND_M + fixture.bellyM)) < 0.001,
|
||
'and it keeps standing on the measurement it actually has');
|
||
} finally {
|
||
Date.now = realNow;
|
||
fixture.layer.stopTracking();
|
||
_clearMeshFloorCellsForTest();
|
||
}
|
||
});
|
||
|
||
test(`${fixture.name}: grounded tracked model waits for a valid terrain snap with no visual gap`, async () => {
|
||
clearTr3bRegistry();
|
||
fixture.layer.stopTracking();
|
||
fixture.layer.setParams({ models3d: false });
|
||
|
||
const realFrom = Cesium.Model.fromGltfAsync;
|
||
const realNow = Date.now;
|
||
let now = 3_000_000;
|
||
Date.now = () => now;
|
||
let sampleCalls = 0;
|
||
let sampledHeight = undefined;
|
||
const viewer = viewerAtHeight(30_000, {
|
||
tilesLoaded: false,
|
||
sampleHeight: () => { sampleCalls++; return sampledHeight; },
|
||
});
|
||
const collection = recordingModelCollection();
|
||
const model = {
|
||
modelMatrix: Cesium.Matrix4.clone(Cesium.Matrix4.IDENTITY),
|
||
ready: false,
|
||
show: true,
|
||
scale: 1,
|
||
destroy() {},
|
||
};
|
||
Cesium.Model.fromGltfAsync = () => Promise.resolve(model);
|
||
|
||
try {
|
||
const billboard = fixture.seed({
|
||
viewer,
|
||
modelCollection: collection,
|
||
onGround: true,
|
||
icao24: fixture.name === 'flights' ? 'a0f001' : 'b0f001',
|
||
});
|
||
|
||
fixture.driveTrackedModel();
|
||
await Promise.resolve(); await Promise.resolve();
|
||
assert.equal(collection.added.length, 1, 'the loaded primitive enters the scene once');
|
||
assert.equal(model.ready, false, 'the production loader is still awaiting GPU readiness');
|
||
assert.equal(model.show, false, 'the loading primitive is admitted hidden, not zero-scaled');
|
||
assert.equal(model.scale, 1, 'its scale is left to the pixel-cap driver');
|
||
assert.equal(fixture.trackedBillboardColor().alpha, 1,
|
||
'the tracked billboard remains opaque while the model lacks safe placement');
|
||
assert.equal(sampleCalls, 0, 'cold tiles do not force a synchronous sample');
|
||
const loadingDetection = fixture.layer.getDetectableObjects({ maxCount: 50 })
|
||
.find((object) => object.sourceId === (fixture.name === 'flights' ? 'a0f001' : 'b0f001'));
|
||
assert.ok(loadingDetection, 'the tracked billboard remains detectable while its model loads');
|
||
assert.ok(Cesium.Cartesian3.equalsEpsilon(
|
||
loadingDetection.position,
|
||
billboard.position,
|
||
0,
|
||
1e-6,
|
||
), 'a hidden tracked model cannot steal the detection anchor');
|
||
billboard.show = false; // tracking hides the fleet sprite; the tracked entity remains the 2D owner
|
||
const loadingNearby = fixture.layer.getNearby(billboard.position, 1_000, 50)
|
||
.find((object) => object.icao24 === (fixture.name === 'flights' ? 'a0f001' : 'b0f001'));
|
||
assert.ok(loadingNearby, 'the tracked contact remains in proximity while its model loads');
|
||
assert.ok(Cesium.Cartesian3.equalsEpsilon(
|
||
loadingNearby.position,
|
||
billboard.position,
|
||
0,
|
||
1e-6,
|
||
), 'a hidden tracked model cannot steal the proximity position');
|
||
|
||
now += 1000;
|
||
viewer.tileset.tilesLoaded = true;
|
||
sampledHeight = 187.5;
|
||
fixture.driveTrackedModel();
|
||
assert.equal(sampleCalls, 0, 'the tracked path honors the same retry backoff before sampling');
|
||
assert.equal(model.show, false, 'retry backoff keeps the unplaced primitive hidden');
|
||
assert.equal(fixture.trackedBillboardColor().alpha, 1,
|
||
'retry backoff leaves the tracked billboard opaque');
|
||
|
||
now += 1500;
|
||
fixture.driveTrackedModel();
|
||
assert.equal(sampleCalls, 1, 'the first settled turn samples once');
|
||
assert.equal(model.show, false, 'terrain alone does not reveal an unready model');
|
||
assert.equal(fixture.trackedBillboardColor().alpha, 1,
|
||
'the billboard retains ownership until GPU readiness too');
|
||
|
||
model.ready = true;
|
||
fixture.driveTrackedModel();
|
||
assert.equal(model.show, true, 'the model appears only after its snapped matrix is written');
|
||
assert.equal(fixture.trackedBillboardColor().alpha, 0,
|
||
'the billboard becomes transparent only when the model actually owns the visual');
|
||
const placed = Cesium.Matrix4.getTranslation(model.modelMatrix, new Cesium.Cartesian3());
|
||
const placedHeight = Cesium.Cartographic.fromCartesian(placed).height;
|
||
assert.ok(Math.abs(placedHeight - (sampledHeight + fixture.bellyM)) < 0.001,
|
||
`tracked placement uses the exact ${fixture.bellyM} m asset-measured belly offset`);
|
||
|
||
viewer.tileset.tilesLoaded = false;
|
||
sampledHeight = undefined;
|
||
billboard.position = Cesium.Cartesian3.fromDegrees(-97.71, 30.21, -500);
|
||
fixture.driveTrackedModel();
|
||
assert.equal(model.show, true,
|
||
'a successful cached snap survives a poll-time vertical datum change');
|
||
assert.equal(sampleCalls, 1,
|
||
'a stationary vertical-only change does not consume another terrain sample');
|
||
|
||
// Taxi past the resample threshold with the tiles gone: the tracked model
|
||
// holds its last measured floor rather than handing the operator's own
|
||
// selection back to a 2D glyph for a retry backoff.
|
||
billboard.position = Cesium.Cartesian3.fromDegrees(-97.709, 30.21, -500);
|
||
fixture.driveTrackedModel();
|
||
assert.equal(model.show, true,
|
||
'a taxi-invalidated tracked snap is HELD through the resample outage');
|
||
assert.equal(fixture.trackedBillboardColor().alpha, 0,
|
||
'the model still owns the visual, so the billboard stays transparent');
|
||
assert.equal(sampleCalls, 1, 'the hold costs no extra synchronous sample');
|
||
|
||
// Past the drift bound the memory is released and the gate takes over.
|
||
billboard.position = Cesium.Cartesian3.fromDegrees(-97.707, 30.21, -500);
|
||
fixture.driveTrackedModel();
|
||
assert.equal(model.show, false,
|
||
'past HELD_SNAP_MAX_DRIFT_M the hold is released and the model is withheld');
|
||
assert.equal(fixture.trackedBillboardColor().alpha, 1,
|
||
'there is no invisible-owner gap while the replacement snap is unavailable');
|
||
} finally {
|
||
Cesium.Model.fromGltfAsync = realFrom;
|
||
Date.now = realNow;
|
||
fixture.layer.stopTracking();
|
||
}
|
||
});
|
||
|
||
test(`${fixture.name}: the tracked contact goes 3D with the fleet 3D toggle OFF`, () => {
|
||
clearTr3bRegistry();
|
||
fixture.layer.setParams({ models3d: false });
|
||
assert.equal(fixture.layer.getParams().models3d, false, 'the fleet toggle really is off');
|
||
|
||
// Zoomed out past the ceiling → the billboard still owns the visual.
|
||
fixture.seed({ viewer: viewerAtHeight(INSIDE_BAND_M) });
|
||
assert.equal(fixture.regimeActive(), false);
|
||
|
||
// Zoom in past the ceiling → the model takes over, toggle untouched.
|
||
fixture.seed({ viewer: viewerAtHeight(WELL_INSIDE_M) });
|
||
assert.equal(fixture.regimeActive(), true,
|
||
'zoom-driven 2D↔3D is DEFAULT for the tracked contact');
|
||
assert.equal(fixture.layer.getParams().models3d, false,
|
||
'and it never arms the fleet toggle as a side effect');
|
||
});
|
||
|
||
test(`${fixture.name}: the tracked regime is hysteretic across a live zoom in/out`, () => {
|
||
clearTr3bRegistry();
|
||
fixture.layer.setParams({ models3d: false });
|
||
const viewer = viewerAtHeight(FAR_OUTSIDE_M);
|
||
fixture.seed({ viewer });
|
||
assert.equal(fixture.regimeActive(), false, 'starts far out, in 2D');
|
||
|
||
// Zoom in through the boundary.
|
||
viewer.camera.positionCartographic.height = TRACKED_MODEL_ENTER_ALT_M - 1;
|
||
assert.equal(fixture.regimeActive(), true, '3D at the enter ceiling');
|
||
|
||
// Drift back over the ENTER ceiling — must HOLD (this is the flap guard).
|
||
viewer.camera.positionCartographic.height = INSIDE_BAND_M;
|
||
assert.equal(fixture.regimeActive(), true, 'the exit band holds the model');
|
||
|
||
// Zoom all the way back out — the billboard returns.
|
||
viewer.camera.positionCartographic.height = TRACKED_MODEL_EXIT_ALT_M + 1;
|
||
assert.equal(fixture.regimeActive(), false, 'past the exit ceiling, 2D returns');
|
||
|
||
// And a second round trip behaves identically (no stuck latch).
|
||
viewer.camera.positionCartographic.height = TRACKED_MODEL_ENTER_ALT_M - 1;
|
||
assert.equal(fixture.regimeActive(), true);
|
||
viewer.camera.positionCartographic.height = TRACKED_MODEL_EXIT_ALT_M + 1;
|
||
assert.equal(fixture.regimeActive(), false);
|
||
});
|
||
|
||
test(`${fixture.name}: deselect + re-track the SAME contact re-enters at the ENTER ceiling`, () => {
|
||
// The regression this pins: a deselect followed by a same-turn re-track of
|
||
// the SAME icao (Contacts re-entry, a cross-layer round trip back to this
|
||
// layer) never makes `_trackedIcao` observably change, so the predicate's
|
||
// icao-change guard never fires. The reset therefore has to live in the
|
||
// PRODUCTION teardown — which is why nothing below touches a test-only
|
||
// latch reset; the seam re-seeds state but deliberately leaves the latches
|
||
// alone, and `stopTracking()` is the real deselect path.
|
||
clearTr3bRegistry();
|
||
fixture.layer.setParams({ models3d: false });
|
||
|
||
// Latch the regime on close in...
|
||
const viewer = viewerAtHeight(TRACKED_MODEL_ENTER_ALT_M - 1);
|
||
fixture.seed({ viewer });
|
||
assert.equal(fixture.regimeActive(), true);
|
||
|
||
// ...drift into the hysteresis band, where the model legitimately holds.
|
||
viewer.camera.positionCartographic.height = INSIDE_BAND_M;
|
||
assert.equal(fixture.regimeActive(), true, 'the ORIGINAL selection still holds in the band');
|
||
|
||
// Deselect through the production path, then re-track the SAME contact.
|
||
fixture.layer.stopTracking();
|
||
fixture.seed({ viewer }); // same icao, still inside the band
|
||
assert.equal(fixture.regimeActive(), false,
|
||
'a re-tracked contact must pass the ENTER ceiling again, not inherit the exit band');
|
||
|
||
// And it still enters normally once actually zoomed in.
|
||
viewer.camera.positionCartographic.height = TRACKED_MODEL_ENTER_ALT_M - 1;
|
||
assert.equal(fixture.regimeActive(), true);
|
||
fixture.layer.stopTracking();
|
||
});
|
||
|
||
test(`${fixture.name}: a converted TR-3B never takes the model, at any zoom`, () => {
|
||
clearTr3bRegistry();
|
||
fixture.layer.setParams({ models3d: false });
|
||
const viewer = viewerAtHeight(1); // as zoomed in as it gets
|
||
fixture.seed({ viewer });
|
||
assert.equal(fixture.regimeActive(), true, 'control: an unconverted contact does model');
|
||
|
||
setTr3b(ICAO, true);
|
||
fixture.seed({ viewer });
|
||
assert.equal(fixture.regimeActive(), false,
|
||
'there is no TR-3B GLB — the triangle billboard stays the visual all the way in');
|
||
|
||
setTr3b(ICAO, false);
|
||
fixture.seed({ viewer });
|
||
assert.equal(fixture.regimeActive(), true, 'un-converting restores the handoff');
|
||
clearTr3bRegistry();
|
||
});
|
||
|
||
test(`${fixture.name}: deselecting drops the regime even zoomed all the way in`, () => {
|
||
clearTr3bRegistry();
|
||
fixture.layer.setParams({ models3d: false });
|
||
const viewer = viewerAtHeight(1);
|
||
fixture.seed({ viewer });
|
||
assert.equal(fixture.regimeActive(), true);
|
||
|
||
fixture.seed({ viewer, tracked: false }); // nothing selected
|
||
assert.equal(fixture.regimeActive(), false,
|
||
'with no selection there is no tracked model to own the visual');
|
||
});
|
||
|
||
test(`${fixture.name}: turning the fleet toggle ON does not disturb the tracked regime`, () => {
|
||
clearTr3bRegistry();
|
||
const viewer = viewerAtHeight(WELL_INSIDE_M);
|
||
fixture.seed({ viewer });
|
||
fixture.layer.setParams({ models3d: true });
|
||
assert.equal(fixture.regimeActive(), true);
|
||
fixture.layer.setParams({ models3d: false });
|
||
assert.equal(fixture.regimeActive(), true,
|
||
'the fleet toggle owns the FLEET; the selected contact is independent of it');
|
||
});
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 3. Bounded on-demand loading
|
||
//
|
||
// The tracked regime is default-on and its driver runs every scene.preUpdate.
|
||
// A missing or corrupt GLB — or a dead network — used to leave the driver in a
|
||
// load→reject loop at frame rate for as long as the contact stayed selected,
|
||
// because the catch cleared only the in-flight flag. These pin the bound.
|
||
// ---------------------------------------------------------------------------
|
||
|
||
/** Drive N frames of the production tracked-model driver, letting each
|
||
* rejected load settle, with a fake clock the backoff can be walked past. */
|
||
async function driveFrames(fixture, count, clock) {
|
||
for (let i = 0; i < count; i++) {
|
||
fixture.driveTrackedModel();
|
||
await Promise.resolve(); await Promise.resolve(); // let the rejection settle
|
||
clock.advance(2000); // past the retry backoff, so only the LATCH can stop it
|
||
}
|
||
}
|
||
|
||
for (const fixture of LAYERS) {
|
||
test(`${fixture.name}: a rejecting GLB loader is bounded, not a per-frame retry loop`, async () => {
|
||
clearTr3bRegistry();
|
||
fixture.layer.setParams({ models3d: false });
|
||
|
||
const realFrom = Cesium.Model.fromGltfAsync;
|
||
const realNow = Date.now;
|
||
let now = 1_000_000;
|
||
const clock = { advance(ms) { now += ms; } };
|
||
Date.now = () => now;
|
||
let attempts = 0;
|
||
const requested = [];
|
||
Cesium.Model.fromGltfAsync = ({ url }) => {
|
||
attempts++; requested.push(url);
|
||
return Promise.reject(new Error('simulated missing GLB'));
|
||
};
|
||
// The give-up path warns once by design; keep the run's output clean.
|
||
const realWarn = console.warn;
|
||
const warnings = [];
|
||
console.warn = (...args) => { warnings.push(args.join(' ')); };
|
||
|
||
try {
|
||
const collection = recordingModelCollection();
|
||
const viewer = viewerAtHeight(30_000); // well inside the regime
|
||
const billboard = fixture.seed({ viewer, modelCollection: collection });
|
||
assert.equal(fixture.regimeActive(), true, 'the regime is open, so the driver will try to load');
|
||
|
||
await driveFrames(fixture, 12, clock);
|
||
const settled = attempts;
|
||
assert.ok(settled > 0, 'it must actually try');
|
||
assert.ok(settled <= 3,
|
||
`a failing asset must give up within 3 attempts per selection, got ${settled}`);
|
||
|
||
// Many more frames must produce NO further attempts — this is the loop bound.
|
||
await driveFrames(fixture, 25, clock);
|
||
assert.equal(attempts, settled,
|
||
'once latched, further frames must not re-ask for the same failed asset');
|
||
|
||
// Nothing was ever handed to the scene, so the billboard stayed the visual.
|
||
assert.equal(collection.added.length, 0, 'a failed load never puts a model up');
|
||
assert.equal(fixture.trackedBillboardColor().alpha, 1,
|
||
'a rejected tracked model leaves the tracked billboard opaque');
|
||
assert.equal(billboard.show, true,
|
||
'a rejected model never creates a frame with both visual representations hidden');
|
||
|
||
// Exactly one operator-facing warning, and it names the asset.
|
||
assert.equal(warnings.length, 1, 'the give-up is announced once, not per frame');
|
||
assert.ok(warnings[0].includes(requested[0]), `the warning names the asset: ${warnings[0]}`);
|
||
|
||
// A NEW selection gets a fresh budget — the latch is per contact.
|
||
fixture.layer.stopTracking();
|
||
fixture.seed({ viewer, icao24: 'def456', modelCollection: collection });
|
||
await driveFrames(fixture, 3, clock);
|
||
assert.ok(attempts > settled, 'selecting another contact restores the retry budget');
|
||
} finally {
|
||
Cesium.Model.fromGltfAsync = realFrom;
|
||
Date.now = realNow;
|
||
console.warn = realWarn;
|
||
fixture.layer.stopTracking();
|
||
}
|
||
});
|
||
|
||
test(`${fixture.name}: the failure latch clears on a same-icao re-track too`, async () => {
|
||
clearTr3bRegistry();
|
||
fixture.layer.setParams({ models3d: false });
|
||
|
||
const realFrom = Cesium.Model.fromGltfAsync;
|
||
const realNow = Date.now;
|
||
const realWarn = console.warn;
|
||
let now = 2_000_000;
|
||
const clock = { advance(ms) { now += ms; } };
|
||
Date.now = () => now;
|
||
let attempts = 0;
|
||
Cesium.Model.fromGltfAsync = () => { attempts++; return Promise.reject(new Error('simulated missing GLB')); };
|
||
console.warn = () => {};
|
||
|
||
try {
|
||
const collection = recordingModelCollection();
|
||
const viewer = viewerAtHeight(30_000);
|
||
fixture.seed({ viewer, modelCollection: collection });
|
||
await driveFrames(fixture, 12, clock);
|
||
const latched = attempts;
|
||
await driveFrames(fixture, 10, clock);
|
||
assert.equal(attempts, latched, 'latched for this selection');
|
||
|
||
// Deselect and re-select the SAME contact through the production path.
|
||
fixture.layer.stopTracking();
|
||
fixture.seed({ viewer, modelCollection: collection });
|
||
await driveFrames(fixture, 3, clock);
|
||
assert.ok(attempts > latched,
|
||
're-tracking the same contact is a new selection and gets its retries back');
|
||
} finally {
|
||
Cesium.Model.fromGltfAsync = realFrom;
|
||
Date.now = realNow;
|
||
console.warn = realWarn;
|
||
fixture.layer.stopTracking();
|
||
}
|
||
});
|
||
}
|
||
|
||
test('both layers gate the tracked-model load and record its failures', async () => {
|
||
for (const name of ['flights.js', 'militaryFlights.js']) {
|
||
const source = readLayerSource(new URL(`./${name}`, import.meta.url));
|
||
assert.match(source, /!(?:flightState\.)?_trackedModel\s*&&\s*!(?:flightState\.)?_trackedModelLoading\s*&&\s*(?:parts\.\w+\.)?_trackedModelLoadAllowed\(\s*,?\s*\)/,
|
||
`${name}: the driver asks permission before starting another tracked-model load`);
|
||
assert.match(source, /(?:parts\.\w+\.)?_noteTrackedModelLoadFailure\(\s*trackedSpec\.url,\s*err,?\s*\)/,
|
||
`${name}: a rejected load is recorded against this selection, not silently retried`);
|
||
// The reset must be reachable from the real teardown, not only a test seam.
|
||
assert.match(source, /(?:parts\.\w+\.)?_releaseTrackedModel\(\s*,?\s*\);\n\s*(?:parts\.\w+\.)?_resetTrackedSelectionState\(\s*,?\s*\);/,
|
||
`${name}: deselect clears the per-selection latches in the production path`);
|
||
assert.match(source, /(?:flightState\.)?_trackedIcao\s*=\s*icao24;\n\s*(?:parts\.\w+\.)?_resetTrackedSelectionState\(\s*,?\s*\);/,
|
||
`${name}: selecting a contact starts from a clean per-selection state`);
|
||
}
|
||
});
|
||
|
||
test('the tracked regime never consults the fleet models3d toggle', async () => {
|
||
for (const name of ['flights.js', 'militaryFlights.js']) {
|
||
const source = readLayerSource(new URL(`./${name}`, import.meta.url));
|
||
const regime = /^([ \t]*)function _trackedModelRegimeActive\b[\s\S]*?\n\1\}/m.exec(source)?.[0];
|
||
assert.ok(regime, `${name}: _trackedModelRegimeActive is defined`);
|
||
assert.doesNotMatch(regime, /(?:flightState\.)?_models3dEnabled|(?:parts\.\w+\.)?_modelRegimeActive\(\s*,?\s*\)/,
|
||
`${name}: the tracked contact's handoff is default-on, not gated on the fleet toggle`);
|
||
assert.match(regime, /trackedModelZoomActive\(/,
|
||
`${name}: the tracked contact uses the shared hysteretic zoom policy`);
|
||
// The FLEET must still obey the toggle — the budget decision stays the operator's.
|
||
const fleet = /^([ \t]*)function _modelRegimeActive\b[\s\S]*?\n\1\}/m.exec(source)?.[0];
|
||
assert.match(fleet, /if\s*\(\s*!(?:flightState\.)?_models3dEnabled,?\s*\)\s*return\s*false;/,
|
||
`${name}: the fleet regime still obeys the DISPLAY-rail 3D toggle`);
|
||
}
|
||
});
|