fix: complete retail parity stability pass
This commit is contained in:
parent
d3df4cb20a
commit
f7aa8e0eb7
131 changed files with 7765 additions and 1190 deletions
|
|
@ -273,6 +273,9 @@ internal sealed class AceSessionModel
|
|||
public IReadOnlyCollection<uint> CachedPacketSequences => _cachedPackets.Keys;
|
||||
/// <summary>Packets silently dropped by CRC/Search failure (NetworkSession.cs:277-280).</summary>
|
||||
public int CrcDropCount { get; private set; }
|
||||
public uint LastCrcDropSequence { get; private set; }
|
||||
public PacketHeaderFlags LastCrcDropFlags { get; private set; }
|
||||
public uint LastCrcDropWatermark { get; private set; }
|
||||
/// <summary>Packets dropped by the duplicate-rejection rule (NetworkSession.cs:342-347).</summary>
|
||||
public int DuplicateDropCount { get; private set; }
|
||||
/// <summary>Packets dropped by the Session.CheckState gate (Session.cs:93-110) — before CRC.</summary>
|
||||
|
|
@ -337,6 +340,9 @@ internal sealed class AceSessionModel
|
|||
if (!VerifyCrc(packet))
|
||||
{
|
||||
CrcDropCount++;
|
||||
LastCrcDropSequence = packet.Header.Sequence;
|
||||
LastCrcDropFlags = packet.Header.Flags;
|
||||
LastCrcDropWatermark = _lastReceivedPacketSequence;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -239,7 +239,12 @@ public sealed class LossyTransportDecoratorTests
|
|||
// 256-key crypto window is intact at convergence.
|
||||
// =====================================================================
|
||||
|
||||
// The real WorldSession receive owner is deliberately part of this
|
||||
// end-to-end test. Under full-suite CPU contention its scheduling affects
|
||||
// when virtual-time server traffic reaches the frame thread, so run it in
|
||||
// the repository's serialized timing lane (#439).
|
||||
[Fact]
|
||||
[Trait("Lane", "Timing")]
|
||||
public void LossySession_FivePercentSeeded_ZeroMessageLoss_Headroom256()
|
||||
{
|
||||
var fake = new FakeAceTransport
|
||||
|
|
@ -338,7 +343,15 @@ public sealed class LossyTransportDecoratorTests
|
|||
+ $"dropped-in={lossy.InboundDropped} "
|
||||
+ $"resends={session.Transport!.Stats.ResendsSent} "
|
||||
+ $"naks-sent={session.Transport.Stats.NaksSent} "
|
||||
+ $"headroom={fake.Model.Crypto.Headroom}";
|
||||
+ $"headroom={fake.Model.Crypto.Headroom} "
|
||||
+ $"crc-drops={fake.Model.CrcDropCount} "
|
||||
+ $"last-crc-seq={fake.Model.LastCrcDropSequence} "
|
||||
+ $"last-crc-flags={fake.Model.LastCrcDropFlags} "
|
||||
+ $"last-crc-watermark={fake.Model.LastCrcDropWatermark} "
|
||||
+ $"dup-drops={fake.Model.DuplicateDropCount} "
|
||||
+ $"ooo={fake.Model.OutOfOrderPacketCount} "
|
||||
+ $"last-seq={fake.Model.LastReceivedPacketSequence} "
|
||||
+ $"cache={session.Transport.Outbound.CacheDepth}";
|
||||
|
||||
// Zero message loss, both directions.
|
||||
Assert.True(s2cReceived == MessagesEachWay, $"S2C loss: {ledger}");
|
||||
|
|
@ -354,8 +367,8 @@ public sealed class LossyTransportDecoratorTests
|
|||
|
||||
// ACE's crypto search window never eroded (no re-key, no
|
||||
// unrequested resend) — Headroom back to the full 256.
|
||||
Assert.Equal(256, fake.Model.Crypto.Headroom);
|
||||
Assert.Equal(0, fake.Model.Crypto.OrphanCount);
|
||||
Assert.True(fake.Model.Crypto.Headroom == 256, ledger);
|
||||
Assert.True(fake.Model.Crypto.OrphanCount == 0, ledger);
|
||||
Assert.False(fake.Model.IsTerminated);
|
||||
Assert.Equal(WorldSession.State.InWorld, session.CurrentState);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -392,6 +392,29 @@ public sealed class OutboundReliableTransportTests
|
|||
Assert.Equal(5u, wrapQueue.AckWatermark);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NewerAckOvertakingQueuedNak_SuppressesStaleEncryptedResend()
|
||||
{
|
||||
(OutboundFlowQueue queue, _, _, TransportStats stats,
|
||||
List<byte[]> sent) = CreateQueue();
|
||||
queue.SendGameMessage(MakeMessage(2), GameMessageGroup.UIQueue);
|
||||
queue.SendGameMessage(MakeMessage(3), GameMessageGroup.UIQueue);
|
||||
queue.SendGameMessage(MakeMessage(4), GameMessageGroup.UIQueue);
|
||||
|
||||
// The NAK for seq 2 is queued first. Before the frame sweep, a later
|
||||
// cumulative ACK proves the server has already advanced through 3.
|
||||
Nak(queue, 2u);
|
||||
queue.OnAckSequence(3u);
|
||||
sent.Clear();
|
||||
|
||||
queue.TransmitPendingResends();
|
||||
|
||||
Assert.Empty(sent);
|
||||
Assert.Equal(0, stats.ResendsSent);
|
||||
Assert.Equal(2, queue.CacheDepth); // watermark itself + newer seq 4
|
||||
Assert.Equal(0, queue.PendingResendCount);
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Conformance against the N0 ACE-behaviour double
|
||||
// =====================================================================
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
using AcDream.Core.Net.Transport;
|
||||
|
||||
namespace AcDream.Core.Net.Tests.Transport;
|
||||
|
||||
public sealed class RetailPacketLossAveragerTests
|
||||
{
|
||||
[Fact]
|
||||
public void Sweep_BeforeRetailHeartbeat_DoesNotPublishPartialSample()
|
||||
{
|
||||
long now = 0;
|
||||
var clock = new TransportClock(() => now, frequency: 10);
|
||||
var stats = new TransportStats { PacketsSent = 5, NakIdsSent = 1 };
|
||||
var sut = new RetailPacketLossAverager(clock, stats);
|
||||
|
||||
stats.PacketsSent += 10;
|
||||
stats.NakIdsSent += 2;
|
||||
now = 19;
|
||||
sut.Sweep(now, stats);
|
||||
|
||||
Assert.Equal(0d, sut.Percentage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Sweep_UsesRetailNakPlusRetransmitOverReceivedPlusSentFormula()
|
||||
{
|
||||
long now = 0;
|
||||
var clock = new TransportClock(() => now, frequency: 10);
|
||||
var stats = new TransportStats();
|
||||
var sut = new RetailPacketLossAverager(clock, stats);
|
||||
|
||||
stats.PacketsSent = 60;
|
||||
stats.PacketsReceived = 40;
|
||||
stats.NakIdsSent = 2;
|
||||
stats.ResendsSent = 3;
|
||||
now = 20;
|
||||
sut.Sweep(now, stats);
|
||||
|
||||
Assert.Equal(5d, sut.Percentage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Sweep_EvictsTheOldestSampleAfterFortyHeartbeats()
|
||||
{
|
||||
long now = 0;
|
||||
var clock = new TransportClock(() => now, frequency: 10);
|
||||
var stats = new TransportStats();
|
||||
var sut = new RetailPacketLossAverager(clock, stats);
|
||||
|
||||
stats.PacketsSent = 10;
|
||||
stats.NakIdsSent = 10;
|
||||
now = 20;
|
||||
sut.Sweep(now, stats);
|
||||
|
||||
for (int i = 0; i < RetailPacketLossAverager.WindowSize; i++)
|
||||
{
|
||||
stats.PacketsSent += 10;
|
||||
now += 20;
|
||||
sut.Sweep(now, stats);
|
||||
}
|
||||
|
||||
Assert.Equal(0d, sut.Percentage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Sweep_ZeroTrafficReportsZero()
|
||||
{
|
||||
long now = 0;
|
||||
var clock = new TransportClock(() => now, frequency: 10);
|
||||
var stats = new TransportStats();
|
||||
var sut = new RetailPacketLossAverager(clock, stats);
|
||||
|
||||
now = 20;
|
||||
sut.Sweep(now, stats);
|
||||
|
||||
Assert.Equal(0d, sut.Percentage);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue