ASRD → GNS gameplay transport - #1100
Closed
ywgATustcbbs wants to merge 2 commits into
Closed
Conversation
Contributor
|
No idea what you generated, but it's not Steam Datagram Relay (SDR). Instruction how to port existing code are here: |
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.
TL;DR
This PR replaces Source's legacy gameplay transport with GameNetworkingSockets (GNS) while preserving the existing Source message serialization, sign-on, handlers, game state, and gameplay logic.
The interception boundary is above
CNetChanpacketization: Source still creates and processes its normalINetMessageobjects, while GNS takes ownership of the underlying connection, reliability, ordering, retransmission, fragmentation, and transport lifecycle. The integration is implemented through validated runtime hooks and interface substitution; noengine.dllbinary file is modified.Current milestone
PASS: one client + one standalone dedicated server.
Validated on both local and real-network connections, including:
This is the current M1 milestone. It does not yet claim multi-client, listen-server, long-duration soak, or full production networking coverage.
Why GNS
This transport replacement provides a much cleaner foundation for future networking work:
CNetChan's legacy packet/reliability transport. The current implementation does not impose a custom bandwidth cap and uses GNS defaults, which also eliminates legacy Sourcechokebehavior on the replaced path.engine.dllfile.AI disclosure
For clarity about how this PR was produced:
The human role has primarily been defining requirements, architecture and constraints, making design decisions, directing implementation/review agents, and performing real gameplay validation.
Test setup
Copy the following files into the corresponding client/server game directories, replacing the existing files where applicable:
Example standalone dedicated-server launch command:
-gns-port 27015specifies the UDP port that GNS binds for gameplay transport.The normal Source
-portand the GNS-gns-portare intentionally separate in this example.ASRD → GNS gameplay transport
Why
The project needs GameNetworkingSockets (GNS) to own the gameplay transport while keeping Alien Swarm: Reactive Drop's existing Source message and game-state semantics intact. Replacing the transport at that boundary avoids rewriting the Source serialization, dispatch, sign-on, and gameplay code, and gives the project one place to define connection ownership, delivery lanes, retransmission, MTU handling, and lifecycle cleanup.
This is also intended to make unreliable delivery behavior deterministic under loss and reordering. The reorder queue now has a wall-clock bound, and automatic window selection compares independently measured candidates instead of stopping at the first window that happens to cross a delivery threshold. The change removes success-path log spam while retaining diagnostics that are useful for failures and lifecycle transitions.
What changed
asrd_gns_wrapperABI. Client/server game DLLs depend only on the wrapper header and import library; GNS, protobuf, Abseil, and utf8 support are statically linked into the wrapper. The target remains Win32/x86 and uses Windows BCrypt/CNG, Winsock, WinMM, and IP Helper rather than a separate OpenSSL orGameNetworkingSockets.dllruntime dependency.INetMessageobjects are serialized into a versioned envelope, transported over GNS, then decoded and passed back through SourceReadFromBufferandProcess.INetChannelsend surface, and a dedicated-server hibernation wake point. These are the minimum interception points needed to move transport ownership without replacing Source message handlers or game logic.engine.dllbuild. Every mismatch fails closed before an unknown target is patched or called.The Source upper layer is deliberately not replaced: message serialization, Source handlers, player/session context, sign-on calls, and game-thread dispatch remain the compatibility surface. GNS owns transport and connection identity, not the meaning of Source messages.
Architecture
Hook and binding boundary: the minimum engine cut
The chosen boundary is immediately after Source has created a semantic network message, but before
CNetChanturns it into legacy packets. On receive, the boundary is immediately after GNS delivery/reordering, but before Source's existingReadFromBufferandProcesshandlers. This is the smallest cut that transfers connection and transport ownership while retaining Source sign-on, message objects, handler state, user-command processing, entity updates, and game rules.connectconsole command.RegisterMessageimplementation, calls the original, then records the liveINetMessage, channel, and handler context.INetChanneladapter is supplied throughConnectionStart; it is temporarily bound for dispatch and promoted to the lifecycle channel while connected. Server-side Source context receives the corresponding adapter duringConnectionStart/ClientConnect.SendNetMsg, rawSendData,SendDatagram,Transmit, and shutdown/lifecycle calls before legacy packet construction.ReadFromBuffer, then callsProcess; client updates are bracketed by the validatedPacketStart/PacketEndcall-ins.SV_Thinkcall-site wrapper pumps only the GNS control frame while the dedicated server is hibernating, then invokes the original target exactly once.Only the connect, registration, and dedicated wake entries are binary detours. Message send interception is primarily an interface substitution: existing Source code continues calling
INetChannel, but the session's channel object is the GNS adapter. Lifecycle methods such asConnectionStart,ClientConnect,SetSignonState,PacketStart,PacketEnd, andClientDisconnectare validated engine call-ins rather than wholesale replacements.Why CNetChan is bypassed instead of hooking UDP
CNetChanis not just a UDP serializer. It owns the legacy connection state, sequence and acknowledgement space, reliable buffering/retransmission, choking, fragmentation, timeouts, and packet receive state machine. Keeping it below GNS would leave two transports trying to own ordering, reliability, fragmentation, timeout, and lifecycle. Reliable Source packets tunneled through reliable GNS messages would also create nested reliability and head-of-line behavior, while nested fragmentation would make MTU and recovery behavior harder to reason about.A lower socket or UDP hook is therefore too late in the pipeline. At that point the semantic message type, effective reliability, voice/snapshot provenance, and Source update boundaries have already been flattened into legacy datagrams. A packet hook could only tunnel those opaque datagrams and would still require the legacy handshake and
CNetChanreceive state to remain authoritative. That would preserve the old transport inside GNS rather than replace it, and it would prevent the bridge from mapping Source messages to GNS lanes or applying message-aware unreliable reordering.The adapter instead implements only the channel surface actually required by Source's upper layer.
SendNetMsg,SendData, andSendDatagramare converted into GNS envelopes before legacy packetization; GNS owns the connection, lanes, acknowledgements, retransmission, and MTU behavior. On receive, the captured Source handlers are invoked directly on the game thread. This keeps the hook surface small, auditable, and fail-closed while avoiding parallelCNetChanand GNS transport state.Connection and lifecycle
On the client, the GNS connect-intent path parses and normalizes a real IPv4 endpoint, retires any previous generation, binds the current Source context, and starts a wrapper connection. Once GNS reports a connection, a registration adapter captures the engine's message registry and promotes the temporary channel adapter to the persistent lifecycle owner. A stale connection generation cannot deliver events into a newer session. Deferred disconnect closes the GNS handle before the normal Source disconnect path is queued.
On the server, the wrapper listens, queues incoming/connected/failed/closed events, and lets the game-thread control frame accept and map them to Source context. Source sign-on is finalized before the real-player transition. Dedicated mode has a dedicated-only wake/control pump so hibernation does not prevent lifecycle processing; listen-server role activation is kept separate from the dedicated path. Terminal events tear down the Source context and the GNS mapping together. The current mapping policy rejects duplicate/unsupported extra gameplay mappings rather than silently sharing a session.
Wrapper callbacks do not call Source code. They enqueue opaque events under the wrapper's synchronization and are polled from the engine/game thread, which keeps transport-thread work independent of Source object lifetime and dispatch rules.
Message transport
Each envelope carries direction, message type, reliability/provenance, flags, and sequence metadata. There are three send lanes:
R);U_REALTIME); andU_NORMAL).The two unreliable lanes share a session-local global sequence gate; reliable traffic is not placed in that gate. Callback provenance and effective reliability choose the lane without changing the Source message's reliability meaning. GNS supplies lane ordering, acknowledgements, retransmission, and MTU fragmentation. Received payloads are drained and dispatched only on the game thread.
The CLC_Move compatibility layer reads the move body at the bit level, classifies first/contiguous/gap/stale/partial-overlap ranges, and computes the existing drop metadata. It does not replace Source's move processing or invent a second command namespace.
Reorder and timeout behavior
The physical reorder ring is capped at 128 slots, with logical candidates
16, 32, 48, 64, 80, 96, 112, 128. Every packet records its first-arrival wall-clock timestamp. The singlereorder_deadlineis 30 ms for the real ring, the current-window model, and every candidate simulator.The receive epoch is only a safe boundary for checking timeout. Each receive pass first drains the GNS queue completely (until receive returns zero), dispatches the reliable traffic, and only then checks timeout. If progress is needed, the code scans occupied slots from the logical tail toward the head, selects the farthest expired slot, pops from the head through and including that slot, and immediately performs another continuous head drain. No timer or timeout thread is added, and timeout is no longer based on an extra epoch count.
Automatic window selection
Every candidate and a separate
D_currentmeasurement use the same generated traffic, loss/reordering model, 30 ms deadline, warm-up rule, eligible sequence range, and measurement period. The formal metrics are:delivered packets / eligible packetsdelivery rate.The implementation uses a fixed 1 ms/256-bin residence histogram and bounded rolling counters rather than sorting samples or allocating per packet. The project baseline supplies
D_min = 98%; the selector does not derive that threshold from the candidates.Selection is deterministic:
D_min, choose window 32 immediately and do not optimize latency.D_min - 0.5%enter latency comparison.D_currentis used only for final stability/no-op decisions and is never substituted for a candidate or used to computeD_best.The former “first window reaching 98%”, “choose the next window”, and epoch-count timeout rules are not retained alongside this selector.
Compatibility boundaries
engine.dllbuild for which the expected PE metadata, RVAs, signatures, and instruction bytes are recorded. Starting the client, issuingconnect, sign-on, map transition, gameplay, and mission completion passed the M1 run. Endpoints must be real/non-loopback IPv4 addresses; the client must not connect to127.0.0.1. Generic or mismatched engine builds fail closed.rd_server_shutdown_when_emptyConVar, whose default is0; this PR does not claim an automatic 60-second exit when that setting is disabled.net_graphcurrently crashes the client and must remain disabled. The precise failing access is not yet isolated; the leading compatibility concern is that this diagnostic path expects concreteCNetChanstate/layout beyond the minimalINetChanneladapter contract.engine.dllfile is modified. Hook installation is process-wide, role-aware, idempotent, and fail-closed on build/signature/expected-byte mismatch, with byte/protection restoration on failure. Coexistence with arbitrary third-party binary hooks has not been runtime-verified; a collision is expected to disable the takeover rather than patch an unknown image.Verification
Static gates
The root contract suite passes:
The existing Python harness performs a Release Win32 rebuild of
reactivedrop_vs13.sln:The recorded build completed with exit code 0 and 0 errors. Warnings remain in the legacy/third-party build; they do not change the 0-error gate result.
Static coverage includes sequence-generation and retirement behavior, rolling-stat eviction/reset, selector thresholds, timeout ordering, packet ownership, and reliable/unreliable ring exclusion. The source branch is represented by consolidated commit
f772d83559b151fa433652f488c7571e6b5e6c17, whose parent is base564fb7568ae9d7da7f175a778b9378acb0017613.Runtime evidence
The M1 runtime test used the real client and a standalone dedicated server. It completed the following path:
connectconsole commandWithin this client/dedicated-server scenario, connection ownership, the message hook boundary, Source dispatch, map transition, and sustained gameplay were stable enough to complete M1. This is the technical validation milestone for the replacement approach. It does not extend the claim to listen servers, multi-client load, arbitrary engine builds, every diagnostic feature, or extended soak/stress testing.
Known limitations and follow-ups
net_graph. Until the exact access is isolated and supported,net_graphmust remain disabled; the minimal adapter is not claimed to reproduce the complete concreteCNetChandiagnostic surface.rd_server_shutdown_when_emptysetting or receive a separate GNS-specific policy; the current default does not start the 60-second grace period.engine.dllbuild unless new independent RVA/signature evidence is added; do not assume compatibility with other engine revisions or arbitrary hook combinations.asrd_gns_smoke_probeand its project entries before final delivery.