feat(control): relaunch, the game started again over the bridge as the Insert menu's Restart now does #97

Open
dyst wants to merge 1 commit from dyst/Longshot:feat/relaunch into main
First-time contributor

Why

A test script that needs a fresh game process, to load a renderer built since or to start a measurement clean, has to quit the game and launch it again itself: the right environment, the saves, a new socket. This lets it ask the running game to start again, the way the Insert menu's Restart now does, and carry on over the same socket.

What changed

  • bridge: cmd_relaunch in host/control.c (the relaunch command) replies first, lets go of its socket (transport_cleanup), then calls the game's relaunch; a game without one answers unavailable.
  • game: game_relaunch in host/control.c does what quit does first (rx_close_asked), then the Insert menu's Restart now path (hos_relaunch_ask, hos_window_closed_exit in host/hos_relaunch.c): the same command and the environment the game started with, so HOS_CONTROL names the same socket. Linux: an execve under the same pid; Windows: a new process that waits for the old one to end (HOS_RESTART_OF).
  • interface: HosControlGame.relaunch (host/control.h), added last.
  • client: relaunch [--timeout 120] in scripts/longshot_ctl.py (relaunch()) waits until a status answers from the process started again: one whose uptime is no longer than the wait. The old process can answer for a moment after its reply, and is not taken for the new one (a bug the first version had; the tests now cover it).
  • tests: tests/control_test.c and tests/test_control.py (a fake game's relaunch: the reply, the socket let go of, then its relaunch; refused by a game without one); tests/test_longshot_ctl.py (a stand-in that really starts again, os.execv, twice back to back).

Flags and settings

None (a bridge command and its client command; no switch, menu row or ini key).

Measurements

main: d725b363 (2026-11-13)
this PR: feat/relaunch @ 84411238
Hardware: RTX 3080 (NVIDIA 621.14), Ryzen 9 3900X, 64 GB, Windows 11
Scene: from the title (the save's Continue) into play in Kokiri Forest; headless, muted; both builds from one work folder (only the host differs)
Method: two interleaved rounds each (main, PR, main, PR). main: what a script does today: quit, wait for the process to end, launch again, press A until status says play. This PR: relaunch, then the same presses. Times from the script's clock, from the ask.
How to reproduce: build both into their own game folders and copy a save in. main: longshot_ctl.py launch <game> --headless, press A until play, then quit, launch again and press A to play. This PR: the same first launch, then longshot_ctl.py relaunch and press A to play.

main @ d725b363 this PR @ 84411238
bridge answering again (s), median of 2 5.9 2.2
in play again (s), median of 2 37.8 41.6
main 1: quit + launch: answering after 5.7 s, in play after 39.1 s
pr 1: relaunch: relaunched: answering again after 2.2 s (uptime 0.0 s); answering after 2.4 s, in play after 49.2 s; map KokiriForest_Child
main 2: quit + launch: answering after 6.1 s, in play after 36.5 s
pr 2: relaunch: relaunched: answering again after 1.6 s (uptime 0.1 s); answering after 2.0 s, in play after 34.0 s; map KokiriForest_Child
errors in the PR runs' logs: 0

The bridge is back sooner; the time to play is the game's own start and the presses either way (one slow PR round; two rounds don't separate them). The point is that a script needs no launch logic of its own, not speed. Rebased since onto main f41db065: its new commits touch the forge's scripts, releases, a DLSS menu row and the update check's removal, nothing on the bridge or the restart path, so the numbers hold.

Testing

Tested: on the Windows PC above: the relaunch in the game (two rounds, the old process logging "window closed for a restart", the new one serving the same pipe, Continue to Kokiri Forest, no errors in the logs); tests/run_all.py, the whole suite, on this branch and on main f41db065 (487 passed and 6 failed on main, 488 passed and 5 failed here, no failure main doesn't have: test_check_third_party, test_fpx_sticky, test_release, test_setup_runtime and test_spawn_emit fail on main too, and test_dlss_sdk_cache failed on main and passed here; it fails now and then on this PC); tests/test_control.py and tests/test_longshot_ctl.py also on Linux (Docker, python:3.12 with gcc), three times.

Not tested: the game's relaunch on Linux (an execve of the same pid; the bridge and the client's sides ran there, the restart path itself is the Insert menu's, unchanged).

Needs other testers:

  • Linux: longshot_ctl.py launch <game>, then longshot_ctl.py relaunch, expecting "relaunched: answering again after N s" and the game back at its title, the log saying "window closed for a restart" (T-C36M)

Screenshots

Nothing on screen.

Risk and rollback

A script that sends relaunch to a game whose restart fails leaves it exited, as the Insert menu's Restart now would ("restart: the game could not be started again" in the log). Nothing changes for players: the command needs the control bridge (HOS_CONTROL), which a player's game does not start. Reverting the commit removes it.

Reviewer notes

The dev-loop proposal's B1, its first part (NotRexed/Hookshot#2636). The second part, a renderer built since taken at the next start (HOS_DEV_DLL_SWAP, scripts/stage_renderer.py), is its own PR and uses this.

Issues

See NotRexed/Hookshot#2636

Checklist

For everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description).
  • Rebased on current main.
  • python -I -B tests/run_all.py run and passing (any failure that also fails on main is named above).
  • Tested in the game, or what wasn't is named under Testing.
  • Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests).
  • Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported.
  • No game files, dumps, build output or generated files.
  • third_party/ untouched (a new folder with a PROVENANCE.md instead).
  • The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled.

