14 KiB
EM FullDirection Correctness Fixes Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use test-driven-development for every task. Execute tasks serially because they share the EM solver pipeline.
Goal: Fix confirmed FullDirection projection, cancellation, curvature-constraint, timeout-budget, and trajectory-coordinate defects without replacing the existing LS/ST planner.
Architecture: Keep IEmPlanningService, EmPlanningRequest, LateralPlanner, LongitudinalPlanner, and trajectory contracts compatible. Apply local fixes where one component owns the invariant; introduce only a shared internal lateral-curvature affine model and internal explicit-budget overloads where the same invariant necessarily crosses components.
Tech Stack: C# 10, .NET Standard 2.0 production assembly, .NET 8 verification host, solver-neutral IQpSolver tests.
Global Constraints
FullDirectionSegmentplans exactly one complete direction segment;RollingHorizonbehavior is not redesigned in this plan.- FullDirection ego admission is restricted to the segment-start prefix
[0, min(L, MaximumProjectionDistanceMeters)]; it must not select a later U-shape/self-overlap branch. - A start heading error with magnitude greater than or equal to
π/2is rejected beforetan(headingError)is evaluated. Cancelledalways carries a null trajectory/path/candidate, even if a strict fallback candidate exists.SolverTimeoutSecondsis one combined LS+ST solve budget for a service call, not a fresh budget for each optimizer.- Every LS QP has a finite linearized vehicle-curvature hard-bound row at every station; nonlinear validation remains authoritative.
SegmentLocalSstores interpolated direction-segment reference S;PathSstores optimized lateral-path arc length.- Do not edit or revert the user's existing
EmPlannerConfiguration.cschange, PathSmoothing work, Map work, orClumsyPilot.csprojchanges. - Do not add actuator calls, change public EM request/result signatures, or commit/stage files from the dirty shared worktree.
Task 1: Anchor FullDirection start projection and reject folded headings
Files:
- Modify:
ClumsyPilot/tests/EMPlannerVerificationHost/EmPlanningServiceChecks.cs - Modify:
ClumsyPilot/ParkrobTrajplanner/EMPlanner/Facade/EmPlanningService.cs
Interfaces:
-
Consumes: existing
FrenetProjector.TryProjectbounded-window overload. -
Produces: service-local FullDirection start-prefix admission; Rolling continues using
[0,L]. -
Step 1: Write failing service tests. Add a FullDirection U-shaped all-forward reference whose later arm is closer to the measured pose, and assert
ProjectionFailedrather than accepting a laterReferenceS. Add a same-position start pose with yawπ, and assertProjectionFailedwith a null trajectory.
EmPlanningResult wrongBranch = service.Plan(fullURequest, CancellationToken.None);
Verification.Equal(EmPlanningStatus.ProjectionFailed, wrongBranch.Status,
"FullDirection cannot enter through a later U branch");
EmPlanningResult reversedHeading = service.Plan(oppositeHeadingRequest, CancellationToken.None);
Verification.Equal(EmPlanningStatus.ProjectionFailed, reversedHeading.Status,
"opposite start heading is rejected before slope conversion");
- Step 2: Run the focused test and verify RED.
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-planning-service
Expected: the later U branch and/or opposite-heading assertion fails under the current global [0,L], seed-zero projection.
- Step 3: Implement the local admission rule. In
EmPlanningService.Plan, choose the projection upper bound from scope and validate heading before constructing lateral input.
double startProjectionUpperS = request.PlanningScope == EmPlanningScope.FullDirectionSegment
? Math.Min(segment.LengthMeters, configuration.Frenet.MaximumProjectionDistanceMeters)
: segment.LengthMeters;
if (!projector.TryProject(request.VehicleState.Pose, segment, 0d, startProjectionUpperS,
configuration.Frenet.MaximumProjectionDistanceMeters, 0d, out FrenetProjection startProjection) ||
Math.Abs(startProjection.HeadingError) >= Math.PI / 2d)
{
return Failure(EmPlanningStatus.ProjectionFailed, request,
"Vehicle pose is not an admissible start state for the selected direction segment.");
}
- Step 4: Re-run
em-planning-serviceand verify GREEN. Existing Rolling projection behavior must remain green.
Task 2: Make cancellation terminal and non-publishable
Files:
- Modify:
ClumsyPilot/tests/EMPlannerVerificationHost/LateralIntegrationChecks.cs - Modify:
ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalIntegrationChecks.cs - Modify:
ClumsyPilot/tests/EMPlannerVerificationHost/EmPlanningServiceChecks.cs - Modify:
ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral/SequentialConvexOptimizer.cs - Modify:
ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/SequentialLongitudinalOptimizer.cs - Modify:
ClumsyPilot/ParkrobTrajplanner/EMPlanner/Facade/EmPlanningService.cs
Interfaces:
-
Produces:
Cancelledresults with null candidate/path/trajectory at every layer. -
Step 1: Write failing optimizer tests. Use a solver that returns one valid candidate and cancels the supplied source before the next iteration. Assert both optimizers return
Cancelled, notSuccessWithFallback, and expose no candidate.
Verification.Equal(EmPlanningStatus.Cancelled, result.Status,
"cancellation is never converted to fallback success");
Verification.True(result.Path == null, "cancelled lateral result has no path");
- Step 2: Verify RED with the focused groups.
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- lateral-integration
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-integration
Expected: at least one optimizer currently returns SuccessWithFallback.
- Step 3: Implement minimal cancellation precedence. Special-case cancellation in each
FallbackOrFailure, and check the token after LS, after ST, after assembly, and immediately before service success publication.
if (failureStatus == EmPlanningStatus.Cancelled)
return Failed(EmPlanningStatus.Cancelled, failureReason);
- Step 4: Re-run both optimizer groups and
em-planning-service; verify GREEN.
Task 3: Add shared linearized curvature hard constraints
Files:
- Create:
ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral/LateralCurvatureLinearization.cs - Modify:
ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral/LateralObjectiveBuilder.cs - Modify:
ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral/LateralConstraintBuilder.cs - Modify:
ClumsyPilot/tests/EMPlannerVerificationHost/LateralModelChecks.cs - Modify:
ClumsyPilot/tests/EMPlannerVerificationHost/EmPlanningServiceChecks.cs
Interfaces:
-
Produces:
LateralCurvatureLinearization.Create(input, layout, iterate)returning immutable station affines with indices, gradient, and constant. -
Consumers: objective terms and hard constraints use the exact same affine coefficients.
-
Step 1: Write a failing QP-shape test. For a straight reference and zero iterate, set vehicle maximum curvature to
0.25 1/m; assert every station has a row equivalent to-0.25 <= DDL(i) <= 0.25. Also assert the total constraint count increases by the station count.
Verification.Equal(expectedOldRows + layout.StationCount, problem.ConstraintCount,
"one curvature hard-bound row is emitted per station");
Verification.True(HasBound(problem,
new Dictionary<int, double> { { layout.DDL(station), 1d } }, -0.25d, 0.25d),
"straight-path curvature affine is hard bounded");
-
Step 2: Run
lateral-modeland verify RED. -
Step 3: Extract the existing affine calculation without changing its formula. Move
CreateCurvatureAffinesand its value type fromLateralObjectiveBuilderto the new internal file. Keep the nonlinear formula inLateralGeometryEvaluator/independent validator unchanged. -
Step 4: Add one curvature row per station in
LateralConstraintBuilder. Bounds are[-maximumVehicleCurvature, +maximumVehicleCurvature]after subtracting the affine constant.
AddRow(constraints, lower, upper, ref row,
-maximumCurvature - affine.Constant,
maximumCurvature - affine.Constant,
affine.Indices, affine.Gradient);
- Step 5: Update test solver problem classification so the added lateral rows are not mistaken for ST rows, then run
lateral-model,lateral-integration, andem-planning-serviceGREEN.
Task 4: Relinearize rejected solved vectors and report iteration exhaustion honestly
Files:
- Modify:
ClumsyPilot/tests/EMPlannerVerificationHost/LateralIntegrationChecks.cs - Modify:
ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral/SequentialConvexOptimizer.cs
Interfaces:
-
Produces: rejected, parseable solver candidates may advance only the SQP iterate/warm start; they never replace
lastValidatedPath. -
Step 1: Write failing tests. Configure a low curvature limit so the first candidate is strict and the second parseable candidate fails nonlinear validation. Assert the third QP/warm start is based on the second candidate, while a later timeout still returns the first strict path. Change the outer-limit assertion from ordinary
SuccesstoSuccessWithFallbackwith a non-empty reason. -
Step 2: Run
lateral-integrationand verify RED. -
Step 3: Move iterate/warm-start advancement to immediately after a parseable solved candidate, while updating
lastValidatedPathonly after independent validation. ReturnSuccessWithFallbackwhen the outer loop ends with a strict candidate but without satisfying convergence. -
Step 4: Run
lateral-integrationandlateral-real-osqpGREEN.
Task 5: Share one LS/ST solver timeout budget
Files:
- Modify:
ClumsyPilot/tests/EMPlannerVerificationHost/EmPlanningServiceChecks.cs - Modify:
ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral/LateralPlanner.cs - Modify:
ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral/SequentialConvexOptimizer.cs - Modify:
ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalPlanner.cs - Modify:
ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/SequentialLongitudinalOptimizer.cs - Modify:
ClumsyPilot/ParkrobTrajplanner/EMPlanner/Facade/EmPlanningService.cs
Interfaces:
-
Keeps: existing public
Plan(input, token)andOptimize(input, token)entry points. -
Adds: internal overloads accepting a finite positive
TimeSpan solveBudget. -
Step 1: Write a failing service test. Delay a lateral fake solve by at least 100 ms under a 2 s configured timeout, record all
QpSolverSettings.MaximumSolveDurationvalues, and assert the first ST call receives less than 1.95 s rather than a fresh 2 s. -
Step 2: Run
em-planning-serviceand verify RED. -
Step 3: Add internal explicit-budget overloads. Default public overloads continue deriving budget from configuration; service starts one monotonic
Stopwatchimmediately before LS and passesconfiguredBudget - elapsedto LS and then ST. Non-positive remaining time returnsSolverTimedOutwith no trajectory. -
Step 4: Add a final elapsed/cancellation check before publication and run
lateral-integration,longitudinal-integration, andem-planning-serviceGREEN.
Task 6: Preserve reference S separately from optimized PathS
Files:
- Modify:
ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryChecks.cs - Modify:
ClumsyPilot/ParkrobTrajplanner/EMPlanner/Trajectory/LateralPathInterpolator.cs - Modify:
ClumsyPilot/ParkrobTrajplanner/EMPlanner/Trajectory/EmTrajectoryAssembler.cs - Modify:
ClumsyPilot/ParkrobTrajplanner/EMPlanner/Validation/EmTrajectoryValidator.cs
Interfaces:
-
Extends internal
InterpolatedLateralPathPointwithReferenceS. -
Keeps public
EmTrajectoryPointshape unchanged. -
Step 1: Write a failing curved/offset-path assembly test. Construct a validated lateral path where reference-S and chord PathS differ; assert each output point's
SegmentLocalSis the interpolated reference-S andPathSremains the ST progress value. -
Step 2: Run
trajectoryand verify RED. Current assembly writessample.PathSinto both fields. -
Step 3: Interpolate
ReferenceSusing the same PathS bracket and passgeometry.ReferenceSasSegmentLocalS. Update publication bounds so segment-local S is checked against direction-segment length/reference bound, while PathS is checked against the optimized path upper bound. -
Step 4: Run
trajectoryandem-core-allGREEN.
Task 7: Core regression and diff hygiene
Files:
-
Verify only; no broad formatting.
-
Step 1: Run the complete EM verification set.
dotnet build ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj --no-restore
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj --no-build -- em-all
- Step 2: Check only scoped diffs.
git diff --check -- ClumsyPilot/ParkrobTrajplanner/EMPlanner ClumsyPilot/tests/EMPlannerVerificationHost
git status --short -- ClumsyPilot/ParkrobTrajplanner/EMPlanner ClumsyPilot/tests/EMPlannerVerificationHost
Expected: all groups pass; no whitespace errors; EmPlannerConfiguration.cs remains exactly the user's pre-existing modification.