< Summary

Line coverage
100%
Covered lines: 781
Uncovered lines: 0
Coverable lines: 781
Total lines: 1741
Line coverage: 100%
Branch coverage
100%
Covered branches: 628
Total branches: 628
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
File 1: .ctor()100%11100%
File 1: get_IsInitialized()100%11100%
File 1: get_PlatformModule()100%11100%
File 1: get_JumpModule()100%11100%
File 1: get_SlideModule()100%11100%
File 1: get_WaterModule()100%11100%
File 1: get_FlyModule()100%11100%
File 1: get_ClimbModule()100%11100%
File 1: get_StateChanged()100%44100%
File 1: get_IsOnSolid()100%11100%
File 1: get_WasOnSolid()100%11100%
File 1: get_IsInGas()100%11100%
File 1: get_WasInGas()100%11100%
File 1: get_IsInLiquid()100%11100%
File 1: get_WasInLiquid()100%11100%
File 1: get_IsFlying()100%22100%
File 1: get_IsJumping()100%22100%
File 1: get_IsFalling()100%11100%
File 1: get_IsClimbing()100%22100%
File 1: get_InLimbo()100%1414100%
File 1: CreateUninitialized(...)100%22100%
File 1: .ctor(...)100%11100%
File 1: BindContext(...)100%11100%
File 1: RequireContext()100%11100%
File 1: get_FrameCount()100%11100%
File 1: get_DeltaTime()100%11100%
File 1: get_InvDeltaTime()100%11100%
File 1: get_TotalTime()100%11100%
File 1: OnInitialize(...)100%44100%
File 1: SetLocomotionProfile(...)100%44100%
File 1: ConfigureLocomotions(...)100%11100%
File 1: get_Handler()100%11100%
File 2: FinalizeTraversal(...)100%22100%
File 2: ShouldFinalizeTraversal()100%22100%
File 2: ValidatePendingTraversalFrame()100%22100%
File 2: RefreshTraversalState(...)100%11100%
File 2: HandleTraversalTransitions()100%1212100%
File 2: HandleClimbState(...)100%2222100%
File 2: HandleGasExitTransition()100%1212100%
File 2: FinalizePlatformMovement(...)100%66100%
File 2: CheckJumpStatus(...)100%88100%
File 2: HandlePlatformTransitions()100%2020100%
File 2: HandleSwimState(...)100%2020100%
File 2: HandleFlightState()100%1010100%
File 2: HandleFallState(...)100%66100%
File 2: ShouldClearActiveFallState()100%44100%
File 2: UpdateActiveFallState(...)100%1414100%
File 2: TryStartFall(...)100%1212100%
File 2: GetScaledFlightSpeedMultiplier(...)100%44100%
File 2: StopClimb(...)100%66100%
File 2: CanContinueActiveMantle()100%66100%
File 2: HasReachedMantleTarget(...)100%22100%
File 2: CompleteMantle()100%11100%
File 3: RecordData(...)100%88100%
File 4: TryTraversal(...)100%22100%
File 4: ResetTraversalOutputs(...)100%11100%
File 4: TryBeginTraversalFrame()100%66100%
File 4: PrepareTraversalState(...)100%88100%
File 4: ResolveTraversalForces(...)100%66100%
File 4: ComputeMovementForces(...)100%44100%
File 4: UpdateControlState()100%1010100%
File 4: SetSlidingState(...)100%22100%
File 4: ResetDownwardMomentumIfNeeded()100%88100%
File 4: GetDesiredVelocity(...)100%1010100%
File 4: GetFlightVelocity(...)100%1010100%
File 4: GetClimbVelocity(...)100%1818100%
File 4: GetMantleVelocity(...)100%66100%
File 4: GetControlledSurfaceVelocity(...)100%88100%
File 4: GetSlidingVelocity(...)100%11100%
File 4: GetHorizontalVelocity(...)100%44100%
File 4: MaxHoritzontalSpeedInDirection(...)100%66100%
File 4: GetFlightHorizontalSpeed(...)100%44100%
File 4: GetFlightSpeedMultiplier(...)100%66100%
File 4: GetLiquidHorizontalSpeed(...)100%1010100%
File 4: GetGroundHorizontalSpeed(...)100%22100%
File 4: GetAirborneControlMultiplier()100%66100%
File 4: GetEllipticalHorizontalSpeed(...)100%11100%
File 4: GetGroundDirectionalMaxSpeed(...)100%66100%
File 4: ResolveLiquidVelocity(...)100%1616100%
File 4: ApplyPlatformTransferVelocity(...)100%22100%
File 4: ApplyGroundVelocityConstraints(...)100%1616100%
File 4: TryApplyStationaryGroundFriction(...)100%1212100%
File 4: ApplyDesiredVelocity(...)100%66100%
File 4: GetMaxAcceleration()100%3030100%
File 4: ApplyEnvironmentalForces()100%2222100%
File 4: ApplyJumpForce(...)100%66100%
File 4: CanApplyJumpForce(...)100%2424100%
File 4: GetWaterBreachJumpForce()100%22100%
File 4: GetGroundJumpForce()100%22100%
File 4: GetClimbDetachJumpForce()100%66100%
File 4: EnsureJumpDirectionInitialized()100%22100%
File 4: CommitJumpForce(...)100%22100%
File 4: UpdateClimbState(...)100%3232100%
File 4: IsCompatibleClimbAffordance(...)100%88100%
File 4: HasCompatibleClimbAxes(...)100%1212100%
File 4: GetClimbContinuityTolerance()100%11100%
File 4: StartClimb(...)100%1010100%
File 4: ShouldStartMantle(...)100%88100%
File 4: StartMantle(...)100%22100%
File 4: UpdateFlightState(...)100%2020100%
File 4: UpdateSwimState(...)100%88100%
File 4: ResolveTraversalVelocityDelta()100%22100%
File 4: ResolvePlatformTraversal(...)100%1414100%
File 5: GetVerticalJumpSpeed()100%22100%
File 5: IsTooSteep(...)100%22100%
File 5: IsOnSlope(...)100%44100%
File 5: SetVelocity(...)100%11100%
File 5: SyncTraversalState(...)100%22100%
File 5: AbortTraversalFrame()100%11100%

File(s)

/home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Navigation/Motor/NavMotor.cs

