gods-eye-view/src/data/renderAltitude.test.mjs

216 lines
7.7 KiB
JavaScript

// src/data/renderAltitude.test.mjs
// docs/plans/2026-07-05-entity-height-datum-fix.md Task 6.
//
// Locks pickRenderAltitudeM's priority chain — the SINGLE source of truth for
// where a flights-layer aircraft (and its dead-reckoned/tracked-camera
// derivatives) renders on the ellipsoidal globe:
//
// onGround && finite(surfaceM) -> surfaceM
// finite(geoAltM) -> geoAltM
// finite(baroAltM) -> baroAltM + (geoidN ?? 0)
// else -> fallback (caller's existing sticky default)
//
// baro+N is a visual FALLBACK, not geometric truth (OpenSky geo_altitude is
// the real WGS84 ellipsoidal value; barometric altitude is MSL-referenced
// and additionally subject to non-standard-pressure QNH error) — no test
// here asserts baro+N is exact, only that it matches the documented formula.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import {
geoidSurfaceLastResortM, pickRenderAltitudeM, reuseGroundedSurfaceM,
} from './renderAltitude.js';
test('onGround with a finite surfaceM wins over everything else', () => {
const got = pickRenderAltitudeM({
geoAltM: 500,
baroAltM: 480,
onGround: true,
surfaceM: 121.5,
geoidN: -26.9,
});
assert.equal(got, 121.5);
});
test('onGround but surfaceM not finite falls through to geoAltM', () => {
const got = pickRenderAltitudeM({
geoAltM: 5,
baroAltM: 3,
onGround: true,
surfaceM: null,
geoidN: -26.9,
});
assert.equal(got, 5);
});
test('airborne: finite geoAltM (WGS84 ellipsoidal) is used verbatim, geoidN ignored', () => {
const got = pickRenderAltitudeM({
geoAltM: 3048,
baroAltM: 3000,
onGround: false,
surfaceM: null,
geoidN: -26.9,
});
assert.equal(got, 3048);
});
test('Denver case: no geo_altitude, baro 1600 + N -17.3 -> ~=1582.7', () => {
const got = pickRenderAltitudeM({
geoAltM: null,
baroAltM: 1600,
onGround: false,
surfaceM: null,
geoidN: -17.3,
});
assert.ok(Math.abs(got - 1582.7) < 0.001, `expected ~=1582.7, got ${got}`);
});
test('baro path: geoidN omitted/undefined treated as 0 (not NaN)', () => {
const got = pickRenderAltitudeM({
geoAltM: null,
baroAltM: 1000,
onGround: false,
surfaceM: null,
});
assert.equal(got, 1000);
});
test('baro path: geoidN explicitly null also treated as 0', () => {
const got = pickRenderAltitudeM({
geoAltM: null,
baroAltM: 1000,
onGround: false,
surfaceM: null,
geoidN: null,
});
assert.equal(got, 1000);
});
test('null-geo fallback: neither geoAltM nor baroAltM finite -> sentinel null', () => {
const got = pickRenderAltitudeM({
geoAltM: null,
baroAltM: null,
onGround: false,
surfaceM: null,
geoidN: -17.3,
});
assert.equal(got, null, 'caller applies its own existing sticky/default fallback on null');
});
test('grounded with no surfaceM and no geo/baro altitude -> still sentinel null (caller default)', () => {
const got = pickRenderAltitudeM({
geoAltM: null,
baroAltM: null,
onGround: true,
surfaceM: null,
geoidN: null,
});
assert.equal(got, null);
});
test('non-finite (NaN/Infinity) inputs are treated as missing, not thrown', () => {
assert.equal(
pickRenderAltitudeM({ geoAltM: NaN, baroAltM: 1200, onGround: false, surfaceM: null, geoidN: 5 }),
1205
);
assert.equal(
pickRenderAltitudeM({ geoAltM: Infinity, baroAltM: 1200, onGround: false, surfaceM: null, geoidN: 5 }),
1205
);
});
test('onGround with surfaceM = 0 (sea-level coast) is a legitimate finite value, not falsy-skipped', () => {
const got = pickRenderAltitudeM({
geoAltM: 10,
baroAltM: 5,
onGround: true,
surfaceM: 0,
geoidN: -32.2,
});
assert.equal(got, 0);
});
// reuseGroundedSurfaceM — taxiing surface reuse (adversarial-review [medium]).
test('reuseGroundedSurfaceM: a warm current fix is used directly', () => {
assert.equal(reuseGroundedSurfaceM(1620.4, 1619.8), 1620.4);
});
test('reuseGroundedSurfaceM: current miss falls back to the previous fix ground', () => {
// The taxiing case: current-fix cache miss (null), but last poll's fix was
// warmed — reuse its ~flat-airport ground instead of the 0 m sentinel.
assert.equal(reuseGroundedSurfaceM(null, 1619.8), 1619.8);
});
test('reuseGroundedSurfaceM: both miss -> null sentinel (caller default applies)', () => {
assert.equal(reuseGroundedSurfaceM(null, null), null);
assert.equal(reuseGroundedSurfaceM(NaN, undefined), null);
});
test('reuseGroundedSurfaceM: sea-level 0 ground is finite and reused, not falsy-skipped', () => {
assert.equal(reuseGroundedSurfaceM(0, 5), 0);
assert.equal(reuseGroundedSurfaceM(null, 0), 0);
});
test('taxiing aircraft over nonzero terrain resolves every poll after the first (no stuck 0 m)', () => {
// Simulate the poll loop for a no-baro/no-geo on-ground aircraft that moves
// to a NEW 5-decimal-rounded coordinate each poll over Denver-elevation
// terrain (~1620 m). Each poll's warm batch resolves THAT poll's fix for the
// NEXT poll, so `cachedCurrent` misses every poll but `cachedPrev` (last
// poll's now-warmed fix) hits from poll 2 onward.
const GROUND = 1620;
const warm = new Map(); // stands in for terrainHeights cache
const key = (lat) => lat.toFixed(5);
let prevFixLat = null;
const rendered = [];
for (let poll = 0; poll < 4; poll++) {
const lat = 39.85000 + poll * 0.001; // ~111 m/poll — a fresh key every poll
const cachedCurrent = warm.has(key(lat)) ? warm.get(key(lat)) : null;
const cachedPrev = prevFixLat != null && warm.has(key(prevFixLat)) ? warm.get(key(prevFixLat)) : null;
const surfaceM = reuseGroundedSurfaceM(cachedCurrent, cachedPrev);
const renderAltM = pickRenderAltitudeM({
geoAltM: null, baroAltM: null, onGround: true, surfaceM, geoidN: -17.3,
});
rendered.push(renderAltM);
// end-of-poll warm batch resolves this poll's fix for later polls
warm.set(key(lat), GROUND);
prevFixLat = lat;
}
// Poll 0 has no prior fix (brand-new contact) -> sentinel null (caller uses
// its 0 m grounded default for one poll). Polls 1..3 reuse the previous
// fix's ground and never stick underground.
assert.equal(rendered[0], null, 'first poll: no prior warm fix yet -> caller default');
assert.deepEqual(rendered.slice(1), [GROUND, GROUND, GROUND], 'subsequent polls resolve via previous fix');
});
// ---------------------------------------------------------------------------
// geoidSurfaceLastResortM — the geoid guess must never outrank what the
// contact already knows (owner incident 2026-08-21: a Re:Earth outage plus
// this guess dropped a parked contact through the mesh at a Texas field).
// ---------------------------------------------------------------------------
test('geoid last resort: a first sighting with no altitude at all gets the guess', () => {
assert.equal(geoidSurfaceLastResortM({
geoAltM: null, baroAltM: null, priorRenderM: null, geoidN: -27.4,
}), -27.4);
});
test('geoid last resort: a contact that already has a render height HOLDS it', () => {
assert.equal(geoidSurfaceLastResortM({
geoAltM: null, baroAltM: null, priorRenderM: 168.2, geoidN: -27.4,
}), null, 'null leaves surfaceM cold, so the caller falls through to its own hold');
});
test('geoid last resort: any reported altitude outranks the guess', () => {
assert.equal(geoidSurfaceLastResortM({
geoAltM: 190, baroAltM: null, priorRenderM: null, geoidN: -27.4,
}), null);
assert.equal(geoidSurfaceLastResortM({
geoAltM: null, baroAltM: 165, priorRenderM: null, geoidN: -27.4,
}), null);
});
test('geoid last resort: no geoid grid yet means no guess to make', () => {
assert.equal(geoidSurfaceLastResortM({
geoAltM: null, baroAltM: null, priorRenderM: null, geoidN: undefined,
}), null);
});