docs: plan EM observation web visualization
This commit is contained in:
@@ -0,0 +1,694 @@
|
||||
# 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<VisualizationChart> 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<string> log);
|
||||
internal PlanningVisualizationSessionInfo Start(PlanningVisualizationStaticSnapshot snapshot);
|
||||
internal bool TryPublish(DateTimeOffset now,
|
||||
Func<PlanningVisualizationDynamicSnapshot> 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.
|
||||
Reference in New Issue
Block a user