Move scene effects and imported geometry behind the globe contract

Move Cesium viewer, map, sky, projection, trail and sonar internals into
its engine. Drive styles, bloom, sharpening and contact sonar through
engine.render. Count camera-input suspensions across all three engines
and migrate the feature callers. Preserve Cesium's effects and input
behaviour; fix transit lens clicks on Cesium and make zero sharpening on
Navara disable sharpening, as it does on Cesium.

Parse imported files into plain records and draw them through movers,
lines, ground and imagery on both engines. Follow Cesium's KML style
precedence, preserve local KMZ images and frame the drawable records.
Open overlays use imagery when the engine has no native map-overlay API.
Run every imported-display case on fake and headless Cesium engines.

The shared import path retains these known differences from Cesium's old
DataSources: wall fills, extruded prisms and gx:LatLonQuad are undrawn;
raised polygon fills drape while outlines retain altitude; GroundOverlay
rotation, altitude and RGB tint are not applied; rhumb lines become great
circles. Ground heights use the loaded surface synchronously instead of
waiting for most-detailed sampling. Supported KMZ ground overlays now
draw on Navara too. Document the limits rather than claiming full parity.

Combine batches B and C because their disjoint path manifests cannot
place the shared boundary and format files in a later commit while
keeping B independently valid. This combined commit owns the shared
docs, package boundaries, format scope, locale catalogs and coupling
baseline. Remove five obsolete import strings from all eleven catalogs.
Make the load-time source scan include new files and skip deleted paths
so the same checks work before and after recording the moves.

Coupling: Navara renderer lines fall from 1695 to 1294, Cesium import
files from 25 to 9, viewer lines from 1177 to 1026, SDK loads from 4 to 3
and engine-name lines from 62 to 42. No count grows.

The full unit suite and the boundary, format and coupling checks pass.
Browser validation remains to be run.

Browser checks to run
GOOGLE_MAPS_API_KEY= CESIUM_ION_TOKEN= npm run dev -- --port 4291
node scripts/qa-contract-suite.mjs http://localhost:4291
node scripts/qa-navara-visual-styles.mjs http://localhost:4291
node scripts/qa-navara-bloom.mjs http://localhost:4291
node scripts/qa-navara-sonar.mjs http://localhost:4291
QA_BASE_URL=http://localhost:4291 node scripts/qa-visual-effects.mjs
QA_BASE_URL=http://localhost:4291 node scripts/qa-zoom-out-guard.mjs
node scripts/qa-sky-model-lighting.mjs --url http://localhost:4291
node scripts/qa-geo-import.mjs http://localhost:4291
node scripts/qa-geo-import-regressions.mjs http://localhost:4291
node scripts/qa-geo-import-large.mjs http://localhost:4291
node scripts/qa-navara-geo-import.mjs http://localhost:4291

Use the keyless server for every check. Photoreal-only cases remain
unverified; do not enable paid tiles to run them.
This commit is contained in:
German Khokhlov committed 2026-11-07 12:45:21 +04:00
1 parent c079b329d0
commit 97c4dc76ce
130 files changed
+4763 -3495

No files matched your search

