From 36a22165280e8452a04b4cfbbe73c3d6e3fe7ae0 Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Sun, 8 Nov 2026 13:17:02 -0800 Subject: [PATCH] fix(cockpit): voice refuses entry while on a vessel bridge Voice entry tracked an aircraft first, which cleared the vessel selection and ended the bridge, and then failed because Cockpit was still active: no bridge, no cockpit, no selected ship, and voice said entry was unavailable. It now refuses before it touches tracking and says to exit the bridge first. --- src/ui/cockpitCoordinator.js | 4 ++++ src/ui/cockpitEntryGate.test.mjs | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/ui/cockpitCoordinator.js b/src/ui/cockpitCoordinator.js index 9a0eb258a..51f81b965 100644 --- a/src/ui/cockpitCoordinator.js +++ b/src/ui/cockpitCoordinator.js @@ -172,6 +172,10 @@ export class CockpitCoordinator { _cockpitEntryRefusal() { if (this._contextModeChanging) return 'Context is still changing — try Cockpit again in a moment'; + // Tracking an aircraft from the bridge ends the bridge and clears the + // 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'; 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 d7e8fd226..adf3d916d 100644 --- a/src/ui/cockpitEntryGate.test.mjs +++ b/src/ui/cockpitEntryGate.test.mjs @@ -199,6 +199,27 @@ test('with no aircraft layer on, entry is refused for that reason, never for Con assert.equal(cockpit.active, false); }); +test('voice entry on a vessel bridge refuses before it touches tracking', (t) => { + const { coordinator, cockpit } = fixture(t, { enabled: ['flights'] }); + let navigated = false; + coordinator.services.militaryAwarenessLayer.navigateNext = () => { + navigated = true; + return true; + }; + // Stand in for a bridge ride: the refusal reads only these two. + cockpit.active = true; + cockpit.subjectKind = 'vessel'; + t.after(() => { + cockpit.active = false; + cockpit.subjectKind = null; + }); + const voice = coordinator.controlCockpit('enter', { targetLayer: 'flights' }); + assert.equal(voice.ok, false); + assert.match(voice.error, /vessel bridge/); + assert.equal(navigated, false, 'no aircraft was tracked'); + assert.equal(cockpit.subjectKind, 'vessel'); +}); + test('with an aircraft layer on but nothing tracked, the reason is the missing aircraft', (t) => { const { coordinator } = fixture(t, { enabled: ['military'] }); assert.equal(