# Worlds: placed objects that stream (gander::world_*) For open worlds with more objects than fit in memory at once: villages, forts, ruins, loot, NPCs spread over kilometres. You write the placements to a file; the engine loads the 128 m cells around the camera, draws the scenery, and tells the game when its gameplay objects come near and go away. Pair it with `terrain_world` for the ground (https://vbgnt.com/docs/gander/terrain.md, "Streamed worlds"). ## The file `assets/world/.txt`, one line each (write it from a script for big worlds): ```text cell 128 # cell size in meters (default 128) load 300 # objects within this of the camera load (default 300; 800 in Pro builds) far 2000 # scenery beyond `load` and up to here shows as impostor cards (default 2000) model house assets/stream/models/house.glb solid model barrel assets/stream/models/barrel.glb model guard assets/stream/models/guard.glb obj house 120.5 ground 33.2 yaw=90 scale=1.2 obj barrel 118 ground 30 tag=loot gold=25 obj guard 125 ground 36 tag=guard route=north hp=80 obj - 100 12 40 tag=spawn team=red ``` * `model NAME PATH [solid]`: models under `assets/stream/` download only when a cell needs them; `solid` gives every object of that model a static mesh collider while its cell is loaded. * `obj MODEL X Y Z [yaw=deg] [scale=s] [tag=word] [key=value ...]`: `Y` can be `ground` (stands on the terrain passed to `world_open`); `-` as the model is an object with no model (spawn points, triggers, zone markers). * An object **without a tag is scenery**: the engine loads, draws, collides and unloads it. An object **with a tag is gameplay**: the game spawns and draws its own state for it. ## The game side ```goose var world: gander::World; var live: i32[>..] = []; // tagged objects in loaded cells fn main() { land = gander::terrain_world("highlands", gen); world = gander::world_open("assets/world/highlands.txt", land); } export "gander_frame" fn frame(dt: f32) { // ... camera ... gander::draw_terrain(land); gander::world_draw(world); // loads cells, draws scenery and far cards var e = gander::world_event(world); while e.kind != gander::WORLD_NONE { if e.kind == gander::WORLD_ENTER { if gander::world_obj_is(world, e.obj, "guard") { spawn_guard(e.obj); } live.push(e.obj); } else { save_and_drop(e.obj); // its cell unloaded: keep what matters } e = gander::world_event(world); } for i: i64 in 0..live.len { // tagged objects are the game's to draw let m = gander::world_obj_model(world, live[i]); if m.id != 0 { gander::draw_model(m, gander::world_obj_xform(world, live[i])); } } } ``` * Object ids (`e.obj`) are stable (the line order in the file): key the game's own state by them, so a looted chest stays looted when its cell comes back. Store that in the save (https://vbgnt.com/docs/gander/saves.md) as a list of ids. * `world_obj_get(world, obj, "gold", out)` reads a value from the line; `world_obj_pos`, `world_obj_yaw`, `world_obj_scale`, `world_obj_xform`, `world_obj_model`, `world_obj_tag`. * `world_source(world, i, pos)` (i 0..7) loads cells around more places than the camera: other players in multiplayer, a convoy far ahead. `world_source_off(world, i)` stops it. * `world_radius(world, load, far)` overrides the file's radii. * Probes: `world_cells_loaded`, `world_drawn` (scenery models), `world_cards` (far cards). ## Rules * Never keep gameplay state only on a tagged object between its LEAVE and the next ENTER: the engine forgets it. Keep it in the game, keyed by the object id. * Do not draw scenery yourself: the engine does, and turns it into cards past `load`. * Big worlds need `camera_clip(0.1, 6000.0)` or the far scenery and terrain are clipped. * Spawn the player once `terrain_ground_ready(land, x, z)` is true.