From ba5c1c5d2576d562d4f44808eed403b48be3b634 Mon Sep 17 00:00:00 2001 From: legop3 Date: Sat, 12 Sep 2026 18:58:32 -0400 Subject: [PATCH] dont stop assignments for help rovers, just allow people to leave them. --- .../services/roverManager/roverLifecycle.js | 5 +++ .../roverManager/roverLifecycle.test.js | 41 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/server/src/services/roverManager/roverLifecycle.js b/server/src/services/roverManager/roverLifecycle.js index 31cbe056..59fe9608 100644 --- a/server/src/services/roverManager/roverLifecycle.js +++ b/server/src/services/roverManager/roverLifecycle.js @@ -184,6 +184,11 @@ function createRoverLifecycle(deps) { const currentRecord = rovers.get(currentId); if (!currentRecord) return { ok: true, currentId }; if (hasOtherDrivers(currentRecord, socket.id)) return { ok: true, currentId }; + // HELP means the normal dock-before-leaving requirement has failed to + // resolve the rover's situation and a person may need to take a different + // rover instead. This exception changes departure only; request eligibility + // and automatic assignment ranking remain owned by their existing paths. + if (currentRecord.needsHelp) return { ok: true, currentId }; if (isDockedAndCharging(currentRecord)) return { ok: true, currentId }; return { ok: false, currentId, message: 'Dock and charge your current rover before switching.' }; } diff --git a/server/src/services/roverManager/roverLifecycle.test.js b/server/src/services/roverManager/roverLifecycle.test.js index a4fa379a..c7564378 100644 --- a/server/src/services/roverManager/roverLifecycle.test.js +++ b/server/src/services/roverManager/roverLifecycle.test.js @@ -57,3 +57,44 @@ test('removing a rover clears driver sets, reverse membership, rooms, and turns' { socketId, roverId, action: 'remove' }, ]); }); + +test('the last driver may leave an undocked HELP rover but not an ordinary undocked rover', () => { + const roverId = 'rover-help'; + const socketId = 'driver-help'; + const socket = { id: socketId }; + const record = { + id: roverId, + drivers: new Set([socketId]), + needsHelp: false, + // A present but explicitly non-charging frame exercises the real policy + // boundary instead of accidentally passing through missing rover state. + lastSensor: { + decoded: { + chargingSources: { homeBase: false }, + chargingState: { code: 0 }, + }, + }, + }; + const lifecycle = createRoverLifecycle({ + io: { sockets: { sockets: new Map() } }, + rovers: new Map([[roverId, record]]), + socketToRovers: new Map([[socketId, new Set([roverId])]]), + managerEvents: new EventEmitter(), + turnService: {}, + isAdmin: () => false, + sendAlert: () => {}, + ALERT_COLOR: '#000000', + getMode: () => 'public', + getControlDenialReason: () => null, + }); + + const ordinaryResult = lifecycle.canLeaveCurrentRover(socket); + assert.equal(ordinaryResult.ok, false); + assert.equal(ordinaryResult.message, 'Dock and charge your current rover before switching.'); + + // Mutating only the server-owned HELP flag proves that no docking, driver, + // role, or assignment condition is being weakened as part of the exception. + record.needsHelp = true; + const helpResult = lifecycle.canLeaveCurrentRover(socket); + assert.deepEqual(helpResult, { ok: true, currentId: roverId }); +});