fix(director): cover queued and uncertain layer states on same-shot seeks

This commit is contained in:
Alex O'Neill committed 2026-10-25 20:20:09 +00:00
1 parent b918939b99
commit 458def08a5
2 files changed
+54 -9

No files matched your search

+11 -8
View File
@@ -1533,6 +1533,12 @@ export class SceneDirector {
this._selectedShotId !== shot.id
)
return null;
if (!this._claimCameraOwnership()) return false;
this._cancelActiveSceneTravel();
this._loadAbort?.abort();
this._loadAbort = null;
const generation = ++this._loadGeneration;
this._clock.stopShot();
const states = this._layerStatesForShot(scene, shot, {
cameraSettled: seekState.cameraProgress >= 1,
sceneSeek: seekState,
@@ -1542,21 +1548,18 @@ export class SceneDirector {
// those through the cancellable admission path.
const staleStates = Object.fromEntries(
Object.entries(states).filter(([id, state]) => {
const wantEnabled = !!state?.enabled;
const lifecycle = this.dataManager.getLayerLifecycleState(id);
return (
lifecycle &&
(lifecycle.lifecycleState === 'enabling' ||
(lifecycle.enabled !== wantEnabled ||
lifecycle.lifecycleState === 'enabling' ||
lifecycle.lifecycleState === 'disabling' ||
this.dataManager.isEffectivelyEnabled(id) !== !!state?.enabled)
lifecycle.uncertain ||
this.dataManager.isEffectivelyEnabled(id) !== wantEnabled)
);
}),
);
if (!this._claimCameraOwnership()) return null;
this._cancelActiveSceneTravel();
this._loadAbort?.abort();
this._loadAbort = null;
const generation = ++this._loadGeneration;
this._clock.stopShot();
if (Object.keys(staleStates).length) {
const controller = new AbortController();
this._loadAbort = controller;
+43 -1
View File
@@ -14,6 +14,7 @@ import { readShellSource } from '../testSupport/readShellSource.mjs';
import assert from 'node:assert/strict';
import test from 'node:test';
import { LayerLifecycle } from '../data/lifecycle.js';
import { SceneDirector } from './director.js';
import { SCENE_TRACKING_PARAM_KEYS } from './scenePolicy.js';
import { SCENE_RECIPES, getSceneAppendRecipeById } from './recipes.js';
@@ -241,7 +242,19 @@ test('same-shot seeking restores visibility a manual toggle is still changing',
});
test('same-shot seeking restores a layer whose superseded LOAD transition rolls back', async () => {
const { director, dataManager, restore } = makeDirector();
const { director, dataManager, styleManager, viewer, restore } = makeDirector();
const lifecycle = new LayerLifecycle(viewer);
for (const id of ['flights', 'traffic', 'satellites']) {
lifecycle.register({
id,
name: id,
init() {},
enable() {},
disable() {},
update() {},
});
}
const realDirector = new SceneDirector(viewer, styleManager, lifecycle);
try {
await director.loadShot('scene-1', 'shot-a', { flyDuration: 0 });
await dataManager.setEnabled('flights', false);
@@ -254,7 +267,36 @@ test('same-shot seeking restores a layer whose superseded LOAD transition rolls
await reload;
await settle();
assert.equal(dataManager.getAll().find(({ id }) => id === 'flights').enabled, true);
await realDirector.loadShot('scene-1', 'shot-a', { flyDuration: 0 });
await lifecycle.setEnabled('flights', false);
const queuedReload = realDirector.loadShot('scene-1', 'shot-a', { flyDuration: 0 });
for (let i = 0; i < 20 && !lifecycle.isEffectivelyEnabled('flights'); i++) {
await Promise.resolve();
}
assert.deepEqual(lifecycle.getLayerLifecycleState('flights'), {
enabled: false,
lifecycleState: 'disabled',
uncertain: false,
});
assert.equal(await realDirector.seekScene('scene-1', 0.25), true);
await queuedReload;
await settle();
assert.equal(lifecycle.isEnabled('flights'), true);
lifecycle.layers.get('flights').lifecycleUncertain = true;
assert.equal(await realDirector.seekScene('scene-1', 0.3), true);
assert.equal(lifecycle.getLayerLifecycleState('flights').uncertain, false);
let navClaims = 0;
styleManager.runImmediateNavigation = () => {
navClaims += 1;
return false;
};
assert.equal(await realDirector.seekScene('scene-1', 0.4), false);
assert.equal(navClaims, 1, 'camera refusal fails the seek without falling back to LOAD');
} finally {
await realDirector.destroy();
await director.destroy();
restore();
}