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

+578
View File
@@ -0,0 +1,578 @@
#!/usr/bin/env node
/**
* qa-sprites-b5.mjs — throwaway QA harness for skylight Tasks 8+9
* (type-aware billboard silhouettes + per-class 2D/3D scale; Batch 5).
*
* Injects synthetic STRAIGHT-FLYING aircraft covering ALL 8 classifyAircraft
* classes — the flights layer via OpenSky extended categories (state[17]),
* the military layer via adsb.lol type codes — using the same fetch-shim +
* history-priming pattern as scripts/qa-heading-b3.mjs (positions analytic at
* serve time so every fix is self-consistent through the render delay).
*
* Machine assertions:
* B1 ingest : both layers ingest the synthetic planes
* B2 billboards : every fleet billboard carries EXACTLY the expected
* per-class glyph (image data-URI === aircraftIcon(klass),
* imported from src) and per-class scale (flights:
* CLASS_SCALE_2D; military: 0.7 × CLASS_SCALE_2D)
* B3 models : with 3D on, fleet Cesium.Model.scale === the layer's
* MODEL_SCALE (both 1; legacy scale is baked into the GLB)
* × CLASS_SCALE_3D[klass] per icao; tracked too
* B4 clean : no console errors
*
* Visual output (qa-shots/b5/, untracked): top-down 2D spreads of both rows,
* per-row close-ups, a tracked-cyan tint shot, and 3D cluster shots at two
* zooms per layer showing per-class model scale differences. The PNGs are the
* acceptance test — read them.
*
* Run: node scripts/qa-sprites-b5.mjs --url http://localhost:4300
* Exits non-zero if any assertion fails. Never commits anything.
*/
import fs from 'node:fs';
import path from 'node:path';
import puppeteer from 'puppeteer';
import { classifyAircraft, CLASS_SCALE_2D, CLASS_SCALE_3D, CLASS_MODEL_REAL } from '../src/data/aircraftClass.js';
// Hangar fleet (2026-08-16): real per-class GLBs render at scale 1; military
// heavies (airliner/quadjet/glider) render airplane.glb at 1 x class; only
// fastjet/unknown keep the per-layer jet/airplane MODEL_SCALE formula.
const flightsWantScale = (klass) => (CLASS_MODEL_REAL[klass] ? 1 : FL_MODEL_SCALE * CLASS_SCALE_3D[klass]);
const MIL_PLANE_CLASSES = new Set(['airliner', 'quadjet', 'glider']);
const militaryWantScale = (klass) => (CLASS_MODEL_REAL[klass] ? 1
: MIL_PLANE_CLASSES.has(klass) ? MIL_PLANE_MODEL_SCALE * CLASS_SCALE_3D[klass]
: MIL_MODEL_SCALE * CLASS_SCALE_3D[klass]);
import { aircraftIcon } from '../src/data/aircraftIcons.js';
// ---------------------------------------------------------------------------
// Args (same shape as qa-heading-b3.mjs)
// ---------------------------------------------------------------------------
const argv = process.argv.slice(2);
const getFlag = (name) => argv.includes(name);
const getOpt = (name, dflt) => {
const i = argv.indexOf(name);
return i >= 0 && argv[i + 1] ? argv[i + 1] : dflt;
};
const APP_URL = getOpt('--url', 'http://localhost:4173');
const HEADFUL = getFlag('--headful');
const SHOT_DIR = path.resolve('qa-shots/b5');
const FL_MODEL_SCALE = 1; // flights.js (airplane.glb is transform-applied and meter-scale)
const MIL_PLANE_MODEL_SCALE = 1; // military heavy classes share the same baked airplane.glb
const MIL_MODEL_SCALE = 1; // militaryFlights.js (jet.glb is already real-world scale, native radius ~29.8 m)
const MIL_BILLBOARD_SCALE = 0.7; // militaryFlights BILLBOARD_SCALE
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 { /* skip */ }
}
return null;
}
const results = [];
function record(name, ok, detail) {
results.push({ name, ok, detail });
const tag = ok ? '\x1b[32mPASS\x1b[0m' : '\x1b[31mFAIL\x1b[0m';
console.log(` [${tag}] ${name}${detail ? ` — ${detail}` : ''}`);
}
function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); }
// ---------------------------------------------------------------------------
// Synthetic straight-flying aircraft.
// Rows (2D spread): flights row at lat 30.32 (6 category-driven classes),
// military row at lat 30.28 (all 8 classes via type codes). Clusters (3D
// scale comparison): tight 130–150 m spacing so the follow-camera frames
// several models at once below the minimumPixelSize clamp distance.
// courseDeg 0 = flying north (glyph nose-up in a north-up top-down view);
// two planes fly rotated courses to confirm sprites still follow the path.
// ---------------------------------------------------------------------------
const ROW_LON0 = -97.82;
const ROW_STEP_DEG = 0.012; // ~1.15 km — full military row (8 planes) fits the unobstructed frame center
const CLUSTER_LON0 = -97.7431;
const mDegLon = (m, lat) => m / (111320 * Math.cos((lat * Math.PI) / 180));
const SPRITES = {
timeOffsetSec: 0,
// OpenSky extended categories: 2 light, 4 large airliner, 6 heavy/widebody,
// 7 high-perf fastjet, 8 rotorcraft, 9 glider. (quadjet/turboprop need type
// codes, which OpenSky lacks — the military row covers those.)
flights: [
{ icao: 'caa001', callsign: 'FL-LGT', lon0: ROW_LON0 + 0 * ROW_STEP_DEG, lat0: 30.32, courseDeg: 0, speedMps: 80, altM: 2500, category: 2 },
{ icao: 'caa002', callsign: 'FL-AIR', lon0: ROW_LON0 + 1 * ROW_STEP_DEG, lat0: 30.32, courseDeg: 0, speedMps: 80, altM: 2600, category: 4 },
{ icao: 'caa003', callsign: 'FL-WID', lon0: ROW_LON0 + 2 * ROW_STEP_DEG, lat0: 30.32, courseDeg: 0, speedMps: 80, altM: 2700, category: 6 },
{ icao: 'caa004', callsign: 'FL-FJ', lon0: ROW_LON0 + 3 * ROW_STEP_DEG, lat0: 30.32, courseDeg: 90, speedMps: 80, altM: 2800, category: 7 },
{ icao: 'caa005', callsign: 'FL-HEL', lon0: ROW_LON0 + 4 * ROW_STEP_DEG, lat0: 30.32, courseDeg: 0, speedMps: 40, altM: 1200, category: 8 },
{ icao: 'caa006', callsign: 'FL-GLD', lon0: ROW_LON0 + 5 * ROW_STEP_DEG, lat0: 30.32, courseDeg: 0, speedMps: 40, altM: 2000, category: 9 },
// 3D cluster (150 m spacing): widebody vs airliner (tracked) vs fastjet
{ icao: 'cbb001', callsign: '3D-WID', lon0: CLUSTER_LON0 - mDegLon(150, 30.10), lat0: 30.10, courseDeg: 0, speedMps: 60, altM: 3000, category: 6 },
{ icao: 'cbb002', callsign: '3D-AIR', lon0: CLUSTER_LON0, lat0: 30.10, courseDeg: 0, speedMps: 60, altM: 3000, category: 4 },
{ icao: 'cbb003', callsign: '3D-FJ', lon0: CLUSTER_LON0 + mDegLon(150, 30.10), lat0: 30.10, courseDeg: 0, speedMps: 60, altM: 3000, category: 7 },
],
military: [
{ hex: 'dda001', flight: 'ML-LGT', t: 'C172', lon0: ROW_LON0 + 0 * ROW_STEP_DEG, lat0: 30.28, courseDeg: 0, speedMps: 60, altFt: 9000 },
{ hex: 'dda002', flight: 'ML-GLD', t: 'DISC', lon0: ROW_LON0 + 1 * ROW_STEP_DEG, lat0: 30.28, courseDeg: 0, speedMps: 40, altFt: 8000 },
{ hex: 'dda003', flight: 'ML-TPR', t: 'C130', lon0: ROW_LON0 + 2 * ROW_STEP_DEG, lat0: 30.28, courseDeg: 0, speedMps: 80, altFt: 10000 },
{ hex: 'dda004', flight: 'ML-AIR', t: 'A320', lon0: ROW_LON0 + 3 * ROW_STEP_DEG, lat0: 30.28, courseDeg: 0, speedMps: 80, altFt: 11000 },
{ hex: 'dda005', flight: 'ML-WID', t: 'C17', lon0: ROW_LON0 + 4 * ROW_STEP_DEG, lat0: 30.28, courseDeg: 0, speedMps: 80, altFt: 12000 },
{ hex: 'dda006', flight: 'ML-QUAD', t: 'B744', lon0: ROW_LON0 + 5 * ROW_STEP_DEG, lat0: 30.28, courseDeg: 0, speedMps: 80, altFt: 13000 },
{ hex: 'dda007', flight: 'ML-HEL', t: 'H60', lon0: ROW_LON0 + 6 * ROW_STEP_DEG, lat0: 30.28, courseDeg: 0, speedMps: 40, altFt: 3000 },
// course 0 like the rest — keeps the row a clean size-comparison line (2D
// rotation is demoed by caa004 and gated by qa-heading-b3 anyway).
{ hex: 'dda008', flight: 'ML-FJ', t: 'F16', lon0: ROW_LON0 + 7 * ROW_STEP_DEG, lat0: 30.28, courseDeg: 0, speedMps: 80, altFt: 15000 },
// 3D cluster: C17 vs F16 (tracked) vs B744 vs C130. jet.glb is a
// real-world-scale asset (native bounding radius ~29.8) rendered at
// MODEL_SCALE 1, so models are ~22–43 m radius — same 150 m spacing and
// 450–900 m shot ranges as the flights cluster.
{ hex: 'ddb001', flight: '3DMWID', t: 'C17', lon0: CLUSTER_LON0 - mDegLon(150, 30.05), lat0: 30.05, courseDeg: 0, speedMps: 60, altFt: 12000 },
{ hex: 'ddb002', flight: '3DMFJ', t: 'F16', lon0: CLUSTER_LON0, lat0: 30.05, courseDeg: 0, speedMps: 60, altFt: 12000 },
{ hex: 'ddb003', flight: '3DMQUA', t: 'B744', lon0: CLUSTER_LON0 + mDegLon(150, 30.05), lat0: 30.05, courseDeg: 0, speedMps: 60, altFt: 12000 },
{ hex: 'ddb004', flight: '3DMTPR', t: 'C130', lon0: CLUSTER_LON0 - mDegLon(300, 30.05), lat0: 30.05, courseDeg: 0, speedMps: 60, altFt: 12000 },
],
};
// Node-side expected classification (same inputs the layers see).
const expectedFlights = new Map(SPRITES.flights.map((p) => [p.icao, classifyAircraft({ category: p.category })]));
const expectedMilitary = new Map(SPRITES.military.map((p) => [p.hex, classifyAircraft({ typeCode: p.t })]));
// ---------------------------------------------------------------------------
async function main() {
console.log(`\nType-aware sprite QA (Batch 5 / skylight Tasks 8+9)`);
console.log(` App URL : ${APP_URL}\n`);
console.log(` Expected classes — flights: ${[...expectedFlights.entries()].map(([k, v]) => `${k}:${v}`).join(' ')}`);
console.log(` Expected classes — military: ${[...expectedMilitary.entries()].map(([k, v]) => `${k}:${v}`).join(' ')}\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(SHOT_DIR, { recursive: true });
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 = [];
const httpErrors = [];
try {
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 800 });
page.on('console', (msg) => {
if (msg.type() === 'error') {
const text = msg.text();
if (!/Failed to load resource.*404/i.test(text)) consoleErrors.push(text);
}
});
page.on('pageerror', (err) => consoleErrors.push(`pageerror: ${err.message}`));
page.on('response', (response) => {
if (response.status() >= 500) httpErrors.push(`${response.status()} ${response.url()}`);
});
// ---- Straight-flight fetch shim (installed before any app code runs) ----
await page.evaluateOnNewDocument((spec) => {
window.__SPR = spec;
window.__SPR.epochMs = Date.now();
window.__SPR_HITS = { opensky: 0, mil: 0 };
// Straight line from (lon0, lat0) along courseDeg at speedMps.
window.__SPR.stateAt = (p, tSec) => {
const cRad = (p.courseDeg * Math.PI) / 180;
const dist = p.speedMps * tSec;
const east = dist * Math.sin(cRad);
const north = dist * Math.cos(cRad);
const lat = p.lat0 + north / 111320;
const lon = p.lon0 + east / (111320 * Math.cos((p.lat0 * Math.PI) / 180));
return { lon, lat, course: p.courseDeg, speedMps: p.speedMps };
};
const realFetch = window.fetch.bind(window);
const jsonResponse = (obj) => new Response(JSON.stringify(obj), {
status: 200, headers: { 'Content-Type': 'application/json' },
});
window.fetch = (input, init) => {
const url = typeof input === 'string' ? input : (input && input.url) || '';
const S = window.__SPR;
if (url.includes('/api/openai/hud-summary')) {
return Promise.resolve(jsonResponse({ summary: 'Aircraft scale QA' }));
}
const nowSec = Date.now() / 1000 + (S.timeOffsetSec || 0);
const tRel = nowSec - S.epochMs / 1000;
if (url.includes('/api/opensky-track')) return Promise.resolve(jsonResponse({ path: [] }));
if (url.includes('/api/adsblol/trace')) {
return Promise.resolve(jsonResponse({ timestamp: Math.floor(nowSec), trace: [] }));
}
if (url.includes('/api/opensky')) {
window.__SPR_HITS.opensky++;
const states = S.flights.map((f) => {
const s = S.stateAt(f, tRel);
return [
f.icao, f.callsign, 'Synthetica',
Math.floor(nowSec), Math.floor(nowSec),
s.lon, s.lat, f.altM,
false, // on_ground
s.speedMps, s.course,
0, null, null, null, false, 0,
f.category, // state[17] — extended emitter category
];
});
return Promise.resolve(jsonResponse({ time: Math.floor(nowSec), states }));
}
if (url.includes('/api/adsblol/mil')) {
window.__SPR_HITS.mil++;
const ac = S.military.map((m) => {
const s = S.stateAt(m, tRel);
return {
hex: m.hex, flight: m.flight,
lon: s.lon, lat: s.lat, alt_baro: m.altFt,
track: s.course, gs: s.speedMps * 1.9438,
t: m.t, r: `SY-${m.hex.slice(-3)}`, ownOp: 'SYNTH AF',
seen_pos: Math.max(0, -(S.timeOffsetSec || 0)),
};
});
return Promise.resolve(jsonResponse({ msg: 'No error', now: Date.now(), ac }));
}
// Synthetic categories/types already exercise every classification
// branch. ADSBDB enrichment is incidental to this visual-scale check,
// so keep an unavailable public enrichment provider from generating an
// unrelated console error.
if (url.includes('/api/adsbdb/')) return Promise.resolve(jsonResponse({ found: false }));
return realFetch(input, init);
};
}, SPRITES);
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, polling: 200 }
);
console.log(' App globals ready.');
// ---- In-page probes: billboard walk, model walk, tracked-model finder ---
await page.evaluate(() => {
// Every billboard (image + alignedAxis + id) in every collection.
window.__collectBillboards = function () {
const v = window.__godsEyeView.viewer;
const out = [];
const walk = (coll) => {
const n = coll.length;
for (let i = 0; i < n; 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) {
out.push({ id: p.id, image: p.image, scale: p.scale, show: p.show });
}
}
};
walk(v.scene.primitives);
return out;
};
// Every glTF model primitive (modelMatrix + ready flag), with id + scale.
window.__collectModels = function () {
const v = window.__godsEyeView.viewer;
const out = [];
const walk = (coll) => {
const n = coll.length;
for (let i = 0; i < n; 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.modelMatrix && typeof p.ready !== 'undefined' && p.image === undefined) {
let radius = null;
try { radius = p.ready && p.boundingSphere ? p.boundingSphere.radius : null; } catch { /* not ready */ }
out.push({ id: p.id, scale: p.scale, ready: p.ready, show: p.show, radius });
}
}
};
walk(v.scene.primitives);
return out;
};
// Displayed (render-delayed) latitude of a synthetic plane right now.
window.__displayedState = function (layer, idx, renderDelaySec) {
const S = window.__SPR;
const p = S[layer][idx];
const tRel = (Date.now() - S.epochMs) / 1000 - renderDelaySec;
return S.stateAt(p, tRel);
};
// Canvas coordinates of every synthetic plane at its displayed position —
// ground truth for identifying which glyph is which in a screenshot.
window.__planeCanvasCoords = function (layer, renderDelaySec, altOf) {
const v = window.__godsEyeView.viewer;
const S = window.__SPR;
const C3 = v.camera.position.constructor;
return S[layer].map((p, i) => {
const s = window.__displayedState(layer, i, renderDelaySec);
const pos = C3.fromDegrees(s.lon, s.lat, altOf === 'ft' ? p.altFt * 0.3048 : p.altM);
const c = v.scene.cartesianToCanvasCoordinates(pos);
return `${p.icao || p.hex}=(${c ? Math.round(c.x) : '-'},${c ? Math.round(c.y) : '-'})`;
}).join(' ');
};
});
// ---- Prime history through the render delay (same pattern as b3) -------
console.log('Priming straight-flight history through the render delay (30 s / 15 s)...');
const primed = await page.evaluate(async () => {
const dm = window.__godsEyeView.dataManager;
const v = window.__godsEyeView.viewer;
window.__SPR.timeOffsetSec = -32;
await dm.setEnabled('flights', true);
await dm.setEnabled('military', true);
const fl = dm.layers.get('flights').module;
const mil = dm.layers.get('military').module;
for (const off of [-24, -16, -8, 0]) {
window.__SPR.timeOffsetSec = off;
await fl.update(v);
await mil.update(v);
}
window.__SPR.timeOffsetSec = 0;
window.__SPR_DRIVER = setInterval(() => { fl.update(v); mil.update(v); }, 15000);
return { fl: fl.getStats().count, mil: mil.getStats().count, hits: window.__SPR_HITS };
});
console.log(` flights count=${primed.fl} military count=${primed.mil} | shim hits opensky=${primed.hits.opensky} mil=${primed.hits.mil}`);
record('B1 ingest: synthetic planes in both layers',
primed.fl === SPRITES.flights.length && primed.mil === SPRITES.military.length,
`flights=${primed.fl}/${SPRITES.flights.length} military=${primed.mil}/${SPRITES.military.length}`);
if (!(primed.fl > 0 && primed.mil > 0)) { finish(); return; }
// ========================================================================
// B2 — billboard glyph + scale per class (machine check)
// ========================================================================
console.log('\nB2 — per-class billboard glyph + scale');
const bbs = await page.evaluate(() => window.__collectBillboards());
const bbById = new Map(bbs.map((b) => [b.id, b]));
let glyphBad = [];
let scaleBad = [];
for (const [id, klass] of expectedFlights) {
const bb = bbById.get(id);
const wantImg = aircraftIcon(klass);
const wantScale = CLASS_SCALE_2D[klass] || 1;
if (!bb || bb.image !== wantImg) glyphBad.push(`${id}(${klass})${bb ? ':wrong-image' : ':missing'}`);
if (bb && Math.abs(bb.scale - wantScale) > 1e-9) scaleBad.push(`${id}: ${bb.scale} != ${wantScale}`);
}
for (const [id, klass] of expectedMilitary) {
const bb = bbById.get(id);
const wantImg = aircraftIcon(klass);
const wantScale = MIL_BILLBOARD_SCALE * (CLASS_SCALE_2D[klass] || 1);
if (!bb || bb.image !== wantImg) glyphBad.push(`${id}(${klass})${bb ? ':wrong-image' : ':missing'}`);
if (bb && Math.abs(bb.scale - wantScale) > 1e-9) scaleBad.push(`${id}: ${bb.scale} != ${wantScale}`);
}
record('B2a: every synthetic billboard has its class glyph (data-URI match)',
glyphBad.length === 0, glyphBad.length ? glyphBad.join(' ') : `${expectedFlights.size + expectedMilitary.size} billboards matched`);
record('B2b: every synthetic billboard has its class scale',
scaleBad.length === 0, scaleBad.length ? scaleBad.join(' ') : 'all scales exact');
const distinctGlyphs = new Set([...expectedFlights.values(), ...expectedMilitary.values()].map((k) => aircraftIcon(k)));
record('B2c: all 8 classes exercised with 8 distinct glyphs',
distinctGlyphs.size === 8, `${distinctGlyphs.size} distinct data URIs`);
// ========================================================================
// 2D screenshots
// ========================================================================
console.log('\n2D screenshots → qa-shots/b5/');
const topDown = async (lat, lon, height) => {
await page.evaluate(({ lat, lon, height }) => {
const v = window.__godsEyeView.viewer;
v.camera.cancelFlight(); // the boot fly-to-Austin otherwise stomps setView
const C3 = v.camera.position.constructor;
v.camera.setView({
destination: C3.fromDegrees(lon, lat, height),
orientation: { heading: 0, pitch: -Math.PI / 2, roll: 0 },
});
}, { lat, lon, height });
await sleep(1800); // let fleet tick land positions + rotations at the new pose
};
// Zoom the follow-camera to an absolute range from the tracked entity.
// In tracked mode camera.position lives in the ENTITY's reference frame, so
// its magnitude IS the range — fixed zoomIn() amounts compound across phases
// (the second tracked plane inherits the previous phase's close-in offset).
const zoomToRange = async (want) => {
await page.evaluate((want) => {
const v = window.__godsEyeView.viewer;
const p = v.camera.position;
const d = Math.hypot(p.x, p.y, p.z);
if (d > 1e6) return; // not in a tracked reference frame — leave it alone
v.camera.zoomIn(d - want);
}, want);
await sleep(1500);
};
// Rows drift north while flying; recenter on the DISPLAYED (render-delayed) row.
const rowLat = async (layer, idx, delay) =>
(await page.evaluate(({ layer, idx, delay }) => window.__displayedState(layer, idx, delay), { layer, idx, delay })).lat;
const flRowLat = await rowLat('flights', 2, 30);
const milRowLat = await rowLat('military', 4, 15);
await topDown((flRowLat + milRowLat) / 2, ROW_LON0 + 3 * ROW_STEP_DEG, 16000);
await page.screenshot({ path: path.join(SHOT_DIR, 'b5-2d-spread-bothrows.png') });
console.log(' saved b5-2d-spread-bothrows.png (flights row top, military row bottom)');
// Row widths: military spans 7 steps (~8.1 km) → frame ~13.9 km at 12 km up;
// flights spans 5 steps (~5.8 km, +caa004's eastward drift) → ~10.4 km at 9 km up.
await topDown(await rowLat('military', 4, 15), ROW_LON0 + 3.5 * ROW_STEP_DEG, 17000);
await page.screenshot({ path: path.join(SHOT_DIR, 'b5-2d-military-row.png') });
console.log(' saved b5-2d-military-row.png (8 classes, amber)');
console.log(` glyph positions: ${await page.evaluate(() => window.__planeCanvasCoords('military', 15, 'ft'))}`);
await topDown(await rowLat('flights', 2, 30), ROW_LON0 + 2.5 * ROW_STEP_DEG, 12000);
await page.screenshot({ path: path.join(SHOT_DIR, 'b5-2d-flights-row.png') });
console.log(' saved b5-2d-flights-row.png (6 category classes, white)');
console.log(` glyph positions: ${await page.evaluate(() => window.__planeCanvasCoords('flights', 30, 'm'))}`);
// Tracked tint: commercial tracked plane goes cyan; neighbors stay white.
await page.evaluate((icao) => {
window.__godsEyeView.dataManager.layers.get('flights').module.trackById(icao);
}, 'caa002');
await sleep(3000);
await page.screenshot({ path: path.join(SHOT_DIR, 'b5-2d-tracked-cyan.png') });
console.log(' saved b5-2d-tracked-cyan.png (tracked airliner cyan, follow camera)');
// ========================================================================
// B3 + 3D screenshots — flights cluster
// ========================================================================
console.log('\n3D — flights cluster (airplane.glb, per-class scale)');
await page.evaluate(() => {
const dm = window.__godsEyeView.dataManager;
dm.layers.get('flights').module.setParams({ models3d: true });
dm.layers.get('flights').module.trackById('cbb002');
});
const flModelsUp = await page.waitForFunction(() => {
const models = window.__collectModels().filter((m) => m.ready && m.show);
const ids = new Set(models.map((m) => m.id));
return ids.has('cbb001') && ids.has('cbb002') && ids.has('cbb003');
}, { timeout: 40000, polling: 400 }).then(() => true).catch(() => false);
record('3D flights: tracked model + 2 fleet cluster models rendered', flModelsUp, flModelsUp ? 'all up' : 'timed out');
if (flModelsUp) {
const flModels = await page.evaluate(() => window.__collectModels().filter((m) => m.ready));
const modelScaleBad = [];
for (const [id, want] of [
['cbb001', flightsWantScale(expectedFlights.get('cbb001'))],
['cbb003', flightsWantScale(expectedFlights.get('cbb003'))],
]) {
const m = flModels.find((x) => x.id === id);
if (!m || Math.abs(m.scale - want) > 1e-9) modelScaleBad.push(`${id}: ${m ? m.scale : 'missing'} != ${want}`);
}
const trackedM = flModels.find((x) => x.id === 'cbb002');
const trackedWant = flightsWantScale(expectedFlights.get('cbb002'));
if (!trackedM || Math.abs(trackedM.scale - trackedWant) > 1e-9) {
modelScaleBad.push(`tracked: ${trackedM ? trackedM.scale : 'missing'} != ${trackedWant}`);
}
record('B3a flights: model scales match the per-class registry (all GLBs meter-scale; shared @1×class)',
modelScaleBad.length === 0,
modelScaleBad.length ? modelScaleBad.join(' ') : 'all exact per registry');
console.log(` world bounding radii (m): ${flModels.filter((m) => m.scale !== undefined).map((m) => `${m.id ?? 'tracked'}=${m.radius?.toFixed(1)}`).join(' ')}`);
await sleep(1500);
await zoomToRange(900);
await page.screenshot({ path: path.join(SHOT_DIR, 'b5-3d-flights-mid.png') });
console.log(' saved b5-3d-flights-mid.png (900 m range — near the min-pixel clamp)');
await zoomToRange(450);
await page.screenshot({ path: path.join(SHOT_DIR, 'b5-3d-flights-close.png') });
console.log(' saved b5-3d-flights-close.png (450 m range: widebody left, airliner center, fastjet right)');
}
// ========================================================================
// B3 + 3D screenshots — military cluster
// ========================================================================
console.log('\n3D — military cluster (jet.glb, per-class scale)');
await page.evaluate(() => {
const dm = window.__godsEyeView.dataManager;
dm.layers.get('military').module.setParams({ models3d: true });
dm.layers.get('military').module.trackById('ddb002');
});
const milModelsUp = await page.waitForFunction(() => {
const models = window.__collectModels().filter((m) => m.ready && m.show);
const ids = new Set(models.map((m) => m.id));
return ids.has('ddb001') && ids.has('ddb002') && ids.has('ddb003') && ids.has('ddb004');
}, { timeout: 40000, polling: 400 }).then(() => true).catch(() => false);
record('3D military: tracked model + 3 fleet cluster models rendered', milModelsUp, milModelsUp ? 'all up' : 'timed out');
if (milModelsUp) {
const milModels = await page.evaluate(() => window.__collectModels().filter((m) => m.ready));
const modelScaleBad = [];
for (const [id, want] of [
['ddb001', militaryWantScale(expectedMilitary.get('ddb001'))],
['ddb003', militaryWantScale(expectedMilitary.get('ddb003'))],
['ddb004', militaryWantScale(expectedMilitary.get('ddb004'))],
]) {
const m = milModels.find((x) => x.id === id);
if (!m || Math.abs(m.scale - want) > 1e-9) modelScaleBad.push(`${id}: ${m ? m.scale : 'missing'} != ${want}`);
}
const trackedM = milModels.find((x) => x.id === 'ddb002');
const trackedWant = militaryWantScale(expectedMilitary.get('ddb002'));
if (!trackedM || Math.abs(trackedM.scale - trackedWant) > 1e-9) {
modelScaleBad.push(`tracked: ${trackedM ? trackedM.scale : 'missing'} != ${trackedWant}`);
}
record('B3b military: model scales match the weight-class registry (real/heavy/jet assets @1×class)',
modelScaleBad.length === 0,
modelScaleBad.length ? modelScaleBad.join(' ') : 'all exact per registry');
console.log(` world bounding radii (m): ${milModels.filter((m) => m.scale !== undefined).map((m) => `${m.id ?? 'tracked'}=${m.radius?.toFixed(1)}`).join(' ')}`);
await sleep(1500);
// jet.glb at MODEL_SCALE 1 is real-world size — same shot ranges as the
// flights cluster (close zoom no longer puts the camera inside a model).
await zoomToRange(900);
await page.screenshot({ path: path.join(SHOT_DIR, 'b5-3d-military-far.png') });
console.log(' saved b5-3d-military-far.png (900 m range: C130/C17 left, F16 center, B744 right)');
await zoomToRange(450);
await page.screenshot({ path: path.join(SHOT_DIR, 'b5-3d-military-close.png') });
console.log(' saved b5-3d-military-close.png (450 m range: C17 left, F16 center, B744 right)');
}
record('B4: no console errors during QA run', consoleErrors.length === 0,
consoleErrors.length
? `${consoleErrors.length}: ${consoleErrors.slice(0, 3).join(' | ')}; HTTP: ${httpErrors.slice(0, 8).join(' | ') || 'none observed'}`
: 'clean');
finish();
} finally {
await browser.close();
}
function finish() {
const failed = results.filter((r) => r.ok === false);
const passed = results.filter((r) => r.ok === true);
console.log(`\n${'─'.repeat(60)}`);
console.log(` RESULT: ${passed.length} passed, ${failed.length} failed`);
console.log(`${'─'.repeat(60)}\n`);
process.exitCode = failed.length > 0 ? 1 : 0;
}
}
main().catch((err) => {
console.error('\n\x1b[31mQA harness error:\x1b[0m', err && err.stack ? err.stack : err);
process.exit(2);
});