559 lines
36 KiB
Markdown
559 lines
36 KiB
Markdown
# Local G2 Diagnostic Visualization 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:** Export a factual Local G2 visualization that shows the ordinary strict `Unchanged` result and the recorded clearance-rejected diagnostic candidate without publishing or recommending that candidate.
|
|
|
|
**Architecture:** The existing comparison service remains the source for normal Local G2 status and output. A test/demo-only evidence loader reads a compact immutable record of `single-turn/s0/r0/w5/seed2`, reconstructs a visual-only spliced path with the existing preprocessor/splicer/analyzer, and appends it to an immutable figure model. The existing SVG/PNG/CSV exporter then produces the six standard figures plus a seventh diagnostic figure only for that augmented model.
|
|
|
|
**Tech Stack:** C# 10 targeting `netstandard2.0`, Newtonsoft.Json 13.0.4 already referenced by `ClumsyPilot.csproj`, existing `System.Drawing` PNG renderer, PowerShell verification scripts, .NET 10 Windows verification host.
|
|
|
|
## Global Constraints
|
|
|
|
- Do not modify `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/`, evaluator, validator, publication, or recommendation logic.
|
|
- The normal comparison must retain the actual Local G2 `PathSmoothingStatus`; a diagnostic candidate must never create a `PathSmoothingResult` or a recommendation.
|
|
- Evidence is fixed to fixture SHA-256 `3d05daee5a211b3e7aa0b77193423b5fa07d3135e241a4413be3518fc7efe563`, batch SHA-256 `ac8166828813d85bf6f8b58f985839e5b2a049e75cb94186ed04f59d540e4eed`, and stable key `single-turn/s0/r0/w5/seed2`.
|
|
- Preserve normal-export file stems `01-coarse-path-overview` through `06-curvature-comparison` and its six-file contract.
|
|
- The diagnostic candidate must be labelled `净空拒绝,未发布`; do not render a collision cross because the evidence records a clearance rejection, not an occupied-cell collision.
|
|
- Keep trajectories point-only. Do not add SVG paths or dashed stroke rendering.
|
|
- Place generated artifacts only below `ClumsyPilot/obj/path_smoothing_reports`.
|
|
- Use targeted `git add -- <paths>` and `git commit --only -- <paths>`; do not include unrelated worktree changes.
|
|
|
|
---
|
|
|
|
## File Structure
|
|
|
|
| Path | Responsibility |
|
|
| --- | --- |
|
|
| `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Comparison/PathSmoothingComparisonRequest.cs` | Adds normal Local G2 to the immutable default offline comparison order. |
|
|
| `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/IeeeFigureStyle.cs` | Holds normal Local G2 and diagnostic-candidate colors. |
|
|
| `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureModelBuilder.cs` | Adds normal Local G2 series and CSV metric row. |
|
|
| `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureModel.cs` | Creates an immutable model copy with one added diagnostic series. |
|
|
| `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureDefinition.cs` | Moves a five-entry legend upward enough to remain inside the fixed figure height. |
|
|
| `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureKind.cs` | Names the optional seventh figure. |
|
|
| `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureSetBuilder.cs` | Adds Local G2 to normal comparison figures and conditionally creates figure 07. |
|
|
| `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/Fixtures/local-g2-diagnostic-single-turn.json` | Immutable compact evidence extract used by the visual-only route. |
|
|
| `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/LocalG2DiagnosticEvidenceLoader.cs` | Parses and verifies evidence identity, geometry, and rejection state. |
|
|
| `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/LocalG2DiagnosticVisualizationDemo.cs` | Reconstructs renderable candidate geometry and invokes the existing exporter. |
|
|
| `ClumsyPilot/tests/PathSmoothingPngVerificationHost/Program.cs` | Verifies and exports the augmented seven-file report. |
|
|
| `ClumsyPilot/tests/verify_path_smoothing_comparison.ps1` | Checks Local G2 default order and normal comparison semantics. |
|
|
| `ClumsyPilot/tests/verify_path_smoothing_svg_csv.ps1` | Checks normal Local G2 figure/CSV content and visual layout contracts. |
|
|
| `ClumsyPilot/tests/verify_path_smoothing_local_g2_diagnostic_evidence.ps1` | Checks evidence parsing and deterministic rejection validation. |
|
|
| `ClumsyPilot/tests/verify_path_smoothing_local_g2_diagnostic_visualization.ps1` | Calls the verification host for the seven-file diagnostic report. |
|
|
| `ClumsyPilot/tests/run_local_g2_diagnostic_visualization.ps1` | Builds and exports the user-facing Local G2 diagnostic image. |
|
|
|
|
## Task 1: Add Normal Local G2 To Existing Comparison Reports
|
|
|
|
**Files:**
|
|
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Comparison/PathSmoothingComparisonRequest.cs`
|
|
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/IeeeFigureStyle.cs`
|
|
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureModelBuilder.cs`
|
|
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureDefinition.cs`
|
|
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureSetBuilder.cs`
|
|
- Modify: `ClumsyPilot/tests/verify_path_smoothing_comparison.ps1`
|
|
- Modify: `ClumsyPilot/tests/verify_path_smoothing_svg_csv.ps1`
|
|
- Modify: `ClumsyPilot/tests/PathSmoothingPngVerificationHost/Program.cs`
|
|
|
|
**Interfaces:**
|
|
- Consumes: `PathSmoothingComparisonRequest(PathSmoothingRequest, IReadOnlyList<SmoothingMethod> methods = null)` and `SmoothingFigureModelBuilder.Build(PathSmoothingComparisonResult, PlanningGridMap, Pose2D, Pose2D, string, string)`.
|
|
- Produces: A default method order of `CubicBSpline`, `LocalCubicBezier`, `PiecewiseQuintic`, `LocalG2Quintic`; figure key `local-g2`; CSV method `LocalG2Quintic`; unchanged standard six-figure export count.
|
|
|
|
- [ ] **Step 1: Write the failing default-order and standard-model assertions**
|
|
|
|
In `verify_path_smoothing_comparison.ps1`, parse the Local G2 enum and construct a request without the optional methods list. Add these assertions after the explicit three-method request assertions:
|
|
|
|
```powershell
|
|
$localG2 = [Enum]::Parse($methodType, 'LocalG2Quintic')
|
|
$defaultComparisonRequest = [Activator]::CreateInstance($comparisonRequestType, @((New-SmoothingRequest), $null))
|
|
Assert-Equal 4 $defaultComparisonRequest.Methods.Count 'Default comparison must include Local G2.'
|
|
Assert-Equal 'CubicBSpline,LocalCubicBezier,PiecewiseQuintic,LocalG2Quintic' (($defaultComparisonRequest.Methods | ForEach-Object ToString) -join ',') 'Default comparison order must be stable.'
|
|
```
|
|
|
|
In `verify_path_smoothing_svg_csv.ps1`, add `#56B4E9` to the expected normal SVG colors, assert that `$model.Series` includes a series whose `Key` is `local-g2`, and assert that the generated CSV contains `LocalG2Quintic,Unchanged` for the frozen `single-turn` request. Add assertions that the standard figure set still contains only the six existing stems.
|
|
|
|
In `Program.cs`, add a `VerifyNormalLocalG2Series(SmoothingFigureModel model)` call immediately after `VerifySixFigureDefinitionContract(model)`. It must require exactly one `local-g2` series and a `PathSmoothingStatus` value defined by the enum. If its strict output path is visible, require that it has at least two samples. Do not hard-code `Unchanged` for this artificial high-curvature host fixture; the frozen `single-turn` evidence assertion in the SVG/CSV test owns that requirement.
|
|
|
|
- [ ] **Step 2: Run the focused checks and confirm they fail before implementation**
|
|
|
|
Run:
|
|
|
|
```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_svg_csv.ps1
|
|
```
|
|
|
|
Expected: the comparison assertion reports three default methods and the SVG/CSV check cannot find `local-g2` or `#56B4E9`.
|
|
|
|
- [ ] **Step 3: Implement the smallest normal-comparison extension**
|
|
|
|
Append the enum in the existing default array, preserving all existing order:
|
|
|
|
```csharp
|
|
private static readonly SmoothingMethod[] DefaultMethods =
|
|
{
|
|
SmoothingMethod.CubicBSpline,
|
|
SmoothingMethod.LocalCubicBezier,
|
|
SmoothingMethod.PiecewiseQuintic,
|
|
SmoothingMethod.LocalG2Quintic,
|
|
};
|
|
```
|
|
|
|
Add these fixed colors to `IeeeFigureStyle`:
|
|
|
|
```csharp
|
|
public const string LocalG2Color = "#56B4E9";
|
|
public const string LocalG2DiagnosticColor = "#B1373E";
|
|
```
|
|
|
|
In `SmoothingFigureModelBuilder`, append the normal series and metric row after the existing piecewise-quintic entries. Use the exact stable key and label:
|
|
|
|
```csharp
|
|
series.Add(CreateSeries(
|
|
Find(comparison, SmoothingMethod.LocalG2Quintic),
|
|
SmoothingMethod.LocalG2Quintic,
|
|
"local-g2",
|
|
"局部 G2",
|
|
IeeeFigureStyle.LocalG2Color,
|
|
string.Empty,
|
|
false,
|
|
map));
|
|
|
|
CreateRow(Find(comparison, SmoothingMethod.LocalG2Quintic), "LocalG2Quintic", "局部 G2")
|
|
```
|
|
|
|
In `SmoothingFigureSetBuilder.Build`, resolve `local-g2` with the other standard series and include it in only the existing all-path and curvature comparisons:
|
|
|
|
```csharp
|
|
SmoothingFigureSeries localG2 = Find(model, "local-g2");
|
|
// Add View(localG2, 1d) after View(quintic, 1d) in figures 02 and 06.
|
|
```
|
|
|
|
Keep individual figures `03` through `05` unchanged. In `SmoothingFigureDefinition`, use a five-entry-safe legend origin:
|
|
|
|
```csharp
|
|
public double LegendYPoints => LegendEntries.Count > 4 ? 332d : 340d;
|
|
```
|
|
|
|
Do not special-case `Unchanged`: `PathSmoothingComparisonService` already supplies its strict path and status. Do not modify that service or the ranker.
|
|
|
|
- [ ] **Step 4: Run the focused checks and confirm they pass**
|
|
|
|
Run:
|
|
|
|
```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_svg_csv.ps1
|
|
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_png.ps1
|
|
```
|
|
|
|
Expected: all three scripts exit `0`; normal exports retain exactly six figures; the Local G2 row is present with its actual `Unchanged` status.
|
|
|
|
- [ ] **Step 5: Commit only Task 1 files**
|
|
|
|
```powershell
|
|
git add -- `
|
|
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Comparison/PathSmoothingComparisonRequest.cs `
|
|
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/IeeeFigureStyle.cs `
|
|
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureModelBuilder.cs `
|
|
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureDefinition.cs `
|
|
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureSetBuilder.cs `
|
|
ClumsyPilot/tests/verify_path_smoothing_comparison.ps1 `
|
|
ClumsyPilot/tests/verify_path_smoothing_svg_csv.ps1 `
|
|
ClumsyPilot/tests/PathSmoothingPngVerificationHost/Program.cs
|
|
git -c core.autocrlf=false diff --cached --check
|
|
git commit --only -m "feat: show Local G2 in smoothing comparisons" -- `
|
|
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Comparison/PathSmoothingComparisonRequest.cs `
|
|
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/IeeeFigureStyle.cs `
|
|
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureModelBuilder.cs `
|
|
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureDefinition.cs `
|
|
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureSetBuilder.cs `
|
|
ClumsyPilot/tests/verify_path_smoothing_comparison.ps1 `
|
|
ClumsyPilot/tests/verify_path_smoothing_svg_csv.ps1 `
|
|
ClumsyPilot/tests/PathSmoothingPngVerificationHost/Program.cs
|
|
```
|
|
|
|
### Task 2: Freeze And Validate The Diagnostic Evidence Extract
|
|
|
|
**Files:**
|
|
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/Fixtures/local-g2-diagnostic-single-turn.json`
|
|
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/LocalG2DiagnosticEvidenceLoader.cs`
|
|
- Create: `ClumsyPilot/tests/verify_path_smoothing_local_g2_diagnostic_evidence.ps1`
|
|
|
|
**Interfaces:**
|
|
- Produces: `public sealed class LocalG2DiagnosticEvidenceLoader` with `public LocalG2DiagnosticEvidence LoadAndVerify(string evidencePath)`.
|
|
- Produces: `LocalG2DiagnosticEvidence` properties `ScenarioId`, `FixtureSha256`, `CandidateStableKey`, `CandidateSha256`, `CandidateIndex`, `SegmentIndex`, `WindowStartArcLengthMeters`, `WindowEndArcLengthMeters`, start/end curvatures, `CandidatePoints`, `EvaluatorResult`, `StopGate`, and `PublishedStatus`.
|
|
- Consumed later by: `LocalG2DiagnosticVisualizationDemo.Export(string fixturePath, string evidencePath, string outputDirectory, CancellationToken cancellationToken = default)`.
|
|
|
|
- [ ] **Step 1: Write the failing evidence-loader verification script**
|
|
|
|
Create `verify_path_smoothing_local_g2_diagnostic_evidence.ps1`. Load `ClumsyPilot.dll`, resolve `MultiWheelC.TrajectoryPlanning.PathSmoothing.Test.LocalG2DiagnosticEvidenceLoader`, and invoke `LoadAndVerify` with the new fixture path. Assert all of these exact values:
|
|
|
|
```powershell
|
|
Assert-Equal 'single-turn' $evidence.ScenarioId 'Diagnostic evidence scenario must be stable.'
|
|
Assert-Equal 'single-turn/s0/r0/w5/seed2' $evidence.CandidateStableKey 'Diagnostic evidence key must be stable.'
|
|
Assert-Equal 5 $evidence.CandidateIndex 'Diagnostic evidence candidate index must be stable.'
|
|
Assert-Equal 10 $evidence.CandidatePoints.Count 'Diagnostic evidence must retain all ten recorded samples.'
|
|
Assert-Equal 'InsufficientClearance' $evidence.EvaluatorResult 'Diagnostic evidence must retain the observed evaluator result.'
|
|
Assert-Equal 'Clearance' $evidence.StopGate 'Diagnostic evidence must retain the observed stop gate.'
|
|
Assert-Equal 'Unchanged' $evidence.PublishedStatus 'Diagnostic evidence must retain the strict final status.'
|
|
```
|
|
|
|
Copy the JSON to a uniquely named temp path, replace only `"StopGate": "Clearance"` with `"StopGate": "Collision"`, and require that `LoadAndVerify` throws. Delete the temp copy in `finally`.
|
|
|
|
- [ ] **Step 2: Run the evidence check and confirm it fails before implementation**
|
|
|
|
Run:
|
|
|
|
```powershell
|
|
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
|
|
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_local_g2_diagnostic_evidence.ps1
|
|
```
|
|
|
|
Expected: failure because the loader type and evidence file do not exist.
|
|
|
|
- [ ] **Step 3: Add the immutable compact evidence extract and loader**
|
|
|
|
Create the JSON file with this exact top-level contract and the ten recorded `CandidatePoints`. Preserve the displayed IEEE-754 decimal values; they are the evidence values, not rounded drawing inputs:
|
|
|
|
```json
|
|
{
|
|
"SourceMeasurementBatchSha256": "ac8166828813d85bf6f8b58f985839e5b2a049e75cb94186ed04f59d540e4eed",
|
|
"FixtureSha256": "3d05daee5a211b3e7aa0b77193423b5fa07d3135e241a4413be3518fc7efe563",
|
|
"ScenarioId": "single-turn",
|
|
"CandidateStableKey": "single-turn/s0/r0/w5/seed2",
|
|
"CandidateSha256": "7cefb76c77e48a49bf3212e7a2472e23036c3899daa94b5c6b68fa5db29a3392",
|
|
"CandidateIndex": 5,
|
|
"SegmentIndex": 0,
|
|
"WindowStartArcLengthMeters": 1.9199999999999982,
|
|
"WindowEndArcLengthMeters": 2.1199999999999983,
|
|
"StartGeometricCurvaturePerMeter": 0.41666666666666663,
|
|
"EndGeometricCurvaturePerMeter": 0.0,
|
|
"StartVehicleCurvaturePerMeter": 0.41666666666666663,
|
|
"EndVehicleCurvaturePerMeter": 0.0,
|
|
"EvaluatorResult": "InsufficientClearance",
|
|
"StopGate": "Clearance",
|
|
"PublishedStatus": "Unchanged",
|
|
"CandidatePoints": [
|
|
{ "X": 2.7216397035494579, "Y": 1.7279184433567971, "ReferenceArcLengthMeters": 1.9199999999999982, "HeadingRadians": 0.799999999999999, "UnwrappedHeadingRadians": 0.799999999999999, "Source": 4 },
|
|
{ "X": 2.7355188205174521, "Y": 1.7423187041196302, "ReferenceArcLengthMeters": 1.9399999999999982, "HeadingRadians": 0.80751185024996908, "UnwrappedHeadingRadians": 0.80751185024996908, "Source": 4 },
|
|
{ "X": 2.7492871654421882, "Y": 1.7568248978050907, "ReferenceArcLengthMeters": 1.9599999999999982, "HeadingRadians": 0.8157618125921976, "UnwrappedHeadingRadians": 0.8157618125921976, "Source": 4 },
|
|
{ "X": 2.7629234692741469, "Y": 1.7714552535786874, "ReferenceArcLengthMeters": 1.9799999999999982, "HeadingRadians": 0.82542852165703573, "UnwrappedHeadingRadians": 0.82542852165703573, "Source": 4 },
|
|
{ "X": 2.7764244476704873, "Y": 1.786210614200662, "ReferenceArcLengthMeters": 1.9999999999999982, "HeadingRadians": 0.83333333333333237, "UnwrappedHeadingRadians": 0.83333333333333237, "Source": 4 },
|
|
{ "X": 2.7925350453990334, "Y": 1.803999635249544, "ReferenceArcLengthMeters": 2.0239999999999982, "HeadingRadians": 0.835253334801853, "UnwrappedHeadingRadians": 0.835253334801853, "Source": 4 },
|
|
{ "X": 2.80865418003672, "Y": 1.8217809211927092, "ReferenceArcLengthMeters": 2.0479999999999983, "HeadingRadians": 0.83333333355178374, "UnwrappedHeadingRadians": 0.83333333355178374, "Source": 4 },
|
|
{ "X": 2.8248074262445737, "Y": 1.8395312269498194, "ReferenceArcLengthMeters": 2.0719999999999983, "HeadingRadians": 0.8318933325232104, "UnwrappedHeadingRadians": 0.8318933325232104, "Source": 4 },
|
|
{ "X": 2.8409691920174533, "Y": 1.8572737784943611, "ReferenceArcLengthMeters": 2.0959999999999983, "HeadingRadians": 0.83237333274470626, "UnwrappedHeadingRadians": 0.83237333274470626, "Source": 4 },
|
|
{ "X": 2.8571139169604551, "Y": 1.8750318365841865, "ReferenceArcLengthMeters": 2.1199999999999983, "HeadingRadians": 0.83333333333333237, "UnwrappedHeadingRadians": 0.83333333333333237, "Source": 4 }
|
|
]
|
|
}
|
|
```
|
|
|
|
Implement the public loader in the test namespace using `Newtonsoft.Json.JsonConvert.DeserializeObject<LocalG2DiagnosticEvidence>(File.ReadAllText(evidencePath))`. Use `InvalidDataException` for every rejected input. The validation must require the three exact SHA/key constants, `ScenarioId == "single-turn"`, `CandidateIndex == 5`, `SegmentIndex == 0`, `EvaluatorResult == "InsufficientClearance"`, `StopGate == "Clearance"`, and `PublishedStatus == "Unchanged"`.
|
|
|
|
Validate the two window values and all point coordinates/headings/reference arc lengths with this helper:
|
|
|
|
```csharp
|
|
private static bool IsFinite(double value) => !double.IsNaN(value) && !double.IsInfinity(value);
|
|
```
|
|
|
|
Require exactly ten points, first/last reference arcs equal the window endpoints within `1e-8d`, strictly increasing reference arcs, `Source == (int)SmoothedPathPointSource.LocalG2Transition`, and non-null start/end curvature values. Return only after all checks pass.
|
|
|
|
- [ ] **Step 4: Run the evidence verification and confirm it passes**
|
|
|
|
Run:
|
|
|
|
```powershell
|
|
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
|
|
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_local_g2_diagnostic_evidence.ps1
|
|
```
|
|
|
|
Expected: exit `0`; the valid extract loads and the altered stop gate is rejected.
|
|
|
|
- [ ] **Step 5: Commit only Task 2 files**
|
|
|
|
```powershell
|
|
git add -- `
|
|
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/Fixtures/local-g2-diagnostic-single-turn.json `
|
|
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/LocalG2DiagnosticEvidenceLoader.cs `
|
|
ClumsyPilot/tests/verify_path_smoothing_local_g2_diagnostic_evidence.ps1
|
|
git -c core.autocrlf=false diff --cached --check
|
|
git commit --only -m "test: freeze Local G2 diagnostic evidence" -- `
|
|
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/Fixtures/local-g2-diagnostic-single-turn.json `
|
|
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/LocalG2DiagnosticEvidenceLoader.cs `
|
|
ClumsyPilot/tests/verify_path_smoothing_local_g2_diagnostic_evidence.ps1
|
|
```
|
|
|
|
### Task 3: Build The Visual-Only Candidate And Optional Figure 07
|
|
|
|
**Files:**
|
|
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureModel.cs`
|
|
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureKind.cs`
|
|
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureSetBuilder.cs`
|
|
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/LocalG2DiagnosticVisualizationDemo.cs`
|
|
- Modify: `ClumsyPilot/tests/PathSmoothingPngVerificationHost/Program.cs`
|
|
- Create: `ClumsyPilot/tests/verify_path_smoothing_local_g2_diagnostic_visualization.ps1`
|
|
|
|
**Interfaces:**
|
|
- Produces: `internal SmoothingFigureModel WithAdditionalSeries(SmoothingFigureSeries series)` that preserves all existing model layout, metric rows, endpoints, and scales.
|
|
- Produces: `SmoothingFigureKind.LocalG2DiagnosticCandidate` and optional stem `07-local-g2-diagnostic-candidate`.
|
|
- Produces: `public SmoothingReportExportResult LocalG2DiagnosticVisualizationDemo.Export(string fixturePath, string evidencePath, string outputDirectory, CancellationToken cancellationToken = default)`.
|
|
- Produces: a host verification command that accepts a fixture path and evidence path, plus an export command that accepts fixture, evidence, and output-directory paths.
|
|
|
|
- [ ] **Step 1: Write the failing seven-file verification host branch and wrapper script**
|
|
|
|
In `Program.Main`, add command dispatch before the existing fixture/export cases:
|
|
|
|
```csharp
|
|
if (arguments.Length == 3 && arguments[0] == "--verify-local-g2-diagnostic")
|
|
{
|
|
VerifyLocalG2Diagnostic(arguments[1], arguments[2]);
|
|
Console.WriteLine("Local G2 diagnostic visualization verification completed.");
|
|
return 0;
|
|
}
|
|
if (arguments.Length == 4 && arguments[0] == "--export-local-g2-diagnostic")
|
|
{
|
|
ExportLocalG2Diagnostic(arguments[1], arguments[2], arguments[3]);
|
|
return 0;
|
|
}
|
|
```
|
|
|
|
Create the wrapper script to resolve the fixture and evidence paths and run this command:
|
|
|
|
```powershell
|
|
& dotnet run --project $hostProject --no-restore -- --verify-local-g2-diagnostic $resolvedFixturePath $resolvedEvidencePath
|
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
```
|
|
|
|
Run it before the demo/model implementation. Expected: build error because `VerifyLocalG2Diagnostic` and `ExportLocalG2Diagnostic` do not yet exist.
|
|
|
|
- [ ] **Step 2: Add immutable model augmentation and the optional figure definition**
|
|
|
|
Add this internal method to `SmoothingFigureModel`:
|
|
|
|
```csharp
|
|
internal SmoothingFigureModel WithAdditionalSeries(SmoothingFigureSeries series)
|
|
{
|
|
if (series == null) throw new ArgumentNullException(nameof(series));
|
|
var combined = new List<SmoothingFigureSeries>(Series.Count + 1);
|
|
for (int index = 0; index < Series.Count; index++)
|
|
{
|
|
if (Series[index].Key == series.Key)
|
|
throw new ArgumentException("Figure series keys must be unique.", nameof(series));
|
|
combined.Add(Series[index]);
|
|
}
|
|
combined.Add(series);
|
|
var copy = new SmoothingFigureModel(
|
|
ScenarioId, ScenarioLabel, WorldXMinMeters, WorldXMaxMeters, WorldYMinMeters, WorldYMaxMeters,
|
|
PathPanelX, PathPanelY, PathPanelWidth, PathPanelHeight,
|
|
CurvaturePanelX, CurvaturePanelY, CurvaturePanelWidth, CurvaturePanelHeight,
|
|
MetricsPanelX, MetricsPanelY, MetricsPanelWidth, MetricsPanelHeight,
|
|
Obstacles, combined, MetricRows, Start, Goal)
|
|
{
|
|
PathScaleX = PathScaleX,
|
|
PathScaleY = PathScaleY,
|
|
};
|
|
return copy;
|
|
}
|
|
```
|
|
|
|
Add `LocalG2DiagnosticCandidate` to `SmoothingFigureKind`. In `SmoothingFigureSetBuilder.Build`, retain the six normal definitions first. Use a non-throwing `TryFind` helper for key `local-g2-diagnostic`; when it succeeds, append exactly this overhead figure:
|
|
|
|
```csharp
|
|
BuildOverhead(
|
|
SmoothingFigureKind.LocalG2DiagnosticCandidate,
|
|
"07-local-g2-diagnostic-candidate",
|
|
"G2 诊断候选:净空拒绝,未发布;严格输出=原始路径",
|
|
model,
|
|
true,
|
|
View(raw, 0.45d),
|
|
View(diagnostic, 1d))
|
|
```
|
|
|
|
The diagnostic series must carry key `local-g2-diagnostic`, status `PathSmoothingStatus.Infeasible`, color `IeeeFigureStyle.LocalG2DiagnosticColor`, and an empty `ViolationMarkers` list. Do not reuse `SmoothingFigureModelBuilder.CreateSeries`, because its generic `Infeasible` behavior synthesizes a violation cross when it cannot identify an occupied point.
|
|
|
|
- [ ] **Step 3: Implement `LocalG2DiagnosticVisualizationDemo` with the existing geometry components**
|
|
|
|
The class stays in namespace `MultiWheelC.TrajectoryPlanning.PathSmoothing.Test` and creates no `PathSmoothingResult`. Its `Export` method must execute this exact sequence:
|
|
|
|
```csharp
|
|
LocalG2DiagnosticEvidence evidence = _evidenceLoader.LoadAndVerify(evidencePath);
|
|
PathSmoothingComparisonRequest request = FindFixtureRequest(fixturePath, evidence.ScenarioId);
|
|
PathSmoothingComparisonResult comparison = _comparisonService.Compare(request, cancellationToken);
|
|
PathSmoothingComparisonEntry localG2 = FindEntry(comparison, SmoothingMethod.LocalG2Quintic);
|
|
Require(localG2 != null && localG2.Status == PathSmoothingStatus.Unchanged,
|
|
"Strict Local G2 result must be Unchanged for the diagnostic evidence.");
|
|
RequireSameGeometry(comparison.RawPathBaseline.Path, localG2.Path);
|
|
_preprocessor.TryPrepare(request.SmoothingRequest, out PreparedPath prepared, out string reason);
|
|
LocalG2CandidateGeometry candidate = CreateCandidate(evidence);
|
|
_splicer.TryReplace(prepared, candidate, out PreparedPath spliced, out reason);
|
|
_analyzer.TryAnalyze(spliced.Segments, request.SmoothingRequest.Configuration.OutputSpacingMeters, out PathGeometryAnalysis analysis, out reason);
|
|
CoarsePathPoint first = request.SmoothingRequest.CoarsePath[0];
|
|
CoarsePathPoint last = request.SmoothingRequest.CoarsePath[request.SmoothingRequest.CoarsePath.Count - 1];
|
|
SmoothingFigureModel normal = _figureBuilder.Build(
|
|
comparison,
|
|
request.SmoothingRequest.Map,
|
|
new Pose2D(first.X, first.Y, first.Heading),
|
|
new Pose2D(last.X, last.Y, last.Heading),
|
|
evidence.ScenarioId,
|
|
evidence.ScenarioId);
|
|
SmoothingFigureModel augmented = normal.WithAdditionalSeries(CreateDiagnosticSeries(analysis.Path));
|
|
return _exporter.Export(new SmoothingReportExportRequest { Model = augmented, OutputDirectory = outputDirectory, FileStem = "comparison" });
|
|
```
|
|
|
|
`FindFixtureRequest` must call `SmoothingScenarioFactory.CreateFixtureRequests(fixturePath)`, locate exactly one request by the matching fixture index from `SmoothingScenarioFixtureLoader.LoadAndVerify(fixturePath)`, and reject missing or duplicate `single-turn` IDs. `CreateCandidate` must turn every evidence point into:
|
|
|
|
```csharp
|
|
new SmoothingPoint2D(
|
|
point.X, point.Y, point.ReferenceArcLengthMeters,
|
|
point.HeadingRadians, point.UnwrappedHeadingRadians,
|
|
0d, false, SmoothedPathPointSource.LocalG2Transition)
|
|
```
|
|
|
|
Construct `LocalG2CandidateGeometry` with evidence index/window/curvatures, `0d` left and right lengths, the converted point list, and `true` for `internalConnectionsAreG2`. The zero clearance exists only to satisfy geometry-analysis input validity; it must not be fed to a validator or a metric row.
|
|
|
|
`CreateDiagnosticSeries` must convert `analysis.Path` to `SmoothingFigurePoint` values, use label `G2 候选(净空拒绝,未发布)`, and supply `Array.Empty<SmoothingFigurePoint>()` as violation markers. Its path can be visualized but is not a published/safe path.
|
|
|
|
- [ ] **Step 4: Implement host verification and run it**
|
|
|
|
`VerifyLocalG2Diagnostic` must create a fresh temp output directory, invoke the demo, and require all of the following before deleting the directory in `finally`:
|
|
|
|
```csharp
|
|
Require(report.Status == SmoothingReportExportStatus.Success, "Diagnostic report export failed: " + report.Reason);
|
|
Require(report.SvgPaths.Count == 7 && report.PngPaths.Count == 7 && File.Exists(report.CsvPath), "Diagnostic export must publish seven SVGs, seven PNGs and one CSV.");
|
|
Require(Path.GetFileName(report.SvgPaths[6]) == "07-local-g2-diagnostic-candidate.svg", "Diagnostic SVG stem is incorrect.");
|
|
Require(Path.GetFileName(report.PngPaths[6]) == "07-local-g2-diagnostic-candidate.png", "Diagnostic PNG stem is incorrect.");
|
|
string diagnosticSvg = File.ReadAllText(report.SvgPaths[6]);
|
|
Require(diagnosticSvg.Contains("data-series=\"raw\"") && diagnosticSvg.Contains("data-series=\"local-g2-diagnostic\""), "Diagnostic SVG must contain raw and diagnostic samples.");
|
|
Require(diagnosticSvg.Contains("净空拒绝") && diagnosticSvg.Contains("未发布"), "Diagnostic SVG must disclose rejection and publication state.");
|
|
Require(!diagnosticSvg.Contains("violation-cross"), "Clearance rejection must not be drawn as an obstacle collision.");
|
|
Require(File.ReadAllText(report.CsvPath).Contains("LocalG2Quintic,Unchanged"), "CSV must retain the normal strict Local G2 row.");
|
|
```
|
|
|
|
Run `VerifyPng(File.ReadAllBytes(path))` for every returned PNG and use the existing `ContainsTemporaryFiles` helper to assert atomic publication. Also copy the evidence file to a temp file, alter the fixture hash, call the demo with a separate empty output path, require an exception, and require that the output directory was never created.
|
|
|
|
Run:
|
|
|
|
```powershell
|
|
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
|
|
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_local_g2_diagnostic_visualization.ps1
|
|
```
|
|
|
|
Expected: exit `0`, seven valid figures, raw and diagnostic series in `07`, no fabricated collision cross, and bad evidence rejected atomically.
|
|
|
|
- [ ] **Step 5: Commit only Task 3 files**
|
|
|
|
```powershell
|
|
git add -- `
|
|
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureModel.cs `
|
|
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureKind.cs `
|
|
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureSetBuilder.cs `
|
|
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/LocalG2DiagnosticVisualizationDemo.cs `
|
|
ClumsyPilot/tests/PathSmoothingPngVerificationHost/Program.cs `
|
|
ClumsyPilot/tests/verify_path_smoothing_local_g2_diagnostic_visualization.ps1
|
|
git -c core.autocrlf=false diff --cached --check
|
|
git commit --only -m "feat: export Local G2 diagnostic candidate" -- `
|
|
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureModel.cs `
|
|
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureKind.cs `
|
|
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureSetBuilder.cs `
|
|
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/LocalG2DiagnosticVisualizationDemo.cs `
|
|
ClumsyPilot/tests/PathSmoothingPngVerificationHost/Program.cs `
|
|
ClumsyPilot/tests/verify_path_smoothing_local_g2_diagnostic_visualization.ps1
|
|
```
|
|
|
|
### Task 4: Add The User-Facing Export Script And Perform Full Acceptance
|
|
|
|
**Files:**
|
|
- Create: `ClumsyPilot/tests/run_local_g2_diagnostic_visualization.ps1`
|
|
- Modify: `ClumsyPilot/tests/verify_path_smoothing_png.ps1`
|
|
|
|
**Interfaces:**
|
|
- Consumes: the diagnostic host export command, its fixture path, its evidence path, and its bounded output directory.
|
|
- Produces: `ClumsyPilot/obj/path_smoothing_reports/local-g2-single-turn/07-local-g2-diagnostic-candidate.png` and six companion standard figures.
|
|
|
|
- [ ] **Step 1: Write the failing runner assertion in the PNG smoke test**
|
|
|
|
In `verify_path_smoothing_png.ps1`, add a call to the not-yet-created runner with an explicit output directory below `obj/path_smoothing_reports`, followed by an existence assertion for its primary PNG. Its default runner paths must be:
|
|
|
|
```powershell
|
|
[string]$FixturePath = (Join-Path $PSScriptRoot '..\ParkrobTrajplanner\PathSmoothing\Test\Fixtures\path-smoothing-fixtures.json'),
|
|
[string]$EvidencePath = (Join-Path $PSScriptRoot '..\ParkrobTrajplanner\PathSmoothing\Test\Fixtures\local-g2-diagnostic-single-turn.json'),
|
|
[string]$OutputDirectory = (Join-Path $PSScriptRoot '..\obj\path_smoothing_reports\local-g2-single-turn')
|
|
```
|
|
|
|
Add this smoke-test block after the existing host invocation, changing no unrelated test behavior:
|
|
|
|
```powershell
|
|
$runnerPath = Join-Path $PSScriptRoot 'run_local_g2_diagnostic_visualization.ps1'
|
|
$runnerOutput = Join-Path $PSScriptRoot '..\obj\path_smoothing_reports\local-g2-png-smoke'
|
|
& powershell -ExecutionPolicy Bypass -File $runnerPath -OutputDirectory $runnerOutput
|
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
$primaryPng = Join-Path $runnerOutput '07-local-g2-diagnostic-candidate.png'
|
|
if (-not (Test-Path -LiteralPath $primaryPng)) {
|
|
throw "Local G2 diagnostic runner did not publish $primaryPng"
|
|
}
|
|
```
|
|
|
|
Run the PNG smoke test. Expected: PowerShell reports that `run_local_g2_diagnostic_visualization.ps1` does not exist, so the runner acceptance assertion fails before implementation.
|
|
|
|
- [ ] **Step 2: Implement the bounded export script and extend the PNG smoke test**
|
|
|
|
Follow the existing `run_path_smoothing_comparison.ps1` root validation exactly: resolve `$clumsyPilotRoot`, require `$OutputDirectory` to equal or be below `$clumsyPilotRoot\obj\path_smoothing_reports`, resolve both input files, build `ClumsyPilot.csproj`, then execute:
|
|
|
|
```powershell
|
|
& dotnet run --project $hostProject --no-restore -- --export-local-g2-diagnostic $resolvedFixturePath $resolvedEvidencePath $resolvedOutputDirectory
|
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
Write-Output "Local G2 diagnostic visualization written below $resolvedOutputDirectory"
|
|
```
|
|
|
|
In `verify_path_smoothing_png.ps1`, call the new diagnostic verification wrapper after the existing host verification so both standard six-file and diagnostic seven-file image contracts run in the normal PNG check. Retain the runner call and primary-PNG existence assertion added in Step 1; use a `local-g2-png-smoke` output subdirectory below the allowed report root.
|
|
|
|
- [ ] **Step 3: Run the full automated acceptance sequence**
|
|
|
|
Run these commands in order and inspect every exit code:
|
|
|
|
```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_svg_csv.ps1
|
|
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_local_g2_diagnostic_evidence.ps1
|
|
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_local_g2_diagnostic_visualization.ps1
|
|
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_png.ps1
|
|
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\run_local_g2_diagnostic_visualization.ps1
|
|
```
|
|
|
|
Expected: every command exits `0`; the primary PNG, matching SVG, and `comparison.csv` exist under `ClumsyPilot/obj/path_smoothing_reports/local-g2-single-turn`.
|
|
|
|
- [ ] **Step 4: Inspect the generated image and report the factual result**
|
|
|
|
Open the primary file with the local image viewer:
|
|
|
|
```text
|
|
ClumsyPilot/obj/path_smoothing_reports/local-g2-single-turn/07-local-g2-diagnostic-candidate.png
|
|
```
|
|
|
|
Confirm visually that the map is nonblank, the gray raw/final path and red diagnostic candidate are both visible, the legend/title disclose `净空拒绝` and `未发布`, there is no collision cross, and text remains inside the 4296-by-3120 PNG frame. Also open `02-all-paths-comparison.png` and `06-curvature-comparison.png` from the same directory to confirm that normal Local G2 appears with `Unchanged` alongside the existing methods.
|
|
|
|
- [ ] **Step 5: Commit only Task 4 source/test files**
|
|
|
|
Do not commit generated `obj` artifacts. Commit only the runner and PNG smoke-test changes:
|
|
|
|
```powershell
|
|
git add -- `
|
|
ClumsyPilot/tests/run_local_g2_diagnostic_visualization.ps1 `
|
|
ClumsyPilot/tests/verify_path_smoothing_png.ps1
|
|
git -c core.autocrlf=false diff --cached --check
|
|
git commit --only -m "test: add Local G2 diagnostic visualization runner" -- `
|
|
ClumsyPilot/tests/run_local_g2_diagnostic_visualization.ps1 `
|
|
ClumsyPilot/tests/verify_path_smoothing_png.ps1
|
|
```
|
|
|
|
## Final Verification Checklist
|
|
|
|
- [ ] Re-read [`2026-08-02-local-g2-diagnostic-visualization-design.md`](../specs/2026-08-02-local-g2-diagnostic-visualization-design.md) and map every acceptance criterion to a passing command or visual check above.
|
|
- [ ] Run `git -c core.autocrlf=false diff --check` only on the files changed by these tasks.
|
|
- [ ] Verify that no `PathSmoothing/LocalG2/` implementation, evaluator, validator, publishing, or ranking file changed.
|
|
- [ ] Verify each task commit contains only its listed paths.
|
|
- [ ] Report the primary image path and state plainly that the red curve is a rejected diagnostic candidate, while the strict Local G2 result remains `Unchanged`.
|