Back to Blog
    Last Week in Unreal

    Last Week in Unreal: Seven Ways a Build Cache Can Lie (August 3 to 9, 2026)

    Venkat S26 min read
    Last Week in Unreal: Seven Ways a Build Cache Can Lie (August 3 to 9, 2026)

    Unreal Engine Weekly: Seven Ways a Build Cache Can Lie

    Week of August 3 – 9, 2026 | 784 commits on ue6-main, 122 on ue5-main | Branch: ue6-main

    Overview

    784 commits, 174 worth reading, and the ones that matter most are all the same kind of bug. A cache returned the wrong answer, or no answer, and nothing failed. The shader job cache silently lost keys whenever its hash table grew. DDC record bytes changed based on whether debug dumping was on, which is deliberately not part of the key. EpicClang build identifiers differed across machines because of line endings. The UBA cache server ratcheted its own expiration window from three days down to four seconds and kept serving traffic. None of these throw. All of them cost you compile time, cache hit rate, or a shared cache whose records disagree with each other.

    The rest of the week ran underneath that: two large decompositions kept moving, HDR output changed for real, and a lifetime check went fatal and took a wave of shutdown crashes with it.


    Build is the top subsystem this week at 85 high-interest commits, and for the first time in this run the work is about correctness of the cache rather than the speed of the build. Build has been rising for weeks (50, 76, 71, 98, 87, 61, 85 across the full-volume weeks). The July arc was modernization: C++23 by default, circular dependencies eliminated, EpicClang versioned, the C# BuildGraph VM deleted. This week the same subsystem produced seven independent defects in which a cache, hash, or identifier was not a function of its inputs. Epic is now paying down the correctness debt of the caching layer it spent the summer speeding up. If you run a shared DDC or a distributed cook, this is the week’s actionable content.

    The UBA cache server is being hardened under live production failure for the second time in three weeks. The week of July 20-26 covered a UBA cache server rewrite driven by production pressure. This week is the sequel, and the failure mode escalated from throughput to process death: repeated SIGBUS crashes on a filesystem at 99% (1e557470, 65deda8a), maintenance that never compacted busy buckets (3fa18243), and a container-compatibility rewrite of the memory-sharing transport (682d941e). Two consecutive escalations on the same component means Epic is running this at a scale where the old design does not hold. Anyone self-hosting the same component is running the same design.

    The renderer decomposition continues, but the pattern has shifted from carving out extensions to changing how they update. For six weeks FScene work meant moving data into a new scene extension. This week FLumenSceneExtension replaced explicit Add/Update/Remove calls with dirty-state-driven updates (9f87848a), and FRayTracingSceneExtension replaced a bCachedRaytracingDataDirty bit on FPrimitiveSceneInfo with FDataArrayDirty (164a546c). The interesting one is 8ffb925b, which moved ISceneViewFamilyExtention derived types from Fork_GameThread to Fork_AnyThread and states plainly that it is groundwork for constructing SceneRenderer on the render thread. Six weeks of extension carving have been heading there.

    Backout volume is high and concentrated: 17 explicit backouts plus four first-line reverts. The Mass module decoupling landed, was backed out, landed, was backed out, and relanded (a09993fd, cd2dd396, 8687b20d, a744a595, ba27d4d6). If you track ue6-main closely, this week is a bad week to sync to an arbitrary changelist.


    TL;DR

    • FShaderJobDataMap::FindOrAdd probed for a free slot before calling Reserve, and Reserve rehashes on block growth, so the key landed off its own probe path and became unfindable. A lost key means every later job with that input hash misses deduplication and the job cache and recompiles for the lifetime of the process. Keys are lost at power-of-two growth boundaries, so larger projects lose proportionally more (40513cd5).
    • The UBA cache server crashed repeatedly in production with SIGBUS, because pool files grow sparse and writes allocate blocks at page-fault time with no error path. Extents are now reserved at commit time via fallocate (65deda8a, 1e557470). Separately, bucket maintenance never won its exclusivity race on busy buckets, so compaction never ran and expiration collapsed from three days to four seconds (3fa18243).
    • The filmic HDR remap was decoding against the 100 nit SDR reference while paper white normalized against the BT.2408 203 nit reference. Film white sat low and mid-tone chroma drifted, worst on bright lightly saturated colors. If you have graded HDR against current UE6 output, re-check it (ac66493c).
    • Delegate lifetime reporting is now fatal on non-shipping builds (a63e30fe). Your CI can start failing on code that has been quietly wrong for years, and the same week produced a dense run of shutdown crash fixes with exactly that shape.
    • mimalloc v3.3.2 is now enabled on Windows, Mac, and Linux for the Editor and standalone programs (bd92d641). Two files, and every editor memory measurement from here is against a different allocator.
    • On ue5-main, three commits are visibly shaped to stay hotfix-safe and one names the release outright. A 5.8.2 point release is being assembled (d8a2e8e0, c32fe678, 463e23cf).

    Highlights

    The shader job cache was dropping keys, silently, at every growth boundary

    What changed. FShaderJobDataMap::FindOrAdd probed for a free slot, then called Reserve, then wrote the item at the slot it found earlier. Reserve rehashes when it adds a block, which changes HashTableMask, so the slot no longer lay on the key’s probe path. The key became unfindable, and if that slot was occupied after the rehash, it displaced the key already living there. The probe now happens after Reserve, and the search index is scoped to its loop. The commit also removes FShaderJobDataMap::Find and FShaderJobCache::Find, which had no callers and the same class of bug (40513cd5).

    Why this is important. The commit spells out the cost: a lost key means every later job with that input hash misses both deduplication and the job cache and recompiles, for the process lifetime. Keys are lost on power-of-two block growth, at 512, 1024, 2048, 4096 unique hashes and up. That means the defect scales with your shader count. A small project might never notice. A large project loses a key at every doubling and pays for it until the process exits. Nothing logs, nothing warns, and the symptom is a shader compile step that is slower than it should be for reasons no profile will explain.

    Who should care. Anyone with a large shader permutation count, anyone benchmarking shader compile throughput, anyone who concluded their job cache was underperforming and could not say why. One file.

    Urgency: Act now.

    The UBA cache server spent the week dying in production

    What changed. The largest single cluster of the week is one engineer chasing a repeatedly crashing production cache server, and the fixes are architectural rather than defensive.

    When the file-mapping backing ladder falls through to disk files (memfd blocked by seccomp in Docker, /dev/shm too small), pool files grow sparse and writes through the mappings allocate blocks at page-fault time. On a full filesystem that fault is a SIGBUS with no error path. The production box showed a pool file with 1TB apparent size and 4KB actual on a filesystem at 99%. Extents are now reserved at commit time via fallocate, so running out of disk surfaces as a failed map plus a logged error instead of killing the process mid-memcpy (65deda8a, 1e557470).

    Bucket maintenance was broken in two independent ways. Exclusivity was only attempted after a multi-second delete pass, so zero-clients maintenance always lost the race against reconnecting clients and compaction never ran on busy buckets. Meanwhile the overflow squeeze triggered on raw table size, which is mostly dead offsets that only compaction can remove, so it re-tightened expiration every pass against its own wreckage. Observed effect: an expiration window ratcheting from three days to four seconds while the tables never shrank (3fa18243). Gating that squeeze then exposed a second bug where an overflowing but busy bucket got re-queued by every sweep tick, producing a 250ms no-op maintenance run every second, forever (3451f7f2).

    Transport changed too. Downloading a 1GB cas file sent one RPC per 256KB segment, which is 4,000 messages each dispatched to its own server worker. Batched segment fetching replaces that with a reserved id train, default 8 segments per batch, with a version handshake so old and new cache servers interoperate (5f8e11d0). Worker threads no longer stay pinned for the whole wire drain (8bf4c31d).

    pidfd_getfd is gone entirely, replaced by an abstract Unix domain socket handing out memfd descriptors via SCM_RIGHTS, with a SO_PEERCRED uid check, a creator-pid match, and a /proc/self/fd readlink confirming the descriptor is a memfd. The reason is specific: pidfd_getfd requires PTRACE_MODE_ATTACH, which default Docker and Podman seccomp profiles block. The old path silently degraded to /dev/shm or disk inside a container (682d941e). All POSIX platforms moved to anonymous mapping descriptors and the name-based shared memory machinery is deleted (11837995).

    Why this is important. If you self-host UBA and run it in a container, you may have been running a disk-backed cache while believing it was memory-backed. If you run it near a full filesystem, you have a crash loop with no error message. If your buckets are busy, your expiration window may not be the number you configured. All three fail quietly, which is the theme of the week.

    Who should care. Build engineers and CI infrastructure teams self-hosting UBA cache or storage servers, especially in Docker or Kubernetes.

    Urgency: Act now.

    Cache keys stopped depending on things that are not inputs

    What changed. Five independent commits, all the same shape: something that should be a pure function of its inputs was not.

    FinalShaderSource was serialized into ShaderCompileJob DDC records unconditionally, so enabling debug dumping (which is intentionally excluded from the DDC key) changed the record bytes for a fixed key. It now crosses the worker boundary without entering the cache record, and debug dumping was reworked so cache hits still get debug-compilable source (b0070937). Shader debug dumps also stopped differing between two cooks for reasons unrelated to the shader: defines moved out of inlined source comments into a Defines.csv artifact, and hash markers became empty filename-encoded files (60b432af).

    EpicClang now normalizes text file line endings when calculating the build hash, which the commit says plainly should keep build identifiers stable across machines (638c39ee). Editor bulk data identifiers are salted with the lowercased owning package name, because byte-identical duplicate packages were sharing persisted identifiers and cache keys (5538b8d6). Nanite fixed build non-determinism for clusters carrying voxels or curves but no triangles, with a note that DDC versions were not bumped because the non-deterministic dwords were never read (1644f558).

    And one commit is not a fix at all: an off-by-default console variable that logs the inputs and outcome of the unused-parameter strip gate for each material instance at cook-save time, described as temporary instrumentation to root-cause non-deterministic cooked headers caused by an async-ordering race in cooked resource availability (b4b96e0e). That race is still open.

    Why this is important. None of these produce a wrong build. They produce a cache that disagrees with itself, and that shows up as hit rate rather than as failure. For a studio on a shared DDC, a key that shifts based on whether an engineer had debug dumping on is a cost that gets absorbed as “the cache is just not that good.” The EpicClang and bulk data fixes are the same story at a different layer.

    Who should care. Anyone running a shared DDC, a distributed cook, or content-hash-based caching. Anyone who has ever tried to explain a cache hit rate.

    Urgency: Track, and pull the shader DDC fixes if you run a shared cache.

    Horde got two CI reliability fixes worth reading even if you do not run Horde

    What changed. A race condition in the Horde agent from incorrect concurrent use of a single AesGcm context (2ea64825). The storage HttpClient timeout went from the default 100 seconds to 300, because the 100 second ceiling cut the retry policy short and caused job step failures where a transient network error had already been recovered from (0df89cad). Separately, PerforceMaterializer did not correctly sync streams with @CL-pinned imports after an edge-server swap, leaving files stale or missing (2947e107), and UnrealCloudDDC made compact partition writes lock-free (fdbf163d).

    Why this is important. The timeout one is the useful one to steal. A retry policy nested inside a shorter client timeout means the retries never get to run, and the failure surfaces as a flaky job step rather than as a timeout. That pattern is not specific to Horde, and if you have flaky CI steps that recover on manual retry, it is worth checking whether your own client timeouts are shorter than your retry budget.

    Who should care. CI and build infrastructure teams. Horde users should take all four.

    Urgency: Act now if you run Horde, track otherwise.

    HDR output changed this week, and it is a correctness fix

    What changed. The automatic HDR remap of the filmic tone curve was decoding film image against the 100 nit SDR reference while the scene multiplier normalized paper white against the BT.2408 203 nit reference. Film white sat a stop low and mid-tone chroma drifted, worst on bright lightly saturated colors such as pale skin. The remap is now anchored at HDR reference white, decoding to min(203, peak luminance) nits and inverting an output transform whose peak matches (ac66493c).

    The inner LUT shaper moved from normalized-PQ index encoding to gamma 2.2, with numbers quoted in the commit: worst-case grayscale interpolation error drops from about 9.0 to about 2.5 10-bit PQ codes at a 4000 nit peak, and from about 1.7 and 4.2 to about 0.4 and 1.0 codes at 1000 and 2000 nit peaks (5f6131cb). A real multi-view performance bug also got fixed: combine-LUT settings caches and ACES 2.0 transform table contexts were function statics shared across every view, so multiple viewports or scene captures rebaked both LUTs and regenerated transform tables every frame. Both now live on the scene view state (e2e9b2d3). The recurring luminance anchors are now named constants (0d458ead).

    Why this is important. Few rendering changes move pixels in shipped content. This one does. If you have graded HDR against current UE6 output, your grade was compensating for a known-wrong white point. The multi-view fix is separately worth having for anyone running scene captures or split screen.

    Who should care. Graphics engineers, technical artists, cinematics and virtual production teams shipping HDR.

    Urgency: Act now if you ship or grade HDR.

    A lifetime check went fatal, and the same week paid the bill

    What changed. Delegate lifetime reporting is now fatal on non-shipping builds, explicitly so detection is caught in general testing rather than only in dedicated tests (a63e30fe). Alongside it came an unusually dense run of shutdown, exit, and cook-teardown crash fixes with one repeated shape: something holds a raw non-owning pointer, or reads a UClass or UScriptStruct after PurgeAllUObjectsOnExit has already destroyed it.

    The canonical case is that FModuleManager::UnloadModulesAtShutdown() runs after PurgeAllUObjectsOnExit, so any ShutdownModule body calling StaticClass()->GetFName() reads freed memory, including indirectly through TSubclassOf<T> (16a52159, 234d7bd9, 9a1da123). The dangling-delegate variants follow the same pattern: TraceInsights managers registered with AddRaw into a delegate that outlived them (e4b44368), UEFN never unregistering BrowseToAssetOverrideForClass on editor cleanup (cd365d9b), the Sequencer playback client capability holding a raw pointer never cleared when the client actor was collected (0a003158).

    One is not editor-only. A use-after-free of FNamedShaderLibrary where a GFP unmount plus deferred CloseLibrary could destroy a library while an InstallBundleManager worker was inside a concurrent pak-mount callback, reproduced from a retail callstack and introduced by an earlier lock-contention change (d2866b8d). Also a use-after-free and a leaked FRunnableThread in FAsyncQueryLatestServerChunkInfo in StorageServerClient (e9cc8c74).

    Why this is important. A fatal check upstream means CI that was green can go red on code that has been wrong for years. Correct outcome, bad timing if it lands on you unannounced. Plugins that touch statics or delegates in ShutdownModule are the highest-risk surface.

    Who should care. All teams. Especially anyone maintaining plugins, and anyone running non-shipping builds in CI.

    Urgency: Track, act now if you run non-shipping builds in CI.

    Two decompositions, one backout storm

    What changed. Lumen data and update logic moved out of FScene and FPrimitiveSceneInfo into FLumenSceneExtension, and the significant part is not the move. Explicit LumenAdd/Update/Remove/InvalidateSurfaceCache calls were replaced by a dirty-state-driven updater, with copy-on-write previous values used to skip no-op changes. One behavior delta is worth knowing: world-origin shifts previously did not update the Lumen scene at all, and now do (9f87848a). The same pattern hit cached ray tracing, where the bCachedRaytracingDataDirty bit was replaced with FDataArrayDirty in FRayTracingSceneExtension and lazy per-view update became eager update during renderer scene update (164a546c). A TSAN race where EnableCopyOnWrite() was called from one extension after another had already spawned a read-only task was fixed by restricting it to the registration phase during FScene construction (595ed7ee).

    On the gameplay side, MassEntity’s core types are being decoupled from the Engine module ahead of a move to MassCore, with chunk memory size becoming an ini-settable mass.DefaultChunkMemorySize. It landed, was backed out, landed, was backed out, and relanded, with non-unity include breakage found only by a Linux preflight (a09993fd, cd2dd396, 8687b20d, a744a595, ba27d4d6). The commit message states 80 files moved and roughly 650 in-tree consumers migrated to new include paths in a single changelist.

    Why this is important. Both refactors compile clean and then break forks at link or at runtime. The Mass one in particular is a migration event for anyone with a Mass-based gameplay layer, and the double backout says it was not smooth for Epic either.

    Who should care. Rendering engineers and engine-fork maintainers for the FScene work. Gameplay tech teams on Mass for the module split.

    Urgency: Track. Act now if you maintain a fork with custom scene proxies or a Mass gameplay layer.

    Shipping to devices got easier, if you were blocked

    What changed. Xcode 27 now passes UBT SDK validation: the Apple MaxVersion was raised to 27.9.0, and the Xcode 27 SDK turning SwiftUI’s @State into a macro is worked around by passing Swift macro plugin search paths explicitly, with platform plugins loaded in-process because swift-plugin-server returns a malformed response under Epic’s build executor. Verified against Xcode 26.1.1, 26.3, 26.6, and 27.0 (5a5295cc).

    Two Android packaging bugs that only reproduce in Installed or Launcher builds: Steamworks_APL.xml was never staged so Steam-on-Android packaging failed outright, and the APL’s $S(NDKRoot) and $S(HostPlatform) were never registered so libc++_shared.so was silently skipped (d1c9d5ea). A Main OBB in the assets folder was not added into the APK on basic deploy (fc93d654). AndroidFileClient small-file writes double-optimized the path, so a first-call base-directory change was lost and files could be misrouted while the push still reported success (689e1c6d).

    On mobile GPU: a Mali-G68 MP5 crash on the Galaxy A54 fixed by adding --simplify-instructions and --eliminate-dead-code-aggressive to the Android SPIR-V driver-patch scalar-replacement pass (624093bc). An occlusion optimization quoting -0.4ms render thread, -0.3ms game thread, and -0.1 RHI landed, with PrimitiveOcclusionHistorySet::Find measured at 43% of sampled cycles on a gen8 mobile console (ba66ef5f). A commit carrying the identical description was backed out the same day (4ce7efd0), so treat that optimization as in flux.

    Why this is important. The Xcode 27 and Android Installed-build fixes are pure unblocks. The AndroidFileClient one is the interesting bug: it reported success while misrouting files, the same silent wrong answer the caching cluster kept producing.

    Who should care. Platform engineers, mobile teams, release engineering.

    Urgency: Act now for iOS and Android teams.

    The editor’s agent layer got a permission model and some API honesty

    What changed. The editor-side agent surface (a ModelContextProtocol plugin with an FModelContextProtocolServer, a Toolsets plugin, an EditorToolset, a ToolsetRegistry that generates tool schemas) got the unglamorous work you only do on something you intend to ship.

    Session teardown in the MCP server removed sessions without calling Tool->CancelAsync, leaking tool-held external state on any session DELETE or server shutdown and leaving the parked HTTP request to time out with no response (a11ec7ae). Two schema-honesty fixes: ToolsetRegistry now rejects optional tool params carrying a non-None default, because the generated schema published null while the implementation substituted a value, and find_assets declared max_results as int | None = 100, telling callers the search was uncapped (a6246539, 27c219bb). The 80KB read_file limit is gone, and find_assets dropped a redundant in-memory asset registry query that walked the whole UObject heap duplicating the on-disk enumeration (691183cd, d9fec4bc).

    The permission work continued from last week: an IToolsetPermissionProvider with GetToolsToHide (1f6f3012), a project-default tool-access permission mode on the agent environment descriptor (1604c0da), and access-level tagging on the skill management tools (11d652a6). A read-only navmesh query toolset landed that registers in cooked dev and test builds, not just the editor (19f14abe).

    Why this is important. Two schema fixes in one week, both about the published contract lying to the caller, plus request cancellation on teardown. Epic is maintaining this like a shipping API. The permission work is the second consecutive week of access control rather than capability, and a toolset registering in cooked dev builds moves the agent layer out of the editor.

    Who should care. Tools engineers, pipeline TDs, anyone evaluating LLM-driven editor automation.

    Urgency: Track.

    mimalloc is now the editor allocator on all three desktop platforms

    What changed. mimalloc v3.3.2 enabled on Windows, Mac, and Linux for the Editor and standalone programs. Two files, Core.Build.cs and MallocMimalloc.cpp. Test notes list multi-process cooks, editor perf tests, and comprehensive editor tests (bd92d641).

    Why this is important. Two files, and it invalidates your baselines. Every editor memory measurement, fragmentation report, and out-of-memory repro from this changelist forward is against a different allocator. If you are carrying an allocator-sensitive bug or a memory budget you defend in CI, this is the commit that moves the numbers.

    Who should care. Anyone tracking editor memory, anyone with allocator-sensitive tooling, anyone about to file a memory regression against ue6-main.

    Urgency: Track.


    Worth Tracking

    “Ness”, a serialization runtime nobody is talking about. A module tree at Engine/Source/Runtime/Ness/NessUObject/ is accumulating steady work from three engineers: property binding factories, a linker with FLinkerPropertyBatch, a roundtrip test, a diff implementation, and a -strictsave argument that skips saving and loading UObject payloads with incomplete Ness support while emitting a report of structs that are not yet compatible plus a skipped-object count. Test tags on one commit read “lyra: nesspacks, ness roundtrip.” The expansion of the name appears nowhere in the commit data, so treat it as a literal token. A -strictsave mode, a compatibility report, and validation against Lyra add up to a serialization replacement mid-migration. Helper libraries do not need any of that. If Ness is what it looks like, it eventually touches every cooked package and every DDC key in the engine. Nothing to act on. The struct-compatibility report is the thing to watch week over week (ffb3a7a5, bc0481c8, 71f405ca, f22d0ce7, 02efb0f5, 3e0e8c6b, 04b50198, 06609cc5).

    ModularTestFramework is a second automation stack, and it now has a UI. Roughly two dozen commits this week in Engine/Plugins/ModularTestFramework/ and Engine/Plugins/Editor/ModularTestRunnerEditor/, almost all rated low or medium individually. Together they are a full test runner: dockable per-run tabs that freeze into snapshots on completion, a run-config tab that displays backend consequences, a multi-process backend with a warm pool that pre-spawns client slots from the resolved test set’s declared count rather than a hardcoded command-line knob, per-run overrides for timeout and environment recycling, and a resolver API that mounts and unmounts test companion plugins through per-resolver dispatch. One commit is explicit that it skips “the foreign FAutomation test walk” when dispatching editor runs, so this is running beside the legacy automation framework, not replacing it yet. The multi-process backend with declared client counts is aimed squarely at flaky multiplayer tests. Worth watching for whether it becomes the recommended path (86d33ee8, f486ed2d, cf9d90bf, 2f9ded31, caf1ed1f, bc0edb00, de268c78, f6526778, 72f13bcd, 219b85a8, 4cf52b4a, dc53b6ed, b9c3611e, e8291790, 6689ab01, e168a4f9).

    SceneGraph is starting to work without proxy actors. The SceneGraph and TEDS commits stopped being infrastructure this week and started being behavior. Duplicate, paste, and alt+drag now work in edit-in-place without proxy actors, and FParticleSystemAssetComponentEditorUtilities dropped its proxy-actor reliance. Proxy actors are the compatibility shim that lets the existing editor manipulate SceneGraph entities. Every commit that removes a dependency on them moves the editor closer to operating on entities natively. That transition eventually changes what a level means for tooling, source control diffing, and anything that walks actors (3a349b8b, ab2e0e3e, 10254923, 2e1bc001, 24bb792b, cfb73504, 73fc199f, 9346b10f, f41b3299).

    One merge hazard to plan around. A sweeping UE_LOG to UE_LOGF migration landed, described in its own message as only TEXT() removal, %s to %ls, and %hs to %s (11593724). Mechanical does not mean cheap: for any studio carrying engine modifications, a tree-wide macro and format-specifier migration is a merge-conflict event. Schedule it rather than discover it.


    For teams shipping on 5.8: the ue5-main sidebar

    122 commits, 118 after noise filtering, against 784 on ue6-main. 54 of those 118 carry #Synced-CL integration markers, so this branch is still taking integrations rather than sitting frozen.

    A 5.8.2 hotfix is being assembled. Three commits are visibly shaped by hotfix constraints, which is a stronger signal than any of the fixes individually. One names the release outright: “Note that fix is made to be HotFix compatible for 5.8.2 and a more robust fix may be required for future releases,” and carries a #p4v-cherrypick marker (d8a2e8e0). Another states the constraint plainly while removing an ensure when hashing User Defined Structs with unhashable members: “To avoid changing the hash in a hotfix, the real fix will be pushed in Main” (c32fe678). A third is simply titled “Hotfix for modular vehicle crash” (463e23cf). Two engineers independently narrowing their fixes to stay hotfix-safe points the same way.

    Bugs that only exist in cooked builds. The highest-value cluster on this branch, because none of these reproduce in PIE. Anim Mixer suppressed Force Root Lock through FAnimExtractContext::bIgnoreRootLock, which is WITH_EDITOR only, so cooked builds applied the lock during decompression and published the locked constant as root motion. Sections using an anim sequence with both Force Root Lock and Enable Root Motion produced no root motion at all in cooked builds (f3ba9867). A Mover fix is explicitly labeled a regression from 5.7: the stale spring-state reset keys off a blackboard flag only a locally ticking simulation sets, so interpolated sim proxies discarded replicated spring state and restarted every trajectory prediction from zero acceleration, visibly lagging motion matching (4712c3cc). Function references stamped with a stale variant Guid failed to resolve in cooked builds while the editor’s event-name fallback hid it (11a5e03d).

    Toolchain and packaging. Xcode 27 support landed here too (92b559b5), plus Android TargetSDK 36 (20741414), the Main OBB packaging fix (3846da3f), and Steam-on-Android for Installed and Launcher builds (1a077aa5). Two UnrealBuildTool manifest bugs shipped: generated UHT outputs missing from installed builds (f246278c) and UHT input cache files wrongly counted as build products (be3a7b30).

    Nanite determinism, on this branch too. The same fix class as ue6-main: assertion fixes for assemblies with Keep Triangle Percent below 100 or Trim Relative Error above 0, plus a build non-determinism fix for voxel and curve clusters (5257e679). Pull it if you cache cooked Nanite output.

    Also worth pulling regardless of hotfix timing: a quick fix removing SupportsNoOverwriteBuffers() on bone and cloth buffers, which addresses motion blur corruption seen in the wild (b3108674), and a DFAO change that marks sky pixels with negative SceneDepth and rejects them during upsampling, replacing behavior the commit describes as causing a lot of flickering in dark regions (50bf95d2).

    Nothing about this branch reads frozen. It keeps taking integrations while a point release gets staged underneath it. New work is landing too: a large editor docking refactor giving every TabManager-managed window a status bar and panel drawer (7cc08fff), a Dynamic Wind tooling series, and a seven-commit Control Rig constraints pass.


    What We Ignored

    563 of the 737 analyzable commits, plus the 47 filtered as noise before analysis. Mostly localization automation, README additions, tooltip and comment polish, static analysis warning cleanup, test asset repairs, and the ordinary weekly tail of single-file crash fixes in narrow plugins. Also 17 explicit backouts and four first-line reverts, counted in the trends section rather than reported individually. None of it changes runtime behavior, build reliability, or upgrade risk for production teams.

    Two from that tail are worth naming anyway because they affect shipped content: a Nanite assembly build crash with Keep Triangle Percent below 100 (39c00366), and a DFAO flicker fix in dark regions (c1cc531e).


    Closing

    The crashes were the least interesting thing that happened this week. Seven separate places returned a confident answer that was not a function of its inputs, and nothing anywhere logged a complaint. That class of defect never shows up in a crash report or a failing test. It shows up as a build that is slower than it should be, a cache hit rate nobody can explain, and a grade that was compensating for a wrong white point.

    Commit SHAs are cited inline, in parentheses, next to each claim. They reference the ue6-main branch of EpicGames/UnrealEngine unless the claim appears in the 5.x sidebar, which references ue5-main. Many of the highest-interest entries resolve to Commit.gitdeps.xml binary-manifest blobs: the commit message is real, but the code lives in Epic’s internal binaries, which we cannot see.

    Share this article