Release God's Eye View as open source

This commit is contained in:
Bilawal Sidhu committed 2026-08-24 17:02:16 -05:00
1 parent a5aaea7e16
commit 880a672b5e
433 files changed
+219893 -23

No files matched your search

+726
View File
@@ -0,0 +1,726 @@
/**
* qa-height-datum.mjs — height/vertical-datum numeric proof harness.
*
* Scaffold reused verbatim from qa-cctv-v2.mjs: the puppeteer launcher
* (Chrome executable discovery, headless flags), `QA_BASE_URL` env,
* `record()`/tally pattern, and the Google-Maps-key-injection dev-server
* recipe documented in its header comment. See that file for the reasoning
* behind the launch flags and the SwiftShader caveats they share.
*
* Unlike qa-cctv-v2 (frustum GEOMETRY / raycast-count invariants), this
* harness asserts NUMBERS: ground heights, altitudes, and terrain-provider
* identity. Every assertion is GL-independent (no pixel/screenshot
* comparisons) — it reads plain numbers off live Cesium entities and the
* app's own public module APIs, exactly like qa-cctv-v2's SERIALIZE_GEOM_SRC
* pattern.
*
* Three assertion groups (brief order):
*
* 1. CCTV ground correctness — for 5 London + 5 Austin + 3 SF cameras,
* assert each record's placed mount ground reads finitely, then compare
* its separately exposed `groundPriorM` against the
* authoritative Re:Earth ellipsoidal value fetched through the app's
* OWN `/api/terrain/heights` proxy (not a second upstream call — the
* proxy IS the oracle here, matching Task 2/3's contract). Keeping the
* prior separate is essential because Google-3D can legitimately refine
* the rendered ground to the photogrammetric mesh while the immutable
* Re:Earth prior remains the fallback/datum reference. The prior check is
* `|groundPriorM - reearthEllipsoid| < 6m`. Google-3D after
* the one-shot snap: London/Austin active-camera ground must fall in
* the brief's bands (tileset-vs-DEM legitimately differs by building
* height, so bands not exact-match).
*
* 2. Aircraft — sample >=10 live aircraft via the flights layer's public
* API. `renderAltitudeM` isn't exposed as a named field on any public
* method (only `flights.js`/`militaryFlights.js` module-private
* `_flightData` carries `geoAltitudeM`/`renderAltitudeM` directly), so
* this harness reconstructs both sides purely from public methods:
* - render height: `getAllPositions()[i].altitudeM`, which is
* `Cartographic.fromCartesian(billboard.position).height` — i.e.
* exactly the height passed to `Cartesian3.fromDegrees(lon, lat,
* renderAltitudeM)` at the placement site (flights.js ~:2100).
* - aviation/baro height: `getNearby()`/`findByQuery()`'s
* `altitudeM`, which is `info.altitude` (the UNTOUCHED sticky
* barometric field — flights.js ~:2111) — this is the "geoAltM ??
* baroAltM" input side, not the render output.
* This is the brief's documented fallback ("if geo/baro fields aren't
* exposed on the public state, assert the weaker but still-meaningful
* under-terrain + finite/plausible checks") PLUS a same-process,
* Node-side independent recomputation of `baroM + geoidHeight(lat,lon)`
* via a direct import of `src/data/geoid.js` (a pure module, imported
* the same way qa-cctv-v2.mjs imports `computeFrustumGeometry` from
* cctv.js) — giving a real numeric cross-check without reaching into
* the layer's private closure state.
*
* Under-terrain detector fingerprint (tightened): a real datum-miss
* renders an aircraft at the ~0m ellipsoidal SENTINEL (the pre-existing
* "no data yet" default) while its actual terrain ground is nowhere
* near 0 — that combination is unambiguous. A non-zero under-terrain
* reading is NOT hard-failed: a legitimately climbing/descending
* aircraft can transiently render close to real ground near an airport
* (e.g. a climb-out a few dozen metres above a field that itself sits
* well above sea level), which is correct behavior, not a bug. Those
* ambiguous cases are recorded INCONCLUSIVE with the raw numbers
* rather than FAILed.
*
* 3. Regime C re-resolve — switch the map stack to keyless OSM in-page via
* `mapStackController.setStack('osm')`, assert
* `viewer.terrainProvider` is NOT an `EllipsoidTerrainProvider`
* (constructor-name check, matching qa-cctv-v2's "borrow statics off a
* live instance's constructor" pattern — no `window.Cesium` global
* exists), and that CCTV grounds re-resolve within one event cycle
* (`gev:map-stack-changed` → cctv.js's regime re-arm — see its Task 5
* handler) by serializing a camera's ground height before/after the
* switch settles and asserting it moved OFF any old fabricated-prior
* value.
*
* Upstream-dependent assertions (live Re:Earth `/api/terrain/heights`, live
* OpenSky aircraft) are marked INCONCLUSIVE — not FAIL — when the upstream
* is unreachable this run, mirroring qa-cctv-v2's tiles-timeout handling.
* GL-dependent tile settling (the Google-3D one-shot ground snap actually
* completing under headless SwiftShader) is likewise inconclusive-on-timeout.
*
* Run: QA_BASE_URL=http://localhost:4300 node scripts/qa-height-datum.mjs
*
* Exit 0 = no hard failures. Non-zero = at least one hard FAIL (or harness error).
*/
import puppeteer from 'puppeteer';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { ensureGeoidReady, geoidHeight } from '../src/data/geoid.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = path.resolve(__dirname, '..');
const argv = process.argv.slice(2);
const getOpt = (name, dflt) => {
const i = argv.indexOf(name);
return i >= 0 && argv[i + 1] ? argv[i + 1] : dflt;
};
const getFlag = (name) => argv.includes(name);
const BASE_URL = process.env.QA_BASE_URL || 'http://localhost:4173';
const APP_URL = getOpt('--url', BASE_URL);
const HEADFUL = getFlag('--headful');
const SHOTS_DIR = path.join(REPO_ROOT, 'qa-shots', 'height-datum-qa');
const CHROME_EXECUTABLE_CANDIDATES = [
process.env.PUPPETEER_EXECUTABLE_PATH,
// Prefer puppeteer's version-pinned Chrome-for-Testing over the system
// Chrome: /Applications auto-updates underneath the harnesses, and its
// software-GL behavior shifts across majors (system Chrome 150 blew the
// tile-gated drain budget under SwiftShader on 2026-07-30 — six
// false-negative qa-cctv-v2 runs against a healthy build). A deterministic
// pinned browser beats the newest one for regression harnesses.
(() => { try { return puppeteer.executablePath(); } catch { return null; } })(),
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
'/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
'/Applications/Chromium.app/Contents/MacOS/Chromium',
].filter(Boolean);
function findChromeExecutable() {
for (const candidate of CHROME_EXECUTABLE_CANDIDATES) {
try {
if (fs.existsSync(candidate)) return candidate;
} catch {
/* ignore */
}
}
return null;
}
const results = [];
function record(name, ok, detail) {
results.push({ name, ok, detail });
const tag = ok === null ? '\x1b[33mINCONCLUSIVE\x1b[0m' : ok ? '\x1b[32mPASS\x1b[0m' : '\x1b[31mFAIL\x1b[0m';
console.log(` [${tag}] ${name}${detail ? ` — ${detail}` : ''}`);
}
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
/**
* Waits for the scene's 3D tileset to report tilesLoaded===true. Copied from
* qa-cctv-v2.mjs (see its header comment for why this must be re-awaited
* fresh before each action that depends on a REAL ground sample, not
* reused from an earlier wait).
* @param {import('puppeteer').Page} page
* @param {number} [timeoutMs=15000]
* @returns {Promise<boolean>}
*/
function waitForTilesLoaded(page, timeoutMs = 15000) {
return page.waitForFunction(
() => {
const scene = window.__godsEyeView.viewer.scene;
const prims = scene.primitives;
for (let i = 0; i < prims.length; i++) {
const p = prims.get(i);
if (p && p.constructor && p.constructor.name === 'Cesium3DTileset') {
return p.tilesLoaded === true;
}
}
return true;
},
{ timeout: timeoutMs }
).then(() => true).catch(() => false);
}
/**
* Reads a camera's placed mount-point cartographic height off its
* `cctv-<id>-ray-tl` polyline entity (first position = the mount, per
* cctv.js `buildCoverageEntities`/`applyFrustumGeometry`), returns
* `{ mountAltM, groundM }` where `groundM = mountAltM - mountHeightM` — the
* SAME arithmetic cctv.js itself uses in reverse
* (`mountAlt = ground + mountHeightM` in `computeFrustumGeometry`).
* @param {import('puppeteer').Page} page
* @param {string} camId
* @returns {Promise<{mountAltM:number, mountHeightM:number, groundM:number}|null>}
*/
async function readCameraGround(page, camId) {
return page.evaluate((id) => {
const viewer = window.__godsEyeView.viewer;
const time = viewer.clock.currentTime;
const ent = viewer.entities.getById(`cctv-${id}-ray-tl`);
if (!ent || !ent.polyline) return null;
const positions = ent.polyline.positions.getValue(time);
if (!Array.isArray(positions) || positions.length < 1) return null;
const mount = positions[0];
// No window.Cesium global (qa-cctv-v2 precedent) — borrow Cartographic's
// static fromCartesian off a live Cartographic instance's constructor.
const carto = viewer.scene.globe.ellipsoid.cartesianToCartographic(mount);
const mountAltM = carto.height;
const mod = window.__godsEyeView.dataManager.layers.get('cctv').module;
const cam = mod.getUIState().cameras.find((c) => c.id === id);
const mountHeightM = cam ? cam.mountHeightM : null;
return {
mountAltM,
mountHeightM,
groundM: Number.isFinite(mountHeightM) ? mountAltM - mountHeightM : null,
};
}, camId);
}
/**
* Fetches the authoritative Re:Earth ellipsoidal ground height for a
* (lat, lon) pair through the APP'S OWN `/api/terrain/heights` proxy (the
* same oracle terrainHeights.js's `resolveEllipsoidalGround` uses) — this is
* the ground-truth reference for assertion group 1, fetched directly (not
* through in-page code) so a proxy hiccup can be told apart from an app bug.
* @param {number} lat
* @param {number} lon
* @returns {Promise<number|null>} ellipsoid height in metres, or null on failure.
*/
async function fetchReearthEllipsoid(lat, lon) {
const url = `${APP_URL}/api/terrain/heights?points=${encodeURIComponent(`${lon.toFixed(5)},${lat.toFixed(5)}`)}`;
try {
const res = await fetch(url, { signal: AbortSignal.timeout(30000) });
if (!res.ok) return null;
const body = await res.json();
const v = Number(body?.results?.[0]?.ellipsoid);
return Number.isFinite(v) ? v : null;
} catch {
return null;
}
}
async function main() {
console.log(`\nHeight-Datum Fix Numeric Proof`);
console.log(` App URL : ${APP_URL}`);
console.log(` Mode : ${HEADFUL ? 'headful' : 'headless'}\n`);
try {
const res = await fetch(APP_URL, { method: 'GET' });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
} catch (e) {
console.error(`\x1b[31mDev server not reachable at ${APP_URL} (${e.message}).\x1b[0m`);
process.exit(2);
}
fs.mkdirSync(SHOTS_DIR, { recursive: true });
// Node-side geoid grid, used only for the aircraft baro+N cross-check
// (independent of the app's own bundled copy — same package, separate
// process, so this is a real recomputation, not reading the app's cache).
await ensureGeoidReady();
const chromeExecutable = findChromeExecutable();
const browser = await puppeteer.launch({
headless: HEADFUL ? false : 'new',
...(chromeExecutable ? { executablePath: chromeExecutable } : {}),
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--use-gl=angle',
'--use-angle=swiftshader',
'--disable-dev-shm-usage',
'--disable-web-security',
'--disable-background-timer-throttling',
'--disable-renderer-backgrounding',
'--window-size=1280,800',
],
});
const consoleErrors = [];
let exitCode = 0;
try {
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 800 });
page.on('console', (msg) => {
if (msg.type() === 'error') {
const t = msg.text();
if (!/Failed to load resource|net::ERR|status of 4\d\d|status of 5\d\d/.test(t)) {
consoleErrors.push(t);
}
}
});
console.log('Loading app...');
await page.goto(APP_URL, { waitUntil: 'domcontentloaded', timeout: 60000 });
await page.waitForFunction(
() => window.__godsEyeView && window.__godsEyeView.viewer && window.__godsEyeView.dataManager,
{ timeout: 60000 }
);
await sleep(4000);
// =========================================================================
// Group 1: CCTV ground correctness
// =========================================================================
console.log('Enabling CCTV layer (full city-packs catalog)...');
await page.evaluate(async () => {
const dm = window.__godsEyeView.dataManager;
const entry = dm.layers.get('cctv');
if (!entry.enabled) await dm.toggle('cctv');
});
const camCount = await page.evaluate(() => window.__godsEyeView.dataManager.layers.get('cctv').module.getUIState().count);
console.log(`CCTV catalog loaded: ${camCount} cameras. Waiting for the geometry-load queue to drain...`);
// The full-catalog drain is NOT a precondition for group 1's assertions
// below: every record renders correctly from `record.groundPrior` the
// instant the Re:Earth prior batch lands (Task 5 contract #1/#4 — "the
// prior IS the resolution" on globe stacks, and the fallback path in
// google-3d before a real sample resolves), which is exactly what makes
// the per-camera ground reads meaningful even mid-drain. The queue drain
// itself additionally attempts ONE REAL scene.sampleHeight per camera in
// google-3d regime (Task 5 contract #3/#5 — the ≤1×N invariant
// qa-cctv-v2 also locks), and with the full city-packs catalog
// size (800 cameras: 250 Austin + 300 Caltrans + 250 TfL — measured
// directly probing this harness's own dev server) that can take many
// minutes under headless SwiftShader (empirically ~2-6s/sample once
// real tile contention kicks in past the first few hundred). Waiting for
// FULL completion here would make routine harness runs impractically
// slow for no assertion gain, so this bounds the wait to a fixed,
// reasonable ceiling and reports the outcome as environmental/scale
// dependent (inconclusive, not a hard fail) rather than failing the
// whole run — mirroring qa-cctv-v2's own tiles-timeout handling.
const DRAIN_WAIT_CEILING_MS = 120000;
const drained = await page.waitForFunction(
() => {
const mod = window.__godsEyeView.dataManager.layers.get('cctv').module;
const ui = mod.getUIState();
return ui.loading && ui.loading.active === false;
},
{ timeout: DRAIN_WAIT_CEILING_MS }
).then(() => true).catch(() => false);
record(`CCTV geometry-load queue drains within ${Math.round(DRAIN_WAIT_CEILING_MS / 1000)}s (N=${camCount})`,
drained ? true : null,
drained
? 'loading.active === false'
: `did not fully drain within the ceiling at N=${camCount} — environmental/scale-dependent under headless SwiftShader ` +
'(each un-drained camera still renders correctly from its Re:Earth prior; group 1 assertions below read that prior directly, so this does not block them)');
// Pull the full camera list, then bucket by city so we can pick 5
// London + 5 Austin + 3 SF regardless of catalog ordering. TfL tags
// `city: 'London'` and Austin's own pack tags `city: 'Austin'` exactly
// (vite.config.js); Caltrans tags `city` with an arbitrary upstream
// `nearbyPlace` string (NOT a stable "San Francisco" literal), so SF-area
// Caltrans cameras are identified by proximity to the SF anchor instead
// (same anchor vite.config.js's CALTRANS_ANCHORS uses for prioritization).
const allCameras = await page.evaluate(() => window.__godsEyeView.dataManager.layers.get('cctv').module.getUIState().cameras);
console.log(`Camera catalog: ${allCameras.length} total.`);
const SF_ANCHOR = { lat: 37.7793, lon: -122.4193 };
const haversineKm = (lat1, lon1, lat2, lon2) => {
const R = 6371;
const dLat = (lat2 - lat1) * Math.PI / 180;
const dLon = (lon2 - lon1) * Math.PI / 180;
const a = Math.sin(dLat / 2) ** 2 + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * Math.sin(dLon / 2) ** 2;
return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
};
const londonCams = allCameras.filter((c) => c.city === 'London').slice(0, 5);
const austinCams = allCameras.filter((c) => c.city === 'Austin').slice(0, 5);
const sfCams = allCameras
.filter((c) => c.provider === 'Caltrans' && haversineKm(c.lat, c.lon, SF_ANCHOR.lat, SF_ANCHOR.lon) < 60)
.slice(0, 3);
record('found >=5 London (TfL) test cameras', londonCams.length >= 5, `found ${londonCams.length}`);
record('found >=5 Austin test cameras', austinCams.length >= 5, `found ${austinCams.length}`);
record('found >=3 SF-area (Caltrans) test cameras', sfCams.length >= 3 ? true : null,
sfCams.length >= 3
? `found ${sfCams.length}`
: `found ${sfCams.length} — live Caltrans catalog unavailable/partial this run (upstream INCONCLUSIVE, not an app datum failure)`);
const cityBuckets = [
{ label: 'London', cams: londonCams },
{ label: 'Austin', cams: austinCams },
{ label: 'SF', cams: sfCams },
];
const groundRows = []; // for the report/console table
for (const { label, cams } of cityBuckets) {
for (const cam of cams) {
const state = await page.evaluate((id) => {
const mod = window.__godsEyeView.dataManager.layers.get('cctv').module;
return mod.getUIState().cameras.find((camera) => camera.id === id) || null;
}, cam.id);
const geom = await readCameraGround(page, cam.id);
if (!geom || !Number.isFinite(geom.groundM)) {
record(`${label} ${cam.id}: record ground reads finitely`, false, 'ray-tl entity missing or non-finite height');
continue;
}
record(`${label} ${cam.id}: rendered ground reads finitely`, true,
`renderedGround=${geom.groundM.toFixed(1)}m`);
const priorM = state?.groundPriorM;
if (!Number.isFinite(priorM)) {
record(`${label} ${cam.id}: Re:Earth ground prior resolved`, null,
`renderedGround=${geom.groundM.toFixed(1)}m; prior unavailable this run`);
groundRows.push({ label, id: cam.id, groundM: geom.groundM, priorM: null, reearthM: null });
continue;
}
const reearth = await fetchReearthEllipsoid(cam.lat, cam.lon);
if (reearth === null) {
record(`${label} ${cam.id}: |ground prior - Re:Earth ellipsoid| < 6m`, null,
`Re:Earth proxy unreachable this run — prior=${priorM.toFixed(1)}m rendered=${geom.groundM.toFixed(1)}m (upstream INCONCLUSIVE, not a harness/app failure)`);
groundRows.push({ label, id: cam.id, groundM: geom.groundM, priorM, reearthM: null });
continue;
}
const delta = Math.abs(priorM - reearth);
record(`${label} ${cam.id}: |ground prior - Re:Earth ellipsoid| < 6m`, delta < 6,
`prior=${priorM.toFixed(1)}m rendered=${geom.groundM.toFixed(1)}m reearth=${reearth.toFixed(1)}m Δ=${delta.toFixed(1)}m`);
groundRows.push({ label, id: cam.id, groundM: geom.groundM, priorM, reearthM: reearth });
}
}
console.log('\n Ground-height table (city, camera, rendered ground, stored prior, Re:Earth ellipsoid):');
for (const row of groundRows) {
console.log(` ${row.label.padEnd(8)} ${row.id.padEnd(24)} rendered=${row.groundM.toFixed(1)}m` +
(row.priorM !== null ? ` prior=${row.priorM.toFixed(1)}m` : ' prior=N/A') +
(row.reearthM !== null ? ` reearth=${row.reearthM.toFixed(1)}m` : ' reearth=N/A'));
}
console.log('');
// -----------------------------------------------------------------------
// Google-3D band check: activate the London and Austin cameras nearest
// the viewer (mirrors qa-cctv-v2's focusNearest activation pattern),
// wait for the one-shot ground snap to complete (tilesLoaded gate), then
// assert the ACTIVE camera's ground falls in the brief's band. Bands
// (not exact) because the Google tileset surface legitimately differs
// from the Re:Earth DEM by building height once the snap refines it.
// -----------------------------------------------------------------------
console.log('Checking Google-3D active-camera ground BANDS (post-snap)...');
async function checkActiveCameraBand(cityLabel, targetCamId, band) {
await page.evaluate((id) => {
window.__godsEyeView.dataManager.layers.get('cctv').module.selectCamera(id, { focus: true, durationSec: 0.1 });
}, targetCamId);
await sleep(600);
const tilesReady = await waitForTilesLoaded(page, 30000);
// Give the one-shot completion pass a beat to land even after tiles
// report ready (update() ticks on its own interval — see cctv.js).
await sleep(1500);
const geom = await readCameraGround(page, targetCamId);
if (!tilesReady) {
record(`${cityLabel} active camera (${targetCamId}) google-3d ground ∈ [${band[0]},${band[1]}]m`, null,
`tiles never reported loaded within 30s under headless GL — environmental, inconclusive (recordGround=${geom?.groundM?.toFixed?.(1) ?? 'n/a'}m)`);
return;
}
if (!geom || !Number.isFinite(geom.groundM)) {
record(`${cityLabel} active camera (${targetCamId}) google-3d ground ∈ [${band[0]},${band[1]}]m`, false, 'ground unreadable');
return;
}
const inBand = geom.groundM >= band[0] && geom.groundM <= band[1];
record(`${cityLabel} active camera (${targetCamId}) google-3d ground ∈ [${band[0]},${band[1]}]m`, inBand,
`recordGround=${geom.groundM.toFixed(1)}m`);
}
if (londonCams.length) {
await checkActiveCameraBand('London', londonCams[0].id, [45, 75]);
} else {
record('London active camera google-3d ground ∈ [45,75]m', null, 'no London camera available to test');
}
if (austinCams.length) {
await checkActiveCameraBand('Austin', austinCams[0].id, [110, 135]);
} else {
record('Austin active camera google-3d ground ∈ [110,135]m', null, 'no Austin camera available to test');
}
// =========================================================================
// Group 2: Aircraft render altitude
// =========================================================================
console.log('Enabling flights layer, waiting for a live aircraft sample...');
await page.evaluate(async () => {
const dm = window.__godsEyeView.dataManager;
const entry = dm.layers.get('flights');
if (!entry.enabled) await dm.toggle('flights');
});
// OpenSky polls on its own interval; give it real time to land a batch
// (the layer polls every ~30s per docs/CURRENT-STATE.md).
const gotAircraft = await page.waitForFunction(
() => {
const mod = window.__godsEyeView.dataManager.layers.get('flights').module;
return mod.getAllPositions(1).length > 0;
},
{ timeout: 45000 }
).then(() => true).catch(() => false);
if (!gotAircraft) {
record('>=10 live aircraft sampled from OpenSky', null,
'no aircraft appeared within 45s — OpenSky upstream INCONCLUSIVE this run (not an app failure)');
} else {
// Let a full second poll cycle land (updateInterval: 30000 — see
// flights.js) before sampling. A just-appeared ON-GROUND aircraft can
// legitimately render at the sticky `alt` default (0m ellipsoidal) for
// ONE tick while `_warmGroundedAircraftSurfaceCache`'s batch resolve is
// still in flight (fire-and-forget; picked up by cachedEllipsoidalGround
// on a LATER poll — see flights.js's own comment on that function). That
// window is a documented, time-bounded product behavior, not a
// datum-fix regression — sampling immediately after the FIRST aircraft
// appears would catch exactly that transient and misreport it as an
// under-terrain violation. Waiting past a second poll interval lets the
// cache warm before the under-terrain detector runs.
console.log('Waiting through a second poll cycle so on-ground aircraft ground-cache warm-up completes...');
await sleep(32000);
// getAllPositions gives render-height (carto.height off the actual
// billboard Cartesian3 — i.e. renderAltitudeM as placed by
// Cartesian3.fromDegrees(lon, lat, renderAltitudeM)); getNearby gives
// the untouched aviation/baro field (info.altitude) per-icao24. Cross
// the two public APIs by icao24/id to reconstruct both sides without
// reaching into the layer's private _flightData closure.
const allPositions = await page.evaluate(() => {
const mod = window.__godsEyeView.dataManager.layers.get('flights').module;
return mod.getAllPositions(60);
});
// getNearby needs an ECEF center + range; use the live camera position
// (a real Cartesian3 instance — no window.Cesium global available, so
// this borrows a live instance rather than constructing one, matching
// qa-cctv-v2's pattern) with an effectively unbounded range so it
// returns everything currently shown regardless of viewer position.
const nearbyList = await page.evaluate(() => {
const mod = window.__godsEyeView.dataManager.layers.get('flights').module;
const center = window.__godsEyeView.viewer.camera.position;
return mod.getNearby(center, Number.MAX_VALUE, 200).map((a) => ({ icao24: a.icao24, altitudeM: a.altitudeM }));
});
const baroById = new Map(nearbyList.map((a) => [a.icao24, a.altitudeM]));
const sampleSize = Math.min(allPositions.length, 25);
const sample = allPositions.slice(0, sampleSize);
record(`>=10 live aircraft sampled from OpenSky (public getAllPositions/getNearby)`,
sample.length >= 10 ? true : (sample.length > 0 ? null : false),
`sampled ${sample.length} (getAllPositions total=${allPositions.length})`);
let plausibleCount = 0;
let checkedCount = 0;
const underTerrainDetails = [];
const baroPlusNDeltas = [];
for (const ac of sample) {
const renderAltM = ac.altitudeM; // carto.height off the live billboard position
const baroM = baroById.get(ac.id);
// Finite + plausible sanity: render altitude must be a real number in
// a physically sane band (-500m .. 20000m covers everything from
// Death-Valley-adjacent ground traffic to high-altitude cruise).
const finitePlausible = Number.isFinite(renderAltM) && renderAltM > -500 && renderAltM < 20000;
if (finitePlausible) plausibleCount += 1;
// Cross-check vs baro+geoidHeight when we have the baro side (weaker
// per-aircraft equality check: geoAltitudeM isn't exposed as a named
// field on any public flights-layer method — only the module-private
// `_flightData` carries geoAltitudeM/renderAltitudeM directly — so
// this harness can only recompute the FALLBACK branch's expected
// value (baro + geoid N) from the public API and log the delta,
// rather than assert it individually, for aircraft where the true
// render source was actually geo_altitude, which can legitimately
// differ from baro+N by tens of metres).
if (Number.isFinite(baroM) && Number.isFinite(renderAltM)) {
const n = geoidHeight(ac.latitude, ac.longitude);
const expectedBaroPlusN = baroM + n;
const deltaVsBaroPlusN = Math.abs(renderAltM - expectedBaroPlusN);
// Not asserted individually (geo_altitude legitimately diverges from
// baro+N) — logged into the report table so the cross-check is
// actually visible, not silently discarded.
baroPlusNDeltas.push({ id: ac.id, renderAltM, expectedBaroPlusN, deltaVsBaroPlusN });
}
checkedCount += 1;
}
record(`aircraft render altitudes are finite + physically plausible (-500..20000m)`,
checkedCount > 0 ? plausibleCount === checkedCount : null,
`${plausibleCount}/${checkedCount} plausible`);
if (baroPlusNDeltas.length) {
console.log('\n baro+geoidN cross-check table (id, renderAlt, expected baro+N, |Δ|):');
for (const d of baroPlusNDeltas) {
console.log(` ${String(d.id).padEnd(10)} renderAlt=${d.renderAltM.toFixed(1)}m` +
` expected=${d.expectedBaroPlusN.toFixed(1)}m Δ=${d.deltaVsBaroPlusN.toFixed(1)}m`);
}
console.log('');
}
// Under-terrain detector, tightened to its unambiguous fingerprint.
// Ground truth is fetched fresh through the app's own
// /api/terrain/heights proxy (same oracle as group 1), so this stays
// GL-independent and upstream-verifiable rather than reaching into
// private closures.
//
// The REAL bug (fixed by this task's Bug 1 change: flights.js's
// _warmGroundedAircraftSurfaceCache now keys its warm-cache resolve off
// the aircraft's RAW POLL lat/lon — the same coords
// pickRenderAltitudeM's surfaceM read uses — instead of the
// continuously dead-reckoned billboard position) has an unambiguous
// numeric fingerprint: an aircraft rendering at the ~0m ellipsoidal
// SENTINEL (the pre-existing "no geo/baro data yet" default) while its
// terrain ground is nowhere near 0m. HARD-FAIL only on that exact
// combination.
//
// Any OTHER under-terrain reading (non-zero renderAltitudeM below
// ground - 50m) is ambiguous — it could legitimately be a climbing/
// descending aircraft briefly near real ground elevation close to an
// airport (e.g. a departure a few dozen metres up over a field that
// itself sits well above sea level) — so those are recorded
// INCONCLUSIVE with the raw values, not FAILed.
const SENTINEL_EPS_M = 1; // Math.abs(renderAlt) < 1 is the exact 0m default
const SENTINEL_GROUND_FLOOR_M = 100; // terrain must be unambiguously non-zero
let underTerrainChecked = 0;
let sentinelViolations = 0;
let ambiguousUnderTerrain = 0;
const ambiguousDetails = [];
for (const gc of sample) {
const ground = await fetchReearthEllipsoid(gc.latitude, gc.longitude);
if (ground === null) continue; // upstream unreachable for this point — skip, don't fail
underTerrainChecked += 1;
if (gc.altitudeM >= ground - 50) continue; // not under terrain at all
const isSentinelMiss = Math.abs(gc.altitudeM) < SENTINEL_EPS_M && Math.abs(ground) > SENTINEL_GROUND_FLOOR_M;
if (isSentinelMiss) {
sentinelViolations += 1;
underTerrainDetails.push(`${gc.id}: renderAlt=${gc.altitudeM.toFixed(1)}m ground=${ground.toFixed(1)}m (0m SENTINEL fingerprint)`);
} else {
ambiguousUnderTerrain += 1;
ambiguousDetails.push(`${gc.id}: renderAlt=${gc.altitudeM.toFixed(1)}m ground=${ground.toFixed(1)}m`);
}
}
record('no aircraft renders at the 0m sentinel while terrain ground is far from 0m (datum-miss fingerprint)',
underTerrainChecked > 0 ? sentinelViolations === 0 : null,
underTerrainChecked > 0
? `${sentinelViolations} violation(s) out of ${underTerrainChecked} checked${underTerrainDetails.length ? ': ' + underTerrainDetails.slice(0, 3).join(' | ') : ''}`
: 'Re:Earth proxy unreachable for every sampled point this run — INCONCLUSIVE');
if (ambiguousUnderTerrain > 0) {
record('other under-terrain readings (non-zero, ambiguous — may be legit climb/descent near an airport)',
null,
`${ambiguousUnderTerrain} case(s): ${ambiguousDetails.slice(0, 5).join(' | ')}`);
}
}
// =========================================================================
// Group 3: Regime C re-resolve
// =========================================================================
console.log('Switching map stack to keyless OSM (regime C)...');
// Grab a London camera's ground BEFORE the switch (still whatever regime
// we were in — Google-3D, given the app's default).
const regimeCTargetId = londonCams[0]?.id || austinCams[0]?.id || allCameras[0]?.id;
const groundBeforeSwitch = regimeCTargetId ? await readCameraGround(page, regimeCTargetId) : null;
await page.evaluate(async () => {
await window.__godsEyeView.mapStackController.setStack('osm');
});
// Real (non-flat) terrain loads ASYNCHRONOUSLY on a globe stack, and by two
// different code paths depending on whether a Cesium Ion token is present:
// - NO token → keyless Re:Earth `CesiumTerrainProvider` via a DIRECT
// assignment (synchronous — ready immediately).
// - token → Cesium World Terrain via `scene.setTerrain(...)`, whose
// provider resolves in the background: `viewer.terrainProvider`
// is transiently `null` until the World Terrain layer.json
// loads (can exceed 1.5 s under SwiftShader), THEN becomes a
// `CesiumTerrainProvider`.
// Either way the END STATE is a real `CesiumTerrainProvider`, never the flat
// `EllipsoidTerrainProvider` regime C used to be stuck on. Poll for that end
// state instead of a fixed sleep (a fixed sleep races the async World Terrain
// load — the old failure mode). Report whatever it settled on.
let terrainCtor = null;
try {
await page.waitForFunction(() => {
const p = window.__godsEyeView?.viewer?.terrainProvider;
const name = p && p.constructor && p.constructor.name;
return !!name && name !== 'EllipsoidTerrainProvider';
}, { timeout: 12000, polling: 250 });
} catch { /* timed out — fall through and report the settled ctor below */ }
terrainCtor = await page.evaluate(() => {
const provider = window.__godsEyeView.viewer.terrainProvider;
return provider?.constructor?.name || null;
});
record('globe-stack terrain is a real CesiumTerrainProvider, not the flat EllipsoidTerrainProvider (regime B/C)',
terrainCtor !== null && terrainCtor !== 'EllipsoidTerrainProvider',
`constructor=${terrainCtor}`);
if (regimeCTargetId) {
const groundAfterSwitch = await readCameraGround(page, regimeCTargetId);
if (!groundBeforeSwitch || !groundAfterSwitch ||
!Number.isFinite(groundBeforeSwitch.groundM) || !Number.isFinite(groundAfterSwitch.groundM)) {
record(`CCTV grounds re-resolved for regime C (${regimeCTargetId})`, false, 'ground unreadable before or after switch');
} else {
// The prior-everywhere invariant (Task 5) means BOTH readings should
// already be close to the Re:Earth ellipsoid value in every regime —
// the real regression this guards is the OLD fabricated-prior bug
// (London ~15m, Austin ~150m fixed catalog numbers regardless of
// regime). Assert the post-switch ground is NOT stuck at either
// known-bad fabricated constant, and is within a generous tolerance
// of the pre-switch value (both should already reflect the same
// Re:Earth prior — a big jump would indicate a stale/blank regime
// record instead of a re-arm).
const isLondonId = londonCams.some((c) => c.id === regimeCTargetId);
const staleValue = isLondonId ? 15 : 150;
const notStuckAtFabricated = Math.abs(groundAfterSwitch.groundM - staleValue) > 5;
const stableAcrossSwitch = Math.abs(groundAfterSwitch.groundM - groundBeforeSwitch.groundM) < 20;
record(`CCTV ground NOT stuck at the old fabricated prior (${staleValue}m) after regime C switch (${regimeCTargetId})`,
notStuckAtFabricated,
`before=${groundBeforeSwitch.groundM.toFixed(1)}m after=${groundAfterSwitch.groundM.toFixed(1)}m`);
record(`CCTV ground re-resolved within one event cycle, consistent with the prior-everywhere invariant (${regimeCTargetId})`,
stableAcrossSwitch,
`Δ=${Math.abs(groundAfterSwitch.groundM - groundBeforeSwitch.groundM).toFixed(1)}m`);
}
} else {
record('CCTV grounds re-resolved for regime C', null, 'no test camera available');
}
// -----------------------------------------------------------------------
// Screenshot for human visual review (not a pass/fail gate).
// -----------------------------------------------------------------------
await sleep(500);
const shot = path.join(SHOTS_DIR, 'height-datum-regimeC.png');
await page.screenshot({ path: shot });
console.log(` screenshot (visual review only, not a gate) → ${path.relative(REPO_ROOT, shot)}`);
record('no console errors during height-datum exercise', consoleErrors.length === 0,
consoleErrors.length ? consoleErrors.slice(0, 3).join(' | ') : 'clean');
for (const r of results) {
if (r.ok === false) exitCode = 1;
}
} finally {
await browser.close();
}
console.log('\n' + '─'.repeat(60));
const pass = results.filter((r) => r.ok === true).length;
const fail = results.filter((r) => r.ok === false).length;
const inconclusive = results.filter((r) => r.ok === null).length;
console.log(` RESULT: ${pass} passed, ${fail} failed, ${inconclusive} inconclusive`);
console.log('─'.repeat(60) + '\n');
process.exit(exitCode);
}
main().catch((e) => {
console.error('\x1b[31mHarness error:\x1b[0m', e);
process.exit(3);
});