< Summary

Line coverage
98%
Covered lines: 446
Uncovered lines: 9
Coverable lines: 455
Total lines: 1320
Line coverage: 98%
Branch coverage
88%
Covered branches: 171
Total branches: 194
Branch coverage: 88.1%
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_Position()100%11100%
File 1: get_LastPosition()100%11100%
File 1: get_Rotation()100%11100%
File 1: get_Velocity()100%11100%
File 1: get_Speed()100%11100%
File 1: get_Acceleration()100%11100%
File 1: get_StuckThresholdSpeed()100%11100%
File 1: get_IsActive()100%22100%
File 1: get_Context()100%11100%
File 1: get_Steering()100%11100%
File 1: get_Turning()100%11100%
File 1: get_Motor()100%11100%
File 1: get_IsGuideded()100%11100%
File 1: get_Size()100%11100%
File 1: get_Radius()100%11100%
File 1: get_FootPositionAdjust()100%11100%
File 1: get_HeightmapGrounding()100%11100%
File 1: get_IsLockedOn()100%11100%
File 1: get_GuidedPathMode()100%11100%
File 1: get_GuidedAllowUnwalkableEndpoints()100%11100%
File 1: get_GuidedAllowTraversalTransitions()100%11100%
File 1: get_GuidedMaxClimbHeight()100%11100%
File 1: get_GuidedAStarHeuristic()100%11100%
File 1: get_GuidedFlowFieldExtraFloodRange()100%11100%
File 1: get_GlobalId()100%11100%
File 1: get_OccupantGroupId()100%11100%
File 1: .ctor(...)100%11100%
File 1: BindContext(...)50%161270%
File 1: Activate(...)100%11100%
File 1: Activate(...)100%11100%
File 1: Setup(...)100%11100%
File 1: Setup(...)100%1414100%
File 1: Initialize(...)100%11100%
File 1: CreateLocomotionProfile()100%11100%
File 1: Reset()50%88100%
File 1: PrewarmMovementGroup()100%22100%
File 1: ApplyInputTrekRequest(...)100%44100%
File 1: ApplyGuidedTrekRequest(...)87.5%88100%
File 1: ConfigureForGuidedTraversal(...)100%1212100%
File 1: ConfigureHeightmapGrounding(...)100%11100%
File 1: TryCreateGuidedPathRequest(...)100%66100%
File 1: SetFrameJumpAffordability(...)100%11100%
File 1: ToggleGuidedJump(...)100%11100%
File 1: ToggleGuidedFlight(...)100%11100%
File 1: ToggleGuidedSwim(...)100%11100%
File 1: ToggleGuidedClimb(...)100%11100%
File 1: SetGuidedTrekRate(...)100%11100%
File 1: TryGetTurnDirection(...)100%1010100%
File 1: Simulate()91.66%1212100%
File 1: CommitFrameMotion()100%1414100%
File 1: NotifyCollision()100%22100%
File 1: SetGroundContact(...)100%22100%
File 1: SetAirborne(...)100%11100%
File 1: SetWaterContact(...)100%11100%
File 1: SetTrekCondition(...)100%1212100%
File 1: SyncCurrentTrekConditionToMotor()50%2275%
File 1: ReplaceTrekCondition(...)83.33%6683.33%
File 1: AddPositionDelta(...)100%22100%
File 1: ApplyRotationDelta(...)100%22100%
File 1: AddVelocityDelta(...)100%22100%
File 1: GetFootPosition()100%11100%
File 1: GenerateGUID()100%11100%
File 1: EnsureContextForSetup()100%22100%
File 1: RequireContext()50%2260%
File 1: CaptureGuidedRouteTopologyVersion()100%11100%
File 1: CheckVoxelOccupancy(...)100%11100%
File 2: TryApplyHeightmapGrounding(...)91.66%1212100%
File 2: GetHeightmapGroundingQueryPosition()100%11100%
File 2: TryProjectRootToHeightmapSample(...)100%66100%
File 2: ProjectRootWithoutVelocity(...)50%2285.71%
File 2: UpdateVoxelOccupancyAfterRootProjection(...)50%6680%
File 3: RecordData(...)90%3030100%

File(s)

/home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Navigation/Navigator/Navigator.cs

