m05 · Baking · reading · 9 min
The conversion problem — two irreconcilable data models
You have been building worlds with no way to author them
Four modules in, you can reason about archetypes, CreateEntity and AddComponent. That works for a probe. It does not
work for a game, because a game has scenes — thousands of placed objects,
each with tuned values, arranged by a human dragging things in a viewport.
So there’s a gap. The thing a designer manipulates is a hierarchy of
GameObjects with MonoBehaviours on them: managed C# objects, each a
separate heap allocation, connected by references, with an Inspector drawing
their fields. The thing your systems iterate is an entity in a chunk: 16 KB of
tightly-packed unmanaged structs with no references and no identity beyond an
index. Something has to turn the first into the second. That something is
baking, and this module is about why it takes the shape it does.
Before naming a single API, ask the question §0 always asks: what constraints force the design? Answer that and the API becomes derivable.
The two models are opposites, on purpose
Start by being precise about the distance between them. This isn’t a formatting difference; the models optimize for contradictory goals.
The authoring model exists to be manipulated by a human. That demands
identity (this specific enemy, which I can select and rename), references
(this spawner points at that prefab), heterogeneity (any object may carry any
combination of components), and mutability at any moment — a designer changes
a value and expects to see it. Every one of those wants is served by scattered
heap objects with pointers between them. That is exactly what GameObject is,
and it is the right shape for its job.
The runtime model exists to be traversed by a CPU. Module 0 established the constraint: a cache miss costs ~200 cycles, so throughput belongs to whoever keeps the prefetcher fed. Module 1 turned that into structure — entities of identical shape packed contiguously into chunks, no references, no per-entity identity in the layout, homogeneity as the whole point. Every want of the authoring model is a cost in the runtime model, and vice versa.
This is the load-bearing observation of the entire module: the two models are not merely different, they are optimized against each other. No single representation serves both. Which means the honest options are exactly three.
Three ways to bridge, two of which are wrong
Option A — make authoring use the runtime model. Design scenes directly as chunk data. This fails on human factors, not technical ones: you’d be hand-editing packed binary with no Inspector, no drag, no undo, no prefab overrides. Tooling is not a nicety; a scene format nobody can author is not a scene format.
Option B — convert at runtime, when the scene loads. Ship the
GameObjects, walk them at startup, produce entities. This is the intuitive
answer and it’s the one worth killing carefully, because why it fails is the
entire justification for baking’s shape.
Conversion is not cheap work. For every authored object you must: allocate and
initialize the managed object graph, resolve inter-object references, run
whatever MonoBehaviour logic decides the final values, determine the
resulting archetype, then create an entity and copy the data in. That is heap
allocation, pointer chasing, and
Worse, you’d be paying it repeatedly to produce an identical result. The conversion of a static scene is a pure function of that scene. Nothing about the player’s machine changes the answer. Running it at load time recomputes, on every launch, a value that was already knowable the moment the designer hit save. That’s the definition of work that belongs offline.
Option C — convert offline, ship the result. Run the conversion at build time (and, for iteration, in the background while the editor is open). The output is entity data in its final chunk-ready form, serialized to disk. At runtime you don’t convert anything; you stream bytes into memory that are already laid out correctly. Load time becomes an I/O problem, not a computation problem.
This is baking. And notice you just derived it: given two opposed data models and a conversion that is a pure function of authored input, offline conversion is the only option that keeps both the tooling and the runtime layout.
What “offline” forces you to accept
The choice isn’t free, and the costs are load-bearing for the rest of the module — every baking API you’ll meet exists to manage one of them.
Baking must be deterministic. Its output is shipped and cached; the same input must always produce the same bytes. That is why baking code cannot read the current time, use unseeded randomness, query player hardware, or depend on what got baked before it. A non-deterministic baker produces a build that differs from the one you tested.
Baking cannot depend on runtime state. There is no player, no physics step, no elapsed frame. Anything that varies per-run must be computed by a system at runtime, not by a baker. This cleanly divides your logic: the fixed, knowable-at-build-time part bakes; the varying part runs.
Changing an authored value invalidates baked output. Since the result is cached, the system must know what to re-bake when a designer nudges a field. Getting this wrong means either stale data (you edited, nothing changed) or re-baking everything constantly (iteration crawls). This dependency-tracking problem is why the baking API is shaped the way it is rather than being a free function you call — the next lesson is entirely about it.
The authored objects don’t ship. Once baked, the GameObjects have served
their purpose. They are a source format, like a .psd that becomes a
compressed texture. The runtime never sees them, which is why runtime code
cannot look up “the GameObject this entity came from” — there isn’t one.
What this buys you
You can now explain why a conversion step must exist at all, why it cannot run at load time without paying a repeated cost for a knowable answer, and why the offline choice forces determinism and dependency tracking rather than merely suggesting them. Next: the Baker — the mechanism that performs this conversion and, more importantly, tracks exactly what each conversion depended on.