Loading .world Scenes in the Background

LoadWorldAsync lets a game continue updating and rendering its current world while Blitz3D+ reads and prepares another one. The returned handle is a normal world handle, but it cannot be selected, rendered, updated, or used for audio until WorldReady is true. Loading is independent of the GUI event system.

The normal loading loop

loadingWorld=CreateWorld()
SetWorld loadingWorld
camera=CreateCamera()
PositionEntity camera,0,0,-5
spinner=CreateCube()

nextWorld=LoadWorldAsync("levels/level2.world")

While Not WorldReady(nextWorld)
    state=WorldLoadState(nextWorld)
    If state=WORLD_LOAD_FAILED Then RuntimeError WorldLoadError(nextWorld)
    If state=WORLD_LOAD_CANCELLED Then RuntimeError "Load cancelled"

    TurnEntity spinner,.4,.8,.2
    UpdateWorld
    RenderWorld
    Text 20,20,"Loading "+(WorldLoadProgress(nextWorld)/10.0)+"%"
    Text 20,44,WorldLoadStage(nextWorld)
    Flip
Wend

SetWorld nextWorld
SetAudioWorld nextWorld
FreeWorld loadingWorld

Flip, FlipCanvas, Delay, and WaitTimer service budgeted main-thread finalization automatically. Loops without those commands can call UpdateAsyncLoads. The automatic calls share one deadline for the current presentation or wait boundary. The default finalization budget is 2 milliseconds and can be changed with SetAsyncLoadBudget.

By default the runtime permits 8 simultaneous world loads and caps aggregate in-flight source data at 256 MiB, decoded CPU data at 1 GiB on Win64 or 512 MiB on Win32, and upload staging at 128 MiB. Decoded data is counted only while a load holds it: a decoded image until its texture is made, and a heightmap, sound, shader or collider file until its world takes it over and counts it as ready. So the decoded limit bounds the loads in progress, not the worlds already loaded, though each loaded world keeps about a kilobyte counted for every file it read. A world's required assets are all decoded before any is finalized, so together they must fit within the decoded limit, beside any other loads in progress. Ready resources, everything kept from loaded worlds, are counted but not capped, so a world that LoadWorld can load also loads asynchronously; a world too big for the machine fails when an allocation fails, and only that load fails. Deployments and automated stress tests may override those values before startup with BLITZ3D_ASYNC_MAX_LOADS, BLITZ3D_ASYNC_SOURCE_BYTES, BLITZ3D_ASYNC_DECODED_BYTES, BLITZ3D_ASYNC_UPLOAD_STAGING_BYTES, and BLITZ3D_ASYNC_READY_BYTES, which sets a cap on ready resources. Memory values are byte counts. A load that would exceed a limit fails with the requested, retained, and limit values in WorldLoadError.

Load states and cancellation

ConstantMeaning
WORLD_LOAD_READY (0)The world is fully published and selectable.
WORLD_LOAD_QUEUED (1)The worker request has been accepted.
WORLD_LOAD_READING (2)The source manifest is being read and validated.
WORLD_LOAD_DECODING (3)Required assets are being decoded.
WORLD_LOAD_FINALIZING (4)Main-thread resources and entities are being created under the time budget.
WORLD_LOAD_FAILED (5)The load ended with an actionable error.
WORLD_LOAD_CANCELLED (6)Cancellation completed without publishing a partial world.

WORLD_LOAD_DEFAULT is 0 and WORLD_LOAD_NO_STREAMING is 1. WORLD_LOAD_PLACEHOLDERS (8) is for editors and tools: a model, picture, heightmap, sound or shader whose file is missing or cannot be read loads as a placeholder (a magenta checkered box for a model) instead of failing the world, and WorldAssetError says why. Asset-kind queries return WORLD_ASSET_KIND_MODEL (1), TEXTURE (2), HEIGHTMAP (3), SOUND (4), SHADER (5), MATERIAL (6), PREFAB (7), or PHYSICS (8), all prefixed with WORLD_ASSET_KIND_. Use the named constants in programs; the numeric values are documented for logs, tools, and saved diagnostics.

Materials and prefabs use the reusable resource V1 format. A world can reference an external descriptor or embed the same document in an asset's inline member. Dependencies inherit the strongest loading policy required by their consumers. A placed material or prefab must be required. Materials are returned as independent brush instances through WorldAssetMaterial.

