perf(net): frame recurring sends in place

Write normal game-message and ACK packet framing directly into bounded stack spans, hash fragments without materialization, and send the populated slice to the socket. Preserve exact wire bytes and ISAAC failure ordering with differential and zero-allocation tests.
This commit is contained in:
Erik 2026-07-25 06:02:52 +02:00
parent e928c5dd02
commit 41c1a59392
7 changed files with 485 additions and 31 deletions

View file

@ -146,8 +146,10 @@ H-c is executed as three independently reversible units:
fields, and fragments as borrowed views. Copy only multi-fragment state
that must survive the current datagram. Evidence:
[`../research/2026-07-25-slice-h-c2-borrowed-packet-decode.md`](../research/2026-07-25-slice-h-c2-borrowed-packet-decode.md).
3. **H-c3 — direct outbound framing — PENDING.** Write packet and fragment
3. **H-c3 — direct outbound framing — COMPLETE.** Write packet and fragment
framing into caller storage and remove intermediate payload arrays.
Evidence:
[`../research/2026-07-25-slice-h-c3-direct-outbound-framing.md`](../research/2026-07-25-slice-h-c3-direct-outbound-framing.md).
Retail/transport invariants:

View file

@ -0,0 +1,74 @@
# Slice H-c3 — direct outbound framing
## Result
Recurring game messages and ACKs now go from caller bytes to the UDP socket
without a managed framing allocation.
For a normal game message, `WorldSession` reserves one 484-byte stack span:
```text
20-byte PacketHeader
16-byte MessageFragmentHeader
up to 448 bytes of existing GameMessage payload
```
`GameMessageFragment.WriteSingleFragment` writes the fragment header and
payload directly after the packet-header reservation.
`PacketCodec.FinalizeInPlace` validates and hashes that body, consumes the
outbound ISAAC word, and writes the fixed header into the reserved prefix.
`NetClient.Send` then passes only the populated slice to `Socket.SendTo`.
ACKs use the same mechanism with one 24-byte stack span: 20 header bytes plus
the four-byte acknowledged server sequence.
The retained public `BuildSingleFragment`, `Serialize`, and `PacketCodec.Encode`
APIs remain available for fixtures and infrequent callers. `Encode` now shares
the span-based fragment hashing primitive, so it no longer materializes a
fragment payload merely to calculate its checksum.
## Behavior and failure ordering
- packet, fragment, and game-action sequence behavior is unchanged;
- fragment `Id`, `Count`, `Index`, `TotalSize`, and queue bytes are unchanged;
- ACKs still reuse the most recently issued client packet sequence;
- encrypted packets consume exactly one ISAAC word after complete structural
validation;
- a malformed fragment throws before consuming the ISAAC stream;
- the synchronous socket call completes before its stack storage expires;
- messages above retail's 448-byte single-fragment payload limit retain the
existing explicit failure rather than silently truncating or inventing a
split policy.
This is storage/mechanical work around the established retail/ACE wire
contract. It does not change AC gameplay or packet-order behavior.
## Deterministic evidence
Tests prove:
- the direct fragment writer is byte-identical to the existing owned
build/serialize path;
- direct encrypted game-message framing is byte-identical to the owned path
with independently seeded ISAAC instances;
- direct ACK framing is byte-identical to the owned path;
- malformed input leaves the ISAAC stream untouched;
- short destinations fail before writing past their boundary;
- the warmed direct framing path allocates zero bytes over 1,000 iterations.
A Release microbenchmark framed 500,000 representative 44-byte game messages:
| Framing path | Time | Throughput cost | Allocated |
|---|---:|---:|---:|
| owned intermediates | 137.206 ms | 274.4 ns/message | 176,000,040 bytes |
| direct stack span | 12.551 ms | 25.1 ns/message | 0 bytes |
That is a 10.932x framing microbenchmark speedup and removes 352 managed bytes
per representative outbound message.
## Slice H-c remaining gate
The code units are complete. H-c closeout requires connected login, world
entry, ACK continuity, reconnect, portal, interaction, and graceful-disconnect
coverage on the available RDP session. RDP is valid for network correctness
but not for final GPU/frame-time performance acceptance.