# 固定粗路径案例使用实时 AMR 位姿 Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 让全部 `粗路径规划-*` 固定 MovementTest 从一次冻结的当前 AMR 世界坐标和航向开始规划,并把案例地图、目标和障碍平移到 AMR 附近。 **Architecture:** `CoarsePathScenarioFactory` 保留确定性的基准案例入口,并新增接受 AMR mm/deg 位姿的入口。一个仅属于固定场景工厂的平移对象将基准案例的起点映射为 AMR 起点,平移地图和障碍,并以起终点基准航向差计算新的终点航向。MovementTest 运行器在前台读取一次 `DetourInterface.getCartLocation()`,校验并冻结快照后才提交已有后台规划流程。 **Tech Stack:** C# / .NET Standard 2.0、Clumsy `MovementTest` 与 Painter、现有 `CoarsePathPlanningService`、PowerShell 反射验证脚本。 ## Global Constraints - 保留 `CoarsePathScenarioFactory.Create(CoarsePathTestScenario)` 的现有确定性行为,自动化离线测试继续使用它。 - 新实时入口的 X/Y 单位为世界 mm、航向单位为 deg;核心 `Pose2D` 仍为 m/rad。 - 固定场景仅做位置平移;TwoLeg 的 `DetectionHeadingRadians` 不得因 AMR 航向改变。 - 起点航向必须等于冻结 AMR 航向;终点航向必须保持基准案例的起终点航向差,并规范化到 `[-pi, pi]`。 - 不改变 Hybrid A*、车辆参数、碰撞模型、地图缓存实现、手动 `粗路径规划` 的终点/障碍/超时输入流程,或任何底盘控制行为。 - 实时位姿为空、读取抛异常或 X/Y/航向不是有限数时,不得创建后台任务;必须向用户报告以 `AMR 位姿不可用` 开头的原因。 - 不暂存工作区中的无关改动;每次提交均明确列出文件路径。 ## File Structure - Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/CoarsePathScenarioFactory.cs` - 固定场景的基准坐标定义、实时 AMR 工厂重载、平移与航向转换的唯一实现位置。 - Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/MovementTest.CoarsePathTest.cs` - 固定场景的 AMR 快照读取、输入失败提示,以及冻结位姿的 Painter 状态显示。 - Modify: `ClumsyPilot/tests/verify_coarse_path_integration.ps1` - 对新工厂重载、平移几何、航向规则、缓存和基准入口不回归的程序集级验证。 - Modify: `ClumsyPilot/tests/verify_coarse_path_ui.ps1` - 对运行器只读一次实时位姿、传入实时工厂入口及 AMR 失败/状态文案的源级验证。 - Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/README.md` - 说明六个固定案例的实时锚定规则、缓存命中条件和定位异常行为。 --- ### Task 1: 为固定案例建立可验证的实时 AMR 平移工厂 **Files:** - Modify: `ClumsyPilot/tests/verify_coarse_path_integration.ps1:303-399` - Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/CoarsePathScenarioFactory.cs:115-284` **Interfaces:** - Consumes: `CoarsePathTestScenario`、`PlanningMapRequest`、`MapBoundsMm`、`ManualObstacleSource`、`TwoLegObstacleSource` 和 `TwoLegProjectionInput`。 - Produces: `public static CoarsePathPlanningJob Create(CoarsePathTestScenario scenario, double amrXMillimeters, double amrYMillimeters, double amrHeadingDegrees)`。 - Produces: 每个固定案例以 `FixedScenarioTransform` 统一转换的起点、终点、地图边界和来源快照;旧 `Create(scenario)` 仍创建原始基准几何。 - [ ] **Step 1: 在程序集级验证脚本中写出失败测试** 在 `$factoryCreate` 定义之后加入实时重载查找;在基准案例循环之后加入以下断言。测试锚点 `(12000, -3000, 90)` 对单矩形场景的偏移应为 `(+11000, -5000)` mm;多来源锚点 `(7000, 8000, 45)` 对该基准场景的偏移应为 `(+6000, +7000)` mm。 ```powershell $factoryCreateAtAmr = Find-Method $scenarioFactoryType 'Create' @( $scenarioEnumType, [double], [double], [double]) Assert-True ($factoryCreateAtAmr -ne $null) 'Scenario factory must expose Create(scenario, amrX, amrY, amrHeading).' foreach ($scenarioName in $scenarioNames) { $scenario = [Enum]::Parse($scenarioEnumType, $scenarioName) $liveJob = $factoryCreateAtAmr.Invoke($null, @($scenario, 12345.0, -6789.0, 135.0)) Assert-Near 12.345 $liveJob.Start.X "Live $scenarioName start X must equal the AMR X." Assert-Near -6.789 $liveJob.Start.Y "Live $scenarioName start Y must equal the AMR Y." Assert-Near (3.0 * [Math]::PI / 4.0) $liveJob.Start.Heading "Live $scenarioName heading must equal the AMR heading." } $rectangleScenario = [Enum]::Parse($scenarioEnumType, 'RectangleDetour') $liveRectangle = $factoryCreateAtAmr.Invoke($null, @($rectangleScenario, 12000.0, -3000.0, 90.0)) Assert-Near 12.0 $liveRectangle.Start.X 'Live rectangle start X must equal the AMR X.' Assert-Near -3.0 $liveRectangle.Start.Y 'Live rectangle start Y must equal the AMR Y.' Assert-Near ([Math]::PI / 2.0) $liveRectangle.Start.Heading 'Live rectangle start heading must equal the AMR heading.' Assert-Near 16.0 $liveRectangle.Goal.X 'Live rectangle goal X must preserve the four-metre relative offset.' Assert-Near -3.0 $liveRectangle.Goal.Y 'Live rectangle goal Y must preserve the relative offset.' Assert-Near ([Math]::PI / 2.0) $liveRectangle.Goal.Heading 'Live rectangle goal heading must preserve the zero baseline heading delta.' Assert-Near 11000.0 $liveRectangle.MapRequest.Bounds.XMin 'Live rectangle map X minimum must translate with the AMR.' Assert-Near -1000.0 $liveRectangle.MapRequest.Bounds.YMax 'Live rectangle map Y maximum must translate with the AMR.' $rectangleProjection = $liveRectangle.MapRequest.ObstacleSources[0].ProjectToWorld() $rectangleObstacle = $rectangleProjection.Obstacles[0] Assert-Near 13700.0 $rectangleObstacle.XMin 'Live rectangle obstacle X minimum must translate with the AMR.' Assert-Near -2200.0 $rectangleObstacle.YMax 'Live rectangle obstacle Y maximum must translate with the AMR.' $multiScenario = [Enum]::Parse($scenarioEnumType, 'ManualAndTwoLeg') $baselineMulti = $factoryCreate.Invoke($null, @($multiScenario)) $liveMulti = $factoryCreateAtAmr.Invoke($null, @($multiScenario, 7000.0, 8000.0, 45.0)) Assert-Near 7.0 $liveMulti.Start.X 'Live multi-source start X must equal AMR X.' Assert-Near 8.0 $liveMulti.Start.Y 'Live multi-source start Y must equal AMR Y.' Assert-Near ([Math]::PI / 4.0) $liveMulti.Goal.Heading 'Live multi-source goal heading must follow AMR heading.' $baselineTwoLeg = $baselineMulti.MapRequest.ObstacleSources[1].ProjectToWorld().Obstacles $liveTwoLeg = $liveMulti.MapRequest.ObstacleSources[1].ProjectToWorld().Obstacles Assert-Near ($baselineTwoLeg[0].CenterX + 6000.0) $liveTwoLeg[0].CenterX 'TwoLeg X must translate without rotation.' Assert-Near ($baselineTwoLeg[0].CenterY + 7000.0) $liveTwoLeg[0].CenterY 'TwoLeg Y must translate without rotation.' $noPathScenario = [Enum]::Parse($scenarioEnumType, 'NoFeasiblePath') $liveNoPath = $factoryCreateAtAmr.Invoke($null, @($noPathScenario, 9000.0, -1000.0, -180.0)) Assert-Near 9000.0 $liveNoPath.Start.X 'Live infeasible scenario must use AMR X.' Assert-Near -1.0 $liveNoPath.Start.Y 'Live infeasible scenario must use AMR Y.' Assert-Near 8000.0 $liveNoPath.MapRequest.Bounds.XMin 'Live infeasible map must translate with its baseline start.' $invalidLivePoseRejected = $false try { $null = $factoryCreateAtAmr.Invoke($null, @($rectangleScenario, [double]::NaN, 0.0, 0.0)) } catch [Reflection.TargetInvocationException] { $invalidLivePoseRejected = $_.Exception.InnerException -is [ArgumentOutOfRangeException] } Assert-True $invalidLivePoseRejected 'Live factory must reject non-finite AMR coordinates.' ``` - [ ] **Step 2: 运行该测试,确认它因缺少实时工厂重载而失败** Run: ```powershell dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1 ``` Expected: build succeeds; the script fails at `Scenario factory must expose Create(scenario, amrX, amrY, amrHeading).` - [ ] **Step 3: 以单一平移对象实现实时工厂重载** 在 `CoarsePathScenarioFactory` 中保留一参 `Create`,并用一个私有锚点和变换对象驱动同一个 switch。使用以下接口形状;`CreateCore` 的每个 case 都先以该案例的基准起点创建 `FixedScenarioTransform`,再传给对应的场景构造函数。 ```csharp public static CoarsePathPlanningJob Create(CoarsePathTestScenario scenario) { return CreateCore(scenario, null); } public static CoarsePathPlanningJob Create(CoarsePathTestScenario scenario, double amrXMillimeters, double amrYMillimeters, double amrHeadingDegrees) { return CreateCore(scenario, new FixedScenarioAnchor(amrXMillimeters, amrYMillimeters, amrHeadingDegrees)); } private static CoarsePathPlanningJob CreateCore(CoarsePathTestScenario scenario, FixedScenarioAnchor anchor) { switch (scenario) { case CoarsePathTestScenario.ExplicitEmpty: return CreateExplicitEmpty(FixedScenarioTransform.From(1000d, 2000d, 0d, anchor)); case CoarsePathTestScenario.RectangleDetour: case CoarsePathTestScenario.CacheHit: return CreateRectangleDetour(FixedScenarioTransform.From(1000d, 2000d, 0d, anchor)); case CoarsePathTestScenario.ManualAndTwoLeg: return CreateManualAndTwoLeg(FixedScenarioTransform.From(1000d, 1000d, 0d, anchor)); case CoarsePathTestScenario.ReverseGearSwitch: return CreateReverseGearSwitch(FixedScenarioTransform.From(1000d, 2000d, 0d, anchor)); case CoarsePathTestScenario.NoFeasiblePath: return CreateNoFeasiblePath(FixedScenarioTransform.From(1000d, 2000d, 0d, anchor)); default: throw new ArgumentOutOfRangeException(nameof(scenario)); } } ``` Implement the two private classes in the same file. They must validate all public live inputs with existing `EnsureFinite`, keep the identity transform when `anchor` is null, convert fixed mm to metres only when creating `Pose2D`, and normalize headings without changing a position. ```csharp private sealed class FixedScenarioAnchor { public FixedScenarioAnchor(double xMillimeters, double yMillimeters, double headingDegrees) { EnsureFinite(xMillimeters, nameof(xMillimeters)); EnsureFinite(yMillimeters, nameof(yMillimeters)); EnsureFinite(headingDegrees, nameof(headingDegrees)); XMillimeters = xMillimeters; YMillimeters = yMillimeters; HeadingRadians = NormalizeRadians((headingDegrees % 360d) * DegreesToRadians); } public double XMillimeters { get; } public double YMillimeters { get; } public double HeadingRadians { get; } } private sealed class FixedScenarioTransform { private FixedScenarioTransform(double deltaXMillimeters, double deltaYMillimeters, double headingDeltaRadians) { DeltaXMillimeters = deltaXMillimeters; DeltaYMillimeters = deltaYMillimeters; HeadingDeltaRadians = headingDeltaRadians; } public double DeltaXMillimeters { get; } public double DeltaYMillimeters { get; } public double HeadingDeltaRadians { get; } public static FixedScenarioTransform From(double baselineStartXMillimeters, double baselineStartYMillimeters, double baselineStartHeadingRadians, FixedScenarioAnchor anchor) { if (anchor == null) return new FixedScenarioTransform(0d, 0d, 0d); return new FixedScenarioTransform(anchor.XMillimeters - baselineStartXMillimeters, anchor.YMillimeters - baselineStartYMillimeters, NormalizeRadians(anchor.HeadingRadians - baselineStartHeadingRadians)); } public float X(float value) { return ToFiniteFloat(value + DeltaXMillimeters, nameof(value)); } public float Y(float value) { return ToFiniteFloat(value + DeltaYMillimeters, nameof(value)); } public Pose2D Pose(double xMeters, double yMeters, double headingRadians) { return new Pose2D((xMeters * MillimetersPerMeter + DeltaXMillimeters) / MillimetersPerMeter, (yMeters * MillimetersPerMeter + DeltaYMillimeters) / MillimetersPerMeter, NormalizeRadians(headingRadians + HeadingDeltaRadians)); } } private static double NormalizeRadians(double angle) { double normalized = angle % (2d * Math.PI); if (normalized <= -Math.PI) return normalized + 2d * Math.PI; return normalized > Math.PI ? normalized - 2d * Math.PI : normalized; } ``` Update every fixed builder to accept `FixedScenarioTransform transform` and use `transform.Pose`, `transform.X` and `transform.Y` for all coordinates: ```csharp new AxisAlignedRectangleObstacle(transform.X(2700f), transform.X(3300f), transform.Y(1200f), transform.Y(2800f)); new CircleObstacle(transform.X(2400f), transform.Y(1300f), 220f); new TwoLegProjectionInput(true, transform.X(3900f), transform.Y(2500f), 0d, -180f, -180f, -180f, 180f, 140f, "P1 fixed TwoLeg snapshot."); ``` Change `CreateMap` to accept the transform and translate all four `MapBoundsMm` limits with the correct axis. It must not change resolution, source IDs, source versions, required flags, empty-map flags, vehicle parameters, direction constraints or existing timeout assignments. - [ ] **Step 4: 运行工厂和回归验证,确认新旧入口都通过** Run: ```powershell dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1 ``` Expected: build has zero errors; script ends with `Coarse path P1 scenario checks passed.` Existing assertions for the one-argument factory must continue to pass. - [ ] **Step 5: 提交仅包含工厂与程序集级测试的可审查变更** ```powershell git add -- ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/CoarsePathScenarioFactory.cs ClumsyPilot/tests/verify_coarse_path_integration.ps1 git commit -m "feat: anchor coarse path scenarios to AMR pose" ``` ### Task 2: 让固定 MovementTest 冻结 AMR 快照并显示诊断 **Files:** - Modify: `ClumsyPilot/tests/verify_coarse_path_ui.ps1:15-52` - Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/MovementTest.CoarsePathTest.cs:221-371,512-540` **Interfaces:** - Consumes: 新的 `CoarsePathScenarioFactory.Create(scenario, xMm, yMm, headingDeg)`、`DetourInterface.getCartLocation()` 和现有 `Run(string, CoarsePathPlanningJob)`。 - Produces: `RunScenario` 只读取一次位姿,创建已冻结的实时 job;手动入口继续调用现有两参 `Run`。 - Produces: 私有 `AmrPoseSnapshot`,仅保存有限 X/Y/deg 和格式化后的状态文本,不向规划后台读取定位。 - [ ] **Step 1: 在 UI 源级验证中加入失败断言** 在现有 `$source` 断言后追加以下代码。使用 ASCII 的方法名和 `AMR` 文本,避免 Windows PowerShell 5 对中文脚本字符串的编码歧义。 ```powershell $runnerStart = $source.IndexOf('internal static class CoarsePathPlanningTestRunner') Assert-True ($runnerStart -ge 0) 'Shared runner source must exist.' $runnerSource = $source.Substring($runnerStart) Assert-Match $runnerSource 'RunScenario[\s\S]*getCartLocation\s*\(' 'Fixed scenarios must read the current AMR pose.' Assert-Match $runnerSource 'CoarsePathScenarioFactory\.Create\s*\(\s*scenario\s*,' 'Fixed scenarios must use the AMR-aware factory overload.' Assert-Match $runnerSource 'AMR' 'The runner must expose AMR pose diagnostics.' Assert-Match $runnerSource 'ArgumentException\("AMR' 'Invalid AMR input must be reported without starting planning.' Assert-Match $runnerSource 'double\.IsNaN|double\.IsInfinity' 'The runner must reject non-finite AMR coordinates.' Assert-Match $runnerSource 'DrawStatus\s*\([\s\S]*AmrPoseSnapshot' 'Result status must receive the frozen AMR snapshot.' ``` - [ ] **Step 2: 运行 UI 脚本,确认新增实时位姿约束尚未满足** Run: ```powershell powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_ui.ps1 ``` Expected: script fails at `Fixed scenarios must read the current AMR pose.` or `Fixed scenarios must use the AMR-aware factory overload.` - [ ] **Step 3: 实现单次 AMR 读取、校验、冻结与绘制传递** 在 `CoarsePathPlanningTestRunner` 内加入以下私有快照类型和工厂调用路径。任何读取或校验异常都在前台转换为 `AMR 位姿不可用:...`,因此不会进入 `Task.Run`。 ```csharp private sealed class AmrPoseSnapshot { public AmrPoseSnapshot(double xMillimeters, double yMillimeters, double headingDegrees) { EnsureFiniteAmrValue(xMillimeters, "X"); EnsureFiniteAmrValue(yMillimeters, "Y"); EnsureFiniteAmrValue(headingDegrees, "航向"); XMillimeters = xMillimeters; YMillimeters = yMillimeters; HeadingDegrees = headingDegrees; } public double XMillimeters { get; } public double YMillimeters { get; } public double HeadingDegrees { get; } public string DisplayText { get { return "AMR 起点:X=" + XMillimeters.ToString("F0", CultureInfo.InvariantCulture) + " mm,Y=" + YMillimeters.ToString("F0", CultureInfo.InvariantCulture) + " mm,航向=" + HeadingDegrees.ToString("F1", CultureInfo.InvariantCulture) + " deg"; } } } private static void EnsureFiniteAmrValue(double value, string name) { if (double.IsNaN(value) || double.IsInfinity(value)) throw new ArgumentException(name + " 必须是有限数。"); } internal static void RunScenario(CoarsePathTestScenario scenario, string scenarioName) { try { var pose = DetourInterface.getCartLocation(); var snapshot = new AmrPoseSnapshot(pose.x, pose.y, pose.th); CoarsePathPlanningJob job = CoarsePathScenarioFactory.Create(scenario, snapshot.XMillimeters, snapshot.YMillimeters, snapshot.HeadingDegrees); Run(scenarioName, job, snapshot); } catch (Exception exception) { ShowInputFailure(new ArgumentException("AMR 位姿不可用:" + exception.Message, exception)); } } ``` Keep the existing `internal static void Run(string scenarioName, CoarsePathPlanningJob job)` as a thin compatibility overload that calls a new private `Run(string, CoarsePathPlanningJob, AmrPoseSnapshot)`. Thread the snapshot through `DrawPending`, the `ContinueWith` lambda, `Finish`, `DrawResult`, and `DrawStatus`. In `DrawStatus`, render `snapshot.DisplayText` after the scene line only when the snapshot is non-null; increase subsequent Y offsets consistently so vehicle and termination-reason text do not overlap. Manual `CoarsePathPlanningTest.Test()` must keep its present `Run(..., job)` call and user-entered timeout behavior. - [ ] **Step 4: 编译并运行 UI 验证,确认后台边界未回归** Run: ```powershell dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_ui.ps1 ``` Expected: build has zero errors; script ends with `Coarse path UI source checks passed.` Existing assertions still confirm one shared service, background `Task.Run`, cancellation and no blocking `Task.Result`/`Wait`. - [ ] **Step 5: 提交仅包含运行器与 UI 验证的可审查变更** ```powershell git add -- ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/MovementTest.CoarsePathTest.cs ClumsyPilot/tests/verify_coarse_path_ui.ps1 git commit -m "feat: use live AMR pose in coarse path tests" ``` ### Task 3: 记录实时固定场景语义并执行完整回归 **Files:** - Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/README.md:289-347` - Modify: `ClumsyPilot/tests/verify_coarse_path_ui.ps1:54-100` **Interfaces:** - Consumes: 已实现的实时工厂入口和 MovementTest 状态文本。 - Produces: 可供现场使用者理解的固定案例锚定、缓存和定位异常说明;文档验证继续只检查稳定 ASCII 标识符。 - [ ] **Step 1: 在 README 源级验证中加入失败断言** 在 `$readmeStructure` 数组中加入以下 ASCII 项,并在数组后加入 `Contains` 断言: ```powershell foreach ($requiredLiveAmrText in @( 'Create(CoarsePathTestScenario scenario, double amrXMillimeters', 'AMR', 'Input', 'TwoLeg')) { Assert-True ($readme.Contains($requiredLiveAmrText)) "P1 README must document live fixed scenarios: $requiredLiveAmrText." } ``` - [ ] **Step 2: 运行 UI 文档验证,确认文档尚未描述实时固定案例** Run: ```powershell powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_ui.ps1 ``` Expected: script fails because README does not yet contain the new `Create(CoarsePathTestScenario scenario, double amrXMillimeters` signature. - [ ] **Step 3: 更新 P1 手动测试与可视化章节** 在固定案例表之前新增“固定案例的实时 AMR 锚点”小节,逐条说明:六个 `粗路径规划-*` 入口读取一次 `getCartLocation`;起点等于这份冻结的世界 mm/deg 位姿;地图边界、目标、圆形/轴对齐矩形和 TwoLeg 检测原点统一平移;终点航向保持和基准起点的航向差;TwoLeg 朝向不旋转;手动 `粗路径规划` 的输入流程不变。 在同一小节以单行 inline code 明确列出公开签名:`Create(CoarsePathTestScenario scenario, double amrXMillimeters, double amrYMillimeters, double amrHeadingDegrees)`。 加入公开签名示例: ```csharp CoarsePathPlanningJob job = CoarsePathScenarioFactory.Create( CoarsePathTestScenario.RectangleDetour, amrXMillimeters, amrYMillimeters, amrHeadingDegrees); ``` 将缓存命中表项改为“相同 AMR X/Y 下的重复完整地图输入”;明确 AMR 位置已移动时未命中是正常的 `None`/非 `Input` 缓存结果,且仅航向变化不改变地图输入、仍可命中。补充运行器在定位读取、空值访问或有限数校验失败时显示 `AMR 位姿不可用` 且不启动后台规划;状态区会显示该次冻结的 AMR 起点。保留关于 `getCartLocation` 可能阻塞和必须在定位准备完成后运行的既有警告。 - [ ] **Step 4: 运行完整粗路径与地图回归集** Run: ```powershell dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_adapter.ps1 powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_factory.ps1 powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_documentation.ps1 powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_image.ps1 powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_test_config.ps1 powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_collision.ps1 powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_search.ps1 powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1 powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_ui.ps1 ``` Expected: build reports zero warnings and zero errors. Every verification script exits 0; integration ends with `Coarse path P1 scenario checks passed.` and UI ends with `Coarse path UI source checks passed.` - [ ] **Step 5: 提交文档与最后验证脚本变更** ```powershell git add -- ClumsyPilot/ParkrobTrajplanner/CoarsePath/README.md ClumsyPilot/tests/verify_coarse_path_ui.ps1 git commit -m "docs: explain live AMR coarse path scenarios" ```