15 KiB
EM Observation Planning Diagnostics Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use executing-plans to implement this plan task-by-task. Steps use checkbox syntax for tracking.
Goal: Print one effective EM planning configuration snapshot at observation-session startup and make trajectory validation failures and successes numerically diagnosable without changing planning behavior.
Architecture: EmTrajectoryValidator enriches its existing jerk rejection reason with the values used in the decision. TrajectoryObservationDiagnostics owns pure invariant-culture formatters for configuration and successful trajectory metrics. The MovementTest runner prints the configuration formatter once after it creates the session-local controller.
Tech Stack: C#/.NET, existing EMPlannerVerificationHost, OSQP-backed EM planner.
Global Constraints
- Keep the MovementTest OBSERVE_ONLY; do not add actuator calls.
- Do not change solver configuration, motion limits, QP formulation, horizon selection, or acceptance rules.
- Print configuration once per session; do not put it in the recurring UI diagnostic or every planning cycle.
- Format values with CultureInfo.InvariantCulture.
- Failed publications keep returning no EmTrajectory; their numeric evidence belongs in the failure reason.
Preflight: Preserve manual MovementTest parameter tuning
TrajectoryObservationMovementTest exposes fields precisely so an operator can tune a test session. The verification host must not require those source literals to equal the defaults of TrajectoryObservationSettings; it must continue to verify that the fields exist, are copied into settings, and that TrajectoryObservationSettings itself retains its independently tested defaults.
- Run
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observationand observe the current source-literal failure. - In
TrajectoryObservationChecks.VerifiesMovementTestVehicleInputsAndSettingsSnapshot, replace only the four source-text checks that require0.50d,12000,6d, and0.10dwith checks forpublicfields namedSolverTimeoutSeconds,MaximumOsqpIterations,TimeHorizonSeconds, andOutputTimeStepSeconds. Preserve the separatenew TrajectoryObservationSettings()default assertions and all field-to-settings propagation checks. - Re-run the same command and require exit code
0before adding the Task 1 regression test.
Task 1: Detail a jerk-limit rejection with computed evidence
Files:
- Modify: ClumsyPilot/ParkrobTrajplanner/EMPlanner/Validation/EmTrajectoryValidator.cs:132-199
- Modify: ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryChecks.cs:58-110
Interfaces:
-
Consumes: the existing EmTrajectoryValidator.Validate(...) method.
-
Produces: an unchanged JerkLimitExceeded result whose Message contains time, dt, previousAcceleration, acceleration, jerk, limit, excess, storedPreviousJerk, and storedCurrentJerk.
-
Step 1: Write the failing test
In TrajectoryChecks.VerifiesWorldSpacePublicationMutationsAreRejected, retain the current jerkMutated trajectory and add:
EmTrajectoryValidationResult jerkResult = new EmTrajectoryValidator().Validate(
jerkMutated, context.EmptyMap, context.Vehicle, context.Configuration, 2, 0.0055d, EmBoundaryType.Goal);
Verification.Equal(EmTrajectoryValidationFailure.JerkLimitExceeded, jerkResult.Failure,
"jerk diagnostic failure code");
Verification.Equal(2, jerkResult.PointIndex, "jerk diagnostic point index");
foreach (string field in new[]
{
"time=", "dt=", "previousAcceleration=", "acceleration=", "jerk=", "limit=", "excess=",
"storedPreviousJerk=", "storedCurrentJerk=",
})
Verification.True(jerkResult.Message.Contains(field), "jerk diagnostic includes " + field);
- Step 2: Run the test to confirm RED
Run:
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory
Expected: the new assertion fails because the current message contains only Trajectory finite-difference jerk exceeds its limit.
- Step 3: Implement the message enrichment
Add using System.Globalization; to EmTrajectoryValidator.cs. Calculate finiteDifferenceJerk before the condition and preserve the existing inequality:
double finiteDifferenceJerk = (acceleration - previousAcceleration) / dt;
if (hasPreviousAcceleration && Math.Abs(finiteDifferenceJerk) > limits.MaximumJerk + limits.KinematicTolerance)
{
double storedPreviousJerk = index >= 2 ? trajectory.Points[index - 2].LongitudinalJerk : 0d;
double storedCurrentJerk = previous.LongitudinalJerk;
return Reject(EmTrajectoryValidationFailure.JerkLimitExceeded, index,
"Trajectory finite-difference jerk exceeds its limit" +
" (time=" + Format(point.TimeFromStart) + "s, dt=" + Format(dt) + "s" +
", previousAcceleration=" + Format(previousAcceleration) + "m/s2" +
", acceleration=" + Format(acceleration) + "m/s2" +
", jerk=" + Format(finiteDifferenceJerk) + "m/s3" +
", limit=" + Format(limits.MaximumJerk) + "m/s3" +
", excess=" + Format(Math.Abs(finiteDifferenceJerk) - limits.MaximumJerk) + "m/s3" +
", storedPreviousJerk=" + Format(storedPreviousJerk) + "m/s3" +
", storedCurrentJerk=" + Format(storedCurrentJerk) + "m/s3).");
}
Add this private method to the same class:
private static string Format(double value)
{
return value.ToString("G17", CultureInfo.InvariantCulture);
}
storedPreviousJerk and storedCurrentJerk are evidence only and do not influence the validation decision.
- Step 4: Confirm GREEN and commit
Run:
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory
Expected: host exit code 0.
Run:
git add -- ClumsyPilot/ParkrobTrajplanner/EMPlanner/Validation/EmTrajectoryValidator.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryChecks.cs
git commit -m "feat: detail trajectory jerk validation failures"
Task 2: Format and print the effective session configuration once
Files:
- Modify: ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationDiagnostics.cs:17-39
- Modify: ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPipeline.cs:188-241
- Modify: ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs:269-305
- Modify: ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs:18-39,96-191
Interfaces:
-
Consumes: the effective EmPlannerConfiguration held privately by TrajectoryObservationController.
-
Produces: TrajectoryObservationDiagnostics.CreateConfiguration(EmPlannerConfiguration) and TrajectoryObservationController.CreateConfigurationDiagnostic().
-
Produces: one terminal planning configuration block before the observation loop begins.
-
Step 1: Write failing configuration tests
Add VerifiesPlanningConfigurationDiagnosticUsesEffectiveConfiguration() to TrajectoryObservationChecks.Run(). Bootstrap a controller with:
var settings = new TrajectoryObservationSettings
{
ReplanPeriodSeconds = 0.25d,
SolverTimeoutSeconds = 1.25d,
MaximumOsqpIterations = 54321,
TimeHorizonSeconds = 3.5d,
OutputTimeStepSeconds = 0.10d,
};
var controller = new TrajectoryObservationController(bootstrap, settings,
new FixedTrajectoryPlanningService(CreatePublishedTrajectory(effectiveAt)), "config-diagnostic");
string text = controller.CreateConfigurationDiagnostic().Text;
foreach (string expected in new[]
{
"planning configuration:", "timeHorizon=3.50s", "distanceHorizon=5.00m", "outputTimeStep=0.10s",
"outputFrequency=10.00Hz", "trajectoryKnots=36", "maximumOsqpIterations=54321",
"solverTimeout=1.25s", "replanPeriod=0.25s",
})
Verification.True(text.Contains(expected), "configuration diagnostic includes " + expected);
Source-read MovementTest.TrajectoryObservationTest.cs and assert the runner call is singular:
int firstCall = runnerSource.IndexOf("CreateConfigurationDiagnostic()", StringComparison.Ordinal);
Verification.True(firstCall >= 0, "runner prints configuration diagnostic");
Verification.Equal(-1, runnerSource.IndexOf("CreateConfigurationDiagnostic()", firstCall + 1,
StringComparison.Ordinal), "runner prints configuration diagnostic once");
- Step 2: Run the test to confirm RED
Run:
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
Expected: compilation fails because CreateConfigurationDiagnostic does not exist.
- Step 3: Implement pure formatting and the one runner call
Add to TrajectoryObservationDiagnostics:
public static TrajectoryObservationDiagnostic CreateConfiguration(EmPlannerConfiguration configuration)
{
if (configuration == null || configuration.Scheduling == null || configuration.Solver == null)
throw new ArgumentNullException(nameof(configuration));
double horizon = configuration.Scheduling.TimeHorizonSeconds;
double step = configuration.Scheduling.OutputTimeStepSeconds;
int knots = checked((int)Math.Ceiling(horizon / step) + 1);
return new TrajectoryObservationDiagnostic("planning configuration:\n" +
"timeHorizon=" + Format(horizon, "F2") + "s\n" +
"distanceHorizon=" + Format(configuration.Scheduling.DistanceHorizonMeters, "F2") + "m\n" +
"outputTimeStep=" + Format(step, "F2") + "s\n" +
"outputFrequency=" + Format(1d / step, "F2") + "Hz\n" +
"trajectoryKnots=" + knots.ToString(CultureInfo.InvariantCulture) + "\n" +
"maximumOsqpIterations=" + configuration.Solver.MaximumOsqpIterations.ToString(CultureInfo.InvariantCulture) + "\n" +
"solverTimeout=" + Format(configuration.Scheduling.SolverTimeoutSeconds, "F2") + "s\n" +
"replanPeriod=" + Format(configuration.Scheduling.ReplanPeriodSeconds, "F2") + "s");
}
private static string Format(double value, string format)
{
return value.ToString(format, CultureInfo.InvariantCulture);
}
Add to TrajectoryObservationController:
public TrajectoryObservationDiagnostic CreateConfigurationDiagnostic()
{
return TrajectoryObservationDiagnostics.CreateConfiguration(configuration);
}
Immediately after constructing controller in RunSessionAsync, before new TrajectoryObservationLoop(controller), add exactly:
LogIfCurrent(sessionId, controller.CreateConfigurationDiagnostic().Text);
Do not call this method from the while loop or the UI drawing methods.
- Step 4: Confirm GREEN and commit
Run:
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
Expected: host exit code 0.
Run:
git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationDiagnostics.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPipeline.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs
git commit -m "feat: log EM observation session planning configuration"
Task 3: Summarize each successfully published trajectory
Files:
- Modify: ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationDiagnostics.cs:17-39
- Modify: ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs:538-561
Interfaces:
-
Consumes: latestCycle.Result.Trajectory only when latestCycle.Published is true.
-
Produces: a trajectory summary with ID, point count, duration, PathS length, max speed, max finite-difference acceleration, and max finite-difference jerk.
-
Step 1: Write the failing summary test
Add VerifiesPublishedPlanningDiagnosticsIncludeTrajectorySummary() to TrajectoryObservationChecks.Run():
EmTrajectory trajectory = CreatePublishedTrajectory(effectiveAt);
var succeeded = new PlanningCycleResult(9L,
new PlanningCycleIdentity(1L, "summary-reference", 2L, string.Empty, 0),
new EmPlanningResult(EmPlanningStatus.Success, trajectory, string.Empty), true, string.Empty);
string text = TrajectoryObservationDiagnostics.Create(
succeeded, TimeSpan.FromMilliseconds(12d), false, trajectory).Text;
foreach (string expected in new[]
{
"trajectory summary:", "trajectoryId=observer-published", "points=2", "duration=1.000s",
"pathLength=1.000m", "maxSpeed=0.400m/s", "maxAcceleration=0.200m/s2", "maxJerk=0.000m/s3",
})
Verification.True(text.Contains(expected), "published diagnostic includes " + expected);
Also add this assertion to the existing failed-cycle test:
Verification.True(!diagnostic.Text.Contains("trajectory summary:"), "failed diagnostic has no stale trajectory summary");
- Step 2: Run the test to confirm RED
Run:
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
Expected: summary assertions fail because the formatter only writes trajectory=.
- Step 3: Implement summary formatting
Replace the publishedTrajectory != null branch in TrajectoryObservationDiagnostics.Create(...) with a branch requiring latestCycle.Published && latestCycle.Result.Trajectory != null. Add CreateTrajectorySummary(EmTrajectory trajectory), which:
-
reads immutable trajectory points;
-
calculates maximum absolute signed speed;
-
calculates acceleration and jerk by the same direction-aware finite-difference convention as EmTrajectoryValidator;
-
ignores a nonpositive or non-finite dt only in logging;
-
reports first and last PathS difference as actual pathLength;
-
uses Format(value, "F3") and CultureInfo.InvariantCulture;
-
returns exactly these two lines:
trajectory summary: trajectoryId=, points=, duration=s, pathLength=m maxSpeed=m/s, maxAcceleration=m/s2, maxJerk=m/s3
Do not call this helper for an unsuccessful cycle, even when an older published trajectory is still observed.
- Step 4: Confirm GREEN, inspect, and commit
Run:
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
git diff --check
Expected: host exit code 0 and no whitespace errors.
Run:
git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationDiagnostics.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs
git commit -m "feat: summarize published EM observation trajectories"
Final verification
-
Run:
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation git diff --check git log -3 --oneline
Expected: verification exits 0, no whitespace errors exist, and three focused diagnostics commits follow the already committed design document.