mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 11:24:00 +00:00
fix(director): restore layer visibility on same-shot seeks
This commit is contained in:
1 parent
1c234ce027
commit
1644acd2a3
5 files changed
+133
-4
No files matched your search
@@ -0,0 +1,3 @@
|
|||||||
|
- Restore a shot's declared layer visibility when seeking within it after manual
|
||||||
|
layer changes. Refused restoration reports a failed seek, while undeclared
|
||||||
|
operator layers and the unchanged-stack shortcut are preserved (@oneill9).
|
||||||
@@ -506,6 +506,10 @@ dateline while preserving both authored endpoint longitudes, and turns heading
|
|||||||
and roll the short way for any authored angles.
|
and roll the short way for any authored angles.
|
||||||
A seek to a shot's start, as a shot action makes, selects that shot rather than
|
A seek to a shot's start, as a shot action makes, selects that shot rather than
|
||||||
the end of the previous one.
|
the end of the previous one.
|
||||||
|
Same-shot seeking restores declared layer visibility after a manual toggle,
|
||||||
|
using the cancellable layer admission path and reporting refused restoration
|
||||||
|
as a failed seek. Undeclared operator layers remain untouched, and unchanged
|
||||||
|
declared visibility retains the lightweight seek path.
|
||||||
See [camera directions](DIRECTOR-CAMERA.md).
|
See [camera directions](DIRECTOR-CAMERA.md).
|
||||||
|
|
||||||
Realtime voice composes separate connection, response/tool, Radio handoff, input
|
Realtime voice composes separate connection, response/tool, Radio handoff, input
|
||||||
|
|||||||
@@ -88,6 +88,57 @@ try {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
for (const [name, passed] of Object.entries(result)) check(name, passed);
|
for (const [name, passed] of Object.entries(result)) check(name, passed);
|
||||||
|
const visibilityRestored = await page.evaluate(async () => {
|
||||||
|
const { sceneDirector: director, dataManager } = window.__godsEyeView;
|
||||||
|
const layerId = 'director-seek-visibility-qa';
|
||||||
|
window.__gevQaRegisterLayer(dataManager, {
|
||||||
|
id: layerId,
|
||||||
|
name: 'Director seek visibility QA',
|
||||||
|
updateInterval: -1,
|
||||||
|
init() {},
|
||||||
|
enable() {},
|
||||||
|
disable() {},
|
||||||
|
update() {},
|
||||||
|
destroy() {},
|
||||||
|
getStats: () => ({ count: 0 }),
|
||||||
|
});
|
||||||
|
const scene = {
|
||||||
|
id: 'visibility-qa',
|
||||||
|
title: 'Visibility QA',
|
||||||
|
shots: [
|
||||||
|
{
|
||||||
|
id: 'visibility-shot',
|
||||||
|
title: 'Visibility shot',
|
||||||
|
durationSec: 0.4,
|
||||||
|
holdSec: 0,
|
||||||
|
camera: director.styleManager.getCameraState(),
|
||||||
|
visual: director.styleManager.getVisualState(),
|
||||||
|
layers: { [layerId]: { enabled: true } },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
director._project.scenes.push(scene);
|
||||||
|
try {
|
||||||
|
const loaded = await director.loadShot(scene.id, scene.shots[0].id, {
|
||||||
|
flyDuration: 0.2,
|
||||||
|
});
|
||||||
|
await dataManager.setEnabled(layerId, false, { origin: 'user' });
|
||||||
|
const sought = await director.seekScene(scene.id, 0.5);
|
||||||
|
return (
|
||||||
|
loaded?.started && sought && dataManager.layers.get(layerId).enabled
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
director.stopScene();
|
||||||
|
director._project.scenes = director._project.scenes.filter(
|
||||||
|
({ id }) => id !== scene.id,
|
||||||
|
);
|
||||||
|
await window.__gevQaUnregisterLayer(dataManager, layerId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
check(
|
||||||
|
'same-shot seek restores manually changed layer visibility',
|
||||||
|
visibilityRestored,
|
||||||
|
);
|
||||||
const content = await page.evaluate(async () => {
|
const content = await page.evaluate(async () => {
|
||||||
const d = window.__godsEyeView.sceneDirector;
|
const d = window.__godsEyeView.sceneDirector;
|
||||||
const scene = d._project.scenes.find(({ title }) =>
|
const scene = d._project.scenes.find(({ title }) =>
|
||||||
|
|||||||
+15
-4
@@ -1529,16 +1529,27 @@ export class SceneDirector {
|
|||||||
this._selectedShotId !== shot.id
|
this._selectedShotId !== shot.id
|
||||||
)
|
)
|
||||||
return false;
|
return false;
|
||||||
|
const states = this._layerStatesForShot(scene, shot, {
|
||||||
|
cameraSettled: seekState.cameraProgress >= 1,
|
||||||
|
sceneSeek: seekState,
|
||||||
|
});
|
||||||
|
const layers = new Map(
|
||||||
|
this.dataManager.getAll().map((layer) => [layer.id, layer.enabled]),
|
||||||
|
);
|
||||||
|
// A manual toggle invalidates the loaded stack. Let LOAD reconcile it
|
||||||
|
// through the normal cancellable admission path before reporting success.
|
||||||
|
if (
|
||||||
|
sceneLayerPlan(states, new Set(layers.keys())).some(
|
||||||
|
({ id, enabled }) => Boolean(layers.get(id)) !== enabled,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return false;
|
||||||
if (!this._claimCameraOwnership()) return false;
|
if (!this._claimCameraOwnership()) return false;
|
||||||
this._cancelActiveSceneTravel();
|
this._cancelActiveSceneTravel();
|
||||||
this._loadAbort?.abort();
|
this._loadAbort?.abort();
|
||||||
this._loadAbort = null;
|
this._loadAbort = null;
|
||||||
this._loadGeneration += 1;
|
this._loadGeneration += 1;
|
||||||
this._clock.stopShot();
|
this._clock.stopShot();
|
||||||
const states = this._layerStatesForShot(scene, shot, {
|
|
||||||
cameraSettled: seekState.cameraProgress >= 1,
|
|
||||||
sceneSeek: seekState,
|
|
||||||
});
|
|
||||||
let applied = true;
|
let applied = true;
|
||||||
for (const [layerId, state] of Object.entries(states)) {
|
for (const [layerId, state] of Object.entries(states)) {
|
||||||
if (!state?.enabled || !state.params) continue;
|
if (!state?.enabled || !state.params) continue;
|
||||||
|
|||||||
@@ -174,6 +174,66 @@ test('scene clock seek resolves the exact shot phase and camera in both directio
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('same-shot seeking restores declared layer visibility after manual changes', async () => {
|
||||||
|
const { director, dataManager, restore } = makeDirector();
|
||||||
|
try {
|
||||||
|
const shot = director._project.scenes[0].shots[0];
|
||||||
|
shot.layers = { flights: { enabled: true }, traffic: { enabled: false } };
|
||||||
|
await director.loadShot('scene-1', 'shot-a', { flyDuration: 0 });
|
||||||
|
await dataManager.setEnabled('flights', false);
|
||||||
|
await dataManager.setEnabled('traffic', true);
|
||||||
|
await dataManager.setEnabled('satellites', true);
|
||||||
|
dataManager.setEnabledCalls.length = 0;
|
||||||
|
|
||||||
|
assert.equal(await director.seekScene('scene-1', 0.25), true);
|
||||||
|
const enabled = new Map(dataManager.getAll().map(({ id, enabled }) => [id, enabled]));
|
||||||
|
assert.equal(enabled.get('flights'), true, 'restore the shot\'s enabled layer');
|
||||||
|
assert.equal(enabled.get('traffic'), false, 'restore the shot\'s disabled layer');
|
||||||
|
assert.equal(enabled.get('satellites'), true, 'preserve undeclared operator layers');
|
||||||
|
assert.ok(dataManager.setEnabledCalls.every(({ id }) => id !== 'satellites'));
|
||||||
|
} finally {
|
||||||
|
await director.destroy();
|
||||||
|
restore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('same-shot seeking reports refusal when a manually disabled layer cannot be restored', async () => {
|
||||||
|
let refuse = false;
|
||||||
|
const { director, dataManager, restore } = makeDirector({
|
||||||
|
data: { refuse: () => refuse },
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await director.loadShot('scene-1', 'shot-a', { flyDuration: 0 });
|
||||||
|
await dataManager.setEnabled('flights', false);
|
||||||
|
refuse = true;
|
||||||
|
|
||||||
|
assert.equal(await director.seekScene('scene-1', 0.25), false);
|
||||||
|
assert.equal(dataManager.getAll().find(({ id }) => id === 'flights').enabled, false);
|
||||||
|
assert.equal(director.getPlaybackTimingState().activeTimers, 0);
|
||||||
|
} finally {
|
||||||
|
await director.destroy();
|
||||||
|
restore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('same-shot seeking keeps the shortcut when declared visibility is unchanged', async () => {
|
||||||
|
const { director, dataManager, styleManager, restore } = makeDirector();
|
||||||
|
try {
|
||||||
|
await director.loadShot('scene-1', 'shot-a', { flyDuration: 0 });
|
||||||
|
await dataManager.setEnabled('satellites', true);
|
||||||
|
dataManager.setEnabledCalls.length = 0;
|
||||||
|
const visualCalls = styleManager.visualCalls.length;
|
||||||
|
|
||||||
|
assert.equal(await director.seekScene('scene-1', 0.25), true);
|
||||||
|
assert.deepEqual(dataManager.setEnabledCalls, []);
|
||||||
|
assert.equal(styleManager.visualCalls.length, visualCalls);
|
||||||
|
assert.equal(director.getPlaybackTimingState().snapshot.sceneProgress, 0.25);
|
||||||
|
} finally {
|
||||||
|
await director.destroy();
|
||||||
|
restore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('scene clock subscribers receive authoritative forward playback snapshots', () => {
|
test('scene clock subscribers receive authoritative forward playback snapshots', () => {
|
||||||
const { director, restore } = makeDirector();
|
const { director, restore } = makeDirector();
|
||||||
const snapshots = [];
|
const snapshots = [];
|
||||||
|
|||||||
Reference in new issue
Block a user