From f2117a98f2800819812763653c627de960069840 Mon Sep 17 00:00:00 2001 From: German Khokhlov Date: Tue, 3 Nov 2026 19:54:45 +0400 Subject: [PATCH] Ask a parked Navara globe for no frames and guard parked engines in the switch gate The Navara look stopped requesting frames once its globe is parked. scripts/qa-app-once.mjs now fails if anything moves a parked engine's camera or asks it to draw (its own resume aside). --- scripts/qa-app-once.mjs | 48 +++++++++++++++++++++++++++++++++++++++ src/globe/navara/index.js | 14 +++++++++--- 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/scripts/qa-app-once.mjs b/scripts/qa-app-once.mjs index 69bb1051a..580b9cc2b 100644 --- a/scripts/qa-app-once.mjs +++ b/scripts/qa-app-once.mjs @@ -166,9 +166,51 @@ try { report.uiBefore = uiBefore; const sequence = ['navara', 'cesium', 'navara', 'cesium', 'navara', 'cesium']; + // Dev guard: once an engine is parked, nothing may move its camera or + // ask it to draw until it is attached again (its own resume aside). + await page.evaluate(() => { + window.__qaParkedCalls = []; + window.__qaWatchParked = (engine) => { + if (!engine || engine.__qaWatched) return; + engine.__qaWatched = true; + const watch = (owner, names, label) => { + for (const name of names) { + const original = owner[name]; + if (typeof original !== 'function') continue; + owner[name] = function (...args) { + const parked = window.__godsEyeView?.engine !== engine; + const resume = name === 'request' && args[0] === 'engine-resume'; + if (parked && !resume && !window.__qaSwitching) + window.__qaParkedCalls.push({ + call: `${engine.kind}.${label}.${name}`, + reason: typeof args[0] === 'string' ? args[0] : null, + stack: new Error().stack.split('\n').slice(2, 6).join(' | '), + }); + return original.apply(this, args); + }; + } + }; + watch( + engine.camera, + ['setView', 'flyTo', 'lookAt', 'flyToBoundingSphere', 'release'], + 'camera', + ); + watch(engine.render, ['request', 'setContinuous'], 'render'); + }; + window.__qaWatchParked(window.__godsEyeView.engine); + }); for (const kind of sequence) { const started = Date.now(); + // The switch itself parks and resumes engines; only the time an engine + // sits parked is watched. + await page.evaluate(() => { + window.__qaSwitching = true; + }); await switchEngine(page, kind); + await page.evaluate(() => { + window.__qaSwitching = false; + window.__qaWatchParked(window.__godsEyeView.engine); + }); const state = await readSession(page); const ui = await readUi(page); if (kind === 'navara') @@ -251,6 +293,12 @@ try { 'each switch detaches once and attaches once', after.counters, ); + report.parkedEngineCalls = await page.evaluate(() => window.__qaParkedCalls); + check( + report.parkedEngineCalls.length === 0, + 'a parked engine had its camera moved or a frame requested', + report.parkedEngineCalls, + ); check( after.counters?.applications === 1, 'the application must be created once', diff --git a/src/globe/navara/index.js b/src/globe/navara/index.js index 0f0e1045e..d5d6c85d1 100644 --- a/src/globe/navara/index.js +++ b/src/globe/navara/index.js @@ -114,6 +114,8 @@ export async function createNavaraGlobe({ let look; let removeLook = null; let removePostSampling = null; + // Parked for a switch back: its render loop is off (setRenderLoop). + let parked = false; try { view = new ThreeView(navaraViewOptions({ container, canvas, pixelRatio })); const plugin = new DefaultPlugin(); @@ -165,7 +167,9 @@ export async function createNavaraGlobe({ camera.moveEnd.addEventListener(() => { moving = false; readGround(); - engine.render.request('navara-look'); + // A parked globe (setRenderLoop(false)) draws nothing: its camera + // settling asks for no frame. + if (!parked) engine.render.request('navara-look'); }), // After the engine's camera sync: before the first frame the camera // has no position yet (camera.height() throws). @@ -188,7 +192,8 @@ export async function createNavaraGlobe({ ground, placed ? lookPlace : null, landscape, - ) + ) && + !parked ) engine.render.request('navara-look'); }), @@ -219,7 +224,10 @@ export async function createNavaraGlobe({ look.resetDaylight(); engine.render.request('restored-daylight'); }, - setRenderLoop: (enabled) => setRenderLoopEnabled(view, enabled), + setRenderLoop: (enabled) => { + parked = !enabled; + setRenderLoopEnabled(view, enabled); + }, destroy() { if (destroyed) return; destroyed = true;