+66 -90
View File
@@ -1,9 +1,10 @@
#!/usr/bin/env node
/** Import regressions through the real UI controller and Cesium loaders.
/** Import regressions through the real UI controller and globe primitives.
* Run against dev and preview: QA_BASE_URL=http://localhost:4173 node scripts/qa-geo-import-regressions.mjs
*/
import puppeteer from 'puppeteer';
import * as Cesium from 'cesium';
import { installImportProbe } from './qa-geo-import-probe.mjs';
import { hookRenderErrors, readRenderErrors } from './qa-browserEvidence.mjs';
const browser = await puppeteer.launch({ headless: true });
@@ -86,7 +87,7 @@ let failed = 0;
try {
await page.setViewport({ width: 390, height: 844 });
await page.goto(
`${process.env.QA_BASE_URL || 'http://localhost:4173'}/?welcome=0`,
`${process.argv.find((arg) => /^https?:/.test(arg)) || process.env.QA_BASE_URL || 'http://localhost:4173'}/?welcome=0`,
{ waitUntil: 'domcontentloaded' },
);
await page.waitForFunction(
@@ -112,8 +113,9 @@ try {
page.on('request', (r) => {
if (!/^(data:|blob:)/.test(r.url())) requests.push(r.url());
});
await installImportProbe(page);
const results = await page.evaluate(async () => {
const { viewer } = window.__godsEyeView,
const { viewer, engine } = window.__godsEyeView,
C = window.__CESIUM__,
tool = window.__gevGeoImport;
const results = [],
@@ -128,14 +130,27 @@ try {
typeof value === 'object' && !ArrayBuffer.isView(value)
? JSON.stringify(value)
: value;
const before = new Set(window.__qaImports.records());
const [outcome] = await tool.importFiles([new File([content], name)]);
return {
outcome,
source: outcome.ok
? viewer.dataSources.get(viewer.dataSources.length - 1)
: null,
drawing: window.__qaImports
.records()
.filter((record) => !before.has(record)),
};
};
const pins = (r) =>
r.drawing
.filter((record) => record.kind === 'movers')
.flatMap((record) => record.items);
const ground = (r) =>
r.drawing
.filter((record) => record.kind === 'ground')
.flatMap((record) => record.items);
const lines = (r) => r.drawing.filter((record) => record.kind === 'lines');
const screenOf = (p) => engine.projector.worldToWindow(p);
const inFrame = (p) =>
p && p.x > 0 && p.x < innerWidth && p.y > 0 && p.y < innerHeight;
const remove = (r) => {
if (r.outcome.ok) tool.remove(r.outcome.id);
};
@@ -171,26 +186,20 @@ try {
},
'xyz.geojson',
);
const pin = (r.source?.importedEntities || r.source?.entities.values).find(
(e) => e.billboard,
),
polygon = (r.source?.importedEntities || r.source?.entities.values).find(
(e) => e.polygon,
);
const pin = pins(r)[0],
polygon = ground(r)[0];
check(
'XYZ ground footprint and isolated entity IDs',
'XYZ ground footprint and isolated pick IDs',
r.outcome.ok &&
pin.id !== 'kartaview-owned' &&
Math.abs(
C.Cartographic.fromCartesian(pin.position.getValue(now)).height,
) < 0.01 &&
polygon.polygon.perPositionHeight?.getValue(now) !== true,
pin.pickId === undefined &&
Math.abs(C.Cartographic.fromCartesian(pin.position).height) < 0.01 &&
polygon.rings.flat().every((p) => p.length === 2),
);
check(
'ground pins have no persistent clamping callbacks',
pin?.billboard.heightReference?.getValue(now) ===
C.HeightReference.NONE &&
pin.billboard.disableDepthTestDistance.getValue(now) === Infinity,
'ground pins use fixed positions and ignore scene depth',
typeof pin.position.x === 'number' &&
r.drawing.find((record) => record.kind === 'movers').options
.depthTest === 'always',
);
remove(r);
r = await load(
@@ -201,24 +210,18 @@ try {
),
'tracks.kml',
);
const find = (name) =>
(r.source?.importedEntities || r.source?.entities.values).find(
(e) => e.name === name,
);
check(
'ground tracks and untessellated authored lines clamp',
find('ground')?.polyline.clampToGround?.getValue(now) &&
find('line')?.polyline.clampToGround?.getValue(now),
ground(r).length === 2,
);
check(
'explicit track altitude and original vertices survive',
find('absolute')?.polyline.positions.getValue(now).length === 2 &&
find('absolute')
.polyline.positions.getValue(now)
.every(
(p) =>
Math.abs(C.Cartographic.fromCartesian(p).height - 10000) < 0.01,
),
lines(r).length === 1 &&
lines(r)[0].positions.length === 2 &&
lines(r)[0].positions.every(
(p) =>
Math.abs(C.Cartographic.fromCartesian(p).height - 10000) < 0.01,
),
);
remove(r);
r = await load(
@@ -245,10 +248,7 @@ try {
);
check(
'import show controls own previously hidden KML content',
r.outcome.ok &&
(r.source.importedEntities || r.source.entities.values).every(
(e) => e.isShowing,
),
r.outcome.ok && pins(r).length === 1 && pins(r).every((pin) => pin.show),
);
remove(r);
r = await load(
@@ -260,9 +260,8 @@ try {
check(
'a colored overlay without an icon is drawable',
r.outcome.ok &&
(r.source.importedEntities || r.source.entities.values).some(
(e) => e.rectangle,
),
ground(r).length === 1 &&
ground(r)[0].fill.join() === '0,1,0,1',
);
remove(r);
const xml = wrap(`<Placemark>${point}</Placemark>`);
@@ -325,11 +324,10 @@ try {
'portrait.geojson',
);
await new Promise((resolve) => setTimeout(resolve, 2300));
const positions = (r.source?.importedEntities ||
r.source?.entities.values)[0].polyline.positions.getValue(now);
const screen = positions?.map((p) =>
C.SceneTransforms.worldToWindowCoordinates(viewer.scene, p),
const positions = ground(r)[0].positions.map(([lon, lat]) =>
C.Cartesian3.fromDegrees(lon, lat),
);
const screen = positions.map(screenOf);
check(
'portrait flight includes both route endpoints',
screen?.every(
@@ -349,28 +347,13 @@ try {
'intercontinental.geojson',
);
await new Promise((resolve) => setTimeout(resolve, 2300));
const intercontinental = (
r.source?.importedEntities || r.source?.entities.values
)
.filter((e) => e.position)
.map((e) => e.position.getValue(now));
const occluder = new C.EllipsoidalOccluder(
C.Ellipsoid.WGS84,
viewer.camera.positionWC,
);
const intercontinental = pins(r).map((pin) => pin.position);
check(
'intercontinental points remain in frame and in front of the horizon',
intercontinental?.every((p) => {
const s = C.SceneTransforms.worldToWindowCoordinates(viewer.scene, p);
return (
occluder.isPointVisible(p) &&
s &&
s.x > 0 &&
s.x < innerWidth &&
s.y > 0 &&
s.y < innerHeight
);
}),
intercontinental.length === 2 &&
intercontinental.every(
(p) => engine.projector.horizonVisible(p) && inFrame(screenOf(p)),
),
);
remove(r);
r = await load(
@@ -381,20 +364,12 @@ try {
);
await new Promise((resolve) => setTimeout(resolve, 2300));
check(
'a wall contributes both endpoints to the flight',
'a wall top edge contributes both endpoints to the flight',
r.outcome.ok &&
(r.source.importedEntities || r.source.entities.values)
.find((e) => e.wall)
?.wall.positions.getValue(now)
.every((p) => {
const s = C.SceneTransforms.worldToWindowCoordinates(
viewer.scene,
p,
);
return (
s && s.x > 0 && s.x < innerWidth && s.y > 0 && s.y < innerHeight
);
}),
lines(r).length === 1 &&
lines(r)[0].positions.length === 2 &&
lines(r)[0].positions.every((p) => inFrame(screenOf(p))) &&
tool.list().find((file) => file.id === r.outcome.id).undrawn === 1,
);
remove(r);
r = await load(
@@ -447,9 +422,7 @@ try {
{ type: 'Point', coordinates: [-122.5, 37.7, 0] },
'mesh-point.geojson',
);
const meshPoint = (r.source?.importedEntities || r.source?.entities.values)
.find((e) => e.billboard)
?.position.getValue(now);
const meshPoint = pins(r)[0]?.position;
const meshHeight =
meshPoint && C.Cartographic.fromCartesian(meshPoint).height;
check(
@@ -463,9 +436,7 @@ try {
);
tool.flyTo(r.outcome.id);
await new Promise((resolve) => setTimeout(resolve, 2300));
const meshScreen =
meshPoint &&
C.SceneTransforms.worldToWindowCoordinates(viewer.scene, meshPoint);
const meshScreen = meshPoint && screenOf(meshPoint);
check(
'a high-surface arrival frames the elevated pin',
meshScreen &&
@@ -480,7 +451,7 @@ try {
meshHeight,
},
);
const groundSource = r.source;
const groundDrawing = r;
const elevated = await load(
wrap(
'<Placemark><Point><altitudeMode>absolute</altitudeMode><coordinates>-122.5,37.7,5000</coordinates></Point></Placemark>',
@@ -492,9 +463,8 @@ try {
'a higher imported pin never becomes the sampled ground',
elevated.outcome.ok &&
Math.abs(
C.Cartographic.fromCartesian(
groundSource.importedBillboards.get(0).position,
).height - 2000,
C.Cartographic.fromCartesian(pins(groundDrawing)[0].position).height -
2000,
) < 2,
);
remove(elevated);
@@ -526,7 +496,13 @@ try {
'the shared clock stays exactly unchanged',
C.JulianDate.equals(now, viewer.clock.currentTime),
);
check('no file sources remain', tool.list().length === 0);
check(
'no files or drawing sets remain',
tool.list().length === 0 &&
Object.values(window.__qaImports.list()).every(
(list) => list.length === 0,
),
);
return results;
});
for (const r of results) {