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

115 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# P1 粗路径 Clumsy UI 集成设计
## 目标
在不改变 P0 地图、Hybrid A* 与碰撞安全语义的前提下,为 Clumsy 增加可手动运行的粗路径场景测试。使用者能够从 MovementTest 列表启动固定场景,或传入 AMR 当前世界位姿并手动输入终点;界面显示栅格地图、起点、终点、连续路径、换向点和扩大车辆矩形检查点,并可随时停止正在进行的规划。
本设计只覆盖 P1 的首个交付:场景工厂、后台 MovementTest、Painter 可视化、自动化集成检查和模块 README。Release 性能基准属于 P1 的下一项交付;旧 TrapMap 迁移、旧验证脚本和旧入口的清理按已确认范围排除。
## 既有边界
- 业务入口仍唯一为 `CoarsePathPlanningService.Plan(job, cancellationToken)`MovementTest 不自行拼接 `PlanningMapFactory``HybridAStarPlanner`、栅格化器、碰撞器、原语或搜索节点。
- `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` 内;不会向 `Map``Search``Vehicle``Facade` 增加 UI 依赖。
## 场景工厂
`CoarsePathScenarioFactory` 公开一个场景枚举和按枚举创建请求的方法。工厂的职责仅是构造纯输入;它不持有服务、缓存、Painter 或取消源。每个请求采用同一组可验证的车辆和搜索默认值,再按场景覆盖障碍物、起终点和方向约束。
场景固定使用世界 mm 地图边界和分辨率,向 `Pose2D` 写入对应的 m 坐标。障碍物只通过 `ManualObstacleSource``TwoLegObstacleSource` 进入 `PlanningMapRequest`,并为内容变化提供固定且正确的 `SourceVersion`
| 场景 | 地图与预期 |
| --- | --- |
| 显式空图 | `AllowExplicitEmptyMap=true`,直达前进路径成功,用于检查最短调用链。 |
| 单矩形绕行 | 中央矩形阻断直线,路径成功且必须绕障。 |
| 手工圆、矩形与 TwoLeg | 同时使用手工圆形、手工矩形和有效 TwoLeg 快照,路径成功,证明多来源经过同一门面。 |
| 缓存命中 | 连续以新建但完全相同的输入调用同一服务两次;第二次 `MapResult.CacheHit` 必须为 `Input`。 |
| 倒车换向 | 起步方向限制为前进、终点进入方向限制为倒车;成功路径必须出现标记的换向点。 |
| 无解 | 完全贯穿地图的障碍带隔开起点和终点,返回 `NoFeasiblePath` 且无路径。 |
| AMR 位姿与手动终点 | 起点使用上层传入并冻结的 AMR 世界位姿;操作者输入同一世界系的终点 X/Y/航向。该入口仅使用明确提供的障碍物快照,显式空图只能作为演示,不能代表现场无障碍。 |
实现期间先用自动化断言固定每个场景的状态;若需为当前 P0 运动原语调整数值,只能调整场景几何或请求参数,不能放宽碰撞、目标或失败语义。
## 后台会话与取消
共享执行器持有一个静态、长期存活的 `CoarsePathPlanningService`。任一入口启动时会创建新的会话:运行编号、专用 `CancellationTokenSource`、场景描述和后台 `Task`。启动新会话前取消旧会话,以确保同时最多只有一个可绘制的规划结果。
AMR 位姿和手动终点在创建任务前被转换、有限值校验并冻结,随后只作为 `CoarsePathPlanningJob` 数据传给后台。MovementTest 不在规划后台持续读取定位;若未来接入可能阻塞的 `DetourInterface.getCartLocation()`,它必须位于独立的上游快照提供者,不能阻塞 UI 或绕过本设计的输入契约。
```text
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.Chassis``SendMotion``DriveTask` 或任何底盘控制 API。
## 绘制规则
使用独立的全局世界坐标 Painter 图层,例如 `CoarsePathPlanningV1`。Painter 输入为 mm,因此所有来自 `Pose2D``CoarsePathPoint` 的 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 + SafetyMarginMeters``Vehicle.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 deg``90 deg` 的 UI 输入分别转换为 `0 rad``pi/2 rad`,同时 X/Y 由 mm 转为 m;手动目标与起点都使用相同转换与有限值校验。
5. 对预先取消的后台调用断言门面映射为 `Cancelled`、地图或路径不发布部分结果;结构检查确认 MovementTest 使用 `Task.Run``CancellationTokenSource`,且没有等待任务。
6. 结构检查确认测试入口只通过 `CoarsePathPlanningService` 进行规划,且不引用底盘命令、栅格化器、碰撞器、原语生成器或搜索节点;并检查绘制代码消费 `PlanningGridMap` 的边界、分辨率与占据状态,包含图例和成功路径保护。
7. 更新 README 断言,确认 P1 的 UI、AMR 位姿单位、手动终点、取消和“无底盘命令”边界可被调用方查阅。
完成后运行 Debug 构建及现有 P0 Map/CoarsePath 验证脚本(不恢复或改动已被排除的旧 TrapMap 验证脚本)。
## 非目标
- 不实现路径平滑、速度规划、跟踪控制、底盘命令、实时重规划或传感器采集。
- 不改变地图指纹、缓存键、障碍物栅格化、车辆碰撞、终点判定、搜索代价或资源上限。
- 不在本交付中实现 Release 性能基准,也不清理、迁移或恢复任何 TrapMap 文件与脚本。