Files
ParkingRobot/docs/superpowers/specs/2026-07-27-p1-coarse-path-ui-integration-design.md
T

11 KiB
Raw Blame History

P1 粗路径 Clumsy UI 集成设计

目标

在不改变 P0 地图、Hybrid A* 与碰撞安全语义的前提下,为 Clumsy 增加可手动运行的粗路径场景测试。使用者能够从 MovementTest 列表启动固定场景,或传入 AMR 当前世界位姿并手动输入终点;界面显示栅格地图、起点、终点、连续路径、换向点和扩大车辆矩形检查点,并可随时停止正在进行的规划。

本设计只覆盖 P1 的首个交付:场景工厂、后台 MovementTest、Painter 可视化、自动化集成检查和模块 README。Release 性能基准属于 P1 的下一项交付;旧 TrapMap 迁移、旧验证脚本和旧入口的清理按已确认范围排除。

既有边界

  • 业务入口仍唯一为 CoarsePathPlanningService.Plan(job, cancellationToken)MovementTest 不自行拼接 PlanningMapFactoryHybridAStarPlanner、栅格化器、碰撞器、原语或搜索节点。
  • PlanningMapRequest 的边界、分辨率和障碍物几何使用 mm;CoarsePathPlanningJob 位姿、车辆尺寸和路径点使用 m,航向使用 rad。
  • 项目传入的 AMR 位姿采用世界 X/Y(mm) 与航向 th(deg)。P1 只在 UI 边界将其一次性转换为 Pose2D(X / 1000, Y / 1000, th × pi / 180);规划核心不接受度或 mm 位姿。当前车队路径代码将来自 getCartLocation().th 的姿态与度制角相加,并在调用三角函数前显式除以 180 再乘 pi,因此 P1 不沿用旧 Movements.cs 直接对 .th 调用 Math.Cos/Sin 的不一致写法。
  • AMR 起点必须表示车辆几何中心。若上游定位的参考点是雷达、天线或其他安装点,上游必须先按外参转换到车辆几何中心;安全余量仍只由 VehicleParameters 表达。
  • PlanningResult 只有 Success 才能携带完整路径;取消、超时、无解和失败不得在 UI 上表现为部分路径。
  • Painter 在现有后台多车线程中已被调用,因此本设计允许规划任务完成后的后台回调操作该图层;规划核心本身始终不依赖 UI。
  • 注释延续 P0 风格:公开类型与成员使用中文 XML 文档,说明单位、并发/停止语义和返回行为;会影响竞态的内部代码保留简短中文行注释。

方案选择

采用“六个薄 MovementTest 入口 + 共享后台执行器”的方案。

每个入口对应一个已命名的固定场景,便于在 Clumsy 的测试列表中直接运行;它们共用同一个静态 CoarsePathPlanningService,因此既能复用地图缓存,也不会让测试代码绕开门面。一个位于同一源文件内的执行器负责互斥会话、Task 生命周期、取消和绘制,避免六个入口复制并发逻辑。

不采用单一测试入口配合代码常量切换,因为手动验证需要反复改代码;也不采用运行时弹窗选项,因为这会增加 UI 输入状态和无法直接观察每个场景的可发现性。

文件与职责

文件 职责
ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/CoarsePathScenarioFactory.cs 创建不读取 UI、传感器、定位或时钟的固定 CoarsePathPlanningJob 场景。每次创建均返回新请求对象。
ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/MovementTest.CoarsePathTest.cs 声明七个 MovementTest 入口,以及共享服务、后台执行、取消、结果日志、AMR 位姿/手动终点输入与 Painter 绘制。
ClumsyPilot/tests/verify_coarse_path_integration.ps1 通过真实程序集反射验证场景、门面调用约束、缓存、换向、无解、取消和测试代码结构。
ClumsyPilot/ParkrobTrajplanner/CoarsePath/README.md 补充 P1 手动测试方法、颜色图例、单位转换、停止语义和非目标。

