13 KiB
EM Observation Diagnostics 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: Make every EM planning-cycle failure visible in the host terminal and the World/L-S/S-T observation canvases without changing observation-only safety behavior.
Architecture: Add a pure diagnostic formatter that keeps EmPlanningStatus and the original FailureReason from PlanningCycleResult. Extend the observation-loop tick with explicit start/completion events so the runner writes one pending line and one completed-cycle line per cycle, while the painters receive the current diagnostic every tick.
Tech Stack: C# / .NET Standard 2.0 plugin, Hedingben.ToastText, UI.GetPainter, EM planner contracts, .NET verification host.
Global Constraints
- The MovementTest remains observe-only: do not add any chassis, brake, wheel, or actuator call.
- Terminal output uses
Console.WriteLineand starts with[TrajectoryObserver]. - Every completed cycle reports raw
EmPlanningStatus,published, version, elapsed time, and the original nonemptyFailureReason. - A 50 ms observer tick must not emit a duplicate terminal record.
- World, L-S, and S-T painters must show the diagnostic even when
PublishedTrajectoryis null.
Task 1: Add a pure planning diagnostic formatter
Files:
- Create:
ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationDiagnostics.cs - Modify:
ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs
Interfaces:
-
Consumes:
PlanningCycleResult,EmTrajectory,TimeSpan, and the in-flight flag. -
Produces:
TrajectoryObservationDiagnostic.Text, a compact multi-line operator string. -
Step 1: Write the failing test
Add VerifiesPlanningDiagnosticsKeepRawFailureReason(); to Run(), then add:
private static void VerifiesPlanningDiagnosticsKeepRawFailureReason()
{
var failed = new PlanningCycleResult(
4L,
new PlanningCycleIdentity(3L, "diagnostic-reference", 7L, string.Empty, 0),
new EmPlanningResult(EmPlanningStatus.CorridorInfeasible, null,
"map=3;reference=diagnostic-reference;state=7;previous=;segment=0;reason=no connected corridor"),
false,
"map=3;reference=diagnostic-reference;state=7;previous=;segment=0;reason=no connected corridor");
TrajectoryObservationDiagnostic diagnostic = TrajectoryObservationDiagnostics.Create(
failed, TimeSpan.FromMilliseconds(18d), false, null);
Verification.True(diagnostic.Text.Contains("cycle=4"), "diagnostic has cycle version");
Verification.True(diagnostic.Text.Contains("status=CorridorInfeasible"), "diagnostic preserves raw status");
Verification.True(diagnostic.Text.Contains("published=False"), "diagnostic preserves publish state");
Verification.True(diagnostic.Text.Contains("elapsed=18ms"), "diagnostic preserves elapsed time");
Verification.True(diagnostic.Text.Contains("reason=map=3;reference=diagnostic-reference"),
"diagnostic preserves planner failure reason");
TrajectoryObservationDiagnostic pending = TrajectoryObservationDiagnostics.Create(
null, TimeSpan.Zero, true, null);
Verification.Equal("planning status=pending", pending.Text, "diagnostic reports pending before completion");
}
- Step 2: Run the test to verify RED
Run:
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
Expected: build failure because TrajectoryObservationDiagnostic and TrajectoryObservationDiagnostics do not exist.
- Step 3: Write the minimal formatter
Create TrajectoryObservationDiagnostics.cs:
using System;
using System.Globalization;
using MultiWheelC.TrajectoryPlanning.EMPlanner;
namespace MultiWheelC.TrajectoryPlanning.TrajectoryObservation;
public sealed class TrajectoryObservationDiagnostic
{
public TrajectoryObservationDiagnostic(string text)
{
Text = text ?? string.Empty;
}
public string Text { get; }
}
public static class TrajectoryObservationDiagnostics
{
public static TrajectoryObservationDiagnostic Create(PlanningCycleResult latestCycle,
TimeSpan elapsed, bool planningInFlight, EmTrajectory publishedTrajectory)
{
if (latestCycle == null)
return new TrajectoryObservationDiagnostic(planningInFlight
? "planning status=pending"
: "planning status=not-started");
string text = "planning cycle=" + latestCycle.Version.ToString(CultureInfo.InvariantCulture) +
" status=" + latestCycle.Result.Status +
" published=" + latestCycle.Published +
" elapsed=" + Math.Max(0d, elapsed.TotalMilliseconds).ToString("F0", CultureInfo.InvariantCulture) + "ms";
if (planningInFlight)
text += "\nreplan=pending";
if (publishedTrajectory != null)
text += "\ntrajectory=" + publishedTrajectory.Metadata.TrajectoryId;
if (!string.IsNullOrWhiteSpace(latestCycle.Result.FailureReason))
text += "\nreason=" + latestCycle.Result.FailureReason;
return new TrajectoryObservationDiagnostic(text);
}
}
- Step 4: Run the test to verify GREEN
Run the Step 2 command.
Expected: PASS trajectory-observation.
- Step 5: Commit
git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationDiagnostics.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs
git commit -m "feat: format EM observation diagnostics"
Task 2: Report once at the start and completion of every planning cycle
Files:
- Modify:
ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPipeline.cs - Modify:
ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs - Modify:
ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs
Interfaces:
-
Produces:
TrajectoryObservationLoopTick.PlanningStartedand.PlanningCompleted. -
Consumes: those flags in the MovementTest to issue one terminal/UI status record per lifecycle event.
-
Step 1: Write the failing test
In VerifiesObserverTicksWhilePlanningIsDelayed, after the first tick, add:
Verification.True(firstTick.PlanningStarted, "observer first tick reports a planning-cycle start");
Verification.True(!firstTick.PlanningCompleted, "observer first tick has no completed cycle");
After finalTick is created, add:
Verification.True(finalTick.PlanningCompleted, "observer completion tick reports cycle completion");
In VerifiesObservationSourceUsesRequiredOperatorText, add:
Verification.True(source.Contains("Console.WriteLine(\"[TrajectoryObserver] \" + text);"),
"observer status is mirrored to the host terminal");
- Step 2: Run the test to verify RED
Run the Task 1 test command.
Expected: assertions fail because lifecycle flags and terminal output do not exist.
- Step 3: Implement lifecycle flags and output
Change TrajectoryObservationLoopTick to accept and expose:
bool planningStarted, bool planningCompleted
public bool PlanningStarted { get; }
public bool PlanningCompleted { get; }
In TrajectoryObservationLoop.Tick, use:
bool planningCompleted = ConsumeCompletedPlanning(now);
bool planningStarted = false;
if (planningTask == null && controller.ShouldStartCycle(now))
{
planningStarted = true;
planningStartedAtUtc = now;
planningTask = controller.StartCycle(now, state, cancellationToken);
planningCompleted |= ConsumeCompletedPlanning(now);
}
Change ConsumeCompletedPlanning to return false when no completed Task is available and true after it assigns latestCycle, updates elapsed time, and clears planningTask. Pass both flags to the tick constructor.
In RunSessionAsync, after the tick is created, make one diagnostic and only log event records:
TrajectoryObservationDiagnostic diagnostic = TrajectoryObservationDiagnostics.Create(
tick.LatestCycle, tick.LatestPlanningElapsed, tick.PlanningInFlight,
observation.PublishedTrajectory);
if (tick.PlanningStarted)
LogIfCurrent(sessionId, "planning status=pending");
if (tick.PlanningCompleted)
LogIfCurrent(sessionId, diagnostic.Text);
Remove the unconditional if (tick.ShouldLog) status call. Keep the existing session-start, stop, bootstrap-failure, and runtime-fault calls.
Change PrintStatus to:
private static void PrintStatus(string message)
{
string text = ObserveOnlyNotice + "\n" + message;
Hedingben.ToastText(text, StatusChannel);
Console.WriteLine("[TrajectoryObserver] " + text);
}
- Step 4: Run the test to verify GREEN
Run the Task 1 test command.
Expected: PASS trajectory-observation; the delayed-planner test still proves the observer does not wait for planning.
- Step 5: Commit
git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPipeline.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs
git commit -m "feat: report EM observation planning cycles"
Task 3: Persist planning diagnostics in all three painter layers
Files:
- Modify:
ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPresentation.cs - Modify:
ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs - Modify:
ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs
Interfaces:
-
Consumes:
TrajectoryObservationDiagnostic.Text. -
Produces: World/L-S/S-T empty states that show the precise planning status and reason.
-
Step 1: Write the failing test
Add VerifiesEmptyChartsReceivePersistentPlanningDiagnostic(); to Run() and add:
private static void VerifiesEmptyChartsReceivePersistentPlanningDiagnostic()
{
string presentationPath = Path.Combine(Directory.GetCurrentDirectory(), "ClumsyPilot",
"ParkrobTrajplanner", "tarjplanner_movementtest", "TrajectoryObservationPresentation.cs");
string source = new UTF8Encoding(false, true).GetString(File.ReadAllBytes(presentationPath));
Verification.True(source.Contains("DrawLs(TrajectoryObservationCharts charts, string diagnosticText)"),
"LS painter accepts planning diagnostic input");
Verification.True(source.Contains("DrawSt(TrajectoryObservationCharts charts, string diagnosticText)"),
"ST painter accepts planning diagnostic input");
Verification.True(source.Contains("No published trajectory available for L-S chart.\n"),
"LS empty state includes diagnostic after chart label");
Verification.True(source.Contains("No published trajectory available for T-S/T-V charts.\n"),
"ST empty state includes diagnostic after chart label");
}
- Step 2: Run the test to verify RED
Run the Task 1 test command.
Expected: source checks fail because painter methods have no diagnostic parameter.
- Step 3: Add painter parameters and wire the diagnostic
Change signatures to:
public void DrawWorld(TrajectoryObservationBootstrapResult bootstrap,
TrajectoryObservationObservation observation, TrajectoryObservationRuntimeState runtimeState,
string diagnosticText)
public void DrawLs(TrajectoryObservationCharts charts, string diagnosticText)
public void DrawSt(TrajectoryObservationCharts charts, string diagnosticText)
Add this helper in TrajectoryObservationPresentation:
private static string EmptyChartMessage(string label, string diagnosticText)
{
return string.IsNullOrWhiteSpace(diagnosticText)
? label
: label + "\n" + diagnosticText;
}
For World, draw EmptyChartMessage("No published trajectory available.", diagnosticText) at bootstrap.Map.Bounds.XMin + 100f, bootstrap.Map.Bounds.YMin + 300f before returning from the empty trajectory path. For L-S and S-T, draw EmptyChartMessage with their existing label at 0f, 0f.
Change DrawIfCurrent to accept TrajectoryObservationDiagnostic diagnostic and call:
Presentation.DrawWorld(bootstrap, observation, runtimeState, diagnostic?.Text ?? string.Empty);
Presentation.DrawLs(charts, diagnostic?.Text ?? string.Empty);
Presentation.DrawSt(charts, diagnostic?.Text ?? string.Empty);
Pass the diagnostic created in Task 2 from RunSessionAsync. For the bootstrap-failure path, pass:
new TrajectoryObservationDiagnostic("bootstrap failed: " + bootstrap.FailureReason)
- Step 4: Verify focused test, build, and diff
Run:
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
dotnet build ClumsyPilot/ClumsyPilot.csproj --no-restore
git diff --check
Expected: PASS trajectory-observation, zero build errors, and no diff whitespace errors.
- Step 5: Commit
git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPresentation.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs
git commit -m "feat: show EM observation failure diagnostics"