# 粗路径手动测试诊断与耗时设计 ## 目标 完善 `[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 中可见,不再被误认为测试入口暗中禁用完整算法。