CancelWorldLoad is cooperative. Poll until the state becomes cancelled, or call FreeWorld to invalidate a loading handle immediately and release its work when the worker unwinds. A failed or cancelled load never changes the selected graphics or audio world.

The .world version 1 format

A .world file is strict UTF-8 JSON. Relative asset paths are resolved from the file containing them, not from the process working directory. A document can contain placed entities, an asset-only preload list, or both.

{
  "format": "blitz3d.world",
  "version": 1,
  "id": "level.castle",
  "settings": {
    "ambient": [64,72,96],
    "collisions": [
      {"sourceType":1,"destinationType":11,
       "method":"polygon","response":"slide"}
    ]
  },
  "assets": [
    {"id":"height","kind":"heightmap","source":"height.bmp","loading":"required"},
    {"id":"ground","kind":"texture","source":"ground.jpg","loading":"required"},
    {"id":"castle","kind":"model","source":"castle.glb","loading":"required"}
  ],
  "entities": [
    {
      "id":"land",
      "transform":{"position":[-1000,-100,-1000],"scale":[7.8125,100,7.8125]},
      "components":[
        {"type":"terrain","heightmap":"height","detail":750,"morph":true,
         "shading":true,"material":{"mode":"legacy","texture":"ground",
         "textureScale":[10,10]}},
        {"type":"collision","entityType":11,"pickMode":"polygon"}
      ]
    },
    {"id":"keep","components":[{"type":"model","asset":"castle"}]}
  ],
  "extensions": {}
}

Settings

settings holds what belongs to the whole world: ambient light, shadows, collision rules (collisions), physics.gravity, the world's environment (an environment asset applied when the world is published, as ApplyWorldEnvironment does), its soundscape (what the listener hears outside every zone: music, ambience and sounds around it, as soundscapes describes), and an optional time-of-day cycle:

"cycle": {
  "mode": "clock",
  "dayLength": 1200,
  "start": 9,
  "sunrise": 6, "sunset": 20,
  "sunPath": {"azimuth": 90, "tilt": 30},
  "keys": [
    {"time": 6.5, "environment": "sunset"},
    {"time": 9, "environment": "clear-day"},
    {"time": 19.5, "environment": "sunset"},
    {"time": 21, "environment": "moonlit-night", "soundscape": "night"}
  ]
}

Each key shows an environment asset at a time of day, in hours. The world's look blends the keys either side of its clock, which starts at start and runs a whole day in dayLength seconds of game time (0 stops it). UpdateWorld moves it on: UpdateWorld 1 is a sixtieth of a second. In clock mode the sun travels an arc from sunrise to sunset, rising towards sunPath.azimuth (measured like a yaw: 0 is +Z) and leaning sunPath.tilt degrees from overhead at noon, and the moon follows the same arc by night; the keys give everything else. In looks mode each key shows its environment as made, sun included, and fades in over its fade hours (1 by default). A key can also name a soundscape, heard instead of the world's from that time and blended between keys the same way. A cycle's environments and soundscapes must be required assets, and a world with a cycle declares the required environment-blending-v1 extension, so a runtime that cannot blend refuses it rather than ignoring the cycle. SetWorldTimeOfDay, WorldTimeOfDay and SetWorldTimeSpeed control the clock. samples/appearance/daynight.world is a complete example.

Coordinates and transforms

World sources use Blitz3D's native left-handed local coordinate space: positive X is right, positive Y is up, and positive Z is forward. Values are ordinary Blitz units; version 1 performs no hidden unit conversion. A transform defaults to position [0,0,0], scale [1,1,1], and an identity quaternion [0,0,0,1]. Quaternion order is x,y,z,w; the loader rejects a zero/non-finite quaternion and normalizes every valid one. Scale follows ScaleEntity, including finite zero or negative components.

Model assets are decoded in their normal Blitz import space: each format gets the conversion LoaderMatrix starts with, so glTF and GLB models have Z negated (their front, +Z in the file, faces -Z) and 3DS models have Y and Z swapped, exactly as LoadMesh and LoadAnimMesh load them in a program that leaves LoaderMatrix alone. LoaderMatrix calls never change how a world imports a model. Put an editor-authored conversion in the entity transform instead. This keeps a world deterministic even when its worker starts later than submission.

Assets

