// scripts/qa-floor-verify.mjs — live floor verification at AUS (round 5). // Pins the camera at Austin airport, enables flights, waits ~3 polls, then // measures every nearby contact's render height against the ACTUAL rendered // mesh (sprite- and model-excluded scene.sampleHeight probes). Caught the mesh-latch // coarse-LOD poison and the taxiing cold-cell regression on 2026-07-06. // Run: node scripts/qa-floor-verify.mjs (dev server on :4173, real GPU best) // with the poison fix + simplified chain live. import puppeteer from 'puppeteer'; import fs from 'node:fs'; // QA_BASE_URL matches the sibling harnesses (qa-height-datum / qa-cctv-v2) so a // secondary checkout can verify against its own dev server instead of the default :4173. const APP_URL = process.env.QA_BASE_URL || 'http://localhost:4173'; // CLI: --lat --lon --floor-min --floor-max (defaults: Austin airport) const argv = Object.fromEntries(process.argv.slice(2).map((a) => a.split('=')).filter((x) => x.length === 2).map(([k, v]) => [k.replace(/^--/, ''), Number(v)])); const SITE = { lat: Number.isFinite(argv.lat) ? argv.lat : 30.197, lon: Number.isFinite(argv.lon) ? argv.lon : -97.666, floorMin: Number.isFinite(argv['floor-min']) ? argv['floor-min'] : 100, floorMax: Number.isFinite(argv['floor-max']) ? argv['floor-max'] : 250, }; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); // Prefer puppeteer's version-pinned Chrome-for-Testing over the system Chrome // (2026-07-30 lesson, already applied to the other harnesses): /Applications // auto-updates underneath the harnesses and its software-GL behavior shifts // across majors. const chrome = [ process.env.PUPPETEER_EXECUTABLE_PATH, (() => { try { return puppeteer.executablePath(); } catch { return null; } })(), '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', ].filter(Boolean).find((p) => fs.existsSync(p)); const browser = await puppeteer.launch({ headless: 'new', executablePath: chrome, args: ['--use-gl=angle', '--use-angle=swiftshader', '--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.goto(APP_URL, { waitUntil: 'domcontentloaded', timeout: 120000 }); await page.waitForFunction(() => window.__godsEyeView?.dataManager && window.__godsEyeView?.viewer, { timeout: 150000 }); await sleep(2000); // Let the app's boot fly-to fully settle FIRST (it was overriding an early // setView, leaving the camera away from Austin — every probe then read // coarse global tiles at ~25 m and the oracle was garbage). await sleep(12000); await page.evaluate((site) => { window.__QA_SITE = site; }, SITE); const camPin = async () => page.evaluate(() => { const v = window.__godsEyeView.viewer; const C = v.camera.positionCartographic.constructor; v.camera.cancelFlight(); v.camera.setView({ destination: v.scene.globe.ellipsoid.cartographicToCartesian(C.fromDegrees(window.__QA_SITE.lon, window.__QA_SITE.lat, 3000)) }); v.camera.moveEnd.raiseEvent(); const c = v.camera.positionCartographic; return { lat: +(c.latitude * 180 / Math.PI).toFixed(3), lon: +(c.longitude * 180 / Math.PI).toFixed(3), h: Math.round(c.height) }; }); console.log('camera pinned:', JSON.stringify(await camPin())); await page.evaluate(async () => { await window.__godsEyeView.dataManager.toggle('flights'); }); console.log('waiting 100s: tiles stream + 3 polls...'); await sleep(100000); // re-pin + report readiness right before probing console.log('camera at probe time:', JSON.stringify(await camPin())); const ready = await page.evaluate(() => { const v = window.__godsEyeView.viewer; const prims = v.scene.primitives; for (let i = 0; i < prims.length; i++) { const p = prims.get(i); if (p && p.tilesLoaded !== undefined && p.show) return { tilesLoaded: !!p.tilesLoaded }; } return { tilesLoaded: null }; }); console.log('tileset ready:', JSON.stringify(ready)); await sleep(8000); const report = await page.evaluate(() => { const gev = window.__godsEyeView; const v = gev.viewer; const layer = gev.dataManager.layers.get('flights')?.module; if (!layer) return { err: 'no flights module' }; const ell = v.scene.globe.ellipsoid; const C = v.camera.positionCartographic.constructor; const center = ell.cartographicToCartesian(C.fromDegrees(window.__QA_SITE.lon, window.__QA_SITE.lat, 200)); const nearby = layer.getNearby(center, 15000, 60); // Exclude EVERY billboard from the probes — sprites are pickable, so an // unexcluded probe can return another aircraft's height as "the mesh". // Exclude every fleet/tracked 3D Model too: getNearby() also returns contacts // whose billboard is hidden because a model owns the visual, and those probes // would otherwise land on aircraft geometry. `activeAnimations` + // `minimumPixelSize` are Model-only — the 3D-Tiles tileset has neither, and // excluding IT would leave sampleHeight with no mesh to read at all. const excludes = []; const walk = (coll) => { const n = coll.length; for (let i = 0; i < n; i++) { let pr; try { pr = coll.get(i); } catch { continue; } if (!pr) continue; if (typeof pr.length === 'number' && typeof pr.get === 'function') { walk(pr); continue; } if (pr.image !== undefined && pr.alignedAxis !== undefined) { excludes.push(pr); continue; } if (pr.activeAnimations !== undefined && pr.minimumPixelSize !== undefined) excludes.push(pr); } }; walk(v.scene.primitives); const out = []; for (const p of nearby) { const c = ell.cartesianToCartographic(p.position); const latDeg = c.latitude * 180 / Math.PI, lonDeg = c.longitude * 180 / Math.PI; let meshH = null; try { const h = v.scene.sampleHeight(C.fromDegrees(lonDeg, latDeg), excludes); if (Number.isFinite(h)) meshH = h; } catch { /* ignore */ } out.push({ id: p.id, renderAltM: +c.height.toFixed(1), meshM: meshH != null ? +meshH.toFixed(1) : null, aboveMeshM: meshH != null ? +(c.height - meshH).toFixed(1) : null, }); } // Visibility census (round 6): getNearby only returns contacts a sprite or a // 3D model is actually drawing, so also walk the raw collections — a culled // "invisible plane" shows up here. let shown = 0, hidden = 0; const censusWalk = (coll) => { const n = coll.length; for (let i = 0; i < n; i++) { let pr; try { pr = coll.get(i); } catch { continue; } if (!pr) continue; if (typeof pr.length === 'number' && typeof pr.get === 'function') { censusWalk(pr); continue; } if (pr.image === undefined || pr.alignedAxis === undefined) continue; const cc = ell.cartesianToCartographic(pr.position); const la = cc.latitude * 180 / Math.PI, lo = cc.longitude * 180 / Math.PI; if (Math.abs(la - window.__QA_SITE.lat) > 0.08 || Math.abs(lo - window.__QA_SITE.lon) > 0.10) continue; if (pr.show) shown++; else hidden++; } }; censusWalk(v.scene.primitives); return { ausContacts: out.length, contacts: out.slice(0, 16), spritesShown: shown, spritesHidden: hidden }; }); console.log(JSON.stringify(report, null, 1)); // verdict — plausible mesh readings only (a wild meshM means the probe ray // missed the tileset entirely; those rows prove nothing either way) // Trust only probes that read a plausible AUS-area surface (~100..250 m // ellipsoidal) — anything else hit a coarse tile or nothing (proves nothing). const lows = (report.contacts || []).filter((c) => c.renderAltM < SITE.floorMax + 450 && c.aboveMeshM != null && c.meshM > SITE.floorMin && c.meshM < SITE.floorMax); const buried = lows.filter((c) => c.aboveMeshM < -2); console.log(`low contacts with plausible mesh readings: ${lows.length}; buried (< -2m): ${buried.length}`); for (const b of buried) { console.log(` BURIED ${b.id}: render ${b.renderAltM} m vs mesh ${b.meshM} m (${b.aboveMeshM} m)`); } const verdict = lows.length === 0 ? 'INCONCLUSIVE' : (buried.length === 0 ? 'PASS' : 'FAIL'); console.log(`VERDICT: ${verdict}`); await browser.close(); // Exit code (2026-08-19): this harness printed VERDICT: FAIL and still exited 0, // so a real regression (taxiing contacts rendering under the mesh) stayed // invisible to every automated caller. Now it reports like its siblings // (qa-height-datum / qa-cctv-v2): 0 = PASS, 1 = FAIL, 2 = could-not-measure. // INCONCLUSIVE is deliberately NOT 0 — a run that measured no usable contacts // proves nothing, and must not read as a pass. The VERDICT line is unchanged, // so anything scraping stdout (the L9 QA matrix) is unaffected. process.exit(verdict === 'PASS' ? 0 : (verdict === 'FAIL' ? 1 : 2));