# 粗路径手动测试诊断与耗时设计

## 目标

完善 `[MovementTest(name = "粗路径规划")]` 的手动测试体验，使调用者能够为每次测试指定正数秒总预算，并在失败时看到真实、可区分的终止原因、搜索统计、总耗时和路径搜索耗时。

本改动不放宽碰撞安全、终点判定或成功路径发布条件，也不把普通规划失败改成异常。普通失败继续通过 `PlanningResult` 返回；输入读取失败和未预期程序错误才按异常信息展示。

## 已确认的现状

手动测试使用正式的 `CoarsePathPlanningService`、`HybridAStarPlanner` 和 `HybridAStarSearch`，没有测试专用搜索器，也没有关闭倒车；`HybridAStarConfiguration.AllowReverse` 默认仍为 `true`。

影响实际成功率和问题定位的限制如下：

- 手动入口未覆盖 `SearchTimeout`，因此沿用 5 秒总预算。
- 已复现一个空图可行案例：起点 `(1000 mm, 2000 mm, 0 deg)`、终点 `(1500 mm, 2500 mm, 90 deg)` 在 5 秒返回 `SearchTimeout`，将预算改为 30 秒后约 10.2 秒成功。
- 手动测试固定使用长 `0.80 m`、宽 `0.60 m`、安全余量 `0.05 m`、最小转弯半径 `1.20 m` 的演示车辆参数，不读取现场 AMR 几何参数。
- `HybridAStarPlanner` 将所有搜索阶段失败改写成同一句“Hybrid A* 搜索未找到可发布路径”，丢失了超时、节点上限、无解和内部错误的具体区别。
- 状态图层只在 `TerminationReason` 非空时绘制原因，而 Toast 不包含原因。
- 第一版算法只支持汽车式恒曲率前进、倒车和原语边界换向；明确不支持 Reeds-Shepp 精确连接、横移、蟹行和原地旋转。这是整个粗规划核心的版本边界，不是手动测试单独阉割。

## 范围

### 包含

- 手动测试增加单次总超时输入。
- 搜索结果保留原始终止原因。
- 面向调用者组合状态、原因、资源上限和节点统计。
- 状态图层与 Toast 展示原因、总耗时和路径搜索耗时。
- README 说明测试车辆参数和第一版运动能力限制。
- 自动化覆盖输入校验、失败分类、耗时边界和 UI 文本。

### 不包含

- Reeds-Shepp 或 Dubins 解析终点连接。
- 横移、蟹行或原地旋转原语。
- 路径平滑、速度规划或控制。
- 将固定演示车辆参数替换为尚未定义来源的现场 AMR 参数。
- 改变地图边界策略、碰撞规则、终点容差或 Open List 排序。

## 设计

### 手动超时输入

`CoarsePathPlanningTest.Test()` 在读取终点航向后读取“粗路径规划总超时（秒，必须大于 0）”。输入按当前文化和不变文化解析为有限 `double`，必须严格大于零，并且转换为 `TimeSpan` 后不溢出。

场景工厂仍负责创建完整业务请求；测试入口在取得 `CoarsePathPlanningJob` 后只覆盖本次请求的：

```csharp
job.Configuration.SearchTimeout = TimeSpan.FromSeconds(timeoutSeconds);
```

固定回归场景继续使用各自既有预算。手动输入不修改全局默认配置，也不影响下次测试。

输入 `0`、负数、NaN、Infinity、非数字或超出 `TimeSpan` 可表示范围时，本次规划不启动，并通过既有输入失败提示显示具体字段。

### 搜索原始终止原因

`HybridAStarSearchResult` 增加只读 `TerminationReason`。搜索的每个失败出口填写与发生位置一致的原因：

- `SearchTimeout`：共享总预算已经耗尽。
- `SearchNodeLimitExceeded`：扩展节点数达到配置上限。
- `NoFeasiblePath`：二维启发式不可达、起始状态无法进入 Open List，或 Open List 耗尽。
- `Cancelled`：收到取消请求。
- 输入、地图、车辆或配置状态：指出对应校验阶段。
- `InternalError`：保留异常类型和消息，不在 UI 展示完整堆栈。

`HybridAStarPlanner` 不再用同一句泛化说明覆盖搜索原因。它将原始原因与可操作统计组合为最终 `PlanningDiagnostics.TerminationReason`。例如：

```text
总预算 8.000 秒已耗尽；扩展 5,366，生成 21,014，Open List 峰值 15,102。
```

节点上限原因包含配置的 `MaximumExpandedNodes`；无解原因包含扩展、生成和 Open List 峰值。起点、终点、碰撞和最终复核的现有明确原因保持不变。