KindVersion 1 preparation
modelB3D, X, 3DS, glTF, or GLB geometry and dependencies; use options.animated for an animated hierarchy.
textureCPU image decode followed by main-thread canvas creation; options.flags matches LoadTexture.
heightmapImmutable 16-bit samples used to construct an independent mutable terrain.
soundWorker-side RIFF/WAVE or single-stream mono/stereo Ogg Vorbis decode; options.spatial selects 3D sound behavior.
shaderSource/dependency preparation on a worker and pipeline creation on the main thread.

Version 1 also has twelve resource kinds: material, prefab, physics, particles, environment, postfx (camera effects), sound-definition, soundscape, audiomix, animset, terrain-definition and native-ui. Each is a definition, inline or in a file of its own, that the loader reads and checks, and that components and other resources use by ID. See Reusable Resources and Materials and resource project workflows.

loading is required, streaming, or manual. Required assets gate WorldReady and may be referenced by placed entities. Streaming assets start automatically after the manifest is prepared and are reported by WorldStreaming. Manual assets begin when FindWorldAsset first resolves them; poll WorldAssetReady and WorldAssetError before using a typed accessor.

Entities and terrain

Entities have stable IDs, optional parent IDs, local position/rotation/scale, visibility, enabled state, string metadata, and components. Version 1 has sixteen component types:

TypeWhat it gives the entity
modelA model asset, or a primitive: cube, sphere, cylinder, cone, quad (which older files call plane) or infinite-plane. It can also bind materials, an animation set and LOD levels.
prefabAn instance of a prefab asset, with its inputs and overrides.
terrainA terrain from a heightmap or a terrain-definition asset.
cameraA camera, with projection, clipping, clearing, fog, shadows and an optional camera-effects asset.
lightA directional, point or spot light.
listenerThe 3D sound listener.
collisionA collision entity type and pick mode.
markerNothing more: a pivot for positions and metadata (also written metadata-only).
attachmentAttaches the entity to another entity, or to a named bone of it.
anchorKeeps the entity on a point of another entity's terrain or mesh, as AnchorEntity does, so it stays on the surface when the surface changes. The entity's transform is measured from that point. The binding is the one AnchorBinding reports; an entity has an anchor or an attachment, not both.
particlesA particle effect asset, with its seed and inputs.
sound-emitterA sound-definition asset that plays from the entity: manual emitters wait for PlaySoundEmitter, on-activate ones start with ActivateWorldSounds.
physicsA physics definition asset for the entity and its body targets.
physics-jointA distance, hinge, ball, slider or fixed joint between two bodies.
ground-shapeShapes a terrain's ground under the entity: levels a pad for it with banks back to the hillside, digs or raises only, or keeps the terrain's scatter off it. The terrain's own heights are kept, so moving the entity brings the hillside back.
zoneA box, a sphere or a painted terrain region with a fade: cameras inside show its environment and camera effects, and the listener inside hears its soundscape and audio mix, and tagged entities coming inside set off its trigger, as Zones below describes. ZoneWeight measures how far inside it any entity is.

A zone component makes its entity a region of the world with a look and sound of its own, such as a cave:

{"type": "zone", "shape": "box", "size": [20, 8, 30], "fade": 4, "priority": 1,
 "environment": "cave", "postfx": "murk", "audioMix": "echo", "soundscape": "drips"}

shape is box (the default, with a size, 10 by 4 by 10 unless given) or sphere (with a radius, 5 unless given), centred on the entity in its own space, so a box turns and scales with the entity and a sphere scales with it. A zone's weight is 1 inside the shape and falls to 0 across fade world units outside it (2 by default). Each camera shows the zone's environment mixed into the world's look by its weight for that camera, after the time of day; zones with a higher priority are mixed in last, so a cave inside a forest zone looks like the cave. Of the zones with camera effects (postfx) that a camera is at least half inside, the one with the highest priority replaces the camera's own effects until the camera leaves. The listener's audioMix switches the same way. The listener hears the zone's soundscape mixed in by its weight, over the world's and the time of day's: its beds grow louder, its one-shots come more often, and its music takes over once it is heard most. Every part is optional, and the assets a zone names must be required assets of their kinds. A world with a zone declares the required environment-blending-v1 extension. ZoneWeight measures how far inside a zone any entity is, and CameraZoneLooks keeps a camera on the world's own look. samples/appearance/zones.world is a complete example.

Outdoors, a zone can take the shape of a region painted on a terrain, such as a swamp with a ragged edge:

{"type": "zone", "shape": "region", "terrain": {"entity": "hills"}, "region": "swamp",
 "height": 30, "fade": 8, "environment": "swamp-fog", "soundscape": "frogs"}

