# CoarsePath 粗路径规划(P0/P1) `CoarsePath` 在 `Map` 提供的不可变 `PlanningGridMap` 上执行 Hybrid A*,输出已经过连续碰撞、终点和输出不变量复核的粗路径。它只处理“能否安全地从当前车辆几何中心到达目标”的几何搜索;不读取传感器或定位,不绘制 UI,也不向底盘发送任何命令。 地图障碍物来源、世界坐标栅格化、距离场和缓存细节由 [Map/README.md](../Map/README.md) 说明。本模块唯一建议的业务调用入口是: ```csharp CoarsePathPlanningService.Plan(job, cancellationToken) ``` ## 模块说明(Module Overview) | 模块 | 负责内容 | 不负责内容 | | --- | --- | --- | | `Map` | 外部障碍物快照、占据栅格、保守障碍距离、地图缓存 | 车辆足迹、运动原语、路径搜索、控制 | | `CoarsePath` | 车辆扩大足迹、连续碰撞检查、前进/倒车原语、Hybrid A*、路径复核与方向分段 | 传感器读取、定位读取、速度规划、路径跟踪、底盘命令 | | `Facade` | 将建图、搜索、取消、总预算和可选调试旁路编排为一次调用 | 修改地图内容、写入 AMR 占据、执行轨迹 | | `Test` | 固定回归场景、P1 手动测试入口与规划结果可视化 | 真实作业地图、实时重规划或车辆控制 | 安全余量只由 `VehicleParameters.SafetyMarginMeters` 扩大车辆矩形。它不会回写到地图障碍物,因此同一个 `PlanningGridMap` 可由不同车辆参数重复使用。 ## 文件结构(File Structure) ```text CoarsePath/ ├── README.md # 本模块说明:结构、数据流、调用与测试 ├── HybridAStarPlanner.cs # 公开规划门面后的核心编排:校验、搜索、回溯和复核 ├── Contracts/ │ ├── Pose2D.cs # 车辆几何中心位姿:m / rad │ ├── PlanningRequest.cs # 已有 PlanningGridMap 上的一次内部搜索请求 │ ├── PlanningResult.cs # 不可变规划结果:成功路径或空结果 │ ├── PlanningStatus.cs # 成功、取消、无解、输入和资源限制状态 │ ├── CoarsePathPoint.cs # 稠密路径点、方向、曲率、净空和换向标记 │ ├── PathSegment.cs # 前进或倒车的包含式路径索引段 │ ├── VehicleParameters.cs # 车体尺寸、安全余量和曲率限制 │ ├── HybridAStarConfiguration.cs # 原语、离散、代价、容差和资源上限 │ └── GoalDirectionConstraint.cs # 目标进入方向约束 ├── Vehicle/ │ ├── VehicleKinematics.cs # 恒曲率车辆运动学积分 │ ├── VehicleFootprint.cs # 扩大后的车辆矩形几何 │ ├── OrientedRectangleCellIntersection.cs # 旋转矩形与占据格相交判定 │ └── FootprintCollisionChecker.cs # 连续扫掠的足迹碰撞检查 ├── Search/ │ ├── BinaryMinHeap.cs # 可更新优先级的 Open List │ ├── GridDijkstraHeuristic.cs # 二维栅格可达性和距离启发式 │ ├── GoalToleranceChecker.cs # 目标位置、航向和方向约束判定 │ ├── MotionPrimitive.cs # 单个前进或倒车恒曲率原语 │ ├── MotionPrimitiveGenerator.cs # 原语离散与连续积分点生成 │ ├── SearchCostCalculator.cs # 长度、倒车、换向、曲率和净空代价 │ ├── HybridAStarNode.cs # 搜索节点与父链信息 │ ├── HybridAStarNodeKey.cs # 离散状态键 │ └── HybridAStarSearch.cs # Hybrid A* 主搜索循环 ├── Output/ │ ├── PathBacktracker.cs # 从终点节点安全回溯父链 │ ├── CoarsePathAssembler.cs # 组装稠密路径和方向段 │ └── CoarsePathValidator.cs # 对最终输出重新进行连续复核 ├── Facade/ │ ├── CoarsePathPlanningJob.cs # 一次完整业务输入:地图请求、位姿、车辆、配置 │ ├── CoarsePathPlanningJobResult.cs # 同时包含地图结果和规划结果的不可变输出 │ ├── CoarsePathPlanningService.cs # 唯一业务调用门面 │ ├── PlanningDebugOptions.cs # 可选调试旁路配置 │ └── IPlanningDebugSink.cs # 调试旁路接收器契约 └── Test/ ├── CoarsePathScenarioFactory.cs # 六个固定场景和手动目标演示请求工厂 └── MovementTest.CoarsePathTest.cs # 七个 Clumsy 入口、后台取消和 Painter 绘制 ``` ## 规划数据流(Planning Data Flow) ```text CoarsePathPlanningJob │ MapRequest 使用 mm;Pose2D/车辆使用 m、rad ▼ CoarsePathPlanningService.Plan(job, cancellationToken) │ ├── PlanningMapFactory.Create(job.MapRequest) │ │ │ ├── 失败、取消或超时 │ │ └── MapResult + 空路径 PlanningResult,搜索不启动 │ │ │ └── 成功:不可变 PlanningGridMap ▼ PlanningRequest │ ▼ HybridAStarPlanner ├── 车辆扩大足迹与连续碰撞检查 ├── GridDijkstraHeuristic + Hybrid A* 搜索 ├── PathBacktracker + CoarsePathAssembler └── CoarsePathValidator 最终复核 ▼ PlanningResult + MapResult ▼ CoarsePathPlanningJobResult ``` 调用方只创建 `CoarsePathPlanningJob` 并消费 `CoarsePathPlanningJobResult`。`PlanningRequest`、`HybridAStarPlanner`、原语和碰撞检查器属于模块内部协作对象,不应由 UI、传感器或 MovementTest 直接拼接。 ## 构建状态与停止(Build Status and Stop) 必须一起处理 `MapResult` 和 `PlanningResult`。`MapResult.Status` 的类型是 `PlanningMapBuildStatus`;地图失败时,门面返回对应的空路径结果,并且不会启动 Hybrid A*。 | 情况 | `MapResult` | `PlanningResult` | 调用方处理 | | --- | --- | --- | --- | | 地图和搜索成功 | `Success` 且 `Map` 非空 | `Success`,发布完整路径和方向段 | 消费粗路径;后续模块仍需自行进行平滑、速度和控制 | | 地图输入/来源失败 | `Failed` | `InvalidMap` | 读取 `FailureReason`,修复地图输入 | | 调用被取消 | `Cancelled` | `Cancelled` | 不重试为普通无解;不会发布地图或部分路径 | | 总预算耗尽 | `TimedOut` | `SearchTimeout` | 根据上层策略调整预算或稍后重试 | | 搜索无解 | 地图成功 | `NoFeasiblePath` | 当前地图、车体和运动约束下无可行路径 | | 节点或搜索资源受限 | 地图成功 | `SearchNodeLimitExceeded` 或 `SearchTimeout` | 读取诊断后调整配置或上层策略 | 除 `PlanningStatus.Success` 外,`PlanningResult.Path` 与 `PlanningResult.Segments` 始终为空。取消、超时、无解、输入错误和最终复核失败都不能作为“部分可执行路径”使用。 ### 总预算与取消 `HybridAStarConfiguration.SearchTimeout` 是从门面开始计时的一次总预算,依次覆盖建图、距离场、二维启发式和 Hybrid A*。同一个 `CancellationToken` 会沿调用链传递,取消优先于超时。 ### 总耗时与路径搜索耗时 `PlanningDiagnostics.Elapsed` 是从 `CoarsePathPlanningService.Plan` 开始的总耗时,包含地图来源、缓存、栅格化、距离场和路径规划。`PathSearchElapsed`(路径搜索耗时)从地图和起终点预检通过后开始,包含二维启发式、Hybrid A*、回溯、装配、方向分段和最终复核;搜索开始前失败时为零。 ## 坐标与单位(Coordinates and Units) | 数据 | 单位 | 说明 | | --- | --- | --- | | `PlanningMapRequest.Bounds`、分辨率、障碍物几何 | mm | 来自 Map 的世界坐标;边界采用 `[min, max)` | | `Pose2D.X`、`Pose2D.Y`、路径位置、车辆尺寸、安全余量、弧长 | m | CoarsePath 的连续世界坐标和长度 | | `Pose2D.Heading`、航向容差 | rad | 核心一律使用弧度 | | 曲率、起步曲率 | 1/m | 最大曲率或最小转弯半径至少提供一个 | | `PlanningGridMap` 世界查询参数 | m | 越界位置按占据处理,净距为 0 | | P1 的 AMR/手动目标 X/Y | mm | 仅在 UI 边界读取,进入核心前除以 1000 | | P1 的 AMR/手动目标航向 | deg | 仅在 UI 边界转换为 `deg * PI / 180 -> rad` | 起点和终点都表示车辆**几何中心**。若上游定位参考点是雷达、天线或其他安装点,必须先在上游应用安装外参;不要在 CoarsePath 内猜测偏移。车辆外扩由 `VehicleParameters.SafetyMarginMeters` 表达,不要把余量写入 Map 障碍物。 ## 最小调用示例(Minimal Call Example) 以下示例明确允许空图,因而只适合算法或单位演示。真实作业必须通过 `IMapObstacleSource` 提供有效障碍物快照;如何构造来源请阅读 [Map/README.md](../Map/README.md)。 ```csharp using System; using System.Threading; using MultiWheelC.TrajectoryPlanning.CoarsePath; using MultiWheelC.TrajectoryPlanning.CoarsePath.Facade; using MultiWheelC.TrajectoryPlanning.Mapping; var service = new CoarsePathPlanningService(); // 长期持有,保留地图缓存 var job = new CoarsePathPlanningJob { MapRequest = new PlanningMapRequest { Bounds = new MapBoundsMm(0f, 6000f, 0f, 4000f), ResolutionMm = 50f, ObstacleSources = Array.Empty(), AllowExplicitEmptyMap = true, // 仅演示时明确允许 }, Start = new Pose2D(1d, 1d, 0d), Goal = new Pose2D(3d, 1d, 0d), Vehicle = new VehicleParameters { LengthMeters = 0.80d, WidthMeters = 0.60d, SafetyMarginMeters = 0.05d, MaximumCurvaturePerMeter = 1d / 1.20d, }, Configuration = new HybridAStarConfiguration(), GoalDirection = GoalDirectionConstraint.Forward, }; CoarsePathPlanningJobResult result = service.Plan(job, CancellationToken.None); if (!result.MapResult.Succeeded) throw new InvalidOperationException(result.MapResult.FailureReason); if (result.PlanningResult.Status != PlanningStatus.Success) throw new InvalidOperationException( result.PlanningResult.Diagnostics.TerminationReason); foreach (CoarsePathPoint point in result.PlanningResult.Path) Console.WriteLine(point.X + "," + point.Y + "," + point.Heading); ``` ## 缓存与 SourceVersion(Cache and SourceVersion) `CoarsePathPlanningService` 在生命周期内长期持有 `PlanningMapFactory`,因此重复调用时能够复用地图缓存。不要每次规划都新建服务,否则会失去缓存收益。 | 缓存层级 | 条件 | 结果 | | --- | --- | --- | | `Input` | 边界、分辨率、空图策略、来源 ID、`SourceVersion`、必需性和来源结果相同 | 返回同一个不可变 `PlanningGridMap` | | `Occupancy` | 输入版本变化,但最终占据栅格相同 | 复用占据/距离数组,生成新的快照元数据 | | `None` | 占据内容变化 | 重建规划快照和距离场 | 障碍来源内容改变时,调用方必须递增该来源的 `SourceVersion`。仅修改起终点、车辆、搜索配置、调试开关或调试接收器不会改变地图输入;改变障碍物却不递增版本则可能错误复用旧快照。 ## 详细使用指南(Detailed Usage Guide) 本节说明调用方如何从一个地图输入得到可消费的粗路径。所有业务调用都通过 `CoarsePathPlanningService.Plan(job, cancellationToken)` 完成。 ### 第 1 步:长期持有服务 服务持有地图工厂和规划器,应该作为规划业务、任务执行器或上层服务的长期字段,而不是在每次调用中创建: ```csharp private readonly CoarsePathPlanningService _coarsePathService = new CoarsePathPlanningService(); ``` ### 第 2 步:准备地图请求 创建 `PlanningMapRequest`,其边界、分辨率和障碍物仍使用 mm。使用手工圆形/矩形或 TwoLeg 快照时,应先按 [Map/README.md](../Map/README.md) 将它们包装为 `IMapObstacleSource`,并为内容变化递增 `SourceVersion`。 ```csharp var mapRequest = new PlanningMapRequest { Bounds = new MapBoundsMm(0f, 6000f, 0f, 4000f), ResolutionMm = 50f, ObstacleSources = sources, AllowExplicitEmptyMap = false, }; ``` `AllowExplicitEmptyMap = true` 只在调用方明确确认空地图安全时使用。未提供有效障碍物且未显式允许空图时,地图不会进入规划。 ### 第 3 步:填写起点、终点和方向约束 将车辆几何中心的世界 X/Y 从 mm 转为 m,并将航向转换为 rad 后创建 `Pose2D`。`StartDirection = null` 表示允许从前进或倒车开始;`GoalDirection` 可以限制最终进入目标的方向。 ```csharp var start = new Pose2D(startXmm / 1000d, startYmm / 1000d, startHeadingDeg * Math.PI / 180d); var goal = new Pose2D(goalXmm / 1000d, goalYmm / 1000d, goalHeadingDeg * Math.PI / 180d); ``` ### 第 4 步:填写车辆参数 车辆尺寸和安全余量全部为 m。曲率限制可填写 `MaximumCurvaturePerMeter`,或填写 `MinimumTurningRadiusMeters`;至少必须提供一个有效限制。 ```csharp var vehicle = new VehicleParameters { LengthMeters = 0.80d, WidthMeters = 0.60d, SafetyMarginMeters = 0.05d, MaximumCurvaturePerMeter = 1d / 1.20d, }; ``` ### 第 5 步:调整搜索配置 默认 `HybridAStarConfiguration` 包含原语长度、积分步长、航向离散、终点容差、代价、节点上限和总超时。若业务需要覆盖默认值,应同时理解安全影响:`MaximumCollisionCheckStepMeters` 不能以牺牲连续碰撞检查精度为代价随意增大。 ```csharp var configuration = new HybridAStarConfiguration { SearchTimeout = TimeSpan.FromSeconds(5d), MaximumExpandedNodes = 200000, }; ``` ### 第 6 步:调用并消费成功结果 只有 `Success` 可以发布完整路径。`Path` 是稠密点序列;`Segments` 是覆盖整条路径的前进/倒车包含式索引段,可供后续的速度规划或显示模块消费。 ```csharp var job = new CoarsePathPlanningJob { MapRequest = mapRequest, Start = start, Goal = goal, Vehicle = vehicle, Configuration = configuration, GoalDirection = GoalDirectionConstraint.Any, }; CoarsePathPlanningJobResult result = _coarsePathService.Plan(job, cancellationToken); if (!result.MapResult.Succeeded) ReportMapFailure(result.MapResult.FailureReason); else if (result.PlanningResult.Status == PlanningStatus.Success) ConsumeCoarsePath(result.PlanningResult.Path, result.PlanningResult.Segments); else ReportPlanningFailure(result.PlanningResult.Diagnostics.TerminationReason); ``` `CoarsePathPoint.IsGearSwitchPoint` 为 `true` 表示该点是新方向段开始处。换向位置会保留一对位置、航向与弧长相同、方向不同的相邻点;`UnwrappedHeading` 用于跨越 `-pi/pi` 时保持显示连续。 ## P1 手动测试与可视化(P1 Manual Tests and Visualization) P1 在 `Test/MovementTest.CoarsePathTest.cs` 提供只读测试入口。它们共用一个长期存活的 `CoarsePathPlanningService`,只提交规划并绘制结果;不发送底盘、速度或转向命令。 | MovementTest 名称 | 场景 | 预期 | | --- | --- | --- | | `粗路径规划-显式空图` | 显式允许的空图 | 前进直达成功 | | `粗路径规划-单矩形绕行` | 中央矩形阻断直线 | 成功绕障 | | `粗路径规划-多来源障碍` | 手工圆形、矩形和 TwoLeg 快照 | 证明多来源经过同一门面 | | `粗路径规划-缓存命中` | 重复相同地图输入 | 后续调用显示 `Input` 缓存命中 | | `粗路径规划-倒车换向` | 前进起步、倒车到达 | 成功路径含 `IsGearSwitchPoint` | | `粗路径规划-无解` | 贯穿地图的障碍带 | 返回 `NoFeasiblePath` 且不发布路径 | | `粗路径规划` | 当前 AMR 位姿、人工终点和可选人工障碍物 | 验证手动障碍物、边界、路径与可视化 | ### 固定案例的实时 AMR 锚定 `粗路径规划-显式空图`、`粗路径规划-单矩形绕行`、`粗路径规划-多来源障碍`、`粗路径规划-缓存命中`、`粗路径规划-倒车换向` 和 `粗路径规划-无解` 是六个固定案例。它们不再以写死的世界起点运行:共享运行器在前台仅调用一次 `DetourInterface.getCartLocation()`,校验并冻结本次 AMR 的世界 `X(mm)`、`Y(mm)` 与航向 `deg`,再创建本次规划请求;后台规划期间不会再次读取定位。 `Create(CoarsePathTestScenario scenario, double amrXMillimeters, double amrYMillimeters, double amrHeadingDegrees)` 该入口把基准案例的起点映射为冻结的 AMR 位姿,并以相同的 `ΔX/ΔY` 平移地图边界、目标、圆形/矩形障碍,以及 TwoLeg 的检测原点。因此,固定案例始终在当前 AMR 附近保留原有的相对几何关系。起点航向严格使用冻结的 AMR 航向;终点航向保持基准案例的“终点航向减起点航向”差值,叠加到当前 AMR 航向后规范化到 `[-pi, pi]`。TwoLeg 只平移检测原点,`DetectionHeadingRadians` 不会因 AMR 航向发生旋转。 `粗路径规划-缓存命中` 只有两次运行冻结到相同的 AMR `X/Y`、从而形成相同的平移后地图输入时,才作为缓存命中场景;AMR 位置移动后,地图输入正常未命中并重建快照。仅 AMR 航向变化不会改变固定案例的地图输入,地图缓存仍可命中。若定位读取为空、抛出异常,或 `X`、`Y`、航向含有 `NaN`/无穷值,运行器不会提交后台规划,状态与 Toast 会显示以“AMR 位姿不可用”开头的诊断原因。 手动 `粗路径规划` 的人工终点、障碍物和超时输入流程保持不变;它不套用固定案例的整体平移规则。 ### AMR 位姿、手动终点与障碍物 `CoarsePathPlanningTest` 启动时读取一次 `DetourInterface.getCartLocation()`,将当前 AMR 世界位姿冻结为起点;随后依次输入目标世界 `X(mm)`、`Y(mm)`、航向 `deg`,以及障碍物数量 `0-20`。每个障碍物再依次输入类型和几何参数: | 类型输入 | 形状 | 输入参数(全部为 mm) | | --- | --- | --- | | `1` | 圆形 | 几何中心 `X`、`Y` 与半径;半径必须大于 0 | | `2` | 轴对齐矩形 | 几何中心 `X`、`Y`、X 向长度、Y 向宽度;两个尺寸必须大于 0 | 矩形只支持 `AxisAlignedRectangle`,不提供旋转角;其中心和长宽由 `ManualCoarsePathObstacle.AxisAlignedRectangle` 表达。圆形由 `ManualCoarsePathObstacle.Circle` 表达。所有无穷、NaN、非数字或不合法尺寸都会在输入阶段拒绝。 `CoarsePathScenarioFactory.CreateManualObstacleDemo` 在唯一边界完成转换: ```text AMR/目标 X、Y:mm / 1000 -> m AMR/目标航向:deg * PI / 180 -> rad ``` 当数量为 0 时,入口使用显式空图;`CreateManualGoalDemo` 保留为同一零障碍物场景的兼容帮助方法。数量大于 0 时,工厂将 `ManualCoarsePathObstacle` 快照封装成来源 ID 为 `manual-user-input` 的地图输入,并为每次手动快照分配新的 `SourceVersion`,避免错误复用地图缓存。规划边界覆盖起点、终点和每个障碍物的完整轮廓,再增加 8000 mm 余量并按 50 mm 对齐。 手动入口还要求输入一次“粗路径规划总超时”,单位为秒,只接受 `TimeSpan` 可表示范围内的有限正数秒;`0`、负数、NaN、Infinity 或溢出值都会在启动规划前拒绝。该值只覆盖本次 `CoarsePathPlanningJob.Configuration.SearchTimeout`,不会改变固定场景或全局默认值。 此入口使用固定演示车辆:长 `0.80 m`、宽 `0.60 m`、四周安全余量 `0.05 m`、最小转弯半径 `1.20 m`。这些值不是从现场 AMR 配置读取的,判断现场可行性前必须确认车辆参数一致。 `getCartLocation` 在无有效定位时可能阻塞,因此应在定位准备完成的测试环境使用。手动障碍物是测试输入,不能替代现场障碍物来源;零障碍物的显式空图也绝不代表现场不存在障碍物。 ### 后台执行、停止与图层 每次测试启动时会创建独立的 `CancellationTokenSource`,以 `Task.Run` 调用门面,并先取消旧会话。`Test()` 不等待任务,也不读取 `Task.Result`;`TestStop` 取消当前令牌、使会话失效并清空图层。已取消任务完成后不会覆盖新会话,也不会显示部分路径。 专用世界坐标 Painter 图层为 `CoarsePathPlanningV1`。它直接读取本次 `PlanningGridMap` 的 `Bounds`、`ResolutionMm`、`SnapshotId` 和 `IsOccupied(row, col)`,因此边界、抽稀网格和占据格与实际规划快照一致,而不是重新绘制原始障碍物。 状态图层显示规划状态、总耗时、`PathSearchElapsed`(路径搜索耗时)、扩展/生成节点数、Open List 峰值、失败原因和固定演示车辆参数。Toast 同时显示两种耗时,并在失败时附加 `TerminationReason`,因此超时、节点上限、无解、碰撞和内部错误不会只显示成泛化失败。 | 颜色 | 可视化元素 | | --- | --- | | 灰白 | 地图边界与栅格网络 | | 暗红 | 占据格 | | 绿色 | 起点、前进路径和方向箭头 | | 橙色 | 终点、航向和位置容差圈 | | 天蓝 | 倒车路径和方向箭头 | | 紫色 | 换向点 | | 金色 | 已纳入安全余量的车辆检查框 | 自动化已检查 UI 入口的后台、取消和数据来源结构。仍需在实际 Clumsy 界面手动运行“粗路径规划-单矩形绕行”和“粗路径规划”,确认图层交互显示与停止按钮效果。 ## 常见错误(Common Errors) | 现象 | 原因 | 处理 | | --- | --- | --- | | 起点、终点或障碍物位置相差 1000 倍 | 将 mm 直接传给 `Pose2D` 或把 m 传给地图输入 | Map 输入使用 mm;`Pose2D`、车辆和路径使用 m | | 路径朝向错误或旋转异常 | 将 P1 的 deg 直接当作核心 rad | 在 UI/上层边界执行 `deg * PI / 180`,核心只保存 rad | | 障碍物已经变化却复用旧地图 | 内容变更后没有递增 `SourceVersion` | 每次来源快照内容变化后增加对应版本号 | | 地图创建成功但规划被阻止 | 没有有效障碍物且未显式允许空图 | 提供有效来源;仅在确认安全的演示中设置 `AllowExplicitEmptyMap = true` | | 无解、取消或超时后仍尝试使用路径 | 没有检查 `PlanningStatus.Success` | 仅成功时消费 `Path` 和 `Segments`;其他状态读取诊断 | | 将粗路径直接下发给车辆 | 粗路径不包含速度、时间、执行控制或实时安全闭环 | 在后续阶段增加平滑、时间参数化、跟踪和独立安全控制 | | P1 手动终点表现为空场地安全 | 手动入口使用显式空图演示 | 真实作业必须提供真实障碍物快照,不能复用空图语义 | ## 第一版限制(First-Version Limits) 当前 P0/P1 已提供安全、确定性的粗路径核心和手动结果可视化,但不包含: - 路径平滑或曲率连续优化; - Reeds-Shepp 或 Dubins 精确终点连接; - 速度、加速度、时间标注、时间轨迹和路径跟踪控制; - 底盘命令、避障闭环、现场传感器采集或实时重规划调度; - 横移、蟹行或其他非前进/倒车运动原语; - 原地旋转; - 真实作业地图接入、交互式场景编辑和 Release 性能/资源/确定性基准。 当前只生成汽车式恒曲率前进/倒车原语,并允许在原语边界换向;未实现的 Reeds-Shepp、横移、蟹行和原地旋转是整个粗规划核心的第一版能力边界,不是 MovementTest 单独关闭。 因此,调用方只能把 `Success` 结果视作后续模块的粗路径输入,不能把它当作可直接下发的时间轨迹。