Description

When an Actor's CustomTimeDilation is 0.0, functions FActorTickFunction::ExecuteTick() and FActorComponentTickFunction::ExecuteTickHelper() will call AActor::TickActor() and UActorComponent::TickComponent() with a DeltaTime of 0.0. This can be dangerous for calculations that rely on a division by DeltaTime. In particular, it currently causes problems when an Animation Blueprint tries to evaluate a "Stride Warping" node from the "Animation Warping" plugin.

When a "Stride Warping" node is active on an actor with a CustomTimeDilation of 0.0, the tick function for the actor's SkeletalMeshComponent ends up calling FAnimNode_StrideWarping::UpdateInternal(), which then sets CachedDeltaTime to 0.0 (see file [Engine\Plugins\Animation\AnimationWarping\Source\Runtime\Private\BoneControllers\AnimNode_StrideWarping.cpp]). Later, when function FAnimNode_StrideWarping::EvaluateSkeletalControl_AnyThread() is called, both CachedRootMotionDeltaTranslation and CachedDeltaTime will be exactly zero. As a result, the line "CachedRootMotionDeltaSpeed = CachedRootMotionDeltaTranslation.Size() / CachedDeltaTime" tries to do 0/0, which yields NaN. If the compiler is using the /fp:precise floating-point environment option, the test (CachedRootMotionDeltaSpeed <= MinRootMotionSpeedThreshold) fails, making ActualStrideScale also become NaN. This ultimately causes the failure of check(!Foot.IKFootBoneTransform.ContainsNaN()) further below on the same function, as well as triggering an ensure(!Bone.ContainsNaN()) in function ValidatePose() from namespace UE::Anim::Private.

Note: The NaN's are not generated when the engine is compiled using /fp:fast (the default), because the test (CachedRootMotionDeltaSpeed <= MinRootMotionSpeedThreshold) succeeds even though CachedRootMotionDeltaSpeed is NaN. In this scenario, we get ActualStrideScale = 1.0f, which does not cause checks and ensures, but is not the expected behavior.

Note: No such issue arises when using a Global Time Dilation of zero. In that situation, the values of CachedRootMotionDeltaTranslation and CachedDeltaTime both become tiny but not zero. As a result, the calculated CachedRootMotionDeltaSpeed has the usual value. The values of LocomotionSpeed, CachedRootMotionDeltaSpeed, and ActualStrideScale are not influenced by Global Time Dilation. This seems like a safer behavior.

 

Steps to Reproduce

1. Open the provided repro project from the Visual Studio debugger.
2. PIE. A character will walk left-to-right with an expected short stride, modified by the "Stride Warping" node on its Animation Blueprint.
3. Press the "P" key on the keyboard. This is handled by the Level Blueprint and toggles the character's CustomTimeDilation between 1 and 0.
4. If the engine was compiled with /fp:precise, a check() should be triggered inside AnimNode_StrideWarping.cpp
5. If the engine was compiled with /fp:fast (the default for released versions from the launcher):
5.1. While the character is frozen because of its CustomTimeDilation, place a breakpoint in AnimNode_StrideWarping.cpp, on line "if (CachedRootMotionDeltaSpeed <= MinRootMotionSpeedThreshold)"
5.2. Note that CachedRootMotionDeltaTranslation and CachedDeltaTime are both zero (dangerous!)
5.3. Step in and note that the test succeeds, making execution flow to line "ActualStrideScale = 1.0f;" (not the expected value)
5.4. Use the debugger to manually set the next execution statement to line "ActualStrideScale = LocomotionSpeed / CachedRootMotionDeltaSpeed;" as if the test had failed
5.5. Continue execution. This should trigger "check(!Foot.IKFootBoneTransform.ContainsNaN());" further below.

Have Comments or More Details?

There's no existing public thread on this issue, so head over to Questions & Answers just mention UE-338254 in the post.

0
Login to Vote

Unresolved
ComponentUE - Anim
CreatedSep 29, 2025
UpdatedOct 14, 2025
View Jira Issue