< Summary

Line coverage
100%
Covered lines: 609
Uncovered lines: 0
Coverable lines: 609
Total lines: 1415
Line coverage: 100%
Branch coverage
100%
Covered branches: 262
Total branches: 262
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: .cctor()100%11100%
File 1: .ctor(...)100%11100%
File 1: get_Destination()100%11100%
File 1: get_TargetDirection()100%11100%
File 1: get_LastTargetDirection()100%11100%
File 1: get_CurrentQuery()100%11100%
File 1: get_AreaPolicyForCommit()100%22100%
File 1: ConsumePendingCommittedAreaPolicy()100%11100%
File 1: get_ShouldMove()100%11100%
File 1: get_IsStuck()100%11100%
File 1: get_HasLineOfSightPath()100%11100%
File 1: get_DistanceToTarget()100%11100%
File 1: get_HasNavigationGuidance()100%44100%
File 1: get_PendingTransitionGuideStatus()100%22100%
File 1: get_IsAtDestination()100%11100%
File 1: BindPendingTransitionOwner(...)100%22100%
File 1: UnbindPendingTransitionOwner(...)100%22100%
File 1: get_CanAutoStop()100%11100%
File 1: get_WaypointTolerance()100%11100%
File 1: set_WaypointTolerance(...)100%11100%
File 1: BindContext(...)100%44100%
File 1: get_MovementGroupID()100%11100%
File 1: set_MovementGroupID(...)100%11100%
File 1: get_IsInGroup()100%11100%
File 2: ComputeCombinedSteering(...)100%2222100%
File 2: CacheOwner(...)100%22100%
File 2: UpdateMovementGroupState(...)100%44100%
File 2: IsGroupNeighbor(...)100%11100%
File 3: ApplyPathQuery(...)100%11100%
File 3: BeginPathSession(...)100%22100%
File 3: PauseAutoStop()100%11100%
File 3: AddToMovementGroup(...)100%66100%
File 3: LeaveMovementGroup()100%11100%
File 3: PrewarmMovementGroup(...)100%44100%
File 4: RecordData(...)100%44100%
File 4: RestoreQueryFromLoadedSession(...)100%44100%
File 4: ReleaseNavigationGuidance()100%44100%
File 4: ResetMovementGroupSession()100%11100%
File 5: OnInitialize()100%11100%
File 5: Reset()100%11100%
File 5: ResolveContext()100%11100%
File 5: get_MovementGroups()100%11100%
File 5: RemoveMovementGroupSession()100%11100%
File 5: get_StuckFrameThresholdForContext()100%11100%
File 5: get_AutoPauseStopTimeForContext()100%11100%
File 5: ResolveFrameRate()100%11100%
File 5: GetHeading(...)100%2626100%
File 5: ValidateMovementPath(...)100%11100%
File 5: ValidateGraphMovementPath(...)100%2222100%
File 5: ValidateGraphFlowMovementPath(...)100%1818100%
File 5: TryHandleInitialGraphDirectTravel(...)100%66100%
File 5: FindTargetDirection(...)100%3434100%
File 5: ShouldAdvanceToNextWaypoint()100%66100%
File 5: CheckStuckStatus(...)100%1010100%
File 5: FinalizeIdleHeading(...)100%22100%
File 5: TryEnsureCurrentRequest(...)100%22100%
File 5: TryPrepareMovementPathForHeading(...)100%44100%
File 5: RefreshLineOfSightState(...)100%1010100%
File 5: HandleInvalidPath(...)100%44100%
File 5: UpdateTargetDirection(...)100%44100%
File 5: IsCurrentAStarTransitionStep()100%22100%
File 5: ShouldArriveWithoutNavigationGuidance()100%44100%
File 5: UpdateNavigationGuidanceProgress(...)100%1414100%
File 5: IsAtFinalWaypoint(...)100%11100%
File 5: CompletePendingTransition(...)100%44100%
File 5: FinalizeHeadingFrame()100%22100%
File 5: ResetStuckStatus()100%11100%
File 5: TryRecoverFromStuck(...)100%22100%
File 5: PreparePathRetry()100%11100%
File 5: DeclareHardStuck()100%22100%
File 5: DisposeCurrentNavigationGuidance()100%11100%
File 5: SetDeceleration(...)100%66100%
File 5: Arrive()100%44100%
File 5: StopMove()100%66100%

File(s)

/home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Navigation/Steering/NavSteering.cs

