docs(net): Campaign N implementation complete - closeout status, encoding repair

All seven slices shipped and reviewed. The campaign doc status header and ISSUES.md #260 now record the implementation-complete state with every slice SHA; the campaign doc's double-encoded punctuation (one early PS5.1 ANSI round-trip) is repaired to clean UTF-8. Remaining acceptance: the user Coldeve endurance session.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-29 17:35:38 +02:00
parent 27c5151189
commit 5872826a13
2 changed files with 79 additions and 69 deletions

View file

@ -121,7 +121,14 @@ and the indicator shows non-zero loss under `tools/run-connected-loss-gate.ps1`.
## #260 — Portal-network wedge: outbound actions die + native/GPU memory climbs
**Status:** ROOT-CAUSED 2026-07-29 — **fix in flight as Campaign N**
**Status:** FIX IMPLEMENTED 2026-07-29 — **Campaign N slices N0N6 all
shipped and reviewed** (outbound resend `43e60a69`, inbound
sequence-aligned ISAAC `46d209d0`, retail ack cadence `0265cc42`, NAK
emission + reclaim `852a59e3`, the permanent loss gate `4e290f00`,
handshake/assembler hardening `f9c5e47e`). The loss gate observes real
injected loss recovering in both directions with the cipher ledger
converged. CLOSES when the user's Coldeve endurance session passes the
historic 15-minute wedge horizon. Original root-cause record below.
([`docs/plans/2026-07-29-network-transport-campaign.md`](plans/2026-07-29-network-transport-campaign.md)).
The wedge is missing packet-loss recovery in BOTH directions: (a) no outbound
retransmission — ACE's `RequestRetransmit` lists are parsed and consumed

View file

