Files
BT411/phases/phase-12-orphan-processes.md
T
arcattackandClaude Opus 5 7afbda900e the terminal that never closes: every launcher waited for ANY btl4.exe on the machine
Operator report: "X-closing the game leaves the terminal open and leaves
orphaned processes."  Investigated on the rig against 4.11.600.

The terminal half is real and is THIS: :btwait polled
`tasklist /FI "IMAGENAME eq btl4.exe"`, which is machine-wide, so a bat that
launched nothing at all keeps spinning while an unrelated instance lives --
proved with btwait_probe.ps1.  A second client, the operator's own pod, or an
orphan from a crash therefore hangs every join window, which reads as "the game
never exited" and invites people to start killing processes.  All four
launchers carried the identical block.

Fix: snapshot the btl4 PIDs alive BEFORE the launch; wait only on PIDs absent
from that snapshot.  The `if /I "%%P"=="btl4.exe"` guard is deliberately kept --
tokens=2 alone parses tasklist's "INFO: No tasks are running" line as a PID and
spins forever with nothing running, which would be worse than the bug.

Verified with the text lifted verbatim from the shipped play_solo.bat: nothing
running -> signs off (the regression guard); someone else's instance -> signs
off; our own generation -> keeps waiting; decoy gone -> signs off.  The patched
bat still launches (pid + launch_report.txt).  NOT verified: the full handoff
E2E, because the bat blocks on the FE menu waiting for a human.

The orphan half did NOT reproduce on 600: closing the MAIN window exits cleanly
in ~1s during solo model-load, in the relay join wait, and after a real console
launch, with the relay logging the seat freed.  The orphans the playtesters saw
match 584 and earlier, where every close relaunched.  Full write-up, including
the aux windows that hide instead of closing and the WM_QUIT that BTLoadPump
swallows, in phases/phase-12-orphan-processes.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 16:06:39 -05:00

5.6 KiB

Phase 12 — orphaned processes, the terminal that never closes, and the MP-hiccup claim

2026-07-27. Triggered by an operator report: "if you X close the game window it leaves the terminal/console open sometimes, and leaves orphaned processes — I think this is a major contributor to multiplayer game hiccups."

All findings below were produced against 4.11.600 on the dev rig, not recalled. Harnesses live in scratchpad/orphan_*.ps1, scratchpad/btwait_probe.ps1, scratchpad/test_loop_logic.ps1.


What the process actually owns

A running pod owns four top-level windows (enumerated live, orphan_win.ps1):

class title size X does
MainWndClass BattleTech 4.11.600 — node 1601 1468x1038 destroy → real exit
BTPlasmaWnd BattleTech - Plasma 528x167 hide only (L4PLASMAWIN.cpp:21)
MSCTFIME UI / IME 0x0 OS input plumbing

Under BT_GLASS_PANELS the per-display panels (L4GLASSWIN.cpp:693) and the pad panel (L4PADPANEL.cpp:468) behave like the plasma window: ShowWindow(SW_HIDE), never quit.

Finding 1 — closing the MAIN window is CLEAN [T2]

Verified in three states: solo mid-model-load ([loadobj] active), the relay join wait, and 16 s after a real console launch. Every run: process exited ~1 s, no relaunch, no orphan, and the relay logged PLAYER 1 LEFT ... (0 seated). The close-means-quit gate (ecb9b33) works.

So the orphans the playtesters saw are consistent with 584 and earlier, where every window close hit BTFE_RelaunchSelfAndExit and resurrected the client. That is already fixed in 600 and still awaiting-verification.

Finding 2 — closing an AUX window hides it irrecoverably [T2]

X on the plasma window (or a glass panel) hides it with no way to bring it back — no menu item, no hotkey — while the game runs on. Verified: vis=True → vis=False, process alive, both relay sockets still Established. Not an orphan (the main window is still there), but it explains the report "eventually all I could find was my plasma display": the small plasma window is what survives when the big one is gone, and a player who closes it has silently lost it for the session.

Finding 3 — the terminal that never closes [T2] — FIXED

join.bat's :btwait loop waited for any btl4.exe on the machine:

for /f "tokens=1" %%P in ('tasklist /FI "IMAGENAME eq btl4.exe" /NH') do (
    if /I "%%P"=="btl4.exe" set "BT_STILL_RUNNING=1"
)

Proved with btwait_probe.ps1: a bat that launched nothing reports would KEEP SPINNING purely because an unrelated instance is alive. So the window never prints its sign-off and looks hung whenever anything else is running — a second client, the operator's own pod, or a genuine orphan. This is the reported symptom, and it is a detector of an orphan, not a cause.

All four launchers carried the identical block. Fixed by snapshotting the PIDs alive before the launch and only waiting on PIDs absent from that snapshot. The if /I "%%P"=="btl4.exe" guard is retained deliberately: tokens=2 alone would parse tasklist's "INFO: No tasks are running" line as a PID and spin forever with nothing running — a worse bug than the one being fixed.

Verified with the text lifted verbatim out of the shipped play_solo.bat:

case expected got
nothing running (regression guard) sign off sign off
someone else's instance alive sign off sign off
our own generation alive keep waiting keep waiting
decoy gone, re-check sign off sign off

The patched bat still launches the game (pid + launch_report.txt confirmed). Not verified: the full handoff E2E — the bat blocks on the FE menu, which waits for human input, so an automated run never reaches :btwait. Needs one human run.

Note the bat also ends in pause by design, so the window always stays until a keypress. That is intended and is a second, benign reason a terminal is "still open".

Finding 4 — BTLoadPump eats WM_QUIT [T3, latent]

L4VIDEO.cpp:9039 drains the queue during mission load:

while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessageA(&msg); }

WM_QUIT dispatched this way is silently discarded — it belongs to no window — so a quit posted during load is destroyed and APPMGR.cpp:102's pump never sees it. This did not produce a hang in testing (the destroyed HWND breaks rendering, which tears the process down another way), so it is not today's orphan mechanism. Flagged as a trap: any future code that relies on WM_QUIT surviving a load will fail here.

The multiplayer-hiccup claim — verdict

The mechanism is real conditional on an orphan existing: an orphan keeps both TCP connections to the relay (:1600 console and :1601 game) Established, so the relay counts a pilot who will never ready — and SEAT_LEFT_GRACE_SECONDS never starts, because nothing ever left. That matches "waiting for mission assignment" forever.

But no orphan could be produced from an X-close in 4.11.600. So on the current build the more likely contributor is Finding 3: a second instance (the operator's own pod is enough) hangs every join window, which reads as "the game didn't exit", which invites people to kill processes and relaunch — and that is what manufactures real ghosts.

Follow-ups

  • Aux windows should either refuse to close or be restorable (Finding 2).
  • Consider making BTLoadPump re-post WM_QUIT instead of swallowing it (Finding 4).
  • The :btwait fix reaches players only when the zip is re-cut; 4.11.600 as shipped still has the machine-wide wait.