# Streaming a large world in chunks (world.streaming) ## What it is for `world.streaming` is bookkeeping for a world larger than one block. It draws nothing and builds nothing: it decides which square chunks are live around the player and tells the kits that build (colliders, props, NPC spawns, terrain). The world is a grid of `chunkSize` metre squares on X and Z; chunk `(x, z)` covers `[x*size, (x+1)*size) x [z*size, (z+1)*size)`. - The focus is the vehicle the player rides, otherwise `character.control`, otherwise the entity tagged `player`, otherwise the last `player.moved`. - Wanted chunks are all those within `radius` chunks of the focus chunk (a square), nearest first (distance to the chunk centre, then z, then x). With `world.ground`, chunks entirely outside its bounds are skipped. - A loaded chunk farther than `radius + 1` chunks is released at once (one chunk of hysteresis, so walking along a border does not thrash). - Entities tagged `streamable` carry the tag `dormant` while their chunk is not loaded, so AI and renderers can skip them. ## Turn it on ```json { "id": "world.streaming", "config": { "chunkSize": 32, "radius": 1, "budgetMs": 2 } } ``` It requires `world.ground` and `game.engine`. `world.terrain` builds its terrain chunks on the events of this kit and uses the same `chunkSize`. ## First example With `chunkSize: 32`, `radius: 1`, `budgetMs: 2` and a player at `(8, 8)`, the first tick loads two chunks, the player's own and the nearest neighbour: `loaded()` returns `[{ x: 0, z: -1 }, { x: 0, z: 0 }]` and `world.chunks.pending` is 7. After four more ticks `world.chunks.loaded` is 9, `isLoaded(1, 1)` is true and `isLoaded(2, 0)` is false. A save and reload keeps the 9 chunks. ## Settings and defaults | Setting | Type | Default | Meaning | |---|---|---|---| | `chunkSize` | number 16-512 | `64` | Chunk edge in metres. The grid other kits build on; a mod does not change it. | | `radius` | integer 1-8 | `2` | Chunks kept loaded around the focus. | | `budgetMs` | number 0.5-16 | `4` | Loading budget per tick. | `budgetMs` is not measured against the wall clock: it becomes a fixed number of loads per tick, `max(1, floor(budgetMs))`, one chunk being priced 1 ms. The default loads four chunks per tick, so the 25 chunks of radius 2 are live after 7 ticks, and a replay loads the same chunks on the same ticks on every machine. A scene of `game.engine` may set `radius` and `budgetMs` in `layers["world.streaming"]`. ## Services, events, variables - `world.streaming` (service): `loaded()` and `pending()` list `[{ x, z }]`; `isLoaded(x, z)`; `chunkOf(pos)`; `around(pos)` lists the wanted chunks nearest first; `onChunk(fn)` calls `fn(chunk, loaded)` on every load and release and returns an unsubscribe function; `chunkSize()`. - `world.chunk.loaded` (event): `{ x, z }`, the chunk is live; kits that build materialise it. - `world.chunk.unloaded` (event): `{ x, z }`, the chunk was released (too far, or its scene was left). - `player.moved` (listened): the last known position when no controlled entity is found. - Variables: `world.chunks.loaded` and `world.chunks.pending`. Leaving a scene releases every chunk (`world.chunk.unloaded`) and forgets the focus; entering one starts the grid around its entry. The loaded chunks and the last focus are saved; loading restores them without replaying events. ## Hooks and modding Both hooks are cancellable, with no mutable field, and asked once per change: - `world.chunk.load` `{ x, z }`: the chunk is about to load. A cancel leaves it unloaded (no `world.chunk.loaded`, its `streamable` entities stay dormant) and it is asked again only after it left the wanted square and is wanted anew. The load it would have taken is spent for this tick. - `world.chunk.unload` `{ x, z }`: the loaded chunk is about to be released. A cancel keeps it loaded; leaving a scene releases every chunk without asking. ```json { "id": "keep_home", "hook": "world.chunk.unload", "match": { "x": 0, "z": 0 }, "cancel": true, "reason": "home" } ``` A refusal is kept until released and saved with the game. A mod may change `radius` and `budgetMs`. ## Engines The same chunk order and per-tick budget apply on the web, the native player, Godot, Unity and Unreal. The hooks run on the web and in the Rust kit host (the native player, and Godot and Unity through the kit library). The controlled-vehicle focus and `world.district` materialisation (`streamEntities`) are web-only for now. ## When it does not do what you expect - Chunks load slowly: raise `budgetMs`; the rest waits in `pending()`. - A chunk never loads: a `world.chunk.load` tap cancels it, or it lies entirely outside `world.ground`. - Props pop in late: build on `world.chunk.loaded` or `onChunk`, not on a timer. ## Limits It does no rendering, no geometry and no asset loading. Chunk contents come from the kits that listen to its events. ## Versions Kit 1.5.0; it provides `world.streaming` 1.0.0 and requires `world.ground@^1` and `game.engine@^1`. The hooks and the held refusals date from 1.4.0 and 1.5.0.