[{"id":"A14","fact":"**`new_level` is DEFERRED to the next editor tick and does NOT block, so a `save_level_as` issued immediately after it saves the world being REPLACED, not the new one.** `H_new_level` schedules `CreateNewMapForEditing` through `MifDeferToNextTick` because tearing the UWorld down inside `FTickTaskManager`'s level-list iteration trips `Assertion failed: !LevelList.Contains(TickTaskLevel)` and takes the editor with it. The handler's own response note says so. Poll `list_sublevels{world:\"editor\"}` for `isPartitioned` / `persistent.packageName` rather than sleeping. MEASURED 2026-09-27 (A137): on 5.8.2 and 5.3.2 a call sent right after the answer landed in the NEW map 9 of 9 times - a request runs after the world tick and the switch fires in the next - so the save_level_as case above is the design's worst case, not something seen. Since that day new_level answers with an opId whose list_sublevels ops[] entry says when the switch is done (result.map), and refuses while a map is dirty (A137); what happened to the replaced map's unsaved edits before that is A136.","source":"`Source/MifBridge/Private/MifBridgeWorld.cpp:286` (`H_new_level`; `RejectUnknownParams` accepts `partitioned` and, since 2026-09-27, `discardUnsaved`), the deferral at `:319`, the response note at `:349-352`. Live on the 5.3 probe, port 8843, 2026-09-09: the two-line `new_level` + `save_level_as` sequence produced a saved map, and the same sequence with `partitioned:true` needed a poll before `list_sublevels` reported `isPartitioned:true`. The bug it hid was invisible while both worlds were empty and unpartitioned - the save was byte-identical either way.","engine":"","ue":[],"bl":[],"section":"MifBridge API contracts"},{"id":"A7","fact":"An endpoint with no harvested table row can gain one **at runtime, mid-session**: `paramsSource` moves `none` -> `runtime` the first time its guard actually executes, so the count of unknown-parameter endpoints is 13 in a cold editor and 12 in a warm one","source":"live, 5.7 probe port 8847: `recipe_override_and_call_parent` answered `params_not_declared`/`none`, then `params_observed`/`runtime` with 20 keys after one deliberately-refused call. It delegates to `add_override_event`, and the harvester does not follow H_ to H_ calls on purpose","engine":"","ue":[],"bl":[],"section":"MifBridge API contracts"},{"id":"A26","fact":"**`check_overlaps` excludes every actor whose LABEL contains \"ground\", case-INsensitively, by DEFAULT - and as of 2026-09-10 it reports exactly which ones.** `ignoreGround` defaults true and the test is `A->GetActorLabel().Contains(TEXT(\"Ground\"))` (`MifBridgeSpatial.cpp:234`); `FString::Contains` defaults to `ESearchCase::IgnoreCase`, which is why a lowercase label matches. **RE-VERIFIED 2026-09-20 against a live 5.8.2 editor, and two parts of the original row are now stale - both because this repo fixed them, not because an engine changed.** (1) The exclusion is no longer silent: the response always carries `excludedAsGroundCount` and `excludedAsGround` (capped at 50 labels, count exact), so a caller can tell \"nothing matched\" from \"an older build that did not say\". Live call returned `excludedAsGroundCount: 2`, `excludedAsGround: [\"BO_Ground\", \"BO_J_SlopeGround\"]` alongside `actorsTested: 147`, `count: 655`, plus `returned`, `truncated` and `truncatedNote` - so the original row's \"carries actorsTested, count, pairs only\" no longer holds. (2) An actor named in `actorPath` is NO LONGER excluded: the guard is `if (!bNamed && bIgnoreGround && ...)`, so the exclusion applies to whole-scene audits only. Both landed in commit `941e39a7` on 2026-09-10 - the same day this row was measured - and the row was never updated. The behaviour is NOT version-guarded: there is no `#if` anywhere in the handler, so it is identical on 5.3 through 5.8. **[Reviewed 2026-09-21 against the code - STALE in part: only the named actor is exempt; every other 'ground' actor is dropped before the pair loop (MifBridgeSpatial.cpp:227-238, 243-266)]**","source":"5.3","engine":"","ue":[],"bl":[],"section":"MifBridge API contracts","value":"ORIGINAL (still true of that build): engine `Runtime/Core/Public/Containers/UnrealString.h:1180` in 5.3.2 source, the 5.3.2 fork (`SearchCase = ESearchCase::IgnoreCase`); live, 5.3 probe port 8843, 2026-09-10: two `/Engine/BasicShapes/Cube` StaticMeshActors at x=5000 and x=5030 labelled `Background_MifOverlapProbe` / `MifOverlapPartner` (70uu overlap). `check_overlaps{actorPath:Background...}` -> actorsTested 3, count 0, ok:true; `+ignoreGround:false` -> actorsTested 4, count 1, depth 70; `{actorPath:MifOverlapPartner}` -> count 0. Both actors deleted, nothing saved. RE-CHECK: live 5.8.2 editor port 8860, `check_overlaps{limit:5}`, read-only, nothing written; source read at `MifBridgeSpatial.cpp:223-238` and `:292-301`.","status":"superseded"},{"id":"M055f2d","fact":"Two bridge write limits that terminate or corrupt work if ignored: any property value over 1023 characters asserts and kills the editor, and BodySetup.AggGeom.ConvexElems is CPF_EditFixedSize so the property system cannot grow it.","source":"docs/29_VILLA_WINDOW_UPGRADES.md:199-208. Measured live while restoring villa wall collision — the FBX round trip replaced vanilla's 29/67/101 hulls with 1, and all 197 were written back and read element-by-element as identical to vanilla (29/29, 67/67, 101/101), CollisionTraceFlag CTF_UseDefault copied too.","engine":"","ue":[],"bl":[],"section":"MifBridge","value":"`BodySetup.AggGeom is 37 KB whole and cannot be written; individual hulls are 524–614 chars. Grow the array with throwaway add_simplified_collision shape=26dop calls first (K-DOP generation appends a convex element), then overwrite each element.`"},{"id":"M37757d","fact":"set_property refuses any value over 1023 characters because exceeding that limit asserts and terminates the editor; BodySetup.AggGeom is ~37 KB so hulls must be written one element at a time (individual hulls measured 524-614 chars).","source":"Eddie_v2/docs/29_VILLA_WINDOW_UPGRADES.md:199-201","engine":"","ue":[],"bl":[],"section":"MifBridge API"},{"id":"M7cc03d","fact":"MifBridge's RunEndpoint wraps every transacted handler in ONE blanket FScopedTransaction and, on failure, calls Transaction.Cancel() — which only discards the undo entry, so a partial mutation stays committed alongside ok:false. Atomicity is therefore a property of the order each handler is written in (validate before first mutation), nothing more.","source":"MifBridge Source/MifBridge/Private/MifBridgeCommon.cpp:1582-1618 (RunEndpoint opens at :1464). CORRECTION to the candidate: its cited :597-599 is a MIF_BIND block, its \"commits unconditionally / no rollback exists\" is FALSE against current source (Cancel() IS called at :1615), and its \"Transaction.Cancel() absent from","engine":"","ue":[],"bl":[],"section":"MifBridge architecture","value":"`FScopedTransaction Transaction(FText::Format(LOCTEXT(\"BridgeEditFmt\", \"Mif Bridge: {0}\"), FText::FromString(Endpoint))); if (Fn) { (*Fn)(In, Out); } else { Ext->Handler(In, Out); } … if (!IsOk(Out)) { Transaction.Cancel(); }`"},{"id":"M5a5b24","fact":"MifBridge handlers run ON the game thread synchronously and inline at the post-world-tick FTSTicker point — not \"mid-frame\" and not via AsyncTask. The plugin explicitly rejects AsyncTask(ENamedThreads::GameThread) because named-thread work is serviced from inside FTickTaskSequencer::ReleaseTickGroup, which reinstances actors mid-tick-group and hard-crashes on a pure-virtual FTickFunctionTask.","source":"MifBridge Source/MifBridge/Private/MifBridgeServer.cpp:427-443 (the AsyncTask rejection and the post-world-tick reasoning) and :460-465 (synchronous inline run). CORRECTION: the candidate's cited :229-265 is the 404-suggestion preprocessor, not this code","engine":"","ue":[],"bl":[],"section":"MifBridge architecture","value":"`if (IsInGameThread()) { … MifBridge::RunEndpoint(Endpoint, InRef, Out); … return true; }`"},{"id":"M10a037","fact":"FHttpServerModule derives from FTSTickerObjectBase and is pumped by the game-thread ticker, so while any synchronous handler runs no HTTP request is read off the socket — mid-operation progress polling is physically impossible, and every long operation must be a request+poll pair sliced at safe boundaries.","source":"Engine/Source/Runtime/Online/HTTPServer/Public/HttpServerModule.h:25 (FTSTickerObjectBase base) and :60 (Tick override). The \"no request is read while a handler runs\" consequence follows from the single-pump model and is corroborated in MifBridge's own comment at Source/MifBridge/Private/MifBridgeServer.cpp:44","engine":"5.3.2","ue":["5.3"],"bl":[],"section":"MifBridge architecture / HTTP","value":"`class FHttpServerModule : public IModuleInterface ,public FTSTickerObjectBase … bool Tick(float DeltaTime) override;`"},{"id":"Mca02b0","fact":"The crash journal writes a 'start' record and flushes it to disk BEFORE the handler runs and an 'end' after, so the diagnostic is an absence: a start with no end means that endpoint was running when the process died, and a session with no shutdown means the editor died rather than closing.","source":"MifBridgeJournal.cpp - path :98, CreateFileWriter with FILEWRITE_Append :139, Flush() per record :125, PID via FPlatformProcess::GetCurrentProcessId() :153 and :281; call sites MifBridgeServer.cpp:423 (JournalCallStart, before dispatch) and :451 (JournalCallEnd). Engine APIs verified on 5.3: IFileManager::CreateFileWri","engine":"","ue":[],"bl":[],"section":"MifBridge crash journal","value":"`one FArchive held open, Flush() per record (an unconditional FlushFileBuffers on Windows); output <ProjectSaved>/MifBridge/journal.jsonl, one JSON record per line, append-only, session records carry the PID`"},{"id":"M206a40","fact":"The journal cannot ride on UE_LOG because FOutputDeviceFile hands lines to a background FAsyncWriter ring buffer and does not flush per line without -FORCELOGFLUSH, losing exactly the tail a crash investigation needs. mif.BridgeJournal defaults to ON, unlike the older mif.BridgeDebug which defaults to false.","source":"Engine 5.3.2 source  Runtime/Core/Private/Misc/OutputDeviceFile.cpp:565 (the -FORCELOGFLUSH parse) with FAsyncWriter defined in the same file from :40; MifBridgeJournal.cpp:53-59 declares CVarMifBridgeJournal with default `true` and the comment 'On by default - a journal you must enable before the crash is off when it matte","engine":"5.3.2","ue":["5.3"],"bl":[],"section":"MifBridge crash journal","value":"`GForceLogFlush = FParse::Param( FCommandLine::Get(), TEXT(\"FORCELOGFLUSH\") );`"},{"id":"M09beea","fact":"After a crash the next editor launch opens a package-restore MODAL, which blocks the game thread that FHttpServerModule ticks on, so the editor is up, the port is bound, and nothing answers. MIF_BRIDGE_AUTO_DECLINE_RECOVERY=1 makes MifBridge call DisableRestorePromptAndDeclinePackageRecovery() at StartupModule, which runs before the offer because EditorInit calls EngineLoop.Init() (loading PostEngineInit plugins) before FUnrealEdMisc::OnInit().","source":"MifBridge.cpp:145 (env read) and :151 (GUnrealEd->GetPackageAutoSaver().DisableRestorePromptAndDeclinePackageRecovery()); engine gate verified verbatim on all six installs - PackageAutoSaver.cpp:436 on 5.3, :439 on 5.4/5.5, :670 on 5.6/5.7, :683 on 5.8. LINE CORRECTED: docs/15's ':670' is the 5.6/5.7 number, not 5.3's.","engine":"","ue":[],"bl":[],"section":"MifBridge recovery modal","value":"`if (HasPackagesToRestore() && !bAutoDeclineRecovery && !FApp::IsUnattended())`"},{"id":"M8c4909","fact":"Measured 2026-09-09: with genuine auto-saved packages present a probe editor took 234 seconds to answer the bridge, and only after a human clicked the dialog; after MIF_BRIDGE_AUTO_DECLINE_RECOVERY the same situation answered in 12 seconds with no dialog.","source":"docs/15_SAFETY_GATE_AND_JOURNAL.md §1b - the 234 s figure was measured on the UE 5.5 probe, the 12 s on a force-killed and relaunched 5.7 probe. CORRECTED: the before and after are on DIFFERENT probe editors; the doc's 'the same situation' is its own framing, not one controlled A/B.","engine":"","ue":[],"bl":[],"section":"MifBridge recovery modal","value":"`234 s -> 12 s`"},{"id":"Mf49f35","fact":"Restart the editor before a sweep: a long-lived editor fails suites a fresh one passes - one run lost test_material_undo to a transaction buffer that had reached its cap at 1941 entries, and test_levelsnapshots to accumulated level mutation; both passed clean after a restart, and a second independent confirmation lost test_levelsnapshots T1103 after ~25 minutes of mutation on the same editor.","source":"docs/14_RELEASE_AND_SYNC.md § The engine compatibility matrix, the 5.3.2 row (2026-08-30 and 2026-08-31 runs). The doc states the rule 'is not folklore' precisely because the second confirmation was a --once sweep on the same editor a day later, and restarting then gave that suite 20 PASS 0 FAIL including the exact ass","engine":"","ue":[],"bl":[],"section":"MifBridge regression sweeps","value":"`transaction buffer cap reached at 1941 entries`"},{"id":"Mf0fb30","fact":"tools/syntax_check.py parses changed .cpp against BOTH engine trees with cl /Zs, which writes nothing - no object, no PCH, no DLL touch - so it runs beside a live editor in seconds and needs no closed editor. It is explicitly NOT a build gate: a parse is not a link and says nothing about unresolved externals. Run --plant to prove each engine's checker still sees an error it must see, because counting errors is not enough when a stale PCH fails every file identically.","source":"docs/14_RELEASE_AND_SYNC.md § Before you reach for either engine gate; it found the register_landscape_layer break that sat uncompilable on 5.7 for two days behind the closed-editor requirement","engine":"","ue":[],"bl":[],"section":"MifBridge release gates","value":"`cl /Zs`"},{"id":"M5659da","fact":"CORRECTED - the docs are stale here. The write mode is read ONCE from MIF_BRIDGE_WRITE_MODE at process start (modes: scratch default, read, full) and there is deliberately no set_write_mode endpoint and no cvar, so nothing that takes a NAME can reach it. But it is NOT immutable thereafter: SetWriteModeFromPanel can change it from the in-editor panel, and the cache comment says 'Read once, then owned by the panel'. Raising is refused while any bridge call is on the stack (FMifBridgeCallScope depth counter); lowering","source":"MifBridgeSafety.cpp - GModeCache 'read once, then owned by the panel' :237, ReadModeFromEnvironment :190, GetWriteMode :243, SetWriteModeFromPanel :276-320, WriteModeName :322. Gate call in RunEndpoint at MifBridgeCommon.cpp:1501 (NOT :1233). docs/15 §1 'immutable thereafter' and MifBridgeSafety.cpp's own header commen","engine":"","ue":[],"bl":[],"section":"MifBridge safety gate","value":"`MIF_BRIDGE_WRITE_MODE=full ; persist with `setx MIF_BRIDGE_WRITE_MODE full``"},{"id":"Mbb7426","fact":"A handler is invoked on exactly four lines in two dispatcher functions, and BOTH dispatchers gate before both their built-in and their external (kr_*) lookups - batch does not recurse through RunEndpoint, so a gate only in RunEndpoint was a complete bypass ({\"op\":\"save_package\"} inside a batch ran while save_package alone was refused).","source":"Re-ran the audit greps docs/15 § 'The audit' prescribes over Source/MifBridge/Private/*.cpp - every invoke line is downstream of a RefuseIfGated in the same function. LINE NUMBERS CORRECTED throughout (docs/15's table says :1293/:1305/:1233/:2521/:2534/:2492). NOTE: docs/15 § 'What is NOT covered' still claims 'batch b","engine":"","ue":[],"bl":[],"section":"MifBridge safety gate","value":"`invoked: MifBridgeCommon.cpp:1571 and :1583 (each line carries both the built-in and external branch) in RunEndpoint (declared :1464), gated at :1501; MifBridgeNodes.cpp:2792 (built-in) and :2805 (external) in H_batch (declared :2620), gated at :2763`"},{"id":"M0a2828","fact":"In a gated mode export_asset refuses only an explicitly named path OUTSIDE the project directory; the guard must be applied to the RESOLVED path, not the raw request, because ConvertRelativePathToFull resolves a relative path against the editor's CWD (its binaries directory), which is outside the project - so a bare \"tile.fbx\" would have been refused for being outside the project it was about to be written into.","source":"MifBridgeExport.cpp - MifExportRootDir() :172-175, relative resolve :571, ConvertRelativePathToFull :578, RefuseFileOutsideProject on the RESOLVED path :594 with the postmortem comment at :585-593; MifBridgeSafety.cpp RefuseFileOutsideProject :354. docs/15 §4. Covered by test T637.","engine":"","ue":[],"bl":[],"section":"MifBridge safety gate","value":"`file:\"C:/Temp/evil.fbx\" -> refused, refusedRule \"file-outside-project\"; file:\"tile.fbx\" -> allowed; no file -> <ProjectSaved>/MifBridge/Export/<Name>.fbx`"},{"id":"Mb1e8d0","fact":"MifBridge handlers run synchronously and inline on the game thread inside FHttpServerModule's own ticker, post-world-tick and outside every tick group - no thread hop, nothing marshalled. FHttpServerModule derives from FTSTickerObjectBase, so the request callback arrives from FTSTicker::GetCoreTicker().Tick(), which FEngineLoop::Tick() runs after GEngine->Tick() has completed the whole world tick.","source":"MifBridge Source/MifBridge/Private/MifBridgeServer.cpp:425-466 (comment block :425-444, inline run :460-466). CITATION CORRECTED: docs/00_ARCHITECTURE.md and docs/05_DESIGN_SPEC.md both cite ':229-265', which in the current tree is the 404-did-you-mean preprocessor, not this code.","engine":"","ue":[],"bl":[],"section":"MifBridge threading","value":"`if (IsInGameThread()) { RunEndpoint(...); RunAndReply(...); return true; }`"},{"id":"M7a7b13","fact":"Transaction.Cancel() on a failed handler discards the undo entry but does NOT revert the handler's writes - UTransBuffer::Cancel never calls FTransaction::Apply(), whose only two callers are Undo and Redo.","source":"Engine 5.3.2 source  Editor/UnrealEd/Private/EditorTransaction.cpp:1387 (Cancel), :1624 (Undo->Transaction.Apply()), :1688 (Redo->Transaction.Apply()); virtual doc at Editor/Transactor.h:514-519. All four verified verbatim. Bridge side: MifBridgeCommon.cpp:1584-1616.","engine":"5.3.2","ue":["5.3"],"bl":[],"section":"MifBridge transactions","value":"`UTransBuffer::Cancel -> broadcast TransactionCanceled, GUndo->EndOperation(), null GUndo, pop UndoBuffer; no Apply()`"},{"id":"M207130","fact":"Modify() records nothing on objects that are not RF_Transactional (e.g. UInheritableComponentHandler and its override templates), because SaveToTransactionBuffer requires the flag.","source":"Engine 5.3.2 source  Runtime/CoreUObject/Private/UObject/UObjectGlobals.cpp:3131-3134 - verified verbatim, line 3131 is exactly the bIsTransactional line; docs/00_ARCHITECTURE.md:326-328","engine":"5.3.2","ue":["5.3"],"bl":[],"section":"MifBridge transactions","value":"`const bool bIsTransactional = Object->HasAnyFlags(RF_Transactional); ... if (GUndo && bIsTransactional && bIsNotScriptPackage)`"},{"id":"M5829ef","fact":"new_level and load_level discard unsaved edits in the current map with no undo and no confirm gate; new_level forces bPromptUserToSave=false deliberately, because a save prompt would be a modal that freezes the editor and the bridge with it and an unattended agent could never dismiss it. Call list_dirty_packages first.","source":"`GEditor->CreateNewMapForEditing(/*bPromptUserToSave*/ false, bPartitioned);`","engine":"MifBridgeWorld.cpp - the reasoning comment at :120-121 and the call at :143 (verified verbatim). LINE CORRECTED: docs/02_GOTCHAS.md §10 cites ':135, reasoning at :120'. The same handler additionally DEFERS to the next tick, because tearing down the UWorld while FTickTaskManager is iterating trips `Assertion failed: !Le |","ue":[],"bl":[],"section":"MifBridge world"},{"id":"M259474","fact":"GAverageFPS and GAverageMS are defined with ENGINE_API but the engine's own consumers re-declare them with a local extern rather than including a public header, so a plugin must copy that extern pattern (it links, but it is fork-fragile).","source":"Engine/Source/Runtime/Engine/Private/UnrealEngine.cpp:634-635 (definitions, both ENGINE_API); local re-extern at Runtime/Engine/Private/Analytics/EngineAnalyticsSessionSummary.cpp:23 and, inside the same file that defines them, UnrealEngine.cpp:11454 — all exact","engine":"5.3.2","ue":["5.3"],"bl":[],"section":"UE diagnostics","value":"`extern ENGINE_API float GAverageFPS;`"},{"id":"M30391b","fact":"FMessageLogModule::GetLogListing silently CREATES a missing listing, so a typo'd name yields a plausible empty log — gate every call on IsRegisteredLogListing. The listing view-model is private, and IMessageLogListing::GetFilteredMessageCount does not exist (use GetFilteredMessages().Num()).","source":"Engine/Source/Developer/MessageLog/Public/MessageLogModule.h:52 (the doc sentence; GetLogListing at :57), IsRegisteredLogListing at :49, private FMessageLogViewModel at :97; IMessageLogListing.h:47 is GetFilteredMessages and grep finds no GetFilteredMessageCount in that header. The candidate's LogSuppressionIn","engine":"5.3.2","ue":["5.3"],"bl":[],"section":"UE diagnostics","value":"`Get a message log listing registered with the message log. If it does not exist it will created.`"},{"id":"Mbd952f","fact":"EditorScriptingHelpers::CheckIfInEditorAndPIE returns false whenever GEditor->PlayWorld or GIsPlayInEditorWorld is set, and nearly every UStaticMeshEditorSubsystem / USkeletalMeshEditorSubsystem entry point is gated on it — so even READ calls return 0 or defaults during PIE (they log an Error, they are not truly silent). Subsystem-backed endpoints must detect PIE and say \"stop_pie first\".","source":"Engine/Source/Editor/UnrealEd/Private/EditorScriptingHelpers.cpp:169-187 (CORRECTION: not Editor/EditorScriptingUtilities/Private/ — that file does not exist). Coverage measured by grep: 43 guards across 48 UStaticMeshEditorSubsystem:: methods, 16 across 19 USkeletalMeshEditorSubsystem:: methods — so \"every ca","engine":"5.3.2","ue":["5.3"],"bl":[],"section":"UE editor / PIE gotcha","value":"`if (GEditor->PlayWorld || GIsPlayInEditorWorld) { UE_LOG(LogUtils, Error, TEXT(\"The Editor is currently in a play mode.\")); return false; }`"},{"id":"Mfe3a48","fact":"UEditorEngine::SelectActor / SelectNone / SelectComponent / SelectGroup / SelectBSPSurf are EMPTY inline stubs on the base class — the real implementations are the UNREALED_API overrides on UUnrealEdEngine, so selection must go through GEditor by virtual dispatch; a non-virtual base call silently does nothing.","source":"Engine/Source/Editor/UnrealEd/Classes/Editor/EditorEngine.h:1443-1464 (stubs; SelectActor :1443, SelectComponent :1446, SelectBSPSurf :1456, SelectNone :1464); Editor/UnrealEd/Classes/Editor/UnrealEdEngine.h:174-179 (real overrides) — exact","engine":"5.3.2","ue":["5.3"],"bl":[],"section":"UE editor API surface","value":"`virtual void SelectActor(AActor* Actor, bool bInSelected, bool bNotify, bool bSelectEvenIfHidden = false, bool bForceRefresh = false) {}`"},{"id":"M67cb3b","fact":"UMaterialEditingLibrary::GetStatistics does NOT return stale numbers — it force-submits the material's compile jobs at High priority and then synchronously FinishCompilation()s them, an unbounded game-thread block on a cold DDC; guard with IsGameThreadShaderMapComplete() first.","source":"Engine/Source/Editor/MaterialEditor/Private/MaterialEditingLibrary.cpp:1355-1362 (function opens :1351) — exact","engine":"5.3.2","ue":["5.3"],"bl":[],"section":"UE editor hazards / blocking","value":"`if (!Resource->IsGameThreadShaderMapComplete()) { Resource->SubmitCompileJobs_GameThread(EShaderCompileJobPriority::High); } Resource->FinishCompilation();`"},{"id":"Mf8d8a2","fact":"UEditorEngine::BuildReflectionCaptures calls FAssetCompilingManager::FinishAllCompilation() (an unbounded editor-wide wait) and hard-check()s the feature level, so it CRASHES below SM5.","source":"Engine/Source/Editor/UnrealEd/Private/EditorEngine.cpp:3978-3982 (FinishAllCompilation at :3981) and :3989 (the check) — exact","engine":"5.3.2","ue":["5.3"],"bl":[],"section":"UE editor hazards / blocking","value":"`check(World->GetFeatureLevel() >= ERHIFeatureLevel::SM5);`"},{"id":"M327e3b","fact":"Passing a non-null OutErrors to UStaticMesh::Build forces a SYNCHRONOUS build and prevents async static-mesh compilation — the engine header states it twice.","source":"Engine/Source/Runtime/Engine/Classes/Engine/StaticMesh.h:1661-1662 (FBuildParameters member + comment) and :1671-1676 (Build declaration + the same doc line)","engine":"5.3.2","ue":["5.3"],"bl":[],"section":"UE editor hazards / blocking","value":"`// If provided, will contain the errors that occurred during this process. This will prevent async static mesh compilation because OutErrors could get out of scope.`"},{"id":"Mc8fbfd","fact":"IAssetRegistry::EnumerateAllPackages runs its callback INSIDE the registry lock, so any registry or asset call from within the callback deadlocks — copy data out and act afterwards. The header states it.","source":"Engine/Source/Runtime/AssetRegistry/Public/AssetRegistry/IAssetRegistry.h:409-414 (doc :410-413, method :414)","engine":"5.3.2","ue":["5.3"],"bl":[],"section":"UE editor hazards / blocking","value":"`The callback is called from within the AssetRegistry's lock, so it must not call arbitrary code that could call back into the AssetRegistry; doing so would deadlock.`"},{"id":"M111451","fact":"Several common editor calls force garbage collection mid-handler — dirty-package enumeration runs CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS), FMaterialEditorUtilities::BuildTextureStreamingData runs CollectGarbage TWICE (and also opens a slow-task dialog), and RemoveLevelsFromWorld ends with GEditor->Cleanse — so any unrooted UObject a plugin holds across them dies.","source":"Engine/Source/Editor/UnrealEd/Private/FileHelpers.cpp:3645 (in InternalGetDirtyPackages, :3641); Editor/MaterialEditor/Private/MaterialEditorUtilities.cpp:789 and :814 (both inside FMaterialEditorUtilities::BuildTextureStreamingData at :781; SlowTask.MakeDialog at :792); Editor/UnrealEd/Private/EditorLevelUtil","engine":"5.3.2","ue":["5.3"],"bl":[],"section":"UE editor hazards / GC","value":"`CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);`"},{"id":"M24e7d3","fact":"set_property on a PLACED actor's component triggers RerunConstructionScripts via the PreEditChange/PostEditChange pair, which destroys the component, renames it TRASH_<Class>_N and builds a replacement - so the bridge re-resolves the component after the rerun (reporting reconstructed, retargetedTo, verifiedOn) and fails as unverified rather than reporting verified:true about a destroyed object.","source":"docs/02_GOTCHAS.md §9 § set_property on a PLACED actor (lines 1157-1173). Consequence recorded there: an instance edit of a property FComponentInstanceDataCache does not carry will now honestly fail; edit the ..._GEN_VARIABLE template instead.","engine":"","ue":[],"bl":[],"section":"UE editor internals","value":"`the notification is PostEditChangeChainProperty - a strict superset of PostEditChangeProperty and the only one that reaches archetype instances`"},{"id":"M444d88","fact":"Never call ConfigureProperties on ANY UFactory from a headless handler — DataTable, CurveTable, Curve and DataAsset factories all open modal windows there; IAssetTools::CreateAsset never calls ConfigureProperties (it goes CanCreateAsset then FactoryCreateNew), which is the safe route.","source":"Engine/Source/Editor/UnrealEd/Private/Factories/DataTableFactory.cpp:30 (ConfigureProperties opens a struct-picker window); CurveTableFactory.cpp:30 with EditorAddModalWindow at :55; EditorFactories.cpp:7231-7259 (UCurveFactory::ConfigureProperties, PickClass at :7251) and :7470 (DataAsset PickClass); safe pat","engine":"5.3.2","ue":["5.3"],"bl":[],"section":"UE editor modals","value":"`GEditor->EditorAddModalWindow(Window.ToSharedRef()); / SClassPickerDialog::PickClass(...)`"},{"id":"M018977","fact":"UAssetToolsImpl::CanCreateAsset (reached from CreateAsset) raises FMessageDialogs on an invalid name, a map-name collision, and a Yes/No overwrite prompt — pre-validate with FName::IsValidObjectName + FPackageName::IsValidLongPackageName + a map-asset check + an existing-object check, and never implement overwrite by letting the engine prompt.","source":"Engine/Source/Developer/AssetTools/Private/AssetTools.cpp:4287-4337 — invalid name dialog :4294, map collision :4301, re-test after full load :4321, overwrite Yes/No prompt :4331-4337; called from CreateAsset at :1647. All lines exact","engine":"5.3.2","ue":["5.3"],"bl":[],"section":"UE editor modals"},{"id":"Mb1e0bf","fact":"The fast-save path hardcodes bUseDialog=true and pops a modal on any failed save; the engine's own workaround is TGuardValue<bool>(GIsRunningUnattendedScript, true), or enumerate dirty packages and save per-package without a dialog.","source":"Engine/Source/Editor/UnrealEd/Private/FileHelpers.cpp — the hardcode is in InternalSavePackages (opens :3799), bUseDialog=true at :3824, warn call :3828; InternalWarnUserAboutFailedSave at :3620 with FMessageDialog::Open at :3638; the engine's own guard usage at :5476. FEditorFileUtils::SaveDirtyPackages (decl","engine":"5.3.2","ue":["5.3"],"bl":[],"section":"UE editor modals","value":"`const bool bUseDialog = true; → InternalWarnUserAboutFailedSave(FailedPackages, bUseDialog); → FMessageDialog::Open`"},{"id":"M9b2774","fact":"Every EditorLevelUtils level operation hides a modal: AddLevelToWorld on an already-present or persistent package, MakeLevelCurrent on a locked level, RemoveLevelFromWorld on a locked level or a failed unload (which also resets the transaction buffer and forces GC via Cleanse), MoveActorsToLevel with default warn flags, and CreateNewStreamingLevelForWorld(bInUseSaveAs=true).","source":"Engine/Source/Editor/UnrealEd/Private/EditorLevelUtils.cpp — AddLevelToWorld_Internal :435 with the already-present ShowModal at :441-450; MakeLevelCurrent :550 with the locked-level FMessageDialog at :586; RemoveLevelFromWorld locked-level dialog :830-832; trans-buffer Reset :888; failed-unload dialog :896; G","engine":"5.3.2","ue":["5.3"],"bl":[],"section":"UE editor modals / levels"},{"id":"M518446","fact":"UE throttles Slate's tick rate when the editor is not the foreground window (EditorPerformanceSettings::bThrottleCPUWhenNotForeground, on by default), and because handlers run inline in that ticker, per-call latency is essentially one tick period - the same build answers several times slower in the background. Never compare timings across two editor sessions without checking focus state.","source":"docs/02_GOTCHAS.md §8 § Latency is one tick (lines 904-933) - the three-row table is verbatim. tools/bench_bridge_latency.py reports median latency and focus state together for this reason. The doc also names autosave as the other distortion: read the median, not the mean.","engine":"","ue":[],"bl":[],"section":"UE editor performance","value":"`measured 2026-08-26, focused vs background: test_widget_animation 2.4s/6.3s; test_widget_animation_props 2.6s/11.0s; test_widget_animation_track 2.5s/6.7s`"},{"id":"M8c6562","fact":"FScopedTransaction::Cancel() DISCARDS the undo entry and reverts NOTHING for any object, transactional or not — UTransBuffer::Cancel broadcasts TransactionCanceled, calls EndOperation, nulls GUndo and POPS the transaction off UndoBuffer; it never calls FTransaction::Apply(), whose only two callers are UTransBuffer::Undo and ::Redo. A cancelled transaction was thrown away, not undone.","source":"Engine/Source/Editor/UnrealEd/Private/EditorTransaction.cpp:1387-1438 (Cancel body, read in full); Apply defined :814, called ONLY from UTransBuffer::Undo (:1591) at :1624 and ::Redo (:1658) at :1688 — grep-verified, no other call site in the file. Live proof recorded in-source: MifBridge Source/MifBridge/Priv","engine":"5.3.2","ue":["5.3"],"bl":[],"section":"UE editor transactions"},{"id":"Mf79fde","fact":"UTransBuffer::Cancel is unchanged on 5.8 and on the installed 5.3: Cancel broadcasts TransactionCanceled, calls EndOperation, nulls GUndo, POPS the transaction off UndoBuffer and puts back the transactions it had removed - it never calls FTransaction::Apply(), so a cancelled transaction still reverts nothing on either engine. On 5.8 Apply's only two calls are inside UTransBuffer::Undo and ::Redo. A handler that mutates and then answers ok:false keeps its writes and loses its undo entry; real rollback has to be written by the handler (undo, or its own inverse), never left to Cancel.","source":"Engine/Source/Editor/UnrealEd/Private/EditorTransaction.cpp:1411-1462 (Cancel body, read in full), :814 (FTransaction::Apply defined), :1648 inside UTransBuffer::Undo (:1615) and :1712 inside ::Redo (:1682) - grep finds no other Apply() call in the file; Engine/Source/Editor/UnrealEd/Private/EditorTransaction.cpp:1397-1429 (same body). Read 2026-09-26 while designing the bulk preview's rollback.","engine":"5.8, 5.3","ue":["5.3","5.8"],"bl":[],"section":"UE editor transactions"},{"id":"M544cca","fact":"AActor::SetActorLabel never makes a label unique: it trims the text, validates it with FActorEditorUtils::ValidateActorName (ENGINE_API, Runtime/Engine/Public/ActorEditorUtils.h:68 on both engines), and on success stores it VERBATIM - two actors may carry the same label. A label that fails validation is logged as a warning and nothing changes, so the actor keeps its previous label. A label an endpoint will set is therefore predictable before the actor exists: trimmed if ValidateActorName accepts it, otherwise unchanged.","source":"Engine/Source/Runtime/Engine/Private/ActorEditor.cpp:1291-1316 (SetActorLabel body, read in full); Engine/Source/Runtime/Engine/Private/ActorEditor.cpp:1194-1220 (same logic). Read 2026-09-27 for the spawn_many preview's label prediction.","engine":"5.8, 5.3","ue":["5.3","5.8"],"bl":[],"section":"UE editor actors"},{"id":"Mb29a79","fact":"In AssetTools and ObjectTools a 'no dialog' flag suppresses the PICKER, never the VALIDATION - IAssetTools::DuplicateAsset with bWithDialog=false still prompts through PerformDuplicateAsset -> CanCreateAsset, and ObjectTools::DeleteAssets with bShowConfirmation=false still prompts when the OnAssetsCanDelete delegate vetoes.","source":"Engine 5.3.2 source  - all four line numbers verified verbatim during this pass, and CanCreateAsset confirmed to open FMessageDialog seven and fourteen lines in. docs/02_GOTCHAS.md §8 § WHICH engine calls raise one; re-verified on every run by tools/audit_modals.py, which re-reads the cited engine lines.","engine":"5.3.2","ue":["5.3"],"bl":[],"section":"UE modal dialogs","value":"`AssetTools.cpp:4287 CanCreateAsset (its own FMessageDialog::Open at :4294 and :4301); ObjectTools.cpp:2833 DeleteObjects; ObjectTools.cpp:866 DuplicateSingleObject (destructive - 'the existing object will be deleted'); Dialogs.cpp:911 PromptUserIfExistingObjec`"},{"id":"Mfc8091","fact":"There are TWO dialog classes with separate guards. FMessageDialog::Open shows UI only when !FApp::IsUnattended() && !GIsRunningUnattendedScript and otherwise returns the DEFAULT (No for a YesNo). FSuppressableWarningDialog instead reads [SuppressableDialogs]<Key> from GEditorPerProjectIni and returns Suppressed (= consent) without showing; GIsRunningUnattendedScript only cancels it via FSlateApplication::AddModalWindow, turning a hang into a silent refusal.","source":"Engine 5.3.2 source  Runtime/Core/Private/Misc/MessageDialog.cpp:172 - verified verbatim, with `EAppReturnType::Type Result = DefaultValue;` two lines above it. docs/02_GOTCHAS.md §8 § There are TWO dialog classes; FMifScopedDialogSuppression in MifBridgeHandlers.h is the lever for the suppressible class, applied per call s","engine":"5.3.2","ue":["5.3"],"bl":[],"section":"UE modal dialogs","value":"`if (!FApp::IsUnattended() && !GIsRunningUnattendedScript)`"},{"id":"Mb025be","fact":"RunEndpoint opens every handler with TGuardValue<bool> UnattendedGuard(GIsRunningUnattendedScript, true) - the same line Epic's editor-scripting subsystems put at the top of every public function. It does NOT cover slow-task windows: the engine's cancel is guarded by GIsRunningUnattendedScript && !bSlowTaskWindow, so FScopedSlowTask::MakeDialog() progress dialogs are deliberately still shown, and they stall the bridge for the task's duration.","source":"docs/02_GOTCHAS.md §8 § The backstop (lines 1059-1105); engine 5.3.2 source  Editor/UnrealEd/Private/Subsystems/EditorAssetSubsystem.cpp:366 verified as exactly `TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);` inside UEditorAssetSubsystem::LoadAsset - 36 occurrences of the guard in that one file","engine":"5.3.2","ue":["5.3"],"bl":[],"section":"UE modal dialogs","value":"`TGuardValue<bool> UnattendedGuard(GIsRunningUnattendedScript, true);`"},{"id":"Mfead87","fact":"UWorld::HasBegunPlay requires all three of bBegunPlay, a non-null PersistentLevel, and a non-empty actor array.","source":"Engine/Source/Runtime/Engine/Private/World.cpp:5770-5773","engine":"5.3.2","ue":["5.3"],"bl":[],"section":"UE PIE / world model","value":"`bool UWorld::HasBegunPlay() const { return bBegunPlay && PersistentLevel && PersistentLevel->Actors.Num(); }`"},{"id":"Mdfed4f","fact":"GEditor->PlayWorld is a per-tick scratch pointer, not a session-liveness authority: two separate loops reassign it to each ticked/rendered PIE context in turn and leave it pointing at whichever was visited last, null-world contexts are filtered out of the tick loop, and both loops sit behind a Slate expensive-tasks throttle gate — so equating PlayWorld==nullptr with \"not playing\" is structurally wrong.","source":"Engine/Source/Editor/UnrealEd/Private/EditorEngine.cpp:1867 (tick loop) and :2164 (render loop); null-world candidate filter :1845; throttle gates :1838 and :2154; declaration EditorEngine.h:407-409. Engine's own corroborating comment: PlayLevel.cpp:2145 \"PlayWorld cannot be depended on as it only points to th","engine":"5.3.2","ue":["5.3"],"bl":[],"section":"UE PIE / world model","value":"`PlayWorld = PieContext.World();`"},{"id":"M16825d","fact":"The three correct PIE phase accessors are inline on UEditorEngine and need no export: IsPlayingSessionInEditor (in progress now), IsPlaySessionRequestQueued (starts next tick), IsPlaySessionInProgress (either).","source":"Engine/Source/Editor/UnrealEd/Classes/Editor/EditorEngine.h:1703-1708 (IsSimulatingInEditor :1711, GetPlayInEditorSessionInfo :1721, ShouldEndPlayMap :1745) — all verified verbatim","engine":"5.3.2","ue":["5.3"],"bl":[],"section":"UE PIE / world model","value":"`bool IsPlayingSessionInEditor() const { return PlayInEditorSessionInfo.IsSet(); } bool IsPlaySessionRequestQueued() const { return PlaySessionRequest.IsSet(); } bool IsPlaySessionInProgress() const { return IsPlayingSessionInEditor() || IsPlaySessionRequestQ`"},{"id":"M653a5f","fact":"RequestPlaySession only stores a request; it is consumed on the NEXT editor tick, which resets the request and creates the session record — so there is a real \"queued, not yet consumed\" phase between the call returning and PIE existing.","source":"Engine/Source/Editor/UnrealEd/Private/EditorEngine.cpp:1807-1810; PlaySessionRequest = InParams at PlayLevel.cpp:950; reset at :1066; PlayInEditorSessionInfo = FPlayInEditorSessionInfo() at :1097; RequestPlaySession decl EditorEngine.h:1683 — all lines exact","engine":"5.3.2","ue":["5.3"],"bl":[],"section":"UE PIE / world model","value":"`// Kick off a Play Session request if one was queued up during the last frame. if (PlaySessionRequest.IsSet()) { StartQueuedPlaySessionRequest();`"},{"id":"M22798d","fact":"Out-of-process play sessions (Standalone Game / multi-process PIE) are undetectable in-process by engine design: the engine cancels its own play-session request immediately after launching the processes, wiping both PlaySessionRequest and PlayInEditorSessionInfo, so a status endpoint always reports stopped while the user plays.","source":"Engine/Source/Editor/UnrealEd/Private/PlayLevelNewProcess.cpp:57-59; CancelRequestPlaySession resets both optionals at PlayLevel.cpp:980-985 — all lines exact","engine":"5.3.2","ue":["5.3"],"bl":[],"section":"UE PIE / world model","value":"`// Now that we've launched the new process, we'll cancel the request so that the UI lets us go into PIE. CancelRequestPlaySession();`"},{"id":"Ma20bf7","fact":"A PIE client (NetMode PIE_Client) is deliberately given a minimal ENTRY world rather than a null world, and that world is made current on the context — so a null-world test misses the mid-connect case entirely.","source":"Engine/Source/Runtime/Engine/Private/GameInstance.cpp:313-317 (comment :315, CreatePIEWorldFromEntry :316), made current at :349. NOTE: the candidate's proposed replacement signal (\"PendingNetGame != nullptr on a context whose world is not the game map\") is a recommendation, NOT verified at this location — dro","engine":"5.3.2","ue":["5.3"],"bl":[],"section":"UE PIE / world model","value":"`// We are going to connect, so just load an empty world`"},{"id":"M5b9a79","fact":"Non-seamless travel nulls and re-points the world context inside ONE synchronous UEngine::LoadMap call, so the null window is not observable across frames.","source":"Engine/Source/Runtime/Engine/Private/UnrealEngine.cpp — MarkObjectsPendingKill :14879, SetCurrentWorld(nullptr) :14899, SetCurrentWorld(NewWorld) :15099; both inside UEngine::LoadMap which opens at :14676 (verified by scanning function starts in range)","engine":"5.3.2","ue":["5.3"],"bl":[],"section":"UE PIE / world model","value":"`WorldContext.SetCurrentWorld(nullptr); // :14899 … WorldContext.SetCurrentWorld(NewWorld); // :15099`"},{"id":"Mce4fdb","fact":"FViewport::ReadPixels returns the BACKBUFFER, not a fresh render, so a non-realtime viewport hands back a byte-identical old frame while the response reports the new camera; and the backbuffer's alpha is leftover renderer state (measured 0 on 99.97% of pixels), which PNGCompressImageArray writes verbatim into a fully transparent PNG.","source":"docs/02_GOTCHAS.md §11 (lines 1522-1545); capture_viewport reports forcedRedraw:true - emitted at MifBridgeSpatial.cpp:464. The doc states both were found by looking at the picture and measuring it, with every JSON field correct.","engine":"","ue":[],"bl":[],"section":"UE viewport capture","value":"`fix: Viewport->Invalidate(); Viewport->Draw(); FlushRenderingCommands(); then force Px.A = 255 before compressing`"},{"id":"M3bcfd1","fact":"A blank-frame check that tests only for BLACK will not fire - a transparent capture composites to WHITE and sails past an all-black guard (it did, twice, and both times was read as 'the scene must be empty'). Measure UNIFORMITY instead: capture_viewport reports distinctColours, uniformity and dominantColour on EVERY response, not only when it thinks something is wrong.","source":"docs/02_GOTCHAS.md §11; the three fields verified emitted unconditionally at MifBridgeSpatial.cpp:482-484","engine":"","ue":[],"bl":[],"section":"UE viewport capture","value":"`uniformity = fraction of the frame that is the single most common colour`"},{"id":"ET1","fact":"A background editor's WHOLE engine loop is capped at 3 fps, not just Slate: `GetMaxTickRate` returns 3.0 whenever `ShouldThrottleCPUUsage()` is true","source":"`Engine/Source/Editor/UnrealEd/Private/EditorEngine.cpp`, `UEditorEngine::GetMaxTickRate`: @5.7.0-release :2522, the `3.0f` :2562; @5.3.2-release :2257, the `3.0f` :2297","engine":"5.3.2, 5.7.0","ue":["5.3","5.7"],"bl":[],"section":"UE editor - the background tick throttle","value":"`if (!ShouldThrottleCPUUsage()) { ... } else { MaxTickRate = 3.0f; }`"},{"id":"ET2","fact":"`ShouldThrottleCPUUsage` returns false first for a commandlet, benchmarking, unattended or cannot-render run, and THEN whenever any `ShouldDisableCPUThrottlingDelegates` entry returns true - before any focus test. Past that it throttles only if the app is neither foreground nor focused, and then if `bThrottleCPUWhenNotForeground` is on OR `AreAllWindowsHidden()` (its definition was NOT read; ET4 measures a minimized editor throttling with the setting off) - except during drag-drop, a lighting-build export, static lighting, shader compiling or asset-registry loading","source":"same file, `UEditorEngine::ShouldThrottleCPUUsage`: @5.7.0-release :5014 (setting read :5044); @5.3.2-release :6376 (setting read :6406)","engine":"5.3.2, 5.7.0","ue":["5.3","5.7"],"bl":[],"section":"UE editor - the background tick throttle","value":"delegates loop `if (It->IsBound() && It->Execute()) return false;` precedes `bIsForeground = FPlatformApplicationMisc::IsThisApplicationForeground()`"},{"id":"ET3","fact":"`ShouldDisableCPUThrottlingDelegates` is a public member of the same type on every release engine the plugin builds on, so a plugin can register one without a version guard (the PCG editor does, and removes it by `GetHandle()`)","source":"`Engine/Source/Editor/UnrealEd/Classes/Editor/EditorEngine.h` @5.3.2-release :552-553, @5.4.4 :552-553, @5.5.4 :606-607, @5.6.1 :659-660, @5.7.4 :661-662, @5.8.2 :692-693. PCG: `Engine/Plugins/PCG/Source/PCGEditor/Private/PCGEditorModule.cpp` @5.7.4-release :165-168, :201-202","engine":"5.3.2 - 5.8.2","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE editor - the background tick throttle","value":"`DECLARE_DELEGATE_RetVal(bool, FShouldDisableCPUThrottling); TArray<FShouldDisableCPUThrottling> ShouldDisableCPUThrottlingDelegates;`"},{"id":"ET6","fact":"A scene capture's FOV is HORIZONTAL at every aspect: `FOVAngle` spans the render target's width and the height follows from the aspect, so a 55-degree capture at 16:9 spans 32.6 degrees vertically. Measured: ue_nightcity v3 preview frame 0 (960x540; camera 6300 out at z 2000 looking at z 700, 11.7 degrees down) has its horizon at row 74 = 13.7% from the top; a horizontal FOV predicts 14.7%, a vertical one 30.2%","source":"`UnscaledFOV = CaptureComponent->FOVAngle * (float)PI / 360.0f` (line 1194) and `XAxisMultiplier = 1.0f; YAxisMultiplier = InRenderTargetSize.X / float(InRenderTargetSize.Y)` (lines 602-603), `Engine/Source/Runtime/Renderer/Private/SceneCaptureRendering.cpp`; MifBridge's capture_camera writes its fov there: `Comp->FOVAngle = (float)Fov` (`Source/MifBridge/Private/MifBridgeSpatial.cpp:1696`)","engine":"measured on 5.7; source read at the `release` branch, not a tag","ue":["5.7"],"bl":[],"section":"UE editor - the background tick throttle","value":"half-vertical = atan(tan(FOV/2) * H/W); horizon from the top = 0.5 - tan(pitch) / (2 * tan(half-vertical))"},{"id":"U41","fact":"**`send_editor_key` does not reach a PIE player controller's input state.** A key sent while PIE is running does not make `APlayerController::IsInputKeyDown` return true, so nothing that polls raw key state sees it. **[Reviewed 2026-09-21 against the code - STALE in part: run_console_captured never reaches pawn exec functions (U60/U68) (MifBridgePIE.cpp:525-538); contradicted by U60/U68: console endpoints call GEngine->Exec, which has no pawn dispatch; the send_editor_key half stands (MifBridgeCommon.cpp:2775-2781; MifBridgePIE.cpp:525-538)]**","source":"measured 2026-09-18: an interaction harness sent E, four Q presses and 31 seconds of held F into a running PIE session and the game logged **zero** input events - no journal pick-up, no page turn, no fix progress - while `pie_status` confirmed the session was running and reported the pawn it was driving. The harness duly reported the fix mechanic broken when nothing had been pressed. **The route that does work** is `run_console_captured`, which runs against the PIE world, driving `UFUNCTION(Exec)` functions on the PAWN - an arbitrary actor is not in Unreal's exec chain, but a pawn, player controller, game mode or game instance is.","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE materials, actor identity and PIE","status":"superseded"},{"id":"B99","fact":"**CORRECTS B98's throughput figure.** The per-image cost is not a constant ~7 minutes - it is dominated by model INITIALISATION, which varies with how much VRAM something else is holding. B98's 437 s came from a run with the GPU otherwise quiet; with the Unreal editor open and resident the same 1024x1024 4-step prompt took **611 s for the first sampling step alone**. B98 is kept because its measurement was correct for its conditions.","source":"measured 2026-09-18 on the same machine and model, reading ComfyUI's own progress line: `0/4 [10:11<?, ?it/s, Model Initialization complete!]` then `1/4 [10:11<30:33, 611.23s/it]`. Sampling after init is seconds; the 10 min is swap. **Plan a batch by what else is on the GPU, not by a per-image constant**, and note that lowering the resolution barely helps because it does not touch the init cost.","engine":"5.0.1","ue":[],"bl":[],"section":"UE materials, actor identity and PIE"},{"id":"B102","fact":"**CORRECTS B98's VRAM framing.** B98 read the `11340MB Staged.` line as the model's size; that is the UNET portion only. The checkpoint on disk is **16.06 GB** - `flux1-schnell-fp8.safetensors` is an all-in-one bundling unet, T5-XXL, CLIP-L and VAE - against **16.38 GB** of card. The model alone is 98% of VRAM before activations or the Windows desktop, so it can NEVER stay resident and will thrash on any single-prompt call regardless of what else is closed. Closing the Unreal editor does not fix it. **What does help is queueing the whole batch at once**: ComfyUI caches `CheckpointLoaderSimple` between queued prompts, so the ten-minute load is paid once instead of once per image.","source":"measured 2026-09-18: `ls -la` on the checkpoint reads 16.06 GB, the card reports `16376 MiB` total with `15665 MiB` used mid-run, and per-process VRAM is unreadable under WDDM so the split cannot be attributed further. Sampling under thrash ran **114 s/step** (step 1 at 10:11, step 2 at 12:05) where a resident schnell does 1-2 s/step.","engine":"5.0.1","ue":[],"bl":[],"section":"UE materials, actor identity and PIE"},{"id":"U50","fact":"**A UE `GPU Crashed or D3D Device Removed` with `Local Used` far below `Local Budget` and `DRED: No PageFault data` is DEVICE REMOVAL, not an out-of-memory.** Reading it as an OOM sends you looking for the editor's memory use, which is not the problem. The editor holds only about **1.6 GB** of VRAM with a small level open, so closing it to free room for something else buys almost nothing (corroborates B104 from the other direction). **The trigger is a TRANSITION on the GPU, not steady-state load** - the editor survived an hour of another process thrashing the card and died the moment that process was killed and restarted.","source":"measured 2026-09-18: the editor ran for an hour beside a ComfyUI batch saturating the GPU, then crashed at the exact second ComfyUI was `taskkill`-ed and relaunched (dump `D3D12.0.2026.09.18-08.22.27.nv-gpudmp`, log `[2026.09.18-12.22.27:346]`). The log's own figures at the crash: `Local Budget: 13059.03 MB`, `Local Used: 1582.39 MB`, `DRED: No PageFault data`, then `FPlatformMisc::RequestExit(1, D3D12Util.TerminateOnGPUCrash)`. Callstack was five `UnrealEditor_D3D12RHI` frames over `Core`/`kernel32`/`ntdll` with no project code.","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE materials, actor identity and PIE"},{"id":"U70","fact":"**`list_level_actors` and `list_pie_actors` return `actorPath`, not `path`, and PIE listings carry no `label`.** Passing the missing key as `objectPath` yields `None` from every read rather than an error.","source":"measured 2026-09-18: an actor record is `{actorPath, name, label, class, folder, location, rotation, scale}`; a PIE record is `{actorPath, name, class, location}` with no label. A tagging script using `a.get('path')` passed `objectPath: None` to `get_property` for all seven lights and got `None` back each time. Matching live actors by LABEL therefore cannot work in PIE - match on a reflected property such as `Tags` or `StationId`.","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE materials, actor identity and PIE"},{"id":"GR4","fact":"**`gameplay_recipe`'s ui_state recipe works as built: 105 steps, the component compiling with no error, warning or note and linting clean, the enum's display names as given; in PIE on the player's own controller a Push applies its screen's state, a second collapses the first and applies its own, a Pop brings the first back as it was made WITH its pause and cursor, the last Pop returns gameplay's state, a Pop on an empty stack answers false, and Clear takes every screen off.**","source":"measured: tools/test_gameplay_recipes_live.py G240-G249 on 5.8.2 (a scratch project, port 8871)","engine":"5.8","ue":["5.8"],"bl":[],"section":"Gameplay recipes - whole systems built from verified calls"},{"id":"GR6","fact":"**`gameplay_recipe`'s puzzle recipe works as built on the interaction build: 92 steps, the base and the tracker compiling with no error, warning or note and linting clean - OnCompleted broadcast once and heard once, by the tracker - with no cast edge (its four couplings: inherits, the tracker's bind and variable type, and ForEach's macros); in PIE a child overriding the hooks is counted per Interact, solved at the third with the pawn recorded and its solved hook run, heard by the tracker, deaf to a fourth attempt, reset through its hook, and AllSolved turns true when a second puzzle completes by its own Complete call.**","source":"measured: tools/test_gameplay_recipes_live.py G270-G279, 115/115 on 5.8.2; tools/test_gameplay_recipes_offline.py 96/96","engine":"5.8","ue":["5.8"],"bl":[],"section":"Gameplay recipes - whole systems built from verified calls"},{"id":"U301","fact":"**A stall writes a `Saved/Crashes/UECC-*` folder too, and the editor carries on - read `CrashType` in `CrashContext.runtime-xml` before calling one a crash.** `SCOPE_STALL_REPORTER(FShaderCompilingManager::BlockOnShaderMapCompletion, 2.0)` reports when that call runs past its 2.0 s budget, and `ReportStall` goes through `ReportEvent(ECrashContextType::Stall, ...)` - the same non-fatal path as `ReportEnsure`; whether a minidump is written is `[CrashReportClient] Stall.RecordDump`. Seen 2026-09-23 on a scratch project (5.3.2): folder `UECC-Windows-97BBBE154AD7CFD7FFD72DBF5E227561_0000` at 10:29:35 local, `CrashType` Stall, `ErrorMessage` FShaderCompilingManager::BlockOnShaderMapCompletion, `SecondsSinceStart` 46, `ProcessId` 44048, no .dmp in the folder - and pid 44048 (created 10:28:45) was still running at 10:50.","source":"UE_5.3 `Engine/Source/Runtime/Engine/Private/ShaderCompiler/ShaderCompiler.cpp:4662`; `Engine/Source/Runtime/Core/Public/ProfilingDebugging/StallDetector.h:41` (`SCOPE_STALL_REPORTER(InName, InBudgetSeconds)`); `Engine/Source/Runtime/Core/Private/Windows/WindowsPlatformCrashContext.cpp:1820-1823` (`ReportStall`) and `:212-213` (`Stall.RecordDump`); the folder's `CrashContext.runtime-xml`; psutil for the pid","engine":"5.3.2 (source and live)","ue":["5.3"],"bl":[],"section":"UE crash reporting - a Saved/Crashes folder is not always a crash"},{"id":"U409","fact":"**Two settings keep a crashing editor from starting CrashReportClient at all - no dialog on the screen, nothing sent, no Saved/Crashes folder: the console variable `WindowsPlatformCrashContext.ForceCrashReportDialogOff=1` and the editor setting `/Script/UnrealEd.CrashReportsPrivacySettings` `bSendUnattendedBugReports=False`.** The crash path sets bNoDialog when the app is unattended, the report is unattended, the process is a dedicated server, or that variable is set; it reads bSendUnattendedBugReports from the editor settings; and with no dialog and no unattended sending it sets `bCanRunCrashReportClient = false`. On 5.8 the crash folder is made only inside the branch that runs the client. The log still ends in the Fatal and its callstack. Measured on a 5.8.2 probe on 2026-09-27: launched with `-ini:Engine:[ConsoleVariables]:WindowsPlatformCrashContext.ForceCrashReportDialogOff=1` and `-ini:EditorSettings:[/Script/UnrealEd.CrashReportsPrivacySettings]:bSendUnattendedBugReports=False`, five deliberate Fatals put no dialog on the screen and left no Saved/Crashes folder, and each log ended in its Fatal and stack.","source":"5.3.2 `Runtime/Core/Private/Windows/WindowsPlatformCrashContext.cpp:770-808`; 5.8 `Runtime/Core/Private/Windows/WindowsPlatformCrashContext.cpp:977-1030`; the probe a scratch project (no Saved/Crashes after five Fatals, 14:47-15:02 local)","engine":"5.3.2-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE crash reporting - a Saved/Crashes folder is not always a crash"},{"id":"U452","fact":"**UE's file writer goes to the disk on every flush that has bytes to write: FArchiveFileWriterGeneric::Flush writes its buffer and then calls the handle's Flush, and on Windows FFileHandleWindows::Flush calls FlushFileBuffers whatever its bFullFlush argument says - so an archive flushed once per record pays a disk flush per record.** MifBridge's crash journal paid two per bridge call (its start and end records) through the archive before d7a83231, and pays the same two after it with its own WriteFile and FlushFileBuffers.","source":"read: 5.8 `Engine/Source/Runtime/Core/Private/Windows/WindowsPlatformFile.cpp:933`, 5.8 `Engine/Source/Runtime/Core/Private/HAL/FileManagerGeneric.cpp:1000`","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE crash reporting - a Saved/Crashes folder is not always a crash"},{"id":"U454","fact":"**UE's Windows file layer adds the long-path prefix by length alone: before a path reaches the Windows API it becomes \\\\?\\<path> (\\\\?\\UNC for a share) once it is 260 (MAX_PATH) characters or more, whatever the machine's LongPathsEnabled setting - and that normalizer is private to Core, so a plugin that calls CreateFileW itself does not get it.** On a machine without LongPathsEnabled, or in a process not marked long-path aware, the unprefixed long path is refused: measured 2026-09-28 with a small probe process that is not long-path aware, a 268-character journal path failed to open with error 3 and opened with the prefix. (This machine has LongPathsEnabled=1 and UnrealEditor.exe declares itself long-path aware, so an editor here opens both spellings.) MifBridge's crash journal, opened with CreateFileW since d7a83231, repeats the normalizer's steps since 59cb7840.","source":"read: 5.3 `Engine/Source/Runtime/Core/Private/Windows/WindowsPlatformFile.cpp:962`, 5.4 `Engine/Source/Runtime/Core/Private/Windows/WindowsPlatformFile.cpp:955`, 5.5 `Engine/Source/Runtime/Core/Private/Windows/WindowsPlatformFile.cpp:1071`, 5.6 `Engine/Source/Runtime/Core/Private/Windows/WindowsPlatformFile.cpp:1108`, 5.7 `Engine/Source/Runtime/Core/Private/Windows/WindowsPlatformFile.cpp:1136`, 5.8 `Engine/Source/Runtime/Core/Private/Windows/WindowsPlatformFile.cpp:1136`","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE crash reporting - a Saved/Crashes folder is not always a crash"},{"id":"U461","fact":"**Handing the engine's package reload a package whose file is no longer on disk is unsafe on UE 5.5, 5.7 and 5.8, in two different places.** On 5.8.2 the reload itself asserts `check(!Loader->IsError())` in the linker, under PackageReloadInternal::LoadReplacementPackage's LoadPackage. On 5.5.4 and 5.7.4 the reload fails quietly, the async loader records the package as failed, and the NEXT load of it - even after the file is back - asserts `check(!bHasFailed)`. A package saved and then moved or deleted still reads as a saved package in memory, so only looking for its file first keeps it away from the engine. Measured with test_bulk_preview_wave3 T7824 (save, edit, move the .uasset aside, `reload_packages`) at MifBridge 7be60ad8: 5.8.2 3 of 5 runs crashed, 5.7.4 3 of 3, 5.5.4 1 of 4, 5.3.2/5.4.4/5.6.1 0 of 4; on the fix agent's own 5.8.2 probe 0 of 14 crashed and 2 of 14 answered `reloaded` with the file gone. MifBridge 769355b8 looks for the .uasset/.umap first and answers notReloaded with missingFile: 10 of 10 clean on 5.8.2, 5.5.4 and 5.7.4.","source":"5.8 `Runtime/CoreUObject/Private/UObject/LinkerLoad.cpp:1465`; 5.8 `Runtime/CoreUObject/Private/UObject/PackageReload.cpp:469` (LoadReplacementPackage's LoadPackage); 5.5 `Runtime/CoreUObject/Private/Serialization/AsyncLoading2.cpp:1129`; 5.7 `Runtime/CoreUObject/Private/Serialization/AsyncLoading2.cpp:1426`. Measured 2026-09-29 by the release agent a2e6e71d and fix agent a9b1bd52","engine":"5.5-5.8","ue":["5.5","5.6","5.7","5.8"],"bl":[],"section":"UE crash reporting - a Saved/Crashes folder is not always a crash"},{"id":"U315","fact":"**A transaction stays on the undo stack only if an object recorded in it CHANGED - an FScopedTransaction that records no change leaves no entry.** UTransBuffer::End finalizes the transaction and, when FTransaction::IsTransient() is true, pops it and puts back the redo entries its Begin set aside. IsTransient() is true when no object record HasChanges(), or when a record holds a PIE object; FObjectRecord::HasChanges() is a serialized change, a custom change or an object annotation, and only object records are counted, not package records. So a \"Mif Bridge: <endpoint>\" row in list_transactions is evidence that the call changed a transactional object, not of the dispatcher's wrapper alone.","source":"Engine/Source/Editor/UnrealEd/Private/EditorTransaction.cpp:1333-1352 (End pops a transient transaction), 515-527 (IsTransient), 653-657 (HasChanges); Engine/Source/Editor/UnrealEd/Classes/Editor/TransBuffer.h:79-90 (BeginInternal sets the redo entries aside)","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE editor undo - which transactions stay on the stack"},{"id":"U316","fact":"**In the editor, an object CREATED with RF_Transactional while a transaction is open is recorded at construction, with no Modify() call.** StaticConstructObject_Internal, when GIsEditor and GUndo are set, the construction flags hold RF_Transactional and not RF_NeedLoad, the class is not a UField and the object is not async-loading, marks the new object garbage, SaveToTransactionBuffer()s it and clears the mark, so an undo marks it garbage again. Like any record it keeps the transaction only if it changes before End: FObjectRecord::Finalize counts a change when the bytes, names or object references serialized at record time differ from those at finalize.","source":"Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectGlobals.cpp:4856-4868; SaveToTransactionBuffer at 3207-3233; Engine/Source/Editor/UnrealEd/Private/EditorTransaction.cpp:388-394 (the change test)","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE editor undo - which transactions stay on the stack"},{"id":"U411","fact":"**FMonitoredProcess can drop a child's last output on UE 5.3-5.5: once the process has exited it closes the pipe without reading it again; 5.6-5.8 read first.** In FMonitoredProcess::TickInternal, 5.3 `MonitoredProcess.cpp:129-157` (the 5.3.2 source fork is the same) reads the pipe at :132, then, when IsProcRunning is false, closes both pipes at :146 and fires OnCompleted at :155 - whatever the child wrote between that read and its exit, often its final summary line, is lost, and a last line with no newline never reaches OnOutput. 5.4 and 5.5 are the same (:129, :146). 5.8 `MonitoredProcess.cpp:205-228` reads once more before closing (\"Make sure to read the final bytes from the pipe before closing\"), as do 5.6 and 5.7. 5.3 also gives the child the parent's READ pipe as its stdin, 5.3 `MonitoredProcess.cpp:75`; 5.8 gives it none, 5.8 `MonitoredProcess.cpp:116`. MifBridge's SETUP tab drives its child itself for this reason (MifBridgeSetupView.cpp, TickClaudeRun).","source":"source: 5.3 `MonitoredProcess.cpp:129-157`, 5.3 `MonitoredProcess.cpp:75`, 5.8 `MonitoredProcess.cpp:205-228`, 5.8 `MonitoredProcess.cpp:116`; the final-read comment searched in all six installs 2026-09-27 (5.6, 5.7, 5.8 only)","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE editor child processes and the Python each engine ships - what running a program from the editor meets"},{"id":"U412","fact":"**FPlatformProcess::ReadPipeToArray never blocks on Windows - it asks PeekNamedPipe how many bytes are waiting and reads only those - while ReadPipe decodes each read as UTF-8 on its own.** The function is the same in 5.3 `WindowsPlatformProcess.cpp:1679-1701` and 5.8 `WindowsPlatformProcess.cpp:2014-2036`. ReadPipe, 5.3 `WindowsPlatformProcess.cpp:1655-1677`, runs FUTF8ToTCHAR over each chunk, so a multi-byte character split between two reads is decoded in halves; reading bytes with ReadPipeToArray and decoding whole lines avoids that. A game-thread ticker can therefore poll a child's pipe every frame without stalling the editor.","source":"source: 5.3 `WindowsPlatformProcess.cpp:1679-1701`, 5.8 `WindowsPlatformProcess.cpp:2014-2036`, 5.3 `WindowsPlatformProcess.cpp:1655-1677`","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE editor child processes and the Python each engine ships - what running a program from the editor meets"},{"id":"U413","fact":"**CreateProc with bLaunchReallyHidden and without bLaunchDetached starts the child with a hidden console window on 5.3 and 5.8 alike; only 5.8 turns a detached, really-hidden launch into CREATE_NO_WINDOW.** 5.3 `WindowsPlatformProcess.cpp:385-401`: bLaunchDetached adds DETACHED_PROCESS, bLaunchReallyHidden sets STARTF_USESHOWWINDOW with SW_HIDE. 5.8 `WindowsPlatformProcess.cpp:583-611`: CREATE_NO_WINDOW when both are set, DETACHED_PROCESS when only detached (Epic's comment there: DETACHED_PROCESS \"can cause console windows to appear if the child spawns a process\"), and the same SW_HIDE. The 10-argument CreateProc sends stderr down the stdout pipe on both, 5.3 `WindowsPlatformProcess.cpp:363-368` and 5.8 `WindowsPlatformProcess.cpp:511-518`. Given a pipe, 5.3 lets the child inherit every inheritable handle, 5.3 `WindowsPlatformProcess.cpp:425`; 5.8 passes bInheritHandles false and lists only the pipes (PROC_THREAD_ATTRIBUTE_HANDLE_LIST), 5.8 `WindowsPlatformProcess.cpp:624-666`.","source":"source: 5.3 `WindowsPlatformProcess.cpp:385-401`, 5.8 `WindowsPlatformProcess.cpp:583-611`, 5.3 `WindowsPlatformProcess.cpp:363-368`, 5.8 `WindowsPlatformProcess.cpp:511-518`, 5.3 `WindowsPlatformProcess.cpp:425`, 5.8 `WindowsPlatformProcess.cpp:624-666`","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE editor child processes and the Python each engine ships - what running a program from the editor meets"},{"id":"U492","fact":"**FMovieSceneChannel::Optimize (the curve editor's key reduction) is greedy: each key is tested once against the curve from the last KEPT key to its immediate next key, and removed when that curve passes within Tolerance of it - so the removals add up and the reduced curve is not bounded by the tolerance.** Measured through reduce_sequence_keys {bound:false}: a 91-key sine of amplitude 50 (one key per frame) at tolerance 0.01 moved up to 2.71 from the original between kept keys on 5.8.2 and 5.59 on 5.3.2.","source":"5.3 `Runtime/MovieScene/Private/Channels/MovieSceneCurveChannelImpl.cpp:1186` and 5.8 `Runtime/MovieScene/Private/Channels/MovieSceneCurveChannelImpl.cpp:880`; tools/test_ue_spec_extensions_e.py SX2503. Read and measured 2026-10-04","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE generic routes settled live - create_asset, set_property, edit_container, call_function, evaluate_sequence (UE 5.8.2 scratch editor, 2026-10-04)"},{"id":"U494","fact":"**USplineComponent's distance lookups (GetLocationAtDistanceAlongSpline and the rest) go through a reparameterisation table of ReparamStepsPerSegment linear steps per segment (default 10), so equal distances are not equal arc lengths.** Measured on 5.8.2 and 5.3.2: keys placed by distance around an 8-point closed spline of radius 500 cm at 300 cm/s read back at 293.0-305.6 cm/s (standard deviation 2.3); placed through a 256-step arc-length table of GetLocationAtSplineInputKey, 299.995 +- 0.005 cm/s.","source":"5.3 `Runtime/Engine/Classes/Components/SplineComponent.h:99` and 5.8 `Runtime/Engine/Classes/Components/SplineComponent.h:119`; tools/test_ue_spec_extensions_e.py SX2700-SX2701. Read and measured 2026-10-04","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE generic routes settled live - create_asset, set_property, edit_container, call_function, evaluate_sequence (UE 5.8.2 scratch editor, 2026-10-04)"},{"id":"U499","fact":"**`GetFaceLandmarks` is not a fixed point set: it returned 79 to 95 points depending on the MetaHuman preset (Zuri, Ada, Lorenzo 79; Jorge, Walter 91; Asha 93; Celeste 95), so landmark distances between two characters measure nothing.** Compare faces in face-model space instead: GetFaceModelCoefficients returns 1397 floats whose first 9 are a header (counts, head quaternion, translation); distinct presets are 17-38 apart (norm of entries 9+). Measured 2026-10-04 on 5.8.2 through call_function.","source":"measured on 5.8.2 (call_function GetFaceLandmarks / GetFaceModelCoefficients on preset copies)","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE generic routes settled live - create_asset, set_property, edit_container, call_function, evaluate_sequence (UE 5.8.2 scratch editor, 2026-10-04)"},{"id":"U500","fact":"**A MetaHuman package (.mhpkg) imports through the ordinary factory path (UMetaHumanPackageFactory), so a typed import reaches it, but it always ends by opening an SImportSummary window on the editor, an outfit package is refused without the ChaosOutfitAssetEditor module, and the import UI has modal-window branches** - run it in a scratch editor, not a working one.","source":"5.8 `MetaHuman/MetaHumanSDK/Source/MetaHumanSDKEditor/Private/Import/MetaHumanPackageFactory.cpp:308` (AddWindowAsNativeChild) and 5.8 `MetaHuman/MetaHumanSDK/Source/MetaHumanSDKEditor/Private/Import/MetaHumanPackageFactory.cpp:189` (outfit check); 5.8 `MetaHuman/MetaHumanSDK/Source/MetaHumanSDKEditor/Private/Import/MetaHumanImportUI.cpp:717` (EditorAddModalWindow)","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE generic routes settled live - create_asset, set_property, edit_container, call_function, evaluate_sequence (UE 5.8.2 scratch editor, 2026-10-04)"},{"id":"U516","fact":"**The profile factories give each new profile a fresh Guid (Specular, Neural, Toon; Subsurface from 5.5) and the subsurface factory sets bEnableBurley and bEnableMeanFreePath; a bare NewObject leaves the zero Guid.** Two profiles of each class made through the factory read back two distinct nonzero Guids on 5.8.2 and 5.3.2.","source":"5.3 `Editor/UnrealEd/Private/Factories/EditorFactories.cpp:7856`; 5.3 `Editor/UnrealEd/Private/Factories/EditorFactories.cpp:7883`; tools/test_ue_spec_extensions_h.py SX4300-SX4301. Measured 2026-10-04","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE generic routes settled live - create_asset, set_property, edit_container, call_function, evaluate_sequence (UE 5.8.2 scratch editor, 2026-10-04)"},{"id":"U538","fact":"**On 5.8 the bool bIncludeNestedObjects overloads of GetObjectsWithOuter, ForEachObjectWithOuterBreakable and ForEachObjectWithOuter are deprecated (C4996) in favor of an EGetObjectsFlags parameter (IncludeNestedObjects / None) whose default is IncludeNestedObjects; 5.3-5.7 have only the bool form, defaulting to true, and no EGetObjectsFlags.** Code for every engine: omit the argument when nested objects are wanted (the default on all of them); pass EGetObjectsFlags::None under MIF_ENGINE_AT_LEAST(5, 8) and false below it when they are not.","source":"5.8: UObjectHash.h:128 (GetObjectsWithOuter, EGetObjectsFlags), :130-131 (UE_INCLUDENESTEDOBJECTS_BOOL_DEPRECATED bool overload), :164 and :169-170 (ForEachObjectWithOuter); 5.7: UObjectHash.h has no EGetObjectsFlags (grep count 0); 5.3: UObjectHash.h:108 and :132 (bool bIncludeNestedObjects = true), read 2026-10-12","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE generic routes settled live - create_asset, set_property, edit_container, call_function, evaluate_sequence (UE 5.8.2 scratch editor, 2026-10-04)"},{"id":"U540","fact":"**A freshly spawned AWaterBodyLake's default spline winds counter-clockwise in UE world X/Y (positive signed area); a lake outline traced from an image (rows growing downward) arrives clockwise and should be reversed before it is set.** Whether the reversed winding is what stopped a large lake rendering is NOT confirmed (water still did not draw after the fix).","source":"measured 2026-10-13 on 5.8.2: EditorActorSubsystem.spawn_actor_from_class(WaterBodyLake) at (0,0,50000), its WaterSplineComponent's 3 world points give shoelace signed area +32,500,000 cm2; re-runnable via run_python","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE generic routes settled live - create_asset, set_property, edit_container, call_function, evaluate_sequence (UE 5.8.2 scratch editor, 2026-10-04)"},{"id":"U683","fact":"**mesh_uv_info held the game thread for minutes on stacked UVs, and the cost was the pair bookkeeping, not the geometry.** Before the fix, on a scratch mesh of 13000 triangles in one 1/64 UV cell: copy 31 ms, islands 1 ms, grid 0.3 ms, pairs 345 s, about 4 us a pair through a TSet of pair keys and the exact test together. 300000 triangles capped at 20000 in one cell: 1304 s. 2000 triangles each covering half the tile: 465 s, 8.4 billion set lookups for 2 million pairs, since each registers in 65 x 65 cells. After sweeping the cell ranges along U with no set: 4.8 s, 13.5 s and 0.14 s with the same overlapPairs on 24 scratch and engine meshes, read before and after. The exact test alone costs 49-108 ns a pair, slowest on triangles that clip to a real polygon.","source":"measured 2026-11-01 on a 5.8.3 scratch probe with temporary stage timers on the unchanged code, and without them on the final code, which also read the same on 5.5.4; tools/test_assets_physics_13.py A1303S","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE generic routes settled live - create_asset, set_property, edit_container, call_function, evaluate_sequence (UE 5.8.2 scratch editor, 2026-10-04)"},{"id":"U684","fact":"**TIntrTriangle2Triangle2::Find only ever clips Triangle1 smaller, so triangles whose 2D boxes do not meet share no area and need no exact test.** A polygon with no vertex on the positive side of a clip line is clipped away whole, which includes a Triangle0 collapsed to a point (zero normal, every test 0), so a collapsed triangle does not answer the other one whole.","source":"5.8: Runtime/GeometryCore/Public/Intersection/IntrTriangle2Triangle2.h:197-313; measured 2026-11-01 on 5.8.3 through mesh_uv_info with point-collapsed, line-collapsed and mirrored triangles in one mesh, the counts equal with and without the box test","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE generic routes settled live - create_asset, set_property, edit_container, call_function, evaluate_sequence (UE 5.8.2 scratch editor, 2026-10-04)"},{"id":"U540","fact":"**GeometryCore's 2D triangle-triangle intersection assumes counterclockwise vertex order:** its convex clip (ClipConvexPolygonAgainstLine) states the input is counterclockwise and keeps that order as an invariant, so a clockwise (mirrored) UV triangle reports no overlap. Orient each triangle counterclockwise before testing.","source":"5.8: Runtime/GeometryCore/Public/Intersection/IntrTriangle2Triangle2.h:197-200; measured 2026-10-13 on 5.8.2 and 5.3.2 (mesh_uv_info read 0 overlaps on mirrored triangles until oriented; tools/test_assets_physics_13.py, pushed 44b011de)","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE generic routes settled live - create_asset, set_property, edit_container, call_function, evaluate_sequence (UE 5.8.2 scratch editor, 2026-10-04)"},{"id":"U571","fact":"**The triangle lists Unreal returns (e.g. `ProceduralMeshLibrary.get_section_from_static_mesh(...)`'s `triangles`) wind front faces CLOCKWISE in Unreal's left-handed space. A normal computed as cross(p1 - p0, p2 - p0) with corners in the returned order (0, 1, 2) points INTO the surface. Take the corners as (0, 2, 1) to get the outward normal. A component with a negative-determinant (mirrored) transform reverses the winding again.** This adds to U570, which gave the array layout but not the winding.","source":"measured 2026-10-17 on a live UE 5.8.3 editor: with (0, 1, 2), every roof-level triangle of two imported static meshes (a building shell and a roll-up door) computed normal z = -1.0, and the door's floor-contact bottom faces, which face down in the Blender source (checked in Blender: 4 down-facing, 0 up-facing at z 0), computed as facing up","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE generic routes settled live - create_asset, set_property, edit_container, call_function, evaluate_sequence (UE 5.8.2 scratch editor, 2026-10-04)"},{"id":"U575","fact":"**An actor moved from editor Python with `set_actor_location` outside a transaction is not marked dirty: its external actor package (one file per actor) stays clean, a save writes nothing, and the move is lost on reload. `modify()` alone dirtied one of three. Inside `unreal.ScopedEditorTransaction`, with `modify(True)` and a real change, all three went dirty and saved.** The viewport shows the new position either way, so the loss is silent.","source":"Live on UE 5.8.3, 2026-10-17, one-file-per-actor level: three static mesh actors moved, `EditorLoadingAndSavingUtils.get_dirty_map_packages()` read after each attempt (0 dirty; 1 of 3 after modify(); 3 of 3 in a transaction), then a save reporting 3 actors. Engine source not read.","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE generic routes settled live - create_asset, set_property, edit_container, call_function, evaluate_sequence (UE 5.8.2 scratch editor, 2026-10-04)"},{"id":"U587","fact":"**`InputSettings.bEnableMouseSmoothing` (on by default in BaseInput.ini) also smooths Enhanced Input mouse actions: UEnhancedPlayerInput fills key values through `UPlayerInput::MassageAxisInput`, which runs `SmoothMouse` on MouseX / MouseY when the setting is on (Mouse2D is built from those).** Felt as the view coasting on after the mouse stops and judder on fast flicks.","source":"Engine source, UE 5.8: `Plugins/EnhancedInput/Source/EnhancedInput/Private/EnhancedPlayerInput.cpp` line 1264 (`KeyState->Value.X = MassageAxisInput(...)`); `Source/Runtime/Engine/Private/UserInterface/PlayerInput.cpp` lines 2344-2373. `Engine/Config/BaseInput.ini` line 5.","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE generic routes settled live - create_asset, set_property, edit_container, call_function, evaluate_sequence (UE 5.8.2 scratch editor, 2026-10-04)"},{"id":"U592","fact":"**Possessing a pawn snaps the controller's control rotation to that pawn's actor rotation (`APlayerController::OnPossess` calls `SetControlRotation(PawnToPossess->GetActorRotation())`; `AController::OnPossess` does the same).** A player camera that follows the control rotation turns to the new pawn's facing on every possess unless something restores it.","source":"Engine source, UE 5.8: `Source/Runtime/Engine/Private/PlayerController.cpp` line 893; `Source/Runtime/Engine/Private/Controller.cpp` line 381.","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE generic routes settled live - create_asset, set_property, edit_container, call_function, evaluate_sequence (UE 5.8.2 scratch editor, 2026-10-04)"},{"id":"U623","fact":"**Turning RendererSettings bEnableRayTracing on while the skin cache is off asks \"Ray Tracing requires enabling skin cache...\", and a Yes writes the skin cache setting into the project's DefaultEngine.ini itself; set_property answers it Yes (dialogsAnswered), so the write holds and the ini changes even with saveConfig none. With the skin cache on first no dialog is asked** (RS1901 on 5.3.2 and 5.8.3, 2026-10-18; 5.8 wrote `r.SkinCache.CompileShaders=1` under [/Script/Engine.RendererSettings]).","source":"5.3 `RendererSettings.cpp:188`; 5.8 `RendererSettings.cpp:267`","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE generic routes settled live - create_asset, set_property, edit_container, call_function, evaluate_sequence (UE 5.8.2 scratch editor, 2026-10-04)"},{"id":"U645","fact":"**In UE 5.8 a Python `AssetImportTask` reimport of an FBX over an existing StaticMesh (FbxImportUI, `save = False`, `replace_existing = True`) still wrote the mesh package to disk: the .uasset mtimes moved to the import time and `save_dirty_packages` afterwards listed only the placed actors, not the meshes.** Do not rely on `save = False` to keep a reimport unsaved.","source":"Live, 2026-10-20, UE 5.8.3: 17 meshes reimported via that task (a project reimport script); `ls` showed each .uasset at the import minute; MifBridge `save_dirty_packages dry_run` listed 17 actor packages and no mesh packages. Re-run: same task on any existing mesh, check mtime and the dirty list.","engine":"","ue":[],"bl":[],"section":"UE generic routes settled live - create_asset, set_property, edit_container, call_function, evaluate_sequence (UE 5.8.2 scratch editor, 2026-10-04)"},{"id":"U652","fact":"**In UE 5.8 `EWorldPartitionRuntimeCellState` is only forward-declared (`enum class EWorldPartitionRuntimeCellState : uint8;`) in `WorldPartition.h`, `WorldPartitionSubsystem.h` and `WorldPartitionLevelStreamingPolicy.h`; code that names a value such as `EWorldPartitionRuntimeCellState::Activated` (e.g. for `UWorldPartitionSubsystem::IsStreamingCompleted`) must include `WorldPartition/WorldPartitionRuntimeCell.h`, which defines it.**","source":"Engine source UE 5.8: `Engine/Source/Runtime/Engine/Public/WorldPartition/WorldPartitionRuntimeCell.h` line 202 (definition); `WorldPartition.h` line 60 (forward declaration). Build errors C2027/C2065 in a game module 2026-10-23, fixed by that include.","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE generic routes settled live - create_asset, set_property, edit_container, call_function, evaluate_sequence (UE 5.8.2 scratch editor, 2026-10-04)"},{"id":"U655","fact":"**`UPhysicsConstraintComponent::GetCurrentSwing1/Swing2/Twist()` return DEGREES: the component converts `FConstraintInstance::GetCurrentSwing2()` (radians, from `FPhysicsInterface`) with `FMath::RadiansToDegrees`. The ConstraintInstance getters themselves are radians.**","source":"5.8: Runtime/Engine/Private/PhysicsEngine/PhysicsConstraintComponent.cpp:1281-1297 (`GetCurrentTwist`, `GetCurrentSwing1`, `GetCurrentSwing2`), ConstraintInstance.cpp:1092-1105","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE generic routes settled live - create_asset, set_property, edit_container, call_function, evaluate_sequence (UE 5.8.2 scratch editor, 2026-10-04)"},{"id":"U657","fact":"**The engine's Git revision-control provider answers `IsCheckedOut()` true for EVERY tracked file (`return IsSourceControlled();`) and `UsesCheckout()` false, so a listing that counts checked-out files as pending lists the whole project on Git; on a provider without checkout a file is pending only when added, deleted, modified or conflicted.**","source":"5.8: Engine/Plugins/Developer/GitSourceControl/Source/GitSourceControl/Private/GitSourceControlState.cpp:190-193 and GitSourceControlProvider.cpp:286-289; live 2026-10-25 on a stock 5.8.3 probe whose root was a Git repository: list_source_control_changes listed every tracked .uasset and ini as checkedOut with nothing else set (tools/test_lane13_pipeline.py GIT100, red before the fix).","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE generic routes settled live - create_asset, set_property, edit_container, call_function, evaluate_sequence (UE 5.8.2 scratch editor, 2026-10-04)"},{"id":"U658","fact":"**The Git provider's Sync runs `git pull --rebase origin HEAD` over the whole repository, whatever file is named; it fails while a loaded package's linker holds that package's file (\"error: unable to unlink old '...uasset': Invalid argument\") and while the workspace has uncommitted changes (\"cannot pull with rebase: Your index contains uncommitted changes\"). Its error text goes to the Revision Control message log, not to the operation.**","source":"5.8: Engine/Plugins/Developer/GitSourceControl/Source/GitSourceControl/Private/GitSourceControlOperations.cpp:234-245 (FGitSyncWorker::Execute); live 2026-10-25, stock 5.8.3 probe editor log lines quoted; detaching the linkers first (ResetLoaders, as SourceControlHelpers::ApplyOperationAndReloadPackages does) let the pull replace the file (GIT108).","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE generic routes settled live - create_asset, set_property, edit_container, call_function, evaluate_sequence (UE 5.8.2 scratch editor, 2026-10-04)"},{"id":"U666","fact":"**From UE 5.5 the engine deletes the `[CoreRedirects]` config section from memory as soon as it has read the redirects at startup, so reading that section back through GConfig afterwards finds nothing; 5.3 and 5.4 keep it.** `ReadRedirectsFromIni` calls `GConfig->RemoveSectionFromBranch(RedirectSectionName, *IniName)` after parsing, under the comment that the data is no longer needed in memory; the call does not exist in the 5.3 or 5.4 file. A tool that lists a project's redirects on 5.5+ has to read the .ini files from disk (and the registered redirect list) instead. Found live by `core_redirects` returning an empty list on 5.5+.","source":"5.5: Engine/Source/Runtime/CoreUObject/Private/UObject/CoreRedirects.cpp:2205; 5.8: Engine/Source/Runtime/CoreUObject/Private/UObject/CoreRedirects.cpp:2877","engine":"5.5-5.8","ue":["5.5","5.6","5.7","5.8"],"bl":[],"section":"UE generic routes settled live - create_asset, set_property, edit_container, call_function, evaluate_sequence (UE 5.8.2 scratch editor, 2026-10-04)"},{"id":"U667","fact":"**`r.DebugSafeZone.TitleRatio` and `r.DebugActionZone.ActionRatio` default to 1.0, which means NO safe zone: on a platform without its own safe zone (the Windows editor), the display metrics report the full screen as safe, so a layout check that reads only these cvars flags nothing.** Both are FAutoConsoleVariableRef cvars whose help text states `default: 1.0`. A safe-zone check has to supply its own ratio (a TV-style 0.9 title / 0.95 action was chosen in `analyze_widget_layout`) unless a cvar has been set below 1.","source":"5.8: Engine/Source/Runtime/ApplicationCore/Private/GenericPlatform/GenericApplication.cpp:124, :127, :129, :132","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE generic routes settled live - create_asset, set_property, edit_container, call_function, evaluate_sequence (UE 5.8.2 scratch editor, 2026-10-04)"},{"id":"U671","fact":"**FBehaviorTreeDebuggerInstance::RootNode changes type between engines: a raw pointer on 5.5, a TWeakObjectPtr on the installed 5.6.1 and 5.7.** The same header's later RootNode fields belong to other structs.","source":"5.5 `AIModule/Classes/BehaviorTree/BehaviorTreeTypes.h` :224, :242; 5.6 same file :231, :249; 5.7 same file :231, :249","engine":"5.5-5.7","ue":["5.5","5.6","5.7"],"bl":[],"section":"UE generic routes settled live - create_asset, set_property, edit_container, call_function, evaluate_sequence (UE 5.8.2 scratch editor, 2026-10-04)","value":"raw `UBTCompositeNode*` on 5.5; `TWeakObjectPtr<UBTCompositeNode>` on 5.6.1 and 5.7"},{"id":"U679","fact":"**A groom flat along an axis kills the editor during import: the voxel grid has no cells on that axis and the next write is out of bounds.**","source":"UE 5.8 Engine/Plugins/Runtime/HairStrands/Source/HairStrandsCore/Private/GroomBuilder.cpp:3059; measured 2026-11-01","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE generic routes settled live - create_asset, set_property, edit_container, call_function, evaluate_sequence (UE 5.8.2 scratch editor, 2026-10-04)","value":"every point at Y 0: Array index out of bounds: 30 into an array of size 1, in VoxelizeCurves"},{"id":"U686","fact":"**FleshAsset's Dataflow pointer moves into DataflowInstance only in 5.8; 5.6 and 5.7 still hold DataflowAsset directly, with no GetDataflowAsset.** set_property DataflowInstance.DataflowAsset on 5.6 and 5.7 answers property not found; DataflowAsset takes it, tools/test_settle_routes.py RS702.","source":"UE 5.7 Engine/Plugins/Experimental/ChaosFlesh/Source/ChaosFleshEngine/Public/ChaosFlesh/FleshAsset.h:125; UE 5.8 Engine/Plugins/Experimental/ChaosFlesh/Source/ChaosFleshEngine/Public/ChaosFlesh/FleshAsset.h:168","engine":"5.6-5.8","ue":["5.6","5.7","5.8"],"bl":[],"section":"UE generic routes settled live - create_asset, set_property, edit_container, call_function, evaluate_sequence (UE 5.8.2 scratch editor, 2026-10-04)"}]