Everything lives in `Udonite.Serialization`.

## ByteWriter

```csharp
new ByteWriter()             // default starting capacity
new ByteWriter(64)           // starting capacity in bytes; grows past it as needed
```

| Member | Writes |
|---|---|
| `WriteByte(byte)` | one byte |
| `WriteBoolean(bool)`, `WriteChar(char)` | fixed-width |
| `WriteInt16`, `WriteUInt16`, `WriteInt32`, `WriteUInt32`, `WriteSingle`, `WriteDouble` | fixed-width, `System.BitConverter`-backed |
| `WriteString(string)` | length-prefixed (`0` for `null`), one character at a time |
| `WriteBytes(byte[])` | length-prefixed raw bytes — the seam a nested payload rides through |
| `WriteVector2`, `WriteVector3`, `WriteQuaternion` | component by component |
| `WriteLength(int)` | a count in as few bytes as it needs — one byte up to 127, two up to 16383 |
| `Position { get; }` | bytes written so far |
| `Reset()` | rewinds to empty without allocating a new buffer |
| `ToArray()` | a right-sized copy — the over-allocated tail is never part of it |

Every `Write` method returns `this`, so calls chain: `new ByteWriter().WriteInt32(1).WriteString("a")`.

**Deliberately a core, not a full mirror of `System.BitConverter`.** No 64-bit integers, no
8-bit-signed, no 16-bit-narrower-than-`short`, no `Vector2Int`. A reached class is compiled
whole, and every method it declares costs heap for the life of the program — the surface here is
exactly what a payload plausibly carries.

## ByteReader

```csharp
new ByteReader(bytes)
```

| Member | Reads |
|---|---|
| `ReadByte()` | one byte |
| `ReadBoolean()`, `ReadChar()` | fixed-width |
| `ReadInt16`, `ReadUInt16`, `ReadInt32`, `ReadUInt32`, `ReadSingle`, `ReadDouble` | fixed-width |
| `ReadString()` | a `WriteString` payload back |
| `ReadBytes()` | a `WriteBytes` payload back |
| `ReadVector2`, `ReadVector3`, `ReadQuaternion` | component by component |
| `ReadLength()` | a `WriteLength` count back |
| `Position { get; }`, `Length { get; }`, `LengthRemaining { get; }` | where the cursor is, how big the buffer is, and what is left |
| `SetBuffer(byte[])` | reuses this reader on a different buffer, cursor reset to the start |

**A read past the end logs and returns the type's default rather than throwing.** Exception
handling is unproven in Udonite, and a malformed or truncated buffer must not take the reading
behaviour down with it. Read in the same order you wrote, and read everything you wrote — nothing
here can tell you the shape was wrong, only that a read ran out of bytes.

## Json

| Member | Does |
|---|---|
| `Serialize<T>(T)` | Packs `T` to JSON, by way of its own `ToJson()`. Null if `T` is null or VRCJson refuses it |
| `Deserialize<T>(string)` | Parses JSON, builds a new `T` (`where T : class, IJsonSerializable, new()`), fills it by way of `FromJson`. Null on malformed JSON |
| `Serialize(DataToken)`, `Serialize(DataToken, bool pretty)` | The low-level form, for JSON built ad hoc with no fixed type behind it |
| `Deserialize(string)` | Returns a `DataToken?` directly, the same escape hatch in reverse |

**Null on failure throughout**, never a `bool` and an `out` parameter.

**No reflection-based `Serialize<T>` that walks an arbitrary type's fields.** Udon has no
reflection at all — confirmed directly against the compiler, not assumed. `Deserialize<T>`'s
`where T : new()` is not reflection either: it is a constraint the C# compiler resolves at the
call site, so it compiles even though nothing here could call `Activator.CreateInstance`.

**Every JSON number parses as `TokenType.Double`, even a bare integer.** `{"score": 42}` parses
to a token whose `.Int` throws — only `.Double` (cast to whatever you need) reads it. This is a
fact about `VRCJson` itself, not something this wrapper changes.

## IJsonSerializable

| Member | Does |
|---|---|
| `DataToken ToJson()` | Packs this instance, typically into a `DataDictionary` |
| `void FromJson(DataToken token)` | Fills this instance from what `ToJson` produced |

A field either method does not mention is never sent and never filled — nothing here inspects a
type's members automatically. `FromJson` mutates the instance it is called on rather than
returning a new one, the same way `NetworkEvent.Deserialize` does in Udonite Net: nothing here
can construct an arbitrary type without either `new()` or a caller who already made one.
