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 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-18 19:45:47 -05:00
co-authored by Claude Fable 5
parent 0b83d71578
commit 9252ce92d4
2 changed files with 30 additions and 0 deletions
+25
View File
@@ -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)
+5
View File
@@ -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: