< Summary

Line coverage
96%
Covered lines: 792
Uncovered lines: 28
Coverable lines: 820
Total lines: 1755
Line coverage: 96.5%
Branch coverage
87%
Covered branches: 636
Total branches: 724
Branch coverage: 87.8%
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_Context()100%210%
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: CreateNew(...)100%11100%
File 1: CreateUninitialized(...)100%22100%
File 1: .ctor(...)100%11100%
File 1: BindContext(...)100%11100%
File 1: RequireContext()50%22100%
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(...)83.33%66100%
File 1: SetLocomotionProfile(...)100%66100%
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(...)81.81%232289.47%
File 2: HandleGasExitTransition()85.71%141491.66%
File 2: FinalizePlatformMovement(...)100%66100%
File 2: CheckJumpStatus(...)87.5%88100%
File 2: HandlePlatformTransitions()95%2020100%
File 2: HandleSwimState(...)100%2020100%
File 2: HandleFlightState()92.85%141488.88%
File 2: HandleFallState(...)100%66100%
File 2: ShouldClearActiveFallState()100%44100%
File 2: ClearFallState()100%22100%
File 2: UpdateActiveFallState(...)93.75%1616100%
File 2: TryStartFall(...)85.71%1414100%
File 2: GetScaledFlightSpeedMultiplier(...)100%44100%
File 2: StopClimb(...)70%101085.71%
File 2: CanContinueActiveMantle()62.5%8890%
File 2: HasReachedMantleTarget(...)50%4483.33%
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(...)87.5%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(...)87.5%1616100%
File 4: GetClimbVelocity(...)70.83%242492.59%
File 4: GetMantleVelocity(...)50%9872.72%
File 4: GetControlledSurfaceVelocity(...)100%88100%
File 4: GetSlidingVelocity(...)50%2290.9%
File 4: GetHorizontalVelocity(...)75%44100%
File 4: MaxHoritzontalSpeedInDirection(...)100%66100%
File 4: GetFlightHorizontalSpeed(...)83.33%66100%
File 4: GetFlightSpeedMultiplier(...)100%66100%
File 4: GetLiquidHorizontalSpeed(...)90%1010100%
File 4: GetGroundHorizontalSpeed(...)100%22100%
File 4: GetAirborneControlMultiplier()87.5%88100%
File 4: GetEllipticalHorizontalSpeed(...)100%11100%
File 4: GetGroundDirectionalMaxSpeed(...)100%66100%
File 4: ResolveLiquidVelocity(...)93.75%1616100%
File 4: ApplyPlatformTransferVelocity(...)75%44100%
File 4: ApplyGroundVelocityConstraints(...)87.5%1616100%
File 4: TryApplyStationaryGroundFriction(...)91.66%1212100%
File 4: ApplyDesiredVelocity(...)100%1010100%
File 4: GetMaxAcceleration()77.77%3636100%
File 4: ApplyEnvironmentalForces()85.71%2828100%
File 4: ApplyJumpForce(...)100%66100%
File 4: CanApplyJumpForce(...)88.46%262692.3%
File 4: GetWaterBreachJumpForce()66.66%6685.71%
File 4: GetGroundJumpForce()75%4483.33%
File 4: GetClimbDetachJumpForce()50%101092.3%
File 4: EnsureJumpDirectionInitialized()83.33%66100%
File 4: CommitJumpForce(...)75%4488.88%
File 4: UpdateClimbState(...)100%3232100%
File 4: IsCompatibleClimbAffordance(...)80%111081.81%
File 4: HasCompatibleClimbAxes(...)85.71%151483.33%
File 4: GetClimbContinuityTolerance()50%2280%
File 4: StartClimb(...)66.66%121294.11%
File 4: ShouldStartMantle(...)66.66%121291.66%
File 4: StartMantle(...)75%4485.71%
File 4: UpdateFlightState(...)85%202094.73%
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
 1using Chronicler;
 2using FixedMathSharp;
 3using SwiftCollections;
 4using System;
 5
 6namespace Trailblazer.Navigation.Motor;
 7
 8/// <summary>
 9/// Controls character movement using an acceleration-based approach in a deterministic, lockstep simulation.
 10/// </summary>
 11/// <remarks>
 12/// This controller processes movement requests, applies forces such as gravity and platform adjustments,
 13/// and finalizes traversal states for consistent movement across frames.
 14/// </remarks>
 15[Serializable]
 16public partial class NavMotor : IRecordable
 17{
 18    #region Fields
 19
 20    /// <summary>
 21    /// Manages locomotion states and behaviors.
 22    /// </summary>
 37023    private LocomotionHandler _handler = new();
 24
 25    private bool _isInitialized;
 26
 27    private TrailblazerWorldContext? _context;
 28
 29    /// <inheritdoc cref="NavMotorEvents"/>
 30    [NonSerialized]
 37031    public NavMotorEvents Events = new();
 32
 33    /// <summary>
 34    /// This stores the current <see cref="Navigator._frameCondition"/> for the frame.
 35    /// </summary>
 36    /// <remarks>
 37    /// This is set on <see cref="OnInitialize"/>, can be explicitly synchronized before traversal
 38    /// through <see cref="SyncTraversalState(TrekCondition, bool)"/>, and is refreshed at the end of
 39    /// the frame in <see cref="FinalizeTraversal"/>.
 40    /// </remarks>
 41    public TransitState CurrentState { get; private set; } = null!;
 42
 43    /// <summary>
 44    /// Indicates whether a traversal has started for the current frame and still requires finalization or abort.
 45    /// </summary>
 46    public bool TraversalInProgress { get; private set; }
 47
 48    /// <summary>
 49    /// Gets a value indicating whether the object has been initialized.
 50    /// </summary>
 415151    public bool IsInitialized => _isInitialized;
 52
 53    /// <summary>
 54    /// Gets the world context this motor is bound to, when explicitly bound.
 55    /// </summary>
 056    public TrailblazerWorldContext? Context => _context;
 57
 58    /// <inheritdoc cref="PlatformLocomotion"/>
 1241659    public PlatformLocomotion PlatformModule => Handler.Platform;
 60
 61    /// <inheritdoc cref="JumpLocomotion"/>
 1755662    public JumpLocomotion? JumpModule => Handler.Jump;
 63
 64    /// <inheritdoc cref="SlideLocomotion"/>
 288465    public SlideLocomotion? SlideModule => Handler.Slide;
 66
 67    /// <inheritdoc cref="WaterLocomotion"/>
 845468    public WaterLocomotion? WaterModule => Handler.Water;
 69
 70    /// <inheritdoc cref="FlyLocomotion"/>
 2443971    public FlyLocomotion? FlyModule => Handler.Fly;
 72
 73    /// <inheritdoc cref="ClimbLocomotion"/>
 2364874    public ClimbLocomotion? ClimbModule => Handler.Climb;
 75
 76    #region Cache
 77
 78    /// <summary>
 79    /// Stores the slope angle for the current frame based on <see cref="TrekRequest.Direction"/>.
 80    /// </summary>
 81    public Fixed64 FrameSlopeAngle { get; private set; }
 82
 83    /// <summary>
 84    /// Accumulates forces applied during the traversal phase before they are committed.
 85    /// </summary>
 86    private Vector3d _forceOutput;
 87
 88    /// <summary>
 89    /// Records the simulation frame that opened the current traversal so stale traversal usage can be detected.
 90    /// </summary>
 37091    private int _pendingTraversalFrame = -1;
 92
 93    #endregion
 94
 95    #region State Status
 96
 97    /// <summary>
 98    /// Indicates if the traversal medium has changed since the last frame.
 99    /// </summary>
 8100    public bool StateChanged => CurrentState.Medium != CurrentState.PreviousMedium
 8101        && CurrentState.Medium != TraversalMedium.Unknown
 8102        && CurrentState.PreviousMedium != TraversalMedium.Unknown;
 103
 104    /// <summary>
 105    /// Determines if the object is currently on the ground.
 106    /// </summary>
 19568107    public bool IsOnSolid => CurrentState.Medium == TraversalMedium.Solid;
 108
 109    /// <summary>
 110    /// Determines if the object was on the ground in the previous frame.
 111    /// </summary>
 32112    public bool WasOnSolid => CurrentState.PreviousMedium == TraversalMedium.Solid;
 113
 114    /// <summary>
 115    /// Determines if the object is currently in the air.
 116    /// </summary>
 18794117    public bool IsInGas => CurrentState.Medium == TraversalMedium.Gas;
 118
 119    /// <summary>
 120    /// Determines if the object was in the air in the previous frame.
 121    /// </summary>
 2287122    public bool WasInGas => CurrentState.PreviousMedium == TraversalMedium.Gas;
 123
 124    /// <summary>
 125    /// Determines if the object is currently in water.
 126    /// </summary>
 17184127    public bool IsInLiquid => CurrentState.Medium == TraversalMedium.Liquid;
 128
 129    /// <summary>
 130    /// Determines if the object was in water in the previous frame.
 131    /// </summary>
 3910132    public bool WasInLiquid => CurrentState.PreviousMedium == TraversalMedium.Liquid;
 133
 134    /// <summary>
 135    /// Determines if the object is currently under active flight control.
 136    /// </summary>
 16022137    public bool IsFlying => FlyModule?.IsFlying == true;
 138
 139    /// <summary>
 140    /// Determines if the object is currently in a jump state.
 141    /// </summary>
 6922142    public bool IsJumping => JumpModule?.IsJumping == true;
 143
 144    /// <summary>
 145    /// Gets a value indicating whether the object is currently falling.
 146    /// </summary>
 6629147    public bool IsFalling => Handler.Fall.IsFalling;
 148
 149    /// <summary>
 150    /// Determines if the object is currently attached to a climb affordance.
 151    /// </summary>
 12702152    public bool IsClimbing => ClimbModule?.IsClimbing == true;
 153
 154    /// <summary>
 155    /// Checks if the object is in a state where it is airborne but not actively jumping, flying, or falling.
 156    /// </summary>
 6002157    public bool InLimbo => !IsOnSolid && !IsInGas && !IsInLiquid
 6002158        || IsInGas && !IsFlying && !IsJumping && !IsFalling && !IsClimbing;
 159
 160    #endregion
 161
 162    #endregion
 163
 164    #region Construct
 165
 166    /// <summary>
 167    /// Creates a new context-bound <see cref="NavMotor"/> instance.
 168    /// </summary>
 169    public static NavMotor CreateNew(
 170        TrailblazerWorldContext context,
 171        TrekCondition initialCondition,
 172        LocomotionProfile? profile = null) =>
 364173        new(context, initialCondition, profile);
 174
 175    /// <summary>
 176    /// Creates a new context-bound <see cref="NavMotor"/> instance without initializing it.
 177    /// </summary>
 178    public static NavMotor CreateUninitialized(TrailblazerWorldContext context, LocomotionHandler? handler = null)
 179    {
 6180        var motor = new NavMotor();
 6181        if (handler != null)
 1182            motor._handler = handler;
 6183        motor.BindContext(context);
 6184        return motor;
 185    }
 186
 187    // Parameterless constructor for serialization purposes. Not intended for direct use.
 12188    private NavMotor() { }
 189
 190    /// <summary>
 191    /// Initializes a new context-bound <see cref="NavMotor"/> instance.
 192    /// </summary>
 364193    public NavMotor(TrailblazerWorldContext context, TrekCondition condition, LocomotionProfile? profile = null)
 194    {
 364195        BindContext(context);
 364196        OnInitialize(condition, profile);
 364197    }
 198
 199    /// <summary>
 200    /// Binds this motor to a world context.
 201    /// </summary>
 202    public void BindContext(TrailblazerWorldContext context)
 203    {
 412204        Trailblazer.Pathing.PathRequestContextResolver.ThrowIfUnusable(context);
 412205        _context = context;
 412206        _handler.BindContext(context);
 412207    }
 208
 209    private TrailblazerWorldContext RequireContext() =>
 12032210        _context ?? throw new InvalidOperationException("NavMotor requires an explicit TrailblazerWorldContext.");
 211
 4136212    private int FrameCount => RequireContext().FrameCount;
 213
 5852214    private Fixed64 DeltaTime => RequireContext().DeltaTime;
 215
 2042216    private Fixed64 InvDeltaTime => RequireContext().InvDeltaTime;
 217
 2218    private Fixed64 TotalTime => RequireContext().TotalTime;
 219
 220    /// <summary>
 221    /// Prepares the controller by linking it to the given object and setting initial state values.
 222    /// </summary>
 223    /// <param name="condition">The initial traversal condition of the object</param>
 224    /// <param name="profile">Optional locomotion composition profile. When omitted, the default profile is used.</param
 225    public void OnInitialize(TrekCondition condition, LocomotionProfile? profile = null)
 226    {
 364227        _handler = profile != null ? new LocomotionHandler(profile) : new LocomotionHandler();
 364228        if (_context != null)
 364229            _handler.BindContext(_context);
 364230        CurrentState = new TransitState(condition);
 364231        if (CurrentState.GroundState.HasValue)
 256232            PlatformModule.HandlePlatformChange(CurrentState.GroundState); // set the initial platform
 233
 364234        _isInitialized = true;
 364235    }
 236
 237    /// <summary>
 238    /// Replaces the currently installed locomotion profile.
 239    /// </summary>
 240    public void SetLocomotionProfile(LocomotionProfile profile)
 241    {
 9242        SwiftThrowHelper.ThrowIfNull(profile, nameof(profile));
 243
 8244        if (TraversalInProgress)
 1245            throw new InvalidOperationException("Cannot change locomotion composition while a traversal frame is in prog
 246
 7247        Handler.ApplyProfile(profile);
 248
 7249        if (CurrentState?.GroundState.HasValue == true)
 4250            PlatformModule.HandlePlatformChange(CurrentState.GroundState);
 7251    }
 252
 253    /// <summary>
 254    /// Reconfigures the currently installed locomotion profile from the active handler state.
 255    /// </summary>
 256    public void ConfigureLocomotions(Action<LocomotionProfileBuilder> configure)
 257    {
 1258        SwiftThrowHelper.ThrowIfNull(configure, nameof(configure));
 259
 1260        var builder = LocomotionProfile.CreateBuilder(Handler);
 1261        configure(builder);
 1262        SetLocomotionProfile(builder.Build());
 1263    }
 264
 265    #endregion
 266
 267    #region Properties
 268
 269    /// <inheritdoc cref="_handler"/>
 124396270    public LocomotionHandler Handler => _handler;
 271
 272    #endregion
 273
 274}

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

#LineLine coverage
 1using FixedMathSharp;
 2using System;
 3using System.Runtime.CompilerServices;
 4
 5namespace Trailblazer.Navigation.Motor;
 6
 7public partial class NavMotor
 8{
 9    #region Phase 2 - Finalize
 10
 11    /// <summary>
 12    /// Finalizes traversal state updates and prepares the object for the next simulation frame.
 13    /// </summary>
 14    /// <remarks>
 15    /// This method updates the frame velocity, applies necessary adjustments based on traversal state changes,
 16    /// and processes platform movement or environmental effects as needed.
 17    /// </remarks>
 18    public void FinalizeTraversal(
 19        Vector3d newPosition,
 20        Vector3d lastPosition,
 21        FixedQuaternion newRotation,
 22        TrekCondition conditonRefresh,
 23        Vector3d? newFootPosition = null)
 24    {
 204825        if (!ShouldFinalizeTraversal())
 526            return;
 27
 204328        ValidatePendingTraversalFrame();
 204229        RefreshTraversalState(newPosition, lastPosition, conditonRefresh);
 204230        HandleTraversalTransitions();
 204231        HandleClimbState(newPosition);
 204232        HandleSwimState(newPosition);
 204233        HandleFlightState();
 204234        HandleFallState(newPosition);
 204235        FinalizePlatformMovement(newFootPosition ?? newPosition, newRotation);
 204236        AbortTraversalFrame();
 204237    }
 38
 39    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 40    private bool ShouldFinalizeTraversal()
 41    {
 204842        return IsInitialized && TraversalInProgress;
 43    }
 44
 45    private void ValidatePendingTraversalFrame()
 46    {
 204347        if (FrameCount == _pendingTraversalFrame)
 204248            return;
 49
 150        throw new InvalidOperationException(
 151            $"NavMotor traversal opened on frame {_pendingTraversalFrame} cannot be finalized on frame {FrameCount}. Cal
 52    }
 53
 54    private void RefreshTraversalState(
 55        Vector3d newPosition,
 56        Vector3d lastPosition,
 57        TrekCondition conditonRefresh)
 58    {
 204259        Handler.Move.FrameVelocity = (newPosition - lastPosition) * InvDeltaTime;
 60
 204261        CurrentState.Update(conditonRefresh, CurrentState.ToTrekCondition());
 62
 204263        PlatformModule.HandlePlatformChange(CurrentState.GroundState);
 204264        HandlePlatformTransitions();
 65
 66        // Ceiling check runs last so platform inertia inherited this frame cannot bypass the clamp.
 204267        CheckJumpStatus(newPosition);
 204268    }
 69
 70    private void HandleTraversalTransitions()
 71    {
 204272        if (WasInGas && !IsInGas)
 1973            HandleGasExitTransition();
 74
 204275        if (WaterModule?.IsEnabled == true && !IsInLiquid && WasInLiquid)
 376            Handler.ClearTransientState<WaterLocomotion>();
 204277    }
 78
 79    private void HandleClimbState(Vector3d position)
 80    {
 204281        ClimbLocomotion? climbModule = ClimbModule;
 204282        if (climbModule?.IsEnabled != true)
 383            return;
 84
 203985        if (climbModule.IsMantling)
 86        {
 1287            if (IsInLiquid || CurrentState.Medium == TraversalMedium.Unknown)
 88            {
 189                StopClimb(wasForced: true);
 190                return;
 91            }
 92
 1193            if (IsOnSolid)
 94            {
 295                CompleteMantle();
 296                return;
 97            }
 98
 999            if (!CanContinueActiveMantle())
 100            {
 1101                StopClimb(wasForced: true);
 1102                return;
 103            }
 104
 8105            if (HasReachedMantleTarget(position))
 0106                CompleteMantle();
 107
 8108            return;
 109        }
 110
 2027111        if ((IsInLiquid || CurrentState.Medium == TraversalMedium.Unknown) && climbModule.IsClimbing)
 0112            StopClimb(wasForced: false);
 2027113    }
 114
 115    private void HandleGasExitTransition()
 116    {
 20117        if (IsJumping)
 118        {
 7119            JumpLocomotion? jumpModule = JumpModule;
 7120            if (jumpModule == null)
 0121                return;
 122
 123            // Reset cooldown on landing.
 7124            jumpModule.ResetJumpCounter();
 125
 7126            if (IsInLiquid)
 2127                Events.OnStopWaterBreach?.Invoke();
 128            else
 5129                Events.OnStopJump?.Invoke();
 130
 2131            return;
 132        }
 133
 13134        if (!IsInLiquid)
 11135            Events.OnLandedFall?.Invoke();
 3136    }
 137
 138    private void FinalizePlatformMovement(Vector3d position, FixedQuaternion rotation)
 139    {
 2042140        PlatformLocomotion platformModule = PlatformModule;
 2042141        if (!platformModule.IsActive || (!IsOnSolid && !platformModule.IsLockedToPlatform))
 1901142            return;
 143
 141144        platformModule.HandlePlatformMovement(position, rotation);
 141145    }
 146
 147    private void CheckJumpStatus(Vector3d position)
 148    {
 149        // Make sure we aren't hitting the ceiling
 2043150        if (Handler.Move.FrameVelocity.y <= Fixed64.Zero || CurrentState.CeilingLevel == Fixed64.MAX_VALUE)
 2040151            return;
 152
 4153        if (position.y <= CurrentState.CeilingLevel) return;
 154
 2155        Handler.Move.FrameVelocity = new(
 2156            Handler.Move.FrameVelocity.x,
 2157            Fixed64.Zero,
 2158            Handler.Move.FrameVelocity.z);
 159
 2160        if (JumpModule != null)
 161        {
 2162            JumpModule.IsJumping = false;
 2163            JumpModule.IsHoldingJump = false;
 164        }
 2165    }
 166
 167    private void HandlePlatformTransitions()
 168    {
 169        // Don't process platform state when in water
 2044170        PlatformLocomotion platformModule = PlatformModule;
 2044171        if (!platformModule.IsEnabled || IsInLiquid)
 174172            return;
 173
 1870174        bool isReleasing = false;
 1870175        if (platformModule.IsHoldingPlatform)
 2176            isReleasing = platformModule.TickHoldOnPlatform();
 177
 1870178        if (isReleasing)
 179        {
 1180            Handler.Move.FrameVelocity -= platformModule.PlatformVelocity;
 1181            return;
 182        }
 183
 3709184        if (!platformModule.InteriaApplied) return;
 185
 29186        if (WasOnSolid && IsInGas)
 187        {
 188            // Scout just left the ground, so it inherits platform inertia into its new velocity.
 4189            platformModule.FramePlatformVelocity = platformModule.PlatformVelocity;
 4190            Handler.Move.FrameVelocity += platformModule.PlatformVelocity;
 4191            return;
 192        }
 193
 25194        if (WasInGas && IsOnSolid)
 195        {
 3196            if (platformModule.IsNewPlatform)
 197                // If object landed on a new platform, we have to wait for two frames
 198                // before we know the new velocity of the platform under the object
 1199                platformModule.SetHoldPlatform(platformModule.ActivePlatform);
 200            else
 201                // If the platform isn’t new, we assume the object landed back on the same platform
 202                // and subtract platform velocity to prevent doubling the effect.
 2203                Handler.Move.FrameVelocity -= platformModule.PlatformVelocity;
 204        }
 24205    }
 206
 207    private void HandleSwimState(Vector3d position)
 208    {
 2043209        if (!IsInLiquid)
 210        {
 1868211            if (WaterModule?.IsEnabled == true && WasInLiquid)
 3212                Handler.ClearTransientState<WaterLocomotion>();
 213
 1868214            return;
 215        }
 216
 175217        bool hadSwimIntent = WaterModule?.RequestedSwimThisTraversal == true;
 218        // Clear the transient state when entering water for the first time
 175219        if (!WasInLiquid)
 5220            Handler.ClearAllTransientState();
 221
 175222        if (WaterModule?.IsEnabled == true)
 223        {
 174224            WaterModule.IsSwimming = WaterModule.CanSwim && hadSwimIntent;
 174225            WaterModule.IsDiving = position.y < CurrentState.SurfaceLevel;
 226
 174227            WaterModule.UpdateDiveTime();
 228
 174229            if (WaterModule.IsDrowning)
 6230                Events.OnDrowning?.Invoke(WaterModule.UnderwaterTimer);
 231        }
 170232    }
 233
 234    private void HandleFlightState()
 235    {
 2043236        if (FlyModule?.IsEnabled != true)
 3237            return;
 238
 2040239        if (!IsInGas || IsClimbing)
 240        {
 370241            if (FlyModule.IsFlying)
 0242                Handler.ClearTransientState<FlyLocomotion>();
 243
 370244            return;
 245        }
 246
 1670247        if (FlyModule.IsFlying && IsFalling)
 1248            Handler.ClearTransientState<FallLocomotion>();
 1670249    }
 250
 251    private void HandleFallState(Vector3d position)
 252    {
 2045253        if (!Handler.Fall.IsEnabled) return;
 254
 2043255        if (ShouldClearActiveFallState())
 256        {
 210257            ClearFallState();
 210258            return;
 259        }
 260
 1833261        if (IsFalling)
 262        {
 1501263            UpdateActiveFallState(position);
 1501264            return;
 265        }
 266
 332267        TryStartFall(position);
 332268    }
 269
 270    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 2043271    private bool ShouldClearActiveFallState() => IsInLiquid || IsFlying || IsClimbing;
 272
 273    private void ClearFallState()
 274    {
 212275        if (IsFalling)
 1276            Handler.ClearTransientState<FallLocomotion>();
 212277    }
 278
 279    private void UpdateActiveFallState(Vector3d position)
 280    {
 1502281        if (position.y > Handler.Fall.FallStart)
 13282            Handler.Fall.FallStart = position.y;
 283
 1502284        if (!IsInGas && !IsTooSteep(FrameSlopeAngle))
 285        {
 15286            Handler.Fall.IsFalling = false;
 15287            Handler.Fall.FallEnd = position.y;
 288
 15289            if (Handler.Fall.FallHeight > Fixed64.Zero)
 12290                Events.OnStopFall?.Invoke(Handler.Fall.FallHeight);
 291
 15292            Handler.ClearTransientState<FallLocomotion>();
 15293            return;
 294        }
 295
 1487296        Fixed64 currentFallHeight = (Handler.Fall.FallStart - position.y).Abs();
 1487297        if (currentFallHeight > Handler.Fall.MaxFallHeight)
 1220298            Events?.OnMaxFallHeightReached?.Invoke();
 299299    }
 300
 301    private void TryStartFall(Vector3d position)
 302    {
 335303        bool isSlidingTooSteep = IsTooSteep(FrameSlopeAngle);
 335304        if (!(IsInGas || isSlidingTooSteep) || _forceOutput.y >= Fixed64.Zero)
 302305            return;
 306
 33307        Handler.Fall.IsFalling = true;
 33308        Handler.Fall.FallStart = position.y;
 309
 33310        if (JumpModule != null && JumpModule.JumpCount > 0 && !JumpModule.IsCoolingDown)
 7311            JumpModule.StartCooldown();
 312
 33313        Events?.OnStartFall?.Invoke();
 2314    }
 315
 6316    private Fixed64 GetScaledFlightSpeedMultiplier(TrekRate rate, Fixed64 maxFastSpeed) => rate switch
 6317    {
 2318        TrekRate.Slow => Handler.Move.MaxSlowSpeed / maxFastSpeed,
 2319        TrekRate.Moderate => Handler.Move.MaxModerateSpeed / maxFastSpeed,
 6320        // Fast never reaches this helper because GetFlightSpeedMultiplier(...) short-circuits it to one.
 2321        _ => Fixed64.Zero
 6322    };
 323
 324    private void StopClimb(bool wasForced)
 325    {
 12326        if (ClimbModule?.IsClimbing != true)
 0327            return;
 328
 12329        Handler.ClearTransientState<ClimbLocomotion>();
 330
 12331        if (wasForced)
 5332            Events.OnClimbSlip?.Invoke();
 333
 12334        Events.OnStopClimb?.Invoke();
 2335    }
 336
 337    private bool CanContinueActiveMantle()
 338    {
 9339        ClimbLocomotion? climbModule = ClimbModule;
 9340        if (climbModule == null || !climbModule.ValidateActiveMantleWithHost)
 5341            return true;
 342
 4343        if (climbModule.ClimbResolver is not IActiveMantleValidator validator)
 0344            return true;
 345
 4346        return validator.TryValidateActiveMantle(
 4347                CurrentState,
 4348                climbModule.CreateActiveMantleState(),
 4349                out MantleValidationSnapshot snapshot)
 4350            && snapshot.CanContinueMantle;
 351    }
 352
 353    private bool HasReachedMantleTarget(Vector3d position)
 354    {
 8355        ClimbLocomotion? climbModule = ClimbModule;
 8356        if (climbModule?.MantleTargetPosition.HasValue != true)
 0357            return false;
 358
 8359        Fixed64 tolerance = climbModule.ClimbStartTolerance;
 8360        Vector3d mantleTargetPosition = climbModule.MantleTargetPosition.GetValueOrDefault();
 8361        return (mantleTargetPosition - position).SqrMagnitude <= tolerance * tolerance;
 362    }
 363
 364    private void CompleteMantle()
 365    {
 2366        StopClimb(wasForced: false);
 2367    }
 368
 369    #endregion
 370}

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

#LineLine coverage
 1using Chronicler;
 2
 3namespace Trailblazer.Navigation.Motor;
 4
 5public partial class NavMotor
 6{
 7    #region Serialization
 8
 9    /// <inheritdoc />
 10    public void RecordData(IChronicler chronicler)
 11    {
 10612        TrekCondition currentCondition = CurrentState?.ToTrekCondition() ?? new TrekCondition();
 10613        TrekCondition? previousCondition = CurrentState?.PreviousState;
 14
 10615        RecordDeep.Look(chronicler, ref _handler, "Handler");
 10616        RecordValues.Look(chronicler, ref currentCondition, "CurrentCondition");
 10617        RecordValues.Look(chronicler, ref previousCondition, "PreviousCondition");
 10618        RecordValues.Look(chronicler, ref _isInitialized, "IsInitialized", false);
 19
 10620        if (chronicler.Mode == SerializationMode.Loading)
 21        {
 5322            CurrentState ??= new(currentCondition, previousCondition);
 5323            CurrentState.Update(currentCondition, previousCondition);
 5324            AbortTraversalFrame();
 25        }
 10626    }
 27
 28    #endregion
 29}

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

#LineLine coverage
 1using FixedMathSharp;
 2using System;
 3using System.Runtime.CompilerServices;
 4
 5namespace Trailblazer.Navigation.Motor;
 6
 7public partial class NavMotor
 8{
 9    #region Phase 1 - Request Traversal
 10
 11    /// <summary>
 12    /// Processes a movement request and applies necessary forces.
 13    /// </summary>
 14    /// <remarks>
 15    /// This method opens a traversal phase for the current frame so duplicate force accumulation is rejected
 16    /// until the host calls <see cref="FinalizeTraversal"/> or <see cref="AbortTraversalFrame"/>.
 17    /// Movement forces such as gravity, jump, and platform adjustments are applied.
 18    /// </remarks>
 19    /// <param name="request">The movement request containing desired movement parameters</param>
 20    /// <param name="velocityDelta">The resulting velocity change to apply to the object</param>
 21    /// <param name="positionDelta">The resulting position change from platform movement to apply to the object</param>
 22    /// <param name="rotationDelta">The resulting rotation change from platform movement to apply to the object</param>
 23    public bool TryTraversal(
 24        TrekRequest request,
 25        out Vector3d velocityDelta,
 26        out Vector3d positionDelta,
 27        out FixedQuaternion rotationDelta)
 28    {
 209129        ResetTraversalOutputs(out velocityDelta, out positionDelta, out rotationDelta);
 209130        if (!TryBeginTraversalFrame())
 231            return false;
 32
 208733        PrepareTraversalState(request);
 208734        ResolveTraversalForces(request);
 35
 208736        velocityDelta = ResolveTraversalVelocityDelta();
 208737        ResolvePlatformTraversal(request, ref positionDelta, ref rotationDelta);
 208738        return true;
 39    }
 40
 41    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 42    private static void ResetTraversalOutputs(
 43        out Vector3d velocityDelta,
 44        out Vector3d positionDelta,
 45        out FixedQuaternion rotationDelta)
 46    {
 209147        velocityDelta = Vector3d.Zero;
 209148        positionDelta = Vector3d.Zero;
 209149        rotationDelta = FixedQuaternion.Identity;
 209150    }
 51
 52    private bool TryBeginTraversalFrame()
 53    {
 209154        if (!IsInitialized)
 155            return false;
 56
 209057        if (TraversalInProgress)
 58        {
 359            if (FrameCount != _pendingTraversalFrame)
 60            {
 261                throw new InvalidOperationException(
 262                    $"NavMotor traversal from frame {_pendingTraversalFrame} was never finalized or aborted before frame
 63            }
 64
 165            return false;
 66        }
 67
 208768        TraversalInProgress = true;
 208769        _pendingTraversalFrame = FrameCount;
 208770        return true;
 71    }
 72
 73    private void PrepareTraversalState(TrekRequest request)
 74    {
 208775        TrailblazerLogger.DebugChannel.Info(
 208776            $"NavMotor State: Grounded={IsOnSolid}, InAir={IsInGas}, InWater={IsInLiquid}, Flying={IsFlying}, InLimbo={I
 77
 78        // Calculate the slope angle for the current frame based on the movement direction and surface normal.
 208779        FrameSlopeAngle = CurrentState.GetSignedSlopeAngle(request.Direction);
 80
 81        // Store the current velocity for manipulation.
 208782        _forceOutput = Handler.Move.FrameVelocity;
 83
 84        // Update platform velocity prior to applying jump force.
 208785        PlatformModule.UpdatePlatformVelocity();
 86
 208787        if (InLimbo)
 3188            Handler.IsInControl = false;
 89
 208790        if (JumpModule?.IsCoolingDown == true)
 10391            JumpModule.UpdateCooldown();
 92
 208793        UpdateClimbState(request);
 208794        UpdateFlightState(request);
 208795        UpdateSwimState(request);
 208796    }
 97
 98    #region Traversal Force Resolution
 99
 100    private void ResolveTraversalForces(TrekRequest request)
 101    {
 2087102        ComputeMovementForces(request);
 103
 104        // Reset this before applying gravity.
 2087105        if (JumpModule != null && (!request.IsRequestingJump || !JumpModule.CanJump))
 2044106            JumpModule.IsHoldingJump = false;
 107
 2087108        ApplyEnvironmentalForces();
 2087109        ApplyJumpForce(request);
 2087110    }
 111
 112    /// <summary>
 113    /// Computes the movement forces based on the traversal state and applies them to the object.
 114    /// </summary>
 115    /// <remarks>
 116    /// This method determines whether the object is in control, calculates velocity adjustments,
 117    /// and applies constraints such as slope resistance and airborne drag.
 118    /// </remarks>
 119    private void ComputeMovementForces(TrekRequest frameRequest)
 120    {
 2087121        UpdateControlState();
 122
 2087123        if (InLimbo)
 14124            return;
 125
 2073126        ResetDownwardMomentumIfNeeded();
 127
 2073128        Vector3d desiredVelocity = GetDesiredVelocity(frameRequest);
 129
 2073130        if (TryApplyStationaryGroundFriction(frameRequest.Direction))
 5131            return;
 132
 2068133        ApplyDesiredVelocity(desiredVelocity);
 2068134    }
 135
 136    private void UpdateControlState()
 137    {
 2087138        if (IsClimbing)
 139        {
 36140            SetSlidingState(false);
 36141            Handler.IsInControl = true;
 36142            return;
 143        }
 144
 2051145        if (IsOnSolid)
 146        {
 220147            if (IsFlying)
 148            {
 5149                SetSlidingState(false);
 5150                Handler.IsInControl = true;
 151            }
 152            else
 153            {
 215154                bool isTooSteep = IsTooSteep(FrameSlopeAngle);
 215155                SetSlidingState(SlideModule?.IsEnabled == true && isTooSteep);
 215156                Handler.IsInControl = !isTooSteep;
 157            }
 158
 215159            return;
 160        }
 161
 1831162        Handler.IsInControl = IsFlying || !InLimbo;
 1831163    }
 164
 165    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 166    private void SetSlidingState(bool isSliding)
 167    {
 256168        if (SlideModule != null)
 254169            SlideModule.IsSliding = isSliding;
 256170    }
 171
 172    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 173    private void ResetDownwardMomentumIfNeeded()
 174    {
 2073175        if (IsClimbing)
 176        {
 36177            _forceOutput = Vector3d.Zero;
 36178            return;
 179        }
 180
 2037181        if ((!IsOnSolid || WasInGas) && !IsFlying)
 1815182            _forceOutput.y = Fixed64.Zero;
 2037183    }
 184
 185    /// <summary>
 186    /// Determines the desired velocity based on input direction, movement constraints, and traversal state.
 187    /// </summary>
 188    /// <returns>The computed velocity vector that the object should move toward.</returns>
 189    private Vector3d GetDesiredVelocity(TrekRequest frameRequest)
 190    {
 2073191        if (IsFlying)
 10192            return GetFlightVelocity(frameRequest);
 193
 2063194        if (ClimbModule?.IsMantling == true)
 12195            return GetMantleVelocity(frameRequest.Origin);
 196
 2051197        if (IsClimbing)
 24198            return GetClimbVelocity(frameRequest);
 199
 2027200        Vector3d result = GetControlledSurfaceVelocity(frameRequest);
 2027201        if (IsInLiquid)
 174202            return ResolveLiquidVelocity(frameRequest, result);
 203
 1853204        result = ApplyPlatformTransferVelocity(result);
 1853205        return ApplyGroundVelocityConstraints(result);
 206    }
 207
 208    /// <summary>
 209    /// Computes the desired world-space velocity while controlled flight is active.
 210    /// </summary>
 211    private Vector3d GetFlightVelocity(TrekRequest frameRequest)
 212    {
 14213        if (FlyModule?.IsEnabled != true || !FlyModule.CanFly || frameRequest.Rate == TrekRate.Stationary)
 3214            return Vector3d.Zero;
 215
 11216        Fixed64 speedMultiplier = GetFlightSpeedMultiplier(frameRequest.Rate);
 11217        if (speedMultiplier <= Fixed64.Zero)
 1218            return Vector3d.Zero;
 219
 10220        Fixed3x3 transposedMatrix = frameRequest.Rotation.ToMatrix3x3();
 10221        Vector3d desiredLocalDirection = Fixed3x3.InverseTransformDirection(transposedMatrix, frameRequest.Direction);
 10222        Vector3d desiredLocalVelocity = Vector3d.Zero;
 223
 10224        Vector3d horizontalInput = new(desiredLocalDirection.x, Fixed64.Zero, desiredLocalDirection.z);
 10225        Fixed64 horizontalMagnitude = FixedMath.Clamp01(horizontalInput.Magnitude);
 10226        if (horizontalMagnitude > Fixed64.Zero)
 3227            desiredLocalVelocity += horizontalInput.Normal * (FlyModule.MaxFlySpeed * speedMultiplier * horizontalMagnit
 228
 10229        Fixed64 verticalInput = FixedMath.Clamp(desiredLocalDirection.y, -Fixed64.One, Fixed64.One);
 10230        if (verticalInput > Fixed64.Zero)
 7231            desiredLocalVelocity.y = verticalInput * FlyModule.MaxAscendSpeed * speedMultiplier;
 3232        else if (verticalInput < Fixed64.Zero)
 2233            desiredLocalVelocity.y = verticalInput.Abs() * -FlyModule.MaxDescendSpeed * speedMultiplier;
 234
 10235        return Fixed3x3.TransformDirection(transposedMatrix, desiredLocalVelocity);
 236    }
 237
 238    private Vector3d GetClimbVelocity(TrekRequest frameRequest)
 239    {
 24240        if (ClimbModule?.IsEnabled != true
 24241            || !ClimbModule.CanClimb
 24242            || frameRequest.Rate == TrekRate.Stationary)
 243        {
 1244            return Vector3d.Zero;
 245        }
 246
 23247        Vector3d upAxis = ClimbModule.AttachedUpDirection != Vector3d.Zero
 23248            ? ClimbModule.AttachedUpDirection.Normal
 23249            : Vector3d.Up;
 23250        Vector3d outwardNormal = ClimbModule.AttachedSurfaceNormal != Vector3d.Zero
 23251            ? ClimbModule.AttachedSurfaceNormal.Normal
 23252            : Vector3d.Backward;
 23253        Vector3d lateralAxis = Vector3d.Cross(upAxis, outwardNormal);
 23254        if (lateralAxis == Vector3d.Zero)
 0255            lateralAxis = Vector3d.Cross(Vector3d.Up, outwardNormal);
 23256        if (lateralAxis == Vector3d.Zero)
 0257            lateralAxis = Vector3d.Right;
 23258        lateralAxis = lateralAxis.Normal;
 259
 23260        Fixed64 verticalAmount = Vector3d.Dot(frameRequest.Direction, upAxis);
 23261        if (!ClimbModule.ActiveAllowDescent && verticalAmount < Fixed64.Zero)
 1262            verticalAmount = Fixed64.Zero;
 263
 23264        Fixed64 lateralAmount = ClimbModule.ActiveAllowLateralTraverse
 23265            ? Vector3d.Dot(frameRequest.Direction, lateralAxis)
 23266            : Fixed64.Zero;
 267
 23268        Vector3d climbDirection = (upAxis * verticalAmount) + (lateralAxis * lateralAmount);
 23269        Fixed64 inputMagnitude = FixedMath.Clamp01(climbDirection.Magnitude);
 23270        if (inputMagnitude <= Fixed64.Zero)
 4271            return Vector3d.Zero;
 272
 19273        return climbDirection.Normal * (ClimbModule.MaxClimbSpeed * inputMagnitude);
 274    }
 275
 276    private Vector3d GetMantleVelocity(Vector3d origin)
 277    {
 12278        ClimbLocomotion? climbModule = ClimbModule;
 12279        if (climbModule?.MantleTargetPosition.HasValue != true)
 0280            return Vector3d.Zero;
 281
 12282        Vector3d mantleTargetPosition = climbModule.MantleTargetPosition.GetValueOrDefault();
 12283        Vector3d toTarget = mantleTargetPosition - origin;
 12284        if (toTarget == Vector3d.Zero)
 0285            return Vector3d.Zero;
 286
 12287        Fixed64 distance = toTarget.Magnitude;
 12288        if (distance <= climbModule.ClimbStartTolerance)
 0289            return Vector3d.Zero;
 290
 12291        return toTarget.Normal * climbModule.MaxClimbSpeed;
 292    }
 293
 294    private Vector3d GetControlledSurfaceVelocity(TrekRequest frameRequest)
 295    {
 2027296        if (SlideModule?.IsSliding == true)
 27297            return GetSlidingVelocity(frameRequest);
 298
 2000299        return Handler.IsInControl && frameRequest.Rate != TrekRate.Stationary
 2000300            ? GetHorizontalVelocity(frameRequest)
 2000301            : Vector3d.Zero;
 302    }
 303
 304    private Vector3d GetSlidingVelocity(TrekRequest frameRequest)
 305    {
 27306        SlideLocomotion? slideModule = SlideModule;
 27307        if (slideModule == null)
 0308            return Vector3d.Zero;
 309
 27310        Vector3d slideDirection = new Vector3d(
 27311            CurrentState.SurfaceNormal.x,
 27312            Fixed64.Zero,
 27313            CurrentState.SurfaceNormal.z).Normal;
 27314        Vector3d projectedMoveDir = Vector3d.Project(frameRequest.Direction, slideDirection);
 27315        Vector3d speedContribution = projectedMoveDir * slideModule.SpeedControl;
 27316        Vector3d sidewaysContribution = (frameRequest.Direction - projectedMoveDir) * slideModule.SidewaysControl;
 27317        return slideDirection + ((speedContribution + sidewaysContribution) * slideModule.SlidingSpeed);
 318    }
 319
 320    private Vector3d GetHorizontalVelocity(TrekRequest frameRequest)
 321    {
 138322        Fixed3x3 transposedMatrix = frameRequest.Rotation.ToMatrix3x3();
 138323        Vector3d desiredLocalDirection = Fixed3x3.InverseTransformDirection(transposedMatrix, frameRequest.Direction);
 138324        Fixed64 speed = MaxHoritzontalSpeedInDirection(desiredLocalDirection, frameRequest.Rate);
 325
 138326        speed *= Handler.Move.MoveSpeedMultiplier;
 327
 328        // Modify max speed on slopes based on slope speed multiplier curve
 138329        if (Handler.Move.ModifySpeedOnSlope && IsOnSlope(FrameSlopeAngle))
 16330            speed *= Handler.Move.SlopeSpeedMultiplier.Evaluate(FrameSlopeAngle);
 331
 138332        return Fixed3x3.TransformDirection(transposedMatrix, desiredLocalDirection * speed);
 333    }
 334
 335    /// <summary>
 336    /// Calculates the maximum allowable speed in a given movement direction.
 337    /// </summary>
 338    /// <param name="desiredMovementDirection">The movement direction to evaluate.</param>
 339    /// <param name="rate">The rate that dictates the maximum speed allowed.</param>
 340    /// <returns>The maximum speed possible in the specified direction.</returns>
 341    public Fixed64 MaxHoritzontalSpeedInDirection(Vector3d desiredMovementDirection, TrekRate rate)
 342    {
 157343        if (desiredMovementDirection == Vector3d.Zero)
 18344            return Fixed64.Zero;
 345
 139346        if (IsFlying)
 9347            return GetFlightHorizontalSpeed(desiredMovementDirection, rate);
 348
 130349        if (IsInLiquid)
 19350            return GetLiquidHorizontalSpeed(desiredMovementDirection);
 351
 111352        return GetGroundHorizontalSpeed(desiredMovementDirection, rate);
 353    }
 354
 355    private Fixed64 GetFlightHorizontalSpeed(Vector3d desiredMovementDirection, TrekRate rate)
 356    {
 12357        if (FlyModule?.IsEnabled != true || !FlyModule.CanFly)
 3358            return Fixed64.Zero;
 359
 9360        Fixed64 horizontalMagnitude = FixedMath.Clamp01(new Vector3d(
 9361            desiredMovementDirection.x,
 9362            Fixed64.Zero,
 9363            desiredMovementDirection.z).Magnitude);
 9364        return horizontalMagnitude * FlyModule.MaxFlySpeed * GetFlightSpeedMultiplier(rate);
 365    }
 366
 367    private Fixed64 GetFlightSpeedMultiplier(TrekRate rate)
 368    {
 20369        if (rate == TrekRate.Stationary)
 2370            return Fixed64.Zero;
 371
 18372        Fixed64 maxFastSpeed = Handler.Move.MaxFastSpeed;
 18373        if (rate == TrekRate.Fast || maxFastSpeed <= Fixed64.Zero)
 12374            return Fixed64.One;
 375
 6376        return FixedMath.Clamp(GetScaledFlightSpeedMultiplier(rate, maxFastSpeed), Fixed64.Zero, Fixed64.One);
 377    }
 378
 379    private Fixed64 GetLiquidHorizontalSpeed(Vector3d desiredMovementDirection)
 380    {
 19381        if (WaterModule?.IsEnabled != true
 19382            || !WaterModule.CanSwim
 19383            || !WaterModule.IsSwimming)
 384        {
 1385            return Fixed64.Zero;
 386        }
 387
 18388        Fixed64 ellipseMultiplier = WaterModule.MaxSwimSpeed / WaterModule.MaxSwimSidewaysSpeed;
 18389        if (ellipseMultiplier <= Fixed64.Zero)
 1390            return Fixed64.Zero;
 391
 17392        return GetEllipticalHorizontalSpeed(
 17393            desiredMovementDirection,
 17394            ellipseMultiplier,
 17395            WaterModule.MaxSwimSidewaysSpeed,
 17396            Fixed64.One);
 397    }
 398
 399    private Fixed64 GetGroundHorizontalSpeed(Vector3d desiredMovementDirection, TrekRate rate)
 400    {
 111401        Fixed64 maxSpeed = GetGroundDirectionalMaxSpeed(desiredMovementDirection, rate);
 111402        Fixed64 ellipseMultiplier = maxSpeed / Handler.Move.MaxSidewaysSpeed;
 111403        if (ellipseMultiplier <= Fixed64.Zero)
 1404            return Fixed64.Zero;
 405
 110406        return GetEllipticalHorizontalSpeed(
 110407            desiredMovementDirection,
 110408            ellipseMultiplier,
 110409            Handler.Move.MaxSidewaysSpeed,
 110410            GetAirborneControlMultiplier());
 411    }
 412
 413    private Fixed64 GetAirborneControlMultiplier()
 414    {
 110415        if (IsOnSolid)
 86416            return Fixed64.One;
 417
 24418        if (IsJumping)
 2419            return JumpModule?.JumpControlMultiplier ?? Fixed64.One;
 420
 22421        if (IsFalling)
 21422            return Handler.Fall.FallControlMultiplier;
 423
 1424        return Fixed64.One;
 425    }
 426
 427    private static Fixed64 GetEllipticalHorizontalSpeed(
 428        Vector3d desiredMovementDirection,
 429        Fixed64 zAxisEllipseMultiplier,
 430        Fixed64 sidewaysSpeed,
 431        Fixed64 controlMultiplier)
 432    {
 127433        Vector3d normalized = new Vector3d(
 127434            desiredMovementDirection.x,
 127435            Fixed64.Zero,
 127436            desiredMovementDirection.z / zAxisEllipseMultiplier).Normal;
 127437        Fixed64 length = new Vector3d(
 127438            normalized.x,
 127439            Fixed64.Zero,
 127440            normalized.z * zAxisEllipseMultiplier).Magnitude;
 127441        return length * sidewaysSpeed * controlMultiplier;
 442    }
 443
 444    private Fixed64 GetGroundDirectionalMaxSpeed(Vector3d desiredMovementDirection, TrekRate rate)
 445    {
 111446        if (desiredMovementDirection.z < Fixed64.Zero)
 2447            return Handler.Move.MaxBackwardsSpeed;
 448
 109449        return rate switch
 109450        {
 35451            TrekRate.Slow => Handler.Move.MaxSlowSpeed,
 59452            TrekRate.Moderate => Handler.Move.MaxModerateSpeed,
 14453            TrekRate.Fast => Handler.Move.MaxFastSpeed,
 1454            _ => Fixed64.Zero
 109455        };
 456    }
 457
 458    private Vector3d ResolveLiquidVelocity(TrekRequest frameRequest, Vector3d desiredVelocity)
 459    {
 174460        if (WaterModule?.IsEnabled != true
 174461            || !WaterModule.CanSwim
 174462            || !WaterModule.IsSwimming)
 463        {
 154464            desiredVelocity = Vector3d.Zero;
 465        }
 466
 174467        if (WaterModule?.IsSwimming == true && frameRequest.Direction.y != Fixed64.Zero)
 10468            desiredVelocity.y = frameRequest.Direction.y * WaterModule.MaxSwimSpeed;
 469
 174470        if (desiredVelocity != Vector3d.Zero)
 16471            desiredVelocity *= FixedMath.Clamp01(Fixed64.One - Handler.Move.WaterDragFactor);
 472
 174473        return desiredVelocity;
 474    }
 475
 476    private Vector3d ApplyPlatformTransferVelocity(Vector3d desiredVelocity)
 477    {
 1854478        PlatformLocomotion platformModule = PlatformModule;
 1854479        if (platformModule.IsEnabled
 1854480            && platformModule.MovementTransfer == MotionTransfer.PermaTransfer)
 481        {
 1482            desiredVelocity += platformModule.FramePlatformVelocity;
 1483            desiredVelocity.y = Fixed64.Zero;
 484        }
 485
 1854486        return desiredVelocity;
 487    }
 488
 489    private Vector3d ApplyGroundVelocityConstraints(Vector3d desiredVelocity)
 490    {
 1856491        if (!IsOnSolid || desiredVelocity == Vector3d.Zero)
 1743492            return desiredVelocity;
 493
 113494        Fixed64 surfaceFriction = CurrentState.GroundState?.SurfaceFriction ?? Fixed64.Zero;
 113495        desiredVelocity *= Fixed64.One - surfaceFriction;
 496
 497        // Flat or host-defined "solid but no sampled normal" surfaces should preserve the raw ground vector.
 498        // Sliding also already produces a slope-aware direction, so re-projecting it here distorts the result.
 113499        if (CurrentState.SurfaceNormal == Vector3d.Zero
 113500            || CurrentState.SlopeAngle == Fixed64.Zero
 113501            || SlideModule?.IsSliding == true)
 502        {
 96503            return desiredVelocity;
 504        }
 505
 17506        Vector3d sideways = Vector3d.Cross(Vector3d.Up, desiredVelocity);
 17507        Vector3d adjustedVelocity = Vector3d.Cross(sideways, CurrentState.SurfaceNormal).Normal * desiredVelocity.Magnit
 17508        if (Fixed64.Sign(adjustedVelocity.y) != Fixed64.Sign(FrameSlopeAngle))
 17509            adjustedVelocity.y *= -1;
 510
 17511        return adjustedVelocity;
 512    }
 513
 514    private bool TryApplyStationaryGroundFriction(Vector3d desiredDirection)
 515    {
 2076516        if (!IsOnSolid || IsFlying || desiredDirection != Vector3d.Zero)
 1944517            return false;
 518
 132519        Fixed64 friction = CurrentState.GroundState?.SurfaceFriction ?? Fixed64.Zero;
 132520        if (friction <= Fixed64.Zero || _forceOutput == Vector3d.Zero)
 126521            return false;
 522
 6523        _forceOutput *= Fixed64.One - friction;
 6524        return true;
 525    }
 526
 527    private void ApplyDesiredVelocity(Vector3d desiredVelocity)
 528    {
 2069529        if (desiredVelocity == _forceOutput)
 1904530            return;
 531
 165532        Fixed64 maxVelocityChange = GetMaxAcceleration() * DeltaTime;
 165533        Vector3d velocityChange = (desiredVelocity - _forceOutput).ClampMagnitude(maxVelocityChange);
 165534        if (!IsOnSolid && !Handler.IsInControl)
 1535            return;
 536
 164537        _forceOutput += velocityChange;
 164538        if (IsOnSolid && !IsFlying)
 92539            _forceOutput.y = FixedMath.Min(_forceOutput.y, Fixed64.Zero);
 164540    }
 541
 542    /// <summary>
 543    /// Retrieves the maximum acceleration value based on the object’s current traversal state.
 544    /// </summary>
 545    /// <returns>The acceleration limit depending on whether the object is grounded, airborne, or swimming.</returns>
 546    public Fixed64 GetMaxAcceleration()
 547    {
 175548        if (CurrentState == null)
 1549            throw new InvalidOperationException("NavMotor must be initialized before querying max acceleration.");
 550
 174551        if (IsInLiquid)
 18552            return WaterModule?.IsEnabled == true
 18553                && WaterModule.CanSwim
 18554                && WaterModule.IsSwimming
 18555                ? WaterModule.MaxSwimAcceleration
 18556                : Handler.Move.MaxAirAcceleration;
 557
 156558        if (IsClimbing)
 31559            return ClimbModule?.IsEnabled == true && ClimbModule.CanClimb
 31560                ? ClimbModule.MaxClimbAcceleration
 31561                : Handler.Move.MaxAirAcceleration;
 562
 125563        if (IsFlying)
 9564            return FlyModule?.IsEnabled == true && FlyModule.CanFly
 9565                ? FlyModule.MaxFlyAcceleration
 9566                : Handler.Move.MaxAirAcceleration;
 567
 207568        if (IsOnSolid) return Handler.Move.MaxGroundAcceleration;
 569
 25570        if (IsJumping || IsFalling || IsInGas)
 24571            return Handler.Move.MaxAirAcceleration;
 572
 1573        throw new InvalidOperationException(
 1574            $"Cannot resolve max acceleration while traversal medium is {CurrentState.Medium}. NavMotor requires a known
 575    }
 576
 577    private void ApplyEnvironmentalForces()
 578    {
 2089579        Fixed64 gravityStep = Handler.Forces.GravityForce * DeltaTime;
 580
 2089581        if (IsFlying)
 582        {
 10583            Fixed64 gravityCompensation = FixedMath.Clamp(
 10584                FlyModule?.GravityCompensation ?? Fixed64.Zero,
 10585                Fixed64.Zero,
 10586                Fixed64.One);
 10587            _forceOutput.y -= gravityStep * (Fixed64.One - gravityCompensation);
 10588            return;
 589        }
 590
 2079591        if (IsClimbing)
 592        {
 36593            Fixed64 gravityCompensation = FixedMath.Clamp(
 36594                ClimbModule?.GravityCompensationWhileClimbing ?? Fixed64.Zero,
 36595                Fixed64.Zero,
 36596                Fixed64.One);
 36597            _forceOutput.y -= gravityStep * (Fixed64.One - gravityCompensation);
 36598            return;
 599        }
 600
 2043601        if (IsOnSolid)
 602        {
 215603            _forceOutput.y = FixedMath.Min(Fixed64.Zero, _forceOutput.y) - gravityStep;
 215604            return;
 605        }
 606
 1828607        if (IsInLiquid)
 608        {
 609            // Apply buoyancy if we can swim, otherwise apply gravity as normal.
 610            // Even if we can swim, we still apply gravity but reduce it based on the buoyancy factor to
 611            // create a more natural sinking effect when not actively swimming upwards.
 175612            if (WaterModule?.IsEnabled == true)
 173613                _forceOutput.y += gravityStep * (WaterModule.BuoyancyFactor - Fixed64.One);
 614            else
 2615                _forceOutput.y = Handler.Move.FrameVelocity.y - gravityStep;
 616
 2617            return;
 618        }
 619
 1654620        if (!IsInGas) return;
 621
 1652622        _forceOutput.y = Handler.Move.FrameVelocity.y - gravityStep;
 623
 624        // Ensure velocity does not exceed terminal fall speed
 1652625        Fixed64 terminalFallSpeed = Handler.Move.FrameVelocity.y + (_forceOutput.y * DeltaTime);
 1652626        if (terminalFallSpeed < -Handler.Forces.TerminalVelocity)
 7627            _forceOutput.y = -Handler.Forces.TerminalVelocity - Handler.Move.FrameVelocity.y;
 628
 629        // When jumping up we don't apply gravity for some time when the user is holding the jump button.
 630        // This allows for more control over jump height by pressing the button longer.
 1652631        JumpLocomotion? jumpModule = JumpModule;
 1652632        if (IsJumping && jumpModule?.IsHoldingJump == true)
 633        {
 634            // Calculate the duration that the extra jump force should have effect.
 635            // If we're still less than that duration after the jumping time, apply the force.
 2636            Fixed64 extraJumpLimit = (jumpModule.JumpStartTime + jumpModule.ExtraJumpHeight) / GetVerticalJumpSpeed();
 637
 638            // Negate the gravity we just applied, except we push in jumpDir rather than jump upwards.
 2639            if (TotalTime <= extraJumpLimit)
 2640                _forceOutput += jumpModule.FrameJumpDirection * gravityStep;
 641        }
 1652642    }
 643
 644    private void ApplyJumpForce(TrekRequest request)
 645    {
 2087646        if (!CanApplyJumpForce(request))
 2053647            return;
 648
 34649        Vector3d jumpForce = IsInLiquid
 34650            ? GetWaterBreachJumpForce()
 34651            : IsClimbing
 34652                ? GetClimbDetachJumpForce()
 34653                : GetGroundJumpForce();
 34654        CommitJumpForce(jumpForce);
 34655    }
 656
 657    private bool CanApplyJumpForce(TrekRequest request)
 658    {
 2090659        if (!(JumpModule?.IsEnabled == true
 2090660            && Handler.IsInControl
 2090661            && request.IsRequestingJump))
 662        {
 1974663            return false;
 664        }
 665
 116666        if (IsFlying || IsFalling)
 18667            return false;
 668
 98669        if (IsClimbing && !(ClimbModule?.ActiveAllowDetachJump ?? false))
 0670            return false;
 671
 98672        if (IsInLiquid && !(WaterModule?.CanBreachWater ?? false))
 2673            return false;
 674
 96675        if (!JumpModule.CanJump)
 60676            return false;
 677
 36678        return request.CanAffordJump;
 679    }
 680
 681    private Vector3d GetWaterBreachJumpForce()
 682    {
 2683        JumpLocomotion? jumpModule = JumpModule;
 2684        WaterLocomotion? waterModule = WaterModule;
 2685        if (jumpModule == null || waterModule == null)
 0686            return Vector3d.Zero;
 687
 2688        jumpModule.FrameJumpDirection = Vector3d.Up;
 2689        Events.OnStartWaterBreach?.Invoke();
 2690        return jumpModule.FrameJumpDirection * (GetVerticalJumpSpeed() * waterModule.BreachJumpMultiplier);
 691    }
 692
 693    private Vector3d GetGroundJumpForce()
 694    {
 31695        JumpLocomotion? jumpModule = JumpModule;
 31696        if (jumpModule == null)
 0697            return Vector3d.Zero;
 698
 31699        EnsureJumpDirectionInitialized();
 31700        Events.OnStartJump?.Invoke(jumpModule.AvoidGroundingTimer);
 31701        return jumpModule.FrameJumpDirection * GetVerticalJumpSpeed();
 702    }
 703
 704    private Vector3d GetClimbDetachJumpForce()
 705    {
 1706        JumpLocomotion? jumpModule = JumpModule;
 1707        ClimbLocomotion? climbModule = ClimbModule;
 1708        if (jumpModule == null || climbModule == null)
 0709            return Vector3d.Zero;
 710
 1711        Vector3d upward = climbModule.AttachedUpDirection != Vector3d.Zero
 1712            ? climbModule.AttachedUpDirection.Normal
 1713            : Vector3d.Up;
 1714        Vector3d outward = climbModule.AttachedSurfaceNormal != Vector3d.Zero
 1715            ? climbModule.AttachedSurfaceNormal.Normal
 1716            : Vector3d.Backward;
 1717        jumpModule.FrameJumpDirection = Vector3d.Slerp(upward, outward, jumpModule.PerpendicularJumpAmount);
 1718        Events.OnStartJump?.Invoke(jumpModule.AvoidGroundingTimer);
 1719        return jumpModule.FrameJumpDirection * GetVerticalJumpSpeed();
 720    }
 721
 722    private void EnsureJumpDirectionInitialized()
 723    {
 33724        JumpLocomotion? jumpModule = JumpModule;
 33725        if (jumpModule == null || jumpModule.IsJumping)
 5726            return;
 727
 28728        Fixed64 slerpAmount = IsTooSteep(FrameSlopeAngle)
 28729            ? jumpModule.SteepPerpendicularJumpAmount
 28730            : jumpModule.PerpendicularJumpAmount;
 731
 28732        jumpModule.FrameJumpDirection = Vector3d.Slerp(
 28733            Vector3d.Up,
 28734            CurrentState.SurfaceNormal,
 28735            slerpAmount);
 28736    }
 737
 738    private void CommitJumpForce(Vector3d jumpForce)
 739    {
 34740        if (IsClimbing)
 1741            StopClimb(wasForced: false);
 742
 34743        JumpLocomotion? jumpModule = JumpModule;
 34744        if (jumpModule == null)
 0745            return;
 746
 34747        jumpModule.RegisterJump();
 748
 749        // Remove any existing downward force before the jump impulse is applied.
 34750        _forceOutput.y = FixedMath.Max(Fixed64.Zero, _forceOutput.y);
 34751        _forceOutput += jumpForce;
 34752    }
 753
 754    private void UpdateClimbState(TrekRequest request)
 755    {
 2087756        if (ClimbModule?.IsEnabled != true)
 3757            return;
 758
 2084759        if (ClimbModule.IsMantling)
 5760            return;
 761
 2079762        bool canAttemptClimb = request.IsRequestingClimb
 2079763            && ClimbModule.CanClimb
 2079764            && !IsInLiquid
 2079765            && CurrentState.Medium != TraversalMedium.Unknown;
 2079766        if (!canAttemptClimb)
 767        {
 2034768            if (ClimbModule.IsClimbing)
 4769                StopClimb(wasForced: false);
 770
 2034771            return;
 772        }
 773
 45774        if (ClimbModule.ClimbResolver == null
 45775            || !ClimbModule.ClimbResolver.TryResolveClimbAffordance(request, CurrentState, out ClimbAffordanceSnapshot s
 776        {
 11777            if (ClimbModule.IsClimbing)
 1778                StopClimb(wasForced: true);
 779
 11780            return;
 781        }
 782
 34783        if (ClimbModule.IsClimbing)
 784        {
 13785            bool canContinue = snapshot.CanContinueClimb
 13786                && IsCompatibleClimbAffordance(snapshot);
 13787            if (!canContinue)
 788            {
 2789                StopClimb(wasForced: true);
 2790                return;
 791            }
 792
 11793            ClimbModule.ApplyClimbSnapshot(snapshot);
 794
 11795            if (ShouldStartMantle(request, snapshot))
 7796                StartMantle(snapshot);
 797
 11798            return;
 799        }
 800
 21801        bool canStart = snapshot.CanStartClimb;
 21802        if (!canStart)
 1803            return;
 804
 20805        StartClimb(snapshot);
 20806    }
 807
 808    private bool IsCompatibleClimbAffordance(ClimbAffordanceSnapshot snapshot)
 809    {
 12810        ClimbLocomotion? climbModule = ClimbModule;
 12811        if (climbModule == null)
 0812            return false;
 813
 12814        if (climbModule.AttachmentId.HasValue && snapshot.AffordanceId.HasValue)
 9815            return climbModule.AttachmentId.Value == snapshot.AffordanceId.Value;
 816
 3817        if (snapshot.Kind != climbModule.ActiveClimbKind)
 0818            return false;
 819
 3820        if (!HasCompatibleClimbAxes(snapshot))
 1821            return false;
 822
 2823        Fixed64 tolerance = GetClimbContinuityTolerance();
 2824        return (snapshot.AttachmentPoint - climbModule.AttachmentPoint).SqrMagnitude <= tolerance * tolerance;
 825    }
 826
 827    private bool HasCompatibleClimbAxes(ClimbAffordanceSnapshot snapshot)
 828    {
 3829        ClimbLocomotion? climbModule = ClimbModule;
 3830        if (climbModule == null)
 0831            return false;
 832
 3833        if (climbModule.AttachedSurfaceNormal != Vector3d.Zero
 3834            && snapshot.SurfaceNormal != Vector3d.Zero
 3835            && Vector3d.Dot(climbModule.AttachedSurfaceNormal.Normal, snapshot.SurfaceNormal.Normal) <= Fixed64.Zero)
 836        {
 1837            return false;
 838        }
 839
 2840        if (climbModule.AttachedUpDirection != Vector3d.Zero
 2841            && snapshot.UpDirection != Vector3d.Zero
 2842            && Vector3d.Dot(climbModule.AttachedUpDirection.Normal, snapshot.UpDirection.Normal) <= Fixed64.Zero)
 843        {
 0844            return false;
 845        }
 846
 2847        return true;
 848    }
 849
 850    private Fixed64 GetClimbContinuityTolerance()
 851    {
 2852        ClimbLocomotion? climbModule = ClimbModule;
 2853        if (climbModule == null)
 0854            return Fixed64.Zero;
 855
 2856        Fixed64 frameTravelAllowance = climbModule.MaxClimbSpeed * DeltaTime;
 2857        return climbModule.ClimbStartTolerance + frameTravelAllowance;
 858    }
 859
 860    private void StartClimb(ClimbAffordanceSnapshot snapshot)
 861    {
 20862        ClimbLocomotion? climbModule = ClimbModule;
 20863        if (climbModule == null)
 0864            return;
 865
 20866        climbModule.ApplyClimbSnapshot(snapshot);
 20867        climbModule.IsClimbing = true;
 20868        climbModule.IsMantling = false;
 869
 20870        if (IsFalling)
 1871            Handler.ClearTransientState<FallLocomotion>();
 872
 20873        if (JumpModule != null)
 874        {
 20875            JumpModule.IsJumping = false;
 20876            JumpModule.IsHoldingJump = false;
 877        }
 878
 20879        if (FlyModule != null)
 20880            FlyModule.IsFlying = false;
 881
 20882        if (SlideModule != null)
 20883            SlideModule.IsSliding = false;
 884
 20885        Events.OnStartClimb?.Invoke(snapshot);
 3886    }
 887
 888    private bool ShouldStartMantle(TrekRequest request, ClimbAffordanceSnapshot snapshot)
 889    {
 11890        ClimbLocomotion? climbModule = ClimbModule;
 11891        if (climbModule == null)
 0892            return false;
 893
 11894        if (snapshot.Kind != ClimbAffordanceKind.Ledge
 11895            || !climbModule.ActiveAllowMantle
 11896            || !climbModule.MantleTargetPosition.HasValue
 11897            || request.Rate == TrekRate.Stationary)
 898        {
 3899            return false;
 900        }
 901
 8902        Vector3d upAxis = climbModule.AttachedUpDirection != Vector3d.Zero
 8903            ? climbModule.AttachedUpDirection.Normal
 8904            : Vector3d.Up;
 8905        return Vector3d.Dot(request.Direction, upAxis) > Fixed64.Zero;
 906    }
 907
 908    private void StartMantle(ClimbAffordanceSnapshot snapshot)
 909    {
 7910        ClimbLocomotion? climbModule = ClimbModule;
 7911        if (climbModule == null)
 0912            return;
 913
 7914        climbModule.ApplyClimbSnapshot(snapshot);
 7915        climbModule.IsMantling = true;
 7916        Events.OnStartMantle?.Invoke();
 1917    }
 918
 919    private void UpdateFlightState(TrekRequest request)
 920    {
 2089921        if (FlyModule?.IsEnabled != true)
 3922            return;
 923
 2086924        bool shouldFly = request.IsRequestingFlight
 2086925            && FlyModule.CanFly
 2086926            && !IsClimbing
 2086927            && !IsInLiquid
 2086928            && CurrentState.Medium != TraversalMedium.Unknown;
 929
 2086930        if (!shouldFly)
 931        {
 2076932            FlyModule.IsFlying = false;
 2076933            return;
 934        }
 935
 10936        FlyModule.IsFlying = true;
 937
 10938        if (IsFalling)
 0939            Handler.ClearTransientState<FallLocomotion>();
 940
 10941        if (JumpModule != null)
 942        {
 10943            JumpModule.IsJumping = false;
 10944            JumpModule.IsHoldingJump = false;
 945        }
 946
 10947        if (SlideModule != null)
 10948            SlideModule.IsSliding = false;
 10949    }
 950
 951    private void UpdateSwimState(TrekRequest request)
 952    {
 2087953        WaterLocomotion? waterModule = WaterModule;
 2087954        if (waterModule?.IsEnabled != true)
 3955            return;
 956
 2084957        waterModule.RequestedSwimThisTraversal = request.IsRequestingSwim;
 2084958        waterModule.IsSwimming = IsInLiquid
 2084959            && waterModule.CanSwim
 2084960            && request.IsRequestingSwim;
 2084961    }
 962
 963    #endregion
 964
 965    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 966    private Vector3d ResolveTraversalVelocityDelta()
 967    {
 2087968        return _forceOutput != Vector3d.Zero
 2087969            ? _forceOutput * DeltaTime
 2087970            : Vector3d.Zero;
 971    }
 972
 973    private void ResolvePlatformTraversal(
 974        TrekRequest request,
 975        ref Vector3d positionDelta,
 976        ref FixedQuaternion rotationDelta)
 977    {
 2087978        PlatformLocomotion platformModule = PlatformModule;
 979
 980        // Do not apply platform movement if we just jumped or if the platform is not actively driving us.
 2087981        bool isMovingWithPlatform = platformModule.IsActive
 2087982            && !IsFlying
 2087983            && !IsClimbing
 2087984            && (IsOnSolid || platformModule.IsLockedToPlatform);
 2087985        if (!isMovingWithPlatform || IsJumping)
 1943986            return;
 987
 144988        platformModule.GetPlatformInfluence(
 144989            request.FootPosition ?? request.Origin,
 144990            request.Rotation,
 144991            out positionDelta,
 144992            out rotationDelta);
 144993    }
 994
 995    #endregion
 996}

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

#LineLine coverage
 1using FixedMathSharp;
 2
 3namespace Trailblazer.Navigation.Motor;
 4
 5public partial class NavMotor
 6{
 7    #region Utility
 8
 9    /// <summary>
 10    /// Computes the vertical jump speed required to reach the desired jump height (apex).
 11    /// </summary>
 12    /// <returns>The initial vertical velocity needed for the jump.</returns>
 3813    public Fixed64 GetVerticalJumpSpeed() => JumpModule == null
 3814        ? Fixed64.Zero
 3815        : FixedMath.Sqrt(2 * JumpModule.BaseJumpHeight * Handler.Forces.GravityForce);
 16
 17    /// <summary>
 18    /// Determines whether the current surface is too steep for normal movement.
 19    /// </summary>
 20    /// <returns>True if the slope exceeds the allowable incline; otherwise, false.</returns>
 21    public bool IsTooSteep(Fixed64 angle)
 22    {
 81523        if (!IsOnSolid) return false;
 24
 41125        Fixed64 absAngle = FixedMath.Abs(angle); // Handle both positive (uphill) and negative (downhill) slopes
 41126        return absAngle > Handler.Move.SlopeLimit - Fixed64.Epsilon;
 27    }
 28
 29    /// <summary>
 30    /// Checks if the object is on a sloped surface that is not considered too steep.
 31    /// </summary>
 32    /// <returns>True if the object is on a valid slope; otherwise, false.</returns>
 33    public bool IsOnSlope(Fixed64 angle)
 34    {
 17535        if (!IsOnSolid) return false;
 36
 10137        Fixed64 absAngle = FixedMath.Abs(angle); // Account for downhill slopes too
 10138        return absAngle > Fixed64.One && absAngle <= Handler.Move.SlopeLimit + Fixed64.Epsilon;
 39    }
 40
 41    /// <summary>
 42    /// Manually sets the object’s velocity, overriding the computed velocity for the next frame.
 43    /// </summary>
 44    /// <param name="velocity">The new velocity to assign to the object.</param>
 45    public void SetVelocity(Vector3d velocity)
 46    {
 36547        Handler.Move.FrameVelocity = velocity;
 36548    }
 49
 50    /// <summary>
 51    /// Pushes a traversal snapshot into the motor before the next traversal phase begins.
 52    /// </summary>
 53    /// <remarks>
 54    /// This is the explicit pre-traversal sync seam for hosts that learn about medium or surface
 55    /// changes before the next call to <see cref="TryTraversal(TrekRequest, out Vector3d, out Vector3d, out FixedQuater
 56    /// </remarks>
 57    public void SyncTraversalState(TrekCondition newCondition, bool isInitializing = false)
 58    {
 28559        if (isInitializing)
 60        {
 61            // Don't set the previous state as an empty state
 22662            CurrentState.Update(newCondition, newCondition);
 22663            return;
 64        }
 65
 5966        TrekCondition previousCondition = CurrentState.ToTrekCondition();
 5967        CurrentState.Update(newCondition, previousCondition);
 5968    }
 69
 70    /// <summary>
 71    /// Clears the current traversal-finalization requirement without reconciling frame results.
 72    /// </summary>
 73    /// <remarks>
 74    /// This is an explicit recovery escape hatch for hosts that must discard an in-progress traversal.
 75    /// It clears traversal bookkeeping only and does not roll back locomotion state changes that already occurred.
 76    /// </remarks>
 77    public void AbortTraversalFrame()
 78    {
 210079        TraversalInProgress = false;
 210080        _pendingTraversalFrame = -1;
 210081        FrameSlopeAngle = Fixed64.Zero;
 210082        _forceOutput = Vector3d.Zero;
 210083    }
 84
 85    #endregion
 86}

Methods/Properties

.ctor()
get_IsInitialized()
get_Context()
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()
CreateNew(Trailblazer.TrailblazerWorldContext,Trailblazer.Navigation.Motor.TrekCondition,Trailblazer.Navigation.Motor.LocomotionProfile)
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()
ClearFallState()
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()