mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-10 05:12:23 +00:00
Port the layer probes in twenty-six browser scripts to engine primitive lists, drawn rows and layer diagnostics. Preserve Cesium as the reference and keep the supported Navara paths on the same observations. This changes QA observation only, with no application behaviour change. Review the interrupted infrastructure group and retain its readiness, drape, selection, label and floor checks. Match both installation outline buffers. Frame each CCTV city before reading its visible coverage. Read Radio colours and selected identities from actual mover writes. Retain transit's GPU upload budgets using engine-owned line primitives rather than substituting rebuild counts. Keep native inspection where a check specifically measures GPU uploads, loaded meshes or ground readiness. The preceding combined B/C commit owns all shared manifests, docs, locale catalogs and the coupling baseline; this commit changes only its own twenty-six QA scripts. Coupling remains unchanged at 344 Cesium renderer lines, 1294 Navara renderer lines, 9 Cesium import files, 1026 viewer lines, 2 Navara kind checks, 0 engine sniff calls, 9 extension reads, 1 ground pass, 3 SDK loads, 4 screen-handler files and 42 engine-name lines. The full unit suite and the boundary, format and coupling checks pass. Browser validation remains to be run. Browser checks to run GOOGLE_MAPS_API_KEY= CESIUM_ION_TOKEN= npm run dev -- --port 4291 node scripts/qa-alpr-journey.mjs http://localhost:4291 node scripts/qa-alpr-journey.mjs 'http://localhost:4291/?engine=navara' node scripts/qa-cables-overlay.mjs --url http://localhost:4291 node scripts/qa-cctv-v2.mjs --url http://localhost:4291 --fixture --terrain-globe node scripts/qa-context-arrivals.mjs http://localhost:4291 node scripts/qa-directions.mjs --url http://localhost:4291 QA_BASE_URL=http://localhost:4291 node scripts/qa-draw-tool.mjs QA_BASE_URL=http://localhost:4291 node scripts/qa-floor-hold.mjs node scripts/qa-ghostmaps.mjs --url http://localhost:4291 node scripts/qa-gnss.mjs --url http://localhost:4291 node scripts/qa-height-datum.mjs --url http://localhost:4291 node scripts/qa-infra-lod.mjs --url http://localhost:4291 node scripts/qa-installation-navigation.mjs http://localhost:4291 node scripts/qa-military-names.mjs http://localhost:4291 --no-video node scripts/qa-overpass-offload.mjs --url http://localhost:4291 node scripts/qa-radio.mjs --url http://localhost:4291 node scripts/qa-traffic-motion.mjs http://localhost:4291 --mode=osm QA_BASE_URL=http://localhost:4291 node scripts/qa-transit-recovery.mjs QA_BASE_URL=http://localhost:4291 node scripts/qa-transit.mjs node scripts/qa-weather-teardown.mjs --url http://localhost:4291 --headless for engine in cesium navara; do node scripts/qa-enrich-ambient.mjs --url http://localhost:4291 --engine=$engine QA_BASE_URL=http://localhost:4291 node scripts/qa-floor-verify.mjs --engine=$engine node scripts/qa-heading-b3.mjs --url http://localhost:4291 --engine=$engine node scripts/qa-overlay-baseline.mjs --url http://localhost:4291 --engine=$engine node scripts/qa-sprites-b5.mjs --url http://localhost:4291 --engine=$engine node scripts/qa-vessel-datum.mjs --url 'http://localhost:4291/?qaFeed=vessels:fixture' --engine $engine done The military-names driver exercises installation-polish; the transit driver exercises transit-scenes. Use only the keyless server. Cases that require photoreal tiles or unavailable providers remain unverified.
310 lines
14 KiB
JavaScript
310 lines
14 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* scripts/qa-floor-hold.mjs — a grounded contact holds its floor through a
|
|
* terrain-proxy outage (owner incident, 2026-08-21).
|
|
*
|
|
* Reproduces the incident end to end against the RENDERED mesh:
|
|
*
|
|
* phase A the terrain proxy is healthy. A GROUNDED contact reporting no
|
|
* altitude at all (the AXEL21 profile — a few knots, alt unknown)
|
|
* settles onto its resolved floor.
|
|
* phase B every `/api/terrain/heights` request answers 504 — the same
|
|
* "[terrain-heights-proxy] refresh incomplete" the server logged —
|
|
* and the contact taxis into cells that were never warmed.
|
|
*
|
|
* The contact must stay ON the mesh through phase B. Without the hold it falls
|
|
* to the geoid, which at an inland field is ~150 m of burial.
|
|
*
|
|
* node scripts/qa-floor-hold.mjs # Metal on macOS, SwiftShader elsewhere
|
|
* node scripts/qa-floor-hold.mjs --headful # watch it
|
|
* QA_BASE_URL=http://localhost:4257 node scripts/qa-floor-hold.mjs
|
|
*
|
|
* GPU BACKEND MATTERS. `scene.sampleHeight` answers on both backends, but
|
|
* software rasterization streams a coarser LOD. Measured directly, one probe
|
|
* point on the Austin apron read ~20.9 m under swiftshader against ~168.4 m
|
|
* under Metal — a backend comparison at one point, not to be confused with the
|
|
* same-backend run-to-run spread (122.1 m vs 20.6 m on a cell centre) that
|
|
* retired the hold chain's unvalidated tier. The
|
|
* default is Metal on macOS and SwiftShader elsewhere; `--angle` overrides it.
|
|
* Mesh samples describe the selected backend, not another GPU's rendered LOD.
|
|
* An unavailable rendered-mesh oracle remains a failed check, even if the
|
|
* separate bare-earth DEM check passes. Software evidence does not establish
|
|
* real-GPU visual correctness.
|
|
*/
|
|
import puppeteer from 'puppeteer';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
|
|
const APP_URL = process.env.QA_BASE_URL || 'http://localhost:4173';
|
|
const argv = Object.fromEntries(process.argv.slice(2)
|
|
.map((a) => a.replace(/^--/, '').split('=')).map(([k, v]) => [k, v ?? true]));
|
|
// Metal is a macOS-only ANGLE backend; defaulting to it off-Mac fails WebGL init.
|
|
const ANGLE = String(argv.angle || (process.platform === 'darwin' ? 'metal' : 'swiftshader'));
|
|
const HEADFUL = !!argv.headful;
|
|
// Austin airport apron by default — the site qa-floor-verify pins, where the
|
|
// mesh sits ~150 m above the geoid and burial is unmistakable.
|
|
const SITE = {
|
|
lat: Number.isFinite(Number(argv.lat)) ? Number(argv.lat) : 30.1975,
|
|
lon: Number.isFinite(Number(argv.lon)) ? Number(argv.lon) : -97.6660,
|
|
};
|
|
const ICAO = 'aa0f01';
|
|
const OUT_DIR = new URL(APP_URL).searchParams.get('engine') === 'navara' ? 'qa-shots/floorhold/navara' : 'qa-shots/floorhold';
|
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
|
const results = [];
|
|
function record(name, ok, detail) {
|
|
results.push({ name, ok, detail });
|
|
console.log(` [${ok ? '\x1b[32mPASS\x1b[0m' : '\x1b[31mFAIL\x1b[0m'}] ${name}${detail ? ` — ${detail}` : ''}`);
|
|
}
|
|
|
|
fs.mkdirSync(OUT_DIR, { recursive: true });
|
|
const chrome = await puppeteer.executablePath().catch(() => undefined);
|
|
const browser = await puppeteer.launch({
|
|
headless: HEADFUL ? false : 'new',
|
|
executablePath: chrome,
|
|
args: ['--use-gl=angle', `--use-angle=${ANGLE}`, '--enable-webgl',
|
|
'--ignore-gpu-blocklist', '--no-sandbox'],
|
|
});
|
|
const page = await browser.newPage();
|
|
await page.setViewport({ width: 1400, height: 900 });
|
|
page.on('pageerror', (e) => console.log('[pageerror]', e.message));
|
|
|
|
await page.evaluateOnNewDocument((site, icao) => {
|
|
window.__CONTACT = { icao, lat: site.lat, lon: site.lon };
|
|
window.__TERRAIN = { ok: 0, failed: 0 };
|
|
window.__TERRAIN_FAIL = false;
|
|
const realFetch = window.fetch.bind(window);
|
|
const json = (o) => new Response(JSON.stringify(o), {
|
|
status: 200, headers: { 'Content-Type': 'application/json' },
|
|
});
|
|
window.fetch = (input, init) => {
|
|
const raw = typeof input === 'string' || input instanceof URL ? String(input) : input?.url;
|
|
let url; try { url = new URL(raw, window.location.href); } catch { return realFetch(input, init); }
|
|
const app = url.origin === window.location.origin;
|
|
if (app && url.pathname === '/api/terrain/heights') {
|
|
if (window.__TERRAIN_FAIL) {
|
|
window.__TERRAIN.failed += 1;
|
|
return Promise.resolve(new Response('upstream timeout', { status: 504 }));
|
|
}
|
|
window.__TERRAIN.ok += 1;
|
|
return realFetch(input, init);
|
|
}
|
|
if (app && url.pathname === '/api/flights') {
|
|
const c = window.__CONTACT;
|
|
const t = Math.floor(Date.now() / 1000);
|
|
// baro_altitude (7) and geo_altitude (13) both null, on_ground true.
|
|
return Promise.resolve(json({
|
|
time: t,
|
|
states: [[c.icao, 'AXEL21', 'Synthetica', t, t, c.lon, c.lat,
|
|
null, true, 1.5, 90, 0, null, null, null, false, 0]],
|
|
}));
|
|
}
|
|
if (app && url.pathname === '/api/flights/track') return Promise.resolve(json({ path: [] }));
|
|
if (app && /^\/api\/adsbdb\//.test(url.pathname)) return Promise.resolve(json({}));
|
|
return realFetch(input, init);
|
|
};
|
|
}, SITE, ICAO);
|
|
|
|
console.log(`\nFloor hold through a terrain outage\n App : ${APP_URL}\n GPU : ANGLE/${ANGLE}\n Site : ${SITE.lat}, ${SITE.lon}\n`);
|
|
await page.goto(APP_URL, { waitUntil: 'domcontentloaded', timeout: 120000 });
|
|
await page.waitForFunction(() => window.__godsEyeView?.layers && window.__godsEyeView?.dataManager,
|
|
{ timeout: 150000 });
|
|
await sleep(12000); // let the boot fly-to settle before pinning
|
|
await page.evaluate(() => {
|
|
document.querySelector('#first-run-launcher:not([hidden]) [data-first-run-choice="explore"]')?.click();
|
|
});
|
|
await sleep(600); // let the launcher dismissal finish before taking evidence
|
|
|
|
await page.evaluate((site) => { window.__SITE = site; }, SITE);
|
|
const pin = () => page.evaluate(async () => {
|
|
const app = window.__godsEyeView;
|
|
if (!app.viewer) {
|
|
const geo = await import('/src/geo/index.js');
|
|
app.engine.camera.cancelFlight();
|
|
app.engine.camera.setView({
|
|
destination: geo.cartesian3.fromDegrees(window.__SITE.lon, window.__SITE.lat - 0.008, 900),
|
|
orientation: { heading: 0, pitch: -0.45, roll: 0 },
|
|
});
|
|
app.engine.camera.moveEnd.raiseEvent();
|
|
return;
|
|
}
|
|
const v = app.viewer;
|
|
const C = v.camera.positionCartographic.constructor;
|
|
v.camera.cancelFlight();
|
|
v.camera.setView({
|
|
destination: v.scene.globe.ellipsoid.cartographicToCartesian(
|
|
C.fromDegrees(window.__SITE.lon, window.__SITE.lat - 0.008, 900),
|
|
),
|
|
orientation: { heading: 0, pitch: -0.45, roll: 0 },
|
|
});
|
|
v.camera.moveEnd.raiseEvent();
|
|
});
|
|
await pin();
|
|
const billboardMode = await page.evaluate(async () => {
|
|
const manager = window.__godsEyeView.dataManager;
|
|
const flights = manager.layers.get('flights').module;
|
|
// This harness measures billboard positions. A ready 3D model deliberately
|
|
// hides its billboard; that handoff is covered by track-regression instead.
|
|
flights.setParams({ models3d: false });
|
|
await manager.toggle('flights');
|
|
return flights.getParams().models3d === false;
|
|
});
|
|
record('the billboard floor test is explicitly in 2D aircraft mode', billboardMode);
|
|
|
|
/** Reads the contact's rendered height and the rendered mesh beneath it. */
|
|
const measure = () => page.evaluate(async (icao) => {
|
|
const app = window.__godsEyeView;
|
|
if (!app.viewer) {
|
|
const geo = await import('/src/geo/index.js');
|
|
const bb = app.layers.drawn('flights').find((row) => row.id === icao && !row.trackedModel);
|
|
if (!bb?.position) return { error: 'contact billboard not found' };
|
|
const carto = geo.cartographic.fromCartesian(bb.position);
|
|
let mesh = null;
|
|
for (let attempt = 0; attempt < 10 && mesh == null; attempt++) {
|
|
app.engine.render.request('floor-hold');
|
|
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
|
|
const height = app.engine.surface.sampleHeight(carto);
|
|
if (Number.isFinite(height)) mesh = height;
|
|
else await new Promise((resolve) => setTimeout(resolve, 1200));
|
|
}
|
|
return { spriteH: carto.height, meshH: mesh, shown: bb.show === true,
|
|
lat: +(carto.latitude * 180 / Math.PI).toFixed(5), lon: +(carto.longitude * 180 / Math.PI).toFixed(5), terrain: { ...window.__TERRAIN } };
|
|
}
|
|
const v = app.viewer;
|
|
const C = v.camera.positionCartographic.constructor;
|
|
// The contact as the layer draws it (the probe's drawn rows: the layer owns
|
|
// no Cesium billboard of its own any more; the engine's batched billboards
|
|
// keep a hidden spare's old id, so an id match there can be stale).
|
|
const bb = app.layers.drawn('flights').find((row) => row.id === icao && !row.trackedModel);
|
|
if (!bb?.position) return { error: 'contact billboard not found' };
|
|
// Every billboard and model in the scene (the engine's mover batches
|
|
// included) stays out of the mesh probes.
|
|
const sprites = []; const models = [];
|
|
const walk = (coll) => {
|
|
for (let i = 0; i < coll.length; i++) {
|
|
let p; try { p = coll.get(i); } catch { continue; }
|
|
if (!p) continue;
|
|
if (typeof p.length === 'number' && typeof p.get === 'function') { walk(p); continue; }
|
|
if (p.image !== undefined && p.alignedAxis !== undefined) sprites.push(p);
|
|
else if (p.boundingSphere && p.modelMatrix) models.push(p);
|
|
}
|
|
};
|
|
try { walk(v.scene.primitives); } catch { /* mid-teardown */ }
|
|
const carto = C.fromCartesian(bb.position);
|
|
const lat = carto.latitude * 180 / Math.PI;
|
|
const lon = carto.longitude * 180 / Math.PI;
|
|
// `sampleHeight` runs an offscreen PICK pass, so it needs the scene to be
|
|
// rendering. This app parks its render loop when idle (the wave-2 governor),
|
|
// and a probe fired against a parked scene — or one whose tiles are still
|
|
// streaming after a camera move — reads `undefined`. Pump a frame through the
|
|
// app's own governor hook before each attempt, exactly as the fly_route
|
|
// harness does, and accept the coarse cell centre as a fallback point.
|
|
const pump = () => new Promise((resolve) => {
|
|
try { window.__godsEyeView.requestRender?.(); } catch { /* no governor */ }
|
|
try { v.scene.requestRender(); } catch { /* explicit-render off */ }
|
|
requestAnimationFrame(() => requestAnimationFrame(resolve));
|
|
});
|
|
let mesh = null;
|
|
for (let attempt = 0; attempt < 10 && mesh == null; attempt += 1) {
|
|
await pump();
|
|
for (const [pLat, pLon] of [[lat, lon], [+lat.toFixed(3), +lon.toFixed(3)]]) {
|
|
for (const exclude of [[...sprites, ...models], []]) {
|
|
try {
|
|
const h = v.scene.sampleHeight(C.fromDegrees(pLon, pLat, 0), exclude);
|
|
if (Number.isFinite(h)) { mesh = h; break; }
|
|
} catch { /* mid-stream */ }
|
|
}
|
|
if (mesh != null) break;
|
|
}
|
|
if (mesh == null) await new Promise((r) => setTimeout(r, 1200));
|
|
}
|
|
return {
|
|
spriteH: carto.height, meshH: mesh, shown: bb.show === true,
|
|
lat: +lat.toFixed(5), lon: +lon.toFixed(5), terrain: { ...window.__TERRAIN },
|
|
};
|
|
}, ICAO);
|
|
|
|
/** Bare-earth ground truth, fetched SERVER-SIDE so the page's simulated outage
|
|
* cannot touch it. The photoreal mesh sits ABOVE bare earth, so this is the
|
|
* weaker of the two oracles — but the geoid is ~150 m below it, so burial is
|
|
* still unmistakable. */
|
|
const truth = new Map();
|
|
async function demTruth(lat, lon) {
|
|
const key = `${lat.toFixed(4)},${lon.toFixed(4)}`;
|
|
if (truth.has(key)) return truth.get(key);
|
|
let h = null;
|
|
try {
|
|
const url = new URL('/api/terrain/heights', APP_URL);
|
|
url.searchParams.set('points', `${lon.toFixed(5)},${lat.toFixed(5)}`);
|
|
const res = await fetch(url);
|
|
if (res.ok) {
|
|
const v = Number((await res.json())?.results?.[0]?.ellipsoid);
|
|
if (Number.isFinite(v)) h = v;
|
|
}
|
|
} catch { /* the oracle is best-effort too */ }
|
|
truth.set(key, h);
|
|
return h;
|
|
}
|
|
|
|
const samples = [];
|
|
async function sample(phase, note) {
|
|
const m = await measure();
|
|
if (m.error) { console.log(` ${phase} ${note}: ${m.error}`); return null; }
|
|
const dem = await demTruth(m.lat, m.lon);
|
|
const row = {
|
|
phase, note, ...m, demH: dem,
|
|
meshClearM: m.meshH != null ? +(m.spriteH - m.meshH).toFixed(2) : null,
|
|
demClearM: dem != null ? +(m.spriteH - dem).toFixed(2) : null,
|
|
};
|
|
samples.push(row);
|
|
console.log(` ${phase} ${note}: @${m.lat},${m.lon} sprite=${m.spriteH.toFixed(1)}`
|
|
+ ` mesh=${m.meshH?.toFixed?.(1) ?? 'n/a'} (${row.meshClearM})`
|
|
+ ` DEM=${dem?.toFixed?.(1) ?? 'n/a'} (${row.demClearM})`
|
|
+ ` terrain=${JSON.stringify(m.terrain)}`);
|
|
return row;
|
|
}
|
|
|
|
console.log('\nphase A — terrain healthy, warming cells (3 polls)');
|
|
for (let i = 0; i < 3; i++) { await sleep(32000); await pin(); }
|
|
await sample('A', 'warm');
|
|
fs.writeFileSync(path.join(OUT_DIR, `hold-A-warm-${ANGLE}.png`), await page.screenshot({ type: 'png' }));
|
|
|
|
console.log('\nphase B — every terrain request answers 504 from here');
|
|
await page.evaluate(() => { window.__TERRAIN_FAIL = true; });
|
|
for (let i = 1; i <= 4; i++) {
|
|
await page.evaluate(() => { window.__CONTACT.lon += 0.0016; }); // ~150 m east per poll
|
|
await sleep(32000);
|
|
await pin();
|
|
await sample('B', `poll+${i}`);
|
|
}
|
|
fs.writeFileSync(path.join(OUT_DIR, `hold-B-outage-${ANGLE}.png`), await page.screenshot({ type: 'png' }));
|
|
await browser.close();
|
|
|
|
console.log('');
|
|
const outage = samples.filter((s) => s.phase === 'B');
|
|
const meshRows = outage.filter((s) => s.meshClearM != null);
|
|
const demRows = outage.filter((s) => s.demClearM != null);
|
|
record('the terrain proxy really went down mid-run',
|
|
outage.length > 0 && outage[outage.length - 1].terrain.failed > 0,
|
|
`${outage[outage.length - 1]?.terrain?.failed ?? 0} requests answered 504`);
|
|
record('the contact stayed visible throughout', outage.every((s) => s.shown === true));
|
|
if (meshRows.length) {
|
|
const worst = Math.min(...meshRows.map((s) => s.meshClearM));
|
|
record('the contact never rendered below the RENDERED MESH', worst >= -0.5,
|
|
`worst clearance ${worst} m across ${meshRows.length} samples`);
|
|
} else {
|
|
record('rendered-mesh oracle available', false,
|
|
`sampleHeight never answered on ANGLE/${ANGLE} — falling back to the bare-earth DEM`);
|
|
}
|
|
if (demRows.length) {
|
|
const worst = Math.min(...demRows.map((s) => s.demClearM));
|
|
record('the contact never fell to the geoid (bare-earth DEM oracle)', worst >= -2,
|
|
`worst clearance ${worst} m above bare earth; the geoid here is ~150 m below it`);
|
|
}
|
|
fs.writeFileSync(path.join(OUT_DIR, `hold-${ANGLE}.json`), JSON.stringify(samples, null, 2));
|
|
console.log(`\n evidence: ${OUT_DIR}/hold-*-${ANGLE}.png, ${OUT_DIR}/hold-${ANGLE}.json`);
|
|
|
|
const failed = results.filter((r) => r.ok === false).length;
|
|
console.log(`\n RESULT: ${results.length - failed} passed, ${failed} failed\n`);
|
|
process.exit(failed ? 1 : 0);
|