test(net): N0 - ACE-behaviour double, virtual clock, lossy link
Campaign N slice N0 (docs/plans/2026-07-29-network-transport-campaign.md): the referee that slices N1-N5 are graded against, test-project only, zero production changes. - VirtualClock: Stopwatch-shaped deterministic time source (fixed 100 ns ticks) that N1 will inject behind the production TransportClock. - AceCryptoModel: verbatim port of ACE CryptoSystem Search/ConsumeKey over our IsaacRandom - 256-key window, parked-key set, Headroom/OrphanCount diagnostics (CryptoSystem.cs:8-49 cited per method). - AceSessionModel: transport-free ACE NetworkSession over raw datagrams, every rule cited to NetworkSession.cs - CRC-before-everything silent drop, cleartext-NAK early return (no timeout refresh, :283-308), 60 s timeout refresh (:329-331), exact-equality ack dedup exemption (:342-347), desired+2 NAK trigger with 1 s limit (:351-363), >window AbnormalSequenceReceived (:393-397), the :474-476 watermark hole, ack-value cache prune (:663-673), fragment gate (:532-543), seq>=2 caching (:730), Retransmission-flag resends with the ORIGINAL IssacXor (:675-686), RejectRetransmit, 2 s cleartext cumulative ack, 20 s TimeSync, EchoResponse, 120 s cache prune (:251-262). ACE's raw wrap-unsafe comparisons are modeled bug-for-bug, not fixed. - LossyLink: deterministic drop/reorder/seeded-loss fault injector, pure data structure. - FakeAceTransport: IWorldSessionTransport binding a REAL WorldSession to the model through the link, with the handshake scripted (ConnectRequest reusing the negotiation fixture layout, CharacterList, ServerReady, logoff confirmation) - genuine Connect/EnterWorld/Tick/Dispose with no sockets. - 19 new tests pin the double, including CleartextNonAckAdvancesWatermark_TheAceHole (the self-induced wedge behind scope rows TS-57/TS-58/AP-125), re-key = permanent orphan, unrequested-resend window burn, the 115-id NAK cap boundary, and a full no-socket session lifecycle with both ISAAC streams verified aligned end-to-end. Core.Net suite: 678 passed / 0 failed (659 existing + 19 new). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
9ed43e27df
commit
7e9134b4d1
7 changed files with 2359 additions and 0 deletions
54
tests/AcDream.Core.Net.Tests/Transport/VirtualClock.cs
Normal file
54
tests/AcDream.Core.Net.Tests/Transport/VirtualClock.cs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
namespace AcDream.Core.Net.Tests.Transport;
|
||||
|
||||
/// <summary>
|
||||
/// Deterministic monotonic time source for transport tests. Exposes the same
|
||||
/// (timestamp, frequency) shape as <c>System.Diagnostics.Stopwatch</c>
|
||||
/// (<c>GetTimestamp()</c> + <c>Frequency</c>) so slice N1 can inject it
|
||||
/// behind the production <c>TransportClock</c> without adapting call sites.
|
||||
/// Time only moves when a test calls <see cref="Advance"/>.
|
||||
///
|
||||
/// <para>
|
||||
/// Deliberately dependency-free (System only) and NOT tied to the machine's
|
||||
/// <c>Stopwatch.Frequency</c>: a fixed 100 ns tick makes every gate
|
||||
/// computation reproducible across platforms.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
internal sealed class VirtualClock
|
||||
{
|
||||
/// <summary>
|
||||
/// Fixed tick rate: 100 ns ticks (10,000,000 per second), equal to
|
||||
/// <see cref="TimeSpan.TicksPerSecond"/> so <see cref="TimeSpan"/>
|
||||
/// arithmetic maps 1:1 onto clock ticks.
|
||||
/// </summary>
|
||||
public const long TicksPerSecond = TimeSpan.TicksPerSecond;
|
||||
|
||||
private long _timestamp;
|
||||
|
||||
public VirtualClock(long startTimestamp = 0) => _timestamp = startTimestamp;
|
||||
|
||||
/// <summary><c>Stopwatch.Frequency</c> equivalent.</summary>
|
||||
public long Frequency => TicksPerSecond;
|
||||
|
||||
/// <summary><c>Stopwatch.GetTimestamp()</c> equivalent.</summary>
|
||||
public long GetTimestamp() => _timestamp;
|
||||
|
||||
/// <summary>
|
||||
/// Seconds since the clock's epoch as a double — the shape of the
|
||||
/// retail/ACE PortalYearTicks-style wall values written into TimeSync
|
||||
/// payloads and the packet header's 16-bit <c>Time</c> field.
|
||||
/// </summary>
|
||||
public double Seconds => (double)_timestamp / TicksPerSecond;
|
||||
|
||||
/// <summary>Move time forward. The clock is monotonic — negative deltas throw.</summary>
|
||||
public void Advance(TimeSpan delta)
|
||||
{
|
||||
if (delta < TimeSpan.Zero)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(delta),
|
||||
"the clock is monotonic — it cannot go backwards");
|
||||
}
|
||||
|
||||
_timestamp += delta.Ticks;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue