From 9252ce92d432c7eac63f70f6b1282bc7ecc6f730 Mon Sep 17 00:00:00 2001 From: arcattack Date: Sat, 18 Jul 2026 19:45:47 -0500 Subject: [PATCH] Back-to-back missions: relay re-arms on rejoin (no Stop/Start needed) Field report: after a mission timed out normally, relaunching a mission did nothing. The relay was single-mission -- once it fired a launch (launches_sent==2) it never re-armed. Fix (operator-side only; the BINARY needs nothing -- a rejoined pod is a brand-new process at WaitingForLaunch): - btconsole _check_launch_gate: when a previous mission finished (launches_sent==2) and ALL seats have RE-ACKED (every pod exited, re-ran join.bat, reconnected), reset the launch state and re-print 'WAITING FOR OPERATOR LAUNCH'. Only reachable on a pod ACK (never mid-mission). Launch is CONSUMED after RunMission #2 (launch_at=None, launch_requested=False) so the next press is a deliberate new round. - btoperator SessionMonitor: reset on each new 'WAITING FOR OPERATOR' so the LAUNCH button RE-ENABLES for mission 2 (it stayed disabled forever before -- the other half of the bug). Verified e2e: two full missions on ONE relay session -- mission 1 runs + times out on the clock, pods rejoin, relay re-arms + re-announces ready, operator LAUNCH -> mission 2's RunMission #1/#2 fire (total fires 2) -> pods running. BACK-TO-BACK PASS. Friends' zip unchanged. Co-Authored-By: Claude Fable 5 --- tools/btconsole.py | 25 +++++++++++++++++++++++++ tools/btoperator.py | 5 +++++ 2 files changed, 30 insertions(+) diff --git a/tools/btconsole.py b/tools/btconsole.py index 0ee91bc..323b57f 100644 --- a/tools/btconsole.py +++ b/tools/btconsole.py @@ -414,6 +414,26 @@ class Relay: return sum(1 for c in self.console_conns if c.acked) def _check_launch_gate(self): + # BACK-TO-BACK MISSIONS (2026-07-18): a previous mission finished + # (launches_sent==2) and now all seats have RE-ACKED -- i.e. every pod + # exited, re-ran join.bat, and reconnected fresh. Reset the launch + # state so the relay (and the operator's LAUNCH button) re-arm for a + # new round -- no Stop/Start needed. The binary needs nothing: a + # rejoined pod is a brand-new process at WaitingForLaunch. (Only ever + # reached on a pod ACK, which never happens mid-mission.) + if self.launches_sent >= 2 and self._pods_ready() >= len(self.roster): + print("[relay] all seats rejoined after the last mission -- " + "re-arming for a NEW mission", flush=True) + self.launches_sent = 0 + self.stop_sent = False + self.stop_requested = False + self.mission_started_at = None + self.launch_at = None + self.launch_requested = False + self._launch_blocked_warned = False + self.eggs_done_at = None + # fall through -> the normal gate re-prints WAITING FOR OPERATOR + # GLOBAL launch: arm the timer when the LAST pod has ACKED its egg # (auto mode), or wait for the operator's 'launch' command (manual # mode -- the operator app's Launch button writes it to our stdin). @@ -522,6 +542,11 @@ class Relay: f"{len(self.console_conns)} pod(s)", flush=True) if self.launches_sent == 2: self.mission_started_at = time.time() + # CONSUME the launch: clear the timer + the request so the NEXT + # LAUNCH press is a fresh, deliberate new-mission request (else the + # back-to-back block would re-fire while this mission is running). + self.launch_at = None + self.launch_requested = False if self.mission_length > 0: print(f"[relay] mission clock: {self.mission_length:.0f}s " "(StopMission at expiry)", flush=True) diff --git a/tools/btoperator.py b/tools/btoperator.py index d4db344..6dfe97b 100644 --- a/tools/btoperator.py +++ b/tools/btoperator.py @@ -85,7 +85,12 @@ class SessionMonitor: self.eggs = int(m.group(1)) changed = True if self.RE_RELAY_READY.search(line): + # WAITING FOR OPERATOR = pods are ready to launch. Reset + # `launched` so the LAUNCH button re-enables -- this fires + # again for a NEW mission after the pods rejoin (back-to-back + # missions, 2026-07-18), not only the first time. self.ready = True + self.launched = False changed = True m = self.RE_RELAY_REG.search(line) if m: