Files
gods-eye-view/scripts/qa-cables-overlay.mjs
T
German Khokhlov 0ef29781e5 Fix the review findings across the engines, layers, data and QA
Navara now matches Cesium where the review found it diverging: a flight
holds the camera inputs, camera.changed counts turns as Cesium does,
render requests made during a frame survive it, follow pinches zoom,
ground widths follow the pixel ratio and Cyber sonar reaches glyph caches
and models. On Cesium, a staged imagery swap keeps loading tiles, setView
replaces a running flight, and a cleared weather detail goes away.

The contract gains MoverSet.failed, maps.terrainRevision,
render.maxTextureSize and surface.sampleTerrainMostDetailed on every
engine, so layers retry failed models, key road heights by terrain,
size weather textures to the GPU and place Bhote Koshi cards on detailed
terrain.

Engine switches keep the live view, follows, focus, layer options and
cards: a pick hands over the exact pose, applies no address options and
pans no carried-over card; a history visit still applies its entry.

Layers put GhostMaps pins, cable landings, OSM Places and directions on
the measured surface without per-frame sampling, draw the Cockpit contact
dot, hide disabled CCTV on attach, and keep ALPR running outside secure
contexts. KML imports draw raised polygon fills, rotated and coloured
ground overlays and north-referenced icon headings, and report files
with nothing to draw.

QA scripts read the contract instead of Cesium internals (test:track is
109/109 again), wait for the camera to rest before clicking, and give
cold switches and parked engines realistic time. Docs follow the code.
2026-11-09 21:34:42 +04:00

255 lines
12 KiB
JavaScript

