DOTS//CORE local · not synced

m05 · Baking · reading · 9 min

Entity scenes — why baked data loads as bytes, not as calls

What the bakers actually produced

Every baker in a scene runs, and the result is a World — populated with entities, in archetypes, laid out in chunks. That’s the artifact. The question of this lesson is how that artifact gets to a running game, and the answer explains why entity scene loading behaves so unlike anything in the GameObject world.

The naive serialization would be a recipe: a list saying “create an entity with these components and these values,” repeated per entity, replayed at load time. It would work. It would also throw away most of what baking bought you, because replaying that recipe means running the entire structural-change machinery from Module 1 — compute archetype, find or allocate a chunk, write each component — 50,000 times, on the main thread, at load.

The actual design skips it. Since baking already produced chunks, and a chunk is a flat 16 KB block of unmanaged memory with a known layout, the serializer writes the chunks themselves. Loading is then reading bytes off disk into memory that is already in final form. No archetype computation, no per-entity component writes, no structural change. The entities exist because their memory exists.

cache line · 64 B fetched 64 B useful · 100% of the bandwidth you paid for a serialized chunk: already packed, already in final layout, already the exact bytes the runtime will iterate — loading is a copy, not a construction

That is the payoff sentence of the whole module. Baking doesn’t just precompute values; it precomputes memory layout. The reason load is fast isn’t that conversion was skipped — it’s that the expensive part of conversion was the structural work, and structural work is exactly what a byte-for-byte chunk image eliminates.

Instantiate versus stream, priced

This reframes a decision you’ll make constantly, so price it explicitly.

Instantiating a prefab at runtime does real work per entity: allocate into a chunk, copy component values, possibly trigger a chunk allocation, and — if done via a command buffer — participate in a sync point (Module 2). Cost scales with entity count and with archetype complexity, and it lands on the main thread at the moment of the call.

Streaming a scene section loads a contiguous block. Cost scales with bytes and with I/O bandwidth, and the read can happen off the main thread; the main thread’s share is close to a pointer handoff. Ten thousand static props cost roughly what their bytes cost, not what ten thousand structural changes cost.

The design consequence follows directly: content that exists in the authored world should be baked and streamed, not instantiated. Reach for instantiation when entities are genuinely dynamic — spawned by gameplay, count unknown at build time. Using instantiation for authored content converts a byte copy into fifty thousand structural changes, which is the same category of mistake as doing structural changes inside a per-frame loop.

Sections make this granular. A scene divides into sections that load and unload independently, which is how open-world streaming works here: section 0 holds the always-resident content, and distant sections arrive as bytes when needed and are dropped by freeing their chunks. Unloading is as cheap as loading for the same reason — you’re releasing memory, not destroying 50,000 entities one at a time.

The one thing bytes can’t survive: references

There is a genuine complication, and it falls out of a fact from Module 1 rather than from any baking decision.

An Entity is not a pointer. It’s an index plus a version — a handle whose meaning depends on which World it lives in. At bake time, entity 412 in the baked world referred to a specific thing. At load time, that world already has its own entities occupying low indices, and index 412 means something else entirely, or nothing.

So any baked component holding an Entity field — a turret referencing its target, a spawner referencing a prefab — contains an index that is wrong the moment it’s loaded into a different world. Left alone, it silently refers to an unrelated entity. That is a correctness bug, not a performance one.

The fix is remapping: the serialized scene carries a table of which entity references live where, and on load the streaming system patches each stored handle to the new world’s index. It’s a fixup pass over a known list, so it costs proportional to the number of references, not the number of entities.

Why baked scenes make iteration feel strange

One practical consequence worth stating, because it produces a confusing first experience. Because the runtime consumes baked output rather than the authored objects, editing an authoring value doesn’t change a running game until that value is re-baked and the section reloaded. The editor works hard to make this feel immediate — it re-bakes in the background and swaps the result in — but the indirection is real, and it’s the reason a baking change can appear not to apply.

When that happens, the diagnosis order follows straight from the last lesson: did the value get read through the Baker (tracked), or around it (untracked and therefore never re-baked)? The stale-value symptom and the untracked-read bug are the same event seen from two ends.

What this buys you

You can now explain why baked scenes serialize memory instead of instructions, why streaming authored content beats instantiating it by an order that scales with entity count, and why entity references need remapping while raw values don’t. Next: prefabs and blob assets — how baking handles the two things that must be shared across many entities rather than copied into each one.

m05.l04