[{"id":"A4","fact":"A component property read taken **immediately after a blueprint compile can be stale**; restart and reload before concluding a per-instance override exists","source":"122 actors read `Unitless 12000` against differing templates; after an editor restart and fresh load the same reads returned the template values","engine":"","ue":[],"bl":[],"section":"MifBridge API contracts"},{"id":"A22","fact":"**`list_components` has two forms that do not share their parameter handling, and until 2026-09-10 the level-actor form accepted `includeNative`/`includeInherited` and ignored both in silence.** The `actorPath` branch returns at `MifBridgeComponents.cpp:1043`; the blueprint form reads those flags at `:1089`. The two forms also use DIFFERENT origin vocabularies: the blueprint form splits `ownSCS` / `parentSCS` / `native` (`:1137-1139`), while `MifComponentOrigin` (`:320-332`) answers `instance` / `blueprintCreated` / `native` for a placed actor - it collapses own-SCS and parent-SCS, which is why `includeInherited` is not answerable on an instance and now emits `includeInheritedWarning` instead of filtering. `includeNative` IS answerable and now filters; a `component` name lookup pins it true, as the blueprint form does at `:1088`. **[Reviewed 2026-09-21 against the code - STALE in part: actor_path includeInherited is answered (declaredBy) and filters; warning only when a class has no SCS (MifBridgeComponents.cpp:436-441, 455-466, 483-493)]**","source":"live, 5.3 probe port 8843, 2026-09-10. Before: true vs false on a real placed StaticMeshActor gave a byte-identical response (found by `tools/probe_inert_params_ue.py`, which diffs an op called both ways - no reader can see this, because the keys ARE read, on the other form). After: guarded by T2207 in `tools/test_instance_components.py`, 31 pass / 0 fail, asserting the native row drops, the instance row stays, `count` remains the actor's unfiltered total, and a name lookup still finds a native component. Fixed in commit 047c643.","engine":"","ue":[],"bl":[],"section":"MifBridge API contracts","status":"superseded"},{"id":"A28","fact":"**`delete_asset` reports `ok:true` and leaves a MODIFIED blueprint alive, wedging its path for the rest of the editor session.** Afterwards three endpoints disagree: `find_assets` and `delete_asset` say the asset is gone, while `create_blueprint` refuses with \"a Blueprint already exists\" and `list_variables` answers ok and lists its variables. A never-saved blueprint is also invisible to `save_package` (\"asset not found\"), so there is no way to save it and then delete it properly. Use a fresh path per run rather than trusting the teardown. **[Reviewed 2026-09-21 against the code - STALE in part: reports pathFreed:false, stillResident and the 'already exists' warning (MifBridgeAssetOps.cpp:229-243)]**","source":"5.7","engine":"","ue":[],"bl":[],"section":"MifBridge API contracts","value":"live, 5.7 probe (a scratch project) port 8848, 2026-09-14, isolated to ONE variable: a PLAIN blueprint created then deleted CAN be re-created (ok:true); the same blueprint with a single `add_variable` beforehand reports delete ok:true and then refuses re-creation with \"already exists\". On the wedged path: `find_assets{pathPrefix}` does not list it, `delete_asset` says \"no asset found at package\", `list_variables` answers ok with 5 variables. Nothing was ever on disk (`Content/_MifVarProbe` absent).","status":"superseded"},{"id":"A30","fact":"**`save_dirty_packages` with NO parameters SAVES every dirty content package** - the buckets are not opt-in, `content` defaults on, and there is no path filter of any kind (`path`, `packages` and `package` are each refused by name saying so). `dryRun:true` shows exactly the same set under `wouldSave`, omits `saved` entirely, and writes nothing. An untitled/transient map is SKIPPED with a reason naming save_level_as rather than counted as saved.","source":"5.7","engine":"","ue":[],"bl":[],"section":"MifBridge API contracts","value":"live, 5.7 probe port 8848, 2026-09-14. A bare `{}` call made during exploration wrote five scratch blueprints from earlier suites to `Content/_MifFnProbe`, `_MifVarProbe` and `_MifSaveProbe` - confirmed on the filesystem, then removed. The same state under `{dryRun:true}` lists them in `wouldSave` and leaves the directories absent. Re-runnable: `tools/test_ue_unsuited_endpoints.py` rows T4401-T4403, which check the DISK rather than the flag. See docs/01's postmortem on this endpoint writing into the UE install."},{"id":"A34","fact":"**A struct can be BREAKABLE and not MAKEABLE in a Blueprint, and the split is whether it has BP-visible members.** `add_break_struct` accepts Vector; `add_make_struct` refuses it with \"struct 'Vector' cannot be made in a Blueprint (no BP-visible members?)\". Unreal's rule, not this bridge's - those structs carry native make functions and expose nothing for K2Node_MakeStruct to fill - but the two endpoints look symmetric and are not. **[Reviewed 2026-09-21 against the code - STALE in part: the engine refuses Make for HasNativeMake and Break for HasNativeBreak symmetrically; the asymmetry is add_break_struct skipping CanBeBroken (K2Node_MakeStruct.cpp CanBeMade; K2Node_BreakStruct.cpp CanBeBroken; MifBridgeNodes2.cpp:235-238, 279-281)]**","source":"UE 5.7.4","engine":"","ue":[],"bl":[],"section":"MifBridge API contracts","value":"live UE 5.7.4, 2026-09-14, each tried by name. REFUSED by add_make_struct: Vector, Transform, Rotator, Vector2D, Timespan. ACCEPTED: LinearColor (pins R,G,B,A), SlateBrush (TintColor, DrawAs, Tiling, Mirroring, ImageSize, Margin, ResourceObj), Margin (Left, Top, Right, Bottom). add_break_struct on Vector gives X, Y, Z, so the asymmetry is real rather than a bad struct name.","status":"superseded"},{"id":"A39","fact":"**`ObjectTools::DeleteAssets` returning 1 does NOT mean the path came free, and the difference is whether the asset was MODIFIED after creation.** Measured side by side on one editor: a blueprint created and deleted leaves nothing - `list_variables` answers \"no package at ...\" and re-creation succeeds. The same blueprint with ONE `add_variable` first reports `deleted:true numDeleted:1`, the registry entry goes (`find_assets` returns no match), and the UObject is STILL AT ITS ORIGINAL PATH - `list_variables` answers `ok:true count:1` and `create_blueprint` refuses with \"a Blueprint already exists\". ~~The holder is the editor's transaction buffer: every mutating endpoint here opens an `FScopedTransaction`, so a modified asset is referenced by undo history and garbage collection cannot release it.~~ **WITHDRAWN 2026-09-15 - see A49.** Emptying the undo queue outright leaves the object alive, so the buffer is not the holder. The measurements in this row stand; this sentence was reasoned from the endpoint's own probe rather than tested, and the first test that could falsify it did. NOT a pending compile - `MarkBlueprintAsStructurallyModified` calls `FBlueprintCompilationManager::CompileSynchronously`, so nothing is left queued.","source":"5.3.2","engine":"","ue":[],"bl":[],"section":"MifBridge API contracts","value":"measured 2026-09-14 on the 5.8 probe (UE_5.8, a scratch project, port 8848) with `scratchpad/repro_delete_wedge.py`, control and subject arms in one run. Engine reading: `Editor/UnrealEd/Private/Kismet2/BlueprintEditorUtils.cpp`, `FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified`. Previously recorded as A28 from a 5.7 probe; this CONFIRMS it on 5.8 and identifies the discriminating variable, which A28 did not have. VERIFIED 2026-09-14 on a live 5.8 editor after the fix: the plain arm reports `pathFreed:true` with no warning and no survivors, the modified arm reports `pathFreed:false`, `stillResident:[\"/Game/MifScratch/MifVerifyVar.MifVerifyVar\"]` and a warning naming the transaction buffer - so the undo-history mechanism is confirmed by the endpoint's own probe (object serialization disabled, nothing else references it) rather than inferred from a comment."},{"id":"Mee7591","fact":"splice_into_exec on a K2Node_MacroInstance exec pin (e.g. a ForEachLoop's 'Completed') crashes the editor on an EdGraphPin OwningNode assert; wire it by hand instead with one disconnect_pin plus two connect_pins. The same endpoint had run nine times without incident against ordinary nodes in the same session (an event's then, four IfThenElse, a SwitchEnum).","source":"docs/01_POSTMORTEMS.md 2026-08-13 entry (:1761). The root-cause explanation there is explicitly labelled '(INFERRED - not read from the source)' and says it 'must be confirmed there' - the crash and the repro are measured, the mechanism is not. Cost recorded: a whole graph rewire lost because it compiled clean but was","engine":"","ue":[],"bl":[],"section":"MifBridge crash","value":"`Assertion failed: OwningNode [EdGraphPin.h Line 427] - MifBridge::SpliceExecAfter() MifBridgeCommon.cpp:2844, H_splice_into_exec() MifBridgeNodes.cpp:2018`"},{"id":"Mdff16f","fact":"MifBridge's shared pin-connect helper hardcodes the K2 schema CDO for CanCreateConnection, TryCreateConnection and BreakPinLinks, so any graph whose schema overrides them gets K2 semantics rather than its own. The fix is Graph->GetSchema() for all three calls.","source":"MifBridge Source/MifBridge/Private/MifBridgeCommon.cpp:3852-3855 (K2() returns GetDefault<UEdGraphSchema_K2>()) and :5082 (ConnectPinsChecked takes it), with BreakPinLinks :5101/:5111, CanCreateConnection :5122, TryCreateConnection :5128; the same hardcode also drives the splice helpers at :3663-3725 and :3759-3800. CO","engine":"","ue":[],"bl":[],"section":"MifBridge endpoint defects","value":"`const UEdGraphSchema_K2* Schema = K2(); → Graph->GetSchema()`"},{"id":"Mc05d59","fact":"Output pin names differ per node class and the bridge only redirects within an alias group when exactly one pin matches, so a node that genuinely has the pin you asked for is never redirected: add_format_text returns 'Result', add_get_data_table_row returns 'Out Row' (with a space), add_cast returns 'As<ClassName>'.","source":"docs/02_GOTCHAS.md §1 § Pin names (lines 340-356)","engine":"","ue":[],"bl":[],"section":"MifBridge parameter grammar","value":"`alias groups: ReturnValue / Result / Output / OutputPin | Array / OutArray | Out Row / OutRow / Row`"},{"id":"Mbb48eb","fact":"make_release.py refuses on seven checks, each printing REFUSING TO PACKAGE and exiting non-zero: README badge, CHANGELOG top row, a recorded successful 5.3 build for this Source/ commit, a fresh 5.7 compile probe, the describe_endpoint param table, value discovery, and the static audits. --record-53 deliberately runs BEFORE all seven, because recording a build that already happened must not be blocked by a stale badge.","source":"docs/14_RELEASE_AND_SYNC.md § What will stop a release - the seven-row gate table and the --record-53 ordering fix (2026-09-02). Two gates exist because a release already went out wrong: the 5.3 gate was added after v0.8.0 shipped not compiling on 5.3 at all, and the changelog gate after the UE column was found one too","engine":"","ue":[],"bl":[],"section":"MifBridge release gates"},{"id":"M55c010","fact":"The safety gate carries its own third classification and must NOT be written against IsReadOnlyEndpoint - that bucket answers 'does this need a transaction', and it contains save_package, save_blueprint, trigger_cook, start_pie, stop_pie, compile, validate, run_console, build_navmesh and export_asset.","source":"MifBridgeCommon.cpp - the ReadOnly TSet literal at :710-819 contains all ten names (verified by reading the set); the set's own comment at :760-762 says it is 81 names. LINE NUMBERS CORRECTED: docs/15's per-endpoint table (:489/:492/:559/:567/:598) is stale by ~200 lines. Both gate directions asserted by tools/test_saf","engine":"","ue":[],"bl":[],"section":"MifBridge safety gate","value":"`if (!IsReadOnlyEndpoint(Endpoint)) { refuse; } // WRONG - permits every save and PIE start`"},{"id":"M97c688","fact":"Using AsyncTask(ENamedThreads::GameThread, ...) for bridge work is forbidden: the named-thread queue is also pumped from FTickTaskSequencer::ReleaseTickGroup() -> WaitUntilTasksComplete(), so a compile-heavy endpoint reinstances actors mid-tick-group and the next FTickFunction hits the pure-virtual fatal, with no MifBridge frame on the stack.","source":"`EngineBaseTypes.h:409 = `ENGINE_API virtual void ExecuteTick(...) PURE_VIRTUAL(,);`. FORMULA CORRECTED: on 5.3 PURE_VIRTUAL expands to LowLevelFatalError(TEXT(\"Pure virtual not implemented (%s)\"), ...) (CoreMiscDefines.h:90), NOT `check(!\"Pure virtual not impl` | MifBridgeServer.cpp:427-444 (source comment 'Do NOT reach for AsyncTask'); engine 5.3.2 source  EngineBaseTypes.h:409 + CoreMiscDefines.h:90; docs/05_DESIGN_SPEC.md:183 marks the old AsyncTask pattern SUPERSEDED in those words | 5.3.2","engine":"","ue":[],"bl":[],"section":"MifBridge threading"},{"id":"M6faf47","fact":"RunEndpoint classifies every endpoint into exactly three transaction buckets: read-only (no transaction), self-managed (handler opens its own tight transactions because it runs a full compile), and everything else (one FScopedTransaction wrapping the whole handler). One test covers read-only and self-managed together, and it consults the external endpoint registry so a kr_* descriptor's bucket is honoured like a built-in's.","source":"MifBridgeCommon.cpp - IsReadOnlyEndpoint at :708, IsSelfManagedEndpoint at :845, IsCompileHeavyEndpoint at :1337; the three-way branch at :1569-1582. LINE NUMBERS CORRECTED: the candidate's :486/:623/:1078 (repeated in docs/15) are stale by ~220-260 lines.","engine":"","ue":[],"bl":[],"section":"MifBridge transactions","value":"`if (IsReadOnlyEndpoint(Endpoint) || IsSelfManagedEndpoint(Endpoint)) { run; return; } else FScopedTransaction Transaction(...); run;`"},{"id":"M8b9896","fact":"References require a prefix (object:, class:/subclassof:, softobject:, softclass:, interface:, enum:); containers go in a separate 'container' field; for container:'map' the 'type' field is the KEY and 'valueType' the VALUE, and map values cannot themselves be containers (Blueprint has no TMap<K,TArray<V>>).","source":"docs/02_GOTCHAS.md §2 (lines 378-402); the map value is stored in FEdGraphPinType::PinValueType and reported back by SerializePinType, so TMap<Name,int> and TMap<Name,bool> are distinguishable on read-back. Handled in MifBridgeCommon.cpp MakePinType :4587 (valueType recursion at :4739).","engine":"","ue":[],"bl":[],"section":"MifBridge type grammar","value":"`{ \"name\": \"ItemCounts\", \"type\": \"name\", \"container\": \"map\", \"valueType\": \"int\" }`"},{"id":"U62","fact":"**An hourly snapshot written BEFORE the per-tick effects pass records values that were never on screen.** `AdvanceClock` rebuilds the readings to the new hour's authored value, so a log line written above `ApplyFaultEffects` samples the meters before any live fault has pulled them. The authoritative timeline then disagrees with the game.","source":"measured 2026-09-18: a night with F8 biting from 01:00 to 06:00 logged `mod=88%,86%,84%,82%,78%` - the authored drift - while the audio was actually dead, and the automated playtest correctly flagged \"the effect is not biting\" about an effect that was working. Moving the log below `ApplyFaultEffects` gave `mod=0%` on every hour the fault was live, with the same fault, same cue and same 1 opening.","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE materials, actor identity and PIE"},{"id":"U72","fact":"**A `UMaterialExpressionFresnel`'s float properties are `Exponent` and `BaseReflectFraction`.** The `...In` names are INPUT PINS and are rejected as scalars.","source":"measured 2026-09-18: `add_material_expression` with `properties:{Exponent:2.2, BaseReflectFractionIn:0.32}` was refused with `ImportText (BaseReflectFractionIn): Missing opening parenthesis: 0.32` and the expression was not added. `BaseReflectFraction` took the same value without complaint.","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE materials, actor identity and PIE"},{"id":"U84","fact":"**A `batch` endpoint exists and `set_property` reports `batchable: true`**, so several property writes can in principle go in one round trip. **NOT VERIFIED for arbitrary asset properties:** `batch`'s own accepted parameters are `ops (array), blueprintId (alias: path), backup, compileAtEnd (default true)`, which reads blueprint-graph-scoped. Anyone relying on this to collapse a bulk property pass should test it on a throwaway asset first.","source":"read 2026-09-18 from `describe_endpoint {name:'batch'}` and `{name:'set_property'}` on 127.0.0.1:8860. Recorded as a contract READ, not a behaviour measured - the `blueprintId` parameter is the reason for the caveat and it has not been exercised against a StaticMesh.","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE materials, actor identity and PIE"},{"id":"U85","fact":"**`batch` DOES take non-blueprint ops, and `blueprintId` is optional.** Each entry in `ops` is `{op: <endpoint name>, ...that endpoint's own params}`, so N `set_property` writes against ASSET paths go in one round trip. **SUPERSEDES the caveat in U84**, which recorded this as unverified because every signal on the endpoint's surface pointed the other way - the guard lives in `MifBridgeNodes.cpp`, the params are `ops/blueprintId/backup/compileAtEnd`, and its own `commonMistakes` says \"graphId belongs on each op inside ops\". It reads blueprint-scoped and is not.","source":"measured 2026-09-18 on UE 5.8 by the MifBridge-owning session against its own probe editor: a `batch` of two `set_property` ops writing `NaniteSettings.bEnabled` on two different StaticMesh assets returned `ok:true, opCount:2` with both per-op results ok, and reading the property back OUTSIDE the batch showed both had gone false -> true. Note `propertyPath`, not `property` - `set_property` refuses the latter (U-series precedent). **Trap they hit and controlled for:** a first attempt returned \"object not found\", which reads exactly like batch resolving asset paths in a blueprint scope; the asset had actually been lost in an editor restart, and calling the same path OUTSIDE batch failed identically. Check a path resolves outside batch before concluding anything about batch.","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE materials, actor identity and PIE"},{"id":"U101","fact":"**An AActor's `Tags` array is reachable through the bridge: `edit_container` to grow it, then `set_property` on `Tags[0]`.** That makes tag-driven gameplay - \"every actor carrying this name is one of these\" - fully buildable without a C++ change or a Blueprint, because the C++ side only has to iterate `ActorHasTag`.","source":"measured 2026-09-19 on UE 5.8: `edit_container {propertyPath:\"Tags\", operation:\"resize\", newSize:1}` then `set_property {propertyPath:\"Tags[0]\", value:\"FaultLamp\"}` on a spawned `/Script/Engine.PointLight`, read back as `['FaultLamp']`, and the running game then logged `SiteJob: fault lamp LIT (1)` having found it by tag. A `PointLight`'s own settings go the same way - `PointLightComponent.Intensity`, `.LightColor` (as `{r,g,b,a}`), `.AttenuationRadius`, `.CastShadows`, `.bVisible` were all accepted and read back.","engine":"","ue":[],"bl":[],"section":"UE materials, actor identity and PIE"},{"id":"U282","fact":"**The Kismet compiler deletes every impure node that no execution flow reaches, and warns about none of it: the warning in `FGraphCompilerContext::PruneIsolatedNodes` is commented out with the reason \"having orphaned chains is standard workflow for LDs\".** Measured on 5.8.2: a function whose entry is not wired to its body (a lone PrintString), and a two-node orphan chain in the event graph, each compile with 0 errors and 0 warnings. The one warning is for a pruned node whose VALUE feeds a live node: \"Line Trace By Channel was pruned because its Exec pin is not connected, the connected value is not available and will instead be read as default\" (LineTraceSingle's ReturnValue wired into a reached Branch: 1 warning). So `compile` and `validate` cannot report dead code; `lint_blueprints` does.","source":"5.3 `KismetCompiler.cpp:2018`, 5.4 `KismetCompiler.cpp:2017`, 5.5 `KismetCompiler.cpp:2040`, 5.6 `KismetCompiler.cpp:2051`, 5.7 `KismetCompiler.cpp:2051`, 5.8 `KismetCompiler.cpp:2063`; 5.3 `EdGraphCompiler.cpp:190`, 5.4 `EdGraphCompiler.cpp:189`, 5.5 `EdGraphCompiler.cpp:204`, 5.6 `EdGraphCompiler.cpp:204`, 5.7 `EdGraphCompiler.cpp:204`, 5.8 `EdGraphCompiler.cpp:204`; live on the 5.8.2 probe","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE Blueprint compile - what it prunes without a word, and what lint_blueprints mirrors"},{"id":"U283","fact":"**The compiler's roots are function entries, events, timelines and nodes whose `IsNodeRootSet()` is true; the pass that runs before macro expansion also roots any impure K2 node with no input pins (input-key events, the entry tunnel of a macro or collapsed graph). Reachability then follows exec OUTPUT pins only (`FNodeVisitorDownExecWires`), so an impure node whose exec input nothing reaches is unreached even when its data inputs are wired.**","source":"5.3 `KismetCompiler.cpp:99`, 5.4 `KismetCompiler.cpp:98`, 5.5 `KismetCompiler.cpp:103`, 5.6 `KismetCompiler.cpp:104`, 5.7 `KismetCompiler.cpp:104`, 5.8 `KismetCompiler.cpp:110`; visitor 5.3 `KismetCompiler.cpp:1887`, 5.4 `KismetCompiler.cpp:1886`, 5.5 `KismetCompiler.cpp:1909`, 5.6 `KismetCompiler.cpp:1920`, 5.7 `KismetCompiler.cpp:1920`, 5.8 `KismetCompiler.cpp:1932`","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE Blueprint compile - what it prunes without a word, and what lint_blueprints mirrors"},{"id":"U284","fact":"**An unreached impure node WITHOUT an exec pin is kept, with a warning that its IsNodePure looks wrong - unless it is a tunnel (macro instance, collapsed graph) or cannot be created under the K2 schema (the source comment says anim nodes): those are pruned regardless.** So \"impure, unreached and carrying an exec pin\" is exactly the set every version deletes, which is the set `lint_blueprints`' unreachable_node reports.","source":"5.3 `KismetCompiler.cpp:2023`, 5.4 `KismetCompiler.cpp:2022`, 5.5 `KismetCompiler.cpp:2045`, 5.6 `KismetCompiler.cpp:2056`, 5.7 `KismetCompiler.cpp:2056`, 5.8 `KismetCompiler.cpp:2068`","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE Blueprint compile - what it prunes without a word, and what lint_blueprints mirrors"},{"id":"U285","fact":"**A disabled node is not compiled: the graph is cloned for compile without it, and each linked INPUT of it is patched through `GetPassThroughPin` to whatever its pass-through output fed. So a chain THROUGH a disabled node still runs, and a disabled node is never a root.** Measured with `lint_blueprints` on 5.8.2: CustomEvent -> [disabled PrintString] -> PrintString reports nothing; [disabled, unreached PrintString] -> PrintString reports the second node alone.","source":"5.3 `EdGraphUtilities.cpp:303`, 5.3 `EdGraphUtilities.cpp:320`, 5.4 `EdGraphUtilities.cpp:303`, 5.4 `EdGraphUtilities.cpp:320`, 5.5 `EdGraphUtilities.cpp:303`, 5.5 `EdGraphUtilities.cpp:320`, 5.6 `EdGraphUtilities.cpp:302`, 5.6 `EdGraphUtilities.cpp:319`, 5.7 `EdGraphUtilities.cpp:303`, 5.7 `EdGraphUtilities.cpp:320`, 5.8 `EdGraphUtilities.cpp:303`, 5.8 `EdGraphUtilities.cpp:320`; live on the 5.8.2 probe","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE Blueprint compile - what it prunes without a word, and what lint_blueprints mirrors"},{"id":"U286","fact":"**The BeginPlay, ActorBeginOverlap and Tick events a new Actor Blueprint starts with are disabled \"ghost\" nodes (`IsAutomaticallyPlacedGhostNode`: not user-set, and Disabled) - so they are neither empty handlers nor compiled.** Measured on 5.8.2: `create_blueprint` {parentClass: Actor} gives an EventGraph holding exactly those three K2Node_Event nodes, each read back `enabledBefore: disabled` by `set_node_state`; `lint_blueprints` reports 0 findings on it.","source":"5.3 `EdGraphNode.cpp:1001`, 5.4 `EdGraphNode.cpp:1010`, 5.5 `EdGraphNode.cpp:1054`, 5.6 `EdGraphNode.cpp:1071`, 5.7 `EdGraphNode.cpp:1127`, 5.8 `EdGraphNode.cpp:1127`; live on the 5.8.2 probe","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE Blueprint compile - what it prunes without a word, and what lint_blueprints mirrors"},{"id":"U287","fact":"**A Blueprint variable's Expose on Spawn and Private are METADATA keys - `ExposeOnSpawn` and `BlueprintPrivate` - on its variable description, not property flags. The Details checkboxes write only the key, and the compiler sets CPF_ExposeOnSpawn on the compiled property from the key's presence (with a warning if the variable is not Instance Editable).** So reading `FBPVariableDescription::PropertyFlags` alone reports every editor-ticked variable as not exposed - which `list_variables` did until 2026-09-23 (measured on 5.8.2: the key written the editor's way read `exposeOnSpawn: false` while `compile` warned \"Variable ... is marked as 'Expose on Spawn' but not marked as 'Instance Editable'\"). It now reads the key and reports `private`; `set_variable_flags` and `add_variable` write `private` as the checkbox does.","source":"5.3 `BlueprintDetailsCustomization.cpp:2051`, 5.4 `BlueprintDetailsCustomization.cpp:2148`, 5.5 `BlueprintDetailsCustomization.cpp:2200`, 5.6 `BlueprintDetailsCustomization.cpp:2201`, 5.7 `BlueprintDetailsCustomization.cpp:2201`, 5.8 `BlueprintDetailsCustomization.cpp:2202`; private 5.3 `BlueprintDetailsCustomization.cpp:2096`, 5.8 `BlueprintDetailsCustomization.cpp:2247`; compiler 5.3 `KismetCompiler.cpp:1264`, 5.4 `KismetCompiler.cpp:1263`, 5.5 `KismetCompiler.cpp:1286`, 5.6 `KismetCompiler.cpp:1298`, 5.7 `KismetCompiler.cpp:1298`, 5.8 `KismetCompiler.cpp:1310`; keys 5.3 `EdGraphSchema_K2.cpp:212`, 5.3 `EdGraphSchema_K2.cpp:223`, 5.8 `EdGraphSchema_K2.cpp:202`, 5.8 `EdGraphSchema_K2.cpp:214`","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE Blueprint compile - what it prunes without a word, and what lint_blueprints mirrors"},{"id":"U288","fact":"**An Enhanced Input mapping with no triggers - none on the mapping and none on its action - is Triggered on every tick its value is non-zero, which for a key is every tick it is held.** So two actions mapped to one key with no triggers both fire, every tick; `lint_blueprints`' input_key_duplicate reports exactly that and exempts a pair whose trigger sets differ (Tap on one, Hold on the other).","source":"5.3 `EnhancedPlayerInput.cpp:198`, 5.4 `EnhancedPlayerInput.cpp:208`, 5.5 `EnhancedPlayerInput.cpp:198`, 5.6 `EnhancedPlayerInput.cpp:188`, 5.7 `EnhancedPlayerInput.cpp:193`, 5.8 `EnhancedPlayerInput.cpp:241`","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE Blueprint compile - what it prunes without a word, and what lint_blueprints mirrors"},{"id":"U289","fact":"**UE 5.7 moved an Input Mapping Context's mappings: `Mappings` is deprecated for `DefaultKeyMappings.Mappings`, and per-profile `MappingProfileOverrides` replace the defaults while a Player Mappable Key Profile is active. `GetMappings()` returns the default mappings on every engine 5.3-5.8.** So an action mapped only in a profile override is still mapped, and a reader of `GetMappings()` alone misses it on 5.7+.","source":"5.7 `InputMappingContext.h:93`, 5.7 `InputMappingContext.h:101`, 5.7 `InputMappingContext.h:110`, 5.7 `InputMappingContext.h:216`, 5.8 `InputMappingContext.h:219`; 5.3 `InputMappingContext.h:53`, 5.6 `InputMappingContext.h:152`","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE Blueprint compile - what it prunes without a word, and what lint_blueprints mirrors"},{"id":"U290","fact":"**`AActor::MakeNoise` does nothing on a client or without an instigator (its own argument, or the actor's `GetInstigator()`), and reaches AI hearing only once a hearing sense object exists: the static noise delegate starts on the legacy PawnNoiseEmitterComponent path and is swapped to the perception system when a UAISense_Hearing instance is constructed.** So MakeNoise in a project that configures no hearing sense is heard by nothing but a PawnSensingComponent.","source":"5.3 `Actor.cpp:86`, 5.3 `Actor.cpp:2869`, 5.4 `Actor.cpp:89`, 5.4 `Actor.cpp:2864`, 5.5 `Actor.cpp:92`, 5.5 `Actor.cpp:2955`, 5.6 `Actor.cpp:102`, 5.6 `Actor.cpp:3338`, 5.7 `Actor.cpp:107`, 5.7 `Actor.cpp:3361`, 5.8 `Actor.cpp:109`, 5.8 `Actor.cpp:3388`; 5.3 `AISense_Hearing.cpp:77`, 5.8 `AISense_Hearing.cpp:77`","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE Blueprint compile - what it prunes without a word, and what lint_blueprints mirrors"},{"id":"U291","fact":"**The asset registry holds no dependency edges for a package that was never saved, so `get_referencers` on it answers 0 whatever references it.** Measured on 5.8.2: a new SaveGame Blueprint, and a new Actor Blueprint holding a compiled variable of that class; `get_referencers` on the SaveGame package gave count 0, packageSource inMemory, dependencyDataAvailable false. `lint_blueprints` therefore judges only saved SaveGame classes and counts the rest in its notes.","source":"live on the 5.8.2 probe, scratch assets","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE Blueprint compile - what it prunes without a word, and what lint_blueprints mirrors"},{"id":"A76","fact":"**`lint_blueprints` refuses instead of answering in part:** neither or both of pathPrefix and blueprintId; an unknown rule id (the error lists all 18); `rules` sent as a string; `path` (its note names pathPrefix); a folder holding nothing to lint (\"An empty scan is not a clean one\"); a folder holding more Blueprints than maxAssets (the error names the count, \"holds 9 Blueprints\"). `limit` caps only the returned rows - `truncated: true` and a note \"N of M findings\" - while every rule's count stays complete.","source":"`tools/test_lint_blueprints.py` BL100-BL172, 51/51 on the 5.8.2 probe in scratch and in full write mode","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE Blueprint compile - what it prunes without a word, and what lint_blueprints mirrors"},{"id":"A77","fact":"**`create_function` with no inputs or outputs makes a graph holding only its FunctionEntry node, whose `then` exec output is unlinked.** A body added to it runs only once something wires `then`.","source":"live on the 5.8.2 probe","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE Blueprint compile - what it prunes without a word, and what lint_blueprints mirrors"},{"id":"U294","fact":"**The Construct Object from Class node is `UK2Node_GenericCreateObject`; its base `UK2Node_ConstructObjectFromClass` is abstract and cannot be placed (Spawn Actor from Class and Create Widget derive from the same base).** So `add_k2_node` needs `K2Node_GenericCreateObject`, and a reader keyed on the base class sees all three.","source":"5.3 `K2Node_ConstructObjectFromClass.h:26`, 5.3 `K2Node_GenericCreateObject.h:17`, 5.4 `K2Node_ConstructObjectFromClass.h:26`, 5.5 `K2Node_ConstructObjectFromClass.h:26`, 5.6 `K2Node_ConstructObjectFromClass.h:26`, 5.7 `K2Node_ConstructObjectFromClass.h:28`, 5.8 `K2Node_ConstructObjectFromClass.h:28`, 5.8 `K2Node_GenericCreateObject.h:19`","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE Blueprint compile - what it prunes without a word, and what lint_blueprints mirrors"},{"id":"U295","fact":"**A new Blueprint whose parent is a Blueprint starts with a \"Parent: Construction Script\" node in its construction script, wired from the entry - a `UK2Node_CallParentFunction` whose function reference is an EXTERNAL member on the parent class, not self-context.** So a graph reader that skips only self-context calls counts every child Blueprint as calling into its parent; `blueprint_coupling` skips the node, since the `inherits` edge already names the relationship.","source":"5.3 `Kismet2.cpp:623`, 5.4 `Kismet2.cpp:615`, 5.5 `Kismet2.cpp:615`, 5.6 `Kismet2.cpp:615`, 5.7 `Kismet2.cpp:626`, 5.8 `Kismet2.cpp:632`; live on the 5.8.2 probe","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE Blueprint compile - what it prunes without a word, and what lint_blueprints mirrors"},{"id":"A78","fact":"**`blueprint_coupling` names 23 mechanisms and leaves out what is not coupling:** self-context references (own and inherited members), disabled nodes, a child's Parent call, and class pins that are wired rather than set (counted as `wiredClassPins`). Each edge has count, detail and up to `sitesPerEdge` sites (graphId + full nodeGuid); `targets[]` ranks by distinct sources, then sites; blueprintId mode reads outgoing edges only and says so; the scope refusals are `lint_blueprints`' (one helper).","source":"`tools/test_blueprint_coupling.py` 38/38 on the 5.8.2 probe: 22 mechanisms planted once each and read exactly, five near misses read as nothing","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE Blueprint compile - what it prunes without a word, and what lint_blueprints mirrors"},{"id":"A79","fact":"**`add_function_call` on a Blueprint interface's function places an interface MESSAGE unless `asMessage: false` is sent; with it, a direct interface call (a `UK2Node_CallFunction`), and the response notes it will fail at runtime on a target that does not implement the interface.** Without the flag, two placements both came back as messages.","source":"live on the 5.8.2 probe","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE Blueprint compile - what it prunes without a word, and what lint_blueprints mirrors"},{"id":"A80","fact":"**`delete_asset` on an ActorComponent Blueprint whose dispatcher a component-bound event named removes its registry entry and leaves the object resident, held by a GCObjectReferencer (3 references), so the call reports `stillResident` and the path stays taken for the session.** Its referencing Blueprint was deleted first and the undo history cleared.","source":"live on the 5.8.2 probe, three runs of `tools/test_blueprint_coupling.py`","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE Blueprint compile - what it prunes without a word, and what lint_blueprints mirrors"},{"id":"A81","fact":"**`find_references` resolves its target before it searches and matches by owner identity:** a class it cannot find, a member the class does not have (the refusal lists the ones it has) or a missing asset is refused by name, never answered with zero sites; a read through a child class counts as a use of the parent's member, and a same-named member declared on another Blueprint does not; engine functions resolve too (GameplayStatics / SetGamePaused); disabled nodes are skipped; each site carries every unwired input pin's literal.","source":"`tools/test_find_references.py` 19/19 on the 5.8.2 probe","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE Blueprint compile - what it prunes without a word, and what lint_blueprints mirrors"},{"id":"A82","fact":"**`list_nodes` marks `pruned: true` exactly on the impure nodes compile deletes and always reports `prunedCount`; `describe_behavior_tree` gives each composite `children` and `empty: true` at zero, plus `emptyComposites`; `list_dispatchers` adds `ownCalls`/`ownBinds`.** Measured: a two-node orphan chain and a live node behind a disabled, unreached head are marked (3), the disabled nodes and a chain through a disabled node are not, an unwired function body gives 1 and a wired one 0; an empty root selector reads children 0 / empty / emptyComposites 1, a tree with no root 0; dispatchers called once, bound once and unused read (1,0), (0,1), (0,0).","source":"`tools/test_lint_blueprints.py` BL117, BL121, BL151 on the 5.8.2 probe (57/57)","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE Blueprint compile - what it prunes without a word, and what lint_blueprints mirrors"},{"id":"U296","fact":"**An actor whose class defaults set Auto Receive Input (any value but Disabled) enables input on itself: `AActor::PreInitializeComponents` calls `EnableInput` on that player's controller, or, when the controller does not exist yet, registers the actor with the persistent level, which enables it when that controller initializes its input system (a pawn in the same list is possessed instead).** So a legacy input event in such an actor fires with no Enable Input call anywhere in its graphs; `input_bindings_report` reports its owner as `autoReceiveInput` and does not mark it needsEnableInput. `AutoReceiveInput` is public on every engine.","source":"5.3 `Actor.cpp:5768`, 5.3 `Actor.cpp:5775`, 5.3 `Actor.h:729`, 5.3 `Level.cpp:4370`, 5.3 `PlayerController.cpp:701`, 5.4 `Actor.cpp:5812`, 5.4 `Actor.cpp:5819`, 5.4 `Actor.h:745`, 5.4 `Level.cpp:4577`, 5.4 `PlayerController.cpp:739`, 5.5 `Actor.cpp:5940`, 5.5 `Actor.cpp:5947`, 5.5 `Actor.h:805`, 5.5 `Level.cpp:4693`, 5.5 `PlayerController.cpp:753`, 5.6 `Actor.cpp:6545`, 5.6 `Actor.cpp:6552`, 5.6 `Actor.h:835`, 5.6 `Level.cpp:4962`, 5.6 `PlayerController.cpp:735`, 5.7 `Actor.cpp:6558`, 5.7 `Actor.cpp:6565`, 5.7 `Actor.h:848`, 5.7 `Level.cpp:4989`, 5.7 `PlayerController.cpp:776`, 5.8 `Actor.cpp:6632`, 5.8 `Actor.cpp:6639`, 5.8 `Actor.h:877`, 5.8 `Level.cpp:5001`, 5.8 `PlayerController.cpp:772`","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE Blueprint compile - what it prunes without a word, and what lint_blueprints mirrors"},{"id":"A83","fact":"**`input_bindings_report` reads a folder's whole input setup in one call and refuses rather than answering in part:** `blueprintId` is refused (the refusal says pathPrefix) and a folder with nothing in it fails. Measured on one fixture: 1 context, 5 mappings, 7 actions; one duplicate key (E fires IA_Alt and IA_Fire, no triggers on either); every status once (ok 4, unconsumed 1, unmapped 1, unused 1); IA_Move mapped in IMC_A on W and handled by one event node; Add Mapping Context read with IMC_A at priority 1 and Remove Mapping Context with IMC_A and no priority (it has no such pin); three legacy events - in a Character (`pawn`), in a plain Actor (`other`, needsEnableInput, the only one flagged) and in an Actor whose class defaults set Auto Receive Input Player 0 (`autoReceiveInput`, not flagged - U296); `limit: 1` caps both lists, each with Truncated true and its total complete (3 and 2). A class default written by `set_property` on `<package>.Default__<Name>_C` survives a compile (read back Player0 after it).","source":"`tools/test_input_bindings_report.py` 14/14 on the 5.8.2 probe","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE Blueprint compile - what it prunes without a word, and what lint_blueprints mirrors"},{"id":"U297","fact":"**The KeyName pin of a Blackboard component's Get/Set Value as X and Clear Value calls is a BY-REFERENCE pin, and the K2 schema refuses a literal default on a by-reference pin unless the function marks it AutoCreateRefTerm - so the Key Name is always wired, typically from Make Literal Name or a Name variable.** Measured on 5.8.2: those KeyName pins read isReference true and set_pin_default 'PatrolPoint' was refused, the pin left at None. The C++ signature does not predict it: SaveGameToSlot's `const FString& SlotName` reads isReference false and takes a literal. `ConvertPropertyToPinType` sets bIsReference only for a parameter carrying both `CPF_OutParm` and `CPF_ReferenceParm`, so measure the pin. `ai_setup_report` reads the key name one hop up the wire.","source":"5.3 `BlackboardComponent.h:157`, 5.3 `EdGraphSchema_K2.cpp:3052`, 5.3 `EdGraphSchema_K2.cpp:3685`, 5.4 `EdGraphSchema_K2.cpp:3018`, 5.4 `EdGraphSchema_K2.cpp:3651`, 5.5 `EdGraphSchema_K2.cpp:3006`, 5.5 `EdGraphSchema_K2.cpp:3639`, 5.6 `EdGraphSchema_K2.cpp:3028`, 5.6 `EdGraphSchema_K2.cpp:3661`, 5.7 `EdGraphSchema_K2.cpp:3024`, 5.7 `EdGraphSchema_K2.cpp:3673`, 5.8 `BlackboardComponent.h:157`, 5.8 `EdGraphSchema_K2.cpp:2993`, 5.8 `EdGraphSchema_K2.cpp:3642`; live on the 5.8.2 probe","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE Blueprint compile - what it prunes without a word, and what lint_blueprints mirrors"},{"id":"U298","fact":"**`UBlackboardComponent::SetValue` returns false, and `GetValue` returns the type's InvalidValue, when the blackboard has no such key or the key's type CLASS is not exactly the call's (a subclass does not match) - and Set Value as X discards that bool, so a misspelled key name, or a Vector write to an Object key, does nothing and reports nothing.** `ai_setup_report` lists both as keyUseProblems (unknownKey, typeMismatch).","source":"5.3 `BlackboardComponent.h:335`, 5.3 `BlackboardComponent.h:389`, 5.3 `BlackboardComponent.h:391`, 5.3 `BlackboardComponent.cpp:735`, 5.4 `BlackboardComponent.h:335`, 5.5 `BlackboardComponent.h:335`, 5.6 `BlackboardComponent.h:345`, 5.6 `BlackboardComponent.h:399`, 5.7 `BlackboardComponent.h:345`, 5.8 `BlackboardComponent.h:348`, 5.8 `BlackboardComponent.h:402`, 5.8 `BlackboardComponent.h:404`, 5.8 `BlackboardComponent.cpp:751`","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE Blueprint compile - what it prunes without a word, and what lint_blueprints mirrors"},{"id":"U299","fact":"**Every blackboard asset gets a persistent SelfActor key (Object, base class Actor) while the AI system's `bAddBlackboardSelfKey` is on, and `AAIController` writes its pawn into that key in `SetPawn` and `InitializeBlackboard` - so SelfActor is used even when no graph or tree names it.** Measured on 5.8.2: a new BlackboardData held SelfActor before any key was added; a second one, created and then given a Parent through set_property, held no SelfActor of its own. `ai_setup_report` gives the key status engine.","source":"5.3 `BlackboardKey.h:9`, 5.3 `BlackboardData.cpp:28`, 5.3 `AISystem.h:89`, 5.3 `AIController.cpp:556`, 5.3 `AIController.cpp:1024`, 5.4 `AIController.cpp:557`, 5.4 `AIController.cpp:1023`, 5.5 `AIController.cpp:557`, 5.5 `AIController.cpp:1077`, 5.6 `AIController.cpp:564`, 5.6 `AIController.cpp:1084`, 5.7 `AIController.cpp:564`, 5.7 `AIController.cpp:1084`, 5.8 `BlackboardKey.cpp:5`, 5.8 `BlackboardData.cpp:28`, 5.8 `AIController.cpp:583`, 5.8 `AIController.cpp:1103`; live on the 5.8.2 probe","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE Blueprint compile - what it prunes without a word, and what lint_blueprints mirrors"},{"id":"U300","fact":"**`UAISenseConfig::GetMaxAge()` returns FAIStimulus::NeverHappenedAge when the stored MaxAge is 0 - which means stimuli never expire - and `IsEnabled()` is deprecated from 5.4 for `GetStartsEnabled()`, which 5.3 does not have.** So a reader spanning 5.3-5.8 reads `MaxAge` and `bStartsEnabled` by reflection, as `ai_setup_report` does.","source":"5.3 `AISenseConfig.h:46`, 5.4 `AISenseConfig.h:48`, 5.4 `AISenseConfig.h:51`, 5.5 `AISenseConfig.h:48`, 5.6 `AISenseConfig.h:49`, 5.6 `AISenseConfig.h:51`, 5.7 `AISenseConfig.h:51`, 5.8 `AISenseConfig.h:49`, 5.8 `AISenseConfig.h:51`, 5.8 `AISenseConfig.h:54`","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE Blueprint compile - what it prunes without a word, and what lint_blueprints mirrors"},{"id":"A84","fact":"**`ai_setup_report` reads a folder's AI setup in one call and refuses rather than answering in part:** blueprintId and path are refused (the refusals name pathPrefix and the one-asset readers) and an empty folder fails. Measured on one fixture: 2 controllers; 1 perception component with sight (SightRadius 1500 read back as 1500.000000, enabled, dominant) and hearing; 1 pawn naming its controller (auto-possess PlacedInWorldOrSpawned) while a pawn on the default AIController is listed under none; 2 Run Behavior Tree sites; a tree on a child blackboard with 2 composites, 2 tasks, 1 decorator aborting Self, the empty selector reported as 'Root > Empty' and one missing key (Ghost), while a key the PARENT blackboard declares resolves; 6 keys (SelfActor engine, 2 unused); 3 judged Blueprint key calls - a Name variable's default resolves, an Object key under SetValueAsVector is typeMismatch, a misspelled Make Literal Name is unknownKey - and a runtime-computed name counted, not judged; 3 stimulus sites, the damage report the only one no sense perceives; a Blueprint task of 4 nodes large at largeNodes 4 and a service of 1 not; limit 1 caps the three site lists with Truncated true and full totals, and usesPerKey 0 empties the per-key lists with their counts intact.","source":"`tools/test_ai_setup_report.py` 22/22 on the 5.8.2 probe","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE Blueprint compile - what it prunes without a word, and what lint_blueprints mirrors"},{"id":"A85","fact":"**capture_camera before and after releasing its render target in the call (ReleaseRenderTargetNow):** 200 captures at 1920x1080 after 20 warm-up shots, get_perf_stats memory.usedVirtualMB sampled every 25 - the old code's slope 1.100 frames per call and +1,723 MB; the fixed code's 0.021 frames per call and +9 MB (a second run from a freshly started editor, -0.293 and -641 MB, as it shed startup memory). render_thumbnail of /Engine/BasicShapes/Cube still renders the cube after the same release, and preview_widget and preview_composite_widget pass test_ue_preview_params 13/13. Confirmed independently on the VFX gallery editor (5.8.2, 2560x1440, rebuilt from 0c0c7f9e): 300 calls, usedVirtualMB read every 50 - 4974 before, then 5342, 5370, 5582, 5344, 5647, 5590 - +368 MB of warm-up in the first 50, then about 1.0 MB a call (0.07 of a frame) that rises and falls, where the old code grew 14.9 MB a call.","source":"`tools/test_capture_memory.py` 3/3 on the fixed 5.8.2 probe, 1/3 on the old code; the VFX session's gallery measurement, 2026-09-23","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE Blueprint compile - what it prunes without a word, and what lint_blueprints mirrors"},{"id":"U302","fact":"**The engine's periodic garbage collection runs only while a world has begun play: `UEngine::ConditionalCollectGarbage`, called at the end of every world tick, purges on its timer inside `if (bHasAWorldBegunPlay)`, and the editor world never begins play - so in the editor outside PIE, an unreferenced object and its render resources stay until something forces a collection.** Measured on 5.8.2: 100 capture_camera calls left 791 MB committed, 70 s of idle returned none of it, and `obj gc` returned 84%. An endpoint that allocates per call must release in the call.","source":"5.3 `UnrealEngine.cpp:1814`, 5.3 `UnrealEngine.cpp:1831`, 5.3 `LevelTick.cpp:1716`, 5.4 `UnrealEngine.cpp:1849`, 5.5 `UnrealEngine.cpp:1942`, 5.6 `UnrealEngine.cpp:1959`, 5.7 `UnrealEngine.cpp:1960`, 5.8 `UnrealEngine.cpp:2263`, 5.8 `UnrealEngine.cpp:2287`, 5.8 `LevelTick.cpp:1970`; live on the 5.8.2 probe","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE Blueprint compile - what it prunes without a word, and what lint_blueprints mirrors"},{"id":"U303","fact":"**`SaveGameToSlot` writes the SaveGame object through `SaveGameToMemory` - a persistent FMemoryWriter wrapped in an FObjectAndNameAsStringProxyArchive that never sets ArIsSaveGame - and `FProperty::ShouldSerializeValue` drops a property for lack of the SaveGame flag only when ArIsSaveGame IS set, while it drops a Transient property whenever the archive is persistent. So every variable of a SaveGame class is saved, SaveGame checkbox or not, except Transient ones, which never are - and on any other class the checkbox is read only by a C++ archive that sets ArIsSaveGame.** `save_schema_report` marks transient fields and lists the checkbox outside SaveGame classes.","source":"5.3 `GameplayStatics.cpp:2355`, 5.3 `GameplayStatics.cpp:2361`, 5.3 `UObject/Property.cpp:1176`, 5.3 `UObject/Property.cpp:1188`, 5.4 `GameplayStatics.cpp:2362`, 5.4 `UObject/Property.cpp:1174`, 5.5 `GameplayStatics.cpp:2362`, 5.5 `UObject/Property.cpp:1243`, 5.6 `GameplayStatics.cpp:2374`, 5.6 `UObject/Property.cpp:1103`, 5.7 `GameplayStatics.cpp:2381`, 5.7 `UObject/Property.cpp:1034`, 5.8 `GameplayStatics.cpp:2375`, 5.8 `GameplayStatics.cpp:2381`, 5.8 `UObject/Property.cpp:1052`, 5.8 `UObject/Property.cpp:1064`","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE Blueprint compile - what it prunes without a word, and what lint_blueprints mirrors"},{"id":"A86","fact":"**`save_schema_report` reads a folder's save data in one call and refuses rather than answering in part:** blueprintId is refused (the refusal says pathPrefix) and an empty folder fails. Measured on one fixture: 2 SaveGame classes, 13 fields; a vector-keyed map is floatKey and a name-keyed one is not; Slot1..Slot4 are one numbered group while a lone Level2 is none, and numberedMin 5 finds none; the one Transient field is marked; a held HitResult expands into its members (Location among them) while a Transform stays a leaf; statuses read Score ok (set in one Blueprint, got in another), PlayerName readOnly, Slot1 writeOnly and 10 unused; SG_Main's users are a cast and a get in one Blueprint and a CreateSaveGameObject class pin and a set in the other, and the class nothing uses is unreferenced; 6 slot calls - Main saved once and loaded twice (a literal and a String variable's default), Backup neverLoaded, Legacy checked through Make Literal String neverSaved, and a runtime-built name counted, not judged; the SaveGame checkbox on two actor variables listed, an unchecked one not; limit 1 caps both site lists with Truncated true and full totals. The folder reports' shared PinValueThroughWire leaves ai_setup_report at 22/22.","source":"`tools/test_save_schema_report.py` 19/19 on the 5.8.2 probe","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE Blueprint compile - what it prunes without a word, and what lint_blueprints mirrors"},{"id":"A87","fact":"**`ui_flow_report` reads a folder's UI flow in one call and refuses rather than answering in part:** blueprintId is refused (the refusal says pathPrefix) and an empty folder fails. Measured on one fixture: 4 widgets; W_HUD created and added by the player controller once each, its ScoreText.Text binding, a wired Tick and BP_Player's Health among its reads; W_Pause's own unwired Remove from Parent attributed to W_Pause; a widget only placed in another's tree is not neverCreated, and one nothing creates is; a Remove from Parent on a generic UserWidget variable counted unattributed; one flow call of each kind with pause true, time dilation 0.5 and the mouse cursor true read back. A new Widget Blueprint already holds ghost Event PreConstruct, Construct and Tick nodes, so add_override_event refuses Tick - and wiring the ghost Tick makes it an enabled, compiled event the report counts.","source":"`tools/test_ui_flow_report.py` 15/15 on the 5.8.2 probe","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE Blueprint compile - what it prunes without a word, and what lint_blueprints mirrors"},{"id":"A88","fact":"**`graph_metrics` counts a graph the way compile sees it, and refuses rather than answering in part:** neither pathPrefix nor blueprintId is refused and an empty folder fails. Measured on one Actor Blueprint: the event graph holds 5 logic nodes, the 2 ghost events left unwired are disabled and not logic, one comment box over 2 of the 5 reads coverage 0.4, one spliced reroute is a reroute and not logic, one unreached Print String is pruned, one Delay, and one wire to a node 5,600 units away is the only long wire (longWire 10000 finds none); BeginPlay reaches 3 nodes - through the reroute, not counting it, not the orphan; a function whose entry is unwired with a node in its body reads unwiredEntry and 1 pruned, one wired to its one node reaches 1; the construction script is a fourth graph holding only its entry, so functions read [1, 2, 2] and event graphs [5]; list_nodes' metrics equal the row's.","source":"`tools/test_graph_metrics.py` 15/15 on the 5.8.2 probe","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE Blueprint compile - what it prunes without a word, and what lint_blueprints mirrors"},{"id":"GR1","fact":"**`gameplay_recipe` builds in plan order and stops at the first failure - a compile whose numErrors is not 0, a graph patch with a failed operation, an answer without ok:true - then deletes every asset it created, newest first, and removes a trace channel it added by writing the array back as it read it.** The channel is added in ONE whole-array `set_property`, so a refused write (saveConfig outside full write mode) leaves the array as it was. A delete the editor's undo history holds answers stillResident: the registry entry goes, the path stays taken until a restart.","source":"measured: tools/test_gameplay_recipes_offline.py G104, G106 (a recording fake editor); tools/test_gameplay_recipes_live.py G206 on 5.8.2 (a planted bad function name: 3 assets deleted, all 3 still resident, the channel removed)","engine":"5.8","ue":["5.8"],"bl":[],"section":"Gameplay recipes - whole systems built from verified calls"},{"id":"GR2","fact":"**`gameplay_recipe`'s interaction recipe works as built: 109 steps, four Blueprints compiling with 0 errors and 0 warnings and linting clean; in PIE an interactable in trace range takes focus, shows its prompt with its own PromptText and takes TryInteract with the owning pawn as instigator, while a mesh that ignores the channel, one that blocks it without the interface, and the interactable beyond trace_distance take none.** Trace distances 120 and 80 hold and drop a face 100 away, and the trace is on the recipe's channel alone.","source":"measured: tools/test_gameplay_recipes_live.py G200-G204 and G210-G217 on 5.8.2 (a scratch project, port 8871); a planted wrong channel (TraceTypeQuery1) failed G203 and G217 and nothing else","engine":"5.8","ue":["5.8"],"bl":[],"section":"Gameplay recipes - whole systems built from verified calls"},{"id":"GR3","fact":"**`gameplay_recipe`'s save recipe works as built: 66 steps, the record struct and four Blueprints compiling with no error, warning or note and linting clean; in PIE two placed saveables get distinct ids that an editor move keeps, SaveToSlot writes the slot, a load puts a moved actor back where it was saved and leaves the other where IT was saved, a missing slot answers Loaded false and moves nothing, and a second PIE session restores the placed actor from the first session's slot.** An actor spawned at run time is not restored: its id is new each spawn.","source":"measured: tools/test_gameplay_recipes_live.py G230-G239 on 5.8.2 (a scratch project, port 8871); planted defects - the id made on every construction, ReadState not moving the actor, one key for every record - each failed G234, G236 or G238","engine":"5.8","ue":["5.8"],"bl":[],"section":"Gameplay recipes - whole systems built from verified calls"},{"id":"GR7","fact":"**`gameplay_recipe`'s inventory recipe works as built: 205 steps, five Blueprints compiling with no error, warning or note and linting clean - OnInventoryChanged broadcast by AddItem and RemoveItem and heard by the grid; in PIE with a capacity of 3 stacks, 7 potions (max 5) go in as 5 and 2, 2 keys (max 1) add 1, 4 more potions add 3 and make 5 and 5 (CountOf 10), removing 6 leaves 4 and the key, removing 10 takes the 4, no definition or a count of 0 adds nothing, and the bound grid shows one slot per stack after every change, its label the stack's count.**","source":"measured: tools/test_gameplay_recipes_live.py G280-G289, 134/134 on 5.8.2; tools/test_gameplay_recipes_offline.py 108/108","engine":"5.8","ue":["5.8"],"bl":[],"section":"Gameplay recipes - whole systems built from verified calls"},{"id":"U328","fact":"**A Blueprint name may hold a space: the engine's own editor scripting renames a function graph to \"F Other\" (`BlueprintEditorLibrary.rename_graph`), while MifBridge's authoring endpoints accept only identifiers (`^[A-Za-z_][A-Za-z0-9_]*$`) - so a lint for such names is fed by the editor or the engine, never by the bridge.**","source":"measured on 5.8.2: rename_graph through exec_console `py` renamed F_Other to \"F Other\" and list_graphs read it back; add_variable refused \"My Var\" with the identifier rule","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE Blueprint lint - design, size and style judgments"},{"id":"U329","fact":"**The compiler accepts a Blueprint function whose entry carries no access specifier at all (`FUNC_AccessSpecifiers` bits 0) without a warning, beside public, protected and private - so no access bits is a fourth state a reader has to treat as unrestricted, not as an error.**","source":"5.3 `KismetCompiler.cpp:2477-2483`, 5.8 `KismetCompiler.cpp:2527-2533`","engine":"5.3, 5.8","ue":["5.3","5.8"],"bl":[],"section":"UE Blueprint lint - design, size and style judgments"},{"id":"A97","fact":"**`lint_blueprints`' design, size and style rules report each planted violation and none of its near misses: legacy input in an actor and an auto-input actor but not a pawn; an interface input typed to a Blueprint class but not to Actor; a vector-keyed SaveGame map but not a name-keyed one; a variable owner read by 3 Blueprints over a limit of 2 but not one read by 2; player state set by two Blueprints, then only outside a designated owner; a function copied by duplicate_asset but not one with a node more; each size limit exceeded by one Blueprint and met exactly by another; the style rules only when named; and a clean fixture with no finding over a non-zero node count.**","source":"measured: tools/test_lint_design_rules.py, 22/22 on 5.8.2; tools/test_lint_blueprints.py still 57/57","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE Blueprint lint - design, size and style judgments"},{"id":"A99","fact":"**`interface_concrete_type` passes a SaveGame and a data asset class through an interface without a finding and still reports a Blueprint pawn class: the save recipe's BPI_Saveable, whose WriteState and ReadState take its SaveGame class, lints clean; before the exemption it drew two findings, one per function.**","source":"measured: tools/test_lint_design_rules.py LD122, 22/22 on 5.8.2; tools/test_gameplay_recipes_live.py G232, 70/70 on 5.8.2 (69/70 before)","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE Blueprint lint - design, size and style judgments"},{"id":"U330","fact":"**A class variable's default value that names a Blueprint class is a package dependency the asset registry records, though nothing in the holder's graphs, variable types, components or parent names that class: BP_Holder, whose one variable is typed Actor class with BP_Target's class as its default, depends on BP_Target once saved.**","source":"measured on 5.8.2: project_dependency_graph over a saved and rescanned fixture folder returned the edge BP_Holder -> BP_Target, and blueprint_coupling's mechanisms found none for it (tools/test_endpoint_additions_d.py ED154)","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE Blueprint reads - listeners, implementers, handlers, key usage and dependency mechanisms"},{"id":"U331","fact":"**An interface message and a call through an interface-typed reference are both call-function nodes whose target function belongs to the interface: `UK2Node_Message` derives from `UK2Node_CallFunction`, so one reader counts both.**","source":"5.3 and 5.8 `K2Node_Message.h:23` (`class UK2Node_Message : public UK2Node_CallFunction`); measured on 5.8.2: two messages and one reference call to Talk counted as 3 (tools/test_endpoint_additions_d.py ED122)","engine":"5.3, 5.8","ue":["5.3","5.8"],"bl":[],"section":"UE Blueprint reads - listeners, implementers, handlers, key usage and dependency mechanisms"},{"id":"U332","fact":"**`UClass::ImplementsInterface` walks the superclass chain and accepts a derived interface: a Blueprint whose parent implements an interface implements it with no interface entry of its own, and an implementer of a child interface implements its base.**","source":"5.3 `Class.cpp:5341-5353`, 5.8 `Class.cpp:6275-6287` (the loop over `GetSuperClass()`, and `IsChildOf(SomeInterface)`); measured on 5.8.2: BP_SpeakerChild reported as an inherited implementer (tools/test_endpoint_additions_d.py ED121)","engine":"5.3, 5.8","ue":["5.3","5.8"],"bl":[],"section":"UE Blueprint reads - listeners, implementers, handlers, key usage and dependency mechanisms"},{"id":"A98","fact":"**Five reads report planted content exactly: list_dispatchers' listeners and broadcast sites over a folder, with an unbind and a disabled bind left out and a component-bound event heard; list_interfaces' implementersOf, direct and inherited, with 3 calls to one function, a disabled message left out and an uncalled function at 0; list_input_mappings' one duplicate key and per-action handlers, a disabled handler left out; describe_behavior_tree's key usage down a two-board chain - used, engine (SelfActor) and unused - and a missing key; and project_dependency_graph's mechanisms: cast, call and variable type on one edge, spawn on another, an empty object on the class-default edge, and none on an edge to a curve asset. A second folder with no Blueprint is refused.**","source":"measured: tools/test_endpoint_additions_d.py, 35/35 on 5.8.2","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE Blueprint reads - listeners, implementers, handlers, key usage and dependency mechanisms"},{"id":"U429","fact":"**Importing text into an object property can LOAD a package: FObjectPropertyBase::FindImportedObject looks the path up in memory first and, failing that, calls StaticLoadObject (LOAD_NoWarn","source":"5.3 `PropertyBaseObject.cpp:556`, 5.4 `PropertyBaseObject.cpp:556`, 5.5 `PropertyBaseObject.cpp:592`, 5.6 `PropertyBaseObject.cpp:595`, 5.7 `PropertyBaseObject.cpp:596`, 5.8 `PropertyBaseObject.cpp:607`","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE Blueprint reads - listeners, implementers, handlers, key usage and dependency mechanisms","value":"LOAD_FindIfFail). So any parse of an object reference - ImportText, FBlueprintEditorUtils::PropertyValueFromString - is a load, not a read; soft references parse a path and load nothing.** Read in engine source; the reason MifBridge compares an object-typed authored default with the class default object's value as TEXT rather than parsing it."},{"id":"U431","fact":"**`list_variables` reads a member variable's default from the class default object once it is compiled (`defaultSource: \"classDefaultObject\"`, get_property's `value` form), else the text set on the variable (`\"variableDescription\"` with `compiled:false`); a default set since the last compile that differs by VALUE is `pendingDefault`, and `defaultNote` says so. `set_variable_default`'s valueBefore/valueAfter are the default as the next compile applies it, re-sending the class's value is changed:false and dirties nothing, and an empty default on a compiled variable is refused naming set_property on the class default object.** One reader, ReadMemberVariableDefault, for both and PinValueThroughWire. tools/test_variable_defaults.py (VD100-VD110): 33/33 on 5.8.2 and 33/33 on 5.3.2 with --allow-disk (saved, clean, reloaded from disk); 8 of 31 on the builds before.","source":"MifBridge b411d004; live on the 5.8.2 and 5.3.2 probes a scratch project, a scratch project","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE Blueprint reads - listeners, implementers, handlers, key usage and dependency mechanisms"},{"id":"U433","fact":"**The window is the registry's INITIAL search only, and renames are refused in it too: IAssetRegistry::IsLoadingAssets() is `!bInitialSearchCompleted` (5.8 `!IsInitialSearchCompleted()`), so it opens once per editor session and nothing reopens it; and FAssetRenameManager::RenameAssets - IAssetTools::RenameAssets, the non-dialog entry point - returns false inside it, logging \"Unable To Rename While Discovering Assets\" (the dialog entry point opens a Discovering Assets dialog instead).** Read in engine source on 2026-09-27 while fixing delete_asset for U430; the bridge's rename_asset (single and batch) and register_landscape_layer's layerInfoPath move go through it.","source":"5.3 `AssetRegistry.cpp:3899` (5.3.2 source; :3847 in the Epic 5.3 install), 5.8 `AssetRegistry.cpp:4659`; 5.3 `AssetRenameManager.cpp:246`, 5.4 `AssetRenameManager.cpp:248`, 5.5 `AssetRenameManager.cpp:143`, 5.6 `AssetRenameManager.cpp:144`, 5.7 `AssetRenameManager.cpp:145`, 5.8 `AssetRenameManager.cpp:145`","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE Blueprint reads - listeners, implementers, handlers, key usage and dependency mechanisms"},{"id":"U435","fact":"**During the registry's first scan FAssetFixUpRedirectors::FixupReferencers does not fix up: it opens a \"Discovering Assets...\" window - a NON-MODAL raw Slate window (AddWindowAsNativeChild, or AddWindow without a main frame) - and returns; the fix-up, ObjectTools::DeleteObjects on the redirectors included, runs from IAssetRegistry::OnFilesLoaded when the scan ends (never, if someone presses the window's Cancel), outside the caller. GIsRunningUnattendedScript does not suppress the window: Slate checks that flag only in AddModalWindow.** Read in engine source on 2026-09-27 while fixing delete_asset for U430. It corrects the bridge's own earlier claim - fixup_redirectors' refusal, its tool_help entry and tools/test_redirectors.py said the window was modal and deadlocked the bridge.","source":"5.3 `AssetFixUpRedirectors.cpp:60`, 5.4 `AssetFixUpRedirectors.cpp:531`, 5.5 `AssetFixUpRedirectors.cpp:532`, 5.6 `AssetFixUpRedirectors.cpp:532`, 5.7 `AssetFixUpRedirectors.cpp:547`, 5.8 `AssetFixUpRedirectors.cpp:547`; 5.3 `SDiscoveringAssetsDialog.cpp:89`, 5.8 `SDiscoveringAssetsDialog.cpp:114`; 5.3 `SlateApplication.cpp:1990`, 5.8 `SlateApplication.cpp:2134`","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE Blueprint reads - listeners, implementers, handlers, key usage and dependency mechanisms"},{"id":"U436","fact":"**The delete refusal's notice lasts 3 seconds: the FNotificationInfo \"Unable To Delete While Discovering Assets\" that ObjectTools raises in the registry's first scan is given ExpireDuration = 3.0f, the line after the notice itself, and is the only thing the editor shows; the call returns 0.** Read in engine source on 2026-09-27 (the IsLoadingAssets guard it follows is U430); the bridge's delete refusal says \"a 3-second notice\" on the strength of it.","source":"5.3 `ObjectTools.cpp:2860`, 5.4 `ObjectTools.cpp:2860`, 5.5 `ObjectTools.cpp:2875`, 5.6 `ObjectTools.cpp:3110`, 5.7 `ObjectTools.cpp:3123`, 5.8 `ObjectTools.cpp:3145`","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE Blueprint reads - listeners, implementers, handlers, key usage and dependency mechanisms"},{"id":"U441","fact":"**The editor's asset rename writes to disk: FAssetRenameManager saves every renamed package and every redirector it leaves, without prompting (`bPromptToSave = false`), and deletes the old package's file when no redirector is left - so renaming an asset that was never saved writes it to disk.** Measured live on 5.8.2 through MifBridge's rename_asset in write mode 'scratch' (probe a scratch project, 2026-09-28 00:43-00:44): a new material /Game/_MifPV/RenProbe/M_RenA_70628, dirty and with no .uasset on disk, renamed to M_RenB_70628 - M_RenB_70628.uasset appeared on disk and the package read clean; renamed again to M_RenC_70628 - M_RenC's file was written and M_RenB's deleted, no redirector (nothing referenced it). The response said only renamed:true, scratchClean:true.","source":"5.8 `Developer/AssetTools/Private/AssetRenameManager.cpp:1992-2006` (\"Save all renamed assets and any redirectors that were left behind\", PromptForCheckoutAndSave with bPromptToSave false); under GIsRunningUnattendedScript PromptForCheckoutAndSave saves outright: 5.8 `Editor/UnrealEd/Private/FileHelpers.cpp:4659-4662`, 5.3 :4296-4299. Probe: MifBridge docs/evidence/bulk-preview-wave3/probe_rename_saves.py, result probe_rename_saves_5.8.2.json. 5.3 read, not measured.","engine":"","ue":[],"bl":[],"section":"UE Blueprint reads - listeners, implementers, handlers, key usage and dependency mechanisms"},{"id":"U442","fact":"**Reloading a package empties the whole undo history, not just that package's part of it: the engine's reload calls GEditor->ResetTransaction(\"Reloaded Package\") at PostBatchPreGC, so every earlier edit - on anything - can no longer be undone.** Measured live on 5.8.2 through MifBridge's reload_packages (probe a scratch project, write mode scratch, 2026-09-28 ~00:58): a spawned actor and a set_property on a saved scratch material left the undo queue at 2; reload_packages {paths:[the material]} answered ok with reloaded [it], and the queue read 0; undo_transactions {count:1} then answered \"Can't undo after: Reloaded Package\", and the unrelated spawned actor stayed. The response said nothing of it.","source":"5.8 `Editor/UnrealEd/Private/PackageTools.cpp:1228-1233` and 5.3 :1183-1188 (PostBatchPreGC -> ResetTransaction(NSLOCTEXT(\"UnrealEd\", \"ReloadedPackage\", \"Reloaded Package\"))). Probe: MifBridge docs/evidence/bulk-preview-wave3/probe_reload_undo.py, result probe_reload_undo_5.8.2.json. 5.3 read, not measured.","engine":"","ue":[],"bl":[],"section":"UE Blueprint reads - listeners, implementers, handlers, key usage and dependency mechanisms"},{"id":"U443","fact":"**Listing the dirty world packages through FEditorFileUtils MARKS packages dirty on an untitled map, while listing the dirty content packages marks nothing.** EnumerateDirtyWorldPackages (behind GetDirtyWorldPackages, GetDirtyPackages and every \"what is unsaved\" read built on them) calls MarkPackageDirty on the level's BuiltData package when the world package is dirty and the BuiltData package has no valid long name, and on a newly created world package that is not dirty itself but has a dirty BuiltData or external package. So a read of the dirty set sets flags; MifBridge's preview tripwire read through it until 2026-09-28 and now reads IsDirty() off each root package, and save_dirty_packages' preview puts back any flag the listing set.","source":"5.8 `Editor/UnrealEd/Private/FileHelpers.cpp:5571-5698` (EnumerateDirtyWorldPackages; the BuiltData mark :5609-5616, the new world package :5687-5694); EnumerateDirtyContentPackages :5733-5806 marks nothing. Read 2026-09-28; 5.3 not read.","engine":"","ue":[],"bl":[],"section":"UE Blueprint reads - listeners, implementers, handlers, key usage and dependency mechanisms"},{"id":"U444","fact":"**On 5.8 FString::Printf checks its format at compile time: the format must be a literal, so a format chosen at run time - even a ternary between two TEXT() literals - does not compile (C2664, cannot convert to TCheckedFormatString). 5.3's Printf takes any `const FmtType&`.** Measured by the MifBridge 5.8.2 build of 2026-09-28 01:04 (MifBridgePreview.cpp(1912,48) error C2664 on `Printf(b ? TEXT(\"...\") : TEXT(\"...\"), ...)`); fixed by two Printf calls under the ternary.","source":"5.8 `Runtime/Core/Public/Containers/UnrealString.h.inl:1376` and :1382 (Printf takes UE::Core::TCheckedFormatString<FmtCharType, Types...>); 5.3 `Runtime/Core/Public/Containers/UnrealString.h:1380` (`static FString Printf(const FmtType& Fmt, Types... Args)`).","engine":"","ue":[],"bl":[],"section":"UE Blueprint reads - listeners, implementers, handlers, key usage and dependency mechanisms"},{"id":"U445","fact":"**A package reload whose load fails keeps the package's edits and clears its dirty flag: with AssumePositive the editor clears a dirty package's flag before reloading it, and when the replacement load fails the engine gives the old package its name back, edits and all - so the edits sit in a package that reads clean.** Measured live on 5.8.2 through MifBridge's reload_packages (probe a scratch project, write mode full, 2026-09-28 ~08:43): a material created and saved in the session (never loaded from disk) was edited (TwoSided true) and its .uasset moved aside; reload_packages {paths:[it]} left the same package object under its name, TwoSided still true, and the dirty flag false until the bridge set it again (notReloaded[].dirtyRestored:true). A package the editor had LOADED from disk could not be moved aside: Windows refused, the file held open (WinError 32).","source":"5.8 `Editor/UnrealEd/Private/PackageTools.cpp:754` (PromptUserForDirtyPackages: SetDirtyFlag(false) under AssumePositive) and 5.8 `Runtime/CoreUObject/Private/UObject/PackageReload.cpp:472-482` (a failed LoadPackage renames the old package back); 5.3 PackageTools.cpp:730 and 5.3 PackageReload.cpp:461-471. Suite: tools/test_bulk_preview_wave3.py T7824, which passed on 5.3.2 as well (probe a scratch project, full, 2026-09-28 ~08:48).","engine":"","ue":[],"bl":[],"section":"UE Blueprint reads - listeners, implementers, handlers, key usage and dependency mechanisms"},{"id":"U447","fact":"**Loading a saved map that is not the editor world calls Modify() on its own actors as they load: its brush components, its default physics volume and its debug-draw actor broadcast OnObjectModified, with no package made dirty and no undo entry.** Measured live on 5.8.2 through MifBridge (probe a scratch project, full, 2026-09-28 ~10:00): a map saved with save_level_as, then a new untitled map opened, then consolidate_assets {dryRun:true} with the saved map as target - the preview's tripwire caught four Modify() calls (PersistentLevel.DefaultPhysicsVolume_0 and its BrushComponent0, ChaosDebugDrawActor, Brush_0.BrushComponent0) while its dirty-list and undo-queue checks saw nothing.","source":"Probe: MifBridge docs/evidence/bulk-preview-wave3/probe_consolidate_openmap.py. The bridge's tripwire now skips objects whose package was not in memory when the plan began (MifBridgePreview.cpp FMifPreviewTripwire). 5.3 not measured.","engine":"","ue":[],"bl":[],"section":"UE Blueprint reads - listeners, implementers, handlers, key usage and dependency mechanisms"},{"id":"U448","fact":"**An undo does not take a consolidation back, and a consolidation with deleteSources clears the whole undo history.** Measured live on 5.8.2 through MifBridge (probe a scratch project, full, 2026-09-28 ~09:57): consolidate_assets (plain, confirm:true) repointed an unsaved MaterialInstanceConstant's Parent from one material to another and left \"Mif Bridge: consolidate_assets\" (1 record, 2882 bytes) on the undo queue; undo_transactions {count:1} answered undone:1 and the Parent still read the target. With deleteSources (tools/test_bulk_preview_consolidate.py T7865) the undo queue went from 5 entries to 0.","source":"Probe: MifBridge docs/evidence/bulk-preview-wave3/probe_consolidate_undo.py. Engine: DeleteSingleObject's reference check resets the transaction buffer when it is an object's only holder (5.3 `Editor/UnrealEd/Private/ObjectTools.cpp:3160-3171`). 5.3: T7865 passed on 5.3.2 as well.","engine":"","ue":[],"bl":[],"section":"UE Blueprint reads - listeners, implementers, handlers, key usage and dependency mechanisms"},{"id":"U449","fact":"**UEditorLevelUtils::MoveActorsToLevel returns its moved actors in the exporter's order, which is the order given only from 5.6 - on 5.3 it follows the level's own actor order, so pairing old paths with new actors by position names the wrong actor there.** Measured live on 5.3.2 and 5.8.2 through MifBridge's move_actors_to_level before its wave-3 fix (probes a scratch project and a scratch project, full, 2026-09-28 ~11:17-11:29): two cubes spawned in order P3, P4 in one sublevel and moved as [P4, P3] into another; the index-paired movedActors[] paired P4's old path with P3's new actor on 5.3.2 and paired both right on 5.8.2. After the fix - each pair matched by the copy-paste id the paste gives the new actor - both engines pair right.","source":"5.8 `Runtime/Engine/Private/UnrealExporter.cpp:1431` (\"the precise order in which objects will be exported\", absent before 5.6). Probe: MifBridge docs/evidence/bulk-preview-wave3/probe_move_notfound.py, results probe_move_notfound_5.3.2_before_fix.json and probe_move_notfound_5.8.2_before_fix.json beside it, with the _after_fix pair. Suite: tools/test_bulk_preview_move.py T7913.","engine":"","ue":[],"bl":[],"section":"UE Blueprint reads - listeners, implementers, handlers, key usage and dependency mechanisms"},{"id":"U333","fact":"**A timeline's SetNewTime fires its Update output: UTimelineComponent::SetNewTime clamps the time to [0, length] and calls SetPlaybackPosition(time, bFireEvents=false), whose bFireUpdate defaults to true, so the post-update delegate - the node's Update pin - runs at once. Event tracks do not fire.**","source":"5.3 `Timeline.cpp:597-601` (FTimeline::SetNewTime), `:891-894` (the component's), `:270`, `:475-477`; 5.8 `Timeline.cpp:601-605`, `:940-943`, `:274`, `:479-481`; 5.3/5.8 `TimelineComponent.h` (SetPlaybackPosition's bFireUpdate = true); measured on 5.8.2: a door restored by LoadFromSlot re-poses its leaf through Update (tools/test_gameplay_recipes_live.py G268)","engine":"5.3, 5.8","ue":["5.3","5.8"],"bl":[],"section":"UE timelines, child construction scripts and pure functions - what a door is built on"},{"id":"U334","fact":"**A Blueprint made as the child of another Blueprint starts with a construction script whose entry is already wired to a Parent: Construction Script call - so a child's own construction work goes after that call, and the parent's still runs.**","source":"measured on 5.8.2: create_blueprint with a Blueprint parent, then the child's UserConstructionScript lists K2Node_FunctionEntry (then -> execute) and K2Node_CallParentFunction","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE timelines, child construction scripts and pure functions - what a door is built on"},{"id":"U335","fact":"**A pure Blueprint function keeps exec pins in its editor graph - its entry has a `then` output, its result an `execute` input - and works with them wired to nothing: the body reaches the result through data, which compile keeps. So an entry that reaches nothing by exec is an unwired body only when compile deletes work in the graph.**","source":"measured on 5.8.2: create_function {pure: true} with an output, get_node on its entry and result; the door recipe's pure HasKey (entry wired to nothing, HasTag into the result) answers true for the held key in PIE (tools/test_gameplay_recipes_live.py G266) and graph_metrics finds nothing pruned in it (tools/test_graph_metrics.py GM132)","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE timelines, child construction scripts and pure functions - what a door is built on"},{"id":"U336","fact":"**A new rich-curve key is linear with automatic tangents - FRichCurveKey's defaults - so a float track needs its interpolation set per key for anything else; AutoSetTangents then shapes cubic keys.**","source":"5.3 `RichCurve.h:120-121`, 5.8 `RichCurve.h:121-122` (InterpMode RCIM_Linear, TangentMode RCTM_Auto); SetKeyInterpMode 5.3 `:282`, 5.8 `:283`; AutoSetTangents 5.3 `:313`, 5.8 `:314`","engine":"5.3, 5.8","ue":["5.3","5.8"],"bl":[],"section":"UE timelines, child construction scripts and pure functions - what a door is built on"},{"id":"A100","fact":"**`add_timeline` float tracks carry keys: a track {name, keys, interp} is created with its keys, read back from the curve (3 keys, cubic, time 0.5 value 1 in the curve itself); six malformed tracks are refused before the node exists, leaving the Blueprint's timelines as they were; a key past the timeline's length is kept and warned about.**","source":"measured: tools/test_timeline.py T515-T517, 36/36 on 5.8.2","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE timelines, child construction scripts and pure functions - what a door is built on"},{"id":"U339","fact":"**A Blueprint function defined in a parent Blueprint can be overridden in a child as an EVENT when it has no outputs (the override event places, with a Parent call, and compiles clean); one with an output cannot be an event and is overridden as a function graph - which implement_interface_function makes for a parent class's function as well as an interface's.**","source":"measured on 5.8.2: add_override_event {OnAttempt(Instigator)} on a child compiles 0/0; add_override_event {IsSolved -> bool} is refused; implement_interface_function {IsSolved} creates the child's IsSolved graph, 0/0; the puzzle recipe's PIE proof runs both kinds (tools/test_gameplay_recipes_live.py G274-G279)","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE timelines, child construction scripts and pure functions - what a door is built on"},{"id":"U340","fact":"**A Bind node in a function graph cannot take a custom event - custom events live only in an event graph - so its delegate is a Create Event node naming an event-graph custom event whose signature is the dispatcher's.**","source":"measured on 5.8.2: the puzzle tracker's Watch binds OnCompleted through add_create_event {function: HandleCompleted}, compiles 0/0 and hears a completion in PIE (tools/test_gameplay_recipes_live.py G275)","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE timelines, child construction scripts and pure functions - what a door is built on"},{"id":"U341","fact":"**StandardMacros' two array loops name their outputs differently: ForEachLoop's are `Array Element` and `Array Index`, with spaces; ReverseForEachLoop's are `ArrayElement` and `ArrayIndex`, without. A wire spelled for one fails on the other.**","source":"measured on 5.8.2: add_macro_instance of each from /Engine/EditorBlueprintResources/StandardMacros, then the node's pins; the inventory recipe wires both (tools/test_gameplay_recipes_live.py G284-G287)","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE timelines, child construction scripts and pure functions - what a door is built on"},{"id":"U342","fact":"**A Construct Object from Class node grows an input pin for each exposed-on-spawn variable of its class once the Class pin is set, and types its result as that class; its Outer pin is named `self` and, unwired, is the calling object.**","source":"measured on 5.8.2: add_k2_node K2Node_GenericCreateObject, set_pin_default Class to a Blueprint with Count exposed on spawn - the pins become Class, ReturnValue (that class), self, Count","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE timelines, child construction scripts and pure functions - what a door is built on"},{"id":"U343","fact":"**A number made by Conv_IntToText is a generated text: read through a property its value exports as `LOCGEN_NUMBER_GROUPED(1, \"\")`, not as \"1\".**","source":"measured on 5.8.2: the inventory slot's CountLabel after SetText(Conv_IntToText(1)), get_property Text (tools/test_gameplay_recipes_live.py G289)","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE timelines, child construction scripts and pure functions - what a door is built on"},{"id":"U344","fact":"**A widget made by Create Widget that is neither in the viewport nor held by a property is collected by the next garbage collection; AddToViewport keeps it. A delegate bound to it does not keep it alive.**","source":"measured on 5.8.2 in PIE: two grids from WidgetBlueprintLibrary Create, one added to the viewport; after KismetSystemLibrary CollectGarbage the loose one is `object not found` and the shown one answers IsInViewport true; the inventory proof's grid, bound to OnInventoryChanged and not on screen, answered nothing after a GC on one run (tools/test_gameplay_recipes_live.py G284, now forced)","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE timelines, child construction scripts and pure functions - what a door is built on"},{"id":"U345","fact":"**An Actor Blueprint's ghost BeginPlay event is titled `Event BeginPlay`, a component Blueprint's `Event Begin Play` - a lookup by title must use the right one.**","source":"measured on 5.8.2: list_nodes on a new Actor Blueprint's and a new ActorComponent Blueprint's event graphs","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE timelines, child construction scripts and pure functions - what a door is built on"},{"id":"U346","fact":"**An AudioComponent's FadeOut to volume 0 stops the component when the fade ends: GetPlayState reads FadingOut during it and Stopped after, while the component fading in reads FadingIn and then Playing.**","source":"measured on 5.8.2 in PIE: the music manager's decks during and after a 0.5 s crossfade with 7 s and 5 s engine waves (tools/test_gameplay_recipes_live.py G294, G296)","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE timelines, child construction scripts and pure functions - what a door is built on"},{"id":"U347","fact":"**UAIPerceptionStimuliSourceComponent rebuilds RegisterAsSourceForSenses in its own PostEditChangeProperty: on a ValueSet it copies the array, empties it and refills it with the unique non-null senses, which can free the buffer an element address points into; a Duplicate change nulls the duplicated element.**","source":"5.3 and 5.8 `AIPerceptionStimuliSourceComponent.cpp:161-190`; measured on 5.8.2: set_property RegisterAsSourceForSenses[0] on a component template crashed the editor reading back through the old address (EXCEPTION_ACCESS_VIOLATION in FObjectPropertyBase::ExportText_Internal)","engine":"5.3, 5.8","ue":["5.3","5.8"],"bl":[],"section":"UE timelines, child construction scripts and pure functions - what a door is built on"},{"id":"U348","fact":"**A sense config detects only enemies by default, and it is config that says so: FAISenseAffiliationFilter's own defaults detect nobody, and BaseGame.ini sets DetectionByAffiliation=(bDetectEnemies=true) for sight and hearing. A pawn with no team is neutral, so a default config perceives nobody in a project without teams.**","source":"5.3 `AIPerceptionTypes.h:211-213`, 5.8 `AIPerceptionTypes.h:218-224`; 5.3 `BaseGame.ini:194` and `:201`, 5.8 `BaseGame.ini:217` and `:224`","engine":"5.3, 5.8","ue":["5.3","5.8"],"bl":[],"section":"UE timelines, child construction scripts and pure functions - what a door is built on"},{"id":"U349","fact":"**On UE 5.8 a Blueprint's graphs are readable and writable from Python through a BlueprintCallable surface: UBlueprintGraphEditor (GetGraphEditorByName, GetGraphEditor, ListAllNodes, ListNodesOfClass, FindEventNode, CreateNodeFromName, AddCallFunctionNode, FindGraphEntryPin), the BlueprintGraphPin library (TryCreateConnection, SetPinValue, GetPinName, GetOwningNode, ListConnectedPins) and UBlueprintEditorLibrary (GeneratedClass, CompileBlueprint, FindGraph, FindEventGraph, GetNodeTitle, ListGraphNames, AddMemberVariable).** Epic's header carries a Python example that adds a PrintString call to a new function graph and wires it to the entry pin.","source":"5.8 `Engine/Source/Editor/BlueprintEditorLibrary/Private/BlueprintEditorLibrary/BlueprintGraphEditor.h:35-51` (the Python example), `:68-69` (GetGraphEditorByName), `:81-82` (ListAllNodes), `:119-120` (CreateNodeFromName), `:148` (FindEventNode); `.../Public/BlueprintEditorLibrary/BlueprintGraphPin.h:101, 139, 146`; `.../Public/BlueprintEditorLibrary.h` GeneratedClass, CompileBlueprint, FindGraph, FindEventGraph, GetNodeTitle, ListGraphNames","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE timelines, child construction scripts and pure functions - what a door is built on"},{"id":"U350","fact":"**Unreal's Python cannot read a Niagara system's emitter handles or module inputs through reflection: UNiagaraSystem::EmitterHandles is a bare UPROPERTY() (Python reads need CPF_Edit, CPF_BlueprintVisible or CPF_BlueprintAssignable) and the class has no UFUNCTIONs; module inputs such as a burst count or a lifetime live in UNiagaraScript::RapidIterationParameters, also bare, and in graph pins.** Reachable instead: UNiagaraFunctionLibrary::GetAllEmitters (each emitter's name, enabled and lightweight flags, and the materials and meshes of its enabled renderers, but not the renderer types), and a placed ANiagaraActor's component and its asset (UNiagaraComponent::GetAsset).","source":"5.8 `Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraSystem.h:975-976`, `.../Classes/NiagaraScript.h:882-883`, `.../Public/NiagaraFunctionLibrary.h:157-158`, `.../Public/NiagaraActor.h:40-41`, `.../Public/NiagaraComponent.h:297-298`; `Engine/Source/Runtime/CoreUObject/Private/UObject/PropertyAccessUtil.cpp:425-433`","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE timelines, child construction scripts and pure functions - what a door is built on"},{"id":"U351","fact":"**Unreal's Python can neither create a landscape nor query its height: ALandscapeProxy::GetHeightAtLocation is plain C++, the LandscapeEditor module has no BlueprintCallable functions, and the only heightmap writer, LandscapeImportHeightmapFromRenderTarget, needs an existing landscape and a render target. Foliage is reachable: AInstancedFoliageActor::AddInstances (static, editor-only) adds instances, and GetInstanceCount and GetInstanceTransform on the foliage's instanced components read them back.** A height check from Python has to line-trace (UKismetSystemLibrary::LineTraceSingle).","source":"5.8 `Engine/Source/Runtime/Landscape/Classes/LandscapeProxy.h:1101` (GetHeightAtLocation, no UFUNCTION), `:1571-1572`; `Engine/Source/Runtime/Foliage/Public/InstancedFoliageActor.h:285-289`; `Engine/Source/Runtime/Engine/Classes/Components/InstancedStaticMeshComponent.h:337-338, 434-435`; `Engine/Source/Runtime/Engine/Classes/Kismet/KismetSystemLibrary.h:1269-1270`","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE timelines, child construction scripts and pure functions - what a door is built on"},{"id":"U352","fact":"**A Blueprint struct can be created from Python (AssetTools create_asset with StructureFactory, which adds one default Boolean member) but its members cannot be added or renamed there: FStructureEditorUtils is plain C++ and the struct's editor data is bare. A DataTable's JSON export (UDataTableFunctionLibrary::ExportDataTableToJSONString) keys each row's own name as \"Name\" and each Blueprint-struct field by its friendly authored name.** So a struct field called Name collides with the row key in that export.","source":"5.8 `Engine/Source/Editor/UnrealEd/Private/Factories/EditorFactories.cpp:7657` (CreateUserDefinedStruct), `StructureEditorUtils.cpp:59`, `UserDefinedStructEditorData.h:38-66`; `Engine/Source/Runtime/Engine/Classes/Kismet/DataTableFunctionLibrary.h:224-225`; `Engine/Source/Runtime/Engine/Private/DataTableJSON.cpp:89` (\"Name\"), `:232, 264`; `Engine/Source/Runtime/Engine/Private/DataTableUtils.cpp:431-437` (GetPropertyExportName returns GetAuthoredName)","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE timelines, child construction scripts and pure functions - what a door is built on"},{"id":"U353","fact":"**Play-in-editor cannot be driven from a -run=pythonscript commandlet, and Python cannot spawn an arbitrary actor into a PIE world: LevelEditorSubsystem's EditorRequestBeginPlay and EditorPlaySimulate do nothing without an active level viewport and are only queued, then started from the editor's tick; GameplayStatics BeginDeferredActorSpawnFromClass and FinishSpawningActor are BlueprintInternalUseOnly; EditorActorSubsystem spawns into the editor world and refuses during PIE. UAIBlueprintHelperLibrary::SpawnAIFromClass is BlueprintCallable, takes a world context and spawns Pawns.**","source":"5.8 `Engine/Source/Editor/LevelEditor/Public/LevelEditorSubsystem.h:62-84`, `.../Private/LevelEditorSubsystem.cpp:196-209, 265-278`; `Engine/Source/Editor/UnrealEd/Private/EditorEngine.cpp:1752, 2061-2064`; `Engine/Source/Runtime/Engine/Classes/Kismet/GameplayStatics.h:66-67`; `EditorScriptingHelpers.cpp:155-157`; `Engine/Source/Runtime/AIModule/Classes/Blueprint/AIBlueprintHelperLibrary.h:44-45`","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE timelines, child construction scripts and pure functions - what a door is built on"},{"id":"U354","fact":"**A SoundCue's graph is walkable from Python from its root: USoundCue::FirstNode and USoundNode::ChildNodes are BlueprintReadOnly, USoundNodeRandom::Weights is EditAnywhere, and a wave player's sound is readable through SoundWaveAssetPtr (EditAnywhere) though not through SoundWave (transient). USoundCue::AllNodes is bare, so a node not connected to the root is invisible to Python.**","source":"5.8 `Engine/Source/Runtime/Engine/Classes/Sound/SoundCue.h:94-95, 109-111`, `.../Sound/SoundNode.h:63-64`, `.../Sound/SoundNodeWavePlayer.h:28-32`, `.../Sound/SoundNodeRandom.h:24-25`","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE timelines, child construction scripts and pure functions - what a door is built on"},{"id":"U355","fact":"**A MetaSound's graph is only partly readable from Python: UMetaSoundEditorSubsystem::FindOrBeginBuilding attaches a builder to a saved asset, but the builder has targeted finders only, with no call that lists nodes or says what feeds an input; GetNodeInputDefault returns only a value the user set, so a Sine whose Frequency was left at its class default of 440 reports Failed there. UMetaSoundSource::OutputFormat is readable, and the whole document dumps through export_text() on its root_metasound_document.**","source":"5.8 `Engine/Plugins/Runtime/Metasound/Source/MetasoundEditor/Public/MetasoundEditorSubsystem.h:71-72`; `.../MetasoundEngine/Public/MetasoundBuilderBase.h:136-585` (no listing call), `:365-366`; `.../MetasoundEngine/Private/MetasoundBuilderBase.cpp:1511-1521`; `.../MetasoundStandardNodes/Private/MetasoundOscillatorNodes.cpp:55`; `.../MetasoundEngine/Public/MetasoundSource.h:139-140, 163-164`","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE timelines, child construction scripts and pure functions - what a door is built on"},{"id":"U356","fact":"**An Animation Blueprint's anim graph is partly readable from Python: UAnimationBlueprintLibrary::GetAnimationGraphs and GetNodesOfClass list its graphs and nodes, a state machine's name is its node title (UBlueprintEditorLibrary::GetNodeTitle), and states appear as child graphs named after them (ListGraphNames). A transition's from and to states are not reachable: they live in pins, and the pin library accepts only K2 nodes, which state and transition nodes are not.**","source":"5.8 `Engine/Source/Editor/AnimationBlueprintLibrary/Public/AnimationBlueprintLibrary.h:680-685`; `Engine/Source/Editor/BlueprintEditorLibrary/Public/BlueprintEditorLibrary.h:712-713`; `AnimGraphNode_StateMachineBase.cpp:133`; `AnimStateNode.cpp:73-76`; `AnimStateNodeBase.h:16-17`","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE timelines, child construction scripts and pure functions - what a door is built on"},{"id":"A101","fact":"**`set_property` into a container its owner rebuilds is re-resolved before it is read back: element 0 of a stimuli source's RegisterAsSourceForSenses sets and verifies with the editor still answering, and a duplicate into element 1, which the owner's rebuild removes, answers applied but unverified - the path no longer resolves - leaving one sense.**","source":"measured: tools/test_ue_property_guards.py T4591, 64/64 on 5.8.2; the pre-fix build crashed on the first case","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE timelines, child construction scripts and pure functions - what a door is built on"},{"id":"U360","fact":"**A Level Blueprint's key events fire with no Enable Input: ALevelScriptActor sets bInputEnabled in its constructor and makes and binds its own InputComponent in PreInitializeComponents.**","source":"5.3 and 5.8 `Runtime/Engine/Private/LevelScriptActor.cpp:29` and `:49`","engine":"5.3, 5.8","ue":["5.3","5.8"],"bl":[],"section":"UE Level Blueprints - events bound to placed actors, and folder scope"},{"id":"U361","fact":"**FKismetEditorUtilities::CreateNewBoundEventForActor opens the Blueprint editor on the node it makes, unconditionally, on 5.3-5.7 (BringKismetToFocusAttentionOnObject); 5.8 added a bShouldJumpToNode parameter, default true.**","source":"5.3 `Editor/UnrealEd/Private/Kismet2/Kismet2.cpp:2501`, 5.7 `Editor/UnrealEd/Private/Kismet2/Kismet2.cpp:2542`, 5.8 `Editor/UnrealEd/Public/Kismet2/KismetEditorUtilities.h:455`","engine":"5.3, 5.7, 5.8","ue":["5.3","5.7","5.8"],"bl":[],"section":"UE Level Blueprints - events bound to placed actors, and folder scope"},{"id":"U362","fact":"**A Level Blueprint is held by its level (ULevel::LevelScriptBlueprint), inside the map's package - never an asset of its own, so an asset-registry search for UBlueprint under a folder lists none.**","source":"5.3 `Runtime/Engine/Classes/Engine/Level.h:448`, 5.8 `Runtime/Engine/Classes/Engine/Level.h:473`; measured on 5.8.2: find_references and list_dispatchers refused /Temp, holding only the untitled map's Level Blueprint, as \"no Blueprint\"","engine":"5.3, 5.8","ue":["5.3","5.8"],"bl":[],"section":"UE Level Blueprints - events bound to placed actors, and folder scope"},{"id":"A104","fact":"**`add_actor_bound_event` makes a K2Node_ActorBoundEvent in the Level Blueprint of the actor's own level, creating that Level Blueprint when there is none, and opens no editor window; a second event for the same actor and dispatcher is refused naming the existing node, an unknown dispatcher with the actor's assignable ones, a missing actor by name and a blueprintId with the reason - none of them adding a node.**","source":"measured: tools/test_actor_bound_event.py AE100-AE105, 16/16 on 5.8.2 and on 5.3.2","engine":"5.3, 5.8","ue":["5.3","5.8"],"bl":[],"section":"UE Level Blueprints - events bound to placed actors, and folder scope"},{"id":"A105","fact":"**Folder readers read the open world's Level Blueprints under their pathPrefix: blueprint_coupling finds the actor_bound_event edge, find_references the bound_event site, list_dispatchers the actorBoundEvent listener, and lint_blueprints reads each Level Blueprint once. Before, find_references and list_dispatchers refused such a folder as holding no Blueprint.**","source":"measured: tools/test_actor_bound_event.py AE110-AE114 on 5.8.2 and 5.3.2; the refusals on the endpoint-only 5.8.2 build, 2026-09-24","engine":"5.3, 5.8","ue":["5.3","5.8"],"bl":[],"section":"UE Level Blueprints - events bound to placed actors, and folder scope"},{"id":"A106","fact":"**input_bindings_report files a Level Blueprint's legacy key event under ownerKind levelScript, without needsEnableInput, and lint_blueprints' legacy_input_outside_pawn still reports it but says the engine enables its input.**","source":"measured: tools/test_actor_bound_event.py AE115 on 5.8.2 and 5.3.2","engine":"5.3, 5.8","ue":["5.3","5.8"],"bl":[],"section":"UE Level Blueprints - events bound to placed actors, and folder scope"},{"id":"U422","fact":"**`FKismetEditorUtilities::CreateBlueprint` begins with `check(FindObject<UBlueprint>(Outer, Name) == NULL)`, so a Blueprint already at the name is an assertion failure - it never reaches StaticAllocateObject's rebuild in place.** Another class at the name passes that check (FindObject<UBlueprint> finds nothing of its class) and then meets StaticAllocateObject's Fatal inside the NewObject that follows (row U406). Either way, an endpoint that creates a Blueprint has to refuse a taken name before calling it. Read, not run.","source":"5.3.2 `Editor/UnrealEd/Private/Kismet2/Kismet2.cpp:432`; 5.8 `Editor/UnrealEd/Private/Kismet2/Kismet2.cpp:441`","engine":"5.3.2-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE object allocation and new packages - a name already taken, and a name with `//`"},{"id":"U515","fact":"**On 5.3 a Blueprint is an editor utility only when its ASSET class is UEditorUtilityBlueprint or UEditorUtilityWidgetBlueprint; a plain UBlueprint parented to EditorUtilityObject is not, the editor logs that it needs converting, and an editor subsystem getter in it fails to compile. 5.8 also accepts an editor-only native parent.** Measured on 5.3.2 and 5.8.2 through create_blueprint and add_get_subsystem.","source":"5.3 `Editor/Blutility/Private/BlutilityModule.cpp:221`; 5.8 `Editor/Blutility/Private/BlutilityModule.cpp:224`; tools/test_ue_spec_extensions_h.py SX4400-SX4403. 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":"U523","fact":"**FMovieSceneSequenceEditor::SupportsDirectorBlueprint (and its virtual CanCreateDirectorBlueprint) exist from 5.7 only; in 5.7/5.8 GetOrCreateDirectorBlueprint returns null when it is false. In 5.3 to 5.6 neither exists and GetOrCreateDirectorBlueprint creates a director Blueprint for any sequence that has a sequence editor (only SupportsEvents gates event tracks).** So an add-event-key path guards SupportsDirectorBlueprint with MIF_ENGINE_AT_LEAST(5, 7).","source":"5.3 `Editor/Sequencer/Private/MovieSceneSequenceEditor.cpp:25`; 5.6 `Editor/Sequencer/Public/MovieSceneSequenceEditor.h:48`; 5.8 `Editor/Sequencer/Private/MovieSceneSequenceEditor.cpp:20`; 5.8 `Editor/Sequencer/Private/MovieSceneSequenceEditor.cpp:32`","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":"U524","fact":"**Supersedes U520's range: FMovieSceneSequencePlaybackSettings.bRestoreState is a plain field only in 5.3; 5.4 to 5.7 rename it bRestoreState_DEPRECATED and add FinishCompletionStateOverride (EMovieSceneCompletionModeOverride, ForceRestoreState among its values), and 5.8 drops the deprecated field.** So code that restores state sets FinishCompletionStateOverride = ForceRestoreState from 5.4 on (MIF_ENGINE_AT_LEAST(5, 4)); a 5.8-only guard fails to compile on 5.4 to 5.7. Found by BuildPlugin against 5.6 (C2039).","source":"5.3 `Runtime/MovieScene/Public/MovieSceneSequencePlaybackSettings.h:86`; 5.4 `Runtime/MovieScene/Public/MovieSceneSequencePlaybackSettings.h:100`; 5.4 `Runtime/MovieScene/Public/MovieSceneSequencePlaybackSettings.h:128`; 5.8 `Runtime/MovieScene/Public/MovieSceneSequencePlaybackSettings.h:117`","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":"U525","fact":"**FName is a Chaos managed-array type (TManagedArray<FName>, FManagedArrayCollection::FindAttributeTyped<FName>) only from 5.7: ManagedArrayTypeValues.inl lists MANAGED_ARRAY_TYPE(FName, Name) in 5.7 and 5.8 and not in 5.3 to 5.6, so the ManagedArrayType<FName>() specialization does not exist there and a 5.6 build fails at LINK (LNK2019), not at compile.** Code reading an FName attribute guards it with MIF_ENGINE_AT_LEAST(5, 7).","source":"5.7 `Runtime/Experimental/Chaos/Public/GeometryCollection/ManagedArrayTypeValues.inl:65`; 5.8 `Runtime/Experimental/Chaos/Public/GeometryCollection/ManagedArrayTypeValues.inl:65`; 5.6 `Runtime/Experimental/Chaos/Public/GeometryCollection/ManagedArrayTypes.h:78`","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":"U531","fact":"**A Blueprint created with a Blueprint parent class gets real, enabled override events in its EventGraph: Event BeginPlay, Event ActorBeginOverlap and Event Tick, each already wired to a \"Parent: <event>\" call node. They are not greyed ghost nodes. While they stay wired to the parent call the parent's logic still runs; delete the event nodes (or keep the parent call) before writing child logic, or the child silently replaces the parent's BeginPlay/Tick.** After deleting the three event nodes the \"Parent:\" call nodes are left unwired and do nothing.","source":"measured 2026-10-12 on 5.8.2 (create_blueprint with a BP parent, then find_nodes on the child EventGraph: three K2Node_Event plus three K2Node_CallParentFunction)","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":"U536","fact":"**UGameplayStatics::DoesSaveGameExist is an impure node on 5.8 (it has exec pins). Wired only by its bool output, with no exec input, the compiler prunes it with a warning (\"was pruned because its Exec pin is not connected ... will instead be read as default\") and the bool reads false.** Put it in the exec chain before the Branch that reads it.","source":"measured 2026-10-12 on 5.8.2 (compile message on a function graph where the node fed a Branch without exec; clean after wiring entry -> DoesSaveGameExist -> Branch)","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":"U537","fact":"**APawn's player state is not a Blueprint-callable GetPlayerState function on 5.8 (an add_function_call for GetPlayerState on Pawn finds nothing); Blueprints read it as the PlayerState property (a variable get targeting Pawn, or Controller).**","source":"measured 2026-10-12 on 5.8.2 (function lookup failed on class Pawn; a variable get of PlayerState with target class Pawn compiled and fed GetComponentByClass)","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":"U552","fact":"**A Blueprint Select node (K2Node_Select) with a bool Index picks `Option 0` when the index is false and `Option 1` when it is true.** The pins keep their internal names `Option 0` / `Option 1` (wire and set defaults by those); only their friendly labels become False / True. So connect_pins / set_pin_default address \"Option 0\" for the false case.","source":"5.8: Editor/BlueprintGraph/Private/K2Node_Select.cpp:244-247 (`PinFriendlyName = (Idx == 0 ? CoreTexts.False : CoreTexts.True)`, read in the stock install 2026-10-16); measured 2026-10-16 on 5.8.3: a Blueprint function returning Select(bool) text called through Python `call_method` on placed editor-world actors returned the Option 0 default for index false and the Option 1 default for index true","engine":"5.8 (source read on 5.8 only)","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":"U562","fact":"**A Blueprint user-defined enum is not a top-level `unreal.<Name>` attribute in Python, but reading any property of that enum type returns an instance whose type is the enum class, with UPPER_CASE members: `E = type(actor.get_editor_property(\"SomeEnumProp\")); actor.set_editor_property(\"SomeEnumProp\", E.KEY)`. Passing a plain int to set_editor_property for such a property fails.**","source":"measured 2026-10-16 on 5.8.3: hasattr(unreal, \"<BP enum name>\") False; type(value) printed `<class '<BP enum name>'>` with members HACK / KEY / LOCKPICK / NONE; set_editor_property with E.KEY read back KEY on 6 actors, the int path raised on all 6","engine":"5.8 (measured on 5.8.3 only)","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":"U568","fact":"**Live Coding refuses a compile of more than 100 actions by default** (\"The live coding request of N actions exceeds the number of allowed actions of 100\"; Result: Failed (LiveCodingLimitError)). A regenerated makefile can push even a one-file change past it (185 actions measured), and then a normal build with the editor closed is the route.","source":"UBT log text quoted from `%LOCALAPPDATA%/UnrealBuildTool/Log.txt`, live_coding_compile on UE 5.8.3, 2026-10-16","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":"U577","fact":"**`SetScalarParameterValueOnMaterials` is a member of UMeshComponent, not UPrimitiveComponent; and the Actor transform node is `GetTransform` (the K2 name), not `GetActorTransform`.** A Blueprint graph op asking for SetScalarParameterValueOnMaterials on class PrimitiveComponent, or GetActorTransform on Actor, was refused 'function not found'; MeshComponent and GetTransform compiled.","source":"Live on UE 5.8.3, 2026-10-17: MifBridge batch add_function_call results and the following compile (0 errors).","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":"U578","fact":"**A placed Blueprint actor's `PrimaryActorTick.TickInterval` cannot be set from editor Python (`set_editor_property` refuses: \"cannot be edited on instances\"), but MifBridge `set_property` on the instance path with `property_path` `PrimaryActorTick.TickInterval` sets it.** Changing the class default does not update instances that already show 0.0. 37 placed actors set through two `batch` calls (25 and 12 ops; the batch caps changes at 25 distinct targets), then read back in Python via `primary_actor_tick.tick_interval`: all 37 read 1.0, and they saved.","source":"Live on UE 5.8.3, 2026-10-17: Python refusal message, batch results (37 ok), Python read-back by class (31 + 4 + 2 at 1.0).","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":"U584","fact":"**Changing a Blueprint parent's class-default value (on its CDO, then compile) did not change 26 child Blueprints' defaults of that variable: each child kept the old value.** Set the value on every child CDO (and placed instances) explicitly.","source":"Live on UE 5.8.3, 2026-10-17: MifBridge `set_property` on the parent CDO, compile, then Python read of every child CDO (all still the old enum value) and of 2 placed instances (old value).","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":"U590","fact":"**Changing a Blueprint class default on a native component's struct properties (ChaosWheeledVehicleMovementComponent `EngineSetup`, `TransmissionSetup`) did not reach actors already placed in a level: they kept their own saved copy of the structs, while the plain float `Mass` on the same component did follow the new default.** Placed actors need the same write, or a reset to the archetype.","source":"Live on UE 5.8.3, 2026-10-17: Python read of two placed actors after the class defaults were set, compiled and saved (mass 1550 / 1650 new; MaxTorque 512 and FinalRatio 4.8 old), then after writing the actors directly (350 / 472, 3.46).","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":"U650","fact":"**Compiling a Blueprint whose Create Event node binds a function to a dynamic delegate with NO input parameters, when the match is only \"convertible\" (e.g. return or param flags/names not an exact match, float/double-holding structs), asserts the editor: `FKismetCompilerContext::ReplaceConvertibleDelegates` builds a proxy function, scans its entry node for the first non-meta pin, and `check(EntryNodePinCursor != -1)` fires because a zero-input signature leaves only meta pins. The compile before the crash logs `LogK2Compiler: Warning: N convertible delegates were not processed during class layout compilation` naming the CreateDelegate nodes; that warning means the next compile can crash. Avoid: give the delegate at least one input (e.g. `AActor* Reader`), or make the bound function match exactly, or bind in C++.**","source":"Engine source UE 5.8.3 `Engine/Source/Editor/KismetCompiler/Private/KismetCompiler.cpp` lines 5928-5942 (read 2026-10-22; re-read 2026-10-25: the check is :5942); convertible test `FKismetCompilerUtilities::DoSignaturesHaveConvertibleFloatTypes` in `KismetCompilerMisc.cpp:2224`; live crash 2026-10-22 03:36 UTC in a game project on 5.8.3 (a character Blueprint with three Create Event nodes binding a no-input dynamic delegate that returns a struct holding floats; the warning was logged on one compile and the assert fired on the next; the editor log has both).","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":"U651","fact":"**`UCommonInputSubsystem::OnInputMethodChanged` (the BlueprintAssignable dynamic delegate) is private in UE 5.8, so C++ outside the class cannot bind it (C2248). From C++, bind the public native event `OnInputMethodChangedNative` (`DECLARE_EVENT_OneParam(..., ECommonInputType)`) with `AddUObject(this, &UMyWidget::Handler)` and unbind with `RemoveAll(this)`.**","source":"Engine source UE 5.8: `Engine/Plugins/Runtime/CommonUI/Source/CommonInput/Public/CommonInputSubsystem.h` line 38 (`FInputMethodChangedEvent OnInputMethodChangedNative;` under `public:` at line 29) and line 139 (`FInputMethodChangedDelegate OnInputMethodChanged;` under `private:` at line 119). Build error C2248 in a game module 2026-10-22; the fix built clean.","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":"U653","fact":"**`GetNameSafe(Pawn->GetPlayerState())` does not compile in UE 5.8 (MSVC C2665 'no overloaded function could convert all the argument types' + C2100): APawn declares both a non-template `APlayerState* GetPlayerState() const` and a template `template<class T> T* GetPlayerState() const`, and GetNameSafe has two overloads (`const UObjectBaseUtility*` and the template `const TObjectPtr<T>&`), so the nested call cannot be resolved. Write `GetNameSafe(Pawn->GetPlayerState<APlayerState>())` (and include GameFramework/PlayerState.h).**","source":"Engine source UE 5.8: Engine/Source/Runtime/Engine/Classes/GameFramework/Pawn.h:187 and :193; CoreUObject/Public/UObject/UObjectBaseUtility.h:893 and :912. Re-runnable: a full editor build 2026-10-23 failed with 20 errors (10 call sites) and succeeded after the change.","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":"U654","fact":"**`UK2Node::ShouldDrawAsBead()` is deprecated from 5.4 and gone in 5.8, so a call to it prints C4996 on 5.4-5.7 and fails to compile (C2039) on 5.8; guard it to engines before 5.4.** 5.8's own promote-to-variable path checks only `ShouldDrawCompact()`.","source":"5.3: Editor/BlueprintGraph/Classes/K2Node.h:270; 5.7: Editor/BlueprintGraph/Classes/K2Node.h:271-272 (UE_DEPRECATED(5.4)); 5.8: no match for ShouldDrawAsBead under Engine/Source/Editor (grep, 2026-10-25).","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":"U663","fact":"**On UE 5.8 a `FJsonObject`'s `Values` map is keyed by `UE::FSharedString`, not `FString`, and `FSharedString` has no implicit conversion to `FString` - so code that passes `KV.Key` from a `Values` loop to a `const FString&` parameter compiles on 5.3-5.7 and fails on 5.8.** `UE_JSONOBJECT_LEGACY_STRING_KEYS` defaults to 0 on 5.8, which selects `FStringType = UE::FSharedString`; `FSharedString` offers only `operator*` (a `const CharType*`) and `ToView()`. On 5.3, 5.6 and 5.7 the map is `TMap<FString, TSharedPtr<FJsonValue>>`. Found by a read-only compile review of `MifBridgeUIComponents.cpp` (fix: convert the key with `MifJsonKey`).","source":"5.8: Engine/Source/Runtime/Json/Public/Dom/JsonObject.h:47, :99; 5.8: Engine/Source/Runtime/Core/Public/Containers/SharedString.h:80, :92; 5.3: Engine/Source/Runtime/Json/Public/Dom/JsonObject.h:26; 5.7: Engine/Source/Runtime/Json/Public/Dom/JsonObject.h:26","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":"U664","fact":"**`UMVVMBlueprintViewCondition::GetOperationValue()` and `GetOperationMaxValue()` are non-const on UE 5.5 and 5.6 and const from 5.7, so calling them through a `const UMVVMBlueprintViewCondition*` compiles on 5.7-5.8 and fails on 5.5-5.6.** A MifBridge block gated `MIF_ENGINE_AT_LEAST(5, 5)` that reads a view's conditions as const pointers therefore breaks only the middle engines; a build on 5.3 and 5.8 alone does not catch it. Found by a read-only compile review of `MifBridgeMVVM.cpp`.","source":"5.5: Engine/Plugins/Runtime/ModelViewViewModel/Source/ModelViewViewModelBlueprint/Public/MVVMBlueprintViewCondition.h:72, :78; 5.6: Engine/Plugins/Runtime/ModelViewViewModel/Source/ModelViewViewModelBlueprint/Public/MVVMBlueprintViewCondition.h:72, :78; 5.7: Engine/Plugins/Runtime/ModelViewViewModel/Source/ModelViewViewModelBlueprint/Public/MVVMBlueprintViewCondition.h:74, :80; 5.8: Engine/Plugins/Runtime/ModelViewViewModel/Source/ModelViewViewModelBlueprint/Public/MVVMBlueprintViewCondition.h:74, :80","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":"U665","fact":"**`FGameplayDebuggerCategory::ForceImmediateCollect()` is protected, so a plugin cannot call it on a replicator's categories; `AGameplayDebuggerCategoryReplicator::CollectCategoryData(true)` already forces a collect past each category's interval.** The method sits after the class's `protected:` label with no `public:` before it. The replicator's collect loop collects when `bForce` is true or `GameTime - LastCollectDataTime` exceeds `CollectDataInterval`, so passing true is the public route to a fresh read. Found by a read-only compile review of `MifBridgeGameplayDebugger.cpp`.","source":"5.8: Engine/Source/Runtime/GameplayDebugger/Public/GameplayDebuggerCategory.h:109, :152; 5.8: Engine/Source/Runtime/GameplayDebugger/Private/GameplayDebuggerCategoryReplicator.cpp:954","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":"U673","fact":"**The flesh terminal overrides only the two-argument Evaluate, so calling the one-argument form reaches the base's ensure(false).** The engine's own blueprint library calls it that way. An ensure on an install without symbols stalls the game thread for its stack walk.","source":"5.6 `Engine/Source/Runtime/Experimental/Dataflow/Core/Public/Dataflow/DataflowTerminalNode.h:60`; 5.6 `Engine/Plugins/Experimental/ChaosFlesh/Source/ChaosFleshNodes/Public/Dataflow/ChaosFleshFleshAssetTerminalNode.h:42`; 5.6 `Engine/Source/Runtime/Experimental/Dataflow/Engine/Private/Dataflow/DataflowBlueprintLibrary.cpp:24`","engine":"5.6","ue":["5.6"],"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":"64.3 s and 67.3 s stack walk on 5.6.1 (FPlatformStackWalk::StackWalkAndDump in the log)"}]