If applicable

  • AI assistance recorded with Assisted-by: / Co-Authored-By: trailers.
  • Flags table filled in; README / docs/development.md switch reference updated.
  • Screenshots for a change that shows on screen.
  • A row in docs/tested-configurations.md for each measured run.
  • MKWVK_API_VERSION bumped (gpu/vk/vk_api.h's MkwVkHost or MkwVkRenderer changed).
  • The SPIR-V headers regenerated with glslang (a shader changed).
  • A game-free test added for the fix, failing on main and passing here.
  • A check under tests/ that reads changed source updated in this PR, not weakened.
  • Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths.
  • Docs updated.

🤖 Generated with Claude Code

## Why A test script that needs a fresh game process, to load a renderer built since or to start a measurement clean, has to quit the game and launch it again itself: the right environment, the saves, a new socket. This lets it ask the running game to start again, the way the Insert menu's Restart now does, and carry on over the same socket. ## What changed - bridge: `cmd_relaunch` in `host/control.c` (the `relaunch` command) replies first, lets go of its socket (`transport_cleanup`), then calls the game's `relaunch`; a game without one answers `unavailable`. - game: `game_relaunch` in `host/control.c` does what `quit` does first (`rx_close_asked`), then the Insert menu's Restart now path (`hos_relaunch_ask`, `hos_window_closed_exit` in `host/hos_relaunch.c`): the same command and the environment the game started with, so `HOS_CONTROL` names the same socket. Linux: an `execve` under the same pid; Windows: a new process that waits for the old one to end (`HOS_RESTART_OF`). - interface: `HosControlGame.relaunch` (`host/control.h`), added last. - client: `relaunch [--timeout 120]` in `scripts/longshot_ctl.py` (`relaunch()`) waits until a `status` answers from the process started again: one whose `uptime` is no longer than the wait. The old process can answer for a moment after its reply, and is not taken for the new one (a bug the first version had; the tests now cover it). - tests: `tests/control_test.c` and `tests/test_control.py` (a fake game's relaunch: the reply, the socket let go of, then its relaunch; refused by a game without one); `tests/test_longshot_ctl.py` (a stand-in that really starts again, `os.execv`, twice back to back). ## Flags and settings None (a bridge command and its client command; no switch, menu row or ini key). ## Measurements main: d725b363 (2026-11-13) this PR: feat/relaunch @ 84411238 Hardware: RTX 3080 (NVIDIA 621.14), Ryzen 9 3900X, 64 GB, Windows 11 Scene: from the title (the save's Continue) into play in Kokiri Forest; headless, muted; both builds from one work folder (only the host differs) Method: two interleaved rounds each (main, PR, main, PR). main: what a script does today: `quit`, wait for the process to end, `launch` again, press A until `status` says play. This PR: `relaunch`, then the same presses. Times from the script's clock, from the ask. How to reproduce: build both into their own game folders and copy a save in. main: `longshot_ctl.py launch <game> --headless`, press A until play, then `quit`, `launch` again and press A to play. This PR: the same first launch, then `longshot_ctl.py relaunch` and press A to play. | | main @ d725b363 | this PR @ 84411238 | |---|---|---| | bridge answering again (s), median of 2 | 5.9 | 2.2 | | in play again (s), median of 2 | 37.8 | 41.6 | ``` main 1: quit + launch: answering after 5.7 s, in play after 39.1 s pr 1: relaunch: relaunched: answering again after 2.2 s (uptime 0.0 s); answering after 2.4 s, in play after 49.2 s; map KokiriForest_Child main 2: quit + launch: answering after 6.1 s, in play after 36.5 s pr 2: relaunch: relaunched: answering again after 1.6 s (uptime 0.1 s); answering after 2.0 s, in play after 34.0 s; map KokiriForest_Child errors in the PR runs' logs: 0 ``` The bridge is back sooner; the time to play is the game's own start and the presses either way (one slow PR round; two rounds don't separate them). The point is that a script needs no launch logic of its own, not speed. Rebased since onto main f41db065: its new commits touch the forge's scripts, releases, a DLSS menu row and the update check's removal, nothing on the bridge or the restart path, so the numbers hold. ## Testing **Tested:** on the Windows PC above: the relaunch in the game (two rounds, the old process logging "window closed for a restart", the new one serving the same pipe, Continue to Kokiri Forest, no errors in the logs); `tests/run_all.py`, the whole suite, on this branch and on main f41db065 (487 passed and 6 failed on main, 488 passed and 5 failed here, no failure main doesn't have: test_check_third_party, test_fpx_sticky, test_release, test_setup_runtime and test_spawn_emit fail on main too, and test_dlss_sdk_cache failed on main and passed here; it fails now and then on this PC); `tests/test_control.py` and `tests/test_longshot_ctl.py` also on Linux (Docker, python:3.12 with gcc), three times. **Not tested:** the game's relaunch on Linux (an `execve` of the same pid; the bridge and the client's sides ran there, the restart path itself is the Insert menu's, unchanged). **Needs other testers:** - Linux: `longshot_ctl.py launch <game>`, then `longshot_ctl.py relaunch`, expecting "relaunched: answering again after N s" and the game back at its title, the log saying "window closed for a restart" (T-C36M) ## Screenshots Nothing on screen. ## Risk and rollback A script that sends `relaunch` to a game whose restart fails leaves it exited, as the Insert menu's Restart now would ("restart: the game could not be started again" in the log). Nothing changes for players: the command needs the control bridge (`HOS_CONTROL`), which a player's game does not start. Reverting the commit removes it. ## Reviewer notes The dev-loop proposal's B1, its first part ([NotRexed/Hookshot#2636](https://github.com/NotRexed/Hookshot/pull/2636)). The second part, a renderer built since taken at the next start (`HOS_DEV_DLL_SWAP`, `scripts/stage_renderer.py`), is its own PR and uses this. ## Issues See NotRexed/Hookshot#2636 ## Checklist ### For everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`). - [x] Rebased on current `main`. - [x] `python -I -B tests/run_all.py` run and passing (any failure that also fails on `main` is named above). - [x] Tested in the game, or what wasn't is named under Testing. - [x] Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests). - [x] Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported. - [x] No game files, dumps, build output or generated files. - [x] `third_party/` untouched (a new folder with a `PROVENANCE.md` instead). - [x] The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled. ### If applicable - [x] AI assistance recorded with `Assisted-by:` / `Co-Authored-By:` trailers. - [ ] Flags table filled in; README / docs/development.md switch reference updated. - [ ] Screenshots for a change that shows on screen. - [ ] A row in `docs/tested-configurations.md` for each measured run. - [ ] `MKWVK_API_VERSION` bumped (`gpu/vk/vk_api.h`'s `MkwVkHost` or `MkwVkRenderer` changed). - [ ] The SPIR-V headers regenerated with glslang (a shader changed). - [ ] A game-free test added for the fix, failing on `main` and passing here. - [x] A check under `tests/` that reads changed source updated in this PR, not weakened. - [ ] Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. - [ ] Docs updated. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(control): relaunch, the game started again over the bridge as the Insert menu's Restart now does
Some checks are pending
Linux AppImage / appimage (pull_request) Blocked by required conditions
Windows builder / Longshot Builder zip (pull_request) Blocked by required conditions
Windows builder / Windows Vulkan back end (llvm-mingw) (pull_request) Blocked by required conditions
Windows builder / dav1d for Windows (llvm-mingw) (pull_request) Blocked by required conditions
CI / Conventional Commit title (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Blocked by required conditions
CI / Python source checks (windows-2022, part 1/2) (pull_request) Blocked by required conditions
CI / Python source checks (windows-2022, part 2/2) (pull_request) Blocked by required conditions
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Blocked by required conditions
CI / Workflow lint (actionlint) (pull_request) Blocked by required conditions
Third-party parts / As their notes say (pull_request) Blocked by required conditions
Third-party parts / The same as upstream (pull_request) Blocked by required conditions
PR description / Description follows the template (pull_request_target) Successful in 2s
0fa72f49f0
A script that wanted a fresh process (to load the renderer built again, or to measure from a clean start) had to quit
the game and launch it again itself, with its environment and saves, and find its new socket. The bridge's relaunch
command takes the Insert menu's Restart now path (host/hos_relaunch.c: the same command and the environment the game
started with), after letting go of its socket, so the game started again serves the same one. longshot_ctl.py relaunch
waits until a status answers from the new process (an uptime no longer than the wait: not the old one's answer before
it lets go). The dev-loop proposal's B1, its first part (NotRexed/Hookshot#2636).

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Author
First-time contributor

Test request T-C36M opened for the Linux run (it needs the 'testing wanted' label to be claimed).

Test request T-C36M opened for the Linux run (it needs the 'testing wanted' label to be claimed).
dyst changed title from WIP: feat(control): relaunch, the game started again over the bridge as the Insert menu's Restart now does to feat(control): relaunch, the game started again over the bridge as the Insert menu's Restart now does 2026-11-13 02:38:22 +00:00
Some checks are pending
Linux AppImage / appimage (pull_request) Blocked by required conditions
Windows builder / Longshot Builder zip (pull_request) Blocked by required conditions
Windows builder / Windows Vulkan back end (llvm-mingw) (pull_request) Blocked by required conditions
Windows builder / dav1d for Windows (llvm-mingw) (pull_request) Blocked by required conditions
CI / Conventional Commit title (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Blocked by required conditions
CI / Python source checks (windows-2022, part 1/2) (pull_request) Blocked by required conditions
CI / Python source checks (windows-2022, part 2/2) (pull_request) Blocked by required conditions
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Blocked by required conditions
CI / Workflow lint (actionlint) (pull_request) Blocked by required conditions
Third-party parts / As their notes say (pull_request) Blocked by required conditions
Third-party parts / The same as upstream (pull_request) Blocked by required conditions
PR description / Description follows the template (pull_request_target) Successful in 2s
This pull request doesn't have enough approvals yet. 0 of 1 approvals granted.
Some workflows are waiting to be reviewed.
This branch is out-of-date with the base branch
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u 'feat/relaunch:dyst-feat/relaunch'
git switch 'dyst-feat/relaunch'
Sign in to join this conversation.
No description provided.