diff --git a/AyCode.Core.Serializers.Console/Program.cs b/AyCode.Core.Serializers.Console/Program.cs
index 1f4996f..103679b 100644
--- a/AyCode.Core.Serializers.Console/Program.cs
+++ b/AyCode.Core.Serializers.Console/Program.cs
@@ -34,7 +34,9 @@ public static class Program
{
private const string ResultsDirectory = @"H:\Applications\Aycode\Source\AyCode.Core\Test_Benchmark_Results\Benchmark";
-#if DEBUG
+#if AYCODE_NATIVEAOT
+ private const string BuildConfiguration = "NativeAOT";
+#elif DEBUG
private const string BuildConfiguration = "Debug";
#else
private const string BuildConfiguration = "Release";
diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs
index 34834e8..3a8fea7 100644
--- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs
+++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs
@@ -364,18 +364,25 @@ public static partial class AcBinarySerializer
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void EnsureCapacity(int additionalBytes)
{
- if (_position + additionalBytes > _bufferEnd)
- Output.Grow(ref _buffer, ref _position, ref _bufferEnd, additionalBytes);
+ if (_position + additionalBytes > _bufferEnd) GrowAndValidate(additionalBytes);
}
///
- /// Ensures the buffer has enough space for the specified number of bytes.
- /// Called before property writes to avoid mid-object Grow() calls.
+ /// Slow path for : invokes Output.Grow and revalidates that
+ /// the request was actually satisfied. A chunk-limited output (e.g. AsyncPipeWriterOutput
+ /// on a single-value write > MaxChunkSize data bytes) may return WITHOUT growing the
+ /// active buffer to the requested size — silent under-provisioning would cause downstream
+ /// ArgumentOutOfRangeException in AsSpan/CopyTo at corrupt offsets.
+ /// NoInlining keeps the hot path of tiny and inline-friendly.
+ /// Related limit: see BINARY_ASYNCPIPE_ISSUES.md#accore-bin-i-b7k9.
///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void ReserveCapacity(int bytes)
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private void GrowAndValidate(int additionalBytes)
{
- EnsureCapacity(bytes);
+ Output.Grow(ref _buffer, ref _position, ref _bufferEnd, additionalBytes);
+
+ var available = _bufferEnd - _position;
+ if (available < additionalBytes) ThrowGrowFailedToSatisfy(additionalBytes, available);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -386,13 +393,6 @@ public static partial class AcBinarySerializer
_buffer[_position++] = value;
}
- /// Writes a single byte without capacity check. Caller must ensure buffer space.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void WriteByteUnsafe(byte value)
- {
- _buffer[_position++] = value;
- }
-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteTwoBytes(byte b1, byte b2)
{
@@ -1004,6 +1004,19 @@ public static partial class AcBinarySerializer
$"This limit is dictated by the writer's worst-case 'charLength * 4' UTF-8 byte allocation; " +
$"larger inputs would silently overflow int arithmetic.");
+ ///
+ /// Throw helper for : invoked when Output.Grow returns
+ /// without satisfying the requested capacity (chunk-limited output, single value larger than
+ /// the per-chunk data capacity). Marked NoInlining so the throw-site stays out of the
+ /// inlined caller body. See BINARY_ASYNCPIPE_ISSUES.md#accore-bin-i-b7k9 for fix direction.
+ ///
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void ThrowGrowFailedToSatisfy(int requested, int available) =>
+ throw new InvalidOperationException(
+ $"Output.Grow did not satisfy the requested capacity: {requested} bytes requested, only {available} bytes available after Grow. " +
+ $"This typically indicates a chunk-limited output (e.g. AsyncPipeWriterOutput) with a single serialized value exceeding the per-chunk data capacity. " +
+ $"See BINARY_ASYNCPIPE_ISSUES.md#accore-bin-i-b7k9.");
+
#endregion
#region Bulk Array Writes — inline
diff --git a/AyCode.Core/Serializers/Binaries/AsyncPipeWriterOutput.cs b/AyCode.Core/Serializers/Binaries/AsyncPipeWriterOutput.cs
index 302e883..7e65f71 100644
--- a/AyCode.Core/Serializers/Binaries/AsyncPipeWriterOutput.cs
+++ b/AyCode.Core/Serializers/Binaries/AsyncPipeWriterOutput.cs
@@ -284,7 +284,22 @@ public struct AsyncPipeWriterOutput : IBinaryOutputBase
}
// Acquire new chunk with header reservation (common to both paths).
- AcquireChunk(Math.Max(needed, _chunkSize), out buffer, out position, out bufferEnd);
+ //
+ // Unit alignment: `needed` is the data-byte count requested by the caller (via EnsureCapacity);
+ // `_chunkSize` is the chunk-on-wire total size (header + data) — same units AcquireChunk's
+ // `requestSize` parameter expects. Convert `needed` to the chunk-on-wire scale (`+ headerOffset`)
+ // before the Max() so apples-to-apples. AcquireChunk then subtracts `headerOffset` internally to
+ // size the data region.
+ //
+ // Without this alignment, a caller request of exactly `_chunkSize - headerOffset` data bytes
+ // would yield a chunk whose data region is `headerOffset` bytes SHORT of the request — every
+ // EnsureCapacity for a value larger than the chunk-data capacity would silently under-provision,
+ // surfacing later as `ArgumentOutOfRangeException` in `AsSpan`/`CopyTo` at corrupt offsets.
+ // The sanity guard in `BinarySerializationContext.EnsureCapacity` (post-Grow revalidation +
+ // `ThrowGrowFailedToSatisfy`) catches the remaining > MaxChunkSize case explicitly — see
+ // `BINARY_ASYNCPIPE_ISSUES.md#accore-bin-i-b7k9`.
+ var headerOffset = _multiMessage ? HeaderSize : 0;
+ AcquireChunk(Math.Max(needed + headerOffset, _chunkSize), out buffer, out position, out bufferEnd);
_currentChunkStart = position;
}
diff --git a/AyCode.Core/docs/BINARY/BINARY_ASYNCPIPE_ISSUES.md b/AyCode.Core/docs/BINARY/BINARY_ASYNCPIPE_ISSUES.md
index 8cfd64f..bc7a9ad 100644
--- a/AyCode.Core/docs/BINARY/BINARY_ASYNCPIPE_ISSUES.md
+++ b/AyCode.Core/docs/BINARY/BINARY_ASYNCPIPE_ISSUES.md
@@ -74,6 +74,48 @@ The current class summary implicitly assumes one of these patterns; the public A
**Possible documentation direction** tracked in [`BINARY_ASYNCPIPE_TODO.md#accore-bin-t-s5n2`](BINARY_ASYNCPIPE_TODO.md#accore-bin-t-s5n2-pattern-catalogue-in-the-public-class-summary).
+### ACCORE-BIN-I-B7K9: AsyncPipeWriterOutput single-value writes > MaxChunkSize data bytes not supported
+
+**Status:** Open
+**Affects:** `AsyncPipeWriterOutput.AcquireChunk` (and indirectly any `BinarySerializationContext.EnsureCapacity` call where the request exceeds per-chunk data capacity)
+**Reach:** any single serialized value whose worst-case byte-count exceeds `MaxChunkSize - HeaderSize` (~65 532 byte in framed mode). Typical trigger: a `string` property with > ~16 383 chars (writer pre-reserves `4 * charLength + 9` bytes for worst-case UTF-8 encoding); also `byte[]` or other values larger than the chunk-data capacity.
+
+**Symptom:** `BinarySerializationContext.EnsureCapacity` now surfaces a clear `InvalidOperationException` ("Output.Grow did not satisfy the requested capacity: N bytes requested, only M bytes available after Grow") instead of the previous downstream `ArgumentOutOfRangeException` from `_buffer.AsSpan(start, length)` / `CopyTo` at corrupt offsets. The exception is intentional — surfaces the chunk-limit-vs-request mismatch at the precise call site, no silent wire corruption.
+
+**Root cause:** The multi-message wire format uses `[201][UINT16 size][data]...[202]` framing. The `UINT16` cap (`MaxChunkSize = 65 535` total = 65 532 data bytes in framed mode) is a wire-format-level constraint. Currently `AcquireChunk` clamps any single contiguous request to this cap; values requiring more contiguous space in one Grow-event cannot be served by chunk-pool memory.
+
+**Why intentional limit (UINT16 size field):** The wire-format `UINT16 size` field is a deliberate compactness trade-off — 1-byte overhead per chunk header is minimal vs UINT32 (would cost +2 bytes per chunk overhead, measurable on small-chunk transports). The cap is intrinsic to the framing format; the FIX is at the producer side (transparent split-on-commit), not the wire format.
+
+**Workarounds (current — pre-fix):**
+- **Avoid single string values > ~16 383 chars** in serialized graphs (4-byte/char worst-case keeps `4 * charLength + 9 ≤ 65 532`). Real workloads with mostly-ASCII content can practically reach ~65K chars before hitting the cap, but the safe-by-construction limit is 16K.
+- **External chunking**: pre-split very large strings (file contents, blobs) into multiple smaller properties or use a dedicated `byte[]` array property with consumer-side chunking.
+- **Use a non-Pipe transport for the affected message types** — `byte[]` / `BufferWriter` output mode has no chunk-cap (`ArrayBinaryOutput.Grow` / `BufferWriterBinaryOutput.Grow` size to whatever is needed).
+
+**Related semantic fix in same session (off-by-headerOffset):** `AsyncPipeWriterOutput.Grow` previously passed `Math.Max(needed, _chunkSize)` to `AcquireChunk`, mixing units (`needed` is data-bytes, `_chunkSize` is chunk-on-wire = header + data). `AcquireChunk` subtracts `headerOffset` from the request internally to size the data region — so every Grow request was short by `headerOffset` (3) bytes. Fixed: `Math.Max(needed + headerOffset, _chunkSize)` aligns both terms to chunk-on-wire scale. This closes the off-by-3 chunk-shortage band (1021..16383 char range was affected even though < cap); the `> MaxChunkSize` band remains a separate concern tracked here.
+
+**Possible fix direction (transparent split-on-commit):**
+
+Implement an owned-buffer fallback inside `AsyncPipeWriterOutput`:
+
+1. `AcquireChunk` detects `requestSize > MaxChunkSize - headerOffset` → force `_ownedBuffer = ArrayPool.Shared.Rent(requestSize)` path (instead of `_pipeWriter.GetMemory`). Returns the contiguous owned buffer; `_hasOwnedBuffer = true`.
+2. Hot-path writers (`WriteStringWithDispatch`, `WriteByteArray`, etc.) write into the owned buffer unchanged — no API contract change, no caller-side awareness.
+3. `CommitCurrentChunk` (or `Flush`) on a `_hasOwnedBuffer = true` state splits the owned buffer into `MaxChunkSize - headerOffset`-sized data chunks, each framed with its own `[201][UINT16 size]` header into the `_pipeWriter`'s memory, and emits them sequentially. The `[202]` end marker remains owned by `Flush` (only DATA chunks are split — the message-end marker is per-message, not per-chunk).
+4. Receiver side (`AsyncPipeReaderInput.Feed` framing-state-machine) is **not affected** — the wire still arrives as a stream of `[201]`-framed data chunks; the sliding-window buffer reassembles bytes for the deserializer regardless of how the producer split them.
+
+Allocation profile: 1× ArrayPool rent + 1× return per `>MaxChunkSize` event. ArrayPool slab reuse keeps per-event overhead near-zero on repeated occurrences. Hot path (`≤ MaxChunkSize` values) entirely unchanged.
+
+**Acceptance** (for the future TODO entry):
+- New test: serialize an entity with a string property whose UTF-8 byte length > 65 532 bytes over `AsyncPipeWriterOutput`; round-trip equality holds.
+- Test: serialize a `byte[]` property > 65 532 bytes; round-trip equality holds.
+- Test: mixed message — small + large + small string properties on the same message → all wire frames valid, deserializer reconstructs each in order.
+- Wire format unchanged — receiver code untouched.
+- Allocation profile: 1 rent + 1 return per >cap event; no allocation regression on ≤cap path.
+
+**Cross-references:**
+- Sanity guard added: `BinarySerializationContext.EnsureCapacity` → `GrowAndValidate` → `ThrowGrowFailedToSatisfy` (clear diagnostic instead of silent under-provisioning).
+- Off-by-headerOffset semantic fix: `AsyncPipeWriterOutput.Grow` line ~287 (see "Related semantic fix" above).
+- Wire format: `BINARY_FORMAT.md` (framing constants).
+
## Streaming protocol — resolved issues
### ACCORE-BIN-I-H4G2: Chunk-on-wire size = `chunkSize + HeaderSize` caused page-fragmentation
diff --git a/AyCode.Core/docs/BINARY/BINARY_FEATURES.md b/AyCode.Core/docs/BINARY/BINARY_FEATURES.md
index e50f01f..10cd08f 100644
--- a/AyCode.Core/docs/BINARY/BINARY_FEATURES.md
+++ b/AyCode.Core/docs/BINARY/BINARY_FEATURES.md
@@ -2,6 +2,8 @@
Advanced serialization features on top of the wire format. Wire format: `BINARY_FORMAT.md` | Options/presets: `BINARY_OPTIONS.md` | Internal architecture: `BINARY_IMPLEMENTATION.md` | Source generation: `BINARY_SGEN.md`.
+> **Architectural framing — why use AcBinary at all?** See [`BINARY_WHYUSE.md`](BINARY_WHYUSE.md) for the category positioning vs wire-only serializers (Protobuf / MessagePack / MemoryPack), the three-pillar value proposition (graph integrity + bandwidth + streaming), real-world WASM SignalR reference numbers, fit/not-fit decision lists, and the framing for how to read AcBinary's benchmark numbers.
+
## Optimization Policy (LLM)
AcBinary is a **general-purpose serializer**, not a benchmark-only implementation.
diff --git a/AyCode.Core/docs/BINARY/BINARY_TODO.md b/AyCode.Core/docs/BINARY/BINARY_TODO.md
index 2a6dbcd..8f2aec1 100644
--- a/AyCode.Core/docs/BINARY/BINARY_TODO.md
+++ b/AyCode.Core/docs/BINARY/BINARY_TODO.md
@@ -1352,6 +1352,8 @@ A V4N4 audit **konklúziója** változatlan érvényes (constant-fold OK, reader
**Re-evaluable as of 2026-05-07 per `ACCORE-BIN-T-D9X3`** — bench stabilization removes the noise-floor that made the original signal unmeasurable; retest before any code change.
**Obsoleted (2026-05-08) by `ACCORE-BIN-T-K7M3`** — the writer hot path no longer calls the custom `EncodeUtf8SinglePass` at all (`WriteStringWithDispatch` was switched to `Utf8.FromUtf16` BCL). The "AOT method-split / inlining audit" target (`Utf8Transcoder` body method-size in NativeAOT inline budget) is moot — the BCL `Utf8.FromUtf16` is a single static method with its own AOT-friendly inline footprint, and the audit's hypothesis space (Vector256 `IsSupported` constant-fold, lambda delegate cache) was correct for the prior code but no longer applies. The V4N4 disasm methodology remains a **valid technique** for future investigations of generic specialization / inline failures, but the specific hot-path target it analyzed is gone.
+
+## ACCORE-BIN-T-J5L9: Remove dead `WriteFixStrDirect` / `WriteStringUtf8Internal` (audit-surfaced uncalled methods)
**Priority:** P3 · **Type:** Refactor / hygiene · **Status:** Closed (2026-05-06) · **Related:** `BinarySerializationContext.cs`
V4N3 audit surfaced two methods with no callers in the entire workspace:
diff --git a/AyCode.Core/docs/BINARY/BINARY_WHYUSE.md b/AyCode.Core/docs/BINARY/BINARY_WHYUSE.md
new file mode 100644
index 0000000..c242b18
--- /dev/null
+++ b/AyCode.Core/docs/BINARY/BINARY_WHYUSE.md
@@ -0,0 +1,62 @@
+# Why AcBinary?
+
+Architectural framing: where AcBinary fits in the serializer landscape, what value it adds over wire-only serializers, and how to read its benchmark numbers in that context.
+
+> Companion docs: features detail in [`BINARY_FEATURES.md`](BINARY_FEATURES.md) · wire format in [`BINARY_FORMAT.md`](BINARY_FORMAT.md) · options/presets in [`BINARY_OPTIONS.md`](BINARY_OPTIONS.md) · streaming I/O in [`BINARY_ASYNCPIPE_ISSUES.md`](BINARY_ASYNCPIPE_ISSUES.md) + [`BINARY_ASYNCPIPE_TODO.md`](BINARY_ASYNCPIPE_TODO.md).
+
+## Category
+
+AcBinary serves a **different category** than wire-only serializers (Protobuf, MessagePack, MemoryPack). It is a **graph-aware** serializer with referential integrity preserved on the wire via IdTracking + StringInterning, and on the receive side via a populate-merge deserialization path that reuses existing reference identity. The primary value is **correctness and developer-ergonomics in stateful live-data scenarios** (Blazor / MAUI / WPF binding, SignalR streaming, server-push reconciliation) — **not** raw single-shot throughput on flat DTO RPC.
+
+## Three-pillar value proposition
+
+### 1. Graph integrity → populate-merge correctness in bound UIs
+
+On a live-data UI, an incoming server update must merge into the in-memory graph **without** breaking binding references, child-list identity, or duplicating shared sub-objects (`Product`, `Customer`, `Partner`, etc. — entities referenced from many parent rows). Wire-only serializers always reconstruct a fresh tree per call → the consumer hand-codes the merge / rebinding logic, which is fragile and verbose. AcBinary's `IId`-keyed reference tracking preserves identity end-to-end: shared sub-objects deduplicate on the wire AND on the client, existing references survive the round-trip, bindings stay valid, change-tracking continues uninterrupted. This is the **central value** for Blazor components, `IList`-bound MAUI grids, and SignalR datasource subscribers.
+
+### 2. Bandwidth → reference + string deduplication on the wire
+
+On real production graphs (many `Order → OrderItem → Product → Customer → Partner → GenericAttribute` references sharing the same back-end entities), IdTracking and StringInterning emit each unique object/string **once** as the full body, then 1-2 bytes per subsequent reference. Wire-only serializers re-emit the full object body every time. Bench numbers (at **20% `IId`-ref-rate** in the test fixture):
+
+- **Latin1Long charset**: -6.9% arith / -7.4% geo wire size vs MemoryPack
+- **Latin1FixAscii charset**: -18.2% arith / -21.3% geo wire size vs MemoryPack
+
+Production graphs with higher ref-rate (the typical case — many rows pointing at the same `Product`/`Customer`) see significantly larger savings; see [How to read AcBinary benchmarks](#how-to-read-acbinary-benchmarks) below.
+
+### 3. Streaming → memory pressure mitigation on memory-constrained hosts
+
+`AsyncPipeReaderInput` + `AsyncPipeWriterOutput` deliver chunked I/O with **peak memory ≈ chunk-size**, not full payload size. On WASM (Mono / NativeAOT-LLVM), a 10 MB monolithic `byte[]` allocation triggers GC pressure or OOM under concurrent task load; chunked delivery keeps the working set bounded. Combined with the wire-size win, decode CPU drops proportionally — **fewer bytes on the wire → fewer varint + UTF-8 decode operations** during deserialization. SignalR's `AcBinaryHubProtocol` consumes via `AsyncPipeReaderInput` directly, no monolithic buffer materialization.
+
+## Real-world reference: WASM SignalR receive
+
+Measured production payload: ~10 MB / ~7900 orders (full graph `Order → OrderItem → Product → Customer → Partner → GenericAttribute`), 4 SignalR messages, Blazor WASM client.
+
+- **Before AcBinary (MemoryPack baseline)**: ~8 seconds Deser for ~4K records / ~20 MB payload — the MemoryPack output was nearly 2× larger because the same `Product`/`Customer` entities re-serialized per Order. JSON baseline was ~30 seconds.
+- **After AcBinary**: ~470 ms Deser for ~8K records / ~10 MB payload — wire compaction from graph deduplication + chunked-pipe streaming + decode-CPU reduction stack multiplicatively.
+
+The speed-up is **not** primarily "AcBinary is faster than MemoryPack on the same bytes." It is **"AcBinary emits ~50% fewer bytes for the same graph, then decodes them with bounded memory pressure"**. The feature stack is the win — single-cell bench Ser/Deser ratios alone do not capture it.
+
+## When AcBinary fits
+
+- Live-data UIs requiring graph-merge into a bound client-side model (Blazor / MAUI / WPF / WinForms with two-way binding, SignalR datasource subscribers)
+- SignalR / WebSocket / IPC transports with repeated entity references across messages
+- Server-push reconciliation flows where ID-keyed identity must survive the wire round-trip
+- Memory-bounded clients (WASM, mobile, embedded) receiving large object graphs
+- Workloads where **wire-size + decode-CPU + memory-peak** matter together, not just single-message raw Ser throughput
+
+## When AcBinary is NOT the right fit
+
+- Single-shot DTO RPC with no entity reuse (e.g., `(int, int) → int` calculator endpoints) — wire-only serializers have less per-message overhead on flat payloads
+- Schema-evolving public contracts where wire-format stability across language ecosystems is the primary requirement → Protobuf is the established choice
+- Cross-platform clients on big-endian hosts → currently unsupported on the wire (see [`BINARY_ISSUES.md#accore-bin-i-e4n9`](BINARY_ISSUES.md#accore-bin-i-e4n9-wire-format-is-host-native-endian-not-canonical-little-endian))
+- Append-only log formats where each record is independent — IdTracking's scan-phase overhead is wasted when there are no shared references to deduplicate
+
+## How to read AcBinary benchmarks
+
+`Console.FullBenchmark` test cells use **20% `IId`-ref-rate** test fixtures — flat enough to compare against wire-only serializers like MemoryPack on the same data shape. The cells **deliberately undermeasure** the feature wins:
+
+- Production graphs typically have 50-90% `IId`-ref-rate (many rows → same `Product` / `Customer`); IdTracking's wire-size win on those graphs is **multiples larger** than the bench numbers show.
+- `Repeated Strings` is the only bench cell stress-testing StringInterning. Other cells have low string-repetition, so the interning win there is bounded by the fixture.
+- The bench does NOT cover the `AsyncPipe` path (only `Byte[]` and `BufferWriter` I/O modes). The streaming-memory advantage doesn't surface in the bench numbers.
+
+Bench wins on the current fixture (-6 to -10% RT, -7 to -18% size) should be read as a **lower bound** for AcBinary's production advantage on graph-shaped, ref-heavy workloads. The matching position against MemoryPack on flat-payload Ser/Deser is **intentional** — AcBinary should not pay a tax for its features on workloads that don't benefit from them. The cases where the features pay back are workloads the bench does not measure directly; the WASM reference numbers above are the production-scale signal.
diff --git a/AyCode.Core/docs/BINARY/README.md b/AyCode.Core/docs/BINARY/README.md
index aa7b22b..f97678c 100644
--- a/AyCode.Core/docs/BINARY/README.md
+++ b/AyCode.Core/docs/BINARY/README.md
@@ -4,6 +4,7 @@ AcBinary serialization system. Primary goal: **speed** (two-phase scan+serialize
## Files in this folder
+- [`BINARY_WHYUSE.md`](BINARY_WHYUSE.md) — Architectural framing: why AcBinary exists (category vs wire-only serializers, three-pillar value proposition, fit/not-fit decision lists, benchmark reading guide)
- [`BINARY_FEATURES.md`](BINARY_FEATURES.md) — High-level features and capabilities
- [`BINARY_FORMAT.md`](BINARY_FORMAT.md) — Wire format specification
- [`BINARY_OPTIONS.md`](BINARY_OPTIONS.md) — Configuration options (`AcBinaryOptions`)
@@ -17,7 +18,7 @@ AcBinary serialization system. Primary goal: **speed** (two-phase scan+serialize
## Start here
-Start with [`BINARY_FEATURES.md`](BINARY_FEATURES.md) (overview), then [`BINARY_FORMAT.md`](BINARY_FORMAT.md) (wire-level). [`BINARY_SGEN.md`](BINARY_SGEN.md) covers build-time code-gen integration.
+Start with [`BINARY_WHYUSE.md`](BINARY_WHYUSE.md) (architectural framing — why this serializer over the alternatives), then [`BINARY_FEATURES.md`](BINARY_FEATURES.md) (feature overview), then [`BINARY_FORMAT.md`](BINARY_FORMAT.md) (wire-level). [`BINARY_SGEN.md`](BINARY_SGEN.md) covers build-time code-gen integration.
## Cross-references