mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 15:52:21 +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.
421 lines
14 KiB
JavaScript
421 lines
14 KiB
JavaScript
#!/usr/bin/env node
|
|
/** Mapped installations: isolated camera navigation, cached revisits and Contacts release. */
|
|
import fs from 'node:fs';
|
|
import puppeteer from 'puppeteer';
|
|
import { moveCamera } from './qa-journey-recorder.mjs';
|
|
|
|
const args = process.argv.slice(2);
|
|
if (args.includes('--help')) {
|
|
console.log(
|
|
'Usage: node scripts/qa-installation-navigation.mjs <url> [--baseline]',
|
|
);
|
|
process.exit(0);
|
|
}
|
|
const baseline = args.includes('--baseline');
|
|
const result = { views: [], errors: [], forbidden: [], failures: [] };
|
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
const browser = await puppeteer.launch({
|
|
headless: true,
|
|
args: ['--use-angle=metal', '--disable-background-timer-throttling'],
|
|
protocolTimeout: 120000,
|
|
});
|
|
try {
|
|
const page = await browser.newPage();
|
|
await page.setViewport({ width: 1280, height: 800 });
|
|
const tiles = [];
|
|
page.on('request', (r) => {
|
|
const u = new URL(r.url());
|
|
if (
|
|
u.hostname === 'tiles.openfreemap.org' &&
|
|
/\/\d+\/\d+\/\d+(\.pbf)?$/.test(u.pathname)
|
|
)
|
|
tiles.push(u.pathname);
|
|
if (
|
|
/overpass/i.test(u.hostname) ||
|
|
u.hostname === 'nominatim.openstreetmap.org'
|
|
)
|
|
result.forbidden.push(u.hostname);
|
|
});
|
|
page.on('pageerror', (e) => result.errors.push(e.message));
|
|
page.on('console', (m) => {
|
|
if (m.type() === 'error') result.errors.push(m.text());
|
|
});
|
|
await page.evaluateOnNewDocument(() => {
|
|
sessionStorage.setItem('gev:first-run-mission-session:v1', 'dismissed');
|
|
// The sites drawn as discs, as their context handles: the layer draws
|
|
// through the globe contract and owns no Cesium entities.
|
|
window.__qaInstallationSites = () =>
|
|
[...(window.__gevContextStore?.entities.values() ?? [])]
|
|
.filter(
|
|
(r) =>
|
|
r.layerId === 'military-installations' &&
|
|
r.entity &&
|
|
!r.entity.record?.namedArea,
|
|
)
|
|
.map((r) => r.entity);
|
|
});
|
|
await page.goto(
|
|
args.find((a) => /^https?:/.test(a)),
|
|
{ waitUntil: 'domcontentloaded' },
|
|
);
|
|
await page.waitForFunction(
|
|
() =>
|
|
window.__godsEyeView?.dataManager &&
|
|
document.getElementById('loading-screen')?.classList.contains('hidden'),
|
|
{ timeout: 120000 },
|
|
);
|
|
await page.evaluate(
|
|
() => window.__godsEyeView.styleManager.initialRestorePromise,
|
|
);
|
|
await page.keyboard.press('Escape');
|
|
await page.evaluate(async () => {
|
|
const g = window.__godsEyeView;
|
|
for (const [id] of g.dataManager.layers)
|
|
await g.dataManager.setEnabled(id, false);
|
|
g.viewer.scene.screenSpaceCameraController.enableCollisionDetection = false;
|
|
window.installationEvents = { changed: 0, end: 0 };
|
|
window.installationEntityRefs = new Map();
|
|
g.viewer.camera.changed.addEventListener(
|
|
() => window.installationEvents.changed++,
|
|
);
|
|
g.viewer.camera.moveEnd.addEventListener(
|
|
() => window.installationEvents.end++,
|
|
);
|
|
});
|
|
let low = 60000,
|
|
high = 400000;
|
|
for (let i = 0; i < 9; i++) {
|
|
const height = (low + high) / 2;
|
|
await moveCamera(page, { lat: 32.72, lon: -117.16, height, pitch: -90 }, 0);
|
|
const fits = await page.evaluate(async () => {
|
|
const { installationTileZoom } =
|
|
await import('/src/layers/installations/source.js');
|
|
const r = window.__godsEyeView.viewer.camera.computeViewRectangle();
|
|
const d = 180 / Math.PI;
|
|
return (
|
|
r &&
|
|
installationTileZoom({
|
|
west: r.west * d,
|
|
east: r.east * d,
|
|
south: r.south * d,
|
|
north: r.north * d,
|
|
}) !== null
|
|
);
|
|
});
|
|
if (fits) low = height;
|
|
else high = height;
|
|
}
|
|
const widestHeight = Math.floor(low);
|
|
result.widestHeight = widestHeight;
|
|
result.firstTooWideHeight = Math.ceil(high);
|
|
let enabled = false;
|
|
const places = [
|
|
['san-diego', 32.72, -117.16],
|
|
['pendleton', 33.33, -117.4],
|
|
['miramar', 32.87, -117.14],
|
|
['san-diego-return', 32.72, -117.16],
|
|
];
|
|
for (const height of [
|
|
5000,
|
|
20000,
|
|
60000,
|
|
150000,
|
|
widestHeight,
|
|
400000,
|
|
5000,
|
|
]) {
|
|
for (const [name, lat, lon] of height >= 150000
|
|
? places.slice(0, 1)
|
|
: places) {
|
|
const before = tiles.length;
|
|
const previous = await page.evaluate(
|
|
() =>
|
|
window.__godsEyeView.dataManager.layers
|
|
.get('military-installations')
|
|
.module.getStats().lastUpdate,
|
|
);
|
|
await moveCamera(
|
|
page,
|
|
{ lat, lon, height, pitch: -90 },
|
|
enabled ? 0.6 : 0,
|
|
);
|
|
const stop = Date.now();
|
|
if (!enabled) {
|
|
await page.evaluate(() =>
|
|
window.__godsEyeView.dataManager.setEnabled(
|
|
'military-installations',
|
|
true,
|
|
),
|
|
);
|
|
enabled = true;
|
|
}
|
|
await page
|
|
.waitForFunction(
|
|
(prev, wide) => {
|
|
const s = window.__godsEyeView.dataManager.layers
|
|
.get('military-installations')
|
|
.module.getStats();
|
|
return (
|
|
!s.loading &&
|
|
(s.lastUpdate > prev || (wide && s.status === 'zoom-in'))
|
|
);
|
|
},
|
|
{ timeout: 6000 },
|
|
previous,
|
|
height === 400000,
|
|
)
|
|
.catch(() => {});
|
|
const snapshot = await page.evaluate(() => {
|
|
const g = window.__godsEyeView;
|
|
// The drawn sites' context handles (render/display.js keeps one per
|
|
// drawn record and prunes the rest); a named area draws no disc.
|
|
const points = window.__qaInstallationSites();
|
|
const stable = points.every(
|
|
(e) =>
|
|
!window.installationEntityRefs.has(e.id) ||
|
|
window.installationEntityRefs.get(e.id) === e,
|
|
);
|
|
window.installationEntityRefs = new Map(points.map((e) => [e.id, e]));
|
|
return {
|
|
...g.dataManager.layers
|
|
.get('military-installations')
|
|
.module.getStats(),
|
|
stable,
|
|
ids: points.map((e) => e.id),
|
|
events: { ...window.installationEvents },
|
|
};
|
|
});
|
|
const row = {
|
|
name,
|
|
height,
|
|
ms: Date.now() - stop,
|
|
tiles: tiles.length - before,
|
|
updated: snapshot.lastUpdate > previous,
|
|
...snapshot,
|
|
};
|
|
if (
|
|
(!row.stable && result.views.at(-1)?.height === height) ||
|
|
new Set(row.ids).size !== row.ids.length
|
|
)
|
|
result.failures.push(
|
|
`${name}/${height}: unstable or duplicate site entities`,
|
|
);
|
|
if (name.endsWith('return')) {
|
|
const first = result.views.find(
|
|
(v) => v.name === 'san-diego' && v.height === height,
|
|
);
|
|
if (
|
|
first &&
|
|
JSON.stringify([...first.ids].sort()) !==
|
|
JSON.stringify([...row.ids].sort())
|
|
)
|
|
result.failures.push(
|
|
`${name}/${height}: changed site ids on revisit`,
|
|
);
|
|
}
|
|
result.views.push(row);
|
|
console.log(JSON.stringify(row));
|
|
if (!row.updated && row.status !== 'zoom-in')
|
|
result.failures.push(`${name}/${height}: no reload`);
|
|
if (row.ms > 1500 && row.status !== 'zoom-in')
|
|
result.failures.push(`${name}/${height}: ${row.ms} ms`);
|
|
if (name.endsWith('return') && row.tiles)
|
|
result.failures.push(`${name}/${height}: revisit fetched ${row.tiles}`);
|
|
}
|
|
}
|
|
for (const gesture of [
|
|
'wheel',
|
|
'keyboard',
|
|
'contacts-close',
|
|
'contacts-clear',
|
|
]) {
|
|
let previous = await page.evaluate(
|
|
() =>
|
|
window.__godsEyeView.dataManager.layers
|
|
.get('military-installations')
|
|
.module.getStats().lastUpdate,
|
|
);
|
|
if (gesture === 'wheel') {
|
|
await page.mouse.move(640, 400);
|
|
await page.mouse.wheel({ deltaY: -350 });
|
|
} else if (gesture === 'keyboard') {
|
|
await page.evaluate(() =>
|
|
document.getElementById('search-toggle').click(),
|
|
);
|
|
await page.focus('#location-search');
|
|
await page.evaluate(() => {
|
|
document.getElementById('location-search').value = '';
|
|
});
|
|
await page.type('#location-search', '33.33, -117.40');
|
|
await page.keyboard.press('Enter');
|
|
await sleep(4500);
|
|
} else {
|
|
await moveCamera(
|
|
page,
|
|
{ lat: 32.87, lon: -117.14, height: 5000, pitch: -90 },
|
|
0.6,
|
|
);
|
|
await page.waitForFunction(
|
|
() => window.__qaInstallationSites().length > 0,
|
|
{ timeout: 5000 },
|
|
);
|
|
await page.evaluate(() => {
|
|
const button = document.getElementById('global-context-flights-btn');
|
|
if (button.getAttribute('aria-selected') !== 'true') button.click();
|
|
});
|
|
await page.waitForFunction(
|
|
() => {
|
|
const g = window.__godsEyeView;
|
|
return (
|
|
g.dataManager.isEffectivelyEnabled('military-awareness') &&
|
|
!g.dataManager.layers.get('military-awareness').module.getParams()
|
|
.passive
|
|
);
|
|
},
|
|
{ timeout: 15000 },
|
|
);
|
|
await sleep(1000);
|
|
await page.evaluate(async () => {
|
|
const g = window.__godsEyeView;
|
|
const id = window.__qaInstallationSites()[0].id;
|
|
window.installationSubjectId = id;
|
|
await g.dataManager.layers
|
|
.get('military-awareness')
|
|
.module.focusTarget('military-installations', id);
|
|
});
|
|
await page.waitForFunction(
|
|
() => {
|
|
const a =
|
|
window.__godsEyeView.dataManager.layers.get(
|
|
'military-awareness',
|
|
).module;
|
|
const subject = a.getContextSnapshot()?.subject;
|
|
return (
|
|
subject?.layerId === 'military-installations' &&
|
|
subject.id === window.installationSubjectId &&
|
|
!a._getAwarenessNavigationStateForTest().pendingSelectionKey
|
|
);
|
|
},
|
|
{ timeout: 15000 },
|
|
);
|
|
await page.waitForFunction(
|
|
() => !window.__godsEyeView.viewer.camera._currentFlight,
|
|
{ timeout: 10000 },
|
|
);
|
|
await page.waitForFunction(
|
|
() =>
|
|
window.__godsEyeView.dataManager.layers
|
|
.get('military-installations')
|
|
.module.getStats().coverage.kind === 'subject',
|
|
{ timeout: 10000 },
|
|
);
|
|
await page.waitForFunction(
|
|
() =>
|
|
document
|
|
.getElementById('global-context-flights-btn')
|
|
.getAttribute('aria-busy') === 'false',
|
|
{ timeout: 30000 },
|
|
);
|
|
const anchored = await page.evaluate(() =>
|
|
window.__godsEyeView.dataManager.layers
|
|
.get('military-installations')
|
|
.module.getStats(),
|
|
);
|
|
if (anchored.coverage.kind !== 'subject')
|
|
result.failures.push(`${gesture}: Contacts did not acquire subject`);
|
|
result.contextClear = await page.evaluate(async (clear) => {
|
|
if (clear) {
|
|
const { clearSelectedEntityContextForLayer, getContextStore } =
|
|
await import('/src/data/contextStore.js');
|
|
const store = getContextStore();
|
|
const before = {
|
|
selectedId: store.selectedEntityId,
|
|
record: store.entities.get(store.selectedEntityId)?.id,
|
|
subject: window.__godsEyeView.dataManager.layers
|
|
.get('military-awareness')
|
|
.module.getContextSnapshot()?.subject,
|
|
};
|
|
clearSelectedEntityContextForLayer('military-installations');
|
|
return before;
|
|
} else document.getElementById('global-context-flights-btn').click();
|
|
}, gesture === 'contacts-clear');
|
|
if (gesture === 'contacts-clear') {
|
|
await page.waitForFunction(
|
|
() =>
|
|
!window.__godsEyeView.dataManager.layers
|
|
.get('military-awareness')
|
|
.module.getContextSnapshot(),
|
|
{ timeout: 5000 },
|
|
);
|
|
} else {
|
|
await page.waitForFunction(
|
|
() => {
|
|
const button = document.getElementById(
|
|
'global-context-flights-btn',
|
|
);
|
|
return (
|
|
button.getAttribute('aria-selected') === 'false' &&
|
|
button.getAttribute('aria-busy') === 'false'
|
|
);
|
|
},
|
|
{ timeout: 30000 },
|
|
);
|
|
}
|
|
await page.waitForFunction(
|
|
() => !window.__godsEyeView.viewer.camera._currentFlight,
|
|
{ timeout: 10000 },
|
|
);
|
|
await sleep(500);
|
|
previous = await page.evaluate(
|
|
() =>
|
|
window.__godsEyeView.dataManager.layers
|
|
.get('military-installations')
|
|
.module.getStats().lastUpdate,
|
|
);
|
|
await moveCamera(
|
|
page,
|
|
{ lat: 33.33, lon: -117.4, height: 5000, pitch: -90 },
|
|
0.6,
|
|
);
|
|
}
|
|
await page
|
|
.waitForFunction(
|
|
(prev) => {
|
|
const s = window.__godsEyeView.dataManager.layers
|
|
.get('military-installations')
|
|
.module.getStats();
|
|
return (
|
|
!s.loading && s.lastUpdate > prev && s.coverage.kind === 'viewport'
|
|
);
|
|
},
|
|
{ timeout: 6000 },
|
|
previous,
|
|
)
|
|
.catch(() => result.failures.push(`${gesture}: no viewport reload`));
|
|
const row = await page.evaluate(() =>
|
|
window.__godsEyeView.dataManager.layers
|
|
.get('military-installations')
|
|
.module.getStats(),
|
|
);
|
|
if (gesture.startsWith('contacts') && row.count !== 1)
|
|
result.failures.push(
|
|
`${gesture}: destination count ${row.count}, expected 1`,
|
|
);
|
|
result.views.push({ name: gesture, ...row });
|
|
console.log(JSON.stringify({ name: gesture, ...row }));
|
|
}
|
|
await page.screenshot({ path: 'qa-shots/installations-navigation.png' });
|
|
} finally {
|
|
fs.mkdirSync('qa-shots', { recursive: true });
|
|
fs.writeFileSync(
|
|
`qa-shots/installations-navigation-${baseline ? 'before' : 'after'}.json`,
|
|
JSON.stringify(result, null, 2),
|
|
);
|
|
await browser.close();
|
|
}
|
|
if (
|
|
!baseline &&
|
|
(result.failures.length || result.errors.length || result.forbidden.length)
|
|
)
|
|
process.exitCode = 1;
|