# 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.WriteLine` and starts with `[TrajectoryObserver]`. - Every completed cycle reports raw `EmPlanningStatus`, `published`, version, elapsed time, and the original nonempty `FailureReason`. - 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 `PublishedTrajectory` is 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: ```csharp 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: ```powershell 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`: ```csharp 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** ```powershell 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.PlanningStarted` and `.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: ```csharp 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: ```csharp Verification.True(finalTick.PlanningCompleted, "observer completion tick reports cycle completion"); ``` In `VerifiesObservationSourceUsesRequiredOperatorText`, add: ```csharp 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: ```csharp bool planningStarted, bool planningCompleted public bool PlanningStarted { get; } public bool PlanningCompleted { get; } ``` In `TrajectoryObservationLoop.Tick`, use: ```csharp 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: ```csharp 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: ```csharp 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** ```powershell 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: ```csharp 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: ```csharp 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`: ```csharp 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: ```csharp 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: ```csharp new TrajectoryObservationDiagnostic("bootstrap failed: " + bootstrap.FailureReason) ``` - [ ] **Step 4: Verify focused test, build, and diff** Run: ```powershell 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** ```powershell 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" ```