@ -1,6 +1,9 @@
# Campaign N — Retail-Faithful Network Transport
# Campaign N — Retail-Faithful Network Transport
**Status: ACTIVE (approved 2026-07-29). Slices N0–N6.**
**Status: IMPLEMENTATION COMPLETE 2026-07-29 — all seven slices (N0N6)
shipped and reviewed. Remaining acceptance: the user Coldeve endurance
session (§8 rung 4) — play past the historic 15-minute wedge horizon with
portal churn; the `[net-tick]`/`[net-final]` counters are the evidence.**
acdream cannot survive a single lost UDP packet in either direction. This
campaign ports retail's reliable-transport mechanism (the Sept 2013 named
@ -15,8 +18,8 @@ Read this before touching `src/AcDream.Core.Net/`.
1. **Outbound (the #260 wedge):** no sent-packet cache, no resend. ACE NAKs a
client-sequence gap with `RequestRetransmit`; we parse the list and consume
it nowhere. One lost C2S datagram → ACE buffers everything after the gap
forever → all actions void, position updates void → ACE stops streaming new
it nowhere. One lost C2S datagram ACE buffers everything after the gap
forever → all actions void, position updates void → ACE stops streaming new
areas (the #256 "invisible portals") while the session stays alive on
cleartext acks. Reproduced twice on Coldeve 2026-07-29 with the
`ACDREAM_PROBE_NET` probe (`artifacts/coldeve-probe-20260729/`, local).
@ -25,7 +28,7 @@ Read this before touching `src/AcDream.Core.Net/`.
shifts the keystream permanently; every later encrypted packet fails
checksum and burns another word. Inbound silent forever.
Loopback ACE never drops packets — every historical gate was structurally
Loopback ACE never drops packets every historical gate was structurally
blind to both bugs. Slice N5's loss gate removes that blindness permanently.
## 2. The retail mechanism (port target)
@ -39,89 +42,89 @@ outbound = `RecipientData` + `ClientFlowQueue` + `SentPacketStore` (under
### 2.1 Outbound resend
- Every reliable packet (fragments aboard) is cached AFTER a successful send
(`FlowQueue::TransmitNewPackets @ 0x00547A60` → `SentPacketStore::
(`FlowQueue::TransmitNewPackets @ 0x00547A60` `SentPacketStore::
AddSentPacket @ 0x0054AB00`), with disposable optional headers stripped
first (`NetPacket::RemoveDisposableOptionalHeaders @ 0x00549510`). Under
our standalone-control design (§4) reliable packets never carry optional
headers, so the strip is a provable no-op — assert `optionalLength == 0`
our standalone-control design (§4) reliable packets never carry optional
headers, so the strip is a provable no-op assert `optionalLength == 0`
in `SentPacketStore.Add`.
- Server `RequestRetransmit` (0x1000) → merge-insert ids wrap-safe sorted
- Server `RequestRetransmit` (0x1000) merge-insert ids wrap-safe sorted
(`FlowQueue::EnqueueAcks @ 0x005488E0`); `ids[0]` doubles as an implicit
cumulative ack (`RecipientData::ProcessNaks @ 0x00547010`). Ids no longer
cached → retail answers `RejectRetransmit`; we drop silently (row TS-57).
cached retail answers `RejectRetransmit`; we drop silently (row TS-57).
- Resend (`FlowQueue::TransmitAcks @ 0x005485B0` / `DequeueAck @
0x005472F0`): re-emit with a REBUILT 20-byte header — flags
0x005472F0`): re-emit with a REBUILT 20-byte header flags
`Retransmission|EncryptedChecksum` (=3; |`BlobFragments` =7 with
fragments), `Time` = the CURRENT interval id, `Sequence`/`DataSize`
verbatim, checksum = fresh header hash + stored sealed checksum. Body
bytes untouched; **the original ISAAC key is reused** (`CryptoSystem::
EncryptData @ 0x0065FF40` takes the key as an optional in/out — null draws,
EncryptData @ 0x0065FF40` takes the key as an optional in/out null draws,
non-null reuses). Never a new keystream word.
- `AckSequence` (0x4000) inbound → wrap-safe max watermark → prune strictly
- `AckSequence` (0x4000) inbound → wrap-safe max watermark → prune strictly
older (`SentPacketStore::Flush @ 0x0054ACD0`, pops from FIFO head while
`seqNum < watermark`). No timer-based resend exists — resend only on
`seqNum < watermark`). No timer-based resend exists resend only on
explicit NAK.
- Sequence allocation: `highestIDSent_` starts 1; `++`; wrap 0xFFFFFFFF → 1
- Sequence allocation: `highestIDSent_` starts 1; `++`; wrap 0xFFFFFFFF 1
(never 0). Unsequenced packets reuse the current watermark, cleartext, no
ISAAC word.
- Pump order per frame (`FlowQueue::Empty @ 0x00548A20`): NAKs → retransmits
→ new packets.
- Pump order per frame (`FlowQueue::Empty @ 0x00548A20`): NAKs retransmits
new packets.
### 2.2 Inbound sequencing + ISAAC discipline
**No transport reorder buffer** — packets process on arrival
(`SharedNet::ProcessPacket @ 0x00544790` → `ProcessNewSeqNum @ 0x00544690`):
**No transport reorder buffer** packets process on arrival
(`SharedNet::ProcessPacket @ 0x00544790` `ProcessNewSeqNum @ 0x00544690`):
1. `SeqIDSanityCheck @ 0x00543A20`: drop if seq is newer than
`highestIDReceived_ + 0x7FFF` (wrap-safe).
2. Encrypted + not newer than the watermark = duplicate/late arrival: remove
from the NAK set — hit → decrypt with the **parked pre-drawn key**;
miss → drop silently at zero keystream cost.
3. Newer than the watermark → gap walk (`ProcessNewestSeqNum @ 0x00541930`):
for each missing id (skip 0) `AddNakked(id, null)` — **pre-draws one
from the NAK set — hit → decrypt with the **parked pre-drawn key**;
miss drop silently at zero keystream cost.
3. Newer than the watermark gap walk (`ProcessNewestSeqNum @ 0x00541930`):
for each missing id (skip 0) `AddNakked(id, null)` **pre-draws one
inbound ISAAC word per missing id, in sequence order, BEFORE the arriving
packet's own key**, parking it beside the id (`ReceiverData::AddNakked @
0x00549240`, idempotent). Cleartext packets walk to `seq+1` (they borrow
an already-delivered sequence). Then watermark = seq.
4. Encrypted packets then decrypt with the parked key or the next drawn word.
5. Parse failure on a sequenced packet → re-park its consumed key
5. Parse failure on a sequenced packet re-park its consumed key
(`AddNakked(seq, &key)`), so the retransmission decodes.
6. Inbound `RejectRetransmit` (`SharedNet::HandleEmptyAck @ 0x005448F0`) →
6. Inbound `RejectRetransmit` (`SharedNet::HandleEmptyAck @ 0x005448F0`)
remove ids from the NAK set, silent abandonment.
This keeps the inbound keystream aligned to SEQUENCE order, not arrival order
— the invariant the whole inbound port hangs on.
the invariant the whole inbound port hangs on.
### 2.3 Ack/NAK sweep
Per-frame per-connection sweep (`ClientNet::ProcessConnection @ 0x00545450`),
mutually exclusive on ONE shared timestamp (`ReceiverData::timeStamp_`):
- NAK set non-empty → `RequestRetransmit`: ≤114 ids ascending, cleartext,
- NAK set non-empty `RequestRetransmit`: ≤114 ids ascending, cleartext,
0.6 s gate (`SharedNet::EnqueueNaks @ 0x00543BD0`, `ReceiverData::GetNaks
@ 0x005490C0`), and NO ack this sweep.
- Else → one cumulative `AckSequence` carrying `highestIDReceived_`,
- Else one cumulative `AckSequence` carrying `highestIDReceived_`,
unsequenced + cleartext, 2.0 s gate (`SharedNet::EnqueuePak @ 0x00543B10`
— the ONLY 0x4000 construction site in the binary; retail never acks
the ONLY 0x4000 construction site in the binary; retail never acks
per-packet).
### 2.4 Keepalive/time/flow (mostly deferred — §5)
### 2.4 Keepalive/time/flow (mostly deferred §5)
TimeSync (8-byte double) + EchoRequest (4-byte float) every ~3 s
(`ClientFlowQueue::IncrementLocalInterval @ 0x00547F10`); inbound TimeSync
adopts the server clock; Flow report sent on remote-interval advance;
inbound Flow ignored, no throttle (`WireRoomLeft` is a folded `return 1` —
inbound Flow ignored, no throttle (`WireRoomLeft` is a folded `return 1`
verified); dead link at 140 s without inbound data (self-stall guarded) with
referral auto-reconnect; 0.5 s interval clock feeds `ProtoHeader::interval_`
(our `PacketHeader.Time`).
### 2.5 Constants
464 max payload after the 20-byte header · 448 fragment payload · 114 NAK
list cap · +0x7FFF sanity window · 0.5 s interval tick · 2.0 s ack gate ·
0.6 s NAK gate · ~3 s TimeSync/Echo · 140 s dead link · 10 s disconnect
drain · `0xBADD70DD` checksum placeholder · sequence init 1, wrap
0xFFFFFFFFâ†1 · 0.333 s ConnectResponse handshake resend.
464 max payload after the 20-byte header · 448 fragment payload · 114 NAK
list cap · +0x7FFF sanity window · 0.5 s interval tick · 2.0 s ack gate ·
0.6 s NAK gate · ~3 s TimeSync/Echo · 140 s dead link · 10 s disconnect
drain · `0xBADD70DD` checksum placeholder · sequence init 1, wrap
0xFFFFFFFF→1 · 0.333 s ConnectResponse handshake resend.
## 3. ACE constraints (Coldeve runs ACE; references in the MAIN repo checkout)
@ -129,37 +132,37 @@ drain · `0xBADD70DD` checksum placeholder · sequence init 1, wrap
|---|---|---|
| NAKs client gaps at `expected+2`, 1 s limit, arrival-driven ONLY | NetworkSession.cs:351-363 | after one C2S loss two more sends must arrive before ACE NAKs; a quiet client is never NAKed |
| Crypto search window 256 keys; re-key orphans permanently | CryptoSystem.cs:30-49 | NEVER re-key a resend; NEVER resend an already-accepted packet |
| Watermark advances on ANY packet with flags ≠ exactly `AckSequence` | NetworkSession.cs:474-476 | standalone unsequenced control packets other than exact-`AckSequence`/exact-`RequestRetransmit` can skip a REAL packet forever (the self-induced wedge) |
| Watermark advances on ANY packet with flags exactly `AckSequence` | NetworkSession.cs:474-476 | standalone unsequenced control packets other than exact-`AckSequence`/exact-`RequestRetransmit` can skip a REAL packet forever (the self-induced wedge) |
| Honours only CLEARTEXT NAKs; encrypted NAKs silently ignored | NetworkSession.cs:283-284 | no NAK piggybacking; NAKs never refresh ACE's 60 s timeout |
| Ack-only dedup exemption requires flags == `AckSequence` exactly | NetworkSession.cs:342-343 | equality check, not HasFlag |
| ACE never proactively resends S2C; S2C cache prunes at 120 s | NetworkSession.cs:675-708, :251-262 | the client MUST NAK or inbound stalls; old NAKs get RejectRetransmit |
| C2S fragment sequence strictly contiguous or dispatch stalls silently | NetworkSession.cs:532-543 | packet-level retransmission heals it automatically |
| ACE sends TimeSync/20 s, cumulative ack/2 s, EchoResponse on request; NO disconnect/error packets ever | NetworkSession.cs:207-216, grep | every transport death is silence |
| Rejects AckSequence/TimeSync/EchoRequest/Flow during AuthLoginRequest | Session.cs:101-102 | no sweep before negotiation completes |
| Inbound `Header.Time` ignored | grep | our interval field is cosmetic against ACE — safe |
| Inbound `Header.Time` ignored | grep | our interval field is cosmetic against ACE safe |
## 4. Design
`src/AcDream.Core.Net/Transport/` — retail's split adapted to one login + one
`src/AcDream.Core.Net/Transport/` retail's split adapted to one login + one
world connection on one frame thread. `TransportClock` (injectable monotonic
source + 0.5 s interval counter), `SequenceMath` (`IsNewer(a,b) =>
unchecked((int)(a-b)) > 0`), `SentPacketStore` (FIFO of rented wire buffers),
`OutboundFlowQueue` (outbound ISAAC, HighestIdSent, fragment seq, pending
resends, NAK/ack consumption), `InboundSequenceTracker` (inbound ISAAC,
HighestIdReceived, NAK set `SortedDictionary<uint,uint>` seq→parked key),
HighestIdReceived, NAK set `SortedDictionary<uint,uint>` seqparked key),
`AckNakScheduler` (ONE shared timestamp; per sweep NAK xor ack),
`ReliableTransport` (composition + `Sweep()`), `TransportStats`
(unconditional counters; printing probe-gated).
Cache entry: `{ Sequence, rented Buffer (header+body), BodyLength,
SealedChecksum (= payloadHash ^ isaacKey — retail `NetPacket::checksum_`),
IsaacKey, HasFragments }`. Resend rewrites ONLY the header per §2.1;
SealedChecksum (= payloadHash ^ isaacKey retail `NetPacket::checksum_`),
IsaacKey, HasFragments }`. Resend rewrites ONLY the header per §2.1;
`PacketCodec.FinalizeInPlace` gains an overload returning
`(isaacKeyUsed, sealedChecksum)`; the old signature forwards.
Inbound: codec splits pure-parse (`TryParseBorrowed` — no keystream) from
Inbound: codec splits pure-parse (`TryParseBorrowed` no keystream) from
`VerifyChecksum(header, headerHash, payloadHash, uint? isaacKey)`; the
tracker owns every key decision (retail's own factoring — `EncryptData`'s
tracker owns every key decision (retail's own factoring `EncryptData`'s
optional key parameter). `TryDecodeBorrowed(datagram, IsaacRandom?)` is
deleted; owned `TryDecode` stays (test-only).
@ -173,21 +176,21 @@ unbounded like retail (ACE acks every 2 s; steady state is tens of entries);
## 5. Scope deferrals (divergence-register rows, filed in-slice)
- **TS-57** no outbound RejectRetransmit — ACE no-ops it; the standalone
unsequenced form trips the watermark hole (§3 row 3).
- **AP-125** standalone control packets only, no `CoalesceData` piggyback —
- **TS-57** no outbound RejectRetransmit ACE no-ops it; the standalone
unsequenced form trips the watermark hole (§3 row 3).
- **AP-125** standalone control packets only, no `CoalesceData` piggyback
piggybacked NAKs become encrypted, which ACE ignores.
- **TS-58** no TimeSync/EchoRequest keepalive — standalone-unsafe against
- **TS-58** no TimeSync/EchoRequest keepalive standalone-unsafe against
the watermark hole; our 2 s ack already refreshes ACE's 60 s timeout.
- **TS-59** no Flow report — ACE parses and has no handler.
- **AD-49** blob-layer ephemeral ordering stamps not ported — provably a
- **TS-59** no Flow report ACE parses and has no handler.
- **AD-49** blob-layer ephemeral ordering stamps not ported provably a
no-op against ACE (fragment `Id` is constant 0x80000000 and the stamp
table keys on ACE's per-message-unique fragment sequence, so
`FragIsObsoleteEmphemeral @ 0x0054A450` can never fire).
- **TS-60** no 140 s dead-link/referral auto-reconnect — LinkStatus exposes
- **TS-60** no 140 s dead-link/referral auto-reconnect LinkStatus exposes
the input; reconnect is Runtime's, its own campaign.
- **TS-61** UDP send-failure burns the sequence+key (retail retries from the
queue head) — effectively unreachable.
queue head) effectively unreachable.
- **AP-126** one monotonic clock for all gates (retail's cur/local split
immaterial to the gates we port).
- Multi-fragment outbound (>448 B) stays unimplemented (nothing sends >448;
@ -205,7 +208,7 @@ unbounded like retail (ACE acks every 2 s; steady state is tens of entries);
| N5 | Observability + LossyTransportDecorator + connected loss gate | M | partly | Opus | unit + loss gate |
| N6 | Optional: ConnectResponse 0.333 s retransmit; assembler TTL | S | yes | Opus | unit + local ACE |
N1–N5 all touch `WorldSession.cs`: strictly sequential, ONE agent at a time.
N1N5 all touch `WorldSession.cs`: strictly sequential, ONE agent at a time.
Implementers Sonnet; a redo escalates to Opus; every diff reviewed by the
Review-column model before commit. Key per-slice test specs are in the
approved plan (`~/.claude/plans/mac-os-is-not-robust-sphinx.md`) and travel
@ -213,22 +216,22 @@ verbatim in each implementer prompt.
## 7. Landmines (verbatim in every implementer prompt)
1. Resends are NOT byte-identical — flags gain `Retransmission`, `Time`
1. Resends are NOT byte-identical flags gain `Retransmission`, `Time`
advances, the header hash MUST be recomputed; checksum = new header hash
+ stored sealed checksum. A verbatim resend fails ACE's CRC silently.
2. Never draw a new ISAAC word on a resend — one re-key permanently orphans
2. Never draw a new ISAAC word on a resend one re-key permanently orphans
a key in ACE's 256-entry window.
3. Never resend unrequested — a duplicate of an accepted packet burns up to
3. Never resend unrequested a duplicate of an accepted packet burns up to
the whole window.
4. The inbound gap-walk draws parked keys BEFORE the arriving packet's own
key, in sequence order — reversed, the stream is off by the gap size
key, in sequence order reversed, the stream is off by the gap size
forever.
5. Ack flags are an EQUALITY check (`Flags == AckSequence`), never HasFlag.
6. NAKs are cleartext (`Flags == RequestRetransmit` exactly) or ACE ignores
them; NAKs do not refresh ACE's timeout.
7. The 0.6 s NAK gate and 2.0 s ack gate share ONE timestamp — a NAK delays
7. The 0.6 s NAK gate and 2.0 s ack gate share ONE timestamp a NAK delays
the next ack and vice versa; never both in one sweep.
8. The sweep must also run inside the blocking handshake pump loops — the
8. The sweep must also run inside the blocking handshake pump loops the
EnterWorld CreateObject flood precedes the first `Tick()`.
9. ACE/holtburger reference sources live in the MAIN repo checkout
(`C:\Users\erikn\source\repos\acdream\references\`), not in worktrees.
@ -238,10 +241,10 @@ verbatim in each implementer prompt.
1. Per slice: `dotnet build` + full `dotnet test -c Release` + slice gate +
one commit (retail symbol+address citations) + model review + revert SHA
recorded here.
2. N1–N5: `tools/run-connected-world-lifecycle-gate.ps1` vs local ACE;
2. N1N5: `tools/run-connected-world-lifecycle-gate.ps1` vs local ACE;
N2/N3 add the canonical nine-stop route.
3. From N4: `tools/run-connected-loss-gate.ps1` at `ACDREAM_NET_DROP_PCT=2`
vs local ACE — passes only with NON-ZERO resend/NAK counters.
vs local ACE passes only with NON-ZERO resend/NAK counters.
4. Final: one user Coldeve session past the 15-minute wedge horizon with
portal churn, after N5. `[net-tick] nak-in/s` becomes the first direct
measure of real C2S loss the project has had.
@ -251,10 +254,10 @@ verbatim in each implementer prompt.
| Slice | Status | Commit | Notes |
|---|---|---|---|
| N0 | complete | `7e9134b4` + `e3958610` | ACE-behaviour double + virtual clock + lossy link; the review fix-up added the `Session.CheckState` inbound gate, faithful `SendBundle` coalescing/splitting, two-phase termination, an ACE-loose C2S fragment parse, and ACE's MessageBuffer edge cases. 687 Core.Net tests green. N1 folded in the re-review's `ProcessFragment` two-branch split (existing-buffer checks Complete; new-buffer parks without checking — the zero-count buffer stays parked). |
| N1 | complete | `43e60a69` | Outbound sent-packet cache + resend on NAK (`Transport/`: `TransportClock`, `SequenceMath`, `SentPacketStore`, `OutboundFlowQueue`, `ReliableTransport`, `TransportStats`); `PacketCodec.FinalizeInPlace` sealed-checksum overload; `WorldSession` sweep in Tick + both handshake pump loops; TS-57 filed, TS-27 narrowed to inbound-only. **Fable review PASS.** Advisories: fresh sends keep `Time=0` (byte-identical wire; retail stamps the interval on every packet and ACE ignores the field — N3 folds the retail stamp in with the cadence work); the stale-NAK single-redundant-resend window is shared with retail (pending prunes at flush, after transmit — same frame order as `RecipientData::UseTime`). |
| N2 | complete | `46d209d0` **Fable review PASS** (tracker/codec-split/admission verified pre-commit against retail rules; gates lifecycle+nine-stop both PASS) | Inbound sequence-aligned ISAAC + NAK set (`Transport/InboundSequenceTracker`: watermark, sanity window, duplicate/parked-key path, sequence-ordered gap-walk pre-draw, verify-failure re-park, RejectRetransmit abandonment — `ProcessNewSeqNum @ 0x00544690`, `ProcessNewestSeqNum @ 0x00541930`, `SeqIDSanityCheck @ 0x00543A20`, `AddNakked @ 0x00549240`, `HandleEmptyAck @ 0x005448F0`); `PacketCodec` split into keystream-free `TryParseBorrowed` + `VerifyChecksum` (`TryDecodeBorrowed` deleted); `RejectRetransmit` ids exposed on both optional-header decoders; `ReliableTransport` now owns both keystreams; stats gained `InboundDupsDropped`/`InboundSanityDrops`/`ChecksumFailures`/`KeysParked`. Watermark init 1 is the AD-50 ACE adaptation (retail zero-init vs ACE's re-prime dance — first encrypted S2C is sequence 2; holtburger api.rs:30 agrees); pinned by the clean-lifecycle conformance test (zero NAKs, min encrypted S2C sequence == 2). 716 Core.Net tests green. **N3/N4 handoff note:** the interim per-packet reflex ack acks the ARRIVING sequence even while a gap is parked, so ACE prunes the lost id from its S2C cache (`AcknowledgeSequence` strictly-below) before N4 can NAK it — message-level recovery of a real loss needs N3's retail NAK-xor-ack sweep (§2.3's mutual exclusivity is load-bearing). Also noted for N4: ACE's `RejectRetransmit` consumes a fresh CLEARTEXT sequence via FlushPackets (no keystream word), so the client's gap walk parks a word for an id that never had one server-side — a real retail-vs-ACE incompatibility to resolve in N4's design (retail never assigns new sequences to cleartext). |
| N3 | complete | `0265cc42` Opus review PASS (advisories folded into N4: transitional-state wording, SharedInit citation, pump-order wording, control-packet Time/Iteration rule, stale budget-break comment) | AckNakScheduler + 2.0 s cumulative ack (`Transport/AckNakScheduler`): ONE shared timestamp (`ReceiverData::timeStamp_` @ +0x10) arbitrating NAK-xor-ack per sweep (`ClientNet::ProcessConnection @ 0x00545450`); the ack is one cleartext exact-flags `AckSequence` carrying the tracker's `highestIDReceived_` behind the >= 2.0 s gate (`SharedNet::EnqueuePak @ 0x00543B10` — the binary's only 0x4000 construction site), armed at connection birth (`ReceiverData::Init @ 0x00548EF0`). The Phase 4.9 per-packet reflex ack and `WorldSession.SendAck` are DELETED; the `[net-tick]` acks/s probe now reads `Stats.AcksSent`. Sweep order per `FlowQueue::Empty @ 0x00548A20`: interval clock, NAK/ack arbitration, pending resends, prune. **N3 transitional state (closed by N4):** a non-empty NAK set suppressed the ack and emitted NOTHING the exposure was real even on loopback, just low-probability: one receive-buffer drop parks an id, every later sweep takes the silent NAK branch, acks stop (witness: `[net-tick] acks/s=0`), and ACE disconnects the quiet session at its 60 s timeout. N4 completed the branch. **N1 advisory retired (fold-in):** fresh reliable sends now stamp `Header.Time` = the current interval id (`FlowQueue::TransmitNewPackets @ 0x00547A60`, header build at 0x00547A84); ACE ignores inbound `Header.Time`, so the wire is unaffected. New `WorldSession.TransportClockSource` seam drives the gate on virtual time. 723 Core.Net tests green (keepalive property proven: a quiet session's 2 s acks refresh ACE's 60 s deadline across a 120 s virtual horizon; storm collapse: a 50-packet flood → ONE ack; the model accepts the reused-sequence ack without advancing its watermark). Connected lifecycle + canonical nine-stop gates PASS. |
| N4 | complete | `852a59e3` **Opus review PASS** (reclaim invariant attacked from five angles, held; NAK fidelity verified to the x87 masks; AP-125 filed + F1 false-arithmetic wording + F5 ordinal sentinel fixed in the acceptance commit; F3 Iteration-on-fresh-sends folds into N5) | Client NAK emission + RejectRetransmit consumption. `AckNakScheduler` completes the NAK branch: one cleartext exact-flags `RequestRetransmit` per sweep behind the STRICT 0.6 s gate on the ONE shared timestamp (`SharedNet::EnqueueNaks @ 0x00543BD0` the 0x41-mask x87 test at 0x00543C03 proceeds only on strictly-greater, contrast the ack's >=), body u32 count + ids ascending capped at 114 (`ReceiverData::GetNaks @ 0x005490C0`, cap 0x72), borrowed sequence, never an ack in a NAK sweep. Control-header rule decided for BOTH emissions: `Time` = interval id, `Iteration` = session iteration, per the shared retail header build (`FlowQueue::TransmitNewPackets @ 0x00547A60` @ 0x00547A84); ACE reads neither. THE design piece: the AD-51 reclaimed-word pool in `InboundSequenceTracker` closes the ACE cleartext-reject keystream hazard the N2 ledger row recorded ACE's `RejectRetransmit` consumes a fresh cleartext sequence with NO keystream word, so the gap walk mis-parks a word for it and the whole inbound stream runs one word ahead. On a VALIDATED cleartext reject (`WorldSession` calls `OnCleartextRejectSequence` post-checksum), the tracker removes the mis-park, bubble-shifts every later-drawn parked word down one position (per-word draw ordinals; ascending id ascending draw order), and pools the excess for the next fresh draws, consumed lowest-draw-order-first exact for any number of interleaved rejects in any arrival order (a plain FIFO is NOT: reject-after-higher-arrival crosses the parked chain, and dual out-of-order rejects pool out of draw order both pinned by tests). Reject BODY ids keep N2's discard (word-bearing server-side, consumed-in-place). N3 advisories all folded: honest transitional wording (above), `ReceiverData::SharedInit @ 0x00548EF0` (from `Init @ 0x00548FA0`) citation, `FlowQueue::Empty` pump-order comment (TransmitNaks → TransmitAcks → TransmitNewPackets, interval increment LAST @ 0x00548A9D; our clock-first order is cosmetic vs ACE), the Time/Iteration rule, and the stale `WorldSession` budget-break comment. Gate arithmetic hardened: gate ticks now round (0.6 has no exact double; truncation opened the strict gate AT the boundary). 737 Core.Net tests green, including: strict-gate boundary, shared-timestamp both directions, NAK-suppresses-ack, 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 (NAKs on the gate cadence, zero acks, heal inside the window), and the capstone soak: 2% seeded bidirectional loss × 10,000 messages → zero message loss both ways, ACE crypto headroom 256 at convergence with a ≥250 no-erosion floor mid-flight, NAK set / reclaim pool / pending resends / ACE out-of-order buffer all zero, cache at the single watermark entry (retail Flush prunes STRICTLY below the ack). Soak notes: ACE never NAKs a quiet client (§3 row 1), so convergence keeps a C2S trickle flowing — a real idle-client tail loss heals only on the next action, an ACE constraint outside N4's scope. |
| N5 | complete | `4e290f00` **Opus review PASS** (structural absence + arming gate + un-gameability + teardown all verified; ledger arithmetic reconciled). Acceptance folded in the review's gate strengthenings: per-direction recovery conjunction + the three keystream-health invariants (cksum-fail/sanity-drop/uncached-nak == 0) + the EnterWorldBody unrecoverable-tail caveat. Revert: `git revert 4e290f00d86ebd320d2da609fd7cd15d7175c4f0`. Test-collection hygiene (static NetDiagnostics mutation) folds into N6 | Loss observability + the LossyTransportDecorator + the connected loss gate — the permanent removal of the loopback blindness (§1). `[net-tick]` gains `resend/s nak-out/s nak-in/s rej-in/s dup-drop/s parked/s reclaim/s cache= nakset=` (window deltas mirroring acks/s; `TransportStats` gains `RejectsReceived`; string work probe-gated, counters unconditional) and `WorldSession.Dispose` emits one cumulative `[net-final]` totals line so the gate asserts exact counters, not rounded rates. N4-review F3 folded: fresh reliable sends stamp `Iteration` = the session iteration through the same shared retail header build already cited for `Time` and the N4 control packets (`FlowQueue::TransmitNewPackets @ 0x00547A60`, the build at 0x00547A84/0x00547AA8) the control-header rule now holds across all three send shapes; ACE reads neither field inbound. `Transport/LossyTransportDecorator`: deterministic seeded per-direction loss (`ACDREAM_NET_DROP_PCT`/`_SEED`/`_DIR` via `NetDiagnostics` typed properties, Rule 5), armed only after the first ENCRYPTED outbound datagram is forwarded (parse-free flags-word check the cleartext handshake always survives; handshake loss belongs to N6), structurally absent at 0% (`WrapIfConfigured` returns the raw transport; the default factory is the only production seam). The logoff-confirmation wait now runs the transport sweep retail's pump (`Client::UseTime @ 0x00411C40`) never stops before `LogOffServer`, and the loss gate exposed that a dropped S2C confirmation was gap-detected but never NAKed during `Dispose`. `tools/run-connected-loss-gate.ps1` (default 2%/seed 1) runs the standard lifecycle route through the decorator vs local ACE and FAILS unless `[net-final]` shows resends>0 OR nak-out>0 OR nak-in>0 AND the decorator's own dropped ledger is non-zero a loss gate that never dropped proves nothing, asserted explicitly. The lifecycle gate defensively clears the drop vars (decorator-absent baseline). **First loss-observing gate evidence (2026-07-29, 2%/seed 1, local ACE):** decorator dropped out=3 in=10 of forwarded out=183 in=496; `[net-final] resends=2 nak-in=2 nak-out=6 rej-in=0 acks-out=114 acks-in=119 dup-drop=0 sanity-drop=0 cksum-fail=0 parked=9 reclaimed=0 uncached-nak=0 cache=1 nakset=0` — both recovery directions fired on a real connected route (ACE NAK → cached resend; client gap-walk park → NAK → ACE retransmit), all six checkpoints validated, graceful logout confirmed, ACE recorded the transport Disconnect, RESULT=PASS. The gate immediately paid for itself: it exposed that the Dispose logoff-confirmation wait processed inbound but never swept the transport, so a lost S2C confirmation could be gap-detected yet never NAKed fixed by running the sweep in that third blocking pump (retail's `Client::UseTime @ 0x00411C40` pump runs until `LogOffServer`). Known tail caveat recorded in the gate header: a drop landing on the single-shot logoff request or transport Disconnect (~pct each) is unrecoverable by ACE's arrival-driven NAK design (§3 row 1) — rerun with another seed, never widen teardown tolerances. #261 filed for `LinkStatusSnapshot.PacketLossPercentage` (retail `CLinkStatusAverages` formula required; inventing a ratio forbidden). 747 Core.Net tests green (decorator determinism/direction/arming/structural-absence, the 5% seeded WorldSession lossy lifecycle with zero message loss + Headroom 256, `[net-tick]` field pins, Iteration-stamp pins). |
| N6 | complete | `f9c5e47e` **Opus review PASS** (all six retail address claims verified incl. the three distinct gate strictness masks; packet-identity, latch, TTL, and ring all clean). Acceptance settles LOW-2 (TS-58/TS-59/TS-60/TS-61/AP-126 filed) + INFO-4 (DropAll resets the completed ring) + INFO-5 (the post-acceptance retry drop is NetworkManager's pre-route, not CheckState same silent pre-CRC outcome). LOW-1 noted: retry cadence dilates to ~0.5 s via the pump's 250 ms receive granularity mechanism/constant/strictness retail-exact, fewer retries than retail, harmless. Revert: `git revert f9c5e47e`. | ConnectResponse handshake retransmit + fragment-assembler eviction the final implementation slice. **Retransmit:** while unconfirmed, the Connect character-list pump resends the IDENTICAL cleartext ConnectResponse (same sequence 1, same cookie, the one encoded datagram no new outbound state) on retail's strict 0.333333333 s gate (`ClientNet::ProcessConnection @ 0x00545450` case `cs_ConnectionRequestAcked` at 0x0054547B, the constant at 0x00545481, the mask-0x41 strictly-greater test at 0x0054548C; `ClientNet::SendConnectAck @ 0x005440F0` re-stamps `lastSentHandshake_` at 0x00544102 and rebuilds the same cookie packet). Confirmation = the first checksum-valid post-negotiation packet without the ConnectRequest flag, retail's `cs_ConnectionRequestAcked cs_Connected` edge (`ClientNet::ProcessPacket @ 0x00545100`: the 0x40000 exclusion at 0x0054514E, `SetConnectionState(..., 5)` at 0x00545160). The cadence rides the TransportClock (virtual-clock testable via `TransportClockSource`); the Connect deadline stays wall-clock. ACE-safety pinned against the N0 model: a duplicate while still `AuthConnectResponse` re-routes idempotently through NetworkManager's pre-route; after acceptance `CheckState` clause 2 drops it pre-CRC at zero keystream cost. Pre-N6 a lost ConnectResponse was a hang to the Connect deadline routine on a real path, and the N5 decorator deliberately arms after this window, so nothing covered it. **Assembler eviction (AD-52):** partials evict 60 s after their last accepted fragment (re-stamp-on-update per retail `ArrivedEphInfo::UpdateNetBlobID @ 0x0054AE00`), swept from `ReliableTransport.Sweep` on retail's 5 s flush cadence (`Indicator::FlushTimedOutEphInfo @ 0x0054A3D0`, gate at 0x0054A3DC; per-entry `fTimedOut @ 0x0054AE30`) N4's RejectRetransmit abandonment had made an unrecoverable partial a reachable permanent state. A 64-entry completed-sequence ring drops late duplicates of already-completed messages instead of re-partialing them (the completed-then-duplicate leak). 60 s is a floor, never a tunable to shrink. **Fold-ins:** N5-review LOW-5 `NetProbeTests` + `LossyTransportDecoratorTests` (the static-`NetDiagnostics`/`Console.SetOut` mutators) share one `DisableParallelization` xunit collection so they never run alongside classes constructing `WorldSession`. 757 Core.Net tests green (drop-first-ConnectResponse-retry-heals with exactly one retry and none after confirmation; clean handshake sends exactly one; server-responses-lost retries drop harmlessly at the model while the N2/N4 gap-walk → NAK → cached-retransmit path heals the handshake responses; model-level duplicate-after-acceptance CheckState pin; assembler TTL floor boundary/refresh-on-update/ring-drop/ring-bound; transport-level sweep eviction). Connected lifecycle gate PASS (capped six-checkpoint route + uncapped reconnect, graceful exits, zero failures). The N5-STRENGTHENED loss gate PASS on its first live run (2%/seed 1, local ACE): decorator `[net-loss] dropped out=3 in=10 forwarded out=181 in=496 armed=True`; `[net-final] resends=1 nak-in=1 nak-out=5 rej-in=0 acks-out=114 acks-in=116 dup-drop=0 sanity-drop=0 cksum-fail=0 parked=8 reclaimed=0 uncached-nak=0 cache=1 nakset=0` the per-direction recovery conjunction held (C2S: resends+nak-in > 0; S2C: nak-out > 0), all three keystream-health invariants zero, ledger converged at the single watermark cache entry. |
| N0 | complete | `7e9134b4` + `e3958610` | ACE-behaviour double + virtual clock + lossy link; the review fix-up added the `Session.CheckState` inbound gate, faithful `SendBundle` coalescing/splitting, two-phase termination, an ACE-loose C2S fragment parse, and ACE's MessageBuffer edge cases. 687 Core.Net tests green. N1 folded in the re-review's `ProcessFragment` two-branch split (existing-buffer checks Complete; new-buffer parks without checking the zero-count buffer stays parked). |
| N1 | complete | `43e60a69` | Outbound sent-packet cache + resend on NAK (`Transport/`: `TransportClock`, `SequenceMath`, `SentPacketStore`, `OutboundFlowQueue`, `ReliableTransport`, `TransportStats`); `PacketCodec.FinalizeInPlace` sealed-checksum overload; `WorldSession` sweep in Tick + both handshake pump loops; TS-57 filed, TS-27 narrowed to inbound-only. **Fable review PASS.** Advisories: fresh sends keep `Time=0` (byte-identical wire; retail stamps the interval on every packet and ACE ignores the field N3 folds the retail stamp in with the cadence work); the stale-NAK single-redundant-resend window is shared with retail (pending prunes at flush, after transmit same frame order as `RecipientData::UseTime`). |
| N2 | complete | `46d209d0` <EFBFBD> **Fable review PASS** (tracker/codec-split/admission verified pre-commit against retail rules; gates lifecycle+nine-stop both PASS) | Inbound sequence-aligned ISAAC + NAK set (`Transport/InboundSequenceTracker`: watermark, sanity window, duplicate/parked-key path, sequence-ordered gap-walk pre-draw, verify-failure re-park, RejectRetransmit abandonment `ProcessNewSeqNum @ 0x00544690`, `ProcessNewestSeqNum @ 0x00541930`, `SeqIDSanityCheck @ 0x00543A20`, `AddNakked @ 0x00549240`, `HandleEmptyAck @ 0x005448F0`); `PacketCodec` split into keystream-free `TryParseBorrowed` + `VerifyChecksum` (`TryDecodeBorrowed` deleted); `RejectRetransmit` ids exposed on both optional-header decoders; `ReliableTransport` now owns both keystreams; stats gained `InboundDupsDropped`/`InboundSanityDrops`/`ChecksumFailures`/`KeysParked`. Watermark init 1 is the AD-50 ACE adaptation (retail zero-init vs ACE's re-prime dance first encrypted S2C is sequence 2; holtburger api.rs:30 agrees); pinned by the clean-lifecycle conformance test (zero NAKs, min encrypted S2C sequence == 2). 716 Core.Net tests green. **N3/N4 handoff note:** the interim per-packet reflex ack acks the ARRIVING sequence even while a gap is parked, so ACE prunes the lost id from its S2C cache (`AcknowledgeSequence` strictly-below) before N4 can NAK it message-level recovery of a real loss needs N3's retail NAK-xor-ack sweep (§2.3's mutual exclusivity is load-bearing). Also noted for N4: ACE's `RejectRetransmit` consumes a fresh CLEARTEXT sequence via FlushPackets (no keystream word), so the client's gap walk parks a word for an id that never had one server-side a real retail-vs-ACE incompatibility to resolve in N4's design (retail never assigns new sequences to cleartext). |
| N3 | complete | `0265cc42` <EFBFBD> Opus review PASS (advisories folded into N4: transitional-state wording, SharedInit citation, pump-order wording, control-packet Time/Iteration rule, stale budget-break comment) | AckNakScheduler + 2.0 s cumulative ack (`Transport/AckNakScheduler`): ONE shared timestamp (`ReceiverData::timeStamp_` @ +0x10) arbitrating NAK-xor-ack per sweep (`ClientNet::ProcessConnection @ 0x00545450`); the ack is one cleartext exact-flags `AckSequence` carrying the tracker's `highestIDReceived_` behind the >= 2.0 s gate (`SharedNet::EnqueuePak @ 0x00543B10` the binary's only 0x4000 construction site), armed at connection birth (`ReceiverData::Init @ 0x00548EF0`). The Phase 4.9 per-packet reflex ack and `WorldSession.SendAck` are DELETED; the `[net-tick]` acks/s probe now reads `Stats.AcksSent`. Sweep order per `FlowQueue::Empty @ 0x00548A20`: interval clock, NAK/ack arbitration, pending resends, prune. **N3 transitional state (closed by N4):** a non-empty NAK set suppressed the ack and emitted NOTHING <EFBFBD> the exposure was real even on loopback, just low-probability: one receive-buffer drop parks an id, every later sweep takes the silent NAK branch, acks stop (witness: `[net-tick] acks/s=0`), and ACE disconnects the quiet session at its 60 s timeout. N4 completed the branch. **N1 advisory retired (fold-in):** fresh reliable sends now stamp `Header.Time` = the current interval id (`FlowQueue::TransmitNewPackets @ 0x00547A60`, header build at 0x00547A84); ACE ignores inbound `Header.Time`, so the wire is unaffected. New `WorldSession.TransportClockSource` seam drives the gate on virtual time. 723 Core.Net tests green (keepalive property proven: a quiet session's 2 s acks refresh ACE's 60 s deadline across a 120 s virtual horizon; storm collapse: a 50-packet flood ONE ack; the model accepts the reused-sequence ack without advancing its watermark). Connected lifecycle + canonical nine-stop gates PASS. |
| N4 | complete | `852a59e3` <EFBFBD> **Opus review PASS** (reclaim invariant attacked from five angles, held; NAK fidelity verified to the x87 masks; AP-125 filed + F1 false-arithmetic wording + F5 ordinal sentinel fixed in the acceptance commit; F3 Iteration-on-fresh-sends folds into N5) | Client NAK emission + RejectRetransmit consumption. `AckNakScheduler` completes the NAK branch: one cleartext exact-flags `RequestRetransmit` per sweep behind the STRICT 0.6 s gate on the ONE shared timestamp (`SharedNet::EnqueueNaks @ 0x00543BD0` <EFBFBD> the 0x41-mask x87 test at 0x00543C03 proceeds only on strictly-greater, contrast the ack's >=), body u32 count + ids ascending capped at 114 (`ReceiverData::GetNaks @ 0x005490C0`, cap 0x72), borrowed sequence, never an ack in a NAK sweep. Control-header rule decided for BOTH emissions: `Time` = interval id, `Iteration` = session iteration, per the shared retail header build (`FlowQueue::TransmitNewPackets @ 0x00547A60` @ 0x00547A84); ACE reads neither. THE design piece: the AD-51 reclaimed-word pool in `InboundSequenceTracker` closes the ACE cleartext-reject keystream hazard the N2 ledger row recorded <EFBFBD> ACE's `RejectRetransmit` consumes a fresh cleartext sequence with NO keystream word, so the gap walk mis-parks a word for it and the whole inbound stream runs one word ahead. On a VALIDATED cleartext reject (`WorldSession` calls `OnCleartextRejectSequence` post-checksum), the tracker removes the mis-park, bubble-shifts every later-drawn parked word down one position (per-word draw ordinals; ascending id ? ascending draw order), and pools the excess for the next fresh draws, consumed lowest-draw-order-first <EFBFBD> exact for any number of interleaved rejects in any arrival order (a plain FIFO is NOT: reject-after-higher-arrival crosses the parked chain, and dual out-of-order rejects pool out of draw order <EFBFBD> both pinned by tests). Reject BODY ids keep N2's discard (word-bearing server-side, consumed-in-place). N3 advisories all folded: honest transitional wording (above), `ReceiverData::SharedInit @ 0x00548EF0` (from `Init @ 0x00548FA0`) citation, `FlowQueue::Empty` pump-order comment (TransmitNaks ? TransmitAcks ? TransmitNewPackets, interval increment LAST @ 0x00548A9D; our clock-first order is cosmetic vs ACE), the Time/Iteration rule, and the stale `WorldSession` budget-break comment. Gate arithmetic hardened: gate ticks now round (0.6 has no exact double; truncation opened the strict gate AT the boundary). 737 Core.Net tests green, including: strict-gate boundary, shared-timestamp both directions, NAK-suppresses-ack, 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 (NAKs on the gate cadence, zero acks, heal inside the window), and the capstone soak: 2% seeded bidirectional loss <EFBFBD> 10,000 messages ? zero message loss both ways, ACE crypto headroom 256 at convergence with a =250 no-erosion floor mid-flight, NAK set / reclaim pool / pending resends / ACE out-of-order buffer all zero, cache at the single watermark entry (retail Flush prunes STRICTLY below the ack). Soak notes: ACE never NAKs a quiet client (<EFBFBD>3 row 1), so convergence keeps a C2S trickle flowing <20> a real idle-client tail loss heals only on the next action, an ACE constraint outside N4's scope. |
| N5 | complete | `4e290f00` <EFBFBD> **Opus review PASS** (structural absence + arming gate + un-gameability + teardown all verified; ledger arithmetic reconciled). Acceptance folded in the review's gate strengthenings: per-direction recovery conjunction + the three keystream-health invariants (cksum-fail/sanity-drop/uncached-nak == 0) + the EnterWorldBody unrecoverable-tail caveat. Revert: `git revert 4e290f00d86ebd320d2da609fd7cd15d7175c4f0`. Test-collection hygiene (static NetDiagnostics mutation) folds into N6 | Loss observability + the LossyTransportDecorator + the connected loss gate <EFBFBD> the permanent removal of the loopback blindness (<28>1). `[net-tick]` gains `resend/s nak-out/s nak-in/s rej-in/s dup-drop/s parked/s reclaim/s cache= nakset=` (window deltas mirroring acks/s; `TransportStats` gains `RejectsReceived`; string work probe-gated, counters unconditional) and `WorldSession.Dispose` emits one cumulative `[net-final]` totals line so the gate asserts exact counters, not rounded rates. N4-review F3 folded: fresh reliable sends stamp `Iteration` = the session iteration through the same shared retail header build already cited for `Time` and the N4 control packets (`FlowQueue::TransmitNewPackets @ 0x00547A60`, the build at 0x00547A84/0x00547AA8) <EFBFBD> the control-header rule now holds across all three send shapes; ACE reads neither field inbound. `Transport/LossyTransportDecorator`: deterministic seeded per-direction loss (`ACDREAM_NET_DROP_PCT`/`_SEED`/`_DIR` via `NetDiagnostics` typed properties, Rule 5), armed only after the first ENCRYPTED outbound datagram is forwarded (parse-free flags-word check <EFBFBD> the cleartext handshake always survives; handshake loss belongs to N6), structurally absent at 0% (`WrapIfConfigured` returns the raw transport; the default factory is the only production seam). The logoff-confirmation wait now runs the transport sweep <EFBFBD> retail's pump (`Client::UseTime @ 0x00411C40`) never stops before `LogOffServer`, and the loss gate exposed that a dropped S2C confirmation was gap-detected but never NAKed during `Dispose`. `tools/run-connected-loss-gate.ps1` (default 2%/seed 1) runs the standard lifecycle route through the decorator vs local ACE and FAILS unless `[net-final]` shows resends>0 OR nak-out>0 OR nak-in>0 AND the decorator's own dropped ledger is non-zero <EFBFBD> a loss gate that never dropped proves nothing, asserted explicitly. The lifecycle gate defensively clears the drop vars (decorator-absent baseline). **First loss-observing gate evidence (2026-07-29, 2%/seed 1, local ACE):** decorator dropped out=3 in=10 of forwarded out=183 in=496; `[net-final] resends=2 nak-in=2 nak-out=6 rej-in=0 acks-out=114 acks-in=119 dup-drop=0 sanity-drop=0 cksum-fail=0 parked=9 reclaimed=0 uncached-nak=0 cache=1 nakset=0` <EFBFBD> both recovery directions fired on a real connected route (ACE NAK ? cached resend; client gap-walk park ? NAK ? ACE retransmit), all six checkpoints validated, graceful logout confirmed, ACE recorded the transport Disconnect, RESULT=PASS. The gate immediately paid for itself: it exposed that the Dispose logoff-confirmation wait processed inbound but never swept the transport, so a lost S2C confirmation could be gap-detected yet never NAKed <EFBFBD> fixed by running the sweep in that third blocking pump (retail's `Client::UseTime @ 0x00411C40` pump runs until `LogOffServer`). Known tail caveat recorded in the gate header: a drop landing on the single-shot logoff request or transport Disconnect (~pct each) is unrecoverable by ACE's arrival-driven NAK design (<EFBFBD>3 row 1) <20> rerun with another seed, never widen teardown tolerances. #261 filed for `LinkStatusSnapshot.PacketLossPercentage` (retail `CLinkStatusAverages` formula required; inventing a ratio forbidden). 747 Core.Net tests green (decorator determinism/direction/arming/structural-absence, the 5% seeded WorldSession lossy lifecycle with zero message loss + Headroom 256, `[net-tick]` field pins, Iteration-stamp pins). |
| N6 | complete | `f9c5e47e` <EFBFBD> **Opus review PASS** (all six retail address claims verified incl. the three distinct gate strictness masks; packet-identity, latch, TTL, and ring all clean). Acceptance settles LOW-2 (TS-58/TS-59/TS-60/TS-61/AP-126 filed) + INFO-4 (DropAll resets the completed ring) + INFO-5 (the post-acceptance retry drop is NetworkManager's pre-route, not CheckState <EFBFBD> same silent pre-CRC outcome). LOW-1 noted: retry cadence dilates to ~0.5 s via the pump's 250 ms receive granularity <EFBFBD> mechanism/constant/strictness retail-exact, fewer retries than retail, harmless. Revert: `git revert f9c5e47e`. | ConnectResponse handshake retransmit + fragment-assembler eviction <EFBFBD> the final implementation slice. **Retransmit:** while unconfirmed, the Connect character-list pump resends the IDENTICAL cleartext ConnectResponse (same sequence 1, same cookie, the one encoded datagram <EFBFBD> no new outbound state) on retail's strict 0.333333333 s gate (`ClientNet::ProcessConnection @ 0x00545450` case `cs_ConnectionRequestAcked` at 0x0054547B, the constant at 0x00545481, the mask-0x41 strictly-greater test at 0x0054548C; `ClientNet::SendConnectAck @ 0x005440F0` re-stamps `lastSentHandshake_` at 0x00544102 and rebuilds the same cookie packet). Confirmation = the first checksum-valid post-negotiation packet without the ConnectRequest flag, retail's `cs_ConnectionRequestAcked ? cs_Connected` edge (`ClientNet::ProcessPacket @ 0x00545100`: the 0x40000 exclusion at 0x0054514E, `SetConnectionState(..., 5)` at 0x00545160). The cadence rides the TransportClock (virtual-clock testable via `TransportClockSource`); the Connect deadline stays wall-clock. ACE-safety pinned against the N0 model: a duplicate while still `AuthConnectResponse` re-routes idempotently through NetworkManager's pre-route; after acceptance `CheckState` clause 2 drops it pre-CRC at zero keystream cost. Pre-N6 a lost ConnectResponse was a hang to the Connect deadline <EFBFBD> routine on a real path, and the N5 decorator deliberately arms after this window, so nothing covered it. **Assembler eviction (AD-52):** partials evict 60 s after their last accepted fragment (re-stamp-on-update per retail `ArrivedEphInfo::UpdateNetBlobID @ 0x0054AE00`), swept from `ReliableTransport.Sweep` on retail's 5 s flush cadence (`Indicator::FlushTimedOutEphInfo @ 0x0054A3D0`, gate at 0x0054A3DC; per-entry `fTimedOut @ 0x0054AE30`) <EFBFBD> N4's RejectRetransmit abandonment had made an unrecoverable partial a reachable permanent state. A 64-entry completed-sequence ring drops late duplicates of already-completed messages instead of re-partialing them (the completed-then-duplicate leak). 60 s is a floor, never a tunable to shrink. **Fold-ins:** N5-review LOW-5 <EFBFBD> `NetProbeTests` + `LossyTransportDecoratorTests` (the static-`NetDiagnostics`/`Console.SetOut` mutators) share one `DisableParallelization` xunit collection so they never run alongside classes constructing `WorldSession`. 757 Core.Net tests green (drop-first-ConnectResponse-retry-heals with exactly one retry and none after confirmation; clean handshake sends exactly one; server-responses-lost retries drop harmlessly at the model while the N2/N4 gap-walk ? NAK ? cached-retransmit path heals the handshake responses; model-level duplicate-after-acceptance CheckState pin; assembler TTL floor boundary/refresh-on-update/ring-drop/ring-bound; transport-level sweep eviction). Connected lifecycle gate PASS (capped six-checkpoint route + uncapped reconnect, graceful exits, zero failures). The N5-STRENGTHENED loss gate PASS on its first live run (2%/seed 1, local ACE): decorator `[net-loss] dropped out=3 in=10 forwarded out=181 in=496 armed=True`; `[net-final] resends=1 nak-in=1 nak-out=5 rej-in=0 acks-out=114 acks-in=116 dup-drop=0 sanity-drop=0 cksum-fail=0 parked=8 reclaimed=0 uncached-nak=0 cache=1 nakset=0` <EFBFBD> the per-direction recovery conjunction held (C2S: resends+nak-in > 0; S2C: nak-out > 0), all three keystream-health invariants zero, ledger converged at the single watermark cache entry. |