Files
ParkingRobot/docs/superpowers/plans/2026-07-28-path-smoothing-comparison.md
T

1039 lines
46 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Path Smoothing Comparison Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Build a validated coarse-path smoothing module that compares cubic B-spline, local cubic Bézier, and piecewise quintic paths, then exports reproducible IEEE-style Chinese reports for existing and fast fixture scenarios.
**Architecture:** Add `PathSmoothing` beside `CoarsePath`; keep contracts, shared geometry processing, algorithms, validation, comparison, and rendering isolated. Every algorithm consumes the same preprocessed direction segments, every candidate passes the same analyzer and full-footprint validator, and only accepted results reach the formal facade. Reporting consumes immutable comparison data and cannot affect planning status.
**Tech Stack:** C# 10, .NET Standard 2.0, existing `PlanningGridMap`/`FootprintCollisionChecker`, PowerShell reflection tests, Newtonsoft.Json 13.0.4, StbImageWriteSharp 1.16.7, System.Drawing.Common 10.0.10 on Windows.
## Global Constraints
- Core units are m, rad, and 1/m; no mm or degree conversion inside smoothing algorithms.
- Preserve start, goal, direction-segment order, gear-switch count, and gear-switch poses exactly.
- Never differentiate, resample, or fit across a gear-switch duplicate pair.
- Default output spacing is `0.05 m`; default swept-collision step is `0.025 m`.
- A formal result publishes a path only for `Success` or explicitly verified `FallbackToCoarsePath`.
- Rejected candidates may appear only in comparison diagnostics, never in `PathSmoothingResult.Path`.
- No speed, acceleration, time, SQP, Frenet, chassis, sensor, localization, or UI dependencies.
- Figure size is `7.16 × 5.2 in`; PNG size is `2148 × 1560 px` with 300 dpi metadata.
- Chinese text uses `SimSun`; English, numbers, Greek, and mathematics use `Times New Roman`.
- Fixed method colors are raw `#4D4D4D`, B-spline `#0072B2`, Bézier `#D55E00`, quintic `#009E73`, and curvature limits `#CC79A7`.
- `System.Drawing.Common` is a Windows-only report-rendering dependency; smoothing, validation, comparison, SVG, and CSV remain independent of its runtime availability.
- Follow TDD for every task: failing verification first, minimal implementation second, full relevant verification before commit.
---
### Task 1: Immutable smoothing contracts
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/SmoothingMethod.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/PathSmoothingStatus.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/SmoothedPathPointSource.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/SmoothedPathPoint.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/SmoothedPathSegment.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/CubicBSplineOptions.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/LocalCubicBezierOptions.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/PiecewiseQuinticOptions.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/PathSmoothingConfiguration.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/PathSmoothingRequest.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/PathQualityMetrics.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/PathSmoothingDiagnostics.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/PathSmoothingResult.cs`
- Test: `ClumsyPilot/tests/verify_path_smoothing_contracts.ps1`
**Interfaces:**
- Consumes: `CoarsePathPoint`, `PathSegment`, `PlanningGridMap`, `VehicleParameters`.
- Produces: `PathSmoothingRequest`; `PathSmoothingResult.Success(...)`; `PathSmoothingResult.Fallback(...)`; `PathSmoothingResult.Failure(...)`; all public enums and value objects used below.
- [ ] **Step 1: Write the failing contract verification**
Create a reflection script that loads `ClumsyPilot.dll`, resolves every type listed above, and verifies these exact defaults and invariants:
```powershell
$configuration = [Activator]::CreateInstance($configurationType)
Assert-Near 0.05 $configuration.OutputSpacingMeters 'Default output spacing must be 0.05 m.'
Assert-Near 0.025 $configuration.MaximumCollisionCheckStepMeters 'Default collision step must be 0.025 m.'
Assert-Equal 4 $configuration.RetryStrengthScales.Count 'Retry schedule must contain four entries.'
Assert-Near 1.0 $configuration.RetryStrengthScales[0] 'First retry scale must be 1.0.'
Assert-Near 0.25 $configuration.RetryStrengthScales[3] 'Last retry scale must be 0.25.'
$failed = $resultType.GetMethod('Failure').Invoke(
$null,
@([Enum]::Parse($statusType, 'InvalidInput'),
[Activator]::CreateInstance($diagnosticsType)))
Assert-Equal 0 $failed.Path.Count 'Failure must publish no path.'
Assert-Equal 0 $failed.Segments.Count 'Failure must publish no segments.'
```
Also construct a `SmoothedPathPoint` and assert all units/properties, construct two `SmoothedPathSegment` instances, and prove returned collections cannot be mutated.
- [ ] **Step 2: Run the verification and confirm RED**
Run:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_contracts.ps1
```
Expected: build succeeds on existing code; script fails because `MultiWheelC.TrajectoryPlanning.PathSmoothing.PathSmoothingConfiguration` is missing.
- [ ] **Step 3: Implement enums, immutable values, and result factories**
Use namespace `MultiWheelC.TrajectoryPlanning.PathSmoothing`. Implement these exact public shapes:
```csharp
public enum SmoothingMethod { CubicBSpline, LocalCubicBezier, PiecewiseQuintic }
public enum PathSmoothingStatus { Success, FallbackToCoarsePath, InvalidInput, Infeasible, Failed, Cancelled }
public enum SmoothedPathPointSource { Anchor, Interpolated, GearSwitch, CoarsePathFallback }
public sealed class PathSmoothingConfiguration
{
public PathSmoothingConfiguration()
{
OutputSpacingMeters = 0.05d;
MaximumCollisionCheckStepMeters = 0.025d;
MinimumClearanceReserveMeters = 0.02d;
SmoothingStrength = 1d;
AllowFallbackToCoarsePath = true;
RetryStrengthScales = new ReadOnlyCollection<double>(
new List<double> { 1d, 0.75d, 0.50d, 0.25d });
}
public SmoothingMethod Method { get; set; }
public double OutputSpacingMeters { get; set; }
public double MaximumCollisionCheckStepMeters { get; set; }
public double MinimumClearanceReserveMeters { get; set; }
public double SmoothingStrength { get; set; }
public bool AllowFallbackToCoarsePath { get; set; }
public IReadOnlyList<double> RetryStrengthScales { get; }
public CubicBSplineOptions CubicBSpline { get; } = new CubicBSplineOptions();
public LocalCubicBezierOptions LocalCubicBezier { get; } = new LocalCubicBezierOptions();
public PiecewiseQuinticOptions PiecewiseQuintic { get; } = new PiecewiseQuinticOptions();
}
```
Use these exact option defaults:
```csharp
public sealed class CubicBSplineOptions
{
public double EndpointTangentScale { get; set; } = 1d / 3d;
}
public sealed class LocalCubicBezierOptions
{
public double CornerHeadingThresholdRadians { get; set; } = Math.PI / 18d;
public double MaximumWindowLengthMeters { get; set; } = 0.60d;
public double HandleLengthRatio { get; set; } = 1d / 3d;
}
public sealed class PiecewiseQuinticOptions
{
public double KnotSpacingMeters { get; set; } = 0.50d;
public double MinimumKnotSpacingMeters { get; set; } = 0.10d;
}
```
Define the request constructor exactly as:
```csharp
public PathSmoothingRequest(
IReadOnlyList<CoarsePathPoint> coarsePath,
IReadOnlyList<PathSegment> segments,
PlanningGridMap map,
VehicleParameters vehicle,
PathSmoothingConfiguration configuration)
```
Define parameterless immutable-empty defaults for `PathQualityMetrics` and `PathSmoothingDiagnostics`, plus overloads that accept all measured values. The three result factories are:
```csharp
public static PathSmoothingResult Success(
SmoothingMethod method, IReadOnlyList<SmoothedPathPoint> path,
IReadOnlyList<SmoothedPathSegment> segments, PathSmoothingDiagnostics diagnostics);
public static PathSmoothingResult Fallback(
SmoothingMethod attemptedMethod, IReadOnlyList<SmoothedPathPoint> path,
IReadOnlyList<SmoothedPathSegment> segments, PathSmoothingDiagnostics diagnostics);
public static PathSmoothingResult Failure(
PathSmoothingStatus status, PathSmoothingDiagnostics diagnostics);
```
`PathSmoothingResult` must copy all input lists into `ReadOnlyCollection<T>`. Reject `Success`/`Fallback` factories with empty paths or segments; reject `Failure` with a success-like status.
- [ ] **Step 4: Run contract verification and existing integration verification**
Run:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_contracts.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1
```
Expected: both scripts end with their passed messages.
- [ ] **Step 5: Commit**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts ClumsyPilot/tests/verify_path_smoothing_contracts.ps1
git commit -m "feat: define path smoothing contracts"
```
### Task 2: Input preparation, direction splitting, resampling, and geometry analysis
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Processing/PreparedPath.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Processing/PreparedDirectionSegment.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Processing/SmoothingPoint2D.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Processing/PathGeometryAnalysis.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Processing/PathSmoothingPreprocessor.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Processing/ArcLengthResampler.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Processing/PathGeometryAnalyzer.cs`
- Test: `ClumsyPilot/tests/verify_path_smoothing_geometry.ps1`
**Interfaces:**
- Consumes: `PathSmoothingRequest`.
- Produces: `PathSmoothingPreprocessor.TryPrepare(request, out PreparedPath, out string)` and `PathGeometryAnalyzer.TryAnalyze(candidateSegments, spacing, out PathGeometryAnalysis analysis, out string reason)`.
- [ ] **Step 1: Write analytic failing tests**
Build reflection helpers that create:
```text
forward straight: (0,0,0) → (1,0,0), expected κ = 0
forward quarter circle: R=2, expected vehicle κ = +0.5
reverse quarter circle: R=2, expected vehicle κ = -0.5
gear switch: duplicated pose and arc length with opposite directions
```
Assert `0.05 m` spacing, exact final point, continuous `UnwrappedHeading`, correct curvature signs, and no derivative across the gear-switch pair.
- [ ] **Step 2: Run and confirm RED**
```powershell
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_geometry.ps1
```
Expected: fails because `PathGeometryAnalyzer` is missing.
- [ ] **Step 3: Implement deterministic preparation and resampling**
Implement segment-local linear arc interpolation:
```csharp
double ratio = (targetArc - left.ArcLength) / (right.ArcLength - left.ArcLength);
double x = left.X + ratio * (right.X - left.X);
double y = left.Y + ratio * (right.Y - left.Y);
double heading = left.UnwrappedHeading +
ratio * (right.UnwrappedHeading - left.UnwrappedHeading);
```
Always append the exact segment end. Preserve separate gear-switch endpoints. Reject non-finite values, non-monotonic arc length, illegal duplicates, and segment coverage gaps.
- [ ] **Step 4: Implement shared geometry formulas**
For each direction segment, recompute arc length from Euclidean position increments, unwrap heading from travel tangents, and use stable one-sided/central differences:
```csharp
double geometricCurvature = deltaHeading / deltaArc;
double directionSign = direction == TravelDirection.Forward ? 1d : -1d;
double vehicleCurvature = directionSign * geometricCurvature;
double totalVariationIncrement = Math.Abs(currentCurvature - previousCurvature);
double variationEnergyIncrement =
Math.Pow((currentCurvature - previousCurvature) / deltaArc, 2d) * deltaArc;
```
At segment boundaries use only samples from that segment. Never include the gear-switch duplicate distance in a denominator.
- [ ] **Step 5: Run geometry, contract, and utility checks**
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_geometry.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_contracts.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_utils.ps1
```
Expected: all pass; circle curvature error stays within the tolerance encoded in the test.
- [ ] **Step 6: Commit**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Processing ClumsyPilot/tests/verify_path_smoothing_geometry.ps1
git commit -m "feat: add smoothing path geometry processing"
```
### Task 3: Full-footprint smoothing validator
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Validation/SmoothedPathValidator.cs`
- Test: `ClumsyPilot/tests/verify_path_smoothing_validation.ps1`
**Interfaces:**
- Consumes: analyzed `SmoothedPathPoint`/`SmoothedPathSegment`, original prepared endpoints, `PlanningGridMap`, `VehicleParameters`, collision step.
- Produces: `TryValidate(..., out IReadOnlyList<SmoothedPathPoint> pathWithClearance, out double minimumClearance, out string reason)`.
- [ ] **Step 1: Write failing safety cases**
Create an empty map, an occupied rectangle map, and paths that are: valid; point-colliding; swept-motion-colliding; outside bounds; over maximum curvature; changed at a gear switch; and overclaiming clearance.
```powershell
Assert-False $collisionAccepted 'A smoothing candidate that cuts through an obstacle must be rejected.'
Assert-False $curvatureAccepted 'A smoothing candidate above vehicle maximum curvature must be rejected.'
Assert-False $switchAccepted 'A moved gear-switch pose must be rejected.'
Assert-True $validAccepted 'A valid straight candidate must pass.'
```
- [ ] **Step 2: Run and confirm RED**
```powershell
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_validation.ps1
```
Expected: missing `SmoothedPathValidator`.
- [ ] **Step 3: Implement validator using existing collision semantics**
For every point call `FootprintCollisionChecker.IsPoseCollisionFree`; for every non-duplicate adjacent pair call `IsSweptMotionCollisionFree`. Enforce:
```csharp
if (Math.Abs(point.VehicleCurvature) > maximumCurvature + 1e-6d)
return Fail("平滑路径车辆曲率超过车辆上限。");
if (!SamePose(candidateStart, originalStart) || !SamePose(candidateEnd, originalEnd))
return Fail("平滑路径改变了方向段端点。");
```
Replace candidate clearance with the conservative checked value; never retain a larger claimed value.
- [ ] **Step 4: Run validator and existing collision checks**
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_validation.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_collision.ps1
```
Expected: both pass.
- [ ] **Step 5: Commit**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Validation ClumsyPilot/tests/verify_path_smoothing_validation.ps1
git commit -m "feat: validate smoothed vehicle paths"
```
### Task 4: Algorithm contract and finite retry runner
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Algorithms/IPathSmoother.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Algorithms/SmoothingCandidate.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Algorithms/SmoothingAlgorithmInput.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Algorithms/SmoothingAlgorithmRunner.cs`
- Test: `ClumsyPilot/tests/verify_path_smoothing_runner.ps1`
**Interfaces:**
- Consumes: `PreparedPath`, method-specific `IPathSmoother`, analyzer, validator, retry scales.
- Produces: one accepted candidate or stable `Infeasible`/`Failed` diagnostics with attempted strengths and optional rejected comparison geometry.
Use this exact internal algorithm interface:
```csharp
internal interface IPathSmoother
{
SmoothingMethod Method { get; }
SmoothingCandidate Smooth(
SmoothingAlgorithmInput input,
double effectiveStrength,
CancellationToken cancellationToken);
}
```
- [ ] **Step 1: Write failing fake-smoother tests**
Use reflection plus internal test doubles exposed from a nested test helper to prove:
```text
strengths attempted exactly: 1.00, 0.75, 0.50, 0.25
stop immediately after first accepted candidate
do not retry numerical invalid input
cancel before the next attempt
retain rejected candidate only in comparison diagnostics
```
- [ ] **Step 2: Run and confirm RED**
```powershell
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_runner.ps1
```
Expected: missing `SmoothingAlgorithmRunner`.
- [ ] **Step 3: Implement the runner**
Use this control flow:
```csharp
foreach (double scale in configuration.RetryStrengthScales)
{
cancellationToken.ThrowIfCancellationRequested();
SmoothingCandidate candidate = smoother.Smooth(input, configuration.SmoothingStrength * scale);
if (!candidate.Succeeded) return AlgorithmRunResult.Failed(candidate.Reason);
if (!_analyzer.TryAnalyze(candidate.Segments, configuration.OutputSpacingMeters, out PathGeometryAnalysis analyzed, out var reason))
return AlgorithmRunResult.Failed(reason);
if (_validator.TryValidate(analyzed.Path, analyzed.Segments, input, out var safePath, out var clearance, out reason))
return AlgorithmRunResult.Success(safePath, analyzed.Segments, metrics, scale);
rejected = candidate;
failures.Add(reason);
}
return AlgorithmRunResult.Infeasible(rejected, failures);
```
Convert `OperationCanceledException` to `Cancelled` at the facade boundary, not inside numerical algorithms.
- [ ] **Step 4: Run runner, geometry, and validator tests**
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_runner.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_geometry.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_validation.ps1
```
Expected: all pass.
- [ ] **Step 5: Commit**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Algorithms ClumsyPilot/tests/verify_path_smoothing_runner.ps1
git commit -m "feat: add smoothing algorithm retry runner"
```
### Task 5: Clamped cubic B-spline smoother
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Algorithms/CubicBSplineSmoother.cs`
- Test: `ClumsyPilot/tests/verify_path_smoothing_bspline.ps1`
**Interfaces:**
- Implements: `IPathSmoother.Method == SmoothingMethod.CubicBSpline`.
- Produces: one geometry-only candidate per input direction segment.
- [ ] **Step 1: Write failing deterministic shape tests**
Verify a straight segment remains collinear, a five-point corner becomes tangent-continuous, endpoints and travel tangents are exact, and every displacement stays within its supplied movement radius.
- [ ] **Step 2: Run and confirm RED**
```powershell
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_bspline.ps1
```
Expected: missing `CubicBSplineSmoother`.
- [ ] **Step 3: Implement clamped basis evaluation and constrained control points**
Use degree 3, endpoint knot multiplicity 4, and Coxde Boor basis evaluation. Fix the first/last control points to endpoints; place adjacent control points on endpoint travel tangents. Blend remaining control points toward local three-point averages:
```csharp
SmoothingPoint2D target = (previous + current + next) / 3d;
SmoothingPoint2D proposed = current + strength * (target - current);
SmoothingPoint2D bounded = ClampDisplacement(current, proposed, allowedRadius);
```
Return the original straight samples when all perpendicular deviations are below `1e-9`.
- [ ] **Step 4: Run algorithm and shared safety tests**
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_bspline.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_runner.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_validation.ps1
```
Expected: all pass.
- [ ] **Step 5: Commit**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Algorithms/CubicBSplineSmoother.cs ClumsyPilot/tests/verify_path_smoothing_bspline.ps1
git commit -m "feat: add cubic b-spline path smoother"
```
### Task 6: Local cubic Bézier smoother
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Algorithms/LocalCubicBezierSmoother.cs`
- Test: `ClumsyPilot/tests/verify_path_smoothing_bezier.ps1`
**Interfaces:**
- Implements: `IPathSmoother.Method == SmoothingMethod.LocalCubicBezier`.
- Consumes: heading-change threshold, maximum local window, handle-length ratio.
- [ ] **Step 1: Write failing local-behavior tests**
Assert no window is created for a straight line; one corner creates one replacement; overlapping windows merge; outside-window samples remain bitwise equal; endpoints and switch points remain fixed.
- [ ] **Step 2: Run and confirm RED**
```powershell
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_bezier.ps1
```
Expected: missing `LocalCubicBezierSmoother`.
- [ ] **Step 3: Implement window detection and Bézier evaluation**
Detect corners from absolute change in travel tangent. Merge touching windows. For each window:
```csharp
SmoothingPoint2D p1 = p0 + entryTangent * handleLength;
SmoothingPoint2D p2 = p3 - exitTangent * handleLength;
SmoothingPoint2D value =
Math.Pow(1d - t, 3d) * p0 +
3d * Math.Pow(1d - t, 2d) * t * p1 +
3d * (1d - t) * t * t * p2 +
t * t * t * p3;
```
For each evaluated point, compare its displacement from the parameter-matched interpolated original reference with `max(0, reference.BodyClearance - MinimumClearanceReserveMeters)`. If any point exceeds that radius, return a failed candidate with no geometry; do not pointwise clamp or project curve samples. Retain original samples outside merged windows.
- [ ] **Step 4: Run Bézier and regression checks**
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_bezier.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_runner.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1
```
Expected: all pass.
- [ ] **Step 5: Commit**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Algorithms/LocalCubicBezierSmoother.cs ClumsyPilot/tests/verify_path_smoothing_bezier.ps1
git commit -m "feat: add local cubic bezier smoother"
```
### Task 7: Piecewise quintic Hermite smoother
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Algorithms/PiecewiseQuinticSmoother.cs`
- Test: `ClumsyPilot/tests/verify_path_smoothing_quintic.ps1`
**Interfaces:**
- Implements: `IPathSmoother.Method == SmoothingMethod.PiecewiseQuintic`.
- Produces: C2-connected local polynomial segments without crossing direction boundaries.
- [ ] **Step 1: Write failing continuity and degeneracy tests**
At every shared knot evaluate left/right position, first derivative, and second derivative; assert equality within `1e-6`. Verify exact endpoint pose, bounded movement, and stable failure for a segment shorter than the configured minimum knot spacing.
- [ ] **Step 2: Run and confirm RED**
```powershell
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_quintic.ps1
```
Expected: missing `PiecewiseQuinticSmoother`.
- [ ] **Step 3: Implement normalized quintic Hermite basis**
For each knot interval use normalized `t ∈ [0,1]` and solve the six coefficients independently for X and Y from:
```text
p(0)=p0, p(1)=p1
p'(0)=v0, p'(1)=v1
p''(0)=a0, p''(1)=a1
```
Derive endpoint velocities from travel tangents times interval length. Blend shared accelerations once per knot and reuse the same value on both adjacent intervals. Reject singular or non-finite coefficients before sampling.
- [ ] **Step 4: Run quintic, geometry, and validator checks**
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_quintic.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_geometry.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_validation.ps1
```
Expected: all pass.
- [ ] **Step 5: Commit**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Algorithms/PiecewiseQuinticSmoother.cs ClumsyPilot/tests/verify_path_smoothing_quintic.ps1
git commit -m "feat: add piecewise quintic path smoother"
```
### Task 8: Formal smoothing facade and explicit coarse-path fallback
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Facade/PathSmoothingService.cs`
- Test: `ClumsyPilot/tests/verify_path_smoothing_service.ps1`
**Interfaces:**
- Produces: `PathSmoothingResult Smooth(PathSmoothingRequest, CancellationToken)`.
- Consumes: preprocessor, method registry, retry runner, analyzer, validator.
- [ ] **Step 1: Write failing facade tests**
Cover valid straight success, invalid input, cancellation, infeasible-without-fallback, and infeasible-with-verified-fallback. Assert fallback points use `CoarsePathFallback` and status never equals `Success`.
- [ ] **Step 2: Run and confirm RED**
```powershell
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_service.ps1
```
Expected: missing `PathSmoothingService`.
- [ ] **Step 3: Implement orchestration**
Use a stable method registry:
```csharp
private IPathSmoother Resolve(SmoothingMethod method) =>
method switch
{
SmoothingMethod.CubicBSpline => _bSpline,
SmoothingMethod.LocalCubicBezier => _bezier,
SmoothingMethod.PiecewiseQuintic => _quintic,
_ => throw new ArgumentOutOfRangeException(nameof(method)),
};
```
Validate the coarse path before smoothing. On allowed fallback, convert the revalidated coarse points to `SmoothedPathPoint` with `CoarsePathFallback`, re-run shared geometry analysis, and return `Fallback`, preserving the failed method diagnostics.
- [ ] **Step 4: Run all core smoothing verifications**
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_contracts.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_geometry.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_validation.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_service.ps1
```
Expected: all pass.
- [ ] **Step 5: Commit**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Facade ClumsyPilot/tests/verify_path_smoothing_service.ps1
git commit -m "feat: expose validated path smoothing service"
```
### Task 9: Comparison metrics, isolation, and deterministic ranking
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Comparison/PathSmoothingComparisonRequest.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Comparison/PathSmoothingComparisonEntry.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Comparison/PathSmoothingComparisonResult.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Comparison/SmoothingMethodRanker.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Facade/PathSmoothingComparisonService.cs`
- Test: `ClumsyPilot/tests/verify_path_smoothing_comparison.ps1`
**Interfaces:**
- Produces: `Compare(PathSmoothingComparisonRequest, CancellationToken)`.
- Ranking order: feasible count; median variation energy; worst peak utilization; worst clearance loss; median length increase; median elapsed.
- [ ] **Step 1: Write failing comparison and ranking tests**
Create synthetic entries whose order changes at each tie-break level. Verify one method failure does not stop the remaining methods, cancellation does, and no feasible method produces a null recommendation.
Also assert the result always contains one separately analyzed raw-path baseline plus exactly one entry for each requested method; the raw baseline is never treated as a candidate method in ranking.
- [ ] **Step 2: Run and confirm RED**
```powershell
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_comparison.ps1
```
Expected: missing comparison service/ranker.
- [ ] **Step 3: Implement metric normalization and lexicographic comparison**
Use stable median:
```csharp
double Median(IReadOnlyList<double> values)
{
double[] sorted = values.OrderBy(value => value).ToArray();
int middle = sorted.Length / 2;
return sorted.Length % 2 == 1
? sorted[middle]
: (sorted[middle - 1] + sorted[middle]) / 2d;
}
```
Use absolute deltas when a raw denominator has magnitude below `1e-12`. The comparison service must force `AllowFallbackToCoarsePath = false` so fallback cannot masquerade as method success.
- [ ] **Step 4: Run comparison and service tests**
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_comparison.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_service.ps1
```
Expected: all pass.
- [ ] **Step 5: Commit**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Comparison ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Facade/PathSmoothingComparisonService.cs ClumsyPilot/tests/verify_path_smoothing_comparison.ps1
git commit -m "feat: compare and rank smoothing methods"
```
### Task 10: Versioned fast fixtures and existing end-to-end scenarios
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/SmoothingScenarioFixture.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/SmoothingScenarioFixtureLoader.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/SmoothingFixtureGenerator.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/SmoothingScenarioFactory.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/Fixtures/path-smoothing-fixtures.json`
- Create: `ClumsyPilot/tests/generate_path_smoothing_fixtures.ps1`
- Create: `ClumsyPilot/tests/verify_path_smoothing_fixtures.ps1`
- Create: `ClumsyPilot/tests/verify_path_smoothing_integration.ps1`
**Interfaces:**
- Produces: eight immutable fast fixtures and four end-to-end comparison requests.
- Consumes: existing `CoarsePathScenarioFactory.Create(scenario)` for end-to-end requests.
- [ ] **Step 1: Write failing fixture tests**
Assert the exact IDs:
```powershell
$expected = @(
'straight', 'single-turn', 's-bend', 'large-heading-change',
'rectangle-detour', 'multi-obstacle-detour',
'narrow-corridor', 'forward-reverse-switch')
```
Verify unique IDs, version > 0, matching fingerprint, valid segment coverage, and that fixture-only loading has no reference to `HybridAStarPlanner`.
- [ ] **Step 2: Write failing end-to-end tests**
For `ExplicitEmpty`, `RectangleDetour`, `ManualAndTwoLeg`, and `ReverseGearSwitch`, call the existing planner then comparison service. Assert the raw path is unchanged and every successful smoothing entry preserves segment count and gear-switch count.
- [ ] **Step 3: Run and confirm RED**
```powershell
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_fixtures.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_integration.ps1
```
Expected: missing fixture loader/factory.
- [ ] **Step 4: Implement deterministic JSON fixtures**
Serialize with invariant numbers and explicit schema:
```json
{
"schemaVersion": 1,
"scenarios": [
{
"id": "straight",
"fixtureVersion": 1,
"configurationFingerprint": "sha256:<lowercase-hex>",
"map": {
"xMinMm": 0, "xMaxMm": 6000,
"yMinMm": 0, "yMaxMm": 4000,
"resolutionMm": 50,
"obstacles": []
},
"vehicle": { "lengthMeters": 0.8, "widthMeters": 0.6, "safetyMarginMeters": 0.05, "maximumCurvaturePerMeter": 0.8333333333333334 },
"path": [],
"segments": []
}
]
}
```
Populate all eight scenarios from explicitly verified successful coarse-path outputs by running:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\generate_path_smoothing_fixtures.ps1 -OutputPath .\ClumsyPilot\ParkrobTrajplanner\PathSmoothing\Test\Fixtures\path-smoothing-fixtures.json
```
`SmoothingFixtureGenerator.Generate(string outputPath, bool overwrite)` must refuse an existing target when `overwrite` is false. The script maps `-Overwrite` to that argument; normal tests call only `LoadAndVerify` and never regenerate data. Each obstacle is serialized as a typed circle or axis-aligned rectangle DTO so the loader can rebuild the exact `PlanningGridMap` without Hybrid A*.
- [ ] **Step 5: Run fixture and integration checks**
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_fixtures.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_integration.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1
```
Expected: all pass. Record actual end-to-end elapsed values as diagnostics, not hardware-dependent pass thresholds.
- [ ] **Step 6: Commit**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test ClumsyPilot/tests/generate_path_smoothing_fixtures.ps1 ClumsyPilot/tests/verify_path_smoothing_fixtures.ps1 ClumsyPilot/tests/verify_path_smoothing_integration.ps1
git commit -m "test: add path smoothing scenarios and fixtures"
```
### Task 11: Shared figure model, IEEE SVG, and UTF-8 CSV
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/IeeeFigureStyle.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureModel.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureModelBuilder.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingSvgRenderer.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingCsvWriter.cs`
- Test: `ClumsyPilot/tests/verify_path_smoothing_svg_csv.ps1`
**Interfaces:**
- Consumes: immutable `PathSmoothingComparisonResult`, map, start/goal, scenario label.
- Produces: one immutable figure model, UTF-8 SVG, and UTF-8-BOM CSV.
- [ ] **Step 1: Write failing style and serialization tests**
Assert exact colors, line styles, panel labels, view box, equal XY scale, legend order, XML escaping, UTF-8 Chinese, BOM bytes `EF BB BF`, and invariant decimal points.
Include one rejected candidate in the model and assert its normal method-colored curve plus violation cross markers are present while its metrics row says `Infeasible`. Include one numerical failure and assert its legend/row remains but no fake path element is emitted.
- [ ] **Step 2: Run and confirm RED**
```powershell
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_svg_csv.ps1
```
Expected: missing `IeeeFigureStyle`.
- [ ] **Step 3: Implement one physical layout model**
Define all coordinates in typographic points (`72 pt/in`):
```csharp
public const double FigureWidthPoints = 7.16d * 72d;
public const double FigureHeightPoints = 5.20d * 72d;
public const string RawColor = "#4D4D4D";
public const string BSplineColor = "#0072B2";
public const string BezierColor = "#D55E00";
public const string QuinticColor = "#009E73";
public const string LimitColor = "#CC79A7";
```
Allocate 60% width to panel `(a)`, split the right side between `(b)` curvature and `(c)` metrics, and compute one world-to-panel transform with equal X/Y scale.
- [ ] **Step 4: Implement SVG and CSV**
SVG text must use explicit runs:
```xml
<text><tspan font-family="SimSun">车辆曲率 </tspan><tspan font-family="Times New Roman">κ (m⁻¹)</tspan></text>
```
CSV header order is fixed:
```text
ScenarioId,Method,Status,PathLengthMeters,MaximumAbsoluteVehicleCurvaturePerMeter,RootMeanSquareVehicleCurvaturePerMeter,TotalAbsoluteCurvatureVariationPerMeter,CurvatureVariationEnergy,MinimumBodyClearanceMeters,ElapsedMilliseconds,RetryCount,AcceptedStrength
```
- [ ] **Step 5: Run SVG/CSV and comparison checks**
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_svg_csv.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_comparison.ps1
```
Expected: all pass.
- [ ] **Step 6: Commit**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization ClumsyPilot/tests/verify_path_smoothing_svg_csv.ps1
git commit -m "feat: render ieee smoothing svg and metrics"
```
### Task 12: Windows font validation and 300 dpi PNG export
**Files:**
- Modify: `ClumsyPilot/ClumsyPilot.csproj`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Test/Visualization/ValidatedPngWriter.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFontResolver.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingPngRenderer.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingReportExportRequest.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingReportExportResult.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingReportExporter.cs`
- Test: `ClumsyPilot/tests/verify_path_smoothing_png.ps1`
**Interfaces:**
- Produces: atomic `.svg`, `.png`, `.csv` export; report failures do not mutate comparison results.
- Runtime boundary: `SmoothingPngRenderer` is Windows-only; SVG/CSV remain usable without GDI+.
- [ ] **Step 1: Add the failing PNG/font verification**
The script must assert:
```text
PNG signature and CRC-valid chunks
IHDR width=2148 and height=1560
pHYs X=11811 and Y=11811 pixels/meter
SimSun and Times New Roman were resolved by exact family name
mixed sample "粗路径 κ(s) X (m) −π" produced non-empty glyph bounds
missing-font test returns FontUnavailable
atomic export leaves no .tmp file
```
- [ ] **Step 2: Run and confirm RED**
```powershell
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_png.ps1
```
Expected: missing renderer and exporter.
- [ ] **Step 3: Add the exact Windows rendering dependency**
Add:
```xml
<PackageReference Include="System.Drawing.Common" Version="10.0.10" />
```
Then run:
```powershell
dotnet restore .\ClumsyPilot\ClumsyPilot.csproj
```
Expected: restore exits 0. Do not add cross-platform fallback switches; Microsoft documents `System.Drawing.Common` as Windows-only.
- [ ] **Step 4: Implement exact font resolution and mixed-run baseline layout**
Use `InstalledFontCollection` to require `SimSun` and `Times New Roman`. Split text by Unicode category/CJK ranges, measure each run with typographic `StringFormat`, and align runs by font-family ascent:
```csharp
float baseline = top + maxAscentPixels;
float runTop = baseline - family.GetCellAscent(style) * emPixels /
family.GetEmHeight(style);
```
Return `FontUnavailable` before creating output files if either exact family is absent.
- [ ] **Step 5: Render the shared model and write validated PNG**
Render at `2148 × 1560`, opaque white background, anti-aliased geometry, and no gradients/shadows. Convert bitmap pixels to RGBA and use the existing validated PNG path, extending it to insert a `pHYs` chunk with `11811` pixels/meter and correct CRC before `IDAT`.
Export all three files to temporary siblings, validate each, then rename into place. On failure, remove only those exact temporary siblings.
- [ ] **Step 6: Run PNG, map-image, and build checks**
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_png.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_image.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_svg_csv.ps1
```
Expected: all pass; existing map PNG export remains valid.
- [ ] **Step 7: Commit**
```powershell
git add -- ClumsyPilot/ClumsyPilot.csproj ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization ClumsyPilot/tests/verify_path_smoothing_png.ps1
git commit -m "feat: export ieee smoothing png reports"
```
### Task 13: Module documentation, report batch entry, and full verification
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/README.md`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/PathSmoothingComparisonDemo.cs`
- Create: `ClumsyPilot/tests/run_path_smoothing_comparison.ps1`
- Create: `ClumsyPilot/tests/verify_path_smoothing_documentation.ps1`
**Interfaces:**
- Produces: documented formal call example and a developer-only batch command that writes reports under `ClumsyPilot/obj/path_smoothing_reports`.
- [ ] **Step 1: Write failing documentation and batch-entry verification**
Assert README contains exact sections for units, facade usage, status handling, fallback, fixture freshness, IEEE colors/fonts, Windows PNG limitation, SQP boundary, and output files. Assert the batch runner offers `-FixtureOnly` and never writes under source directories.
- [ ] **Step 2: Run and confirm RED**
```powershell
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_documentation.ps1
```
Expected: missing README and demo.
- [ ] **Step 3: Write README and batch demo**
Document this minimal formal flow:
```csharp
if (coarseResult.PlanningResult.Status != PlanningStatus.Success)
return;
var smoothing = new PathSmoothingService().Smooth(
new PathSmoothingRequest(
coarseResult.PlanningResult.Path,
coarseResult.PlanningResult.Segments,
coarseResult.MapResult.Map,
job.Vehicle,
smoothingConfiguration),
cancellationToken);
if (smoothing.Status == PathSmoothingStatus.Success ||
smoothing.Status == PathSmoothingStatus.FallbackToCoarsePath)
ConsumeSpatialReference(smoothing.Path, smoothing.Segments);
```
The batch script builds once, runs eight fixtures by default, optionally runs the four end-to-end cases, prints one-line metrics per method, and writes only beneath `ClumsyPilot/obj/path_smoothing_reports`.
- [ ] **Step 4: Run the complete verification suite**
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_utils.ps1
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_image.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_path_smoothing_contracts.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_geometry.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_validation.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_runner.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_bspline.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_bezier.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_quintic.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_service.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_comparison.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_fixtures.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_integration.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_svg_csv.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_png.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_documentation.ps1
```
Expected: build exits 0 and every script prints its passed message with no terminating errors.
- [ ] **Step 5: Generate and inspect representative reports**
```powershell
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\run_path_smoothing_comparison.ps1 -FixtureOnly
```
Inspect `straight`, `rectangle-detour`, and `forward-reverse-switch` PNG/SVG files. Confirm four-method legend order, equal path axes, readable 810 pt text, no Chinese mojibake, no clipping/overlap, curvature limits, and infeasible markers where applicable.
- [ ] **Step 6: Commit**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/PathSmoothing/README.md ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/PathSmoothingComparisonDemo.cs ClumsyPilot/tests/run_path_smoothing_comparison.ps1 ClumsyPilot/tests/verify_path_smoothing_documentation.ps1
git commit -m "docs: document path smoothing comparison workflow"
```