# EM 观察网页可视化阶段 07 交接

状态：完成

阶段目标：完成集成计划 Task 6–7：MovementTest 的回环网页会话生命周期、一次性故障隔离、可选原生 Painter、插件 DLL 原子打包和操作文档；保持 `OBSERVE_ONLY`，不进行实车验收。

基线提交：`819ff7324b4242ba9fad158618b49a49d76e93f2`（启动时分支 `trajplanner`，批准设计 `b0b79e5` 与 phase-06 实现提交均为祖先）。

实现提交：

- `fbd3ba6` `feat: host EM observation dashboard`
- `6034568` `docs: package EM observation dashboard`

修改文件：

- `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationVisualizationPublisher.cs`
- `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationVisualizationChecks.cs`
- `ClumsyPilot/scripts/Publish-ClumsyPilotPlugin.ps1`
- `ClumsyPilot/tests/EMPlannerVerificationHost/PluginPackagingChecks.cs`
- `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/README.md`
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md`

新增或确认的接口：

- 内部 `ITrajectoryObservationVisualizationSink` 提供 `Start`、`Publish`、`Stop`；生产 sink 包装一个 `PlanningVisualizationSession`，验证宿主注入记录/抛错 sink，不模拟网页类库。
- `TrajectoryObservationVisualizationPublisher` 仅在启用且到达 `WebRefreshRateHz` 门控点时构建动态快照；第一次网页快照、服务或适配器异常会停止 sink、仅记录一次中文原因并永久熔断本会话网页输出，观察循环继续。
- `EnableWebVisualization=false` 不创建网页静态/动态快照或历史，也不启动服务；成功 bootstrap 后才建立静态快照、记录完整 token URI，并把浏览器启动失败与服务生命周期隔离。
- 原生 Painter 不再静态预创建；只在 `EnableNativePainterVisualization=true` 的会话内创建。`TestStop()`、会话替换和当前会话故障会幂等停止网页、释放端口并清理既有 Painter。
- 实际 tick 使用 `controller.ActiveSegment`、当前/上一轨迹和当前循环信息创建网页快照；不在观察循环等待网页序列化或客户端。
- 发布脚本只从显式 `ManagedDll` 同级目录解析 `TrajectoryPlanningVisualization.dll`，先将其复制进 staging，再保留既有 root 防护、OSQP hash、原子交换与恢复逻辑。插件精确树现为 `ClumsyPilot.dll`、`TrajectoryPlanningVisualization.dll`、`osqp.dll` 和三份 OSQP 许可证。

验证命令与结果：

1. RED：`dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation`，退出 `1`；首个有效错误为缺少 `ITrajectoryObservationVisualizationSink`，确认新 seam 尚未实现。
2. GREEN：同一 `trajectory-observation` 命令，退出 `0`，输出 `PASS trajectory-observation`；验证 throwing sink 单次熔断、20 Hz tick 下 10 Hz 最多 11 帧、禁用网页不建快照、Painter 源码惰性创建。
3. Task 7 RED：`dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- plugin-package`，退出 `1`；精确包树缺少 `plugins/TrajectoryPlanningVisualization.dll`。
4. 最终 `trajectory-observation`，退出 `0`，输出 `PASS trajectory-observation`。
5. 最终 `plugin-package` 连续两次均退出 `0`，均输出 `PASS plugin-package`；验证精确包树、程序集名 `TrajectoryPlanningVisualization` 和无 staging/backup 残留。
6. `dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj`，退出 `0`，输出 `PASS trajectory-planning-visualization`。
7. `dotnet build ClumsyPilot/ClumsyPilot.csproj -p:ExcludeLegacyAutoAvoidance=true`，退出 `0`，输出 `0 个警告 / 0 个错误`。
8. `rg -n "SendXYThSpeed|SendMotion|DriveStop|PredefinedDriveStop|AccumulateSpeed" ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest` 无匹配，确认运行观察源码没有执行器调用。
9. `git diff --check fbd3ba6^..6034568 -- <Task 6–7 文件>`，退出 `0`；并以 `git diff-tree` 核对两个实现提交的精确文件树和插件输出契约。

未运行的验证及原因：未执行 Task 8 的 30 秒网页烟雾会话或任何实车观察/验收；本阶段授权范围严格限于 Task 6–7。

已知警告或遗留问题：`trajectory-observation` 编译时仍可见 phase-06 已记录的两条非本阶段 obsolete 警告：`MovementTests.TireFollowing.cs` 的 `Lidar2dDetect2LegTray.LegWidth` 与 `TireFollowing.cs` 的 `MultiWheelChassis.GetSteerWheels()`。Task 7 首次 GREEN 尝试曾在 staging `Directory.Move` 处遇到瞬时 Windows 拒绝访问；随后以同一脚本在独立临时目录连续两次原子交换成功，且最终 `plugin-package` 连续两次通过，未修改既有交换/恢复机制。

与原计划的偏差：无功能偏差。计划文件列出的 `TrajectoryObservationPresentation.cs` 无需修改：其 Painter 获取仍在实例字段初始化中，而实例本身已被 MovementTest 的会话级开关完全隔离，避免无关重写。

下一阶段注意事项：仅可在新窗口执行 Task 8 自动化与受监督实车观察；必须保持 `OBSERVE_ONLY`，不得把网页或轨迹输出接入任何车辆写接口。先运行完整自动化入口，再按清单记录网页烟雾和实车结果；若实车不可用，必须明确标为待验收。