#!/usr/bin/env node
/**
* qa-cables-overlay — submarine-cable label path measurement + contract gate.
*
* Two jobs:
* 1. MEASURE (both builds, `--legacy` on the pre-migration build): with the
* cables layer ON at a fixed mid-Atlantic camera, capture
* a. per-frame `scene.render` cost over a ~10 s driven-orbit window
* (mean / p50 / p95 / max — relative numbers, SwiftShader surface),
* b. toggle-ON → first-labels-visible latency,
* c. contract set (ground paths, mover tips, stems) / host-entry counts,
* d. parked-idle postRender fires over 5 s (render-governor honesty).
* 2. GATE (unified build only): assert the migration contract —
* no layer-owned Cesium data source (paths, pins and tips are globe
* contract sets), host entries
* present for the source, idle stays near-zero, and a clean OFF→ON cycle
* (no orphan host entries after disable).
*
* Usage: node scripts/qa-cables-overlay.mjs [--url http://localhost:4214] [--legacy]
* Requires a running dev server. Headless; rAF throttling disabled so the
* frame clock is honest (hidden-pane gotcha).
*/
import puppeteer from 'puppeteer';
const argv = process.argv;
const url = argv.includes('--url') ? argv[argv.indexOf('--url') + 1] : 'http://localhost:4214';
const legacy = argv.includes('--legacy');
// --control: never enable the layer; measure the empty-scene orbit cost so the
// cables layer's share of frame time is attributable.
const control = argv.includes('--control');
const LAYER_ID = 'telegeography-submarine-cables';
const results = [];
function check(name, pass, detail) {
results.push({ name, pass });
const tag = pass ? 'PASS' : 'FAIL';
console.log(` [${tag}] ${name}${detail !== undefined ? ` — ${JSON.stringify(detail)}` : ''}`);
}
function report(name, detail) {
console.log(` [MEAS] ${name} — ${JSON.stringify(detail)}`);
}
const browser = await puppeteer.launch({
headless: 'new',
protocolTimeout: 300_000,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--window-size=1440,900',
'--disable-backgrounding-occluded-windows',
'--disable-renderer-backgrounding',
'--disable-background-timer-throttling',
],
});
try {
const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 860 });
await page.goto(url, { waitUntil: 'domcontentloaded' });
await page.waitForFunction(() => !!window.__godsEyeView?.viewer, { timeout: 90_000 });
await new Promise((r) => setTimeout(r, 12_000)); // boot flyTo + deferred init
// Park mid-Atlantic (many cables + both coasts' landings in range) and
// disable every layer so the cables layer is measured in isolation.
await page.evaluate(async () => {
const gev = window.__godsEyeView;
const v = gev.viewer;
v.camera.cancelFlight();
const ell = v.scene.globe.ellipsoid;
v.camera.setView({
destination: ell.cartographicToCartesian({
longitude: -40 * Math.PI / 180, latitude: 35 * Math.PI / 180, height: 4_500_000,
}),
orientation: { heading: 0, pitch: -Math.PI / 2, roll: 0 },
});
for (const [id, entry] of gev.dataManager.layers) {
if (entry.enabled) { try { await gev.dataManager.setEnabled(id, false, { origin: 'user' }); } catch { /* measured via counts */ } }
}
});
await new Promise((r) => setTimeout(r, 6_000)); // tiles + fades settle
// ── b. toggle-ON → labels visible ─────────────────────────────────────
if (!control) {
const labelLatency = await page.evaluate(async (layerId, isLegacy) => {
const gev = window.__godsEyeView;
const t0 = performance.now();
await gev.dataManager.setEnabled(layerId, true, { origin: 'user' });
const deadline = t0 + 60_000;
const labelsUp = () => {
if (isLegacy) {
const stats = gev.dataManager.layers.get(layerId)?.module?.getStats?.() || {};
return (stats.referenceLabelCount || 0) > 0;
}
const diag = window.__gevWorldOverlay?.getDiagnostics?.() || {};
return (diag.paintedBySource?.[layerId] || 0) > 0;
};
while (performance.now() < deadline) {
if (labelsUp()) return { ms: Math.round(performance.now() - t0), timedOut: false };
gev.viewer.scene.requestRender?.();
await new Promise((r) => setTimeout(r, 100));
}
return { ms: 60_000, timedOut: true };
}, LAYER_ID, legacy);
report('toggle-ON -> labels visible (ms)', labelLatency);
check('labels became visible after enable', labelLatency.timedOut === false, labelLatency);
await new Promise((r) => setTimeout(r, 3_000)); // let load/labels settle
}
// ── c. drawn-set / label / host counts ────────────────────────────────
// The layer draws through the globe contract (render/display.js): cable
// paths in one engine.ground corridor set, landing points and reference
// tips in mover sets, stems in engine.lines, labels on the world-overlay
// host. It owns no Cesium DataSource or Entity any more.
const counts = await page.evaluate((layerId, legacy) => {
const gev = window.__godsEyeView;
const v = gev.viewer;
const engine = gev.engine;
const out = {
layerDataSources: 0, layerEntities: 0,
pathSets: 0, pathItems: 0, markers: 0, tipSets: 0, tips: 0, stems: 0,
hostEntries: 0, hostPainted: 0,
};
for (let i = 0; i < (v?.dataSources?.length ?? 0); i++) {
const ds = v.dataSources.get(i);
if (!/TeleGeography/.test(ds.name || '')) continue;
out.layerDataSources++;
out.layerEntities += ds.entities.values.length;
}
if (legacy) return out;
for (const set of engine.ground.list()) {
if (set.id !== `${layerId}-paths`) continue;
out.pathSets++;
out.pathItems += set.count;
}
for (const set of engine.movers.list()) {
if (set.id === `${layerId}-markers`) out.markers += set.count;
else if (set.id.startsWith(`${layerId}-`) && set.id.endsWith('-tips')) {
out.tipSets++;
out.tips += set.count;
}
}
out.stems = (engine.lines.list().find((line) =>
line.id === `${layerId}-stems` && line.shown)?.points ?? 0) / 2;
const diag = window.__gevWorldOverlay?.getDiagnostics?.() || {};
out.hostEntries = diag.entriesBySource?.[layerId] || 0;
out.hostPainted = diag.paintedBySource?.[layerId] || 0;
const stats = gev.dataManager.layers.get(layerId)?.module?.getStats?.() || {};
out.referenceLabelCount = stats.referenceLabelCount ?? null;
return out;
}, LAYER_ID, legacy);
report(control ? 'counts (control, cables OFF)' : 'counts with cables ON', counts);
// ── a. per-frame scene.render cost over a ~10 s driven orbit ─────────
const frameCost = await page.evaluate(() => new Promise((resolve) => {
const v = window.__godsEyeView.viewer;
const scene = v.scene;
const durations = [];
const originalRender = scene.render;
scene.render = function patchedRender(...args) {
const started = performance.now();
const result = originalRender.apply(this, args);
durations.push(performance.now() - started);
return result;
};
const t0 = performance.now();
const tick = () => {
// Slow orbit: every frame is a camera change, so Cesium renders every
// rAF — the honest "user is interacting" cost of the layer.
v.camera.rotateRight(0.0004);
if (performance.now() - t0 < 10_000) requestAnimationFrame(tick);
else {
scene.render = originalRender;
durations.sort((a, b) => a - b);
const n = durations.length;
const sum = durations.reduce((s, d) => s + d, 0);
resolve({
frames: n,
meanMs: +(sum / Math.max(1, n)).toFixed(2),
p50Ms: +(durations[Math.floor(n * 0.5)] || 0).toFixed(2),
p95Ms: +(durations[Math.floor(n * 0.95)] || 0).toFixed(2),
maxMs: +(durations[n - 1] || 0).toFixed(2),
effectiveFps: +(n / 10).toFixed(1),
});
}
};
requestAnimationFrame(tick);
}));
report(control
? 'scene.render cost, control (cables OFF), 10s orbit'
: 'scene.render cost, cables ON, 10s orbit', frameCost);
// ── d. parked idle honesty (labels must not force continuous render) ──
await new Promise((r) => setTimeout(r, 4_000)); // orbit stop + fades settle
const idle = await page.evaluate(() => new Promise((resolve) => {
const scene = window.__godsEyeView.viewer.scene;
let renders = 0;
const remove = scene.postRender.addEventListener(() => { renders += 1; });
setTimeout(() => { remove(); resolve({ renders }); }, 5_000);
}));
report(control
? 'parked idle, control (postRender fires / 5s)'
: 'parked idle with cables ON (postRender fires / 5s)', idle);
if (!legacy && !control) {
// ── unified-build contract gates ────────────────────────────────────
// Paths, pins and tips belong to contract sets; labels belong to the host.
check('no layer-owned Cesium data source or entities', counts.layerDataSources === 0 && counts.layerEntities === 0, counts);
check('cable paths drawn as an engine.ground corridor set', counts.pathSets === 1 && counts.pathItems > 0, counts);
check('reference tips drawn as mover sets', counts.tipSets > 0 && counts.tips > 0, counts);
check('host has cable entries', counts.hostEntries > 0, counts.hostEntries);
check('host painted cable labels', counts.hostPainted > 0, counts.hostPainted);
// The cables source never republishes an unchanged cohort (unit pin), and
// since the 2026-08-18 host fix the occluder observer is scoped (body
// childList filtered to inventory chrome; attribute observation
// element-only) and the right-rail allocator writes-if-changed — so a
// parked camera measures 0 postRender fires / 5 s with cables ON, equal
// to the empty-scene control (pre-fix: ~56-61). The headroom below covers
// an occasional genuine chrome transition (loading-chip flip + its 100 ms
// occluder-refresh echo) landing inside the window; ticking churn (~10)
// or the old observer leak (~56+) must fail.
check('parked idle stays near zero (≤6 / 5s)', idle.renders <= 6, idle);
// OFF must clear the host source (no orphan labels), ON must restore.
const cycle = await page.evaluate(async (layerId) => {
const gev = window.__godsEyeView;
await gev.dataManager.setEnabled(layerId, false, { origin: 'user' });
gev.viewer.scene.requestRender?.();
await new Promise((r) => setTimeout(r, 1_200));
const offDiag = window.__gevWorldOverlay?.getDiagnostics?.() || {};
const offEntries = offDiag.entriesBySource?.[layerId] || 0;
const offPainted = offDiag.paintedBySource?.[layerId] || 0;
await gev.dataManager.setEnabled(layerId, true, { origin: 'user' });
const t0 = performance.now();
let onPainted = 0;
while (performance.now() - t0 < 20_000) {
const diag = window.__gevWorldOverlay?.getDiagnostics?.() || {};
onPainted = diag.paintedBySource?.[layerId] || 0;
if (onPainted > 0) break;
gev.viewer.scene.requestRender?.();
await new Promise((r) => setTimeout(r, 100));
}
return { offEntries, offPainted, onPainted };
}, LAYER_ID);
check('disable clears host entries (no orphans)', cycle.offEntries === 0 && cycle.offPainted === 0, cycle);
check('re-enable restores painted labels', cycle.onPainted > 0, cycle);
}
} finally {
await browser.close();
}
const passed = results.filter((r) => r.pass).length;
console.log(`\nqa-cables-overlay: ${passed}/${results.length} passed${legacy ? ' (legacy measurement mode)' : ''}${control ? ' (control mode)' : ''}`);
console.log(`RESULT: ${passed} passed, ${results.length - passed} failed, 0 skipped`);
process.exit(passed === results.length ? 0 : 1);