From 087adc6dc97d9f84bd152218b934398e3c34460e Mon Sep 17 00:00:00 2001 From: German Khokhlov Date: Tue, 10 Nov 2026 19:11:08 +0400 Subject: [PATCH] Count a running mission replay as the camera's owner in readiness A Space Missions replay flies the camera until it ends, so waiting for a still camera after a switch let the replay finish before the scenario looked at it. While a replay runs, readiness no longer asks for enabled inputs or a still pose, as for the cockpit and a followed target. --- scripts/qa/ready.mjs | 8 +++++++- scripts/qa/ready.test.mjs | 13 ++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/scripts/qa/ready.mjs b/scripts/qa/ready.mjs index 4609aa049..9ea79cf38 100644 --- a/scripts/qa/ready.mjs +++ b/scripts/qa/ready.mjs @@ -94,14 +94,20 @@ export function waitForSettled(createState, { frames, timeoutMs }) { const cockpitActive = Boolean( window.__godsEyeView?.styleManager?.getCockpitState?.()?.active, ); + // A running Space Missions replay flies the camera until it ends. + const drawn = window.__godsEyeView?.dataManager?.layers + ?.get('rocket-launches') + ?.module?.qaDrawn?.(); + const replaying = Boolean(drawn?.replay?.active && !drawn.paused); status.observed = { inputsEnabled: status.conditions.cameraInputsEnabled, following, cockpitActive, + replaying, lastPoseDelta, }; // Only a live camera owner excuses motion or held inputs, never a scenario flag. - if (following || cockpitActive) { + if (following || cockpitActive || replaying) { status.conditions.cameraInputsEnabled = true; status.conditions.cameraPoseStable = true; } diff --git a/scripts/qa/ready.test.mjs b/scripts/qa/ready.test.mjs index cda74e583..2f3ca759f 100644 --- a/scripts/qa/ready.test.mjs +++ b/scripts/qa/ready.test.mjs @@ -14,8 +14,11 @@ import { function pageEngine(t) { const engine = createFakeEngine(); const previous = globalThis.window; + const launches = { qaDrawn: () => ({ paused: false, replay: null }) }; const app = { engine, + launches, + dataManager: { layers: new Map([['rocket-launches', { module: launches }]]) }, styleManager: { initialRestorePromise: Promise.resolve(), getCockpitState: () => ({ active: false }), @@ -30,14 +33,16 @@ function pageEngine(t) { return app; } -test('cockpit and follow readiness allow held inputs and moving poses after drawn frames', async (t) => { - for (const owner of ['cockpit', 'follow']) { +test('cockpit, follow and replay readiness allow held inputs and moving poses after drawn frames', async (t) => { + for (const owner of ['cockpit', 'follow', 'replay']) { await t.test(owner, async (t) => { - const { engine, styleManager } = pageEngine(t); + const { engine, styleManager, launches } = pageEngine(t); engine.input.suspendCameraInputs(); let drawn = 0; if (owner === 'cockpit') styleManager.getCockpitState = () => ({ active: true }); + else if (owner === 'replay') + launches.qaDrawn = () => ({ paused: false, replay: { active: true } }); else engine.camera.follow({ position: () => ({ x: 6378137, y: drawn * 10, z: 0 }), @@ -58,6 +63,7 @@ test('cockpit and follow readiness allow held inputs and moving poses after draw assert.equal(status.observed.inputsEnabled, false); assert.equal(status.observed.cockpitActive, owner === 'cockpit'); assert.equal(status.observed.following, owner === 'follow'); + assert.equal(status.observed.replaying, owner === 'replay'); assert.ok(status.observed.lastPoseDelta.orientation > 0); assert.equal(engine.frames.postRender.numberOfListeners, 0); }); @@ -128,6 +134,7 @@ test('unowned input locks and moving cameras time out with observed state', asyn inputsEnabled: moving, following: false, cockpitActive: false, + replaying: false, lastPoseDelta: { positionM: moving ? 1 : 0, orientation: 0,