refactor(cctv): isolate native rendering resources

Keep shared CCTV logic independent of Cesium, release the original renderer's resources, and guard retired initialization/media work. Preserve native marker, plane, volume, calibration and input behavior.

Record keyless dev/preview validation and retain unresolved shell idle/terrain and photoreal acceptance gaps. Routine QA builds use empty Google and Cesium credentials and block paid maps before navigation.
This commit is contained in:
German Khokhlov committed 2026-10-29 00:06:22 +04:00
1 parent f72bf6a774
commit 93c71ffc86
32 files changed
+1536 -740

No files matched your search

+5
View File
@@ -542,6 +542,11 @@
"src/layers/cctv/projectionScheduling.test.mjs",
"src/layers/cctv/gizmo.test.mjs",
"src/layers/cctv/render/cesiumGizmo.js",
"src/layers/cctv/render/cesium.js",
"src/layers/cctv/render/cesiumViewshed.js",
"src/layers/cctv/render/cesium.test.mjs",
"src/layers/cctv/lifecycle.test.mjs",
"src/layers/cctv/projectionMedia.test.mjs",
"src/layers/cctv/cards.js",
"src/layers/cctv/rendering.js",
"src/layers/cctv/testing.js",
+6 -8
View File
@@ -1449,10 +1449,6 @@
"src/layers/shared/color.js",
"src/data/cctvLod.js",
"src/data/cctvViewshed.js",
"src/data/iconOrientation.js",
"src/data/render/cesiumProjection.js",
"src/data/trackingClickGesture.js",
"src/data/render/cesiumConstants.js",
"src/layers/cctv/calibration.js",
"src/layers/cctv/cards.js",
"src/layers/cctv/catalog.js",
@@ -1497,11 +1493,9 @@
"src/geo/scaleToGeodeticSurface.js",
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js",
"src/globe/contract.js",
"src/globe/cesium/engineFromViewer.js"
"src/geo/developerError.js"
],
"external": ["hls.js", "cesium"]
"external": ["hls.js"]
},
"street-level-layer": {
"exports": [
@@ -1835,6 +1829,8 @@
"src/app/layers/bikeshare.js",
"src/app/layers/cctv.js",
"src/layers/cctv/render/cesiumGizmo.js",
"src/layers/cctv/render/cesium.js",
"src/layers/cctv/render/cesiumViewshed.js",
"src/app/layers/earthquakes.js",
"src/app/layers/firms.js",
"src/app/layers/flights.js",
@@ -2793,6 +2789,8 @@
"src/app/layers/bikeshare.js",
"src/app/layers/cctv.js",
"src/layers/cctv/render/cesiumGizmo.js",
"src/layers/cctv/render/cesium.js",
"src/layers/cctv/render/cesiumViewshed.js",
"src/app/layers/earthquakes.js",
"src/app/layers/firms.js",
"src/app/layers/flights.js",
+77 -3
View File
@@ -68,6 +68,8 @@
* is no longer needed to avoid a plane/wireframe disagreement.
*
* Run: node scripts/qa-cctv-v2.mjs --url <explicit-server-url> --out <evidence-directory> [--fixture] [--preview]
* Routine keyless builds also pass --terrain-globe: assert zero scene floor
* samples instead of claiming mesh-floor coverage on a globe-only map.
*
* Exit 0 = all asserts passed. Non-zero = a hard failure.
*/
@@ -99,6 +101,9 @@ const output = getOpt('--out');
if (!explicitUrl || !output)
throw new Error('Explicit --url and --out are required');
const mapPolicy = { allowPaidMaps: getFlag('--allow-paid-maps') };
const terrainGlobe = getFlag('--terrain-globe');
if (terrainGlobe && mapPolicy.allowPaidMaps)
throw new Error('Terrain-globe QA must keep paid maps blocked');
const CCTV_URL = new URL(explicitUrl);
if (
['4173', '4185', '4186', '4187', '4188', '4189', '4194'].includes(
@@ -404,6 +409,17 @@ async function main() {
try {
const page = await browser.newPage();
await blockPaidQaMaps(page, mapPolicy);
const terrainRequests = new Set();
let terrainActivityAt = 0;
page.on('request', (request) => {
if (new URL(request.url()).pathname !== '/api/terrain/heights') return;
terrainRequests.add(request);
terrainActivityAt = Date.now();
});
for (const event of ['requestfinished', 'requestfailed'])
page.on(event, (request) => {
if (terrainRequests.delete(request)) terrainActivityAt = Date.now();
});
await page.setViewport({ width: 1280, height: 800 });
watchW1Page(page, evidence, {
name: 'cctv-legacy',
@@ -435,6 +451,27 @@ async function main() {
if (layer.enabled) await app.dataManager.setEnabled(id, false);
app.styleManager._setDetectionMode('OFF');
});
evidence.surface = await page.evaluate(() => {
const app = window.__godsEyeView;
return {
map: app.mapStackController.getActiveId(),
globeVisible: app.viewer.scene.globe.show,
tileset: Boolean(app.tileset),
googleKeyPresent: Boolean(window.__GOOGLE_MAPS_API_KEY__),
photorealAvailable:
app.mapStackController.isStackAvailable('photoreal'),
};
});
evidence.surfaceMode = terrainGlobe ? 'terrain-globe' : 'mesh-floor';
if (terrainGlobe) {
const keyless =
evidence.surface.globeVisible === true &&
!evidence.surface.tileset &&
!evidence.surface.googleKeyPresent &&
!evidence.surface.photorealAvailable;
record('keyless terrain-globe startup', keyless);
if (!keyless) throw new Error('Expected a keyless globe before CCTV QA');
}
// Let the initial fly-to Austin and first tiles settle.
await sleep(4000);
@@ -619,7 +656,7 @@ async function main() {
console.log('Waiting for tiles + the one-shot completion pass...');
const tilesSeenLoaded = await waitForTilesLoaded(page, 45000);
let sampleFloorOk = false;
if (tilesSeenLoaded) {
if (tilesSeenLoaded && !terrainGlobe) {
sampleFloorOk = await page
.waitForFunction(
(base) => window.__qaCounters.sampleHeight - base >= 1,
@@ -631,8 +668,12 @@ async function main() {
}
const cFloor = await readCounters();
record(
'>=1 real ground sample once tilesLoaded observed (one-shot snap completed)',
tilesSeenLoaded && sampleFloorOk,
terrainGlobe
? 'terrain-globe resolves from priors with zero scene ground samples'
: '>=1 real ground sample once tilesLoaded observed (one-shot snap completed)',
terrainGlobe
? cFloor.sampleHeight - c0.sampleHeight === 0
: tilesSeenLoaded && sampleFloorOk,
tilesSeenLoaded
? `total real samples since enable=${cFloor.sampleHeight - c0.sampleHeight}`
: 'tilesLoaded never observed within 45s — floor not assessable this run',
@@ -1588,6 +1629,24 @@ async function main() {
});
const cBeforeViewshed = await readCounters();
await page.evaluate(() => {
const primitives = window.__godsEyeView.viewer.scene.primitives;
const events = [];
window.__qaViewshedTrace = events;
for (const method of ['add', 'remove']) {
const original = primitives[method].bind(primitives);
primitives[method] = (primitive, ...args) => {
if (primitive?._gevViewshed)
events.push({
method,
camera: primitive._gevViewshed,
at: performance.now(),
stack: new Error().stack,
});
return original(primitive, ...args);
};
}
});
const modeAfterSet = await page.evaluate(() => {
const mod = window.__godsEyeView.dataManager.layers.get('cctv').module;
mod.setParams({ coverageMode: 'viewshed' });
@@ -1638,6 +1697,18 @@ async function main() {
mod.setParams({ coverageMode: 'viewshed' });
});
await sleep(400);
// NEXT starts an asynchronous footprint-height lookup. Its completion is
// allowed to update geometry; the unchanged 8s idle window starts after it.
const terrainDeadline = Date.now() + 35000;
while (
(terrainRequests.size || Date.now() - terrainActivityAt < 250) &&
Date.now() < terrainDeadline
)
await sleep(50);
const terrainSettled =
terrainRequests.size === 0 && Date.now() - terrainActivityAt >= 250;
record('footprint requests settle before viewshed idle', terrainSettled);
if (!terrainSettled) throw new Error('Footprint requests did not settle');
await page.evaluate(() => {
const prims = window.__godsEyeView.viewer.scene.primitives;
window.__qaViewshedRefs = [];
@@ -1659,6 +1730,9 @@ async function main() {
return { sameCount, sameIdentity, count: now.length };
});
const cAfterViewshed = await readCounters();
evidence.viewshedTrace = await page.evaluate(
() => window.__qaViewshedTrace,
);
const lateFloorSamples =
cAfterViewshed.sampleHeight - cBeforeViewshed.sampleHeight;
record(