From e007350645c0c1acd2159215d6cb1c7c7eca44dd Mon Sep 17 00:00:00 2001 From: German Khokhlov Date: Sat, 17 Oct 2026 03:59:51 +0400 Subject: [PATCH] fix(ui): explicit navigation releases flights when an engine leaves out military awareness On Navara the military-awareness module has no controls, so its releaseCameraOwnership was skipped silently and a city pill, voice fly_to_location or reset ended the follow but kept the aircraft selected. The trackers it would release are released directly instead, as on Cesium (selection and follow both cleared). --- src/ui/cameraOrientationControls.test.mjs | 33 +++++++++++++++++++++++ src/ui/locationNavigation.js | 4 +++ src/ui/navigationController.js | 4 +++ 3 files changed, 41 insertions(+) diff --git a/src/ui/cameraOrientationControls.test.mjs b/src/ui/cameraOrientationControls.test.mjs index 43ce28e91..1b3d3445d 100644 --- a/src/ui/cameraOrientationControls.test.mjs +++ b/src/ui/cameraOrientationControls.test.mjs @@ -811,3 +811,36 @@ test("north-up and tilt inside an engine's own follow keep following (Navara)", handle.stop(); engine.destroy(); }); + +test('a navigation releases the flights follow when military awareness has no controls (Navara)', () => { + const stopped = []; + const navigation = new NavigationController({ + viewer: null, + engine: createFakeEngine(), + tracking: { + flightsLayer: { + stopTracking: ({ origin }) => stopped.push(`flights:${origin}`), + }, + militaryFlightsLayer: { + stopTracking: ({ origin }) => stopped.push(`military:${origin}`), + }, + satellitesLayer: {}, + aisLiveVesselsLayer: { clearSelection: () => stopped.push('vessels') }, + // A layer this engine leaves out: its module, without the controls. + militaryAwarenessLayer: {}, + rocketLaunchesLayer: {}, + }, + isCockpitActive: () => false, + cancelOrientation() {}, + clearLocation() {}, + cancelShareSelection() {}, + interruptCameraMotion() {}, + stopOrbit() {}, + showToast() {}, + }); + assert.equal( + navigation._releaseFollowCamera({ preserveVesselSelection: false }), + false, + ); + assert.deepEqual(stopped, ['flights:tool', 'military:tool', 'vessels']); +}); diff --git a/src/ui/locationNavigation.js b/src/ui/locationNavigation.js index 342a57b20..ca521b71c 100644 --- a/src/ui/locationNavigation.js +++ b/src/ui/locationNavigation.js @@ -327,6 +327,10 @@ export class LocationNavigation { this._stopOrbit(); this.cockpitView?.exit({ restoreTracking: false }); try { + // A layer an engine leaves out (military awareness on Navara) has no + // controls: release the trackers it would release, below. + if (typeof militaryAwarenessLayer?.releaseCameraOwnership !== 'function') + throw new Error('no military awareness controls'); militaryAwarenessLayer.releaseCameraOwnership?.({ origin: 'tool' }); } catch { // Keep reset available if Context has not initialized completely. diff --git a/src/ui/navigationController.js b/src/ui/navigationController.js index 44c866840..8c164bc7f 100644 --- a/src/ui/navigationController.js +++ b/src/ui/navigationController.js @@ -145,6 +145,10 @@ export class NavigationController { } = this.tracking; let contactSelected = false; try { + // A layer an engine leaves out (military awareness on Navara) has no + // controls: release the trackers it would release, below. + if (typeof militaryAwarenessLayer?.releaseCameraOwnership !== 'function') + throw new Error('no military awareness controls'); contactSelected = Boolean( militaryAwarenessLayer.releaseCameraOwnership?.({ preserveVesselSelection,