mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-12 06:12:18 +00:00
test(qa): an engine-neutral layer probe
- src/app/qaProbe.js → window.__godsEyeView.layers: list() (with
`available`), unavailable(), available(id), enable/disable(id) (a user
toggle through the layer manager), objects(id) → [{ id, label, position,
screen }], screenOf(id, objectId), selected(id), following(), movers(),
framesDrawn() - all through the engine and the catalog's availability, the
same on Cesium and Navara. Installed after the tools.
- src/app/layers/qaSources.js: `?qaFeed=flights:synthetic:10000,
vessels:fixture` (dev server only, allowQaRegistration) replaces those
sources with seeded synthetic answers served through the live sources'
own parsers (same snapshot shape; loaded with import() only then). The
pure generators moved from src/tooling/w1Feeds.js to src/bench/w1Feeds.js
(the browser graph may not reach src/tooling); the W1 recordings
re-export them.
- scripts/qa-w1-layers.mjs: the parity checklist's frame, --layer
--engine=cesium|navara with the fixture feeds, JSON per run and
--compare (objects on screen per pose, 5 known objects within 2 px).
Filled: 1 view, 8 idle, 9 errors/no leftover mover sets after 10
on/off; 2-7 report TODO for the layer tasks.
- qa-perf, qa-labels, qa-overlay-baseline and qa-label-readability take
--engine= and use the engine (camera, frames, render requests, tiles) and
the probe (layers, following, movers) instead of the viewer; checks whose
layers an engine lacks are SKIPPED with the reason (Navara today), as are
qa-perf's animated-style checks there (Cesium post stages).
Verdicts, Cesium, before → after: qa-perf 24/24 → 24/24, qa-labels 14/14 →
14/14, qa-overlay-baseline (datacenters, firms, detection-50) 2 measured +
1 skipped → the same. Navara: qa-perf 14 passed, 3 skipped; qa-labels 3/3,
1 skipped; qa-overlay-baseline 3 skipped; qa-w1-layers flights: Cesium 5/5
(10 000 synthetic aircraft), Navara skipped (no flights renderer yet).
This commit is contained in:
1 parent
834d34296f
commit
ec7dcc944e
11 files changed
+754
-126
No files matched your search
@@ -15,6 +15,12 @@
|
||||
* node scripts/qa-overlay-baseline.mjs --screenshots-dir /tmp/overlay-shots
|
||||
* node scripts/qa-overlay-baseline.mjs --hardware-gpu --headful
|
||||
* node scripts/qa-overlay-baseline.mjs --dist-dir /tmp/gev-dist
|
||||
* node scripts/qa-overlay-baseline.mjs --engine=navara
|
||||
*
|
||||
* Engine-neutral: the camera and frames go through the globe engine and the
|
||||
* layers through the QA probe (window.__godsEyeView.layers). A scene whose
|
||||
* layers the engine does not have is SKIPPED; without a Cesium viewer the
|
||||
* entity inventory reports the engine's mover sets instead.
|
||||
*/
|
||||
|
||||
import fs from 'node:fs';
|
||||
@@ -263,7 +269,10 @@ function printScene(result) {
|
||||
}
|
||||
if (result.entityInventory) {
|
||||
const totals = result.entityInventory.totals;
|
||||
console.log(` scene total : entities ${totals.entities} · LabelGraphics ${totals.labelGraphics} (${totals.shownLabelGraphics} show=true) · primitive labels ${totals.primitiveLabels}`);
|
||||
if (totals.entities === undefined)
|
||||
console.log(` scene total : mover sets ${totals.moverSets} · instances ${totals.moverInstances}`);
|
||||
else
|
||||
console.log(` scene total : entities ${totals.entities} · LabelGraphics ${totals.labelGraphics} (${totals.shownLabelGraphics} show=true) · primitive labels ${totals.primitiveLabels}`);
|
||||
}
|
||||
for (const phaseName of ['motion', 'rest']) {
|
||||
const phase = result.samples?.[phaseName];
|
||||
@@ -345,33 +354,29 @@ async function installPageInstrumentation(page) {
|
||||
async function waitForApp(page) {
|
||||
await page.goto(APP_URL, { waitUntil: 'domcontentloaded', timeout: 60_000 });
|
||||
await page.waitForFunction(
|
||||
() => Boolean(window.__godsEyeView?.viewer && window.__godsEyeView?.dataManager),
|
||||
() => Boolean(window.__godsEyeView?.layers && window.__godsEyeView?.dataManager),
|
||||
{ timeout: 60_000 },
|
||||
);
|
||||
await page.evaluate(() => {
|
||||
const viewer = window.__godsEyeView.viewer;
|
||||
try { viewer.camera.cancelFlight(); } catch { /* no flight */ }
|
||||
try { viewer.scene.tweens?.removeAll?.(); } catch { /* no tween collection */ }
|
||||
const { engine, viewer } = window.__godsEyeView;
|
||||
try { engine.camera.cancelFlight(); } catch { /* no flight */ }
|
||||
try { viewer?.scene.tweens?.removeAll?.(); } catch { /* no tween collection */ }
|
||||
});
|
||||
await sleep(1_250);
|
||||
}
|
||||
|
||||
async function setCamera(page, camera) {
|
||||
if (!camera) return;
|
||||
await page.evaluate(([lon, lat, height, heading, pitch]) => {
|
||||
const viewer = window.__godsEyeView.viewer;
|
||||
const ellipsoid = viewer.scene.globe.ellipsoid;
|
||||
viewer.camera.cancelFlight?.();
|
||||
viewer.scene.tweens?.removeAll?.();
|
||||
viewer.camera.setView({
|
||||
destination: ellipsoid.cartographicToCartesian({
|
||||
longitude: lon * Math.PI / 180,
|
||||
latitude: lat * Math.PI / 180,
|
||||
height,
|
||||
}),
|
||||
await page.evaluate(async ([lon, lat, height, heading, pitch]) => {
|
||||
const { engine, viewer } = window.__godsEyeView;
|
||||
const geo = await import('/src/geo/index.js');
|
||||
engine.camera.cancelFlight();
|
||||
viewer?.scene.tweens?.removeAll?.();
|
||||
engine.camera.setView({
|
||||
destination: geo.cartesian3.fromDegrees(lon, lat, height),
|
||||
orientation: { heading, pitch, roll: 0 },
|
||||
});
|
||||
viewer.scene.requestRender?.();
|
||||
engine.render.request('qa-overlay-baseline');
|
||||
}, camera);
|
||||
}
|
||||
|
||||
@@ -411,7 +416,19 @@ async function waitForLayer(page, layerId) {
|
||||
|
||||
async function readEntityInventory(page) {
|
||||
return page.evaluate(() => {
|
||||
const viewer = window.__godsEyeView.viewer;
|
||||
const { viewer, layers } = window.__godsEyeView;
|
||||
// Without a Cesium viewer (Navara): the engine's mover sets.
|
||||
if (!viewer) {
|
||||
const sets = layers.movers();
|
||||
return {
|
||||
totals: {
|
||||
moverSets: sets.length,
|
||||
moverInstances: sets.reduce((sum, set) => sum + set.count, 0),
|
||||
},
|
||||
collections: sets,
|
||||
primitiveTypes: {},
|
||||
};
|
||||
}
|
||||
const time = viewer.clock.currentTime;
|
||||
const collections = [];
|
||||
const totals = {
|
||||
@@ -605,7 +622,7 @@ async function prepareTrackedFlight(page, cockpit) {
|
||||
});
|
||||
if (!tracked.ok) return `Civil tracking unavailable (${tracked.reason})`;
|
||||
try {
|
||||
await page.waitForFunction(() => Boolean(window.__godsEyeView.viewer.trackedEntity?.position), { timeout: 10_000 });
|
||||
await page.waitForFunction(() => Boolean(window.__godsEyeView.layers.following()), { timeout: 10_000 });
|
||||
} catch {
|
||||
return 'Civil tracking did not create a tracked entity';
|
||||
}
|
||||
@@ -638,7 +655,7 @@ async function prepareMission(page) {
|
||||
});
|
||||
if (!selected) return 'Mission roster did not expose a selectable mission';
|
||||
try {
|
||||
await page.waitForFunction(() => String(window.__godsEyeView.viewer.selectedEntity?.id || '').startsWith('rocket-launch:'), { timeout: 10_000 });
|
||||
await page.waitForFunction(() => String(window.__godsEyeView.viewer?.selectedEntity?.id || '').startsWith('rocket-launch:'), { timeout: 10_000 });
|
||||
} catch {
|
||||
return 'Mission selection did not reach the Cesium selected entity';
|
||||
}
|
||||
@@ -647,6 +664,12 @@ async function prepareMission(page) {
|
||||
}
|
||||
|
||||
async function prepareScene(page, scene) {
|
||||
const missing = await page.evaluate(
|
||||
(ids) => ids.filter((id) => !window.__godsEyeView.layers.available(id)),
|
||||
scene.layers,
|
||||
);
|
||||
if (missing.length)
|
||||
return { layerActivations: [], skipReason: `no ${missing.join(', ')} on ${ENGINE}` };
|
||||
if (scene.camera) await setCamera(page, scene.camera);
|
||||
const layerActivations = await activateLayers(page, scene.layers);
|
||||
let skipReason = null;
|
||||
@@ -678,7 +701,28 @@ async function readCanvasSummary(page) {
|
||||
|
||||
async function samplePhase(page, moving) {
|
||||
const raw = await page.evaluate(async ({ durationMs, movingCamera }) => {
|
||||
const viewer = window.__godsEyeView.viewer;
|
||||
const { engine } = window.__godsEyeView;
|
||||
// Cesium's rotateRight / rotateUp: the camera turned about the axis
|
||||
// through the globe centre along its up (right) vector.
|
||||
const rotate = (axis, angle) => {
|
||||
const pose = engine.camera.pose();
|
||||
const len = Math.hypot(axis.x, axis.y, axis.z);
|
||||
const [ux, uy, uz] = [axis.x / len, axis.y / len, axis.z / len];
|
||||
const cos = Math.cos(angle);
|
||||
const sin = Math.sin(angle);
|
||||
const turn = (v) => {
|
||||
const dot = ux * v.x + uy * v.y + uz * v.z;
|
||||
return {
|
||||
x: v.x * cos + (uy * v.z - uz * v.y) * sin + ux * dot * (1 - cos),
|
||||
y: v.y * cos + (uz * v.x - ux * v.z) * sin + uy * dot * (1 - cos),
|
||||
z: v.z * cos + (ux * v.y - uy * v.x) * sin + uz * dot * (1 - cos),
|
||||
};
|
||||
};
|
||||
engine.camera.setView({
|
||||
destination: turn(pose.position),
|
||||
orientation: { direction: turn(pose.direction), up: turn(pose.up) },
|
||||
});
|
||||
};
|
||||
const instrumentation = window.__overlayBaselineInstrumentation;
|
||||
instrumentation?.reset?.();
|
||||
const intervals = [];
|
||||
@@ -690,10 +734,11 @@ async function samplePhase(page, moving) {
|
||||
lastFrameAt = now;
|
||||
const elapsed = now - startedAt;
|
||||
if (movingCamera) {
|
||||
viewer.camera.rotateRight(0.00035);
|
||||
viewer.camera.rotateUp(0.00008 * Math.sin(elapsed / 600));
|
||||
const pose = engine.camera.pose();
|
||||
rotate(pose.up, -0.00035);
|
||||
rotate(engine.camera.pose().right, 0.00008 * Math.sin(elapsed / 600));
|
||||
}
|
||||
viewer.scene.requestRender?.();
|
||||
engine.render.request('qa-overlay-baseline');
|
||||
if (elapsed >= durationMs) resolve();
|
||||
else requestAnimationFrame(step);
|
||||
};
|
||||
@@ -813,7 +858,12 @@ async function runScene(browser, scene, shotsDir) {
|
||||
}
|
||||
}
|
||||
|
||||
const APP_URL = getOpt('--url', DEFAULT_URL);
|
||||
const ENGINE = process.argv.find((value) => value.startsWith('--engine='))?.slice(9) ?? 'cesium';
|
||||
const APP_URL = (() => {
|
||||
const url = new URL(getOpt('--url', DEFAULT_URL));
|
||||
url.searchParams.set('engine', ENGINE);
|
||||
return url.href;
|
||||
})();
|
||||
const DPR = Number(getOpt('--dpr', '1'));
|
||||
const JSON_PATH = getOpt('--json');
|
||||
const SCREENSHOTS_DIR = getOpt('--screenshots-dir');
|
||||
|
||||
Reference in new issue
Block a user