ReliableChannel.cs here is a verbatim copy of the file Basis ships:
Packages/com.basis.server/LiteNetLib/ReliableChannel.cs
from a Basis Unity checkout. It is not edited. Everything else in this directory is a thin stub so that one file can be compiled and driven on its own, which lets us generate the authoritative ACK bytes instead of reasoning about what they should be.
LiteNetLib's ACK encoding is not what it looks like at a glance, and getting it wrong is a silent stall rather than a visible error:
- ACK bits are absolute -- bit
sequence % 128. The window start in the header is not an offset base; the receiving peer uses it only to bound-check the packet (ReliableChannel.ProcessAck). - The window slides only when a packet arrives from beyond it, never as contiguous packets arrive. A retransmit of anything still inside the window therefore re-sets its bit and is acknowledged again.
The second point is what makes an over-eager "improvement" so damaging. Re-anchoring the bit set on the newest sequence looks equivalent and is not: once the anchor moves past a sequence, no ACK the receiver can still send covers it, so the sender's oldest unacknowledged packet is stranded for good. The Rust client did exactly this at one point and 117 of 5000 packets stuck in flight.
dotnet run --project scripts/interop
Program.cs drives the real channel through a set of scenarios and prints the ACK datagrams
it puts on the wire. S1-S5 are encode vectors; S6 is the decode direction, and it is
deliberately discriminating: the same coverage of sequences 64..=127 is offered twice, once
with absolute bits and once with window-relative bits. The real C# drains on the first and
releases nothing on the second. S7 drops sequence zero, checks that the first sequence-one
ACK stays anchored at window zero, then verifies recovery when zero arrives.
The Basis fork also has a separate SequencedChannel: reliable-sequenced ACKs are exactly the
4-byte packet header, and the sequence field acknowledges the receiver's latest sequence. The
transport has focused tests for that shape, duplicate/old updates, sequence wrap, and sender-side
retirement. These checks mirror the vendored SequencedChannel.cs behavior; the small interop
program compiles only ReliableChannel.cs.
In the local validation environment, dotnet was unavailable and Unity's Mono compiler could not
compile Program.cs's newer C# syntax. The first-loss vectors were therefore rechecked using a
temporary Mono harness around the verbatim ReliableChannel.cs and its existing stubs. This is a
direct channel test with stub transport, not a live Rust/C# socket session.
The expected values live in
crates/basis-transport/src/lib.rs::outgoing_acks_match_golden_vectors_from_the_csharp and
::decoding_matches_what_the_csharp_accepts. If Basis changes its reliable layer, copy the
new ReliableChannel.cs over this one, re-run, and update those two tests -- they will tell
you exactly what moved.
copy /Y "<BasisUnity>\Basis\Packages\com.basis.server\LiteNetLib\ReliableChannel.cs" scripts\interop\
If the real file needs new types to compile, add them to Stubs.cs -- do not edit
ReliableChannel.cs, or the vectors stop being authoritative.