feat(control): keep durable command-line test sessions #47

Open
mooki wants to merge 8 commits from mooki/Longshot:feat/ctl-sessions into main
First-time contributor

Why

A command-line test should remain usable after its launching client exits, with its log, screenshots and actual shutdown result intact. This is the standard-library session layer, part 1 of the work continued from Nicholas Gower's https://github.com/NotRexed/Hookshot/issues/1025; his agreement to hand it over is still pending in the retained discussion, and the offer to hand it back after acknowledging his later push stands.

What changed

  • Builder folders select their game in bin, not the root launcher. Development image, executable, user and romfs paths are explicit.
  • A versioned run manifest and original-parent supervisor retain process identity, logs, acknowledged screenshots and exit status. Later invocations attach by run folder.
  • The optional test profile uses a fresh or copied user folder, no window, muted audio and an empty environment file. Normal launch choices stay unchanged.
  • Readiness distinguishes bridge, present and play. Startup failure reaps its child. Stop waits for the game, owner and wrapper, and force requires an explicit flag. An observed exit is not hidden behind a readiness timeout.
  • Log cursors are bounded UTF-8 byte cursors; scripted seconds become integer frames. Save draining is explicitly unsupported; this is not a sandbox or a run-long physical-input lock.
  • No native-game change or new dependency. Nicholas's Builder-layout fix and fake-game design are credited in the retained commits and the trailers below. The optional server is separate: https://github.com/NotRexed/Hookshot/issues/2921.
python -I scripts/longshot_ctl.py launch GAME_DIR --profile test --run work/my-run --ready present
python -I scripts/longshot_ctl.py --run work/my-run status
python -I scripts/longshot_ctl.py --run work/my-run stop

How this fits with my workbench

My separate workbench supplies queues, a run wrapper and agent skills. This client is the generic, standard-library part; --wrapper lets an external admission command own the entire game lifetime without embedding machine policy here. The workbench adds evidence handling and checks, and speaks the same bridge. Its separate MCP server is not this PR. The original GitHub runs used its queue and its fork checks caught the two Windows bugs fixed before publication. It is my tooling, not part of Longshot, not endorsed by its maintainers, and not a dependency.

Flags and settings

Flag What it does Default now Recommendation Helps on Doesn't help on Untested on
HOS_CONTROL Lifecycle changed; native switch existing: Record the chosen endpoint; by default allocate a short private socket Unset outside launch; launch allocates its own endpoint Off by default: local test/control use Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
HOS_HEADLESS Test-profile setting: Suppress the game window 1 in test profile; ordinary profile unchanged Off by default: test profile only Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
HOS_AUDIO Test-profile setting: Mute the test game 0 in test profile; ordinary profile unchanged Off by default: test profile only Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
HOS_ENV_FILE Test-profile setting: Use an owned empty environment file instead of ambient file Owned empty file in test profile; ordinary behavior unchanged Off by default: isolation for tests Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
--run New global selector: Reattach using a retained run manifest No selected run Off by default: explicit owned run Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
launch --run Changed launch output: Choose a new or empty durable run folder Unique folder below root Off by default: unique default avoids overwrite Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
launch --root New: Parent of unique run folders work/control-runs below caller working folder On by default: durable default root Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
launch --profile New: Choose ordinary or isolated test setup default Off by default: test is opt-in Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
launch --ready New: Wait for bridge, present or play readiness bridge Off by default: caller chooses stronger readiness Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
launch --timeout Changed readiness semantics: Bound readiness after admission; observed exits win over timeout 600 seconds On by default: bounded readiness, not a game wall limit Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
launch --exe New: Choose explicit executable instead of Builder/bin discovery Discover native game in bin before root Off by default: development override Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
launch --image New: Pass an explicit positional image path Game folder image Off by default: development override Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
launch --romfs New: Pass explicit game content folder Game folder romfs Off by default: development override Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
launch --user New: Pass explicit user folder; rejected in test profile Existing ordinary location; test profile owns its user folder Off by default: explicit development path Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
launch --cwd New: Choose working/cache folder; requires explicit image Game folder Off by default: explicit isolation choice Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
launch --wrapper New: JSON argv around lifetime supervisor for admission and wall policy None Off by default: machine-specific trusted configuration Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
launch --save Changed test-profile support: Copy source saves to the owned test user folder Fresh test user if omitted Off by default: never select a player save implicitly Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
launch --slot Changed test-profile support: Select the copied save slot All source slots Off by default: caller chooses slot Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
launch --data Changed test-profile restriction: Ordinary data root; test profile refuses redirection Ordinary behavior retained Off by default: test profile owns its location Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
launch --env Changed test-profile precedence: Explicit overrides; profile headless/audio/env-file choices win No extra overrides Off by default: trusted caller values Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
--exclusive Changed input default: Exclude physical input for each test-session request True for test-session inputs; normal behavior otherwise On by default: within each test request only Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
--no-exclusive New negative flag: Allow physical input alongside the request Not selected Off by default: deliberate opt-out Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
sequence --hos Changed conversion: Convert seconds/milliseconds to tested integer game frames Frame units unless selected Off by default: input-format choice Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
stop --timeout New: Wait for original owner to quit/reap game and wrapper 10 seconds On by default: bounded truthful stop Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
stop --terminate New: Authorize owner termination/kill after graceful timeout False Off by default: explicit force only Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
log --lines New: Tail retained run log 100 lines On by default: bounded tail Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
log --since New: Read after a byte cursor No cursor Off by default: explicit incremental reader Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
log --bytes New: Bound read/output bytes 65536; at most 1048576 On by default: bounded output Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
log --expect Wait for new matching text after since cursor No expectation Off by default: explicit witness Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
log --regex New: Treat expectation as regular expression False Off by default: explicit interpretation Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
log --timeout New: Bound the log expectation wait 10 seconds On by default: bounded wait Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
--json Changed session replies: Return manifest/log/status structured values for session commands Plain output without flag Off by default: machine-readable opt-in Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
launch --headless New profile interaction: Existing flag remains valid; test profile is always headless False in ordinary profile; test profile forces headless Off by default: ordinary play choice Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)
--sock New session interaction: Existing explicit socket selector; --run selects a retained owned session instead HOS_CONTROL or launch-allocated endpoint Off by default: explicit trusted endpoint Linux fake process tests and Longshot title lifecycle Renderer/gameplay performance Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows)

