A Windows launch is a hand-off, and 0.30 read its exit as the game's #288
Merged
enricobuehler
merged 1 commits from 2026-08-18 08:19:52 +00:00
worktree-win-game-launch-untracked into main
1
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6352ff629d |
fix(host/library): a Windows launch is a hand-off, and 0.30 read its exit as the game's
ci / web (pull_request) Successful in 1m8s
ci / docs-site (pull_request) Successful in 1m17s
ci / rust-arm64 (pull_request) Successful in 1m27s
ci / bun-nix (pull_request) Successful in 1m41s
android / android (pull_request) Successful in 12m3s
ci / rust (pull_request) Successful in 16m5s
A user launched a title on a 0.30 Windows host and watched the game come up while the stream dropped, with the console reporting nothing running. Two changes from 0.30 meet here, and the second turns the first into a disconnect. `LeaseRequest::spawned` (0.30) started carrying the pid `CreateProcessAsUserW` hands back, so a Windows launch could finally be a `LeaseKind::Child` instead of degrading to `Untracked`. But `windows_launch_for` builds a protocol hand-off for almost every kind — `explorer.exe "playnite://…"`, `Steam.exe "steam://…"`, `explorer.exe "shell:AppsFolder\…"` — so that pid is a forwarder's, not the game's, and it says nothing about the game in either direction: it quits a second after launch when the launcher was already running, and it *becomes* the launcher when it was not. The watcher's shim window exists for exactly this, and both callers of the pid path documented it as their protection — but it was skipped whenever the title had no detect signals, which is the one shape that needs it. So a hint-less title (a Playnite entry whose install dir Playnite does not know, a custom entry with no hint) reported `running` on its first poll because the forwarder was alive, and `exited` a second later when the forwarder quit: `finish` fired `on_exit`, which closes the connection with `APP_EXITED`. In 0.29 the same title was `Untracked`, nothing watched it, and the session stayed up — which is the "it used to just show as running" the report describes. The forwarder pid is not only a false liveness signal, it is a termination target: `windows_term_ladder` adds `shared.spawned` to the pids it `WM_CLOSE`s and then terminates. A Steam title launched while Steam was closed makes that pid the Steam *client*, so `POST /game/end`, a grace expiry or `game_on_session_end` would ask the player's whole Steam client to close rather than the game. * The Windows recipe table now says which lines start the game and which only forward it (`WinRecipe::owns_game`); a forwarder's pid is dropped rather than carried, so the lease falls back to detect signals exactly as it did before. Only `gog` (the resolved exe), `command` and a plugin's own recipe (`cmd.exe /c` blocks on them) own their game. * The shim window now applies to a bare child or pid whatever the spec holds. An empty spec is fewer reasons to trust the child, not more. * Giving up on tracking lands the lease on `GameState::Untracked` instead of leaving the console on `launching` forever — the same honest answer `open` reaches when it starts no watcher at all. `a_pid_only_launch_reports_its_exit` used a 4-second fixture, inside the shim window; it passed only because of the bug above. It is 8 seconds now, and a new ignored test drives the field report itself: a pid that hands off inside the window with no signals must not end the session, and must leave the row `untracked`. Backing the phase-1 change out fails that test with `on_exit` fired once — the disconnect, reproduced. Gates. Windows .173: `clippy --all-targets -D warnings` clean (non-vacuous), and `library::launch` 11 + `gamelease` 12 tests pass. Linux (punktfunk-rust-ci): fmt clean, `clippy --all-targets -D warnings` clean, `gamelease` 14 pass, and all three `--ignored` live-process tests pass. |