mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 15:52:21 +00:00
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.
235 lines
7.4 KiB
JavaScript
235 lines
7.4 KiB
JavaScript
#!/usr/bin/env node
|
|
/** Rendered acceptance for style transitions and effect-state ownership. */
|
|
import fs from 'node:fs';
|
|
import puppeteer from 'puppeteer';
|
|
|
|
const browser = await puppeteer.launch({
|
|
headless: true,
|
|
args: [
|
|
'--no-sandbox',
|
|
...(process.platform === 'darwin'
|
|
? ['--use-angle=metal', '--enable-gpu']
|
|
: ['--use-gl=angle', '--use-angle=swiftshader']),
|
|
],
|
|
});
|
|
const page = await browser.newPage();
|
|
const errors = [];
|
|
page.on('pageerror', (error) => errors.push(error.message));
|
|
let failures = 0;
|
|
function check(name, passed) {
|
|
console.log(`[${passed ? 'PASS' : 'FAIL'}] ${name}`);
|
|
if (!passed) failures++;
|
|
}
|
|
try {
|
|
await page.setViewport({ width: 1440, height: 900 });
|
|
const url = new URL(process.env.QA_BASE_URL || 'http://127.0.0.1:4173');
|
|
url.searchParams.set('welcome', '0');
|
|
await page.goto(url.href, { waitUntil: 'domcontentloaded' });
|
|
await page.waitForFunction(
|
|
() =>
|
|
window.__godsEyeView?.styleManager?._visualEffects &&
|
|
document.getElementById('loading-screen')?.classList.contains('hidden'),
|
|
{ timeout: 60_000 },
|
|
);
|
|
await page.evaluate(() => {
|
|
const viewer = window.__godsEyeView.viewer;
|
|
viewer.camera.cancelFlight?.();
|
|
viewer.scene.tweens?.removeAll?.();
|
|
viewer.camera.setView({
|
|
destination: viewer.scene.globe.ellipsoid.cartographicToCartesian({
|
|
longitude: (-97.7431 * Math.PI) / 180,
|
|
latitude: (30.2568 * Math.PI) / 180,
|
|
height: 1200,
|
|
}),
|
|
orientation: { heading: 0, pitch: -0.85, roll: 0 },
|
|
});
|
|
viewer.scene.requestRender();
|
|
});
|
|
await page.evaluate(
|
|
() =>
|
|
new Promise((resolve) =>
|
|
requestAnimationFrame(() => requestAnimationFrame(resolve)),
|
|
),
|
|
);
|
|
const settled = await page
|
|
.waitForFunction(
|
|
() => {
|
|
const primitives = window.__godsEyeView.viewer.scene.primitives;
|
|
for (let i = 0; i < primitives.length; i++) {
|
|
const primitive = primitives.get(i);
|
|
if (
|
|
typeof primitive.tilesLoaded === 'boolean' &&
|
|
!primitive.tilesLoaded
|
|
)
|
|
return false;
|
|
}
|
|
return true;
|
|
},
|
|
{ timeout: 60_000 },
|
|
)
|
|
.then(
|
|
() => true,
|
|
() => false,
|
|
);
|
|
check('visible tile content settles before visual captures', settled);
|
|
fs.mkdirSync('qa-shots/visual-effects', { recursive: true });
|
|
for (const style of [
|
|
'normal',
|
|
'retro',
|
|
'surveillance',
|
|
'thermal',
|
|
'anime',
|
|
'noir',
|
|
'normal',
|
|
]) {
|
|
await page.evaluate(
|
|
(name) => window.__godsEyeView.styleManager.setStyle(name),
|
|
style,
|
|
);
|
|
await page.waitForFunction(
|
|
() => window.__godsEyeView.styleManager.transitions.size === 0,
|
|
{ timeout: 10_000 },
|
|
);
|
|
const state = await page.evaluate(() => {
|
|
const manager = window.__godsEyeView.styleManager;
|
|
const effects = manager._visualEffects;
|
|
const visible = Object.entries(effects.stages)
|
|
.filter(([, stage]) => stage.enabled)
|
|
.map(([name]) => name);
|
|
const animated = Object.values(effects.stages).some(
|
|
(stage) =>
|
|
stage.enabled &&
|
|
stage.uniforms.time !== undefined &&
|
|
stage.uniforms.intensity > 0.001,
|
|
);
|
|
return {
|
|
active: manager.activeStyle,
|
|
visible,
|
|
clock: effects.frameId !== null,
|
|
animated,
|
|
};
|
|
});
|
|
check(
|
|
`${style}: only the selected style remains visible`,
|
|
state.active === style &&
|
|
JSON.stringify(state.visible) ===
|
|
JSON.stringify(style === 'normal' ? [] : [style]),
|
|
);
|
|
check(
|
|
`${style}: the animation clock follows visible animated stages`,
|
|
state.clock === state.animated,
|
|
);
|
|
if (['normal', 'surveillance', 'thermal'].includes(style)) {
|
|
await page.screenshot({ path: `qa-shots/visual-effects/${style}.png` });
|
|
if (style === 'surveillance')
|
|
await page.evaluate(() =>
|
|
window.__godsEyeView.viewer.camera.lookRight(0.2),
|
|
);
|
|
}
|
|
}
|
|
await page.evaluate(() => {
|
|
const manager = window.__godsEyeView.styleManager;
|
|
manager.setStyle('noir');
|
|
manager.setStyle('retro');
|
|
manager.setStyle('normal');
|
|
});
|
|
await page.waitForFunction(
|
|
() => window.__godsEyeView.styleManager.transitions.size === 0,
|
|
{ timeout: 10_000 },
|
|
);
|
|
check(
|
|
'rapid switches settle to Normal without an old style or clock',
|
|
await page.evaluate(() => {
|
|
const effects = window.__godsEyeView.styleManager._visualEffects;
|
|
return (
|
|
effects.frameId === null &&
|
|
Object.values(effects.stages).every((stage) => !stage.enabled)
|
|
);
|
|
}),
|
|
);
|
|
check(
|
|
'bloom actions, effect state and saved snapshot agree',
|
|
await page.evaluate(() => {
|
|
const manager = window.__godsEyeView.styleManager;
|
|
const states = [];
|
|
const unsubscribe = manager.subscribeShareState((value) =>
|
|
states.push(value),
|
|
);
|
|
const result = manager.setBloom({ enabled: true, intensityPct: 98 });
|
|
unsubscribe();
|
|
const published = states.at(-1);
|
|
const snapshot = manager.getVisualState();
|
|
return (
|
|
states[0].initial &&
|
|
states.length > 1 &&
|
|
Object.isFrozen(published.state.options) &&
|
|
published.state.bloomEnabled &&
|
|
published.state.options.bloomIntensity === 98 &&
|
|
result.ok &&
|
|
result.bloom.intensityPct === 98 &&
|
|
snapshot.bloom.intensity === 98 &&
|
|
manager._visualEffects.bloomIntensity === 98 &&
|
|
window.__godsEyeView.viewer.scene.postProcessStages.bloom.enabled
|
|
);
|
|
}),
|
|
);
|
|
check(
|
|
'sharpen actions, effect state and saved snapshot agree',
|
|
await page.evaluate(() => {
|
|
const manager = window.__godsEyeView.styleManager;
|
|
const result = manager.setSharpen({ enabled: true, intensityPct: 61 });
|
|
return (
|
|
result.ok &&
|
|
result.sharpen.intensityPct === 61 &&
|
|
manager.getVisualState().sharpen.intensity === 61 &&
|
|
manager._visualEffects.sharpenIntensity === 0.61 &&
|
|
Math.abs(window.__godsEyeView.engine.render.sharpen().amount - 1.32) <
|
|
1e-9
|
|
);
|
|
}),
|
|
);
|
|
await page.setViewport({ width: 620, height: 900 });
|
|
await page.evaluate(() =>
|
|
window.__godsEyeView.styleManager.setPanelCollapsed('pp-toggles', false, {
|
|
persist: false,
|
|
syncShare: false,
|
|
}),
|
|
);
|
|
await page.screenshot({ path: 'qa-shots/visual-effects/narrow.png' });
|
|
const teardown = await page.evaluate(async () => {
|
|
const manager = window.__godsEyeView.styleManager;
|
|
const effects = manager._visualEffects;
|
|
manager.setStyle('retro');
|
|
const engine = window.__godsEyeView.engine;
|
|
const pending = manager.dispose();
|
|
const stoppedBeforeAwait =
|
|
effects.stopped &&
|
|
effects.frameId === null &&
|
|
effects.transitions.size === 0;
|
|
await pending;
|
|
return {
|
|
stoppedBeforeAwait,
|
|
disabled:
|
|
engine.render.visualStyles.every(
|
|
(name) => engine.render.style(name).intensity === 0,
|
|
) &&
|
|
!engine.render.bloom().enabled &&
|
|
!engine.render.sharpen().enabled,
|
|
};
|
|
});
|
|
check(
|
|
'UI teardown stops effect animation before its first await',
|
|
teardown.stoppedBeforeAwait,
|
|
);
|
|
check('UI teardown disables owned effects on the engine', teardown.disabled);
|
|
check(
|
|
'effects do not introduce uncaught browser errors',
|
|
errors.length === 0,
|
|
);
|
|
if (errors.length) console.log(JSON.stringify(errors));
|
|
} finally {
|
|
await browser.close();
|
|
}
|
|
console.log(`RESULT: ${failures} failures`);
|
|
process.exitCode = failures ? 1 : 0;
|