### 异常语义

普通规划失败不抛异常，因为超时、无解、碰撞和资源上限是可预期业务结果。调用者继续通过 `PlanningStatus` 做稳定分支，并读取 `TerminationReason`。

搜索或规划器捕获未预期异常时返回 `InternalError`，原因包含异常类型和非空消息。完整堆栈不进入 Toast，避免界面噪声；自动化测试验证异常不会再次被完全静默吞掉。

### 耗时

保留既有 `PlanningDiagnostics.Elapsed`，定义为从 `CoarsePathPlanningService.Plan` 入口开始的总耗时，包括建图、缓存查询、栅格化、距离场和路径规划。

新增只读 `PlanningDiagnostics.PathSearchElapsed`，边界为：

- 开始：规划请求、地图、车辆、配置、起点、终点和初始碰撞预检均已通过，即将进入 `HybridAStarSearch.Search`。
- 包含：二维 Dijkstra 启发式、Hybrid A* 扩展、回溯、路径装配、方向分段和最终复核。
- 结束：`HybridAStarPlanner` 准备构造最终 `PlanningResult`。
- 不包含：地图来源读取、地图缓存、栅格化、距离场构建和搜索前预检。

搜索开始前失败时为 `TimeSpan.Zero`；搜索开始后的成功、失败、取消、超时、节点上限、回溯失败或最终复核失败均保留截至返回时的路径搜索耗时。所有结果满足：

```text
TimeSpan.Zero <= PathSearchElapsed <= Elapsed
```

构造函数的新参数位于现有参数之后并具有 `TimeSpan.Zero` 默认值，保持现有调用兼容。

### MovementTest 展示

状态图层显示：

- 地图状态、缓存命中和快照。
- 规划状态。
- 总耗时与路径搜索耗时。
- 扩展节点数、生成节点数和 Open List 峰值。
- 非空失败原因。
- 当前测试采用的车辆长、宽、安全余量和最小转弯半径。

Toast 保持单行摘要，但成功和失败都显示两种耗时；失败时必须附加 `TerminationReason`。这样即使用户没有看到地图左下角的状态图层，也不会只收到一个无原因的失败状态。

### 文档

`CoarsePath/README.md` 增加以下说明：

- 手动入口要求输入有限正数秒总预算。
- 总耗时与路径搜索耗时的不同边界。
- 手动入口采用固定演示车辆参数，不代表现场 AMR。
- 当前算法支持汽车式恒曲率前进、倒车和换向，但不支持 Reeds-Shepp 精确连接、横移、蟹行或原地旋转。

## 测试策略

所有行为修改遵循 Red-Green-Refactor。

### 输入与 UI 源码检查

`verify_coarse_path_ui.ps1` 先增加失败断言，再实现：

- 手动入口读取并应用超时秒数。
- 超时输入使用正数有限值校验。
- 状态图层和 Toast 都读取 `PathSearchElapsed`。
- Toast 读取 `TerminationReason`。
- 状态图层显示节点统计和车辆参数。
- README 包含新的耗时边界、手动预算和能力限制说明。

### 搜索与集成行为

`verify_coarse_path_search.ps1` 和 `verify_coarse_path_integration.ps1` 先增加失败断言，再实现：

- 超时、节点上限、无解和内部错误返回不同的非空原因。
- `InternalError` 原因包含异常类型或消息。
- 搜索前失败的 `PathSearchElapsed` 为零。
- 一个真实成功规划的路径搜索耗时非负且不超过总耗时。
- 搜索阶段失败保留已消耗的路径搜索耗时。
- 5 秒会超时而更长手动预算可成功的近距离换向案例得到回归覆盖；测试应使用足够稳定的预算余量，避免仅依赖精确墙钟阈值形成脆弱测试。

### 最终回归

最终运行 Debug 构建，以及现有地图、碰撞、搜索、集成和 UI 脚本。既有固定成功场景、取消、总超时、节点上限、无解、地图缓存和最终碰撞复核语义不得改变。

## 验收标准

- 手动“粗路径规划”每次要求输入有限正数秒总预算，并应用到该次请求。
- 失败 Toast 能直接区分超时、节点上限、无解、碰撞和内部错误，并包含可操作原因。
- 状态图层同时显示两种耗时、搜索统计和演示车辆参数。
- 可行但超过默认 5 秒的复现场景可以通过更长的用户输入预算成功。
- 所有新旧自动化验证通过。
- 算法第一版能力限制在 README 中可见，不再被误认为测试入口暗中禁用完整算法。