Measurements

Branch state: on main f41db065cf6667ec638a10aa6990099392d10dca; head a1743d01d9872093731282fd20a1eac9acc6bdcc; whole game-free suite on this head: 509 passed, 0 failed, 19 skipped (main f41db065cf6667ec638a10aa6990099392d10dca: 509 passed, 0 failed, 19 skipped); the commits are unchanged by rebases apart from the results table.

On the measured commits the branch's only failure was also on main: tests/test_updater_asset_digest.py, local .NET SDK 10.0.113 missing Microsoft.AspNetCore.App.Ref (= 10.0.13), error NU1100. That test left main together with the in-game updater, so the suites in the branch-state block no longer contain it.

main: d4a69456c9 (2026-11-12)
this PR: feat/ctl-sessions @ 596c0a3372

Measurement provenance: main @ d4a69456c9. tested client @ 596c0a3372; 44b0bea032 adds only these evidence rows.
Hardware: Core i9-13900K, 96 GB RAM, Arch Linux 7.2.15; game check on RTX 4090 / NVIDIA 615.78.08.
Scene: game-free Builder/session fixtures; separate fresh-profile Longshot title-menu run, windowless, muted, 1280×720 captures, frame generation off, shader preparation deferred.
Method: whole suites first, then the same 22 new tests against main and branch in before/after/before/after order, fresh HOME each time. Medians below are passing-case counts, not execution speeds. The ordinary shared game run proves lifecycle operations, not frame rates. This is a command-line tool, with no change to the game when the client is not run.

How to reproduce: Run python -I -B tests/test_longshot_ctl.py GamePaths Sessions against the PR client, then the same tests with LONGSHOT_CTL_SOURCE selecting the pinned main client; interleave twice. For the functional game check use an owned copy, the test profile, present readiness then stable menu state for 30 frames, B for 8 frames, shot and stop; shader preparation was deferred, not timed.

Main d725b3631 adds only scripts, docs and tests, with no game-code change; the measured d4a69456 comparisons and pinned game evidence remain valid and were not repeated. Original results below retain their measured commits; the new suite is recorded in the branch-state block.

Baseline: main @ d4a69456

main @ d4a69456 this PR @ 596c0a33
Pair 1, new tests passing 0/22; 27 errors including subtests 22/22; 0 errors
Pair 2, new tests passing 0/22; 27 errors including subtests 22/22; 0 errors
Median passing cases 0 22
Whole game-free suite 507 passed / 2 failed / 20 skipped 508 passed / 1 failed / 20 skipped
Before 1: Ran 22 tests in 0.097s
FAILED (errors=27)
After 1: Ran 22 tests in 6.473s
OK
Before 2: Ran 22 tests in 0.286s
FAILED (errors=27)
After 2: Ran 22 tests in 10.067s
OK
Before suite: 507 passed, 2 failed, 20 skipped in 504 s (linux, 6 at once)
After suite: 508 passed, 1 failed, 20 skipped in 315 s (linux, 6 at once)

Main lacks the new session APIs, so those baseline negatives include AttributeError: module 'longshot_ctl' has no attribute 'start_session'; they are feature-absence checks, not a claim that main's existing client is broken. Both suites used tests/run_all.py --jobs 6 --strict with the repository's default compiler. The main suite is the shared baseline run once for this day's moved pull requests at this commit. These checks have not yet been run on a forge runner.

Main additionally failed two timeout/sequence cases in tests/test_appimage_package_retry.py under heavy load; the requested isolated rerun passed all 9 cases (Ran 9 tests in 15.166s, OK). It fails only under heavy load on this PC; timing-dependent. This branch's full run passed that file. Neither full-suite failure has been erased or called green.

Real Longshot lifecycle

The client above drove an owned copy of Longshot main d3bd28050 (v2.2.0-rc.1-15-gd3bd28050, source fingerprint 2bce0d57). The native code is unchanged by this PR; the donor was copied, never modified. The successful run waited for drawable menu state to persist for 30 frames before input.

Attempt Observed result
Cold release preparation, 341.0 s Retained failure: ready=present timed out at frame 0 while shader/pipeline preparation was progressing. The supervisor terminated and reaped its child: exit -15, process_exited:true, stop_method:startup-terminate. No picture or completed preparation claimed.
Deferred preparation, 31.6 s Start; menu status at frame 1366; B held for 8 frames, 1369–1377; acknowledged 1280×720 PNG at present 1381; another client attached at frame 1386; stop and offline manifest read succeeded.

Selected fields copied from the successful replies:

