From 5c25bc54e6d03146e8ceaa39e67f8bffa3c8c6df Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Sun, 8 Nov 2026 15:42:16 -0800 Subject: [PATCH] fix(cockpit): voice entry in an orbital view is refused before it touches tracking --- docs/CURRENT-STATE.md | 4 ++-- src/ui/cockpitCoordinator.js | 6 ++++++ src/ui/cockpitEntryGate.test.mjs | 20 ++++++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index 653ba1d99..c0d8eec4c 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -1538,8 +1538,8 @@ HUD on, in its Tactical and Cyber layouts). Turning Live AIS Vessels off ends the bridge: the layer then has no subject (`getCockpitSubject` returns null). The bridge picture uses AIS alone, so no other feed being off makes it partial. Voice (`control_cockpit`) and the selection entry transaction stay aircraft-only; -voice `enter` on a bridge is refused before it touches tracking, with the -reason to exit the bridge first. +voice `enter` on a bridge or in an orbital view is refused before it touches +tracking, with the reason to exit it first. The vessel layer supplies the subject (`getCockpitSubject`), hides that ship's sprite, card, detection bracket and trail while it is held (`setCockpitSubject`), and diff --git a/src/ui/cockpitCoordinator.js b/src/ui/cockpitCoordinator.js index 686e5f16a..60a3259f9 100644 --- a/src/ui/cockpitCoordinator.js +++ b/src/ui/cockpitCoordinator.js @@ -189,6 +189,12 @@ export class CockpitCoordinator { // vessel selection before the entry can run, leaving neither. if (this.cockpitView?.active && this.cockpitView.subjectKind === 'vessel') return 'Cockpit is on a vessel bridge — exit the bridge first, then track an aircraft to enter Cockpit'; + // Likewise in orbit: the aircraft's tracking takes the satellite's. + if ( + this.cockpitView?.active && + this.cockpitView.subjectKind === 'satellite' + ) + return 'Cockpit is in an orbital view — exit it first, then track an aircraft to enter Cockpit'; if (!this._aircraftLayerEnabled) return 'No aircraft layer is on — turn on Flights or Military Flights and track an aircraft to enter Cockpit'; return null; diff --git a/src/ui/cockpitEntryGate.test.mjs b/src/ui/cockpitEntryGate.test.mjs index ec144128b..fd5dfb52b 100644 --- a/src/ui/cockpitEntryGate.test.mjs +++ b/src/ui/cockpitEntryGate.test.mjs @@ -246,6 +246,26 @@ test('voice entry on a vessel bridge refuses before it touches tracking', (t) => assert.equal(cockpit.subjectKind, 'vessel'); }); +test('voice entry in an orbital view refuses before it touches tracking', (t) => { + const { coordinator, cockpit } = fixture(t, { enabled: ['flights'] }); + let navigated = false; + coordinator.services.militaryAwarenessLayer.navigateNext = () => { + navigated = true; + return true; + }; + cockpit.active = true; + cockpit.subjectKind = 'satellite'; + t.after(() => { + cockpit.active = false; + cockpit.subjectKind = null; + }); + const voice = coordinator.controlCockpit('enter', { targetLayer: 'flights' }); + assert.equal(voice.ok, false); + assert.match(voice.error, /orbital view/); + assert.equal(navigated, false, 'no aircraft was tracked'); + assert.equal(cockpit.subjectKind, 'satellite'); +}); + test('with an aircraft layer on but nothing tracked, the reason is the missing aircraft', (t) => { const { coordinator } = fixture(t, { enabled: ['military'] }); assert.equal(