Cut duplicated and dead code across the engines, the layers and the QA scripts

- src/geo drops the ported debug argument checks and the tests that
  bitExact already covers; package boundaries accept a directory entry,
  so src/geo/ is listed once per group.
- Cesium no longer carries movers and lines nobody draws with: they are
  optional engine capabilities. The fake engine and the Cesium camera use
  the shared cameraMath; unused contract tables, clock.ticked, the
  'finest' surface detail and Navara click statistics are gone.
- ALPR keeps one overlay core and one presentation model for both
  engines, bikeshare one station bookkeeping, the Navara ground effects
  one definition helper; image atlases, view centres, mover banks and
  colours are shared.
- The legacy switch route, the clock offset and rate controls and other
  unused exports are removed.
- The acceptance runs the Navara draw, geo import and scope checks;
  scenarios use the harness switch and page helpers; the main chunk
  check keeps the size budget only and runs once in CI.
This commit is contained in:
German Khokhlov committed 2026-11-05 13:32:39 +04:00
1 parent c502404e1d
commit 7abf74518f
272 files changed
+2618 -8003

No files matched your search

+2 -7
View File
@@ -59,7 +59,8 @@ jobs:
- name: Build production bundle - name: Build production bundle
run: npm run build run: npm run build
- name: Main chunk (no three or Navara in Cesium mode; size budget when recorded) - name: Main chunk size budget
if: matrix.node == '24.22.2'
run: npm run check:main-chunk run: npm run check:main-chunk
street-level-browser: street-level-browser:
@@ -90,9 +91,6 @@ jobs:
MAPILLARY_CLIENT_TOKEN: 'MLY|0|qa-fixture' MAPILLARY_CLIENT_TOKEN: 'MLY|0|qa-fixture'
run: npm run build run: npm run build
- name: Main chunk (no three or Navara in Cesium mode; size budget when recorded)
run: npm run check:main-chunk
- name: Start the preview server - name: Start the preview server
env: env:
MAPILLARY_CLIENT_TOKEN: 'MLY|0|qa-fixture' MAPILLARY_CLIENT_TOKEN: 'MLY|0|qa-fixture'
@@ -154,6 +152,3 @@ jobs:
- name: Build production bundle - name: Build production bundle
run: npm run build run: npm run build
- name: Main chunk (no three or Navara in Cesium mode; size budget when recorded)
run: npm run check:main-chunk
+3 -4
View File
@@ -52,14 +52,13 @@ resolved dependency inventory remains in `package-lock.json`.
are adapted from `@cesium/engine/Source/Core` (`Math.js`, `Ellipsoid.js`, are adapted from `@cesium/engine/Source/Core` (`Math.js`, `Ellipsoid.js`,
`scaleToGeodeticSurface.js`, `Cartographic.js`, `Cartesian2.js`, `scaleToGeodeticSurface.js`, `Cartographic.js`, `Cartesian2.js`,
`Cartesian3.js`, `Matrix3.js`, `Matrix4.js`, `Quaternion.js`, `Cartesian3.js`, `Matrix3.js`, `Matrix4.js`, `Quaternion.js`,
`Transforms.js`, `Check.js`, `DeveloperError.js`) and say so in their first `Transforms.js`) and say so in their first
line. The changes: the classes' static functions became module functions, line. The changes: the classes' static functions became module functions,
vectors, geodetic positions and quaternions became plain-data instances of vectors, geodetic positions and quaternions became plain-data instances of
module-private constructors (own enumerable fields only, no methods), module-private constructors (own enumerable fields only, no methods),
matrices plain arrays, and the ellipsoid a frozen object; the debug matrices plain arrays, and the ellipsoid a frozen object; the debug
argument checks were ported with Cesium's conditions and messages and, like argument checks were left out, as in Cesium's release build; only the subset
Cesium's, are active in development and tests and stripped from production gev uses was kept.
builds; only the subset gev uses was kept.
- Author: Copyright 2011-2024 CesiumJS Contributors - Author: Copyright 2011-2024 CesiumJS Contributors
- Source: <https://github.com/CesiumGS/cesium> - Source: <https://github.com/CesiumGS/cesium>
- License: Apache License 2.0 - License: Apache License 2.0
+1 -2
View File
@@ -2,8 +2,7 @@
// initial chunks are every entry chunk and what it imports statically; // initial chunks are every entry chunk and what it imports statically;
// navaraMode.js reaches Navara only through import(), so three and // navaraMode.js reaches Navara only through import(), so three and
// @navaramap/* belong to lazy chunks. This checks the bundle's module ids // @navaramap/* belong to lazy chunks. This checks the bundle's module ids
// (what Rollup put in each chunk), and fails the build otherwise; // (what Rollup put in each chunk), and fails the build otherwise.
// scripts/check-main-chunk.mjs checks the built files' text as well.
/** /**
* Module ids that only the Navara engine may load, when no package list is * Module ids that only the Navara engine may load, when no package list is
+2 -1
View File
@@ -55,7 +55,8 @@ See [application construction](APPLICATION.md) and the
2. `scripts/check-package-boundaries.mjs` builds every declared export without 2. `scripts/check-package-boundaries.mjs` builds every declared export without
app Vite configuration or environment files. `scripts/package-boundaries.json` app Vite configuration or environment files. `scripts/package-boundaries.json`
assigns each export exactly once and lists its owned modules and external assigns each export exactly once and lists its owned modules and external
dependencies. Unused imports still count. Node exports have only a `node` dependencies; a module entry ending in `/` (such as `src/geo/`) owns every
file under that directory. Unused imports still count. Node exports have only a `node`
condition; browser groups cannot use build-only dependency exceptions. condition; browser groups cannot use build-only dependency exceptions.
Portable source graphs cannot reach application/rendering, Node, Cesium or Portable source graphs cannot reach application/rendering, Node, Cesium or
+7 -7
View File
@@ -61,21 +61,21 @@
"@jtarrio/webrtlsdr": "^3.0.6", "@jtarrio/webrtlsdr": "^3.0.6",
"@mapbox/vector-tile": "^3.0.0", "@mapbox/vector-tile": "^3.0.0",
"@meri-imperiumi/eccodes-wasm": "^2.48.2", "@meri-imperiumi/eccodes-wasm": "^2.48.2",
"@navaramap/three": "0.2.1",
"@navaramap/three-default-plugin": "0.2.1",
"cesium": "^1.124.0", "cesium": "^1.124.0",
"egm96-universal": "^1.1.1", "egm96-universal": "^1.1.1",
"hls.js": "^1.7.3", "hls.js": "^1.7.3",
"kdbush": "4.0.2",
"mapillary-js": "^4.1.2", "mapillary-js": "^4.1.2",
"mgrs": "^2.1.0", "mgrs": "^2.1.0",
"pbf": "^5.1.2", "pbf": "^5.1.2",
"pinia": "^2.3.1", "pinia": "^2.3.1",
"satellite.js": "^6.0.2",
"vue": "^3.5.44",
"vue-i18n": "^11.4.15",
"@navaramap/three": "0.2.1",
"@navaramap/three-default-plugin": "0.2.1",
"kdbush": "4.0.2",
"postprocessing": "6.39.4", "postprocessing": "6.39.4",
"three": "0.185.1" "satellite.js": "^6.0.2",
"three": "0.185.1",
"vue": "^3.5.44",
"vue-i18n": "^11.4.15"
}, },
"devDependencies": { "devDependencies": {
"@happy-dom/global-registrator": "^20.14.6", "@happy-dom/global-registrator": "^20.14.6",
+3 -3
View File
@@ -30,9 +30,9 @@ const globeEngine = (file) => /^src\/globe\/(?:cesium|navara)\//.test(file);
const cesiumPackage = /^(?:cesium(?:\/|$)|@cesium\/)/; const cesiumPackage = /^(?:cesium(?:\/|$)|@cesium\/)/;
// Navara renders through three.js and its postprocessing peer. // Navara renders through three.js and its postprocessing peer.
const navaraPackage = /^(?:(?:three|postprocessing)(?:\/|$)|@navaramap\/)/; const navaraPackage = /^(?:(?:three|postprocessing)(?:\/|$)|@navaramap\/)/;
// Phase 3: the layers moved onto layer renderers (the *a tasks) keep each // Layers built on layer renderers keep each engine in their renderers:
// engine in their renderers: cesium only in render/cesium*.js, three and // cesium only in render/cesium*.js, three and Navara only in
// Navara only in render/navara*.js. The list grows with each moved layer. // render/navara*.js. The list grows with each layer moved onto renderers.
const RENDERED_LAYER_DIRS = [ const RENDERED_LAYER_DIRS = [
'src/layers/alpr/', 'src/layers/alpr/',
'src/layers/airQuality/', 'src/layers/airQuality/',
+14 -94
View File
@@ -1,17 +1,12 @@
#!/usr/bin/env node #!/usr/bin/env node
/** /**
* The main-chunk gate of the Cesium-mode bundle (run after `npm run build`): * The size budget of the Cesium-mode main chunk (run after `npm run build`):
* gzip-9 of the entry chunk the built page loads (from the Vite manifest, or
* the module script dist/index.html loads) may exceed the baseline in
* scripts/main-chunk-baseline.json by at most its allowance. What the initial
* chunks may hold is the build's own check (build/initialChunkGuard.js).
* *
* 1. Composition: the main chunk (the Vite manifest's entry, or the module * Prints the size. `--json` prints the whole result; `--dist=DIR` reads
* script dist/index.html loads) and every chunk it imports statically carry
* no three or Navara code (text markers; the build itself also fails on
* three/@navaramap module ids in initial chunks, build/initialChunkGuard.js).
* 2. Budget, when scripts/main-chunk-baseline.json exists: gzip-9 of the
* largest dist/assets/index-*.js may exceed the recorded baseline by at
* most its allowance (the phase-4 branch's check; same exports,
* checkMainChunk and measureMainChunk, so the two merge into this script).
*
* Prints the sizes. `--json` prints the whole result; `--dist=DIR` reads
* another build. * another build.
* *
* npm run build && npm run check:main-chunk * npm run build && npm run check:main-chunk
@@ -20,12 +15,7 @@ import fs from 'node:fs';
import path from 'node:path'; import path from 'node:path';
import { gzipSync } from 'node:zlib'; import { gzipSync } from 'node:zlib';
import { fileURLToPath, pathToFileURL } from 'node:url'; import { fileURLToPath, pathToFileURL } from 'node:url';
import { import { entryFromHtml, entryFromManifest } from '../src/tooling/mainChunk.js';
entryFromHtml,
entryFromManifest,
forbiddenMarkersIn,
initialChunks,
} from '../src/tooling/mainChunk.js';
const ROOT = fileURLToPath(new URL('..', import.meta.url)); const ROOT = fileURLToPath(new URL('..', import.meta.url));
@@ -44,40 +34,16 @@ export function checkMainChunk(
/** /**
* gzip-9 size of the main chunk, with its name: the entry the built page * gzip-9 size of the main chunk, with its name: the entry the built page
* loads (`entry`, or found from dist/index.html or the Vite manifest), else * loads (`entry`, else found from the Vite manifest or dist/index.html).
* (phase 4's rule, for a dist without a page) the largest
* dist/assets/index-*.js.
*/ */
export function measureMainChunk(distDir, { entry = null } = {}) { export function measureMainChunk(distDir, { entry = null } = {}) {
let file = entry; const file = entry ?? findEntry(distDir).entry;
if (!file) {
try {
file = findEntry(distDir).entry;
} catch {
file = null;
}
}
if (file) {
const bytes = fs.readFileSync(path.join(distDir, file)); const bytes = fs.readFileSync(path.join(distDir, file));
return { return {
name: path.basename(file), name: path.basename(file),
gzipBytes: gzipSync(bytes, { level: 9 }).length, gzipBytes: gzipSync(bytes, { level: 9 }).length,
}; };
} }
const assets = path.join(distDir, 'assets');
const candidates = fs
.readdirSync(assets)
.filter((name) => /^index-.*\.js$/.test(name))
.map((name) => ({ name, bytes: fs.readFileSync(path.join(assets, name)) }))
.sort((a, b) => b.bytes.length - a.bytes.length);
if (!candidates.length)
throw new Error('no dist/assets/index-*.js: run npm run build first');
const [main] = candidates;
return {
name: main.name,
gzipBytes: gzipSync(main.bytes, { level: 9 }).length,
};
}
/** The entry chunk (relative to dist) and how it was found. */ /** The entry chunk (relative to dist) and how it was found. */
function findEntry(dist) { function findEntry(dist) {
@@ -96,44 +62,15 @@ function findEntry(dist) {
return { entry, via: 'index.html' }; return { entry, via: 'index.html' };
} }
/** Composition of the initial chunks: sizes and forbidden markers. */
export function checkComposition(dist) {
const read = (file) => {
const absolute = path.join(dist, file);
return fs.existsSync(absolute) ? fs.readFileSync(absolute, 'utf8') : null;
};
const { entry, via } = findEntry(dist);
const chunks = initialChunks(entry, read).map((file) => {
const code = read(file);
if (code == null)
return { file, missing: true, bytes: 0, gzipBytes: 0, markers: [] };
return {
file,
bytes: Buffer.byteLength(code),
gzipBytes: gzipSync(code).length,
markers: forbiddenMarkersIn(code),
};
});
return {
entry,
via,
mainBytes: chunks[0].bytes,
mainGzipBytes: chunks[0].gzipBytes,
initialBytes: chunks.reduce((sum, chunk) => sum + chunk.bytes, 0),
chunks,
ok: chunks.every((chunk) => !chunk.missing && !chunk.markers.length),
};
}
function main() { function main() {
const option = (name) => const option = (name) =>
process.argv.find((arg) => arg.startsWith(`--${name}=`))?.split('=')[1]; process.argv.find((arg) => arg.startsWith(`--${name}=`))?.split('=')[1];
const dist = path.resolve(ROOT, option('dist') ?? 'dist'); const dist = path.resolve(ROOT, option('dist') ?? 'dist');
const composition = checkComposition(dist); const { entry, via } = findEntry(dist);
const baselinePath = path.join(ROOT, 'scripts/main-chunk-baseline.json'); const baselinePath = path.join(ROOT, 'scripts/main-chunk-baseline.json');
let budget = null; let budget = null;
if (fs.existsSync(baselinePath)) { if (fs.existsSync(baselinePath)) {
const measured = measureMainChunk(dist, { entry: composition.entry }); const measured = measureMainChunk(dist, { entry });
budget = { budget = {
...measured, ...measured,
...checkMainChunk( ...checkMainChunk(
@@ -142,28 +79,11 @@ function main() {
), ),
}; };
} }
const ok = composition.ok && (budget?.ok ?? true); const ok = budget?.ok ?? true;
if (process.argv.includes('--json')) { if (process.argv.includes('--json')) {
console.log(JSON.stringify({ composition, budget, ok }, null, 2)); console.log(JSON.stringify({ entry, via, budget, ok }, null, 2));
} else { } else {
const kb = (bytes) => `${(bytes / 1000).toFixed(2)} kB`; console.log(`[check:main-chunk] entry ${entry} (via ${via})`);
console.log(
`[check:main-chunk] entry ${composition.entry} (via ${composition.via})`,
);
for (const chunk of composition.chunks)
console.log(
`[check:main-chunk] ${chunk.file}: ${kb(chunk.bytes)} (gzip ${kb(chunk.gzipBytes)})` +
(chunk.missing ? ' MISSING' : '') +
(chunk.markers.length
? ` FORBIDDEN: ${chunk.markers.join(', ')}`
: ''),
);
console.log(
`[check:main-chunk] main ${composition.mainBytes} bytes (gzip ${composition.mainGzipBytes}); initial chunks ${composition.initialBytes} bytes; ` +
(composition.ok
? 'no three or Navara'
: 'three or Navara in the initial chunks'),
);
console.log( console.log(
budget budget
? `[check:main-chunk] ${budget.name}: ${budget.message}` ? `[check:main-chunk] ${budget.name}: ${budget.message}`
+10 -2
View File
@@ -37,7 +37,11 @@ export async function checkPackageBoundaries(root) {
) { ) {
throw new Error(`Invalid package boundary: ${name}`); throw new Error(`Invalid package boundary: ${name}`);
} }
// A module entry ending in "/" owns every file under that directory.
const allowed = new Set(); const allowed = new Set();
const allowedDirectories = [];
const owns = (id) =>
allowed.has(id) || allowedDirectories.some((dir) => id.startsWith(dir));
for (const module of group.modules) { for (const module of group.modules) {
if ( if (
typeof module !== 'string' || typeof module !== 'string' ||
@@ -56,8 +60,12 @@ export async function checkPackageBoundaries(root) {
) { ) {
throw new Error(`Boundary module escapes repository: ${name}`); throw new Error(`Boundary module escapes repository: ${name}`);
} }
if (module.endsWith('/')) {
allowedDirectories.push(`${normalizePath(resolved)}/`);
} else {
allowed.add(normalizePath(resolved)); allowed.add(normalizePath(resolved));
} }
}
for (const external of group.external) { for (const external of group.external) {
if ( if (
!Object.hasOwn(pkg.dependencies || {}, external) && !Object.hasOwn(pkg.dependencies || {}, external) &&
@@ -82,7 +90,7 @@ export async function checkPackageBoundaries(root) {
if ( if (
typeof target !== 'string' || typeof target !== 'string' ||
!target.startsWith('./') || !target.startsWith('./') ||
!allowed.has(normalizePath(path.resolve(root, target))) !owns(normalizePath(path.resolve(root, target)))
) { ) {
throw new Error(`Export must point to an owned module: ${key}`); throw new Error(`Export must point to an owned module: ${key}`);
} }
@@ -109,7 +117,7 @@ export async function checkPackageBoundaries(root) {
// template blocks belong to the component file itself. // template blocks belong to the component file itself.
if (info.id.startsWith('\0')) return; if (info.id.startsWith('\0')) return;
const id = info.id.split('?')[0]; const id = info.id.split('?')[0];
if (!allowed.has(id)) { if (!owns(id)) {
throw new Error( throw new Error(
`Package boundary ${name} imports an unowned module: ${path.relative(root, id)}`, `Package boundary ${name} imports an unowned module: ${path.relative(root, id)}`,
); );
+10 -4
View File
@@ -589,6 +589,8 @@
"src/layers/alpr/model.js", "src/layers/alpr/model.js",
"src/layers/alpr/policy.js", "src/layers/alpr/policy.js",
"src/layers/alpr/visuals.js", "src/layers/alpr/visuals.js",
"src/layers/alpr/overlay.js",
"src/layers/alpr/presentation.js",
"src/layers/alpr/overlay.test.mjs", "src/layers/alpr/overlay.test.mjs",
"src/layers/alpr/source.js", "src/layers/alpr/source.js",
"src/layers/alpr/source.test.mjs", "src/layers/alpr/source.test.mjs",
@@ -1217,6 +1219,7 @@
"src/layers/aircraft/render/contract.js", "src/layers/aircraft/render/contract.js",
"src/layers/shared/color.test.mjs", "src/layers/shared/color.test.mjs",
"src/layers/shared/color.js", "src/layers/shared/color.js",
"src/layers/shared/viewRectangle.js",
"src/layers/military/render/cesium.js", "src/layers/military/render/cesium.js",
"src/layers/military/render/navara.js", "src/layers/military/render/navara.js",
"src/layers/aircraft/qaDrawn.js", "src/layers/aircraft/qaDrawn.js",
@@ -1225,13 +1228,18 @@
"src/layers/vessels/render/cesium.js", "src/layers/vessels/render/cesium.js",
"src/layers/vessels/render/contract.js", "src/layers/vessels/render/contract.js",
"src/layers/vessels/renderer.test.mjs", "src/layers/vessels/renderer.test.mjs",
"src/layers/vessels/testFixtures.mjs",
"src/layers/vessels/render/navara.js", "src/layers/vessels/render/navara.js",
"src/layers/shared/render/engineView.js", "src/layers/shared/render/engineView.js",
"src/layers/shared/render/navaraGroundEffect.js",
"src/layers/shared/render/navaraMoverBank.js",
"src/layers/shared/render/imageAtlas.js",
"src/layers/installations/render/cesium/lifecycle.js", "src/layers/installations/render/cesium/lifecycle.js",
"src/layers/installations/render/cesium/rendering.js", "src/layers/installations/render/cesium/rendering.js",
"src/layers/installations/render/cesium/selection.js", "src/layers/installations/render/cesium/selection.js",
"src/layers/bikeshare/render/cesium/lifecycle.js", "src/layers/bikeshare/render/cesium/lifecycle.js",
"src/layers/bikeshare/presentation.js", "src/layers/bikeshare/presentation.js",
"src/layers/bikeshare/stationPoints.js",
"src/layers/bikeshare/presentation.test.mjs", "src/layers/bikeshare/presentation.test.mjs",
"src/layers/bikeshare/selectionModel.js", "src/layers/bikeshare/selectionModel.js",
"src/layers/bikeshare/render/cesium.js", "src/layers/bikeshare/render/cesium.js",
@@ -1329,8 +1337,8 @@
"src/globe/cesium/render.js", "src/globe/cesium/render.js",
"src/globe/cesium/surface.js", "src/globe/cesium/surface.js",
"src/globe/contract.js", "src/globe/contract.js",
"src/globe/navara/cameraMath.js", "src/globe/cameraMath.js",
"src/globe/navara/cameraMath.test.mjs", "src/globe/cameraMath.test.mjs",
"src/globe/navara/cameraModel.js", "src/globe/navara/cameraModel.js",
"src/globe/navara/cameraModel.test.mjs", "src/globe/navara/cameraModel.test.mjs",
"src/globe/navara/createNavaraEngine.js", "src/globe/navara/createNavaraEngine.js",
@@ -1364,14 +1372,12 @@
"src/tooling/mainChunkComposition.test.mjs", "src/tooling/mainChunkComposition.test.mjs",
"src/tooling/initialChunkGuard.test.mjs", "src/tooling/initialChunkGuard.test.mjs",
"src/globe/movers.test.mjs", "src/globe/movers.test.mjs",
"src/globe/cesium/movers.test.mjs",
"src/globe/navara/movers/pack.test.mjs", "src/globe/navara/movers/pack.test.mjs",
"src/globe/navara/movers/pickIds.test.mjs", "src/globe/navara/movers/pickIds.test.mjs",
"src/globe/navara/movers/moverMesh.test.mjs", "src/globe/navara/movers/moverMesh.test.mjs",
"src/globe/navara/movers/modelMover.test.mjs", "src/globe/navara/movers/modelMover.test.mjs",
"src/bench/syntheticFlights.test.mjs", "src/bench/syntheticFlights.test.mjs",
"src/globe/lines.test.mjs", "src/globe/lines.test.mjs",
"src/globe/cesium/lines.test.mjs",
"src/globe/navara/lines/lineMesh.test.mjs", "src/globe/navara/lines/lineMesh.test.mjs",
"src/globe/pickDelivery.test.mjs", "src/globe/pickDelivery.test.mjs",
"src/globe/navara/picking.test.mjs", "src/globe/navara/picking.test.mjs",
+47 -534
View File
@@ -897,24 +897,7 @@
"src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-CN.json",
"src/i18n/locales/zh-Hant.json", "src/i18n/locales/zh-Hant.json",
"src/ui/unitLabels.js", "src/ui/unitLabels.js",
"src/geo/index.js", "src/geo/"
"src/geo/math.js",
"src/geo/ellipsoid.js",
"src/geo/cartographic.js",
"src/geo/ellipsoidGeodesic.js",
"src/geo/boundingSphere.js",
"src/geo/cartesian2.js",
"src/geo/cartesian3.js",
"src/geo/matrix3.js",
"src/geo/matrix4.js",
"src/geo/quaternion.js",
"src/geo/transforms.js",
"src/geo/simon1994.js",
"src/geo/julianDate.js",
"src/geo/scaleToGeodeticSurface.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js"
], ],
"external": ["cesium"] "external": ["cesium"]
}, },
@@ -1176,24 +1159,7 @@
"src/globe/cesium/engineFromViewer.js", "src/globe/cesium/engineFromViewer.js",
"src/cameraAngles.js", "src/cameraAngles.js",
"src/globe/contract.js", "src/globe/contract.js",
"src/geo/index.js", "src/geo/",
"src/geo/math.js",
"src/geo/ellipsoid.js",
"src/geo/cartographic.js",
"src/geo/ellipsoidGeodesic.js",
"src/geo/boundingSphere.js",
"src/geo/cartesian2.js",
"src/geo/cartesian3.js",
"src/geo/matrix3.js",
"src/geo/matrix4.js",
"src/geo/quaternion.js",
"src/geo/transforms.js",
"src/geo/simon1994.js",
"src/geo/julianDate.js",
"src/geo/scaleToGeodeticSurface.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js",
"src/app/layers/directory.js", "src/app/layers/directory.js",
"src/globe/follow.js", "src/globe/follow.js",
"src/ui/kit/UiSegmented.vue" "src/ui/kit/UiSegmented.vue"
@@ -1333,24 +1299,7 @@
"src/ui/i18n.js", "src/ui/i18n.js",
"src/globe/cesium/engineFromViewer.js", "src/globe/cesium/engineFromViewer.js",
"src/globe/contract.js", "src/globe/contract.js",
"src/geo/index.js", "src/geo/"
"src/geo/math.js",
"src/geo/ellipsoid.js",
"src/geo/cartographic.js",
"src/geo/ellipsoidGeodesic.js",
"src/geo/boundingSphere.js",
"src/geo/cartesian2.js",
"src/geo/cartesian3.js",
"src/geo/matrix3.js",
"src/geo/matrix4.js",
"src/geo/quaternion.js",
"src/geo/transforms.js",
"src/geo/simon1994.js",
"src/geo/julianDate.js",
"src/geo/scaleToGeodeticSurface.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js"
], ],
"external": ["cesium", "vue", "pinia", "vue-i18n"] "external": ["cesium", "vue", "pinia", "vue-i18n"]
}, },
@@ -1432,24 +1381,7 @@
"src/ui/unitLabels.js", "src/ui/unitLabels.js",
"src/data/render/cesiumProjection.js", "src/data/render/cesiumProjection.js",
"src/data/render/cesiumConstants.js", "src/data/render/cesiumConstants.js",
"src/geo/index.js", "src/geo/",
"src/geo/math.js",
"src/geo/ellipsoid.js",
"src/geo/cartographic.js",
"src/geo/ellipsoidGeodesic.js",
"src/geo/boundingSphere.js",
"src/geo/cartesian2.js",
"src/geo/cartesian3.js",
"src/geo/matrix3.js",
"src/geo/matrix4.js",
"src/geo/quaternion.js",
"src/geo/transforms.js",
"src/geo/simon1994.js",
"src/geo/julianDate.js",
"src/geo/scaleToGeodeticSurface.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js",
"src/time/clock.js", "src/time/clock.js",
"src/globe/contract.js", "src/globe/contract.js",
"src/globe/cesium/engineFromViewer.js", "src/globe/cesium/engineFromViewer.js",
@@ -1516,24 +1448,7 @@
"src/data/render/cesiumProjection.js", "src/data/render/cesiumProjection.js",
"src/data/render/cesiumConstants.js", "src/data/render/cesiumConstants.js",
"src/layers/shared/color.js", "src/layers/shared/color.js",
"src/geo/index.js", "src/geo/",
"src/geo/math.js",
"src/geo/ellipsoid.js",
"src/geo/cartographic.js",
"src/geo/ellipsoidGeodesic.js",
"src/geo/boundingSphere.js",
"src/geo/cartesian2.js",
"src/geo/cartesian3.js",
"src/geo/matrix3.js",
"src/geo/matrix4.js",
"src/geo/quaternion.js",
"src/geo/transforms.js",
"src/geo/simon1994.js",
"src/geo/julianDate.js",
"src/geo/scaleToGeodeticSurface.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js",
"src/time/clock.js", "src/time/clock.js",
"src/globe/contract.js", "src/globe/contract.js",
"src/globe/cesium/engineFromViewer.js", "src/globe/cesium/engineFromViewer.js",
@@ -1571,24 +1486,7 @@
"src/data/inputOwnership.js", "src/data/inputOwnership.js",
"src/data/render/cesiumProjection.js", "src/data/render/cesiumProjection.js",
"src/data/render/cesiumConstants.js", "src/data/render/cesiumConstants.js",
"src/geo/index.js", "src/geo/",
"src/geo/math.js",
"src/geo/ellipsoid.js",
"src/geo/cartographic.js",
"src/geo/ellipsoidGeodesic.js",
"src/geo/boundingSphere.js",
"src/geo/cartesian2.js",
"src/geo/cartesian3.js",
"src/geo/matrix3.js",
"src/geo/matrix4.js",
"src/geo/quaternion.js",
"src/geo/transforms.js",
"src/geo/simon1994.js",
"src/geo/julianDate.js",
"src/geo/scaleToGeodeticSurface.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js",
"src/globe/contract.js", "src/globe/contract.js",
"src/globe/cesium/engineFromViewer.js" "src/globe/cesium/engineFromViewer.js"
], ],
@@ -1635,24 +1533,7 @@
"src/layers/vessels/icons.js", "src/layers/vessels/icons.js",
"src/layers/shared/color.js", "src/layers/shared/color.js",
"src/data/render/cesiumProjection.js", "src/data/render/cesiumProjection.js",
"src/geo/index.js", "src/geo/",
"src/geo/math.js",
"src/geo/ellipsoid.js",
"src/geo/cartographic.js",
"src/geo/ellipsoidGeodesic.js",
"src/geo/boundingSphere.js",
"src/geo/cartesian2.js",
"src/geo/cartesian3.js",
"src/geo/matrix3.js",
"src/geo/matrix4.js",
"src/geo/quaternion.js",
"src/geo/transforms.js",
"src/geo/simon1994.js",
"src/geo/julianDate.js",
"src/geo/scaleToGeodeticSurface.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js",
"src/globe/contract.js", "src/globe/contract.js",
"src/globe/cesium/engineFromViewer.js" "src/globe/cesium/engineFromViewer.js"
], ],
@@ -1665,24 +1546,7 @@
"src/layers/earthquakes/model.js", "src/layers/earthquakes/model.js",
"src/layers/earthquakes/source.js", "src/layers/earthquakes/source.js",
"src/layers/earthquakes/records.js", "src/layers/earthquakes/records.js",
"src/geo/boundingSphere.js", "src/geo/",
"src/geo/cartesian2.js",
"src/geo/cartesian3.js",
"src/geo/cartographic.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js",
"src/geo/ellipsoid.js",
"src/geo/ellipsoidGeodesic.js",
"src/geo/index.js",
"src/geo/math.js",
"src/geo/matrix3.js",
"src/geo/matrix4.js",
"src/geo/quaternion.js",
"src/geo/scaleToGeodeticSurface.js",
"src/geo/transforms.js",
"src/geo/simon1994.js",
"src/geo/julianDate.js",
"src/layers/shared/color.js" "src/layers/shared/color.js"
], ],
"external": [] "external": []
@@ -1751,24 +1615,7 @@
"src/i18n/locales/uk.json", "src/i18n/locales/uk.json",
"src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-CN.json",
"src/i18n/locales/zh-Hant.json", "src/i18n/locales/zh-Hant.json",
"src/geo/index.js", "src/geo/",
"src/geo/math.js",
"src/geo/ellipsoid.js",
"src/geo/cartographic.js",
"src/geo/ellipsoidGeodesic.js",
"src/geo/boundingSphere.js",
"src/geo/cartesian2.js",
"src/geo/cartesian3.js",
"src/geo/matrix3.js",
"src/geo/matrix4.js",
"src/geo/quaternion.js",
"src/geo/transforms.js",
"src/geo/simon1994.js",
"src/geo/julianDate.js",
"src/geo/scaleToGeodeticSurface.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js",
"src/layers/firms/rectangle.js", "src/layers/firms/rectangle.js",
"src/layers/shared/color.js" "src/layers/shared/color.js"
], ],
@@ -1810,24 +1657,7 @@
"src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-CN.json",
"src/i18n/locales/zh-Hant.json", "src/i18n/locales/zh-Hant.json",
"src/ui/unitLabels.js", "src/ui/unitLabels.js",
"src/geo/index.js", "src/geo/",
"src/geo/math.js",
"src/geo/ellipsoid.js",
"src/geo/cartographic.js",
"src/geo/ellipsoidGeodesic.js",
"src/geo/boundingSphere.js",
"src/geo/cartesian2.js",
"src/geo/cartesian3.js",
"src/geo/matrix3.js",
"src/geo/matrix4.js",
"src/geo/quaternion.js",
"src/geo/transforms.js",
"src/geo/simon1994.js",
"src/geo/julianDate.js",
"src/geo/scaleToGeodeticSurface.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js",
"src/time/clock.js", "src/time/clock.js",
"src/layers/shared/color.js" "src/layers/shared/color.js"
], ],
@@ -1872,24 +1702,7 @@
"src/i18n/locales/zh-Hant.json", "src/i18n/locales/zh-Hant.json",
"src/layers/shared/color.js", "src/layers/shared/color.js",
"src/layers/launches/render/cesium.js", "src/layers/launches/render/cesium.js",
"src/geo/index.js", "src/geo/",
"src/geo/math.js",
"src/geo/ellipsoid.js",
"src/geo/cartographic.js",
"src/geo/ellipsoidGeodesic.js",
"src/geo/boundingSphere.js",
"src/geo/cartesian2.js",
"src/geo/cartesian3.js",
"src/geo/matrix3.js",
"src/geo/matrix4.js",
"src/geo/quaternion.js",
"src/geo/transforms.js",
"src/geo/simon1994.js",
"src/geo/julianDate.js",
"src/geo/scaleToGeodeticSurface.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js",
"src/time/clock.js", "src/time/clock.js",
"src/globe/contract.js", "src/globe/contract.js",
"src/globe/cesium/engineFromViewer.js" "src/globe/cesium/engineFromViewer.js"
@@ -1946,24 +1759,7 @@
"src/layers/installations/render/cesium/selection.js", "src/layers/installations/render/cesium/selection.js",
"src/layers/shared/color.js", "src/layers/shared/color.js",
"src/layers/installations/render/cesium/namedMarkers.js", "src/layers/installations/render/cesium/namedMarkers.js",
"src/geo/index.js", "src/geo/",
"src/geo/math.js",
"src/geo/ellipsoid.js",
"src/geo/cartographic.js",
"src/geo/ellipsoidGeodesic.js",
"src/geo/boundingSphere.js",
"src/geo/cartesian2.js",
"src/geo/cartesian3.js",
"src/geo/matrix3.js",
"src/geo/matrix4.js",
"src/geo/quaternion.js",
"src/geo/transforms.js",
"src/geo/simon1994.js",
"src/geo/julianDate.js",
"src/geo/scaleToGeodeticSurface.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js",
"src/globe/contract.js", "src/globe/contract.js",
"src/globe/cesium/engineFromViewer.js" "src/globe/cesium/engineFromViewer.js"
], ],
@@ -2015,24 +1811,7 @@
"src/i18n/locales/zh-Hant.json", "src/i18n/locales/zh-Hant.json",
"src/ui/unitLabels.js", "src/ui/unitLabels.js",
"src/data/render/cesiumProjection.js", "src/data/render/cesiumProjection.js",
"src/geo/index.js", "src/geo/",
"src/geo/math.js",
"src/geo/ellipsoid.js",
"src/geo/cartographic.js",
"src/geo/ellipsoidGeodesic.js",
"src/geo/boundingSphere.js",
"src/geo/cartesian2.js",
"src/geo/cartesian3.js",
"src/geo/matrix3.js",
"src/geo/matrix4.js",
"src/geo/quaternion.js",
"src/geo/transforms.js",
"src/geo/simon1994.js",
"src/geo/julianDate.js",
"src/geo/scaleToGeodeticSurface.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js",
"src/globe/contract.js", "src/globe/contract.js",
"src/globe/cesium/engineFromViewer.js" "src/globe/cesium/engineFromViewer.js"
], ],
@@ -2094,24 +1873,7 @@
"src/i18n/locales/uk.json", "src/i18n/locales/uk.json",
"src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-CN.json",
"src/i18n/locales/zh-Hant.json", "src/i18n/locales/zh-Hant.json",
"src/geo/index.js", "src/geo/",
"src/geo/math.js",
"src/geo/ellipsoid.js",
"src/geo/cartographic.js",
"src/geo/ellipsoidGeodesic.js",
"src/geo/boundingSphere.js",
"src/geo/cartesian2.js",
"src/geo/cartesian3.js",
"src/geo/matrix3.js",
"src/geo/matrix4.js",
"src/geo/quaternion.js",
"src/geo/transforms.js",
"src/geo/simon1994.js",
"src/geo/julianDate.js",
"src/geo/scaleToGeodeticSurface.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js",
"src/globe/contract.js", "src/globe/contract.js",
"src/globe/cesium/engineFromViewer.js" "src/globe/cesium/engineFromViewer.js"
], ],
@@ -2151,24 +1913,7 @@
"src/layers/bikeshare/selectionModel.js", "src/layers/bikeshare/selectionModel.js",
"src/layers/bikeshare/presentation.js", "src/layers/bikeshare/presentation.js",
"src/layers/shared/color.js", "src/layers/shared/color.js",
"src/geo/index.js", "src/geo/",
"src/geo/math.js",
"src/geo/ellipsoid.js",
"src/geo/cartographic.js",
"src/geo/ellipsoidGeodesic.js",
"src/geo/boundingSphere.js",
"src/geo/cartesian2.js",
"src/geo/cartesian3.js",
"src/geo/matrix3.js",
"src/geo/matrix4.js",
"src/geo/quaternion.js",
"src/geo/transforms.js",
"src/geo/simon1994.js",
"src/geo/julianDate.js",
"src/geo/scaleToGeodeticSurface.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js",
"src/globe/contract.js", "src/globe/contract.js",
"src/globe/cesium/engineFromViewer.js" "src/globe/cesium/engineFromViewer.js"
], ],
@@ -2227,24 +1972,7 @@
"src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-CN.json",
"src/i18n/locales/zh-Hant.json", "src/i18n/locales/zh-Hant.json",
"src/layers/shared/color.js", "src/layers/shared/color.js",
"src/geo/index.js", "src/geo/"
"src/geo/math.js",
"src/geo/ellipsoid.js",
"src/geo/cartographic.js",
"src/geo/ellipsoidGeodesic.js",
"src/geo/boundingSphere.js",
"src/geo/cartesian2.js",
"src/geo/cartesian3.js",
"src/geo/matrix3.js",
"src/geo/matrix4.js",
"src/geo/quaternion.js",
"src/geo/transforms.js",
"src/geo/simon1994.js",
"src/geo/julianDate.js",
"src/geo/scaleToGeodeticSurface.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js"
], ],
"external": ["hls.js"] "external": ["hls.js"]
}, },
@@ -2318,24 +2046,7 @@
"src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-CN.json",
"src/i18n/locales/zh-Hant.json", "src/i18n/locales/zh-Hant.json",
"src/layers/streetLevel/providers/mapillary/policy.js", "src/layers/streetLevel/providers/mapillary/policy.js",
"src/geo/index.js", "src/geo/"
"src/geo/math.js",
"src/geo/ellipsoid.js",
"src/geo/cartographic.js",
"src/geo/ellipsoidGeodesic.js",
"src/geo/boundingSphere.js",
"src/geo/cartesian2.js",
"src/geo/cartesian3.js",
"src/geo/matrix3.js",
"src/geo/matrix4.js",
"src/geo/quaternion.js",
"src/geo/transforms.js",
"src/geo/simon1994.js",
"src/geo/julianDate.js",
"src/geo/scaleToGeodeticSurface.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js"
], ],
"external": ["cesium", "pbf", "@mapbox/vector-tile", "mapillary-js"] "external": ["cesium", "pbf", "@mapbox/vector-tile", "mapillary-js"]
}, },
@@ -2371,24 +2082,7 @@
"src/i18n/locales/uk.json", "src/i18n/locales/uk.json",
"src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-CN.json",
"src/i18n/locales/zh-Hant.json", "src/i18n/locales/zh-Hant.json",
"src/geo/index.js", "src/geo/"
"src/geo/math.js",
"src/geo/ellipsoid.js",
"src/geo/cartographic.js",
"src/geo/ellipsoidGeodesic.js",
"src/geo/boundingSphere.js",
"src/geo/cartesian2.js",
"src/geo/cartesian3.js",
"src/geo/matrix3.js",
"src/geo/matrix4.js",
"src/geo/quaternion.js",
"src/geo/transforms.js",
"src/geo/simon1994.js",
"src/geo/julianDate.js",
"src/geo/scaleToGeodeticSurface.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js"
], ],
"external": ["@mapbox/vector-tile", "pbf"] "external": ["@mapbox/vector-tile", "pbf"]
}, },
@@ -2437,24 +2131,7 @@
"src/i18n/locales/uk.json", "src/i18n/locales/uk.json",
"src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-CN.json",
"src/i18n/locales/zh-Hant.json", "src/i18n/locales/zh-Hant.json",
"src/geo/index.js", "src/geo/"
"src/geo/math.js",
"src/geo/ellipsoid.js",
"src/geo/cartographic.js",
"src/geo/ellipsoidGeodesic.js",
"src/geo/boundingSphere.js",
"src/geo/cartesian2.js",
"src/geo/cartesian3.js",
"src/geo/matrix3.js",
"src/geo/matrix4.js",
"src/geo/quaternion.js",
"src/geo/transforms.js",
"src/geo/simon1994.js",
"src/geo/julianDate.js",
"src/geo/scaleToGeodeticSurface.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js"
], ],
"external": [] "external": []
}, },
@@ -2486,24 +2163,7 @@
"src/i18n/locales/uk.json", "src/i18n/locales/uk.json",
"src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-CN.json",
"src/i18n/locales/zh-Hant.json", "src/i18n/locales/zh-Hant.json",
"src/geo/index.js", "src/geo/"
"src/geo/math.js",
"src/geo/ellipsoid.js",
"src/geo/cartographic.js",
"src/geo/ellipsoidGeodesic.js",
"src/geo/boundingSphere.js",
"src/geo/cartesian2.js",
"src/geo/cartesian3.js",
"src/geo/matrix3.js",
"src/geo/matrix4.js",
"src/geo/quaternion.js",
"src/geo/transforms.js",
"src/geo/simon1994.js",
"src/geo/julianDate.js",
"src/geo/scaleToGeodeticSurface.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js"
], ],
"external": ["cesium"] "external": ["cesium"]
}, },
@@ -3692,7 +3352,7 @@
"src/globe/cesium/render.js", "src/globe/cesium/render.js",
"src/globe/cesium/surface.js", "src/globe/cesium/surface.js",
"src/globe/cesium/engineFromViewer.js", "src/globe/cesium/engineFromViewer.js",
"src/globe/navara/cameraMath.js", "src/globe/cameraMath.js",
"src/globe/navara/cameraModel.js", "src/globe/navara/cameraModel.js",
"src/globe/navara/createNavaraEngine.js", "src/globe/navara/createNavaraEngine.js",
"src/globe/navara/credits.js", "src/globe/navara/credits.js",
@@ -3722,6 +3382,8 @@
"src/data/render/cesiumTrailRenderer.js", "src/data/render/cesiumTrailRenderer.js",
"src/layers/alpr/render/cesiumPresentation.js", "src/layers/alpr/render/cesiumPresentation.js",
"src/layers/alpr/render/cesiumOverlay.js", "src/layers/alpr/render/cesiumOverlay.js",
"src/layers/alpr/overlay.js",
"src/layers/alpr/presentation.js",
"src/layers/alpr/render/cesium.js", "src/layers/alpr/render/cesium.js",
"src/layers/bikeshare/selectionModel.js", "src/layers/bikeshare/selectionModel.js",
"src/layers/bikeshare/presentation.js", "src/layers/bikeshare/presentation.js",
@@ -3742,35 +3404,16 @@
"src/layers/localAdsb/qaDrawn.js", "src/layers/localAdsb/qaDrawn.js",
"src/ui/imageryBoxNavara.js", "src/ui/imageryBoxNavara.js",
"src/layers/installations/render/cesium/namedMarkers.js", "src/layers/installations/render/cesium/namedMarkers.js",
"src/geo/index.js", "src/geo/",
"src/geo/math.js",
"src/geo/ellipsoid.js",
"src/geo/cartographic.js",
"src/geo/ellipsoidGeodesic.js",
"src/geo/boundingSphere.js",
"src/geo/cartesian2.js",
"src/geo/cartesian3.js",
"src/geo/matrix3.js",
"src/geo/matrix4.js",
"src/geo/quaternion.js",
"src/geo/transforms.js",
"src/geo/simon1994.js",
"src/geo/julianDate.js",
"src/geo/scaleToGeodeticSurface.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js",
"src/time/clock.js", "src/time/clock.js",
"src/globe/navara/overlay.js", "src/globe/navara/overlay.js",
"src/globe/navara/frameDescriptor.js", "src/globe/navara/frameDescriptor.js",
"src/globe/cesium/clock.js", "src/globe/cesium/clock.js",
"src/globe/pickDelivery.js", "src/globe/pickDelivery.js",
"src/globe/navara/clock.js", "src/globe/navara/clock.js",
"src/globe/cesium/lines.js",
"src/globe/follow.js", "src/globe/follow.js",
"src/globe/lines.js", "src/globe/lines.js",
"src/globe/navara/movers/pickIds.js", "src/globe/navara/movers/pickIds.js",
"src/globe/cesium/movers.js",
"src/globe/movers.js", "src/globe/movers.js",
"src/globe/cesium/follow.js", "src/globe/cesium/follow.js",
"src/globe/cpuPicks.js", "src/globe/cpuPicks.js",
@@ -3847,6 +3490,8 @@
"src/layers/cyclones/render/navara.js", "src/layers/cyclones/render/navara.js",
"src/layers/shared/render/groundRing.js", "src/layers/shared/render/groundRing.js",
"src/layers/bikeshare/render/navara.js", "src/layers/bikeshare/render/navara.js",
"src/layers/bikeshare/stationPoints.js",
"src/layers/shared/viewRectangle.js",
"src/layers/installations/render/navaraGeometry.js", "src/layers/installations/render/navaraGeometry.js",
"src/layers/installations/render/navaraOutlineBatch.js", "src/layers/installations/render/navaraOutlineBatch.js",
"src/layers/installations/render/navaraStencilBatch.js", "src/layers/installations/render/navaraStencilBatch.js",
@@ -3857,6 +3502,7 @@
"src/layers/radio/render/navara.js", "src/layers/radio/render/navara.js",
"src/layers/transit/render/navara.js", "src/layers/transit/render/navara.js",
"src/layers/transit/render/navaraSprites.js", "src/layers/transit/render/navaraSprites.js",
"src/layers/shared/render/navaraMoverBank.js",
"src/layers/transit/render/navaraView.js", "src/layers/transit/render/navaraView.js",
"src/layers/transit/render/navaraTrail.js", "src/layers/transit/render/navaraTrail.js",
"src/layers/recentImagery/render/navara.js", "src/layers/recentImagery/render/navara.js",
@@ -3868,14 +3514,18 @@
"src/layers/cctv/render/navaraLine.js", "src/layers/cctv/render/navaraLine.js",
"src/layers/alpr/render/navara.js", "src/layers/alpr/render/navara.js",
"src/layers/alpr/render/navaraOverlay.js", "src/layers/alpr/render/navaraOverlay.js",
"src/layers/alpr/overlay.js",
"src/layers/alpr/presentation.js",
"src/layers/radio/screenClusters.js", "src/layers/radio/screenClusters.js",
"src/layers/shared/render/navaraCellGeometry.js", "src/layers/shared/render/navaraCellGeometry.js",
"src/layers/shared/render/navaraGroundCells.js", "src/layers/shared/render/navaraGroundCells.js",
"src/layers/firms/render/navaraSprites.js", "src/layers/firms/render/navaraSprites.js",
"src/layers/shared/render/imageAtlas.js",
"src/layers/firms/render/navaraHeat.js", "src/layers/firms/render/navaraHeat.js",
"src/layers/shared/render/navaraGroundGeometry.js", "src/layers/shared/render/navaraGroundGeometry.js",
"src/layers/shared/render/navaraGroundGrid.js", "src/layers/shared/render/navaraGroundGrid.js",
"src/layers/shared/render/navaraGroundSurface.js", "src/layers/shared/render/navaraGroundSurface.js",
"src/layers/shared/render/navaraGroundEffect.js",
"src/annotations/outlineRungs.js", "src/annotations/outlineRungs.js",
"src/sources/openFreeMapOutlines.js", "src/sources/openFreeMapOutlines.js",
"src/sources/nominatimOutlines.js", "src/sources/nominatimOutlines.js",
@@ -4364,6 +4014,7 @@
"src/layers/transit/state.js", "src/layers/transit/state.js",
"src/layers/transit/testing.js", "src/layers/transit/testing.js",
"src/layers/transit/viewport.js", "src/layers/transit/viewport.js",
"src/layers/shared/viewRectangle.js",
"src/data/cameraSensitivity.js", "src/data/cameraSensitivity.js",
"src/data/contactPlayback.js", "src/data/contactPlayback.js",
"src/data/contactTrailRenderer.js", "src/data/contactTrailRenderer.js",
@@ -4528,6 +4179,7 @@
"src/globe/cesium/input.js", "src/globe/cesium/input.js",
"src/globe/cesium/convert.js", "src/globe/cesium/convert.js",
"src/globe/cesium/camera.js", "src/globe/cesium/camera.js",
"src/globe/cameraMath.js",
"src/globe/cesium/frames.js", "src/globe/cesium/frames.js",
"src/globe/cesium/picker.js", "src/globe/cesium/picker.js",
"src/globe/cesium/events.js", "src/globe/cesium/events.js",
@@ -4543,6 +4195,8 @@
"src/data/render/cesiumTrailRenderer.js", "src/data/render/cesiumTrailRenderer.js",
"src/layers/alpr/render/cesiumPresentation.js", "src/layers/alpr/render/cesiumPresentation.js",
"src/layers/alpr/render/cesiumOverlay.js", "src/layers/alpr/render/cesiumOverlay.js",
"src/layers/alpr/overlay.js",
"src/layers/alpr/presentation.js",
"src/layers/alpr/render/cesium.js", "src/layers/alpr/render/cesium.js",
"src/layers/bikeshare/selectionModel.js", "src/layers/bikeshare/selectionModel.js",
"src/layers/bikeshare/presentation.js", "src/layers/bikeshare/presentation.js",
@@ -4550,6 +4204,7 @@
"src/layers/bikeshare/render/cesium/viewport.js", "src/layers/bikeshare/render/cesium/viewport.js",
"src/layers/bikeshare/render/cesium/lifecycle.js", "src/layers/bikeshare/render/cesium/lifecycle.js",
"src/layers/bikeshare/render/cesium/rendering.js", "src/layers/bikeshare/render/cesium/rendering.js",
"src/layers/bikeshare/stationPoints.js",
"src/layers/bikeshare/render/cesium/selection.js", "src/layers/bikeshare/render/cesium/selection.js",
"src/layers/installations/render/cesium/lifecycle.js", "src/layers/installations/render/cesium/lifecycle.js",
"src/layers/installations/render/cesium.js", "src/layers/installations/render/cesium.js",
@@ -4561,24 +4216,7 @@
"src/data/render/cesiumContactTrailRenderer.js", "src/data/render/cesiumContactTrailRenderer.js",
"src/layers/localAdsb/qaDrawn.js", "src/layers/localAdsb/qaDrawn.js",
"src/layers/installations/render/cesium/namedMarkers.js", "src/layers/installations/render/cesium/namedMarkers.js",
"src/geo/index.js", "src/geo/",
"src/geo/math.js",
"src/geo/ellipsoid.js",
"src/geo/cartographic.js",
"src/geo/ellipsoidGeodesic.js",
"src/geo/boundingSphere.js",
"src/geo/cartesian2.js",
"src/geo/cartesian3.js",
"src/geo/matrix3.js",
"src/geo/matrix4.js",
"src/geo/quaternion.js",
"src/geo/transforms.js",
"src/geo/simon1994.js",
"src/geo/julianDate.js",
"src/geo/scaleToGeodeticSurface.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js",
"src/time/clock.js", "src/time/clock.js",
"src/globe/follow.js", "src/globe/follow.js",
"src/globe/cesium/follow.js", "src/globe/cesium/follow.js",
@@ -4687,24 +4325,7 @@
"src/globe/cesium/engineFromViewer.js", "src/globe/cesium/engineFromViewer.js",
"src/globe/cesium/credits.js", "src/globe/cesium/credits.js",
"src/globe/contract.js", "src/globe/contract.js",
"src/geo/index.js", "src/geo/",
"src/geo/math.js",
"src/geo/ellipsoid.js",
"src/geo/cartographic.js",
"src/geo/ellipsoidGeodesic.js",
"src/geo/boundingSphere.js",
"src/geo/cartesian2.js",
"src/geo/cartesian3.js",
"src/geo/matrix3.js",
"src/geo/matrix4.js",
"src/geo/quaternion.js",
"src/geo/transforms.js",
"src/geo/simon1994.js",
"src/geo/julianDate.js",
"src/geo/scaleToGeodeticSurface.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js",
"src/globe/surfaceDetail.js", "src/globe/surfaceDetail.js",
"src/sources/openFreeMap.js", "src/sources/openFreeMap.js",
"src/sources/vectorTiles.js", "src/sources/vectorTiles.js",
@@ -4985,24 +4606,7 @@
"src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-CN.json",
"src/i18n/locales/zh-Hant.json", "src/i18n/locales/zh-Hant.json",
"src/ui/unitLabels.js", "src/ui/unitLabels.js",
"src/geo/julianDate.js", "src/geo/"
"src/geo/simon1994.js",
"src/geo/developerError.js",
"src/geo/check.js",
"src/geo/math.js",
"src/geo/cartesian2.js",
"src/geo/cartographic.js",
"src/geo/cartesian3.js",
"src/geo/matrix3.js",
"src/geo/quaternion.js",
"src/geo/matrix4.js",
"src/geo/ellipsoid.js",
"src/geo/transforms.js",
"src/geo/ellipsoidGeodesic.js",
"src/geo/boundingSphere.js",
"src/geo/debug.js",
"src/geo/scaleToGeodeticSurface.js",
"src/geo/index.js"
], ],
"external": ["cesium"] "external": ["cesium"]
}, },
@@ -5315,6 +4919,7 @@
"src/layers/transit/testing.js", "src/layers/transit/testing.js",
"src/layers/transit/trails.js", "src/layers/transit/trails.js",
"src/layers/transit/viewport.js", "src/layers/transit/viewport.js",
"src/layers/shared/viewRectangle.js",
"src/overlays/worldOverlay.js", "src/overlays/worldOverlay.js",
"src/overlays/worldOverlayDraw.js", "src/overlays/worldOverlayDraw.js",
"src/overlays/worldOverlayTokens.js", "src/overlays/worldOverlayTokens.js",
@@ -5349,6 +4954,7 @@
"src/globe/cesium/picker.js", "src/globe/cesium/picker.js",
"src/globe/cesium/frames.js", "src/globe/cesium/frames.js",
"src/globe/cesium/camera.js", "src/globe/cesium/camera.js",
"src/globe/cameraMath.js",
"src/globe/cesium/convert.js", "src/globe/cesium/convert.js",
"src/globe/cesium/input.js", "src/globe/cesium/input.js",
"src/maps/google3d.js", "src/maps/google3d.js",
@@ -5373,24 +4979,7 @@
"src/data/render/cesiumProjection.js", "src/data/render/cesiumProjection.js",
"src/layers/shared/color.js", "src/layers/shared/color.js",
"src/layers/transit/render/cesium.js", "src/layers/transit/render/cesium.js",
"src/geo/index.js", "src/geo/",
"src/geo/math.js",
"src/geo/ellipsoid.js",
"src/geo/cartographic.js",
"src/geo/ellipsoidGeodesic.js",
"src/geo/boundingSphere.js",
"src/geo/cartesian2.js",
"src/geo/cartesian3.js",
"src/geo/matrix3.js",
"src/geo/matrix4.js",
"src/geo/quaternion.js",
"src/geo/transforms.js",
"src/geo/simon1994.js",
"src/geo/julianDate.js",
"src/geo/scaleToGeodeticSurface.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js",
"src/time/clock.js", "src/time/clock.js",
"src/globe/surfaceDetail.js", "src/globe/surfaceDetail.js",
"src/celestialRing/render/cesium.js" "src/celestialRing/render/cesium.js"
@@ -5418,6 +5007,7 @@
"src/layers/transit/state.js", "src/layers/transit/state.js",
"src/layers/transit/testing.js", "src/layers/transit/testing.js",
"src/layers/transit/viewport.js", "src/layers/transit/viewport.js",
"src/layers/shared/viewRectangle.js",
"src/data/contactPlayback.js", "src/data/contactPlayback.js",
"src/data/contactTrailRenderer.js", "src/data/contactTrailRenderer.js",
"src/data/transitProxy.js", "src/data/transitProxy.js",
@@ -5445,24 +5035,7 @@
"src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-CN.json",
"src/i18n/locales/zh-Hant.json", "src/i18n/locales/zh-Hant.json",
"src/layers/shared/color.js", "src/layers/shared/color.js",
"src/geo/index.js", "src/geo/",
"src/geo/math.js",
"src/geo/ellipsoid.js",
"src/geo/cartographic.js",
"src/geo/ellipsoidGeodesic.js",
"src/geo/boundingSphere.js",
"src/geo/cartesian2.js",
"src/geo/cartesian3.js",
"src/geo/matrix3.js",
"src/geo/matrix4.js",
"src/geo/quaternion.js",
"src/geo/transforms.js",
"src/geo/simon1994.js",
"src/geo/julianDate.js",
"src/geo/scaleToGeodeticSurface.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js",
"src/globe/contract.js" "src/globe/contract.js"
], ],
"external": ["cesium", "pbf"] "external": ["cesium", "pbf"]
@@ -5506,9 +5079,6 @@
"exports": ["./layers/perimeters"], "exports": ["./layers/perimeters"],
"modules": [ "modules": [
"src/geo/cartesian3.js", "src/geo/cartesian3.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js",
"src/geo/math.js", "src/geo/math.js",
"src/layers/perimeters/index.js", "src/layers/perimeters/index.js",
"src/layers/perimeters/render/cesium.js", "src/layers/perimeters/render/cesium.js",
@@ -5611,9 +5181,6 @@
"src/layers/ghostMaps/kml.js", "src/layers/ghostMaps/kml.js",
"src/layers/ghostMaps/source.js", "src/layers/ghostMaps/source.js",
"src/geo/cartesian3.js", "src/geo/cartesian3.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js",
"src/geo/math.js", "src/geo/math.js",
"src/sources/httpBody.js", "src/sources/httpBody.js",
"src/data/geoFileImport.js", "src/data/geoFileImport.js",
@@ -5663,24 +5230,7 @@
"exports": ["./layers/drone-airspace"], "exports": ["./layers/drone-airspace"],
"modules": [ "modules": [
"src/layers/droneAirspace/index.js", "src/layers/droneAirspace/index.js",
"src/geo/index.js", "src/geo/",
"src/geo/ellipsoid.js",
"src/geo/scaleToGeodeticSurface.js",
"src/geo/debug.js",
"src/geo/ellipsoidGeodesic.js",
"src/geo/boundingSphere.js",
"src/geo/transforms.js",
"src/geo/matrix4.js",
"src/geo/cartesian3.js",
"src/geo/quaternion.js",
"src/geo/math.js",
"src/geo/matrix3.js",
"src/geo/cartographic.js",
"src/geo/cartesian2.js",
"src/geo/check.js",
"src/geo/developerError.js",
"src/geo/simon1994.js",
"src/geo/julianDate.js",
"src/layers/shared/render/cesiumZones.js", "src/layers/shared/render/cesiumZones.js",
"src/sources/httpBody.js", "src/sources/httpBody.js",
"src/layers/droneAirspace/source.js", "src/layers/droneAirspace/source.js",
@@ -5769,24 +5319,7 @@
"src/tools/queries/panelRequest.js", "src/tools/queries/panelRequest.js",
"src/geoDistance.js", "src/geoDistance.js",
"src/visualStylePolicy.js", "src/visualStylePolicy.js",
"src/geo/boundingSphere.js", "src/geo/"
"src/geo/cartesian2.js",
"src/geo/cartesian3.js",
"src/geo/cartographic.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js",
"src/geo/ellipsoid.js",
"src/geo/ellipsoidGeodesic.js",
"src/geo/index.js",
"src/geo/julianDate.js",
"src/geo/math.js",
"src/geo/matrix3.js",
"src/geo/matrix4.js",
"src/geo/quaternion.js",
"src/geo/scaleToGeodeticSurface.js",
"src/geo/simon1994.js",
"src/geo/transforms.js"
], ],
"external": ["satellite.js"] "external": ["satellite.js"]
}, },
@@ -5898,9 +5431,6 @@
"exports": ["./layers/air-quality"], "exports": ["./layers/air-quality"],
"modules": [ "modules": [
"src/geo/cartesian3.js", "src/geo/cartesian3.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js",
"src/geo/math.js", "src/geo/math.js",
"src/layers/airQuality/index.js", "src/layers/airQuality/index.js",
"src/layers/airQuality/render/cesium.js", "src/layers/airQuality/render/cesium.js",
@@ -5946,24 +5476,7 @@
"src/loadingFeedback.js", "src/loadingFeedback.js",
"src/data/installationFeedback.js", "src/data/installationFeedback.js",
"src/i18n/marker.js", "src/i18n/marker.js",
"src/geo/boundingSphere.js", "src/geo/"
"src/geo/cartesian2.js",
"src/geo/cartesian3.js",
"src/geo/cartographic.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js",
"src/geo/ellipsoid.js",
"src/geo/ellipsoidGeodesic.js",
"src/geo/index.js",
"src/geo/julianDate.js",
"src/geo/math.js",
"src/geo/matrix3.js",
"src/geo/matrix4.js",
"src/geo/quaternion.js",
"src/geo/scaleToGeodeticSurface.js",
"src/geo/simon1994.js",
"src/geo/transforms.js"
], ],
"external": ["cesium"] "external": ["cesium"]
} }
+41 -57
View File
@@ -83,42 +83,28 @@ try {
// Every name the checkout's .env files define, plus key-like names in this // Every name the checkout's .env files define, plus key-like names in this
// environment: all blank for the build and the preview. // environment: all blank for the build and the preview.
const KEY_LIKE = /(_KEY|_TOKEN|_SECRET|_CLIENT_ID|PASSWORD)$/; const KEY_LIKE = /(_KEY|_TOKEN|_SECRET|_CLIENT_ID|PASSWORD)$/;
const dotenvNames = () => { /** The [name, value] pairs of the checkout's .env and .env.local, in order. */
const names = new Set(); const dotenv = () =>
for (const file of ['.env', '.env.local']) ['.env', '.env.local']
if (existsSync(join(root, file))) .filter((file) => existsSync(join(root, file)))
for (const line of readFileSync(join(root, file), 'utf8').split('\n')) { .flatMap((file) => readFileSync(join(root, file), 'utf8').split('\n'))
const name = line.match(/^\s*([A-Z0-9_]+)\s*=/)?.[1]; .map((line) => line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*)/))
if (name) names.add(name); .filter(Boolean)
} .map(([, name, raw]) => [name, raw.trim().replace(/^["']|["']$/g, '')]);
return [...names].sort(); const dotenvNames = () => [...new Set(dotenv().map(([name]) => name))].sort();
}; const envNames = () =>
const envNames = () => { [
const names = new Set(); ...new Set([
for (const file of ['.env', '.env.local']) ...dotenvNames(),
if (existsSync(join(root, file))) ...Object.keys(process.env).filter((name) => KEY_LIKE.test(name)),
for (const line of readFileSync(join(root, file), 'utf8').split('\n')) { ]),
const name = line.match(/^\s*([A-Z0-9_]+)\s*=/)?.[1]; ].sort();
if (name) names.add(name); // Credentials only (model names and URLs are settings the code may also
} // carry as defaults).
for (const name of Object.keys(process.env)) const envValues = () =>
if (KEY_LIKE.test(name)) names.add(name); dotenv()
return [...names].sort(); .filter(([name, value]) => KEY_LIKE.test(name) && value.length >= 8)
}; .map(([, value]) => value);
const envValues = () => {
const values = [];
for (const file of ['.env', '.env.local'])
if (existsSync(join(root, file)))
for (const line of readFileSync(join(root, file), 'utf8').split('\n')) {
const [, name, raw] = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*)$/) ?? [];
const value = raw?.trim().replace(/^["']|["']$/g, '');
// Credentials only (model names and URLs are settings the code may
// also carry as defaults).
if (KEY_LIKE.test(name ?? '') && value && value.length >= 8)
values.push(value);
}
return values;
};
const scrubbed = () => ({ const scrubbed = () => ({
...process.env, ...process.env,
...Object.fromEntries(envNames().map((name) => [name, ''])), ...Object.fromEntries(envNames().map((name) => [name, ''])),
@@ -368,9 +354,9 @@ const runs = [
['rocket-launches'], ['rocket-launches'],
['--missions'], ['--missions'],
], ],
// Stage 3's own checks (one application per page, voice across a switch). // One application per page, and voice across a switch: these block paid
// They block paid routes themselves but do not run under the strict // routes themselves but do not run under the strict harness, so they add
// harness, so they add no assertion manifest and no network ledger. // no assertion manifest and no network ledger.
['app-once', 'app-once', [], [], 600, true], ['app-once', 'app-once', [], [], 600, true],
['voice-engine-switch', 'voice-engine-switch', [], [], 300, true], ['voice-engine-switch', 'voice-engine-switch', [], [], 300, true],
['strict-rapid-switch', 'strict-rapid-switch', []], ['strict-rapid-switch', 'strict-rapid-switch', []],
@@ -379,14 +365,18 @@ const runs = [
['strict-failure-matrix', 'strict-failure-matrix', [], [], 600], ['strict-failure-matrix', 'strict-failure-matrix', [], [], 600],
['strict-cold-chunks', 'strict-cold-chunks', []], ['strict-cold-chunks', 'strict-cold-chunks', []],
['strict-prod-smoke', 'strict-prod-smoke', []], ['strict-prod-smoke', 'strict-prod-smoke', []],
// Stage 6: the app's visual styles, bloom, contact sonar, cockpit clouds // The app's visual styles, bloom, contact sonar, cockpit clouds, and the
// and annotations drawn on Navara (annotations is a tool, not a catalog // annotations, Draw, Import and the scope mask with the celestial ring on
// layer: TOOL_IDS). // Navara (those are tools and display overlays, not catalog layers:
// TOOL_IDS).
['navara-visual-styles', 'navara-visual-styles', []], ['navara-visual-styles', 'navara-visual-styles', []],
['navara-bloom', 'navara-bloom', []], ['navara-bloom', 'navara-bloom', []],
['navara-sonar', 'navara-sonar', []], ['navara-sonar', 'navara-sonar', []],
['navara-cockpit-clouds', 'navara-cockpit-clouds', []], ['navara-cockpit-clouds', 'navara-cockpit-clouds', []],
['navara-annotations', 'navara-annotations', ['annotations']], ['navara-annotations', 'navara-annotations', ['annotations']],
['navara-draw', 'navara-draw', ['draw']],
['navara-geo-import', 'navara-geo-import', ['geo-import']],
['navara-scope', 'navara-scope', ['scope-mask', 'celestial-ring']],
['strict-soak', 'strict-soak', [], soakRuns, 1200], ['strict-soak', 'strict-soak', [], soakRuns, 1200],
[ [
'strict-soak--long', 'strict-soak--long',
@@ -398,7 +388,13 @@ const runs = [
]; ];
// Ids a scenario covers that are tools, not catalog layers: their // Ids a scenario covers that are tools, not catalog layers: their
// assertions are recorded, but the catalog comparison leaves them out. // assertions are recorded, but the catalog comparison leaves them out.
const TOOL_IDS = new Set(['annotations']); const TOOL_IDS = new Set([
'annotations',
'draw',
'geo-import',
'scope-mask',
'celestial-ring',
]);
const scenarioIds = [ const scenarioIds = [
...new Set( ...new Set(
runs.flatMap(([, , ids]) => ids).filter((id) => !TOOL_IDS.has(id)), runs.flatMap(([, , ids]) => ids).filter((id) => !TOOL_IDS.has(id)),
@@ -414,9 +410,7 @@ const liveCatalog = {};
layers: [], layers: [],
name: 'catalog', name: 'catalog',
}); });
const browser = await strict.launch({ const browser = await strict.launch();
args: ['--use-angle=metal', '--enable-gpu'],
});
try { try {
for (const engine of ENGINES) { for (const engine of ENGINES) {
const page = await browser.newPage(); const page = await browser.newPage();
@@ -497,17 +491,7 @@ for (const [label, name, layers, extra = [], seconds = 300, external] of runs) {
const script = 'scripts/qa-' + name + '.mjs'; const script = 'scripts/qa-' + name + '.mjs';
const evidence = join(output, 'evidence', label + '.json'); const evidence = join(output, 'evidence', label + '.json');
rmSync(evidence, { force: true }); rmSync(evidence, { force: true });
const args = const args = [base.origin, ...extra];
name === 'w2-imagery'
? [
'--url',
base.origin + '/?engine=navara',
'--report',
join(output, 'evidence', 'imagery-report.json'),
'--switch',
'--preview',
]
: [base.origin, ...extra];
if (external) args.splice(0, 1, base.origin + '/'); if (external) args.splice(0, 1, base.origin + '/');
else args.push(`--layers=${layers.join(',')}`, `--evidence=${evidence}`); else args.push(`--layers=${layers.join(',')}`, `--evidence=${evidence}`);
const start = Date.now(); const start = Date.now();
+1 -18
View File
@@ -221,7 +221,7 @@ try {
await new Promise((resolve) => setTimeout(resolve, 2000)); await new Promise((resolve) => setTimeout(resolve, 2000));
report.firstRoundTrip = await listenerCounts(page, { bySource: true }); report.firstRoundTrip = await listenerCounts(page, { bySource: true });
} }
// Both engines draw the session's marks and files (Navara since Stage 6). // Both engines draw the session's marks and files (Navara included).
await page.waitForFunction( await page.waitForFunction(
() => window.__gevGeoImport.diagnostics().drawn === 1, () => window.__gevGeoImport.diagnostics().drawn === 1,
{ timeout: 30_000 }, { timeout: 30_000 },
@@ -412,23 +412,6 @@ try {
report.errors = errors; report.errors = errors;
check(errors.length === 0, 'page errors', errors); check(errors.length === 0, 'page errors', errors);
// The legacy route (?switch=legacy) still builds an application per
// engine, for one release.
const legacy = await openSessionPage(
browser,
sessionAddress(base, { engine: 'cesium', extra: { switch: 'legacy' } }),
);
await waitReady(legacy.page, 'cesium');
await switchEngine(legacy.page, 'navara');
await switchEngine(legacy.page, 'cesium');
report.legacy = await readSession(legacy.page);
check(
report.legacy.counters.applications === 3,
'the legacy route builds an application per engine',
report.legacy.counters,
);
check(legacy.errors.length === 0, 'legacy page errors', legacy.errors);
await legacy.page.close();
console.log(JSON.stringify(report, null, 2)); console.log(JSON.stringify(report, null, 2));
if (!reportOnly) console.log('PASS application created once; session kept'); if (!reportOnly) console.log('PASS application created once; session kept');
} finally { } finally {
+1 -1
View File
@@ -45,7 +45,7 @@ export function isExpectedPreviewSetupError(
} }
} }
// W1 acceptance shares one strict runtime verdict across its main page and // The runtime evidence gate shares one strict runtime verdict across its main page and
// every share-link page. Response diagnostics never retain query credentials. // every share-link page. Response diagnostics never retain query credentials.
export function watchW1Page(page, report, { name, preview = false, baseUrl }) { export function watchW1Page(page, report, { name, preview = false, baseUrl }) {
const errors = (report.browserErrors ??= []); const errors = (report.browserErrors ??= []);
+1 -1
View File
@@ -37,7 +37,7 @@ const { stdout: video } = await promisify(execFile)(
{ encoding: 'buffer', maxBuffer: 16000000 }, { encoding: 'buffer', maxBuffer: 16000000 },
); );
const strict = createStrict({ origin: url.origin, layers: ['cctv'] }); const strict = createStrict({ origin: url.origin, layers: ['cctv'] });
await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => { await strict.run({}, async (b) => {
const p = await b.newPage(); const p = await b.newPage();
const errors = []; const errors = [];
p.on('pageerror', (e) => { p.on('pageerror', (e) => {
+2 -2
View File
@@ -42,7 +42,7 @@
* *
* This legacy harness is the geometry, lifecycle and real-input oracle. * This legacy harness is the geometry, lifecycle and real-input oracle.
* Migration runs use headless Metal and strict runtime error observation. * Migration runs use headless Metal and strict runtime error observation.
* Screenshots remain visual-review evidence here; the W2 companion must * Screenshots remain visual-review evidence here; a separate pixel gate must
* independently prove actual marker, image-plane and video pixels. * independently prove actual marker, image-plane and video pixels.
* *
* No `cesium` package import for in-page math: the app does not expose a * No `cesium` package import for in-page math: the app does not expose a
@@ -2245,7 +2245,7 @@ async function main() {
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
// Screenshots supplement this state/geometry oracle. Positive rendered // Screenshots supplement this state/geometry oracle. Positive rendered
// media pixels are a separate mandatory W2 companion gate. // media pixels are a separate mandatory pixel gate.
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
await sleep(1000); await sleep(1000);
const shot = path.join(SHOTS_DIR, 'cctv-v2-active.png'); const shot = path.join(SHOTS_DIR, 'cctv-v2-active.png');
+10 -26
View File
@@ -1,6 +1,11 @@
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import { fromDegrees } from '../src/geo/cartesian3.js'; import { fromDegrees } from '../src/geo/cartesian3.js';
import { createStrict, localPreview } from './qa-strict-harness.mjs'; import {
createStrict,
localPreview,
openPage,
waitForEngine,
} from './qa-strict-harness.mjs';
const base = localPreview(process.argv[2]); const base = localPreview(process.argv[2]);
base.search = '?engine=navara&welcome=0&look=cinematic'; base.search = '?engine=navara&welcome=0&look=cinematic';
@@ -10,14 +15,8 @@ const strict = createStrict({
origin: base.origin, origin: base.origin,
layers: ['military-awareness', 'military-installations'], layers: ['military-awareness', 'military-installations'],
}); });
await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => { await strict.run({}, async (b) => {
const p = await b.newPage(); const { page: p, errors } = await openPage(b);
await p.setViewport({ width: 1024, height: 768 });
const errors = [];
p.on('pageerror', (e) => {
errors.push(e.message);
console.log('error', e.message.split('?')[0]);
});
await p.evaluateOnNewDocument(() => { await p.evaluateOnNewDocument(() => {
const fetch = window.fetch.bind(window); const fetch = window.fetch.bind(window);
window.fetch = (input, options) => { window.fetch = (input, options) => {
@@ -89,21 +88,7 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
); );
}); });
await p.goto(base.href); await p.goto(base.href);
const ready = async (kind) => { await waitForEngine(p, 'navara');
await p.waitForFunction(
(k) =>
window.__godsEyeView?.engine?.kind === k &&
!document.querySelector('#globe-engine')?.disabled &&
!document.querySelector('.engine-switch-frame') &&
document.querySelector('#loading-screen.hidden'),
{ timeout: 90000 },
kind,
);
await p.evaluate(
() => window.__godsEyeView.styleManager.initialRestorePromise,
);
};
await ready('navara');
await p.evaluate(async () => { await p.evaluate(async () => {
const { dataManager, styleManager, tileset } = window.__godsEyeView; const { dataManager, styleManager, tileset } = window.__godsEyeView;
if (tileset || window.__GOOGLE_MAPS_API_KEY__) if (tileset || window.__GOOGLE_MAPS_API_KEY__)
@@ -235,8 +220,7 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
baseline, baseline,
); );
for (const kind of ['cesium', 'navara']) { for (const kind of ['cesium', 'navara']) {
await strict.pick(p, kind); await strict.switchTo(p, kind);
await ready(kind);
const current = await sample(); const current = await sample();
console.log('handoff', kind, JSON.stringify(current)); console.log('handoff', kind, JSON.stringify(current));
assert.deepEqual(current, { assert.deepEqual(current, {
+11 -28
View File
@@ -1,19 +1,18 @@
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import { createStrict, localPreview } from './qa-strict-harness.mjs'; import {
createStrict,
localPreview,
openPage,
waitForEngine,
} from './qa-strict-harness.mjs';
const base = localPreview(process.argv[2]); const base = localPreview(process.argv[2]);
base.search = '?engine=navara&welcome=0&look=cinematic'; base.search = '?engine=navara&welcome=0&look=cinematic';
base.hash = base.hash =
'v=2&lat=55.3241&lon=10.7995&alt=25000&heading=0&pitch=-90&roll=0&map=sentinel2&l='; 'v=2&lat=55.3241&lon=10.7995&alt=25000&heading=0&pitch=-90&roll=0&map=sentinel2&l=';
const strict = createStrict({ origin: base.origin, layers: ['directions'] }); const strict = createStrict({ origin: base.origin, layers: ['directions'] });
await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => { await strict.run({}, async (b) => {
const p = await b.newPage(); const { page: p, errors } = await openPage(b);
await p.setViewport({ width: 1024, height: 768 });
const errors = [];
p.on('pageerror', (e) => {
errors.push(e.message);
console.log('error', e.message.split('?')[0]);
});
let routeReads = 0, let routeReads = 0,
release; release;
await p.setRequestInterception(true); await p.setRequestInterception(true);
@@ -55,21 +54,7 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
}); });
}); });
await p.goto(base.href); await p.goto(base.href);
const ready = async (kind) => { await waitForEngine(p, 'navara');
await p.waitForFunction(
(k) =>
window.__godsEyeView?.engine?.kind === k &&
!document.querySelector('#globe-engine')?.disabled &&
!document.querySelector('.engine-switch-frame') &&
document.querySelector('#loading-screen.hidden'),
{ timeout: 90000 },
kind,
);
await p.evaluate(
() => window.__godsEyeView.styleManager.initialRestorePromise,
);
};
await ready('navara');
await p.evaluate(async () => { await p.evaluate(async () => {
const { tileset, dataManager } = window.__godsEyeView; const { tileset, dataManager } = window.__godsEyeView;
if (tileset || window.__GOOGLE_MAPS_API_KEY__) if (tileset || window.__GOOGLE_MAPS_API_KEY__)
@@ -91,8 +76,7 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
'directions', 'directions',
'A and B set by clicks on the globe; one route request', 'A and B set by clicks on the globe; one route request',
); );
await strict.pick(p, 'cesium'); await strict.switchTo(p, 'cesium');
await ready('cesium');
release(); release();
await p.waitForFunction(() => window.qaRoute.getStats().count === 3); await p.waitForFunction(() => window.qaRoute.getStats().count === 3);
await p.waitForFunction(() => { await p.waitForFunction(() => {
@@ -105,8 +89,7 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
'the route asked for on Navara arrives on Cesium; a step is selectable', 'the route asked for on Navara arrives on Cesium; a step is selectable',
); );
const transfer = async (kind) => { const transfer = async (kind) => {
await strict.pick(p, kind); await strict.switchTo(p, kind);
await ready(kind);
assert.equal( assert.equal(
await p.evaluate( await p.evaluate(
() => () =>
+1 -1
View File
@@ -135,7 +135,7 @@ const strict = createStrict({
layers: [], layers: [],
fixtures: ['https://earthquake.usgs.gov/*'], fixtures: ['https://earthquake.usgs.gov/*'],
}); });
await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => { await strict.run({}, async (b) => {
const p = await b.newPage(); const p = await b.newPage();
await p.setViewport({ width: 1280, height: 900 }); await p.setViewport({ width: 1280, height: 900 });
const errors = []; const errors = [];
+9 -25
View File
@@ -7,9 +7,7 @@ base.search = '?welcome=0&look=cinematic';
base.hash = base.hash =
'v=2&lat=42.3554&lon=-71.0605&alt=6000&heading=0&pitch=-90&map=osm&l=e'; 'v=2&lat=42.3554&lon=-71.0605&alt=6000&heading=0&pitch=-90&map=osm&l=e';
const strict = createStrict({ origin: base.origin, layers: ['earthquakes'] }); const strict = createStrict({ origin: base.origin, layers: ['earthquakes'] });
await strict.run( await strict.run({}, async (browser) => {
{ args: ['--use-angle=metal', '--enable-gpu'] },
async (browser) => {
const page = await browser.newPage(); const page = await browser.newPage();
await page.evaluateOnNewDocument(() => { await page.evaluateOnNewDocument(() => {
localStorage.setItem('gev:first-run-mission:v1', 'suppressed'); localStorage.setItem('gev:first-run-mission:v1', 'suppressed');
@@ -33,9 +31,7 @@ await strict.run(
}; };
window.fetch = (url, options) => { window.fetch = (url, options) => {
if ( if (
String(url).startsWith( String(url).startsWith('https://earthquake.usgs.gov/earthquakes/feed/')
'https://earthquake.usgs.gov/earthquakes/feed/',
)
) )
return Promise.resolve( return Promise.resolve(
Response.json({ Response.json({
@@ -69,9 +65,7 @@ await strict.run(
window.__godsEyeView?.engine?.kind === kind && window.__godsEyeView?.engine?.kind === kind &&
document.querySelector('#globe-engine')?.value === kind && document.querySelector('#globe-engine')?.value === kind &&
!document.querySelector('#globe-engine').disabled && !document.querySelector('#globe-engine').disabled &&
document document.getElementById('loading-screen')?.classList.contains('hidden'),
.getElementById('loading-screen')
?.classList.contains('hidden'),
{ timeout: 90000 }, { timeout: 90000 },
kind, kind,
); );
@@ -128,8 +122,7 @@ await strict.run(
on, on,
); );
} }
const stored = () => const stored = () => page.evaluate(() => localStorage.getItem('gev.engine'));
page.evaluate(() => localStorage.getItem('gev.engine'));
async function switchTo(kind) { async function switchTo(kind) {
if ( if (
await page.$eval( await page.$eval(
@@ -164,9 +157,7 @@ await strict.run(
}); });
await strict.pick(page, kind); await strict.pick(page, kind);
await ready(kind); await ready(kind);
assert.ok( assert.ok(await page.evaluate(() => window.__switchDocument === document));
await page.evaluate(() => window.__switchDocument === document),
);
assert.ok( assert.ok(
await page.evaluate(() => { await page.evaluate(() => {
window.__switchObserver.disconnect(); window.__switchObserver.disconnect();
@@ -221,8 +212,7 @@ await strict.run(
assert.ok(Math.abs(Number(hash.get('alt')) - 6000) < 30); assert.ok(Math.abs(Number(hash.get('alt')) - 6000) < 30);
const view = await page.evaluate(() => { const view = await page.evaluate(() => {
const { engine, mapStackController } = window.__godsEyeView; const { engine, mapStackController } = window.__godsEyeView;
const { latitude, longitude, height } = const { latitude, longitude, height } = engine.camera.pose().cartographic;
engine.camera.pose().cartographic;
return { return {
latitude: latitude * (180 / Math.PI), latitude: latitude * (180 / Math.PI),
longitude: longitude * (180 / Math.PI), longitude: longitude * (180 / Math.PI),
@@ -345,11 +335,7 @@ await strict.run(
'earthquakes', 'earthquakes',
'Forward restores the disabled layer on the same model', 'Forward restores the disabled layer on the same model',
); );
assert.equal( assert.equal(await settledAddress(), cesiumLayers, 'Forward keeps its entry');
await settledAddress(),
cesiumLayers,
'Forward keeps its entry',
);
await page.evaluate(() => await page.evaluate(() =>
window.__godsEyeView.dataManager.setEnabled('earthquakes', true, { window.__godsEyeView.dataManager.setEnabled('earthquakes', true, {
origin: 'user', origin: 'user',
@@ -384,8 +370,7 @@ await strict.run(
}); });
const session = () => const session = () =>
page.evaluate(() => { page.evaluate(() => {
const { dataManager, styleManager, sceneDirector } = const { dataManager, styleManager, sceneDirector } = window.__godsEyeView;
window.__godsEyeView;
const { director, context, vessels } = window.qaSession; const { director, context, vessels } = window.qaSession;
return { return {
director: director === sceneDirector && !director._destroyed, director: director === sceneDirector && !director._destroyed,
@@ -480,5 +465,4 @@ await strict.run(
console.log( console.log(
'PASS Cesium default, both switches, same document, warm engine identity, parked canvas, no startup splash, share pose/map/layers, Back/Forward, remembered engine, blocked preference storage', 'PASS Cesium default, both switches, same document, warm engine identity, parked canvas, no startup splash, share pose/map/layers, Back/Forward, remembered engine, blocked preference storage',
); );
}, });
);
+1 -1
View File
@@ -67,7 +67,7 @@ const getOpt = (name, dflt) => {
}; };
const BASE_URL = getOpt('--url', 'http://localhost:4173'); const BASE_URL = getOpt('--url', 'http://localhost:4173');
// --engine=cesium|navara (phase 3): Navara reads the layer through the QA // --engine=cesium|navara: Navara reads the layer through the QA
// probe (window.__godsEyeView.layers.drawn) and moves the engine camera. // probe (window.__godsEyeView.layers.drawn) and moves the engine camera.
const ENGINE = (argv.find((arg) => arg.startsWith('--engine=')) ?? '--engine=cesium').slice(9); const ENGINE = (argv.find((arg) => arg.startsWith('--engine=')) ?? '--engine=cesium').slice(9);
const APP_URL = const APP_URL =
+1 -1
View File
@@ -21,7 +21,7 @@ const strict = createStrict({
// installAlprFixture answers the camera tiles. // installAlprFixture answers the camera tiles.
fixtures: ['https://tiles.dontgetflocked.com/*'], fixtures: ['https://tiles.dontgetflocked.com/*'],
}); });
await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => { await strict.run({}, async (b) => {
const p = await b.newPage(); const p = await b.newPage();
await p.setViewport({ width: 1280, height: 900 }); await p.setViewport({ width: 1280, height: 900 });
const errors = []; const errors = [];
+2 -2
View File
@@ -11,7 +11,7 @@ import fs from 'node:fs';
// QA_BASE_URL matches the sibling harnesses (qa-height-datum / qa-cctv-v2) so a // 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. // secondary checkout can verify against its own dev server instead of the default :4173.
const BASE_URL = process.env.QA_BASE_URL || 'http://localhost:4173'; const BASE_URL = process.env.QA_BASE_URL || 'http://localhost:4173';
// --engine=cesium|navara (phase 3): on Navara the camera, the mesh probes and // --engine=cesium|navara: on Navara the camera, the mesh probes and
// the sprite census go through the engine and the QA probe. // the sprite census go through the engine and the QA probe.
const ENGINE = (process.argv.find((arg) => arg.startsWith('--engine=')) ?? '--engine=cesium').slice(9); const ENGINE = (process.argv.find((arg) => arg.startsWith('--engine=')) ?? '--engine=cesium').slice(9);
const APP_URL = const APP_URL =
@@ -125,7 +125,7 @@ const report = await page.evaluate(async () => {
drawnBy: modelAt ? 'model' : 'sprite', drawnBy: modelAt ? 'model' : 'sprite',
meshM: meshH != null ? +meshH.toFixed(1) : null, meshM: meshH != null ? +meshH.toFixed(1) : null,
aboveMeshM: meshH != null ? +(c.height - meshH).toFixed(1) : null, aboveMeshM: meshH != null ? +(c.height - meshH).toFixed(1) : null,
// Why a contact sits where it does (10b review 6): the probe's own // Why a contact sits where it does: the probe's own
// answer (source, tile size) and the coarse DEM floor (the prior). // answer (source, tile size) and the coarse DEM floor (the prior).
probe: answer ? { source: answer.source, tileSizeM: answer.tileSizeM ?? null } : null, probe: answer ? { source: answer.source, tileSizeM: answer.tileSizeM ?? null } : null,
demFloorM: cachedGroundFloor(c.latitude * 180 / Math.PI, c.longitude * 180 / Math.PI) ?? null, demFloorM: cachedGroundFloor(c.latitude * 180 / Math.PI, c.longitude * 180 / Math.PI) ?? null,
+10 -26
View File
@@ -1,21 +1,20 @@
import { fromDegrees } from '../src/geo/cartesian3.js'; import { fromDegrees } from '../src/geo/cartesian3.js';
import { readFile } from 'node:fs/promises'; import { readFile } from 'node:fs/promises';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import { createStrict, localPreview } from './qa-strict-harness.mjs'; import {
createStrict,
localPreview,
openPage,
waitForEngine,
} from './qa-strict-harness.mjs';
const base = localPreview(process.argv[2]); const base = localPreview(process.argv[2]);
base.search = '?engine=navara&welcome=0&look=cinematic'; base.search = '?engine=navara&welcome=0&look=cinematic';
base.hash = base.hash =
'v=2&lat=43.6500&lon=-79.3800&alt=46000&heading=0&pitch=-90&roll=0&map=sentinel2&l='; 'v=2&lat=43.6500&lon=-79.3800&alt=46000&heading=0&pitch=-90&roll=0&map=sentinel2&l=';
const strict = createStrict({ origin: base.origin, layers: ['s2-ghostmaps'] }); const strict = createStrict({ origin: base.origin, layers: ['s2-ghostmaps'] });
await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => { await strict.run({}, async (b) => {
const p = await b.newPage(); const { page: p, errors } = await openPage(b);
await p.setViewport({ width: 1024, height: 768 });
const errors = [];
p.on('pageerror', (e) => {
errors.push(e.message);
console.log('error', e.message.split('?')[0]);
});
const file = await readFile( const file = await readFile(
new URL('./fixtures/ghostmaps-scenes.kmz', import.meta.url), new URL('./fixtures/ghostmaps-scenes.kmz', import.meta.url),
); );
@@ -63,21 +62,7 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
[...file], [...file],
); );
await p.goto(base.href); await p.goto(base.href);
const ready = async (kind) => { await waitForEngine(p, 'navara');
await p.waitForFunction(
(k) =>
window.__godsEyeView?.engine?.kind === k &&
!document.querySelector('#globe-engine')?.disabled &&
!document.querySelector('.engine-switch-frame') &&
document.querySelector('#loading-screen.hidden'),
{ timeout: 90000 },
kind,
);
await p.evaluate(
() => window.__godsEyeView.styleManager.initialRestorePromise,
);
};
await ready('navara');
await p.evaluate(async () => { await p.evaluate(async () => {
const { dataManager } = window.__godsEyeView; const { dataManager } = window.__godsEyeView;
await dataManager.setEnabled('s2-ghostmaps', true, { origin: 'user' }); await dataManager.setEnabled('s2-ghostmaps', true, { origin: 'user' });
@@ -111,8 +96,7 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
await p.evaluate(() => { await p.evaluate(() => {
window.ghostStats.titles = []; window.ghostStats.titles = [];
}); });
await strict.pick(p, kind); await strict.switchTo(p, kind);
await ready(kind);
await p.waitForFunction(() => await p.waitForFunction(() =>
window.ghostStats.titles.some((v) => /QA zone/i.test(v)), window.ghostStats.titles.some((v) => /QA zone/i.test(v)),
); );
+12 -30
View File
@@ -1,7 +1,12 @@
import { fromDegrees } from '../src/geo/cartesian3.js'; import { fromDegrees } from '../src/geo/cartesian3.js';
import sharp from 'sharp'; import sharp from 'sharp';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import { createStrict, localPreview } from './qa-strict-harness.mjs'; import {
createStrict,
localPreview,
openPage,
waitForEngine,
} from './qa-strict-harness.mjs';
const base = localPreview(process.argv[2]); const base = localPreview(process.argv[2]);
base.search = '?engine=navara&welcome=0&look=cinematic'; base.search = '?engine=navara&welcome=0&look=cinematic';
@@ -16,14 +21,8 @@ const strict = createStrict({
'fire-perimeters', 'fire-perimeters',
], ],
}); });
await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => { await strict.run({}, async (b) => {
const p = await b.newPage(); const { page: p, errors } = await openPage(b);
await p.setViewport({ width: 1024, height: 768 });
const errors = [];
p.on('pageerror', (e) => {
errors.push(e.message);
console.log('error', e.message.split('?')[0]);
});
await p.evaluateOnNewDocument(() => { await p.evaluateOnNewDocument(() => {
const fetch = window.fetch.bind(window); const fetch = window.fetch.bind(window);
window.fetch = (input, o) => { window.fetch = (input, o) => {
@@ -122,20 +121,6 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
}; };
}); });
await p.goto(base.href); await p.goto(base.href);
const ready = async (kind) => {
await p.waitForFunction(
(k) =>
window.__godsEyeView?.engine?.kind === k &&
!document.querySelector('#globe-engine')?.disabled &&
!document.querySelector('.engine-switch-frame') &&
document.querySelector('#loading-screen.hidden'),
{ timeout: 90000 },
kind,
);
await p.evaluate(
() => window.__godsEyeView.styleManager.initialRestorePromise,
);
};
const capture = async (visible, layerId = 'air-quality') => { const capture = async (visible, layerId = 'air-quality') => {
await p.evaluate( await p.evaluate(
async (show, id) => { async (show, id) => {
@@ -230,7 +215,7 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
} }
return on; return on;
}; };
await ready('navara'); await waitForEngine(p, 'navara');
await p.evaluate(() => { await p.evaluate(() => {
const d = window.__godsEyeView; const d = window.__godsEyeView;
if (d.tileset || window.__GOOGLE_MAPS_API_KEY__) if (d.tileset || window.__GOOGLE_MAPS_API_KEY__)
@@ -240,8 +225,7 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
}); });
for (const kind of ['navara', 'cesium', 'navara']) { for (const kind of ['navara', 'cesium', 'navara']) {
if ((await p.evaluate(() => window.__godsEyeView.engine.kind)) !== kind) { if ((await p.evaluate(() => window.__godsEyeView.engine.kind)) !== kind) {
await strict.pick(p, kind); await strict.switchTo(p, kind);
await ready(kind);
const same = await p.evaluate( const same = await p.evaluate(
() => () =>
window.__godsEyeView.dataManager.layers.get('air-quality').module === window.__godsEyeView.dataManager.layers.get('air-quality').module ===
@@ -357,8 +341,7 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
}; };
for (const kind of ['navara', 'cesium', 'navara']) { for (const kind of ['navara', 'cesium', 'navara']) {
if ((await p.evaluate(() => window.__godsEyeView.engine.kind)) !== kind) { if ((await p.evaluate(() => window.__godsEyeView.engine.kind)) !== kind) {
await strict.pick(p, kind); await strict.switchTo(p, kind);
await ready(kind);
await card(true); await card(true);
await strict.check( await strict.check(
p, p,
@@ -478,8 +461,7 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
let histories = await p.evaluate(() => window.qaVesselHistories); let histories = await p.evaluate(() => window.qaVesselHistories);
for (const kind of ['navara', 'cesium', 'navara']) { for (const kind of ['navara', 'cesium', 'navara']) {
if ((await p.evaluate(() => window.__godsEyeView.engine.kind)) !== kind) { if ((await p.evaluate(() => window.__godsEyeView.engine.kind)) !== kind) {
await strict.pick(p, kind); await strict.switchTo(p, kind);
await ready(kind);
} }
const sample = await p.evaluate(() => { const sample = await p.evaluate(() => {
const entry = const entry =
+10 -26
View File
@@ -1,7 +1,12 @@
import { fromDegrees } from '../src/geo/cartesian3.js'; import { fromDegrees } from '../src/geo/cartesian3.js';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs'; import { readFileSync } from 'node:fs';
import { createStrict, localPreview } from './qa-strict-harness.mjs'; import {
createStrict,
localPreview,
openPage,
waitForEngine,
} from './qa-strict-harness.mjs';
const base = localPreview(process.argv[2]); const base = localPreview(process.argv[2]);
base.search = '?engine=navara&welcome=0&look=cinematic'; base.search = '?engine=navara&welcome=0&look=cinematic';
@@ -26,14 +31,8 @@ const strict = createStrict({
origin: base.origin, origin: base.origin,
layers: ['local-dams', 'local-datacenters'], layers: ['local-dams', 'local-datacenters'],
}); });
await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => { await strict.run({}, async (b) => {
const p = await b.newPage(); const { page: p, errors } = await openPage(b);
await p.setViewport({ width: 1024, height: 768 });
const errors = [];
p.on('pageerror', (e) => {
errors.push(e.message);
console.log('error', e.message.split('?')[0]);
});
const reads = { datacenters: 0, dams: 0 }; const reads = { datacenters: 0, dams: 0 };
p.on('request', (req) => { p.on('request', (req) => {
const path = new URL(req.url()).pathname; const path = new URL(req.url()).pathname;
@@ -52,21 +51,7 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
}); });
}); });
await p.goto(base.href); await p.goto(base.href);
const ready = async (kind) => { await waitForEngine(p, 'navara');
await p.waitForFunction(
(k) =>
window.__godsEyeView?.engine?.kind === k &&
!document.querySelector('#globe-engine')?.disabled &&
!document.querySelector('.engine-switch-frame') &&
document.querySelector('#loading-screen.hidden'),
{ timeout: 90000 },
kind,
);
await p.evaluate(
() => window.__godsEyeView.styleManager.initialRestorePromise,
);
};
await ready('navara');
const ids = ['local-datacenters', 'local-dams']; const ids = ['local-datacenters', 'local-dams'];
const dataset = await p.evaluate(async (ids) => { const dataset = await p.evaluate(async (ids) => {
const { dataManager, tileset } = window.__godsEyeView; const { dataManager, tileset } = window.__godsEyeView;
@@ -184,8 +169,7 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
await p.evaluate(() => { await p.evaluate(() => {
window.qaSelections = []; window.qaSelections = [];
}); });
await strict.pick(p, kind); await strict.switchTo(p, kind);
await ready(kind);
await p.waitForFunction( await p.waitForFunction(
() => window.qaSelections.includes('local-datacenters:342664739'), () => window.qaSelections.includes('local-datacenters:342664739'),
{ timeout: 15000 }, { timeout: 15000 },
+1 -2
View File
@@ -314,8 +314,7 @@ async function main() {
(ids) => ids.filter((id) => !window.__godsEyeView.layers.available(id)), (ids) => ids.filter((id) => !window.__godsEyeView.layers.available(id)),
fieldLayers, fieldLayers,
); );
// An engine with flights but not the other field layers (Navara, W1 so // An engine with flights but not the other field layers: the same 12,000 / 5,200 observations, all as aircraft, so the
// far): the same 12,000 / 5,200 observations, all as aircraft, so the
// detection lane is measured on the layers the engine has. // detection lane is measured on the layers the engine has.
let fieldCounts = FIELD_COUNTS; let fieldCounts = FIELD_COUNTS;
let normalCounts = NORMAL_COUNTS; let normalCounts = NORMAL_COUNTS;
+6 -17
View File
@@ -15,9 +15,7 @@ url.hash =
// left. // left.
const HIDDEN_PICKER = 'the Globe Engine picker is hidden in cockpit mode'; const HIDDEN_PICKER = 'the Globe Engine picker is hidden in cockpit mode';
const strict = createStrict({ origin: url.origin, layers: [layerId] }); const strict = createStrict({ origin: url.origin, layers: [layerId] });
await strict.run( await strict.run({}, async (browser) => {
{ args: ['--use-angle=metal', '--enable-gpu'] },
async (browser) => {
const page = await browser.newPage(); const page = await browser.newPage();
const errors = []; const errors = [];
page.on('pageerror', (e) => errors.push(e.message)); page.on('pageerror', (e) => errors.push(e.message));
@@ -270,10 +268,7 @@ await strict.run(
stamp: entry.module.getStats().lastUpdate, stamp: entry.module.getStats().lastUpdate,
}; };
const deadline = Date.now() + 2.5 * every + 5000; const deadline = Date.now() + 2.5 * every + 5000;
while ( while (window.__handoff.ticks - start.ticks < 2 && Date.now() < deadline)
window.__handoff.ticks - start.ticks < 2 &&
Date.now() < deadline
)
await new Promise((resolve) => setTimeout(resolve, 250)); await new Promise((resolve) => setTimeout(resolve, 250));
// Let the last tick's request land. // Let the last tick's request land.
await new Promise((resolve) => setTimeout(resolve, 1500)); await new Promise((resolve) => setTimeout(resolve, 1500));
@@ -297,8 +292,7 @@ await strict.run(
polls, polls,
); );
await page.waitForFunction( await page.waitForFunction(
() => () => Math.abs(window.__godsEyeView.engine.camera.height() - 9700) < 1000,
Math.abs(window.__godsEyeView.engine.camera.height() - 9700) < 1000,
); );
console.log( console.log(
`PASS ${kind} cockpit, aircraft, HUD, manager and timer (${result.sourcePolls} polls since entry)`, `PASS ${kind} cockpit, aircraft, HUD, manager and timer (${result.sourcePolls} polls since entry)`,
@@ -372,10 +366,7 @@ await strict.run(
// retry starts Cesium cold on the same session without a reload. // retry starts Cesium cold on the same session without a reload.
const lengthBeforeDoubleFailure = await page.evaluate(() => { const lengthBeforeDoubleFailure = await page.evaluate(() => {
window.__failResume = true; window.__failResume = true;
for (const engine of [ for (const engine of [window.__godsEyeView.engine, window.__parkedCesium]) {
window.__godsEyeView.engine,
window.__parkedCesium,
]) {
const request = engine.render.request; const request = engine.render.request;
engine.render.request = function (reason, ...args) { engine.render.request = function (reason, ...args) {
if (reason === 'engine-resume' && window.__failResume) { if (reason === 'engine-resume' && window.__failResume) {
@@ -422,8 +413,7 @@ await strict.run(
.querySelector('#loading-screen') .querySelector('#loading-screen')
.classList.contains('hidden') && .classList.contains('hidden') &&
/QA parked renderer failure/.test( /QA parked renderer failure/.test(
document.querySelector('#loading-screen .loader-status') document.querySelector('#loading-screen .loader-status').textContent,
.textContent,
), ),
lengthBeforeDoubleFailure, lengthBeforeDoubleFailure,
), ),
@@ -524,5 +514,4 @@ await strict.run(
after, after,
); );
assert.deepEqual(errors, []); assert.deepEqual(errors, []);
}, });
);
+10 -26
View File
@@ -1,19 +1,18 @@
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import { createStrict, localPreview } from './qa-strict-harness.mjs'; import {
createStrict,
localPreview,
openPage,
waitForEngine,
} from './qa-strict-harness.mjs';
const base = localPreview(process.argv[2]); const base = localPreview(process.argv[2]);
base.search = '?engine=navara&welcome=0&look=cinematic'; base.search = '?engine=navara&welcome=0&look=cinematic';
base.hash = base.hash =
'v=2&lat=41.5340&lon=-90.5070&alt=20000&heading=0&pitch=-90&roll=0&map=sentinel2&l='; 'v=2&lat=41.5340&lon=-90.5070&alt=20000&heading=0&pitch=-90&roll=0&map=sentinel2&l=';
const strict = createStrict({ origin: base.origin, layers: ['local-adsb'] }); const strict = createStrict({ origin: base.origin, layers: ['local-adsb'] });
await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => { await strict.run({}, async (b) => {
const p = await b.newPage(); const { page: p, errors } = await openPage(b);
await p.setViewport({ width: 1024, height: 768 });
const errors = [];
p.on('pageerror', (e) => {
errors.push(e.message);
console.log('error', e.message.split('?')[0]);
});
await p.evaluateOnNewDocument(() => { await p.evaluateOnNewDocument(() => {
const raw = window.fetch; const raw = window.fetch;
window.fetch = function (input, ...args) { window.fetch = function (input, ...args) {
@@ -53,21 +52,7 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
); );
}); });
await p.goto(base.href); await p.goto(base.href);
const ready = async (kind) => { await waitForEngine(p, 'navara');
await p.waitForFunction(
(k) =>
window.__godsEyeView?.engine?.kind === k &&
!document.querySelector('#globe-engine')?.disabled &&
!document.querySelector('.engine-switch-frame') &&
document.querySelector('#loading-screen.hidden'),
{ timeout: 90000 },
kind,
);
await p.evaluate(
() => window.__godsEyeView.styleManager.initialRestorePromise,
);
};
await ready('navara');
await p.evaluate(async () => { await p.evaluate(async () => {
const { dataManager, tileset } = window.__godsEyeView; const { dataManager, tileset } = window.__godsEyeView;
if (tileset || window.__GOOGLE_MAPS_API_KEY__) if (tileset || window.__GOOGLE_MAPS_API_KEY__)
@@ -107,8 +92,7 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
await p.evaluate(() => { await p.evaluate(() => {
window.qaSelections = []; window.qaSelections = [];
}); });
await strict.pick(p, kind); await strict.switchTo(p, kind);
await ready(kind);
await p.waitForFunction(() => await p.waitForFunction(() =>
window.qaSelections.includes('local-adsb:abc123'), window.qaSelections.includes('local-adsb:abc123'),
); );
+7 -20
View File
@@ -9,7 +9,11 @@
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import fs from 'node:fs'; import fs from 'node:fs';
import sharp from 'sharp'; import sharp from 'sharp';
import { createStrict, localPreview } from './qa-strict-harness.mjs'; import {
createStrict,
localPreview,
waitForEngine,
} from './qa-strict-harness.mjs';
const LAT = 37.7749; const LAT = 37.7749;
const LON = -122.4194; const LON = -122.4194;
@@ -31,8 +35,6 @@ const RING = [
await strict.run( await strict.run(
{ {
args: [ args: [
'--use-angle=metal',
'--enable-gpu',
'--disable-background-timer-throttling', '--disable-background-timer-throttling',
'--disable-renderer-backgrounding', '--disable-renderer-backgrounding',
], ],
@@ -43,21 +45,7 @@ await strict.run(
const errors = []; const errors = [];
page.on('pageerror', (error) => errors.push(error.message)); page.on('pageerror', (error) => errors.push(error.message));
await page.goto(base.href); await page.goto(base.href);
const ready = async (kind) => { await waitForEngine(page, 'navara');
await page.waitForFunction(
(k) =>
window.__godsEyeView?.engine?.kind === k &&
!document.querySelector('#globe-engine')?.disabled &&
!document.querySelector('.engine-switch-frame') &&
document.querySelector('#loading-screen.hidden'),
{ timeout: 120000 },
kind,
);
await page.evaluate(
() => window.__godsEyeView.styleManager.initialRestorePromise,
);
};
await ready('navara');
assert.equal( assert.equal(
await page.evaluate(() => await page.evaluate(() =>
Boolean(window.__godsEyeView.tileset || window.__GOOGLE_MAPS_API_KEY__), Boolean(window.__godsEyeView.tileset || window.__GOOGLE_MAPS_API_KEY__),
@@ -172,8 +160,7 @@ await strict.run(
if ( if (
(await page.evaluate(() => window.__godsEyeView.engine.kind)) !== kind (await page.evaluate(() => window.__godsEyeView.engine.kind)) !== kind
) { ) {
await strict.pick(page, kind); await strict.switchTo(page, kind);
await ready(kind);
} }
await wait(2500); await wait(2500);
const state = await page.evaluate(() => ({ const state = await page.evaluate(() => ({
+3 -6
View File
@@ -1,4 +1,4 @@
// The app's Bloom on Navara (Stage 6 B2): the Display bloom glows the // The app's Bloom on Navara: the Display bloom glows the
// Navara frame (brighter, more at a higher slider), it sits after the // Navara frame (brighter, more at a higher slider), it sits after the
// overlays and before the visual styles, Cesium's dead zone draws nothing, // overlays and before the visual styles, Cesium's dead zone draws nothing,
// turning it off restores the frame exactly, and on Cesium the scene's own // turning it off restores the frame exactly, and on Cesium the scene's own
@@ -53,9 +53,7 @@ const setBloom = (page, enabled, intensityPct) =>
intensityPct, intensityPct,
); );
await strict.run( await strict.run({}, async (browser) => {
{ args: ['--use-angle=metal', '--enable-gpu'] },
async (browser) => {
const page = await browser.newPage(); const page = await browser.newPage();
const errors = []; const errors = [];
page.on('pageerror', (error) => errors.push(error.message)); page.on('pageerror', (error) => errors.push(error.message));
@@ -201,5 +199,4 @@ await strict.run(
path.join(outDir, 'bloom.json'), path.join(outDir, 'bloom.json'),
JSON.stringify(report, null, 2), JSON.stringify(report, null, 2),
); );
}, });
);
+5 -10
View File
@@ -1,4 +1,4 @@
// The cockpit weather clouds on Navara (Stage 6 C3): in cockpit mode with // The cockpit weather clouds on Navara: in cockpit mode with
// the cockpit weather on, Navara's own volumetric clouds take their cover // the cockpit weather on, Navara's own volumetric clouds take their cover
// from the weather under the camera (clear: none; overcast: a heavy deck), // from the weather under the camera (clear: none; overcast: a heavy deck),
// with no second canvas or WebGL context; leaving the cockpit gives the look // with no second canvas or WebGL context; leaving the cockpit gives the look
@@ -101,9 +101,7 @@ const coverage = (page) =>
() => window.__godsEyeView.engine.render.cloudCover?.().coverage, () => window.__godsEyeView.engine.render.cloudCover?.().coverage,
); );
await strict.run( await strict.run({}, async (browser) => {
{ args: ['--use-angle=metal', '--enable-gpu'] },
async (browser) => {
const page = await browser.newPage(); const page = await browser.newPage();
const errors = []; const errors = [];
page.on('pageerror', (error) => errors.push(error.message)); page.on('pageerror', (error) => errors.push(error.message));
@@ -160,8 +158,7 @@ await strict.run(
await page.evaluate(() => { await page.evaluate(() => {
const { engine } = window.__godsEyeView; const { engine } = window.__godsEyeView;
const place = engine.camera.pose().cartographic; const place = engine.camera.pose().cartographic;
const east = const east = place.longitude + 30000 / (6378137 * Math.cos(place.latitude));
place.longitude + 30000 / (6378137 * Math.cos(place.latitude));
const { sin, cos } = Math; const { sin, cos } = Math;
// WGS84 Earth-fixed position of (east, latitude, height). // WGS84 Earth-fixed position of (east, latitude, height).
const a = 6378137; const a = 6378137;
@@ -221,8 +218,7 @@ await strict.run(
await page.waitForFunction( await page.waitForFunction(
() => () =>
Number( Number(
document.getElementById('cockpit-cloud-effects')?.dataset document.getElementById('cockpit-cloud-effects')?.dataset.cloudStrength,
.cloudStrength,
) > 0.3, ) > 0.3,
{ timeout: 15000, polling: 100 }, { timeout: 15000, polling: 100 },
); );
@@ -248,5 +244,4 @@ await strict.run(
path.join(outDir, 'cockpit-clouds.json'), path.join(outDir, 'cockpit-clouds.json'),
JSON.stringify(report, null, 2), JSON.stringify(report, null, 2),
); );
}, });
);
-2
View File
@@ -32,8 +32,6 @@ const AREA = [
await strict.run( await strict.run(
{ {
args: [ args: [
'--use-angle=metal',
'--enable-gpu',
'--disable-background-timer-throttling', '--disable-background-timer-throttling',
'--disable-renderer-backgrounding', '--disable-renderer-backgrounding',
], ],
-2
View File
@@ -136,8 +136,6 @@ const FILES = [
await strict.run( await strict.run(
{ {
args: [ args: [
'--use-angle=metal',
'--enable-gpu',
'--disable-background-timer-throttling', '--disable-background-timer-throttling',
'--disable-renderer-backgrounding', '--disable-renderer-backgrounding',
], ],
+7 -20
View File
@@ -10,7 +10,11 @@ import assert from 'node:assert/strict';
import fs from 'node:fs'; import fs from 'node:fs';
import sharp from 'sharp'; import sharp from 'sharp';
import { fromDegrees } from '../src/geo/cartesian3.js'; import { fromDegrees } from '../src/geo/cartesian3.js';
import { createStrict, localPreview } from './qa-strict-harness.mjs'; import {
createStrict,
localPreview,
waitForEngine,
} from './qa-strict-harness.mjs';
const base = localPreview(process.argv[2]); const base = localPreview(process.argv[2]);
base.search = '?engine=navara&welcome=0'; base.search = '?engine=navara&welcome=0';
@@ -31,8 +35,6 @@ const EDGE = { x: 1250, y: 560, width: 26, height: 90 };
await strict.run( await strict.run(
{ {
args: [ args: [
'--use-angle=metal',
'--enable-gpu',
'--disable-background-timer-throttling', '--disable-background-timer-throttling',
'--disable-renderer-backgrounding', '--disable-renderer-backgrounding',
], ],
@@ -43,21 +45,7 @@ await strict.run(
const errors = []; const errors = [];
page.on('pageerror', (error) => errors.push(error.message)); page.on('pageerror', (error) => errors.push(error.message));
await page.goto(base.href); await page.goto(base.href);
const ready = async (kind) => { await waitForEngine(page, 'navara');
await page.waitForFunction(
(k) =>
window.__godsEyeView?.engine?.kind === k &&
!document.querySelector('#globe-engine')?.disabled &&
!document.querySelector('.engine-switch-frame') &&
document.querySelector('#loading-screen.hidden'),
{ timeout: 120000 },
kind,
);
await page.evaluate(
() => window.__godsEyeView.styleManager.initialRestorePromise,
);
};
await ready('navara');
assert.equal( assert.equal(
await page.evaluate(() => await page.evaluate(() =>
Boolean(window.__godsEyeView.tileset || window.__GOOGLE_MAPS_API_KEY__), Boolean(window.__godsEyeView.tileset || window.__GOOGLE_MAPS_API_KEY__),
@@ -92,8 +80,7 @@ await strict.run(
if ( if (
(await page.evaluate(() => window.__godsEyeView.engine.kind)) !== kind (await page.evaluate(() => window.__godsEyeView.engine.kind)) !== kind
) { ) {
await strict.pick(page, kind); await strict.switchTo(page, kind);
await ready(kind);
} }
await wait(2500); await wait(2500);
const mask = await page.evaluate(() => { const mask = await page.evaluate(() => {
+4 -8
View File
@@ -1,4 +1,4 @@
// The Cyber HUD sonar on Navara (Stage 6 C2): with the Cyber HUD on, the // The Cyber HUD sonar on Navara: with the Cyber HUD on, the
// sweep lights the contacts it passes and dims the rest to the opacity // sweep lights the contacts it passes and dims the rest to the opacity
// floor, by each contact's place on screen exactly as the CPU sampler of // floor, by each contact's place on screen exactly as the CPU sampler of
// src/cyberSonar.js rates it; the sweep turns; with the sweep off every // src/cyberSonar.js rates it; the sweep turns; with the sweep off every
@@ -98,9 +98,7 @@ const framesIn = (page, ms) =>
const mean = (values) => const mean = (values) =>
values.reduce((sum, value) => sum + value, 0) / Math.max(1, values.length); values.reduce((sum, value) => sum + value, 0) / Math.max(1, values.length);
await strict.run( await strict.run({}, async (browser) => {
{ args: ['--use-angle=metal', '--enable-gpu'] },
async (browser) => {
const page = await browser.newPage(); const page = await browser.newPage();
const errors = []; const errors = [];
page.on('pageerror', (error) => errors.push(error.message)); page.on('pageerror', (error) => errors.push(error.message));
@@ -265,8 +263,7 @@ await strict.run(
{ timeout: 10000 }, { timeout: 10000 },
); );
const cesium = await page.evaluate(() => ({ const cesium = await page.evaluate(() => ({
setContactSonar: setContactSonar: typeof window.__godsEyeView.engine.render.setContactSonar,
typeof window.__godsEyeView.engine.render.setContactSonar,
gpu: document.documentElement.dataset.cyberSonarGpu, gpu: document.documentElement.dataset.cyberSonarGpu,
})); }));
check( check(
@@ -286,5 +283,4 @@ await strict.run(
JSON.stringify(report, null, 2), JSON.stringify(report, null, 2),
); );
} }
}, });
);
+8 -25
View File
@@ -1,4 +1,4 @@
// The app's visual styles on Navara (Stage 6 B1): each of CRT, NVG, FLIR, // The app's visual styles on Navara: each of CRT, NVG, FLIR,
// Anime and Noir filters the Navara frame (pixels and a style-specific // Anime and Noir filters the Navara frame (pixels and a style-specific
// signature against Normal), switching styles crossfades (both styles drawn // signature against Normal), switching styles crossfades (both styles drawn
// at once mid-way, the fade completes), Normal restores the frame, the // at once mid-way, the fade completes), Normal restores the frame, the
@@ -45,9 +45,7 @@ const check = checker(report, assert);
const save = (name, pixels) => saveCapture(outDir, name, pixels); const save = (name, pixels) => saveCapture(outDir, name, pixels);
await strict.run( await strict.run({}, async (browser) => {
{ args: ['--use-angle=metal', '--enable-gpu'] },
async (browser) => {
const page = await browser.newPage(); const page = await browser.newPage();
const errors = []; const errors = [];
page.on('pageerror', (error) => errors.push(error.message)); page.on('pageerror', (error) => errors.push(error.message));
@@ -88,11 +86,7 @@ await strict.run(
const normal = await capture(page); const normal = await capture(page);
const normalAgain = await capture(page); const normalAgain = await capture(page);
const noise = diff(normal, normalAgain); const noise = diff(normal, normalAgain);
check( check('Normal is a still frame to compare against', noise.mean < 0.5, noise);
'Normal is a still frame to compare against',
noise.mean < 0.5,
noise,
);
save('normal', normal); save('normal', normal);
const normalStats = stats(normal); const normalStats = stats(normal);
@@ -114,8 +108,7 @@ await strict.run(
); );
if (previous !== 'normal') { if (previous !== 'normal') {
const both = fade.samples.filter( const both = fade.samples.filter(
([out, next]) => ([out, next]) => out > 0.05 && out < 0.95 && next > 0.05 && next < 0.95,
out > 0.05 && out < 0.95 && next > 0.05 && next < 0.95,
); );
check( check(
`${name}: ${previous} and ${name} draw together mid-crossfade`, `${name}: ${previous} and ${name} draw together mid-crossfade`,
@@ -163,8 +156,7 @@ await strict.run(
styled.saturation < normalStats.saturation && styled.saturation < normalStats.saturation &&
styled.vignette < normalStats.vignette * 0.85, styled.vignette < normalStats.vignette * 0.85,
// Anime: cel bands, more saturated. // Anime: cel bands, more saturated.
anime: anime: styled.saturation > normalStats.saturation * 1.05 || shift > 0.05,
styled.saturation > normalStats.saturation * 1.05 || shift > 0.05,
// CRT: pixelated, dithered, scanlined. // CRT: pixelated, dithered, scanlined.
retro: shift > 0.05 || change.mean > 12, retro: shift > 0.05 || change.mean > 12,
}[name]; }[name];
@@ -307,11 +299,7 @@ await strict.run(
cesiumState, cesiumState,
); );
const cesiumChange = diff(cesiumNormal, cesiumThermal); const cesiumChange = diff(cesiumNormal, cesiumThermal);
check( check('Cesium: FLIR changes its frame', cesiumChange.mean > 6, cesiumChange);
'Cesium: FLIR changes its frame',
cesiumChange.mean > 6,
cesiumChange,
);
await pickStyle(page, 'normal', 'thermal'); await pickStyle(page, 'normal', 'thermal');
await page.evaluate((sharpen) => { await page.evaluate((sharpen) => {
const { styleManager } = window.__godsEyeView; const { styleManager } = window.__godsEyeView;
@@ -319,11 +307,7 @@ await strict.run(
styleManager.setSharpen(sharpen); styleManager.setSharpen(sharpen);
}, display); }, display);
const cesiumBack = diff(cesiumNormal, await capture(page)); const cesiumBack = diff(cesiumNormal, await capture(page));
check( check('Cesium: Normal restores its frame', cesiumBack.mean < 1.5, cesiumBack);
'Cesium: Normal restores its frame',
cesiumBack.mean < 1.5,
cesiumBack,
);
check('no page errors', errors.length === 0, errors); check('no page errors', errors.length === 0, errors);
if (outDir) if (outDir)
@@ -331,5 +315,4 @@ await strict.run(
path.join(outDir, 'visual-styles.json'), path.join(outDir, 'visual-styles.json'),
JSON.stringify(report, null, 2), JSON.stringify(report, null, 2),
); );
}, });
);
+11 -32
View File
@@ -1,6 +1,11 @@
import sharp from 'sharp'; import sharp from 'sharp';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import { createStrict, localPreview } from './qa-strict-harness.mjs'; import {
createStrict,
localPreview,
openPage,
waitForEngine,
} from './qa-strict-harness.mjs';
const base = localPreview(process.argv[2]); const base = localPreview(process.argv[2]);
base.search = '?engine=navara&welcome=0&look=cinematic'; base.search = '?engine=navara&welcome=0&look=cinematic';
@@ -54,18 +59,8 @@ const strict = createStrict({
}, },
], ],
}); });
await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => { await strict.run({}, async (b) => {
const p = await b.newPage(); const { page: p, errors } = await openPage(b, { shaderErrors: true });
await p.setViewport({ width: 1024, height: 768 });
const errors = [];
p.on('pageerror', (e) => {
errors.push(e.message);
console.log('error', e.message.split('?')[0]);
});
p.on('console', (msg) => {
if (msg.type() === 'error' && /Shader|WebGLProgram/i.test(msg.text()))
errors.push(msg.text());
});
const assets = []; const assets = [];
p.on('request', (req) => { p.on('request', (req) => {
const path = new URL(req.url()).pathname; const path = new URL(req.url()).pathname;
@@ -77,21 +72,7 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
assets.push(path); assets.push(path);
}); });
await p.goto(base.href); await p.goto(base.href);
const ready = async (kind) => { await waitForEngine(p, 'navara');
await p.waitForFunction(
(k) =>
window.__godsEyeView?.engine?.kind === k &&
!document.querySelector('#globe-engine')?.disabled &&
!document.querySelector('.engine-switch-frame') &&
document.querySelector('#loading-screen.hidden'),
{ timeout: 90000 },
kind,
);
await p.evaluate(
() => window.__godsEyeView.styleManager.initialRestorePromise,
);
};
await ready('navara');
await p.evaluate(async () => { await p.evaluate(async () => {
const { tileset, dataManager, sceneDirector } = window.__godsEyeView; const { tileset, dataManager, sceneDirector } = window.__godsEyeView;
if (!sceneDirector?.listScenes().length) if (!sceneDirector?.listScenes().length)
@@ -118,8 +99,7 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
}; };
for (const kind of ['navara', 'cesium', 'navara']) { for (const kind of ['navara', 'cesium', 'navara']) {
if ((await p.evaluate(() => window.__godsEyeView.engine.kind)) !== kind) { if ((await p.evaluate(() => window.__godsEyeView.engine.kind)) !== kind) {
await strict.pick(p, kind); await strict.switchTo(p, kind);
await ready(kind);
} }
const state = await p.evaluate(() => { const state = await p.evaluate(() => {
const { dataManager } = window.__godsEyeView; const { dataManager } = window.__godsEyeView;
@@ -184,8 +164,7 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
await p.waitForFunction( await p.waitForFunction(
() => window.qaEvent.getPlaybackState().progress > 0.53, () => window.qaEvent.getPlaybackState().progress > 0.53,
); );
await strict.pick(p, 'cesium'); await strict.switchTo(p, 'cesium');
await ready('cesium');
const live = await p.evaluate(() => window.qaEvent.getPlaybackState()); const live = await p.evaluate(() => window.qaEvent.getPlaybackState());
await strict.check( await strict.check(
p, p,
+10 -30
View File
@@ -1,7 +1,12 @@
import { fromDegrees } from '../src/geo/cartesian3.js'; import { fromDegrees } from '../src/geo/cartesian3.js';
import sharp from 'sharp'; import sharp from 'sharp';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import { createStrict, localPreview } from './qa-strict-harness.mjs'; import {
createStrict,
localPreview,
openPage,
waitForEngine,
} from './qa-strict-harness.mjs';
const base = localPreview(process.argv[2]); const base = localPreview(process.argv[2]);
base.search = '?engine=navara&welcome=0&look=cinematic'; base.search = '?engine=navara&welcome=0&look=cinematic';
@@ -11,34 +16,10 @@ const strict = createStrict({
origin: base.origin, origin: base.origin,
layers: ['bhote-koshi-locator'], layers: ['bhote-koshi-locator'],
}); });
await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => { await strict.run({}, async (b) => {
const p = await b.newPage(); const { page: p, errors } = await openPage(b, { shaderErrors: true });
await p.setViewport({ width: 1024, height: 768 });
const errors = [];
p.on('pageerror', (e) => {
errors.push(e.message);
console.log('error', e.message.split('?')[0]);
});
p.on('console', (msg) => {
if (msg.type() === 'error' && /Shader|WebGLProgram/i.test(msg.text()))
errors.push(msg.text());
});
await p.goto(base.href); await p.goto(base.href);
const ready = async (kind) => { await waitForEngine(p, 'navara');
await p.waitForFunction(
(k) =>
window.__godsEyeView?.engine?.kind === k &&
!document.querySelector('#globe-engine')?.disabled &&
!document.querySelector('.engine-switch-frame') &&
document.querySelector('#loading-screen.hidden'),
{ timeout: 90000 },
kind,
);
await p.evaluate(
() => window.__godsEyeView.styleManager.initialRestorePromise,
);
};
await ready('navara');
await p.evaluate(async () => { await p.evaluate(async () => {
const { tileset, dataManager } = window.__godsEyeView; const { tileset, dataManager } = window.__godsEyeView;
if (tileset || window.__GOOGLE_MAPS_API_KEY__) if (tileset || window.__GOOGLE_MAPS_API_KEY__)
@@ -51,8 +32,7 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
}); });
for (const kind of ['navara', 'cesium', 'navara']) { for (const kind of ['navara', 'cesium', 'navara']) {
if ((await p.evaluate(() => window.__godsEyeView.engine.kind)) !== kind) { if ((await p.evaluate(() => window.__godsEyeView.engine.kind)) !== kind) {
await strict.pick(p, kind); await strict.switchTo(p, kind);
await ready(kind);
} }
const { same, stats, params, at } = await p.evaluate( const { same, stats, params, at } = await p.evaluate(
(world) => { (world) => {
+10 -26
View File
@@ -1,6 +1,11 @@
import sharp from 'sharp'; import sharp from 'sharp';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import { createStrict, localPreview } from './qa-strict-harness.mjs'; import {
createStrict,
localPreview,
openPage,
waitForEngine,
} from './qa-strict-harness.mjs';
// Drone airspace, OSM places, sea marks and ski pistes on both engines, over // Drone airspace, OSM places, sea marks and ski pistes on both engines, over
// Navara → Cesium → Navara: each draws over the view centre, and the switch // Navara → Cesium → Navara: each draws over the view centre, and the switch
@@ -57,14 +62,8 @@ const strict = createStrict({
tileRoute('https://www.opensnowmap.org/*', 'ski-pistes'), tileRoute('https://www.opensnowmap.org/*', 'ski-pistes'),
], ],
}); });
await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => { await strict.run({}, async (b) => {
const p = await b.newPage(); const { page: p, errors } = await openPage(b);
await p.setViewport({ width: 1024, height: 768 });
const errors = [];
p.on('pageerror', (e) => {
errors.push(e.message);
console.log('error', e.message.split('?')[0]);
});
await p.evaluateOnNewDocument((ring) => { await p.evaluateOnNewDocument((ring) => {
const fetch = window.fetch.bind(window); const fetch = window.fetch.bind(window);
window.qaZoneReads = 0; window.qaZoneReads = 0;
@@ -101,20 +100,6 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
}; };
}, RING); }, RING);
await p.goto(base.href); await p.goto(base.href);
const ready = async (kind) => {
await p.waitForFunction(
(k) =>
window.__godsEyeView?.engine?.kind === k &&
!document.querySelector('#globe-engine')?.disabled &&
!document.querySelector('.engine-switch-frame') &&
document.querySelector('#loading-screen.hidden'),
{ timeout: 90000 },
kind,
);
await p.evaluate(
() => window.__godsEyeView.styleManager.initialRestorePromise,
);
};
const setEnabled = (id, on) => const setEnabled = (id, on) =>
p.evaluate( p.evaluate(
(layerId, show) => (layerId, show) =>
@@ -177,7 +162,7 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
const greener = (on, off) => const greener = (on, off) =>
2 * on[1] - on[0] - on[2] > 2 * off[1] - off[0] - off[2] + 10; 2 * on[1] - on[0] - on[2] > 2 * off[1] - off[0] - off[2] + 10;
await ready('navara'); await waitForEngine(p, 'navara');
await p.evaluate(() => { await p.evaluate(() => {
const { dataManager } = window.__godsEyeView; const { dataManager } = window.__godsEyeView;
window.qaModels = Object.fromEntries( window.qaModels = Object.fromEntries(
@@ -190,8 +175,7 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
for (const [leg, kind] of ['navara', 'cesium', 'navara'].entries()) { for (const [leg, kind] of ['navara', 'cesium', 'navara'].entries()) {
const readsBeforeSwitch = await p.evaluate(() => window.qaZoneReads); const readsBeforeSwitch = await p.evaluate(() => window.qaZoneReads);
if ((await p.evaluate(() => window.__godsEyeView.engine.kind)) !== kind) { if ((await p.evaluate(() => window.__godsEyeView.engine.kind)) !== kind) {
await strict.pick(p, kind); await strict.switchTo(p, kind);
await ready(kind);
const same = await p.evaluate(() => const same = await p.evaluate(() =>
Object.entries(window.qaModels).every( Object.entries(window.qaModels).every(
([id, model]) => ([id, model]) =>
+2 -5
View File
@@ -6,9 +6,7 @@ const url = localPreview(process.argv[2]);
url.search = '?engine=navara&welcome=0'; url.search = '?engine=navara&welcome=0';
url.hash = 'v=2&lat=30.27&lon=-97.74&alt=12000&pitch=-90&map=sentinel2&l='; url.hash = 'v=2&lat=30.27&lon=-97.74&alt=12000&pitch=-90&map=sentinel2&l=';
const strict = createStrict({ origin: url.origin, layers: ['radio'] }); const strict = createStrict({ origin: url.origin, layers: ['radio'] });
await strict.run( await strict.run({}, async (browser) => {
{ args: ['--use-angle=metal', '--enable-gpu'] },
async (browser) => {
const page = await browser.newPage(); const page = await browser.newPage();
const errors = []; const errors = [];
page.on('pageerror', (e) => errors.push(e.message)); page.on('pageerror', (e) => errors.push(e.message));
@@ -87,5 +85,4 @@ await strict.run(
console.log( console.log(
'PASS one live radio player, one station directory and the same selection', 'PASS one live radio player, one station directory and the same selection',
); );
}, });
);
+1 -1
View File
@@ -14,7 +14,7 @@ const strict = createStrict({
origin: url.origin, origin: url.origin,
layers: [missions ? 'rocket-launches' : 'satellites'], layers: [missions ? 'rocket-launches' : 'satellites'],
}); });
await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => { await strict.run({}, async (b) => {
const p = await b.newPage(); const p = await b.newPage();
const errors = []; const errors = [];
p.on('pageerror', (e) => { p.on('pageerror', (e) => {
+1 -1
View File
@@ -54,7 +54,7 @@ const getOpt = (name, dflt) => {
}; };
const BASE_URL = getOpt('--url', 'http://localhost:4173'); const BASE_URL = getOpt('--url', 'http://localhost:4173');
// --engine=cesium|navara (phase 3): without a viewer the layer is read through // --engine=cesium|navara: without a viewer the layer is read through
// the QA probe (window.__godsEyeView.layers.drawn) and the military checks are // the QA probe (window.__godsEyeView.layers.drawn) and the military checks are
// skipped where the engine has no military layer. // skipped where the engine has no military layer.
const ENGINE = (argv.find((arg) => arg.startsWith('--engine=')) ?? '--engine=cesium').slice(9); const ENGINE = (argv.find((arg) => arg.startsWith('--engine=')) ?? '--engine=cesium').slice(9);
+11 -27
View File
@@ -1,5 +1,9 @@
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import { createStrict, localPreview } from './qa-strict-harness.mjs'; import {
createStrict,
localPreview,
waitForEngine,
} from './qa-strict-harness.mjs';
import { serveFixtures, newFixture } from './qa-street-level.mjs'; import { serveFixtures, newFixture } from './qa-street-level.mjs';
import { KV_LINE, KV_SEQUENCE_ID } from './fixtures/street-level/kartaView.mjs'; import { KV_LINE, KV_SEQUENCE_ID } from './fixtures/street-level/kartaView.mjs';
import { import {
@@ -28,9 +32,7 @@ const strict = createStrict({
'https://panoramax.openstreetmap.fr/*', 'https://panoramax.openstreetmap.fr/*',
], ],
}); });
await strict.run( await strict.run({}, async (browser) => {
{ args: ['--use-angle=metal', '--enable-gpu'] },
async (browser) => {
const p = await browser.newPage(); const p = await browser.newPage();
await p.setViewport({ width: 1280, height: 900 }); await p.setViewport({ width: 1280, height: 900 });
const errors = [], const errors = [],
@@ -53,21 +55,7 @@ await strict.run(
photoRequests++; photoRequests++;
}); });
await p.goto(base.href); await p.goto(base.href);
const ready = async (k) => { await waitForEngine(p, 'navara');
await p.waitForFunction(
(kind) =>
window.__godsEyeView?.engine?.kind === kind &&
!document.querySelector('#globe-engine')?.disabled &&
!document.querySelector('.engine-switch-frame') &&
document.querySelector('#loading-screen.hidden'),
{ timeout: 90000 },
k,
);
await p.evaluate(
() => window.__godsEyeView.styleManager.initialRestorePromise,
);
};
await ready('navara');
await p.evaluate(async (provider) => { await p.evaluate(async (provider) => {
const { dataManager, tileset } = window.__godsEyeView; const { dataManager, tileset } = window.__godsEyeView;
if (tileset || window.__GOOGLE_MAPS_API_KEY__) if (tileset || window.__GOOGLE_MAPS_API_KEY__)
@@ -112,8 +100,7 @@ await strict.run(
window.qaStreet.getUIState().street.open && window.qaStreet.getUIState().street.open &&
window.qaStreet.getUIState().sequence.images > 0 && window.qaStreet.getUIState().sequence.images > 0 &&
(document.querySelector('#sl-viewer img')?.naturalWidth > 0 || (document.querySelector('#sl-viewer img')?.naturalWidth > 0 ||
document.querySelector('#sl-viewer canvas:not([hidden])')?.width > document.querySelector('#sl-viewer canvas:not([hidden])')?.width > 0),
0),
); );
await strict.passed( await strict.passed(
p, p,
@@ -147,8 +134,7 @@ await strict.run(
); );
const reads = photoRequests; const reads = photoRequests;
for (const kind of ['cesium', 'navara']) { for (const kind of ['cesium', 'navara']) {
await strict.pick(p, kind); await strict.switchTo(p, kind);
await ready(kind);
// The new globe reloads coverage right after it attaches: compare once // The new globe reloads coverage right after it attaches: compare once
// that load is over. // that load is over.
await until( await until(
@@ -195,8 +181,7 @@ await strict.run(
// Click-picking on Cesium: close the viewer, switch, click the line again. // Click-picking on Cesium: close the viewer, switch, click the line again.
await p.evaluate(() => window.qaStreet.closeViewer()); await p.evaluate(() => window.qaStreet.closeViewer());
await until(() => !window.qaStreet.getUIState().street.open); await until(() => !window.qaStreet.getUIState().street.open);
await strict.pick(p, 'cesium'); await strict.switchTo(p, 'cesium');
await ready('cesium');
await until( await until(
() => () =>
!window.qaStreet.getUIState().coverage.loading && !window.qaStreet.getUIState().coverage.loading &&
@@ -209,5 +194,4 @@ await strict.run(
console.log( console.log(
'PASS native line pick, same mounted photo and sequence across two switches', 'PASS native line pick, same mounted photo and sequence across two switches',
); );
}, });
);
+1 -1
View File
@@ -41,7 +41,7 @@ const navaraCode = async (url) => {
}; };
const results = []; const results = [];
strict.extra.contracts = results; strict.extra.contracts = results;
await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => { await strict.run({}, async (b) => {
for (const start of ['navara', 'cesium']) { for (const start of ['navara', 'cesium']) {
const p = await b.newPage(); const p = await b.newPage();
await p.setViewport({ width: 1280, height: 800 }); await p.setViewport({ width: 1280, height: 800 });
+1 -1
View File
@@ -32,7 +32,7 @@ const other = (kind) => (kind === 'navara' ? 'cesium' : 'navara');
const strict = createStrict({ origin: base.origin, layers: [] }); const strict = createStrict({ origin: base.origin, layers: [] });
const results = []; const results = [];
strict.extra.contracts = results; strict.extra.contracts = results;
await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => { await strict.run({}, async (b) => {
let p = null; let p = null;
const errors = []; const errors = [];
// A fresh page on `from`; warm visits the other engine first. // A fresh page on `from`; warm visits the other engine first.
+1 -1
View File
@@ -37,7 +37,7 @@ const other = (kind) => (kind === 'navara' ? 'cesium' : 'navara');
const strict = createStrict({ origin: base.origin, layers: [] }); const strict = createStrict({ origin: base.origin, layers: [] });
const results = []; const results = [];
strict.extra.contracts = results; strict.extra.contracts = results;
await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => { await strict.run({}, async (b) => {
// A fresh page on `start` with the session; `warm` visits the other // A fresh page on `start` with the session; `warm` visits the other
// engine first so it is parked. // engine first so it is parked.
const open = async (start, warm) => { const open = async (start, warm) => {
+64 -78
View File
@@ -29,8 +29,21 @@ import {
import { createMissLoader, tileCacheFile } from './qa/tileCache.mjs'; import { createMissLoader, tileCacheFile } from './qa/tileCache.mjs';
export const ENGINES = Object.freeze(['cesium', 'navara']); export const ENGINES = Object.freeze(['cesium', 'navara']);
const RESERVED_PORTS = ['', '4173', '4185', '4186', '4187', '4188', '4189']; /** Local ports that belong to the user's own servers: never a QA preview. */
const RESERVED = [...RESERVED_PORTS, '4194', '4196']; export const RESERVED_PORTS = Object.freeze([
'',
'4173',
'4174',
'4185',
'4186',
'4187',
'4188',
'4189',
'4194',
'4196',
]);
/** Chrome flags every scenario's browser starts with (the GPU path). */
const CHROME_ARGS = Object.freeze(['--use-angle=metal', '--enable-gpu']);
/** The values of --layers=a,b and --evidence=FILE (null when absent). */ /** The values of --layers=a,b and --evidence=FILE (null when absent). */
export function strictArguments(argv = process.argv) { export function strictArguments(argv = process.argv) {
@@ -48,7 +61,7 @@ export function localPreview(href) {
const url = new URL(href); const url = new URL(href);
if ( if (
!['localhost', '127.0.0.1'].includes(url.hostname) || !['localhost', '127.0.0.1'].includes(url.hostname) ||
RESERVED.includes(url.port) RESERVED_PORTS.includes(url.port)
) )
throw Error('Supply an unreserved local keyless preview URL'); throw Error('Supply an unreserved local keyless preview URL');
return url; return url;
@@ -89,11 +102,18 @@ export async function installStrictNetwork(
// workers' own requests are never held. // workers' own requests are never held.
const intercept = async (session, type) => { const intercept = async (session, type) => {
session.on('Fetch.requestPaused', async ({ requestId, request }) => { session.on('Fetch.requestPaused', async ({ requestId, request }) => {
const fulfil = (status, headers, body) => // Every answer allows any origin; `noStore` keeps it out of the cache.
const fulfil = (status, contentType, body, { noStore = false } = {}) =>
session.send('Fetch.fulfillRequest', { session.send('Fetch.fulfillRequest', {
requestId, requestId,
responseCode: status, responseCode: status,
responseHeaders: headers, responseHeaders: [
...(contentType
? [{ name: 'Content-Type', value: contentType }]
: []),
...(noStore ? [{ name: 'Cache-Control', value: 'no-store' }] : []),
{ name: 'Access-Control-Allow-Origin', value: '*' },
],
...(body == null ...(body == null
? {} ? {}
: { body: Buffer.from(body).toString('base64') }), : { body: Buffer.from(body).toString('base64') }),
@@ -106,12 +126,7 @@ export async function installStrictNetwork(
const answer = await route.respond(request); const answer = await route.respond(request);
return await fulfil( return await fulfil(
answer.status ?? 200, answer.status ?? 200,
[ answer.contentType,
...(answer.contentType
? [{ name: 'Content-Type', value: answer.contentType }]
: []),
{ name: 'Access-Control-Allow-Origin', value: '*' },
],
answer.body ?? '', answer.body ?? '',
); );
} }
@@ -128,14 +143,9 @@ export async function installStrictNetwork(
(ledger.probes[probe.pattern] ?? 0) + 1; (ledger.probes[probe.pattern] ?? 0) + 1;
return await fulfil( return await fulfil(
probe.status, probe.status,
[ probe.contentType,
...(probe.contentType
? [{ name: 'Content-Type', value: probe.contentType }]
: []),
{ name: 'Cache-Control', value: 'no-store' },
{ name: 'Access-Control-Allow-Origin', value: '*' },
],
probe.body ?? null, probe.body ?? null,
{ noStore: true },
); );
} }
const accept = Object.entries(request.headers ?? {}).find( const accept = Object.entries(request.headers ?? {}).find(
@@ -172,16 +182,7 @@ export async function installStrictNetwork(
const host = new URL(request.url).host; const host = new URL(request.url).host;
tiles.fetchedByHost[host] = (tiles.fetchedByHost[host] ?? 0) + 1; tiles.fetchedByHost[host] = (tiles.fetchedByHost[host] ?? 0) + 1;
} }
await fulfil( await fulfil(entry.status, entry.contentType, entry.body);
entry.status,
[
...(entry.contentType
? [{ name: 'Content-Type', value: entry.contentType }]
: []),
{ name: 'Access-Control-Allow-Origin', value: '*' },
],
entry.body,
);
} catch (error) { } catch (error) {
if ( if (
/Invalid InterceptionId|closed|detached/i.test(String(error?.message)) /Invalid InterceptionId|closed|detached/i.test(String(error?.message))
@@ -326,21 +327,15 @@ export async function installStrictNetwork(
* each page gets an accessor in place of the global: reads before the script * each page gets an accessor in place of the global: reads before the script
* has assigned it are logged with their stack in `ledger.cesiumBeforeLoad` * has assigned it are logged with their stack in `ledger.cesiumBeforeLoad`
* and answer `undefined`, as production would. * and answer `undefined`, as production would.
*
* GEV_QA_CESIUM_TRAP=record (a survey, not a check) answers such a read with
* the SDK instead, loaded synchronously on the first one, and logs every
* distinct read site until the page itself loads Cesium.js, so one run lists
* all the Navara paths that still need Cesium.
*/ */
async function trapCesiumGlobal(session, ledger) { async function trapCesiumGlobal(session, ledger) {
const binding = '__gevStrictCesiumRead'; const binding = '__gevStrictCesiumRead';
const record = process.env.GEV_QA_CESIUM_TRAP === 'record';
session.on('Runtime.bindingCalled', (event) => { session.on('Runtime.bindingCalled', (event) => {
if (event.name !== binding) return; if (event.name !== binding) return;
const stack = String(event.payload).slice(0, 1500); const stack = String(event.payload).slice(0, 1500);
const known = ledger.cesiumBeforeLoad.find((read) => read.stack === stack); const known = ledger.cesiumBeforeLoad.find((read) => read.stack === stack);
if (known) known.count++; if (known) known.count++;
else if (ledger.cesiumBeforeLoad.length < (record ? 2000 : 50)) else if (ledger.cesiumBeforeLoad.length < 50)
ledger.cesiumBeforeLoad.push({ stack, count: 1 }); ledger.cesiumBeforeLoad.push({ stack, count: 1 });
}); });
await session.send('Runtime.enable'); await session.send('Runtime.enable');
@@ -349,10 +344,8 @@ async function trapCesiumGlobal(session, ledger) {
await session.send('Page.addScriptToEvaluateOnNewDocument', { await session.send('Page.addScriptToEvaluateOnNewDocument', {
source: `(() => { source: `(() => {
if (window.top !== window) return; if (window.top !== window) return;
const record = ${record};
const seen = new Set(); const seen = new Set();
let sdk; let sdk;
let pageLoaded = false;
const report = () => { const report = () => {
const limit = Error.stackTraceLimit; const limit = Error.stackTraceLimit;
Error.stackTraceLimit = 12; Error.stackTraceLimit = 12;
@@ -366,21 +359,8 @@ async function trapCesiumGlobal(session, ledger) {
Object.defineProperty(window, 'Cesium', { Object.defineProperty(window, 'Cesium', {
configurable: true, configurable: true,
get() { get() {
if (!record) {
if (sdk === undefined) report(); if (sdk === undefined) report();
return sdk; return sdk;
}
if (!pageLoaded) {
pageLoaded = !!document.querySelector('script[src$="/Cesium.js"]');
if (!pageLoaded) report();
}
if (sdk === undefined) {
const request = new XMLHttpRequest();
request.open('GET', '/cesium/Cesium.js', false);
request.send();
(0, eval)(request.responseText);
}
return sdk;
}, },
set(value) { set(value) {
sdk = value; sdk = value;
@@ -434,30 +414,8 @@ export async function pickEngine(
); );
const opened = !(await expanded()); const opened = !(await expanded());
if (opened) { if (opened) {
await page.click('#control-panel-toggle').catch(async (error) => { await page.click('#control-panel-toggle').catch((error) => {
const state = await page.evaluate(() => { throw Error(`cannot click the control panel toggle: ${error.message}`);
const el = document.querySelector('#control-panel-toggle');
const box = el?.getBoundingClientRect();
const style = el && getComputedStyle(el);
const hit =
box &&
document.elementFromPoint(
box.x + box.width / 2,
box.y + box.height / 2,
);
return {
box: box && [box.x, box.y, box.width, box.height],
display: style?.display,
visibility: style?.visibility,
hidden: el?.closest('[hidden]')?.id ?? null,
hit: hit?.id || hit?.className || null,
body: document.body.className,
};
});
throw Error(
`cannot click the control panel toggle (${error.message}): ` +
JSON.stringify(state),
);
}); });
await page.waitForFunction( await page.waitForFunction(
() => () =>
@@ -592,6 +550,27 @@ export async function waitForEngine(page, kind, { timeout = 120000 } = {}) {
); );
} }
/**
* A new 1024×768 page of `browser` whose uncaught errors (and, with
* `shaderErrors`, shader and WebGL program errors on the console) land in
* `errors`; each uncaught error is also logged without its query string.
*/
export async function openPage(browser, { shaderErrors = false } = {}) {
const page = await browser.newPage();
await page.setViewport({ width: 1024, height: 768 });
const errors = [];
page.on('pageerror', (e) => {
errors.push(e.message);
console.log('error', e.message.split('?')[0]);
});
if (shaderErrors)
page.on('console', (msg) => {
if (msg.type() === 'error' && /Shader|WebGLProgram/i.test(msg.text()))
errors.push(msg.text());
});
return { page, errors };
}
/** /**
* The strict context of one scenario run. * The strict context of one scenario run.
* *
@@ -653,9 +632,16 @@ export function createStrict({
ledger, ledger,
extra, extra,
timings, timings,
/** Launch Chrome with the policy on every target. */ /**
async launch(options = {}) { * Launch headless Chrome with the policy on every target; `args` adds
const browser = await puppeteer.launch({ headless: true, ...options }); * flags to CHROME_ARGS.
*/
async launch({ args = [], ...options } = {}) {
const browser = await puppeteer.launch({
headless: true,
...options,
args: [...CHROME_ARGS, ...args],
});
await installStrictNetwork(browser, { await installStrictNetwork(browser, {
origin, origin,
fixtures, fixtures,
@@ -665,7 +651,7 @@ export function createStrict({
}); });
return browser; return browser;
}, },
/** Launch, run `body(browser)`, close, then finish(). */ /** Launch with `options`, run `body(browser)`, close, then finish(). */
async run(options, body) { async run(options, body) {
const browser = await strict.launch(options); const browser = await strict.launch(options);
let failed = null; let failed = null;
+1 -1
View File
@@ -20,7 +20,7 @@ import {
const base = localPreview(process.argv[2]); const base = localPreview(process.argv[2]);
const strict = createStrict({ origin: base.origin, layers: [] }); const strict = createStrict({ origin: base.origin, layers: [] });
await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => { await strict.run({}, async (b) => {
const p = await b.newPage(); const p = await b.newPage();
await p.setViewport({ width: 1280, height: 800 }); await p.setViewport({ width: 1280, height: 800 });
const errors = []; const errors = [];
+1 -1
View File
@@ -20,7 +20,7 @@ const base = localPreview(process.argv[2]);
const strict = createStrict({ origin: base.origin, layers: [] }); const strict = createStrict({ origin: base.origin, layers: [] });
const results = []; const results = [];
strict.extra.contracts = results; strict.extra.contracts = results;
await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => { await strict.run({}, async (b) => {
for (const start of ENGINES) { for (const start of ENGINES) {
const page = await b.newPage(); const page = await b.newPage();
await page.setViewport({ width: 1280, height: 800 }); await page.setViewport({ width: 1280, height: 800 });
+1 -1
View File
@@ -24,7 +24,7 @@ import {
const base = localPreview(process.argv[2]); const base = localPreview(process.argv[2]);
const strict = createStrict({ origin: base.origin, layers: [] }); const strict = createStrict({ origin: base.origin, layers: [] });
await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => { await strict.run({}, async (b) => {
const p = await b.newPage(); const p = await b.newPage();
await p.setViewport({ width: 1280, height: 800 }); await p.setViewport({ width: 1280, height: 800 });
const errors = []; const errors = [];
+1 -3
View File
@@ -156,9 +156,7 @@ strict.extra.soak = report;
for (let run = 1; run <= runs; run++) { for (let run = 1; run <= runs; run++) {
const start = run % 2 ? 'navara' : 'cesium'; const start = run % 2 ? 'navara' : 'cesium';
const browser = await strict.launch({ const browser = await strict.launch();
args: ['--use-angle=metal', '--enable-gpu'],
});
const row = { run, start, swaps: [], checkpoints: [], errors: [] }; const row = { run, start, swaps: [], checkpoints: [], errors: [] };
report.runs.push(row); report.runs.push(row);
try { try {
+10 -30
View File
@@ -1,7 +1,12 @@
import { fromDegrees } from '../src/geo/cartesian3.js'; import { fromDegrees } from '../src/geo/cartesian3.js';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs'; import { readFileSync } from 'node:fs';
import { createStrict, localPreview } from './qa-strict-harness.mjs'; import {
createStrict,
localPreview,
openPage,
waitForEngine,
} from './qa-strict-harness.mjs';
const base = localPreview(process.argv[2]); const base = localPreview(process.argv[2]);
base.search = '?engine=navara&welcome=0&look=cinematic'; base.search = '?engine=navara&welcome=0&look=cinematic';
@@ -27,39 +32,15 @@ const strict = createStrict({
origin: base.origin, origin: base.origin,
layers: ['telegeography-submarine-cables'], layers: ['telegeography-submarine-cables'],
}); });
await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => { await strict.run({}, async (b) => {
const p = await b.newPage(); const { page: p, errors } = await openPage(b, { shaderErrors: true });
await p.setViewport({ width: 1024, height: 768 });
const errors = [];
p.on('pageerror', (e) => {
errors.push(e.message);
console.log('error', e.message.split('?')[0]);
});
const reads = []; const reads = [];
p.on('request', (req) => { p.on('request', (req) => {
const path = new URL(req.url()).pathname; const path = new URL(req.url()).pathname;
if (/\/(cable-geo|landing-point-geo).*\.json$/.test(path)) reads.push(path); if (/\/(cable-geo|landing-point-geo).*\.json$/.test(path)) reads.push(path);
}); });
p.on('console', (msg) => {
if (msg.type() === 'error' && /Shader|WebGLProgram/i.test(msg.text()))
errors.push(msg.text());
});
await p.goto(base.href); await p.goto(base.href);
const ready = async (kind) => { await waitForEngine(p, 'navara');
await p.waitForFunction(
(k) =>
window.__godsEyeView?.engine?.kind === k &&
!document.querySelector('#globe-engine')?.disabled &&
!document.querySelector('.engine-switch-frame') &&
document.querySelector('#loading-screen.hidden'),
{ timeout: 90000 },
kind,
);
await p.evaluate(
() => window.__godsEyeView.styleManager.initialRestorePromise,
);
};
await ready('navara');
await p.evaluate(async () => { await p.evaluate(async () => {
const { dataManager, tileset } = window.__godsEyeView; const { dataManager, tileset } = window.__godsEyeView;
if (tileset || window.__GOOGLE_MAPS_API_KEY__) if (tileset || window.__GOOGLE_MAPS_API_KEY__)
@@ -71,8 +52,7 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
}); });
for (const kind of ['navara', 'cesium', 'navara']) { for (const kind of ['navara', 'cesium', 'navara']) {
if ((await p.evaluate(() => window.__godsEyeView.engine.kind)) !== kind) { if ((await p.evaluate(() => window.__godsEyeView.engine.kind)) !== kind) {
await strict.pick(p, kind); await strict.switchTo(p, kind);
await ready(kind);
} }
await p.waitForFunction( await p.waitForFunction(
() => window.qaCables.getStats().referenceLabelCount > 0, () => window.qaCables.getStats().referenceLabelCount > 0,
+10 -30
View File
@@ -1,7 +1,12 @@
import { trafficFixtureResponse } from './traffic-fixtures.mjs'; import { trafficFixtureResponse } from './traffic-fixtures.mjs';
import sharp from 'sharp'; import sharp from 'sharp';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import { createStrict, localPreview } from './qa-strict-harness.mjs'; import {
createStrict,
localPreview,
openPage,
waitForEngine,
} from './qa-strict-harness.mjs';
import { STRICT_TILE_HOSTS } from './qa-map-policy.mjs'; import { STRICT_TILE_HOSTS } from './qa-map-policy.mjs';
const base = localPreview(process.argv[2]); const base = localPreview(process.argv[2]);
@@ -22,14 +27,8 @@ const strict = createStrict({
fixtures: ['https://tiles.maps.eox.at/*', 'https://tiles.openfreemap.org/*'], fixtures: ['https://tiles.maps.eox.at/*', 'https://tiles.openfreemap.org/*'],
tileHosts: STRICT_TILE_HOSTS.filter((host) => host !== 'tiles.maps.eox.at'), tileHosts: STRICT_TILE_HOSTS.filter((host) => host !== 'tiles.maps.eox.at'),
}); });
await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => { await strict.run({}, async (b) => {
const p = await b.newPage(); const { page: p, errors } = await openPage(b, { shaderErrors: true });
await p.setViewport({ width: 1024, height: 768 });
const errors = [];
p.on('pageerror', (e) => {
errors.push(e.message);
console.log('error', e.message.split('?')[0]);
});
const keyless = process.argv.includes('--simulation'); const keyless = process.argv.includes('--simulation');
const reads = new Map(); const reads = new Map();
@@ -73,26 +72,8 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
} }
return req.continue(); return req.continue();
}); });
p.on('console', (msg) => {
if (msg.type() === 'error' && /Shader|WebGLProgram/i.test(msg.text()))
errors.push(msg.text());
});
await p.goto(base.href); await p.goto(base.href);
const ready = async (kind) => { await waitForEngine(p, 'navara');
await p.waitForFunction(
(k) =>
window.__godsEyeView?.engine?.kind === k &&
!document.querySelector('#globe-engine')?.disabled &&
!document.querySelector('.engine-switch-frame') &&
document.querySelector('#loading-screen.hidden'),
{ timeout: 90000 },
kind,
);
await p.evaluate(
() => window.__godsEyeView.styleManager.initialRestorePromise,
);
};
await ready('navara');
await p.evaluate(async () => { await p.evaluate(async () => {
const { tileset, dataManager } = window.__godsEyeView; const { tileset, dataManager } = window.__godsEyeView;
if (tileset || window.__GOOGLE_MAPS_API_KEY__) if (tileset || window.__GOOGLE_MAPS_API_KEY__)
@@ -199,8 +180,7 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
); );
for (const kind of ['navara', 'cesium', 'navara']) { for (const kind of ['navara', 'cesium', 'navara']) {
if ((await p.evaluate(() => window.__godsEyeView.engine.kind)) !== kind) { if ((await p.evaluate(() => window.__godsEyeView.engine.kind)) !== kind) {
await strict.pick(p, kind); await strict.switchTo(p, kind);
await ready(kind);
} }
await waitTraffic(); await waitTraffic();
const check = await p.evaluate(() => { const check = await p.evaluate(() => {
+1 -1
View File
@@ -31,7 +31,7 @@ const strict = createStrict({
'*://*.earthdata.nasa.gov/*', '*://*.earthdata.nasa.gov/*',
], ],
}); });
await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => { await strict.run({}, async (b) => {
const p = await b.newPage(); const p = await b.newPage();
await p.setViewport({ width: 1280, height: 900 }); await p.setViewport({ width: 1280, height: 900 });
const errors = []; const errors = [];
+1 -1
View File
@@ -38,7 +38,7 @@ const getOpt = (name, dflt) => {
}; };
const APP_URL = getOpt('--url', 'http://localhost:4173'); const APP_URL = getOpt('--url', 'http://localhost:4173');
// --engine cesium|navara (phase 3): frame, settle and read the renderer // --engine cesium|navara: frame, settle and read the renderer
// through window.__godsEyeView.engine (either engine) instead of the Cesium // through window.__godsEyeView.engine (either engine) instead of the Cesium
// viewer; the URL gets ?engine=. Without it, as before. // viewer; the URL gets ?engine=. Without it, as before.
const ENGINE = getOpt('--engine', null); const ENGINE = getOpt('--engine', null);
+1 -1
View File
@@ -19,7 +19,7 @@
* node scripts/qa-vessel-datum.mjs --ports rotterdam --url http://localhost:4173 * node scripts/qa-vessel-datum.mjs --ports rotterdam --url http://localhost:4173
* node scripts/qa-vessel-datum.mjs --engine navara --url 'http://localhost:PORT/?qaFeed=vessels:fixture' * node scripts/qa-vessel-datum.mjs --engine navara --url 'http://localhost:PORT/?qaFeed=vessels:fixture'
* *
* --engine cesium|navara (phase 3): the engine-neutral path on either engine, * --engine cesium|navara: the engine-neutral path on either engine,
* through window.__godsEyeView.engine and the QA probe (layers.drawn: each * through window.__godsEyeView.engine and the QA probe (layers.drawn: each
* chevron's drawn position and depth-test flag) instead of the Cesium * chevron's drawn position and depth-test flag) instead of the Cesium
* viewer's primitives; the URL gets ?engine=. Without it, as before. * viewer's primitives; the URL gets ?engine=. Without it, as before.
+23 -18
View File
@@ -1,19 +1,28 @@
// Recent imagery on a keyless preview, starting on Navara: the NASA catalog
// and imagery tiles are fixtures, and the image, A/B, swap, divider, opacity,
// basemap, toggle, failure, box-drag and zoom-to-fit checks read the rendered
// pixels; both pinned dates survive a switch to Cesium and back. The report
// and its screenshots go next to the evidence file (or to the temp directory).
//
// node scripts/qa-w2-imagery.mjs http://127.0.0.1:<PORT>
// [--layers=recent-imagery] [--evidence=FILE]
import fs from 'node:fs/promises'; import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import sharp from 'sharp'; import sharp from 'sharp';
import { createStrict, localPreview } from './qa-strict-harness.mjs'; import {
createStrict,
localPreview,
strictArguments,
} from './qa-strict-harness.mjs';
import { isExpectedPreviewSetupError, redact } from './qa-browserEvidence.mjs'; import { isExpectedPreviewSetupError, redact } from './qa-browserEvidence.mjs';
const args = process.argv.slice(2); const { origin } = localPreview(process.argv[2]);
const option = (name, fallback) => const url = origin + '/?engine=navara';
args.includes(name) ? args[args.indexOf(name) + 1] : fallback; const { evidence } = strictArguments();
const url = option('--url', 'http://127.0.0.1:4193'); const output = evidence
const output = option('--report', '/private/tmp/gev-imagery.json'); ? path.join(path.dirname(evidence), 'imagery-report.json')
if ( : path.join(os.tmpdir(), 'gev-imagery.json');
['4173', '4185', '4186', '4187', '4188', '4189', '4194'].includes(
new URL(url).port,
)
)
throw new Error('Reserved QA port');
const report = { const report = {
url, url,
checks: [], checks: [],
@@ -26,7 +35,7 @@ let failLandsat = false,
// The engine the page runs, for the strict assertion manifest. // The engine the page runs, for the strict assertion manifest.
let engine = null; let engine = null;
const strict = createStrict({ const strict = createStrict({
origin: localPreview(url).origin, origin,
layers: ['recent-imagery'], layers: ['recent-imagery'],
// The scenario answers the NASA catalog and imagery tiles itself. // The scenario answers the NASA catalog and imagery tiles itself.
fixtures: ['*://*.earthdata.nasa.gov/*'], fixtures: ['*://*.earthdata.nasa.gov/*'],
@@ -48,8 +57,6 @@ const browser = await strict.launch({
headless: true, headless: true,
args: [ args: [
'--no-sandbox', '--no-sandbox',
'--use-angle=metal',
'--enable-gpu',
'--disable-background-timer-throttling', '--disable-background-timer-throttling',
'--disable-renderer-backgrounding', '--disable-renderer-backgrounding',
], ],
@@ -63,7 +70,7 @@ try {
const text = redact(message.text()); const text = redact(message.text());
const expected = const expected =
isExpectedPreviewSetupError(message, { isExpectedPreviewSetupError(message, {
preview: args.includes('--preview'), preview: true,
baseUrl: url, baseUrl: url,
}) || }) ||
(failedTiles > 0 && (failedTiles > 0 &&
@@ -231,7 +238,6 @@ try {
await sleep(2500); await sleep(2500);
image = await capture('ab'); image = await capture('ab');
check('A/B dates on opposite sides', red(image.left) && green(image.right)); check('A/B dates on opposite sides', red(image.left) && green(image.right));
if (args.includes('--switch')) {
const first = await page.evaluate(() => { const first = await page.evaluate(() => {
window.qaImageryOwner = window.__gevRecentImagery.layer; window.qaImageryOwner = window.__gevRecentImagery.layer;
return window.qaImageryOwner.diagnostics().pins; return window.qaImageryOwner.diagnostics().pins;
@@ -262,7 +268,6 @@ try {
); );
check(kind + ' reuses the STAC search', reads() === expectedReads); check(kind + ' reuses the STAC search', reads() === expectedReads);
} }
}
await page.evaluate(() => window.__gevRecentImagery.layer.swapSides()); await page.evaluate(() => window.__gevRecentImagery.layer.swapSides());
image = await capture('swap'); image = await capture('swap');
check('swap reverses dates', green(image.left) && red(image.right)); check('swap reverses dates', green(image.left) && red(image.right));
+11 -27
View File
@@ -1,7 +1,12 @@
import { fromDegrees } from '../src/geo/cartesian3.js'; import { fromDegrees } from '../src/geo/cartesian3.js';
import sharp from 'sharp'; import sharp from 'sharp';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import { createStrict, localPreview } from './qa-strict-harness.mjs'; import {
createStrict,
localPreview,
openPage,
waitForEngine,
} from './qa-strict-harness.mjs';
const base = localPreview(process.argv[2]); const base = localPreview(process.argv[2]);
base.search = '?engine=navara&welcome=0&look=cinematic'; base.search = '?engine=navara&welcome=0&look=cinematic';
@@ -16,14 +21,8 @@ const strict = createStrict({
'weather-cyclones', 'weather-cyclones',
], ],
}); });
await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => { await strict.run({}, async (b) => {
const p = await b.newPage(); const { page: p, errors } = await openPage(b);
await p.setViewport({ width: 1024, height: 768 });
const errors = [];
p.on('pageerror', (e) => {
errors.push(e.message);
console.log('error', e.message.split('?')[0]);
});
const times = ['2026-11-02T12:00:00.000Z', '2026-11-02T12:05:00.000Z']; const times = ['2026-11-02T12:00:00.000Z', '2026-11-02T12:05:00.000Z'];
const ids = ['weather-radar', 'weather-satellite', 'weather-lightning']; const ids = ['weather-radar', 'weather-satellite', 'weather-lightning'];
const images = new Map(), const images = new Map(),
@@ -168,21 +167,7 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
console.log('GPU', msg.text().slice(0, 3000)); console.log('GPU', msg.text().slice(0, 3000));
}); });
await p.goto(base.href); await p.goto(base.href);
const ready = async (kind) => { await waitForEngine(p, 'navara');
await p.waitForFunction(
(k) =>
window.__godsEyeView?.engine?.kind === k &&
!document.querySelector('#globe-engine')?.disabled &&
!document.querySelector('.engine-switch-frame') &&
document.querySelector('#loading-screen.hidden'),
{ timeout: 90000 },
kind,
);
await p.evaluate(
() => window.__godsEyeView.styleManager.initialRestorePromise,
);
};
await ready('navara');
const waitTime = (ids, time) => const waitTime = (ids, time) =>
p.waitForFunction( p.waitForFunction(
(ids, time) => (ids, time) =>
@@ -251,8 +236,7 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
const polls = [...reads.entries()]; const polls = [...reads.entries()];
for (const kind of ['navara', 'cesium', 'navara']) { for (const kind of ['navara', 'cesium', 'navara']) {
if ((await p.evaluate(() => window.__godsEyeView.engine.kind)) !== kind) { if ((await p.evaluate(() => window.__godsEyeView.engine.kind)) !== kind) {
await strict.pick(p, kind); await strict.switchTo(p, kind);
await ready(kind);
} }
await waitTime(ids, times[0]); await waitTime(ids, times[0]);
const state = await p.evaluate( const state = await p.evaluate(
@@ -364,7 +348,7 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
for (const kind of ['navara', 'cesium', 'navara']) { for (const kind of ['navara', 'cesium', 'navara']) {
if ((await p.evaluate(() => window.__godsEyeView.engine.kind)) !== kind) { if ((await p.evaluate(() => window.__godsEyeView.engine.kind)) !== kind) {
await p.select('#globe-engine', kind); await p.select('#globe-engine', kind);
await ready(kind); await waitForEngine(p, kind);
} }
await p.waitForFunction( await p.waitForFunction(
() => () =>
+10 -26
View File
@@ -1,6 +1,11 @@
import sharp from 'sharp'; import sharp from 'sharp';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import { createStrict, localPreview } from './qa-strict-harness.mjs'; import {
createStrict,
localPreview,
openPage,
waitForEngine,
} from './qa-strict-harness.mjs';
const base = localPreview(process.argv[2]); const base = localPreview(process.argv[2]);
base.search = '?engine=navara&welcome=0&look=cinematic'; base.search = '?engine=navara&welcome=0&look=cinematic';
@@ -9,14 +14,8 @@ base.hash =
// The fixture field is uniform, so the GPU seeds a fixed particle count. // The fixture field is uniform, so the GPU seeds a fixed particle count.
const WIND_PATHS = 7200; const WIND_PATHS = 7200;
const strict = createStrict({ origin: base.origin, layers: ['wind'] }); const strict = createStrict({ origin: base.origin, layers: ['wind'] });
await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => { await strict.run({}, async (b) => {
const p = await b.newPage(); const { page: p, errors } = await openPage(b);
await p.setViewport({ width: 1024, height: 768 });
const errors = [];
p.on('pageerror', (e) => {
errors.push(e.message);
console.log('error', e.message.split('?')[0]);
});
const reads = []; const reads = [];
await p.setRequestInterception(true); await p.setRequestInterception(true);
p.on('request', (req) => { p.on('request', (req) => {
@@ -70,21 +69,7 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
} }
}); });
await p.goto(base.href); await p.goto(base.href);
const ready = async (kind) => { await waitForEngine(p, 'navara');
await p.waitForFunction(
(k) =>
window.__godsEyeView?.engine?.kind === k &&
!document.querySelector('#globe-engine')?.disabled &&
!document.querySelector('.engine-switch-frame') &&
document.querySelector('#loading-screen.hidden'),
{ timeout: 90000 },
kind,
);
await p.evaluate(
() => window.__godsEyeView.styleManager.initialRestorePromise,
);
};
await ready('navara');
await p.evaluate(async () => { await p.evaluate(async () => {
const { dataManager, tileset } = window.__godsEyeView; const { dataManager, tileset } = window.__godsEyeView;
if (tileset || window.__GOOGLE_MAPS_API_KEY__) if (tileset || window.__GOOGLE_MAPS_API_KEY__)
@@ -103,8 +88,7 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
const baseline = reads.length; const baseline = reads.length;
for (const kind of ['navara', 'cesium', 'navara']) { for (const kind of ['navara', 'cesium', 'navara']) {
if ((await p.evaluate(() => window.__godsEyeView.engine.kind)) !== kind) { if ((await p.evaluate(() => window.__godsEyeView.engine.kind)) !== kind) {
await strict.pick(p, kind); await strict.switchTo(p, kind);
await ready(kind);
} }
await p.waitForFunction( await p.waitForFunction(
() => () =>
+4 -34
View File
@@ -5,19 +5,7 @@
// the session ids, listener counts and transport counters. // the session ids, listener counts and transport counters.
import puppeteer from 'puppeteer'; import puppeteer from 'puppeteer';
import { blockPaidQaMaps } from '../qa-map-policy.mjs'; import { blockPaidQaMaps } from '../qa-map-policy.mjs';
import { localPreview, waitForEngine } from '../qa-strict-harness.mjs';
const RESERVED_PORTS = new Set([
'',
'4173',
'4174',
'4185',
'4186',
'4187',
'4188',
'4189',
'4194',
'4196',
]);
/** Paid or live model endpoints no switch gate may reach. */ /** Paid or live model endpoints no switch gate may reach. */
export const BLOCKED_URLS = Object.freeze([ export const BLOCKED_URLS = Object.freeze([
@@ -36,13 +24,7 @@ export const BLOCKED_URLS = Object.freeze([
/** The preview origin from argv, refusing the ports that belong to the user. */ /** The preview origin from argv, refusing the ports that belong to the user. */
export function previewBase(argument) { export function previewBase(argument) {
const base = new URL(argument ?? ''); return localPreview(argument ?? '');
if (
!['localhost', '127.0.0.1'].includes(base.hostname) ||
RESERVED_PORTS.has(base.port)
)
throw new Error('Supply an unreserved local preview URL');
return base;
} }
/** The page address: Boston on OSM, no welcome, on the given engine. */ /** The page address: Boston on OSM, no welcome, on the given engine. */
@@ -247,20 +229,8 @@ export async function openSessionPage(browser, address) {
} }
/** The engine runs, its picker is idle and the loading screen is gone. */ /** The engine runs, its picker is idle and the loading screen is gone. */
export async function waitReady(page, kind) { export function waitReady(page, kind) {
await page.waitForFunction( return waitForEngine(page, kind, { timeout: 90_000 });
(kind) =>
window.__godsEyeView?.engine?.kind === kind &&
document.querySelector('#globe-engine')?.value === kind &&
!document.querySelector('#globe-engine').disabled &&
document.getElementById('loading-screen')?.classList.contains('hidden') &&
!document.querySelector('.engine-switch-frame'),
{ timeout: 90_000, polling: 100 },
kind,
);
await page.evaluate(
() => window.__godsEyeView.styleManager.initialRestorePromise,
);
} }
/** /**
+7 -88
View File
@@ -1,14 +1,12 @@
// Map tiles for headless QA: a disk cache shared by every run (in the system // Map tiles for headless QA: the disk cache entry of a tile request and one
// temp directory) and one fetch per missing tile, with backoff on HTTP 429, // fetch per missing tile, with backoff on HTTP 429, for the free tile hosts
// for the free tile hosts the QA pages load (OpenStreetMap imagery, the // the QA pages load (OpenStreetMap imagery, the keyless re:earth terrain).
// keyless re:earth terrain). Back-to-back QA runs otherwise reload the same // Back-to-back QA runs otherwise reload the same tiles until those hosts
// tiles until those hosts rate-limit them. Only requests to these hosts are // rate-limit them. The strict harness (scripts/qa-strict-harness.mjs) pauses
// paused (CDP Fetch with URL patterns); page-wide request interception also // only the requests to these hosts (CDP Fetch with URL patterns) and answers
// held the Navara workers' requests and kept workers alive past their globe. // them from the cache in the system temp directory.
import { createHash } from 'node:crypto'; import { createHash } from 'node:crypto';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path'; import path from 'node:path';
export const CACHED_TILE_HOSTS = Object.freeze([ export const CACHED_TILE_HOSTS = Object.freeze([
@@ -119,82 +117,3 @@ export function createMissLoader({
}, },
}; };
} }
/**
* Serves the page's requests to CACHED_TILE_HOSTS (or `hosts`) from the
* cache (filling it on a miss). Returns the counts.
*/
export async function installTileCache(
page,
{
dir = path.join(os.tmpdir(), 'gev-qa-tiles'),
hosts = CACHED_TILE_HOSTS,
} = {},
) {
fs.mkdirSync(dir, { recursive: true });
const stats = { hits: 0, fetched: 0, cancelled: 0, failed: 0, errors: {} };
const client = await page.createCDPSession();
const misses = createMissLoader();
client.on('Fetch.requestPaused', async ({ requestId, request }) => {
const accept = Object.entries(request.headers ?? {}).find(
([name]) => name.toLowerCase() === 'accept',
)?.[1];
const file =
request.method === 'GET'
? tileCacheFile(request.url, accept, dir, hosts)
: null;
try {
if (!file)
return await client.send('Fetch.continueRequest', { requestId });
let entry = null;
if (fs.existsSync(`${file}.json`)) {
entry = {
...JSON.parse(fs.readFileSync(`${file}.json`, 'utf8')),
body: fs.readFileSync(file),
};
stats.hits++;
} else {
const tile = await misses.load(request.url, accept);
// Only successes are cached; a 404 (no tile there) is passed on.
if (tile.status === 200) {
fs.writeFileSync(file, tile.body);
fs.writeFileSync(
`${file}.json`,
JSON.stringify({ status: 200, contentType: tile.contentType }),
);
stats.fetched++;
} else if (tile.status === 429) stats.failed++;
entry = tile;
}
await client.send('Fetch.fulfillRequest', {
requestId,
responseCode: entry.status,
responseHeaders: [
...(entry.contentType
? [{ name: 'Content-Type', value: entry.contentType }]
: []),
{ name: 'Access-Control-Allow-Origin', value: '*' },
],
body: entry.body.toString('base64'),
});
} catch (error) {
// The page dropped the request meanwhile (a tile no longer in view).
if (/Invalid InterceptionId/.test(String(error?.message))) {
stats.cancelled++;
return;
}
stats.failed++;
const reason = String(error?.message ?? error).slice(0, 80);
stats.errors[reason] = (stats.errors[reason] ?? 0) + 1;
await client
.send('Fetch.failRequest', { requestId, errorReason: 'Failed' })
.catch(() => {});
}
});
await client.send('Fetch.enable', {
patterns: hosts.map((host) => ({
urlPattern: `https://${host}/*`,
})),
});
return stats;
}
+1 -1
View File
@@ -14,7 +14,7 @@ const LINE_ID = 'gev-draw-preview';
const DOTS_ID = 'gev-draw-vertices'; const DOTS_ID = 'gev-draw-vertices';
/** '#rrggbb' → rgba in 0..1. */ /** '#rrggbb' → rgba in 0..1. */
export function cssRgba(css, alpha = 1) { function cssRgba(css, alpha = 1) {
const hex = Number.parseInt(String(css).slice(1), 16); const hex = Number.parseInt(String(css).slice(1), 16);
return [ return [
((hex >> 16) & 255) / 255, ((hex >> 16) & 255) / 255,
-11
View File
@@ -68,14 +68,3 @@ export function catalogControlServices(catalog) {
}), }),
); );
} }
/**
* Layer roles for controls on a page without data layers: one empty object
* per role, so the shell's optional calls
* (`layer.method?.()`) do nothing.
*/
export function absentLayerControlServices() {
return Object.fromEntries(
Object.keys(CONTROL_LAYER_IDS).map((role) => [role, {}]),
);
}
+1 -39
View File
@@ -1,10 +1,6 @@
import test from 'node:test'; import test from 'node:test';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import { import { createLayerCatalog, catalogControlServices } from './catalog.js';
absentLayerControlServices,
createLayerCatalog,
catalogControlServices,
} from './catalog.js';
import { createApplicationData } from './data.js'; import { createApplicationData } from './data.js';
const metadata = (layers) => const metadata = (layers) =>
@@ -158,40 +154,6 @@ test('controls bind catalog instances rather than similarly named defaults', ()
); );
}); });
test('without data layers the controls get one empty stand-in per layer role', () => {
const ids = [
'traffic',
'flights',
'military',
'satellites',
'cctv',
'street-level',
'radio',
'bikeshare',
'transit',
'ais-live-vessels',
'military-awareness',
'military-installations',
'rocket-launches',
'local-adsb',
];
const layers = ids.map((id) => ({ id }));
const roles = Object.keys(
catalogControlServices(createLayerCatalog(layers, metadata(layers))),
);
const absent = absentLayerControlServices();
assert.deepEqual(Object.keys(absent), roles);
for (const role of roles) {
assert.deepEqual(Object.keys(absent[role]), [], role);
assert.equal(absent[role].subscribe, undefined);
}
assert.notEqual(absent.flightsLayer, absent.trafficLayer);
assert.notEqual(
absentLayerControlServices().flightsLayer,
absent.flightsLayer,
);
});
test('data setup without a viewer registers an empty catalog and no sonar scene', async (t) => { test('data setup without a viewer registers an empty catalog and no sonar scene', async (t) => {
const previous = globalThis.document; const previous = globalThis.document;
globalThis.document = { getElementById: () => null }; globalThis.document = { getElementById: () => null };
+9 -9
View File
@@ -6,17 +6,17 @@ import { createAirQualityCesiumRenderer } from '../../layers/airQuality/render/c
import { createFirmsCesiumRenderer } from '../../layers/firms/render/cesium.js'; import { createFirmsCesiumRenderer } from '../../layers/firms/render/cesium.js';
import { createEarthquakesCesiumRenderer } from '../../layers/earthquakes/render/cesium.js'; import { createEarthquakesCesiumRenderer } from '../../layers/earthquakes/render/cesium.js';
import { createLaunchesCesiumRenderer } from '../../layers/launches/render/cesium.js'; import { createLaunchesCesiumRenderer } from '../../layers/launches/render/cesium.js';
// Which engine draws which layer: the layer renderer registry (plan, "layer // Which engine draws which layer: the layer renderer registry and the layer
// renderer interface") and the layer availability API built on it. // availability API built on it.
// //
// A renderer factory `(engine, options) => renderer` draws one layer on one // A renderer factory `(engine, options) => renderer` draws one layer on one
// engine kind (`render/cesium.js`, `render/navara.js` of a layer). Cesium // engine kind (`render/cesium.js`, `render/navara.js` of a layer). Cesium
// layers still draw themselves until their render/cesium.js is extracted // layers without a render/cesium.js draw themselves: they are registered as
// (the *a tasks): they are registered as CESIUM_BUILTIN. Cesium entries are // CESIUM_BUILTIN. Cesium entries are registered here, statically (main
// registered here, statically (main chunk); Navara factories are registered // chunk); Navara factories are registered by the Navara mode after its
// by the Navara mode after its import() (src/app/navaraMode.js). // import() (src/app/navaraMode.js).
// //
// ## Layer availability (stable API, used by phase 4's layer list) // ## Layer availability (stable API, used by the layer list)
// //
// import { createLayerAvailability } from './app/layers/renderers.js'; // import { createLayerAvailability } from './app/layers/renderers.js';
// const availability = createLayerAvailability(engine.kind); // const availability = createLayerAvailability(engine.kind);
@@ -28,8 +28,8 @@ import { createLaunchesCesiumRenderer } from '../../layers/launches/render/cesiu
// The object is frozen; available() and unavailable() return fresh arrays. // The object is frozen; available() and unavailable() return fresh arrays.
// It reads the registry when called, so a Navara factory registered later // It reads the registry when called, so a Navara factory registered later
// (after the Navara mode loads) shows up. `name` is the layer's display name // (after the Navara mode loads) shows up. `name` is the layer's display name
// (src/app/layers/directory.js). Phase 4 injects it as its `availability` // (src/app/layers/directory.js). The layer list takes it as its
// provider (`{ engine, unavailable() }` is the part it reads). // `availability` provider (`{ engine, unavailable() }` is the part it reads).
import { APPLICATION_LAYER_DIRECTORY } from './directory.js'; import { APPLICATION_LAYER_DIRECTORY } from './directory.js';
import { createFlightsCesiumRenderer } from '../../layers/flights/render/cesium.js'; import { createFlightsCesiumRenderer } from '../../layers/flights/render/cesium.js';
+2 -2
View File
@@ -14,7 +14,7 @@
// drawn(id) what the layer draws per object (glyph, model), // drawn(id) what the layer draws per object (glyph, model),
// as plain data, or null (layers with qaDrawn) // as plain data, or null (layers with qaDrawn)
// following() { label, native } of engine.camera.following() // following() { label, native } of engine.camera.following()
// movers() engine.movers.list() // movers() engine.movers.list() ([] without movers: Cesium)
// framesDrawn() frames drawn since the probe started // framesDrawn() frames drawn since the probe started
const plain = (position) => const plain = (position) =>
@@ -83,7 +83,7 @@ export function createQaProbe({ engine, dataManager, availability }) {
native: handle.native?.id ?? null, native: handle.native?.id ?? null,
}; };
}, },
movers: () => engine.movers.list(), movers: () => engine.movers?.list() ?? [],
framesDrawn: () => frames, framesDrawn: () => frames,
destroy: () => removeFrames(), destroy: () => removeFrames(),
}; };
+5 -5
View File
@@ -217,11 +217,11 @@ async function createRenderer({
googleTileset: tileset, googleTileset: tileset,
cesiumToken, cesiumToken,
initialStack: tileset ? 'photoreal' : 'esri-imagery', initialStack: tileset ? 'photoreal' : 'esri-imagery',
// Task 5 (height-datum fix): rebroadcast stack changes as a window // Rebroadcast stack changes as a window CustomEvent so data layers (CCTV
// CustomEvent so data layers (CCTV per-regime ground resolution) can // per-regime ground resolution) can react without coupling
// react without coupling MapStackController to layer modules. Fires on // MapStackController to layer modules. Fires on 'switching'/'ready'/
// 'switching'/'ready'/'error'; listeners derive the surface regime from // 'error'; listeners derive the surface regime from live scene state, so
// live scene state, so intermediate emissions are harmless. // intermediate emissions are harmless.
onChange: (state) => { onChange: (state) => {
window.dispatchEvent( window.dispatchEvent(
new CustomEvent('gev:map-stack-changed', { detail: state }), new CustomEvent('gev:map-stack-changed', { detail: state }),
-9
View File
@@ -9,13 +9,6 @@
// a release that runs, in reverse, when the part detaches (before its // a release that runs, in reverse, when the part detaches (before its
// detachScene). // detachScene).
const STATES = Object.freeze([
'detached',
'attaching',
'attached',
'detaching',
]);
export class SceneSwitchingError extends Error { export class SceneSwitchingError extends Error {
constructor(message = 'The globe is switching engines') { constructor(message = 'The globe is switching engines') {
super(message); super(message);
@@ -208,5 +201,3 @@ export function createSceneHost({ onError = null } = {}) {
}, },
}); });
} }
export const SCENE_HOST_STATES = STATES;
+1 -1
View File
@@ -327,7 +327,7 @@ function attachViewerTools(
); );
// Install the explicit scope mask used by the DISPLAY controls. // Install the explicit scope mask used by the DISPLAY controls.
installScopeMask(viewer); installScopeMask(engine);
defer(() => destroyScopeMask()); defer(() => destroyScopeMask());
// The follow camera recomputes the tracked target's dead-reckon position // The follow camera recomputes the tracked target's dead-reckon position
+1 -1
View File
@@ -9,7 +9,7 @@ export const PITCH_CLAMP_MIN_HEIGHT_M = 300_000;
// with the first Cesium globe, after this module (src/app/loadCesium.js). // with the first Cesium globe, after this module (src/app/loadCesium.js).
const RADIANS_PER_DEGREE = Math.PI / 180; const RADIANS_PER_DEGREE = Math.PI / 180;
/** Look this far below the Earth's limb, so the globe stays on screen. */ /** Look this far below the Earth's limb, so the globe stays on screen. */
export const LIMB_MARGIN_RAD = 8 * RADIANS_PER_DEGREE; const LIMB_MARGIN_RAD = 8 * RADIANS_PER_DEGREE;
const PITCH_EASE = 0.35; const PITCH_EASE = 0.35;
const PITCH_SNAP_RAD = 0.1 * RADIANS_PER_DEGREE; const PITCH_SNAP_RAD = 0.1 * RADIANS_PER_DEGREE;
const HEIGHT_EPSILON_M = 0.0001; const HEIGHT_EPSILON_M = 0.0001;
+1 -1
View File
@@ -1,6 +1,6 @@
// Deterministic Launch Library 2 records for phase-3 mission/replay acceptance. // Deterministic Launch Library 2 records for phase-3 mission/replay acceptance.
import { SATELLITE_FIXTURE_EPOCH } from './satelliteFixture.js'; import { SATELLITE_FIXTURE_EPOCH } from './satelliteFixture.js';
export const LAUNCH_FIXTURE_EPOCH = SATELLITE_FIXTURE_EPOCH; const LAUNCH_FIXTURE_EPOCH = SATELLITE_FIXTURE_EPOCH;
export function launchFixture() { export function launchFixture() {
const definitions = [ const definitions = [
[ [
+1 -3
View File
@@ -1,4 +1,4 @@
// Deterministic TLE catalogs for phase 3 browser QA; no live feed requests. // Deterministic TLE catalogs for browser QA; no live feed requests.
import { W1_FROZEN_MS } from './w1Feeds.js'; import { W1_FROZEN_MS } from './w1Feeds.js';
const checksum = (line) => { const checksum = (line) => {
@@ -30,8 +30,6 @@ const GROUPS = {
starlink: [50000, 6000, 53, 15.2, 'QA STARLINK'], starlink: [50000, 6000, 53, 15.2, 'QA STARLINK'],
}; };
export const SATELLITE_FIXTURE_EPOCH = W1_FROZEN_MS; export const SATELLITE_FIXTURE_EPOCH = W1_FROZEN_MS;
export const SATELLITE_FIXTURE_CORE_COUNT = 839;
export const SATELLITE_FIXTURE_DENSE_COUNT = 6839;
export function satelliteFixture(group) { export function satelliteFixture(group) {
const spec = GROUPS[group]; const spec = GROUPS[group];
if (!spec) throw new TypeError('Unknown fixture group'); if (!spec) throw new TypeError('Unknown fixture group');
+1
View File
@@ -57,6 +57,7 @@ function fakeViewer({ cameraHeightM, surfaceM, canvas = null }) {
const position = Cesium.Cartographic.fromDegrees(-97.7603, 30.3141, cameraHeightM); const position = Cesium.Cartographic.fromDegrees(-97.7603, 30.3141, cameraHeightM);
const viewer = { const viewer = {
flights, flights,
canvas,
scene: { scene: {
canvas, canvas,
sampleHeightSupported: true, sampleHeightSupported: true,
+1 -1
View File
@@ -400,7 +400,7 @@ test('vessel and fire layers announce valid clicks and never fly cameras', () =>
assert.doesNotMatch(source, /camera\.flyTo/); assert.doesNotMatch(source, /camera\.flyTo/);
} }
// Vessels' click rule is a function the engine's selection dispatcher // Vessels' click rule is a function the engine's selection dispatcher
// calls (phase 3, 13a): the same body, out of the old LEFT_CLICK handler. // calls: the same body, out of the old LEFT_CLICK handler.
const vesselClick = body( const vesselClick = body(
vessels, vessels,
/function handleClick\(click\) \{([\s\S]*?)\n \}/, /function handleClick\(click\) \{([\s\S]*?)\n \}/,
-14
View File
@@ -2370,17 +2370,3 @@ export function flyRoute(
}; };
return typeof runNavigation === 'function' ? runNavigation(start) : start(); return typeof runNavigation === 'function' ? runNavigation(start) : start();
} }
// QA (development builds only; production builds drop it): the verbs and
// their diagnostics, so a browser scenario can drive them on an engine whose
// voice tools do not offer them yet.
if (import.meta.env?.DEV && typeof window !== 'undefined')
window.__gevCameraVerbs = Object.freeze({
cameraVerbsSupported,
flyRoute,
getActiveCameraMotion,
initCameraVerbs,
interruptCameraMotion,
moveCamera,
releaseCameraVerbs,
});
+1 -3
View File
@@ -261,10 +261,8 @@ const TRACKED_RASTER_PX = 192;
* Default size serves the fleet; pass `aircraftIcon(kind, TRACKED_ICON_PX)` * Default size serves the fleet; pass `aircraftIcon(kind, TRACKED_ICON_PX)`
* (re-exported below) for the tracked billboard. */ * (re-exported below) for the tracked billboard. */
export const TRACKED_ICON_PX = TRACKED_RASTER_PX; export const TRACKED_ICON_PX = TRACKED_RASTER_PX;
/** The fleet raster size (aircraftIcon's default). */
export const FLEET_ICON_PX = FLEET_RASTER_PX;
/** Every silhouette kind aircraftIcon draws (an engine's sprite atlas). */ /** Every silhouette kind aircraftIcon draws (an engine's sprite atlas). */
export const AIRCRAFT_ICON_KINDS = Object.freeze(Object.keys(BODIES)); const AIRCRAFT_ICON_KINDS = Object.freeze(Object.keys(BODIES));
export function aircraftIcon(kind, px = FLEET_RASTER_PX) { export function aircraftIcon(kind, px = FLEET_RASTER_PX) {
const k = BODIES[kind] ? kind : 'airliner'; const k = BODIES[kind] ? kind : 'airliner';
const key = `${k}@${px}`; const key = `${k}@${px}`;
+2 -2
View File
@@ -2,8 +2,8 @@ import { engineFromViewer } from '../globe/cesium/engineFromViewer.js';
/** /**
* Arrival corrections belong to one globe engine and yield to its next camera * Arrival corrections belong to one globe engine and yield to its next camera
* owner. Callers may name the engine or (layers until phase 1c) the viewer it * owner. Callers may name the engine or (layers that still hold one) the
* is registered for; both reach the same correction. * viewer it is registered for; both reach the same correction.
*/ */
const arrivals = new WeakMap(); const arrivals = new WeakMap();
+1 -1
View File
@@ -29,7 +29,7 @@ for (const layer of LAYERS) {
test(`${layer.name}: every GLB creation bypasses the tile-contended frame-spread queue`, () => { test(`${layer.name}: every GLB creation bypasses the tile-contended frame-spread queue`, () => {
// The aircraft renderer loads through its `loadModel`, Cesium.Model.fromGltfAsync // The aircraft renderer loads through its `loadModel`, Cesium.Model.fromGltfAsync
// unless a test injects a loader (phase 3). // unless a test injects a loader.
assert.match(source, /loadModel = \(options\) => Cesium\.Model\.fromGltfAsync\(options\)/); assert.match(source, /loadModel = \(options\) => Cesium\.Model\.fromGltfAsync\(options\)/);
const calls = [...source.matchAll(/(?:Cesium\.Model\.fromGltfAsync|\bloadModel)\(\{([\s\S]*?)\}\)/g)]; const calls = [...source.matchAll(/(?:Cesium\.Model\.fromGltfAsync|\bloadModel)\(\{([\s\S]*?)\}\)/g)];
assert.ok(calls.length >= 3, `expected fleet, tracked, and preload model calls; found ${calls.length}`); assert.ok(calls.length >= 3, `expected fleet, tracked, and preload model calls; found ${calls.length}`);
+2 -2
View File
@@ -34,7 +34,7 @@ export const OPENMAPTILES_CREDIT = {
key: 'openfreemap', key: 'openfreemap',
html: 'Vector tiles: <a href="https://openfreemap.org" target="_blank" rel="noopener">OpenFreeMap</a> © <a href="https://openmaptiles.org" target="_blank" rel="noopener">OpenMapTiles</a>', html: 'Vector tiles: <a href="https://openfreemap.org" target="_blank" rel="noopener">OpenFreeMap</a> © <a href="https://openmaptiles.org" target="_blank" rel="noopener">OpenMapTiles</a>',
}; };
export const GOOGLE_PLACES_CREDIT = { const GOOGLE_PLACES_CREDIT = {
key: 'google-maps-places', key: 'google-maps-places',
html: 'Google Maps Places: <a href="https://developers.google.com/maps/documentation/places/web-service/policies" target="_blank" rel="noopener">Google Maps</a>', html: 'Google Maps Places: <a href="https://developers.google.com/maps/documentation/places/web-service/policies" target="_blank" rel="noopener">Google Maps</a>',
}; };
@@ -45,7 +45,7 @@ const viewerCredits = new WeakMap();
/** /**
* Credits of a globe engine, or of a Cesium viewer's credit display for * Credits of a globe engine, or of a Cesium viewer's credit display for
* callers that still hold a viewer (layers until phase 1c); null without one. * callers that still hold a viewer (Cesium layers); null without one.
* @returns {{ addStatic: Function, request: () => void } | null} * @returns {{ addStatic: Function, request: () => void } | null}
*/ */
function creditsOf(target) { function creditsOf(target) {
+1 -1
View File
@@ -143,7 +143,7 @@ test('every scene click handler consults ownership before it picks', () => {
'src/layers/submarineCables/interaction.js', 'src/layers/submarineCables/interaction.js',
'viewer.scene.pick(click.position)', 'viewer.scene.pick(click.position)',
], ],
// Vessels take the engine dispatcher's pick (phase 3, 13a): the guard // Vessels take the engine dispatcher's pick: the guard
// comes before the click rule reads it. // comes before the click rule reads it.
['src/layers/vessels/selection.js', 'resolvePickId(picked)'], ['src/layers/vessels/selection.js', 'resolvePickId(picked)'],
]; ];
+1 -1
View File
@@ -140,7 +140,7 @@ test('sampler on a globe engine: samples engine.surface and latches against the
assert.equal(cachedMeshFloor(cell.lat, cell.lon), 137.0); assert.equal(cachedMeshFloor(cell.lat, cell.lon), 137.0);
const [call] = engine.fake.callsOf('surface.sampleHeightDetailed'); const [call] = engine.fake.callsOf('surface.sampleHeightDetailed');
assert.ok(Math.abs(Cesium.Math.toDegrees(call.args[0].latitude) - cell.lat) < 1e-9); assert.ok(Math.abs(Cesium.Math.toDegrees(call.args[0].latitude) - cell.lat) < 1e-9);
assert.deepEqual(call.args[1], { exclude: ['own'], lod: 'visible' }); assert.deepEqual(call.args[1], { exclude: ['own'] });
}); });
test('sampler: a coarse tile answer is a miss; a fine one latches', async () => { test('sampler: a coarse tile answer is a miss; a fine one latches', async () => {
+1 -2
View File
@@ -1,7 +1,6 @@
// Native constants shared by the W1 adapters; keep the SDK import here. // Native constants shared by the W1 adapters; keep the SDK import here.
// Read on call: the production build's Cesium global loads with the first // Read on call: the production build's Cesium global loads with the first
// Cesium globe, after this module has loaded on a Navara start. // Cesium globe, after this module has loaded on a Navara start.
import { Axis, ScreenSpaceEventType } from 'cesium'; import { ScreenSpaceEventType } from 'cesium';
export const modelAxis = () => Axis;
export const trackingEventTypes = () => ScreenSpaceEventType; export const trackingEventTypes = () => ScreenSpaceEventType;
+3 -3
View File
@@ -1,6 +1,6 @@
import { nearbyContact } from './pickRegistry.js'; import { nearbyContact } from './pickRegistry.js';
// One selection dispatcher for the layers that have moved onto the globe // One selection dispatcher for the layers that draw through the globe
// engine (phase 3 Task 4): one click subscription with picks // engine: one click subscription with picks
// (engine.input.on('click', …, { pick: true, drill: 8 })), the press // (engine.input.on('click', …, { pick: true, drill: 8 })), the press
// gesture and one Escape listener, instead of a ScreenSpaceEventHandler // gesture and one Escape listener, instead of a ScreenSpaceEventHandler
// per layer. Owners register as with pickRegistry (registerPickOwner, so // per layer. Owners register as with pickRegistry (registerPickOwner, so
@@ -38,7 +38,7 @@ import {
isTrackingSelectionGesture, isTrackingSelectionGesture,
} from './trackingClickGesture.js'; } from './trackingClickGesture.js';
export const SELECTION_DRILL = 8; const SELECTION_DRILL = 8;
/** /**
* @param {import('../globe/contract.js').GlobeEngine} engine * @param {import('../globe/contract.js').GlobeEngine} engine
+1 -1
View File
@@ -109,7 +109,7 @@ test('flights, AIS, and FIRMS enable paths are wired through the shared sprite r
assert.deepEqual(calls, [viewer, viewer, viewer]); assert.deepEqual(calls, [viewer, viewer, viewer]);
const firmsLayer = createFirmsHeatmapLayer({ id: 'firms', name: 'FIRMS' }); const firmsLayer = createFirmsHeatmapLayer({ id: 'firms', name: 'FIRMS' });
// Flights draws through its renderer (phase 3): enable asks the fleet to // Flights draws through its renderer: enable asks the fleet to
// restore its order, which the Cesium aircraft renderer wires to the shared // restore its order, which the Cesium aircraft renderer wires to the shared
// restorer under the flights' sprite slot. // restorer under the flights' sprite slot.
assert.match(flightsLayer.enable.toString(), /fleet\.restoreOrderOnEnable\(\)/); assert.match(flightsLayer.enable.toString(), /fleet\.restoreOrderOnEnable\(\)/);
+1 -1
View File
@@ -114,7 +114,7 @@ test('civilian and military click handlers apply duration only at the deselect b
readLayerSource(new URL('./flights.js', import.meta.url)), readLayerSource(new URL('./flights.js', import.meta.url)),
readLayerSource(new URL('./militaryFlights.js', import.meta.url)), readLayerSource(new URL('./militaryFlights.js', import.meta.url)),
]; ];
// Flights and military moved to the selection dispatcher (phase 3), whose // Flights and military use the selection dispatcher, whose
// source applies the same two rules: the selection gesture before a // source applies the same two rules: the selection gesture before a
// select, the full click classifier before the empty-space deselect, which // select, the full click classifier before the empty-space deselect, which
// each layer answers with a user-origin clear. // each layer answers with a user-origin clear.
+7 -9
View File
@@ -1,12 +1,15 @@
// Seeded, deterministic bit-for-bit comparison of src/geo against Cesium. // Seeded, deterministic bit-for-bit comparison of src/geo against Cesium's
// release build (Build/Cesium, debug checks stripped, as production ships it).
// Every number is compared with Object.is (so -0 and NaN count), a throw on one // Every number is compared with Object.is (so -0 and NaN count), a throw on one
// side must be a throw on the other, and `result` aliasing is exercised the // side must be a throw on the other, and `result` aliasing is exercised the
// same way on both sides. // same way on both sides.
import { test } from 'node:test'; import { test } from 'node:test';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import * as Cesium from 'cesium'; import { createRequire } from 'node:module';
import * as geo from './index.js'; import * as geo from './index.js';
const Cesium = createRequire(import.meta.url)('cesium/Build/Cesium/index.cjs');
const { const {
cartesian3: c3, cartesian3: c3,
cartographic, cartographic,
@@ -108,7 +111,7 @@ const cesium = (v) => new C3(v.x, v.y, v.z);
test('vector operations, including -0 inputs and aliased results, equal Cesium bit for bit', () => { test('vector operations, including -0 inputs and aliased results, equal Cesium bit for bit', () => {
const { scalar, vector } = inputs(1); const { scalar, vector } = inputs(1);
// Directionless vectors first: normalize must throw on both sides. // Directionless vectors first: normalize returns NaN on both sides.
const DEGENERATE = [ const DEGENERATE = [
{ x: 0, y: 0, z: 0 }, { x: 0, y: 0, z: 0 },
{ x: -0, y: 0, z: -0 }, { x: -0, y: 0, z: -0 },
@@ -397,12 +400,7 @@ test('matrices and quaternions, including aliased results and every fromRotation
][i % 4]; ][i % 4];
const angle = const angle =
i % 8 < 4 ? Math.PI - 0.5 * random() : (random() - 0.5) * 2 * Math.PI; i % 8 < 4 ? Math.PI - 0.5 * random() : (random() - 0.5) * 2 * Math.PI;
let m; const m = rotation(axis, angle);
try {
m = rotation(axis, angle);
} catch {
continue; // a zero axis: Cesium throws, nothing to compare
}
const trace = m[0] + m[4] + m[8]; const trace = m[0] + m[4] + m[8];
branches.add( branches.add(
trace > 0 trace > 0
-7
View File
@@ -1,8 +1,6 @@
// Ported from CesiumJS 1.138 (Apache-2.0) — BoundingSphere.js subset. // Ported from CesiumJS 1.138 (Apache-2.0) — BoundingSphere.js subset.
// Exact mission framing: Ritter/box fit and the union of two spheres. // Exact mission framing: Ritter/box fit and the union of two spheres.
import * as Cartesian3 from './cartesian3.js'; import * as Cartesian3 from './cartesian3.js';
import { Check } from './check.js';
import { DEBUG } from './debug.js';
const defined = (value) => value !== null && value !== undefined; const defined = (value) => value !== null && value !== undefined;
export function create(center = Cartesian3.ZERO, radius = 0) { export function create(center = Cartesian3.ZERO, radius = 0) {
return { center: Cartesian3.clone(center), radius }; return { center: Cartesian3.clone(center), radius };
@@ -184,11 +182,6 @@ export function fromPoints(positions, result) {
return result; return result;
} }
export function union(left, right, result) { export function union(left, right, result) {
if (DEBUG) {
Check.typeOf.object('left', left);
Check.typeOf.object('right', right);
}
if (!defined(result)) { if (!defined(result)) {
result = create(); result = create();
} }
+1 -11
View File
@@ -1,17 +1,12 @@
// Ported from CesiumJS 1.138 (Apache-2.0) — Cartesian2.js // Ported from CesiumJS 1.138 (Apache-2.0) — Cartesian2.js
// //
// 2D vectors (screen coordinates) as plain-data {x, y} objects. With DEBUG on // 2D vectors (screen coordinates) as plain-data {x, y} objects.
// (development and tests; see debug.js) invalid arguments throw the same
// DeveloperError as Cesium's debug checks.
// //
// Geo values are not Object.prototype objects, so src/app/stateChannel.js // Geo values are not Object.prototype objects, so src/app/stateChannel.js
// copyState and src/director/documentFields.js jsonTree reject them: copy // copyState and src/director/documentFields.js jsonTree reject them: copy
// the fields into a literal before putting one into product state or a // the fields into a literal before putting one into product state or a
// scene document. // scene document.
import { DEBUG } from './debug.js';
import { Check } from './check.js';
// Values are instances of a module-private constructor rather than object // Values are instances of a module-private constructor rather than object
// literals: every `{x, y}` literal in the process shares one V8 hidden // literals: every `{x, y}` literal in the process shares one V8 hidden
// class, and code elsewhere that builds such literals would make geo's hot // class, and code elsewhere that builds such literals would make geo's hot
@@ -38,11 +33,6 @@ export function clone(cartesian, result) {
/** The distance between two points. */ /** The distance between two points. */
export function distance(left, right) { export function distance(left, right) {
if (DEBUG) {
Check.typeOf.object('left', left);
Check.typeOf.object('right', right);
}
const x = left.x - right.x; const x = left.x - right.x;
const y = left.y - right.y; const y = left.y - right.y;
return Math.sqrt(x * x + y * y); return Math.sqrt(x * x + y * y);
+3 -144
View File
@@ -3,19 +3,14 @@
// 3D vectors as plain-data {x, y, z} objects. Arithmetic takes a required `result` // 3D vectors as plain-data {x, y, z} objects. Arithmetic takes a required `result`
// (as Cesium does) that may be one of the inputs; creators take an optional // (as Cesium does) that may be one of the inputs; creators take an optional
// one. The operation order matches Cesium so the outputs are bit-identical. // one. The operation order matches Cesium so the outputs are bit-identical.
// With DEBUG on (development and tests; see debug.js) invalid arguments throw // As in release Cesium, arguments are not checked and `normalize` returns NaN
// the same DeveloperError as Cesium's debug checks, and `normalize` throws on a // components for a zero or non-finite vector.
// zero or non-finite vector; production builds skip the checks and return NaN
// components there, as release Cesium does.
// //
// Geo values are not Object.prototype objects, so src/app/stateChannel.js // Geo values are not Object.prototype objects, so src/app/stateChannel.js
// copyState and src/director/documentFields.js jsonTree reject them: copy // copyState and src/director/documentFields.js jsonTree reject them: copy
// the fields into a literal before putting one into product state or a // the fields into a literal before putting one into product state or a
// scene document. // scene document.
import { DEBUG } from './debug.js';
import { Check } from './check.js';
import { DeveloperError } from './developerError.js';
import { equalsEpsilon as scalarEqualsEpsilon, toRadians } from './math.js'; import { equalsEpsilon as scalarEqualsEpsilon, toRadians } from './math.js';
// Values are instances of a module-private constructor rather than object // Values are instances of a module-private constructor rather than object
@@ -59,10 +54,6 @@ export const UNIT_Y = Object.freeze(create(0.0, 1.0, 0.0));
export const UNIT_Z = Object.freeze(create(0.0, 0.0, 1.0)); export const UNIT_Z = Object.freeze(create(0.0, 0.0, 1.0));
export function magnitudeSquared(cartesian) { export function magnitudeSquared(cartesian) {
if (DEBUG) {
Check.typeOf.object('cartesian', cartesian);
}
return ( return (
cartesian.x * cartesian.x + cartesian.x * cartesian.x +
cartesian.y * cartesian.y + cartesian.y * cartesian.y +
@@ -77,60 +68,29 @@ export function magnitude(cartesian) {
const distanceScratch = create(); const distanceScratch = create();
export function distance(left, right) { export function distance(left, right) {
if (DEBUG) {
Check.typeOf.object('left', left);
Check.typeOf.object('right', right);
}
subtract(left, right, distanceScratch); subtract(left, right, distanceScratch);
return magnitude(distanceScratch); return magnitude(distanceScratch);
} }
export function distanceSquared(left, right) { export function distanceSquared(left, right) {
if (DEBUG) {
Check.typeOf.object('left', left);
Check.typeOf.object('right', right);
}
subtract(left, right, distanceScratch); subtract(left, right, distanceScratch);
return magnitudeSquared(distanceScratch); return magnitudeSquared(distanceScratch);
} }
/** The unit vector; with DEBUG on, throws when the vector has no direction (zero or non-finite). */ /** The unit vector; NaN components when the vector has no direction (zero or non-finite). */
export function normalize(cartesian, result) { export function normalize(cartesian, result) {
if (DEBUG) {
Check.typeOf.object('cartesian', cartesian);
Check.typeOf.object('result', result);
}
const length = magnitude(cartesian); const length = magnitude(cartesian);
result.x = cartesian.x / length; result.x = cartesian.x / length;
result.y = cartesian.y / length; result.y = cartesian.y / length;
result.z = cartesian.z / length; result.z = cartesian.z / length;
if (DEBUG) {
if (isNaN(result.x) || isNaN(result.y) || isNaN(result.z)) {
throw new DeveloperError('normalized result is not a number');
}
}
return result; return result;
} }
export function dot(left, right) { export function dot(left, right) {
if (DEBUG) {
Check.typeOf.object('left', left);
Check.typeOf.object('right', right);
}
return left.x * right.x + left.y * right.y + left.z * right.z; return left.x * right.x + left.y * right.y + left.z * right.z;
} }
export function multiplyComponents(left, right, result) { export function multiplyComponents(left, right, result) {
if (DEBUG) {
Check.typeOf.object('left', left);
Check.typeOf.object('right', right);
Check.typeOf.object('result', result);
}
result.x = left.x * right.x; result.x = left.x * right.x;
result.y = left.y * right.y; result.y = left.y * right.y;
result.z = left.z * right.z; result.z = left.z * right.z;
@@ -138,12 +98,6 @@ export function multiplyComponents(left, right, result) {
} }
export function add(left, right, result) { export function add(left, right, result) {
if (DEBUG) {
Check.typeOf.object('left', left);
Check.typeOf.object('right', right);
Check.typeOf.object('result', result);
}
result.x = left.x + right.x; result.x = left.x + right.x;
result.y = left.y + right.y; result.y = left.y + right.y;
result.z = left.z + right.z; result.z = left.z + right.z;
@@ -151,12 +105,6 @@ export function add(left, right, result) {
} }
export function subtract(left, right, result) { export function subtract(left, right, result) {
if (DEBUG) {
Check.typeOf.object('left', left);
Check.typeOf.object('right', right);
Check.typeOf.object('result', result);
}
result.x = left.x - right.x; result.x = left.x - right.x;
result.y = left.y - right.y; result.y = left.y - right.y;
result.z = left.z - right.z; result.z = left.z - right.z;
@@ -164,12 +112,6 @@ export function subtract(left, right, result) {
} }
export function multiplyByScalar(cartesian, scalar, result) { export function multiplyByScalar(cartesian, scalar, result) {
if (DEBUG) {
Check.typeOf.object('cartesian', cartesian);
Check.typeOf.number('scalar', scalar);
Check.typeOf.object('result', result);
}
result.x = cartesian.x * scalar; result.x = cartesian.x * scalar;
result.y = cartesian.y * scalar; result.y = cartesian.y * scalar;
result.z = cartesian.z * scalar; result.z = cartesian.z * scalar;
@@ -177,12 +119,6 @@ export function multiplyByScalar(cartesian, scalar, result) {
} }
export function divideByScalar(cartesian, scalar, result) { export function divideByScalar(cartesian, scalar, result) {
if (DEBUG) {
Check.typeOf.object('cartesian', cartesian);
Check.typeOf.number('scalar', scalar);
Check.typeOf.object('result', result);
}
result.x = cartesian.x / scalar; result.x = cartesian.x / scalar;
result.y = cartesian.y / scalar; result.y = cartesian.y / scalar;
result.z = cartesian.z / scalar; result.z = cartesian.z / scalar;
@@ -190,11 +126,6 @@ export function divideByScalar(cartesian, scalar, result) {
} }
export function negate(cartesian, result) { export function negate(cartesian, result) {
if (DEBUG) {
Check.typeOf.object('cartesian', cartesian);
Check.typeOf.object('result', result);
}
result.x = -cartesian.x; result.x = -cartesian.x;
result.y = -cartesian.y; result.y = -cartesian.y;
result.z = -cartesian.z; result.z = -cartesian.z;
@@ -204,25 +135,12 @@ export function negate(cartesian, result) {
const lerpScratch = create(); const lerpScratch = create();
export function lerp(start, end, t, result) { export function lerp(start, end, t, result) {
if (DEBUG) {
Check.typeOf.object('start', start);
Check.typeOf.object('end', end);
Check.typeOf.number('t', t);
Check.typeOf.object('result', result);
}
multiplyByScalar(end, t, lerpScratch); multiplyByScalar(end, t, lerpScratch);
result = multiplyByScalar(start, 1.0 - t, result); result = multiplyByScalar(start, 1.0 - t, result);
return add(lerpScratch, result, result); return add(lerpScratch, result, result);
} }
export function cross(left, right, result) { export function cross(left, right, result) {
if (DEBUG) {
Check.typeOf.object('left', left);
Check.typeOf.object('right', right);
Check.typeOf.object('result', result);
}
const leftX = left.x; const leftX = left.x;
const leftY = left.y; const leftY = left.y;
const leftZ = left.z; const leftZ = left.z;
@@ -241,12 +159,6 @@ export function cross(left, right, result) {
} }
export function midpoint(left, right, result) { export function midpoint(left, right, result) {
if (DEBUG) {
Check.typeOf.object('left', left);
Check.typeOf.object('right', right);
Check.typeOf.object('result', result);
}
result.x = (left.x + right.x) * 0.5; result.x = (left.x + right.x) * 0.5;
result.y = (left.y + right.y) * 0.5; result.y = (left.y + right.y) * 0.5;
result.z = (left.z + right.z) * 0.5; result.z = (left.z + right.z) * 0.5;
@@ -292,11 +204,6 @@ let scratchK = create();
/** The Earth-fixed position of a geodetic one in radians on the ellipsoid (WGS84 by default). */ /** The Earth-fixed position of a geodetic one in radians on the ellipsoid (WGS84 by default). */
export function fromRadians(longitude, latitude, height, ellipsoid, result) { export function fromRadians(longitude, latitude, height, ellipsoid, result) {
if (DEBUG) {
Check.typeOf.number('longitude', longitude);
Check.typeOf.number('latitude', latitude);
}
height = height ?? 0.0; height = height ?? 0.0;
const radiiSquared = const radiiSquared =
ellipsoid == null ? WGS84_RADII_SQUARED : ellipsoid.radiiSquared; ellipsoid == null ? WGS84_RADII_SQUARED : ellipsoid.radiiSquared;
@@ -318,11 +225,6 @@ export function fromRadians(longitude, latitude, height, ellipsoid, result) {
/** The Earth-fixed position of a geodetic one in degrees on the ellipsoid (WGS84 by default). */ /** The Earth-fixed position of a geodetic one in degrees on the ellipsoid (WGS84 by default). */
export function fromDegrees(longitude, latitude, height, ellipsoid, result) { export function fromDegrees(longitude, latitude, height, ellipsoid, result) {
if (DEBUG) {
Check.typeOf.number('longitude', longitude);
Check.typeOf.number('latitude', latitude);
}
longitude = toRadians(longitude); longitude = toRadians(longitude);
latitude = toRadians(latitude); latitude = toRadians(latitude);
return fromRadians(longitude, latitude, height, ellipsoid, result); return fromRadians(longitude, latitude, height, ellipsoid, result);
@@ -330,15 +232,6 @@ export function fromDegrees(longitude, latitude, height, ellipsoid, result) {
/** Positions from a flat [lon, lat, lon, lat, …] list in degrees, at height 0. */ /** Positions from a flat [lon, lat, lon, lat, …] list in degrees, at height 0. */
export function fromDegreesArray(coordinates, ellipsoid, result) { export function fromDegreesArray(coordinates, ellipsoid, result) {
if (DEBUG) {
Check.defined('coordinates', coordinates);
if (coordinates.length < 2 || coordinates.length % 2 !== 0) {
throw new DeveloperError(
'the number of coordinates must be a multiple of 2 and at least 2',
);
}
}
const length = coordinates.length; const length = coordinates.length;
if (result == null) { if (result == null) {
result = new Array(length / 2); result = new Array(length / 2);
@@ -358,43 +251,9 @@ export function fromDegreesArray(coordinates, ellipsoid, result) {
return result; return result;
} }
/** Positions from a flat [lon, lat, height, …] list in degrees and metres. */
export function fromDegreesArrayHeights(coordinates, ellipsoid, result) {
if (DEBUG) {
Check.defined('coordinates', coordinates);
if (coordinates.length < 3 || coordinates.length % 3 !== 0) {
throw new DeveloperError(
'the number of coordinates must be a multiple of 3 and at least 3',
);
}
}
const length = coordinates.length;
if (result == null) {
result = new Array(length / 3);
} else {
result.length = length / 3;
}
for (let i = 0; i < length; i += 3) {
const index = i / 3;
result[index] = fromDegrees(
coordinates[i],
coordinates[i + 1],
coordinates[i + 2],
ellipsoid,
result[index],
);
}
return result;
}
const angleBetweenScratch = create(); const angleBetweenScratch = create();
const angleBetweenScratch2 = create(); const angleBetweenScratch2 = create();
export function angleBetween(left, right) { export function angleBetween(left, right) {
if (DEBUG) {
Check.typeOf.object('left', left);
Check.typeOf.object('right', right);
}
normalize(left, angleBetweenScratch); normalize(left, angleBetweenScratch);
normalize(right, angleBetweenScratch2); normalize(right, angleBetweenScratch2);
const cosine = dot(angleBetweenScratch, angleBetweenScratch2); const cosine = dot(angleBetweenScratch, angleBetweenScratch2);
+2 -90
View File
@@ -21,78 +21,16 @@ const same = (ours, theirs, label) =>
label, label,
); );
test('binary vector ops equal Cesium bit for bit', () => { test('magnitudeSquared equals Cesium', () => {
for (const a of SAMPLES)
for (const b of SAMPLES) {
for (const op of ['add', 'subtract', 'cross', 'multiplyComponents']) {
same(
c3[op](asPlain(a), asPlain(b), c3.create()),
C[op](asCesium(a), asCesium(b), new C()),
`${op}`,
);
}
assert.equal(
c3.dot(asPlain(a), asPlain(b)),
C.dot(asCesium(a), asCesium(b)),
);
assert.equal(
c3.distance(asPlain(a), asPlain(b)),
C.distance(asCesium(a), asCesium(b)),
);
assert.equal(
c3.distanceSquared(asPlain(a), asPlain(b)),
C.distanceSquared(asCesium(a), asCesium(b)),
);
same(
c3.lerp(asPlain(a), asPlain(b), 0.3, c3.create()),
C.lerp(asCesium(a), asCesium(b), 0.3, new C()),
'lerp',
);
same(
c3.midpoint(asPlain(a), asPlain(b), c3.create()),
C.midpoint(asCesium(a), asCesium(b), new C()),
'midpoint',
);
assert.equal(
c3.equalsEpsilon(asPlain(a), asPlain(b), 1e-7),
C.equalsEpsilon(asCesium(a), asCesium(b), 1e-7),
);
}
});
test('unary ops and scalars equal Cesium', () => {
for (const a of SAMPLES) { for (const a of SAMPLES) {
assert.equal(c3.magnitude(asPlain(a)), C.magnitude(asCesium(a)));
assert.equal( assert.equal(
c3.magnitudeSquared(asPlain(a)), c3.magnitudeSquared(asPlain(a)),
C.magnitudeSquared(asCesium(a)), C.magnitudeSquared(asCesium(a)),
); );
same(
c3.negate(asPlain(a), c3.create()),
C.negate(asCesium(a), new C()),
'negate',
);
same(
c3.multiplyByScalar(asPlain(a), 2.5, c3.create()),
C.multiplyByScalar(asCesium(a), 2.5, new C()),
'mul',
);
same(
c3.divideByScalar(asPlain(a), 4, c3.create()),
C.divideByScalar(asCesium(a), 4, new C()),
'div',
);
if (C.magnitude(asCesium(a)) !== 0) {
same(
c3.normalize(asPlain(a), c3.create()),
C.normalize(asCesium(a), new C()),
'normalize',
);
}
} }
}); });
test('geodetic constructors equal Cesium', () => { test('fromRadians and fromDegreesArray equal Cesium', () => {
for (const [lon, lat, h] of [ for (const [lon, lat, h] of [
[0, 0, 0], [0, 0, 0],
[-73.9857, 40.7484, 381], [-73.9857, 40.7484, 381],
@@ -100,11 +38,6 @@ test('geodetic constructors equal Cesium', () => {
[180, -90, 1e4], [180, -90, 1e4],
[12.5, 89.999999, 1], [12.5, 89.999999, 1],
]) { ]) {
same(
c3.fromDegrees(lon, lat, h),
C.fromDegrees(lon, lat, h),
`fromDegrees ${lon},${lat}`,
);
const lr = [Cesium.Math.toRadians(lon), Cesium.Math.toRadians(lat), h]; const lr = [Cesium.Math.toRadians(lon), Cesium.Math.toRadians(lat), h];
same(c3.fromRadians(...lr), C.fromRadians(...lr), 'fromRadians'); same(c3.fromRadians(...lr), C.fromRadians(...lr), 'fromRadians');
} }
@@ -112,10 +45,6 @@ test('geodetic constructors equal Cesium', () => {
c3.fromDegreesArray(flat).forEach((p, i) => c3.fromDegreesArray(flat).forEach((p, i) =>
same(p, C.fromDegreesArray(flat)[i], `array ${i}`), same(p, C.fromDegreesArray(flat)[i], `array ${i}`),
); );
const heights = [-74, 40.7, 10, -73.9, 40.8, 20];
c3.fromDegreesArrayHeights(heights).forEach((p, i) =>
same(p, C.fromDegreesArrayHeights(heights)[i], `heights ${i}`),
);
}); });
test('result aliasing works like Cesium and creators reuse result', () => { test('result aliasing works like Cesium and creators reuse result', () => {
@@ -138,20 +67,3 @@ test('plain results are accepted by Cesium functions (duck typing)', () => {
), ),
); );
}); });
test('normalize throws on a zero or NaN vector, as Cesium does when run from source', () => {
for (const [x, y, z] of [
[0, 0, 0],
[-0, 0, -0],
[Number.NaN, 1, 2],
]) {
assert.throws(
() => C.normalize(new C(x, y, z), new C()),
/normalized result is not a number/,
);
assert.throws(
() => c3.normalize({ x, y, z }, c3.create()),
/normalized result is not a number/,
);
}
});
+1 -19
View File
@@ -1,17 +1,13 @@
// Ported from CesiumJS 1.138 (Apache-2.0) — Cartographic.js // Ported from CesiumJS 1.138 (Apache-2.0) — Cartographic.js
// //
// Geodetic positions as plain-data {longitude, latitude, height} objects (radians and // Geodetic positions as plain-data {longitude, latitude, height} objects (radians and
// metres). Every function takes an optional `result` and returns it. With // metres). Every function takes an optional `result` and returns it.
// DEBUG on (development and tests; see debug.js) invalid arguments throw the
// same DeveloperError as Cesium's debug checks.
// //
// Geo values are not Object.prototype objects, so src/app/stateChannel.js // Geo values are not Object.prototype objects, so src/app/stateChannel.js
// copyState and src/director/documentFields.js jsonTree reject them: copy // copyState and src/director/documentFields.js jsonTree reject them: copy
// the fields into a literal before putting one into product state or a // the fields into a literal before putting one into product state or a
// scene document. // scene document.
import { DEBUG } from './debug.js';
import { Check } from './check.js';
import { toRadians } from './math.js'; import { toRadians } from './math.js';
import * as cartesian3 from './cartesian3.js'; import * as cartesian3 from './cartesian3.js';
import { scaleToGeodeticSurface } from './scaleToGeodeticSurface.js'; import { scaleToGeodeticSurface } from './scaleToGeodeticSurface.js';
@@ -51,11 +47,6 @@ const WGS84_CENTER_TOLERANCE_SQUARED = 0.1;
/** A geodetic position from radians. */ /** A geodetic position from radians. */
export function fromRadians(longitude, latitude, height, result) { export function fromRadians(longitude, latitude, height, result) {
if (DEBUG) {
Check.typeOf.number('longitude', longitude);
Check.typeOf.number('latitude', latitude);
}
height = height ?? 0.0; height = height ?? 0.0;
if (result == null) return create(longitude, latitude, height); if (result == null) return create(longitude, latitude, height);
result.longitude = longitude; result.longitude = longitude;
@@ -66,11 +57,6 @@ export function fromRadians(longitude, latitude, height, result) {
/** A geodetic position from degrees; the result is in radians. */ /** A geodetic position from degrees; the result is in radians. */
export function fromDegrees(longitude, latitude, height, result) { export function fromDegrees(longitude, latitude, height, result) {
if (DEBUG) {
Check.typeOf.number('longitude', longitude);
Check.typeOf.number('latitude', latitude);
}
longitude = toRadians(longitude); longitude = toRadians(longitude);
latitude = toRadians(latitude); latitude = toRadians(latitude);
return fromRadians(longitude, latitude, height, result); return fromRadians(longitude, latitude, height, result);
@@ -133,10 +119,6 @@ export function fromCartesian(cartesian, ellipsoid, result) {
* where Cesium uses Ellipsoid.default; gev never changes it). * where Cesium uses Ellipsoid.default; gev never changes it).
*/ */
export function toCartesian(cartographic, ellipsoid, result) { export function toCartesian(cartographic, ellipsoid, result) {
if (DEBUG) {
Check.defined('cartographic', cartographic);
}
return cartesian3.fromRadians( return cartesian3.fromRadians(
cartographic.longitude, cartographic.longitude,
cartographic.latitude, cartographic.latitude,
-29
View File
@@ -4,12 +4,6 @@ import assert from 'node:assert/strict';
import * as Cesium from 'cesium'; import * as Cesium from 'cesium';
import * as cartographic from './cartographic.js'; import * as cartographic from './cartographic.js';
const close = (a, b, tol, label) =>
assert.ok(
Math.abs(a - b) <= tol * Math.max(1, Math.abs(b)),
`${label}: ${a} vs ${b}`,
);
test('fromDegrees and fromRadians equal Cesium', () => { test('fromDegrees and fromRadians equal Cesium', () => {
for (const [lon, lat, h] of [ for (const [lon, lat, h] of [
[0, 0, 0], [0, 0, 0],
@@ -30,29 +24,6 @@ test('fromDegrees and fromRadians equal Cesium', () => {
} }
}); });
test('fromCartesian and toCartesian equal Cesium, including the centre case', () => {
for (const [x, y, z] of [
[1334762.2, -4654869.1, 4137287.9],
[6378137, 0, 0],
[0.01, 0, 0],
]) {
const ours = cartographic.fromCartesian({ x, y, z });
const theirs = Cesium.Cartographic.fromCartesian(
new Cesium.Cartesian3(x, y, z),
);
if (theirs === undefined) {
assert.equal(ours, undefined);
continue;
}
close(ours.longitude, theirs.longitude, 1e-12, 'lon');
close(ours.latitude, theirs.latitude, 1e-12, 'lat');
close(ours.height, theirs.height, 1e-9, 'h');
const back = cartographic.toCartesian(ours);
const cBack = Cesium.Cartographic.toCartesian(theirs);
for (const k of ['x', 'y', 'z']) close(back[k], cBack[k], 1e-12, k);
}
});
test('result objects are reused', () => { test('result objects are reused', () => {
const out = cartographic.create(); const out = cartographic.create();
assert.equal(cartographic.fromDegrees(1, 2, 3, out), out); assert.equal(cartographic.fromDegrees(1, 2, 3, out), out);
-61
View File
@@ -1,61 +0,0 @@
// Ported from CesiumJS 1.138 (Apache-2.0) — Check.js
//
// Cesium's argument checks with the same conditions and message text. Only the
// checks src/geo needs are ported. Callers run them inside `if (DEBUG)` blocks
// (see debug.js), as Cesium runs them inside its debug pragmas, so production
// builds drop the calls and this module with them.
import { DeveloperError } from './developerError.js';
const undefinedMessage = (name) =>
`${name} is required, actual value was undefined`;
const typeMessage = (actual, expected, name) =>
`Expected ${name} to be typeof ${expected}, actual typeof was ${actual}`;
/** Throws unless `test` is neither undefined nor null. */
function defined(name, test) {
if (test === undefined || test === null) {
throw new DeveloperError(undefinedMessage(name));
}
}
/** Throws unless `typeof test` is 'number' (NaN passes, as in Cesium). */
function number(name, test) {
if (typeof test !== 'number') {
throw new DeveloperError(typeMessage(typeof test, 'number', name));
}
}
// Attached in a function marked pure, not by top-level assignments, so that a
// production build, where no caller runs a check, drops this module entirely.
function withBounds(check) {
check.lessThanOrEquals = function (name, test, limit) {
check(name, test);
if (test > limit) {
throw new DeveloperError(
`Expected ${name} to be less than or equal to ${limit}, actual value was ${test}`,
);
}
};
check.greaterThanOrEquals = function (name, test, limit) {
check(name, test);
if (test < limit) {
throw new DeveloperError(
`Expected ${name} to be greater than or equal to ${limit}, actual value was ${test}`,
);
}
};
return check;
}
/** Throws unless `typeof test` is 'object' (null passes, as in Cesium). */
function object(name, test) {
if (typeof test !== 'object') {
throw new DeveloperError(typeMessage(typeof test, 'object', name));
}
}
export const Check = /* @__PURE__ */ Object.freeze({
defined,
typeOf: Object.freeze({ number: /* @__PURE__ */ withBounds(number), object }),
});
-486
View File
@@ -1,486 +0,0 @@
// Argument-check parity with Cesium. gev runs Cesium from source, where its
// debug checks are live, so for any input both sides must throw an error with
// the same name and message, or neither may throw. The inputs are seeded and
// deterministic: each case builds its arguments twice from the same seed, once
// for geo and once for Cesium, so no object is shared between the two sides.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import * as Cesium from 'cesium';
import * as geo from './index.js';
import { DeveloperError } from './developerError.js';
const TRIALS = 150;
function generator(seed) {
return () => {
seed = (seed + 0x6d2b79f5) | 0;
let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
// Invalid values every argument may receive. NaN is a number to Check.typeOf.number
// and null an object to Check.typeOf.object, so both reach the arithmetic.
const INVALID = [undefined, null, Number.NaN, 'abc', 5, true, {}];
const vectorValues = { x: 1.5, y: -2, z: 3 };
const VALID = {
num: (side, random) => [0, -0, 0.7, -2.5, 1e6][Math.floor(random() * 5)],
index3: (side, random) => Math.floor(random() * 5) - 1,
index4: (side, random) => Math.floor(random() * 6) - 1,
nonNegative: (side, random) => [0, 3, 6378137, -1][Math.floor(random() * 4)],
vec: (side) =>
side === 'geo' ? { ...vectorValues } : new Cesium.Cartesian3(1.5, -2, 3),
zeroVec: (side) =>
side === 'geo' ? { x: 0, y: 0, z: 0 } : new Cesium.Cartesian3(),
vec4: (side) =>
side === 'geo' ? { x: 0, y: 0, z: 0, w: 0 } : new Cesium.Cartesian4(),
carto: (side) =>
side === 'geo'
? { longitude: 0.3, latitude: -0.2, height: 10 }
: new Cesium.Cartographic(0.3, -0.2, 10),
partialCarto: (side) =>
side === 'geo'
? { longitude: 0.3, latitude: -0.2 }
: { longitude: 0.3, latitude: -0.2 },
quat: (side) =>
side === 'geo'
? { x: 0.1, y: 0.2, z: 0.3, w: 0.9 }
: new Cesium.Quaternion(0.1, 0.2, 0.3, 0.9),
mat3: (side) =>
side === 'geo'
? geo.matrix3.fromRotationZ(0.4)
: Cesium.Matrix3.fromRotationZ(0.4, new Cesium.Matrix3()),
mat4: (side) =>
side === 'geo'
? geo.transforms.eastNorthUpToFixedFrame({ x: 6378137, y: 1, z: 2 })
: Cesium.Transforms.eastNorthUpToFixedFrame(
new Cesium.Cartesian3(6378137, 1, 2),
),
hpr: (side) =>
side === 'geo'
? { heading: 0.3, pitch: -0.2, roll: 0.1 }
: new Cesium.HeadingPitchRoll(0.3, -0.2, 0.1),
partialHpr: () => ({ heading: 0.3 }),
ellipsoid: (side) =>
side === 'geo' ? geo.ellipsoid.WGS84 : Cesium.Ellipsoid.WGS84,
coords2: (side, random) =>
[[1, 2], [1, 2, 3], [], [1, 2, 3, 4]][Math.floor(random() * 4)],
coords3: (side, random) =>
[[1, 2, 3], [1, 2], [], [1, 2, 3, 4, 5, 6], [1, 2, 'h']][
Math.floor(random() * 5)
],
nanVec: (side) =>
side === 'geo'
? { x: 1, y: Number.NaN, z: 2 }
: new Cesium.Cartesian3(1, Number.NaN, 2),
partialVec: () => ({ x: 1, y: 2 }),
stringHeight: () => '5',
};
/** One argument: usually a valid value of its kind, sometimes an invalid one. */
function argument(kinds, side, random) {
if (random() < 0.4) return INVALID[Math.floor(random() * INVALID.length)];
const kind = kinds[Math.floor(random() * kinds.length)];
return VALID[kind](side, random);
}
const W = Cesium.Ellipsoid.WGS84;
const C3 = Cesium.Cartesian3;
const V = ['vec', 'zeroVec', 'nanVec', 'partialVec'];
const N = ['num'];
const R = ['vec'];
// [name, geo function, Cesium function, argument kinds per position]
const CASES = [
['math.toRadians', geo.math.toRadians, Cesium.Math.toRadians, [N]],
['math.toDegrees', geo.math.toDegrees, Cesium.Math.toDegrees, [N]],
['math.clamp', geo.math.clamp, Cesium.Math.clamp, [N, N, N]],
['math.lerp', geo.math.lerp, Cesium.Math.lerp, [N, N, N]],
['math.mod', geo.math.mod, Cesium.Math.mod, [N, ['num', 'nonNegative']]],
['math.zeroToTwoPi', geo.math.zeroToTwoPi, Cesium.Math.zeroToTwoPi, [N]],
[
'math.negativePiToPi',
geo.math.negativePiToPi,
Cesium.Math.negativePiToPi,
[N],
],
[
'math.equalsEpsilon',
geo.math.equalsEpsilon,
Cesium.Math.equalsEpsilon,
[N, N, N, N],
],
['cartesian2.clone', geo.cartesian2.clone, Cesium.Cartesian2.clone, [R, R]],
[
'cartesian2.distance',
geo.cartesian2.distance,
Cesium.Cartesian2.distance,
[R, R],
],
['cartesian3.clone', geo.cartesian3.clone, C3.clone, [V, R]],
[
'cartesian3.fromElements',
geo.cartesian3.fromElements,
C3.fromElements,
[N, N, N, R],
],
['cartesian3.magnitude', geo.cartesian3.magnitude, C3.magnitude, [V]],
[
'cartesian3.magnitudeSquared',
geo.cartesian3.magnitudeSquared,
C3.magnitudeSquared,
[V],
],
['cartesian3.distance', geo.cartesian3.distance, C3.distance, [V, V]],
[
'cartesian3.distanceSquared',
geo.cartesian3.distanceSquared,
C3.distanceSquared,
[V, V],
],
['cartesian3.normalize', geo.cartesian3.normalize, C3.normalize, [V, R]],
['cartesian3.dot', geo.cartesian3.dot, C3.dot, [V, V]],
[
'cartesian3.multiplyComponents',
geo.cartesian3.multiplyComponents,
C3.multiplyComponents,
[V, V, R],
],
['cartesian3.add', geo.cartesian3.add, C3.add, [V, V, R]],
['cartesian3.subtract', geo.cartesian3.subtract, C3.subtract, [V, V, R]],
[
'cartesian3.multiplyByScalar',
geo.cartesian3.multiplyByScalar,
C3.multiplyByScalar,
[V, N, R],
],
[
'cartesian3.divideByScalar',
geo.cartesian3.divideByScalar,
C3.divideByScalar,
[V, N, R],
],
['cartesian3.negate', geo.cartesian3.negate, C3.negate, [V, R]],
['cartesian3.lerp', geo.cartesian3.lerp, C3.lerp, [V, V, N, R]],
['cartesian3.cross', geo.cartesian3.cross, C3.cross, [V, V, R]],
['cartesian3.midpoint', geo.cartesian3.midpoint, C3.midpoint, [V, V, R]],
['cartesian3.equals', geo.cartesian3.equals, C3.equals, [V, V]],
[
'cartesian3.equalsEpsilon',
geo.cartesian3.equalsEpsilon,
C3.equalsEpsilon,
[V, V, N, N],
],
[
'cartesian3.fromRadians',
geo.cartesian3.fromRadians,
C3.fromRadians,
[N, N, ['num', 'stringHeight'], ['ellipsoid'], R],
],
[
'cartesian3.fromDegrees',
geo.cartesian3.fromDegrees,
C3.fromDegrees,
[N, N, ['num', 'stringHeight'], ['ellipsoid'], R],
],
[
'cartesian3.fromDegreesArray',
geo.cartesian3.fromDegreesArray,
C3.fromDegreesArray,
[['coords2'], ['ellipsoid']],
],
[
'cartesian3.fromDegreesArrayHeights',
geo.cartesian3.fromDegreesArrayHeights,
C3.fromDegreesArrayHeights,
[['coords3'], ['ellipsoid']],
],
[
'cartographic.fromRadians',
geo.cartographic.fromRadians,
Cesium.Cartographic.fromRadians,
[N, N, N, ['carto']],
],
[
'cartographic.fromDegrees',
geo.cartographic.fromDegrees,
Cesium.Cartographic.fromDegrees,
[N, N, N, ['carto']],
],
[
'cartographic.fromCartesian',
geo.cartographic.fromCartesian,
Cesium.Cartographic.fromCartesian,
[V, ['ellipsoid'], ['carto']],
],
[
'cartographic.toCartesian',
geo.cartographic.toCartesian,
Cesium.Cartographic.toCartesian,
[['carto', 'partialCarto'], ['ellipsoid'], R],
],
[
'cartographic.clone',
geo.cartographic.clone,
Cesium.Cartographic.clone,
[['carto'], ['carto']],
],
[
'ellipsoid.create',
geo.ellipsoid.create,
(x, y, z) => new Cesium.Ellipsoid(x, y, z),
[['nonNegative'], ['nonNegative'], ['nonNegative']],
],
[
'WGS84.geodeticSurfaceNormal',
geo.ellipsoid.WGS84.geodeticSurfaceNormal,
(c, r) => W.geodeticSurfaceNormal(c, r),
[V, R],
],
[
'WGS84.geodeticSurfaceNormalCartographic',
geo.ellipsoid.WGS84.geodeticSurfaceNormalCartographic,
(c, r) => W.geodeticSurfaceNormalCartographic(c, r),
[['carto', 'partialCarto'], R],
],
[
'WGS84.scaleToGeodeticSurface',
geo.ellipsoid.WGS84.scaleToGeodeticSurface,
(c, r) => W.scaleToGeodeticSurface(c, r),
[V, R],
],
[
'WGS84.cartographicToCartesian',
geo.ellipsoid.WGS84.cartographicToCartesian,
(c, r) => W.cartographicToCartesian(c, r),
[['carto', 'partialCarto'], R],
],
[
'WGS84.cartesianToCartographic',
geo.ellipsoid.WGS84.cartesianToCartographic,
(c, r) => W.cartesianToCartographic(c, r),
[V, ['carto']],
],
[
'matrix3.fromQuaternion',
geo.matrix3.fromQuaternion,
Cesium.Matrix3.fromQuaternion,
[['quat'], ['mat3']],
],
[
'matrix3.fromRotationZ',
geo.matrix3.fromRotationZ,
Cesium.Matrix3.fromRotationZ,
[N, ['mat3']],
],
[
'matrix3.multiplyByVector',
geo.matrix3.multiplyByVector,
Cesium.Matrix3.multiplyByVector,
[['mat3'], V, R],
],
[
'matrix3.getColumn',
geo.matrix3.getColumn,
Cesium.Matrix3.getColumn,
[['mat3'], ['index3'], R],
],
[
'matrix4.clone',
geo.matrix4.clone,
Cesium.Matrix4.clone,
[['mat4'], ['mat4']],
],
[
'matrix4.equals',
geo.matrix4.equals,
Cesium.Matrix4.equals,
[['mat4'], ['mat4']],
],
[
'matrix4.multiply',
geo.matrix4.multiply,
Cesium.Matrix4.multiply,
[['mat4'], ['mat4'], ['mat4']],
],
[
'matrix4.multiplyTransformation',
geo.matrix4.multiplyTransformation,
Cesium.Matrix4.multiplyTransformation,
[['mat4'], ['mat4'], ['mat4']],
],
[
'matrix4.multiplyByPoint',
geo.matrix4.multiplyByPoint,
Cesium.Matrix4.multiplyByPoint,
[['mat4'], V, R],
],
[
'matrix4.multiplyByPointAsVector',
geo.matrix4.multiplyByPointAsVector,
Cesium.Matrix4.multiplyByPointAsVector,
[['mat4'], V, R],
],
[
'matrix4.getTranslation',
geo.matrix4.getTranslation,
Cesium.Matrix4.getTranslation,
[['mat4'], R],
],
[
'matrix4.getMatrix3',
geo.matrix4.getMatrix3,
Cesium.Matrix4.getMatrix3,
[['mat4'], ['mat3']],
],
[
'matrix4.getColumn',
geo.matrix4.getColumn,
Cesium.Matrix4.getColumn,
[['mat4'], ['index4'], ['vec4']],
],
[
'matrix4.inverseTransformation',
geo.matrix4.inverseTransformation,
Cesium.Matrix4.inverseTransformation,
[['mat4'], ['mat4']],
],
[
'matrix4.fromRotationTranslation',
geo.matrix4.fromRotationTranslation,
Cesium.Matrix4.fromRotationTranslation,
[['mat3'], R, ['mat4']],
],
[
'matrix4.fromUniformScale',
geo.matrix4.fromUniformScale,
Cesium.Matrix4.fromUniformScale,
[N, ['mat4']],
],
[
'quaternion.fromAxisAngle',
geo.quaternion.fromAxisAngle,
Cesium.Quaternion.fromAxisAngle,
[V, N, ['quat']],
],
[
'quaternion.fromRotationMatrix',
geo.quaternion.fromRotationMatrix,
Cesium.Quaternion.fromRotationMatrix,
[['mat3'], ['quat']],
],
[
'transforms.eastNorthUpToFixedFrame',
geo.transforms.eastNorthUpToFixedFrame,
Cesium.Transforms.eastNorthUpToFixedFrame,
[V, ['ellipsoid'], ['mat4']],
],
[
'transforms.headingPitchRollToFixedFrame',
geo.transforms.headingPitchRollToFixedFrame,
Cesium.Transforms.headingPitchRollToFixedFrame,
[V, ['hpr', 'partialHpr'], ['ellipsoid'], [], ['mat4']],
],
];
function outcome(fn, args) {
try {
fn(...args);
return 'returned';
} catch (error) {
return `${error?.name}: ${error?.message}`;
}
}
test('every ported function throws exactly where Cesium does, with the same name and message', () => {
let thrown = 0;
let checked = 0;
const mismatches = [];
CASES.forEach(([name, ours, theirs, kinds], caseIndex) => {
for (let trial = 0; trial < TRIALS; trial++) {
const seed = caseIndex * 100003 + trial;
// Trial 0 passes no arguments at all; the rest draw from the pools.
const build = (side) => {
if (trial === 0) return [];
const random = generator(seed);
return kinds.map((k) =>
k.length ? argument(k, side, random) : undefined,
);
};
const a = outcome(ours, build('geo'));
const b = outcome(theirs, build('cesium'));
if (a !== b)
mismatches.push(`${name} (seed ${seed}): geo ${a} | Cesium ${b}`);
checked++;
if (b !== 'returned') thrown++;
}
});
assert.deepEqual(mismatches, []);
// The pools must actually reach the checks.
assert.ok(thrown > checked / 4, `only ${thrown} of ${checked} cases threw`);
});
test('the checks throw a DeveloperError with Cesium message text', () => {
const cases = [
[() => geo.math.toRadians(undefined), 'degrees is required.'],
[() => geo.math.mod(5, 0), 'divisor cannot be 0.'],
[
() => geo.math.clamp('1', 0, 1),
'Expected value to be typeof number, actual typeof was string',
],
[
() => geo.cartesian3.fromDegrees(undefined, 1),
'Expected longitude to be typeof number, actual typeof was undefined',
],
[
() => geo.cartesian3.add({ x: 0, y: 0, z: 0 }, { x: 0, y: 0, z: 0 }),
'Expected result to be typeof object, actual typeof was undefined',
],
[
() => geo.cartesian3.fromDegreesArray([1]),
'the number of coordinates must be a multiple of 2 and at least 2',
],
[
() => geo.cartesian3.fromDegreesArray(),
'coordinates is required, actual value was undefined',
],
[
() => geo.ellipsoid.create(-1, 1, 1),
'Expected x to be greater than or equal to 0, actual value was -1',
],
[
() => geo.matrix3.getColumn(geo.matrix3.create(), 3, {}),
'Expected index to be less than or equal to 2, actual value was 3',
],
[
() =>
geo.transforms.eastNorthUpToFixedFrame({ x: 1, y: Number.NaN, z: 0 }),
'origin has a NaN component',
],
[
() => geo.cartographic.fromCartesian({ x: 1, y: 2, z: 3 }, {}),
'oneOverRadii is required.',
],
];
for (const [fn, message] of cases) {
assert.throws(fn, (error) => {
assert.ok(error instanceof DeveloperError);
assert.equal(error.name, 'DeveloperError');
assert.equal(error.message, message);
return true;
});
}
// NaN is typeof number, so Check.typeOf.number lets it through (as in Cesium).
assert.ok(Number.isNaN(geo.math.clamp(Number.NaN, 0, 1)));
assert.ok(Number.isNaN(Cesium.Math.clamp(Number.NaN, 0, 1)));
});
-12
View File
@@ -1,12 +0,0 @@
// Whether src/geo runs Cesium's debug argument checks.
//
// Cesium wraps its argument checks in `//>>includeStart('debug')` pragmas: they
// are live in its Source modules (Node tests, the Vite dev server) and stripped
// from Build/Cesium, which vite-plugin-cesium copies into production builds.
// src/geo does the same: every Check call and every debug throw sits in an
// `if (DEBUG)` block. DEBUG is true in Node (no import.meta.env) and in Vite
// dev; `vite build` folds it to false and the minifier drops the blocks, so a
// production build returns what release Cesium returns (NaN, not a throw, for
// a degenerate input).
export const DEBUG = import.meta.env?.PROD !== true;
-13
View File
@@ -1,13 +0,0 @@
// Ported from CesiumJS 1.138 (Apache-2.0) — DeveloperError.js
//
// The error Cesium throws for invalid arguments. Its debug argument checks are
// live in Cesium's Source modules (Node tests, the Vite dev server) and
// stripped from production builds; src/geo throws the same name and message
// wherever Cesium does while DEBUG is on (see debug.js).
export class DeveloperError extends Error {
constructor(message) {
super(message);
this.name = 'DeveloperError';
}
}
+1 -22
View File
@@ -3,14 +3,9 @@
// A quadratic surface x²/a² + y²/b² + z²/c² = 1 as a plain, frozen object. // A quadratic surface x²/a² + y²/b² + z²/c² = 1 as a plain, frozen object.
// Every method takes an optional `result` and returns it (a new object when it // Every method takes an optional `result` and returns it (a new object when it
// is absent); `result` may be the input. The algorithms and the operation order // is absent); `result` may be the input. The algorithms and the operation order
// match Cesium so the outputs are bit-identical. With DEBUG on (development // match Cesium so the outputs are bit-identical.
// and tests; see debug.js) invalid arguments throw the same DeveloperError as
// Cesium's debug checks.
import { DEBUG } from './debug.js';
import { Check } from './check.js';
import { create as createCartographic } from './cartographic.js'; import { create as createCartographic } from './cartographic.js';
import { DeveloperError } from './developerError.js';
import { EPSILON14 } from './math.js'; import { EPSILON14 } from './math.js';
import { scaleToGeodeticSurface } from './scaleToGeodeticSurface.js'; import { scaleToGeodeticSurface } from './scaleToGeodeticSurface.js';
import { import {
@@ -39,11 +34,6 @@ export function create(x, y, z) {
x = x ?? 0.0; x = x ?? 0.0;
y = y ?? 0.0; y = y ?? 0.0;
z = z ?? 0.0; z = z ?? 0.0;
if (DEBUG) {
Check.typeOf.number.greaterThanOrEquals('x', x, 0.0);
Check.typeOf.number.greaterThanOrEquals('y', y, 0.0);
Check.typeOf.number.greaterThanOrEquals('z', z, 0.0);
}
const radii = Object.freeze(vec(x, y, z)); const radii = Object.freeze(vec(x, y, z));
const radiiSquared = Object.freeze(vec(x * x, y * y, z * z)); const radiiSquared = Object.freeze(vec(x * x, y * y, z * z));
@@ -68,10 +58,6 @@ export function create(x, y, z) {
/** The normal to the ellipsoid surface at a geodetic position. */ /** The normal to the ellipsoid surface at a geodetic position. */
function geodeticSurfaceNormalCartographic(cartographic, result) { function geodeticSurfaceNormalCartographic(cartographic, result) {
if (DEBUG) {
Check.typeOf.object('cartographic', cartographic);
}
const longitude = cartographic.longitude; const longitude = cartographic.longitude;
const latitude = cartographic.latitude; const latitude = cartographic.latitude;
const cosLatitude = Math.cos(latitude); const cosLatitude = Math.cos(latitude);
@@ -89,13 +75,6 @@ export function create(x, y, z) {
/** The normal to the ellipsoid surface at a position, or `undefined` at the centre. */ /** The normal to the ellipsoid surface at a position, or `undefined` at the centre. */
function geodeticSurfaceNormal(cartesian, result) { function geodeticSurfaceNormal(cartesian, result) {
if (DEBUG) {
Check.typeOf.object('cartesian', cartesian);
if (isNaN(cartesian.x) || isNaN(cartesian.y) || isNaN(cartesian.z)) {
throw new DeveloperError('cartesian has a NaN component');
}
}
if (equalsEpsilon(cartesian, ZERO, EPSILON14)) { if (equalsEpsilon(cartesian, ZERO, EPSILON14)) {
return undefined; return undefined;
} }
-45
View File
@@ -12,18 +12,6 @@ const close = (a, b, tol, label) =>
const closeVec = (a, b, tol, label) => { const closeVec = (a, b, tol, label) => {
for (const k of ['x', 'y', 'z']) close(a[k], b[k], tol, `${label}.${k}`); for (const k of ['x', 'y', 'z']) close(a[k], b[k], tol, `${label}.${k}`);
}; };
const POINTS = [
[6378137, 0, 0],
[0, 6378137, 0],
[0, 0, 6356752.314245],
[1334762.2, -4654869.1, 4137287.9],
[-2694045, -4293642, 3857878],
[1e3, 1e3, 1e3],
[0.01, 0.01, 0.01],
[7e6, -2e6, 1e6],
[4510000, 4510000, 1],
[1, 1, 6400000],
];
test('WGS84 constants equal Cesium', () => { test('WGS84 constants equal Cesium', () => {
const c = Cesium.Ellipsoid.WGS84; const c = Cesium.Ellipsoid.WGS84;
@@ -40,39 +28,6 @@ test('WGS84 constants equal Cesium', () => {
assert.equal(WGS84.maximumRadius, c.maximumRadius); assert.equal(WGS84.maximumRadius, c.maximumRadius);
}); });
test('surface normals, scaling and conversions equal Cesium', () => {
const c = Cesium.Ellipsoid.WGS84;
for (const [x, y, z] of POINTS) {
const p = { x, y, z };
const cp = new Cesium.Cartesian3(x, y, z);
closeVec(
WGS84.geodeticSurfaceNormal(p),
c.geodeticSurfaceNormal(cp),
1e-12,
'normal',
);
const ours = WGS84.scaleToGeodeticSurface(p);
const theirs = c.scaleToGeodeticSurface(cp);
if (theirs === undefined) assert.equal(ours, undefined);
else closeVec(ours, theirs, 1e-12, 'scale');
const carto = WGS84.cartesianToCartographic(p);
const cCarto = c.cartesianToCartographic(cp);
if (cCarto === undefined) {
assert.equal(carto, undefined);
} else {
close(carto.longitude, cCarto.longitude, 1e-12, 'lon');
close(carto.latitude, cCarto.latitude, 1e-12, 'lat');
close(carto.height, cCarto.height, 1e-9, 'height');
closeVec(
WGS84.cartographicToCartesian(carto),
c.cartographicToCartesian(cCarto),
1e-12,
'roundtrip',
);
}
}
});
test('result parameters are filled in place and may alias the input', () => { test('result parameters are filled in place and may alias the input', () => {
const p = { x: 1334762.2, y: -4654869.1, z: 4137287.9 }; const p = { x: 1334762.2, y: -4654869.1, z: 4137287.9 };
const out = { x: 0, y: 0, z: 0 }; const out = { x: 0, y: 0, z: 0 };
-48
View File
@@ -1,11 +1,8 @@
// Ported from CesiumJS 1.138 (Apache-2.0) — EllipsoidGeodesic.js. // Ported from CesiumJS 1.138 (Apache-2.0) — EllipsoidGeodesic.js.
// Vincenty inverse and surface interpolation, preserving operation order. // Vincenty inverse and surface interpolation, preserving operation order.
import * as Cartesian3 from './cartesian3.js';
import * as Cartographic from './cartographic.js'; import * as Cartographic from './cartographic.js';
import * as Ellipsoid from './ellipsoid.js'; import * as Ellipsoid from './ellipsoid.js';
import * as CesiumMath from './math.js'; import * as CesiumMath from './math.js';
import { Check } from './check.js';
import { DEBUG } from './debug.js';
const defined = (value) => value !== null && value !== undefined; const defined = (value) => value !== null && value !== undefined;
function setConstants(ellipsoidGeodesic) { function setConstants(ellipsoidGeodesic) {
const uSquared = ellipsoidGeodesic._uSquared; const uSquared = ellipsoidGeodesic._uSquared;
@@ -230,29 +227,7 @@ function vincentyInverseFormula(
ellipsoidGeodesic._uSquared = uSquared; ellipsoidGeodesic._uSquared = uSquared;
} }
const scratchCart1 = Cartesian3.create();
const scratchCart2 = Cartesian3.create();
function computeProperties(ellipsoidGeodesic, start, end, ellipsoid) { function computeProperties(ellipsoidGeodesic, start, end, ellipsoid) {
const firstCartesian = Cartesian3.normalize(
ellipsoid.cartographicToCartesian(start, scratchCart2),
scratchCart1,
);
const lastCartesian = Cartesian3.normalize(
ellipsoid.cartographicToCartesian(end, scratchCart2),
scratchCart2,
);
if (DEBUG) {
Check.typeOf.number.greaterThanOrEquals(
'value',
Math.abs(
Math.abs(Cartesian3.angleBetween(firstCartesian, lastCartesian)) -
Math.PI,
),
0.0125,
);
}
vincentyInverseFormula( vincentyInverseFormula(
ellipsoidGeodesic, ellipsoidGeodesic,
ellipsoid.maximumRadius, ellipsoid.maximumRadius,
@@ -322,10 +297,6 @@ Object.defineProperties(EllipsoidGeodesic.prototype, {
*/ */
surfaceDistance: { surfaceDistance: {
get: function () { get: function () {
if (DEBUG) {
Check.defined('distance', this._distance);
}
return this._distance; return this._distance;
}, },
}, },
@@ -362,10 +333,6 @@ Object.defineProperties(EllipsoidGeodesic.prototype, {
*/ */
startHeading: { startHeading: {
get: function () { get: function () {
if (DEBUG) {
Check.defined('distance', this._distance);
}
return this._startHeading; return this._startHeading;
}, },
}, },
@@ -378,10 +345,6 @@ Object.defineProperties(EllipsoidGeodesic.prototype, {
*/ */
endHeading: { endHeading: {
get: function () { get: function () {
if (DEBUG) {
Check.defined('distance', this._distance);
}
return this._endHeading; return this._endHeading;
}, },
}, },
@@ -394,11 +357,6 @@ Object.defineProperties(EllipsoidGeodesic.prototype, {
* @param {Cartographic} end The final planetodetic point on the path. * @param {Cartographic} end The final planetodetic point on the path.
*/ */
EllipsoidGeodesic.prototype.setEndPoints = function (start, end) { EllipsoidGeodesic.prototype.setEndPoints = function (start, end) {
if (DEBUG) {
Check.defined('start', start);
Check.defined('end', end);
}
computeProperties(this, start, end, this._ellipsoid); computeProperties(this, start, end, this._ellipsoid);
}; };
@@ -425,17 +383,11 @@ EllipsoidGeodesic.prototype.interpolateUsingFraction = function (
* @param {number} distance The distance from the initial point to the point of interest along the geodesic * @param {number} distance The distance from the initial point to the point of interest along the geodesic
* @param {Cartographic} [result] The object in which to store the result. * @param {Cartographic} [result] The object in which to store the result.
* @returns {Cartographic} The location of the point along the geodesic. * @returns {Cartographic} The location of the point along the geodesic.
*
* @exception {DeveloperError} start and end must be set before calling function interpolateUsingSurfaceDistance
*/ */
EllipsoidGeodesic.prototype.interpolateUsingSurfaceDistance = function ( EllipsoidGeodesic.prototype.interpolateUsingSurfaceDistance = function (
distance, distance,
result, result,
) { ) {
if (DEBUG) {
Check.defined('distance', this._distance);
}
const constants = this._constants; const constants = this._constants;
const s = constants.distanceRatio + distance / constants.b; const s = constants.distanceRatio + distance / constants.b;
-14
View File
@@ -66,17 +66,3 @@ test('endpoint reset, result reuse, custom sphere and dateline/polar paths retai
); );
} }
}); });
test('unset and near-antipodal geodesics reject the same debug inputs', () => {
assert.throws(() => geo.ellipsoidGeodesic.create().surfaceDistance, {
name: 'DeveloperError',
});
const a = geo.cartographic.fromDegrees(0, 0),
b = geo.cartographic.fromDegrees(180, 0);
assert.throws(() => geo.ellipsoidGeodesic.create(a, b), {
name: 'DeveloperError',
});
assert.throws(
() => geo.ellipsoidGeodesic.create().setEndPoints(undefined, b),
{ name: 'DeveloperError' },
);
});
Loaded 100 of 272 files, more files were not shown because too many files have changed in this diff. Show more