fix(control): a command's reply wait ends at its deadline while events keep arriving #538
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
beanstalk: approved @ 1697603
beanstalk: approved @ 2c5a62e
beanstalk: approved @ 4fd82b7
beanstalk: approved @ 64d6d09
beanstalk: approved @ 6f91fae
beanstalk: approved @ 70a78f9
beanstalk: approved @ 8015c9a
beanstalk: approved @ 8d361dc
beanstalk: approved @ 9d8356e
beanstalk: approved @ abf2227
beanstalk: approved @ d82f202
beanstalk: approved @ e5f1e40
beanstalk: approved @ ee8ab5f
ci: approved
good first issue
gpu/amd
gpu/igpu
gpu/intel
gpu/nvidia
help wanted
kind
bug
kind
chore
kind
docs
kind
feature
kind
performance
kind
question
needs measurements
platform/linux
platform/macos
platform/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
regression
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
ci: approved
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!538
Loading…
Reference in a new issue
No description provided.
Delete branch "jarynclouatre/Longshot:fix/ctl-reply-deadline"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Type: fix · Risk: low · Tested on: Linux x86-64 (game-free checks only) · Needs: none
Why
Bridge.request(scripts/longshot_ctl.py) computes one deadline for a command's reply, but withoutreply_timeoutit handedline()the whole wait again after every line that was not the reply. A client subscribed to present or log events, whose command then gets no reply, waited as long as the events kept coming: with an event every 5 s, astatusthat should give up after 70 s never did.None filed.
How to reproduce
Seen on: found reading the code; not seen in the game
Settings and switches: A client subscribed to present or log events, without
reply_timeout.Steps: Send
status, receive an event every 5 s, and give the command no reply.Expected: The command gives up after 70 s.
Actual: The command waits as long as events keep coming, so it never gives up while they continue.
What changed
A command now keeps one deadline while events arrive without a reply. Each read gets only the time left, while buffered replies are still read.
scripts/longshot_ctl.py): each read gets the time left to the one deadline. Once it has passed and no line is buffered, the request fails with the timeout's code 3 and "no answer from the game in s", the whole wait. A buffered line, the reply among them, is still read.quit's closed connection andreply_timeoutare unchanged.tests/test_longshot_ctl.py): a fake bridge on a controlled clock (no real waiting): events every 5 s and no reply fail with code 3 by 70 s; a reply at 69 s after events is taken; withreply_timeout=3it still ends by 3 s. The existing wait test runs on a stopped clock, so it still sees each command's whole wait (3610, 70, 15).Measurements
Verdict: the new checks fail on main and pass here; the suite has no new failures.
Hardware: none used (game-free checks on a Linux x86-64 PC, Python 3.14)
Scene: none, the game was not run
Method: 2026-11-14.
python3 -I -B tests/run_all.py, two runs each, interleaved (main, PR, main, PR). The new case also ran with main'sscripts/longshot_ctl.py. Branch:fix/ctl-reply-deadline.How to reproduce: the commands above in a checkout of each commit
1be60eaea847cd0ftests/run_all.py(run 1, run 2)test_bake_portability.py,test_walk_key.pytests/test_longshot_ctl.py, main's clienttest_bake_portability.pyfails here for a missing Python module (zstandard).test_walk_key.py's fixture doesn't link on main (undefined reference to hos_oot_autoload_input).Flags and settings
None.
Testing
Tested: the game-free suite on a Linux PC, as in Measurements.
Not tested: a client against a running game.
Must be tested: None.
Risks
What could break: #56 and #494 change other lines of
longshot_ctl.py.Turning it off: Reverting the commit brings the renewed wait back.
Compatibility: A command whose reply comes after its wait while events keep arriving now fails where it waited before; the waits themselves are unchanged (60 s plus 10, an hour for inputs, or the bridge's timeout plus 10).
watchreads events withline()itself and is unchanged.Checklist
Everyone
type(scope): description) of the status line's type.main.python -I -B tests/run_all.pypasses (any failure that also fails onmainis named).third_party/untouched (a new folder with aPROVENANCE.mdinstead).When it applies
mainand passing here.tests/that reads changed source updated in this PR, not weakened.WIP: fix(control): a command's reply wait ends at its deadline while events keep arrivingto fix(control): a command's reply wait ends at its deadline while events keep arrivingIndependent, targeted validation of your existing PR538 head
a847cd0fd05e482b0266eb07224f581ef9ab79cf, compared with pinned pre-fix main7ff5256e000671d0fccf27f77ba41275916b21df. The anonymous main/PR refresh at 2026-11-14 19:05 UTC still showed those commits and an open, unmerged PR.I replayed your exact two public method bodies (
test_input_waits_as_long_as_the_bridgeandtest_reply_wait_ends_at_its_deadline_while_events_arrive) in a fresh unittest.TestCase, using the complete unchanged client source for each revision. This deliberately bypassedLongshotCtl.setUpClass; it was not native full-module or full-suite execution.Two interleaved pairs (main, head, main, head) agreed:
[3610, 70, 15]on both.265 <= 70: the finite event stream ended at virtual 195 s and another 70 s was granted. That failure stops the method before its later reply/explicit-timeout subcases.Additional private controls exercised the real Bridge.request/send/line, with scheduled fake byte transport rather than overriding line: an already-buffered matching reply at a zero deadline; an event, unrelated reply and matching reply coalesced exactly at 70 s (with the trailing event retained); real-line no-reply timeout at 70; a reply at 71 after an event at 69 rejected at 70; explicit 3 s timeout on both revisions; and status/quit EOF behavior. All six private scenarios passed on head, twice. Main reproduced the two real-line deadline failures; buffering and EOF compatibility controls also passed on main.
Eight unique methods/scenarios, 32 method executions across four isolated Python processes: 26 passes and six expected baseline assertion failures, zero harness errors. Native fixture exits were
1, 0, 1, 0; complete receipts and byte/AST fingerprints are retained locally for review. All times above are virtual, not latency measurements. No production or public-test changes were made, no competing fix was started, and no real sockets, product processes, Windows pipe path, game, or full suite were exercised. This is scoped evidence, not full-PR approval.Assisted-by: Hermes Agent (OpenAI GPT-6.1-sol)
Co-Authored-By: Claudia (OpenAI GPT-6.1-sol)
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.