#LineLine coverage
 1//=======================================================================
 2// NavSteering.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;
 11using GridForge.Grids;
 12using SwiftCollections;
 13using Trailblazer.Navigation.MovementGroups;
 14using Trailblazer.Pathing;
 15
 16namespace Trailblazer.Navigation.Steering;
 17
 18/// <summary>
 19/// Handles agent steering and path navigation logic by coordinating pathfinding, movement,
 20/// and group behaviors within a lockstep simulation. Supports both direct line-of-sight travel
 21/// and graph-backed A* or flow-field guidance.
 22/// </summary>
 23public partial class NavSteering : IRecordable
 24{
 25    #region Constants & Defaults
 26
 27    /// <summary>
 28    /// Default range to scan for other agents when calculating steering behaviors.
 29    /// </summary>
 130    protected static readonly Fixed64 DefaultGroupFactor = (Fixed64)10;
 31
 32    /// <summary>
 33    /// Default padding radius used to maintain space between nearby agents.
 34    /// </summary>
 135    protected static readonly Fixed64 DefaultAvoidFactor = (Fixed64)3;
 36
 37    /// <summary>
 38    /// Default weights used for group-based steering calculations (separation, alignment, cohesion).
 39    /// </summary>
 140    protected static readonly GroupBehaviorWeights DefaultBehaviorWeights = new()
 141    {
 142        Separation = (Fixed64)2,
 143        Alignment = Fixed64.Half,
 144        Cohesion = Fixed64.FromFraction(1, 5),
 145        Avoidance = Fixed64.One
 146    };
 47
 48    /// <summary>
 49    /// Default world-space tolerance used when advancing between ordinary guide steps.
 50    /// </summary>
 151    public static readonly Fixed64 DefaultWaypointTolerance = Fixed64.Half;
 52
 53    /// <summary>
 54    /// Number of frames between pathfinding LOS rechecks.
 55    /// </summary>
 56    protected const int DefaultPathRecheckCooldown = 16;
 57
 58    /// <summary>
 59    /// Maximum number of repath attempts before declaring the agent fully stuck.
 60    /// </summary>
 61    protected const int StuckRepathTries = 4;
 62
 63    /// <summary>
 64    /// Default braking factor applied when decelerating or stopping motion.
 65    /// </summary>
 166    public static readonly Fixed64 DefaultBrakingPower = Fixed64.FromFraction(3, 20);
 67
 68    #endregion
 69
 70    #region Fields
 71
 72    /// <summary>
 73    /// The final destination this agent is attempting to reach.
 74    /// </summary>
 75    protected Vector3d _destination;
 76
 77    /// <summary>
 78    /// Gets the current target direction as a three-dimensional vector.
 79    /// </summary>
 80    protected Vector3d _targetDirection;
 81
 82    /// <summary>
 83    /// Gets the direction vector of the most recent target interaction.
 84    /// </summary>
 85    protected Vector3d _lastTargetDirection;
 86
 87    /// <summary>
 88    /// Whether the object is following a path or guide to the destination.
 89    /// </summary>
 90    protected bool _shouldMove;
 91
 92    /// <summary>
 93    /// Whether the agent has become stuck and exhausted repathing attempts.
 94    /// </summary>
 95    protected bool _isStuck;
 96
 97    /// <summary>
 98    /// True if the agent can reach the destination without requiring a path.
 99    /// </summary>
 100    protected bool _hasLineOfSightPath;
 101
 102    /// <summary>
 103    /// Has this unit arrived at destination?
 104    /// </summary>
 105    protected bool _isAtDestination;
 106
 107    /// <summary>
 108    /// Number of consecutive frames where movement failed and deceleration is occurring.
 109    /// </summary>
 110    protected int _stoppedFrameCount;
 111
 112    #endregion
 113
 114    #region Runtime State - Pathfinding
 115
 116    /// <summary>
 117    /// Disable if a unit never needs graph-guide validation or repathing.
 118    /// </summary>
 342119    public bool CanPathfind = true;
 120
 121    /// <inheritdoc cref="_destination"/>
 13122    public Vector3d Destination => _destination;
 123
 124    private Vector3d _requestedDestination;
 125
 126    /// <inheritdoc cref="DefaultPathRecheckCooldown"/>
 342127    public int PathRecheckCooldownFrames = DefaultPathRecheckCooldown;
 128
 129    /// <inheritdoc cref="_targetDirection"/>
 311130    public Vector3d TargetDirection => _targetDirection;
 131
 132    /// <inheritdoc cref="_lastTargetDirection"/>
 18133    public Vector3d LastTargetDirection => _lastTargetDirection;
 134
 135    /// <summary>
 136    /// Gets the immutable graph-backed medium-state query owned by the current steering session.
 137    /// </summary>
 138    private PathQuery? _currentQuery;
 139
 140    private NavigationAreaPolicyKey? _pendingCommittedAreaPolicy;
 141
 142    /// <inheritdoc cref="_currentQuery"/>
 153143    public PathQuery? CurrentQuery => _currentQuery;
 144
 145    internal NavigationAreaPolicyKey? AreaPolicyForCommit =>
 32146        _currentQuery?.AreaPolicy ?? _pendingCommittedAreaPolicy;
 147
 58148    internal void ConsumePendingCommittedAreaPolicy() => _pendingCommittedAreaPolicy = null;
 149
 150    private NavigationGuideLease? _navigationGuideLease;
 151
 152    private NavigationFlowFieldLease? _navigationFlowFieldLease;
 153
 154    private Navigator? _pendingTransitionOwner;
 155
 156    /// <inheritdoc cref="_shouldMove"/>
 355157    public bool ShouldMove => _shouldMove;
 158
 159    /// <inheritdoc cref="_isStuck"/>
 42160    public bool IsStuck => _isStuck;
 161
 162    /// <inheritdoc cref="_hasLineOfSightPath"/>
 361163    public bool HasLineOfSightPath => _hasLineOfSightPath;
 164
 165    /// <summary>
 166    /// Current pathfinding search status.
 167    /// </summary>
 168    protected bool _shouldRequestPathThisFrame;
 169
 170    /// <summary>
 171    /// Represents the cooldown period, in milliseconds, before the next path check can be performed.
 172    /// </summary>
 173    protected int _pathCheckCooldown;
 174
 175    /// <summary>
 176    /// How far we move each update
 177    /// </summary>
 178    protected Fixed64 _distanceToTarget;
 179
 180    /// <inheritdoc cref="_distanceToTarget"/>
 115181    public Fixed64 DistanceToTarget => _distanceToTarget;
 182
 183    /// <summary>
 184    /// Indicates whether the agent is actively following an A* or Flow guide.
 185    /// </summary>
 236186    public bool HasNavigationGuidance => !HasLineOfSightPath
 236187        && (_navigationGuideLease != null
 236188            || _navigationFlowFieldLease != null);
 189
 190    internal NavigationGuideStatus PendingTransitionGuideStatus =>
 4191        _currentQuery!.Value.Algorithm == PathAlgorithm.AStar
 4192            ? _navigationGuideLease!.Value.Status
 4193            : _navigationFlowFieldLease!.Value.Status;
 194
 195    /// <inheritdoc cref="_isAtDestination"/>
 312196    public bool IsAtDestination => _isAtDestination;
 197
 198    internal void BindPendingTransitionOwner(Navigator owner)
 199    {
 283200        SwiftThrowHelper.ThrowIfTrue(
 283201            _pendingTransitionOwner != null
 283202            && !ReferenceEquals(_pendingTransitionOwner, owner),
 283203            message: "NavSteering is already bound to a different pending-transition owner.");
 204
 281205        _pendingTransitionOwner = owner;
 281206    }
 207
 208    internal void UnbindPendingTransitionOwner(Navigator owner)
 209    {
 25210        if (ReferenceEquals(_pendingTransitionOwner, owner))
 24211            _pendingTransitionOwner = null;
 25212    }
 213
 214    #endregion
 215
 216    #region Runtime State - Steering & Motion
 217
 218    /// <summary>
 219    /// Whether this agent can currently move.
 220    /// </summary>
 342221    public bool CanMove = true;
 222
 223    /// <summary>
 224    /// Internal cooldown before the agent can automatically stop again (used for bursty movement).
 225    /// </summary>
 226    protected int _autoStopFrameCount;
 227
 228    /// <summary>
 229    /// Indicates whether the agent is currently eligible for automatic stopping logic.
 230    /// </summary>
 163231    public bool CanAutoStop => _autoStopFrameCount <= 0;
 232
 233    /// <summary>
 234    /// Number of attempts to repath after getting stuck.
 235    /// </summary>
 236    protected int _repathTries;
 237
 238    /// <summary>
 239    /// Number of frames the agent has failed movement checks (used for stuck detection).
 240    /// </summary>
 241    protected int _stuckFrameCount;
 242
 342243    private Fixed64 _waypointTolerance = DefaultWaypointTolerance;
 244
 245    /// <summary>
 246    /// Gets or sets the non-negative world-space tolerance used for ordinary guide steps.
 247    /// </summary>
 248    public Fixed64 WaypointTolerance
 249    {
 111250        get => _waypointTolerance;
 251        set
 252        {
 13253            SwiftThrowHelper.ThrowIfArgumentOutOfRange(
 13254                value < Fixed64.Zero,
 13255                null,
 13256                nameof(value));
 12257            _waypointTolerance = value;
 12258        }
 259    }
 260
 261    /// <summary>
 262    /// How far to look for group neighbors (separation/alignment/cohesion).
 263    /// </summary>
 342264    public Fixed64 GroupFactor = DefaultGroupFactor;
 265
 266    /// <summary>
 267    /// How far to look for obstacles to avoid.
 268    /// </summary>
 342269    public Fixed64 AvoidFactor = DefaultAvoidFactor;
 270
 271    /// <summary>
 272    /// Weights for separating, aligning, and cohesion in group behavior.
 273    /// Avoidance weight is baked in here as well.
 274    /// </summary>
 342275    public GroupBehaviorWeights BehaviorWeights = DefaultBehaviorWeights;
 276
 277    /// <summary>
 278    /// Friction-based deceleration rate used when slowing down on ground surfaces.
 279    /// </summary>
 342280    public Fixed64 BrakingPower = DefaultBrakingPower;
 281
 342282    private readonly MovementGroupSession _movementGroupSession = new();
 283
 342284    private readonly SwiftList<ISteer> _nearbySteerAgents = new();
 285
 342286    private readonly GridScanScratch _scanScratch = new();
 287
 288    private MovementGroupTravelMode _movementGroupMode;
 289
 290    private TrailblazerWorldContext? _context;
 291
 292    #endregion
 293
 294    #region Events
 295
 296    /// <summary>
 297    /// Container for delegate events that fire on pathfinding state changes (start, stop, arrive).
 298    /// </summary>
 299    public NavSteeringEvents Events { get; protected set; } = new();
 300
 301    #endregion
 302
 303    #region Constructors
 304
 305    /// <summary>
 306    /// Initializes a new context-bound <see cref="NavSteering"/> instance.
 307    /// </summary>
 342308    public NavSteering(TrailblazerWorldContext context)
 309    {
 342310        BindContext(context);
 342311        OnInitialize();
 342312    }
 313
 314    /// <summary>
 315    /// Binds this steering controller to a world context.
 316    /// </summary>
 317    public void BindContext(TrailblazerWorldContext context)
 318    {
 374319        TrailblazerWorldContext.ThrowIfUnusable(context);
 320
 374321        if (ReferenceEquals(_context, context))
 31322            return;
 323
 343324        if (_context != null)
 1325            _context.Navigation.MovementGroups.Remove(_movementGroupSession);
 326
 343327        _context = context;
 343328    }
 329
 330    #endregion
 331
 332    #region Group Properties
 333
 334    /// <summary>
 335    /// Gets or sets the unique identifier for the movement group associated with the current session.
 336    /// </summary>
 337    public int MovementGroupID
 338    {
 723339        get => _movementGroupSession.GroupId;
 612340        set => _movementGroupSession.GroupId = value;
 341    }
 342
 343    /// <summary>
 344    /// Gets a value indicating whether the item is assigned to a movement group.
 345    /// </summary>
 683346    public bool IsInGroup => MovementGroupID != -1;
 347
 348    #endregion
 349
 350
 351
 352
 353
 354
 355}

