# EM Observation Web Visualization Integration 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:** Upgrade `TrajectoryObservationMovementTest` into a complete multi-segment, observe-only EM planning experiment that publishes effective configuration, global/active path geometry, LS/ST and kinematic evidence to the reusable local scientific dashboard. **Architecture:** A pure segment tracker advances only after a real stop and stable signed-speed confirmation. The controller owns one coordinator per active direction segment, while pure snapshot builders convert existing EM/map/smoothing data into the generic visualization contracts. A thin MovementTest host conditionally owns web and Painter sessions and isolates every visualization failure from planning. **Tech Stack:** C# 10, `netstandard2.0`, existing EMPlanner/TrajectoryExecution/Map/PathSmoothing modules, `TrajectoryPlanningVisualization`, OSQP, MDCS read interfaces, existing console verification host. ## Prerequisite Complete and review `docs/superpowers/plans/2026-08-05-trajectory-planning-visualization-library.md` first. This plan consumes its exact `PlanningVisualizationSession`, snapshot, chart, geometry, and configuration contracts. ## Global Constraints - Continue `OBSERVE_ONLY`; do not call chassis, motor, steering, brake, wheel, gear, or controller write APIs. - Use actual MDCS pose and signed longitudinal speed only as read inputs. - Preserve user defaults exactly: solver timeout `5.0 s`, OSQP maximum iterations `100000`, ST horizon `2.0 s`, output step `0.10 s`. - Default web visualization is off; default native Painter visualization is off. - Web refresh defaults to `10 Hz`; history defaults to `60`; port defaults to `0`. - Segment transitions are strictly `N -> N+1`; never skip, infer at zero speed, or reuse a previous-direction trajectory as an EM seed. - Current and previous cycles may compare world position and shared direction-segment `ReferenceS`; never compare their independent local `PathS` origins. - `j[i]` represents `[t_i, t_i+1)`; publish exactly `trajectory.Points.Count - 1` jerk samples and no synthetic terminal sample. - Static geometry/configuration is built once after successful bootstrap. Dynamic snapshots are built at most at `WebRefreshRateHz`. - Visualization exceptions are logged once and disable only the web output. - Preserve unrelated worktree changes and stage only task files. --- ## File Structure | File | Responsibility | | --- | --- | | `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationContracts.cs` | Runtime, web and direction-confirmation settings with frozen validation. | | `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationSegmentTracker.cs` | Pure stop/direction/projection state machine. | | `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPipeline.cs` | Active-segment controller and asynchronous loop integration. | | `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationStaticSnapshotBuilder.cs` | Map/global paths/direction segments/effective configuration. | | `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationKinematicChartBuilder.cs` | LS/ST/curvature/v/a/j/yaw-rate generic charts. | | `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationHandoffAnalyzer.cs` | Absolute-time interpolation and shared-segment handoff deltas. | | `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationDynamicSnapshotBuilder.cs` | World overlays, status, charts and cycle summary. | | `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationVisualizationPublisher.cs` | 10 Hz gating and exception fuse. | | `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs` | Public fields, lifecycle, browser launch and conditional Painter/web ownership. | | `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPresentation.cs` | Lazily created optional native Painter fallback. | | `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/README.md` | Operator workflow and chart interpretation. | | `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationSettingsChecks.cs` | Defaults, validation and frozen-setting checks. | | `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationSegmentChecks.cs` | Pure multi-segment transition checks. | | `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationVisualizationChecks.cs` | Static/dynamic adapter and web isolation checks. | | `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs` | Existing regression entry; calls the new focused groups. | | `ClumsyPilot/tests/EMPlannerVerificationHost/PluginPackagingChecks.cs` | New managed DLL packaging contract. | | `ClumsyPilot/scripts/Publish-ClumsyPilotPlugin.ps1` | Copies the visualization class library beside `ClumsyPilot.dll`. | | `ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md` | Updated plugin tree and observation link. | --- ### Task 1: Synchronize effective settings and visualization controls **Files:** - Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationContracts.cs` - Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs` - Create: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationSettingsChecks.cs` - Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs` - Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/README.md` **Interfaces:** - Extends `TrajectoryObservationSettings` with web/Painter and direction-confirmation properties. - `CreateValidatedSnapshot()` freezes every new property. - MovementTest public fields map one-to-one into settings before background work begins. - [ ] **Step 1: Write failing defaults, copy, and validation checks** Add `TrajectoryObservationSettingsChecks.Run()` and invoke it from `TrajectoryObservationChecks.Run()`. ```csharp var defaults = new TrajectoryObservationSettings(); Verification.NearlyEqual(5d, defaults.SolverTimeoutSeconds, "observer solver timeout default"); Verification.Equal(100000, defaults.MaximumOsqpIterations, "observer OSQP default"); Verification.NearlyEqual(2d, defaults.TimeHorizonSeconds, "observer ST horizon default"); Verification.True(!defaults.EnableWebVisualization, "web defaults off"); Verification.True(!defaults.EnableNativePainterVisualization, "Painter defaults off"); Verification.Equal(0, defaults.WebVisualizationPort, "dynamic port default"); Verification.NearlyEqual(10d, defaults.WebRefreshRateHz, "web refresh default"); Verification.Equal(60, defaults.VisualizationHistoryCycleLimit, "history default"); Verification.NearlyEqual(0.02d, defaults.DirectionConfirmationSpeedMetersPerSecond, "direction speed threshold"); Verification.Equal(3, defaults.DirectionConfirmationSamples, "direction sample default"); Verification.NearlyEqual(0.50d, defaults.GearSwitchProjectionToleranceMeters, "gear projection default"); Verification.NearlyEqual(0.20d, defaults.GearSwitchStopHoldSeconds, "gear stop hold default"); ``` Mutate all source values after `CreateValidatedSnapshot()` and assert the snapshot retains the originals. Add invalid cases for port `-1/1/1023/65536`, refresh `0`, history `0`, speed threshold `0`, sample count `0`, projection tolerance `0`, and hold `0`. - [ ] **Step 2: Run RED** ```powershell dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation ``` Expected: failure because settings still use `0.50 / 12000 / 6.0` and the new properties do not exist. - [ ] **Step 3: Implement settings and MovementTest field mapping** Add exact get/set defaults to settings and matching public fields to MovementTest: ```csharp public bool EnableWebVisualization { get; set; } = false; public bool AutoOpenWebVisualization { get; set; } = true; public int WebVisualizationPort { get; set; } = 0; public double WebRefreshRateHz { get; set; } = 10d; public int VisualizationHistoryCycleLimit { get; set; } = 60; public bool EnableNativePainterVisualization { get; set; } = false; public double DirectionConfirmationSpeedMetersPerSecond { get; set; } = 0.02d; public int DirectionConfirmationSamples { get; set; } = 3; public double GearSwitchProjectionToleranceMeters { get; set; } = 0.50d; public double GearSwitchStopHoldSeconds { get; set; } = 0.20d; ``` Use inclusive validation for port `0` and `1024..65535`. Update the Chinese XML summaries so the ST default says `2 s`, not `6 s`. Update the README configuration table with all solver, ST, web, Painter and direction-confirmation fields. - [ ] **Step 4: Run GREEN** Run `trajectory-observation`. Expected: PASS and no assertion still expects the superseded defaults. - [ ] **Step 5: Commit settings** ```powershell git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationContracts.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/README.md ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationSettingsChecks.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs git commit -m "feat: configure observation visualization" ``` --- ### Task 2: Pure sequential direction-segment tracker **Files:** - Create: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationSegmentTracker.cs` - Create: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationSegmentChecks.cs` - Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs` **Interfaces:** - Produces: `TrajectoryObservationSegmentPhase`, `TrajectoryObservationSegmentState`, and `TrajectoryObservationSegmentTracker.Update(...)`. - Consumes: frozen settings, ordered `DirectionSegmentView` list, EM stop tolerance, caller time, measured `VehicleMotionState`, and current published trajectory. - [ ] **Step 1: Write failing state-machine checks** Build a two-segment forward/reverse fixture with a duplicated world pose at the gear switch. Exercise this exact sequence: ```csharp var tracker = new TrajectoryObservationSegmentTracker(segments, settings, stopSpeedTolerance: 0.01d); Verification.Equal(0, tracker.State.ActiveSegmentIndex, "tracker begins on segment zero"); tracker.Update(t0, StateAtSwitch(0d, t0, 1L), GearTerminal(t0)); Verification.Equal(TrajectoryObservationSegmentPhase.WaitingForStop, tracker.State.Phase, "first zero sample begins stop hold"); tracker.Update(t0.AddSeconds(0.21d), StateAtSwitch(0d, t0.AddSeconds(0.21d), 2L), GearTerminal(t0)); Verification.Equal(TrajectoryObservationSegmentPhase.WaitingForDirection, tracker.State.Phase, "continuous stop arms next direction"); tracker.Update(t0.AddSeconds(0.25d), StateAtSwitch(-0.03d, t0.AddSeconds(0.25d), 3L), GearTerminal(t0)); tracker.Update(t0.AddSeconds(0.30d), StateAtSwitch(-0.03d, t0.AddSeconds(0.30d), 4L), GearTerminal(t0)); TrajectoryObservationSegmentUpdate advanced = tracker.Update( t0.AddSeconds(0.35d), StateAtSwitch(-0.03d, t0.AddSeconds(0.35d), 5L), GearTerminal(t0)); Verification.True(advanced.Advanced && tracker.State.ActiveSegmentIndex == 1, "three stable reverse samples advance exactly one segment"); ``` Separate checks prove that wrong sign, a zero sample, a sequence-id repeat, excessive switch distance, a non-gear terminal, and a discontinuous timestamp reset confirmation. A one-segment path reaches `Completed` rather than indexing past the end. - [ ] **Step 2: Run RED** Run `trajectory-observation`. Expected: compile failure because tracker types do not exist. - [ ] **Step 3: Implement the tracker** Use these stable phases: ```csharp public enum TrajectoryObservationSegmentPhase { Planning, WaitingForStop, WaitingForDirection, Completed, } ``` `Update` first validates strictly increasing `SequenceId` and nondecreasing caller time. It only leaves `Planning` when the current published trajectory has matching segment/direction, `TerminalType.GearSwitch`, and the caller time is at or beyond `trajectory.Metadata.EffectiveAtUtc + trajectory.Points[^1].TimeFromStart`. Use `FrenetProjector` to confirm the measured pose is within the configured tolerance of both the current-segment end and next-segment start. The expected speed sign is positive for `Forward` and negative for `Reverse`. A successful update changes the active index once, resets counters/timers, and returns a Chinese transition diagnostic. It must not write to coordinator, browser, UI, or hardware. - [ ] **Step 4: Run GREEN** Run `trajectory-observation` twice. Expected: PASS both times with deterministic state transitions. - [ ] **Step 5: Commit tracker** ```powershell git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationSegmentTracker.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationSegmentChecks.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs git commit -m "feat: track observed direction segments" ``` --- ### Task 3: Active-segment rolling controller and loop **Files:** - Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPipeline.cs` - Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationSegmentChecks.cs` - Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs` **Interfaces:** - `TrajectoryObservationController.ActiveSegment`, `SegmentState`, `PreviousTrajectoryForVisualization`, and `TryAdvanceSegment(...)` become the single active-segment source. - `TrajectoryObservationController.CreateEffectiveConfigurationSnapshot()` returns `configuration.Copy()` so adapters never read mutable MovementTest fields or retain the controller's private configuration object. - `StartCycle` builds a request for `ActiveSegment.SegmentIndex`. - `TrajectoryObservationLoop.Tick` may call `TryAdvanceSegment` only when no planning task is in flight. - [ ] **Step 1: Write failing controller integration checks** Use a recording `IEmPlanningService` and two-segment bootstrap: ```csharp controller.StartCycle(t0, forwardState, CancellationToken.None).GetAwaiter().GetResult(); Verification.Equal(0, service.Requests[0].SegmentIndex, "first request uses segment zero"); AdvanceTrackerThroughRealStopAndReverse(controller, gearTrajectory, t0); controller.StartCycle(t0.AddSeconds(1d), reverseState, CancellationToken.None).GetAwaiter().GetResult(); Verification.Equal(1, service.Requests[1].SegmentIndex, "next request uses segment one"); Verification.True(service.Requests[1].PreviousTrajectory == null, "new direction does not reuse old segment trajectory"); ``` Also prove two cycles on the same segment retain the exact published previous trajectory reference and ID, and prove an in-flight task prevents a segment reset. - [ ] **Step 2: Run RED** Expected: checks fail because the controller hardcodes `segmentIndex = 0`. - [ ] **Step 3: Refactor controller ownership** Store the planning service and make coordinator/executor replaceable per segment: ```csharp private readonly IEmPlanningService planningService; private EmPlanningCoordinator coordinator; private TrajectoryExecutor executor; private readonly TrajectoryObservationSegmentTracker segmentTracker; private EmTrajectory previousTrajectoryForVisualization; ``` Add: ```csharp internal EmPlannerConfiguration CreateEffectiveConfigurationSnapshot() { return configuration.Copy(); } ``` On a confirmed transition, preserve the old published trajectory only in `previousTrajectoryForVisualization`, then construct a new coordinator and executor. Same-segment `StartCycle` uses `coordinator.PublishedTrajectory`; cross-segment starts with null. Continue monotonic session cycle IDs across coordinator replacement. `Observe` supplies the tracker-confirmed current direction to `TrajectoryExecutor`. While waiting at a gear switch, desired direction is the next segment direction and `directionConfirmed=false`; after transition both current and desired are the new direction. - [ ] **Step 4: Update the async loop** In `Tick`, after consuming a completed task and before starting a new task: ```csharp bool segmentAdvanced = planningTask == null && controller.TryAdvanceSegment(now, state); if (segmentAdvanced) latestCycle = null; ``` Expose `SegmentAdvanced` and the immutable segment state in `TrajectoryObservationLoopTick`. Never cancel a running planner solely to advance a segment. - [ ] **Step 5: Run GREEN and existing coordinator/executor regressions** ```powershell dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- coordinator dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- executor ``` Expected: all three PASS. - [ ] **Step 6: Commit controller integration** ```powershell git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPipeline.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationSegmentChecks.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs git commit -m "feat: observe EM planning across gear segments" ``` --- ### Task 4: Static world geometry and effective configuration adapter **Files:** - Create: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationStaticSnapshotBuilder.cs` - Create: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationVisualizationChecks.cs` - Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs` **Interfaces:** - Produces: `PlanningVisualizationStaticSnapshot Build(bootstrap, effectiveConfiguration, settings, obstacleSnapshotVersion)`. - Static polylines use metres; map source bounds/resolution remain named with their original millimetre units in configuration values. - [ ] **Step 1: Write failing static snapshot checks** Assert a forward/reverse bootstrap produces every segment and switch marker: ```csharp PlanningVisualizationStaticSnapshot snapshot = builder.Build(bootstrap, configuration, settings, 17L); Verification.Equal(bootstrap.Segments.Count, snapshot.DirectionSegments.Count, "all direction segments exported"); Verification.Equal("Forward", snapshot.DirectionSegments[0].Direction, "forward direction exported"); Verification.Equal("Reverse", snapshot.DirectionSegments[1].Direction, "reverse direction exported"); Verification.True(snapshot.StaticMarkers.Any(x => x.Kind == "gear-switch"), "gear switch marker exported"); Verification.Equal((bootstrap.GridMap.Rows * bootstrap.GridMap.Cols + 7) / 8, Convert.FromBase64String(snapshot.OccupancyGrid.OccupancyBitsBase64).Length, "occupancy bitset has exact compact length"); Verification.True(IsOccupiedBitSet(snapshot.OccupancyGrid, occupiedRow, occupiedColumn), "occupied map cell uses row-major least-significant-bit-first encoding"); ``` Flatten configuration entries by raw field name and assert exact values for `TimeHorizonSeconds=2`, `DistanceHorizonMeters=5`, `MaximumOsqpIterations=100000`, all solver tolerances, all LS/ST weights, vehicle fields, map snapshot/resolution/bounds, web settings, and direction-confirmation settings. - [ ] **Step 2: Run RED** Expected: compile failure because builder does not exist. - [ ] **Step 3: Implement static conversion** Build configuration groups in this stable order: `调度`, `OSQP`, `车辆与安全`, `纵向限制`, `ST 权重`, `横向限制`, `LS 权重`, `走廊与投影`, `地图`, `可视化与换向确认`. Use raw field names exactly as C# properties and invariant values. Encode `PlanningGridMap.IsOccupied(row, column)` into the generic row-major occupancy bitset using bit index `row * map.Cols + column`; do not create one DTO, SVG element, or polyline per occupied cell. Build global coarse and Local G2 polylines once. Direction segments must retain segment index, direction and switch topology; dynamic state decides completed/current/future highlighting. - [ ] **Step 4: Run GREEN** Run `trajectory-observation`. Expected: PASS with all effective values read from the controller's frozen `EmPlannerConfiguration`, not editable MovementTest fields. - [ ] **Step 5: Commit static adapter** ```powershell git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationStaticSnapshotBuilder.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationVisualizationChecks.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs git commit -m "feat: export EM observation configuration" ``` --- ### Task 5: Kinematic charts, rolling semantics, and handoff evidence **Files:** - Create: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationKinematicChartBuilder.cs` - Create: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationHandoffAnalyzer.cs` - Create: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationDynamicSnapshotBuilder.cs` - Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationVisualizationChecks.cs` **Interfaces:** - Produces chart IDs `ls`, `st`, `curvature-s`, `curvature-t`, `velocity-t`, `acceleration-t`, `jerk-t`, `yaw-rate-t`. - Produces `TrajectoryObservationHandoffMetrics` with availability, `DeltaPositionMeters`, `DeltaReferenceSMeters`, `DeltaVelocityMetersPerSecond`, and `DeltaAccelerationMetersPerSecondSquared`. - Produces one `PlanningVisualizationDynamicSnapshot` per accepted publication tick. - [ ] **Step 1: Write failing jerk and chart checks** For a 21-point rolling trajectory: ```csharp IReadOnlyList charts = chartBuilder.Build(trajectory, segment, configuration); VisualizationChart jerk = FindChart(charts, "jerk-t"); Verification.Equal(20, jerk.Series[0].Points.Count, "jerk has one real sample per interval"); Verification.True(jerk.NoteChinese.Contains("末点后无时间区间"), "jerk explains terminal interval"); Verification.Equal(21, FindChart(charts, "velocity-t").Series[0].Points.Count, "velocity remains knot based"); Verification.Equal("ReferenceS (m)", FindChart(charts, "ls").XAxisLabel, "LS uses shared segment station"); Verification.Equal("PathS (m)", FindChart(charts, "st").YAxisLabel, "ST uses local planned PathS"); ``` Assert acceleration, jerk, curvature and signed-speed limit series are present with `VisualizationLineStyle.Limit`. Do not invent a yaw-rate limit: the effective configuration has no independent maximum yaw-rate field. - [ ] **Step 2: Write failing rolling/exact-stop semantic checks** Build dynamic snapshots for `RollingContinuation` and `ExactStopAtBoundary`. Assert Chinese status values respectively contain `滚动末端停车硬约束:未启用` and `精确停车锚点`, and that the displayed terminal speed/acceleration equal the actual final point. - [ ] **Step 3: Write failing handoff coordinate checks** Create two trajectories whose local `PathS` both start at zero but whose world points project to different positions on the same full segment. Align at `current.Metadata.EffectiveAtUtc`; assert: ```csharp Verification.NearlyEqual(expectedWorldDelta, metrics.DeltaPositionMeters, "handoff world delta"); Verification.NearlyEqual(expectedReferenceDelta, metrics.DeltaReferenceSMeters, "handoff compares shared ReferenceS"); Verification.True(expectedReferenceDelta != current.Points[0].PathS - previous.Points[0].PathS, "handoff does not compare local PathS origins"); ``` Projection failure must return unavailable `DeltaReferenceS` while preserving world/velocity/acceleration deltas. - [ ] **Step 4: Run RED** Expected: compilation failure because builders and metrics do not exist. - [ ] **Step 5: Implement chart and handoff builders** For jerk, iterate only while `index + 1 < trajectory.Points.Count` and use the interval start time/value. For LS, project every world pose to the complete active `DirectionSegmentView` and use `segment.SourceStartArcLength + projection.ReferenceS`. For ST, retain `point.TimeFromStart` and the plan-local `point.PathS`. Use `TrajectorySampler.TrySample` at: ```csharp double previousTime = (current.Metadata.EffectiveAtUtc - previous.Metadata.EffectiveAtUtc).TotalSeconds; ``` Compare the sampled previous point with current point zero. Project both to the same full segment for `DeltaReferenceS`; never subtract local `PathS`. - [ ] **Step 6: Implement dynamic snapshot composition** Dynamic world overlays contain real pose, current trajectory, previous trajectory, active-segment highlight and current projected horizon. Status values include active segment/direction, planning status, mode, terminal type, elapsed, trajectory age, projection failures, rolling constraint semantics, terminal `v/a`, handoff deltas, and unmet gear-confirmation condition. Cycle summary contains no full point arrays. - [ ] **Step 7: Run GREEN** ```powershell dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-core-all ``` Expected: both commands PASS; the existing rolling service regression still has 21 knots and nonzero rolling terminal speed. - [ ] **Step 8: Commit the visualization adapter** ```powershell git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationKinematicChartBuilder.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationHandoffAnalyzer.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationDynamicSnapshotBuilder.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationVisualizationChecks.cs git commit -m "feat: visualize EM rolling kinematics" ``` --- ### Task 6: MovementTest web lifecycle and optional Painter fallback **Files:** - Create: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationVisualizationPublisher.cs` - Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs` - Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPresentation.cs` - Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationVisualizationChecks.cs` - Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs` **Interfaces:** - `TrajectoryObservationVisualizationPublisher.Start(...)` creates the generic session and returns the URI. - Internal `ITrajectoryObservationVisualizationSink` defines `Start`, `Publish`, and `Stop`; the production adapter wraps `PlanningVisualizationSession`, while tests inject a throwing sink without mocking the library. - `TryPublish(...)` enforces the configured cadence and fuses on the first adapter/server fault. - `Stop()` is idempotent. - Native Painter objects are constructed only when `EnableNativePainterVisualization=true`. - [ ] **Step 1: Write failing lifecycle/isolation checks** Add a fake visualization sink whose `Publish` throws. Verify the wrapper logs/disables once, later calls are no-ops, and the `TrajectoryObservationLoop` continues producing ticks. Add a source audit asserting the MovementTest has no static eager `new TrajectoryObservationPresentation()` and conditionally creates it only under the native Painter flag. Add a cadence check: observer ticks at 20 Hz with web refresh at 10 Hz produce no more than 11 snapshots over one second, including the initial snapshot. - [ ] **Step 2: Run RED** Expected: failures because the publisher does not exist and Painter creation is eager. - [ ] **Step 3: Implement publisher cadence and fuse** Use this internal seam and facade surface: ```csharp internal interface ITrajectoryObservationVisualizationSink { PlanningVisualizationSessionInfo Start(PlanningVisualizationOptions options, PlanningVisualizationStaticSnapshot snapshot); void Publish(PlanningVisualizationDynamicSnapshot snapshot); void Stop(); } internal sealed class TrajectoryObservationVisualizationPublisher { internal TrajectoryObservationVisualizationPublisher(TrajectoryObservationSettings settings, ITrajectoryObservationVisualizationSink sink, Action log); internal PlanningVisualizationSessionInfo Start(PlanningVisualizationStaticSnapshot snapshot); internal bool TryPublish(DateTimeOffset now, Func snapshotFactory); internal void Stop(); internal string FaultReason { get; } } ``` The production sink constructs and disposes one `PlanningVisualizationSession`; the fake sink records calls or throws. The publisher stores `nextPublishAtUtc`. When enabled and healthy: ```csharp if (now < nextPublishAtUtc) return false; nextPublishAtUtc = now + TimeSpan.FromSeconds(1d / settings.WebRefreshRateHz); session.Publish(dynamicBuilder.Build(...)); return true; ``` Catch any exception from build/start/publish, call `session.Stop()`, set one immutable Chinese fault reason, invoke the supplied log callback once, and return false thereafter. - [ ] **Step 4: Integrate successful bootstrap and browser launch** After bootstrap succeeds and before the loop begins, build the static snapshot and start the web session only when enabled. Log the full tokenized URI. If auto-open is enabled, call: ```csharp Process.Start(new ProcessStartInfo { FileName = sessionInfo.Uri.AbsoluteUri, UseShellExecute = true, }); ``` Catch browser-launch exceptions separately and keep the web session running. - [ ] **Step 5: Make Painter lazy and conditional** Remove static eager `Presentation`. Create a session-local presentation only when enabled, pass null otherwise, and guard all `DrawWorld/DrawLs/DrawSt/ClearAll` calls. Stopping or replacing a session stops its web publisher and clears only an existing Painter. Bootstrap failure still logs through UI/console even when both visualization modes are off. - [ ] **Step 6: Publish dynamic snapshots from real ticks** Use `controller.ActiveSegment` for LS/ST building and world highlight. Pass the completed cycle, elapsed time, in-flight flag, segment state, current/previous trajectories, live state and current sample into `TryPublish`. Do not serialize or wait in the observer loop. - [ ] **Step 7: Run GREEN and source audit** ```powershell dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation rg -n "SendXYThSpeed|SendMotion|DriveStop|PredefinedDriveStop|AccumulateSpeed" ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest ``` Expected: check PASS and `rg` finds no actuator call in runtime observer sources. - [ ] **Step 8: Commit MovementTest lifecycle** ```powershell git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationVisualizationPublisher.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPresentation.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationVisualizationChecks.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs git commit -m "feat: host EM observation dashboard" ``` --- ### Task 7: Plugin packaging and operator documentation **Files:** - Modify: `ClumsyPilot/scripts/Publish-ClumsyPilotPlugin.ps1` - Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/PluginPackagingChecks.cs` - Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/README.md` - Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md` **Interfaces:** - Plugin output adds `plugins/TrajectoryPlanningVisualization.dll` beside `ClumsyPilot.dll`. - The publisher resolves the visualization DLL only as a sibling of the explicit `ManagedDll`; it does not search `PATH` or current directory. - [ ] **Step 1: Write failing package-tree check** Change the expected sorted tree to: ```text plugins/ClumsyPilot.dll| plugins/TrajectoryPlanningVisualization.dll| plugins/licenses/OSQP-LICENSE.txt| plugins/licenses/OSQP-NOTICE.txt| plugins/licenses/OSQP-VERSION.txt| plugins/osqp.dll ``` Also use `AssemblyName.GetAssemblyName` to verify the new file is a managed assembly named `TrajectoryPlanningVisualization`. - [ ] **Step 2: Run RED** ```powershell dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- plugin-package ``` Expected: failure because the publish script omits the visualization DLL. - [ ] **Step 3: Update the atomic publisher** Resolve: ```powershell $visualizationDll = Join-Path ([System.IO.Path]::GetDirectoryName($managedDllPath)) 'TrajectoryPlanningVisualization.dll' if (-not [System.IO.File]::Exists($visualizationDll)) { throw "Visualization DLL does not exist beside managed DLL: $visualizationDll" } ``` Copy it into staging before the atomic directory swap. Keep all existing drive-root/workspace-root protections, OSQP hash validation and recovery behavior unchanged. - [ ] **Step 4: Run package GREEN** Run `plugin-package` twice. Expected: PASS both times and no stale staging/backup directories. - [ ] **Step 5: Update both READMEs** The MovementTest README must document: - how to enable web/Painter modes; - localhost/token URL behavior; - Chinese scientific chart conventions; - `ReferenceS` versus local `PathS`; - jerk interval semantics; - rolling/approach/exact-stop labels; - active/completed/future segment colors; - direction-confirmation conditions; - browser closure and TestStop behavior. The EM README plugin tree must include `TrajectoryPlanningVisualization.dll` and link to the observation README. - [ ] **Step 6: Commit packaging and docs** ```powershell git add -- ClumsyPilot/scripts/Publish-ClumsyPilotPlugin.ps1 ClumsyPilot/tests/EMPlannerVerificationHost/PluginPackagingChecks.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/README.md ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md git commit -m "docs: package EM observation dashboard" ``` --- ### Task 8: Full automated and manual acceptance **Files:** - No planned source changes. If a command fails, return to the owning task, add a focused failing regression there, implement the minimal correction, and rerun this acceptance task from Step 1. **Interfaces:** - Consumes: both verification hosts, the packaged plugin output, the deterministic smoke mode, and the deployed MovementTest entry. - Produces: fresh automated evidence plus an explicit completed-or-pending vehicle checklist; it does not introduce a new runtime API. - [ ] **Step 1: Run the complete automated suite** ```powershell dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-all dotnet build ClumsyPilot/ClumsyPilot.csproj -p:ExcludeLegacyAutoAvoidance=true git diff --check ``` Expected: visualization host PASS; every `em-all` component PASS; main build exits `0`; no new whitespace errors. Record any pre-existing warnings separately rather than claiming they were introduced here. - [ ] **Step 2: Run a local dashboard smoke session** Run the deterministic smoke mode delivered by Plan 1: ```powershell dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj -- --smoke-seconds 30 ``` Open the printed tokenized URI during the 30-second window and verify: - Chinese titles/status and English/scientific axes; - thin white-background scientific plots; - all four tabs and all required charts; - active segment/world horizon highlight; - effective configuration values; - stale-state display after sample publication stops; - port release after the host exits. This smoke command must publish synthetic snapshots only and must not reference MDCS or hardware. - [ ] **Step 3: Perform the deployed vehicle observation checklist** In a supervised safe environment: 1. Enable web and leave native Painter disabled. 2. Confirm `OBSERVE_ONLY` appears in UI/console/page. 3. Observe rolling, approach and exact-stop modes; confirm no forced rolling parking label. 4. Confirm jerk has `N-1` interval samples and no point-21 successor interval or `JerkLimitExceeded`. 5. Confirm current/previous handoff uses `Δposition/ΔReferenceS/Δv/Δa`. 6. At a real gear switch, confirm stop hold and three signed-speed samples precede `N -> N+1` highlight. 7. Close the browser and confirm planning cadence continues. 8. Stop MovementTest and confirm server/port/Painter cleanup and no actuator output. - [ ] **Step 4: Commit only acceptance-driven fixes** If Step 1–3 required code changes, commit each regression and fix with exact file paths. If no changes were required, do not create an empty commit. Plan 2 is complete only after automated evidence is fresh and the vehicle-only checklist is explicitly reported as completed or, if the vehicle is unavailable, explicitly reported as pending rather than silently treated as passed.