{"state":"menu","frame":1366,"stalled":false}
{"ok":true,"frame":1377,"start":1369,"end":1377}
{"ok":true,"frame":1384,"present":1381,"width":1280,"height":720}
{"state":"finished","exit_code":0,"process_exited":true,"stop_method":"quit","save_drain":"unsupported"}

Both attempts had fatal=0, crash=0, device-loss=0 and kernel faults=0, with complete journal coverage. No performance comparison: the successful run's one-minute load was 241.39 → 230.52.

Historical evidence, not relabelled as Longshot: on 2026-11-12 the Hookshot game ba005ca4 with client 3f1da556 completed a 38.4 s Kokiri Forest session, screenshot, reattachment and quit exit 0. Its earlier 72.9 s attempt reached play but stopped at an invalid Linux log expectation. The retained image below belongs to that older successful run.

The complete suite was rerun after every source/documentation commit, with the default compiler and fresh HOME. The earlier measurement rows remain labelled by their actual commits. Main's suite was run once for all branches on this commit. No hosted CI or new game run is claimed.

Testing

Tested: on this PC (Arch Linux, RTX 4090 with NVIDIA 615.78.08, i9-13900K), the whole game-free suite and the same 22 new Builder/session tests against main and this PR twice in interleaved order, with a fresh test home each time. The Longshot title-menu check waited for stable menu state for 30 frames, held B for 8 frames, took an acknowledged screenshot, reattached from another client, then stopped and read the finished manifest; the cold-preparation readiness timeout and child reaping are retained too. Tested but not measured: The new Longshot title lifecycle and historical Hookshot gameplay were functional checks, not performance or sampling-freshness measurements.

Not tested: the explicitly listed platforms, integration and runtime cases below remain untested; only the bounded Linux/NVIDIA lifecycle above was run for Longshot.

  • AMD/Intel/Steam Deck, real controllers, WebGPU capture, long sessions or a complete playthrough. No test request was posted during this move.
  • Gameplay state freshness or copied-save gameplay in the new Longshot run: this run stayed at the title, where game is null. Historical Hookshot gameplay remains separately labelled.
  • Quiet-machine speed or off/on frame-time cost; this PR changes no native code. Save-safe shutdown remains unsupported.
  • The old GitHub fork's Windows/Ubuntu passes are historical, not Forgejo CI or new Windows evidence. No C/C++ fixture changed here.

Needs other testers:

  • Windows x64: launch an owned test copy, close the launching client, reattach from another client, read logs, take a screenshot and stop the game, expecting the session survives the first client, the screenshot is acknowledged, and stop records the real game exit without leaving the game or wrapper running (test request T-8DJG)

Screenshots

Nothing on screen changes in this tool. main @ d4a69456 has no session-layer comparison picture. this PR @ 596c0a33 client with native game main @ d3bd28050: the title-menu lifecycle picture below; not a rendering comparison.

New Longshot run: the title and New Game menu, captured through the retained session after the acknowledged B press.

Historical Hookshot ba005ca4 / client 3f1da556, 2026-11-12: child Link, hearts and minimap in Kokiri Forest. Not the new Longshot run or a rendering comparison.

Risk and rollback

A wrong path or reused manifest could target the wrong executable or expose private logs. Use only trusted owned run folders; pid-birth checks and the original parent prevent signalling unrelated processes. Stop the owned test session and return to ordinary launch, or revert this client change. Quit has no save-drain barrier; force is opt-in, and no rollback can promise an unwritten save was drained.

Reviewer notes

Read game_exe, start_session, wait_ready, Session log/stop and the supervisor ownership checks first. Confirm Builder bin-first selection, linked-root handling and UTF-8 cursor tests. No game-side protocol, save-safe quit or run-long input lock is added; those limits remain deliberate. Nicholas Gower credit and the pending handover agreement are retained.

Issues

Original pull request: https://github.com/NotRexed/Hookshot/issues/2904.
Original work and credit: https://github.com/NotRexed/Hookshot/issues/1025; command-line-first discussion: https://github.com/NotRexed/Hookshot/pull/764#issuecomment-6729605427.
No corresponding issue exists in the forge's public issue inventory checked for this move. No old GitHub number is used as a forge closing reference.

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 noted, and the switch reference updated (docs/development.md, the README). Relevant command/protocol details are in the topic guide; no unrelated menu default is added.
  • A row in docs/tested-configurations.md for each measured run.
  • 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.
  • Docs updated.

Moved from NotRexed/Hookshot#2904.

Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra [email protected]
Co-authored-by: Nicholas Gower [email protected]

