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>
111 lines
5.6 KiB
Markdown
111 lines
5.6 KiB
Markdown
# 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**:
|
|
|
|
```bat
|
|
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:
|
|
|
|
```c
|
|
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.
|