pipelines/UE_BUILD_AUTOMATION.md
Compiled 2026-07-15. Scope: the autonomous/programmatic build surface of the current Unreal Engine release, evaluated against this project's pipeline — a typed story spine (site rows, boss phase maps, scene rows, fork tables) plus CSV canon registries (bosses 282 rows, creatures 132 rows, weapons, etc.) driving an eventual playable build across ~69 regions.
www.unrealengine.com marketing/legal pages returned HTTP 403 to automated fetching throughout this research; dev.epicgames.com documentation and forums.unrealengine.com were directly fetchable, so official-doc claims lean on the latter and are cross-corroborated against news coverage of the former).---
1. UE 5.8 is correct. It shipped ~mid-June 2026 (announced at Unreal Fest Chicago / State of Unreal 2026) and is explicitly the last planned major UE5 release — Epic is now ramping UE6 (Early Access targeted end of 2027, full release 12-18 months after). Build on 5.8; plan a UE6 evaluation for late 2027, not sooner.
2. UE 5.8 ships an official, Epic-authored, Experimental "Unreal MCP" plugin that names Claude explicitly as a supported LLM for driving the editor (including a dedicated PCG toolset). This is Epic's own sanctioned answer to this project's exact goal, but it is Experimental and positioned as supervised/assistive, not a deterministic backbone.
3. The deterministic automation spine is real and largely mature: Python 3.11.8 embedded scripting, headless commandlets, RunUAT BuildCookRun, and Epic's own Horde CI system prove "an external agent drives the editor end-to-end with no human clicking" is an Epic-sanctioned pattern, not a stretch goal.
4. The one genuine hole in pure-Python automation is Landscape heightmap import — there is no stock Python call to create a landscape from a heightmap; it requires a small C++ shim (or a third-party plugin) wrapping ALandscape::Import(). This is the single highest-priority engineering dependency in the whole chain.
5. PCG (Procedural Content Generation) is Production-Ready (since 5.7) and has first-class DataTable ingestion (Load Data Table, Data Table Row to Attribute Set) — this is the direct, verified mechanism for turning CSV registry rows into populated 3D encounter zones.
6. DataTables require one C++ USTRUCT per registry (Blueprint-only structs are rejected — Epic bug UE-152346) but are otherwise fully CSV/JSON-scriptable with zero GUI interaction.
7. MetaHuman changed architecture substantially and recently: fully in-editor since UE 5.6 (no more cloud/browser Creator app), with an official Python API for batch character creation and parametric face variation added in 5.7 and extended in 5.8 — but auto-rigging and texture synthesis still round-trip to Epic's cloud per character, an undocumented throughput bottleneck at "hundreds of NPCs" scale.
8. Sequencer is fully Python-scriptable end-to-end (create asset, camera cuts, actor bindings, tracks, keyframes) and Movie Render Queue renders headlessly from the command line with documented, verbatim invocation syntax.
9. There is no Epic-documented worldstate/save architecture for "many typed persistent flags." Lyra (Epic's flagship sample) ships no save/persistence reference at all. The 39-key ws.* architecture in Section 7 is a synthesized recommendation, not a cited Epic pattern — flag it for a validation spike.
10. Licensing is clean for a solo commercial game: free under $1M lifetime gross per product, 5% above (dropping to 3.5% or 0% depending on Epic Games Store day-and-date/payment-processor choices). MetaHuman is no longer UE-only and follows the same royalty terms.
11. The EULA's generative-AI clause is the one licensing item needing a legal read before locking the pipeline architecture — it restricts using Epic's engine/content as AI *training data*, with genuinely ambiguous community debate over "prompt-based input" wording; it does not, on the plain reading, restrict using AI to *author* your own content or *drive* the editor.
---
[VERIFIED, converged independently across all four research passes.]
Practical implication: every API/class name below has been checked against 5.8-era documentation (not older tutorial-era assumptions). Where an agent found that a name/pattern had changed across 5.x (e.g., EditorLevelLibrary deprecation, MetaHuman's cloud-to-in-editor migration), that delta is called out explicitly rather than silently using the old name.
---
init_unreal.py — auto-executed by the editor at startup if found anywhere on the Python path (project, engine, or any enabled plugin's Content/Python). This is the canonical hook for an agent that wants code loaded every editor launch.sys.path auto-includes <Project>/Content/Python, <Engine>/Content/Python, every enabled plugin's Content/Python, and <UserDir>/Documents/UnrealEngine/Python; extendable via Project Settings "Additional Paths" or the UE_PYTHONPATH environment variable.UnrealEditor-Cmd.exe "C:\projects\MyProject.uproject" -ExecutePythonScript="C:\my_script.py"
(boots the full editor, then runs the script), or the faster, headless-oriented commandlet path:
UnrealEditor-Cmd.exe "C:\projects\MyProject.uproject" -run=pythonscript -script="C:\my_script.py"
UnrealEditor-Cmd.exe is the console subvariant — no GUI window, logs to stdout. [FLAG — INFERRED] the exact flag spelling above is stable, widely-used community practice across 5.x, but was not re-confirmed against a specific 5.8 documentation page this session; a 1-minute live check is worth doing before wiring CI around it.
Core unreal module API surface [VERIFIED unless noted]:
unreal.AssetToolsHelpers.get_asset_tools() (note: some tutorials misspell this "AssetToolHelpers" — the correct current class has the "s") → unreal.AssetTools. unreal.AssetImportTask() with .filename, .destination_path, .destination_name, .factory, .options, .replace_existing, .automated, .save. Execute a batch via asset_tools.import_asset_tasks([task, ...]); read results via task.get_objects() / task.imported_object_paths. Related: unreal.EditorAssetLibrary / unreal.EditorAssetSubsystem for save/load/duplicate/delete by path.unreal.EditorLevelLibrary is deprecated. Its functions split across three subsystems:unreal.EditorActorSubsystem — actor operations: spawn_actor_from_class(actor_class, location, rotation=[0,0,0], transient=False), spawn_actor_from_object(...), destroy_actor(actor) / destroy_actors(actors), get_all_level_actors(), get_selected_level_actors(), set_selected_level_actors(actors), duplicate_actor(...), set_actor_transform(...).unreal.LevelEditorSubsystem — viewport/level-file operations: new_level, load_level, save_current_level, editor_set_game_view.unreal.UnrealEditorSubsystem — global app/world queries: get_editor_world, get_game_world.unreal.get_editor_subsystem(unreal.EditorActorSubsystem) is the current idiom. The old free-function calls still exist but emit deprecation warnings.unreal.PCGComponent.generate(True) mirrors clicking "Generate" in-editor; .generated (read-only) reports state; .cleanup() reverts. [FLAG] PCG churned through 5.7/5.8 — re-pin exact PCGComponent/PCGGraph signatures against the live 5.8 Python API pages before coding.[VERIFIED — https://dev.epicgames.com/documentation/en-us/unreal-engine/remote-control-for-unreal-engine]
30010, WebSocket 30020 (both changeable in Project Settings > Web Remote Control). In a packaged/standalone build the server is off by default unless launched with -RCWebControlEnable (and -RCWebInterfaceEnable for the UI).PUT /remote/object/call — call a function on a loaded UObject. Body: {"objectPath": "...", "functionName": "...", "parameters": {...}, "generateTransaction": true}.PUT /remote/object/property — get/set a property.PUT /remote/object/describe — enumerate properties/functions/metadata.PUT /remote/batch — batch multiple calls. [FLAG] a forum report describes a crash bug under some conditions — test before depending on batching. https://forums.unrealengine.com/t/remote-batch-endpoint-on-remote-control-http-api-crashes-ue/2013233GET /remote/info — list available routes./remote/object/call against an Editor Utility Blueprint/Widget or Python-registered ufunction that performs the import/edit — because Remote Control reaches "any function exposed to Blueprint and Python," this works; (b) a subprocess/commandlet invocation (§1.1) is usually simpler for one-shot batch authoring, while Remote Control shines for a persistent warm-editor session an agent nudges repeatedly.127.0.0.1 by default (LAN exposure requires editing DefaultBindAddress in DefaultEngine.ini). A passphrase mechanism exists but Epic's own docs frame auth as light — do not expose the port to the open Internet; treat it as an unauthenticated local control plane and firewall accordingly. Implication: run the agent on the same host or a trusted LAN/VPN as the editor.[VERIFIED unless noted]
UnrealEditor-Cmd.exe "<Project>.uproject" -run=<CommandletName> [args].-run=pythonscript -script="…" — headless Python (§1.1).-run=ResavePackages — resave/upgrade packages, bulk migrations/fixups. Common args: -IgnoreChangeList -BuildLighting -Quality=Preview -MapsOnly -ProjectOnly -AllowCommandletRendering. (Community CLI reference: https://unrealcommunity.wiki/command-line-interface-cli-3mcqmc4z)-run=DataValidation — runs the Data Validation system across assets, built for CI; extensible with custom UEditorValidatorBase subclasses (C++, Blueprint, or Python-authored validators). https://dev.epicgames.com/documentation/unreal-engine/data-validation-in-unreal-engine-run=cook — cooks content for -targetplatform=…; usually driven through BuildCookRun rather than called raw.-ExecCmds="Automation RunTests …" for headless test suites. [INFERRED] — standard practice, not re-verified against 5.8 this pass.Engine/Build/BatchFiles/RunUAT.bat (Windows) / RunUAT.sh (Mac/Linux), wrapping UAT (Unreal AutomationTool).
RunUAT.bat BuildCookRun -project="C:\path\MyProject.uproject" ^
-noP4 -platform=Win64 -clientconfig=Shipping ^
-cook -allmaps -build -stage -pak -archive -archivedirectory="C:\out" ^
-nocompileeditor -utf8output -unattended -nosplash
Key flags: -cook, -build, -stage, -pak, -archive/-archivedirectory, -clientconfig=Shipping|Development, -platform=/-targetplatform=, -server+-noclient (dedicated-server build), -noP4, -utf8output. Linux: RunUAT.sh BuildCookRun … -targetplatform=Linux -cook -stage -archive -pak.
[VERIFIED]
-unattended (no interactive prompts/dialogs — the critical CI flag), -nosplash, -nullrhi (no rendering hardware interface at all — for cooks, validation, pure data processing; no GPU needed), -nopause, -stdout/-fullstdoutlogoutput, -log.-RenderOffScreen) — renders normally but to no display (needs a GPU; editor support is Linux-only); vs no-rendering mode (-nullrhi) — skips graphics entirely (best for cooks/validation/data work).-nullrhi/-unattended suffice on a headless build agent.RunUAT.sh. This is the standard substrate for containerized/CI build farms. https://unrealcontainers.com/docs/use-cases/dedicated-servers-AllowCommandletRendering and a GPU even when "headless"; pure asset/data commandlets run fine under -nullrhi.[VERIFIED — https://dev.epicgames.com/documentation/unreal-engine/horde-in-unreal-engine]
UnrealEngine5-mcp, other unreal_mcp projects) already bridge external AI agents to the editor using exactly the Python + Remote Control mechanisms above, on 5.6+. Not Epic-official, but they demonstrate the "LLM agent outside the engine drives the editor" architecture is being built today, alongside Epic's own official MCP plugin (§0, §3.7).PCGComponent.generate() / PCGGraph.* should be re-pinned against the live Python API pages before coding./remote/batch has a reported crash bug — validate before relying on batched Remote Control calls.-run=pythonscript, -ExecutePythonScript=) is community-standard but not re-confirmed against one specific official 5.8 page this session.---
[VERIFIED — https://dev.epicgames.com/documentation/en-us/unreal-engine/importing-and-exporting-landscape-heightmaps-in-unreal-engine]
Supported formats for the classic Landscape (heightfield) system:
.png).r16 (raw).r8Native format is single-channel grayscale, 16 bits per pixel (0-65535, 32768 = sea level). RGB images are accepted but color is discarded and precision degrades — do not use them. 8-bit is technically accepted but causes visible terracing (256 height steps) — standardize on PNG16 or .r16.
EXR is NOT a supported classic-heightmap import format — flagged explicitly because external terrain tools (Houdini, Gaea) often default to EXR for other purposes; do not let that default leak into the heightmap export step. Third-party tools explicitly called out as supported upstream sources: Gaea, World Machine, Houdini, Terragen. Tiled heightmaps are supported (importer detects/prompts for the tiled path).
[VERIFIED — https://dev.epicgames.com/documentation/unreal-engine/landscape-technical-guide-in-unreal-engine]
overall_verts = (num_components_per_axis × quads_per_component) + 1 per axis.Recommended landscape sizes (use these exact combinations):
| Overall size (verts) | Quads/section | Sections/component | Component size | Total components |
|---|---|---|---|---|
| 8129 × 8129 | 127 | 4 (2×2) | 254×254 | 1024 (32×32) |
| 4033 × 4033 | 63 | 4 (2×2) | 126×126 | 1024 (32×32) |
| 2017 × 2017 | 63 | 4 (2×2) | 126×126 | 256 (16×16) |
| 1009 × 1009 | 63 | 4 (2×2) | 126×126 | 64 (8×8) |
| 505 × 505 (default) | 63 | 4 (2×2) | 126×126 | 16 (4×4) |
| 127 × 127 | 63 | 4 (2×2) | 126×126 | 1 (1×1) |
Pipeline implication: generate heightmaps at exactly one of these vertex dimensions. [FLAG — partially INFERRED] the modern import panel exposes Section Size / Sections-Per-Component / Component Count / Overall Resolution fields and auto-populates a nearest-fit — but a dedicated "resolution calculator" widget beyond those fields was not confirmed from public docs. Treat the table above as the reliable contract; pre-size heightmaps externally.
There is no clean pure-Python path to create a Landscape and import a heightmap. This is the single biggest automation gap identified across all eight research targets.
[VERIFIED — https://forums.unrealengine.com/t/creating-a-lanscape-using-c-or-the-python-api/504997 ; corroborated https://forums.unrealengine.com/t/landscape-creation-using-the-python-api/2063596]
spawn_actor_from_class) yields a LandscapePlaceholder actor that "does not support any heightmap operations."unreal.LandscapeSubsystem exists but does not expose a documented one-call heightmap-import function to Python.landscape.landscape_import(...) / heightmap_expand(...) calls that surface in search results belong to the deprecated third-party 20tab UnrealEnginePython plugin — not the official API. Do not design against them.What actually works:
World->SpawnActor<ALandscape>() → Landscape->Import(FGuid, MinX, MinY, MaxX, MaxY, SectionsPerComponent, QuadsPerSection, HeightData (TMap<FGuid,TArray<uint16>>), nullptr, ImportLayers (TArray<FLandscapeImportLayerInfo>), Flags) → RegisterAllComponents() → PostEditChangeProperty(...). Reference implementations Epic engineers point to: FDatasmithLandscapeImporter::ImportLandscapeActor, FLandscapeEditorDetailCustomization_NewLandscape::OnCreateButtonClicked.Import() call as a UFUNCTION(BlueprintCallable) wrapper, callable from Python/a commandlet — the standard, community-proven workaround. Alternatively, the third-party TAPython plugin ships unreal.PythonLandscapeLib with create_landscape() / set_heightmap_data().Bottom line: heightmap → Landscape actor creation must go through C++ (or a C++/Python shim/commandlet), or a third-party plugin — this is the one link in the entire pipeline requiring bespoke engine C++ rather than pure data/scripting. Budget engineering time for this specifically.
[VERIFIED]
RuntimeGrid/RuntimeHashSet CellSize) in World Settings. There is no clean, documented pure-Python "set WP grid size" call — this is a level/World-Settings property or handled via the convert commandlet.WorldPartitionConvertCommandlet (exposes CellSize, HLOD layer paths/defaults); also reachable via editor Tools > Convert Level.-run=WorldPartitionBuilderCommandlet -Builder=<Name> with headless flags -Unattended -SCCProvider=None -AllowCommandletRendering -IterativeCellLoading -IterativeCellSize=25600. Available builders: WorldPartitionHLODsBuilder (headless HLOD builds), WorldPartitionMiniMapBuilder, WorldPartitionNavigationDataBuilder (navmesh), WorldPartitionResaveActorsBuilder, WorldPartitionRenameDuplicateBuilder, and PCGWorldPartitionBuilder (§3.3).unreal.DataLayerEditorSubsystem: create_data_layer(), create_data_layer_instance(), add_actor_to_data_layer() / add_actors_to_data_layers(), remove_actor_from_data_layer(), set_data_layer_is_loaded_in_editor(), set_data_layer_visibility(), get_all_data_layers(), get_actors_from_data_layer(). This is a clean, well-supported win — map the site rows' protected/safe-zone flags directly onto Data Layers (e.g., tag protected sites into a "SafeZone" layer that gates encounter spawners).Net: WP creation/grid sizing is commandlet/World-Settings-driven, not pure Python; Data Layer tagging and HLOD/navmesh builds *are* headless-automatable.
[VERIFIED — https://dev.epicgames.com/documentation/en-us/unreal-engine/creating-and-using-custom-heightmaps-and-layers-in-unreal-engine]
LandscapeLayerInfoObject), combined via LandscapeLayerBlend / LandscapeLayerWeight / LandscapeLayerCoords material nodes. Custom weightmaps can be imported per-layer at landscape resolution.Import() path via FLandscapeImportLayerInfo). [FLAG] procedurally *painting* an already-existing landscape's layers via Python is not a clean documented API — the realistic options are (a) supply weightmaps at import, (b) drive layers via the auto-material (no painting needed), or (c) use PCG to sample the landscape and write layers/place assets (§3).[VERIFIED — https://dev.epicgames.com/documentation/unreal-engine/landscape-blueprint-brushes-in-unreal-engine]
Landmass is a mature, shipping (non-experimental) plugin in 5.8, providing CustomBrush_Landmass — a Landscape Blueprint Brush generating terrain from a spline shape plus erosion/curl-noise/displacement, operating on Landscape Edit Layers. It's what the Water plugin uses to carve terrain under rivers/lakes.
Assessment: Landmass is spline-and-brush / art-directed, not a bulk data-driven generator. Brushes are Blueprint actors (scriptable spline/parameter placement is possible) but this is not a cleaner "CSV row → terrain" path than heightmap import. Reserve Landmass for hero features (a specific river, a plateau) layered atop a base landscape; use external heightmap generation for bulk, data-driven terrain.
[VERIFIED — https://www.unrealengine.com/news/unreal-engine-5-8-is-now-available ; https://80.lv/articles/unreal-engine-5-8-is-out-today-with-big-optimization-improvements-and-mesh-terrain]
UE 5.8 introduces Mesh Terrain, a new Experimental true-3D-mesh terrain system (vs. 2.5D heightfield Landscape) supporting overhangs/floating islands/tunnels, a non-destructive modifier stack, mesh-or-heightmap import, and full PCG/World Partition/Nanite interoperability. Recommendation: do not build the pipeline on Mesh Terrain yet — it's Experimental (API churn likely, scripting surface undocumented). Use classic Landscape now; revisit if/when Mesh Terrain reaches Beta/Production (plausibly UE6-era), especially if any regions genuinely need overhangs/caves.
Given per-site rows (terrain_type, ambient_threat, vril_density, climactic_affordance, protected) across ~69 regions:
1. Heightmap generation (external, headless): generate a valid-sized PNG16/.r16 heightmap per region from terrain_type, via a procedural tool (Gaea/World Machine/Houdini or a bespoke noise generator) — outside the engine, easiest to script there.
2. Landscape creation (C++/shim commandlet): import via the C++ ALandscape::Import() path wrapped as a commandlet/Python-callable function (§2.3), supplying initial weightmaps for base layers. *(The one step needing engine C++.)*
3. World Partition + Data Layers (commandlet + Python): convert to WP (WorldPartitionConvertCommandlet, set CellSize), tag protected/safe-zone sites into Data Layers via DataLayerEditorSubsystem (pure Python). Build HLODs/navmesh via builder commandlets.
4. Material/layer assignment: slope/height auto-material keyed to terrain_type (no manual painting), or PCG-written layers.
5. Population (PCG, §3): CSV rows → DataTable → PCG Load Data Table to scatter flora/fauna/encounter markers by ambient_threat/vril_density/climactic_affordance, baked headlessly.
---
[VERIFIED — https://www.unrealengine.com/news/unreal-engine-5-7-is-now-available ; https://www.tweaktown.com/news/107858/unreal-engine-5-7-preview-now-out-with-production-ready-procedural-content-generation-framework/index.html]
The core PCG Framework became Production-Ready in UE 5.7 (Experimental since 5.2, Beta since 5.4) and remains so in 5.8. 5.7 also delivered ~2× performance over 5.5, a GPU compute path, and a new PCG Editor Mode (draw splines/paint points/create volumes bound to graphs). Caveat: some adjacent pieces remain Experimental — notably PCG Biome Core (v0.2) and GPU/runtime-generation extras — verify runtime performance before shipping those specific sub-features.
[VERIFIED — https://dev.epicgames.com/documentation/en-us/unreal-engine/python-api/class/PCGGraph]
Python *can* author graphs, not merely trigger them: create the asset (asset_tools.create_asset('MyGraph', '/Game/PCG', unreal.PCGGraph, unreal.PCGGraphFactory())), add nodes (add_node_of_type(settings_class), add_node_copy(settings), add_node_instance(settings)), wire them (add_edge(from_node, from_pin_label, to_node, to_pin_label), remove_edge(...)), and manage them (remove_node(), get_input_node(), get_output_node()).
Precise distinction: this is a low-level, sparsely-documented surface, tedious/brittle for complex graphs. The reliable, recommended pattern: author reusable template graphs once in the visual PCG Editor, expose tunable knobs as graph parameters/user attributes, and use Python/data only to instantiate, parameterize, and execute them per site. Treat visual authoring as the design tool; Python as the orchestration tool.
[VERIFIED — https://dev.epicgames.com/documentation/en-us/unreal-engine/procedural-content-generation-overview ; https://dev.epicgames.com/documentation/en-us/unreal-engine/using-pcg-generation-modes-in-unreal-engine]
unreal.PCGComponent manages generation both in-editor and at runtime. Python-exposed: generation_trigger (GenerateOnLoad / GenerateOnDemand / GenerateAtRuntime), current_editing_mode, generate(). [FLAG — community-verified gotcha] calling Generate() before the owning actor has a valid world/transform (i.e., before SpawnActor completes) silently fails.PCGWorldPartitionBuilder runs PCG generation across a whole WP level from the command line, no GUI:
UnrealEditor.exe "Project.uproject" "/Game/Maps/MyOpenWorld" \
-run=WorldPartitionBuilderCommandlet -Builder=PCGWorldPartitionBuilder \
-IncludeGraphNames=PCG_GraphA;PCG_GraphB \
-Unattended -AllowCommandletRendering
Flags: -IncludeGraphNames= (filter to specific graphs), -IterativeCellLoading/-IterativeCellSize= (memory management), -Unattended, -AllowCommandletRendering. https://dev.epicgames.com/documentation/unreal-engine/world-partition-builder-commandlet-reference ; https://forums.unrealengine.com/t/world-partition-pcg-builder/2730237. This maps to Epic's public roadmap item "PCG Offline Builder" and is the intended mechanism to bake all PCG content in a build step. [FLAG] community reports note edge-case bugs (iterative-cell-loading + a Grid Size node can throw an executor error); pin engine version and test the specific graphs.
[VERIFIED — https://dev.epicgames.com/documentation/en-us/unreal-engine/procedural-content-generation-framework-node-reference-in-unreal-engine]
PCG has first-class DataTable ingestion — directly the "CSV row → populated zone" mechanism this project needs:
Load Data Table node — loads a UDataTable into PCG point data, either as Point Data or as an Attribute Set. Since a UDataTable is CSV/JSON-backed, registry CSVs → DataTable → straight into PCG.Data Table Row to Attribute Set node — extracts a single row to an Attribute Set. Ideal per-site driver: one site row → one Attribute Set → parameterizes that site's graph (density, threat, creature list).Get Actor Property / Get Property From Object Path / Get Actor Data — general property/data readers for data-driven workflows.Get Landscape Data (sample height/layers/steepness — gate spawns by slope), Get Spline Data (control-point attributes — roads/rivers/borders), Surface Sampler/Mesh Sampler/Texture Sampler (scatter on surfaces; texture-mask density).biome/threat/vril_density rides along each point, and downstream nodes branch on it).Static Mesh Spawner reads an Attribute Set list of meshes and picks per-point by a MeshPath-style attribute — "spawn creature X where biome==Z at density Y" is the intended, supported pattern.Roadmap context (not blocking): Epic's public roadmap lists further refinements — "Attribute Set Tables," "Native Level to PCG Data Asset," "PCG Offline Builder." The shipping Load Data Table + Data Table Row to Attribute Set nodes already cover the core need in 5.8. [FLAG] exact ship-versions of those roadmap cards were not fully determinable (productboard cards didn't fully load) — the shipping nodes themselves are confirmed in the 5.8 node reference, which is what matters for now.
Load Data Table → point-data/attribute-set → Static Mesh Spawner (attribute-driven mesh selection), per the node reference and PCG overview docs.Row_Name, position, orient, scale, mesh_id, plus custom attrs like material/biome) as CSV → DataTable → PCG graph, driving "copy-mesh-to-points" entirely from a mesh_id/piece attribute. This is a direct template for "282-row boss registry + 132-row creature registry → CSV → DataTable → PCG spawn at per-row density/placement." Also useful: https://dev.epicgames.com/community/learning/knowledge-base/KP2D/unreal-engine-a-tech-artists-guide-to-pcgRecommended shape for this project: import creature_registry.csv and boss_registry.csv as DataTables; per region, Data Table Row to Attribute Set yields that site's spawn spec; a template graph reads ambient_threat → spawn density, vril_density → creature-tier filter, climactic_affordance → boss/set-piece marker placement, protected → suppress hostiles (or route via the Data Layer, §2.4). Boss "encounter markers" are cleanest as PCG-spawned trigger/spawner actors rather than baking live AI directly (see §9 Step 8 on why the boss's actual fight logic is a separate, non-PCG concern).
[VERIFIED — https://dev.epicgames.com/documentation/unreal-engine/working-with-pcg-and-llms-using-unreal-mcp-in-unreal-engine]
UE 5.8 ships an Experimental Unreal MCP server giving "LLM-driven access to Unreal Engine," with a dedicated PCG toolset: an LLM (explicitly Claude, per Epic's docs) can do graph manipulation/generation, node wiring, parameter config, and attribute-driven data-flow through it. Requirements: enable the MCP plugin, load the PCG toolset, and load the "PCG graph generation skill" (Epic calls this *mandatory* — without it the LLM "misunderstands PCG concepts, overcomplicates, misuses nodes"). Limitations: Experimental; intended for supervised collaboration, not autonomous execution; large-graph analysis is resource-expensive; no direct attribute inspection (needs a "Dataview" workflow).
Assessment: promising and on-mission, but Experimental and supervision-oriented. For a reliable, repeatable ~69-region pipeline, the deterministic path (DataTable → template PCG graphs → PCGWorldPartitionBuilder bake) should be the spine, with the MCP plugin used for one-off graph authoring/iteration rather than the production backbone.
PCGWorldPartitionBuilder (iterative cell loading + Grid Size node) — test against the specific template graphs before relying on it in CI.---
[VERIFIED — https://dev.epicgames.com/documentation/en-us/unreal-engine/data-driven-gameplay-elements]
FTableRowBase to be recognized by the importer.Name; holds the unique RowName). Duplicates are not allowed.True/False parsed from string form). [VERIFIED-community]Key=Value syntax, e.g. a vector is (X=0,Y=0,Z=0); a struct is (ValueAsString="String",ValueAsFloat=0,ValueAsBool=True). [VERIFIED-community — https://medium.com/@fidelhau/how-to-populate-datatables-cells-of-vector-and-struct-type-in-unreal-engine-5-using-csvs-and-c07f4ccc5739]"Texture2D'/Game/Textures/Icon1'" (double quotes required). Prefer TSoftObjectPtr<> for lazy references.DataTable row structs must be C++ USTRUCTs deriving from FTableRowBase. Blueprint User-Defined Structs (UUserDefinedStruct) are rejected by the importer and the "Add Data Table Row" node. Epic tracks this as bug UE-152346. [VERIFIED — https://issues.unrealengine.com/issue/UE-152346]
USTRUCT(BlueprintType)
struct FBossRow : public FTableRowBase {
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite) FText DisplayName;
UPROPERTY(EditAnywhere, BlueprintReadWrite) TArray<FBossPhase> Phases;
UPROPERTY(EditAnywhere, BlueprintReadWrite) FDataTableRowHandle ThreadRef;
};
Implication: a thin C++ layer of struct definitions is required — one USTRUCT per registry. This is pipeline-friendly: those headers can be auto-generated from the CSV/markdown schema by the ingestion tool itself, so the "C++ requirement" becomes one generated header per registry (compiled once), not hand-authoring per registry. [INFERRED — reasonable extension; codegen of USTRUCT headers is a known technique, but no official Epic generator was found for it.]
[VERIFIED against the 5.8 Python API]
(a) Create the asset + first import:
csv_factory = unreal.CSVImportFactory() csv_factory.automated_import_settings.import_row_struct = unreal.load_object(None, "/Script/YourModule.BossRow") task = unreal.AssetImportTask() task.filename = r"C:/export/bosses.csv" task.destination_path = "/Game/Data/Bosses" task.destination_name = "DT_Bosses" task.replace_existing = True task.automated = True # suppresses all dialogs task.save = True task.factory = csv_factory unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
Confirmed classes/props: unreal.CSVImportFactory, .automated_import_settings.import_row_struct, unreal.AssetImportTask (filename/destination_path/destination_name/replace_existing/automated/save/factory), unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([...]). Also unreal.DataTableFactory (with .struct) for creating an empty typed DataTable. https://github.com/Kympy/Unreal_CSV_To_DataTableAsset/blob/main/asset_generator.py ; class existence corroborated at https://dev.epicgames.com/documentation/en-us/unreal-engine/python-api/class/DataTableFactory
(b) Fill/refresh an existing DataTable via unreal.DataTableFunctionLibrary (5.8-confirmed):
fill_data_table_from_csv_file(data_table, csv_file_path, import_row_struct=None) -> boolfill_data_table_from_csv_string(data_table, csv_string, import_row_struct=None) -> boolfill_data_table_from_json_file(data_table, json_file_path, import_row_struct=None) -> boolfill_data_table_from_json_string(data_table, json_string, import_row_struct=None) -> boolexport_data_table_to_{csv,json}_{file,string}, get_data_table_row_names, get_data_table_column_names, does_data_table_row_exist, remove_data_table_row, get_data_table_row_struct. https://dev.epicgames.com/documentation/en-us/unreal-engine/python-api/class/DataTableFunctionLibraryHeadless/CI: run either via a commandlet, e.g. UnrealEditor-Cmd.exe <Project.uproject> -run=pythonscript -script="ingest.py" — the whole ingest becomes a build-script step with zero editor UI. [INFERRED — standard pattern, exact flag spelling not re-verified against an official 5.8 page this session.]
RowName. FDataTableRowHandle = {DataTable, RowName}; cross-table references resolve by row name. https://dev.epicgames.com/documentation/en-us/unreal-engine/API/Runtime/Engine/FDataTableRowHandleFindRow fails). Trailing whitespace in the name column is a common silent breaker. [VERIFIED-community — https://bugnet.io/blog/fix-unreal-data-table-row-not-found]RowName must be a stable, canonical key (the registry's own ID column — boss_id, thread_id), never a display name or positional index, and must never be re-keyed. Don't cache row-struct pointers past local scope, since re-import invalidates them.UObjects; DataAssets support subclassing/inheritance, per-instance polymorphism, and UObject references, at the cost of one asset per entry. Use DataTables for large uniform tabular registries; DataAssets when entries need inheritance/nested object graphs/heavy per-entry customization. [VERIFIED-community — https://dev.epicgames.com/community/learning/tutorials/Z13/unreal-engine-dataassets-vs-datatables]UPrimaryDataAsset + Asset Manager is the discovery/streaming layer: GetPrimaryAssetId() auto-derives <ClassName>:<AssetShortName>; register the type via Asset Manager scan directories or a custom manager set via AssetManagerClassName under [/Script/Engine.Engine] in DefaultEngine.ini. Programmatic access: UAssetManager::GetPrimaryAssetIdList(FPrimaryAssetType), GetPrimaryAssetData(...). https://dev.epicgames.com/documentation/en-us/unreal-engine/asset-management-in-unreal-engine ; https://dev.epicgames.com/documentation/en-us/unreal-engine/data-assets-in-unreal-engineFJsonObjectConverter (module JsonUtilities) provides JsonObjectStringToUStruct/UStructToJsonObjectString. For DataTables specifically, the Python fill_data_table_from_json_* functions already accept JSON and handle arrays/nested structs natively. There is no built-in "JSON → PrimaryDataAsset" one-click importer — you script it (read JSON, create asset, set properties via Python or FJsonObjectConverter). [FLAG — absence of evidence, not fully confirmable][VERIFIED — https://dev.epicgames.com/documentation/en-us/unreal-engine/using-gameplay-tags-in-unreal-engine]
FName-backed labels (Boss.Phase.Enrage), managed by UGameplayTagsManager, matched hierarchically. 1. Config INI — Config/DefaultGameplayTags.ini plus any .ini under Config/Tags/, syntax +GameplayTagList=(Tag="Boss.Phase.Enrage",DevComment="..."), gated by the "Import Tags From Config" project setting.
2. DataTable with row type FGameplayTagTableRow — importable as CSV/JSON like any other DataTable.
3. C++ native tags — UE_DECLARE_GAMEPLAY_TAG_EXTERN / UE_DEFINE_GAMEPLAY_TAG(_COMMENT) from NativeGameplayTags.h.
.ini files are plain text; the pipeline can write Config/Tags/*.ini directly (or generate the FGameplayTagTableRow DataTable) to register hundreds of tags in one shot rather than clicking Add(+) per tag. [VERIFIED mechanism; INFERRED as "the" bulk pattern — Epic frames these as authoring sources without prescribing a script-generation workflow explicitly.].ini files by concern but does not anoint one canonical mechanism for a large taxonomy. [INFERRED recommendation]: generate Config/Tags/*.ini per registry from canon.Happy-path pipeline (fully scriptable, near-zero clicking):
1. Author/codegen one C++ USTRUCT : FTableRowBase per registry (FBossRow, FCreatureRow, FThreadRow, …).
2. Emit each registry as JSON (not CSV) if it has nested/array fields (e.g., boss phases); flat CSV otherwise.
3. Run a headless Python commandlet: CSVImportFactory/AssetImportTask to create DT_Bosses, DT_Creatures, …, then DataTableFunctionLibrary.fill_data_table_from_json_file for refreshes.
4. Generate Config/Tags/*.ini for any tag taxonomy in the same run.
Impedance mismatches to design around:
Phases a TArray<FBossPhase> and import via JSON (arrays are native there), or (b) normalize into a linked DT_BossPhases table keyed by boss_id + index. (a) is simpler; (b) is more queryable/diffable. [INFERRED]thread_id → Threads table, boss→creature, etc.) — three tiers, cheapest first:FDataTableRowHandle — built-in typed pointer to another table's row, good for direct 1:1 refs, resolves by RowName.UDataRegistrySubsystem, FDataRegistryId = RegistryType:ItemName) — gives many DataTables one global namespace with sync (GetCachedItem) and async (AcquireItem) lookup and source override/fallback ordering. This is the closest thing UE offers to a relational/FK layer across a ~30-registry graph, and the recommended backbone for cross-registry references at this project's scale. [VERIFIED for mechanics — https://dev.epicgames.com/documentation/en-us/unreal-engine/data-registries-in-unreal-engine ; [INFERRED] that it's the best fit here — Epic doesn't explicitly call it "the FK solution."].uassets → not diffable in git. The CSV/JSON stays the source of truth in git; the .uasset is a regenerable build artifact — this fits the "CSV registries are canon" model already in place here.---
MetaHuman Creator is now fully integrated into the Unreal Editor, as of UE 5.6 (shipped June 3, 2025). The old separate browser-based/cloud "MetaHuman Creator" web app is gone, and Quixel Bridge is no longer the delivery mechanism.
[VERIFIED]
unreal.MetaHumanComponentUE exists for assembling/driving a MetaHuman on an actor. [INFERRED from Python API class listing] https://dev.epicgames.com/documentation/en-us/unreal-engine/python-api/class/MetaHumanComponentUENames to know: MetaHuman Creator plugin, MetaHuman Creator Core Data, MetaHuman Character asset, MetaHuman Animator, Mesh to MetaHuman.
This changed materially and recently: the honest current answer is "substantially yes, within the MetaHuman parametric model" — different from the pre-5.7 world where creation was purely manual/GUI.
[VERIFIED]
Engine\Plugins\MetaHuman\MetaHumanCharacter\Content\Python\examples: example_create_asset.py and test_set_character_properties.py ("a list of all the properties that can be set in the character without needing to open it to edit").MetaHuman Character Editor Subsystem, performing edits on face/body and exposing request_texture_sources and request_auto_rigging to call MetaHuman Cloud services for texture download and auto-rigging — so a batch generator still round-trips to Epic's cloud for the rig/texture bake per character.GetFaceModelCoefficients and SetFaceModelCoefficients ("conform-to-PCA support"), plus a MetaHuman category added to all Blueprint-exposed functions — the concrete lever for programmatic face variation: the face is a PCA/parametric model, and coefficients are gettable/settable from code. https://dev.epicgames.com/documentation/metahuman/metahuman-5-8-release-notes-in-unreal-engineThe precise, honest distinction for pipeline design:
The DNA format is documented with an open-source SDK — but it modifies existing DNA, it does not generate novel faces.
[VERIFIED]
EpicGames/MetaHuman-DNA-Calibration on GitHub; docs at https://epicgames.github.io/MetaHuman-DNA-Calibration/).dna (core format reader/writer), dnacalib/DNACalib (modification), dnaviewer/DNAViewer (Maya rig build, FBX export, DNA introspection). Compiled against Python 3.7/3.9..dna file has four layers: Descriptor, Definition, Behavior (RigLogic), Geometry. DNACalib can rename/remove joints/meshes/blendshapes/animated maps, remove joint animation, rotate/scale/translate the rig, remove LODs, change neutral joint/mesh positions and blendshape delta values, prune/clear blendshapes.neck_01, neck_02, FACIAL_C_FacialRoot) must not be removed/renamed (head-to-body connection).[VERIFIED]
Epic's own documented architecture is exactly the tiered pattern this project needs: full MetaHumans for hero/named, a separate crowd system for the masses.
[VERIFIED]
---
[VERIFIED — https://dev.epicgames.com/documentation/unreal-engine/python-scripting-in-sequencer-in-unreal-engine]
The full create → bind → track → key → range surface is scriptable:
unreal.AssetToolsHelpers.get_asset_tools().create_asset(asset_name, package_path, unreal.LevelSequence, unreal.LevelSequenceFactoryNew()).unreal.get_editor_subsystem(unreal.LevelSequenceEditorSubsystem) → ls_system.create_camera(spawnable=True) (adds a spawnable camera actor binding *and* a camera cut track, mirroring the UI). Camera cut track class: unreal.MovieSceneCameraCutTrack.ls_system.add_actors([actor]); convert with ls_system.convert_to_spawnable(binding).unreal.LevelSequence.add_spawnable_from_instance(...) / add_spawnable_from_class(...). [VERIFIED as native API members; exact signatures live in the per-class Python API reference rather than the tutorial page]binding.add_track(unreal.MovieScene3DTransformTrack) then track.add_section(). Referenced classes: unreal.MovieScene3DTransformTrack (section unreal.MovieScene3DTransformSection), unreal.MovieSceneSkeletalAnimationTrack, unreal.MovieSceneSubTrack.unreal.MovieSceneAudioTrack/unreal.MovieSceneAudioSection are the native classes, added as a root/master track via the same add_track pattern. [VERIFIED class existence; INFERRED exact add-call from the uniform pattern — the tutorial page doesn't show an audio example.] Note: search results also surface the legacy third-party 20tab sequencer_add_master_track API — that is not the current native API; ignore it.section.get_channels() → channel.add_key(unreal.FrameNumber(...), value). [VERIFIED-as-native-pattern; the tutorial page points to shipped examples for keying specifics rather than showing it directly.]level_sequence.get_movie_scene(); set_playback_start(...)/set_playback_end(...) (frames, or _seconds variants).Engine\Plugins\MovieScene\SequencerScripting\Content\Python — Epic's own reference scripts for all of the above.Bottom line: yes — create a Level Sequence, add camera-cut/transform/skeletal-animation/audio/subsequence tracks, bind participants as possessables or spawnables, key transforms/properties, and set the playback range, entirely from Python.
Honest answer: there is no turnkey Epic "table/JSON → Level Sequence" feature — but the complete Python surface to build that translation layer exists, and doing so is established professional practice.
scene_type) containing camera rigs, cut cadence, and empty named binding slots; a Python driver duplicates the matching template per data row, resolves participants to bindings, drops in retargeted body animation + audio-driven facial + audio tracks, sets the playback range, and saves. The Shots/Takes/SubSequence system (MovieSceneSubTrack; "each Shot is its own Sequence asset") is the native structure for composing many generated shots. [VERIFIED — https://dev.epicgames.com/documentation/unreal-engine/sequences-shots-and-takes-in-unreal-engine][VERIFIED — https://dev.epicgames.com/documentation/unreal-engine/using-command-line-rendering-with-move-render-queue-in-unreal-engine]
Verbatim examples from Epic's 5.8 docs:
Render one Level Sequence with a preset config:
UnrealEditor-Cmd.exe "E:\SubwaySequencer\SubwaySequencer.uproject" subwaySequencer_P -game -LevelSequence="/Game/Sequencer/SubwaySequencerMASTER.SubwaySequencerMASTER" -MoviePipelineConfig="/Game/Cinematics/MoviePipeline/Presets/SmallTestPreset.SmallTestPreset" -windowed -resx=1280 -resy=720 -log -notexturestreaming
Render a whole queue asset:
UnrealEditor-Cmd.exe "E:\SubwaySequencer\SubwaySequencer.uproject" subwaySequencer_P -game -MoviePipelineConfig="/Game/Cinematics/MoviePipeline/Presets/BigTestQueue.BigTestQueue" -windowed -resx=1280 -resy=720 -log -notexturestreaming
Custom Python executor (for a fully scripted pipeline):
UnrealEditor-Cmd.exe "E:\SubwaySequencer\SubwaySequencer.uproject" subwaySequencer_P -game -MoviePipelineLocalExecutorClass=/Script/MovieRenderPipelineCore.MoviePipelinePythonHostExecutor -ExecutorPythonClass=/Engine/PythonTypes.MoviePipelineExampleRuntimeExecutor -windowed -resx=1280 -resy=720 -log -notexturestreaming
Notes:
UnrealEditor-Cmd.exe. Args: <uproject> <MapName> -game, then either -LevelSequence=... -MoviePipelineConfig=<preset> or -MoviePipelineConfig=<queue asset>, plus -windowed -resx -resy -log -notexturestreaming.MoviePipelinePythonHostExecutor + -ExecutorPythonClass) is the sanctioned automation path.-RenderOffscreen/-NoLoadingScreen; these are common-practice flags but were not shown verbatim on this specific doc page (which uses -windowed in its own examples). There is also a separate "Movie Render Queue in Runtime" path for in-game rendering. https://dev.epicgames.com/documentation/unreal-engine/movie-render-queue-in-runtime-in-unreal-engineSchema: scene_anchor_id | beat_ref | scene_type | path_bucket | anchor_type | participant_refs | thematic_anchor. Realistic translation layer [INFERRED, each grounded in the verified API above]:
scene_anchor_id → asset name/path of the generated Level Sequence (or Shot sub-sequence), e.g. /Game/Cine/<scene_anchor_id> — also the natural primary key for idempotent regeneration. Clean.beat_ref → which parent/master sequence (the beat) this shot's MovieSceneSubTrack slots into (Shots/Takes composition). Clean.scene_type → selects the reusable "shot template" Level Sequence to duplicate and populate (dialogue-2-shot, establishing, combat-beat, etc.) — the highest-leverage mapping, since templates carry the camera blocking/cut cadence that shouldn't be regenerated per row. Clean.path_bucket (narrative branch) → organizational/naming + which parent sequence or variant the shot belongs to; folderization and conditional inclusion — not Sequencer-native, but trivially handled in the driver. Clean.anchor_type (location anchor) → which level/sublevel/environment the sequence renders against (the map name in the MRQ command line, or a streamed sub-level/level-variant the driver loads). Clean — this is the MapName arg in §6.3.participant_refs → maps cleanly to Sequencer bindings — each ref becomes a possessable (add_actors) if the NPC actor is already placed, or a spawnable (add_spawnable_from_class/instance, or create_camera for cameras) if the sequence should spawn it. The cleanest structural fit in the whole schema — provided each ref resolves to a concrete actor/MetaHuman asset (so the NPC-generation pipeline, §5, must emit stable asset IDs the scene driver can look up).thematic_anchor → non-functional metadata only. No Sequencer equivalent — carry it as a comment/asset metadata tag/marked-frame label at most; it informs template/music/lighting choice upstream in the driver's logic, but the engine does nothing with it directly.What in the schema has NO clean automatable Sequencer equivalent — flag these for pipeline design:
1. Per-shot camera blocking, timing, and cut rhythm are not in the schema at all — they must come from the scene_type templates (or a separate heuristic/LLM authoring step). The schema tells you who/what/where/why, not how the camera moves. This is the single biggest gap between "scene row" and "finished cinematic"; templates are the intended bridge.
2. Actual performance content — specific body animation and facial performance per beat — isn't in the schema; it must come from the Target-5 animation pipeline (retargeted mocap library + audio-driven facial from dialogue SoundWaves). The scene row references participants; it doesn't contain their motion.
3. Dialogue/audio assets — participant_refs implies who speaks, but the schema carries no line/audio reference. A separate dialogue→SoundWave source is needed to populate audio tracks and drive MetaHuman Animator's audio-driven facial mode. This is a genuine schema gap worth closing if auto-populated audio + lip-sync is wanted.
add_spawnable_from_class/instance, audio-track add_track, and channel keying are native API members but not quoted verbatim from the one tutorial page — confirm against the per-class Python API reference and the shipped SequencerScripting\Content\Python examples during implementation.-RenderOffscreen/-NoLoadingScreen as fully-headless flags are community-standard, not quoted on the specific 5.8 MRQ command-line doc page (which demonstrates -windowed instead).---
USaveGame — current architecture & the "many flags" gap[VERIFIED — https://dev.epicgames.com/documentation/en-us/unreal-engine/saving-and-loading-your-game-in-unreal-engine]
USaveGame, add UPROPERTY() fields, serialize via UGameplayStatics: SaveGameToSlot/AsyncSaveGameToSlot (Epic recommends the async variant during active gameplay to avoid frame hitches), LoadGameFromSlot/AsyncLoadGameFromSlot, plus memory/binary variants SaveGameToMemory, LoadGameFromMemory, SaveDataToSlot, LoadDataFromSlot.FGameplayTagContainer can track dozens of quest/world flags compactly. [VERIFIED-community — https://www.strayspark.studio/blog/complete-guide-save-systems-unreal-engine ; https://medium.com/object-oriented-worlds/implementing-quest-systems-in-ue5-blueprints-47ea0ac00599][VERIFIED subsystem lifecycles — https://dev.epicgames.com/documentation/en-us/unreal-engine/programming-subsystems-in-unreal-engine]
UEngineSubsystem (whole process), UGameInstanceSubsystem (persists across level/map transitions, for the whole session), UWorldSubsystem (per-world/level, dies on level change), ULocalPlayerSubsystem (per local player), UEditorSubsystem (editor-only). All auto-instantiated, singletons within scope, auto-exposed to Blueprint with contextless typed nodes. Access: GameInstance->GetSubsystem<T>(), GEngine->GetEngineSubsystem<T>(); from Python, unreal.get_engine_subsystem(...).UGameInstanceSubsystem is the correct scope — the GameInstance lives for the whole session. Epic's page doesn't name this "the world-state pattern" explicitly, but the lifecycle makes it the right choice, and community practice consistently puts quest/state managers in a GameInstance-scoped subsystem. **[VERIFIED lifecycle; INFERRED that it's the right home for a ws.* map]**UWorldStateSubsystem : UGameInstanceSubsystem holding a TMap<FGameplayTag /* or FName */, FWorldStateValue> (or an FGameplayTagContainer for pure booleans), with save = copy the map into a USaveGame subclass and write it. A well-established community pattern, not an officially-named one. [INFERRED — synthesized from verified subsystem lifecycles + community save guidance][VERIFIED — https://dev.epicgames.com/documentation/en-us/unreal-engine/lyra-sample-game-in-unreal-engine ; https://x157.github.io/UE5/LyraStarterGame/]
Lyra does NOT ship a general save/persistence system for progression, quests, or world state. Lyra demonstrates modular gameplay (GameFeatures, Experiences, the Game Phase Subsystem, GAS, UI); developers are expected to build their own persistence layer on top. There is no first-party "quest-flag persistence" reference implementation to copy — a known gap, confirmed by the recurring "how do you save in Lyra" forum threads. The Lyra-adjacent subsystem worth studying is the Game Phase Subsystem (hierarchical gameplay-tag-driven phases) — conceptually close to a tag-driven state machine, though it's runtime phase logic, not persistence.
[VERIFIED — https://dev.epicgames.com/documentation/en-us/unreal-engine/overview-of-state-tree-in-unreal-engine ; https://dev.epicgames.com/documentation/unreal-engine/external-statetree-quickstart-guide]
[VERIFIED — https://dev.epicgames.com/documentation/unreal-engine/API/Plugins/GameplayAbilities/UAbilitySystemComponent/AddLooseGameplayTag ; https://github.com/tranek/GASDocumentation]
UAbilitySystemComponent maintains a live FGameplayTagCountContainer (AddLooseGameplayTag/RemoveLooseGameplayTag/HasMatchingGameplayTag), and some narrative systems piggyback quest flags on this. But it's a poor fit as the primary worldstate store: GAS is heavyweight (attributes, effects, prediction/replication), loose tags are not auto-persisted (still need hand-serializing to a SaveGame), and tags are presence/count only — they model booleans well but not enums or integers (two of the three ws.* types here). Pulling in the whole GAS plugin just to hold narrative flags is over-engineering. Use GameplayTags as the key vocabulary; don't use GAS-the-system as the container. [INFERRED — grounded in verified GAS tag mechanics + the boolean-only limitation]
[Clearly INFERRED architectural judgment, built on the verified pieces above — no Epic-cited reference architecture exists for this; validate with a spike before committing.]
A custom UWorldStateSubsystem : UGameInstanceSubsystem as the runtime authority, holding live state, backed by a DataTable-driven fork-rule evaluator, persisted through a dedicated USaveGame:
1. State store: TMap<FGameplayTag, FWorldStateValue> on the subsystem, FWorldStateValue a small tagged-union struct (an enum discriminator + bool/int/enum payload) so all three ws.* types live in one map. GameplayTags give a generated, canon-driven key vocabulary (Ws.Chapter12.MetCassius, etc.) from the §4.6 pipeline.
2. Fork tables → a DT_ForkRules DataTable (row struct FForkRule : FTableRowBase: condition key, comparator, condition value, target key, set value, chapter/scope). The subsystem exposes EvaluateForks(FGameplayTag Scope), reading matching rows and applying writes to the TMap. Fork rows stay the flat data they already are in the spine, imported by the same Target-4 pipeline — no re-authoring into a state machine.
3. Persistence: UWorldStateSaveGame : USaveGame with one serialized copy of the map, saved via AsyncSaveGameToSlot. Only the ws.* map needs saving (small), so saves stay cheap.
4. State Tree as a consumer, not the store: where a quest/NPC needs to *react* to ws.* over time, drive an External State Tree whose Enter Conditions read the subsystem's tags — using State Tree for what it's genuinely good at (runtime state/consequence) while fork tables remain data.
Why this over the alternatives: State Tree alone mismatches stateless rule rows (§7.4); GAS alone can't hold enums/ints and isn't auto-persisted (§7.5); scattering fields across actors fails the "many independently-tracked flags" requirement (§7.1). The subsystem+DataTable+SaveGame hybrid reuses the existing CSV/registry pipeline for the fork rules, survives level loads by construction, and keeps canon as flat git-diffable data.
---
[VERIFIED via multiple corroborating sources quoting https://www.unrealengine.com/license and the EULA — Epic's own pages returned HTTP 403 to automated fetching, so figures are cross-checked via secondary sources rather than a direct fetch of the primary page]
Scope clarification [VERIFIED/INFERRED synthesis]: Epic's April-2025 pricing restructure introduced a per-seat subscription (~US $1,850/seat/year) — but that applies to non-game / "other industries" use (film/TV, animation, architecture, automotive, simulation) for entities over $1M annual revenue. For games, the royalty model applies, not the seat fee. A solo commercial *game* pays $0 up front and 5% only above $1M/product — don't let the "$1,850 per seat" figure (which also surfaces in MetaHuman coverage) confuse the game-licensing picture.
[VERIFIED — https://www.unrealengine.com/en-US/blog/self-service-publishing-now-available-for-the-epic-games-store ; CG Channel; PocketGamer]
[VERIFIED — https://www.metahuman.com/license ; https://www.cgchannel.com/2025/06/you-can-now-sell-metahumans-or-use-them-in-unity-or-godot/ ; Creative Bloq; Epic forum "Licensing Changes to MetaHumans"]
[VERIFIED that these clauses exist, via search summaries quoting https://www.unrealengine.com/eula/unreal and /eula/content, plus https://forums.unrealengine.com/t/new-eula-ai-restriction/2068913 — the EULA pages themselves 403'd automated fetching, so exact section numbers are unconfirmed and should be pulled from a live browser session before this is treated as final legal guidance]
1. Restriction on using the Licensed Technology to train generative AI: "You must ensure that your activities with the Licensed Technology do not… result in using the Licensed Technology as a training input… [or prompt-based input] into any Generative AI Program." "Generative AI Program" is defined broadly (AI/ML/deep-learning/neural-nets generating audio, visual, or text content).
2. "NoAI" content: Fab/marketplace content can be tagged "NoAI"; users must not use NoAI content in datasets/development/inputs for Generative AI Programs. Epic commits not to feed Licensed Content into generative AI itself.
3. MetaHuman AI-training prohibition (§8.3).
Scope interpretation [INFERRED, flagged for legal confirmation]:
[DECISION NEEDED — legal review] item before locking the pipeline architecture. Safe working design: AI authors/drives; Epic content never becomes training data. Re-check the EULA change log (https://www.unrealengine.com/eula-change-log/unreal) at each engine upgrade, especially heading into UE6.unrealengine.com/eula/* and /license pages returned 403 to automated fetching) — a human should open the live EULA in a browser and capture exact section references before this is cited as legal fact.---
This is the concrete, ordered list of what the autonomous pipeline feeds the engine, from this project's schema to a playable region. Each step gives artifact → format → API/mechanism, followed by its automation confidence and any flag. Steps 0-1 are one-time setup; Steps 2-12 repeat per region/chapter; Steps 13-14 are whole-project.
Our site rows, boss phase→beat maps, scene rows, and fork tables currently live embedded in typed markdown inside the per-chapter story spine docs (§3b, §9, §13), not as standalone tables — only the registries (bosses, creatures, weapons, etc.) are already flat CSV. Before any of the UE-side steps below can run, a markdown-table-extraction step (our own tooling, not an Epic API) must pull the site/scene/fork rows out of the spine .md files into the same CSV/JSON shape as the registries. This is called out here for completeness, but it is not a UE research question — it doesn't appear in the residual-unknowns register below because it has nothing to do with Epic's documentation.
USTRUCT : FTableRowBase per registry/table (FBossRow, FCreatureRow, FSiteRow, FSceneRow, FForkRule, …); generated Config/Tags/*.ini for the GameplayTag taxonomy (thread names, worldstate keys, boss phases, etc.).registries/*.csv (bosses, creatures, weapons, threads, …), converted to JSON wherever a registry has nested/repeated fields (e.g., boss combat phases).UnrealEditor-Cmd.exe -run=pythonscript, using unreal.CSVImportFactory/unreal.AssetImportTask for first creation and unreal.DataTableFunctionLibrary.fill_data_table_from_json_file/_csv_file for refresh, producing DT_Bosses, DT_Creatures, etc. Cross-registry FK-style references resolve via FDataTableRowHandle (1:1) or the UDataRegistrySubsystem (many-to-many namespace across the ~30 registries)..umap level, World-Partitioned.WorldPartitionConvertCommandlet (sets CellSize, HLOD defaults) or manual initial setup via Tools > Convert Level.terrain_type (+ any geography reference material)..r16 heightmap, sized to one of the valid vertex dimensions (e.g., 1009², 2017², 4033² depending on region scale), produced by an external procedural tool (Gaea/World Machine/Houdini, or a bespoke noise generator) parameterized by terrain_type.ALandscape::Import() C++ call parameters (bounds, sections-per-component, quads-per-section, a TMap<FGuid,TArray<uint16>> of height data, an FLandscapeImportLayerInfo array).UFUNCTION(BlueprintCallable) shim wrapping ALandscape::Import(), callable from a commandlet or exposed to Python, or (b) a third-party plugin (TAPython's unreal.PythonLandscapeLib).terrain_type → slope/height rule mapping, authored once.LandscapeLayerBlend/LandscapeLayerWeight/LandscapeLayerCoords nodes, self-assigning layers by slope/height (no manual painting).protected flag.unreal.DataLayerEditorSubsystem (create_data_layer, add_actor_to_data_layer, set_data_layer_is_loaded_in_editor, etc.) (§2.4).ambient_threat, vril_density, climactic_affordance) + the creature/boss DataTables from Step 1.Load Data Table/Data Table Row to Attribute Set to read DT_Creatures/DT_Bosses, parameterized per site.unreal.PCGComponent.generate()); a full headless bake across a whole World-Partitioned region runs via UnrealEditor.exe -run=WorldPartitionBuilderCommandlet -Builder=PCGWorldPartitionBuilder -IncludeGraphNames=... (§3.3-3.5).PCGWorldPartitionBuilder (iterative cell loading + a Grid Size node) — test against the specific template graphs before relying on it in CI. Raw-Python graph *authoring* (add_node_of_type/add_edge) exists but is brittle; templates+parameterization is the reliable path, a design choice rather than a hard blocker.DT_Bosses rows by RowName; the actual multi-phase fight logic (attack patterns, phase transitions) as Blueprint/C++ gameplay code, plausibly State Tree-driven (§7.4), reading the same DataTable row.GetFaceModelCoefficients/SetFaceModelCoefficients for parametric variation) plus a cloud round-trip (request_auto_rigging, request_texture_sources); for background faction rosters — the Experimental MetaHuman Crowd plugin or classic modular/instanced characters.Engine\Plugins\MetaHuman\MetaHumanCharacter\Content\Python\examples (§5.2).DT_ForkRules DataTable (condition key/comparator/value, target key, set value, scope) — ingested via the same Step 1 mechanism.UWorldStateSubsystem : UGameInstanceSubsystem holding TMap<FGameplayTag, FWorldStateValue>, evaluating DT_ForkRules at story beats, persisted via a UWorldStateSaveGame : USaveGame (AsyncSaveGameToSlot) (§7.6).scene_anchor_id, beat_ref, scene_type, path_bucket, anchor_type, participant_refs, thematic_anchor).scene_type "shot templates" (pre-authored camera cut tracks/cadence), with participant_refs resolved to possessable/spawnable actor bindings (the MetaHuman or generic-NPC assets from Step 9), audio tracks populated from dialogue SoundWave assets (a separate dialogue/VO pipeline not covered by the given schema), and facial animation from MetaHuman Animator's audio-driven mode.unreal.LevelSequenceEditorSubsystem (create_camera, add_actors, convert_to_spawnable), MovieScene track/section APIs (MovieScene3DTransformTrack, MovieSceneAudioTrack, MovieSceneSkeletalAnimationTrack), headless render/QA via Movie Render Queue command-line (-MoviePipelineConfig=...) (§6.1-6.3).thematic_anchor has no engine equivalent at all (metadata/comment only); actual camera blocking and shot rhythm are not in the schema and must come from the scene_type templates or a separate authoring/heuristic step; the schema carries no dialogue/audio reference, so a dialogue→SoundWave linkage must be added for auto-populated audio + lip-sync (§6.4).-run=DataValidation commandlet (extensible with custom UEditorValidatorBase subclasses in C++/Blueprint/Python), run headlessly in CI (§1.3)..pak.RunUAT.bat/.sh BuildCookRun (-cook -allmaps -stage -pak -archive), fully headless with -unattended -nullrhi (or -RenderOffScreen if a GPU-dependent step like lighting is involved), on Windows or Linux CI agents; Epic's own Horde system (BuildGraph scripts) is the proven at-scale orchestration pattern for this exact step (§1.3-1.5).UE 5.8's official, Experimental Unreal MCP plugin (Claude-named by Epic) can drive PCG graph authoring and general editor operations conversationally (§1.1 bonus finding, §3.6). Epic positions it as supervised/assistive, not a deterministic backbone. Recommendation: keep Steps 0-13 above as the pipeline's deterministic spine; use the MCP plugin for one-off template/graph authoring and iteration, not for the production run across all 69 regions, until it matures out of Experimental.
---
Every item below is something this research could not determine from public Epic documentation (or found only via community/secondary corroboration) — the true residual unknowns for human review, in one place.
| # | Area | Unknown | Why it matters |
|---|---|---|---|
| 1 | Target 1 | Exact 5.8 PCGComponent/PCGGraph Python signatures not re-pinned this session | PCG API churned through 5.7/5.8; verify before coding |
| 2 | Target 1 | /remote/batch has a reported crash bug | Don't depend on batched Remote Control calls without testing |
| 3 | Target 1 | Headless commandlet flag spelling (-run=pythonscript, -ExecutePythonScript=) not re-confirmed against one specific 5.8 page | Low risk (stable community practice), but a 1-minute check is cheap insurance before CI wiring |
| 4 | Target 2 | Whether the Landscape import dialog has a distinct "resolution calculator" beyond auto-populating fields | Minor UX question, doesn't change the underlying size-table contract |
| 5 | Target 2 | No documented way to procedurally paint layers on an *existing* landscape via Python | Forces the auto-material-or-PCG-only approach for layer assignment |
| 6 | Target 2/4 | Landscape heightmap import has no stock Python path at all — requires a C++ shim or third-party plugin | The single largest pure-engineering dependency in the terrain chain (Step 4) |
| 7 | Target 3 | Exact ship-versions of PCG roadmap items (Attribute Set Tables, Native Level to PCG Data Asset, PCG Offline Builder) | The shipping nodes already cover the core need; these are refinements, not blockers |
| 8 | Target 3 | Community-reported edge-case bugs in PCGWorldPartitionBuilder (iterative cell loading + Grid Size node) | Test against the specific template graphs before relying on it in CI |
| 9 | Target 4 | Row-reference-survives-rename-iff-stable-RowName is a sound inference, not an Epic-quoted guarantee | Reinforces the "never re-key a RowName" pipeline rule |
| 10 | Target 4 | No official JSON→PrimaryDataAsset importer confirmed to exist | Affects only the DataAsset path, not the DataTable path this project will mostly use |
| 11 | Target 5 | No published hard number for max simultaneous MetaHumans (VRAM/groom-bound) | Affects how aggressively named-NPC MetaHumans can be used per scene |
| 12 | Target 5 | Cloud auto-rig/texture-synthesis throughput/latency at "hundreds of characters" scale is undocumented | The real risk to validate empirically before committing to MetaHuman-per-NPC generation at this project's roster size |
| 13 | Target 5 | Whether the Experimental MetaHuman Crowd plugin is production-stable enough to ship a solo title on | Determines whether a classic modular-NPC fallback is needed for launch |
| 14 | Target 6 | Exact call signatures for add_spawnable_from_class/instance, audio-track add_track, and channel keying not quoted verbatim from the one tutorial page | Confirm against the per-class Python API reference and shipped examples during implementation — low risk, native API members |
| 15 | Target 6 | -RenderOffscreen/-NoLoadingScreen as fully-headless MRQ flags are community-standard, not quoted on the specific 5.8 doc page | Low risk, easy to test empirically |
| 16 | Target 6 | The §13 scene-row schema has no camera-blocking, performance-content, or dialogue/audio fields | Design gap on our side, not a UE documentation gap — needs schema extension or a template/heuristic authoring layer |
| 17 | Target 7 | No official Epic worldstate/save architecture for many typed flags exists; Lyra ships no save reference at all | The largest architecture-level unknown in this brief — §7.6's subsystem design is synthesized, not cited; spike it before committing |
| 18 | Target 8 | Exact EULA section numbers for royalty and AI clauses unconfirmed (pages 403'd to automated fetch) | A human should open the live EULA in a browser and capture exact section references before citing as legal fact |
| 19 | Target 8 | The EULA's generative-AI "prompt-based input" clause scope is genuinely ambiguous, per Epic's own community | Flag for legal review before locking the pipeline architecture — see §8.4 for the safe-design interpretation in the meantime |
| 20 | Target 9 (chain) | Boss phase→beat maps translating into actual multi-phase fight AI logic has no UE-documentation answer at all | This is a game-code design problem, comparable in scope to the landscape C++ shim, not a pipeline-wiring problem |
---