## Why A command-line test should remain usable after its launching client exits, with its log, screenshots and actual shutdown result intact. This is the standard-library session layer, part 1 of the work continued from Nicholas Gower's https://github.com/NotRexed/Hookshot/issues/1025; his agreement to hand it over is still pending in the retained discussion, and the offer to hand it back after acknowledging his later push stands. ## What changed - Builder folders select their game in `bin`, not the root launcher. Development image, executable, user and romfs paths are explicit. - A versioned run manifest and original-parent supervisor retain process identity, logs, acknowledged screenshots and exit status. Later invocations attach by run folder. - The optional test profile uses a fresh or copied user folder, no window, muted audio and an empty environment file. Normal launch choices stay unchanged. - Readiness distinguishes bridge, present and play. Startup failure reaps its child. Stop waits for the game, owner and wrapper, and force requires an explicit flag. An observed exit is not hidden behind a readiness timeout. - Log cursors are bounded UTF-8 byte cursors; scripted seconds become integer frames. Save draining is explicitly unsupported; this is not a sandbox or a run-long physical-input lock. - No native-game change or new dependency. Nicholas's Builder-layout fix and fake-game design are credited in the retained commits and the trailers below. The optional server is separate: https://github.com/NotRexed/Hookshot/issues/2921. ```sh python -I scripts/longshot_ctl.py launch GAME_DIR --profile test --run work/my-run --ready present python -I scripts/longshot_ctl.py --run work/my-run status python -I scripts/longshot_ctl.py --run work/my-run stop ``` ### How this fits with my workbench My separate [workbench](https://github.com/mooki000/hookshot-workbench) supplies [queues](https://github.com/mooki000/hookshot-workbench/tree/main/gates), a [run wrapper](https://github.com/mooki000/hookshot-workbench/tree/main/harness) and [agent skills](https://github.com/mooki000/hookshot-workbench/tree/main/skills). This client is the generic, standard-library part; `--wrapper` lets an external admission command own the entire game lifetime without embedding machine policy here. The workbench adds evidence handling and checks, and speaks the same bridge. Its [separate MCP server](https://github.com/mooki000/hookshot-workbench/tree/mcp-server/mcp) is not this PR. The original GitHub runs used its queue and its fork checks caught the two Windows bugs fixed before publication. It is my tooling, not part of Longshot, not endorsed by its maintainers, and not a dependency. ## Flags and settings | Flag | What it does | Default now | Recommendation | Helps on | Doesn't help on | Untested on | |---|---|---|---|---|---|---| | `HOS_CONTROL` | Lifecycle changed; native switch existing: Record the chosen endpoint; by default allocate a short private socket | Unset outside launch; launch allocates its own endpoint | Off by default: local test/control use | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `HOS_HEADLESS` | Test-profile setting: Suppress the game window | 1 in test profile; ordinary profile unchanged | Off by default: test profile only | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `HOS_AUDIO` | Test-profile setting: Mute the test game | 0 in test profile; ordinary profile unchanged | Off by default: test profile only | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `HOS_ENV_FILE` | Test-profile setting: Use an owned empty environment file instead of ambient file | Owned empty file in test profile; ordinary behavior unchanged | Off by default: isolation for tests | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `--run` | New global selector: Reattach using a retained run manifest | No selected run | Off by default: explicit owned run | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `launch --run` | Changed launch output: Choose a new or empty durable run folder | Unique folder below root | Off by default: unique default avoids overwrite | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `launch --root` | New: Parent of unique run folders | work/control-runs below caller working folder | On by default: durable default root | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `launch --profile` | New: Choose ordinary or isolated test setup | default | Off by default: test is opt-in | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `launch --ready` | New: Wait for bridge, present or play readiness | bridge | Off by default: caller chooses stronger readiness | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `launch --timeout` | Changed readiness semantics: Bound readiness after admission; observed exits win over timeout | 600 seconds | On by default: bounded readiness, not a game wall limit | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `launch --exe` | New: Choose explicit executable instead of Builder/bin discovery | Discover native game in bin before root | Off by default: development override | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `launch --image` | New: Pass an explicit positional image path | Game folder image | Off by default: development override | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `launch --romfs` | New: Pass explicit game content folder | Game folder romfs | Off by default: development override | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `launch --user` | New: Pass explicit user folder; rejected in test profile | Existing ordinary location; test profile owns its user folder | Off by default: explicit development path | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `launch --cwd` | New: Choose working/cache folder; requires explicit image | Game folder | Off by default: explicit isolation choice | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `launch --wrapper` | New: JSON argv around lifetime supervisor for admission and wall policy | None | Off by default: machine-specific trusted configuration | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `launch --save` | Changed test-profile support: Copy source saves to the owned test user folder | Fresh test user if omitted | Off by default: never select a player save implicitly | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `launch --slot` | Changed test-profile support: Select the copied save slot | All source slots | Off by default: caller chooses slot | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `launch --data` | Changed test-profile restriction: Ordinary data root; test profile refuses redirection | Ordinary behavior retained | Off by default: test profile owns its location | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `launch --env` | Changed test-profile precedence: Explicit overrides; profile headless/audio/env-file choices win | No extra overrides | Off by default: trusted caller values | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `--exclusive` | Changed input default: Exclude physical input for each test-session request | True for test-session inputs; normal behavior otherwise | On by default: within each test request only | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `--no-exclusive` | New negative flag: Allow physical input alongside the request | Not selected | Off by default: deliberate opt-out | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `sequence --hos` | Changed conversion: Convert seconds/milliseconds to tested integer game frames | Frame units unless selected | Off by default: input-format choice | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `stop --timeout` | New: Wait for original owner to quit/reap game and wrapper | 10 seconds | On by default: bounded truthful stop | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `stop --terminate` | New: Authorize owner termination/kill after graceful timeout | False | Off by default: explicit force only | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `log --lines` | New: Tail retained run log | 100 lines | On by default: bounded tail | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `log --since` | New: Read after a byte cursor | No cursor | Off by default: explicit incremental reader | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `log --bytes` | New: Bound read/output bytes | 65536; at most 1048576 | On by default: bounded output | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `log --expect` | Wait for new matching text after since cursor | No expectation | Off by default: explicit witness | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `log --regex` | New: Treat expectation as regular expression | False | Off by default: explicit interpretation | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `log --timeout` | New: Bound the log expectation wait | 10 seconds | On by default: bounded wait | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `--json` | Changed session replies: Return manifest/log/status structured values for session commands | Plain output without flag | Off by default: machine-readable opt-in | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `launch --headless` | New profile interaction: Existing flag remains valid; test profile is always headless | False in ordinary profile; test profile forces headless | Off by default: ordinary play choice | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | | `--sock` | New session interaction: Existing explicit socket selector; --run selects a retained owned session instead | HOS_CONTROL or launch-allocated endpoint | Off by default: explicit trusted endpoint | Linux fake process tests and Longshot title lifecycle | Renderer/gameplay performance | Fresh Windows, other GPUs and long sessions (should help on: Equivalent CLI workflows on Windows) | ## Measurements <!-- branch-state --> Branch state: on main `f41db065cf6667ec638a10aa6990099392d10dca`; head `a1743d01d9872093731282fd20a1eac9acc6bdcc`; whole game-free suite on this head: `509 passed, 0 failed, 19 skipped` (main `f41db065cf6667ec638a10aa6990099392d10dca`: `509 passed, 0 failed, 19 skipped`); the commits are unchanged by rebases apart from the results table. <!-- /branch-state --> On the measured commits the branch's only failure was also on main: `tests/test_updater_asset_digest.py`, local .NET SDK 10.0.113 missing `Microsoft.AspNetCore.App.Ref (= 10.0.13)`, error `NU1100`. That test left main together with the in-game updater, so the suites in the branch-state block no longer contain it. main: d4a69456c9de5131589af1fcf9a09adba4c403d1 (2026-11-12) this PR: feat/ctl-sessions @ 596c0a3372854aa3bd5346f52aad796e44bd53a4 Measurement provenance: main @ d4a69456c9de5131589af1fcf9a09adba4c403d1. tested client @ 596c0a3372854aa3bd5346f52aad796e44bd53a4; 44b0bea032e26cde0e258d735292f83ca5499c46 adds only these evidence rows. Hardware: Core i9-13900K, 96 GB RAM, Arch Linux 7.2.15; game check on RTX 4090 / NVIDIA 615.78.08. Scene: game-free Builder/session fixtures; separate fresh-profile Longshot title-menu run, windowless, muted, 1280×720 captures, frame generation off, shader preparation deferred. Method: whole suites first, then the same 22 new tests against main and branch in before/after/before/after order, fresh HOME each time. Medians below are passing-case counts, not execution speeds. The ordinary shared game run proves lifecycle operations, not frame rates. This is a command-line tool, with no change to the game when the client is not run. How to reproduce: Run `python -I -B tests/test_longshot_ctl.py GamePaths Sessions` against the PR client, then the same tests with LONGSHOT_CTL_SOURCE selecting the pinned main client; interleave twice. For the functional game check use an owned copy, the test profile, present readiness then stable menu state for 30 frames, B for 8 frames, shot and stop; shader preparation was deferred, not timed. Main d725b3631 adds only scripts, docs and tests, with no game-code change; the measured d4a69456 comparisons and pinned game evidence remain valid and were not repeated. Original results below retain their measured commits; the new suite is recorded in the branch-state block. Baseline: main @ d4a69456 | | main @ d4a69456 | this PR @ 596c0a33 | |---|---|---| | Pair 1, new tests passing | 0/22; 27 errors including subtests | 22/22; 0 errors | | Pair 2, new tests passing | 0/22; 27 errors including subtests | 22/22; 0 errors | | Median passing cases | 0 | 22 | | Whole game-free suite | 507 passed / 2 failed / 20 skipped | 508 passed / 1 failed / 20 skipped | ```text Before 1: Ran 22 tests in 0.097s FAILED (errors=27) After 1: Ran 22 tests in 6.473s OK Before 2: Ran 22 tests in 0.286s FAILED (errors=27) After 2: Ran 22 tests in 10.067s OK Before suite: 507 passed, 2 failed, 20 skipped in 504 s (linux, 6 at once) After suite: 508 passed, 1 failed, 20 skipped in 315 s (linux, 6 at once) ``` Main lacks the new session APIs, so those baseline negatives include `AttributeError: module 'longshot_ctl' has no attribute 'start_session'`; they are feature-absence checks, not a claim that main's existing client is broken. Both suites used `tests/run_all.py --jobs 6 --strict` with the repository's default compiler. The main suite is the shared baseline run once for this day's moved pull requests at this commit. These checks have not yet been run on a forge runner. Main additionally failed two timeout/sequence cases in `tests/test_appimage_package_retry.py` under heavy load; the requested isolated rerun passed all 9 cases (`Ran 9 tests in 15.166s`, `OK`). It fails only under heavy load on this PC; timing-dependent. This branch's full run passed that file. Neither full-suite failure has been erased or called green. ### Real Longshot lifecycle The client above drove an owned copy of Longshot main d3bd28050 (`v2.2.0-rc.1-15-gd3bd28050`, source fingerprint `2bce0d57`). The native code is unchanged by this PR; the donor was copied, never modified. The successful run waited for drawable menu state to persist for 30 frames before input. | Attempt | Observed result | |---|---| | Cold release preparation, 341.0 s | Retained failure: `ready=present` timed out at frame 0 while shader/pipeline preparation was progressing. The supervisor terminated and reaped its child: exit -15, `process_exited:true`, `stop_method:startup-terminate`. No picture or completed preparation claimed. | | Deferred preparation, 31.6 s | Start; menu status at frame 1366; B held for 8 frames, 1369–1377; acknowledged 1280×720 PNG at present 1381; another client attached at frame 1386; stop and offline manifest read succeeded. | Selected fields copied from the successful replies: ```json {"state":"menu","frame":1366,"stalled":false} {"ok":true,"frame":1377,"start":1369,"end":1377} {"ok":true,"frame":1384,"present":1381,"width":1280,"height":720} {"state":"finished","exit_code":0,"process_exited":true,"stop_method":"quit","save_drain":"unsupported"} ``` Both attempts had fatal=0, crash=0, device-loss=0 and kernel faults=0, with complete journal coverage. No performance comparison: the successful run's one-minute load was 241.39 → 230.52. Historical evidence, not relabelled as Longshot: on 2026-11-12 the Hookshot game ba005ca4 with client 3f1da556 completed a 38.4 s Kokiri Forest session, screenshot, reattachment and quit exit 0. Its earlier 72.9 s attempt reached play but stopped at an invalid Linux log expectation. The retained image below belongs to that older successful run. The complete suite was rerun after every source/documentation commit, with the default compiler and fresh HOME. The earlier measurement rows remain labelled by their actual commits. Main's suite was run once for all branches on this commit. No hosted CI or new game run is claimed. ## Testing **Tested:** on this PC (Arch Linux, RTX 4090 with NVIDIA 615.78.08, i9-13900K), the whole game-free suite and the same 22 new Builder/session tests against main and this PR twice in interleaved order, with a fresh test home each time. The Longshot title-menu check waited for stable menu state for 30 frames, held B for 8 frames, took an acknowledged screenshot, reattached from another client, then stopped and read the finished manifest; the cold-preparation readiness timeout and child reaping are retained too. Tested but not measured: The new Longshot title lifecycle and historical Hookshot gameplay were functional checks, not performance or sampling-freshness measurements. **Not tested:** the explicitly listed platforms, integration and runtime cases below remain untested; only the bounded Linux/NVIDIA lifecycle above was run for Longshot. - AMD/Intel/Steam Deck, real controllers, WebGPU capture, long sessions or a complete playthrough. No test request was posted during this move. - Gameplay state freshness or copied-save gameplay in the new Longshot run: this run stayed at the title, where `game` is null. Historical Hookshot gameplay remains separately labelled. - Quiet-machine speed or off/on frame-time cost; this PR changes no native code. Save-safe shutdown remains unsupported. - The old GitHub fork's Windows/Ubuntu passes are historical, not Forgejo CI or new Windows evidence. No C/C++ fixture changed here. **Needs other testers:** - Windows x64: launch an owned test copy, close the launching client, reattach from another client, read logs, take a screenshot and stop the game, expecting the session survives the first client, the screenshot is acknowledged, and stop records the real game exit without leaving the game or wrapper running (test request T-8DJG) ## Screenshots Nothing on screen changes in this tool. main @ d4a69456 has no session-layer comparison picture. this PR @ 596c0a33 client with native game main @ d3bd28050: the title-menu lifecycle picture below; not a rendering comparison. ![New Longshot run: the title and New Game menu, captured through the retained session after the acknowledged B press.](https://git.magicbean.dev/attachments/3dae6f02-44de-45ee-93b3-41930b705aa4) ![Historical Hookshot ba005ca4 / client 3f1da556, 2026-11-12: child Link, hearts and minimap in Kokiri Forest. Not the new Longshot run or a rendering comparison.](https://git.magicbean.dev/attachments/21b4d7cf-1c6e-47e2-a070-b7c152a98907) ## Risk and rollback A wrong path or reused manifest could target the wrong executable or expose private logs. Use only trusted owned run folders; pid-birth checks and the original parent prevent signalling unrelated processes. Stop the owned test session and return to ordinary launch, or revert this client change. Quit has no save-drain barrier; force is opt-in, and no rollback can promise an unwritten save was drained. ## Reviewer notes Read game_exe, start_session, wait_ready, Session log/stop and the supervisor ownership checks first. Confirm Builder bin-first selection, linked-root handling and UTF-8 cursor tests. No game-side protocol, save-safe quit or run-long input lock is added; those limits remain deliberate. Nicholas Gower credit and the pending handover agreement are retained. ## Issues Original pull request: https://github.com/NotRexed/Hookshot/issues/2904. Original work and credit: https://github.com/NotRexed/Hookshot/issues/1025; command-line-first discussion: https://github.com/NotRexed/Hookshot/pull/764#issuecomment-6729605427. No corresponding issue exists in the forge's public issue inventory checked for this move. No old GitHub number is used as a forge closing reference. ## 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. - [x] Flags noted, and the switch reference updated (docs/development.md, the README). Relevant command/protocol details are in the topic guide; no unrelated menu default is added. - [x] A row in `docs/tested-configurations.md` for each measured run. - [x] 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. - [x] Docs updated. --- Moved from NotRexed/Hookshot#2904. Assisted-by: GPT-6 Astra Co-Authored-By: GPT-6 Astra <[email protected]> Co-authored-by: Nicholas Gower <[email protected]>
Adapt Nicholas Gower’s bin-first executable lookup and flat/builder layout regression from 7f76855d in #1025 to the command-line client. Refuse an incomplete bin folder instead of starting the root launcher. Expose the runtime’s existing image, romfs and user arguments for development builds.