#LineLine coverage
 1//=======================================================================
 2// NavMotor.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2024-present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using System;
 9using Chronicler;
 10using FixedMathSharp;
 11
 12namespace Trailblazer.Navigation.Motor;
 13
 14/// <summary>
 15/// Controls character movement using an acceleration-based approach in a deterministic, lockstep simulation.
 16/// </summary>
 17/// <remarks>
 18/// This controller processes movement requests, applies forces such as gravity and platform adjustments,
 19/// and finalizes traversal states for consistent movement across frames.
 20/// </remarks>
 21[Serializable]
 22public partial class NavMotor : IRecordable
 23{
 24    #region Fields
 25
 26    /// <summary>
 27    /// Manages locomotion states and behaviors.
 28    /// </summary>
 54529    private LocomotionHandler _handler = new();
 30
 31    private bool _isInitialized;
 32
 33    private TrailblazerWorldContext? _context;
 34
 35    /// <inheritdoc cref="NavMotorEvents"/>
 36    [NonSerialized]
 54537    public NavMotorEvents Events = new();
 38
 39    /// <summary>
 40    /// This stores the current <see cref="Navigator._frameCondition"/> for the frame.
 41    /// </summary>
 42    /// <remarks>
 43    /// This is set on <see cref="OnInitialize"/>, can be explicitly synchronized before traversal
 44    /// through <see cref="SyncTraversalState(TrekCondition, bool)"/>, and is refreshed at the end of
 45    /// the frame in <see cref="FinalizeTraversal"/>.
 46    /// </remarks>
 47    public TransitState CurrentState { get; private set; } = null!;
 48
 49    /// <summary>
 50    /// Indicates whether a traversal has started for the current frame and still requires finalization or abort.
 51    /// </summary>
 52    public bool TraversalInProgress { get; private set; }
 53
 54    /// <summary>
 55    /// Gets a value indicating whether the object has been initialized.
 56    /// </summary>
 438057    public bool IsInitialized => _isInitialized;
 58
 59    /// <inheritdoc cref="PlatformLocomotion"/>
 1313260    internal PlatformLocomotion PlatformModule => Handler.Platform;
 61
 62    /// <inheritdoc cref="JumpLocomotion"/>
 1814763    internal JumpLocomotion? JumpModule => Handler.Jump;
 64
 65    /// <inheritdoc cref="SlideLocomotion"/>
 320166    internal SlideLocomotion? SlideModule => Handler.Slide;
 67
 68    /// <inheritdoc cref="WaterLocomotion"/>
 883169    internal WaterLocomotion? WaterModule => Handler.Water;
 70
 71    /// <inheritdoc cref="FlyLocomotion"/>
 2379472    internal FlyLocomotion? FlyModule => Handler.Fly;
 73
 74    /// <inheritdoc cref="ClimbLocomotion"/>
 2489375    internal ClimbLocomotion? ClimbModule => Handler.Climb;
 76
 77    #region Cache
 78
 79    /// <summary>
 80    /// Stores the slope angle for the current frame based on <see cref="TrekRequest.Direction"/>.
 81    /// </summary>
 82    public Fixed64 FrameSlopeAngle { get; private set; }
 83
 84    /// <summary>
 85    /// Accumulates forces applied during the traversal phase before they are committed.
 86    /// </summary>
 87    private Vector3d _forceOutput;
 88
 89    /// <summary>
 90    /// Records the simulation frame that opened the current traversal so stale traversal usage can be detected.
 91    /// </summary>
 54592    private int _pendingTraversalFrame = -1;
 93
 94    #endregion
 95
 96    #region State Status
 97
 98    /// <summary>
 99    /// Indicates if the traversal medium has changed since the last frame.
 100    /// </summary>
 8101    public bool StateChanged => CurrentState.Medium != CurrentState.PreviousMedium
 8102        && CurrentState.Medium != TraversalMedium.Unknown
 8103        && CurrentState.PreviousMedium != TraversalMedium.Unknown;
 104
 105    /// <summary>
 106    /// Determines if the object is currently on the ground.
 107    /// </summary>
 20294108    public bool IsOnSolid => CurrentState.Medium == TraversalMedium.Solid;
 109
 110    /// <summary>
 111    /// Determines if the object was on the ground in the previous frame.
 112    /// </summary>
 31113    public bool WasOnSolid => CurrentState.PreviousMedium == TraversalMedium.Solid;
 114
 115    /// <summary>
 116    /// Determines if the object is currently in the air.
 117    /// </summary>
 19341118    public bool IsInGas => CurrentState.Medium == TraversalMedium.Gas;
 119
 120    /// <summary>
 121    /// Determines if the object was in the air in the previous frame.
 122    /// </summary>
 2441123    public bool WasInGas => CurrentState.PreviousMedium == TraversalMedium.Gas;
 124
 125    /// <summary>
 126    /// Determines if the object is currently in water.
 127    /// </summary>
 18025128    public bool IsInLiquid => CurrentState.Medium == TraversalMedium.Liquid;
 129
 130    /// <summary>
 131    /// Determines if the object was in water in the previous frame.
 132    /// </summary>
 4105133    public bool WasInLiquid => CurrentState.PreviousMedium == TraversalMedium.Liquid;
 134
 135    /// <summary>
 136    /// Determines if the object is currently under active flight control.
 137    /// </summary>
 16704138    public bool IsFlying => FlyModule?.IsFlying == true;
 139
 140    /// <summary>
 141    /// Determines if the object is currently in a jump state.
 142    /// </summary>
 7087143    public bool IsJumping => JumpModule?.IsJumping == true;
 144
 145    /// <summary>
 146    /// Gets a value indicating whether the object is currently falling.
 147    /// </summary>
 6577148    public bool IsFalling => Handler.Fall.IsFalling;
 149
 150    /// <summary>
 151    /// Determines if the object is currently attached to a climb affordance.
 152    /// </summary>
 13407153    public bool IsClimbing => ClimbModule?.IsClimbing == true;
 154
 155    /// <summary>
 156    /// Checks if the object is in a state where it is airborne but not actively jumping, flying, or falling.
 157    /// </summary>
 6213158    public bool InLimbo => !IsOnSolid && !IsInGas && !IsInLiquid
 6213159        || IsInGas && !IsFlying && !IsJumping && !IsFalling && !IsClimbing;
 160
 161    #endregion
 162
 163    #endregion
 164
 165    #region Construct
 166
 167    /// <summary>
 168    /// Creates a new context-bound <see cref="NavMotor"/> instance without initializing it.
 169    /// </summary>
 170    internal static NavMotor CreateUninitialized(TrailblazerWorldContext context, LocomotionHandler? handler = null)
 171    {
 6172        var motor = new NavMotor();
 6173        if (handler != null)
 1174            motor._handler = handler;
 6175        motor.BindContext(context);
 6176        return motor;
 177    }
 178
 179    // Parameterless constructor for serialization purposes. Not intended for direct use.
 12180    private NavMotor() { }
 181
 182    /// <summary>
 183    /// Initializes a new context-bound <see cref="NavMotor"/> instance.
 184    /// </summary>
 539185    public NavMotor(TrailblazerWorldContext context, TrekCondition condition, LocomotionProfile? profile = null)
 186    {
 539187        BindContext(context);
 539188        OnInitialize(condition, profile);
 539189    }
 190
 191    /// <summary>
 192    /// Binds this motor to a world context.
 193    /// </summary>
 194    public void BindContext(TrailblazerWorldContext context)
 195    {
 576196        TrailblazerWorldContext.ThrowIfUnusable(context);
 576197        _context = context;
 576198        _handler.BindContext(context);
 576199    }
 200
 13139201    private TrailblazerWorldContext RequireContext() => _context!;
 202
 4346203    private int FrameCount => RequireContext().FrameCount;
 204
 6100205    private Fixed64 DeltaTime => RequireContext().DeltaTime;
 206
 2151207    private Fixed64 InvDeltaTime => RequireContext().InvDeltaTime;
 208
 3209    private Fixed64 TotalTime => RequireContext().TotalTime;
 210
 211    /// <summary>
 212    /// Prepares the controller by linking it to the given object and setting initial state values.
 213    /// </summary>
 214    /// <param name="condition">The initial traversal condition of the object</param>
 215    /// <param name="profile">Optional locomotion composition profile. When omitted, the default profile is used.</param
 216    public void OnInitialize(TrekCondition condition, LocomotionProfile? profile = null)
 217    {
 539218        _handler = profile != null ? new LocomotionHandler(profile) : new LocomotionHandler();
 539219        _handler.BindContext(RequireContext());
 539220        CurrentState = new TransitState(condition);
 539221        if (CurrentState.GroundState.HasValue)
 401222            PlatformModule.HandlePlatformChange(CurrentState.GroundState); // set the initial platform
 223
 539224        _isInitialized = true;
 539225    }
 226
 227    /// <summary>
 228    /// Replaces the currently installed locomotion profile.
 229    /// </summary>
 230    public void SetLocomotionProfile(LocomotionProfile profile)
 231    {
 9232        SwiftThrowHelper.ThrowIfNull(profile, nameof(profile));
 233
 8234        SwiftThrowHelper.ThrowIfTrue(
 8235            TraversalInProgress,
 8236            message: "Cannot change locomotion composition while a traversal frame is in progress.");
 237
 7238        Handler.ApplyProfile(profile);
 239
 7240        if (CurrentState?.GroundState.HasValue == true)
 4241            PlatformModule.HandlePlatformChange(CurrentState.GroundState);
 7242    }
 243
 244    /// <summary>
 245    /// Reconfigures the currently installed locomotion profile from the active handler state.
 246    /// </summary>
 247    public void ConfigureLocomotions(Action<LocomotionProfileBuilder> configure)
 248    {
 1249        SwiftThrowHelper.ThrowIfNull(configure, nameof(configure));
 250
 1251        var builder = LocomotionProfileBuilder.FromHandler(Handler);
 1252        configure(builder);
 1253        SetLocomotionProfile(builder.Build());
 1254    }
 255
 256    #endregion
 257
 258    #region Properties
 259
 260    /// <inheritdoc cref="_handler"/>
 128476261    public LocomotionHandler Handler => _handler;
 262
 263    #endregion
 264
 265}

/home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Navigation/Motor/NavMotor.Finalization.cs

