Open-World Narrative · Architecture

Modes, World Streaming & Procedural Generation

A focused page within the Open-World Narrative Architecture documentation. The full map and every sibling page live in the Architecture hub.

7sections12 minread1diagram

On this page

V5 ships seven games in one trench coat — a 1947 noir, a Hong Kong triad open-world, an 1899 frontier, a Witcher-cell monster hunt, a sci-fi fleet PvP, a steampunk whodunit, and the Mind-Palace that links them — and three jobs have to hold that catalogue together without forking the engine per genre. A player has to get from "pick a thing to play" to "a session is running" through one registry; the open world that session opens into has to stream a 256-metre partition lattice on a console frame budget while a quarter-thousand civilians mill around; and the side-quests, encounters, contracts, loot, decoration, and galaxy detail that fill the dead space between authored beats have to be born deterministically from a seed. Those are the three subjects of this page — mode orchestration, world streaming, and procedural generation — and they live in four small, separately-compiled C++ modules under V5/ue/Source/: V5Modes (the registry, build-target gate, HUD router, and esports/cabinet catalogs), V5Open (the engine-integrated World-Partition streaming source, HLOD ladder, weather/time/era data layers, and population spawners), V5Crowd (the density-profile catalog and the Mass-Entity batch descriptor), and V5Procgen (six seeded content generators). This page is the world-and-modes companion in the Multiplayer, Modes & World Simulation group; the orientation hub is ../V5_ARCHITECTURE.md.

What ships, honestly#

Six claims up front, so the rest reads at face value.

  • Real, compiled, value-tested. All four modules link on this box — the linker has produced Binaries/Linux/libUnrealEditor-V5Modes.so, …-V5Open.so, …-V5Crowd.so, and …-V5Procgen.so — and the automation specs assert computed values, not shapes: that a player at the origin pre-warms exactly a 3×3 = 9-cell block, that a 76800 cm streaming radius comes back from a 768 m request, that a heavy-rain transition at its midpoint reports alpha 0.5 and thins the crowd-density scalar below 0.5, that a 48-minute day advanced by half lands at 18:00, and that the same procgen seed reproduces a byte-identical fingerprint while a different one does not. Those fail against placeholders.
  • V5Open is the most engine-integrated of the cluster. It is not a standalone planner that hands back arrays for someone else to apply — its streaming subsystem is a genuine IWorldPartitionStreamingSourceProvider registered with the live UWorldPartitionSubsystem, its population spawners call UWorld::SpawnActor and AActor::Destroy for real, and its weather/era subsystems drive the engine's UDataLayerManager::SetDataLayerRuntimeState. The tests run against a real UWorld::CreateWorld(EWorldType::Game).
  • V5Modes is a real catalog and a real gate — but it is not a UGameFeatureAction. The 29-mode catalog, the build-target gating, the per-cell HUD routing, and the plugin discovery (a real IPluginManager walk) are all genuine C++. What is not here, and what V4's mode module did reach, is an async-asset-streaming UGameFeatureAction subclass: a grep for GameFeatureAction / RequestAsyncLoad / UGameFeaturesSubsystem across V5Modes returns nothing. V5's modes are described, discovered, and gated; they are not activated through the GameFeatures streaming callback in-tree.
  • V5Procgen is real seeded generation — but it is not PCG. The architecture prose calls this "the PCG (Procedural Content Generation) pipeline"; the shipped code is six hand-written FRandomStream generators with no PCG dependency at all. V5Procgen.Build.cs links only Core/CoreUObject/Engine/V5Core, and the only pcg token in the module is an id-prefix string (pcg.<cell>.<x>.<y>). The determinism and the content are real; the "PCG" naming is aspirational.
  • The crowd density side is Mass-linked, not Mass-driven. V5Crowd's Build.cs lists MassEntity, but a grep for FMassEntityManager, FMassEntityHandle, or any UMassProcessor returns nothing. BuildPopulationBatch produces a descriptor — a lightweight entity count, a spacing, and a named MassEntityTemplateId — that a Mass spawner would consume; it does not itself run Mass processors. The panicking, fleeing, bark-shouting behavioural half of V5Crowd is documented separately in ./perception-crowd-and-morality.md; this page covers only the streaming/density seam.
  • Content is modelled as data, not cooked binaries. V5/ue/Plugins/ holds 29 *.uplugin GameFeature descriptors, one per mode, and zero have a Source/ module — they are content-and-manifest plugins (e.g. Plugins/V5Mode_Urban_Online/Content/Data/urban_online_manifest.json). A scan of the whole V5/ue tree finds zero binary .uasset and a single .umap (Content/V5Core/Maps/BootMap.umap). The crowd vocabulary is the exception that proves the model: a real 1.5 MB, 6,050-bark crowd_catalog.json on disk (more below).