Co-authored-by: Nicholas Gower <[email protected]>
Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra <[email protected]>
Keep a versioned manifest, process birth identity, isolated test profile, bounded readiness and log reads, acknowledged shots and an actual child exit receipt after the launching client exits. A lifetime wrapper supplies admission without machine policy in the client. Re-attached callers ask the original owner to stop; they never signal a pid from disk. Quit has no save-drain guarantee.

Extend the existing socket/named-pipe fake with state, quit refusal and an authored decodable PNG. The tiny authored PNG and fake-process evidence approach adapt Nicholas Gower’s work in #1025; no old live-file protocol or MCP dependency is carried. HOS sequence units become integer frame steps, with half-up and minimum-hold assertions.

Co-authored-by: Nicholas Gower <[email protected]>
Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra <[email protected]>
The first fork Windows run caught two branch regressions before publication: a plain RUNNER~1 temporary path compared unequal to its resolved spelling, and stop could return while an admission wrapper still held owner.log. Check link/reparse entries directly, compare canonical source/destination identities, record the wrapper birth identity and wait for both owners before stop reports completion. Explicit close_fds preserves only redirected standard handles.

Normalize expected filesystem, JSON and argument errors at the callable session boundary to Failed(code). Regressions cover alternate path spellings, a deliberately held wrapper, closed parent log handles, manifest-reader closure and binary CRLF/UTF-8 log cursors. The capture fake now emits the existing renderer publication line verbatim for the log-expect proof.

Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra <[email protected]>
Allow ordinary linked parent folders while refusing overlapping resolved save trees and links at the save/UID entries the copier creates. Main did not reject linked destination ancestors. Keep partial trailing UTF-8 bytes behind the returned cursor, including at a live EOF, and only replace an incomplete tail after the run ends. Accept older stop receipts without process_exited.

