UntoldEngine streams large worlds through a manifest-driven tiled scene pipeline.
The public rule is simple:
| Use case | API |
|---|---|
| Streamed world geometry (manifest-driven) | setEntityStreamScene(entityId:manifest:withExtension:completion:) |
| Handcrafted streaming zones (no manifest) | StreamingRegionManager — register StreamingRegion AABB + asset lists directly |
| Always-resident assets | setEntityMeshAsync(entityId:filename:withExtension:completion:) |
GeometryStreamingSystem manages the runtime once a streamed scene is loaded. It is not a public component-authoring workflow for standalone entities.
For handcrafted zone streaming without a manifest (e.g. dungeon rooms, level sectors), use
StreamingRegionManager.shared. See the StreamingRegionManager architecture doc for the full API.
let sceneRoot = createEntity()
setEntityName(entityId: sceneRoot, name: "city")
setEntityStreamScene(entityId: sceneRoot, manifest: "city", withExtension: "json") { success in
setSceneReady(success)
}let sceneRoot = createEntity()
setEntityName(entityId: sceneRoot, name: "city")
if let url = URL(string: "https://cdn.example.com/city/city.json") {
setEntityStreamScene(entityId: sceneRoot, url: url) { success in
setSceneReady(success)
}
}Legacy overloads —
loadTiledScene(manifest:)andloadTiledScene(url:)remain available for backwards compatibility. They create an internal root entity automatically. PrefersetEntityStreamScene(entityId:...)when you need a stable handle to the scene.
Remote manifests are downloaded and cached locally. Tile, HLOD, and per-tile LOD URLs are resolved relative to the manifest URL and fetched on demand.
The engine uses multiple geometry layers:
- Full tile: the main tile payload loaded by
loadTile(). Tile assets use the.untoldbinary format, loaded byUntoldReaderwithout ModelIO. - Per-tile LOD: intermediate meshes shown while the full tile is still out of range
- HLOD: coarse far-distance proxy
- OCC sub-mesh stubs: fine-grained
StreamingComponententities created internally inside large tiles
StreamingComponent is internal to the tile-owned OCC path. External callers should not attach it manually or rely on enableStreaming(...).
These are the important fields for geometry streaming:
| Field | Meaning |
|---|---|
streaming_radius |
Full tile display zone |
unload_radius |
Tile teardown threshold |
prefetch_radius |
Background parse threshold before the tile becomes visible |
priority |
Tile load ordering when many tiles compete |
hlod_levels |
Optional far proxy meshes |
lod_levels |
Optional per-tile intermediate LOD meshes |
file_size_bytes |
Parse-budget hint used by the runtime gate |
If prefetch_radius is omitted, the engine computes it automatically from the gap between streaming_radius and unload_radius.
Each update tick, GeometryStreamingSystem:
- Queries the octree within
maxQueryRadius. - Chooses tile parse candidates using predictive camera motion (velocity look-ahead), a frustum gate, and an optional interior zone gate.
- Parses up to
maxConcurrentTileLoadstiles, subject totileParseMemoryBudgetMB. - Streams OCC child meshes inside loaded tiles using
maxConcurrentLoads. - Unloads tiles, LODs, HLODs, and OCC meshes when they leave range or memory pressure requires eviction.
Important defaults:
maxConcurrentTileLoads = 2maxConcurrentLoads = 3maxConcurrentLODLoads = 4maxConcurrentHLODLoads = 4updateInterval = 0.1burstTickInterval = 0.016
// Tile concurrency
GeometryStreamingSystem.shared.maxConcurrentTileLoads = 2
GeometryStreamingSystem.shared.maxConcurrentLoads = 3
GeometryStreamingSystem.shared.maxConcurrentLODLoads = 4
GeometryStreamingSystem.shared.maxConcurrentHLODLoads = 4
// Frustum gate
GeometryStreamingSystem.shared.enableFrustumGate = true
GeometryStreamingSystem.shared.tileFrustumGatePadding = 20.0 // m — wider pad for tiles
GeometryStreamingSystem.shared.frustumGatePadding = 5.0 // m — pad for mesh-level OCC
// Spatial query
GeometryStreamingSystem.shared.maxQueryRadius = 500.0 // must cover farthest unload_radius
// Velocity predictor (predictive tile loading)
GeometryStreamingSystem.shared.velocityLookAheadTime = 0.5 // s — how far ahead to project
GeometryStreamingSystem.shared.velocityLookAheadMinSpeed = 1.5 // m/s — activation threshold
// Interior zone gating (v4 quadtree-floor manifests)
// Tiles tagged interior=true only load when the camera is inside this AABB.
// Set automatically from the manifest; override if needed:
GeometryStreamingSystem.shared.interiorZone = AABB(
min: simd_float3(-10, 0, -10),
max: simd_float3(10, 5, 10)
)
// Parse safety
GeometryStreamingSystem.shared.tileParseTimeoutSeconds = 60.0 // watchdog deadline per tileUse maxQueryRadius large enough to cover the farthest unload_radius in the scene, or out-of-range tiles may not be discovered for teardown.
- Texture streaming:
setEntityStreamScene(...)automatically aligns texture distance bands to the manifest radii. - Batching: full-load tiles, per-tile LODs, and HLODs notify
BatchingSystemautomatically. OCC sub-mesh uploads join batching incrementally through normal residency events. - Memory pressure: texture quality is shed first; geometry eviction follows only when geometry pressure remains high.
- Increase
GeometryStreamingSystem.shared.tileFrustumGatePadding - Keep
enableFrustumGate = true
- Increase the gap between
streaming_radiusandunload_radius - Increase or explicitly author
prefetch_radius
- Lower
maxConcurrentTileLoads - Reduce per-tile file sizes in the exported manifest
- Verify you loaded the scene through
setEntityStreamScene(...) - Verify the manifest radii are reasonable for your scene scale
- Do not expect standalone
StreamingComponententities to stream; tile ownership is enforced