# 规划操作预算与诊断收尾设计

## 目标

在进入 P1 的 UI 集成前，使一次 `CoarsePathPlanningService.Plan` 调用的取消与超时语义覆盖完整链路：地图创建、距离场构建、二维 Dijkstra 启发式和 Hybrid A* 搜索。同时让现有 `PlanningDiagnostics` 中的 Open List 陈旧条目数与峰值容量反映真实搜索数据。

成功、无解、输入无效和碰撞安全语义不改变；任何取消或超时结果均不得发布部分路径或部分地图。

## 方案选择

采用一个内部共享的、基于单调 `Stopwatch` 的 `PlanningOperationBudget`。它保存调用方的 `CancellationToken`、整次调用开始时刻与总超时，并在每个耗时循环中返回三态结果：继续、已取消、已超时。

不采用“只在 Dijkstra 前后检查”的方案，因为大图 Dijkstra 和 EDT 仍可能长时间无响应；也不替换 Dijkstra 启发式，避免在收尾阶段改变 Hybrid A* 的搜索特性。

## 边界与数据流

```text
CoarsePathPlanningService.Plan(job, token)
  -> PlanningOperationBudget(token, job.Configuration.SearchTimeout)
  -> PlanningMapFactory.Create(mapRequest, budget)
       -> EnvironmentMapBuilder / PlanningMapAdapter / EDT
  -> HybridAStarPlanner.Plan(planningRequest, budget)
       -> GridDijkstraHeuristic
       -> HybridAStarSearch Open List
  -> PlanningResult + PlanningDiagnostics
```

`PlanningOperationBudget` 放在不依赖 `Map` 或 `CoarsePath` 的公共工具层，仅暴露中立的停止原因。Map 与粗规划分别把该原因映射到自己的结果类型，避免 `Map` 反向依赖 `CoarsePath`。

地图构建结果增加明确的终止状态（成功、普通构建失败、取消、超时）。`CoarsePathPlanningService` 将地图阶段的取消映射为 `PlanningStatus.Cancelled`，地图阶段的超时映射为 `PlanningStatus.SearchTimeout`；两种结果都保留地图构建诊断但路径和分段为空。

现有不带预算参数的 `PlanningMapFactory.Create`、`HybridAStarPlanner.Plan` 和 `GridDijkstraHeuristic` 入口保持可用，作为不受取消限制的兼容包装；业务门面只使用带共享预算的内部入口。

## 响应与一致性规则

- 每个耗时循环在开始处及每处理最多 256 个工作单元后检查预算；检查不改变正常情况下的栅格、启发式或 Open List 排序。
- 等待地图工厂创建锁时使用可轮询的获取方式，以便取消和超时也能中断排队等待。
- 缓存命中仍立即返回原有不可变快照；预算已停止时优先返回取消/超时，不能借缓存绕过调用方停止请求。
- 已被取消或超时的地图构建不得写入任何缓存，也不得发布部分 `PlanningGridMap`。
- 规划器总耗时从门面开始计时；搜索器不重新开始独立的 5 秒窗口。

## 诊断

`HybridAStarSearchResult` 增加陈旧 Open List 条目数和 Open List 峰值。每次丢弃失效的普通节点时递增陈旧计数；每次成功入堆后更新峰值。`HybridAStarPlanner` 原样将这两个统计写入 `PlanningDiagnostics`。

目标候选仍保留其当前规则：不受普通离散键的 best-G 压制，出队时复核。它们占用 Open List 容量，因此计入峰值；不因候选自身而计为陈旧条目。

## 测试与验收

- 在大于一个检查批次的地图上，Dijkstra 预计算期间取消，断言返回 `Cancelled`、空路径和有限响应时间。
- 使用足以覆盖 Dijkstra 工作的极短总超时，断言返回 `SearchTimeout`、空路径，且总耗时不超出预算一个检查批次的合理余量。
- 在地图适配器/EDT 处理中取消和超时，断言地图结果带对应状态、没有地图快照且缓存未被污染。
- 使用产生失效 Open List 条目的场景，断言陈旧数大于零；任意正常搜索断言峰值至少为一，并与最终 `PlanningDiagnostics` 一致。
- 重新运行 Debug 构建、所有既有 P0 地图/粗规划检查，以及新增取消、超时和诊断检查。

## 非目标

- 不修改运动原语、碰撞保守性、代价公式、目标候选排序或路径装配。
- 不在本次收尾中实现 UI、Painter、场景工厂或 Release 性能门槛；这些仍属于 P1。
