Last Week in Unreal: The Build Cache Trusted a Version String (July 20 to 26, 2026)

Unreal Engine Weekly: The Build Cache Trusted a Version String
Week of July 20 – 26, 2026 | 960 commits on ue6-main, 217 on ue5-main | Branch: ue6-main
Overview
The busiest week since mid-May, and most of it is repair work. Close to 40% of the week’s notable commits are crash, use-after-free, hang, or assert fixes, the mix you get when a branch is being driven toward a release. The rest concentrates in two places: the renderer, where FScene is being taken apart into scene extensions on what is now clearly a scheduled architectural project, and build infrastructure, where UBA and the EpicClang toolchain absorbed a week of correctness work with the tone of an incident review. One incidental commit makes the framing official: Engine/Build/Build.version had its branch name updated to UE6 (e4af2492).
Development Trends
The weeks of June 29 and July 6 (75 and 64 commits) are Epic’s summer shutdown, not a decline. Week counts below refer to full-volume weeks.
FScene decomposition has become a scheduled UE6 project, and this week it broke API. The scene-extension refactor has escalated at each of its three full-volume appearances: scaffolding in early June, four extractions on July 19, and five more in one coordinated push this week, with rendering activity hitting its highest count since the branch switched to ue6-main in June (66, against a June and July range of 31 to 53). The API break is the new part. FPrimitiveSceneProxy‘s level-added and level-removed render-thread hooks left ENGINE_API entirely (97e8f671). Behavior-neutral refactors do not usually take public API with them, which puts a schedule behind this one. The consistent thread across all three weeks is persistent-primitive-ID indexing and change-set-driven lifecycle.
Build and toolchain work shifted from modernization to correctness under failure. Build has been the longest-running thread since early February, and for two weeks straight it has been the busiest area on the branch. The content changed sharply between the two. July 19 was structural modernization: C++23 as the default standard, the circular-dependency allowlist deleted, UnrealBuildTool moved onto modern .NET. This week is the response to a failure, and the failure was correctness. A cache keyed on a version string instead of on the compiler binary was silently reusing stale PCHs (2699d884). Two quarters of build investment produced a system fast and distributed enough that its bugs now hand you wrong binaries, and Epic is responding by making the whole path fail closed. Banning Clang 21.0.0 through 22.1.3 outright (56b0b3e9) is the version-pin equivalent of the same reflex.
Artifact reproducibility is emerging as a named UE6 shipping requirement. Determinism fixes have surfaced individually for roughly eight weeks, but this is the first week they arrive as a coherent campaign: twelve commits that exist solely to stop cooked packages from serializing differently between runs of the same cook, ten of them from a single author, five citing the same internal validation tool, with one fix already backed out (391a9237). Read alongside the UBA cache-key and EpicClang work, this is one problem rather than two: distributed build and cook infrastructure only pays off if identical inputs reliably produce identical bytes, and Epic is closing both ends of that contract in the same week. A parallel wall-clock pass from a second author (053e3401, 464f299c) says the cook has an owner this quarter.
The editor agent stack is hardening, and starting to leave the editor. This initiative has been visible for roughly twenty weeks, and the phases line up cleanly: infrastructure buildout in March, toolset proliferation and a first-party MCP server through June, a crash-hardening audit on July 19, and now transactional safety and access control. Two details matter more than the volume: the agent undo manager could not stay landed for four days running, and the new audit verbs exist specifically to repair graph corruption left by previously failed agent writes (afef1d65). Separately, a cluster under a single ticket started moving the toolset and MCP surface out of the editor and into cooked runtime builds, which turns an authoring question into an operational one. Details in Worth Tracking below. The hardening arc is well established; the cooked-runtime direction is one week old and worth confirming before drawing conclusions.
TL;DR
- If you self-host a UBA cache server, read the cache section first. A production server wild-wrote up to 512 MB past a maintenance block during compaction, and corrupted entries could turn cache misses into false hits. The bounds-checking and fail-closed fixes are worth pulling regardless of your engine version (
92d13a09,d0348830). - EpicClang now generates a build ID from its own source hash and feeds it into the UBA cache key (
2699d884). Before this, a compiler update that changed PCH binary format was invisible to the cache key, so stale intermediates were silently reused. The commit describes the result as compiler crashes and completely UB builds. Clang 21.0.0 through 22.1.3 are now banned outright (56b0b3e9). FScenedecomposition accelerated into a five-subsystem push, and it broke public API.FPrimitiveSceneProxy::OnLevelAddedToWorld_RenderThreadandOnLevelRemovedFromWorld_RenderThreadleftENGINE_APIentirely (97e8f671). Anyone overriding those hooks breaks at compile time.- Epic is treating byte-reproducible cook output as a UE6 shipping requirement. Ten determinism fixes landed this week from one author working a backlog, plus two more from other authors, alongside a separate wall-clock pass that removed roughly 21% of a 25-minute game cook in a single commit (
053e3401).
Highlights
FScene is being dismantled into scene extensions, and this one breaks API
What changed
Five separate subsystems were lifted out of FScene into dedicated scene extensions in a single coordinated push, mostly by one author. HLOD hierarchy (FLODSceneTree) moved out of FScene and out of SceneVisibility into FLODHierarchySceneExtension (86fab2e2). Lighting attachment groups moved into FLightingAttachmentGroupSceneExtension, with group membership now stored as persistent primitive IDs (34dcf40b). Nanite’s shading commands, raster pipelines, shading pipelines, visibility, and their per-FPrimitiveSceneInfo bin state all moved into Nanite::FMaterialsSceneExtension, touching 26 files across the deferred renderer, Lumen, and virtual shadow maps (ead01072). Ray tracing data started the same migration into FRayTracingSceneExtension (2142af0d).
Underneath it, SceneData grew a TDataArray<bool> bit-per-ID specialization that dirties only on value change, editor primitive selection was rewritten from a packed-index TBitArray on FScene into FEditorSceneDataExtension (c50ee288), and bPendingAddToScene became a dirty array in the same pattern (b68b7b6f).
The API break is in the level command work. When level handling became FLevelCommandSceneExtension, FPrimitiveSceneProxy::OnLevelAddedToWorld_RenderThread and OnLevelRemovedFromWorld_RenderThread were removed from ENGINE_API entirely, with the extension calling SetForceHidden through friend access and an in-commit TODO to replace that with SceneData-tracked visibility (97e8f671).
Why this is important
The recurring pattern in these commit messages is persistent-primitive-ID indexing and change-set-driven lifecycle. You need both before scene updates can be parallelized or driven end to end on the GPU. This has been running for weeks as steady cleanup; at five subsystems in one push it is a scheduled UE6 project. A public API removal inside a refactor described as behavior-neutral is unusual and worth noting on its own.
Who should care
Rendering engineers, anyone maintaining a modified Renderer module, and anyone with a custom FPrimitiveSceneProxy.
Urgency
Act now if you carry renderer patches. This is the week your merge cost went up, and the ENGINE_API removal is a compile-time break, not a silent behavior change. Track otherwise.
Production failures forced a UBA cache server rewrite
What changed
A production cache server SIGBUS’d during bucket compaction, a Mac client SEGV’d fetching entries, clients spun forever on entries the server could never serve, and large caches took ten-plus minutes to boot. The response was structural rather than a patch.
Maintenance moved from a global stop-the-world pass that kicked clients to per-bucket, event-driven work, triggered on a bucket hitting zero clients with new entries, on expiration, on caskey table overflow, or on web request. Cas liveness is now tracked by two refcounts (in-flight transfers and cache entry references), CacheEntry::inputCasRefs replaces inputsThatAreOutputs, and the database moves to v10 with v9 conversion on load (fc5ca41d). A follow-up made the delete pass run without taking the bucket exclusively, using the same per-cache-key locks as fetch and store, so long-lived client sessions no longer starve maintenance (68497718).
The corruption work is the sharp end. Entries whose caskey and path offsets landed mid-entry passed a range-only scrub and resolved garbage, and compaction traversal then did a wild write up to 512 MB past the maintenance block. CompactPathTable::GetString and CompactCasKeyTable::GetKey are now fully bounds-checked, with an explicitly fail-closed policy (92d13a09). A separate pass added a per-boot offset scrub, disconnected clients requesting table data past the table end, and made FetchCompactTable fail as a miss rather than retry forever (d0348830). Boot time for caches with millions of entries was fixed by dropping the v9-to-v10 input ref synthesis that hit casdb for every input of every entry, and moving refcount rebuild into a parallel post-deserialize pass (01d879de). Separately, memfd handles are now tagged in the handle itself rather than trusted from a process-wide env var, so a stale UBA_FILE_MAPPING_MEMFD=1 baked into a Docker image no longer masks the real error as EBADF (74d41354).
Why this is important
The fail-closed policy is the line to read twice: corruption that shrinks an entry’s input set turns cache misses into false hits. It shows up as a flaky product, not as a cache error. Everything else here is availability and boot time, which matter, but the corruption hardening is the part that can hand you a wrong binary.
Who should care
Build engineers, studio infrastructure, and anyone running distributed UE compilation at scale.
Urgency
Act now if you self-host a UBA cache server. The corruption commits are cherry-pick candidates independent of your engine version.
EpicClang stops being a vendored binary and becomes a versioned toolchain
What changed
A compiler update changed the binary format of PCH files, but UBT and UBA keyed their cache on the LLVM version string rather than the compiler binary, so stale intermediates from an older EpicClang were silently reused. The commit describes the result as compiler crashes and “completely UB builds.”
EpicClang now generates a build ID from the hash of all source layered on top of Clang, errors if you use a PCH built by a different source version, and feeds that build ID into the UBA cache key (2699d884). The day before that fix landed, the mitigation was bumping the patch version to try to invalidate the cache (bfd51aae). Clang 21.0.0 through 22.1.3 are now banned outright in MicrosoftPlatformSDK.cs and Windows_SDK.json due to a compiler crash (56b0b3e9).
The toolchain also got real tooling around it: build.go split into packages with a new symbolize tool that resolves non-symbolized Clang crash stacks, since Epic does not commit the debug symbols, plus a BuildGraph pipeline to acquire, build, test, deploy, codesign, and submit toolchain binaries (31a3b9f0, b3e6cb9a). A MergeClangProfData BuildGraph task now resolves llvm-profdata through the same Windows toolchain search used for compilation, which matters because raw PGO profiles must be merged by a tool matching the linked compiler-rt runtime (a2fa541a). Related for anyone on LTO or PGO with merged-module targets: the LLVM IR object parser unconditionally skipped linkonce_odr globals so they could never become export candidates, breaking dllimported template static data members from Verse interop codegen (77366fe1).
Why this is important
Keying a shared build cache on a version string rather than on the compiler binary is a class of bug that any studio with a shared cache should audit for in its own toolchain wrappers. The version-pin ban is the immediate action item; the build ID is the structural fix.
Who should care
Build engineers, anyone using UBA caching with a custom or updated Clang, and anyone doing PGO on Windows.
Urgency
Act now. Check your toolchain pinning before your next Clang update.
Two authors are making the cook byte-reproducible and materially faster, in parallel
What changed
Twelve commits exist this week for one reason: to stop cooked packages from serializing differently between runs of the same cook. Five of them cite the same tool, Cook IncrementalValidate, which double-saves and diffs the on-disk artifact against a fresh save. The failures are the classic non-determinism set. Raw TMap iteration order leaked into serialized bytes for RigVM function compilation data (704306bd, backed out, resubmitted as f55e2715) and into the Niagara compile hash, where NestedPropertiesAppendCompileHash hashed values in map order while sorting only the key labels and then mis-paired the two (46be1f8d). RigVM asset variants with no valid FRigVMVariant::Guid minted random GUIDs on load, now fixed by passing the package path so GenerateGUID uses FGuid::NewDeterministicGuid (4debd224). MakeUniqueObjectName‘s process-global per-class counter leaked into cooked PCG GPU compute-graph export tables under multiprocess cooking (859468f6). FTextHistory_Base re-keyed text with FGuid::NewGuid() whenever a duplication archive assigned a package localization namespace during cook, randomizing cooked text keys and breaking the localization pipeline’s key match (b9892d57).
The races are more interesting than the ordering bugs. Landscape Nanite meshes created their UBodySetup in the async build’s completion callback rather than at mesh creation, so a package serialized differently depending on whether it was saved before or after that callback ran (e9337bd6). USplineComponent::GetUsedMaterials ignored bGetDebugMaterials and reported editor debug visualization materials unconditionally, so PreSave‘s bHasNoStreamableTextures computation flipped depending on whether those async-loaded debug materials had finished loading, which under unversioned property serialization inserted or removed a one-byte payload and shifted every subsequent byte of the export (5974c3af). Material instance cooking ran unused-parameter cleanup against the parent, which is not guaranteed to be cooked first (a9a6a2d3), and the material translator emitted two byte-different but semantically identical HLSL variants under the same MaterialTranslation cache key (5b1bf314). The heaviest is the ControlRig/RigVM fix (dfee7c15), which documents roughly 800 packages flagged on every validation run, traces it to three cooperating defects including a load-order race over whether a consumer embeds persisted or in-session-recompiled function bytecode, and forces RigVMCompiler to always recompile referenced graph functions during cook at a measured ~3% of cook tick time. Cooker DiffOnly diagnostics now also report BulkDataMap differences in the package header (19d769b3). The campaign is still churning: one RigVM determinism fix was backed out (391a9237).
Running alongside it, a different author spent the week on cook wall clock. FLocalizationCookArtifact::StoreDataInOplog recursively scanned the entire project directory twice for *.locres and *.locmeta at cook shutdown, costing 319 seconds of a 25-minute game cook, now fixed by scanning only registered localization paths (053e3401). UChunkDependencyInfo::BuildChunkDependencyGraph was 64 seconds of a full cook and rebuilt whenever the highest seen chunk id grew, and was rewritten to produce an identical tree (464f299c). Shader DDC keys are now built as UTF-8 end to end with byte-identical output, so existing entries stay valid (f70b8ddb); material shader-map DDC key validation, about 30 seconds of a single-process cook in the DDC-load poll path, moved to a worker task joined in FShaderCompilingManager::FinishAllCompilation (28c6fb99); and ConvertToUTF8 gained an ASCII-run fast path (f77dc4d9). The determinism author contributed one wall-clock fix of their own: the deterministic static mesh lighting GUID moved off the game thread, where recomputing it forced a synchronous wait on the async build and hung the editor during interactive edits (e8c849ac). On the build side, UAT script module rebuilds went from a ~40 second all-modules storm on any shared .cs timestamp change to a content-hash-validated, reference-assembly-gated path, taking a full rebuild from ~17s to ~10s (d22404fe).
Why this is important
Every one of the determinism bugs silently poisons incremental cooking and shared-DDC reuse. A package that serializes differently on each run defeats hash-based artifact caching, so a CI system doing incremental cooks re-uploads and re-stages content that did not change, and content-addressed hit rates degrade in a way that looks like infrastructure flakiness rather than an engine bug. The volume suggests Epic is working a backlog, not chasing one-offs, which is the strongest available signal that byte-reproducible cook output is a UE6 shipping requirement. The wall-clock work is the same story from the other direction, and both authors are measuring in seconds of real cook time rather than microbenchmarks.
Who should care
Anyone running incremental cooks on CI, anyone operating a shared DDC, and studios with rig-heavy or PCG-heavy content.
Urgency
Track, and act now if your incremental cook hit rates have been unexplainably low. dfee7c15 bumps ERigVMCookVersion, so cooked rig packages rebuild once on upgrade.
A TLSF GPU sub-allocator lands, wired into half the renderer, shipped switched off
What changed
Epic added an O(1) two-level segregated fit allocator for GPU memory ranges the CPU cannot address, complete with a defrag planner, a GPU-side compute copy pass, a validation layer, and a test suite. FGpuTlsfAllocator lives in RHI with all block metadata in a CPU-side slot pool, so GPU memory is never touched; allocations are opaque handles that survive pool growth and defrag relocation. BuildDefragPlan does true left-compaction behind a write cursor and is capped at 4 GiB per pool because the copy shader addresses bytes with 32-bit offsets. The companion GpuTlsfCopyPass and GpuTlsfCopy.usf execute batched transfers, running overlapping compaction ops as a single group-cooperative forward copy (8db4a063).
Close to twenty existing FSpanAllocator allocators were then converted: GPUScene instance scene data, instance payload, and lightmap allocators, plus the Nanite material and editor hit-proxy buffers, behind GPUSCENE_USE_TLSF_ALLOCATOR and NANITE_MATERIALS_USE_TLSF_ALLOCATOR (68c85fee); skinning bone and transform buffers with the anim-sequence and anim-bank transform providers (9fe7f2f6); and curve skinning rest/deformed allocators plus the SceneCulling cell-chunk-id allocator (bd67821a). Every conversion is behind a compile define defaulting to 0, and the GPUScene defrag is behind r.GPUScene.Defrag.* CVars defaulting to off. Stats overlay registration was generalized to a getter-based seam so both allocator types report into SpanAllocator.Stats.*.
Why this is important
Nothing changes this week. The defrag is the part to understand now: when enabled, it selects the highest-offset persistent primitives each step and injects force-reallocate updates into the live scene update queue, with SceneCulling and Lumen reconciling through the normal FScene::Update path. Nothing else in the engine slides live GPUScene instances toward lower offsets while the scene is updating. Worth knowing before the flip, not after.
Who should care
Rendering engineers on large open worlds, and anyone who has hit GPUScene fragmentation or span-allocator high-water growth.
Urgency
Track. Infrastructure staged for a later flip, not a change you will see today.
The editor’s AI agent stack is being hardened against its own tool calls
What changed
The work in AIAssistant and ToolsetRegistry this week is almost entirely defensive: undo semantics, input validation, and non-destructive graph edits. FAgentUndoManager gives per-conversation undo for agent tool calls, with a per-agent mode that is explicitly not a stack unwind. Each tool call runs in a capture window with a dedicated transaction buffer swapped over GEditor->Trans, teeing pre-state into a reinstancing-safe record store that rollback replays, because flip-applying engine records can crash on reinstanced objects. It also guards objects the user hand-edited after the agent so rollback does not clobber them (75271521). It was backed out (9162cdf4), re-landed with fixes for divergence between the source stream and Main (3fe14476), and backed out again the same day (0679850b).
Enum properties in ToolsetRegistry had no registered JSON converter, so they fell through to FJsonObjectConverter‘s numeric import path, which writes an out-of-range integer to the enum with no range check. An agent passing {"shareType": 7} could drive a tool’s enum switch into a checkNoEntry() path and take down the editor. FToolsetEnumConverter now validates plain enums (range, fractional, int64 overflow, _MAX sentinel, unknown name) and bitmask enums as bitfields (16acb955). write_graph_dsl moved from full-replace to diff-based editing: it re-decompiles the graph, diffs incoming DSL statement by statement, and leaves unchanged statements’ nodes completely untouched including positions, wires, reroute knots, and node ids. It also adds audit verbs (find_disconnected_nodes, find_duplicate_entry_nodes, find_duplicate_graphs) specifically to clean up graph corruption left by previously failed writes (afef1d65). Around the edges: an access-control model with read, write, and execute modes for toolset tools (88cd8fd5), a cross-conversation tool-response contamination fix (8dae6628), a guard against async tool-call completion during exit-time UObject purge (4808a910), and new toolsets for undo history (f87e5a6f), TextureGraph (c2807012), and GeometryTools (03b0e722).
Why this is important
The engineering problem here is reversibility: making it safe for an agent to touch a live editor session and undo what it did. The audit verbs are the most honest detail in the cluster, because Epic is shipping tools to repair graph corruption its own agent left behind. And the undo manager shipping, being backed out, re-landing, and being backed out again inside four days tells you how invasive swapping the editor’s transaction buffer actually is.
Who should care
Tools engineers, technical directors evaluating in-editor agent workflows, and anyone building on ToolsetRegistry.
Urgency
Track. The undo path is not settled, so do not build against it yet.
The actor is becoming optional
What changed
Three separate teams spent the week removing AActor and UActorComponent assumptions from systems that had them baked in. Mover lost its dependency on an actor component in the state machine (71e7bcbf), had MovementMixer converted from an owned UObject-ish thing into a regular C++ class owned by the state machine explicitly to decouple Mover core simulation from the actor framework (4dd1628b), and gained operational mode options so an external system can drive a Mover object and suppress simulation conflict, with attached movement and locally-driven seamless cinematics as the stated use cases (96da392a). ChaosMover picked this up on the physics side: the backend can now take a TScriptInterface<IPhysicsBodyInstanceOwnerResolver> instead of relying on the MoverComponent’s UpdatedComponent, and UNetworkPhysicsComponent::GetController() now walks the owner chain rather than assuming direct ownership by a Controller or Pawn (3cfc7605).
The editor side moved in lockstep. Editor modes can now allow or disallow non-actor selections, with the actor-only IsSelectionAllowed deprecated and unconverted modes defaulting to permitting non-actors behind Editor.ModesAllowNonActorSelectionByDefault (e3d55a9a). IActorPickerMode and FEdModeActorPicker were extended to use HHitProxy::GetElementHandle() so entities, which use HEntityComponentHitProxy instead of HActor, can be picked in the viewport (f6d89ab1). A crash fix confirms this is live rather than aspirational: “Since entity proxy actors were disabled by default, entity element handles go directly into editor selection sets” (eb511c4f). Supporting signals: EntityFrameworkTests and EntityFrameworkEditorTests moved out of NotForLicensees (1b276cf4), SceneGraph per-player custom primitive data callbacks moved off mesh_component, where they allocated two event UObjects per instance, into an opt-in player_primitive_data_component (c3cc53c1), and Mass core continued decoupling from Engine (99c7cab4, 01626205). Mass also removed code deprecated in 5.6 across 22 files (73ebccd3) and replaced the now-deprecated CSubsystem with UE::Mass::CSystem (c290a520).
Why this is important
The compatibility shim that let entities masquerade as actors in editor selection is off, which is exactly why the picker and mode-selection APIs had to be widened in the same week. Epic is rewriting gameplay simulation code so it can be hosted on something that is not an actor. Anything a studio has written against IsSelectionAllowed, IActorPickerMode, or Mover’s assumption that there is a MoverComponent on an AActor is on a deprecation clock, and the Mass removals are already hard breaks.
Who should care
Gameplay engineers using Mover or Mass, tools engineers with custom editor modes or pickers, and anyone tracking SceneGraph.
Urgency
Track, and act now if you have custom editor modes or Mass code that was riding 5.6-era deprecations.
Mobile scalable local lights, Vulkan modernization, and the first bones of a frame-pacing DAG
What changed
r.Mobile.ScalableLocalLights (default off) splits local point and spot lights into three tiers on the mobile deferred path: near keeps the existing per-light volume draw with full shading models, IES, shadows, and light functions; medium uses one instanced draw per light type with simplified diffuse and specular; far is diffuse-only. To make it work, Metallic and AO were swapped in the GBuffer so DiffuseColor needs a single GBufferC sample (8687b3ca), with a follow-up making medium and far instanced local lights respect primitive lighting channel settings (97e96c48).
Vulkan added VK_IMAGE_LAYOUT_RENDERING_LOCAL_READ support for dynamic rendering (ab197057) and VK_KHR_pipeline_binary plumbing (1eac9c6b), consolidated image acquire onto either a semaphore or a fence and removed VULKAN_USE_IMAGE_ACQUIRE_FENCES with desktop still on fences (b525937f), and picked up a real correctness fix: textures with StoreAction::EMultisampleResolve that are not memoryless are now written, fixing lost contents when multiple passes shared MSAA targets, such as the clear in editor selection outlines (60421447).
On latency, an experimental frame-timing DAG landed compiled out behind UE_TASK_TIMING_ENABLE. It builds a per-frame DAG of thread work where the shortest path is the frame’s critical path, intended to extend into GPU work via platform RHIs and eventually drive game-thread throttling to cut input latency, and it adds FRHIPresentArgsWithStats (8e606bd7). Enhanced Input now applies continuous and forced input at consume time, removing a +1 frame latency (3c273389), with the game thread polling the input thread fresh instead of waiting for the next poll (48dc2716). Directional VSM projections can now write directly into the shadowmask texture when the projection is the first writer, eliminating the intermediate texture and the fullscreen composite, which mostly helps low-end platforms (0abadf08). Also here: instanced static mesh GPU LOD selection touching virtual shadow maps (4d2ffa93), dithered LOD transitions for GPU-instance-LOD instanced meshes in bRenderStatic mode driven through the instance culling pass rather than GetDynamicMeshElements so mesh draw command caching is preserved (ba525ddc), PSO precache skipping post-process and translucency-lighting-volume material precaching on low-core PCD3D_ES3_1 (92dc62b8), net receive processing time limits with optional congestion control (4c47cb82), and a client-side CPU-time circuit breaker for Verse <predicts> with per-execution timeout and loop budgets feeding a leaky-bucket breaker (a11f0644).
Why this is important
The mobile direction is the notable one: reduce per-light cost by distance tier rather than by cutting light counts. The GBuffer channel swap is the catch, and it will bite anyone with custom mobile shading code regardless of whether they turn the feature on. The frame-pacing DAG is not usable yet, but it is the first visible piece of an input-latency story.
Who should care
Mobile and platform engineers, Vulkan backend maintainers, and anyone shipping many-light scenes on mid-tier hardware.
Urgency
Track. Act now only if you maintain custom mobile GBuffer or shading code, in which case check the Metallic and AO swap.
The stabilization wave
What changed
Those defect fixes cluster hard: Control Rig (six), Sequencer (six), PCG (five), plus a set of Mac and Linux crashes, one of which had been hiding behind a lucky uninitialized byte on MSVC.
Control Rig is the heaviest. 64874c17 replaces a StaticLoadObject workaround that deadlocked the cook with version-gated dynamic imports, adds a coalescing re-entrancy guard to RecompileModularRig to fix random heap corruption from nested recompiles tearing down module instances mid-initialization, and bounds-checks override copies against a possibly partially-loaded class layout. It was backed out (44f6ba17) and had not re-landed by week’s end. RigHierarchy topology sharing now uses a dedicated refcount behind a transactionally-safe mutex instead of TSharedPtr::IsUnique(), which is unreliable inside an AutoRTFM transaction and was producing a use-after-free (5e7f6f96).
Two cross-platform fixes are worth reading for the root cause alone. 1cc86453 makes the WITH_EXTENDED_TLS singleton immortal via placement-new into static storage, because its function-local-static destructor ran at process exit and freed the per-thread TLS backing store while the allocator and lingering threads still used it, crashing only the Mac-host iOS and tvOS cooks with SIGILL. ebcefa8c traces a Linux editor crash to a SLATE_ARGUMENT bool that was never initialized, where MSVC happened to give it 0 and clang gave it garbage that produced an out-of-bounds slot index.
Elsewhere in the same vein: out-of-bounds crashes from unvalidated transform selections in GeometryCollection Dataflow nodes (8e38d6d0), a Mass entity leak plus a tripped SetPuppetHandle assert from dormant pooled actors staying world-registered (5866fc3e), a silent poison leak in the new material translator that triggered an assert (a8f53b37), a TSAN data race on FNiagaraTypeDefinition‘s lazy Size and Alignment cache (cc68d4d5), an AutoRTFM race on FContext::Active when reading CurrentThreadID (034c85dd), and a DynamicWind race with the cook step (4b7275ce). MallocStomp2 was made platform-agnostic with two virtual address reservation strategies (a ring buffer under 1 TB of VA space, multi-block up to 32 TB otherwise), a fixed underrun mode where the payload is page-aligned against an unmapped guard rather than a committed page, double-free and free-of-decommitted detection, and console commands to trigger each error class (8ff06b1e).
Four infrastructure fixes are act-now if they touch you. Horde’s once-a-minute Perforce health probe had no deadline, so a server accepting TCP but never responding wedged the whole tick under a cluster-wide Redis lock and failed server selection fleet-wide after 7.5 minutes; probes are now capped at 30s (5f6c7776). Linux bundled OpenSSL symbols are isolated via a private version script, fixing a crash on cloud-licensing systems where GPU drivers load their own SSL libraries (0ffae966). Steamworks arm64 Android binaries are now staged in Installed and Launcher builds, where Steamworks.build.cs previously threw when packaging any Android project with OnlineSubsystemSteam enabled (1ab38619). And UBA runtime files that were missing from staged and downloaded builds were restored (45abb63e).
Worth a line each: the Renderer got a private PCH after ClangBuildAnalyzer measured SceneRendering.h at 150.5s and BasePassRendering.h at 89.1s of frontend parse across a Win64 Development rebuild, because ~40 unity TUs each re-parsed the private headers (a8b65242); Gauntlet gained -build=cotf, running a cook-on-the-fly server as an implicit editor session role from loose workspace binaries with no staged build and no upfront cook, verified end to end on Lyra (ad825fcd); Aftermath GPU crash failure classification and a crash fingerprint were added to the crash context (53c2f87a); a new .rhiresourcesnapshot format captures RHIGetTrackedResourceStats() for loading into tools like the Render Resource Viewer (240db376); Android TargetSDK 36 changes landed in UEDeployAndroid.cs (4bf0b050); and TPointHashGrid3 now falls back to linear scan when the search region is very large or when modulo binning would bin a distant min and max close together, which is a correctness bug and not only a perf one (8300cbaa).
Why this is important
Individually most of these are unremarkable. Collectively they say the branch is in convergence. The two cross-platform fixes are worth reading because the root causes are patterns you will hit in your own code: a function-local static destructor racing process teardown, and an uninitialized member that only misbehaves under a different compiler.
Who should care
Everyone on ue6-main, specifically animation, Sequencer, and PCG users, Mac and Linux editor users, and self-hosted Horde operators.
Urgency
Act now on the infrastructure four if they apply to you. Track the rest, and pull what matters if you are pinning a build.
The 5.x Stabilization Sidebar: ue5-main
217 commits on ue5-main, 4.5x last week’s 48, but the headline number overstates the engine-facing delta. There is no engine version bump: nothing touched Engine/Build/Build.version, and all five commits titled “Version bump” or “Package bump” (1198f4d6, 264f084d, 5b7dd791, cdc7c60e, 1bd78343) modify Engine/Extras/RoboMerge/v3/package.json, Epic’s internal merge bot. This is bug-fix-and-port work, not release prep.
The largest cluster is engine code rather than tooling: 37 commits touching Engine/Plugins/Experimental/UAF across 185 file touches, including UAF Notify V1: Runtime (3412ea58), initial anim node support for montages (ddc68852), and Proxy Table support (d62e90e8). Two further commits carrying the same UAF integration-stream tag are explicit UE6-to-UE5 cherry-picks into unrelated editor gizmo and Control Rig code (ab3c7edd, f66f093a). Close behind is RoboMerge at 33 commits, 32 from one author, which inflates the count without touching anything a shipping studio consumes.
The 5.8 hotfix line is visibly live in the metadata: df54130f is tagged “(5.8.2 hotfix)”, 715a826f notes “Bug introduced in 5.8.1”, and 4049e80b was tested in both FNMain and Release 5.8 editors. Worth a cherry-pick if you ship on 5.8: a Mass actor spawning top crash, where MassActorSpawnerSubsystem now recovers instead of asserting when a spawned or pool-retrieved actor’s UMassAgentComponent is in an unexpected registration state, with root cause deferred (df54130f); a blueprint rename and redirector data-loss bug where moving a blueprint onto its own redirector caused a fatal error (afaf8482); a DLC cook failure under -basedonreleaseversion from resolving filenames for WorldPartition generated packages that do not exist on disk (62fe53b4); an AssetRegistry gatherer deadlock (83307182); a StateTree heap use-after-free from recursive SelectStateInternal_LinkedAsset calls invalidating an array element held by reference (a2685056); and a global shader recompile assert from recompiled shaders being removed from ShaderMap Content but left in the CodeResource (51dff0ae). Also credible: 4a5b9eaa, d6178fe6, a125ae00, 27986b0c, and the 5.8.1 PCG pin validation inversion (715a826f).
The structural finding is the dual landing. 128 of the 213 ue5-main commits we could compare (60%) have a byte-identical first-line commit message on ue6-main, and about 120 of those land on the same calendar day in both branches. Epic is landing these in both branches at once, not backporting them weeks later. Confirmed same-day pairs include the StateTree fix (a2685056 / ue6 dcb1b670) and the DLC cook fix (62fe53b4 / ue6 f1423aaa). Roughly 60% of the 85 ue5-only commits are the UAF and Proxy Table animation work plus six localization-automation commits; the rest is the 5.8 hotfix traffic covered above. SHAs in this section reference ue5-main unless noted.
Worth Tracking
Toolsets and MCP are escaping the editor into cooked runtime builds. Mostly under a single ticket, PerfToolset was converted from an editor-only plugin to a runtime plugin so its stats and GPU profiler tools can be served from cooked builds (524fc101), the ModelContextProtocol adapter for ToolsetRegistry moved into the base runtime module (7453886e) with a follow-up fixing MCP not seeing ToolsetRegistry due to module load order (683c606a), ToolsetRegistry settings were promoted to a per-project setting explicitly so cooked builds can still use block lists (30c2fb5c), plugin manifest generation moved from writing into the source content directory to generating during cook (0c02acca), and the cvar tool moved out of EditorAppToolset into ConsoleToolset, described as available at cooked runtime (66602199). Most agent commits on ue6-main are editor-facing and easy to file as an authoring feature. This cluster is infrastructure for exposing engine toolsets over MCP from a cooked, shipped build, with a per-project allow and block list as the gate. If the direction holds, the questions are operational: what the block list defaults to, whether the MCP surface is compiled out of Shipping, and what the attack surface looks like on a cooked client.
The derived data cache is being pulled off its process-global singleton. CreateCache was corrected to return ICacheEx* (f1032c6c), ICacheStore was updated to use the index across more of its API (d4241031), the hierarchy tests were migrated to work on FCache (d2ca136f) while tests that operate on the global cache were removed outright (9c92aa7e), the hierarchy constructor dropped an unnecessary OutOwner (ed509870), StopStore coverage was extended (db0b487a), and derived data loading can now take an optional cache override (b37a9e77). Almost all of it is marked #rnx, so none of it will show up in release notes, and the messages are one-liners that reveal nothing about intent. But “remove tests that operate on the global cache” plus “take an optional cache override when loading derived data” points somewhere specific: more than one cache instance in a process, or derived-data loads scoped to a particular cache. If you run a shared DDC, a cook-server topology, or a custom ICacheStore, this is the API that is about to move.
What We Ignored
53 commits were pure noise and got no further reading. Beyond that, we set aside the usual branch churn (at least 15 backout and revert commits among the routine traffic alone), whitespace fixes, P4VUtils binary bumps, Commit.gitdeps.xml-only submissions, Test Automation Hub dashboard iteration, Epic-internal CI plumbing, test-harness fixes, and UBT naming-convention refactors. RoboMerge and ModularTestFramework are genuinely low-signal on both branches. A long tail of individually routine null guards and bounds checks was counted into the stabilization wave rather than listed. Two clusters were read and deliberately deferred: the new material translator rollout (a8f53b37, bd9a63cc, 7377912b, b0c39317, aa759628, 6fee7ad4), which is still converging onto legacy behavior and carries no decision this week, and cooked-server dependency trimming that drops the Renderer module for dedicated servers (64414b0a, 4fbda163, b5ce96ce), which is worth knowing about if you are chasing server build size and otherwise not.
Closing
The busiest week since mid-May, and almost none of it is features. Epic spent it repairing the machinery that ships the game rather than the game. Pull the build-cache fixes before you find them the hard way.
Commit SHAs are cited inline, in parentheses, next to each claim. They reference the ue6-main branch of EpicGames/UnrealEngine except in the 5.x sidebar, where they reference ue5-main.
Never miss a ue5-main update.
Get them delivered straight to your inbox.