所有 UI 辅助类型保留在 CoarsePath/Test 内;不会向 MapSearchVehicleFacade 增加 UI 依赖。

场景工厂

CoarsePathScenarioFactory 公开一个场景枚举和按枚举创建请求的方法。工厂的职责仅是构造纯输入;它不持有服务、缓存、Painter 或取消源。每个请求采用同一组可验证的车辆和搜索默认值,再按场景覆盖障碍物、起终点和方向约束。

场景固定使用世界 mm 地图边界和分辨率,向 Pose2D 写入对应的 m 坐标。障碍物只通过 ManualObstacleSourceTwoLegObstacleSource 进入 PlanningMapRequest,并为内容变化提供固定且正确的 SourceVersion

场景 地图与预期
显式空图 AllowExplicitEmptyMap=true,直达前进路径成功,用于检查最短调用链。
单矩形绕行 中央矩形阻断直线,路径成功且必须绕障。
手工圆、矩形与 TwoLeg 同时使用手工圆形、手工矩形和有效 TwoLeg 快照,路径成功,证明多来源经过同一门面。
缓存命中 连续以新建但完全相同的输入调用同一服务两次;第二次 MapResult.CacheHit 必须为 Input
倒车换向 起步方向限制为前进、终点进入方向限制为倒车;成功路径必须出现标记的换向点。
无解 完全贯穿地图的障碍带隔开起点和终点,返回 NoFeasiblePath 且无路径。
AMR 位姿与手动终点 起点使用上层传入并冻结的 AMR 世界位姿;操作者输入同一世界系的终点 X/Y/航向。该入口仅使用明确提供的障碍物快照,显式空图只能作为演示,不能代表现场无障碍。

实现期间先用自动化断言固定每个场景的状态;若需为当前 P0 运动原语调整数值,只能调整场景几何或请求参数,不能放宽碰撞、目标或失败语义。

后台会话与取消

共享执行器持有一个静态、长期存活的 CoarsePathPlanningService。任一入口启动时会创建新的会话:运行编号、专用 CancellationTokenSource、场景描述和后台 Task。启动新会话前取消旧会话,以确保同时最多只有一个可绘制的规划结果。

AMR 位姿和手动终点在创建任务前被转换、有限值校验并冻结,随后只作为 CoarsePathPlanningJob 数据传给后台。MovementTest 不在规划后台持续读取定位;若未来接入可能阻塞的 DetourInterface.getCartLocation(),它必须位于独立的上游快照提供者,不能阻塞 UI 或绕过本设计的输入契约。

MovementTest.Test
  -> 生成场景的全新 CoarsePathPlanningJob
  -> 创建运行编号和 CancellationTokenSource
  -> Task.Run(() => service.Plan(job, token))
  -> 完成回调:仅当运行编号仍为当前会话时记录并绘制结果

MovementTest.TestStop
  -> 取消当前 CancellationTokenSource
  -> 使当前运行编号失效并解绑 Task 引用
  -> 清空专用 Painter 图层
  -> 旧任务完成后只释放其 CancellationTokenSource,不再绘制

Test 绝不等待 Task、不读取 Task.Result,因此不会阻塞 Clumsy 界面。TestStop 不等待规划任务退出;P0 的共享预算会将令牌传递至建图、EDT、Dijkstra 和 Hybrid A*,任务在其检查点返回 Cancelled。运行编号检查可防止已取消的旧任务在新任务结果之后覆盖画面。

任务异常只记录清晰的测试诊断并释放资源,不伪造 PlanningResult。正常停止、超时、无解与输入失败均使用门面实际返回的状态。

所有七个入口只创建规划请求、任务和绘制;不得引用 BasicPilotBase.ChassisSendMotionDriveTask 或任何底盘控制 API。

绘制规则

