# Slice H-c1 — cancellable pooled receive owner ## Result The in-world transport now has one asynchronous socket receive owner: ```text one retained full-size socket buffer -> ReceiveFromAsync(cancellation token) -> copy actual datagram length into a right-sized ArrayPool bucket -> FIFO Channel -> render-thread decode/dispatch -> return bucket exactly once ``` The synchronous `PumpOnce` handshake path keeps its existing 250 ms pacing, but receives into a temporarily rented caller buffer rather than accepting a fresh `UdpClient.Receive` array. The normal in-world path no longer wakes four times per idle second and no longer uses `SocketException(TimedOut)` as its heartbeat. `NetClient.Send` also consumes its caller's `ReadOnlySpan` directly through `Socket.SendTo`; the transport-only `datagram.ToArray()` copy is gone. Higher-level outbound framing allocations are intentionally left to H-c3. ## Retail and protocol boundary Behavioral invariants remain unchanged: - one outstanding receive; - kernel arrival order is FIFO channel order; - decode, ISAAC, fragment assembly, ACK decisions, and event dispatch remain on the existing render/update thread; - ACK generation remains inside `ProcessDatagram`, immediately after decode acceptance; - `LinkStatusHolder::OnHeartbeat` (`0x004113D0`) last-heard placement remains immediately after successful decode; - blocking `PumpOnce` handshake cadence is unchanged. The initial design review caught that 484 bytes is a fragment limit, not a safe UDP receive limit. Named-retail `CNetLayerPacket` (type 3705 in `acclient.h`) contains a separate `ProtoHeader` plus `m_Data[65484]`. Accordingly the socket edge retains a full UDP buffer. Only the received byte count crosses the queue boundary, preventing small queued packets from each pinning a 64 KiB bucket. Holtburger's receive/finalize path remains the cross-reference for ACK-per-accepted-packet ordering. The transport ownership mechanism is a modern .NET adaptation; it does not change AC wire behavior. ## Deterministic evidence - real loopback `ReceiveAsync` writes a 1,500-byte datagram into caller-owned memory; - cancelling an idle receive terminates the one outstanding operation; - a scripted recoverable `SocketException` does not kill the receive owner; - three accepted packets retain arrival order and produce ACKs in the exact sequence `41, 42, 43`; - blocking handshake timeout behavior and cached socket-timeout tests remain unchanged; - graceful confirmation drain releases every consumed pooled datagram, and shutdown cancels/joins the receive task before returning unread buffers. ## Remaining H-c work H-c2 removes `Packet`, `BodyBytes`, `PacketHeaderOptional`, `List`, and single-fragment payload copies from the production decode path using borrowed memory with an explicit lifetime boundary. H-c3 writes fragment and packet framing directly into caller storage.