> For the complete documentation index, see [llms.txt](https://wise-shepherd-games.gitbook.io/theia-ecs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wise-shepherd-games.gitbook.io/theia-ecs/serialization.md).

# Serialization

One-call world save and load via MessagePack

Theia provides **one-call world serialization built on** [**MessagePack**](https://github.com/MessagePack-CSharp/MessagePack-CSharp). No per-type attributes, no surrogate types, no manual registration: **component and relation structs serialize as-is**.

MessagePack is a binary format. Binary serialization produces smaller files and deserializes significantly faster than JSON. A world with thousands of entities and dense component data would be slow to save and load as text, and **the human-readability of JSON offers no practical benefit for a save file that is never meant to be hand-edited**.

```csharp
/* Save */
world.Serialize("save.bin");

/* Load */
world.Deserialize("save.bin");
```

Both throw if any query is currently executing or a deferred flush is in progress.

#### What Is Serialized

A single call to `Serialize` captures the entire world state:

* **Every entity and its component data**;
* The **full relation graph**: both tag and evaluated relations, with their payloads;
* All **uniques** and their current values.

#### What Is Not Serialized

* **Assemblages and queries**: structural registrations are recreated at world setup, not persisted;
* **Resources**: managed reference-type data is expected to be reloaded at startup from the game's asset pipeline.

#### Async Variants

For save points where blocking the main thread is undesirable, `async` variants are available. The world's data transfer object is built synchronously before the file write begins; the async portion is the I/O only:

```csharp
/* Save */
await world.SerializeAsync("save.bin");

/* Load */
await world.DeserializeAsync("save.bin");
```

Both accept an optional `CancellationToken` and custom `MessagePackSerializerOptions`.

#### Type Name Contract

Theia matches component and relation types by **their fully-qualified assembly name**, resolved at deserialization time via reflection. The reflection path is cached, it runs once per type on first encounter, not on every load, so the convenience of attribute-free structs comes at no ongoing runtime cost.

**This is what enables zero-ceremony serialization on the component side.** The tradeoff is that the fully-qualified type name becomes part of the save format contract. If a type cannot be resolved at deserialization time, **the world throws**; it does not silently skip the affected archetype or relation and continue loading a partially restored world.

**Renaming a component or relation type after shipping is a breaking change to existing save files**. Treat the fully-qualified type name the same way you'd treat a database column name: **stable by design**.

An explicit stable key per type, such as an integer attribute or a string tag, sounds like a solution until you consider that the developer can change that too, and then it breaks just the same. The name-as-contract approach is stricter but **honest**: the failure is loud and immediate rather than silent and corrupted.

#### Custom Components with Inline Arrays

Components that contain **inline arrays require a custom MessagePack serializer and deserializer**. The default contractless resolver does not handle `[InlineArray]` types. Supply custom `MessagePackSerializerOptions` with the appropriate resolver registered for those types:

```csharp
MessagePackSerializerOptions options = /* Custom options with inline array resolver */;

world.Serialize("save.bin", options);
world.Deserialize("save.bin", options);
```

The same options must be used for both serialize and deserialize.

#### Entity IDs

Entities cannot keep their original handles across deserialization. The destination world has its own slot allocator and version counters. Handles are remapped internally during the load and the relation graph is fully restored using the new handles, so **cross-entity references remain consistent after load**.

This also benefits performance: previously ghoulified entity slots, empty archetype chunks, and **any fragmentation accumulated during the source world's lifetime are discarded**. The deserialized world starts compact and clean.