terrain names a terrain entity of the same world and region one of the region IDs of its regionMaps (see TerrainRegionMap). The zone covers that region's ground up to height metres above it, 30 unless given, or with a negative height, that far below it, for a cave under the swamp; the first half metre the other way counts too, so anything standing on the ground is inside. The height is measured from the ground under each point, so the zone climbs the region's hills and dips into its valleys. The shape follows the terrain as it moves, turns and scales, and changes at once when the region is repainted (PaintTerrainRegion). The zone's own entity does not move it: it is there to be hidden, picked and named. A region zone's edges are as sharp as its region map: a pixel a cell on a tiled terrain, and the region image's pixels on a classic one. Far from the cameras, a tiled terrain's ground is measured from the coarse heights it always keeps, so no tile loads to test it. A world with a region zone declares the required region-zones-v1 extension. A zone whose terrain is freed, or whose region its terrain no longer has, is never entered.

A zone's trigger runs actions when an entity with a tag comes inside its shape (its fade does not count), a painted region's included:

"trigger": {"enteredBy": "player", "once": false, "actions": [
  {"do": "hide", "target": {"entity": "door"}},
  {"do": "show", "target": {"entity": "lantern"}},
  {"do": "playSound", "sound": "chime"}]}

enteredBy is the tag (player unless given) and once stops the trigger after its first entry. Each action does show, hide, enable, disable or remove to its target, an entity of the world, or playSound: a required sound-definition asset, heard from the target when one is given and from the zone otherwise, and only in the audio world when the sound is 3D. Actions that start and stop behaviours are refused until the runtime has behaviours. Every UpdateWorld tests the positions of the tagged entities, runs the actions of any that came in, and records an entered or left event for each crossing, read with CountWorldEvents, WorldEventName, WorldEventEntity (the zone) and WorldEventOther (the entity). samples/appearance/triggers.world is a complete example.

FindWorldEntity returns a published entity by ID. WorldEntityMetadata reads authoring metadata without scanning entity names. The metadata key tags holds an entity's tags, words of letters, digits, _ and - separated by spaces, such as "tags": "coin pickup": the loaded entity has them, for EntityHasTag, CountTaggedEntities, GetTaggedEntity and zone triggers.

Terrain supports a required heightmap, detail, morphing, shading, LOD ranges, legacy texture scaling, and layered terrain materials. Once the world is ready, the result is an ordinary terrain: TerrainY, TerrainHeight, collisions, and ModifyTerrain work as normal. A material uses either the legacy members (texture and a positive textureScale) or the layers members, never both. Layered materials contain one to four textures with positive uvScale values. Version 1 maps these to the uniform scale accepted by TerrainLayer, so both components must match. More than one layer requires a splatMap. Sharing a heightmap never shares later terrain deformation.

Asset-only preload manifests

preload=LoadWorldAsync("asset-preload.world")
While Not WorldReady(preload)
    If WorldLoadState(preload)=WORLD_LOAD_FAILED Then RuntimeError WorldLoadError(preload)
    Flip
Wend

modelAsset=FindWorldAsset(preload,"ship")
textureAsset=FindWorldAsset(preload,"ship-paint")
ship=CreateWorldAsset(modelAsset)
paint=WorldAssetTexture(textureAsset)
EntityTexture ship,paint

Typed texture, sound, and shader accessors return ordinary public handles. They remain valid after the source world is freed and must be released with the matching FreeTexture, FreeSound, or FreeShader. A created model entity belongs to its destination world. Compatible texture loads and compatible assets requested by several worlds reuse ready shared data where their options match.

Optional streaming

WorldReady does not wait for assets declared streaming. Use WorldStreaming and WorldStreamingProgress if the game wants to show their progress. Pass WORLD_LOAD_NO_STREAMING to disable automatic optional streaming. Required content and manually requested assets are unaffected.

Streaming big worlds

A world too big to load at once, or too slow to start, can stream. A streaming world places every object as usual, so every handle, name, tag, collider and behaviour is there from the first frame, but the heavy data behind the objects - mesh geometry and textures - loads as cameras come near, and what is far away unloads again when memory runs short. WorldReady comes as soon as what the start view needs has loaded.

world=LoadWorldAsync("big.world",WORLD_LOAD_STREAMING)
While Not WorldReady(world)
    If WorldLoadState(world)=WORLD_LOAD_FAILED Then RuntimeError WorldLoadError(world)
    Flip
