Keep a restored tracked aircraft when Cesium starts or resumes instead of treating it as new navigation

This commit is contained in:
German Khokhlov committed 2026-11-03 22:56:24 +04:00
1 parent 59a2e31aa8
commit 01ae045346
4 files changed
+73 -3

No files matched your search

+2 -2
View File
@@ -114,10 +114,10 @@ test('voice Cockpit entry reaches the camera only through stamping seams', () =>
assert.doesNotMatch(cockpitTracking, /trackedEntity\s*=/);
assert.doesNotMatch(cockpitTracking, /flyTo/);
// Seam 1: any tracker handoff stamps, so the adoption step is covered.
// Seam 1: a new tracker handoff stamps; restoring the session does not.
assert.match(
ui,
/viewer\.trackedEntityChanged\.addEventListener\(\s*\(entity\) => \{\s*if \(entity && !this\._disposed\)\s*this\._stampNavigation\(\{\s*cancelPendingSelection: false,?\s*\}\);/,
/viewer\.trackedEntityChanged\.addEventListener\(\s*\(entity\) => \{[\s\S]*?if \(\s*entity &&\s*!this\._disposed &&\s*entity\.gevSelectionOrigin !== 'share-restore'\s*\)\s*this\._stampNavigation\(\{\s*cancelPendingSelection: false,?\s*\}\);/,
'tracker handoff must stamp',
);
// Seam 2 is pinned by "Cockpit takeover invalidates deferred work" above.
+1
View File
@@ -58,6 +58,7 @@ export function createAircraftSceneBinding({
parts.snapshots.presentRecords();
if (flightState._trackedIcao) {
parts.tracking._trackFlight(flightState._trackedIcao, {
origin: 'share-restore',
resume: true,
follow: resumeFollow,
});
+63
View File
@@ -0,0 +1,63 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { createAircraftSceneBinding } from './sceneBinding.js';
import { LayerBindings } from '../../ui/layerBindings.js';
import { createEngineEvent } from '../../globe/contract.js';
test('resuming an aircraft on Cesium preserves the pending share navigation', (t) => {
const previous = globalThis.window;
globalThis.window = new EventTarget();
t.after(() => {
globalThis.window = previous;
});
const trackedEntityChanged = createEngineEvent();
let generation = 1;
const shell = new LayerBindings({
viewer: { trackedEntityChanged },
services: {},
operations: {
_stampNavigation() {
generation++;
},
},
});
shell.observeCamera();
t.after(() => shell.stop());
const renderer = {
fleet: { attach() {}, setVisible() {}, restoreOrder() {} },
models: { setVisible() {} },
};
let restored;
const binding = createAircraftSceneBinding({
id: 'flights',
flightState: {
initialized: true,
_trackedIcao: 'a00000',
_preloadModel: {},
},
services: { renderer: { create: () => renderer } },
parts: {
snapshots: { presentRecords() {} },
tracking: {
_trackFlight(id, options) {
restored = { id, ...options };
trackedEntityChanged.raiseEvent({
gevSelectionOrigin: options.origin,
});
},
},
},
});
binding.attachRenderer({});
assert.equal(restored.id, 'a00000');
assert.equal(restored.resume, true);
assert.equal(
generation,
1,
'restored tracking cannot supersede and clear itself',
);
for (const origin of ['user', 'voice', 'programmatic']) {
trackedEntityChanged.raiseEvent({ gevSelectionOrigin: origin });
}
assert.equal(generation, 4, 'new tracking still takes navigation authority');
});
+7 -1
View File
@@ -86,7 +86,13 @@ export class LayerBindings {
if (this.viewer)
this._navigationOwnerChangedRemover =
this.viewer.trackedEntityChanged.addEventListener((entity) => {
if (entity && !this._disposed)
// Rebinding the session's aircraft is part of the pending share
// restoration, not a new navigation that supersedes that restore.
if (
entity &&
!this._disposed &&
entity.gevSelectionOrigin !== 'share-restore'
)
this._stampNavigation({ cancelPendingSelection: false });
});
// Vessel/installation focus flies without ever assigning a tracked entity,