Game-free tests fail on the previous code and pass with these changes: linked parent success, source-link and save-link refusal, a three-byte character split at the read limit or a live EOF, tail clipping, and a compatible older stop result. The recorded game run remains on 3f1da556; these changes were not rerun in the game.

Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra <[email protected]>
Record the command-line-only run against the completed main ba005ca4 game build: copied test profile, ready play, frame-acknowledged sequence, inspected bridge shot, live log cursor expectation, second invocation and observed quit exit. No game rebuild, performance or save-drain claim.

Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra <[email protected]>
Check the child before and after each readiness poll and again at the deadline, before reporting a timeout. Keep bridge I/O within the remaining readiness budget. At stop and termination deadlines, recheck the process before reporting a timeout or escalating; an already-exited child is not forced.

Replace the early-exit test’s race between Python startup and 0.3 seconds with an actual fake child reaped before the first poll. Deterministic clock/process tests cover an exit exactly at the deadline, exit during a transport poll, a still-live timeout, stop-deadline exits and termination escalation. The regressions fail on the open #2904 base; the complete client and real-C bridge checks pass afterward.

Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra <[email protected]>
Assisted-by: GPT-6 Astra

Co-Authored-By: GPT-6 Astra <[email protected]>
Author
First-time contributor

Discussion carried over from NotRexed/Hookshot#2904

