diff --git a/players/join.bat b/players/join.bat index c18bed1..9bf4cf5 100644 --- a/players/join.bat +++ b/players/join.bat @@ -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 diff --git a/players/join_lan.bat b/players/join_lan.bat index 16f2c84..5bff005 100644 --- a/players/join_lan.bat +++ b/players/join_lan.bat @@ -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 diff --git a/tools/btconsole.py b/tools/btconsole.py index 43b289d..0ee91bc 100644 --- a/tools/btconsole.py +++ b/tools/btconsole.py @@ -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: diff --git a/tools/btoperator.py b/tools/btoperator.py index d7a248c..f9ad83f 100644 --- a/tools/btoperator.py +++ b/tools/btoperator.py @@ -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)