/home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Navigation/Steering/NavSteering.Groups.cs

#LineLine coverage
 1//=======================================================================
 2// NavSteering.Groups.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;
 11using GridForge.Grids;
 12using Trailblazer.Navigation.MovementGroups;
 13
 14namespace Trailblazer.Navigation.Steering;
 15
 16public partial class NavSteering
 17{
 18    #region Steering Behaviors (Group & Avoidance)
 19
 20    /// <summary>
 21    /// Computes a combined steering vector—
 22    /// Separation, Alignment, Cohesion, plus single-nearest obstacle avoidance.
 23    /// </summary>
 24    public Vector3d ComputeCombinedSteering(
 25        Vector3d position,
 26        Vector3d velocity,
 27        Fixed64 speed,
 28        Fixed64 radius,
 29        Guid id)
 30    {
 35531        if (speed <= Fixed64.Zero)
 8832            return Vector3d.Zero;
 33
 26734        TrailblazerWorldContext context = ResolveContext();
 26735        int currentFrame = context.FrameCount;
 36
 37        // we need to see everybody who might influence us—either for group or for avoidance
 26738        Fixed64 groupRadius = radius * GroupFactor;
 26739        Fixed64 invGR = Fixed64.One / groupRadius;
 26740        Fixed64 avoidRadius = radius * AvoidFactor;
 26741        Fixed64 scanRadius = FixedMath.Max(groupRadius, avoidRadius);
 26742        Fixed64 groupRadiusSq = groupRadius * groupRadius;
 43
 44        // Accumulators
 26745        Vector3d separation = Vector3d.Zero;
 26746        Vector3d alignment = Vector3d.Zero;
 26747        Vector3d cohesionCM = Vector3d.Zero;
 26748        int groupCount = 0;
 49
 26750        ISteer? closest = null;
 26751        Fixed64 closestDistSq = avoidRadius * avoidRadius;
 52
 26753        GridScanManager.ScanRadiusInto<ISteer>(
 26754            context.World,
 26755            position,
 26756            scanRadius,
 26757            _nearbySteerAgents,
 26758            _scanScratch);
 59
 1700060        for (int i = 0; i < _nearbySteerAgents.Count; i++)
 61        {
 823362            ISteer other = _nearbySteerAgents[i];
 823363            if (other.Radius <= Fixed64.Zero)
 64                continue;
 65
 823266            if (other.GlobalId == id)
 67                continue;
 68
 823069            Vector3d offset = other.Position - position;
 823070            Fixed64 distSq = offset.MagnitudeSquared;
 823071            if (distSq <= Fixed64.Epsilon)
 72                continue;
 73
 74            // Group behaviors
 797375            if (IsGroupNeighbor(other.GlobalId, currentFrame) && distSq < groupRadiusSq)
 76            {
 177                groupCount++;
 178                Fixed64 d = FixedMath.Sqrt(distSq);
 179                Fixed64 invD = Fixed64.One / d;
 180                Vector3d norm = offset * invD;  // offset.Normalized
 81                // stronger separation the closer they are
 182                Fixed64 push = (groupRadius - d) * invGR;
 183                separation -= norm * push;
 184                alignment += other.Velocity.Normalized;
 185                cohesionCM += other.Position;
 86            }
 87
 88            // Track nearest for avoidance
 797389            if (distSq < closestDistSq)
 90            {
 52091                closestDistSq = distSq;
 52092                closest = other;
 93            }
 94        }
 95
 26796        Vector3d groupForce = Vector3d.Zero;
 97        // Finalize group forces
 26798        if (groupCount > 0)
 99        {
 1100            Vector3d sep = separation * BehaviorWeights.Separation;
 1101            Vector3d align = (alignment / groupCount).Normalized * BehaviorWeights.Alignment;
 1102            Vector3d coh = ((cohesionCM / groupCount - position).Normalized) * BehaviorWeights.Cohesion;
 1103            groupForce = sep + align + coh;
 104        }
 105
 106        // Compute avoidance
 267107        Vector3d avoidance = Vector3d.Zero;
 267108        if (closest != null)
 109        {
 263110            Vector3d dir = closest.Position - position;
 111            // pick left/right dodge
 263112            bool dodgeLeft = Vector3d.Dot(velocity, dir) >= Fixed64.Zero;
 263113            Vector3d perp = dodgeLeft
 263114                ? new(-dir.Z, Fixed64.Zero, dir.X)
 263115                : new(dir.Z, Fixed64.Zero, -dir.X);
 116
 117            // prioritize evasive action when facing direct collision(dot ~ ±1),
 118            // and de-emphasize near misses(dot ~0)
 263119            Fixed64 dynamicAvoidWeight = Vector3d.Dot(velocity.Normalized, dir.Normalized);
 263120            Fixed64 totalAvoidWeight = BehaviorWeights.Avoidance * dynamicAvoidWeight;
 263121            avoidance = perp.Normalized
 263122                * ((radius + closest.Radius) / FixedMath.Sqrt(closestDistSq))
 263123                * totalAvoidWeight;
 124        }
 125
 267126        return groupForce + avoidance;
 127    }
 128
 129    #endregion
 130
 131    #region Movement Groups
 132
 133    private void CacheOwner(ISteer vessel)
 134    {
 151135        if (IsInGroup)
 13136            MovementGroups.CacheOwner(_movementGroupSession, vessel.GlobalId);
 151137    }
 138
 139    private void UpdateMovementGroupState(
 140        Vector3d position,
 141        Fixed64 radius,
 142        bool resetFormationOffset = false)
 143    {
 250144        var target = new MovementGroupTarget(
 250145            travelMode: IsInGroup ? MovementGroupTravelMode.Individual : MovementGroupTravelMode.None,
 250146            destination: _requestedDestination);
 147
 250148        if (IsInGroup)
 149        {
 33150            target = MovementGroups.UpdateTarget(
 33151                _movementGroupSession,
 33152                _requestedDestination,
 33153                position,
 33154                radius,
 33155                resetFormationOffset);
 156        }
 157
 250158        _destination = target.Destination;
 250159        _movementGroupMode = target.TravelMode;
 250160    }
 161
 162    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 163    private bool IsGroupNeighbor(Guid otherId, int currentFrame)
 7973164        => MovementGroups.IsNeighbor(_movementGroupSession, otherId, _requestedDestination, currentFrame);
 165
 166    #endregion
 167}