Every comment that was readable on the original thread when it was copied on 2026-11-12, in order, unchanged except that issue and pull request numbers are written as links to the old repository and names carry no @. Comments by an account that has since been closed there could no longer be read and are missing; where a reply quotes them, the quote remains.


mooki000, 2026-11-12 22:06 UTC (comment, original):

One more commit, 870d3815: a game that exits before it ever listens was sometimes reported as "ready timed out" instead of "exited with code N".

The readiness wait checked the deadline before it looked at the child again, so which message you got depended on wall time. It showed on loaded Windows runners in my fork: tests/test_hookshot_ctl.py (test_ready_levels_and_cleanup, the exit case) failed two runs out of two while testing the next part of this stack, with "ready=play timed out: no game listens" where "exited 7" was expected. This pull request's own checks had passed, so the test was timing-dependent rather than wrong every time.

  • The wait now looks at the child before and after each poll and once more at the deadline, before it reports a timeout; stop and its grace period do the same before escalating.
  • The test no longer races Python's start against 0.3 s: the fake child has exited and been reaped before the first poll, and clock-controlled cases cover an exit exactly at the deadline, an exit during a bridge poll, and a real timeout of a live game. The new cases fail on the previous head.
  • Checked: both complete test files pass locally; the Windows and Ubuntu source checks pass in my fork on a branch that contains this commit (run).

NotRexed/Hookshot#2921 (the draft on top of this) is not rebased yet; it does not need this commit to be read.

Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra [email protected]

## Discussion carried over from NotRexed/Hookshot#2904 Every comment that was readable on [the original thread](https://github.com/NotRexed/Hookshot/pull/2904) when it was copied on 2026-11-12, in order, unchanged except that issue and pull request numbers are written as links to the old repository and names carry no @. Comments by an account that has since been closed there could no longer be read and are missing; where a reply quotes them, the quote remains. --- **mooki000**, 2026-11-12 22:06 UTC (comment, [original](https://github.com/NotRexed/Hookshot/pull/2904#issuecomment-6837229994)): One more commit, `870d3815`: a game that exits before it ever listens was sometimes reported as "ready timed out" instead of "exited with code N". The readiness wait checked the deadline before it looked at the child again, so which message you got depended on wall time. It showed on loaded Windows runners in my fork: `tests/test_hookshot_ctl.py` (`test_ready_levels_and_cleanup`, the exit case) failed two runs out of two while testing the next part of this stack, with "ready=play timed out: no game listens" where "exited 7" was expected. This pull request's own checks had passed, so the test was timing-dependent rather than wrong every time. - The wait now looks at the child before and after each poll and once more at the deadline, before it reports a timeout; stop and its grace period do the same before escalating. - The test no longer races Python's start against 0.3 s: the fake child has exited and been reaped before the first poll, and clock-controlled cases cover an exit exactly at the deadline, an exit during a bridge poll, and a real timeout of a live game. The new cases fail on the previous head. - Checked: both complete test files pass locally; the Windows and Ubuntu source checks pass in my fork on a branch that contains this commit ([run](https://github.com/mooki000/Hookshot/actions/runs/43314981160)). [NotRexed/Hookshot#2921](https://github.com/NotRexed/Hookshot/issues/2921) (the draft on top of this) is not rebased yet; it does not need this commit to be read. Assisted-by: GPT-6 Astra Co-Authored-By: GPT-6 Astra <[email protected]>
The control guide linked ../readme.md; this repository's file is README.md,
as the other guides link it. Found by the documentation link check carried
in the follow-up branch.