Wend

Turn it on with WORLD_LOAD_STREAMING, or in the world itself: the World Editor's Loading settings write settings.streaming, and then every load of the world streams unless the program passes WORLD_LOAD_NO_STREAMING.

"settings": {
  "streaming": { "enabled": true, "start": "spawn-1", "detail": 1.0,
                 "wait": 0.5, "fade": 0.25, "budget": 800 }
}

start is where the first view is: an entity ID, or {"position": [x, y, z], "direction": [x, y, z]}. Without it the world starts at a marker whose ID or name says "spawn", or else at its first camera. detail scales how far away things load, wait is how long a camera that jumps waits for what fills its view, in seconds, and fade how long data that arrives in view takes to fade in. budget, in megabytes, is optional: the world's own memory budget. "enabled": false keeps the other members, so turning streaming back on restores them.

Your program sees no difference apart from timing. Counts, sizes, vertices, picks and collisions always give the same answers, and anything that reads data that has not loaded - VertexX, LinePick, TextureBuffer and the rest - loads it on the spot. An object loads where it would cover a few pixels, worked out from its size and the camera's zoom and view, never beyond the camera's range, and a moving camera reaches farther ahead. Nobody sets a distance for each object; WorldStreamingDetail scales them all.

Data unloads only when the memory budget is reached (SetStreamingBudget; by default the GPU's own budget), and only when it is well beyond where it is needed: first its GPU copy is freed (parked), then the rest, farthest and least recently needed first. Textures keep a small version that draws until the full one is back. Data that arrives while its object is in view fades in.

Sounds stream too. The samples of a sound a world's objects play (sound emitter components) load where a listener could hear them, from how fast the listener's sounds fade (CreateListener), and go a few seconds after no emitter can be heard and none plays it, or sooner when memory runs short. Every object that plays the sound shares its samples. Playing one whose samples have gone loads them first. Sounds long enough to play from their files, such as music, take no memory to stream.

A camera that jumps - a respawn, a teleporter, a cut - waits for what covers much of its new view, up to wait seconds, and the rest fades in. For a loading screen instead, call PreloadWorldView with the place before the jump, and wait until WorldStreaming is False. An entity with "keepLoaded": true (EntityKeepLoaded) stays loaded wherever it is, ready for a cut scene.

A model asset can say how big it is before it loads, with a summary; streaming leaves a model unloaded at the start only when it has one. The World Editor writes summaries whenever it saves, and b3dworldc --summarise level.world adds them to a world made by hand:

{ "id": "barrel", "kind": "model", "source": "models/barrel.glb",
  "summary": { "bounds": [-0.4, 0, -0.4, 0.4, 1.1, 0.4], "surfaces": 2,
               "vertices": 1840, "triangles": 2210, "bytes": 412000 } }

Without summaries a world still streams after the start, but loads every model at the start to measure it. DrawRenderStats with flag 2048 shows what is loaded, parked and unloaded against the budget, and setting the environment variable BLITZ3D_STREAMING_STRESS=1 streams every world and unloads everything it can after every frame: a world that plays the same under it plays the same streaming.

Validation and authoring

The shipped b3dworldc command validates the same format used by the runtime:

b3dworldc level.world
b3dworldc --no-files editor-draft.world
b3dworldc --quiet worlds/*.world

The machine-readable contract is installed as world/world-v1.schema.json. Runtime validation additionally rejects duplicate keys, malformed UTF-8, non-finite values, excessive nesting and document sizes, invalid references, parent cycles, unsupported required extensions, and missing dependencies.

Editor round trips

An editor may normalize whitespace and property order, but it must preserve the order of asset, entity, component, layer, and collision arrays because that order is observable. It must keep explicit IDs stable, retain unknown optional properties and the complete JSON value of every unknown optional namespaced extension, and refuse to silently discard an unknown extension whose required member is true.

Keep relative asset paths relative to the .world file; do not replace them with workstation-specific absolute paths. Emit strict UTF-8 JSON, finite numbers, unique object keys, and quaternion components in x,y,z,w order. Saving must not copy runtime terrain deformation or other gameplay state back into the authoring source. Run b3dworldc after every save and treat the runtime validator as authoritative where JSON Schema cannot express duplicate keys, finite-number rules, path resolution, or cross-reference semantics.

Troubleshooting

Complete samples

Back to Guides