#LineLine coverage
 1using Chronicler;
 2using FixedMathSharp;
 3using GridForge.Grids;
 4using System;
 5using System.Runtime.CompilerServices;
 6using Trailblazer.Navigation.Motor;
 7using Trailblazer.Navigation.Steering;
 8using Trailblazer.Navigation.Turning;
 9using Trailblazer.Pathing;
 10
 11namespace Trailblazer.Navigation;
 12
 13/// <summary>
 14/// Base class representing a object, responsible for handling movement, traversal state, and simulation flow.
 15/// </summary>
 16/// <remarks>
 17/// This class acts as a bridge between the simulation logic and the entity's external representation.
 18/// It defines common traversal behaviors and lifecycle methods that can be extended by concrete implementations.
 19/// </remarks>
 20[Serializable]
 21public abstract partial class Navigator : INavigate, IRecordable
 22{
 23    #region Constants
 24
 25    /// <summary>
 26    /// Default vertical offset used to determine the object’s contact point with the ground.
 27    /// </summary>
 128    public static readonly Fixed64 DefaultFootPositionAdjust = new(0.25f);
 29
 30    #endregion
 31
 32    #region Fields
 33
 34    /// <inheritdoc cref="Position"/>
 35    protected Vector3d _position;
 36
 37    /// <inheritdoc cref="LastPosition"/>
 38    protected Vector3d _lastPosition;
 39
 40    /// <inheritdoc cref="Rotation"/>
 16241    protected FixedQuaternion _rotation = FixedQuaternion.Identity;
 42
 43    /// <inheritdoc cref="Velocity"/>
 44    protected Vector3d _velocity;
 45
 46    /// <inheritdoc cref="Speed"/>
 47    protected Fixed64 _speed;
 48
 49    /// <inheritdoc cref="Acceleration"/>
 50    protected Vector3d _acceleration;
 51
 52    /// <inheritdoc cref="Size"/>
 16253    protected Fixed64 _size = Fixed64.One;
 54
 55    /// <summary>
 56    /// Adjustment factor for the foot position, used to determine ground contact points.
 57    /// </summary>
 16258    protected Fixed64 _footPositionAdjust = DefaultFootPositionAdjust;
 59
 60    private SolidPathAlgorithm _guidedPathMode = SolidPathAlgorithm.AStar;
 61
 62    private bool _guidedAllowUnwalkableEndpoints;
 63
 64    private bool _guidedAllowTraversalTransitions;
 65
 16266    private Fixed64 _guidedMaxClimbHeight = Fixed64.One;
 67
 68    private HeuristicMethod _guidedAStarHeuristic = HeuristicMethod.Manhattan;
 69
 16270    private int _guidedFlowFieldExtraFloodRange = FlowFieldPathRequest.DefaultExtraFloodRange;
 71
 72    /// <summary>
 73    /// Stable runtime identity used for occupancy and steering coordination.
 74    /// </summary>
 75    /// <remarks>
 76    /// By default this is allocated deterministically from object setup order.
 77    /// Hosts can override it during <see cref="Setup(Vector3d, FixedQuaternion?, Vector3d?, Fixed64?, Guid?)"/>
 78    /// when a broader simulation stack already owns stable agent ids.
 79    /// </remarks>
 80    protected Guid _globalId;
 81
 16282    private byte _occupantGroupId = 1;
 83
 84    private bool _isLockedOn;
 85
 86    /// <summary>
 87    /// The controller responsible for managing the object's desired movement direction.
 88    /// </summary>
 89    protected NavSteering? _steering;
 90
 91    /// <summary>
 92    /// The controller responsible for managing the object's rotation towards the movement direction.
 93    /// </summary>
 94    protected NavTurning? _turning;
 95
 96    /// <summary>
 97    /// The controller responsible for managing the object's movement and physics interactions.
 98    /// </summary>
 99    protected NavMotor? _motor;
 100
 101    private Fixed64 _stuckThresholdSpeed;
 102
 103    private bool _isGuideded;
 104
 105    /// <summary>
 106    /// The change in position to apply during the current simulation frame.
 107    /// </summary>
 108    protected Vector3d _positionDelta;
 109
 110    /// <summary>
 111    /// The change in rotation to apply during the current simulation frame.
 112    /// </summary>
 162113    protected FixedQuaternion _rotationDelta = FixedQuaternion.Identity;
 114
 115    /// <summary>
 116    /// The velocity change computed during the simulation frame.
 117    /// </summary>
 118    protected Vector3d _velocityDelta;
 119
 120    /// <summary>
 121    /// Indicates whether the Navigator has been set.
 122    /// </summary>
 123    protected bool _isSet;
 124
 125    /// <summary>
 126    /// Indicates whether the Navigator has been initialized.
 127    /// </summary>
 128    protected bool _isInitialized;
 129
 130    /// <inheritdoc cref="TrekCondition"/>
 162131    protected TrekCondition _frameCondition = new();
 132
 133    /// <inheritdoc cref="TrekRequest"/>
 162134    protected TrekRequest _frameRequest = new();
 135
 136    private GuidedVolumeExitHandoff? _pendingGuidedVolumeExitHandoff;
 137
 138    private bool _guidedClimbIntent;
 139
 140    private GuidedClimbIntentMode _guidedClimbIntentMode;
 141
 142    private int _lastSeenGuidedRouteTopologyVersion;
 143
 144    private TrailblazerWorldContext? _context;
 145
 162146    private NavigatorHeightmapGroundingSettings _heightmapGrounding = new();
 147
 148    #endregion
 149
 150    #region State - Identity / Transform
 151
 152    /// <inheritdoc/>
 690153    public Vector3d Position => _position;
 154
 155    /// <inheritdoc/>
 221156    public Vector3d LastPosition => _lastPosition;
 157
 158    /// <inheritdoc/>
 150159    public FixedQuaternion Rotation => _rotation;
 160
 161    /// <inheritdoc/>
 162    public Vector3d Forward { get; protected set; }
 163
 164    /// <inheritdoc/>
 195165    public Vector3d Velocity => _velocity;
 166
 167    /// <inheritdoc/>
 164168    public Fixed64 Speed => _speed;
 169
 170    /// <inheritdoc/>
 57171    public Vector3d Acceleration => _acceleration;
 172
 173    /// <summary>
 174    /// Minimum velocity threshold used to determine if the object is considered stuck.
 175    /// </summary>
 52176    public Fixed64 StuckThresholdSpeed => _stuckThresholdSpeed;
 177
 178    /// <summary>
 179    /// Indicates whether the Navigator is currently active and ready for simulation.
 180    /// </summary>
 265181    public bool IsActive => _isSet && _isInitialized;
 182
 183    /// <summary>
 184    /// Gets the world context this navigator is bound to.
 185    /// </summary>
 3186    public TrailblazerWorldContext? Context => _context;
 187
 188    #endregion
 189
 190    #region State - Controllers
 191
 192    /// <summary>
 193    /// The controller responsible for managing the object's desired movement direction.
 194    /// </summary>
 358195    public NavSteering? Steering => _steering;
 196
 197    /// <summary>
 198    /// The controller responsible for managing the object's rotation towards the movement direction.
 199    /// </summary>
 93200    public NavTurning? Turning => _turning;
 201
 202    /// <summary>
 203    /// The controller responsible for managing the object's movement and physics interactions.
 204    /// </summary>
 160205    public NavMotor? Motor => _motor;
 206
 207    /// <summary>
 208    /// Indicates whether the current traversal session is guided via a TrailGuide path (e.g., A* or flow field).
 209    /// </summary>
 287210    public bool IsGuideded => _isGuideded;
 211
 212    #endregion
 213
 214    #region Settings
 215
 216    /// <inheritdoc/>
 530217    public Fixed64 Size => _size;
 218
 219    /// <inheritdoc/>
 417220    public Fixed64 Radius => Size * Fixed64.Half;
 221
 222    /// <inheritdoc cref="_footPositionAdjust"/>
 66223    public Fixed64 FootPositionAdjust { get => _footPositionAdjust; set => _footPositionAdjust = value; }
 224
 225    /// <summary>
 226    /// Gets the opt-in heightmap grounding settings owned by this navigator.
 227    /// </summary>
 23228    public NavigatorHeightmapGroundingSettings HeightmapGrounding => _heightmapGrounding;
 229
 230    /// <summary>
 231    /// Gets or sets a value indicating whether the object is currently locked on to a target.
 232    /// </summary>
 21233    public bool IsLockedOn { get => _isLockedOn; set => _isLockedOn = value; }
 234
 235    /// <summary>
 236    /// Path request mode used for guided travel.
 237    /// </summary>
 58238    public SolidPathAlgorithm GuidedPathMode => _guidedPathMode;
 239
 240    /// <summary>
 241    /// Whether object-built guided requests may target unwalkable voxels.
 242    /// </summary>
 62243    public bool GuidedAllowUnwalkableEndpoints => _guidedAllowUnwalkableEndpoints;
 244
 245    /// <summary>
 246    /// Whether object-built guided requests may use authored traversal transitions for chart fallback,
 247    /// bounded swim exits, or bounded aerial landing handoffs.
 248    /// </summary>
 62249    public bool GuidedAllowTraversalTransitions => _guidedAllowTraversalTransitions;
 250
 251    /// <summary>
 252    /// Default max climb height used when the object builds guided requests.
 253    /// </summary>
 62254    public Fixed64 GuidedMaxClimbHeight => _guidedMaxClimbHeight;
 255
 256    /// <summary>
 257    /// Default heuristic used when the object builds A* requests.
 258    /// </summary>
 59259    public HeuristicMethod GuidedAStarHeuristic => _guidedAStarHeuristic;
 260
 261    /// <summary>
 262    /// Default extra flood range used when the object builds flow-field requests.
 263    /// </summary>
 62264    public int GuidedFlowFieldExtraFloodRange => _guidedFlowFieldExtraFloodRange;
 265
 266    #endregion
 267
 268    #region Voxel Occupancy
 269
 270    /// <inheritdoc cref="_globalId"/>
 688271    public Guid GlobalId => _globalId;
 272
 273    /// <inheritdoc />
 10274    public byte OccupantGroupId { get => _occupantGroupId; set => _occupantGroupId = value; }
 275
 276    #endregion
 277
 278    #region Setup / Initialization
 279
 280    /// <summary>
 281    /// Initializes a new unbound navigator. Bind it to a context before setup.
 282    /// </summary>
 8283    protected Navigator() { }
 284
 285    /// <summary>
 286    /// Initializes a new navigator bound to the supplied world context.
 287    /// </summary>
 158288    protected Navigator(TrailblazerWorldContext context)
 289    {
 158290        BindContext(context);
 158291    }
 292
 293    /// <summary>
 294    /// Binds this navigator to a world context before setup.
 295    /// </summary>
 296    public virtual void BindContext(TrailblazerWorldContext context)
 297    {
 161298        PathRequestContextResolver.ThrowIfUnusable(context);
 299
 161300        if (ReferenceEquals(_context, context))
 0301            return;
 302
 161303        if (_isSet || _isInitialized)
 0304            throw new InvalidOperationException("Navigator context cannot be changed after setup. Call Reset() before re
 305
 161306        _context = context;
 161307        _steering?.BindContext(context);
 161308        _motor?.BindContext(context);
 161309        _turning?.BindContext(context);
 0310    }
 311
 312    /// <summary>
 313    /// Initializes and activates the object with the specified condition, position, and optional parameters.
 314    /// </summary>
 315    /// <param name="context">The world context that owns this navigator.</param>
 316    /// <param name="condition">The condition that determines how the object is initialized and activated.</param>
 317    /// <param name="position">The position in world coordinates where the object will be placed.</param>
 318    /// <param name="rotation">The optional rotation to apply to the object. If null, a default rotation is used.</param
 319    /// <param name="velocity">The optional initial velocity of the object. If null, the object is initialized with zero
 320    /// <param name="size">The optional size of the object. If null, a default size is used.</param>
 321    /// <param name="globalId">The optional global identifier for the object. If null, a new identifier may be generated
 322    public virtual void Activate(
 323        TrailblazerWorldContext context,
 324        TrekCondition condition,
 325        Vector3d position,
 326        FixedQuaternion? rotation = null,
 327        Vector3d? velocity = null,
 328        Fixed64? size = null,
 329        Guid? globalId = null)
 330    {
 1331        BindContext(context);
 1332        Activate(condition, position, rotation, velocity, size, globalId);
 1333    }
 334
 335    /// <summary>
 336    /// Initializes and activates the object with the specified condition, position, and optional parameters.
 337    /// </summary>
 338    /// <param name="condition">The condition that determines how the object is initialized and activated.</param>
 339    /// <param name="position">The position in world coordinates where the object will be placed.</param>
 340    /// <param name="rotation">The optional rotation to apply to the object. If null, a default rotation is used.</param
 341    /// <param name="velocity">The optional initial velocity of the object. If null, the object is initialized with zero
 342    /// <param name="size">The optional size of the object. If null, a default size is used.</param>
 343    /// <param name="globalId">The optional global identifier for the object. If null, a new identifier may be generated
 344    public virtual void Activate(
 345        TrekCondition condition,
 346        Vector3d position,
 347        FixedQuaternion? rotation = null,
 348        Vector3d? velocity = null,
 349        Fixed64? size = null,
 350        Guid? globalId = null)
 351    {
 9352        Setup(position, rotation, velocity, size, globalId);
 9353        Initialize(condition);
 9354    }
 355
 356    /// <summary>
 357    /// Sets the initial configuration of the object, including position, rotation, velocity, size, and optional stable 
 358    /// </summary>
 359    /// <param name="context">The world context that owns this navigator.</param>
 360    /// <param name="position">Initial world-space position.</param>
 361    /// <param name="rotation">Optional starting rotation.</param>
 362    /// <param name="velocity">Optional initial velocity.</param>
 363    /// <param name="size">Optional grid size (defaults to 1).</param>
 364    /// <param name="globalId">Optional host-provided stable identity. When omitted, Trailblazer assigns one determinist
 365    public virtual void Setup(
 366        TrailblazerWorldContext context,
 367        Vector3d position,
 368        FixedQuaternion? rotation = null,
 369        Vector3d? velocity = null,
 370        Fixed64? size = null,
 371        Guid? globalId = null)
 372    {
 1373        BindContext(context);
 1374        Setup(position, rotation, velocity, size, globalId);
 1375    }
 376
 377    /// <summary>
 378    /// Sets the initial configuration of the object, including position, rotation, velocity, size, and optional stable 
 379    /// </summary>
 380    /// <param name="position">Initial world-space position.</param>
 381    /// <param name="rotation">Optional starting rotation.</param>
 382    /// <param name="velocity">Optional initial velocity.</param>
 383    /// <param name="size">Optional grid size (defaults to 1).</param>
 384    /// <param name="globalId">Optional host-provided stable identity. When omitted, Trailblazer assigns one determinist
 385    public virtual void Setup(
 386        Vector3d position,
 387        FixedQuaternion? rotation = null,
 388        Vector3d? velocity = null,
 389        Fixed64? size = null,
 390        Guid? globalId = null)
 391    {
 157392        EnsureContextForSetup();
 393
 156394        if (globalId.HasValue && globalId.Value == Guid.Empty)
 1395            throw new ArgumentException("Navigator globalId cannot be Guid.Empty.", nameof(globalId));
 396
 155397        _globalId = globalId ?? GenerateGUID();
 398
 155399        _lastPosition = _position = position;
 155400        _rotation = rotation ?? FixedQuaternion.Identity;
 155401        if (_rotation != FixedQuaternion.Identity)
 82402            Forward = _rotation.Rotate(Vector3d.Forward);
 403        else
 73404            Forward = Vector3d.Forward;
 155405        _velocity = velocity ?? Vector3d.Zero;
 155406        _size = size ?? Fixed64.One;
 407
 155408        _isSet = true;
 155409    }
 410
 411    /// <summary>
 412    /// Initializes the object by setting up its defaults, events, traversal state, and movement controller.
 413    /// </summary>
 414    public virtual void Initialize(TrekCondition condition)
 415    {
 139416        TrailblazerWorldContext context = RequireContext();
 417
 139418        _frameCondition = condition.Clone();
 419
 139420        _steering = NavSteering.CreateNew(context, Radius);
 421
 139422        _motor = NavMotor.CreateNew(context, _frameCondition, CreateLocomotionProfile());
 139423        _motor.SetVelocity(Velocity);
 424
 139425        _turning = NavTurning.CreateNew(context, Radius);
 426
 139427        CheckVoxelOccupancy(true);
 428
 139429        _isInitialized = true;
 139430    }
 431
 432    /// <summary>
 433    /// Creates the locomotion profile used when this object initializes its motor.
 434    /// </summary>
 435    /// <remarks>
 436    /// Override this to install a smaller or custom locomotion set per object type while preserving
 437    /// the default profile for callers that do not opt in.
 438    /// </remarks>
 439    protected virtual LocomotionProfile CreateLocomotionProfile()
 440    {
 138441        return LocomotionProfile.CreateDefault();
 442    }
 443
 444    /// <summary>
 445    /// Resets the state of the object to its initial configuration, clearing any active conditions, requests, and inter
 446    /// </summary>
 447    /// <remarks>
 448    /// Call this method to reinitialize the object for reuse or to clear any ongoing operations.
 449    /// After calling this method, the object will be in the same state as after construction, and any previous state or
 450    /// intent will be lost.
 451    /// This method is intended to be overridden in derived classes to extend the reset behavior as needed.
 452    /// </remarks>
 453    public virtual void Reset()
 454    {
 3455        _frameCondition.Reset();
 3456        _frameRequest.Reset();
 3457        _isGuideded = false;
 3458        _pendingGuidedVolumeExitHandoff = null;
 3459        NavigatorGuidedTraversalState.ResetClimbIntent(
 3460            ref _guidedClimbIntent,
 3461            ref _guidedClimbIntentMode,
 3462            ref _lastSeenGuidedRouteTopologyVersion);
 3463        _heightmapGrounding.Reset();
 3464        _steering?.Reset();
 465
 3466        if (_context != null && !_context.IsDisposed && _context.World.IsActive)
 3467            GridOccupantManager.TryDeregister(_context.World, this);
 468
 3469        _isSet = false;
 3470        _isInitialized = false;
 3471        _context = null;
 3472    }
 473
 474    #endregion
 475
 476    #region Host Bindings
 477
 478    /// <summary>
 479    /// Prewarms the steering movement-group coordinator from this object's currently loaded state.
 480    /// </summary>
 481    /// <remarks>
 482    /// This is primarily useful after loading several grouped navigators through Chronicler. Call it once
 483    /// for each loaded object before the next simulation frame if you want movement-group formation state
 484    /// available immediately. If it is skipped, grouped steering will still rejoin lazily on the next update.
 485    /// </remarks>
 486    public virtual void PrewarmMovementGroup()
 487    {
 3488        if (!IsActive)
 1489            throw new InvalidOperationException("Navigator must be Setup and Initialized before prewarming movement grou
 490
 2491        Steering!.PrewarmMovementGroup(this);
 2492    }
 493
 494    #endregion
 495
 496    #region Input / Travel Requests
 497
 498    /// <summary>
 499    /// Constructs and applies a traversal request using high-level navigation input values.
 500    /// </summary>
 501    /// <param name="direction">Desired direction of travel.</param>
 502    /// <param name="rate">Rate of travel (walk, run, etc.).</param>
 503    /// <param name="isRequestingJump">Whether the agent is requesting a jump action.</param>
 504    /// <param name="isRequestingFlight">Whether the agent is requesting to fly or glide.</param>
 505    /// <param name="isRequestingSwim">Whether the agent is requesting active swim control while in liquid.</param>
 506    /// <param name="isRequestingClimb">Whether the agent is requesting to climb or maintain climb intent.</param>
 507    /// <param name="facingDirection">Optional world-space facing direction to use instead of facing along the movement 
 508    /// <param name="canAffordJump">Frame-owned jump affordability answer for this request.</param>
 509    public virtual void ApplyInputTrekRequest(
 510        Vector3d? direction = null,
 511        TrekRate? rate = null,
 512        Vector3d? facingDirection = null,
 513        bool? isRequestingFlight = null,
 514        bool? isRequestingSwim = null,
 515        bool? isRequestingClimb = null,
 516        bool? isRequestingJump = null,
 517        bool canAffordJump = true)
 518    {
 21519        if (!IsActive) return;
 520
 19521        _isGuideded = false;
 19522        _pendingGuidedVolumeExitHandoff = null;
 19523        NavigatorGuidedTraversalState.ResetClimbIntent(
 19524            ref _guidedClimbIntent,
 19525            ref _guidedClimbIntentMode,
 19526            ref _lastSeenGuidedRouteTopologyVersion);
 19527        _frameRequest.SetRequest(
 19528                direction: direction ?? Vector3d.Zero,
 19529                rate: rate ?? TrekRate.Stationary,
 19530                isRequestingJump: isRequestingJump ?? false,
 19531                isRequestingFlight: isRequestingFlight ?? false,
 19532                isRequestingSwim: isRequestingSwim ?? false,
 19533                isRequestingClimb: isRequestingClimb ?? false,
 19534                facingDirection: facingDirection,
 19535                canAffordJump: canAffordJump
 19536        );
 19537    }
 538
 539    /// <summary>
 540    /// Constructs and applies a guided traversal request toward a destination using object-owned path request defaults.
 541    /// </summary>
 542    /// <param name="targetPosition">The desired world-space target position.</param>
 543    /// <param name="rate">Desired movement rate (walk, run, etc.).</param>
 544    /// <param name="isRequestingJump">Whether the object intends to jump during traversal.</param>
 545    /// <param name="isRequestingFlight">Whether the object intends to fly or glide during traversal.</param>
 546    /// <param name="isRequestingSwim">Whether the object intends to actively swim while traversing liquid.</param>
 547    /// <param name="isRequestingClimb">Whether the object intends to climb during traversal.</param>
 548    /// <param name="groupId">Optional shared group identifier used to preserve formation offsets between navigators.</p
 549    /// <param name="canAffordJump">Frame-owned jump affordability answer for this request.</param>
 550    public virtual void ApplyGuidedTrekRequest(
 551        Vector3d targetPosition,
 552        TrekRate? rate = null,
 553        bool? isRequestingFlight = null,
 554        bool? isRequestingSwim = null,
 555        bool? isRequestingClimb = null,
 556        bool? isRequestingJump = null,
 557        bool canAffordJump = true,
 558        int groupId = -1)
 559    {
 56560        if (!IsActive) return;
 561
 54562        if (!TryCreateGuidedPathRequest(targetPosition, out IPathRequest pathRequest))
 563        {
 3564            TrailblazerLogger.Channel.Warn(
 3565                $"Unable to create a {GuidedPathMode} path request for object {GlobalId} at {Position} targeting {target
 3566            return;
 567        }
 568
 51569        if (_pendingGuidedVolumeExitHandoff != null)
 12570            _pendingGuidedVolumeExitHandoff.MovementGroupId = groupId;
 571
 51572        _guidedClimbIntent = NavigatorGuidedTraversalState.ResolveInitialClimbIntent(
 51573            pathRequest,
 51574            _pendingGuidedVolumeExitHandoff,
 51575            isRequestingClimb,
 51576            out _guidedClimbIntentMode);
 577
 51578        _isGuideded = true;
 51579        _frameRequest.SetRequest(
 51580                direction: Vector3d.Zero,
 51581                rate: rate ?? TrekRate.Stationary,
 51582                isRequestingJump: isRequestingJump ?? false,
 51583                isRequestingFlight: isRequestingFlight ?? false,
 51584                isRequestingSwim: isRequestingSwim ?? false,
 51585                isRequestingClimb: _guidedClimbIntent,
 51586                facingDirection: null,
 51587                canAffordJump: canAffordJump
 51588        );
 589
 51590        Steering!.ApplyPathRequest(pathRequest, groupId);
 51591        CaptureGuidedRouteTopologyVersion();
 51592    }
 593
 594    /// <summary>
 595    /// Configures the object-owned defaults used when building guided path requests from a target position.
 596    /// </summary>
 597    /// <param name="pathAlgorithm">The pathfinding algorithm to use for guided requests.</param>
 598    /// <param name="allowUnwalkableEndpoints">Whether to allow object-built guided requests to target unwalkable voxels
 599    /// <param name="allowTraversalTransitions">Whether to allow object-built guided requests to use authored traversal 
 600    /// <param name="aStarHeuristic">The default heuristic to use when building A* guided requests.</param>
 601    /// <param name="flowFieldExtraFloodRange">The default extra flood range to use when building flow field guided requ
 602    /// <param name="maxClimbHeight">The default max climb height to use when building guided requests.</param>
 603    public virtual void ConfigureForGuidedTraversal(
 604        SolidPathAlgorithm? pathAlgorithm = null,
 605        bool? allowUnwalkableEndpoints = null,
 606        bool? allowTraversalTransitions = null,
 607        HeuristicMethod? aStarHeuristic = null,
 608        int? flowFieldExtraFloodRange = null,
 609        Fixed64? maxClimbHeight = null)
 610    {
 48611        _guidedPathMode = pathAlgorithm ?? _guidedPathMode;
 48612        _guidedAllowUnwalkableEndpoints = allowUnwalkableEndpoints ?? _guidedAllowUnwalkableEndpoints;
 48613        _guidedAllowTraversalTransitions = allowTraversalTransitions ?? _guidedAllowTraversalTransitions;
 48614        _guidedAStarHeuristic = aStarHeuristic ?? _guidedAStarHeuristic;
 48615        _guidedFlowFieldExtraFloodRange = flowFieldExtraFloodRange ?? _guidedFlowFieldExtraFloodRange;
 48616        _guidedMaxClimbHeight = maxClimbHeight ?? _guidedMaxClimbHeight;
 48617    }
 618
 619    /// <summary>
 620    /// Configures explicit heightmap grounding for concrete navigators that opt in during traversal checks.
 621    /// </summary>
 622    /// <param name="mode">Heightmap grounding behavior to use when <see cref="TryApplyHeightmapGrounding"/> is called.<
 623    /// <param name="layerName">Optional initial layer preference used before an active layer is established.</param>
 624    /// <param name="groundOffset">Optional extra root offset above the sampled ground Y.</param>
 625    /// <param name="snapTolerance">Optional maximum root-Y correction allowed for positional projection.</param>
 626    public virtual void ConfigureHeightmapGrounding(
 627        HeightmapGroundingMode mode,
 628        string? layerName = null,
 629        Fixed64? groundOffset = null,
 630        Fixed64? snapTolerance = null)
 631    {
 12632        _heightmapGrounding.Configure(mode, layerName, groundOffset, snapTolerance);
 12633    }
 634
 635    /// <summary>
 636    /// Builds a concrete path request for guided travel from the object's current state and defaults.
 637    /// Subclasses can override this to support custom request types without changing steering.
 638    /// </summary>
 639    protected virtual bool TryCreateGuidedPathRequest(
 640        Vector3d targetPosition,
 641        out IPathRequest pathRequest)
 642    {
 54643        _pendingGuidedVolumeExitHandoff = null;
 644
 54645        bool success = NavigatorPathRequestFactory.TryCreate(
 54646            context: RequireContext(),
 54647            origin: Position,
 54648            targetPosition: targetPosition,
 54649            unitSize: Size,
 54650            pathMode: GuidedPathMode,
 54651            allowUnwalkableEndpoints: GuidedAllowUnwalkableEndpoints,
 54652            allowTraversalTransitions: GuidedAllowTraversalTransitions,
 54653            maxClimbHeight: GuidedMaxClimbHeight,
 54654            traversalMedium: _frameCondition.Medium,
 54655            aStarHeuristic: GuidedAStarHeuristic,
 54656            flowFieldExtraFloodRange: GuidedFlowFieldExtraFloodRange,
 54657            out IPathRequest? createdRequest,
 54658            out GuidedVolumeExitHandoff? handoff);
 54659        if (!success || createdRequest == null)
 660        {
 3661            pathRequest = null!;
 3662            return false;
 663        }
 664
 51665        pathRequest = createdRequest;
 51666        if (handoff != null)
 12667            _pendingGuidedVolumeExitHandoff = handoff;
 668
 51669        return true;
 670    }
 671
 672    /// <summary>
 673    /// Sets the frame-owned jump affordability snapshot used by <see cref="NavMotor"/> on the next traversal step.
 674    /// </summary>
 675    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1676    public virtual void SetFrameJumpAffordability(bool canAffordJump) => _frameRequest.CanAffordJump = canAffordJump;
 677
 678    /// <summary>
 679    /// Called to make the agent jump if allowed and in a valid state.
 680    /// </summary>
 681    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1682    public virtual void ToggleGuidedJump(bool status) => _frameRequest.IsRequestingJump = status;
 683
 684    /// <summary>
 685    /// Called to toggle controlled flight if supported by the installed locomotion profile.
 686    /// </summary>
 687    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1688    public virtual void ToggleGuidedFlight(bool status) => _frameRequest.IsRequestingFlight = status;
 689
 690    /// <summary>
 691    /// Called to toggle active swim control if supported by the installed locomotion profile.
 692    /// </summary>
 693    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1694    public virtual void ToggleGuidedSwim(bool status) => _frameRequest.IsRequestingSwim = status;
 695
 696    /// <summary>
 697    /// Called to toggle climb intent if supported by the installed locomotion profile.
 698    /// </summary>
 699    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 700    public virtual void ToggleGuidedClimb(bool status)
 701    {
 1702        NavigatorGuidedTraversalState.SetClimbIntent(
 1703            ref _frameRequest,
 1704            status,
 1705            GuidedClimbIntentMode.Explicit,
 1706            ref _guidedClimbIntent,
 1707            ref _guidedClimbIntentMode);
 1708    }
 709
 710    /// <summary>
 711    /// Changes the speed at which the object is currently traveling without altering direction.
 712    /// </summary>
 713    /// <param name="rate">New traversal rate to apply (walk, run, etc.).</param>
 714    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1715    public virtual void SetGuidedTrekRate(TrekRate rate) => _frameRequest.Rate = rate;
 716
 717    #endregion
 718
 719    #region Simulation Lifecycle
 720
 721    /// <summary>
 722    /// Attempts to resolve the direction the object should try to face for the current frame.
 723    /// </summary>
 724    protected virtual bool TryGetTurnDirection(TrekRequest request, out Vector3d turnDirection)
 725    {
 44726        if (request.FacingDirection.HasValue && request.FacingDirection.Value != Vector3d.Zero)
 727        {
 2728            turnDirection = request.FacingDirection.Value;
 2729            return true;
 730        }
 731
 732        // Lock-on strafing/backpedaling keeps the current facing unless the host
 733        // explicitly supplies a facing override or the request is treated as sprinting.
 42734        if (!IsGuideded
 42735            && IsLockedOn
 42736            && request.Rate != TrekRate.Fast)
 737        {
 3738            turnDirection = Vector3d.Zero;
 3739            return false;
 740        }
 741
 39742        turnDirection = request.Direction;
 39743        return turnDirection != Vector3d.Zero;
 744    }
 745
 746    /// <summary>
 747    /// Runs simulation logic for this object (input handling, steering, etc.).
 748    /// </summary>
 749    public virtual void Simulate()
 750    {
 45751        if (!IsActive)
 1752            throw new InvalidOperationException("Navigator must be Setup and Initialized before Simulate().");
 753
 44754        bool activatedGuidedHandoff = NavigatorGuidedTraversalState.TryActivatePendingVolumeExitHandoff(
 44755            IsGuideded,
 44756            RequireContext(),
 44757            Position,
 44758            Size,
 44759            ref _frameRequest,
 44760            Steering,
 44761            ref _pendingGuidedVolumeExitHandoff,
 44762            ref _guidedClimbIntent,
 44763            _guidedClimbIntentMode,
 44764            ref _lastSeenGuidedRouteTopologyVersion,
 44765            out bool handoffRequestedClimb);
 44766        NavigatorGuidedTraversalState.PrepareFrame(
 44767            IsGuideded,
 44768            ref _frameRequest,
 44769            Steering,
 44770            _pendingGuidedVolumeExitHandoff,
 44771            ref _guidedClimbIntent,
 44772            ref _guidedClimbIntentMode,
 44773            ref _lastSeenGuidedRouteTopologyVersion);
 774
 44775        Vector3d heading = Vector3d.Zero;
 44776        if (IsGuideded)
 777        {
 36778            heading = Steering!.GetHeading(this);
 36779            NavigatorGuidedTraversalState.SyncFromSteering(
 36780                IsGuideded,
 36781                ref _frameRequest,
 36782                Steering,
 36783                _pendingGuidedVolumeExitHandoff,
 36784                activatedGuidedHandoff,
 36785                handoffRequestedClimb,
 36786                ref _guidedClimbIntent,
 36787                ref _guidedClimbIntentMode,
 36788                ref _lastSeenGuidedRouteTopologyVersion);
 789        }
 790
 44791        _frameRequest.SetTransientState(
 44792             origin: Position,
 44793             footPosition: GetFootPosition(),
 44794             rotation: Rotation,
 44795             direction: IsGuideded ? heading : null
 44796        );
 797
 44798        if (TryGetTurnDirection(_frameRequest, out Vector3d turnDirection))
 23799            Turning!.RequestTurnDirection(Forward, turnDirection);
 800
 44801        if (Motor!.TryTraversal(_frameRequest, out Vector3d vDelta, out Vector3d pDelta, out FixedQuaternion rDelta))
 802        {
 44803            AddVelocityDelta(vDelta);
 44804            AddPositionDelta(pDelta);
 44805            ApplyRotationDelta(rDelta);
 806        }
 807
 44808        if (Turning!.TrySimulateTurn(_position, _lastPosition, Forward, _rotation, out FixedQuaternion appliedRotation))
 24809            _rotation = appliedRotation;
 44810    }
 811
 812    /// <summary>
 813    /// Finalizes traversal by updating movement calculations and applying corrections.
 814    /// </summary>
 815    /// <remarks>
 816    /// Should be called once every rendering/player interfacing frame,
 817    /// after physics bodies apply velocity changes.
 818    /// </remarks>
 819    public virtual void CommitFrameMotion()
 820    {
 10821        if (!IsActive)
 1822            throw new InvalidOperationException("Navigator must be Setup and Initialized before CommitFrameMotion().");
 823
 9824        _lastPosition = _position;
 9825        _position += _positionDelta + _velocityDelta;
 826
 9827        CheckVoxelOccupancy();
 828
 9829        if (_rotationDelta != FixedQuaternion.Identity)
 830        {
 1831            _rotation *= _rotationDelta;
 1832            _rotationDelta = FixedQuaternion.Identity;
 833        }
 834
 9835        if (_rotation != FixedQuaternion.Identity)
 6836            Forward = _rotation.Rotate(Vector3d.Forward);
 837        else
 3838            Forward = Vector3d.Forward;
 839
 9840        CheckTrekCondition();
 841
 9842        Vector3d previousVelocity = _velocity;
 9843        TrailblazerWorldContext context = RequireContext();
 9844        Fixed64 invDelta = context.InvDeltaTime;
 9845        _velocity = (Position - LastPosition) * invDelta;
 9846        _speed = _velocity != Vector3d.Zero ? _velocity.Magnitude : Fixed64.Zero;
 9847        _acceleration = (_velocity - previousVelocity) * invDelta;
 848
 9849        if (Steering!.ShouldMove && _acceleration != Vector3d.Zero)
 2850            _stuckThresholdSpeed = (_acceleration / context.FrameRate).Magnitude;
 851        else
 7852            _stuckThresholdSpeed = Fixed64.Zero;
 853
 9854        _positionDelta = Vector3d.Zero;
 9855        _velocityDelta = Vector3d.Zero;
 856
 9857        Motor!.FinalizeTraversal(Position, LastPosition, Rotation, _frameCondition, newFootPosition: GetFootPosition());
 858
 859        // If the object is currently following a guided path,
 860        // reset only the transient request state to preserve path-following values.
 9861        if (IsGuideded)
 2862            _frameRequest.ResetTransient();
 863        else
 7864            _frameRequest.Reset();
 7865    }
 866
 867    /// <summary>
 868    /// Notifies the object that a collision occurred so collision-driven subsystem responses can run on the next simula
 869    /// </summary>
 870    public virtual void NotifyCollision()
 871    {
 3872        if (!IsActive) return;
 873
 1874        Turning!.NotifyCollision();
 1875    }
 876
 877    #endregion
 878
 879    #region Traversal Condition Management
 880
 881    /// <summary>
 882    /// Updates the object to a grounded state using a host-provided platform snapshot plus surface settings.
 883    /// Inert snapshots still describe the contacted surface but opt out of moving-platform carry logic.
 884    /// </summary>
 885    public virtual void SetGroundContact(
 886        Fixed64 surfaceLevel,
 887        PlatformSnapshot platform = default,
 888        Fixed64? surfaceFriction = null,
 889        MotionTransfer motionTransfer = MotionTransfer.None,
 890        Fixed64? ceilingLevel = null,
 891        bool updateMotorState = false)
 892    {
 35893        SetTrekCondition(
 35894            medium: TraversalMedium.Solid,
 35895            surfaceLevel: surfaceLevel,
 35896            surfaceCondition: new GroundCondition()
 35897            {
 35898                Platform = platform,
 35899                SurfaceFriction = surfaceFriction ?? Fixed64.Zero,
 35900                MotionTransferState = motionTransfer
 35901            },
 35902            ceilingLevel: ceilingLevel,
 35903            updateMotorState: updateMotorState);
 35904    }
 905
 906    /// <summary>
 907    /// Updates the object to an airborne state while preserving the last known ground condition unless an override is p
 908    /// </summary>
 909    public virtual void SetAirborne(
 910        Fixed64? surfaceLevel = null,
 911        GroundCondition? launchCondition = null,
 912        Fixed64? ceilingLevel = null,
 913        bool updateMotorState = false)
 914    {
 6915        SetTrekCondition(
 6916            medium: TraversalMedium.Gas,
 6917            surfaceLevel: surfaceLevel,
 6918            surfaceCondition: launchCondition,
 6919            replaceGroundContact: launchCondition.HasValue,
 6920            ceilingLevel: ceilingLevel,
 6921            updateMotorState: updateMotorState);
 6922    }
 923
 924    /// <summary>
 925    /// Updates the object to a water-contact state and clears any grounded platform contact.
 926    /// </summary>
 927    public virtual void SetWaterContact(
 928        Fixed64 surfaceLevel,
 929        Fixed64? ceilingLevel = null,
 930        bool updateMotorState = false)
 931    {
 12932        SetTrekCondition(
 12933            medium: TraversalMedium.Liquid,
 12934            surfaceLevel: surfaceLevel,
 12935            surfaceCondition: null,
 12936            ceilingLevel: ceilingLevel,
 12937            updateMotorState: updateMotorState);
 12938    }
 939
 940    /// <summary>
 941    /// Updates the scout’s traversal state, including its current medium and surface information.
 942    /// </summary>
 943    /// <remarks>
 944    /// Make sure to update this before the next <see cref="CommitFrameMotion"/> so <see cref="NavMotor.FinalizeTraversa
 945    /// If the motor must see the new snapshot before the next <see cref="Simulate"/>, either pass
 946    /// <paramref name="updateMotorState"/> as <c>true</c> or call <see cref="SyncCurrentTrekConditionToMotor"/>.
 947    /// </remarks>
 948    /// <param name="medium">The traversal medium (e.g., ground, air, water).</param>
 949    /// <param name="surfaceLevel">The vertical surface level, if applicable.</param>
 950    /// <param name="surfaceCondition">The ground state data, if applicable.</param>
 951    /// <param name="replaceGroundContact">Whether to replace the current ground contact platform.  This should be true 
 952    /// <param name="ceilingLevel">The vertical ceiling level, if applicable.</param>
 953    /// <param name="updateMotorState">Flags whether or not to update the motor's internal surface state.  Otherwise, it
 954    public virtual void SetTrekCondition(
 955        TraversalMedium? medium = null,
 956        Fixed64? surfaceLevel = null,
 957        GroundCondition? surfaceCondition = null,
 958        bool replaceGroundContact = true,
 959        Fixed64? ceilingLevel = null,
 960        bool updateMotorState = false)
 961    {
 70962        if (!IsActive)
 1963            return;
 964
 69965        _frameCondition.Medium = medium ?? _frameCondition.Medium;
 69966        _frameCondition.SurfaceLevel = surfaceLevel ?? _frameCondition.SurfaceLevel;
 69967        if (replaceGroundContact)
 62968            _frameCondition.GroundState = surfaceCondition;
 69969        _frameCondition.CeilingLevel = ceilingLevel ?? _frameCondition.CeilingLevel;
 970
 69971        if (updateMotorState)
 38972            SyncCurrentTrekConditionToMotor();
 69973    }
 974
 975    /// <summary>
 976    /// Pushes the current traversal snapshot into the motor before the next traversal phase begins.
 977    /// </summary>
 978    public virtual void SyncCurrentTrekConditionToMotor()
 979    {
 40980        if (!IsActive)
 0981            throw new InvalidOperationException("Navigator must be Setup and Initialized before syncing traversal state 
 982
 40983        Motor!.SyncTraversalState(_frameCondition);
 40984    }
 985
 986    /// <summary>
 987    /// Replaces the current traversal state with the given one.
 988    /// </summary>
 989    /// <param name="state">The new traversal condition to apply.</param>
 990    /// <param name="updateMotorState">Flags whether or not to immediately sync the new traversal snapshot into the moto
 991    public virtual void ReplaceTrekCondition(TrekCondition state, bool updateMotorState)
 992    {
 3993        if (updateMotorState && !IsActive)
 0994            throw new InvalidOperationException("Navigator must be Setup and Initialized before syncing traversal state 
 995
 3996        _frameCondition = state.Clone();
 3997        if (updateMotorState)
 1998            SyncCurrentTrekConditionToMotor();
 3999    }
 1000
 1001    /// <summary>
 1002    /// Checks and updates the current traversal condition.
 1003    /// </summary>
 1004    public abstract void CheckTrekCondition();
 1005
 1006    #endregion
 1007
 1008    #region Deltas - Position / Velocity / Rotation
 1009
 1010    /// <summary>
 1011    /// Applies a positional delta to the current position and updates the last known position accordingly.
 1012    /// </summary>
 1013    /// <remarks>
 1014    /// This method adjusts both the current and last positions to maintain consistent velocity calculations.
 1015    /// Use this method to apply external position changes without affecting velocity tracking.
 1016    /// </remarks>
 1017    /// <param name="delta">
 1018    /// The vector representing the positional change to apply. If the vector is <see cref="Vector3d.Zero"/>, no change 
 1019    /// </param>
 1020    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1021    public virtual void AddPositionDelta(Vector3d delta)
 1022    {
 911023        if (delta == Vector3d.Zero) return;
 1024
 11025        _positionDelta += delta;
 1026        // shift last position so it doesn't alter object's velocity
 11027        _lastPosition += delta;
 11028    }
 1029
 1030    /// <summary>
 1031    /// Applies the specified rotation delta to the current rotation state.
 1032    /// </summary>
 1033    /// <param name="delta">
 1034    /// The rotation delta to apply. Must not be <see cref="FixedQuaternion.Identity"/>; otherwise, no change is made.
 1035    /// </param>
 1036    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1037    public virtual void ApplyRotationDelta(FixedQuaternion delta)
 1038    {
 911039        if (delta == FixedQuaternion.Identity) return;
 1040
 11041        _rotationDelta *= delta;
 11042    }
 1043
 1044    /// <summary>
 1045    /// Adds the specified velocity change to the current velocity delta.
 1046    /// </summary>
 1047    /// <param name="delta">The velocity change to add. If this value is <see cref="Vector3d.Zero"/>, no change is appli
 1048    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1049    public virtual void AddVelocityDelta(Vector3d delta)
 1050    {
 471051        if (delta == Vector3d.Zero) return;
 1052
 1053        // assume a mass of 1...for now
 451054        _velocityDelta += delta;
 451055    }
 1056
 1057    #endregion
 1058
 1059    #region Utilities
 1060
 1061    /// <summary>
 1062    /// Calculates the world-space position of the object's foot, adjusted by the configured foot position offset.
 1063    /// </summary>
 1064    /// <remarks>
 1065    /// Use this method to obtain the precise ground contact point for the object,
 1066    /// which may be offset from its origin depending on the foot adjustment value.
 1067    /// </remarks>
 1068    /// <returns>
 1069    /// A <see cref="Vector3d"/> representing the foot position in world coordinates,
 1070    /// or <see langword="null"/> if the position is undefined.
 1071    /// </returns>
 1072    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1073    public virtual Vector3d? GetFootPosition()
 1074    {
 531075        return Position + Vector3d.Down * FootPositionAdjust;
 1076    }
 1077
 1078    /// <summary>
 1079    /// Generates a new globally unique identifier (GUID) for use in object identification or tracking.
 1080    /// </summary>
 1081    /// <remarks>Override this method to customize GUID generation logic if a different strategy is required by derived 
 1082    /// <returns>A new <see cref="Guid"/> value that is guaranteed to be unique across space and time.</returns>
 1083    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1084    protected virtual Guid GenerateGUID()
 1085    {
 1541086        TrailblazerWorldContext context = RequireContext();
 1541087        return context.Navigation.CreateNavigatorId();
 1088    }
 1089
 1090    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1091    private TrailblazerWorldContext EnsureContextForSetup()
 1092    {
 1571093        if (_context != null)
 1561094            return RequireContext();
 1095
 11096        throw new InvalidOperationException(
 11097            "Navigator requires a TrailblazerWorldContext before setup. Pass a context to the constructor, " +
 11098            "call BindContext(context), or use Setup(context, ...).");
 1099    }
 1100
 1101    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1102    private TrailblazerWorldContext RequireContext()
 1103    {
 8041104        if (_context == null)
 1105        {
 01106            throw new InvalidOperationException(
 01107                "Navigator requires a TrailblazerWorldContext before simulation.");
 1108        }
 1109
 8041110        PathRequestContextResolver.ThrowIfUnusable(_context);
 8041111        return _context;
 1112    }
 1113
 1114    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1115    private void CaptureGuidedRouteTopologyVersion() =>
 511116        NavigatorGuidedTraversalState.CaptureRouteTopologyVersion(Steering, ref _lastSeenGuidedRouteTopologyVersion);
 1117
 1118    #endregion
 1119
 1120    #region Occupancy Mangement
 1121
 1122    /// <summary>
 1123    /// Checks and updates the occupancy status of the current and previous voxels based on the object's position.
 1124    /// </summary>
 1125    /// <remarks>
 1126    /// This method ensures that the object is registered as an occupant in the correct voxel and
 1127    /// removed from the previous voxel if the position has changed.
 1128    /// It should be called whenever the object's position may have changed to maintain accurate occupancy tracking.
 1129    /// </remarks>
 1130    /// <param name="init">Indicates whether the occupancy check is being performed during initialization.
 1131    /// If set to <see langword="true"/>, the check is performed regardless of position changes.
 1132    /// </param>
 1133    protected virtual void CheckVoxelOccupancy(bool init = false)
 1134    {
 1921135        NavigatorOccupancyTracker.Update(
 1921136            RequireContext().World, this, Position, LastPosition, init);
 1921137    }
 1138
 1139    #endregion
 1140}

/home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Navigation/Navigator/Navigator.HeightmapGrounding.cs

#LineLine coverage
 1using FixedMathSharp;
 2using Trailblazer.Heightmaps;
 3using Trailblazer.Navigation.Motor;
 4
 5namespace Trailblazer.Navigation;
 6
 7public abstract partial class Navigator
 8{
 9    #region Heightmap Grounding
 10
 11    /// <summary>
 12    /// Applies configured heightmap grounding when a concrete navigator chooses to opt in.
 13    /// </summary>
 14    /// <remarks>
 15    /// The base navigator never calls this automatically. Host/concrete navigators should invoke it
 16    /// from their traversal probing code after they know the navigator is grounded on solid terrain.
 17    /// </remarks>
 18    /// <param name="updateMotorState">Whether to immediately sync the resulting ground contact into the motor.</param>
 19    /// <param name="surfaceFriction">Optional friction stored in the generated ground condition.</param>
 20    /// <param name="motionTransfer">Motion transfer mode stored in the generated ground condition.</param>
 21    /// <returns>True when a registered heightmap sample updated this navigator's ground contact.</returns>
 22    protected bool TryApplyHeightmapGrounding(
 23        bool updateMotorState = false,
 24        Fixed64? surfaceFriction = null,
 25        MotionTransfer motionTransfer = MotionTransfer.None)
 26    {
 1527        if (!IsActive || _heightmapGrounding.Mode == HeightmapGroundingMode.Disabled)
 128            return false;
 1429        if (_frameCondition.Medium != TraversalMedium.Solid)
 230            return false;
 31
 1232        TrailblazerWorldContext context = RequireContext();
 1233        Vector3d queryPosition = GetHeightmapGroundingQueryPosition();
 1234        string? preferredLayerName = _heightmapGrounding.ActiveLayerName ?? _heightmapGrounding.LayerName;
 1235        if (!context.Heightmaps.TrySampleGround(queryPosition, preferredLayerName, out HeightmapSample sample))
 36        {
 237            _heightmapGrounding.ActiveLayerName = null;
 238            return false;
 39        }
 40
 1041        _heightmapGrounding.ActiveLayerName = sample.LayerName;
 1042        SetGroundContact(
 1043            surfaceLevel: sample.GroundY,
 1044            surfaceFriction: surfaceFriction,
 1045            motionTransfer: motionTransfer,
 1046            updateMotorState: updateMotorState);
 47
 1048        if (_heightmapGrounding.Mode == HeightmapGroundingMode.SurfaceLevelAndPosition)
 749            TryProjectRootToHeightmapSample(sample);
 50
 1051        return true;
 52    }
 53
 54    private Vector3d GetHeightmapGroundingQueryPosition()
 55    {
 1256        return new Vector3d(
 1257            _position.x,
 1258            _position.y - _footPositionAdjust - _heightmapGrounding.GroundOffset,
 1259            _position.z);
 60    }
 61
 62    private void TryProjectRootToHeightmapSample(HeightmapSample sample)
 63    {
 764        Fixed64 targetRootY = sample.GroundY + _footPositionAdjust + _heightmapGrounding.GroundOffset;
 765        Fixed64 correctionY = targetRootY - _position.y;
 766        if (correctionY == Fixed64.Zero)
 467            return;
 68
 369        Fixed64? snapTolerance = _heightmapGrounding.SnapTolerance;
 370        if (snapTolerance.HasValue && correctionY.Abs() > snapTolerance.Value)
 171            return;
 72
 273        ProjectRootWithoutVelocity(new Vector3d(Fixed64.Zero, correctionY, Fixed64.Zero));
 274    }
 75
 76    private void ProjectRootWithoutVelocity(Vector3d correction)
 77    {
 278        if (correction == Vector3d.Zero)
 079            return;
 80
 281        Vector3d oldPosition = _position;
 282        _position += correction;
 283        _lastPosition += correction;
 284        UpdateVoxelOccupancyAfterRootProjection(oldPosition, _position);
 285    }
 86
 87    private void UpdateVoxelOccupancyAfterRootProjection(Vector3d oldPosition, Vector3d newPosition)
 88    {
 289        if (_context == null || _context.IsDisposed || !_context.World.IsActive)
 090            return;
 91
 292        NavigatorOccupancyTracker.UpdateAfterRootProjection(
 293            _context.World, this, oldPosition, newPosition);
 294    }
 95
 96    #endregion
 97}

/home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Navigation/Navigator/Navigator.Serialization.cs

#LineLine coverage
 1using Chronicler;
 2using FixedMathSharp;
 3using System;
 4using Trailblazer.Pathing;
 5
 6namespace Trailblazer.Navigation;
 7
 8public abstract partial class Navigator
 9{
 10    #region Serialization
 11
 12    /// <inheritdoc />
 13    public virtual void RecordData(IChronicler chronicler)
 14    {
 8615        GuidedVolumeExitHandoff? pendingGuidedVolumeExitHandoff = _pendingGuidedVolumeExitHandoff;
 8616        if (chronicler.Mode == SerializationMode.Loading && pendingGuidedVolumeExitHandoff == null)
 4417            pendingGuidedVolumeExitHandoff = new GuidedVolumeExitHandoff();
 18
 8619        RecordValues.Look(chronicler, ref _position, "Position", Vector3d.Zero);
 8620        RecordValues.Look(chronicler, ref _lastPosition, "LastPosition", Vector3d.Zero);
 8621        RecordValues.Look(chronicler, ref _rotation, "Rotation", FixedQuaternion.Identity);
 8622        RecordValues.Look(chronicler, ref _velocity, "Velocity", Vector3d.Zero);
 8623        RecordValues.Look(chronicler, ref _speed, "Speed", Fixed64.Zero);
 8624        RecordValues.Look(chronicler, ref _acceleration, "Acceleration", Vector3d.Zero);
 8625        RecordValues.Look(chronicler, ref _size, "Size", Fixed64.One);
 8626        RecordValues.Look(chronicler, ref _footPositionAdjust, "FootPositionAdjust", DefaultFootPositionAdjust);
 8627        RecordValues.Look(chronicler, ref _guidedPathMode, "GuidedPathMode", SolidPathAlgorithm.AStar);
 8628        RecordValues.Look(chronicler, ref _guidedAllowUnwalkableEndpoints, "GuidedAllowUnwalkableEndpoints", false);
 8629        RecordValues.Look(chronicler, ref _guidedAllowTraversalTransitions, "GuidedAllowTraversalTransitions", false);
 8630        RecordValues.Look(chronicler, ref _guidedMaxClimbHeight, "GuidedMaxClimbHeight", Fixed64.One);
 8631        RecordValues.Look(chronicler, ref _guidedAStarHeuristic, "GuidedAStarHeuristic", HeuristicMethod.Manhattan);
 8632        RecordValues.Look(chronicler, ref _guidedFlowFieldExtraFloodRange, "GuidedFlowFieldExtraFloodRange", FlowFieldPa
 8633        RecordValues.Look(chronicler, ref _globalId, "GlobalId", Guid.Empty);
 8634        RecordValues.Look(chronicler, ref _occupantGroupId, "OccupantGroupId", (byte)1);
 8635        RecordValues.Look(chronicler, ref _isLockedOn, "IsLockedOn", false);
 8636        RecordValues.Look(chronicler, ref _stuckThresholdSpeed, "StuckThresholdSpeed", Fixed64.Zero);
 8637        RecordValues.Look(chronicler, ref _isGuideded, "IsGuideded", false);
 8638        RecordValues.Look(chronicler, ref _guidedClimbIntent, "GuidedClimbIntent", false);
 8639        RecordValues.Look(chronicler, ref _guidedClimbIntentMode, "GuidedClimbIntentMode", GuidedClimbIntentMode.Auto);
 8640        RecordValues.Look(chronicler, ref _lastSeenGuidedRouteTopologyVersion, "LastSeenGuidedRouteTopologyVersion", 0);
 8641        RecordDeepStruct.Look(chronicler, ref _frameCondition, "FrameCondition");
 8642        RecordDeepStruct.Look(chronicler, ref _frameRequest, "FrameRequest");
 8643        RecordDeep.Look(chronicler, ref _heightmapGrounding, "HeightmapGrounding");
 8644        RecordDeep.Look(chronicler, ref pendingGuidedVolumeExitHandoff!, "PendingGuidedVolumeExitHandoff");
 8645        if (_steering != null)
 8246            RecordDeep.Look(chronicler, ref _steering, "Steering");
 8647        if (_turning != null)
 8248            RecordDeep.Look(chronicler, ref _turning, "Turning");
 8649        if (_motor != null)
 8250            RecordDeep.Look(chronicler, ref _motor, "Motor");
 51
 8652        if (chronicler.Mode == SerializationMode.Loading)
 53        {
 4454            TrailblazerWorldContext context = RequireContext();
 4455            _pendingGuidedVolumeExitHandoff = pendingGuidedVolumeExitHandoff?.IsValid == true
 4456                ? pendingGuidedVolumeExitHandoff
 4457                : null;
 58
 4459            Forward = Rotation != FixedQuaternion.Identity
 4460                ? Rotation.Rotate(Vector3d.Forward)
 4461                : Vector3d.Forward;
 62
 4463            _positionDelta = Vector3d.Zero;
 4464            _velocityDelta = Vector3d.Zero;
 4465            _rotationDelta = FixedQuaternion.Identity;
 4466            _heightmapGrounding ??= new NavigatorHeightmapGroundingSettings();
 4467            _isSet = true;
 4468            _isInitialized = Motor != null;
 69
 4470            _steering?.BindContext(context);
 4471            _steering?.UpdateOwnerRadius(Radius);
 72
 4473            _motor?.BindContext(context);
 74
 4475            _turning?.BindContext(context);
 4476            _turning?.OnInitialize(Radius);
 77
 4478            CheckVoxelOccupancy(true);
 79        }
 8680    }
 81
 82    #endregion
 83}

Methods/Properties

.cctor()
.ctor()
get_Position()
get_LastPosition()
get_Rotation()
get_Velocity()
get_Speed()
get_Acceleration()
get_StuckThresholdSpeed()
get_IsActive()
get_Context()
get_Steering()
get_Turning()
get_Motor()
get_IsGuideded()
get_Size()
get_Radius()
get_FootPositionAdjust()
get_HeightmapGrounding()
get_IsLockedOn()
get_GuidedPathMode()
get_GuidedAllowUnwalkableEndpoints()
get_GuidedAllowTraversalTransitions()
get_GuidedMaxClimbHeight()
get_GuidedAStarHeuristic()
get_GuidedFlowFieldExtraFloodRange()
get_GlobalId()
get_OccupantGroupId()
.ctor(Trailblazer.TrailblazerWorldContext)
BindContext(Trailblazer.TrailblazerWorldContext)
Activate(Trailblazer.TrailblazerWorldContext,Trailblazer.Navigation.Motor.TrekCondition,FixedMathSharp.Vector3d,System.Nullable`1<FixedMathSharp.FixedQuaternion>,System.Nullable`1<FixedMathSharp.Vector3d>,System.Nullable`1<FixedMathSharp.Fixed64>,System.Nullable`1<System.Guid>)
Activate(Trailblazer.Navigation.Motor.TrekCondition,FixedMathSharp.Vector3d,System.Nullable`1<FixedMathSharp.FixedQuaternion>,System.Nullable`1<FixedMathSharp.Vector3d>,System.Nullable`1<FixedMathSharp.Fixed64>,System.Nullable`1<System.Guid>)
Setup(Trailblazer.TrailblazerWorldContext,FixedMathSharp.Vector3d,System.Nullable`1<FixedMathSharp.FixedQuaternion>,System.Nullable`1<FixedMathSharp.Vector3d>,System.Nullable`1<FixedMathSharp.Fixed64>,System.Nullable`1<System.Guid>)
Setup(FixedMathSharp.Vector3d,System.Nullable`1<FixedMathSharp.FixedQuaternion>,System.Nullable`1<FixedMathSharp.Vector3d>,System.Nullable`1<FixedMathSharp.Fixed64>,System.Nullable`1<System.Guid>)
Initialize(Trailblazer.Navigation.Motor.TrekCondition)
CreateLocomotionProfile()
Reset()
PrewarmMovementGroup()
ApplyInputTrekRequest(System.Nullable`1<FixedMathSharp.Vector3d>,System.Nullable`1<Trailblazer.Navigation.TrekRate>,System.Nullable`1<FixedMathSharp.Vector3d>,System.Nullable`1<System.Boolean>,System.Nullable`1<System.Boolean>,System.Nullable`1<System.Boolean>,System.Nullable`1<System.Boolean>,System.Boolean)
ApplyGuidedTrekRequest(FixedMathSharp.Vector3d,System.Nullable`1<Trailblazer.Navigation.TrekRate>,System.Nullable`1<System.Boolean>,System.Nullable`1<System.Boolean>,System.Nullable`1<System.Boolean>,System.Nullable`1<System.Boolean>,System.Boolean,System.Int32)
ConfigureForGuidedTraversal(System.Nullable`1<Trailblazer.Navigation.SolidPathAlgorithm>,System.Nullable`1<System.Boolean>,System.Nullable`1<System.Boolean>,System.Nullable`1<Trailblazer.Pathing.HeuristicMethod>,System.Nullable`1<System.Int32>,System.Nullable`1<FixedMathSharp.Fixed64>)
ConfigureHeightmapGrounding(Trailblazer.Navigation.HeightmapGroundingMode,System.String,System.Nullable`1<FixedMathSharp.Fixed64>,System.Nullable`1<FixedMathSharp.Fixed64>)
TryCreateGuidedPathRequest(FixedMathSharp.Vector3d,Trailblazer.Pathing.IPathRequest&)
SetFrameJumpAffordability(System.Boolean)
ToggleGuidedJump(System.Boolean)
ToggleGuidedFlight(System.Boolean)
ToggleGuidedSwim(System.Boolean)
ToggleGuidedClimb(System.Boolean)
SetGuidedTrekRate(Trailblazer.Navigation.TrekRate)
TryGetTurnDirection(Trailblazer.Navigation.Motor.TrekRequest,FixedMathSharp.Vector3d&)
Simulate()
CommitFrameMotion()
NotifyCollision()
SetGroundContact(FixedMathSharp.Fixed64,Trailblazer.Navigation.Motor.PlatformSnapshot,System.Nullable`1<FixedMathSharp.Fixed64>,Trailblazer.Navigation.Motor.MotionTransfer,System.Nullable`1<FixedMathSharp.Fixed64>,System.Boolean)
SetAirborne(System.Nullable`1<FixedMathSharp.Fixed64>,System.Nullable`1<Trailblazer.Navigation.Motor.GroundCondition>,System.Nullable`1<FixedMathSharp.Fixed64>,System.Boolean)
SetWaterContact(FixedMathSharp.Fixed64,System.Nullable`1<FixedMathSharp.Fixed64>,System.Boolean)
SetTrekCondition(System.Nullable`1<Trailblazer.TraversalMedium>,System.Nullable`1<FixedMathSharp.Fixed64>,System.Nullable`1<Trailblazer.Navigation.Motor.GroundCondition>,System.Boolean,System.Nullable`1<FixedMathSharp.Fixed64>,System.Boolean)
SyncCurrentTrekConditionToMotor()
ReplaceTrekCondition(Trailblazer.Navigation.Motor.TrekCondition,System.Boolean)
AddPositionDelta(FixedMathSharp.Vector3d)
ApplyRotationDelta(FixedMathSharp.FixedQuaternion)
AddVelocityDelta(FixedMathSharp.Vector3d)
GetFootPosition()
GenerateGUID()
EnsureContextForSetup()
RequireContext()
CaptureGuidedRouteTopologyVersion()
CheckVoxelOccupancy(System.Boolean)
TryApplyHeightmapGrounding(System.Boolean,System.Nullable`1<FixedMathSharp.Fixed64>,Trailblazer.Navigation.Motor.MotionTransfer)
GetHeightmapGroundingQueryPosition()
TryProjectRootToHeightmapSample(Trailblazer.Heightmaps.HeightmapSample)
ProjectRootWithoutVelocity(FixedMathSharp.Vector3d)
UpdateVoxelOccupancyAfterRootProjection(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
RecordData(Chronicler.IChronicler)