docs: document trajectory execution contracts

This commit is contained in:
梁薄云
2026-08-04 14:21:13 +08:00
parent fda84ce148
commit 4a7dd875a4
14 changed files with 149 additions and 18 deletions
@@ -6,8 +6,8 @@ using MultiWheelC.TrajectoryPlanning.CoarsePath;
namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>
/// Latest-wins rolling coordinator around the pure one-shot planning service.
/// It owns no localization, hardware, UI, or wall-clock source; callers supply each cycle's state and time.
/// 围绕纯单次 <see cref="IEmPlanningService"/> 的 latest-wins 滚动规划协调器。
/// 它只使用调用方提供的状态快照和时钟值;不读取定位、硬件、UI 或系统时钟,并且只原子发布当前身份的完整成功轨迹。
/// </summary>
public sealed class EmPlanningCoordinator
{
@@ -22,12 +22,16 @@ public sealed class EmPlanningCoordinator
private DateTimeOffset? lastCycleStartedAtUtc;
private double replanPeriodSeconds = 0.20d;
/// <summary>创建滚动规划协调器。</summary>
/// <param name="planningService">同步纯规划服务;每个周期在后台任务中调用其 <c>Plan</c> 方法。</param>
/// <param name="sink">可选周期完成观察器;观察器异常只追加诊断,不能回滚发布结果。</param>
public EmPlanningCoordinator(IEmPlanningService planningService, IEmPlanningCycleSink sink = null)
{
this.planningService = planningService ?? throw new ArgumentNullException(nameof(planningService));
this.sink = sink;
}
/// <summary>最后一条已原子发布的完整不可变轨迹;尚无成功周期或失败周期未替换旧轨迹时可为 <see langword="null"/>。</summary>
public EmTrajectory PublishedTrajectory
{
get
@@ -37,7 +41,9 @@ public sealed class EmPlanningCoordinator
}
}
/// <summary>Returns whether a caller-supplied time is due for another rolling cycle.</summary>
/// <summary>判断调用方提供的时刻是否到达下一次滚动规划周期。</summary>
/// <param name="now">调用方冻结的当前时间;协调器不读取墙上时钟。</param>
/// <returns>尚未启动周期或距上次开始已达到配置周期时为 <see langword="true"/>;否则为 <see langword="false"/>。</returns>
public bool ShouldStartCycle(DateTimeOffset now)
{
lock (publicationGate)
@@ -47,6 +53,10 @@ public sealed class EmPlanningCoordinator
}
}
/// <summary>异步启动一个 latest-wins 滚动周期,并取消此前仍在执行的周期。</summary>
/// <param name="input">调用方冻结的规划请求、完整周期身份和 caller-clocked 时刻;其地图、路径和状态版本必须一致。</param>
/// <param name="cancellationToken">调用方取消令牌;会与本周期内部令牌链接。</param>
/// <returns>完成后给出不可变 <see cref="PlanningCycleResult"/>;只有版本和身份仍当前且结果成功完整时其 <c>Published</c> 为 <see langword="true"/>。</returns>
public Task<PlanningCycleResult> PlanLatestAsync(PlanningCycleInput input, CancellationToken cancellationToken)
{
if (input == null)
@@ -68,7 +78,10 @@ public sealed class EmPlanningCoordinator
return Task.Run(() => CompleteCycle(version, input, cycleCancellation));
}
/// <summary>Builds a handoff decision from the coordinator's immutable published trajectory snapshot.</summary>
/// <summary>从协调器当前已发布的不可变轨迹构建下一周期的安全交接决策。</summary>
/// <param name="input">下一周期的请求、状态快照和时刻。</param>
/// <param name="expectedDirection">下一周期期望的实际行驶方向。</param>
/// <returns>同段、同方向、年龄和追踪误差均安全时返回未来轨迹样本;否则返回调用方测量状态和拒绝原因。</returns>
public TrajectoryHandoffSelection SelectHandoff(PlanningCycleInput input, TravelDirection expectedDirection)
{
if (input == null)
@@ -81,6 +94,11 @@ public sealed class EmPlanningCoordinator
input.Now, input.Request.Configuration);
}
/// <summary>执行一次后台纯规划并在锁内比较版本和身份后决定是否发布。</summary>
/// <param name="version">启动时分配的单调递增周期版本。</param>
/// <param name="input">该周期的不可变请求、身份和 caller-clocked 时刻。</param>
/// <param name="cycleCancellation">本周期与调用方取消状态链接的令牌源。</param>
/// <returns>包含原始或 Superseded 规划结果、发布标记和观察器诊断的周期记录。</returns>
private PlanningCycleResult CompleteCycle(long version, PlanningCycleInput input,
CancellationTokenSource cycleCancellation)
{
@@ -1,11 +1,18 @@
namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>轨迹执行器的换向安全状态;所有离开 Following 的状态都禁止非零运动,直到方向已确认。</summary>
public enum GearSwitchState
{
/// <summary>正常跟随当前方向段的轨迹运动。</summary>
Following,
/// <summary>目标方向不同,正在接近精确换向边界。</summary>
ApproachingGearSwitch,
/// <summary>位于换向边界并保持零速度,等待满足停稳 dwell。</summary>
HoldingZero,
/// <summary>零速 dwell 已满足;本次更新发送一次方向变更请求。</summary>
RequestingDirectionChange,
/// <summary>请求已发出,保持制动直到调用方确认实际方向改变。</summary>
AwaitingDirectionConfirmation,
/// <summary>到达 Goal 或 RollingSafetyStop,永久保持零运动。</summary>
Completed,
}
@@ -3,13 +3,16 @@ using MultiWheelC.TrajectoryPlanning.CoarsePath;
namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>Pure caller-clocked state machine for a zero-speed direction change.</summary>
/// <summary> caller-clocked 的零速换向状态机;只依据调用方时间、测量速度和方向确认推进状态。</summary>
public sealed class GearSwitchStateMachine
{
private readonly double stopSpeedToleranceMetersPerSecond;
private readonly TimeSpan zeroSpeedHoldDuration;
private DateTimeOffset? zeroSpeedSince;
/// <summary>创建换向状态机。</summary>
/// <param name="stopSpeedToleranceMetersPerSecond">判定已停稳的绝对带符号速度上限,单位 m/s。</param>
/// <param name="zeroSpeedHoldSeconds">发送方向请求前必须连续保持停稳的时长,单位 s。</param>
public GearSwitchStateMachine(double stopSpeedToleranceMetersPerSecond, double zeroSpeedHoldSeconds)
{
if (!IsNonNegativeFinite(stopSpeedToleranceMetersPerSecond))
@@ -21,8 +24,18 @@ public sealed class GearSwitchStateMachine
zeroSpeedHoldDuration = TimeSpan.FromSeconds(zeroSpeedHoldSeconds);
}
/// <summary>当前不可回退的换向状态;初始为 <see cref="GearSwitchState.Following"/>。</summary>
public GearSwitchState State { get; private set; } = GearSwitchState.Following;
/// <summary>基于调用方时刻、测量速度和边界语义推进一次换向状态。</summary>
/// <param name="now">调用方当前时刻,用于计算零速 dwell,不读取系统时钟。</param>
/// <param name="measuredSignedSpeed">测量带符号纵向速度,单位 m/s。</param>
/// <param name="desiredDirection">轨迹要求的实际行驶方向。</param>
/// <param name="currentDirection">调用方确认的当前实际方向。</param>
/// <param name="directionConfirmed">调用方是否已确认硬件方向切换完成。</param>
/// <param name="atGearSwitchBoundary">选中点是否为精确换向接近边界。</param>
/// <param name="atTerminal">选中点是否为 Goal 或 RollingSafetyStop 终端。</param>
/// <returns>包含停零、一次换向请求、是否允许轨迹运动和完成语义的不可变状态更新。</returns>
public GearSwitchStateUpdate Update(DateTimeOffset now, double measuredSignedSpeed, TravelDirection desiredDirection,
TravelDirection currentDirection, bool directionConfirmed, bool atGearSwitchBoundary, bool atTerminal)
{
@@ -117,6 +130,7 @@ public sealed class GearSwitchStateMachine
}
}
/// <summary>一次换向状态机更新的不可变安全决策,供执行器映射为零速度/制动或轨迹跟随命令。</summary>
public sealed class GearSwitchStateUpdate
{
internal GearSwitchStateUpdate(GearSwitchState state, bool holdZero, bool requestDirectionChange,
@@ -130,14 +144,17 @@ public sealed class GearSwitchStateUpdate
Reason = reason ?? string.Empty;
}
/// <summary>更新后的换向状态。</summary>
public GearSwitchState State { get; }
public bool HoldZero { get; }
/// <summary>是否仅在本次更新请求一次方向变更。</summary>
public bool RequestDirectionChange { get; }
public bool AllowsTrajectoryMotion { get; }
/// <summary>是否因 Goal 或 RollingSafetyStop 完成轨迹并需要持续制动。</summary>
public bool IsTrajectoryComplete { get; }
public string Reason { get; }
@@ -1,7 +1,9 @@
namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>Optional observer for completed rolling planning cycles.</summary>
/// <summary>滚动规划周期完成后的可选观察器;实现不得依赖其回调来维持发布正确性。</summary>
public interface IEmPlanningCycleSink
{
/// <summary>接收已经确定的周期完成记录。</summary>
/// <param name="result">不可变周期结果;观察器异常会被协调器隔离并仅追加诊断。</param>
void OnCycleCompleted(PlanningCycleResult result);
}
@@ -1,7 +1,9 @@
namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>Execution-layer source of an immutable caller-owned vehicle-state snapshot.</summary>
/// <summary>执行层的车辆状态快照来源;具体实现可在模块外读取定位或硬件,但核心只消费返回的不可变快照。</summary>
public interface IVehicleStateProvider
{
/// <summary>捕获一次车辆位姿、带符号纵向速度、可选加速度、时刻和序列号快照。</summary>
/// <returns>不可变 <see cref="VehicleMotionState"/>;其位置单位为 m、航向为 rad、速度为 m/s。</returns>
VehicleMotionState Capture();
}
@@ -2,9 +2,15 @@ using System;
namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>Immutable identity bound to one rolling planning cycle.</summary>
/// <summary>绑定一次滚动周期的不可变版本身份;所有字段必须同时匹配,旧结果才可发布。</summary>
public sealed class PlanningCycleIdentity : IEquatable<PlanningCycleIdentity>
{
/// <summary>创建完整周期身份。</summary>
/// <param name="mapSnapshotId">规划地图快照的非负版本 ID。</param>
/// <param name="referencePathId">平滑参考路径的非空版本 ID。</param>
/// <param name="vehicleStateSequenceId">调用方捕获的车辆状态非负序列号。</param>
/// <param name="previousTrajectoryId">上一条轨迹 ID;没有时使用空字符串。</param>
/// <param name="segmentIndex">当前前进或倒车方向段的非负索引。</param>
public PlanningCycleIdentity(long mapSnapshotId, string referencePathId, long vehicleStateSequenceId,
string previousTrajectoryId, int segmentIndex)
{
@@ -24,6 +30,7 @@ public sealed class PlanningCycleIdentity : IEquatable<PlanningCycleIdentity>
SegmentIndex = segmentIndex;
}
/// <summary>规划地图快照版本 ID。</summary>
public long MapSnapshotId { get; }
public string ReferencePathId { get; }
@@ -34,6 +41,9 @@ public sealed class PlanningCycleIdentity : IEquatable<PlanningCycleIdentity>
public int SegmentIndex { get; }
/// <summary>从已冻结的 EM 请求派生发布身份。</summary>
/// <param name="request">必须携带地图和车辆状态快照的 EM 请求。</param>
/// <returns>包含请求地图、路径、状态、上一轨迹和方向段字段的不可变身份。</returns>
public static PlanningCycleIdentity FromRequest(EmPlanningRequest request)
{
if (request == null)
@@ -2,9 +2,12 @@ using System;
namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>Caller-captured request and clock value for one rolling cycle.</summary>
/// <summary>一次滚动周期的调用方冻结请求、身份和时钟值;构造后不读取外部状态。</summary>
public sealed class PlanningCycleInput
{
/// <summary>从一次 EM 规划请求和调用方时刻创建周期输入。</summary>
/// <param name="request">已冻结的 EM 请求;用于派生地图、路径、状态、上一轨迹和方向段身份。</param>
/// <param name="now">调用方提供的当前 <see cref="DateTimeOffset"/>;用于周期调度,不由协调器替换。</param>
public PlanningCycleInput(EmPlanningRequest request, DateTimeOffset now)
{
Request = request ?? throw new ArgumentNullException(nameof(request));
@@ -13,12 +16,16 @@ public sealed class PlanningCycleInput
ReplanPeriodSeconds = ReadReplanPeriod(request.Configuration);
}
/// <summary>本周期消费的不可变 EM 规划请求快照。</summary>
public EmPlanningRequest Request { get; }
/// <summary>从 <see cref="Request"/> 派生的完整版本身份,用于拒绝过期发布。</summary>
public PlanningCycleIdentity Identity { get; }
/// <summary>调用方冻结的周期开始时刻。</summary>
public DateTimeOffset Now { get; }
/// <summary>从请求配置读取的重规划周期,单位 s;无效配置回退为 0.20 s。</summary>
public double ReplanPeriodSeconds { get; }
private static double ReadReplanPeriod(EmPlannerConfiguration configuration)
@@ -2,9 +2,15 @@ using System;
namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>Immutable completion record for one rolling planning cycle.</summary>
/// <summary>一次滚动规划周期的不可变完成记录,区分规划结果与是否获准发布。</summary>
public sealed class PlanningCycleResult
{
/// <summary>创建周期完成记录。</summary>
/// <param name="version">协调器分配的正数单调周期版本。</param>
/// <param name="identity">本周期绑定的地图、路径、状态、上一轨迹和方向段身份。</param>
/// <param name="result">纯规划服务返回的结果,可能为 Superseded、取消或失败。</param>
/// <param name="published">仅当完整成功轨迹仍为当前身份时为 <see langword="true"/>。</param>
/// <param name="diagnostic">附加的周期或观察器诊断文本。</param>
public PlanningCycleResult(long version, PlanningCycleIdentity identity, EmPlanningResult result, bool published,
string diagnostic)
{
@@ -17,14 +23,19 @@ public sealed class PlanningCycleResult
Diagnostic = diagnostic ?? string.Empty;
}
/// <summary>本周期的单调递增版本号。</summary>
public long Version { get; }
/// <summary>决定该结果是否仍可发布的完整不可变身份。</summary>
public PlanningCycleIdentity Identity { get; }
/// <summary>纯 EM 规划结果;失败结果不携带可执行轨迹。</summary>
public EmPlanningResult Result { get; }
/// <summary>该结果是否已原子替换协调器的已发布轨迹。</summary>
public bool Published { get; }
/// <summary>周期处理或可选观察器产生的诊断文本。</summary>
public string Diagnostic { get; }
internal PlanningCycleResult WithDiagnostic(string diagnostic)
@@ -2,9 +2,13 @@ using System;
namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>Converts immutable execution state into a controller-neutral longitudinal and yaw command.</summary>
/// <summary>将不可变执行状态转换为控制器中立的纵向速度和 yaw rate 命令,并在停稳/完成时强制零运动。</summary>
public sealed class TrajectoryControlAdapter
{
/// <summary>依据选中轨迹点和换向状态创建安全控制命令。</summary>
/// <param name="trajectoryPoint">已验证轨迹中的选中点,提供带符号速度、yaw rate 和方向。</param>
/// <param name="executionState">不可变执行状态;停零、完成或不允许运动时优先于轨迹点运动字段。</param>
/// <returns>常规跟随时保留轨迹点 m/s 和 rad/s;保持或完成时返回零速度、零 yaw rate 和制动命令。</returns>
public TrajectoryControlCommand CreateCommand(EmTrajectoryPoint trajectoryPoint,
TrajectoryExecutionState executionState)
{
@@ -3,9 +3,19 @@ using MultiWheelC.TrajectoryPlanning.CoarsePath;
namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>Immutable generic motion command derived from a validated trajectory execution state.</summary>
/// <summary>
/// 从已验证轨迹执行状态导出的不可变、控制器中立运动命令。
/// 只表示带符号纵向速度和 yaw rate;不包含横向车体速度、蟹行、原地旋转、UI 或硬件字段。
/// </summary>
public sealed class TrajectoryControlCommand
{
/// <summary>创建控制器中立命令。</summary>
/// <param name="signedLongitudinalVelocity">车体纵向速度,单位 m/s;正数前进、负数倒车,保持制动时必须为 0。</param>
/// <param name="yawRate">车辆 yaw rate,单位 rad/s;保持制动或完成时必须为 0。</param>
/// <param name="direction">命令关联的实际行驶方向。</param>
/// <param name="requestDirectionChange">仅在零速 dwell 完成后的单个更新周期请求方向变更。</param>
/// <param name="holdBrake">为 <see langword="true"/> 时调用方必须保持零速度和零 yaw rate。</param>
/// <param name="isTrajectoryComplete">到达 Goal 或 RollingSafetyStop 后为 <see langword="true"/>。</param>
public TrajectoryControlCommand(double signedLongitudinalVelocity, double yawRate, TravelDirection direction,
bool requestDirectionChange, bool holdBrake, bool isTrajectoryComplete)
{
@@ -24,14 +34,18 @@ public sealed class TrajectoryControlCommand
IsTrajectoryComplete = isTrajectoryComplete;
}
/// <summary>权威带符号纵向速度,单位 m/s。</summary>
public double SignedLongitudinalVelocity { get; }
/// <summary>逆时针为正的 yaw rate,单位 rad/s。</summary>
public double YawRate { get; }
public TravelDirection Direction { get; }
/// <summary>是否在本次更新请求一次方向切换。</summary>
public bool RequestDirectionChange { get; }
/// <summary>是否要求调用方保持制动,不允许轨迹运动。</summary>
public bool HoldBrake { get; }
public bool IsTrajectoryComplete { get; }
@@ -2,7 +2,7 @@ using System;
namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>Immutable trajectory-execution outcome without controller or hardware coupling.</summary>
/// <summary>不耦合控制器或硬件的不可变轨迹执行状态,组合选中点和换向状态机的安全语义。</summary>
public sealed class TrajectoryExecutionState
{
internal TrajectoryExecutionState(EmTrajectoryPoint selectedPoint, GearSwitchStateUpdate gearSwitchUpdate)
@@ -19,14 +19,17 @@ public sealed class TrajectoryExecutionState
Reason = gearSwitchUpdate.Reason;
}
/// <summary>按调用方时刻选中或插值得到的轨迹点;位置为 m、航向为 rad、速度为 m/s。</summary>
public EmTrajectoryPoint SelectedPoint { get; }
public GearSwitchState GearSwitchState { get; }
/// <summary>是否要求保持零速度和零 yaw rate。</summary>
public bool HoldZero { get; }
public bool RequestDirectionChange { get; }
/// <summary>状态机是否允许跟随选中点的非零轨迹运动。</summary>
public bool AllowsTrajectoryMotion { get; }
public bool IsTrajectoryComplete { get; }
@@ -3,18 +3,21 @@ using MultiWheelC.TrajectoryPlanning.CoarsePath;
namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>Samples immutable EM trajectories and applies the pure zero-speed gear-switch state machine.</summary>
/// <summary>按调用方时刻采样不可变 EM 轨迹,并应用纯零速换向状态机;从不在轨迹末点之后外推。</summary>
public sealed class TrajectoryExecutor
{
private readonly TrajectorySampler sampler = new TrajectorySampler();
private readonly TrajectoryControlAdapter controlAdapter = new TrajectoryControlAdapter();
private readonly GearSwitchStateMachine gearSwitchStateMachine;
/// <summary>使用默认 EM 配置创建执行器,默认停车容差和零速 dwell 来自该配置。</summary>
public TrajectoryExecutor()
: this(EmPlannerConfiguration.CreateDefault())
{
}
/// <summary>使用指定 EM 配置创建执行器。</summary>
/// <param name="configuration">必须提供纵向停车速度容差(m/s)和零速 dwells)的配置快照。</param>
public TrajectoryExecutor(EmPlannerConfiguration configuration)
{
if (configuration?.Longitudinal == null)
@@ -24,8 +27,17 @@ public sealed class TrajectoryExecutor
configuration.Longitudinal.ZeroSpeedHoldSeconds);
}
/// <summary>最近一次更新产生的不可变执行状态;尚未调用 <see cref="Update"/> 时为 <see langword="null"/>。</summary>
public TrajectoryExecutionState State { get; private set; }
/// <summary>按调用方时刻选择轨迹点并更新换向状态机。</summary>
/// <param name="now">调用方当前时刻;相对轨迹生效时间换算为采样秒数。</param>
/// <param name="measuredState">车辆测量状态快照;带符号纵向速度单位 m/s,用于判断是否已停稳。</param>
/// <param name="trajectory">已发布的完整不可变轨迹;开始前取首点、结束后取末点且不外推。</param>
/// <param name="desiredDirection">下一方向段要求的实际行驶方向。</param>
/// <param name="currentDirection">调用方确认的当前实际方向。</param>
/// <param name="directionConfirmed">硬件/调用方是否已确认方向变更完成。</param>
/// <returns>包含选中点、换向状态、停零、换向请求和完成标记的不可变执行状态。</returns>
public TrajectoryExecutionState Update(DateTimeOffset now, VehicleMotionState measuredState, EmTrajectory trajectory,
TravelDirection desiredDirection, TravelDirection currentDirection, bool directionConfirmed)
{
@@ -45,7 +57,14 @@ public sealed class TrajectoryExecutor
return State;
}
/// <summary>Updates pure execution state and returns its controller-neutral motion command.</summary>
/// <summary>更新纯执行状态并返回控制器中立命令。</summary>
/// <param name="now">调用方当前时刻。</param>
/// <param name="measuredState">车辆测量状态快照。</param>
/// <param name="trajectory">已发布完整轨迹。</param>
/// <param name="desiredDirection">目标实际行驶方向。</param>
/// <param name="currentDirection">已确认当前方向。</param>
/// <param name="directionConfirmed">是否已确认换向。</param>
/// <returns>常规跟随时输出轨迹 m/s 与 rad/s;停零、等待确认或完成时输出零运动和制动。</returns>
public TrajectoryControlCommand UpdateCommand(DateTimeOffset now, VehicleMotionState measuredState,
EmTrajectory trajectory, TravelDirection desiredDirection, TravelDirection currentDirection,
bool directionConfirmed)
@@ -3,12 +3,14 @@ using MultiWheelC.TrajectoryPlanning.CoarsePath;
namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>下一轮规划起点的来源;只允许安全未来样本或调用方测量状态二选一。</summary>
public enum TrajectoryHandoffSource
{
MeasuredState,
PreviousTrajectory,
}
/// <summary>拒绝使用已发布轨迹进行交接的稳定原因;拒绝时必须回退到测量状态。</summary>
public enum TrajectoryHandoffRejectionReason
{
None,
@@ -25,7 +27,7 @@ public enum TrajectoryHandoffRejectionReason
SampleUnavailable,
}
/// <summary>Immutable handoff decision for the next one-shot planning request.</summary>
/// <summary>下一次单次规划的不可变交接决策;不会跨方向段、方向或终端边界复用旧轨迹。</summary>
public sealed class TrajectoryHandoffSelection
{
internal TrajectoryHandoffSelection(TrajectoryHandoffSource source, VehicleMotionState startState,
@@ -38,6 +40,7 @@ public sealed class TrajectoryHandoffSelection
RejectionReason = rejectionReason;
}
/// <summary>交接使用未来旧轨迹样本还是调用方测量状态。</summary>
public TrajectoryHandoffSource Source { get; }
public VehicleMotionState StartState { get; }
@@ -49,12 +52,13 @@ public sealed class TrajectoryHandoffSelection
public TrajectoryHandoffRejectionReason RejectionReason { get; }
}
/// <summary>Selects a future same-segment seed or preserves the caller's measured state.</summary>
/// <summary>选择安全的同段同方向未来轨迹种子;任一检查失败时保留调用方测量状态。</summary>
public sealed class TrajectoryHandoffSelector
{
private const double TimeEpsilonSeconds = 1e-9d;
private readonly TrajectorySampler sampler;
/// <summary>使用默认同质区间采样器创建交接选择器。</summary>
public TrajectoryHandoffSelector()
: this(new TrajectorySampler())
{
@@ -65,6 +69,14 @@ public sealed class TrajectoryHandoffSelector
this.sampler = sampler ?? throw new ArgumentNullException(nameof(sampler));
}
/// <summary>从已发布轨迹选择下一轮规划的未来交接状态。</summary>
/// <param name="previousTrajectory">最后一条完整已发布轨迹;为 <see langword="null"/> 时直接使用测量状态。</param>
/// <param name="measuredState">调用方当前车辆状态快照;位置 m、航向 rad、带符号速度 m/s。</param>
/// <param name="expectedSegmentIndex">下一轮请求期望的非负方向段索引。</param>
/// <param name="expectedDirection">下一轮请求期望的实际行驶方向。</param>
/// <param name="now">调用方当前时刻,用于检查旧轨迹年龄和 lookahead。</param>
/// <param name="configuration">包含最大状态年龄、lookahead 和追踪容差的配置快照。</param>
/// <returns>只有年龄、追踪、同段同方向、末点和边界检查均通过时返回未来轨迹样本;否则返回测量状态及拒绝原因。</returns>
public TrajectoryHandoffSelection Select(EmTrajectory previousTrajectory, VehicleMotionState measuredState,
int expectedSegmentIndex, TravelDirection expectedDirection, DateTimeOffset now,
EmPlannerConfiguration configuration)
@@ -2,11 +2,16 @@ using System;
namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
/// <summary>Interpolates an immutable trajectory only inside one homogeneous trajectory interval.</summary>
/// <summary>仅在同一方向段、同一方向和同一边界类型的轨迹区间内插值不可变轨迹。</summary>
public sealed class TrajectorySampler
{
private const double TimeEpsilonSeconds = 1e-9d;
/// <summary>按轨迹起点后的时间采样一个同质区间内的轨迹点。</summary>
/// <param name="trajectory">完整不可变轨迹;其点序列必须按 <c>TimeFromStart</c> 严格递增。</param>
/// <param name="timeFromStart">相对轨迹起点的采样时刻,单位 s。</param>
/// <param name="sampledPoint">成功时为原始点或插值新点;失败时为 <see langword="null"/>。</param>
/// <returns>时刻位于轨迹范围内且不需跨方向段/方向/边界插值时为 <see langword="true"/>;区间外或跨边界时为 <see langword="false"/>。</returns>
public bool TrySample(EmTrajectory trajectory, double timeFromStart, out EmTrajectoryPoint sampledPoint)
{
sampledPoint = null;