Repository navigation
feat(network): Control arrival cushion and starting runahead, and start the match in sync - #664
Open
tintinhamans wants to merge 12 commits into
Conversation
tintinhamans
force-pushed
the
arctic/go/stack2/09-runahead-controller
branch
2 times, most recently
from
October 25, 2026 11:15
a3681d2 to
e3feda5
Compare
tintinhamans
force-pushed
the
arctic/go/stack2/09-runahead-controller
branch
2 times, most recently
from
October 26, 2026 15:39
4c5132e to
eb5d0a6
Compare
…and forwarded traffic
tintinhamans
force-pushed
the
arctic/go/stack2/09-runahead-controller
branch
from
October 28, 2026 01:47
eb5d0a6 to
3eb6426
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Merge after #663.
A latency formula sizes the run ahead from a round trip, but what stalls the game is whether the commands of the slowest sender arrive before the frame that needs them. The formula also has to guess a start value, and after a loss burst it kept the run ahead high for a long time.
With
input_delay.controller(true) the run ahead follows the arrival cushion instead. Every player reports its lowest cushion per sender with its late commands and stall time, and the packet router sums the two directions of each pair and keepsinput_delay.margin_frames(1) spare per direction. A baseline from a forgetting quantile of the normal windows is raised only for spikes that recur. Withinput_delay.fast_recovery(true) the run ahead falls a third of the excess per step after an isolated spike.input_delay.max_frames(64, retail 128) caps it. A slow producer is left out, an outage holds the run ahead, and without a complete pair the formula of the previous PR takes over. Stalls from a screenshot capture do not count: the hold runs up to 15 s from the capture of a gameplay screenshot and again from the start of its delayed upload, 2 s past the end of the upload, and 3 s for the other screenshot types. At match start the router decides the run ahead from every player's lobby round trip, and the load waits for it.With the controller on, nothing in the game said why the input delay moved, so a log was needed to tell a lag spike from a stall from a start value. The network overlay header now shows whether the input delay is rising, lowering or steady for 10 s after a change, and why (start, lag spike, stall, clean). In the developer view (Shift+F10) the router also sees the controller's last decision: its reason (seed, hold, baseline, cover, uncover, congestion, outage or formula), the lowest pair sum against the
2 x margin_framesit needs and which pair that is, the baseline and cover, the spike rate per minute and median spike size, and the time since the last change. Other players see the last change and "host decides", and with the controller off it says "controller off". The cushion column also turns amber when the 5th percentile cushion is within the margin.The match start is synchronized on the new
NETWORK_CHANNEL_START. Players send READY at two sync points: on the load screen once loading is done (waiting up to 3 s for the direct path), and after local setup with the load screen still up. The router answers with GO and a per-peer delay from the seed latency (at most 250 ms), each stage times out after 10 s, players that were lost are left out, and it is online only. A GO is only accepted from the router, also when it arrives before the router is known. The stock fade-in from black advances only on logic frames, so a start stall showed a black screen.Wire change: the run ahead metrics message carries the cushion, late count, stall ms and screenshot flag, and the start channel is new. Trade-off: the controller returns to a low run ahead quickly when loss ends, which costs more stall time if loss returns. In the returning-loss rig run the old code stayed at R 56 to 64 and this went back to about 7, but stalled about 30 s of 110 s against 18.7 s. The synchronized start costs about 60 to 80 ms per stage, and relay-only players add up to 3 s.
controllerandfast_recoveryare on by default as in the tester build, and the service can turn them off.Tested: builds x86 Release
z_generals. Rig runs of the full stack, 3 players, 0 CRC mismatches, n=1. On a slow CPU-bound host R stayed at 4 to 5 instead of hitting the old 64 cap. Start sync rig: frame-1 skew went from 1.5 to 2 s down to 6 to 15 ms, no start stalls, the fade takes 0.6 s on both.