/home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Navigation/Steering/NavSteering.Requests.cs

#LineLine coverage
 1//=======================================================================
 2// NavSteering.Requests.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.Runtime.CompilerServices;
 9using FixedMathSharp;
 10using Trailblazer.Navigation.MovementGroups;
 11using Trailblazer.Pathing;
 12
 13namespace Trailblazer.Navigation.Steering;
 14
 15public partial class NavSteering
 16{
 17    #region Public Interface
 18
 19    /// <summary>
 20    /// Starts or replaces one graph-backed A* or flow-field steering request.
 21    /// </summary>
 22    internal virtual void ApplyPathQuery(PathQuery query, int groupId = -1)
 23    {
 11824        BeginPathSession(
 11825            query.End.Position,
 11826            query.Start.Position,
 11827            groupId,
 11828            query);
 11829    }
 30
 31    private void BeginPathSession(
 32        Vector3d destination,
 33        Vector3d origin,
 34        int groupId,
 35        PathQuery query)
 36    {
 11837        _pendingCommittedAreaPolicy = null;
 11838        _hasLineOfSightPath = false;
 11839        _isAtDestination = false;
 11840        _stoppedFrameCount = 0;
 11841        _isStuck = false;
 11842        _stuckFrameCount = 0;
 11843        _shouldMove = true;
 11844        _requestedDestination = destination;
 11845        _destination = destination;
 46
 11847        ReleaseNavigationGuidance();
 11848        _currentQuery = query;
 49
 11850        _repathTries = 0;
 11851        _shouldRequestPathThisFrame = true;
 11852        AddToMovementGroup(groupId);
 11853        UpdateMovementGroupState(origin, query.Agent.Shape.Radius, true);
 11854        Events.OnMoveRequestApplied?.Invoke();
 155    }
 56
 57    /// <summary>
 58    /// Applies a short delay to prevent auto-stopping behavior for a few frames.
 59    /// </summary>
 60    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 361    public void PauseAutoStop() => _autoStopFrameCount = AutoPauseStopTimeForContext;
 62
 63    /// <summary>
 64    /// Assigns this steering session to a movement group.
 65    /// </summary>
 66    /// <param name="groupId">A non-negative group identifier. Negative values remove the current group assignment.</par
 67    public void AddToMovementGroup(int groupId)
 68    {
 12169        if (groupId < 0)
 70        {
 9871            LeaveMovementGroup();
 9872            return;
 73        }
 74
 2375        if (MovementGroupID >= 0 && MovementGroupID != groupId)
 176            LeaveMovementGroup();
 77
 2378        MovementGroupID = groupId;
 2379        _movementGroupMode = MovementGroupTravelMode.Individual;
 2380    }
 81
 82    /// <summary>
 83    /// Removes this steering session from its current movement group.
 84    /// </summary>
 85    public void LeaveMovementGroup()
 86    {
 51887        RemoveMovementGroupSession();
 51888        MovementGroupID = -1;
 51889        _movementGroupSession.GroupIndex = -1;
 51890        _movementGroupMode = MovementGroupTravelMode.None;
 51891        _destination = _requestedDestination;
 51892    }
 93
 94    /// <summary>
 95    /// Rebuilds this steering session's shared movement-group membership from the current runtime owner state.
 96    /// </summary>
 97    /// <remarks>
 98    /// Call this after loading multiple grouped steering sessions when you want the coordinator warmed
 99    /// before the next simulation frame. If it is skipped, grouped steering will still recover lazily
 100    /// during <see cref="GetHeading"/>.
 101    /// </remarks>
 102    /// <param name="vessel">The current steering owner whose position, radius, and stable id should seed the coordinato
 103    public void PrewarmMovementGroup(ISteer vessel)
 104    {
 10105        SwiftThrowHelper.ThrowIfNull(vessel, nameof(vessel));
 106
 9107        if (!ShouldMove || !IsInGroup)
 1108            return;
 109
 8110        MovementGroups.Prewarm(
 8111            _movementGroupSession,
 8112            vessel.GlobalId,
 8113            _requestedDestination,
 8114            vessel.Position,
 8115            vessel.Radius);
 8116    }
 117
 118    #endregion
 119}

/home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Navigation/Steering/NavSteering.Serialization.cs

