diff --git a/src/Prospect.Unreal/Net/UNetConnection.cs b/src/Prospect.Unreal/Net/UNetConnection.cs index 358070a..36674fc 100644 --- a/src/Prospect.Unreal/Net/UNetConnection.cs +++ b/src/Prospect.Unreal/Net/UNetConnection.cs @@ -267,6 +267,8 @@ public abstract class UNetConnection : UPlayer /// /// Full incoming packet index. /// + private bool _bAdoptedInitialSequence; + public int InPacketId { get; private set; } /// @@ -540,6 +542,25 @@ public abstract class UNetConnection : UPlayer } var packetSequenceDelta = PacketNotify.GetSequenceDelta(header); + + // The Cycle client doesn't derive its initial packet sequences from the handshake + // cookie the way stock UE does, so our cookie-based InitSequence disagrees with it + // and every packet looks out-of-order. On the very first packet, adopt the client's + // announced sequences (Seq + AckedSeq) instead of the cookie-derived ones. + if (packetSequenceDelta <= 0 && !_bAdoptedInitialSequence && Driver != null && Driver.IsServer()) + { + _bAdoptedInitialSequence = true; + var adoptIn = new SequenceNumber((ushort)(header.Seq.Value - 1)); + var adoptOut = new SequenceNumber((ushort)(header.AckedSeq.Value + 1)); + PacketNotify.Init(adoptIn, adoptOut); + InPacketId = header.Seq.Value - 1; + OutPacketId = header.AckedSeq.Value + 1; + OutAckPacketId = header.AckedSeq.Value; + LastNotifiedPacketId = OutAckPacketId; + Logger.Information("[HS-SEQ] Adopted client sequences: inSeq={In} outSeq={Out}", header.Seq.Value, header.AckedSeq.Value + 1); + packetSequenceDelta = PacketNotify.GetSequenceDelta(header); + } + if (packetSequenceDelta > 0) { var bPacketOrderCacheActive = !_bFlushingPacketOrderCache && _packetOrderCache != null;