World Streaming
A scene loads whole. That is the right answer until the world is bigger than a machine wants resident at once — and then the question stops being “what is drawn” and becomes “what exists”. World streaming answers the second one: the cook cuts an authored world into square cells, and at runtime a cell exists because something is near it, and stops existing when nothing is.
Nothing about this is a visibility trick. A cell that leaves is destroyed: its entities, its physics bodies, its navigating agents and its asset references all go. That is the point — a place that is merely hidden still costs memory, still collides, and still lets an enemy nobody can see attack the player.
Declaring a streamed world
Section titled “Declaring a streamed world”Two components, both authored in the scene:
// On the world's settings entity: this scene is cut into 1000-unit cells.world.insert(settings, StreamedWorld, { cellSize: 1000 });
// On everything that must never be streamed away.world.insert(player, WorldPersistent, {});world.insert(camera, WorldPersistent, {});world.insert(sun, WorldPersistent, {});Everything else is placed into a cell by where its top-level entity stands.
A scene with no StreamedWorld in it is not a streamed world and loads exactly
as it always did.
Asking for places: streaming sources
Section titled “Asking for places: streaming sources”A cell exists because a source is near it:
world.insert(player, WorldStreamingSource, { prefetchRadius: 3000, // cells this close are READIED, not brought in loadRadius: 2000, // cells this close are brought in unloadRadius: 3000, // resident cells are kept until past this});Several sources may exist, and what they ask for is unioned — a cell any one of them needs stays resident. That is what makes split screen, a spectator camera, a cinematic camera and an editor preview the same kind of thing rather than four special cases. Nothing folds them into a single focus, because a fold lets the last one written decide, and the frame after a second camera exists it deletes the world the first is standing in.
Distance is measured to the cell’s box, not its centre: to a centre, a source standing on a diagonal corner reads the same as one a whole cell away, which is where holes open along the diagonals of a grid.
Readied before it is wanted
Section titled “Readied before it is wanted”A cell inside prefetchRadius is prepared: its document is fetched, its
prefabs expanded, its assets acquired — and not one of its entities exists. So
nothing can observe it. No query returns it, no renderer draws it, no body
collides with it, no enemy in it decides anything, because there is nothing
there yet. Publication is the only thing that creates entities, and it happens
when a source actually reaches loadRadius.
That is worth about twenty milliseconds of a player’s time. On Estella’s own fixture, walking into a readied place costs 0.6 ms; walking into an unreadied one costs the fetching and decoding too.
Preparation is speculation, never authority. A cell readied for a player who turns around is thrown away and its assets given back — never published because it happened to be ready. And a preparation that finishes asks again whether the cell is still wanted, rather than assuming the answer from when it started.
prefetchRadius is never used as less than loadRadius; beyond that it is not
ordered against unloadRadius at all. They answer different questions — how
early to get ready, and how late to let go.
The band between the two radii
Section titled “The band between the two radii”A cell comes in at loadRadius and only leaves once it is past unloadRadius.
The gap between them is the whole reason both exist: a source standing on a
boundary would otherwise spend every frame instantiating and destroying a place.
Set unloadRadius comfortably larger than loadRadius — half again is a good
starting point.
What the cook produces
Section titled “What the cook produces”Partitioning happens at cook time, not at boot:
main.esscene ──cook──▶ main.esscene the persistent world world/main.world.json the manifest world/main.cell_0_0.json world/main.cell_1_0.json ...The entry scene the package boots is what is left after the cells are taken out of it — the things residency never removes. Cells are not switchable scenes: they are content residency brings in, and a game never names one.
Cells are cut on the XZ plane only. A cell is a vertical column, so a tower’s floors are one place.
The rules a streamed world has to obey
Section titled “The rules a streamed world has to obey”A subtree is the unit of residency. Only top-level entities are placed, so a door cannot be sorted away from its house even if the door’s world position falls in the next cell. A cell’s box grows to cover its content, so a prop hanging over the edge does not pop in at the boundary.
Hard entity references may not cross a residency boundary. Two entities in one cell may reference each other; so may anything in the persistent world; and a cell may reference the persistent world, which outlives it. A reference from one cell into another — or from the persistent world into a cell — fails the build, because either one points at something residency is entitled to delete.
Residency owns existence; it does not own continuity. A cell that unloads loses its runtime state, and reloading rebuilds it from what was authored. An enemy at 25 health that you walk away from comes back at full health. What does survive is the entity’s authored identity, which is what a save system will be built on later.
Reading what is resident
Section titled “Reading what is resident”const report = worldResidencyReport(app);report.residentCells; // ['main.cell_1_0', 'main.cell_2_0']report.cellEntityCounts; // live entities each resident cell ownsreport.loadCount; // places brought in since bootCounts rather than a picture, because a cell that is not drawn and a cell that does not exist look the same from a camera.
What this version does not do
Section titled “What this version does not do”Cells load asynchronously through the same scene loading a game already uses, but there is no priority, no prefetch and no budget yet: a large cell arriving is a hitch. There is no HLOD, so a place that is not resident is not represented at all in the distance. Navigation meshes are not streamed — a world-sized navmesh stays resident. Neither is texture streaming, and the world origin is never rebased.