Campaign N slice N4 completes the AckNakScheduler NAK branch and closes the ACE cleartext-reject keystream hazard - the slice that makes S2C loss actually RECOVER. NAK emission (SharedNet::EnqueueNaks @ 0x00543BD0): - One cleartext exact-flags RequestRetransmit per sweep behind the STRICT 0.6 s gate on the ONE shared timestamp (the x87 0x41-mask test at 0x00543C03 proceeds only on strictly-greater; the ack's gate stays >=). Never an ack in a NAK sweep; a NAK delays the next ack by 2.0 s and vice versa (landmine #7). - Body = u32 count + ids ascending, capped at 114 (ReceiverData::GetNaks @ 0x005490C0, cap 0x72; the m_cbData = 4*count+4 store at 0x00543C3E); header Sequence borrowed from highestIDSent_ without incrementing; cleartext or ACE ignores it (landmine #6, NetworkSession.cs:283-284) - and a NAK never refreshes ACE's 60 s timeout. - Control-header rule decided once for BOTH ack and NAK: Time = the interval id, Iteration = the session iteration, matching retail's shared header build (FlowQueue::TransmitNewPackets @ 0x00547A60, the stack build at 0x00547A84). ACE reads neither field inbound. - Gate ticks now round instead of truncate: 0.6 has no exact double form, and truncation opened the strict gate exactly AT the boundary. RejectRetransmit reclaim (divergence register AD-51, ACE adaptation): - ACE's RejectRetransmit consumes a FRESH sequence, cleartext, with NO keystream word, and is cached (ACE NetworkSession.cs:299-304, :722-725, :743-748) - the one place ACE breaks retail's gap-walk invariant that every missing id was word-bearing (retail cleartext always borrows live sequences). Unhandled, the gap walk parks a word for the reject's id and the inbound stream runs permanently one word ahead - the N2 desync class reintroduced through the reject path. - Fix: on a VALIDATED cleartext reject, InboundSequenceTracker removes the mis-park, shifts every later-drawn parked word down one position (per-word draw ordinals; ascending wrap-safe id <=> ascending draw order), and pools the excess word, consumed lowest-draw-order-first ahead of fresh ISAAC draws. Exact for any number of interleaved rejects in ANY arrival order - a plain reclaim FIFO is not: a reject arriving after a higher encrypted arrival crosses the parked chain, and two out-of-order rejects pool their excess words out of draw order (both orderings pinned by tests). - Reject BODY ids keep N2's discard: word-bearing server-side, consumed-in-place. The pool is provably empty against retail servers. N3 advisories folded (all five): honest transitional-state wording (the empty N3 NAK branch could silently disconnect a loopback session at ACE's 60 s timeout, witness [net-tick] acks/s=0), the ReceiverData::SharedInit @ 0x00548EF0 (from Init @ 0x00548FA0) citation, the FlowQueue::Empty pump-order wording (TransmitNaks -> TransmitAcks -> TransmitNewPackets with the interval increment LAST @ 0x00548A9D; our clock-first Sweep is cosmetic vs ACE), the Time/Iteration rule above, and the stale WorldSession budget-break comment rewritten to the sweep reality. Tests: 737 Core.Net green (14 new in NakEmissionTests + updated N3 pins): strict-gate boundary, shared timestamp both directions, NAK-xor-ack exclusivity, full wire-shape + 114-cap pins, model-served retransmission round trip, five tracker reclaim proofs, the 130 s virtual prune -> fresh-sequence reject system test (victim abandoned, later traffic decodes, pool drains to zero), 10 s long-loss survival (NAKs on the gate cadence, zero acks, heal inside the window), and the capstone soak: 2% seeded bidirectional loss x 10,000 messages -> zero message loss both ways, ACE crypto headroom 256 at convergence, every ledger drained (cache at the single watermark entry - retail's Flush prunes STRICTLY below the ack). Full solution Release: 9,758 passed / 5 skipped. Connected world-lifecycle gate PASS (logs/connected-world-gate-20260729-150238); canonical nine-stop soak PASS (logs/connected-r6-soak-20260729-150856). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
73 lines
3.3 KiB
C#
73 lines
3.3 KiB
C#
namespace AcDream.Core.Net.Transport;
|
|
|
|
/// <summary>
|
|
/// Unconditional reliable-transport counters. Increment sites are NOT
|
|
/// probe-gated — the counters are plain field writes and always current, so
|
|
/// a later probe (N5's <c>[net-tick]</c> extension) or a connected gate can
|
|
/// read them without having been armed in advance. Printing stays
|
|
/// probe-gated at the call sites that choose to surface them.
|
|
/// </summary>
|
|
internal sealed class TransportStats
|
|
{
|
|
/// <summary>Datagrams re-emitted in response to a server NAK.</summary>
|
|
public long ResendsSent;
|
|
|
|
/// <summary>Inbound packets carrying <c>RequestRetransmit</c>.</summary>
|
|
public long NakRequestsReceived;
|
|
|
|
/// <summary>
|
|
/// NAKed ids no longer (or never) in the sent-packet cache, dropped
|
|
/// silently instead of answering retail's <c>RejectRetransmit</c>
|
|
/// (divergence register TS-57 — ACE no-ops the reject, and the
|
|
/// standalone unsequenced form would trip ACE's watermark hole).
|
|
/// </summary>
|
|
public long UncachedNakIds;
|
|
|
|
/// <summary>Inbound <c>AckSequence</c> values folded into the watermark
|
|
/// (explicit acks plus the NAK <c>ids[0]</c> implicit ack).</summary>
|
|
public long AcksConsumed;
|
|
|
|
/// <summary>N2: inbound duplicates of already-decoded packets, dropped
|
|
/// at zero keystream cost (encrypted, at/below the watermark, no parked
|
|
/// key — <c>ProcessNewSeqNum @ 0x00544690</c>).</summary>
|
|
public long InboundDupsDropped;
|
|
|
|
/// <summary>N2: inbound packets past the wrap-safe
|
|
/// <c>watermark + 0x7FFF</c> horizon
|
|
/// (<c>SeqIDSanityCheck @ 0x00543A20</c>).</summary>
|
|
public long InboundSanityDrops;
|
|
|
|
/// <summary>N2: inbound packets whose checksum failed verification
|
|
/// after admission (a sequenced encrypted failure also re-parks its
|
|
/// consumed key for the retransmission).</summary>
|
|
public long ChecksumFailures;
|
|
|
|
/// <summary>N2: inbound keystream words parked in the NAK set — one per
|
|
/// gap-walked missing id (<c>ReceiverData::AddNakked @ 0x00549240</c>
|
|
/// pre-draw) plus one per checksum-failure re-park.</summary>
|
|
public long KeysParked;
|
|
|
|
/// <summary>N3: cumulative <c>AckSequence</c> packets emitted by the
|
|
/// 2.0 s sweep (<c>SharedNet::EnqueuePak @ 0x00543B10</c> — retail's
|
|
/// only ack construction site; there is no per-packet ack).</summary>
|
|
public long AcksSent;
|
|
|
|
/// <summary>N4: <c>RequestRetransmit</c> packets emitted by the 0.6 s
|
|
/// NAK branch (<c>SharedNet::EnqueueNaks @ 0x00543BD0</c> →
|
|
/// <c>ReceiverData::GetNaks @ 0x005490C0</c>, ≤114 ids each).</summary>
|
|
public long NaksSent;
|
|
|
|
/// <summary>N4: mis-parked keystream words reclaimed from validated
|
|
/// cleartext <c>RejectRetransmit</c> sequences (the AD-51 ACE
|
|
/// adaptation; always zero against a retail server).</summary>
|
|
public long RejectWordsReclaimed;
|
|
|
|
/// <summary>Live sent-packet cache depth — the N5 watchdog value
|
|
/// (<c>cache=N</c> in <c>[net-tick]</c>; the cache is unbounded like
|
|
/// retail's, so depth is the health signal, not a cap).</summary>
|
|
public int CacheDepth => CacheDepthSource?.Invoke() ?? 0;
|
|
|
|
/// <summary>Wired by <see cref="ReliableTransport"/> to the store's
|
|
/// <c>Count</c>.</summary>
|
|
internal Func<int>? CacheDepthSource { get; set; }
|
|
}
|