# P1 手动障碍物输入设计

## 目标

扩展 `[MovementTest(name = "粗路径规划")]`，使操作者可在一次手动测试中输入多个圆形或轴对齐矩形障碍物的中心与尺寸。输入经纯场景工厂转换为 `ManualObstacleSource`，再由已有 `CoarsePathPlanningService` 创建地图和规划；不绕过门面，也不添加任何底盘控制。

## 输入流程

1. 读取一次 `DetourInterface.getCartLocation()`，冻结 AMR 车身几何中心的世界 `X/Y(mm)` 与 `th(deg)`。
2. 输入目标世界 `X(mm)`、`Y(mm)` 与航向 `deg`。
3. 输入障碍物数量，允许范围为 `0` 到 `20`。
4. 对每个障碍物输入类型：`1` 为圆形，`2` 为矩形。
5. 输入障碍物几何中心世界 `X(mm)`、`Y(mm)`：
   - 圆形再输入半径 `r(mm)`；
   - 矩形再输入 X 方向长度与 Y 方向宽度（均为 mm）。矩形不提供旋转角，始终与世界坐标轴平行。
6. 将已冻结的 AMR 位姿、目标和障碍物集合提交给现有后台执行器。

输入必须是有限数字。数量、类型、半径、长度和宽度不合法时拒绝启动规划并显示输入失败信息；不会产生不完整的规划请求。

## 工厂契约

`CoarsePathScenarioFactory` 新增面向手动测试的纯数据类型与工厂方法：

```csharp
public enum ManualCoarsePathObstacleKind
{
    Circle,
    AxisAlignedRectangle,
}

public sealed class ManualCoarsePathObstacle
{
    public static ManualCoarsePathObstacle Circle(
        double centerXMillimeters, double centerYMillimeters, double radiusMillimeters);

    public static ManualCoarsePathObstacle AxisAlignedRectangle(
        double centerXMillimeters, double centerYMillimeters,
        double lengthXMillimeters, double widthYMillimeters);
}

public static CoarsePathPlanningJob CreateManualObstacleDemo(
    double startXMillimeters, double startYMillimeters, double startHeadingDegrees,
    double goalXMillimeters, double goalYMillimeters, double goalHeadingDegrees,
    IReadOnlyList<ManualCoarsePathObstacle> obstacles, long obstacleSnapshotVersion);
```

原有 `CreateManualGoalDemo` 保留不变，并委托到相同的边界/位姿转换逻辑和空障碍物路径，因此既有调用方与验证不受破坏。

工厂将中心/尺寸转换为 `CircleObstacle` 或 `AxisAlignedRectangleObstacle`。有障碍物时请求使用必需的 `ManualObstacleSource("manual-user-input", obstacleSnapshotVersion, true, ...)` 且 `AllowExplicitEmptyMap=false`；没有障碍物时使用空来源数组和 `AllowExplicitEmptyMap=true`。工厂校验障碍物列表、版本、几何数值与正尺寸，避免将缓存版本或无效几何交给 Map。

## 边界和缓存

手动地图边界的候选范围由起点、终点和每个障碍物的完整外轮廓共同决定：圆形使用中心 ± 半径，矩形使用中心 ± 半长/半宽。候选范围的每侧保留 2000 mm，随后按既有 50 mm 分辨率向外取整。

后台执行器为每次包含障碍物的手动提交生成单调递增的 `obstacleSnapshotVersion`，并在 UI 线程完成输入后冻结它。这样新输入绝不会复用旧障碍物地图；固定场景的缓存命中测试保持原样。空障碍物演示不需要障碍物来源版本。

## 可视化与停止

不新增 Painter 专用绘图分支。现有结果绘制已从 `PlanningGridMap.IsOccupied(row, col)` 消费占据格，因此新的手动障碍物会自动在同一 `CoarsePathPlanningV1` 图层显示为实际栅格快照。起点、终点、路径、换向、扩大车体检查框、状态和 `TestStop` 取消语义均保持不变。

## 验证与文档

- `verify_coarse_path_integration.ps1` 增加工厂反射与行为断言：新类型/方法存在；圆形和矩形输入生成非空手动来源、关闭显式空图、边界覆盖外轮廓；空障碍物仍保留显式空图；无效尺寸被拒绝。
- `verify_coarse_path_ui.ps1` 检查手动入口读取障碍物数量、类型、中心和尺寸，并调用 `CreateManualObstacleDemo`，同时仍不直接创建地图或搜索器。
- `CoarsePath/README.md` 的 P1 节说明输入顺序、单位、20 个上限、矩形无旋转、空图仅限零障碍物演示，以及可视化仍基于最终规划快照。

## 非目标

- 不支持旋转矩形、多边形、导入文件、拖拽编辑或实时编辑已运行任务。
- 不让手动障碍物直接跳过 `ManualObstacleSource`、Map 缓存或 `CoarsePathPlanningService`。
- 不改变已有固定场景、车辆安全参数、搜索算法、P1 Painter 颜色或任何底盘控制边界。
