# Local G2 Split-Derivative-Scale Recovery 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:** Replace the infeasible Local G2 soft-position-anchor family with a bounded hard-position, split incoming/outgoing derivative-scale family, then complete and verify the dedicated Task 8 publication path without relaxing any safety or quality gate.
**Architecture:** A stateful candidate-build session owns deterministic representative-window/profile scheduling so production constructs Tier 1 first and constructs Tier 2 only after Tier 1 has no accepted candidate. Every primitive-boundary coordinate remains fixed; independent positive incoming/outgoing Hermite derivative scales supply the finite geometric freedom, while a per-region evaluator session caches only raw-window analysis and still runs every candidate-specific geometry, collision, clearance, deviation, and quality check.
-`MinimumWindowLengthMeters = 0.20`, `PreferredWindowLengthMeters = 0.50`, and `MaximumWindowLengthMeters = 0.80` are left-plus-right total lengths.
- Path start, path end, gear switches, outer window endpoints, and every internal primitive boundary remain hard position anchors.
- Internal vehicle heading and distance-weighted shared vehicle curvature remain hard boundary values.
- Segment `node[i] -> node[i+1]` uses `node[i].OutgoingDerivativeScale` and `node[i+1].IncomingDerivativeScale`.
- Boundary derivatives remain `r' = lambda * T` and `r'' = lambda^2 * kappa_geometric * N`; geometric G2 requires common position, unit tangent, and geometric curvature, not equal parameter speeds.
- Internal profiles are exactly `P0=(1.00,1.00)`, `P1=(0.75,1.25)`, and `P2=(1.25,0.75)`; outer endpoint factors are always `1.00`.
- A multi-event region applies one profile to every internal node; a window without an internal node emits only P0.
- Representative windows are, in order, planner-first, shortest, longest, and most asymmetric, de-duplicated by start/end arc and tie-broken by planner candidate index.
- Tier 1 is all representative P0 attempts followed by first-window P1 and P2, with at most six attempts. Tier 2 is remaining-window P1 followed by remaining-window P2.
- The configured limit truncates the global attempt order and the hard limit is `12`; failed constructions consume attempt budget, while successful geometries receive dense candidate indices.
-`PathSmoothingRegionReport.CandidateCount` is the number of candidates actually passed to the evaluator, not window count or build-attempt count.
- Maximum vehicle curvature, raw curvature range tolerance `1e-6`, full-body collision, extra clearance, maximum deviation, 20 percent peak-gradient improvement, and 2 percent variation-cost tolerance remain unchanged.
- Do not change Hybrid A*, vehicle parameters, SQP, legacy smoothers, comparison defaults, fixtures, expected status thresholds, or collision/clearance behavior.
- Preserve existing cancellation checks, derivative certification depth `40`, derivative certification interval cap `8192`, and adaptive sampling depth `32`.
- Identical requests must produce identical attempt order, candidate indices, coordinates, status, and region reports.
- Use TDD. The disposable `SingleTurn` feasibility gate must be GREEN before the shared `LocalG2CandidateBuilder.cs` is edited.
- Preserve unrelated dirty worktree files. Stage only the exact files named by each commit step.
- Never edit or delete `ClumsyPilot/ParkrobTrajplanner/auto_avoidance`; when its unavailable assemblies block the normal build, use an isolated copy excluding only that directory.
- Commit `bd08a9b` already makes the window planner cover preferred/minimum/maximum total lengths before asymmetric variants consume the budget.
- The old soft-anchor Task 3 is intentionally abandoned: its `0.05 m` coordinate offsets caused real `SingleTurn` curvature up to approximately `14.86 1/m` against the approximately `0.833333 1/m` vehicle limit.
- This plan supersedes `docs/superpowers/plans/2026-07-31-local-g2-soft-anchor-candidate-recovery.md` from its Task 3 onward. Do not revert or reimplement its completed Tasks 1 and 2.
-`BuildFallback` throws `InvalidOperationException` if called before `BuildPrimary`, and production does not call it after a Tier 1 acceptance.
- [ ]**Step 1: Replace the obsolete soft-anchor RED assertion**
In `verify_path_smoothing_local_g2_candidates.ps1`, replace `$acceptedSoftCandidates`, normal-offset inference, and the final non-zero-soft-anchor assertion with these checks:
Expected: FAIL because the real candidate diagnostics do not expose the fixed-anchor/split-scale G2 measurements, or because no accepted zero-offset split-scale candidate exists. A pass at this point means the test did not exercise the new contract; correct the test before continuing.
if($LASTEXITCODE-gt7){throw"robocopy failed with exit code $LASTEXITCODE"}
```
Before applying the candidate patch in the disposable copy, run the real `SingleTurn` builder/evaluator path 5 warmups plus 30 measurements. Record the elapsed milliseconds as `baselineSamples`, then compute:
The report must identify commit `bd08a9b`, `Debug/netstandard2.0`, the `SingleTurn` fixture/configuration, the machine, 5 warmups, 30 measurements, P50, and P95. If the candidate/evaluator sources differ from `bd08a9b`, overwrite only these three files in the disposable copy from a `git archive bd08a9b` extraction before measuring:
```text
LocalG2CandidateBuilder.cs
LocalG2CandidateGeometry.cs
LocalG2CandidateEvaluator.cs
```
- [ ]**Step 4: Add scale metadata in the disposable copy**
Add this enum and diagnostic type to `LocalG2CandidateGeometry.cs`:
Do not alter transition X/Y construction. Step 7 builds one `LocalG2InternalScaleDiagnostic` for every real internal connection from the actual incoming/outgoing curve endpoint derivatives.
- [ ]**Step 6: Add deterministic representative scheduling and lazy Tier 2 construction in the disposable copy**
Delete `DerivativeScaleMultipliers`. Add `BeginBuild`; keep `Build` only as a test/backward-compatibility wrapper that concatenates `BuildPrimary` and `BuildFallback` from one session:
Change `TryBuildCandidate` to receive `LocalG2DerivativeScaleProfile profile` and `int candidateTier`, call `TryAssignDerivativeScales(nodes, profile)`, and pass the profile, tier, and diagnostics into `LocalG2CandidateGeometry`.
Pass `scaleDiagnostics` into the candidate geometry. These measurements are internal feasibility diagnostics required by the design; they are not exposed as a new callable test API.
- [ ]**Step 8: Run the isolated feasibility gate**
Build the disposable copy and run its candidate verification against the real `SingleTurn` fixture. Evaluate all successfully constructed Tier 1 candidates; only if none is accepted, construct and evaluate Tier 2. The gate is GREEN only when all of these are true:
```text
at least one real candidate is accepted
accepted candidate internal anchor error <= 1e-9 m
at least one accepted candidate has different actual incoming/outgoing scales
Record every attempted tuple as `(tier, representative index, start arc, end arc, profile)`, every successful candidate's internal scales, failure reason or accepted metrics, maximum deviation, minimum clearance, curvature range, peak, variation cost, and elapsed time in `.superpowers/sdd/local-g2-split-scale-feasibility-report.md`.
If no tuple is accepted, stop the plan with the shared builder and geometry unchanged. Do not alter fixtures, coordinates, shared curvature, vehicle limits, evaluator tolerances, safety gates, or quality gates.
- [ ]**Step 9: Apply the proven patch to the shared tree and verify GREEN**
Only after Step 8 is GREEN, apply the exact geometry/builder patch from Steps 4–7 to the shared files. Run:
If the normal build is blocked only by `auto_avoidance`, repeat the build and scripts in a disposable copy excluding that directory and record both the shared-tree failure and isolated GREEN evidence. Expected: build succeeds in the valid source set and both scripts pass.
In the candidate script, inspect candidate profile, tier, window start/end, and candidate index. Assert exact global order:
```text
Tier 1: representative P0 in representative order, first representative P1, first representative P2
Tier 2: remaining representatives P1, then remaining representatives P2
```
Loop `MaximumCandidatesPerRegion` from `1` through `12`; require `AttemptCount <= limit`, output count `<= limit`, dense successful indices `0..count-1`, and no Tier 2 attempt before all enabled Tier 1 attempts. Run forward, reverse, asymmetric-window, and clustered multi-transition scenarios twice and compare every profile, tier, window, index, point coordinate, heading, arc, and source.
- [ ]**Step 11: Commit the proven candidate family**
- Consumes: candidates from one `LocalG2CandidateBuilder.LocalG2CandidateBuildSession` and one fixed `(rawPath, currentPath, region, request, options)` tuple.
- Produces:
```csharp
internalEvaluationSessionBeginRegionEvaluation(
PreparedPathrawPath,
PreparedPathcurrentPath,
LocalG2SmoothingRegionregion,
PathSmoothingRequestrequest,
LocalG2OptionsSnapshotoptions);
internalsealedclassEvaluationSession
{
internalLocalG2CandidateEvaluationEvaluate(
LocalG2CandidateGeometrycandidate,
CancellationTokencancellationToken);
internalintRawWindowAnalysisCount{get;}
}
```
- Session lifetime supplies request identity; its cache key is exact `(SegmentIndex, StartArcLengthMeters, EndArcLengthMeters)` and cannot escape one region evaluation.
- [ ]**Step 1: Add a failing repeated-window cache assertion**
Add evaluator TestHook scenario `RawWindowCache`. It creates one evaluation session, evaluates three valid geometries sharing the same segment/start/end window, and returns `RawWindowAnalysisCount`. Add to the PowerShell script:
The session owns `Dictionary<WindowKey, RawWindowEntry>`, all request inputs, and `RawWindowAnalysisCount`. On the first key, it extracts/analyzes the current raw window, verifies finite metrics, computes its curvature range, stores success or the stable failure reason, and increments the count exactly once. Repeated profiles reuse that immutable entry.
- [ ]**Step 3: Split raw-window preparation from candidate-specific evaluation**
Move only raw extraction, raw unified resampling/analysis, finite checks, and raw curvature-range computation into session preparation. Keep these operations inside `EvaluatePrepared` for every candidate:
```text
candidate unified geometry analysis
vehicle maximum curvature
raw curvature-range comparison
maximum deviation
splice and full-path geometry analysis
full-body collision validation
clearance reserve
20 percent peak improvement
2 percent variation-cost tolerance
accepted path-length change
```
Keep the current seven-argument `Evaluate` as a compatibility wrapper:
- [ ]**Step 4: Preserve the first concrete rejection when no candidate is accepted**
Change `SelectBest` so it still compares all accepted candidates by deviation, peak, variation cost, length change, then candidate index, but remembers the first non-null rejection:
Expected: all pass; repeated-window count is exactly one, and every existing collision/clearance/curvature/improvement/variation failure classification remains unchanged.
- Guarantees: Tier 2 is neither built nor evaluated after a Tier 1 acceptance; valid non-cancelled requests publish a complete verified path; cancellation publishes no partial path.
- [ ]**Step 1: Run integration/service RED and record the first contractual failure**
Expected before the tier/publication corrections: at least one required fixture status, actual candidate count, raw baseline, selected window, rollback, cancellation, or Tier 2 early-stop assertion fails. Record the first failure in `.superpowers/sdd/local-g2-split-scale-task-8-integration-report.md`.
The candidate count passed into every report is `evaluations.Count`. Do not use `region.WindowVariants.Count`, build attempts, or all possible schedule entries.
- [ ]**Step 3: Bind accepted and rollback reports to the actual selected geometry**
Resolve the selected geometry by dense candidate index:
For an accepted evaluation, require `selectedCandidate != null`, store it and `evaluations.Count` in `AcceptedRegion`, and report its exact start/end/left/right window values. For no accepted candidate, use candidate index `-1` only when no evaluation exists; otherwise retain the first concrete rejection and its reason. Rollback reports use the stored selected candidate and actual evaluated count.
Extend `AcceptedRegion` with:
```csharp
internalAcceptedRegion(
LocalG2SmoothingRegionregion,
PreparedPathbefore,
LocalG2CandidateEvaluationevaluation,
LocalG2CandidateGeometrycandidate,
intevaluatedCandidateCount)
```
and read-only `Candidate` and `EvaluatedCandidateCount` properties.
- [ ]**Step 4: Publish the trusted raw baseline for zero transitions and zero surviving improvements**
Immediately after detection/planning, if `transitions.Count == 0`, publish the existing `rawBaseline` as `NotNeeded`; do not re-run analysis. After global rollback, if `improvedCount == 0`, publish that same baseline as `Unchanged` with detector-order reports.
- [ ]**Step 5: Preserve work/report order, full validation, and rollback**
Keep immutable ascending `reportOrder`; process `workRegions` from `LocalG2RegionWorkOrder`. Candidate construction always uses `preparedPath.Segments[region.SegmentIndex]`; evaluation uses immutable `preparedPath` as raw reference and evolving `current` as splice input.
After regional processing, validate the complete `current` path. On failure, roll accepted regions back in reverse acceptance order and revalidate after each rollback. Mark removed regions `GlobalValidationRollback`. Derive status after rollback only:
```csharp
if(improvedCount==0)
status=PathSmoothingStatus.Unchanged;
elseif(improvedCount==regions.Count)
status=PathSmoothingStatus.Complete;
else
status=PathSmoothingStatus.PartialImprovement;
```
`Complete` and `PartialImprovement` require at least one `Improved` report. If every acceptance rolls back, publish the verified raw baseline.
- [ ]**Step 6: Keep public service dispatch isolated**
After common request validation, preparation, and `RawPathBaselineBuilder.TryCreate`, retain:
Validate `LocalG2QuinticOptions` only for Local G2. Legacy methods continue through `Resolve(configuration.Method)` and `_runner`; `PathSmoothingComparisonRequest.DefaultMethods` remains unchanged.
gitcommit-m"feat: publish tiered Local G2 presmoothing"
```
Expected staged names: exactly the four files above. `PathSmoothingService.cs` and its service test already contain provisional dirty hunks; stage only Local G2 Task 8 hunks and inspect the cached diff before committing.
---
### Task 4: Enforce performance and final verification gates
- Verify: all `ClumsyPilot/tests/verify_path_smoothing_*.ps1`
- Record without committing: `.superpowers/sdd/local-g2-split-scale-task-8-final-report.md`
**Interfaces:**
- Consumes: a baseline assembly built from commit `bd08a9b` sources and the final assembly, the same `SingleTurn` fixture/configuration, and modes `Primary` and `Fallback`; the assembly path identifies baseline versus final code.
- Produces one JSON object per run:
```json
{
"Mode":"Primary",
"WarmupCount":5,
"MeasurementCount":30,
"P50Milliseconds":0.0,
"P95Milliseconds":0.0,
"MaximumAttemptCount":6,
"MaximumEvaluatedCandidateCount":6,
"MaximumRawWindowAnalysisCount":4
}
```
- [ ]**Step 1: Write the fixed benchmark harness**
- [ ]**Step 2: Build comparable baseline and final assemblies**
Create two disposable copies from the same current workspace, excluding `.git`, build outputs, `auto_avoidance`, and `.task8-sweep`. In the baseline copy, extract exactly these `bd08a9b` files over the copy:
The pipeline did not exist at the baseline commit. Resolve the baseline copy's absolute `LocalG2PreSmoothingPipeline.cs` path, assert it starts with the resolved disposable baseline root, and remove only that disposable file before building. Do not remove the shared-tree file. Build both copies with:
Use the same machine, `Debug/netstandard2.0`, fixture JSON, map, vehicle, and request configuration for both.
- [ ]**Step 3: Run and compare 5+30 performance measurements**
Run the benchmark in separate PowerShell processes so the two `ClumsyPilot.dll` versions do not collide in one load context. Capture baseline primary/fallback and final primary/fallback JSON. Assert:
Record all four P50/P95 values, ratios, attempt/evaluation/raw-analysis maxima, machine, commit IDs, build configuration, fixture, warmup count, and measurement count. A wall-clock regression blocks merge but must not become a production timeout. Fix duplicate analysis or scheduling overhead; do not remove safety/quality checks.
if($LASTEXITCODE-ne0){throw"$test failed with exit code $LASTEXITCODE"}
}
```
Expected: zero build errors and all 19 scripts pass in the valid source set. Record any shared-build limitation plus the isolated-copy evidence if `auto_avoidance` is the sole unrelated blocker.
Confirm no safety value changed, no staged files remain, unrelated dirty files are untouched, Local G2 is absent from comparison defaults, repeated integration runs match exactly, and Task 9 README/documentation work has not started.
- [ ]**Step 7: Record final evidence without an empty commit**
Write `.superpowers/sdd/local-g2-split-scale-task-8-final-report.md` with the accepted `SingleTurn` tuple and scales, zero anchor error, G2 errors, unchanged safety/quality metrics, Tier 1/Tier 2 counts, raw-window cache counts, fixture statuses, rollback/cancellation results, performance P50/P95 ratios, build result, and `19/19` script result. Do not create a verification-only commit.