Assisted-by: Claude Fable 5.2
Co-Authored-By: Claude Fable 5.2 <[email protected]>
mooki changed title from feat(control): keep durable command-line test sessions to WIP: feat(control): keep durable command-line test sessions 2026-11-13 00:58:41 +00:00
Author
First-time contributor

Back to work in progress, for its description only. This was opened shortly before #48 merged and follows the earlier template, so it does not pass the new description check (no Flags and settings and no Risk and rollback section, table columns named Before and After). The code and the measurements are unchanged: the whole suite passes on this exact commit apart from one local .NET failure that also fails on main. The description is being moved to the new template and the branch rebased onto d725b363; it comes back through pr ready once the check passes.

Back to work in progress, for its description only. This was opened shortly before #48 merged and follows the earlier template, so it does not pass the new description check (no Flags and settings and no Risk and rollback section, table columns named Before and After). The code and the measurements are unchanged: the whole suite passes on this exact commit apart from one local .NET failure that also fails on main. The description is being moved to the new template and the branch rebased onto d725b363; it comes back through `pr ready` once the check passes.
Author
First-time contributor

Rebased onto main d725b3631 as 23cc17be6a; all eight carried patches are unchanged, and the newer main changes only scripts, docs and tests, so the earlier measurements retain their original commit labels. The description now follows the new template, including every changed control, reproduction steps, risks and rollback, while keeping both uploaded pictures and the original credit. The whole suite on this exact head finished 510 passed, 1 failed, 20 skipped, with only main's known local .NET reference-pack failure; the description checker passes without WIP.

Rebased onto main d725b3631 as 23cc17be6ac3bf11e5553f0c3008b05325b3249c; all eight carried patches are unchanged, and the newer main changes only scripts, docs and tests, so the earlier measurements retain their original commit labels. The description now follows the new template, including every changed control, reproduction steps, risks and rollback, while keeping both uploaded pictures and the original credit. The whole suite on this exact head finished 510 passed, 1 failed, 20 skipped, with only main's known local .NET reference-pack failure; the description checker passes without WIP.
mooki changed title from WIP: feat(control): keep durable command-line test sessions to feat(control): keep durable command-line test sessions 2026-11-13 01:31:03 +00:00
mooki force-pushed feat/ctl-sessions from 9f5f6dd292 to 23cc17be6a 2026-11-13 01:31:03 +00:00 Compare
mooki changed title from feat(control): keep durable command-line test sessions to WIP: feat(control): keep durable command-line test sessions 2026-11-13 01:48:28 +00:00
Author
First-time contributor

Back to work in progress for two reasons, neither a change to the code or the measurements. The template gained a Testing section with #72 (what was tested, what was not, and which testers are needed, with test requests), which this description does not have yet. And #39's merge added a row to the results table at the same place as this pull request's rows, so the branch conflicts in that one file until it is rebased. Both are being done; it returns through pr ready.

Back to work in progress for two reasons, neither a change to the code or the measurements. The template gained a Testing section with #72 (what was tested, what was not, and which testers are needed, with test requests), which this description does not have yet. And #39's merge added a row to the results table at the same place as this pull request's rows, so the branch conflicts in that one file until it is rebased. Both are being done; it returns through `pr ready`.
mooki force-pushed feat/ctl-sessions from 23cc17be6a to a1743d01d9
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
Vulkan back end with NVIDIA DLSS / Windows Vulkan back end with NVIDIA DLSS and AMD FSR 4 (llvm-mingw, link check) (pull_request) Blocked by required conditions
PR description / Description follows the template (pull_request_target) Successful in 2s
2026-11-13 02:42:04 +00:00
Compare
Author
First-time contributor

The description now has the current template's Testing section: what was tested, what was not, and which testers are needed; the test request is open (T-8DJG). Rebased onto main f41db065c keeping both sides' rows of the results table; whole suite on the new head a1743d01d: 509 passed, 0 failed, 19 skipped. Ready for review.

The description now has the current template's Testing section: what was tested, what was not, and which testers are needed; the test request is open (T-8DJG). Rebased onto main f41db065c keeping both sides' rows of the results table; whole suite on the new head a1743d01d: 509 passed, 0 failed, 19 skipped. Ready for review.
mooki changed title from WIP: feat(control): keep durable command-line test sessions to feat(control): keep durable command-line test sessions 2026-11-13 02:42:06 +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
Vulkan back end with NVIDIA DLSS / Windows Vulkan back end with NVIDIA DLSS and AMD FSR 4 (llvm-mingw, link check) (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/ctl-sessions:mooki-feat/ctl-sessions'
git switch 'mooki-feat/ctl-sessions'
Sign in to join this conversation.
No description provided.