Game modes — the V5Modes registry#

The mode definition and the 29-mode catalog#

Everything begins with FV5ModeDefinition (V5ModeTypes.h:59): a PluginName key, a DisplayName, an EV5Cell, an EV5ModeNetworkModel, an EV5ModeHUDSurface, a MaxPlayers, and the boolean policy flags (bRequiresOnlineServices, bSupportsDedicatedServer, bSupportsListenServer, bEditorOnly, bPersistentLobby). It is richer than V4's five-field routing record because the gate downstream reasons over the network model and the policy flags rather than just opening a map. The catalog is authored in code by Catalog() (V5ModeSystems.cpp:38), whose 29 MakeMode(...) rows (:41:69) span the seven cells: three Urban (HeistCity, StreetTriad, Online), three Period (MadeMan, ViceSquad, InterrogationKit), two Frontier, three Hunter, three Sci-Fi, plus the Steampunk, Mind-Palace/Cold-Cases, Spec-Ops, Horde, Roguelike, four editor tools, Replay, Spectator, Training, Bureau-HQ lobby, and Photo modes. EnumerateEnabledGameFeaturePlugins (V5ModeSystems.cpp:306) intersects this catalog with a set of enabled plugin names and splits the result into EnabledModes and GatedModes for a target — the registry test pins the partition exactly (four enabled plugins on a dedicated target yield 2 enabled / 2 gated).

Build-target gating#

The interesting logic is GateKnownMode (V5ModeSystems.cpp:129), a real fail-closed policy switch over EV5ModeBuildTarget (V5ModeTypes.h:19 — six targets: ShippingClient, DedicatedServer, ListenServer, Editor, LANOffline, ArcadeCabinet). An editor-only mode enables only on the Editor target. A dedicated server keeps only modes whose bSupportsDedicatedServer is set. A listen target also admits offline/utility rulesets. LANOffline rejects anything that bRequiresOnlineServices, or is CompetitivePvP, or is a SocialLobby. And ArcadeCabinet cooks only the five curated cabinet modes (IsArcadeCabinetMode, :117) and forces bRequiresOnlineServices false so a cabinet runs offline. Each branch returns a populated Reason string, so a gated mode explains itself. The spec drives the edges: an editor mode in a shipping client returns disabled with a reason containing "Editor-only"; Hard Vacuum is dedicated-eligible; a curated cabinet set yields exactly 5 enabled / 1 gated.

Per-cell HUD routing#

BuildHUDRoute (V5ModeSystems.cpp:333) maps (EV5Cell, ActiveModePlugin) to an FV5ModeHUDRoute — a HUD surface, a WidgetClassPath soft path, primary/ secondary status labels, feature toggles (bShowsNetworkPanel, bShowsInvestigationNotebook, bShowsVehicleOrMountStatus, bShowsSquadOrCrewStatus), and a RequiredLayers list. It is genuinely per-cell-and-per-mode: the Period cell forks ViceSquad ("Case"/"Evidence", notebook + evidence tray) from MadeMan ("Family"/"Heat"), the Sci-Fi cell forks the ship HUD ("Hull"/"Crew") from the squad HUD, and the Urban default surfaces the network panel only for V5Mode_Urban_Online. The widget paths (/Game/V5/UI/HUD/WBP_HUD_*) are soft references; no cooked widget binary backs them in-tree, consistent with the content model above.

Plugin discovery, lobby, and the esports/cabinet catalogs#

Discovery is real engine I/O: DiscoverGameFeaturePlugins (V5ModeSystems.cpp:102) walks IPluginManager::Get().GetEnabledPlugins() or GetDiscoveredPlugins(), keeps only plugins whose name starts with V5Mode_ and carry an enabled GameFeatures reference in their descriptor (IsV5GameFeaturePlugin, :86), and returns a deterministically sorted FName list. On top of the registry sit three authored catalogs with real validators: BuildLobbyState (:451) mints the Bureau-HQ social lobby with seven cell portals and four queue groups, clamping connected players to a 32-cap and switching its primary action to "Retry online services" when services are unhealthy; ValidateArcadeCabinetProfile (:503) is a ~30-assertion linter over the 4K60 kiosk cabinet (five mode slots, coin-op disabled, watchdog/service-menu gates); and ValidateProStadiumTourCatalog (:577) checks the six-region, six-stop esports tour and its per-stop broadcast routes (SRT/NDI/HLS/DASH, ≥14 camera feeds, a 15-second observer delay). These are evergreen data validators, not gameplay runtimes, and their specs assert the counts directly.

World streaming — V5Open#

A real World-Partition streaming source#