使用独立的全局世界坐标 Painter 图层,例如 CoarsePathPlanningV1。Painter 输入为 mm,因此所有来自 Pose2DCoarsePathPoint 的 X/Y 必须乘以 1000;航向仍以 rad 计算旋转矩形。不得混用 Map 的 mm 和 CoarsePath 的 m。所有地图输入、AMR 起点和手动终点均处于同一个世界坐标系。

  • 先绘制地图 [XMin, XMax) × [YMin, YMax) 的粗外边界、世界 X/Y 参考和栅格网络。格线遵循真实 ResolutionMm;当格线数量超过显示上限时,按整数格距抽稀,并在状态文本中保留真实分辨率与显示步距。
  • PlanningGridMap.IsOccupied(row, col) 绘制占据格,而不是重新绘制原始障碍物几何;因此显示内容与实际规划快照一致。空闲格使用背景,不为每个空格增加填充。
  • 起点为绿色圆、方向短线和“起点”标签;终点为橙色圆、方向短线、“终点”标签及目标位置容差圈。
  • 成功路径逐段连接:前进与倒车使用不同颜色,并以固定间距绘制方向箭头;路径不成功时不绘制任何路径段。
  • IsGearSwitchPoint=true 的点使用紫色标记和“换向”标签。
  • 对首点、末点、每个换向点和固定间隔点绘制旋转矩形。矩形半长/半宽为 Vehicle.LengthMeters / 2 + SafetyMarginMetersVehicle.WidthMeters / 2 + SafetyMarginMeters,仅用于显示 P0 已采用的扩大车体,不参与碰撞判断。
  • 在地图角落绘制固定图例:边界、占据格、起点、终点、前进、倒车、换向与扩大车体检查框的颜色含义。无论成功与否,绘制文本状态:场景名称、地图快照 ID、地图构建状态、缓存层级、规划状态、耗时和终止原因。停止或新会话开始时先清空旧图层。

绘制只消费 CoarsePathPlanningJobResult 的只读结果;不修改 PlanningMapRequest、地图快照、路径、调试开关或服务缓存。

测试与验收

按 Red-Green-Refactor 顺序扩展 verify_coarse_path_integration.ps1:先增加以下会失败的反射/行为断言,再实现最小代码,最后运行相同脚本。

  1. 断言 CoarsePath/Test 中的场景工厂和 MovementTest 文件存在;工厂提供六类固定场景与一个 AMR 位姿/手动终点入口,且每次创建返回独立请求。
  2. 使用同一 CoarsePathPlanningService 运行工厂场景:空图、矩形、多来源和倒车换向均成功;倒车换向路径含 IsGearSwitchPoint;无解结果为 NoFeasiblePath 且路径为空。
  3. 对缓存场景连续调用两次,断言第二个地图结果为 Input 命中,且路径状态和点数不因缓存改变。
  4. 断言 AMR 0 deg90 deg 的 UI 输入分别转换为 0 radpi/2 rad,同时 X/Y 由 mm 转为 m;手动目标与起点都使用相同转换与有限值校验。
  5. 对预先取消的后台调用断言门面映射为 Cancelled、地图或路径不发布部分结果;结构检查确认 MovementTest 使用 Task.RunCancellationTokenSource,且没有等待任务。
  6. 结构检查确认测试入口只通过 CoarsePathPlanningService 进行规划,且不引用底盘命令、栅格化器、碰撞器、原语生成器或搜索节点;并检查绘制代码消费 PlanningGridMap 的边界、分辨率与占据状态,包含图例和成功路径保护。
  7. 更新 README 断言,确认 P1 的 UI、AMR 位姿单位、手动终点、取消和“无底盘命令”边界可被调用方查阅。

完成后运行 Debug 构建及现有 P0 Map/CoarsePath 验证脚本(不恢复或改动已被排除的旧 TrapMap 验证脚本)。

非目标

  • 不实现路径平滑、速度规划、跟踪控制、底盘命令、实时重规划或传感器采集。
  • 不改变地图指纹、缓存键、障碍物栅格化、车辆碰撞、终点判定、搜索代价或资源上限。
  • 不在本交付中实现 Release 性能基准,也不清理、迁移或恢复任何 TrapMap 文件与脚本。