Files
gods-eye-view/src/cockpitMath.test.mjs
T
Sameh Khamis 95c0070412 Keep tracked-entity state in Core records instead of on Cesium entities
Flights, military, satellites, installations, local ADS-B and ALPR hung
gevTrackedId, gevDisplayPosition, gevVisualPosition, gevSelectionOrigin and
gevLabelModel on the Cesium entities they draw, and the tracked readout,
follow camera, cockpit, share links and awareness read them back. They now
live in src/data/entityRecords.js, a Core-owned record per entity: layers
write entityRecord(entity), readers use readEntityRecord(entity). Nothing
changes on screen. This is the first slice of the renderer plan's state
mirror (P1).

The module is exported as ./data/entity-records so hosted code can read the
same records, and window.__godsEyeView.entityRecords gives the QA scripts
the same access from the page.
2026-11-03 08:09:00 -08:00

396 lines
14 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import {
altitudeRulerStep,
altitudeRulerTicks,
altitudeRulerCurveInset,
bearingBetweenCoordinates,
cockpitAnchorCorrectionStep,
cockpitAltitudeDisplayFt,
cockpitGroundSafeHeight,
cockpitSurfaceWaitExpired,
cockpitUiUpdateDue,
compassDivisions,
formatAltitudeRulerTick,
formatCockpitContextScope,
formatCockpitEmergency,
formatCompassDivision,
formatHeadingReadout,
formatSpeedRulerTick,
normalizeHeading,
relativeBearing,
resolveCockpitContextReadout,
resolveHudRailLayout,
resolveTrackedAircraftInfo,
slewHeading,
speedRulerStep,
speedRulerTicks,
} from './cockpitMath.js';
test('cockpit presentation updates are throttled independently of camera frames', () => {
assert.equal(cockpitUiUpdateDue(1000, 0, 100), true);
assert.equal(cockpitUiUpdateDue(1099, 1000, 100), false);
assert.equal(cockpitUiUpdateDue(1100, 1000, 100), true);
assert.equal(cockpitUiUpdateDue(10, 1000, 100), true);
});
test('grounded cockpit surface acquisition has a bounded wait', () => {
assert.equal(cockpitSurfaceWaitExpired(5999, 1000, 5000), false);
assert.equal(cockpitSurfaceWaitExpired(6000, 1000, 5000), true);
});
test('cockpit anchor correction cannot turn a forward step into a reversal', () => {
const speedMps = 200;
const dtSec = 0.1;
const forwardStepM = speedMps * dtSec;
const backwardCorrectionM = cockpitAnchorCorrectionStep(1000, speedMps, dtSec);
assert.ok(backwardCorrectionM > 0);
assert.ok(backwardCorrectionM < forwardStepM);
});
test('cockpit anchor correction bounds late-fix catch-up and render stalls', () => {
assert.ok(cockpitAnchorCorrectionStep(5000, 250, 2) <= 5.5);
assert.equal(cockpitAnchorCorrectionStep(0, 250, 0.1), 0);
assert.equal(cockpitAnchorCorrectionStep(Number.NaN, 250, 0.1), 0);
});
test('cockpit camera clearance never falls below the rendered ground floor', () => {
assert.equal(cockpitGroundSafeHeight(47, 57, 12), 69);
assert.equal(cockpitGroundSafeHeight(1200, 57, 12), 1200);
assert.equal(cockpitGroundSafeHeight(47, null, 12), 47);
assert.equal(cockpitGroundSafeHeight(47, 57, -4), 57);
});
test('cockpit altitude reads zero on the ground without changing airborne MSL', () => {
assert.equal(cockpitAltitudeDisplayFt(200.56, true), 0);
assert.ok(Math.abs(cockpitAltitudeDisplayFt(3048, false) - 10000) < 0.01);
assert.equal(cockpitAltitudeDisplayFt(null, false), null);
});
test('cockpit Context scope distinguishes radius-complete feeds from viewport installations', () => {
assert.equal(
formatCockpitContextScope('TEST123', 250000, 'CURRENT VIEWPORT ONLY'),
'TEST123 · 250 KM AIR/SEA WINDOW · INSTALLATIONS CURRENT VIEWPORT ONLY',
);
assert.equal(
formatCockpitContextScope('TEST123', 250000),
'TEST123 · 250 KM AIR/SEA WINDOW',
);
});
test('cockpit Context scope preserves zero and replaces missing values intentionally', () => {
assert.equal(
formatCockpitContextScope('', 0),
'— · 0 KM AIR/SEA WINDOW',
);
assert.equal(
formatCockpitContextScope(undefined, Number.NaN, {}),
'— · — KM AIR/SEA WINDOW',
);
});
test('altitude ruler chooses tighter intervals close to the surface', () => {
assert.equal(altitudeRulerStep(1200), 100);
assert.equal(altitudeRulerStep(9000), 250);
assert.equal(altitudeRulerStep(34000), 500);
assert.equal(altitudeRulerStep(Number.NaN), 500);
});
test('altitude ruler ticks move fractionally behind the fixed pointer', () => {
const ticks = altitudeRulerTicks(34875);
assert.equal(ticks.length, 9);
assert.equal(ticks[4].valueFt, 34500);
assert.equal(ticks[4].slot, -0.75);
assert.equal(ticks[5].valueFt, 35000);
assert.equal(ticks[5].slot, 0.25);
assert.equal(ticks[5].major, true);
});
test('altitude ruler clamps below ground and formats fixed-width labels', () => {
assert.equal(altitudeRulerTicks(-20)[4].valueFt, 0);
assert.equal(formatAltitudeRulerTick(900), '00900');
assert.equal(formatAltitudeRulerTick(34874.6), '34875');
assert.equal(formatAltitudeRulerTick(null), '-----');
});
test('altitude ruler curve follows the circular keyhole at wide aspect ratios', () => {
assert.equal(altitudeRulerCurveInset(0), 0);
assert.equal(altitudeRulerCurveInset(-4), altitudeRulerCurveInset(4));
assert.ok(Math.abs(altitudeRulerCurveInset(4) - 0.231625) < 0.00001);
});
test('speed ruler chooses useful taxi, regional, and cruise intervals', () => {
assert.equal(speedRulerStep(45), 10);
assert.equal(speedRulerStep(180), 20);
assert.equal(speedRulerStep(460), 25);
assert.equal(speedRulerStep(Number.NaN), 25);
});
test('speed ruler ticks move smoothly behind a fixed pointer', () => {
const ticks = speedRulerTicks(467);
assert.equal(ticks.length, 9);
assert.equal(ticks[4].valueKt, 450);
assert.ok(Math.abs(ticks[4].slot + 0.68) < 0.00001);
assert.equal(ticks[5].valueKt, 475);
assert.ok(Math.abs(ticks[5].slot - 0.32) < 0.00001);
assert.equal(formatSpeedRulerTick(8), '008');
assert.equal(formatSpeedRulerTick(null), '---');
});
test('normalizeHeading wraps negative and oversized courses', () => {
assert.equal(normalizeHeading(-1), 359);
assert.equal(normalizeHeading(721), 1);
});
test('slewHeading follows the shortest arc across north', () => {
assert.equal(slewHeading(350, 10, 5), 355);
assert.equal(slewHeading(10, 350, 5), 5);
});
test('compass divisions remain ordered across north', () => {
assert.deepEqual(compassDivisions(359), [270, 300, 330, 0, 30, 60, 90]);
});
test('compass labels use cardinals where exact and degrees elsewhere', () => {
assert.equal(formatCompassDivision(270), 'W');
assert.equal(formatCompassDivision(30), '030');
});
test('a heading that rounds to north reads 000, not 360', () => {
assert.equal(formatHeadingReadout(359.6), '000');
assert.equal(formatHeadingReadout(359.4), '359');
assert.equal(formatHeadingReadout(0), '000');
assert.equal(formatHeadingReadout(10.2), '010');
assert.equal(formatHeadingReadout(-0.6), '359');
assert.equal(formatHeadingReadout(Number.NaN), '---');
});
test('great-circle bearing resolves cardinal directions', () => {
assert.ok(Math.abs(bearingBetweenCoordinates(0, 0, 1, 0) - 0) < 0.001);
assert.ok(Math.abs(bearingBetweenCoordinates(0, 0, 0, 1) - 90) < 0.001);
assert.equal(bearingBetweenCoordinates(0, 0, 0, 0), null);
});
test('relative bearing stays on the shortest signed arc', () => {
assert.equal(relativeBearing(10, 350), 20);
assert.equal(relativeBearing(350, 10), -20);
assert.equal(relativeBearing(180, 0), -180);
assert.equal(relativeBearing(null, 0), null);
});
test('HUD rail layout centers between intersecting upper and lower obstacles', () => {
assert.deepEqual(resolveHudRailLayout({
viewportHeight: 1000,
panelHeight: 200,
laneLeft: 50,
laneRight: 410,
baseTop: 280,
baseBottom: 940,
gap: 10,
obstacles: [
{ left: 40, right: 240, top: 320, bottom: 460 },
{ left: 300, right: 1000, top: 780, bottom: 930 },
{ left: 900, right: 1100, top: 300, bottom: 700 },
],
}), {
top: 520,
maxHeight: 300,
safeTop: 470,
safeBottom: 770,
constrained: false,
});
});
test('HUD rail layout constrains an oversized panel to the safe corridor', () => {
const layout = resolveHudRailLayout({
viewportHeight: 800,
panelHeight: 300,
laneLeft: 20,
laneRight: 340,
baseTop: 250,
baseBottom: 700,
obstacles: [{ left: 0, right: 350, top: 500, bottom: 760 }],
});
assert.equal(layout.top, 250);
assert.equal(layout.maxHeight, 238);
assert.equal(layout.constrained, true);
});
test('HUD rail layout can anchor controls to the start of the safe corridor', () => {
const layout = resolveHudRailLayout({
viewportHeight: 1000,
panelHeight: 100,
laneLeft: 900,
laneRight: 1250,
baseTop: 260,
baseBottom: 940,
align: 'start',
});
assert.equal(layout.top, 260);
assert.equal(layout.safeTop, 260);
assert.equal(layout.safeBottom, 940);
});
// --- Contact panel readout (Monday recording bug: NEXT onto a non-aircraft) ---
const TRACKED_INFO = Object.freeze({
layerId: 'flights',
icao24: 'aaa077',
latitude: 30.2,
longitude: -97.7,
});
function contextSnapshot(subject, extra = {}) {
return {
subject,
evaluatedAt: 1_700_000_000_000,
radiusM: 250_000,
cohorts: [],
navigation: { canPrevious: true, canNext: true },
...extra,
};
}
test('Contact panel hides only when there is no snapshot at all', () => {
const readout = resolveCockpitContextReadout({ snapshot: null, info: TRACKED_INFO });
assert.equal(readout.visible, false);
assert.equal(readout.mode, 'standby');
assert.equal(
resolveCockpitContextReadout({ info: TRACKED_INFO }).visible,
false,
'a missing snapshot argument is the same standby case',
);
});
test('Contact panel survives NEXT onto a vessel or installation subject', () => {
// The panel owns the NEXT button. Hiding it because the subject is not the
// tracked aircraft strands the operator with no way back (owner hit this on
// camera: "click next... whole left panel disappears").
for (const subject of [
{ layerId: 'ais-live-vessels', id: '353136000', label: 'MAERSK DETROIT' },
{ layerId: 'military-installations', id: 'fort-hood', label: 'FORT CAVAZOS' },
{ layerId: 'military', id: 'ae01ce', label: 'RCH451' },
]) {
const readout = resolveCockpitContextReadout({
snapshot: contextSnapshot(subject),
info: TRACKED_INFO,
});
assert.equal(readout.visible, true, `${subject.layerId} subject must keep the panel up`);
assert.equal(readout.mode, 'foreign');
assert.equal(readout.subjectMatchesTracked, false);
}
});
test('a foreign subject dashes the aircraft-relative fields and keeps the rest live', () => {
const readout = resolveCockpitContextReadout({
snapshot: contextSnapshot({ layerId: 'ais-live-vessels', id: '353136000', label: 'MAERSK DETROIT' }),
info: TRACKED_INFO,
});
assert.equal(readout.visible, true);
assert.equal(readout.mode, 'foreign');
// Nose-relative direction/bearing are measured in the tracked aircraft's own
// frame while every other row value is measured from the subject; rendering
// both live would present one mixed-frame reading as a single measurement.
assert.equal(readout.aircraftRelative, false);
assert.equal(readout.contactLost, false, 'subject-frame values still refresh');
});
test('the tracked aircraft as subject keeps every field in its own frame', () => {
const readout = resolveCockpitContextReadout({
snapshot: contextSnapshot({ layerId: 'flights', id: 'aaa077', label: 'SWA1234' }),
info: TRACKED_INFO,
});
assert.equal(readout.visible, true);
assert.equal(readout.mode, 'tracked');
assert.equal(readout.subjectMatchesTracked, true);
assert.equal(readout.aircraftRelative, true);
assert.equal(readout.contactLost, false);
});
test('a fast-culled subject keeps the panel up as CONTACT LOST instead of collapsing it', () => {
for (const subject of [
{ layerId: 'flights', id: 'aaa077', label: 'SWA1234' },
{ layerId: 'ais-live-vessels', id: '353136000', label: 'MAERSK DETROIT' },
]) {
const readout = resolveCockpitContextReadout({
snapshot: contextSnapshot(subject, { subjectPresent: false }),
info: TRACKED_INFO,
});
assert.equal(readout.visible, true, 'a lost contact must not take the panel down');
assert.equal(readout.mode, 'lost');
assert.equal(readout.contactLost, true, 'last-known values stay on screen');
}
});
test('an explicitly present subject is never reported lost', () => {
const present = resolveCockpitContextReadout({
snapshot: contextSnapshot({ layerId: 'flights', id: 'aaa077' }, { subjectPresent: true }),
info: TRACKED_INFO,
});
assert.equal(present.contactLost, false);
assert.equal(present.mode, 'tracked');
// Snapshots from before the presence field existed must not read as lost.
const legacy = resolveCockpitContextReadout({
snapshot: contextSnapshot({ layerId: 'flights', id: 'aaa077' }),
info: TRACKED_INFO,
});
assert.equal(legacy.contactLost, false);
});
test('the tracked flight layer is resolved by normalized tracked identity', () => {
const civilian = { icao24: 'aaa077', callsign: 'SWA1234' };
const military = { icao24: 'ae01ce', callsign: 'RCH451' };
// Both layers describe a tracked aircraft during a cross-layer handoff;
// civilian-first precedence would hand the cockpit the wrong aircraft.
assert.equal(
resolveTrackedAircraftInfo({ civilian, military, trackedId: 'military:ae01ce' }).layerId,
'military',
);
assert.equal(
resolveTrackedAircraftInfo({ civilian, military, trackedId: 'military:ae01ce' }).icao24,
'ae01ce',
);
assert.equal(
resolveTrackedAircraftInfo({ civilian, military, trackedId: 'flights:aaa077' }).layerId,
'flights',
);
// Case-insensitive: layers stamp lowercase hex, callers may not.
assert.equal(
resolveTrackedAircraftInfo({ civilian, military, trackedId: 'MILITARY:AE01CE' }).layerId,
'military',
);
});
test('tracked-identity resolution falls back to layer precedence', () => {
const civilian = { icao24: 'aaa077' };
const military = { icao24: 'ae01ce' };
// No stamped identity (or one that matches neither layer) keeps the historic
// civilian-first order so tracking paths without an entity record trackedId still work.
assert.equal(resolveTrackedAircraftInfo({ civilian, military }).layerId, 'flights');
assert.equal(
resolveTrackedAircraftInfo({ civilian, military, trackedId: 'satellites:25544' }).layerId,
'flights',
);
assert.equal(resolveTrackedAircraftInfo({ military, trackedId: '' }).layerId, 'military');
assert.equal(resolveTrackedAircraftInfo({ trackedId: 'flights:aaa077' }), null);
assert.equal(resolveTrackedAircraftInfo(), null);
});
test('cockpit emergency wording states the broadcast code, not a conclusion', () => {
assert.equal(
formatCockpitEmergency({ label: 'General emergency', squawk: '7700', source: 'squawk', severity: 'emergency' }),
'SQUAWK 7700 · GENERAL EMERGENCY',
);
assert.equal(
formatCockpitEmergency({ label: 'Downed aircraft', squawk: '1200', source: 'ads-b', severity: 'emergency' }),
'ADS-B EMERGENCY · DOWNED AIRCRAFT',
'an unrelated squawk is not quoted beside an ADS-B status',
);
assert.equal(
formatCockpitEmergency({ label: 'Minimum fuel', squawk: null, source: 'ads-b', severity: 'priority' }),
'ADS-B PRIORITY · MINIMUM FUEL',
);
assert.equal(formatCockpitEmergency(null), null);
assert.equal(formatCockpitEmergency({ label: '' }), null);
});