#LineLine coverage
 1//=======================================================================
 2// NavSteering.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;
 9using FixedMathSharp;
 10using Trailblazer.Navigation.MovementGroups;
 11using Trailblazer.Pathing;
 12
 13namespace Trailblazer.Navigation.Steering;
 14
 15public partial class NavSteering
 16{
 17    #region Serialization
 18
 19    /// <inheritdoc />
 20    public virtual void RecordData(IChronicler chronicler)
 21    {
 14522        int movementGroupId = _movementGroupSession.GroupId;
 23
 14524        RecordValues.Look(chronicler, ref CanPathfind, "CanPathfind", true);
 14525        RecordValues.Look(chronicler, ref _destination, "Destination", Vector3d.Zero);
 14526        RecordValues.Look(chronicler, ref _requestedDestination, "RequestedDestination", Vector3d.Zero);
 14527        RecordValues.Look(chronicler, ref PathRecheckCooldownFrames, "PathRecheckCooldownFrames", DefaultPathRecheckCool
 14528        RecordValues.Look(chronicler, ref _targetDirection, "TargetDirection", Vector3d.Zero);
 14529        RecordValues.Look(chronicler, ref _lastTargetDirection, "LastTargetDirection", Vector3d.Zero);
 14530        RecordValues.Look(chronicler, ref _shouldMove, "ShouldMove", false);
 14531        RecordValues.Look(chronicler, ref _isStuck, "IsStuck", false);
 14532        RecordValues.Look(chronicler, ref _hasLineOfSightPath, "HasLineOfSightPath", false);
 14533        RecordValues.Look(chronicler, ref _shouldRequestPathThisFrame, "ShouldRequestPathThisFrame", false);
 14534        RecordValues.Look(chronicler, ref _pathCheckCooldown, "PathCheckCooldown", 0);
 14535        RecordValues.Look(chronicler, ref _distanceToTarget, "DistanceToTarget", Fixed64.Zero);
 14536        RecordValues.Look(chronicler, ref _isAtDestination, "IsAtDestination", false);
 14537        RecordValues.Look(chronicler, ref CanMove, "CanMove", false);
 14538        RecordValues.Look(chronicler, ref _stoppedFrameCount, "StoppedFrameCount", 0);
 14539        RecordValues.Look(chronicler, ref _autoStopFrameCount, "AutoStopFrameCount", 0);
 14540        RecordValues.Look(chronicler, ref _repathTries, "RepathTries", 0);
 14541        RecordValues.Look(chronicler, ref _stuckFrameCount, "StuckFrameCount", 0);
 14542        RecordValues.Look(chronicler, ref _waypointTolerance, "WaypointTolerance", DefaultWaypointTolerance);
 14543        RecordValues.Look(chronicler, ref GroupFactor, "GroupFactor", DefaultGroupFactor);
 14544        RecordValues.Look(chronicler, ref AvoidFactor, "AvoidFactor", DefaultAvoidFactor);
 14545        RecordValues.Look(chronicler, ref BehaviorWeights, "BehaviorWeights", DefaultBehaviorWeights);
 14546        RecordValues.Look(chronicler, ref BrakingPower, "BrakingPower", DefaultBrakingPower);
 14547        RecordValues.Look(chronicler, ref movementGroupId, "MovementGroupId", 0);
 14548        RecordValues.Look(chronicler, ref _movementGroupMode, "MovementGroupMode", MovementGroupTravelMode.None);
 14549        if (chronicler.Mode == SerializationMode.Loading)
 50        {
 7151            ReleaseNavigationGuidance();
 7152            ResetMovementGroupSession();
 53
 7154            _currentQuery = null;
 7155            _pendingCommittedAreaPolicy = null;
 7156            _shouldRequestPathThisFrame = false;
 7157            MovementGroupID = movementGroupId;
 7158            _movementGroupSession.GroupIndex = -1;
 7159            if (movementGroupId < 0)
 6360                _movementGroupMode = MovementGroupTravelMode.None;
 61        }
 14562    }
 63
 64    internal void RestoreQueryFromLoadedSession(PathQuery? query)
 65    {
 2866        _pendingCommittedAreaPolicy = null;
 2867        _currentQuery = query;
 2868        ReleaseNavigationGuidance();
 2869        if (!query.HasValue)
 1970            return;
 71
 972        _requestedDestination = query.Value.End.Position;
 973        if (!HasLineOfSightPath)
 774            _shouldRequestPathThisFrame = true;
 975    }
 76
 77    private void ReleaseNavigationGuidance()
 78    {
 39679        _navigationGuideLease?.Dispose();
 39680        _navigationGuideLease = null;
 39681        _navigationFlowFieldLease?.Dispose();
 39682        _navigationFlowFieldLease = null;
 39683    }
 84
 85    private void ResetMovementGroupSession()
 86    {
 7187        RemoveMovementGroupSession();
 7188        _movementGroupSession.Reset();
 7189    }
 90
 91    #endregion
 92}

/home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Navigation/Steering/NavSteering.Simulation.cs

#LineLine coverage
 1//=======================================================================
 2// NavSteering.Simulation.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;
 11using Trailblazer.Navigation.MovementGroups;
 12using Trailblazer.Pathing;
 13
 14namespace Trailblazer.Navigation.Steering;
 15
 16public partial class NavSteering
 17{
 18    #region Simulation Lifecycle
 19
 20    /// <summary>
 21    /// Initializes the object by setting up its defaults, events, traversal state, and movement controller.
 22    /// </summary>
 23    protected virtual void OnInitialize()
 24    {
 36525        LeaveMovementGroup();
 26
 36527        _stoppedFrameCount = 0;
 36528        _autoStopFrameCount = 0;
 29
 36530        _shouldRequestPathThisFrame = false;
 36531        _hasLineOfSightPath = false;
 36532        _shouldMove = false;
 33
 36534        _isStuck = false;
 36535        _stuckFrameCount = 0;
 36536        _repathTries = 0;
 37
 36538        _isAtDestination = false;
 39
 36540        _currentQuery = null;
 36541        _pendingCommittedAreaPolicy = null;
 36542        _navigationGuideLease = null;
 36543        _navigationFlowFieldLease = null;
 36544        _requestedDestination = Vector3d.Zero;
 36545        _movementGroupSession.Reset();
 36546        _movementGroupMode = MovementGroupTravelMode.None;
 36547    }
 48
 49    internal void Reset()
 50    {
 2351        ReleaseNavigationGuidance();
 2352        OnInitialize();
 2353    }
 54
 55    private TrailblazerWorldContext ResolveContext()
 56    {
 907857        TrailblazerWorldContext context = _context!;
 907858        TrailblazerWorldContext.ThrowIfUnusable(context);
 907859        return context;
 60    }
 61
 802762    private MovementGroupCoordinatorState MovementGroups => ResolveContext().Navigation.MovementGroups;
 63
 64    private void RemoveMovementGroupSession()
 65    {
 58966        ResolveContext().Navigation.MovementGroups.Remove(_movementGroupSession);
 58967    }
 68
 3969    private int StuckFrameThresholdForContext => ResolveFrameRate() / 4;
 70
 371    private int AutoPauseStopTimeForContext => ResolveFrameRate() / 8;
 72
 73    private int ResolveFrameRate()
 74    {
 4275        return ResolveContext().FrameRate;
 76    }
 77
 78    /// <summary>
 79    /// Called every simulation step to handle agent steering and movement logic.
 80    /// </summary>
 81    public virtual Vector3d GetHeading(
 82        ISteer vessel,
 83        out NavigationTransitionInstruction? pendingTransition)
 84    {
 15185        pendingTransition = null;
 15186        CacheOwner(vessel);
 87
 15188        if (!CanMove)
 189            return Vector3d.Zero;
 90
 15091        if (!ShouldMove || IsAtDestination)
 1792            return FinalizeIdleHeading(vessel.Speed);
 93
 13394        if (!TryEnsureCurrentRequest(out Vector3d heading))
 195            return heading;
 96
 13297        Vector3d pathPosition = vessel.Position
 13298            + Vector3d.Down * vessel.BodyShape.RootToFootOffsetY;
 13299        UpdateMovementGroupState(vessel.Position, vessel.Radius);
 100
 132101        if (!TryPrepareMovementPathForHeading(pathPosition))
 4102            return Vector3d.Zero;
 103
 128104        UpdateTargetDirection(
 128105            vessel,
 128106            pathPosition,
 128107            out pendingTransition,
 128108            out bool isAStarTransitionApproach);
 128109        if (!ShouldMove || IsAtDestination)
 4110            return Vector3d.Zero;
 124111        if (_currentQuery.HasValue && _shouldRequestPathThisFrame)
 7112            return FinalizeHeadingFrame();
 113
 117114        if (ShouldArriveWithoutNavigationGuidance())
 115        {
 4116            Arrive();
 4117            return Vector3d.Zero;
 118        }
 119
 113120        if (!CheckStuckStatus(vessel.Position, vessel.Speed, vessel.StuckThresholdSpeed))
 121        {
 2122            TrailblazerLogger.DebugChannel.Info($"Stuck agent arriving!");
 2123            Arrive();
 2124            return Vector3d.Zero;
 125        }
 126
 111127        if (UpdateNavigationGuidanceProgress(
 111128                vessel.Acceleration,
 111129                vessel.Speed,
 111130                isAStarTransitionApproach))
 131        {
 1132            Arrive();
 1133            return Vector3d.Zero;
 134        }
 110135        return FinalizeHeadingFrame();
 136    }
 137
 138    /// <summary>
 139    /// Periodically called to initiate a pathfinding query based on the current position and destination.
 140    /// Note: This runs once on the next simulation after a guided trek request is applied.
 141    /// </summary>
 142    protected virtual bool ValidateMovementPath(Vector3d origin)
 102143        => ValidateGraphMovementPath(origin);
 144
 145    private bool ValidateGraphMovementPath(Vector3d origin)
 146    {
 102147        if (_hasLineOfSightPath)
 2148            return true;
 100149        if (_currentQuery!.Value.Algorithm == PathAlgorithm.FlowField)
 34150            return ValidateGraphFlowMovementPath(origin);
 151
 66152        if (_navigationGuideLease?.Status is NavigationGuideStatus.Stale
 66153            or NavigationGuideStatus.CapacityExceeded)
 2154            PreparePathRetry();
 155
 66156        if (!_shouldRequestPathThisFrame)
 18157            return true;
 158
 48159        _shouldRequestPathThisFrame = false;
 48160        PathQuery currentQuery = _currentQuery!.Value;
 48161        PathQuery query = currentQuery.WithStartState(
 48162            origin,
 48163            currentQuery.Traversal.StartMedium);
 48164        _currentQuery = query;
 48165        _requestedDestination = query.End.Position;
 48166        _destination = _requestedDestination;
 48167        if (TryHandleInitialGraphDirectTravel(query, origin))
 6168            return true;
 42169        ReleaseNavigationGuidance();
 42170        _pathCheckCooldown = PathRecheckCooldownFrames;
 171
 42172        NavigationGuideStatus status = ResolveContext().Guides.RequestGuide(
 42173            query,
 42174            out NavigationGuideLease? lease);
 42175        if (status != NavigationGuideStatus.Success)
 176        {
 4177            if (status is NavigationGuideStatus.Stale
 4178                or NavigationGuideStatus.CapacityExceeded)
 179            {
 3180                PreparePathRetry();
 3181                return true;
 182            }
 183
 1184            return false;
 185        }
 186
 38187        _navigationGuideLease = lease!.Value;
 38188        return true;
 189    }
 190
 191    private bool ValidateGraphFlowMovementPath(Vector3d origin)
 192    {
 34193        PathQuery currentQuery = _currentQuery!.Value;
 34194        NavigationGuideStatus currentStatus = _navigationFlowFieldLease?.Status
 34195            ?? NavigationGuideStatus.Stale;
 34196        if (_navigationFlowFieldLease != null
 34197            && currentStatus is NavigationGuideStatus.Stale
 34198                or NavigationGuideStatus.CapacityExceeded)
 199        {
 1200            PreparePathRetry();
 201        }
 202
 34203        if (!_shouldRequestPathThisFrame)
 9204            return currentStatus == NavigationGuideStatus.Success;
 205
 25206        _shouldRequestPathThisFrame = false;
 25207        PathQuery query = currentQuery.WithStartState(
 25208            origin,
 25209            currentQuery.Traversal.StartMedium);
 25210        _currentQuery = query;
 25211        _requestedDestination = query.End.Position;
 25212        _destination = _requestedDestination;
 25213        if (TryHandleInitialGraphDirectTravel(query, origin))
 1214            return true;
 24215        ReleaseNavigationGuidance();
 24216        _pathCheckCooldown = PathRecheckCooldownFrames;
 217
 24218        NavigationGuideStatus status = ResolveContext().Guides.RequestFlowField(
 24219            query,
 24220            out NavigationFlowFieldLease? lease);
 24221        if (status != NavigationGuideStatus.Success)
 222        {
 3223            if (status is NavigationGuideStatus.Stale
 3224                or NavigationGuideStatus.CapacityExceeded)
 225            {
 2226                PreparePathRetry();
 2227                return true;
 228            }
 229
 1230            return false;
 231        }
 232
 21233        _navigationFlowFieldLease = lease!.Value;
 21234        return true;
 235    }
 236
 237    private bool TryHandleInitialGraphDirectTravel(PathQuery query, Vector3d origin)
 238    {
 73239        if ((query.Traversal.TargetMedia
 73240                & NavigationCell.ToMedia(query.Traversal.StartMedium)) == 0)
 241        {
 19242            _hasLineOfSightPath = false;
 19243            return false;
 244        }
 54245        NavigationRayStatus status = ResolveContext().Guides.TryGetDirectHeading(
 54246            query,
 54247            origin,
 54248            out _);
 54249        if (status == NavigationRayStatus.Success)
 250        {
 6251            _hasLineOfSightPath = true;
 6252            ReleaseNavigationGuidance();
 6253            _pathCheckCooldown = PathRecheckCooldownFrames;
 6254            return true;
 255        }
 256
 48257        _hasLineOfSightPath = false;
 48258        if (status != NavigationRayStatus.Stale)
 47259            return false;
 1260        PreparePathRetry();
 1261        return true;
 262    }
 263
 264    /// <summary>
 265    /// Computes the steering direction toward the destination or along the path.
 266    /// </summary>
 267    protected virtual Vector3d FindTargetDirection(
 268        Vector3d position,
 269        out NavigationTransitionInstruction? pendingTransition)
 270    {
 104271        pendingTransition = null;
 104272        Vector3d targetDirection = Vector3d.Zero;
 104273        if (HasLineOfSightPath)
 11274            targetDirection = Destination - position;
 93275        else if (_navigationFlowFieldLease is NavigationFlowFieldLease flowGuide)
 276        {
 30277            NavigationGuideStatus status = flowGuide.TrySample(
 30278                position,
 30279                ResolveContext().Settings.GuideSampleBudget,
 30280                out NavigationFlowSample sample);
 30281            targetDirection = sample.Heading;
 30282            if (status is NavigationGuideStatus.Stale
 30283                or NavigationGuideStatus.CapacityExceeded)
 284            {
 1285                PreparePathRetry();
 1286                return Vector3d.Zero;
 287            }
 29288            if (status == NavigationGuideStatus.BudgetExceeded)
 2289                return Vector3d.Zero;
 27290            if (status == NavigationGuideStatus.LocalRecoveryRequired)
 3291                return Vector3d.Zero;
 24292            if (sample.HasTransition)
 293            {
 3294                pendingTransition = sample.Transition;
 3295                return Vector3d.Zero;
 296            }
 21297            if (targetDirection == Vector3d.Zero)
 298            {
 3299                Arrive();
 3300                return Vector3d.Zero;
 301            }
 302        }
 63303        else if (_navigationGuideLease != null)
 304        {
 55305            NavigationGuideLease guide = _navigationGuideLease.Value;
 55306            guide.TryGetCurrentStep(out NavigationGuideStep step);
 307
 55308            if (step.HasTransition)
 309            {
 6310                targetDirection = step.Position - position;
 6311                if (targetDirection == Vector3d.Zero)
 312                {
 4313                    pendingTransition = step.Transition;
 4314                    return Vector3d.Zero;
 315                }
 316            }
 317            else
 318            {
 49319                targetDirection = step.Position - position;
 320            }
 51321            if (targetDirection == Vector3d.Zero)
 322            {
 43323                if (IsAtFinalWaypoint(guide))
 324                {
 3325                    ReleaseNavigationGuidance();
 3326                    return Vector3d.Zero;
 327                }
 328
 40329                guide.TryAdvanceStep();
 40330                guide.TryGetCurrentStep(out step);
 331
 40332                if (step.HasTransition)
 333                {
 16334                    targetDirection = step.Position - position;
 16335                    if (targetDirection == Vector3d.Zero)
 336                    {
 14337                        pendingTransition = step.Transition;
 14338                        return Vector3d.Zero;
 339                    }
 340                }
 341                else
 342                {
 24343                    targetDirection = step.Position - position;
 344                }
 345            }
 346        }
 71347        if (targetDirection == Vector3d.Zero)
 348        {
 9349            TrailblazerLogger.DebugChannel.Info($"No viable movement direction found.");
 9350            return Vector3d.Zero;
 351        }
 352
 353        // This is now the direction we want to be travelling in
 62354        return targetDirection.NormalizeInPlace(out _distanceToTarget);
 355    }
 356
 357    /// <summary>
 358    /// Returns true if the agent passed a step within its arrival radius,
 359    /// or entered the explicit ordinary-step tolerance.
 360    /// </summary>
 361    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 362    public bool ShouldAdvanceToNextWaypoint()
 363    {
 33364        Fixed64 arrivalRadius = _currentQuery?.Agent.ArrivalRadius ?? Fixed64.Zero;
 33365        return (_distanceToTarget <= arrivalRadius
 33366                    && Vector3d.Dot(TargetDirection, LastTargetDirection) < Fixed64.Zero)
 33367            || _distanceToTarget <= WaypointTolerance;
 368    }
 369
 370    /// <summary>
 371    /// Evaluates the agent's current movement direction and velocity, updating stuck and arrival state.
 372    /// </summary>
 373    protected virtual bool CheckStuckStatus(
 374        Vector3d position,
 375        Fixed64 speed,
 376        Fixed64 stuckThreshold)
 377    {
 158378        if (!CanAutoStop)
 32379            return true;
 380
 126381        if (stuckThreshold <= Fixed64.Zero || speed >= stuckThreshold)
 87382            return ResetStuckStatus();
 383
 39384        _stuckFrameCount++;
 39385        if (_stuckFrameCount <= StuckFrameThresholdForContext)
 24386            return true;
 387
 15388        return _repathTries < StuckRepathTries
 15389            ? TryRecoverFromStuck(position)
 15390            : DeclareHardStuck();
 391    }
 392
 393    private Vector3d FinalizeIdleHeading(Fixed64 speed)
 394    {
 17395        _targetDirection = Vector3d.Zero;
 17396        if (speed <= Fixed64.Epsilon)
 16397            _stoppedFrameCount++;
 398
 17399        return FinalizeHeadingFrame();
 400    }
 401
 402    private bool TryEnsureCurrentRequest(out Vector3d heading)
 403    {
 133404        if (_currentQuery.HasValue)
 405        {
 132406            heading = Vector3d.Zero;
 132407            return true;
 408        }
 409
 1410        Arrive();
 1411        heading = TargetDirection;
 1412        return false;
 413    }
 414
 415    private bool TryPrepareMovementPathForHeading(Vector3d position)
 416    {
 132417        if (CanPathfind && !ValidateMovementPath(position))
 418        {
 4419            HandleInvalidPath("Invalid path detected!");
 4420            return false;
 421        }
 422
 128423        RefreshLineOfSightState(position);
 128424        return true;
 425    }
 426
 427    private void RefreshLineOfSightState(Vector3d position)
 428    {
 128429        if (_pathCheckCooldown > 0)
 123430            return;
 431
 5432        if (_currentQuery is not PathQuery currentQuery)
 1433            return;
 4434        bool wasDirect = _hasLineOfSightPath;
 4435        PathQuery query = currentQuery.WithStartState(
 4436            position,
 4437            currentQuery.Traversal.StartMedium);
 4438        if ((query.Traversal.TargetMedia
 4439                & NavigationCell.ToMedia(query.Traversal.StartMedium)) == 0)
 440        {
 1441            _hasLineOfSightPath = false;
 1442            _pathCheckCooldown = PathRecheckCooldownFrames;
 1443            return;
 444        }
 3445        NavigationRayStatus status = ResolveContext().Guides.TryGetDirectHeading(
 3446            query,
 3447            position,
 3448            out _);
 3449        if (status == NavigationRayStatus.Success)
 450        {
 1451            _currentQuery = query;
 1452            _hasLineOfSightPath = true;
 1453            ReleaseNavigationGuidance();
 454        }
 2455        else if (status == NavigationRayStatus.Stale || wasDirect)
 456        {
 1457            _hasLineOfSightPath = false;
 1458            PreparePathRetry();
 459        }
 3460        _pathCheckCooldown = PathRecheckCooldownFrames;
 3461    }
 462
 463    private void HandleInvalidPath(string debugMessage)
 464    {
 4465        TrailblazerLogger.DebugChannel.Info($"{debugMessage}");
 4466        Events.OnInvalidPath?.Invoke();
 4467        Arrive();
 4468    }
 469
 470    private void UpdateTargetDirection(
 471        ISteer vessel,
 472        Vector3d pathPosition,
 473        out NavigationTransitionInstruction? pendingTransition,
 474        out bool isAStarTransitionApproach)
 475    {
 128476        _lastTargetDirection = _targetDirection;
 128477        _targetDirection = FindTargetDirection(
 128478            pathPosition,
 128479            out pendingTransition);
 128480        isAStarTransitionApproach = pendingTransition == null
 128481            && IsCurrentAStarTransitionStep();
 128482        if (_targetDirection == Vector3d.Zero)
 41483            return;
 87484        _targetDirection += ComputeCombinedSteering(
 87485            vessel.Position,
 87486            vessel.Velocity,
 87487            vessel.Speed,
 87488            vessel.Radius,
 87489            vessel.GlobalId);
 87490    }
 491
 492    private bool IsCurrentAStarTransitionStep()
 493    {
 107494        if (_navigationGuideLease is not NavigationGuideLease guide)
 73495            return false;
 496
 34497        guide.TryGetCurrentStep(out NavigationGuideStep step);
 34498        return step.HasTransition;
 499    }
 500
 501    private bool ShouldArriveWithoutNavigationGuidance()
 502    {
 117503        if (HasNavigationGuidance)
 78504            return false;
 505
 39506        Fixed64 moveAmount = FixedMath.Clamp01(TargetDirection.Magnitude);
 39507        bool reachedTarget = _distanceToTarget <= _currentQuery!.Value.Agent.ArrivalRadius;
 39508        bool noInput = moveAmount == Fixed64.Zero;
 39509        return reachedTarget || (!IsStuck && noInput);
 510    }
 511
 512    private bool UpdateNavigationGuidanceProgress(
 513        Vector3d acceleration,
 514        Fixed64 speed,
 515        bool isAStarTransitionApproach)
 516    {
 111517        if (TargetDirection == Vector3d.Zero)
 34518            return false;
 519
 77520        if (!isAStarTransitionApproach && _navigationGuideLease is NavigationGuideLease currentGuide)
 521        {
 30522            if (IsAtFinalWaypoint(currentGuide))
 523            {
 2524                if (_distanceToTarget <= _currentQuery!.Value.Agent.ArrivalRadius)
 525                {
 1526                    ReleaseNavigationGuidance();
 1527                    return true;
 528                }
 529            }
 28530            else if (ShouldAdvanceToNextWaypoint())
 531            {
 27532                currentGuide.TryAdvanceStep();
 533            }
 534        }
 535
 76536        if (HasNavigationGuidance)
 51537            SetDeceleration(acceleration, speed);
 538
 76539        return false;
 540    }
 541
 542    private static bool IsAtFinalWaypoint(NavigationGuideLease guide) =>
 73543        guide.CurrentStepIndex == guide.StepCount - 1;
 544
 545    internal NavigationGuideStatus CompletePendingTransition(
 546        in NavigationTransitionInstruction instruction)
 547    {
 9548        PathQuery query = _currentQuery!.Value;
 9549        NavigationGuideStatus status = query.Algorithm == PathAlgorithm.AStar
 9550            ? _navigationGuideLease!.Value.CompletePendingTransition(instruction)
 9551            : _navigationFlowFieldLease!.Value.CompletePendingTransition(instruction);
 9552        if (status == NavigationGuideStatus.Success)
 553        {
 5554            _currentQuery = query.WithStartState(
 5555                query.Start.Position,
 5556                instruction.DestinationMedium);
 557        }
 9558        return status;
 559    }
 560
 561    private Vector3d FinalizeHeadingFrame()
 562    {
 134563        _autoStopFrameCount--;
 134564        _pathCheckCooldown--;
 565
 134566        Events.OnStartTraversal?.Invoke(TargetDirection);
 134567        return TargetDirection;
 568    }
 569
 570    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 571    private bool ResetStuckStatus()
 572    {
 87573        _isStuck = false;
 87574        _stuckFrameCount = 0;
 87575        _repathTries = 0;
 87576        return true;
 577    }
 578
 579    private bool TryRecoverFromStuck(Vector3d position)
 580    {
 12581        _hasLineOfSightPath = false;
 582
 12583        if (IsInGroup)
 1584            LeaveMovementGroup();
 585
 12586        PreparePathRetry();
 12587        _repathTries++;
 12588        return true;
 589    }
 590
 591    private void PreparePathRetry()
 592    {
 23593        _targetDirection = Vector3d.Zero;
 23594        _shouldRequestPathThisFrame = true;
 23595        DisposeCurrentNavigationGuidance();
 23596    }
 597
 598    private bool DeclareHardStuck()
 599    {
 3600        _isStuck = true;
 3601        DisposeCurrentNavigationGuidance();
 3602        Events.OnIsStuck?.Invoke();
 3603        return false;
 604    }
 605
 606    private void DisposeCurrentNavigationGuidance()
 607    {
 26608        ReleaseNavigationGuidance();
 26609    }
 610
 611    /// <summary>
 612    /// Adjusts the target direction to decelerate the object as it approaches its destination based on the specified
 613    /// acceleration and current speed.
 614    /// </summary>
 615    /// <remarks>
 616    /// This method is intended to be overridden in derived classes to customize deceleration behavior.
 617    /// It modulates the target direction to ensure smooth slowing as the object nears its target.
 618    /// </remarks>
 619    /// <param name="acceleration">
 620    /// The acceleration vector used to determine the deceleration rate.
 621    /// If the vector is zero, a default braking power is used.
 622    /// </param>
 623    /// <param name="speed">The current speed of the object, used to calculate the distance required to slow down.</para
 624    protected virtual void SetDeceleration(Vector3d acceleration, Fixed64 speed)
 625    {
 626        // Scaling direction before passing to the motor lets us
 627        // modulate movement before acceleration is applied
 56628        Fixed64 deceleration = acceleration != Vector3d.Zero
 56629            ? acceleration.Magnitude
 56630            : BrakingPower;
 56631        Fixed64 slowDistance = speed / deceleration;
 56632        if (DistanceToTarget > Fixed64.Epsilon && DistanceToTarget <= slowDistance)
 633        {
 3634            Fixed64 closingSpeed = DistanceToTarget / slowDistance;
 3635            _targetDirection *= closingSpeed; // reduce magnitude = slow down
 636        }
 56637    }
 638
 639    /// <summary>
 640    /// Triggers the arrival event and resets internal movement tracking.
 641    /// </summary>
 642    public void Arrive()
 643    {
 22644        NavigationAreaPolicyKey? areaPolicy = _currentQuery?.AreaPolicy;
 22645        StopMove();
 22646        _pendingCommittedAreaPolicy = areaPolicy;
 647
 22648        _distanceToTarget = Fixed64.Zero;
 22649        _isAtDestination = true;
 22650        _targetDirection = Vector3d.Zero;
 651
 22652        Events.OnArrive?.Invoke();
 6653    }
 654
 655    /// <summary>
 656    /// Resets the movement and pathfinding logic, halting the agent.
 657    /// </summary>
 658    public virtual void StopMove()
 659    {
 53660        bool wasMoving = _shouldMove;
 53661        _pendingCommittedAreaPolicy = null;
 53662        ReleaseNavigationGuidance();
 53663        _pendingTransitionOwner?.NotifySteeringSessionEnded();
 53664        _currentQuery = null;
 53665        _requestedDestination = Vector3d.Zero;
 53666        _destination = Vector3d.Zero;
 53667        _shouldMove = false;
 53668        _shouldRequestPathThisFrame = false;
 53669        _hasLineOfSightPath = false;
 53670        LeaveMovementGroup();
 53671        if (!wasMoving)
 17672            return;
 673
 36674        _autoStopFrameCount = 0;
 36675        _stuckFrameCount = 0;
 36676        _stoppedFrameCount = 0;
 677
 36678        Events.OnStopMove?.Invoke();
 2679    }
 680
 681    #endregion
 682}

Methods/Properties

.cctor()
.ctor(Trailblazer.TrailblazerWorldContext)
get_Destination()
get_TargetDirection()
get_LastTargetDirection()
get_CurrentQuery()
get_AreaPolicyForCommit()
ConsumePendingCommittedAreaPolicy()
get_ShouldMove()
get_IsStuck()
get_HasLineOfSightPath()
get_DistanceToTarget()
get_HasNavigationGuidance()
get_PendingTransitionGuideStatus()
get_IsAtDestination()
BindPendingTransitionOwner(Trailblazer.Navigation.Navigator)
UnbindPendingTransitionOwner(Trailblazer.Navigation.Navigator)
get_CanAutoStop()
get_WaypointTolerance()
set_WaypointTolerance(FixedMathSharp.Fixed64)
BindContext(Trailblazer.TrailblazerWorldContext)
get_MovementGroupID()
set_MovementGroupID(System.Int32)
get_IsInGroup()
ComputeCombinedSteering(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Guid)
CacheOwner(Trailblazer.Navigation.ISteer)
UpdateMovementGroupState(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,System.Boolean)
IsGroupNeighbor(System.Guid,System.Int32)
ApplyPathQuery(Trailblazer.Pathing.PathQuery,System.Int32)
BeginPathSession(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,System.Int32,Trailblazer.Pathing.PathQuery)
PauseAutoStop()
AddToMovementGroup(System.Int32)
LeaveMovementGroup()
PrewarmMovementGroup(Trailblazer.Navigation.ISteer)
RecordData(Chronicler.IChronicler)
RestoreQueryFromLoadedSession(System.Nullable`1<Trailblazer.Pathing.PathQuery>)
ReleaseNavigationGuidance()
ResetMovementGroupSession()
OnInitialize()
Reset()
ResolveContext()
get_MovementGroups()
RemoveMovementGroupSession()
get_StuckFrameThresholdForContext()
get_AutoPauseStopTimeForContext()
ResolveFrameRate()
GetHeading(Trailblazer.Navigation.ISteer,System.Nullable`1<Trailblazer.Pathing.NavigationTransitionInstruction>&)
ValidateMovementPath(FixedMathSharp.Vector3d)
ValidateGraphMovementPath(FixedMathSharp.Vector3d)
ValidateGraphFlowMovementPath(FixedMathSharp.Vector3d)
TryHandleInitialGraphDirectTravel(Trailblazer.Pathing.PathQuery,FixedMathSharp.Vector3d)
FindTargetDirection(FixedMathSharp.Vector3d,System.Nullable`1<Trailblazer.Pathing.NavigationTransitionInstruction>&)
ShouldAdvanceToNextWaypoint()
CheckStuckStatus(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
FinalizeIdleHeading(FixedMathSharp.Fixed64)
TryEnsureCurrentRequest(FixedMathSharp.Vector3d&)
TryPrepareMovementPathForHeading(FixedMathSharp.Vector3d)
RefreshLineOfSightState(FixedMathSharp.Vector3d)
HandleInvalidPath(System.String)
UpdateTargetDirection(Trailblazer.Navigation.ISteer,FixedMathSharp.Vector3d,System.Nullable`1<Trailblazer.Pathing.NavigationTransitionInstruction>&,System.Boolean&)
IsCurrentAStarTransitionStep()
ShouldArriveWithoutNavigationGuidance()
UpdateNavigationGuidanceProgress(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,System.Boolean)
IsAtFinalWaypoint(Trailblazer.Pathing.NavigationGuideLease)
CompletePendingTransition(Trailblazer.Pathing.NavigationTransitionInstruction&)
FinalizeHeadingFrame()
ResetStuckStatus()
TryRecoverFromStuck(FixedMathSharp.Vector3d)
PreparePathRetry()
DeclareHardStuck()
DisposeCurrentNavigationGuidance()
SetDeceleration(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64)
Arrive()
StopMove()