#LineLine coverage
 1//=======================================================================
 2// NavMotor.Finalization.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2024-present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using System;
 9using System.Runtime.CompilerServices;
 10using FixedMathSharp;
 11
 12namespace Trailblazer.Navigation.Motor;
 13
 14public partial class NavMotor
 15{
 16    #region Finalize
 17
 18    /// <summary>
 19    /// Finalizes traversal state updates and prepares the object for the next simulation frame.
 20    /// </summary>
 21    /// <remarks>
 22    /// This method updates the frame velocity, applies necessary adjustments based on traversal state changes,
 23    /// and processes platform movement or environmental effects as needed.
 24    /// </remarks>
 25    public void FinalizeTraversal(
 26        Vector3d newPosition,
 27        Vector3d lastPosition,
 28        FixedQuaternion newRotation,
 29        TrekCondition conditonRefresh,
 30        Vector3d? newFootPosition = null)
 31    {
 218232        if (!ShouldFinalizeTraversal())
 3033            return;
 34
 215235        ValidatePendingTraversalFrame();
 215136        RefreshTraversalState(newPosition, lastPosition, conditonRefresh);
 215137        HandleTraversalTransitions();
 215138        HandleClimbState(newPosition);
 215139        HandleSwimState(newPosition);
 215140        HandleFlightState();
 215141        HandleFallState(newPosition);
 215142        FinalizePlatformMovement(newFootPosition ?? newPosition, newRotation);
 215143        AbortTraversalFrame();
 215144    }
 45
 46    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 47    private bool ShouldFinalizeTraversal()
 48    {
 218249        return IsInitialized && TraversalInProgress;
 50    }
 51
 52    private void ValidatePendingTraversalFrame()
 53    {
 215254        if (FrameCount == _pendingTraversalFrame)
 215155            return;
 56
 157        throw new InvalidOperationException(
 158            $"NavMotor traversal opened on frame {_pendingTraversalFrame} cannot be finalized on frame {FrameCount}. Cal
 59    }
 60
 61    private void RefreshTraversalState(
 62        Vector3d newPosition,
 63        Vector3d lastPosition,
 64        TrekCondition conditonRefresh)
 65    {
 215166        Handler.Move.FrameVelocity = (newPosition - lastPosition) * InvDeltaTime;
 67
 215168        CurrentState.Update(conditonRefresh, CurrentState.ToTrekCondition());
 69
 215170        PlatformModule.HandlePlatformChange(CurrentState.GroundState);
 215171        HandlePlatformTransitions();
 72
 73        // Ceiling check runs last so platform inertia inherited this frame cannot bypass the clamp.
 215174        CheckJumpStatus(newPosition);
 215175    }
 76
 77    private void HandleTraversalTransitions()
 78    {
 215179        if (WasInGas && !IsInGas)
 2380            HandleGasExitTransition();
 81
 215182        if (WaterModule?.IsEnabled == true && !IsInLiquid && WasInLiquid)
 383            Handler.ClearTransientState<WaterLocomotion>();
 215184    }
 85
 86    private void HandleClimbState(Vector3d position)
 87    {
 215188        ClimbLocomotion? climbModule = ClimbModule;
 215189        if (climbModule?.IsEnabled != true)
 1090            return;
 91
 214192        if (climbModule.IsMantling)
 93        {
 2094            if (IsInLiquid || CurrentState.Medium == TraversalMedium.Unknown)
 95            {
 296                StopClimb(wasForced: true);
 297                return;
 98            }
 99
 18100            if (IsOnSolid)
 101            {
 2102                CompleteMantle();
 2103                return;
 104            }
 105
 16106            if (!CanContinueActiveMantle())
 107            {
 2108                StopClimb(wasForced: true);
 2109                return;
 110            }
 111
 14112            if (HasReachedMantleTarget(position))
 2113                CompleteMantle();
 114
 14115            return;
 116        }
 117
 2121118        if ((IsInLiquid || CurrentState.Medium == TraversalMedium.Unknown) && climbModule.IsClimbing)
 2119            StopClimb(wasForced: false);
 2121120    }
 121
 122    private void HandleGasExitTransition()
 123    {
 23124        if (IsJumping)
 125        {
 8126            JumpLocomotion jumpModule = JumpModule!;
 127
 128            // Reset cooldown on landing.
 8129            jumpModule.ResetJumpCounter();
 130
 8131            if (IsInLiquid)
 3132                Events.OnStopWaterBreach?.Invoke();
 133            else
 5134                Events.OnStopJump?.Invoke();
 135
 2136            return;
 137        }
 138
 15139        if (!IsInLiquid)
 12140            Events.OnLandedFall?.Invoke();
 4141    }
 142
 143    private void FinalizePlatformMovement(Vector3d position, FixedQuaternion rotation)
 144    {
 2151145        PlatformLocomotion platformModule = PlatformModule;
 2151146        if (!platformModule.IsActive || (!IsOnSolid && !platformModule.IsLockedToPlatform))
 1959147            return;
 148
 192149        platformModule.HandlePlatformMovement(position, rotation);
 192150    }
 151
 152    private void CheckJumpStatus(Vector3d position)
 153    {
 154        // Make sure we aren't hitting the ceiling
 2151155        if (Handler.Move.FrameVelocity.Y <= Fixed64.Zero || CurrentState.CeilingLevel == Fixed64.MaxValue)
 2147156            return;
 157
 5158        if (position.Y <= CurrentState.CeilingLevel) return;
 159
 3160        Handler.Move.FrameVelocity = new(
 3161            Handler.Move.FrameVelocity.X,
 3162            Fixed64.Zero,
 3163            Handler.Move.FrameVelocity.Z);
 164
 3165        if (JumpModule != null)
 166        {
 2167            JumpModule.IsJumping = false;
 2168            JumpModule.IsHoldingJump = false;
 169        }
 3170    }
 171
 172    private void HandlePlatformTransitions()
 173    {
 174        // Don't process platform state when in water
 2151175        PlatformLocomotion platformModule = PlatformModule;
 2151176        if (!platformModule.IsEnabled || IsInLiquid)
 185177            return;
 178
 1966179        bool isReleasing = false;
 1966180        if (platformModule.IsHoldingPlatform)
 2181            isReleasing = platformModule.TickHoldOnPlatform();
 182
 1966183        if (isReleasing)
 184        {
 1185            Handler.Move.FrameVelocity -= platformModule.PlatformVelocity;
 1186            return;
 187        }
 188
 3902189        if (!platformModule.InertiaApplied) return;
 190
 28191        if (WasOnSolid && IsInGas)
 192        {
 193            // Scout just left the ground, so it inherits platform inertia into its new velocity.
 4194            platformModule.FramePlatformVelocity = platformModule.PlatformVelocity;
 4195            Handler.Move.FrameVelocity += platformModule.PlatformVelocity;
 4196            return;
 197        }
 198
 24199        if (WasInGas && IsOnSolid)
 200        {
 3201            if (platformModule.IsNewPlatform)
 202                // If object landed on a new platform, we have to wait for two frames
 203                // before we know the new velocity of the platform under the object
 1204                platformModule.SetHoldPlatform(platformModule.ActivePlatform);
 205            else
 206                // If the platform isn’t new, we assume the object landed back on the same platform
 207                // and subtract platform velocity to prevent doubling the effect.
 2208                Handler.Move.FrameVelocity -= platformModule.PlatformVelocity;
 209        }
 23210    }
 211
 212    private void HandleSwimState(Vector3d position)
 213    {
 2151214        if (!IsInLiquid)
 215        {
 1967216            if (WaterModule?.IsEnabled == true && WasInLiquid)
 3217                Handler.ClearTransientState<WaterLocomotion>();
 218
 1967219            return;
 220        }
 221
 184222        bool hadSwimIntent = WaterModule?.RequestedSwimThisTraversal == true;
 223        // Clear the transient state when entering water for the first time
 184224        if (!WasInLiquid)
 9225            Handler.ClearAllTransientState();
 226
 184227        if (WaterModule?.IsEnabled == true)
 228        {
 180229            WaterModule.IsSwimming = WaterModule.CanSwim && hadSwimIntent;
 180230            WaterModule.IsDiving = position.Y < CurrentState.SurfaceLevel;
 231
 180232            WaterModule.UpdateDiveTime();
 233
 180234            if (WaterModule.IsDrowning)
 6235                Events.OnDrowning?.Invoke(WaterModule.UnderwaterTimer);
 236        }
 179237    }
 238
 239    private void HandleFlightState()
 240    {
 2151241        if (FlyModule?.IsEnabled != true)
 10242            return;
 243
 2141244        if (!IsInGas || IsClimbing)
 245        {
 454246            if (FlyModule.IsFlying)
 1247                Handler.ClearTransientState<FlyLocomotion>();
 248
 454249            return;
 250        }
 251
 252        System.Diagnostics.Debug.Assert(
 253            !(FlyModule.IsFlying && IsFalling),
 254            "UpdateFlightState owns fall-state cleanup before traversal finalization.");
 1687255    }
 256
 257    private void HandleFallState(Vector3d position)
 258    {
 2152259        if (!Handler.Fall.IsEnabled) return;
 260
 2150261        if (ShouldClearActiveFallState())
 262        {
 263            System.Diagnostics.Debug.Assert(
 264                !IsFalling,
 265                "Swim, flight, and climb transitions own fall-state cleanup before fall finalization.");
 248266            return;
 267        }
 268
 1902269        if (IsFalling)
 270        {
 1498271            UpdateActiveFallState(position);
 1498272            return;
 273        }
 274
 404275        TryStartFall(position);
 404276    }
 277
 278    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 2150279    private bool ShouldClearActiveFallState() => IsInLiquid || IsFlying || IsClimbing;
 280
 281    private void UpdateActiveFallState(Vector3d position)
 282    {
 1498283        if (position.Y > Handler.Fall.FallStart)
 4284            Handler.Fall.FallStart = position.Y;
 285
 1498286        if (!IsInGas && !IsTooSteep(FrameSlopeAngle))
 287        {
 13288            Handler.Fall.IsFalling = false;
 13289            Handler.Fall.FallEnd = position.Y;
 290
 13291            if (Handler.Fall.FallHeight > Fixed64.Zero)
 9292                Events.OnStopFall?.Invoke(Handler.Fall.FallHeight);
 293
 13294            Handler.ClearTransientState<FallLocomotion>();
 13295            return;
 296        }
 297
 1485298        Fixed64 currentFallHeight = (Handler.Fall.FallStart - position.Y).Abs();
 1485299        if (currentFallHeight > Handler.Fall.MaxFallHeight)
 1219300            Events.OnMaxFallHeightReached?.Invoke();
 297301    }
 302
 303    private void TryStartFall(Vector3d position)
 304    {
 404305        bool isSlidingTooSteep = IsTooSteep(FrameSlopeAngle);
 404306        if (!(IsInGas || isSlidingTooSteep) || _forceOutput.Y >= Fixed64.Zero)
 366307            return;
 308
 38309        Handler.Fall.IsFalling = true;
 38310        Handler.Fall.FallStart = position.Y;
 311
 38312        if (JumpModule != null && JumpModule.JumpCount > 0 && !JumpModule.IsCoolingDown)
 7313            JumpModule.StartCooldown();
 314
 38315        Events.OnStartFall?.Invoke();
 3316    }
 317
 4318    private Fixed64 GetScaledFlightSpeedMultiplier(TrekRate rate, Fixed64 maxFastSpeed) => rate switch
 4319    {
 2320        TrekRate.Slow => Handler.Move.MaxSlowSpeed / maxFastSpeed,
 1321        TrekRate.Moderate => Handler.Move.MaxModerateSpeed / maxFastSpeed,
 4322        // Fast never reaches this helper because GetFlightSpeedMultiplier(...) short-circuits it to one.
 1323        _ => Fixed64.Zero
 4324    };
 325
 326    private void StopClimb(bool wasForced)
 327    {
 19328        Handler.ClearTransientState<ClimbLocomotion>();
 329
 19330        if (wasForced)
 9331            Events.OnClimbSlip?.Invoke();
 332
 19333        Events.OnStopClimb?.Invoke();
 4334    }
 335
 336    private bool CanContinueActiveMantle()
 337    {
 16338        ClimbLocomotion climbModule = ClimbModule!;
 16339        if (!climbModule.ValidateActiveMantleWithHost)
 10340            return true;
 341
 6342        if (climbModule.ClimbResolver is not IActiveMantleValidator validator)
 1343            return true;
 344
 5345        return validator.TryValidateActiveMantle(
 5346                CurrentState,
 5347                climbModule.CreateActiveMantleState(),
 5348                out MantleValidationSnapshot snapshot)
 5349            && snapshot.CanContinueMantle;
 350    }
 351
 352    private bool HasReachedMantleTarget(Vector3d position)
 353    {
 14354        ClimbLocomotion climbModule = ClimbModule!;
 14355        if (!climbModule.MantleTargetPosition.HasValue)
 1356            return false;
 357
 13358        Fixed64 tolerance = climbModule.ClimbStartTolerance;
 13359        Vector3d mantleTargetPosition = climbModule.MantleTargetPosition.GetValueOrDefault();
 13360        return (mantleTargetPosition - position).MagnitudeSquared <= tolerance * tolerance;
 361    }
 362
 363    private void CompleteMantle()
 364    {
 4365        StopClimb(wasForced: false);
 4366    }
 367
 368    #endregion
 369}

/home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Navigation/Motor/NavMotor.Serialization.cs

#LineLine coverage
 1//=======================================================================
 2// NavMotor.Serialization.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2024-present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using Chronicler;
 9
 10namespace Trailblazer.Navigation.Motor;
 11
 12public partial class NavMotor
 13{
 14    #region Serialization
 15
 16    /// <inheritdoc />
 17    public void RecordData(IChronicler chronicler)
 18    {
 16419        TrekCondition currentCondition = CurrentState?.ToTrekCondition() ?? new TrekCondition();
 16420        TrekCondition? previousCondition = CurrentState?.PreviousState;
 21
 16422        RecordDeep.Look(chronicler, ref _handler, "Handler");
 16423        RecordValues.Look(chronicler, ref currentCondition, "CurrentCondition");
 16424        RecordValues.Look(chronicler, ref previousCondition, "PreviousCondition");
 16425        RecordValues.Look(chronicler, ref _isInitialized, "IsInitialized", false);
 26
 16427        if (chronicler.Mode == SerializationMode.Loading)
 28        {
 7729            CurrentState ??= new(currentCondition, previousCondition);
 7730            CurrentState.Update(currentCondition, previousCondition);
 7731            AbortTraversalFrame();
 32        }
 16433    }
 34
 35    #endregion
 36}

/home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Navigation/Motor/NavMotor.Traversal.cs

#LineLine coverage
 1//=======================================================================
 2// NavMotor.Traversal.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2024-present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using System;
 9using System.Runtime.CompilerServices;
 10using FixedMathSharp;
 11
 12namespace Trailblazer.Navigation.Motor;
 13
 14public partial class NavMotor
 15{
 16    #region Request Traversal
 17
 18    /// <summary>
 19    /// Processes a movement request and applies necessary forces.
 20    /// </summary>
 21    /// <remarks>
 22    /// This method opens a traversal phase for the current frame so duplicate force accumulation is rejected
 23    /// until the host calls <see cref="FinalizeTraversal"/> or <see cref="AbortTraversalFrame"/>.
 24    /// Movement forces such as gravity, jump, and platform adjustments are applied.
 25    /// </remarks>
 26    /// <param name="request">The movement request containing desired movement parameters</param>
 27    /// <param name="velocityDelta">The resulting velocity change to apply to the object</param>
 28    /// <param name="positionDelta">The resulting position change from platform movement to apply to the object</param>
 29    /// <param name="rotationDelta">The resulting rotation change from platform movement to apply to the object</param>
 30    public bool TryTraversal(
 31        TrekRequest request,
 32        out Vector3d velocityDelta,
 33        out Vector3d positionDelta,
 34        out FixedQuaternion rotationDelta)
 35    {
 219236        ResetTraversalOutputs(out velocityDelta, out positionDelta, out rotationDelta);
 219237        if (!TryBeginTraversalFrame())
 638            return false;
 39
 218440        PrepareTraversalState(request);
 218441        ResolveTraversalForces(request);
 42
 218443        velocityDelta = ResolveTraversalVelocityDelta();
 218444        ResolvePlatformTraversal(request, ref positionDelta, ref rotationDelta);
 218445        return true;
 46    }
 47
 48    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 49    private static void ResetTraversalOutputs(
 50        out Vector3d velocityDelta,
 51        out Vector3d positionDelta,
 52        out FixedQuaternion rotationDelta)
 53    {
 219254        velocityDelta = Vector3d.Zero;
 219255        positionDelta = Vector3d.Zero;
 219256        rotationDelta = FixedQuaternion.Identity;
 219257    }
 58
 59    private bool TryBeginTraversalFrame()
 60    {
 219261        if (!IsInitialized)
 162            return false;
 63
 219164        if (TraversalInProgress)
 65        {
 766            SwiftThrowHelper.ThrowIfTrue(
 767                FrameCount != _pendingTraversalFrame,
 768                message: $"NavMotor traversal from frame {_pendingTraversalFrame} was never finalized or aborted before 
 69
 570            return false;
 71        }
 72
 218473        TraversalInProgress = true;
 218474        _pendingTraversalFrame = FrameCount;
 218475        return true;
 76    }
 77
 78    private void PrepareTraversalState(TrekRequest request)
 79    {
 218480        TrailblazerLogger.DebugChannel.Info(
 218481            $"NavMotor State: Grounded={IsOnSolid}, InAir={IsInGas}, InWater={IsInLiquid}, Flying={IsFlying}, InLimbo={I
 82
 83        // Calculate the slope angle for the current frame based on the movement direction and surface normal.
 218484        FrameSlopeAngle = CurrentState.GetSignedSlopeAngle(request.Direction);
 85
 86        // Store the current velocity for manipulation.
 218487        _forceOutput = Handler.Move.FrameVelocity;
 88
 89        // Update platform velocity prior to applying jump force.
 218490        PlatformModule.UpdatePlatformVelocity();
 91
 218492        if (InLimbo)
 5593            Handler.IsInControl = false;
 94
 218495        if (JumpModule?.IsCoolingDown == true)
 10296            JumpModule.UpdateCooldown();
 97
 218498        UpdateClimbState(request);
 218499        UpdateFlightState(request);
 2184100        UpdateSwimState(request);
 2184101    }
 102
 103    #region Traversal Force Resolution
 104
 105    private void ResolveTraversalForces(TrekRequest request)
 106    {
 2184107        ComputeMovementForces(request);
 108
 109        // Reset this before applying gravity.
 2184110        if (JumpModule != null && (!request.IsRequestingJump || !JumpModule.CanJump))
 2134111            JumpModule.IsHoldingJump = false;
 112
 2184113        ApplyEnvironmentalForces();
 2184114        ApplyJumpForce(request);
 2184115    }
 116
 117    /// <summary>
 118    /// Computes the movement forces based on the traversal state and applies them to the object.
 119    /// </summary>
 120    /// <remarks>
 121    /// This method determines whether the object is in control, calculates velocity adjustments,
 122    /// and applies constraints such as slope resistance and airborne drag.
 123    /// </remarks>
 124    private void ComputeMovementForces(TrekRequest frameRequest)
 125    {
 2184126        UpdateControlState();
 127
 2184128        if (InLimbo)
 22129            return;
 130
 2162131        ResetDownwardMomentumIfNeeded();
 132
 2162133        Vector3d desiredVelocity = GetDesiredVelocity(frameRequest);
 134
 2162135        if (TryApplyStationaryGroundFriction(frameRequest.Direction))
 5136            return;
 137
 2157138        ApplyDesiredVelocity(desiredVelocity);
 2157139    }
 140
 141    private void UpdateControlState()
 142    {
 2184143        if (IsClimbing)
 144        {
 66145            SetSlidingState(false);
 66146            Handler.IsInControl = true;
 66147            return;
 148        }
 149
 2118150        if (IsOnSolid)
 151        {
 266152            if (IsFlying)
 153            {
 1154                SetSlidingState(false);
 1155                Handler.IsInControl = true;
 156            }
 157            else
 158            {
 265159                bool isTooSteep = IsTooSteep(FrameSlopeAngle);
 265160                SetSlidingState(SlideModule?.IsEnabled == true && isTooSteep);
 265161                Handler.IsInControl = !isTooSteep;
 162            }
 163
 265164            return;
 165        }
 166
 1852167        Handler.IsInControl = IsFlying || !InLimbo;
 1852168    }
 169
 170    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 171    private void SetSlidingState(bool isSliding)
 172    {
 332173        if (SlideModule != null)
 327174            SlideModule.IsSliding = isSliding;
 332175    }
 176
 177    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 178    private void ResetDownwardMomentumIfNeeded()
 179    {
 2162180        if (IsClimbing)
 181        {
 66182            _forceOutput = Vector3d.Zero;
 66183            return;
 184        }
 185
 2096186        if ((!IsOnSolid || WasInGas) && !IsFlying)
 1823187            _forceOutput.Y = Fixed64.Zero;
 2096188    }
 189
 190    /// <summary>
 191    /// Determines the desired velocity based on input direction, movement constraints, and traversal state.
 192    /// </summary>
 193    /// <returns>The computed velocity vector that the object should move toward.</returns>
 194    private Vector3d GetDesiredVelocity(TrekRequest frameRequest)
 195    {
 2162196        if (IsFlying)
 11197            return GetFlightVelocity(frameRequest);
 198
 2151199        if (ClimbModule?.IsMantling == true)
 20200            return GetMantleVelocity(frameRequest.Origin);
 201
 2131202        if (IsClimbing)
 46203            return GetClimbVelocity(frameRequest);
 204
 2085205        Vector3d result = GetControlledSurfaceVelocity(frameRequest);
 2085206        if (IsInLiquid)
 179207            return ResolveLiquidVelocity(frameRequest, result);
 208
 1906209        result = ApplyPlatformTransferVelocity(result);
 1906210        return ApplyGroundVelocityConstraints(result);
 211    }
 212
 213    /// <summary>
 214    /// Computes the desired world-space velocity while controlled flight is active.
 215    /// </summary>
 216    private Vector3d GetFlightVelocity(TrekRequest frameRequest)
 217    {
 11218        if (frameRequest.Rate == TrekRate.Stationary)
 4219            return Vector3d.Zero;
 220
 7221        FlyLocomotion flyModule = FlyModule!;
 7222        Fixed64 speedMultiplier = GetFlightSpeedMultiplier(frameRequest.Rate);
 7223        if (speedMultiplier <= Fixed64.Zero)
 1224            return Vector3d.Zero;
 225
 6226        Fixed3x3 transposedMatrix = frameRequest.Rotation.ToMatrix3x3();
 6227        Vector3d desiredLocalDirection = Fixed3x3.InverseTransformDirection(transposedMatrix, frameRequest.Direction);
 6228        Vector3d desiredLocalVelocity = Vector3d.Zero;
 229
 6230        Vector3d horizontalInput = new(desiredLocalDirection.X, Fixed64.Zero, desiredLocalDirection.Z);
 6231        Fixed64 horizontalMagnitude = FixedMath.Clamp01(horizontalInput.Magnitude);
 6232        if (horizontalMagnitude > Fixed64.Zero)
 3233            desiredLocalVelocity += horizontalInput.Normalized * (flyModule.MaxFlySpeed * speedMultiplier * horizontalMa
 234
 6235        Fixed64 verticalInput = FixedMath.Clamp(desiredLocalDirection.Y, -Fixed64.One, Fixed64.One);
 6236        if (verticalInput > Fixed64.Zero)
 1237            desiredLocalVelocity.Y = verticalInput * flyModule.MaxAscendSpeed * speedMultiplier;
 5238        else if (verticalInput < Fixed64.Zero)
 1239            desiredLocalVelocity.Y = verticalInput.Abs() * -flyModule.MaxDescendSpeed * speedMultiplier;
 240
 6241        return Fixed3x3.TransformDirection(transposedMatrix, desiredLocalVelocity);
 242    }
 243
 244    private Vector3d GetClimbVelocity(TrekRequest frameRequest)
 245    {
 46246        if (frameRequest.Rate == TrekRate.Stationary)
 2247            return Vector3d.Zero;
 248
 44249        ClimbLocomotion climbModule = ClimbModule!;
 44250        Vector3d upAxis = climbModule.AttachedUpDirection != Vector3d.Zero
 44251            ? climbModule.AttachedUpDirection.Normalized
 44252            : Vector3d.Up;
 44253        Vector3d outwardNormal = climbModule.AttachedSurfaceNormal != Vector3d.Zero
 44254            ? climbModule.AttachedSurfaceNormal.Normalized
 44255            : Vector3d.Backward;
 44256        Vector3d lateralAxis = Vector3d.Cross(upAxis, outwardNormal);
 44257        if (lateralAxis == Vector3d.Zero)
 1258            lateralAxis = Vector3d.Cross(Vector3d.Up, outwardNormal);
 44259        if (lateralAxis == Vector3d.Zero)
 1260            lateralAxis = Vector3d.Right;
 44261        lateralAxis = lateralAxis.Normalized;
 262
 44263        Fixed64 verticalAmount = Vector3d.Dot(frameRequest.Direction, upAxis);
 44264        if (!climbModule.ActiveAllowDescent && verticalAmount < Fixed64.Zero)
 1265            verticalAmount = Fixed64.Zero;
 266
 44267        Fixed64 lateralAmount = climbModule.ActiveAllowLateralTraverse
 44268            ? Vector3d.Dot(frameRequest.Direction, lateralAxis)
 44269            : Fixed64.Zero;
 270
 44271        Vector3d climbDirection = (upAxis * verticalAmount) + (lateralAxis * lateralAmount);
 44272        Fixed64 inputMagnitude = FixedMath.Clamp01(climbDirection.Magnitude);
 44273        if (inputMagnitude <= Fixed64.Zero)
 6274            return Vector3d.Zero;
 275
 38276        return climbDirection.Normalized * (climbModule.MaxClimbSpeed * inputMagnitude);
 277    }
 278
 279    private Vector3d GetMantleVelocity(Vector3d origin)
 280    {
 20281        ClimbLocomotion climbModule = ClimbModule!;
 20282        if (!climbModule.MantleTargetPosition.HasValue)
 1283            return Vector3d.Zero;
 284
 19285        Vector3d mantleTargetPosition = climbModule.MantleTargetPosition.GetValueOrDefault();
 19286        Vector3d toTarget = mantleTargetPosition - origin;
 19287        if (toTarget == Vector3d.Zero)
 1288            return Vector3d.Zero;
 289
 18290        Fixed64 distance = toTarget.Magnitude;
 18291        if (distance <= climbModule.ClimbStartTolerance)
 1292            return Vector3d.Zero;
 293
 17294        return toTarget.Normalized * climbModule.MaxClimbSpeed;
 295    }
 296
 297    private Vector3d GetControlledSurfaceVelocity(TrekRequest frameRequest)
 298    {
 2085299        if (SlideModule?.IsSliding == true)
 27300            return GetSlidingVelocity(frameRequest);
 301
 2058302        return Handler.IsInControl && frameRequest.Rate != TrekRate.Stationary
 2058303            ? GetHorizontalVelocity(frameRequest)
 2058304            : Vector3d.Zero;
 305    }
 306
 307    private Vector3d GetSlidingVelocity(TrekRequest frameRequest)
 308    {
 27309        SlideLocomotion slideModule = SlideModule!;
 310
 27311        Vector3d slideDirection = new Vector3d(
 27312            CurrentState.SurfaceNormal.X,
 27313            Fixed64.Zero,
 27314            CurrentState.SurfaceNormal.Z).Normalized;
 27315        Vector3d projectedMoveDir = Vector3d.Project(frameRequest.Direction, slideDirection);
 27316        Vector3d speedContribution = projectedMoveDir * slideModule.SpeedControl;
 27317        Vector3d sidewaysContribution = (frameRequest.Direction - projectedMoveDir) * slideModule.SidewaysControl;
 27318        return slideDirection + ((speedContribution + sidewaysContribution) * slideModule.SlidingSpeed);
 319    }
 320
 321    private Vector3d GetHorizontalVelocity(TrekRequest frameRequest)
 322    {
 164323        Fixed3x3 transposedMatrix = frameRequest.Rotation.ToMatrix3x3();
 164324        Vector3d desiredLocalDirection = Fixed3x3.InverseTransformDirection(transposedMatrix, frameRequest.Direction);
 164325        Fixed64 speed = MaxHoritzontalSpeedInDirection(desiredLocalDirection, frameRequest.Rate);
 326
 164327        speed *= Handler.Move.MoveSpeedMultiplier;
 328
 329        // Modify max speed on slopes based on slope speed multiplier curve
 164330        if (Handler.Move.ModifySpeedOnSlope && IsOnSlope(FrameSlopeAngle))
 34331            speed *= Handler.Move.SlopeSpeedMultiplier.Evaluate(FrameSlopeAngle);
 332
 164333        return Fixed3x3.TransformDirection(transposedMatrix, desiredLocalDirection * speed);
 334    }
 335
 336    /// <summary>
 337    /// Calculates the maximum allowable speed in a given movement direction.
 338    /// </summary>
 339    /// <param name="desiredMovementDirection">The movement direction to evaluate.</param>
 340    /// <param name="rate">The rate that dictates the maximum speed allowed.</param>
 341    /// <returns>The maximum speed possible in the specified direction.</returns>
 342    public Fixed64 MaxHoritzontalSpeedInDirection(Vector3d desiredMovementDirection, TrekRate rate)
 343    {
 190344        if (desiredMovementDirection == Vector3d.Zero)
 4345            return Fixed64.Zero;
 346
 186347        if (IsFlying)
 10348            return GetFlightHorizontalSpeed(desiredMovementDirection, rate);
 349
 176350        if (IsInLiquid)
 26351            return GetLiquidHorizontalSpeed(desiredMovementDirection);
 352
 150353        return GetGroundHorizontalSpeed(desiredMovementDirection, rate);
 354    }
 355
 356    private Fixed64 GetFlightHorizontalSpeed(Vector3d desiredMovementDirection, TrekRate rate)
 357    {
 10358        FlyLocomotion flyModule = FlyModule!;
 10359        if (!flyModule.IsEnabled || !flyModule.CanFly)
 3360            return Fixed64.Zero;
 361
 7362        Fixed64 horizontalMagnitude = FixedMath.Clamp01(new Vector3d(
 7363            desiredMovementDirection.X,
 7364            Fixed64.Zero,
 7365            desiredMovementDirection.Z).Magnitude);
 7366        return horizontalMagnitude * flyModule.MaxFlySpeed * GetFlightSpeedMultiplier(rate);
 367    }
 368
 369    private Fixed64 GetFlightSpeedMultiplier(TrekRate rate)
 370    {
 14371        if (rate == TrekRate.Stationary)
 1372            return Fixed64.Zero;
 373
 13374        Fixed64 maxFastSpeed = Handler.Move.MaxFastSpeed;
 13375        if (rate == TrekRate.Fast || maxFastSpeed <= Fixed64.Zero)
 9376            return Fixed64.One;
 377
 4378        return FixedMath.Clamp(GetScaledFlightSpeedMultiplier(rate, maxFastSpeed), Fixed64.Zero, Fixed64.One);
 379    }
 380
 381    private Fixed64 GetLiquidHorizontalSpeed(Vector3d desiredMovementDirection)
 382    {
 26383        if (WaterModule?.IsEnabled != true
 26384            || !WaterModule.CanSwim
 26385            || !WaterModule.IsSwimming)
 386        {
 9387            return Fixed64.Zero;
 388        }
 389
 17390        Fixed64 ellipseMultiplier = WaterModule.MaxSwimSpeed / WaterModule.MaxSwimSidewaysSpeed;
 17391        if (ellipseMultiplier <= Fixed64.Zero)
 1392            return Fixed64.Zero;
 393
 16394        return GetEllipticalHorizontalSpeed(
 16395            desiredMovementDirection,
 16396            ellipseMultiplier,
 16397            WaterModule.MaxSwimSidewaysSpeed,
 16398            Fixed64.One);
 399    }
 400
 401    private Fixed64 GetGroundHorizontalSpeed(Vector3d desiredMovementDirection, TrekRate rate)
 402    {
 150403        Fixed64 maxSpeed = GetGroundDirectionalMaxSpeed(desiredMovementDirection, rate);
 150404        Fixed64 ellipseMultiplier = maxSpeed / Handler.Move.MaxSidewaysSpeed;
 150405        if (ellipseMultiplier <= Fixed64.Zero)
 2406            return Fixed64.Zero;
 407
 148408        return GetEllipticalHorizontalSpeed(
 148409            desiredMovementDirection,
 148410            ellipseMultiplier,
 148411            Handler.Move.MaxSidewaysSpeed,
 148412            GetAirborneControlMultiplier());
 413    }
 414
 415    private Fixed64 GetAirborneControlMultiplier()
 416    {
 148417        if (IsOnSolid)
 124418            return Fixed64.One;
 419
 24420        if (IsJumping)
 2421            return JumpModule!.JumpControlMultiplier;
 422
 22423        if (IsFalling)
 21424            return Handler.Fall.FallControlMultiplier;
 425
 1426        return Fixed64.One;
 427    }
 428
 429    private static Fixed64 GetEllipticalHorizontalSpeed(
 430        Vector3d desiredMovementDirection,
 431        Fixed64 zAxisEllipseMultiplier,
 432        Fixed64 sidewaysSpeed,
 433        Fixed64 controlMultiplier)
 434    {
 164435        Vector3d normalized = new Vector3d(
 164436            desiredMovementDirection.X,
 164437            Fixed64.Zero,
 164438            desiredMovementDirection.Z / zAxisEllipseMultiplier).Normalized;
 164439        Fixed64 length = new Vector3d(
 164440            normalized.X,
 164441            Fixed64.Zero,
 164442            normalized.Z * zAxisEllipseMultiplier).Magnitude;
 164443        return length * sidewaysSpeed * controlMultiplier;
 444    }
 445
 446    private Fixed64 GetGroundDirectionalMaxSpeed(Vector3d desiredMovementDirection, TrekRate rate)
 447    {
 150448        if (desiredMovementDirection.Z < Fixed64.Zero)
 4449            return Handler.Move.MaxBackwardsSpeed;
 450
 146451        return rate switch
 146452        {
 82453            TrekRate.Slow => Handler.Move.MaxSlowSpeed,
 57454            TrekRate.Moderate => Handler.Move.MaxModerateSpeed,
 6455            TrekRate.Fast => Handler.Move.MaxFastSpeed,
 1456            _ => Fixed64.Zero
 146457        };
 458    }
 459
 460    private Vector3d ResolveLiquidVelocity(TrekRequest frameRequest, Vector3d desiredVelocity)
 461    {
 179462        if (WaterModule?.IsEnabled != true
 179463            || !WaterModule.CanSwim
 179464            || !WaterModule.IsSwimming)
 465        {
 161466            desiredVelocity = Vector3d.Zero;
 467        }
 468
 179469        if (WaterModule?.IsSwimming == true && frameRequest.Direction.Y != Fixed64.Zero)
 10470            desiredVelocity.Y = frameRequest.Direction.Y * WaterModule.MaxSwimSpeed;
 471
 179472        if (desiredVelocity != Vector3d.Zero)
 14473            desiredVelocity *= FixedMath.Clamp01(Fixed64.One - Handler.Move.WaterDragFactor);
 474
 179475        return desiredVelocity;
 476    }
 477
 478    private Vector3d ApplyPlatformTransferVelocity(Vector3d desiredVelocity)
 479    {
 1906480        PlatformLocomotion platformModule = PlatformModule;
 1906481        if (platformModule.MovementTransfer == MotionTransfer.PermaTransfer)
 482        {
 2483            desiredVelocity += platformModule.FramePlatformVelocity;
 2484            desiredVelocity.Y = Fixed64.Zero;
 485        }
 486
 1906487        return desiredVelocity;
 488    }
 489
 490    private Vector3d ApplyGroundVelocityConstraints(Vector3d desiredVelocity)
 491    {
 1906492        if (!IsOnSolid || desiredVelocity == Vector3d.Zero)
 1758493            return desiredVelocity;
 494
 148495        Fixed64 surfaceFriction = CurrentState.GroundState?.SurfaceFriction ?? Fixed64.Zero;
 148496        desiredVelocity *= Fixed64.One - surfaceFriction;
 497
 498        // Flat or host-defined "solid but no sampled normal" surfaces should preserve the raw ground vector.
 499        // Sliding also already produces a slope-aware direction, so re-projecting it here distorts the result.
 148500        if (CurrentState.SurfaceNormal == Vector3d.Zero
 148501            || CurrentState.SlopeAngle == Fixed64.Zero
 148502            || SlideModule?.IsSliding == true)
 503        {
 98504            return desiredVelocity;
 505        }
 506
 50507        Vector3d sideways = Vector3d.Cross(Vector3d.Up, desiredVelocity);
 50508        Vector3d adjustedVelocity = Vector3d.Cross(sideways, CurrentState.SurfaceNormal).Normalized * desiredVelocity.Ma
 50509        if (Fixed64.Sign(adjustedVelocity.Y) != Fixed64.Sign(FrameSlopeAngle))
 49510            adjustedVelocity.Y *= -1;
 511
 50512        return adjustedVelocity;
 513    }
 514
 515    private bool TryApplyStationaryGroundFriction(Vector3d desiredDirection)
 516    {
 2162517        if (!IsOnSolid || IsFlying || desiredDirection != Vector3d.Zero)
 2023518            return false;
 519
 139520        Fixed64 friction = CurrentState.GroundState?.SurfaceFriction ?? Fixed64.Zero;
 139521        if (friction <= Fixed64.Zero || _forceOutput == Vector3d.Zero)
 134522            return false;
 523
 5524        _forceOutput *= Fixed64.One - friction;
 5525        return true;
 526    }
 527
 528    private void ApplyDesiredVelocity(Vector3d desiredVelocity)
 529    {
 2157530        if (desiredVelocity == _forceOutput)
 1933531            return;
 532
 224533        Fixed64 maxVelocityChange = GetMaxAcceleration() * DeltaTime;
 224534        Vector3d velocityChange = (desiredVelocity - _forceOutput).ClampMagnitude(maxVelocityChange);
 535        System.Diagnostics.Debug.Assert(
 536            IsOnSolid || Handler.IsInControl,
 537            "UpdateControlState rejects limbo before desired velocity can be applied.");
 538
 224539        _forceOutput += velocityChange;
 224540        if (IsOnSolid && !IsFlying)
 132541            _forceOutput.Y = FixedMath.Min(_forceOutput.Y, Fixed64.Zero);
 224542    }
 543
 544    /// <summary>
 545    /// Retrieves the maximum acceleration value based on the object’s current traversal state.
 546    /// </summary>
 547    /// <returns>The acceleration limit depending on whether the object is grounded, airborne, or swimming.</returns>
 548    public Fixed64 GetMaxAcceleration()
 549    {
 239550        SwiftThrowHelper.ThrowIfTrue(
 239551            CurrentState == null,
 239552            message: "NavMotor must be initialized before querying max acceleration.");
 553
 238554        if (IsInLiquid)
 19555            return WaterModule?.IsEnabled == true
 19556                && WaterModule.CanSwim
 19557                && WaterModule.IsSwimming
 19558                ? WaterModule.MaxSwimAcceleration
 19559                : Handler.Move.MaxAirAcceleration;
 560
 219561        if (IsClimbing)
 58562            return ClimbModule!.IsEnabled && ClimbModule.CanClimb
 58563                ? ClimbModule.MaxClimbAcceleration
 58564                : Handler.Move.MaxAirAcceleration;
 565
 161566        if (IsFlying)
 8567            return FlyModule!.IsEnabled && FlyModule.CanFly
 8568                ? FlyModule.MaxFlyAcceleration
 8569                : Handler.Move.MaxAirAcceleration;
 570
 284571        if (IsOnSolid) return Handler.Move.MaxGroundAcceleration;
 572
 22573        if (IsJumping || IsFalling || IsInGas)
 21574            return Handler.Move.MaxAirAcceleration;
 575
 1576        throw new InvalidOperationException(
 1577            $"Cannot resolve max acceleration while traversal medium is {CurrentState.Medium}. NavMotor requires a known
 578    }
 579
 580    private void ApplyEnvironmentalForces()
 581    {
 2184582        Fixed64 gravityStep = Handler.Forces.GravityForce * DeltaTime;
 583
 2184584        if (IsFlying)
 585        {
 11586            Fixed64 gravityCompensation = FixedMath.Clamp(
 11587                FlyModule!.GravityCompensation,
 11588                Fixed64.Zero,
 11589                Fixed64.One);
 11590            _forceOutput.Y -= gravityStep * (Fixed64.One - gravityCompensation);
 11591            return;
 592        }
 593
 2173594        if (IsClimbing)
 595        {
 66596            Fixed64 gravityCompensation = FixedMath.Clamp(
 66597                ClimbModule!.GravityCompensationWhileClimbing,
 66598                Fixed64.Zero,
 66599                Fixed64.One);
 66600            _forceOutput.Y -= gravityStep * (Fixed64.One - gravityCompensation);
 66601            return;
 602        }
 603
 2107604        if (IsOnSolid)
 605        {
 265606            _forceOutput.Y = FixedMath.Min(Fixed64.Zero, _forceOutput.Y) - gravityStep;
 265607            return;
 608        }
 609
 1842610        if (IsInLiquid)
 611        {
 612            // Apply buoyancy if we can swim, otherwise apply gravity as normal.
 613            // Even if we can swim, we still apply gravity but reduce it based on the buoyancy factor to
 614            // create a more natural sinking effect when not actively swimming upwards.
 179615            if (WaterModule?.IsEnabled == true)
 175616                _forceOutput.Y += gravityStep * (WaterModule.BuoyancyFactor - Fixed64.One);
 617            else
 4618                _forceOutput.Y = Handler.Move.FrameVelocity.Y - gravityStep;
 619
 4620            return;
 621        }
 622
 1664623        if (!IsInGas) return;
 624
 1662625        _forceOutput.Y = Handler.Move.FrameVelocity.Y - gravityStep;
 626
 627        // Ensure velocity does not exceed terminal fall speed
 1662628        Fixed64 terminalFallSpeed = Handler.Move.FrameVelocity.Y + (_forceOutput.Y * DeltaTime);
 1662629        if (terminalFallSpeed < -Handler.Forces.TerminalVelocity)
 7630            _forceOutput.Y = -Handler.Forces.TerminalVelocity - Handler.Move.FrameVelocity.Y;
 631
 632        // When jumping up we don't apply gravity for some time when the user is holding the jump button.
 633        // This allows for more control over jump height by pressing the button longer.
 1662634        JumpLocomotion? jumpModule = JumpModule;
 1662635        if (IsJumping && jumpModule!.IsHoldingJump)
 636        {
 637            // Calculate the duration that the extra jump force should have effect.
 638            // If we're still less than that duration after the jumping time, apply the force.
 3639            Fixed64 extraJumpLimit = (jumpModule.JumpStartTime + jumpModule.ExtraJumpHeight) / GetVerticalJumpSpeed();
 640
 641            // Negate the gravity we just applied, except we push in jumpDir rather than jump upwards.
 3642            if (TotalTime <= extraJumpLimit)
 2643                _forceOutput += jumpModule.FrameJumpDirection * gravityStep;
 644        }
 1662645    }
 646
 647    private void ApplyJumpForce(TrekRequest request)
 648    {
 2184649        if (!CanApplyJumpForce(request))
 2152650            return;
 651
 32652        Vector3d jumpForce = IsInLiquid
 32653            ? GetWaterBreachJumpForce()
 32654            : IsClimbing
 32655                ? GetClimbDetachJumpForce()
 32656                : GetGroundJumpForce();
 32657        CommitJumpForce(jumpForce);
 32658    }
 659
 660    private bool CanApplyJumpForce(TrekRequest request)
 661    {
 2184662        if (!(JumpModule?.IsEnabled == true
 2184663            && Handler.IsInControl
 2184664            && request.IsRequestingJump))
 665        {
 2070666            return false;
 667        }
 668
 114669        if (IsFlying || IsFalling)
 17670            return false;
 671
 97672        if (IsClimbing && !ClimbModule!.ActiveAllowDetachJump)
 1673            return false;
 674
 96675        if (IsInLiquid && !(WaterModule?.CanBreachWater ?? false))
 2676            return false;
 677
 94678        if (!JumpModule.CanJump)
 60679            return false;
 680
 34681        return request.CanAffordJump;
 682    }
 683
 684    private Vector3d GetWaterBreachJumpForce()
 685    {
 2686        JumpLocomotion jumpModule = JumpModule!;
 2687        WaterLocomotion waterModule = WaterModule!;
 688
 2689        jumpModule.FrameJumpDirection = Vector3d.Up;
 2690        Events.OnStartWaterBreach?.Invoke();
 2691        return jumpModule.FrameJumpDirection * (GetVerticalJumpSpeed() * waterModule.BreachJumpMultiplier);
 692    }
 693
 694    private Vector3d GetGroundJumpForce()
 695    {
 28696        JumpLocomotion jumpModule = JumpModule!;
 697
 28698        EnsureJumpDirectionInitialized();
 28699        Events.OnStartJump?.Invoke(jumpModule.AvoidGroundingTimer);
 28700        return jumpModule.FrameJumpDirection * GetVerticalJumpSpeed();
 701    }
 702
 703    private Vector3d GetClimbDetachJumpForce()
 704    {
 2705        JumpLocomotion jumpModule = JumpModule!;
 2706        ClimbLocomotion climbModule = ClimbModule!;
 707
 2708        Vector3d upward = climbModule.AttachedUpDirection != Vector3d.Zero
 2709            ? climbModule.AttachedUpDirection.Normalized
 2710            : Vector3d.Up;
 2711        Vector3d outward = climbModule.AttachedSurfaceNormal != Vector3d.Zero
 2712            ? climbModule.AttachedSurfaceNormal.Normalized
 2713            : Vector3d.Backward;
 2714        jumpModule.FrameJumpDirection = Vector3d.Slerp(upward, outward, jumpModule.PerpendicularJumpAmount);
 2715        Events.OnStartJump?.Invoke(jumpModule.AvoidGroundingTimer);
 2716        return jumpModule.FrameJumpDirection * GetVerticalJumpSpeed();
 717    }
 718
 719    private void EnsureJumpDirectionInitialized()
 720    {
 28721        JumpLocomotion jumpModule = JumpModule!;
 28722        if (jumpModule.IsJumping)
 4723            return;
 724
 725        System.Diagnostics.Debug.Assert(
 726            !IsTooSteep(FrameSlopeAngle),
 727            "Ground-jump admission requires control, which rejects surfaces above the slope limit.");
 728
 24729        jumpModule.FrameJumpDirection = Vector3d.Slerp(
 24730            Vector3d.Up,
 24731            CurrentState.SurfaceNormal,
 24732            jumpModule.PerpendicularJumpAmount);
 24733    }
 734
 735    private void CommitJumpForce(Vector3d jumpForce)
 736    {
 32737        if (IsClimbing)
 2738            StopClimb(wasForced: false);
 739
 32740        JumpLocomotion jumpModule = JumpModule!;
 741
 32742        jumpModule.RegisterJump();
 743
 744        // Remove any existing downward force before the jump impulse is applied.
 32745        _forceOutput.Y = FixedMath.Max(Fixed64.Zero, _forceOutput.Y);
 32746        _forceOutput += jumpForce;
 32747    }
 748
 749    private void UpdateClimbState(TrekRequest request)
 750    {
 2184751        if (ClimbModule?.IsEnabled != true)
 10752            return;
 753
 2174754        if (ClimbModule.IsMantling)
 11755            return;
 756
 2163757        bool canAttemptClimb = request.IsRequestingClimb
 2163758            && ClimbModule.CanClimb
 2163759            && !IsInLiquid
 2163760            && CurrentState.Medium != TraversalMedium.Unknown;
 2163761        if (!canAttemptClimb)
 762        {
 2088763            if (ClimbModule.IsClimbing)
 2764                StopClimb(wasForced: false);
 765
 2088766            return;
 767        }
 768
 75769        if (ClimbModule.ClimbResolver == null
 75770            || !ClimbModule.ClimbResolver.TryResolveClimbAffordance(request, CurrentState, out ClimbAffordanceSnapshot s
 771        {
 15772            if (ClimbModule.IsClimbing)
 1773                StopClimb(wasForced: true);
 774
 15775            return;
 776        }
 777
 60778        if (ClimbModule.IsClimbing)
 779        {
 25780            bool canContinue = snapshot.CanContinueClimb
 25781                && IsCompatibleClimbAffordance(snapshot);
 25782            if (!canContinue)
 783            {
 4784                StopClimb(wasForced: true);
 4785                return;
 786            }
 787
 21788            ClimbModule.ApplyClimbSnapshot(snapshot);
 789
 21790            if (ShouldStartMantle(request, snapshot))
 9791                StartMantle(snapshot);
 792
 21793            return;
 794        }
 795
 35796        bool canStart = snapshot.CanStartClimb;
 35797        if (!canStart)
 1798            return;
 799
 34800        StartClimb(snapshot);
 34801    }
 802
 803    private bool IsCompatibleClimbAffordance(ClimbAffordanceSnapshot snapshot)
 804    {
 24805        ClimbLocomotion climbModule = ClimbModule!;
 806
 24807        if (climbModule.AttachmentId.HasValue && snapshot.AffordanceId.HasValue)
 19808            return climbModule.AttachmentId.Value == snapshot.AffordanceId.Value;
 809
 5810        if (snapshot.Kind != climbModule.ActiveClimbKind)
 1811            return false;
 812
 4813        if (!HasCompatibleClimbAxes(snapshot))
 2814            return false;
 815
 2816        Fixed64 tolerance = GetClimbContinuityTolerance();
 2817        return (snapshot.AttachmentPoint - climbModule.AttachmentPoint).MagnitudeSquared <= tolerance * tolerance;
 818    }
 819
 820    private bool HasCompatibleClimbAxes(ClimbAffordanceSnapshot snapshot)
 821    {
 4822        ClimbLocomotion climbModule = ClimbModule!;
 823
 4824        if (climbModule.AttachedSurfaceNormal != Vector3d.Zero
 4825            && snapshot.SurfaceNormal != Vector3d.Zero
 4826            && Vector3d.Dot(climbModule.AttachedSurfaceNormal.Normalized, snapshot.SurfaceNormal.Normalized) <= Fixed64.
 827        {
 1828            return false;
 829        }
 830
 3831        if (climbModule.AttachedUpDirection != Vector3d.Zero
 3832            && snapshot.UpDirection != Vector3d.Zero
 3833            && Vector3d.Dot(climbModule.AttachedUpDirection.Normalized, snapshot.UpDirection.Normalized) <= Fixed64.Zero
 834        {
 1835            return false;
 836        }
 837
 2838        return true;
 839    }
 840
 841    private Fixed64 GetClimbContinuityTolerance()
 842    {
 2843        ClimbLocomotion climbModule = ClimbModule!;
 844
 2845        Fixed64 frameTravelAllowance = climbModule.MaxClimbSpeed * DeltaTime;
 2846        return climbModule.ClimbStartTolerance + frameTravelAllowance;
 847    }
 848
 849    private void StartClimb(ClimbAffordanceSnapshot snapshot)
 850    {
 34851        ClimbLocomotion climbModule = ClimbModule!;
 852
 34853        climbModule.ApplyClimbSnapshot(snapshot);
 34854        climbModule.IsClimbing = true;
 34855        climbModule.IsMantling = false;
 856
 34857        if (IsFalling)
 1858            Handler.ClearTransientState<FallLocomotion>();
 859
 34860        if (JumpModule != null)
 861        {
 33862            JumpModule.IsJumping = false;
 33863            JumpModule.IsHoldingJump = false;
 864        }
 865
 34866        if (FlyModule != null)
 33867            FlyModule.IsFlying = false;
 868
 34869        if (SlideModule != null)
 33870            SlideModule.IsSliding = false;
 871
 34872        Events.OnStartClimb?.Invoke(snapshot);
 3873    }
 874
 875    private bool ShouldStartMantle(TrekRequest request, ClimbAffordanceSnapshot snapshot)
 876    {
 21877        ClimbLocomotion climbModule = ClimbModule!;
 878
 21879        if (snapshot.Kind != ClimbAffordanceKind.Ledge
 21880            || !climbModule.ActiveAllowMantle
 21881            || request.Rate == TrekRate.Stationary)
 882        {
 11883            return false;
 884        }
 885
 10886        Vector3d upAxis = climbModule.AttachedUpDirection != Vector3d.Zero
 10887            ? climbModule.AttachedUpDirection.Normalized
 10888            : Vector3d.Up;
 10889        return Vector3d.Dot(request.Direction, upAxis) > Fixed64.Zero;
 890    }
 891
 892    private void StartMantle(ClimbAffordanceSnapshot snapshot)
 893    {
 9894        ClimbLocomotion climbModule = ClimbModule!;
 895
 9896        climbModule.ApplyClimbSnapshot(snapshot);
 9897        climbModule.IsMantling = true;
 9898        Events.OnStartMantle?.Invoke();
 1899    }
 900
 901    private void UpdateFlightState(TrekRequest request)
 902    {
 2184903        if (FlyModule?.IsEnabled != true)
 10904            return;
 905
 2174906        bool shouldFly = request.IsRequestingFlight
 2174907            && FlyModule.CanFly
 2174908            && !IsClimbing
 2174909            && !IsInLiquid
 2174910            && CurrentState.Medium != TraversalMedium.Unknown;
 911
 2174912        if (!shouldFly)
 913        {
 2163914            FlyModule.IsFlying = false;
 2163915            return;
 916        }
 917
 11918        FlyModule.IsFlying = true;
 919
 11920        if (IsFalling)
 1921            Handler.ClearTransientState<FallLocomotion>();
 922
 11923        if (JumpModule != null)
 924        {
 10925            JumpModule.IsJumping = false;
 10926            JumpModule.IsHoldingJump = false;
 927        }
 928
 11929        if (SlideModule != null)
 10930            SlideModule.IsSliding = false;
 11931    }
 932
 933    private void UpdateSwimState(TrekRequest request)
 934    {
 2184935        WaterLocomotion? waterModule = WaterModule;
 2184936        if (waterModule?.IsEnabled != true)
 12937            return;
 938
 2172939        waterModule.RequestedSwimThisTraversal = request.IsRequestingSwim;
 2172940        waterModule.IsSwimming = IsInLiquid
 2172941            && waterModule.CanSwim
 2172942            && request.IsRequestingSwim;
 2172943    }
 944
 945    #endregion
 946
 947    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 948    private Vector3d ResolveTraversalVelocityDelta()
 949    {
 2184950        return _forceOutput != Vector3d.Zero
 2184951            ? _forceOutput * DeltaTime
 2184952            : Vector3d.Zero;
 953    }
 954
 955    private void ResolvePlatformTraversal(
 956        TrekRequest request,
 957        ref Vector3d positionDelta,
 958        ref FixedQuaternion rotationDelta)
 959    {
 2184960        PlatformLocomotion platformModule = PlatformModule;
 961
 962        // Do not apply platform movement if we just jumped or if the platform is not actively driving us.
 2184963        bool isMovingWithPlatform = platformModule.IsActive
 2184964            && !IsFlying
 2184965            && !IsClimbing
 2184966            && (IsOnSolid || platformModule.IsLockedToPlatform);
 2184967        if (!isMovingWithPlatform || IsJumping)
 1981968            return;
 969
 203970        platformModule.GetPlatformInfluence(
 203971            request.FootPosition ?? request.Origin,
 203972            request.Rotation,
 203973            out positionDelta,
 203974            out rotationDelta);
 203975    }
 976
 977    #endregion
 978}

/home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Navigation/Motor/NavMotor.Utility.cs

#LineLine coverage
 1//=======================================================================
 2// NavMotor.Utility.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2024-present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using FixedMathSharp;
 9
 10namespace Trailblazer.Navigation.Motor;
 11
 12public partial class NavMotor
 13{
 14    #region Utility
 15
 16    /// <summary>
 17    /// Computes the vertical jump speed required to reach the desired jump height (apex).
 18    /// </summary>
 19    /// <returns>The initial vertical velocity needed for the jump.</returns>
 3720    public Fixed64 GetVerticalJumpSpeed() => JumpModule == null
 3721        ? Fixed64.Zero
 3722        : FixedMath.Sqrt(2 * JumpModule.BaseJumpHeight * Handler.Forces.GravityForce);
 23
 24    /// <summary>
 25    /// Determines whether the current surface is too steep for normal movement.
 26    /// </summary>
 27    /// <returns>True if the slope exceeds the allowable incline; otherwise, false.</returns>
 28    public bool IsTooSteep(Fixed64 angle)
 29    {
 91830        if (!IsOnSolid) return false;
 31
 48632        Fixed64 absAngle = FixedMath.Abs(angle); // Handle both positive (uphill) and negative (downhill) slopes
 48633        return absAngle > Handler.Move.SlopeLimit - Fixed64.Epsilon;
 34    }
 35
 36    /// <summary>
 37    /// Checks if the object is on a sloped surface that is not considered too steep.
 38    /// </summary>
 39    /// <returns>True if the object is on a valid slope; otherwise, false.</returns>
 40    public bool IsOnSlope(Fixed64 angle)
 41    {
 18742        if (!IsOnSolid) return false;
 43
 10944        Fixed64 absAngle = FixedMath.Abs(angle); // Account for downhill slopes too
 10945        return absAngle > Fixed64.One && absAngle <= Handler.Move.SlopeLimit + Fixed64.Epsilon;
 46    }
 47
 48    /// <summary>
 49    /// Manually sets the object’s velocity, overriding the computed velocity for the next frame.
 50    /// </summary>
 51    /// <param name="velocity">The new velocity to assign to the object.</param>
 52    public void SetVelocity(Vector3d velocity)
 53    {
 50254        Handler.Move.FrameVelocity = velocity;
 50255    }
 56
 57    /// <summary>
 58    /// Pushes a traversal snapshot into the motor before the next traversal phase begins.
 59    /// </summary>
 60    /// <remarks>
 61    /// This is the explicit pre-traversal sync seam for hosts that learn about medium or surface
 62    /// changes before the next call to <see cref="TryTraversal(TrekRequest, out Vector3d, out Vector3d, out FixedQuater
 63    /// </remarks>
 64    public void SyncTraversalState(TrekCondition newCondition, bool isInitializing = false)
 65    {
 28266        if (isInitializing)
 67        {
 68            // Don't set the previous state as an empty state
 24969            CurrentState.Update(newCondition, newCondition);
 24970            return;
 71        }
 72
 3373        TrekCondition previousCondition = CurrentState.ToTrekCondition();
 3374        CurrentState.Update(newCondition, previousCondition);
 3375    }
 76
 77    /// <summary>
 78    /// Clears the current traversal-finalization requirement without reconciling frame results.
 79    /// </summary>
 80    /// <remarks>
 81    /// This is an explicit recovery escape hatch for hosts that must discard an in-progress traversal.
 82    /// It clears traversal bookkeeping only and does not roll back locomotion state changes that already occurred.
 83    /// </remarks>
 84    public void AbortTraversalFrame()
 85    {
 223886        TraversalInProgress = false;
 223887        _pendingTraversalFrame = -1;
 223888        FrameSlopeAngle = Fixed64.Zero;
 223889        _forceOutput = Vector3d.Zero;
 223890    }
 91
 92    #endregion
 93}

Methods/Properties

.ctor()
get_IsInitialized()
get_PlatformModule()
get_JumpModule()
get_SlideModule()
get_WaterModule()
get_FlyModule()
get_ClimbModule()
get_StateChanged()
get_IsOnSolid()
get_WasOnSolid()
get_IsInGas()
get_WasInGas()
get_IsInLiquid()
get_WasInLiquid()
get_IsFlying()
get_IsJumping()
get_IsFalling()
get_IsClimbing()
get_InLimbo()
CreateUninitialized(Trailblazer.TrailblazerWorldContext,Trailblazer.Navigation.Motor.LocomotionHandler)
.ctor(Trailblazer.TrailblazerWorldContext,Trailblazer.Navigation.Motor.TrekCondition,Trailblazer.Navigation.Motor.LocomotionProfile)
BindContext(Trailblazer.TrailblazerWorldContext)
RequireContext()
get_FrameCount()
get_DeltaTime()
get_InvDeltaTime()
get_TotalTime()
OnInitialize(Trailblazer.Navigation.Motor.TrekCondition,Trailblazer.Navigation.Motor.LocomotionProfile)
SetLocomotionProfile(Trailblazer.Navigation.Motor.LocomotionProfile)
ConfigureLocomotions(System.Action`1<Trailblazer.Navigation.Motor.LocomotionProfileBuilder>)
get_Handler()
FinalizeTraversal(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,Trailblazer.Navigation.Motor.TrekCondition,System.Nullable`1<FixedMathSharp.Vector3d>)
ShouldFinalizeTraversal()
ValidatePendingTraversalFrame()
RefreshTraversalState(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Trailblazer.Navigation.Motor.TrekCondition)
HandleTraversalTransitions()
HandleClimbState(FixedMathSharp.Vector3d)
HandleGasExitTransition()
FinalizePlatformMovement(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion)
CheckJumpStatus(FixedMathSharp.Vector3d)
HandlePlatformTransitions()
HandleSwimState(FixedMathSharp.Vector3d)
HandleFlightState()
HandleFallState(FixedMathSharp.Vector3d)
ShouldClearActiveFallState()
UpdateActiveFallState(FixedMathSharp.Vector3d)
TryStartFall(FixedMathSharp.Vector3d)
GetScaledFlightSpeedMultiplier(Trailblazer.Navigation.TrekRate,FixedMathSharp.Fixed64)
StopClimb(System.Boolean)
CanContinueActiveMantle()
HasReachedMantleTarget(FixedMathSharp.Vector3d)
CompleteMantle()
RecordData(Chronicler.IChronicler)
TryTraversal(Trailblazer.Navigation.Motor.TrekRequest,FixedMathSharp.Vector3d&,FixedMathSharp.Vector3d&,FixedMathSharp.FixedQuaternion&)
ResetTraversalOutputs(FixedMathSharp.Vector3d&,FixedMathSharp.Vector3d&,FixedMathSharp.FixedQuaternion&)
TryBeginTraversalFrame()
PrepareTraversalState(Trailblazer.Navigation.Motor.TrekRequest)
ResolveTraversalForces(Trailblazer.Navigation.Motor.TrekRequest)
ComputeMovementForces(Trailblazer.Navigation.Motor.TrekRequest)
UpdateControlState()
SetSlidingState(System.Boolean)
ResetDownwardMomentumIfNeeded()
GetDesiredVelocity(Trailblazer.Navigation.Motor.TrekRequest)
GetFlightVelocity(Trailblazer.Navigation.Motor.TrekRequest)
GetClimbVelocity(Trailblazer.Navigation.Motor.TrekRequest)
GetMantleVelocity(FixedMathSharp.Vector3d)
GetControlledSurfaceVelocity(Trailblazer.Navigation.Motor.TrekRequest)
GetSlidingVelocity(Trailblazer.Navigation.Motor.TrekRequest)
GetHorizontalVelocity(Trailblazer.Navigation.Motor.TrekRequest)
MaxHoritzontalSpeedInDirection(FixedMathSharp.Vector3d,Trailblazer.Navigation.TrekRate)
GetFlightHorizontalSpeed(FixedMathSharp.Vector3d,Trailblazer.Navigation.TrekRate)
GetFlightSpeedMultiplier(Trailblazer.Navigation.TrekRate)
GetLiquidHorizontalSpeed(FixedMathSharp.Vector3d)
GetGroundHorizontalSpeed(FixedMathSharp.Vector3d,Trailblazer.Navigation.TrekRate)
GetAirborneControlMultiplier()
GetEllipticalHorizontalSpeed(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
GetGroundDirectionalMaxSpeed(FixedMathSharp.Vector3d,Trailblazer.Navigation.TrekRate)
ResolveLiquidVelocity(Trailblazer.Navigation.Motor.TrekRequest,FixedMathSharp.Vector3d)
ApplyPlatformTransferVelocity(FixedMathSharp.Vector3d)
ApplyGroundVelocityConstraints(FixedMathSharp.Vector3d)
TryApplyStationaryGroundFriction(FixedMathSharp.Vector3d)
ApplyDesiredVelocity(FixedMathSharp.Vector3d)
GetMaxAcceleration()
ApplyEnvironmentalForces()
ApplyJumpForce(Trailblazer.Navigation.Motor.TrekRequest)
CanApplyJumpForce(Trailblazer.Navigation.Motor.TrekRequest)
GetWaterBreachJumpForce()
GetGroundJumpForce()
GetClimbDetachJumpForce()
EnsureJumpDirectionInitialized()
CommitJumpForce(FixedMathSharp.Vector3d)
UpdateClimbState(Trailblazer.Navigation.Motor.TrekRequest)
IsCompatibleClimbAffordance(Trailblazer.Navigation.Motor.ClimbAffordanceSnapshot)
HasCompatibleClimbAxes(Trailblazer.Navigation.Motor.ClimbAffordanceSnapshot)
GetClimbContinuityTolerance()
StartClimb(Trailblazer.Navigation.Motor.ClimbAffordanceSnapshot)
ShouldStartMantle(Trailblazer.Navigation.Motor.TrekRequest,Trailblazer.Navigation.Motor.ClimbAffordanceSnapshot)
StartMantle(Trailblazer.Navigation.Motor.ClimbAffordanceSnapshot)
UpdateFlightState(Trailblazer.Navigation.Motor.TrekRequest)
UpdateSwimState(Trailblazer.Navigation.Motor.TrekRequest)
ResolveTraversalVelocityDelta()
ResolvePlatformTraversal(Trailblazer.Navigation.Motor.TrekRequest,FixedMathSharp.Vector3d&,FixedMathSharp.FixedQuaternion&)
GetVerticalJumpSpeed()
IsTooSteep(FixedMathSharp.Fixed64)
IsOnSlope(FixedMathSharp.Fixed64)
SetVelocity(FixedMathSharp.Vector3d)
SyncTraversalState(Trailblazer.Navigation.Motor.TrekCondition,System.Boolean)
AbortTraversalFrame()