UV5_Open_StreamingSubsystem (V5OpenStreamingSubsystem.h:10) is a UWorldSubsystem that implements IWorldPartitionStreamingSourceProvider. On Initialize it takes a dependency on UWorldPartitionSubsystem and registers itself (V5OpenStreamingSubsystem.cpp:6, :207), so the engine's streamer polls it every frame. Game code registers sources by actor or by location (RegisterStreamingActor / RegisterStreamingLocation) tagged with an EV5OpenStreamingSourceKind (Player, Companion, MissionMarker), and the override GetStreamingSources (:109) translates each tracked source into a real FWorldPartitionStreamingSource: it converts the metre load-radius to centimetres (× 100), attaches an explicit-radius FStreamingSourceShape, sets a debug colour by kind, forces 2D, and calls UpdateHash. Priority is policy, not a constant: ResolvePriority (:192) gives the player Highest, a mission marker High (Highest in the Sci-Fi cell, where objectives are the streaming anchor), and a companion Normal. The high-density spec registers a player, a mission marker, and 128 companions and asserts all 130 sources survive sorted — player first, mission marker second, companion priority last — and that the first emitted World-Partition source carries the 76800 cm radius. There is no megabyte-budget HLOD pruner here the way V4 has one; V5 emits every registered source and lets the engine's grid loading-range arbitrate.

Mission pre-warm and the HLOD ladder#

GetPrewarmCellsForMissionStart (:167) resolves a mission centre to the 256 m partition lattice with FloorToInt(center / PartitionCellSizeMeters) and returns the (2r+1)² block around it — radius 1 ⇒ the 9-cell 3×3 the spec checks, including the centre cell. The HLOD model is UV5_Open_HLODRules (V5OpenHLODRules.cpp:3): four layers on a contiguous distance ladder — HLOD0 0–256 m, HLOD1 256–512 m, HLOD2 512–1000 m, HLOD3 1000–2000 m flagged as the silhouette. ValidateRules (:46) is a real authoring linter: it rejects any cell size other than 256 m, demands exactly four layers indexed contiguously with each band starting where the last ended, and requires HLOD3 to be the 2 km silhouette layer. GetRuleForDistance (:26) resolves a distance to its band, and the spec confirms 1500 m falls in HLOD3 and HLOD3 is the silhouette.

Data layers: weather, time-of-day, era#

Three UTickableWorldSubsystems drive World-Partition data layers for real. UV5_Open_Weather (V5OpenWeather.cpp) runs a seven-state machine (Dry/LightRain/HeavyRain/Snow/Fog/DustStorm/SolarFlare) with timed transitions; GetCurrentGameplayModifiers (:99) lerps four gameplay scalars — visibility, traction, crowd-density, clue-legibility — across the transition, and ApplyWeatherDataLayer (:63) activates the bound UDataLayerAsset and unloads the rest through UDataLayerManager::SetDataLayerRuntimeState. The spec proves the blend: heavy rain at its midpoint reports alpha 0.5, reduced visibility, and on completion a crowd-density scalar below 0.5 — weather literally thins the streamed crowd. UV5_Open_TimeOfDay (V5OpenTimeOfDay.h:13) models a 48 × 60-second day (the spec advances it half a cycle from 06:00 and lands on 18:00, and IsNight is hour ≥ 20 || hour < 5). UV5_Open_DataLayer_Era (V5OpenDataLayerEra.cpp:16) swaps the period-cell era bundle (1930s / 1947 / 1968) by activating exactly one era's data layer — the engine seam for the period-authenticity world dressing.

Population: traffic and pedestrian spawners#

V5Open actually populates the streamed world. UV5_Open_TrafficSpawner and UV5_Open_PedSpawner (V5OpenPopulation.cpp) hold per-cell densities and spawn against them. GetPedestrianDensityPerSquareKm (:193) encodes the architecture spec directly — Urban 250 people within a 100 m radius, Period 120, Frontier 40 (settlements) / 6 (wilds), Hunter 60 / 10, Sci-Fi 80 / 30 — and applies the period era scalars (1930s × 0.85, 1968 × 1.15). GetTrafficDensityPerSquareKm (:137) does the same for vehicles (Urban 260/km², era-scaled Period 85/120/165, Frontier 18, Hunter 6, Sci-Fi 0 — no cars in space). CalculateCountFromDensity (:19) converts a per-km² density and a radius into an actor count via the real circle area π·(r/1000)², clamped to a MaxActors ceiling. SpawnPopulation (:65) then seeds an FRandomStream, picks a weighted spawn class (PickSpawnClass), and calls UWorld::SpawnActor<AActor> at a uniformly-sampled radial position; CullOutsideRadius (:100) Destroys anyone who drifts past the radius. The density spec confirms Urban > Period > Frontier traffic, that 1968 pedestrians outnumber 1930s, and — against a real game world — that spawning then culling round-trips the active-actor count to the spawned count.

