← Back to Tutorials
Advanced ~20 min read

World Partition & Streaming Performance

World Partition replaced sublevel streaming as UE5's open-world default. The mental model is different from World Composition; the failure modes are different too. This tutorial covers cell streaming and runtime grids, HLOD layers and their bake cost, data layers, the loading-range tradeoff, the dedicated-server gotcha that silently breaks multiplayer scaling, and the debug visualizers that make all of it diagnosable.

1

Mental model: actors, not chunks

The most important thing to understand about World Partition is what unit it streams. WP partitions at the actor level via One File Per Actor (OFPA), not at the level-asset level. Every actor in the world gets its own .uasset file. At cook time, a spatial hash maps every actor to a runtime cell. At runtime, the engine streams cells in/out around streaming sources.

This is fundamentally different from World Composition (UE4's open-world system, deprecated in UE 5.0): WC streamed sublevels as a unit; WP streams individual actors. The implications cascade:

  • Designers can edit a single rock in a 64 km² world without locking a sublevel.
  • Source control conflicts shrink to per-actor instead of per-sublevel.
  • Runtime memory is finer-grained: only the cells the player actually needs are loaded.
  • The cooker has more work to do (spatial hash generation, HLOD build), but the runtime is more efficient.

WP went stable in UE 5.0; significant changes landed in 5.1 (server streaming), 5.3 (Level Instances inside WP), 5.4 (Project Titan reference), 5.5 (static lighting in WP), and 5.6 (Fast Geometry Streaming Plugin). For a deep architectural reference, Sam Bloomberg's "Unreal's World Partition: Internals" is the best community write-up of the actual data structures.

2

The runtime grid

The runtime grid is the data structure mapping world position to cells. Each grid level has a cell size; cells of finer levels nest inside coarser cells. Default loading range: 25,600 cm (256 m) per the Epic forums (per community confirmation).

The two knobs that decide streaming behavior:

  • Cell size — how much world fits in one cell. Smaller = finer-grained streaming = more disk I/O and more cells in flight. Larger = coarser, with less overhead but more wasted memory.
  • Loading range — how far around the streaming source cells are loaded. Wider = smoother traversal, more memory; narrower = lower memory ceiling, more risk of pop-in.

Practical advice from shipped UE5 open-world projects:

  • Cell size: minimum ~3,200 cm before performance degrades. 12,800–25,600 cm is typical for outdoor open-world.
  • Loading range: 12,000 cm minimum; 25,600–51,200 cm is the typical shipped range. Tune to player movement speed.

Grid promotion: actors larger than a cell, or with hard references that span cells, get promoted to a coarser grid level. Promotion is invisible to the author and silently inflates the loading set. This is the subtlest WP performance trap — one Blueprint that hard-refs a far-away anchor can promote both into a coarser grid level.

Hard references span cells; soft references don't Replace hard references with soft references (TSoftObjectPtr / TSoftClassPtr) wherever possible to keep grid promotion under control. This is the same lesson as Gotcha #3 on cast nodes: the issue isn't the cast itself, it's whether the reference forces something into memory.

To override the loading range at runtime for testing:

PIE / packaged build
wp.Runtime.OverrideRuntimeSpatialHashLoadingRange -grid=0 -range=51200
3

Streaming sources

By default, the player camera is the only streaming source. Any actor can be a streaming source via UWorldPartitionStreamingSourceComponent. Multiple sources compose — cells in range of any source are loaded.

Common authored streaming sources:

  • Cinematic cameras — a level sequence that flies the camera through unloaded territory needs a streaming source attached to keep the path loaded.
  • Fast-travel preloaders — an invisible actor at the destination, activated 2–3 seconds before the teleport, ensures the player arrives in already-loaded territory.
  • AI streamers — relevant for games where AI moves outside player view; without an AI streaming source, distant AI fall into unloaded cells and the actor reference dangles.
  • Multiplayer party members — in cooperative games, each player is implicitly a streaming source on their own client; on the server, all players need to be sources for collision and gameplay logic.

Streaming sources have priorities and shapes (sphere by default; custom AABBs for specific use cases). A high-priority source preempts cell unload of a low-priority one.

4

HLOD layers — build vs runtime cost

Hierarchical Level of Detail (HLOD) replaces unloaded cells' contents with simplified proxies. Without HLODs, the world past your loading range is empty. With HLODs, distant terrain has texture, silhouette, and even baked lighting from far away.

Layer types in WP HLOD:

  • Instancing — replace individual actor instances with hierarchical instanced static meshes. Lowest authoring cost; runtime cost is just the instanced mesh draw.
  • MergedMesh — merge multiple actors into a single mesh. Good for clusters of small props that don't need to be addressable individually past streaming range.
  • SimplifiedMesh — aggressive geometric simplification, lower triangle count.
  • Custom HLOD Actor (5.7+) — hand-authored low-LOD proxies. Use for hero geometry where the auto-generated version isn't good enough.

The tradeoff is build time. Running WorldPartitionHLODsBuilder on a multi-km world routinely takes hours, especially with material baking enabled. Most teams treat HLOD bake as a CI step, running on a build farm overnight rather than on developer workstations.

HLOD bake commandlet
UnrealEditor-Cmd.exe Project.uproject MapName ^
    -run=WorldPartitionBuilderCommandlet ^
    -Builder=WorldPartitionHLODsBuilder ^
    -AllowCommandletRendering

-AllowCommandletRendering is required for any HLOD layer that bakes materials (i.e., MergedMesh with material baking, SimplifiedMesh with proxy-mesh material). Without it, the bake fails silently with no error.

HLOD bakes can OOM your GPU on big worlds Material baking holds a lot of textures in VRAM during the bake. Multi-km worlds with 4K textures regularly OOM workstation GPUs. Run on a workstation with at least 24GB VRAM for hero open-world projects, or use SimplifiedMesh + vertex-color material baking as a less-VRAM-hungry alternative.
5

Data Layers — runtime visibility & streaming filters

Data Layers (5.1+) are categories you can attach to actors. At runtime, any layer can be set to Loaded, Activated, or Unloaded. The combination of spatial loading + data layer state determines whether an actor is in memory or visible.

Common patterns:

  • Narrative state. A village burning down in chapter 3? Two data layers: "village_intact" (active in chapters 1–2) and "village_burned" (active in chapter 3+). Switching layers is instantaneous and avoids needing two copies of the level. STALKER 2 publicly described using data layers for visual progression like this (per the GSC dev interview).
  • Server-only and client-only actors. AI navigation meshes, anti-cheat probes, server-side gameplay markers all live on a "server_only" data layer.
  • Difficulty- or game-mode-specific actors. Hard mode adds extra enemies; tutorial mode disables certain hazards. Layer-toggled.

The interaction with spatial loading produces a 4-state truth table (in/out of streaming range × data layer loaded/unloaded). Reference: hzfishy's WP+Data Layers truth table notes.

6

Loading range vs streaming source

Sizing the loading range correctly is a function of player speed, disk throughput, and acceptable pop-in distance. The math:

  • If your player can move 1000 cm/s (~36 km/h jog), and you want 10 seconds of buffer before they reach the edge of loaded territory, you need 10,000 cm of loading range past the camera.
  • If your player can move 5000 cm/s in a vehicle (~180 km/h), the same buffer requires 50,000 cm.
  • Doubled loading range = roughly 4× the loaded set on a 2D grid (area scales with radius squared) = 4× memory.

Most shipped UE5 open-world games target 25,000–50,000 cm loading range, with cinematic cameras and fast-travel preloaders extending it temporarily for specific events.

Blocking loads are the failure mode where the player crosses into an unloaded cell faster than the cell can be streamed in. The engine then issues a synchronous load that stalls the game thread until the cell is on disk. Blocking loads are visible to the player as a hitch.

Detect them in Insights with the Loading Time channel; the trace shows blocking loads as red blocks. Or in code, watch for FlushLevelStreaming or BlockTillLevelStreamingCompleted calls in the call stack.

📝
UE 5.6 unified streaming budget In 5.6 Epic added s.UseUnifiedTimeBudgetForStreaming — a unified frame budget shared across ProcessAsyncLoading and UpdateLevelStreaming. Unused streaming time is donated to asset loading, and vice versa. Worth enabling on streaming-heavy projects (per Tom Looman's 5.6 highlights).
7

Server-only vs client-only streaming (the dedicated-server trap)

This is the single biggest performance trap in WP, and it's specific to multiplayer titles with dedicated servers.

By default, WP loads the entire map on the dedicated server. Even though clients only stream cells around themselves, the server defaults to keeping every cell loaded so any actor can be queried at any time. On a 64 km² world with many players, this crushes server CPU and memory.

The fix is two CVars, both off by default:

DefaultEngine.ini
[/Script/Engine.RendererSettings]
wp.Runtime.EnableServerStreaming=1
wp.Runtime.EnableServerStreamingOut=1
  • EnableServerStreaming=1 (5.1+) — the server streams cells around player streaming sources, just like the client does.
  • EnableServerStreamingOut=1 — the server unloads cells when no client streaming source references them. Without this, server memory grows monotonically.

Epic itself describes the default WP server behavior as "a core limitation at the moment" (per the Epic forums). The mitigation is well-understood; you just have to opt in.

Caveats with replicated actors:

  • An actor that's dormant on the client (out of streaming range) but exists on the server for replication purposes — the server must keep it loaded. Usually handled by streaming sources, but multiplayer game logic that crosses cell boundaries needs careful design.
  • Replicated actors with hard references force grid promotion on the server side, just like on the client. The same anti-pattern.

For dedicated servers, also consider wp.Runtime.HLOD.ForceDisable=1 — the server typically doesn't need HLOD geometry, just collision and gameplay.

8

Migrating from World Composition

World Composition was deprecated in UE 5.0; the engine displays a notice when you open a WC project recommending conversion. WP's actor-level streaming is genuinely better for most use cases, but the conversion has gotchas:

  • Sublevel-to-cells conversion happens via the WP conversion commandlet. Runs across all sublevels and re-distributes actors based on world location.
  • Streaming volumes don't auto-port. WC's level streaming volumes need to be replaced by WP streaming sources or removed entirely.
  • Blueprints that referenced specific sublevels by name (LoadStreamLevel etc.) need rewriting against WP cells or data layers.
  • Baked lighting was off by default in WP until 5.5. Pre-5.5, your post-conversion build silently lost lightmaps unless you set r.AllowStaticLightingInWorldPartitionMaps=1. Easy to miss; visually obvious in retrospect.
  • Source control conflicts will happen during the conversion — do it on a branch with the team's awareness.

For a fresh project, just start with WP. Conversion is a tax, not a benefit.

9

Debug visualizers and profiling

The full WP diagnostic kit:

  • wp.Runtime.ToggleDrawRuntimeHash2D — 2D top-down overlay of WP cells, streaming sources, and load states. Cells colored by state: green = loaded, yellow = loading, red = unloaded.
  • wp.Runtime.ToggleDrawRuntimeHash3D — 3D in-world cell visualization (floating boxes around the camera).
  • wp.Runtime.ShowRuntimeSpatialHashGridLevel — cycles which grid level is drawn. Useful when grid promotion sends actors up a level.
  • wp.Editor.DumpStreamingGenerationLog — editor-only commandlet that dumps the streaming generation report to Saved/Logs/WorldPartition. Useful for understanding why specific actors landed where they did.
  • stat levels — standard level streaming stat. Lists all loaded levels and their state.
  • stat streaming — texture streaming, but also reports level streaming load times.
  • Insights' Loading Time channel — for blocking-load detection and async loading visualization.

For more detail on Insights workflows, see the Unreal Insights From Zero tutorial.

10

Level Instances, FastGeo, and 5.7 Custom HLOD Actor

Three newer features worth knowing about:

Level Instances inside WP (5.3+). Lets you author a Level once and instance it many times in the world — a town center, a building interior, a recurring puzzle room. Each instance contributes to WP cells via its own actor footprint. Useful for repeated content; the alternative is duplicating actors.

Fast Geometry Streaming Plugin (5.6, experimental). Co-developed with CD Projekt Red, FastGeo targets static-only geometry alongside (not replacing) WP. Trades some WP flexibility for cheaper streaming on giant static-heavy environments. Experimental; expect rough edges. The tunable budget CVars (LevelStreaming.LevelStreamingPriorityBias, VisibilityPrioritySort) are documented in Tom Looman's 5.6 highlights.

Custom HLOD Actor (5.7+). Hand-authored low-LOD proxies that override the auto-generated HLOD output for specific actor groups. Use when the auto-generation produces visually unacceptable far-shot silhouettes — e.g., a hero monument that needs a clean low-poly proxy rather than a merged blob.

The 5.6 open-world goal stated by Epic was 60 Hz on current-gen consoles for high-fidelity large worlds (per Puget Systems' coverage). Project Titan, Epic's 5.4 reference open-world sample, demonstrates this on an 8 km × 8 km landscape with 1,400+ contributors and 10,000+ meshes (per the Titan release announcement).

Locking the win in CI

WP regressions tend to look like one of three patterns: (1) a designer raises loading range to fix a pop-in, accidentally doubling the active cell count and crushing memory; (2) a Blueprint hard-refs an actor across a cell boundary, silently promoting the cell to a coarser grid level; (3) a multiplayer change shipped without enabling server streaming, so the dedicated server is loading the whole map again.

PerfGuard can capture per-cell streaming timelines (cells loaded, blocking-load count, HLOD swaps) at known waypoints across a build, then flag regressions when cell load counts spike, blocking loads appear, or HLODs fail to engage. The "we forgot to flip EnableServerStreamingOut" class of bug becomes a one-line CI failure rather than a "the server CPU is at 100% in production" production incident.