Diagnostics: pre-launch readiness warning + named friend logs (join.log)

Two additions after the live-session troubleshooting:

1. LAUNCH-SHORT is now VISIBLE (btconsole).  Pressing LAUNCH with an
   empty roster seat previously did NOTHING (eggs_done_at only sets when
   ALL seats ACK, so the manual-launch arming silently no-op'd -- the
   'I pressed launch and nothing happened' confusion).  Now the relay
   logs 'LAUNCH pressed but NOT all seats are filled' + a per-seat
   readiness line + a WARNING naming the empty seat(s) and the fix
   (reduce the roster).  When all seats DO fill it auto-arms (launch
   request persists).  Verified: 2/3 filled -> clear warning naming
   seat3.

2. Friend logs are now retrievable (btoperator exporter).  join.bat /
   join_lan.bat set BT_LOG=join.log and their exit message tells the
   player to send content\join.log to the operator if the game closed
   unexpectedly -- closes the friend-side-crash blind spot (their log
   was the generic btl4.log, un-named, easy to miss).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-18 19:05:34 -05:00
co-authored by Claude Fable 5
parent 9f1bd8d6d4
commit 954175e44e
4 changed files with 52 additions and 16 deletions
+5 -3
View File
@@ -2,10 +2,12 @@
rem BT411 -- join 107.202.218.169's game (internet). Your seat/mech
rem is assigned automatically by the operator console.
set BT_RELAY=107.202.218.169:1500
set BT_LOG=join.log
cd /d %~dp0content
..\build\Release\btl4.exe -egg OPERATOR.EGG -net 1501
echo.
echo The game has exited. If you did NOT quit on purpose,
echo the session may not be running yet or the server was
echo unreachable -- check with the operator and try again.
echo The game has exited. If you did NOT quit on purpose
echo (crash, or it never connected), send the operator the
echo file content\join.log so they can see what happened.
echo Otherwise the session may not be up yet -- try again.
pause
+5 -3
View File
@@ -3,10 +3,12 @@ rem BT411 -- join the game from the operator's OWN
rem network (LAN) -- finds the console automatically.
rem Joining over the INTERNET? Use join.bat instead.
set BT_RELAY=auto
set BT_LOG=join.log
cd /d %~dp0content
..\build\Release\btl4.exe -egg OPERATOR.EGG -net 1501
echo.
echo The game has exited. If you did NOT quit on purpose,
echo the session may not be running yet or the server was
echo unreachable -- check with the operator and try again.
echo The game has exited. If you did NOT quit on purpose
echo (crash, or it never connected), send the operator the
echo file content\join.log so they can see what happened.
echo Otherwise the session may not be up yet -- try again.
pause
+36 -7
View File
@@ -284,6 +284,7 @@ class Relay:
self.manual_launch = manual_launch
self._reserved_tags = set(reserved_tags or ())
self.launch_requested = False # set by the operator (stdin)
self._launch_blocked_warned = False # warned once about empty seats
self.stop_requested = False # operator 'stop' (End Mission)
self.mission_length = parse_egg_mission_length(egg_path)
self.mission_started_at = None # set when RunMission #2 fires
@@ -463,18 +464,46 @@ class Relay:
if conn in self.console_conns:
self.console_conns.remove(conn)
def _log_launch_readiness(self):
# PRE-LAUNCH READINESS (2026-07-18): make the "launching short" footgun
# VISIBLE. A roster seat with no registered pod means the mission will
# STALL -- every pod waits on that empty seat's connection and the
# All-connections-completed gate never fires. Log filled vs empty
# seats so the operator sees it BEFORE the confusion.
filled, empty = [], []
for i, tag in enumerate(self.roster):
host_id = FIRST_GAME_HOST_ID + i
(filled if host_id in self.by_host else empty).append(
f"seat{i + 1}({tag})")
print(f"[relay] LAUNCH readiness: {len(filled)}/{len(self.roster)} "
f"seats filled -- {', '.join(filled) or 'NONE'}", flush=True)
if empty:
print(f"[relay] *** WARNING: {len(empty)} EMPTY seat(s): "
f"{', '.join(empty)} -- the mission will STALL (every pod "
f"waits on the empty seat). Stop, reduce the roster to the "
f"players present, and re-launch.", flush=True)
def _tick_launch(self):
# Manual mode: the operator's 'launch' arms the timer, still honouring
# the settle window (pods must reach WaitingForLaunch or the handler
# Fail()s -- the same reason the auto timer waits).
if (self.manual_launch and self.launch_requested
and self.launch_at is None and self.eggs_done_at is not None
and self.launches_sent == 0):
self.launch_at = max(time.time(),
self.eggs_done_at + LAUNCH_SETTLE_SECONDS)
wait = max(0.0, self.launch_at - time.time())
print(f"[relay] operator LAUNCH received; firing in {wait:.0f}s",
flush=True)
and self.launch_at is None and self.launches_sent == 0):
if self.eggs_done_at is not None:
self.launch_at = max(time.time(),
self.eggs_done_at + LAUNCH_SETTLE_SECONDS)
wait = max(0.0, self.launch_at - time.time())
print(f"[relay] operator LAUNCH received; firing in "
f"{wait:.0f}s", flush=True)
self._log_launch_readiness()
elif not self._launch_blocked_warned:
# Operator pressed LAUNCH but the gate is NOT ready (not every
# roster seat has ACKed its egg) -- previously this did NOTHING
# visible ("I pressed launch and nothing happened"). Say WHY.
self._launch_blocked_warned = True
print("[relay] LAUNCH pressed but NOT all seats are filled:",
flush=True)
self._log_launch_readiness()
if self.launch_at is None or self.launches_sent >= 2:
return
if time.time() < self.launch_at:
+6 -3
View File
@@ -756,15 +756,17 @@ class Operator(QMainWindow):
# file -- who flies which mech is whoever sits down first, exactly
# like walking up to a pod.
tail = ("echo.\n"
"echo The game has exited. If you did NOT quit on purpose,\n"
"echo the session may not be running yet or the server was\n"
"echo unreachable -- check with the operator and try again.\n"
"echo The game has exited. If you did NOT quit on purpose\n"
"echo (crash, or it never connected), send the operator the\n"
"echo file content\\join.log so they can see what happened.\n"
"echo Otherwise the session may not be up yet -- try again.\n"
"pause\n")
with open(os.path.join(out_dir, "join.bat"), "w", newline="\r\n") as f:
f.write("@echo off\n"
"rem BT411 -- join %s's game (internet). Your seat/mech\n"
"rem is assigned automatically by the operator console.\n"
"set BT_RELAY=%s:%d\n"
"set BT_LOG=join.log\n"
"cd /d %%~dp0content\n"
"..\\build\\Release\\btl4.exe -egg %s -net 1501\n"
% (host, host, self.f_port.value(), egg_name) + tail)
@@ -775,6 +777,7 @@ class Operator(QMainWindow):
"rem network (LAN) -- finds the console automatically.\n"
"rem Joining over the INTERNET? Use join.bat instead.\n"
"set BT_RELAY=auto\n"
"set BT_LOG=join.log\n"
"cd /d %%~dp0content\n"
"..\\build\\Release\\btl4.exe -egg %s -net 1501\n"
% egg_name + tail)