[{"id":"A24","fact":"**`count` does not mean one thing across the MifBridge response surface, and the row count is spelled FOUR ways.** Of 63 sites that set `count` on the response, **55 mean rows returned, 5 mean something else** and 3 are a literal 0 on an empty path. Only THREE of those five are listings at all (`find_assets`, `list_components`' level-actor form, `list_layers`); the other two are virtual-bone writes reporting state after the write. The four listing conventions in use, none matching another: `list_level_actors` count=ROWS + `matched`=total + `excluded`; `find_assets` count=TOTAL + `returned`=rows + `matched`=alias of count; `list_layers` count=TOTAL + `reported`=rows, emitted ONLY when truncation happened; `list_components` count=TOTAL + `returned`=rows + `matched`=rows after filtering. So `count` INVERTS between the first and the other three. Consequence: 41 assertions across 27 suites assert `count == len(list)`, and any bound that truncates while keeping `count` exact breaks them. **[Reviewed 2026-09-21 against the code - STALE in part: list_layers always emits returned; reported only on truncation (MifBridgeStreaming.cpp:2390-2407)]**","source":"static, re-runnable: `python tools/audit_count_semantics.py` (exit 1 while any site is unclassified; it checks itself against hand-read anchors and refuses to print on disagreement). Measured 2026-09-10; **the 53/7 first published in commit d99a213 was itself wrong** and is corrected here. That run classified a bare scalar as \"a separately maintained total\" on the reasoning that nobody writes one otherwise - true for `find_assets`' `Matched` and `remove_virtual_bone`'s `After`, and FALSE for `list_bones` and `list_niagara_user_parameters`, where `++Shown;` sits on the line before the row is built with no `continue` between, so `Shown` IS the row count (`MifBridgeSkeleton.cpp:159-173`, `MifBridgeNiagara.cpp:467-471`). Two of the four sites that rule touched were wrong. The tool now sends bare identifiers to the hand-read bucket too. **This SUPERSEDES the 56/63 recorded in the spec on 2026-09-09**, whose classifier scoped to `H_` handlers only and therefore could not see `MifInstListComponents` (count=`All.Num()` while the emitted `Rows` is filtered by name, limit and includeNative). Both measurements total 63 by coincidence - `MifInstListComponents` came in as `capture_camera`'s nested `Vp->count` went out - so the matching total was not confirmation. The other two that moved: `H_list_layers` (`MifBridgeStreaming.cpp:2347-2352`, count=`Names.Num()` with the row loop breaking at `Limit`) and `H_add_virtual_bone` (state after the write, beside `countBefore`).","engine":"","ue":[],"bl":[],"section":"MifBridge API contracts","status":"superseded"},{"id":"A38","fact":"**`UPoseAssetFactory::FactoryCreateNew` returns `nullptr` when `SourceAnimation` is null, and does ALL its work inside that branch** - `TargetSkeleton = SourceAnimation->GetSkeleton()`, `NewObject<UPoseAsset>`, `CreatePoseFromAnimation(SourceAnimation, &InputPoseNames)`, `SetSkeleton(TargetSkeleton)`. There is no fallback path: without a source animation the engine's own factory declines to make a PoseAsset at all. So `create_asset`, which never calls the factory, is the only way to obtain a PoseAsset with no poses and no skeleton - which makes PoseAsset one of the strongest entries in `FactoryInitClasses`, not a marginal one.","source":"5.3.2","engine":"","ue":[],"bl":[],"section":"MifBridge API contracts","value":"read 2026-09-14 from `Editor/UnrealEd/Private/Factories/PoseAssetFactory.cpp:223` (`UPoseAssetFactory::FactoryCreateNew`). Settled a live question: the entry had been removed from `FactoryInitClasses` in an undescribed change and the choice was whether it was the UMaterial false-alarm shape (work behind a condition that is normally false and harmless) or the UMaterialInstanceConstant shape (work behind a plain null check that really runs). It is neither - it is stronger than both, because the guarded branch is the ONLY branch. Restored in 31c4415e."},{"id":"A41","fact":"**`set_variable_flags` broke its own PARTIAL-UPDATE contract in both directions: a key the caller never sent was silently changed.** Its accepted summary promises \"only the keys actually present are applied, the rest are left alone\". Measured on a 5.8 probe against a variable set to `replicated:true, repNotify:true`: sending **`{replicated:true}` alone** cleared `repNotify` to false and `repNotifyFunction` to null; sending **`{repNotify:false}` alone** cleared `replicated` to false. Both returned `ok:true` with no warning. Sending `{transient:true}` alone changed nothing, because the whole replication block is guarded by `JHasAny` on the four replication keys - which is why the breach only shows when one of them is present. MECHANISM: `bWantRepNotify` is false whenever neither `repNotify` nor `repNotifyFunction` is PRESENT, so the else-branch that clears `CPF_RepNotify` fired on absence rather than on a request; and `bReplicated` defaulted to `bWantRepNotify` rather than to the variable's current `CPF_Net`, so an absent `replicated` computed as false. The OnRep graph is left in the blueprint while the variable stops pointing at it, so the loss is invisible until a replicated value silently stops notifying.","source":"measured 2026-09-14 on the 5.8 probe (a scratch project, port 8848), read back through `list_variables`, which reports every flag. Handler: `Source/MifBridge/Private/MifBridgeIntrospect.cpp`, `H_set_variable_flags`. Fixed by gating the clear on `bMentionedRepNotify` and defaulting `replicated` to `bCurrentlyReplicated`; the legitimate clears now report `repNotifyCleared`. Verified after: `replicated` alone keeps repNotify, `repNotify:false` keeps replicated, and `replicated:false` still turns both off as asked. No regression in test_modal_hazards 33/0, test_mvvm 39/0, test_ue_variable_params 15/0, test_undo_integrity 23/0.","engine":"","ue":[],"bl":[],"section":"MifBridge API contracts"},{"id":"Mc9e2b6","fact":"ResolveClass treats an empty or 'self' class name as the blueprint's own class by design (correct only for add_function_call); every endpoint where the class is mandatory now uses ResolveClassStrict and rejects an empty value, because the downstream IsChildOf guards are satisfied by self.","source":"MifBridgeCommon.cpp ResolveClass :4481-4493 (formula verified verbatim), ResolveClassStrict :4521, ResolveClassStrictField :4542; docs/01_POSTMORTEMS.md PM-001 (:1608); docs/02_GOTCHAS.md §1 § Class parameters with the per-endpoint alias table","engine":"","ue":[],"bl":[],"section":"MifBridge parameter grammar","value":"`if (N.IsEmpty() || N.Equals(TEXT(\"self\"), ESearchCase::IgnoreCase)) { return ContextBP->SkeletonGeneratedClass ? ContextBP->SkeletonGeneratedClass : ContextBP->GeneratedClass; }`"},{"id":"M5a92f1","fact":"A full FKismetEditorUtilities::CompileBlueprint must never run inside an FScopedTransaction, because compiling reinstances the generated class and trashes the old class/CDO, so a later Ctrl-Z restores pointers to freed objects and crashes the editor. A skeleton-only regen (MarkBlueprintAsStructurallyModified) is NOT a full compile and is transaction-safe.","source":"docs/00_ARCHITECTURE.md:308-313; enforced by the IsSelfManagedEndpoint bucket, MifBridgeCommon.cpp:845 (comment block :840-845 states the reinstancing reason)","engine":"","ue":[],"bl":[],"section":"MifBridge transactions"},{"id":"Mca8ab8","fact":"A UAssetImportTask importing a skeletal FBX returns ONLY the SkeletalMesh from GetObjects(). The Skeleton the factory creates beside it - and the AnimSequence, when the task's UFbxImportUI has bImportAnimations set - are separate packages in the destination folder that are NOT returned. Anything that saves \"what the import returned\" leaves them unsaved, and the saved mesh then references a skeleton that is not on disk.","source":"Live 2026-09-10 on the 5.3 probe: after import_asset save:true (which saved GetObjects() packages only) SKM_ProbeHuman was on disk while SKM_ProbeHuman_Skeleton and SKM_ProbeHuman_Anim read dirty:true, existsOnDisk:false. Re-runnable: tools/test_import_animation.py T3300 (numImported, createdAlongside naming the Skeleton).","engine":"5.3 (live).","ue":["5.3"],"bl":[],"section":"FBX import","value":"GetObjects() -> 1 object (the mesh); the destination also gains <Name>_Skeleton and <Name>_Anim"},{"id":"AN1","fact":"Every notify and notify-track mutator in `UAnimationBlueprintLibrary` ends by calling `RefreshCacheData()` on the sequence, so a caller that inspects the track array AFTER one of them sees the repaired state, never the one it started from","source":"`Editor/AnimationBlueprintLibrary/Private/AnimationBlueprintLibrary.cpp`: `AddAnimationNotifyEvent` :680, `AddAnimationNotifyStateEvent` :741, `RemoveAnimationNotifyEventsByName` :1011, `RemoveAnimationNotifyEventsByTrack` :1037, `AddAnimationNotifyTrack` :1082, `RemoveAnimationNotifyTrack` :1131","engine":"5.3.2","ue":["5.3"],"bl":[],"section":"UE animation — notify tracks","value":"add/remove notify, add/remove track → `AnimationSequenceBase->RefreshCacheData()`"},{"id":"AN2","fact":"`RefreshCacheData` repairs any notify whose `TrackIndex` has no track. It ensures (silenced only for a package `bIsCookedForEditor`), resets an index below 0 or above 20 to 0, appends tracks with `AddNewTrack` until the index is valid, then reassigns every notify to the first track from its index on that `CanNotifyUseTrack` (appending one if none) and **overwrites `Notify.TrackIndex`**. A sequence with notifies and an EMPTY `AnimNotifyTracks` (the cooked shape: `Notifies` survives the cook, the track array is editor-only) therefore comes out of any refreshing call with synthesised tracks and rewritten indices","source":"`Runtime/Engine/Private/Animation/AnimSequenceBase.cpp`, `UAnimSequenceBase::RefreshCacheData` :702 — ensure :718, clamp :721-723, synthesise :726-728, fallback track :748, index overwrite :755","engine":"5.3.2","ue":["5.3"],"bl":[],"section":"UE animation — notify tracks","value":"invalid index → `ensureMsgf(bIsCookedForEditor)`; `if (idx < 0 || idx > 20) idx = 0`; `while (!IsValidIndex(idx)) AddNewTrack`; `Notify.TrackIndex = TrackIndexToUse`"},{"id":"AN5","fact":"Every notify carries a stable editor-only identity, `FAnimNotifyEvent::Guid`, on every engine this plugin builds on - so AN4's \"match by identity\" is possible without inventing one. It is inside `#if WITH_EDITORONLY_DATA`, which an editor module always has","source":"`Runtime/Engine/Public/Animation/AnimTypes.h` - struct / `#if WITH_EDITORONLY_DATA` / `Guid`: 5.3.2 source and stock 5.3 :288 / :352 / :359; 5.4, 5.5, 5.6 :288 / :356 / :363; 5.7 :276 / :344 / :351; 5.8 :276 / :344 / :351. Read 2026-09-11 (the struct is NOT in Classes/Animation - AnimNotify.h:15 there is a forward declaration, which a first search mistook for the definition)","engine":"5.3.2 - 5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE animation — notify tracks","value":"`FGuid Guid;` inside `struct FAnimNotifyEvent : public FAnimLinkableElement`"},{"id":"U28","fact":"**`UPoseableMeshComponent::SetBoneRotationByName` offers ComponentSpace and WorldSpace only - there is no parent-relative option** - so a chain driven by LOCAL angles has to accumulate them caller-side and give each bone its absolute orientation. That works because the imported bones' rest orientation IS identity in component space.","source":"measured 2026-09-17: setting every bone to `FRotator::ZeroRotator` in ComponentSpace put `page_9` back at exactly its rest (15.08, 0, 0), which it would not have done had the rest orientation been anything else. Every rotation in this rig is about one axis, so the accumulation is a running sum rather than quaternion composition.","engine":"5.8","ue":["5.8"],"bl":[],"section":""},{"id":"U318","fact":"**Every transition's rule graph is named \"Transition\": the engine creates it outered to its own transition node and renames it to the nearest free name there, so the name is unique only inside that node and every rule graph in one state machine carries it.**","source":"5.3 `AnimStateTransitionNode.cpp:586-590`, 5.8 `AnimStateTransitionNode.cpp:676-680` (`CreateBoundGraph`: `CreateNewGraph(this, ...)`, then `RenameGraphToNameOrCloseToName(BoundGraph, \"Transition\")`); measured on 5.8.2: the three rule graphs of one machine all named Transition","engine":"5.3, 5.8","ue":["5.3","5.8"],"bl":[],"section":"UE Animation Blueprints - state machines, rule graphs, update hooks and cached poses"},{"id":"U319","fact":"**What lets a transition through is its rule graph's result node's `bCanEnterTransition` input: wired, a rule; left as a literal, a constant - always or never taken; and a transition on its automatic rule (`bAutomaticRuleBasedOnSequencePlayerInState`) ignores the rule graph and follows the most relevant asset player's remaining time against its Automatic Rule Trigger Time. A new transition's literal is not true: an untouched one reads false.**","source":"5.3 `AnimStateTransitionNode.h:55-57` (the property's own comment); measured on 5.8.2 through list_nodes and describe_anim_blueprint (tools/test_describe_anim_blueprint.py AR123, AR126)","engine":"5.3, 5.8","ue":["5.3","5.8"],"bl":[],"section":"UE Animation Blueprints - state machines, rule graphs, update hooks and cached poses"},{"id":"U320","fact":"**An anim Blueprint's two update hooks run on different threads: `BlueprintUpdateAnimation` is called from `UAnimInstance::UpdateAnimation` just after the subsystems' `OnPreUpdate_GameThread`, `BlueprintThreadSafeUpdateAnimation` from the anim instance proxy's update just after `OnPreUpdate_WorkerThread`. The second is declared BlueprintThreadSafe, so it can be overridden only as a function graph, never as an event.**","source":"5.3 `AnimInstance.cpp:583,596`, `AnimInstanceProxy.cpp:1274,1286`, `AnimInstance.h:516-518`; 5.8 `AnimInstance.cpp:784,797`, `AnimInstanceProxy.cpp:1343,1355`","engine":"5.3, 5.8","ue":["5.3","5.8"],"bl":[],"section":"UE Animation Blueprints - state machines, rule graphs, update hooks and cached poses"},{"id":"U321","fact":"**The Blueprint compiler fails an override event for any parent-class function that `FunctionCanBePlacedAsEvent` refuses - not overridable, static, const, thread-safe, ForceAsFunction, or with an output or reference parameter - with \"cannot be overridden and/or placed as event\"; a deprecated function's event only warns, and a const function's passes as a legacy node.**","source":"5.3 `KismetCompiler.cpp:3907-3929`, `EdGraphSchema_K2.cpp:1029`; 5.8 `KismetCompiler.cpp:3973-3981`, `EdGraphSchema_K2.cpp:932`; measured on 5.8.2: an event placed for BlueprintThreadSafeUpdateAnimation compiled with that error","engine":"5.3, 5.8","ue":["5.3","5.8"],"bl":[],"section":"UE Animation Blueprints - state machines, rule graphs, update hooks and cached poses"},{"id":"U322","fact":"**A Use Cached Pose node holds a pointer to its Save Cached Pose node and the cache name it last showed. Compile keeps the pointer only while that save node's pose input is wired, else finds the save node by that name, and a use neither resolves is a compile error (\"does not have an associated Save Cached Pose node\"). The name is written only when the node's title is computed while its cache exists.**","source":"5.3 `AnimGraphNode_UseCachedPose.cpp:26-75,86,232`, 5.8 `AnimGraphNode_UseCachedPose.cpp:28,88,234`; measured on 5.8.2: a use pointed at a cache and never listed recorded no name, and one listed first recorded it (tools/test_describe_anim_blueprint.py AR154)","engine":"5.3, 5.8","ue":["5.3","5.8"],"bl":[],"section":"UE Animation Blueprints - state machines, rule graphs, update hooks and cached poses"},{"id":"U323","fact":"**A new anim Blueprint's update event is titled \"Event BlueprintUpdateAnimation\" - no spaces, because the function has no display name - and sits beside a TryGetPawnOwner call with nothing wired to either.**","source":"measured on 5.8.2: list_nodes on a new anim Blueprint's EventGraph (a title match on \"Update Animation\" found nothing)","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE Animation Blueprints - state machines, rule graphs, update hooks and cached poses"},{"id":"U324","fact":"**The engine's own readers of a state machine's wiring index pins unchecked: `UAnimStateTransitionNode::GetPreviousState` and `GetNextState` read `Pins[0]` and `Pins[1]` with no bounds check, and `UAnimStateEntryNode::GetOutputNode` asserts that the entry has at most one link.**","source":"5.3 `AnimStateTransitionNode.cpp:253,265`, `AnimStateEntryNode.cpp:47`; 5.8 `AnimStateTransitionNode.cpp:297,309`, `AnimStateEntryNode.cpp:49`","engine":"5.3, 5.8","ue":["5.3","5.8"],"bl":[],"section":"UE Animation Blueprints - state machines, rule graphs, update hooks and cached poses"},{"id":"A92","fact":"**`describe_anim_blueprint` reads the 5.8 Third Person mannequin's anim Blueprint, ABP_Unarmed, as 2 machines - Locomotion, 2 states and 2 transitions, entered at Idle; Main States, 4 states, 2 aliases and 6 transitions, entered at Locomotion - with 1 slot, 1 Control Rig node, 1 cached pose in use, 7 variables and a game-thread update; each machine's states, aliases and transitions equal list_nodes' own count of the machine's graph.**","source":"measured: tools/test_describe_anim_blueprint.py AR210-AR211 on 5.8.2 (the stock UE 5.8 Third Person template's Characters content copied into a scratch probe for the run, then removed)","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE Animation Blueprints - state machines, rule graphs, update hooks and cached poses"},{"id":"A93","fact":"**`describe_anim_blueprint` reads planted content exactly: copies of a machine and of an anim Blueprint are grouped, and a near miss - one more state, one missing transition - is not; sameAssets is true for duplicates and false where one state plays another sequence; canEnter reads wired, true, false and automatic as planted; ik lists TwoBoneIK and FABRIK and not LookAt; cached poses count their uses, an unused cache and the uses no cache answers; the update verdict is both, threadSafe or none as wired, the ghost event and an unwired thread-safe function counting as none; writtenFromOutside finds the one live outside Set and not a disabled Set, a read, a same-named variable of another class or a copy's self-write.**","source":"measured: tools/test_describe_anim_blueprint.py, 49/49 on 5.8.2","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE Animation Blueprints - state machines, rule graphs, update hooks and cached poses"},{"id":"A94","fact":"**A transition rule graph's id names its transition - `<machine>.Transition(Idle->Walk)`, and a second between the same two states `#2` - so every graph in an anim Blueprint has its own id; a node added through the second rule's id lands there and not in the first; the old `<machine>.Transition` still resolves where the machine has one rule, and is refused, listing the new ids, where it has several.**","source":"measured: tools/test_describe_anim_blueprint.py AR130-AR135 on 5.8.2 (read before the change: ResolveGraph's exact-path loop returned the first graph whose path matched, so every rule after the first was unreachable)","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE Animation Blueprints - state machines, rule graphs, update hooks and cached poses"},{"id":"A95","fact":"**`add_override_event` refuses a function the compiler will not take as an event - BlueprintThreadSafeUpdateAnimation among them - naming the reason and implement_interface_function, and adds no node; a placeable hook, BlueprintInitializeAnimation, still places; implement_interface_function overrides the thread-safe hook as a function graph.**","source":"measured: tools/test_describe_anim_blueprint.py AR170-AR171 and its fixtures on 5.8.2","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE Animation Blueprints - state machines, rule graphs, update hooks and cached poses"},{"id":"U363","fact":"**UAnimStateTransitionNode derives from UAnimStateNodeBase, as UAnimStateNode, UAnimStateConduitNode and UAnimStateAliasNode do - so counting UAnimStateNodeBase in a state machine counts its transitions too.**","source":"5.3 and 5.8 `Editor/AnimGraph/Public/AnimStateTransitionNode.h:20`; measured on 5.8.2: add_anim_state's old statesInMachine read 5 for three states and two transitions","engine":"5.3, 5.8","ue":["5.3","5.8"],"bl":[],"section":"UE Animation Blueprints - conduits, placed and counted"},{"id":"U364","fact":"**A conduit's bound graph is a UAnimationTransitionGraph that its PostPlacedNewNode creates, and its name is that graph's name (GetStateName).**","source":"5.3 `Editor/AnimGraph/Private/AnimStateConduitNode.cpp:52`, `:67`, `:71`; 5.8 the same file `:54`, `:69`, `:73`","engine":"5.3, 5.8","ue":["5.3","5.8"],"bl":[],"section":"UE Animation Blueprints - conduits, placed and counted"},{"id":"A107","fact":"**`add_anim_state {kind: \"conduit\"}` places a UAnimStateConduitNode named as asked, whose boundGraphId holds the bCanEnterTransition result; connect_pins joins it like a state, describe_anim_blueprint reads it as a conduit with its rule and the transitions through it, the counts agree with list_nodes, and the Anim Blueprint compiles. statesInMachine counts states only (3 with a conduit and two transitions) and conduitsInMachine the conduits.**","source":"measured: tools/test_describe_anim_blueprint.py AR220-AR226, 55/55 on 5.8.2 and 5.3.2; the rows failed on the unfixed build","engine":"5.3, 5.8","ue":["5.3","5.8"],"bl":[],"section":"UE Animation Blueprints - conduits, placed and counted"},{"id":"U473","fact":"**SetMorphTarget on a placed editor skeletal mesh stores the weight and draws nothing: the renderer reads MorphTargetWeights, which RefreshMorphTargets fills from the overrides, and the editor tick calls it only in the branch where the component is not refreshing its bone transforms. InitAnim (public) ends in RefreshMorphTargets.** Measured through capture_camera: SKM_ProbeMorph (a cube body with a Bulge morph, the body scaled 1.25) drew the same 388 px at weight 0 and 1 on 5.8.2 until InitAnim ran; after it, 792 px at 0 and 1243 at 1 (5.8.2), 790 and 1242 (5.3.2).","source":"5.3 `Runtime/Engine/Private/Components/SkinnedMeshComponent.cpp:1396` (the editor-only refresh) and 5.3 `Runtime/Engine/Private/Components/SkeletalMeshComponent.cpp:1030` (InitializeAnimScriptInstance's RefreshMorphTargets); 5.8 `Runtime/Engine/Private/Components/SkinnedMeshComponent.cpp:1905` and 5.8 `Runtime/Engine/Private/Components/SkeletalMeshComponent.cpp:1413`; tools/test_ue_spec_extensions_c.py SX1403. Read and measured 2026-10-04","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE skinned-mesh previews, skeletal FBX import options, locomotion slip"},{"id":"U474","fact":"**A placed skinned mesh's morph weights and hidden material sections are not saved with the level: MorphTargetCurves is UPROPERTY(Transient) on 5.8 and no UPROPERTY at all on 5.3, and LODInfo (HiddenMaterials) is UPROPERTY(transient) on both.** Measured on 5.8.2 and 5.3.2: a level saved with Bulge at 0.75 and section 1 hidden, reloaded, read MorphTargetCurves empty (5.8; unreflected on 5.3), the weight 0 and HiddenMaterials empty.","source":"5.3 `Runtime/Engine/Classes/Components/SkeletalMeshComponent.h:1560` and 5.3 `Runtime/Engine/Classes/Components/SkinnedMeshComponent.h:547`; 5.8 `Runtime/Engine/Classes/Components/SkeletalMeshComponent.h:1778` and 5.8 `Runtime/Engine/Classes/Components/SkinnedMeshComponent.h:777`. Read and measured 2026-10-04","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE skinned-mesh previews, skeletal FBX import options, locomotion slip"},{"id":"U475","fact":"**USkinnedMeshComponent hides by MATERIAL, not by render section: IsMaterialSectionShown(MaterialID, LOD) reads LODInfo[LOD].HiddenMaterials[MaterialID], so every section drawing one slot hides together.** Measured on 5.8.2 and 5.3.2 with a two-slot body: hiding slot 1 removed its faces from a capture (825 to 604 px on both 5.8.2 and 5.3.2), hiding both left 0.","source":"5.3 `Runtime/Engine/Private/Components/SkinnedMeshComponent.cpp:3252` and 5.8 `Runtime/Engine/Private/Components/SkinnedMeshComponent.cpp:4125`; tools/test_ue_spec_extensions_c.py SX1300-SX1304, SX1404. Read and measured 2026-10-04","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE skinned-mesh previews, skeletal FBX import options, locomotion slip"},{"id":"U476","fact":"**A skeletal FBX import builds a new physics asset whenever bCreatePhysicsAsset is on and reads the PhysicsAsset field only when it is off; when FPhysicsAssetUtils::CreateFromSkeletalMesh fails it deletes the asset it made and the import still succeeds.** Measured on 5.8.2 and 5.3.2: SKM_ProbeMorph.fbx and SKM_ProbeHuman.fbx with createPhysicsAsset came in with no physics asset, the log saying the bone size was too small; with createPhysicsAsset off and PhysicsAsset set, the mesh took it.","source":"5.3 `Editor/UnrealEd/Private/Fbx/FbxSkeletalMeshImport.cpp:2190` and 5.3 `Editor/UnrealEd/Private/Fbx/FbxSkeletalMeshImport.cpp:2218`; 5.8 `Editor/UnrealEd/Private/Fbx/FbxSkeletalMeshImport.cpp:2301` and 5.8 `Editor/UnrealEd/Private/Fbx/FbxSkeletalMeshImport.cpp:2329`; tools/test_ue_spec_extensions_c.py SX1604-SX1605. Read and measured 2026-10-04","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE skinned-mesh previews, skeletal FBX import options, locomotion slip"},{"id":"U477","fact":"**An FBX's shape keys import as morph targets only with bImportMorphTargets, which is off at the class default.** Measured on 5.8.2 and 5.3.2: SKM_ProbeMorph.fbx (one shape key, Bulge) imported with zero morph targets by default and with importMorphTargets false, and with one, Bulge, with it true.","source":"5.3 `Editor/UnrealEd/Classes/Factories/FbxSkeletalMeshImportData.h:89` and 5.8 `Editor/UnrealEd/Classes/Factories/FbxSkeletalMeshImportData.h:89`; tools/test_ue_spec_extensions_c.py SX1602-SX1603. Read and measured 2026-10-04","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE skinned-mesh previews, skeletal FBX import options, locomotion slip"},{"id":"U478","fact":"**The UE tutorial walk (Tutorial_Walk_Fwd) played along a straight path at its own planted-foot speed slips 1.0-1.6 cm per stance, and over 100 cm when the mesh is keyed 90 degrees off its path; the mannequin mesh faces +Y in its own space.** Measured through bake_locomotion's unpinned measure on 5.8.2 and 5.3.2: a 1.03-scaled actor at 150 cm/s, yaw -90, 7 stances, unpinned max 1.56 cm, mean 1.03; pinned 0.00011 cm, confirmed through get_anim_pose and the actor's keys. Its planted feet travel about 192 cm/s.","source":"tools/test_ue_spec_extensions_c.py SX1100-SX1105. 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 skinned-mesh previews, skeletal FBX import options, locomotion slip"},{"id":"U479","fact":"**FPhysicsAssetUtils::CreateFromSkeletalMesh fits again at MinBoneSize 1 cm when its first pass makes no body, and reports nothing: a MinBoneSize no bone reaches returns MORE bodies than a smaller one.** Measured through create_physics_asset on 5.8.2 and 5.3.2 on the tutorial mannequin (68 bones, capsules): 32 bodies at 5 cm, 12 at 20, 8 at 40, 6 at 80, 52 at 500 - the same 52 bones as a fit at 1 cm; 68 with bodyForAll.","source":"5.3 `Developer/PhysicsUtilities/Private/PhysicsAssetUtils.cpp:515` and 5.8 `Developer/PhysicsUtilities/Private/PhysicsAssetUtils.cpp:627`; tools/test_ue_spec_extensions_c.py SX1700-SX1704. Read and measured 2026-10-04","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE skinned-mesh previews, skeletal FBX import options, locomotion slip"},{"id":"U480","fact":"**A skeletal LOD reduced by the engine's reducer keeps no morph target data unless FSkeletalMeshOptimizationSettings::bRemapMorphTargets is set, and it defaults to false.** Measured through generate_lods on 5.8.2 and 5.3.2: SKM_ProbeMorph (one morph, Bulge) reduced to two LODs had Bulge with no LOD 1 data by default and with 12 vertices of LOD 1 data with the flag on; the tutorial mannequin reduced at 0.5 and 0.25 of LOD0's 6118 triangles gave 3058 and 1528.","source":"5.3 `Runtime/Engine/Public/SkeletalMeshReductionSettings.h:190` and 5.8 `Runtime/Engine/Public/SkeletalMeshReductionSettings.h:195`; tools/test_ue_spec_extensions_c.py SX1801, SX1807-SX1808. Read and measured 2026-10-04","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE skinned-mesh previews, skeletal FBX import options, locomotion slip"},{"id":"U481","fact":"**An additive AnimSequence with RefPoseType ABPT_AnimScaled takes its base pose from RefPoseSeq at the same normalized time, so a local-space additive written as (target pose) over a base of the same length plays back the target pose on top of that base, frame for frame.** Measured through bake_locomotion's lean on 5.8.2 and 5.3.2 (UAnimPoseExtensions, raw data, additive accumulated onto its base): the engine's contact bones within 0.0001 cm (5.8.2) and 0.009 cm (5.3.2) of the pose written.","source":"5.3 `Runtime/Engine/Private/Animation/AnimSequence.cpp:1776` and 5.8 `Runtime/Engine/Private/Animation/AnimSequence.cpp:1943`; tools/test_ue_spec_extensions_d.py SX1900-SX1906. Read and measured 2026-10-04","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE skinned-mesh previews, skeletal FBX import options, locomotion slip"},{"id":"U482","fact":"**USceneCaptureComponent2D's FOVAngle is the horizontal field of view: a capture with FOVAngle = 2 atan(sensorWidth / (2 focalLength)) frames what a cine camera does (35 mm on a 23.76 mm filmback = 37.497 degrees).** Measured on 5.8.2 and 5.3.2: a 100 cm cube 550 cm in front of the lens, its 8 bounding corners projected with fx = (W/2) / tan(hfov/2) on a 640x360 capture, against the pixels it changes: within 1.2 px (5.8.2) and 2.2 px (5.3.2), 171.4 px tall.","source":"5.3 `Runtime/Engine/Classes/Components/SceneCaptureComponent2D.h:31` and 5.8 `Runtime/Engine/Classes/Components/SceneCaptureComponent2D.h:44`; tools/test_ue_spec_extensions_d.py SX2100-SX2102. Read and measured 2026-10-04","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE skinned-mesh previews, skeletal FBX import options, locomotion slip"},{"id":"U483","fact":"**A MetaHuman character's parametric body is not an asset property: UMetaHumanCharacterEditorSubsystem::GetBodyConstraints answers only for a character added for editing (TryAddObjectToEdit) and returns nothing otherwise, and a body set with SetBodyConstraints persists past the session only after CommitBodyState.** Measured through describe_metahuman_character and call_function on 5.8.2: a fresh character reads 30 constraints, Height 171.7 cm (135-220), none active; Height set active at 185 and committed read 185 in a new session, Chest refitted 104.8 -> 110.2.","source":"5.8 Engine/Plugins/MetaHuman/MetaHumanCharacter/Source/MetaHumanCharacterEditor/Private/MetaHumanCharacterEditorSubsystem.cpp:5533`; tools/test_ue_spec_extensions_d.py SX2200-SX2204. Read and measured 2026-10-04","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE skinned-mesh previews, skeletal FBX import options, locomotion slip"},{"id":"U496","fact":"**`UMetaHumanCharacterExportBlueprintLibrary::ExportGeometry` has no rig or texture gate - it needs only the character open for editing (TryAddObjectToEdit) - so an unrigged character's head, body and combined skeletal meshes export offline,** on the engine's metahuman_base_skel with gray topology materials. Measured 2026-10-04 on 5.8.2 through call_function on the library CDO: ten unrigged characters exported (3 meshes each). ExportDCC, by contrast, checks the rig and the textures.","source":"5.8 `MetaHuman/MetaHumanCharacter/Source/MetaHumanCharacterEditor/Private/MetaHumanCharacterExportBlueprintLibrary.cpp:428` and 5.8 `MetaHuman/MetaHumanCharacter/Source/MetaHumanCharacterEditor/Private/MetaHumanCharacterExportBlueprintLibrary.cpp:450`","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":"U498","fact":"**Manny clips retargeted onto a MetaHuman body keep planted toes only with a Blend to Source op on the legs: without it planted balls drifted 1.15 / 1.62 cm per stance; with BlendToSource 1 (TranslationAlpha 1) 0.00 cm on all 32 stances, toes within 0.08 cm of Manny's on every key.** Measured 2026-10-04 on 5.8.2, get_anim_pose component space over all 235 keys of a run clip (source SKM_Manny_Simple, target an exported MetaHuman body; stance = Manny ball z < 2 cm); the op was added with IKRetargeterController.AddRetargetOp through call_function (5.8 places it before Run IK Rig). Defaults are BlendToSource 0, TranslationAlpha 1. 5.8 marks the old IK Chains op Deprecated.","source":"5.8 `Animation/IKRig/Source/IKRig/Public/Retargeter/RetargetOps/BlendToSourceOp.h:33` and 5.8 `Animation/IKRig/Source/IKRig/Public/Retargeter/RetargetOps/BlendToSourceOp.h:39`; 5.8 `Animation/IKRig/Source/IKRigEditor/Public/RetargetEditor/IKRetargeterController.h:109`; 5.8 `Animation/IKRig/Source/IKRig/Public/Retargeter/RetargetOps/IKChainsOp.h:199`","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":"U503","fact":"**A physical animation profile's per-body data is USkeletalBodySetup::PhysicalAnimationData, a private bare UPROPERTY() array; the asset's PhysicalAnimationProfiles name list is separate, and a profile works at runtime only through a PhysicalAnimationComponent on bodies that simulate.** Measured in PIE with two tutorial ragdolls landing from 1 m (profile local, applied below the pelvis): mean local bone deviation from the pose 49 degrees without a profile, 44 at orientation strength 1000 and 15 at 10000 on 5.8.2; 53 without and 7.4 at 10000 on 5.3.2.","source":"5.3 `Runtime/Engine/Classes/PhysicsEngine/PhysicsAsset.h:469` and 5.8 `Runtime/Engine/Classes/PhysicsEngine/SkeletalBodySetup.h:73`; tools/test_ue_spec_extensions_g.py SX3704. Read and measured 2026-10-04","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE generic routes settled live - create_asset, set_property, edit_container, call_function, evaluate_sequence (UE 5.8.2 scratch editor, 2026-10-04)"},{"id":"U504","fact":"**A skeletal mesh component that is not rendered skips its pose tick by default (VisibilityBasedAnimTickOption), so a ragdoll simulating off screen never moves its bones.** Measured on 5.8.2: SetAllBodiesBelowSimulatePhysics on, IsSimulatingPhysics true, and the bone transforms the same for three seconds; with AlwaysTickPoseAndRefreshBones the bodies' motion reached the bones.","source":"5.3 `Runtime/Engine/Classes/Components/SkinnedMeshComponent.h:564` and 5.8 `Runtime/Engine/Classes/Components/SkinnedMeshComponent.h:794`. 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":"U505","fact":"**FPhysicsAssetUtils::GenerateSkinnedTriangleMesh (5.6+) puts the whole mesh LOD on the asset's ROOT body as one skinned triangle mesh and returns that body's index; it takes no bone.** Measured through add_physics_body {geomType: skinnedTriangleMesh} on the tutorial physics asset (5.8.2): the pelvis body, 3189 vertices, 6118 triangles.","source":"5.6 `Developer/PhysicsUtilities/Public/PhysicsAssetUtils.h:188` and 5.8 `Developer/PhysicsUtilities/Public/PhysicsAssetUtils.h:216`; tools/test_ue_spec_extensions_g.py SX3708. Read and measured 2026-10-04","engine":"5.6-5.8","ue":["5.6","5.7","5.8"],"bl":[],"section":"UE generic routes settled live - create_asset, set_property, edit_container, call_function, evaluate_sequence (UE 5.8.2 scratch editor, 2026-10-04)"},{"id":"U506","fact":"**UDataflowEditorBlueprintLibrary::AddDataflowNode takes the node's StaticType, which DATAFLOW_NODE_DEFINE_INTERNAL makes the C++ struct name (FChaosClothAssetSkeletalMeshImportNode_v2), not its display name; the display name ('SkeletalMeshImport') returns None and adds nothing.** Measured through call_function on 5.8.2.","source":"5.8 `Runtime/Dataflow/Core/Public/Dataflow/DataflowNode.h:986` and 5.8 `ChaosClothAssetDataflowNodes/Source/ChaosClothAssetDataflowNodes/Public/ChaosClothAsset/SkeletalMeshImportNode.h:18`; tools/test_ue_spec_extensions_g.py SX4002. Read and measured 2026-10-04","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":"U507","fact":"**The 5.8 UChaosClothAssetFactory opens a template picker in an interactive editor and embeds the empty cloth template only on its non-interactive branch (automation, unattended, commandlet or GIsRunningUnattendedScript); a cloth asset's PhysicsAsset is EditCondition !HasDataflow and the graph's evaluation overwrites it.** Measured on 5.8.2: with GIsRunningUnattendedScript held, the factory made the asset with an embedded Dataflow holding one ClothAssetTerminal node; a physics asset written on the asset read None after the terminal was evaluated; a Skeletal Mesh Import node of the tutorial mannequin built one LOD of 3924 vertices and 68 bones.","source":"5.8 `ChaosClothAsset/Source/ChaosClothAssetTools/Private/ChaosClothAsset/ClothAssetFactory.cpp:31` and 5.8 `ChaosClothAsset/Source/ChaosClothAssetTools/Private/ChaosClothAsset/ClothAssetFactory.cpp:51`; 5.8 `ChaosClothAsset/Source/ChaosClothAssetEngine/Public/ChaosClothAsset/ClothAsset.h:248`; tools/test_ue_spec_extensions_g.py SX4000-SX4004. Read and measured 2026-10-04","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":"U508","fact":"**A Pose Search schema whose only skeleton carries a role other than the default finalizes to nothing: the default Trajectory and Pose channels read the default role, FinalizedChannels comes out empty and SchemaCardinality 0, and nothing says so.** Measured on 5.8.2 through create_asset {class: PoseSearchSchema, role: Rider}; the same schema without a role finalized to cardinality 28 on the tutorial skeleton. Skeletons [{Skeleton, MirrorDataTable, Role}] arrives in 5.4; 5.3 has one Skeleton.","source":"5.4 `Animation/PoseSearch/Source/Runtime/Public/PoseSearch/PoseSearchSchema.h:71` and 5.8 `Animation/PoseSearch/Source/Runtime/Private/PoseSearchSchema.cpp:81`; tools/test_ue_spec_extensions_g.py SX3905-SX3906. Read and measured 2026-10-04","engine":"5.4-5.8","ue":["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":"U509","fact":"**A Pose Search database keeps its animations as TArray<FInstancedStruct> AnimationAssets on 5.3 (FPoseSearchDatabaseSequence and its siblings) and as DatabaseAnimationAssets of FPoseSearchDatabaseAnimationAsset on 5.7+, where the BlueprintPure GetNumAnimationAssets reads them; the search index is not reflected.** Measured on 5.8.2: two clips written by create_pose_search_database, GetNumAnimationAssets 2.","source":"5.3 `Experimental/Animation/PoseSearch/Source/Runtime/Public/PoseSearch/PoseSearchDatabase.h:283` and 5.8 `Animation/PoseSearch/Source/Runtime/Public/PoseSearch/PoseSearchDatabase.h:697`; tools/test_ue_spec_extensions_g.py SX3907-SX3908. Read and measured 2026-10-04","engine":"5.3-5.8","ue":["5.3","5.4","5.5","5.6","5.7","5.8"],"bl":[],"section":"UE generic routes settled live - create_asset, set_property, edit_container, call_function, evaluate_sequence (UE 5.8.2 scratch editor, 2026-10-04)"},{"id":"U522","fact":"**Splitting a skeletal animation section in 5.8 gives the new right-hand section FirstLoopStartFrameOffset = the clip time at the split (Params.MakeTransform(TickResolution, Range).TransformTime(SplitTime) in seconds x tick resolution) and restores the left part's own offset; the section's timing transform is built from its own range start (StartFrameOffset, EndFrameOffset, FirstLoopStartFrameOffset and PlayRate are all relative to it), so moving a whole section along the timeline keeps exactly what it plays, and a Params copy on a new section of the same length plays the same frames.** Used 2026-10-09 to copy running sections into an inserted stretch of a sequence.","source":"5.8 `Runtime/MovieSceneTracks/Private/Sections/MovieSceneSkeletalAnimationSection.cpp:56-86` (MakeTransform), `:405-417` (SplitSection). Read 2026-10-09","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":"U526","fact":"**A skeletal mesh component renders the mesh in SkeletalMeshAsset - a Transient mirror whose setter (SetSkeletalMeshAsset) keeps SkinnedAsset in step - so a raw SkinnedAsset write leaves it on the old mesh.** Writing SkinnedAsset read back changed, but SkeletalMeshAsset stayed old and PIE posed the old mesh, for a placed actor's component, a Blueprint's SCS template (<Name>_GEN_VARIABLE) and a native subobject on a Blueprint's class defaults (a Character's CharacterMesh0); a new instance of the SCS Blueprint even came back with SkinnedAsset old too. Writing SkeletalMeshAsset (its setter) changed both everywhere. A poseable mesh component (U29, U57) has no mirror; SkinnedAsset is its field.","source":"measured 2026-10-11 on 5.3.2, 5.4.4 and 5.8.2 (two meshes, read_component_pose bone count in PIE); 5.3 `Runtime/Engine/Classes/Components/SkeletalMeshComponent.h:324`; 5.8 `Runtime/Engine/Classes/Components/SkeletalMeshComponent.h:361`; 5.3 `Runtime/Engine/Classes/Components/SkinnedMeshComponent.h:211`; 5.8 `Runtime/Engine/Classes/Components/SkinnedMeshComponent.h:285`","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":"U528","fact":"**A Transform (Modify) Bone node with rotation mode Additive in Component Space on a mid-spine bone turns every bone above it (clavicles and arms included); placed BEFORE a Two Bone IK in the chain, the IK solves against the turned spine and its effector still reaches its world target, while a turn placed after the IK would carry the solved hand off target.** Measured: 0.4 x a 50 deg view yaw on spine_03 of a MetaHuman body turned both clavicles 20 deg in world space with the component unturned.","source":"measured 2026-10-12 on 5.8.2 (read_component_pose on a PIE pawn before and after SetControlRotation: clavicle yaw -159.3 -> -137.6, component yaw -180 both times)","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":"U555","fact":"**AWheeledVehiclePawn's constructor turns physics simulation OFF on its skeletal mesh (`Mesh->BodyInstance.bSimulatePhysics = false`), so a Blueprint subclass placed in a level just sits where it was placed in play until Simulate Physics is set on its VehicleMesh** (on the Blueprint's component defaults, and on any instance placed before that change: those keep the old value).","source":"5.8: Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Private/WheeledVehiclePawn.cpp:18 (read in the stock install 2026-10-16); measured 2026-10-16 on 5.8.3: a placed 6-wheel subclass read bSimulatePhysics false in PIE and did not move; after the class default was set the already placed instances still read false; set on them, they settled onto their suspension","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":"U561","fact":"**A PhysicsAsset body's primitives (AggGeom boxes, spheres, capsules) are expressed in its BONE's frame, not the skeletal mesh component's. On a mesh whose root bone is rotated in the reference pose (here yawed 90 degrees, as a Blender armature bone along +X can come in), a box written in mesh coordinates lands turned by that rotation; convert mesh-space boxes with the bone's component-space transform first (an auto-fitted body shows the rotation as an odd Roll / swapped extents).**","source":"measured 2026-10-16 on 5.8.3: root bone component-space rotation yaw 90 (get_socket_transform RTS_COMPONENT); a box set to X 1264 / Y 303 (mesh length along mesh X) traced as 3 m long and 12 m wide in mesh space; the same box with X and Y swapped and the centre turned (bone = (mesh y, -mesh x)) traced at the mesh's real extents. After a set_property on SkeletalBodySetups[N].AggGeom, placed components picked the new shape up only after set_physics_asset(None, True) then set_physics_asset(pa, True)","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":"U629-superseded","fact":"**SUPERSEDED 2026-10-19 by U629 below - the cause was wrong: Unreal never reads FBX shape normals at all; it computes one normal change per point. Blender's FBX exporter writes a shape key's normals as ZERO (the blend shape's `Normals` array is all 0), and Unreal's skeletal import reads that: the morph target's normal deltas (`TangentZDelta`) are 0 at every vertex, so faces a shape key turns keep their base normals under the morph. Measured with a cube body whose shape key is the body turned 30 degrees about Z: the file's 8 shape normals 0; in Unreal 24 render-vertex deltas, each moved 14.64 cm (the file's), none with a normal change - under the skeletal import's normals compute, import and importWithTangents alike. A shape key that only scales would change no normal anyway.**","source":"Live on stock UE 5.8.3 and 5.3.2, 2026-10-19: tools/fixtures/SKM_ProbeMorphTurn.fbx (tools/make_morph_fixture.py --turn, Blender 4.4) through import_asset and list_morph_targets deltas (tools/test_lane13_x2b.py MD101; the file's shape read from its binary nodes); engine source not read.","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)","status":"superseded","replacedBy":"U629"},{"id":"U625","fact":"**Motion matching's trajectory input is the Motion Matching node's own Trajectory on 5.3, the Pose Search History Collector's Trajectory on 5.4-5.5, and the collector's TransformTrajectory (an FTransformTrajectory, shown as \"Trajectory\") from 5.6, where the old one is deprecated; bound to an Animation Blueprint variable of that type through set_anim_node_pin the graph compiles clean** (RS1506 on 5.3.2, 5.5.4, 5.7.4 and 5.8.3, 2026-10-18; on 5.7.4 set_anim_node_pin refuses Trajectory, offering TransformTrajectory).","source":"5.3 `AnimNode_MotionMatching.h:60`; 5.5 `AnimNode_PoseSearchHistoryCollector.h:73`; 5.8 `AnimNode_PoseSearchHistoryCollector.h:84`","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":"U632","fact":"**`StaticMeshEditorSubsystem.set_lods(mesh, EditorScriptingMeshReductionOptions(auto_compute_lod_screen_size=False, reduction_settings=[EditorScriptingMeshReductionSettings(percent_triangles, screen_size), ...]))` from editor Python builds the whole static-mesh LOD chain at the given screen sizes; read back with `get_lod_count`, `get_number_verts(mesh, i)` and `get_lod_reduction_settings(mesh, i).percent_triangles`. Example: [1.0/1.0, 0.45/0.3, 0.18/0.1] on a 12,336-vertex mesh gave LOD vertex counts 12,336 / 7,258 / 2,704. The mesh is left dirty, not saved. Separately, the bridge's `generate_lods` refuses `screen_sizes` unless `auto_screen_size:false` is passed (\"the engine would compute them and discard yours\"), and refuses `read_settings` on a static mesh (that is for skeletal meshes only).**","source":"Live, 2026-10-19: UE 5.8.3 editor Python on seven static meshes, with values read back after the call; `generate_lods` refusal texts captured verbatim from the bridge's replies.","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":"U672","fact":"**On 5.6 and 5.7, cancelling an animation sequence's compression can deadlock the game thread: TryCancelAsyncTasks holds the clip's compressed-data lock for reading while FRequestOwner::Cancel waits for the request, and a DDC hit's serialize task in that request wants the lock for writing.** cdb on a hung 5.6.1 editor (CPU idle) shows both stacks. 5.8 takes another mutex there. a.SkipDDC 1 sends the request to a build, whose lock comes after its cancel check, and WaitOnExistingCompression(true) does not cancel.","source":"5.6 `Engine/Source/Runtime/Engine/Private/Animation/AnimSequence.cpp:5411`; 5.6 `Engine/Source/Runtime/Engine/Private/Animation/AnimationCompressionDerivedData.cpp:139`; 5.7 `Engine/Source/Runtime/Engine/Private/Animation/AnimSequence.cpp:5431`","engine":"5.6-5.7","ue":["5.6","5.7"],"bl":[],"section":"UE generic routes settled live - create_asset, set_property, edit_container, call_function, evaluate_sequence (UE 5.8.2 scratch editor, 2026-10-04)","value":"1 hang at about the 50th retarget without; 300 of 300 with a.SkipDDC held (test loop, 5.6.1)"},{"id":"U690","fact":"On 5.6 and 5.7 a clip marked garbage while it compiles makes the next clip's cache step assert IsValid() in the same frame: BeginCacheDerivedData calls FinishCompilation({this, RefPoseSeq}) with a null RefPoseSeq, Contains(null) on the weak-pointer queue matches the dead entry, and the null it queues asserts in GetName. 5.8 tests the pointer first. Clearing dead entries with FAssetCompilingManager::ProcessAsyncTasks(false) before an export ended it.","source":"5.6 Engine/Source/Runtime/Engine/Private/Animation/AnimSequence.cpp:5124; 5.6 Engine/Source/Runtime/Engine/Private/Animation/AnimationSequenceCompiler.cpp:156; 5.8 Engine/Source/Runtime/Engine/Private/Animation/AnimationSequenceCompiler.cpp:158; test_ue_spec_extensions_e.py SX2600 on 5.6.1","engine":"5.6-5.7","ue":["5.6","5.7"],"bl":[],"section":"UE animation export, constraint keys, reimport, cloners and saved scalability"},{"id":"U691","fact":"On 5.4 and 5.5 SetConstraintActiveKey at a frame that already has a key REMOVES that key and answers false, because CanAddKey treats it as a toggle before the value is compared. Leaving a key that already holds the value alone reads back {0 on, 30 off}.","source":"5.5 Engine/Source/Editor/MovieSceneTools/Private/Constraints/TransformConstraintChannelInterface.cpp:41; test_mh_quick.py QM170 on 5.4.4 and 5.5.4","engine":"5.4-5.5","ue":["5.4","5.5"],"bl":[],"section":"UE animation export, constraint keys, reimport, cloners and saved scalability"},{"id":"U692","fact":"r.VirtualTextures defaults to 0 on 5.5 and to 1 on 5.6 and later, and from 5.5 a texture's flag set while it is 0 is turned straight back off in the texture's build.","source":"5.5 Engine/Source/Runtime/Engine/Private/Texture.cpp:64; 5.6 Engine/Source/Runtime/Engine/Private/Texture.cpp:67; 5.5 Engine/Source/Runtime/Engine/Private/Texture.cpp:687","engine":"5.5-5.8","ue":["5.5","5.6","5.7","5.8"],"bl":[],"section":"UE animation export, constraint keys, reimport, cloners and saved scalability"},{"id":"U693","fact":"On 5.5 a scene reimport puts a deleted actor back whatever bForceReimportDeletedActors says, for any actor whose name ends in _N: GetFactoryNode compares GetBaseName of the stored path, which drops the number, with the unchopped name asked for. Measured with IrScene_node_0.","source":"5.5 Engine/Source/Runtime/Interchange/Engine/Private/InterchangeSceneImportAsset.cpp:299; test_scene_reimport13.py IR101 on 5.5.4","engine":"5.5","ue":["5.5"],"bl":[],"section":"UE animation export, constraint keys, reimport, cloners and saved scalability"},{"id":"U695","fact":"An editor's [ScalabilityGroups] are read at each launch from the per-user %LOCALAPPDATA%/UnrealEngine/<version>/Saved/Config/WindowsEditor/EditorSettings.ini, for every project on that engine. Low there gives sg.EffectsQuality 0, which leaves a Fountain's emitter out so the system completes at once, and grass.densityScale 0, which grows no grass. test_niagara_scalability and test_wave3_w2e went green on 5.6.1 at epic with no other change.","source":"5.6 Engine/Source/Runtime/Engine/Private/Scalability.cpp:1171; test_niagara_scalability.py NS11 and test_wave3_w2e.py W2E202 on 5.6.1","engine":"5.6","ue":["5.6"],"bl":[],"section":"UE animation export, constraint keys, reimport, cloners and saved scalability"},{"id":"U702","fact":"**World Partition's spatial hash lays its streaming cells out on a 2D grid, so a cell's bounds have no height and altitude cannot select a cell.** `FGridLevel` derives from `FGrid2D`, and `GetCellBounds` answers an `FBox2D`. Read from source only, not measured live. `RuntimeHashSet` is a separate class and was not read.","source":"5.8: Engine/Source/Runtime/Engine/Public/WorldPartition/RuntimeSpatialHash/RuntimeSpatialHashGridHelper.h:28, :267, :415","engine":"5.8.3","ue":["5.8"],"bl":[],"section":"UE animation export, constraint keys, reimport, cloners and saved scalability"},{"id":"U703","fact":"**A deferred decal takes its texture's U from the decal's local Z and its V from local Y, not U from Y.** The pixel shader swizzles the box position before it builds the UVs, so a picture laid out for \"width along Y, height along Z\" comes out turned 90 degrees and with its proportions swapped. Measured 2026-11-04 on a wall decal with roll 0: the texture's top edge sat on local -Y; roll 90 with the box's Y and Z sizes swapped put the top up.","source":"5.8.3: Engine/Shaders/Private/DeferredDecal.usf:152-155 (DecalVector = OSPosition * 0.5 + 0.5; SwizzlePos = DecalVector.zyx)","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE animation export, constraint keys, reimport, cloners and saved scalability"},{"id":"U704","fact":"**UNavigationSystemV1::Build refuses silently from the caller's view while AsyncLoadLock (0x20) is held in an editor world, which lasts from level load until FAssetCompilingManager reports no remaining assets and a ticker delay has passed.** The only sign is the log line 'Navigation NOT building because navigation build is locked (flags: 0x20)'. Measured 2026-11-04: a build right after opening a large map did nothing and nav_status showed building false with unchanged tiles; the same call about 2.5 minutes later built.","source":"5.8.3: Engine/Source/Runtime/NavigationSystem/Public/NavigationSystem.h:102-112 (AsyncLoadLock = 1 << 5); Private/NavigationSystem.cpp:1060-1063, :5985-5997","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE animation export, constraint keys, reimport, cloners and saved scalability"},{"id":"U705","fact":"**A Custom material node wired from VertexColor's default output receives Float3, so reading `.a` fails with `vector swizzle 'a' is out of bounds`. Wire the node's A output separately.** Measured: compile error then a clean compile after adding the A pin as its own input.","source":"material_statistics compileErrors, generated Material.ush, 2026-11-04","engine":"5.8.3","ue":["5.8"],"bl":[],"section":"UE animation export, constraint keys, reimport, cloners and saved scalability"},{"id":"U706","fact":"**ProceduralMeshComponent's scene proxy builds through `InitFromDynamicVertex`, which calls `StaticMeshVertexBuffer.Init` and sets no high-precision tangents in that function. On a gentle terrain mesh under low sun this showed as zigzag light bands that follow the triangles; passing the same normal through UV channels and using it as a world-space material normal removed them (measured by captures).** That the stored format is 8-bit is inferred from the default tangent type, not read directly.","source":"Plugins/Runtime/ProceduralMeshComponent/.../ProceduralMeshComponent.cpp:134; Runtime/Engine/Private/StaticMesh.cpp:1377-1398","engine":"5.8.3","ue":["5.8"],"bl":[],"section":"UE animation export, constraint keys, reimport, cloners and saved scalability"},{"id":"U707","fact":"**In UE 5.8 the actor-space bounds function is `AActor::CalculateComponentsBoundingBoxInLocalSpace`; `CalculateComponentsBoundingBoxInActorSpace` doesn't exist (error C2039). `FSlateVectorImageBrush` is declared in `Brushes/SlateImageBrush.h`; there is no `Brushes/SlateVectorImageBrush.h`.**","source":"`Engine/Source/Runtime/Engine/Classes/GameFramework/Actor.h:3592`, `Engine/Source/Runtime/SlateCore/Public/Brushes/SlateImageBrush.h`","engine":"5.8.3","ue":["5.8"],"bl":[],"section":"UE animation export, constraint keys, reimport, cloners and saved scalability"},{"id":"U707","fact":"**Two UnrealBuildTool runs on one machine can fail at once with exit 6 and an IOException on `%LOCALAPPDATA%/UnrealEngine/Intermediate/Build/BuildRules/MarketplaceRules.dll`, even for different projects; `-WaitMutex` doesn't prevent it.** Waiting for the other run to exit is the fix.","source":"UBT output captured 2026-11-05 while another session's UBT process was running","engine":"5.8.3","ue":["5.8"],"bl":[],"section":"UE animation export, constraint keys, reimport, cloners and saved scalability"},{"id":"U708","fact":"**`FVector::GetSafeNormal()` returns zero when the squared length is under its default tolerance of 1e-8, which is a length of 1e-4. Differences of unit direction vectors on a 77 km sphere fall under that below about 7.7 m, so scale to metres before normalising.** Measured: a 7 m road stub drew nothing, and scaling first made it draw.","source":"Engine default tolerance `UE_SMALL_NUMBER` in `Engine/Source/Runtime/Core/Public/Math/UnrealMathUtility.h`, tolerance not reread this session; symptom and fix by PIE captures, 2026-11-05","engine":"5.8.3","ue":["5.8"],"bl":[],"section":"UE animation export, constraint keys, reimport, cloners and saved scalability"},{"id":"U708","fact":"**Confirms the earlier GetSafeNormal row's tolerance, which that row said was not reread: `TVector<T>::GetSafeNormal(T Tolerance=UE_SMALL_NUMBER, ...)`, with `UE_SMALL_NUMBER` defined as 1.e-8f.**","source":"`Engine/Source/Runtime/Core/Public/Math/Vector.h:647`, `Engine/Source/Runtime/Core/Public/Math/UnrealMathUtility.h:130`","engine":"5.8.3","ue":["5.8"],"bl":[],"section":"UE animation export, constraint keys, reimport, cloners and saved scalability"},{"id":"U710","fact":"**`assign_materials_by_slot_prefix` issues no `previewId` when a dry run finds nothing to change, so a following `preview_id` call returns `ok: false` with no error. Read `slotsToChange` first: 0 means they already hold their values.**","source":"Observed through the tool's own note on the dry run, 133 meshes and 187 slots","engine":"5.8.3, MifBridge 1.3.0","ue":["5.8"],"bl":[],"section":"UE animation export, constraint keys, reimport, cloners and saved scalability","newer":true},{"id":"U711","fact":"**Options on a packaged game's command-line map (`/Game/.../L_World?Families=8?Supplies=2?StartMonths=3`) arrive in `GetWorld()->URL`, readable with `URL.GetOption(TEXT(\"Families=\"), nullptr)`.**","source":"Live run of a packaged Win64 Development build: the on-screen sim showed 8 families, doubled stock and June","engine":"5.8.3","ue":["5.8"],"bl":[],"section":"UE animation export, constraint keys, reimport, cloners and saved scalability"},{"id":"U712","fact":"**`split_static_mesh` takes `by` as `parts` or `materials` only; anything else is refused with nothing written. A dry run refuses a split of more than 256 pieces and names `minTriangles` as the way out.**","source":"`describe_endpoint` plus live dry runs: market stall 19 parts, tree 1,425 refused","engine":"5.8.3, MifBridge 1.3.0","ue":["5.8"],"bl":[],"section":"UE animation export, constraint keys, reimport, cloners and saved scalability","newer":true},{"id":"U713","fact":"**A material's TextureSample coordinate input is named `UVs`; `Coordinates` is refused by `connect_material_expressions`, whose error lists `[UVs, Tex, Apply View MipBias]`. A new Custom node takes its code on a second call with `existing`.**","source":"Live error from `connect_material_expressions` and `add_material_expression` on `M_RoadDecalAsphalt`","engine":"5.8.3, MifBridge 1.3.0","ue":["5.8"],"bl":[],"section":"UE animation export, constraint keys, reimport, cloners and saved scalability","newer":true},{"id":"U715","fact":"**The localisation gather finds a String Table read from a CSV file only when the code registers it with the `LOCTABLE_FROMFILE_GAME(Id, Namespace, FilePath)` (or `_ENGINE`) macro written with literal arguments: GatherTextFromSource parses that macro's text in source files. A table registered through `FStringTableRegistry::Internal_LocTableFromFile` with variables (say, a loop over a folder's files) is invisible to the gather. The macro itself expands to that same `Internal_LocTableFromFile` call against `FPaths::ProjectContentDir()`.**","source":"`Engine/Source/Editor/UnrealEd/Private/Commandlets/GatherTextFromSourceCommandlet.cpp`: `FStringTableFromFileMacroDescriptor` registered for `LOCTABLE_FROMFILE_GAME` (line 513), `TryParse` (~line 3227) reads three literal arguments into `Context.AddStringTableFromFile`; `Engine/Source/Runtime/Core/Public/Internationalization/StringTableRegistry.h:117` for the expansion","engine":"5.8","ue":["5.8"],"bl":[],"section":"UE animation export, constraint keys, reimport, cloners and saved scalability"}]