Crowd density and the Mass seam — V5Crowd#

The streaming side of crowds is the catalog plus the batch descriptor. UV5_Crowd_Catalog::ValidateCrowdCatalogJson (V5CrowdCatalog.cpp:248) is a strict schema-1 linter over Content/V5Crowd/Data/crowd_catalog.json: it deserialises with FJsonSerializer, parses density profiles / body-language libraries / bark lines, enforces unique ids, and rejects the set unless it has ≥ 8 density profiles, exactly six era-bundle body-language libraries covering six eras, ≥ 5000 ambient bark lines, and coverage of all five playable crowd cells. The file on disk satisfies it for real — 1.5 MB, 8 density profiles, 6 libraries, and 6,050 authored barks — so the "thousands of lines" claim is bytes, not a template generator. BuildPopulationBatch (V5CrowdSystems.cpp:18) is the bridge to streaming load: it scales a profile's PopulationWithin100m by a console-throttle percent and a streaming-churn scalar, then derives an even EntitySpacingMeters from the spawn-circle area (√(πr²/N)) and stamps the authored MassEntityTemplateId. The catalog spec reads back Urban's 250-NPC target and asserts the batch preserves 250 on desktop but throttles below it on a console build. GetPromotionRadius (V5CrowdSystems.cpp:36) holds the lightweight-to-pawn promotion thresholds the architecture names — Named 18 m, Generic 30 m, Ambient 60 m — and EvaluatePromotion (:50) promotes mission-critical or actively-interacting NPCs unconditionally. The panic, flee, witness-report, and bark-selection logic that rides on top of these entities lives in ./perception-crowd-and-morality.md.

Procedural generation — V5Procgen#

Six BlueprintPure generators fill the open world deterministically, each seeding an FRandomStream and emitting a StableFingerprint string so a re-run with the same seed is byte-identical (every spec asserts fingerprintA == fingerprintB and != differentSeed). The shared seed mixer CombineSeed (V5ProcgenSystems.cpp:44) xors the inputs with the classic spatial-hash constants (73856093, 19349663, 83492791) so distinct cell/coord/pattern tuples decorrelate. The generators are: side-quests (GenerateSideQuest, :259) over five authored patterns (talk-to-NPC, escort, hunt, scavenge, race) with per-step jittered objective locations; frontier random encounters (BuildFrontierEncounterTemplates, :288) — 320 weighted templates across eight kinds and five region tags, with SelectFrontierEncounter (:317) biasing Rescue encounters up for high-honor players; monster contracts (GenerateContract, :360) drawing from 11 hand-authored archetypes with weakness tags, threat tiers, and crown rewards; loot tables (RollLoot, :392) — a weighted-random walk (SelectWeightedLoot, :139) over eight rarity-tiered entries; city decoration (GeneratePartitionDecoration, :409) placing 36–60 cell-typed prop instances per 256 m partition; and galaxy detail (GenerateSystemDetails, :437) seeding mineral hotspots, anomalies, and derelicts per Sci-Fi system. The spec coverage is concrete — 5 patterns, 320 encounters, 8 loot entries, ≥ 36 decoration instances, all three galaxy kinds — and would fail instantly against a Math::random stub because it pins the fingerprint, not just the shape.

How a streamed open-world session fits together#

The intended flow — assembled from the real functions above, even though no single in-tree loop wires all four modules together per tick — looks like this:

flowchart TD Pick[Player picks mode] --> Reg[V5Modes registry] subgraph Modes [V5Modes] Reg --> Gate[GateKnownMode<br/>build-target policy] --> Hud[BuildHUDRoute<br/>per-cell HUD surface] end Gate -->|enabled mode + cell| Open subgraph Open [V5Open] Src[StreamingSubsystem<br/>IWorldPartitionStreamingSourceProvider] --> WP[(UWorldPartitionSubsystem)] Pre[GetPrewarmCellsForMissionStart<br/>3x3 of 256m cells] --> WP Layers[Weather / TimeOfDay / Era<br/>UDataLayerManager] --> WP WP --> Pop[Traffic + Ped spawners<br/>density to UWorld::SpawnActor] end Pop -->|density profile| Crowd subgraph Crowd [V5Crowd] Batch[BuildPopulationBatch<br/>lightweight count + spacing] --> Promo[GetPromotionRadius<br/>18 / 30 / 60 m] end Seed[Run seed] --> Procgen subgraph Procgen [V5Procgen] Gen[6 FRandomStream generators<br/>StableFingerprint] --> Content[quests / encounters / loot / decoration] end Content -.fills streamed cells.-> Pop Layers -.crowd-density scalar.-> Batch