From ecb9b338bd2f02880f0f537b672ecf927b299c42 Mon Sep 17 00:00:00 2001 From: arcattack Date: Mon, 27 Jul 2026 00:38:05 -0500 Subject: [PATCH] the X button means QUIT: a user-closed window exits for real instead of relaunching Operator report (00:30): "my local instance auto-relaunches whenever I stop and start a session, even if I close my window." Root cause is the pod's AUTHENTIC immortality: an arcade cabinet's join loop never exits, so every RunMissions return relaunches into the join wait -- and a human closing the window was indistinguishable from a round ending. On a desktop that made the client unkillable outside Task Manager, and explains every orphaned-plasma zombie hunted this week (my own test teardowns included), plus tonight's "eventually all I could find was my plasma display". FIX, one flag + one gate: * btl4main WndProc WM_CLOSE stamps gBTUserRequestedExit before DestroyWindow (covers X, Alt-F4, taskbar close, Task Manager's polite phase). * BTFE_RelaunchSelfAndExit -- the single choke point every relaunch path funnels through (round end, console-pad lost, round abort, FE loop) -- exits for real when the flag is set, logging 'window closed by the user -- exiting for real (no relaunch)' to the marshal log. Round-end auto-rejoin is UNTOUCHED: the flag only sets on WM_CLOSE. VERIFIED both directions on the built exe (4.11.591): * WM_CLOSE posted to a live solo instance -> the whole process tree is GONE 8s later: no relaunched generation, no plasma orphan. [was: immortal] * Offset-port rig, full launch -> StopMission -> both pods AUTO-REJOINED and re-ACKed for the next round (2/2 ready, WAITING FOR OPERATOR); zero exiting-for-real lines fired (nobody closed a window). Requires the next zip for players; the operator's running instance keeps the old behavior until restarted onto this build. Co-Authored-By: Claude Opus 5 (1M context) --- game/btl4main.cpp | 11 +++++++++++ game/glass/btl4console.cpp | 22 ++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/game/btl4main.cpp b/game/btl4main.cpp index a83f599..19409d3 100644 --- a/game/btl4main.cpp +++ b/game/btl4main.cpp @@ -97,6 +97,11 @@ struct BTDriveInput { float throttle; float turn; int forced; int fire; int fire int keyFwd; int keyBack; int keyLeft; int keyRight; int allStop; }; BTDriveInput gBTDrive = { 0.0f, 0.0f, 0, 0, 0, 1.0f, 0, 0, 0, 0, 0 }; +// The human clicked X (WM_CLOSE). Read by BTFE_RelaunchSelfAndExit: a +// user-closed window exits for REAL instead of relaunching into the join +// wait (the pod's authentic immortality, wrong for a desktop window). +volatile int gBTUserRequestedExit = 0; + LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) @@ -157,6 +162,12 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) } return 0; case WM_CLOSE: + // The human clicked X / pressed Alt-F4. Without this stamp the + // relaunch chain treats the resulting RunMissions return like a + // round ending and RESURRECTS the client (the pod was immortal by + // design; a desktop window must not be) -- see the gate in + // BTFE_RelaunchSelfAndExit. + gBTUserRequestedExit = 1; DestroyWindow(hWnd); return 0; case WM_DESTROY: diff --git a/game/glass/btl4console.cpp b/game/glass/btl4console.cpp index 6dff2a3..9c2ed68 100644 --- a/game/glass/btl4console.cpp +++ b/game/glass/btl4console.cpp @@ -137,6 +137,28 @@ static unsigned long void BTFE_RelaunchSelfAndExit(const char *arguments) { + // + // THE X BUTTON MEANS QUIT (2026-07-27, operator report: "my local + // instance auto-relaunches whenever I stop and start a session, even if + // I close my window"). The 1995 pod was IMMORTAL by design -- an arcade + // cabinet's join loop never exits -- so every RunMissions return + // relaunches, and a human closing the window was indistinguishable from + // a round ending. On a desktop that made the client unkillable outside + // Task Manager (the orphaned-plasma zombies). btl4main's WM_CLOSE now + // stamps gBTUserRequestedExit; every relaunch path funnels through here, + // so this single gate turns a user-closed window into a REAL exit while + // round-end / console-loss / abort relaunches stay exactly as they were. + // + { + extern volatile int gBTUserRequestedExit; + if (gBTUserRequestedExit) + { + MarshalLog("window closed by the user -- exiting for real " + "(no relaunch)"); + ExitProcess(0); + } + } + // // RELAUNCH-STORM DAMPER (issue #33, night-2 capture): a client whose // generations die young (stale identity -> HELLO reject -> drop ->