# EM 闭环 MovementTest 本地离线场景验证设计

## 1. 目标

基于现有 `EM闭环测试` 的规划入口，在本地验证宿主中执行多组一次性规划，提前暴露路径搜索、EM 轨迹发布、求解预算和速度回退问题。测试只运行到轨迹发布成功为止，不创建控制执行器、不读取实车状态、不启动可视化服务器、不下发任何底盘命令。

本轮保持规划速度上限和期望速度为 `1.0 m/s`，核心判据是最终发布轨迹峰值是否大于 `0.2 m/s`，而不是测试超过 `1.0 m/s`。

## 2. 方案选择

### 方案 A：只扩展纵向速度测试

直接向 `EmSpeedProfileChecks` 注入更多已生成的 `LateralPath`。优点是快速、容易定位纵向问题；缺点是绕过粗路径搜索、平滑、分段和 MovementTest 发布链，无法回答完整一次性规划耗时。

### 方案 B：直接启动 MovementTest Runner

调用 `EmClosedLoopMovementTestRunner`。优点是表面上最接近部署；缺点是发布后会进入控制器接管和底盘相关路径，不符合纯离线安全边界。

### 方案 C：复用 MovementTest 的规划前半段（采用）

在验证宿主中复用以下调用链，并在发布成功处停止：

```text
TrajectoryObservationSetupFactory.CreateBootstrapJob
    → TrajectoryObservationBootstrapper.Bootstrap
    → TrajectoryObservationController.StartCycle
    → PublishedTrajectory
    → 停止
```

该方案覆盖 MovementTest 实际使用的地图、粗路径、平滑、方向段选择、EM 规划和发布门禁，同时不进入 `ParkingGeometricController`、`TrajectoryTrackingMovement` 或底盘命令执行。

## 3. 测试结构

新增验证宿主入口 `em-closed-loop-offline`，实现独立的 `EmClosedLoopOfflinePlanningChecks`。测试使用与 `MovementTest.EmClosedLoopTest.cs` 一致的关键设置：

- `PlanningScope = FullDirectionSegment`
- `SolverTimeoutSeconds = 5`
- `MaximumOsqpIterations = 100000`
- `OutputTimeStepSeconds = 0.10`
- 车辆长度 `0.80 m`
- 车辆宽度 `0.60 m`
- 安全余量 `0.05 m`
- 最大曲率 `1 / 1.20 m⁻¹`
- 规划速度上限与期望速度保持 `1.0 m/s`

每个场景从静止状态 `(0, 0, 0)` 独立建立地图和规划任务，只调用一次 `StartCycle`。场景之间不复用上一条轨迹，不模拟车辆沿轨迹运动。

## 4. 仅前进场景矩阵

场景坐标和障碍物尺寸在实现时作为固定测试夹具保存，若粗路径搜索证明某组几何不可行，只允许调整该夹具，不降低安全余量或通过门禁。

1. **15 m 直线基准**：无障碍，目标 `(15, 0, 0)`。
2. **单圆障碍绕行**：路径中央放置一个圆形障碍，要求绕开后回到目标轴线。
3. **单矩形障碍绕行**：使用较宽矩形制造持续弯道，验证转弯后直线加速。
4. **交错双圆 S 形**：两个障碍在中心线两侧交错布置，迫使参考路径连续转弯并回正。
5. **连续双矩形弯**：两个错位矩形形成较长的连续双弯，增加曲率变化和节点数量。
6. **紧凑局部绕行**：在车辆曲率能力和安全余量内放置更靠近起点的局部障碍，观察短距离内加速、转弯和停车是否触发保守回退。

所有场景必须满足：活动方向段为 `Forward`，发布轨迹中所有点的方向为 `Forward`，速度不出现超出容差的负值。若粗路径产生倒车段，场景测试失败并报告方向分段，不自动忽略。

## 5. 耗时口径

每个场景分别记录：

- `BootstrapElapsed`：地图建立、粗路径搜索、路径平滑和方向分段耗时；
- `EmPlanPublishElapsed`：`StartCycle` 到 `PublishedTrajectory` 可用的耗时；
- `TotalOneShotElapsed`：从创建 bootstrap job 到轨迹发布完成的端到端墙钟耗时；
- `TrajectoryPeakSpeed`：最终发布轨迹的最大绝对纵向速度；
- `PlanningStatus`、`Published`、方向段数、轨迹点数和是否包含 fallback 诊断。

第一次场景运行可能包含 OSQP DLL 加载和 JIT 冷启动，因此报告中明确标注首场为冷启动样本，其余为同一进程内的热启动样本。测试不以单次机器耗时设置过紧断言，只要求端到端时间不超过现有 `5 s` 规划预算；具体毫秒数作为诊断输出。

## 6. 通过标准

每个场景同时满足以下条件才算通过：

1. bootstrap 成功；
2. `StartCycle` 返回且 `Published = true`；
3. `PublishedTrajectory` 非空；
4. 活动方向段和所有发布点均保持前进；
5. 峰值速度严格大于 `0.2 m/s`；
6. 峰值速度不超过 `1.0 m/s` 加数值容差；
7. 终点速度为零；
8. 端到端一次性规划耗时小于 `5 s`；
9. 失败时输出阶段、状态、诊断和已消耗时间，不吞掉异常。

测试只证明本机离线规划链路；不声称已经验证控制跟踪效果、底盘限速、定位反馈或实车安全性。

## 7. TDD 与验证

先新增测试入口和六个场景断言，确认在缺少离线场景运行器时测试按预期失败。随后实现最小的本地规划夹具与计时记录，使测试转绿。最终运行：

```text
dotnet build ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -c Release --no-restore
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -c Release --no-build -- em-closed-loop-offline
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -c Release --no-build -- em-speed-profile
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -c Release --no-build -- longitudinal-integration
ClumsyPilot/tests/verify_em_closed_loop_movement.ps1
```

验收输出汇总每个场景的 bootstrap、EM发布和端到端耗时，并给出最慢场景、平均热启动耗时、所有场景峰值速度范围以及是否出现 fallback。

## 8. 明确不做的内容

- 不启动 `EM闭环测试` 的在线运行线程；
- 不实例化或调用现有控制器；
- 不读取 `Shared`、状态估计或底盘的实时状态；
- 不修改 `Control`、`Shared`、状态估计和他人的底盘代码；
- 不打开网页或原生可视化；
- 不模拟车辆沿轨迹逐步反馈，本轮仅验证一次性规划和发布。
