Files
ParkingRobot/docs/superpowers/plans/2026-08-06-em-full-direction-segment-visualization-repair.md
T

1108 lines
60 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# EM Full-Direction-Segment Planning and Visualization Repair 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:** Add one-shot full-direction-segment LS/ST planning that starts from rest, reaches the configured cruise speed when feasible, stops at the true segment boundary with pose tolerances, and repairs the existing paper-style observation UI without adding actuator output.
**Architecture:** Keep rolling planning intact behind an explicit `RollingHorizon` scope and add `FullDirectionSegment` as a separate request scope. Full-segment planning selects the remaining active direction segment, derives a bounded adaptive optimization schedule from the actual Local G2 `s_end` and jerk-limited speed envelope, then publishes a separately resampled trajectory. Observation snapshots remain immutable; Web and Native Painter consume the same corrected semantics, while a test-only jsdom host verifies actual DOM/SVG output.
**Tech Stack:** C#/.NET 8, existing EM planner and OSQP abstraction, existing MovementTest observer, embedded HTML/CSS/vanilla JavaScript/SVG/Canvas, Node.js 24 test runtime with `jsdom` 29.1.1 as a development-only DOM dependency.
## Global Constraints
- MovementTest remains `OBSERVE_ONLY`; do not add chassis, steering, brake, motor, or gear write calls.
- “Global” means the complete remaining part of one coarse-path direction segment. A trajectory never crosses a forward/reverse boundary.
- `FullDirectionSegment` plans once when a segment becomes active and reuses the frozen result. Runtime rolling refinement and dynamic-obstacle replanning are outside this plan.
- If MovementTest starts in the middle of a segment, plan from the valid Local G2 projection of the startup pose to that segments true end boundary.
- Full mode derives `s_end` from actual Local G2 `PathS` and derives `T_end`; `DistanceHorizonMeters` and `TimeHorizonSeconds` do not truncate full mode.
- Forward desired/hard maximum speed is `1.0 m/s`; reverse desired/hard maximum speed is `0.5 m/s`.
- Terminal world-position error must be at most `0.03 m`; normalized yaw error must be at most `5°`.
- Published trajectory sampling is `0.1 s` for MovementTest and is independent of adaptive optimization knots.
- Jerk contains exactly `N-1` interval values for `N` trajectory points; there is no jerk sample after the terminal point.
- Resource exhaustion returns `FullSegmentResourceLimitExceeded`; it never silently truncates or falls back to rolling.
- Preserve the existing four-tab white paper/scientific Web layout, Chinese titles, mathematical symbols, and thin lines.
- Web zoom changes only the browser viewport. It never mutates snapshot data or sends planner settings.
- Native Painter receives coordinate, scale, marker, and semantic corrections only; do not redesign or beautify it.
- Before editing any already-dirty file, record `git diff -- <path>` and preserve all unrelated user hunks. Stage every task with explicit paths only.
- The approved design is `docs/superpowers/specs/2026-08-06-em-full-direction-segment-visualization-repair-design.md`.
---
## File and Responsibility Map
**New planner files**
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Contracts/EmPlanningScope.cs`: explicit rolling versus full-direction-segment request scope.
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalKnotSchedule.cs`: immutable optimization-knot schedule, separate from publication sampling.
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/FullDirectionSegmentScheduleBuilder.cs`: derives `T_end`, adaptive knots, and reference progress/speed from Local G2 `PathS` and the speed/stopping envelope.
**New Web-test files**
- `ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom/package.json`: test-only Node package with exact jsdom dependency.
- `ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom/package-lock.json`: locked dependency graph generated by npm.
- `ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom/dashboard.dom.test.mjs`: actual DOM/SVG regression tests.
**Existing files with focused changes**
- Configuration/contracts: `EmPlannerConfiguration.cs`, `SchedulingConfiguration.cs`, `LongitudinalConfiguration.cs`, `ValidationConfiguration.cs`, `EmPlanningRequest.cs`, `EmPlanningStatus.cs`, `EmTrajectoryMetadata.cs`.
- Scope and full planning: `PlanningHorizonSelector.cs`, `EmPlanningRequestValidator.cs`, `EmPlanningService.cs`.
- Longitudinal model: `PathSpeedLimitBuilder.cs`, `LongitudinalPlanningInput.cs`, `SequentialLongitudinalOptimizer.cs`, `LongitudinalObjectiveBuilder.cs`, `LongitudinalConstraintBuilder.cs`, `LongitudinalSolutionValidator.cs`, `LongitudinalPreviousTrajectorySeedBuilder.cs`.
- Publication: `TrajectorySampleSchedule.cs`, `EmTrajectoryAssembler.cs`, `EmTrajectoryValidator.cs`.
- MovementTest: `TrajectoryObservationContracts.cs`, `TrajectoryObservationPipeline.cs`, `MovementTest.TrajectoryObservationTest.cs`.
- Observation semantics: `TrajectoryObservationStaticSnapshotBuilder.cs`, `TrajectoryObservationDynamicSnapshotBuilder.cs`, `TrajectoryObservationKinematicChartBuilder.cs`, `TrajectoryObservationPresentation.cs`.
- Visualization contracts/assets: `VisualizationCharts.cs`, `VisualizationGeometry.cs`, `PlanningVisualizationSnapshots.cs`, `index.html`, `app.css`, `app.js`, `EmbeddedWebAssets.cs`.
- Tests: existing EM and visualization verification hosts plus the new test-only jsdom package.
- Operator docs: MovementTest and visualization READMEs, followed by a new acceptance handoff.
---
### Task 1: Add explicit planning scope and validated configuration
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Contracts/EmPlanningScope.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Contracts/EmPlanningRequest.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Contracts/EmPlanningStatus.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Contracts/EmTrajectoryMetadata.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Configuration/SchedulingConfiguration.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Configuration/LongitudinalConfiguration.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Configuration/ValidationConfiguration.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Configuration/EmPlannerConfiguration.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Validation/EmPlanningRequestValidator.cs`
- Test: `ClumsyPilot/tests/EMPlannerVerificationHost/FoundationChecks.cs`
- Test: `ClumsyPilot/tests/EMPlannerVerificationHost/EmPlanningServiceChecks.cs`
**Interfaces:**
- Produces: `EmPlanningScope.RollingHorizon` and `EmPlanningScope.FullDirectionSegment`.
- Produces: `EmPlanningRequest.PlanningScope` and `EmTrajectoryMetadata.PlanningScope`.
- Produces configuration properties used by Tasks 2-5:
`DesiredForwardSpeedMetersPerSecond`, `DesiredReverseSpeedMetersPerSecond`,
`MaximumOptimizationTimeStepSeconds`, `MaximumOptimizationSpatialStepMeters`,
`MaximumOptimizationKnotCount`, `MaximumPublishedSampleCount`,
`TerminalPositionToleranceMeters`, and `TerminalYawToleranceRadians`.
- Produces status values `NoProgress`, `TerminalPoseMismatch`, and `FullSegmentResourceLimitExceeded`.
- [ ] **Step 1: Write failing contract/default/copy tests**
Add checks that construct both request scopes, copy the configuration, and assert the exact defaults:
```csharp
Verification.Equal(EmPlanningScope.FullDirectionSegment, fullRequest.PlanningScope,
"full request freezes its scope");
Verification.NearlyEqual(1.0d, defaults.Longitudinal.DesiredForwardSpeedMetersPerSecond,
"forward desired speed");
Verification.NearlyEqual(0.5d, defaults.Longitudinal.DesiredReverseSpeedMetersPerSecond,
"reverse desired speed");
Verification.NearlyEqual(1.0d, defaults.Longitudinal.MaximumForwardSpeedMetersPerSecond,
"forward hard limit");
Verification.NearlyEqual(0.5d, defaults.Longitudinal.MaximumReverseSpeedMetersPerSecond,
"reverse hard limit");
Verification.NearlyEqual(0.20d, defaults.Scheduling.MaximumOptimizationTimeStepSeconds,
"adaptive maximum dt");
Verification.NearlyEqual(0.10d, defaults.Scheduling.MaximumOptimizationSpatialStepMeters,
"adaptive maximum ds");
Verification.Equal(401, defaults.Scheduling.MaximumOptimizationKnotCount,
"optimization knot cap");
Verification.Equal(5001, defaults.Scheduling.MaximumPublishedSampleCount,
"publication sample cap");
Verification.NearlyEqual(0.03d, defaults.Validation.TerminalPositionToleranceMeters,
"terminal position tolerance");
Verification.NearlyEqual(5d * Math.PI / 180d, defaults.Validation.TerminalYawToleranceRadians,
"terminal yaw tolerance");
```
Mutate the source after `Copy()` and verify the copy remains unchanged. Add invalid-value tests for zero/negative knot caps, desired speed above the hard limit, and terminal yaw tolerance above π.
- [ ] **Step 2: Run RED**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- foundation
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-planning-service
```
Expected: compilation fails because the scope, statuses, and configuration members do not exist.
- [ ] **Step 3: Add the contracts and defaults**
Create:
```csharp
namespace MultiWheelC.TrajectoryPlanning.EMPlanner;
public enum EmPlanningScope
{
RollingHorizon,
FullDirectionSegment,
}
```
Add a required `EmPlanningScope planningScope` constructor parameter and immutable property to `EmPlanningRequest`. Add the same field to `EmTrajectoryMetadata`. Update every constructor call explicitly; do not add an implicit default that could silently change legacy rolling behavior.
Add the configuration members listed above and copy them in their owning `Copy()` methods. Set the exact defaults from the test. Add the three new failure statuses to `EmPlanningStatus`.
- [ ] **Step 4: Validate cross-field invariants**
Extend `TryValidateConfiguration` with exact checks:
```csharp
if (!Positive(longitudinal.DesiredForwardSpeedMetersPerSecond) ||
longitudinal.DesiredForwardSpeedMetersPerSecond > longitudinal.MaximumForwardSpeedMetersPerSecond ||
!Positive(longitudinal.DesiredReverseSpeedMetersPerSecond) ||
longitudinal.DesiredReverseSpeedMetersPerSecond > longitudinal.MaximumReverseSpeedMetersPerSecond ||
!Positive(scheduling.MaximumOptimizationTimeStepSeconds) ||
!Positive(scheduling.MaximumOptimizationSpatialStepMeters) ||
scheduling.MaximumOptimizationKnotCount < 3 ||
scheduling.MaximumPublishedSampleCount < 2 ||
!Positive(validation.TerminalPositionToleranceMeters) ||
!Positive(validation.TerminalYawToleranceRadians) ||
validation.TerminalYawToleranceRadians > Math.PI)
{
return false;
}
```
Validate `request.PlanningScope` with `Enum.IsDefined`. Keep the rolling distance/time sufficiency validation only for `RollingHorizon`; full mode still validates positive legacy values for compatibility but does not use them as truncation inputs.
- [ ] **Step 5: Run GREEN**
Run the two commands from Step 2. Expected: `PASS foundation` and `PASS em-planning-service`.
- [ ] **Step 6: Commit the contract boundary**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/EMPlanner/Contracts/EmPlanningScope.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Contracts/EmPlanningRequest.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Contracts/EmPlanningStatus.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Contracts/EmTrajectoryMetadata.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Configuration/SchedulingConfiguration.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Configuration/LongitudinalConfiguration.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Configuration/ValidationConfiguration.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Configuration/EmPlannerConfiguration.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Validation/EmPlanningRequestValidator.cs ClumsyPilot/tests/EMPlannerVerificationHost/FoundationChecks.cs ClumsyPilot/tests/EMPlannerVerificationHost/EmPlanningServiceChecks.cs
git commit -m "feat: define full-direction EM planning scope"
```
---
### Task 2: Select the complete remaining direction segment
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Segmentation/PlanningHorizonSelector.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Facade/EmPlanningService.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Trajectory/EmTrajectoryAssembler.cs`
- Test: `ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalModelChecks.cs`
- Test: `ClumsyPilot/tests/EMPlannerVerificationHost/EmPlanningServiceChecks.cs`
**Interfaces:**
- Consumes: `EmPlanningScope` from Task 1.
- Produces: `PlanningHorizonSelector.Select(..., EmPlanningScope planningScope, ...)`.
- Guarantees: full mode returns the real `Goal` or `GearSwitch` boundary with `ExactStopAtBoundary`; rolling mode preserves existing rolling/approach/exact selection.
- [ ] **Step 1: Write failing scope-selection tests**
Create a 10 m segment, project the vehicle at segment-local 3 m, set legacy distance horizon to 1 m and time horizon to 2 s, then assert:
```csharp
EmPlanningStatus status = selector.Select(segment, 3d, 0d, 0d,
EmPlanningScope.FullDirectionSegment, configuration, out PlanningHorizonSelection full, out string failure);
Verification.Equal(EmPlanningStatus.Success, status, "full selection: " + failure);
Verification.NearlyEqual(segment.LengthMeters, full.WindowEndReferenceS,
"full selection reaches the true segment boundary");
Verification.Equal(EmLongitudinalMode.ExactStopAtBoundary, full.LongitudinalMode,
"full selection always plans a stopped boundary");
Verification.True(full.HasStopBoundary, "full selection retains a real stop boundary");
```
Use the same fixture with `RollingHorizon` and assert the window ends at 4 m. Add a gear-pair fixture and assert segment 0 ends at `GearSwitch` without including segment 1 points.
- [ ] **Step 2: Run RED**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-model
```
Expected: compile failure because `Select` does not accept planning scope.
- [ ] **Step 3: Implement the explicit full branch**
Change the selector signature and branch after the initial-state/stopping-distance precheck:
```csharp
if (planningScope == EmPlanningScope.FullDirectionSegment)
{
EmBoundaryType boundary = segment.EndBoundary.BoundaryType;
if (!IsStopBoundary(boundary))
{
failureReason = "A full direction segment must end at Goal or GearSwitch.";
return EmPlanningStatus.InvalidReferencePath;
}
selection = new PlanningHorizonSelection(segment.LengthMeters, boundary,
ToTerminalType(boundary), EmLongitudinalMode.ExactStopAtBoundary,
segment.LengthMeters, true);
return EmPlanningStatus.Success;
}
```
Leave the existing rolling selection below this branch unchanged. Pass `request.PlanningScope` from `EmPlanningService` and include the scope in metadata and diagnostics.
- [ ] **Step 4: Verify the service slices from the startup projection to the real boundary**
Add an end-to-end service test where the initial pose projects into the middle of a direction segment. Assert the first published point is at the projected startup pose, the final point is the segment endpoint, and no point belongs to the next direction segment.
- [ ] **Step 5: Run GREEN**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-model
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-planning-service
```
Expected: both commands print PASS.
- [ ] **Step 6: Commit segment selection**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/EMPlanner/Segmentation/PlanningHorizonSelector.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Facade/EmPlanningService.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Trajectory/EmTrajectoryAssembler.cs ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalModelChecks.cs ClumsyPilot/tests/EMPlannerVerificationHost/EmPlanningServiceChecks.cs
git commit -m "feat: select complete EM direction segments"
```
---
### Task 3: Derive adaptive full-segment optimization knots
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalKnotSchedule.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/FullDirectionSegmentScheduleBuilder.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/PathSpeedLimitBuilder.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalPlanningInput.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalConstraintBuilder.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalSolutionValidator.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/SequentialLongitudinalOptimizer.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalPreviousTrajectorySeedBuilder.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Facade/EmPlanningService.cs`
- Test: `ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalModelChecks.cs`
- Test: `ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalIntegrationChecks.cs`
**Interfaces:**
- Produces immutable `LongitudinalKnotSchedule` with `KnotTimes`, `ReferencePathS`, `ReferenceSpeedMetersPerSecond`, `TotalDurationSeconds`, and `IsAdaptive`.
- Produces `FullDirectionSegmentScheduleBuilder.TryBuild(...)` returning `EmPlanningStatus.Success` or `FullSegmentResourceLimitExceeded`.
- `LongitudinalPlanningInput.PlanningScope` carries the request scope into the independent validator.
- `LongitudinalPlanningInput.KnotSchedule` becomes the only optimization-knot source.
- Publication still uses `Scheduling.OutputTimeStepSeconds`; no caller treats it as the optimization step in full mode.
- [ ] **Step 1: Write failing duration, adaptivity, and resource-limit tests**
Cover these deterministic fixtures:
```csharp
// A short segment completes before a legacy 10 s horizon.
Verification.True(shortSchedule.TotalDurationSeconds < 10d, "short segment derives its own T_end");
// A longer segment takes longer without changing DistanceHorizonMeters/TimeHorizonSeconds.
Verification.True(longSchedule.TotalDurationSeconds > shortSchedule.TotalDurationSeconds,
"duration grows from s_end and limits");
// Curvature transitions and the stop boundary are represented in the adaptive schedule.
Verification.True(longSchedule.KnotTimes.Count <= configuration.Scheduling.MaximumOptimizationKnotCount,
"adaptive schedule respects knot cap");
Verification.NearlyEqual(path.Points[path.Points.Count - 1].PathS,
longSchedule.ReferencePathS[longSchedule.ReferencePathS.Count - 1], "schedule reaches s_end");
Verification.NearlyEqual(0d,
longSchedule.ReferenceSpeedMetersPerSecond[longSchedule.ReferenceSpeedMetersPerSecond.Count - 1],
"schedule stops at s_end");
```
Set `MaximumOptimizationKnotCount = 4` on a path that needs more intervals and assert exact status `FullSegmentResourceLimitExceeded`, no schedule, and a diagnostic containing required versus configured knots.
- [ ] **Step 2: Run RED**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-model
```
Expected: compile failure for the new schedule types.
- [ ] **Step 3: Implement the immutable schedule contract**
Use this public shape:
```csharp
public sealed class LongitudinalKnotSchedule
{
public LongitudinalKnotSchedule(IReadOnlyList<double> knotTimes,
IReadOnlyList<double> referencePathS,
IReadOnlyList<double> referenceSpeedMetersPerSecond, bool isAdaptive);
public IReadOnlyList<double> KnotTimes { get; }
public IReadOnlyList<double> ReferencePathS { get; }
public IReadOnlyList<double> ReferenceSpeedMetersPerSecond { get; }
public double TotalDurationSeconds { get; }
public bool IsAdaptive { get; }
public static LongitudinalKnotSchedule CreateRolling(
double timeHorizonSeconds, double timeStepSeconds);
}
```
Validate equal counts, exact zero first time/path, strictly increasing times, nondecreasing PathS, finite nonnegative speed, and exact zero terminal speed for adaptive schedules.
- [ ] **Step 4: Extract a PathSpeedLimitBuilder overload that does not require knot times**
Add:
```csharp
public EmPlanningStatus Build(LateralPath path, TravelDirection direction,
double initialProgressSpeedMetersPerSecond, EmTerminalType terminalType,
EmPlannerConfiguration configuration, out PathSpeedLimit speedLimit,
out string failureReason);
```
Keep the existing `Build(LongitudinalPlanningInput, ...)` as a delegating compatibility overload. This allows the service to build the physical PathS envelope before it creates the optimization schedule.
- [ ] **Step 5: Implement full-segment schedule derivation**
Use one forward/backward envelope pass over the existing `PathSpeedLimit.PathS` stations:
1. Start with the hard maximum-speed array.
2. Forward-propagate a jerk-limited reachable speed from `v0/a0`, never exceeding desired speed or the hard PathS envelope.
3. Backward-propagate the jerk-limited stopping envelope to exact `(s_end, v=0, a=0)` using `JerkLimitedStoppingMath` for every remaining-distance check.
4. Intersect forward, backward, curvature, and desired-speed limits.
5. Integrate `dt = 2*ds/(v0+v1)` on nonzero-speed intervals; use the jerk-limited acceleration/deceleration phase duration when either endpoint speed is zero.
6. Add exact breakpoints for startup, speed-limit changes, brake onset, terminal stop, and the zero-speed hold.
7. Refine any interval exceeding either configured maximum `dt` or maximum `ds`.
8. If refinement needs more than `MaximumOptimizationKnotCount`, return `FullSegmentResourceLimitExceeded`.
The builder signature is:
```csharp
public EmPlanningStatus TryBuild(LateralPath path, PathSpeedLimit speedLimit,
double initialProgressSpeedMetersPerSecond,
double initialAccelerationMetersPerSecondSquared,
double desiredSpeedMetersPerSecond, EmPlannerConfiguration configuration,
out LongitudinalKnotSchedule schedule, out string failureReason);
```
Do not clamp a result to `s_end` after integration. A schedule is accepted only if its final reference state is exactly `s_end/0/0` within configured kinematic tolerance.
- [ ] **Step 6: Make all ST components consume `input.KnotSchedule`**
Add both `EmPlanningScope planningScope` and the schedule to `LongitudinalPlanningInput`, copy them into immutable `PlanningScope` and `KnotSchedule` properties, and reject an adaptive schedule supplied to rolling scope or a rolling schedule supplied to full scope. Replace every direct call to:
```csharp
LongitudinalCandidate.CreateKnotTimes(
input.Configuration.Scheduling.TimeHorizonSeconds,
input.Configuration.Scheduling.OutputTimeStepSeconds)
```
with:
```csharp
input.KnotSchedule.KnotTimes
```
This includes constraints, initial iterate creation, exact-stop stabilization, previous-trajectory resampling, and solution validation. The rolling service path creates `LongitudinalKnotSchedule.CreateRolling(...)`; the full path calls `FullDirectionSegmentScheduleBuilder`.
- [ ] **Step 7: Run GREEN and prove publication sampling is independent**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-model
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-integration
```
Expected: both PASS. Add one assertion that changing `OutputTimeStepSeconds` from 0.1 to 0.05 doubles publication density but leaves `KnotSchedule.KnotTimes.Count` unchanged.
- [ ] **Step 8: Commit adaptive scheduling**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalKnotSchedule.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/FullDirectionSegmentScheduleBuilder.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/PathSpeedLimitBuilder.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalPlanningInput.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalConstraintBuilder.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalSolutionValidator.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/SequentialLongitudinalOptimizer.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalPreviousTrajectorySeedBuilder.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Facade/EmPlanningService.cs ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalModelChecks.cs ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalIntegrationChecks.cs
git commit -m "feat: derive adaptive full-segment ST schedule"
```
---
### Task 4: Fix static-start progress and reject false success
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/SequentialLongitudinalOptimizer.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalObjectiveBuilder.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalSolutionValidator.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalPlanningResult.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Facade/EmPlanningService.cs`
- Test: `ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalModelChecks.cs`
- Test: `ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalIntegrationChecks.cs`
- Test: `ClumsyPilot/tests/EMPlannerVerificationHost/EmPlanningServiceChecks.cs`
**Interfaces:**
- Consumes: desired speeds and adaptive reference profile from Tasks 1 and 3.
- Produces: a nonzero progress seed from `v0 = 0` when the segment is feasible.
- Produces: exact `NoProgress` failure with no candidate/trajectory publication.
- [ ] **Step 1: Write the failing static-start regression**
Use a clear 5 m forward segment, `v0 = 0`, `a0 = 0`, desired/max speed 1.0 m/s, and full scope. Assert:
```csharp
Verification.True(result.Status == EmPlanningStatus.Success ||
result.Status == EmPlanningStatus.SuccessWithFallback, "static start succeeds");
Verification.True(result.Trajectory.Points.Any(point => point.PathS > 0.05d),
"static start makes measurable progress");
Verification.True(result.Trajectory.Points.Any(point => point.SignedLongitudinalVelocity > 0.05d),
"static start accelerates");
Verification.NearlyEqual(0d, result.Trajectory.Points[^1].SignedLongitudinalVelocity,
"full segment stops at the terminal boundary");
```
Add a direct validator fixture containing all-zero `S/U/A/J` over a nonzero full segment. Expect `NoProgress`. Add allowed zero-progress fixtures for “already within terminal tolerance” and gear-switch stop hold.
- [ ] **Step 2: Run RED against the original symptom**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-integration
```
Expected: the static-start assertion fails because the current envelope seed targets the initial zero speed.
- [ ] **Step 3: Drive the seed and objective toward the feasible reference speed**
In `CreateEnvelopeSeed`, replace the current target:
```csharp
double targetSpeed = Math.Min(input.InitialProgressSpeedMetersPerSecond, speedLimitAtS);
```
with:
```csharp
double desiredSpeed = input.Direction == TravelDirection.Forward
? configuration.DesiredForwardSpeedMetersPerSecond
: configuration.DesiredReverseSpeedMetersPerSecond;
double scheduleSpeed = input.KnotSchedule.ReferenceSpeedMetersPerSecond[index];
double targetSpeed = Math.Min(desiredSpeed, Math.Min(scheduleSpeed, speedLimitAtS));
```
In `LongitudinalObjectiveBuilder`, track the same reference rather than only the current linearization-point hard limit. Preserve acceleration, jerk, previous-trajectory, and exact-stop terms.
- [ ] **Step 4: Add the independent no-progress gate**
After physical/dynamics checks and before returning a canonical candidate, reject full-scope output when:
```csharp
bool requiresProgress = input.PathUpperBoundS >
input.Configuration.Validation.TerminalPositionToleranceMeters;
double achievedProgress = candidate.S[candidate.S.Count - 1] - candidate.S[0];
if (input.PlanningScope == EmPlanningScope.FullDirectionSegment &&
requiresProgress && achievedProgress <= input.Configuration.Validation.SpatialToleranceMeters)
{
failureReason = "NoProgress: a nonterminal full direction segment produced zero progress.";
failureStatus = EmPlanningStatus.NoProgress;
return false;
}
```
Extend the validator/result seam to return a status as well as a reason so `NoProgress` is not collapsed into generic `LongitudinalInfeasible`.
- [ ] **Step 5: Run GREEN and the service gate**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-model
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-integration
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-planning-service
```
Expected: all PASS; the explicit all-zero service fixture has `Trajectory == null` and diagnostic `NoProgress`.
- [ ] **Step 6: Commit the static-start fix**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/SequentialLongitudinalOptimizer.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalObjectiveBuilder.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalSolutionValidator.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalPlanningResult.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Facade/EmPlanningService.cs ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalModelChecks.cs ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalIntegrationChecks.cs ClumsyPilot/tests/EMPlannerVerificationHost/EmPlanningServiceChecks.cs
git commit -m "fix: accelerate EM trajectories from rest"
```
---
### Task 5: Enforce terminal world pose and publication limits
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Validation/EmTrajectoryValidator.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Facade/EmPlanningService.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Trajectory/TrajectorySampleSchedule.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Trajectory/EmTrajectoryAssembler.cs`
- Test: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryChecks.cs`
- Test: `ClumsyPilot/tests/EMPlannerVerificationHost/EmPlanningServiceChecks.cs`
**Interfaces:**
- Produces: `EmTrajectoryValidator.Validate(..., Pose2D terminalPose, ...)`.
- Produces normalized yaw helper with result in `[-π, π]`.
- Produces publication sample-count gate using `MaximumPublishedSampleCount`.
- Maps pose mismatch to `EmPlanningStatus.TerminalPoseMismatch` and sample overflow to `FullSegmentResourceLimitExceeded`.
- [ ] **Step 1: Write failing position, yaw-wrap, and sample-limit tests**
Create terminal trajectories at 2.9 cm and 3.1 cm position error; create yaw cases `179°` versus `-179°`, `4.9°`, and `5.1°`. Assert only the outside cases fail. Add a trajectory whose output sampling would need one sample above `MaximumPublishedSampleCount` and assert no partial trajectory is assembled.
- [ ] **Step 2: Run RED**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory
```
Expected: compile failure because terminal pose is not part of validation.
- [ ] **Step 3: Add normalized terminal-pose validation**
Use the final Local G2 point as the expected world pose and validate the real terminal anchor:
```csharp
private static double NormalizeAngle(double angle)
{
while (angle > Math.PI) angle -= 2d * Math.PI;
while (angle < -Math.PI) angle += 2d * Math.PI;
return angle;
}
double dx = terminal.X - terminalPose.X;
double dy = terminal.Y - terminalPose.Y;
double positionError = Math.Sqrt(dx * dx + dy * dy);
double yawError = Math.Abs(NormalizeAngle(terminal.Yaw - terminalPose.Heading));
if (positionError > configuration.Validation.TerminalPositionToleranceMeters ||
yawError > configuration.Validation.TerminalYawToleranceRadians)
{
return Reject(EmTrajectoryValidationFailure.TerminalPoseMismatch, terminalIndex,
"TerminalPoseMismatch: position=" + Format(positionError) +
";yaw=" + Format(yawError));
}
```
Do this only for real `Goal` and `GearSwitchApproach` anchors, not rolling-safety window ends.
- [ ] **Step 4: Enforce exact publication cardinality**
Before allocating published samples, compute the required count including the exact terminal anchor and hold samples. If it exceeds `MaximumPublishedSampleCount`, return `FullSegmentResourceLimitExceeded`; do not remove the terminal anchor, increase `OutputTimeStepSeconds`, or truncate.
Keep jerk interval semantics: the assembler uses interval `i` for point `i` only while `i + 1 < N`; the terminal point stores no successor-interval jerk and the chart builder publishes only `N-1` values.
- [ ] **Step 5: Run GREEN**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-planning-service
```
Expected: both PASS, including yaw wrap and boundary-outside rejection.
- [ ] **Step 6: Commit publication gates**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/EMPlanner/Validation/EmTrajectoryValidator.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Facade/EmPlanningService.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Trajectory/TrajectorySampleSchedule.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Trajectory/EmTrajectoryAssembler.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryChecks.cs ClumsyPilot/tests/EMPlannerVerificationHost/EmPlanningServiceChecks.cs
git commit -m "feat: validate EM terminal world pose"
```
---
### Task 6: Make MovementTest one-shot per direction segment
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationContracts.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPipeline.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationStaticSnapshotBuilder.cs`
- Test: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationSettingsChecks.cs`
- Test: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs`
- Test: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationSegmentChecks.cs`
**Interfaces:**
- Produces: `TrajectoryObservationSettings.PlanningScope`, defaulting to `FullDirectionSegment`.
- Produces: one plan attempt per active segment, reset only after a confirmed segment transition.
- Preserves: stop hold plus three correctly signed speed samples before `N -> N+1`.
- [ ] **Step 1: Write failing settings and cadence tests**
Assert the validated snapshot freezes `FullDirectionSegment`, Web defaults on, Native Painter defaults off, and `OutputTimeStepSeconds == 0.1`. Drive 20 observer ticks after one successful full plan and assert planning starts once, while observation and Web publication continue on every configured cadence. Then complete the existing stop-hold/three-sample handshake and assert exactly one new plan starts for segment N+1.
- [ ] **Step 2: Run RED**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
```
Expected: failures because the current controller delegates to rolling coordinator cadence.
- [ ] **Step 3: Add scope to settings and request creation**
Add:
```csharp
public EmPlanningScope PlanningScope { get; set; } = EmPlanningScope.FullDirectionSegment;
```
Copy and validate it. In `TrajectoryObservationController.StartCycle`, pass the scope into `EmPlanningRequest`. Apply the approved observer defaults in the MovementTest entry:
```csharp
public bool UseFullDirectionSegmentPlanning = true;
public double OutputTimeStepSeconds = 0.10d;
public bool EnableWebVisualization = true;
public bool EnableNativePainterVisualization = false;
```
Keep `TimeHorizonSeconds` visible only as a rolling compatibility field and label it accordingly. Do not preserve the experimental 20 s/500 m values as the mechanism for full planning.
- [ ] **Step 4: Gate one-shot planning in the controller**
Track `plannedSegmentIndex` and `planAttemptedForActiveSegment` in the controller:
```csharp
public bool ShouldStartCycle(DateTimeOffset now)
{
if (settings.PlanningScope == EmPlanningScope.FullDirectionSegment)
return !planAttemptedForActiveSegment;
return coordinator.ShouldStartCycle(now);
}
```
Set the attempt flag before launching the task so a slow solver cannot trigger duplicates. Reset it only after `TryAdvanceSegment` confirms the real transition or when a new MovementTest session constructs a new controller. A failed full plan remains failed and visible; no automatic rolling retry occurs.
- [ ] **Step 5: Keep the existing gear-switch safety state machine unchanged**
Run existing stop-hold and signed-speed tests plus the new one-shot assertions. The waiting notice must remain explicit and the active highlight must remain on segment N until all conditions pass.
- [ ] **Step 6: Run GREEN and source audit**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
rg -n "SendXYThSpeed|SendMotion|DriveStop|PredefinedDriveStop|AccumulateSpeed|SetGear|SetBrake" ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest
```
Expected: `PASS trajectory-observation`; `rg` returns no actuator call in observer runtime sources.
- [ ] **Step 7: Commit one-shot observation**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationContracts.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPipeline.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationStaticSnapshotBuilder.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationSettingsChecks.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationSegmentChecks.cs
git commit -m "feat: observe one full EM direction segment"
```
---
### Task 7: Publish unambiguous path, chart, and boundary semantics
**Files:**
- Modify: `ClumsyPilot/TrajectoryPlanningVisualization/Contracts/VisualizationCharts.cs`
- Modify: `ClumsyPilot/TrajectoryPlanningVisualization/Contracts/VisualizationGeometry.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationStaticSnapshotBuilder.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationDynamicSnapshotBuilder.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationKinematicChartBuilder.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs`
- Test: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationVisualizationChecks.cs`
- Test: `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/ContractChecks.cs`
- Test: `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/SampleSnapshotFactory.cs`
**Interfaces:**
- Produces: `VisualizationChartAnnotation` for vertical `s_end` lines and labeled points.
- Produces marker kinds `plan-start`, `gear-switch-end`, and `final-goal`.
- Produces distinct polyline kinds `coarse`, `local-g2`, `active-segment`, `previous`, and `current` with no duplicate `current-horizon` in full mode.
- Chart axes remain `ReferenceS (m)`, `PathS (m)`, `t (s)`, `l (m)`, `v (m/s)`, `a (m/s²)`, `j (m/s³)`, `κ (m⁻¹)`, and `ω (rad/s)`.
- [ ] **Step 1: Write failing snapshot semantics tests**
Assert that a full-mode snapshot contains exactly one current trajectory polyline, contains both coarse and Local G2 paths, has distinct `s_end` and vehicle/plan-start markers, and uses the correct terminal kind for gear switch versus final goal.
For charts assert:
```csharp
Verification.Equal("ReferenceS (m)", ls.XAxisLabel, "LS uses shared reference S");
Verification.Equal("PathS (m)", st.YAxisLabel, "ST uses actual path arc length");
Verification.Equal(trajectory.Points.Count - 1, jerk.Series[0].Points.Count,
"jerk has N-1 interval samples");
Verification.True(st.Annotations.Any(a => a.Kind == "s-end"), "ST marks s_end");
Verification.True(curvatureS.Series[0].Points.Any(p => Math.Abs(p.Y) > 1e-6),
"curvature-distance uses vehicle curvature data");
```
Ensure the curvature hard-limit series uses `VehicleParameters.MaximumCurvaturePerMeter`, not `MaximumCurvatureRatePerMeterPerSecond`.
- [ ] **Step 2: Run RED**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
```
Expected: failures for duplicate current horizon, missing annotations, and incorrect curvature limit.
- [ ] **Step 3: Add chart annotations without breaking old consumers**
Add:
```csharp
public sealed class VisualizationChartAnnotation
{
public VisualizationChartAnnotation(string id, string kind, string labelChinese,
double x, double? y = null);
public string Id { get; }
public string Kind { get; }
public string LabelChinese { get; }
public double X { get; }
public double? Y { get; }
}
```
Extend `VisualizationChart` with immutable `Annotations`. Keep the old constructor as a delegating overload with an empty annotation list for backward compatibility.
- [ ] **Step 4: Correct snapshot builders**
In full mode, remove `current-horizon`; in rolling mode publish it only if it is a genuinely different subset. Add current trajectory once above static paths. Add `s_end` annotations to world, LS, and ST. Label a direction boundary as `换向点 N / s_end` only for gear switches; use a distinct final-goal label and marker.
Extend `TrajectoryObservationDynamicSnapshotBuilder.Build(...)` with an immutable `VehicleParameters vehicle` argument. Pass `bootstrap.Vehicle` from the MovementTest publication callback into the dynamic builder, then into the kinematic chart builder, so the curvature charts use the real vehicle curvature limit. Change yaw-rate axis text to `ω (rad/s)`.
- [ ] **Step 5: Run GREEN**
Run both commands from Step 2. Expected: both hosts PASS.
- [ ] **Step 6: Commit observation semantics**
```powershell
git add -- ClumsyPilot/TrajectoryPlanningVisualization/Contracts/VisualizationCharts.cs ClumsyPilot/TrajectoryPlanningVisualization/Contracts/VisualizationGeometry.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationStaticSnapshotBuilder.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationDynamicSnapshotBuilder.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationKinematicChartBuilder.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationVisualizationChecks.cs ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/ContractChecks.cs ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/SampleSnapshotFactory.cs
git commit -m "fix: publish distinct EM observation semantics"
```
---
### Task 8: Add real DOM/SVG tests and repair the existing Web page
**Files:**
- Create: `ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom/package.json`
- Create: `ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom/package-lock.json`
- Create: `ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom/dashboard.dom.test.mjs`
- Modify: `ClumsyPilot/TrajectoryPlanningVisualization/Web/index.html`
- Modify: `ClumsyPilot/TrajectoryPlanningVisualization/Web/app.css`
- Modify: `ClumsyPilot/TrajectoryPlanningVisualization/Web/app.js`
- Modify: `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/WebAssetChecks.cs`
**Interfaces:**
- Test-only dependency: exact `jsdom` 29.1.1; no npm package is copied into the production plugin.
- Test hook: when `window.__TRAJECTORY_VISUALIZATION_TEST__ === true`, `app.js` exposes immutable-input render hooks and does not start fetch/SSE.
- Produces actual DOM assertions for tab visibility, y ticks, units, paths, markers, empty state, and scientific line classes.
- [ ] **Step 1: Add the test package and failing DOM tests**
Create `package.json`:
```json
{
"name": "trajectory-planning-visualization-web-dom-tests",
"private": true,
"type": "module",
"scripts": { "test": "node --test dashboard.dom.test.mjs" },
"devDependencies": { "jsdom": "29.1.1" },
"engines": { "node": ">=24" }
}
```
Run `npm install --package-lock-only` in that directory to create the lock file. In `dashboard.dom.test.mjs`, load the real `index.html` and `app.js`, create `JSDOM` with `runScripts: "outside-only"` and `pretendToBeVisual: true`, stub Canvas `getContext`, set fixed element rectangles, set the test flag, and evaluate the real script.
Test these behaviors with actual DOM queries:
```javascript
assert.equal(document.querySelector("#overview").hidden, false);
click(document.querySelector('button[data-tab="ls-st"]'));
assert.equal(document.querySelector("#overview").hidden, true);
assert.equal(document.querySelector("#ls-st").hidden, false);
assert.equal(document.querySelector("#kinematics").hidden, true);
assert.ok(document.querySelectorAll("#st .axis-tick-y").length >= 4);
assert.equal(document.querySelector("#st .axis-label-y").textContent, "PathS (m)");
assert.ok(document.querySelector("#world-overlay .world-current"));
assert.ok(document.querySelector("#world-overlay .marker-vehicle"));
```
Add an empty-snapshot test expecting a visible Chinese reason instead of an empty world canvas.
- [ ] **Step 2: Run RED**
```powershell
npm test --prefix ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom
```
Expected: failures because hidden sections are overridden by CSS, y tick labels are absent, and semantic classes/empty messages are missing.
- [ ] **Step 3: Fix tab visibility and empty-state behavior**
Add the high-specificity CSS rule before panel grids:
```css
main > section[hidden] { display: none !important; }
#ls-st:not([hidden]), #kinematics:not([hidden]) {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(360px, 1fr));
gap: 18px;
}
```
In `renderWorld`, count valid static/dynamic path points. If none exist, add a `.world-empty-state` text element naming the missing source. Do not fabricate fallback points.
- [ ] **Step 4: Render y ticks, units, and stable nonzero domains**
Split tick generation into `niceDomain`, `formatTick`, and `renderAxes`. Create both x and y text nodes:
```javascript
const yt = svg("text", {
x: left - 7, y, class: "axis-tick axis-tick-y",
"text-anchor": "end", "dominant-baseline": "middle"
});
yt.textContent = formatTick(domain.y1 - tick * (domain.y1 - domain.y0) / 4);
```
Use `.axis-label-x` and `.axis-label-y` classes. For constant data, pad around the actual constant with `max(abs(value)*0.05, unitFloor)`; never force a nonzero constant onto a zero-centered 1-unit range.
- [ ] **Step 5: Apply semantic path order and thin paper style**
Dispatch line class by `kind`, not by static/dynamic origin. Draw in this exact order: coarse, Local G2, direction segments, previous, current, annotations/vehicle. Use:
```css
.world-coarse { stroke: #9aa1a8; stroke-opacity: .48; stroke-dasharray: 5 4; stroke-width: .85; }
.world-local-g2 { stroke: #4f5963; stroke-width: .95; }
.world-segment-active { stroke: #5d91bd; stroke-opacity: .62; stroke-width: 1.0; }
.world-previous { stroke: #8d959d; stroke-dasharray: 5 4; stroke-width: .85; }
.world-current { stroke: #1769aa; stroke-width: 1.15; }
```
Render vehicle pose as a small oriented outline plus thin heading line using `frame.vehiclePose.heading`; render plan start, gear-switch `s_end`, and final goal with different marker classes. Do not use a large filled triangle.
- [ ] **Step 6: Expose safe test hooks and keep production boot unchanged**
At the end of `app.js`:
```javascript
if (window.__TRAJECTORY_VISUALIZATION_TEST__ === true) {
window.__trajectoryVisualizationTestHooks = Object.freeze({
setBootstrap(snapshot) { state.staticSnapshot = snapshot; },
receiveFrame,
renderActiveTab,
installTabs
});
installTabs();
} else {
boot();
}
```
Hooks accept snapshots but never expose mutable internal state. Production still performs tokenized fetch and SSE exactly once.
- [ ] **Step 7: Run GREEN plus embedded-asset host**
```powershell
npm test --prefix ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom
dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
```
Expected: Node tests report all tests passed; .NET host prints `PASS trajectory-planning-visualization`.
- [ ] **Step 8: Commit Web repair and DOM tests**
```powershell
git add -- ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom/package.json ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom/package-lock.json ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom/dashboard.dom.test.mjs ClumsyPilot/TrajectoryPlanningVisualization/Web/index.html ClumsyPilot/TrajectoryPlanningVisualization/Web/app.css ClumsyPilot/TrajectoryPlanningVisualization/Web/app.js ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/WebAssetChecks.cs
git commit -m "fix: repair EM observation web charts"
```
---
### Task 9: Add per-chart viewport zoom without changing data
**Files:**
- Modify: `ClumsyPilot/TrajectoryPlanningVisualization/Web/index.html`
- Modify: `ClumsyPilot/TrajectoryPlanningVisualization/Web/app.css`
- Modify: `ClumsyPilot/TrajectoryPlanningVisualization/Web/app.js`
- Modify: `ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom/dashboard.dom.test.mjs`
**Interfaces:**
- Produces per-chart `chartViewports[id]` containing only visible x/y domains.
- Produces actions `box zoom`, `wheel zoom`, `reset`, and `fullscreen`.
- Consumes immutable chart snapshot arrays and never writes to them.
- [ ] **Step 1: Write failing interaction tests**
Freeze the input chart and point arrays, dispatch wheel and pointer events, and assert the rendered axis tick labels and path coordinates change while the serialized input remains byte-for-byte identical. Click reset and assert the original tick domain returns. Click fullscreen and assert only the selected `.chart` receives the fullscreen class/API request.
- [ ] **Step 2: Run RED**
```powershell
npm test --prefix ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom
```
Expected: zoom controls and viewport state are missing.
- [ ] **Step 3: Implement viewport-only controls**
Add a compact `.chart-tools` toolbar in each chart host. Store domains as copied numbers:
```javascript
state.chartViewports[id] = { x0, x1, y0, y1 };
```
Wheel zoom uses the pointers data coordinate as its anchor. Box zoom ignores drags shorter than 4 CSS pixels. Reset deletes only `state.chartViewports[id]`. Fullscreen uses `requestFullscreen()` with a CSS fallback for the jsdom/test environment. SSE frame arrival re-renders using the active viewport if it is still finite; it does not alter the snapshot.
- [ ] **Step 4: Run GREEN**
```powershell
npm test --prefix ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom
dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
```
Expected: all DOM tests and embedded-asset checks PASS.
- [ ] **Step 5: Commit chart navigation**
```powershell
git add -- ClumsyPilot/TrajectoryPlanningVisualization/Web/index.html ClumsyPilot/TrajectoryPlanningVisualization/Web/app.css ClumsyPilot/TrajectoryPlanningVisualization/Web/app.js ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom/dashboard.dom.test.mjs
git commit -m "feat: add observation chart viewport zoom"
```
---
### Task 10: Correct Native Painter geometry and semantics
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPresentation.cs`
- Test: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs`
- Test: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationVisualizationChecks.cs`
**Interfaces:**
- Consumes the same coarse/Local G2/current/previous/s_end semantics from Task 7.
- Produces small vehicle/pose outlines and thin heading rays with `endArrow: false`.
- LS x-axis is `ReferenceS (m)`; ST is `t (s)` versus `PathS (m)`; all world coordinates remain x/y equal-scale millimetres.
- [ ] **Step 1: Write failing source/geometry checks**
Add checks that `DrawPose` no longer calls `endArrow: true`, that pose outlines contain four rotated vehicle corners, and that world paths use one millimetres-per-metre factor for both x and y. Assert LS/ST labels and `s_end`/gear-switch labels match the Web semantics.
- [ ] **Step 2: Run RED**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
```
Expected: failure on the current large arrow and old `path-S`/`T-S` labels.
- [ ] **Step 3: Replace the black triangle with scaled pose geometry**
Draw a four-corner outline based on the observed vehicle length/width and a thin heading ray no longer than half the vehicle length:
```csharp
worldPainter.DrawLine(color, x, y, headingX, headingY, endArrow: false, width: 1);
```
Use thin line widths for coarse, Local G2, current, and previous paths. Mark current vehicle, plan start, gear-switch `s_end`, and final goal distinctly. Keep all existing Painter windows and lifecycle cleanup; do not add new Painter layers.
- [ ] **Step 4: Correct Painter LS/ST axes**
Use shared world `ReferenceS` for LS horizontal values and actual trajectory `PathS` for ST vertical values. Calculate plot scales from data bounds with the same x/y unit conversion. Show the explicit waiting notice during gear handoff.
- [ ] **Step 5: Run GREEN**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
```
Expected: `PASS trajectory-observation`.
- [ ] **Step 6: Commit Painter correctness**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPresentation.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationVisualizationChecks.cs
git commit -m "fix: correct EM observation painter geometry"
```
---
### Task 11: Update operator documentation and run complete acceptance
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/README.md`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md`
- Modify: `ClumsyPilot/TrajectoryPlanningVisualization/README.md`
- Create after safe execution: `docs/superpowers/handoffs/em-observation-web/phase-08-vehicle.md`
**Interfaces:**
- Documents the full-versus-rolling switch, derived `s_end/T_end`, 1.0/0.5 m/s limits, terminal pose tolerances, chart units, zoom, and `OBSERVE_ONLY` safety boundary.
- Produces fresh automated evidence and either a passed or explicitly pending/blocked vehicle checklist.
- [ ] **Step 1: Update the three READMEs**
Document these exact operator facts:
- `FullDirectionSegment` is the MovementTest default and performs one optimization per active direction segment.
- `DistanceHorizonMeters` and `TimeHorizonSeconds` apply only to `RollingHorizon` truncation.
- `s_end` comes from actual Local G2 `PathS`; `T_end` is derived from feasible acceleration/cruise/stopping behavior.
- Forward desired/max is 1.0 m/s; reverse desired/max is 0.5 m/s.
- Successful real boundaries require stop plus 3 cm position and 5° normalized yaw tolerances.
- Web is primary, Native Painter is optional correctness/audit output.
- Each charts axes/units, the `N-1` jerk rule, semantic path line styles, boundary markers, and zoom controls.
- Browser closure does not stop observation; MovementTest stop reclaims HTTP/SSE/port/Painter.
- No part of MovementTest writes chassis, steering, brake, motor, or gear commands.
- [ ] **Step 2: Run all fresh automated verification**
```powershell
npm test --prefix ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom
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
rg -n "SendXYThSpeed|SendMotion|DriveStop|PredefinedDriveStop|AccumulateSpeed|SetGear|SetBrake" ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest
git diff --check
```
Expected: Node tests pass; both visualization and observation hosts print PASS; every `em-all` component prints PASS; build exits 0; actuator audit has no observer-runtime matches; `git diff --check` reports no new whitespace errors. Record pre-existing warnings separately.
- [ ] **Step 3: Run deterministic local Web smoke**
```powershell
dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj -- --smoke-seconds 60
```
Open the printed tokenized URL. Verify the four tabs, nonblank world overview, nonhorizontal increasing ST for the moving fixture, y ticks/units, thin path comparison, `s_end`/gear/final markers, vehicle pose, chart zoom/reset/fullscreen, stale state, and port release. This smoke uses synthetic snapshots only.
- [ ] **Step 4: Run the supervised vehicle checklist only when safe**
In a supervised safe environment, keep MovementTest read-only and verify:
1. Web enabled, Native Painter disabled by default, and UI/console/page show `OBSERVE_ONLY`.
2. The current full direction segment produces complete LS/ST; static start accelerates, reaches feasible cruise speed, begins braking from the envelope, and stops at the real boundary.
3. ST `PathS` increases; jerk has `N-1` intervals and no terminal successor or false `JerkLimitExceeded`.
4. World handoff displays `DeltaPosition`, shared-direction `DeltaReferenceS`, `DeltaV`, and `DeltaA`, never a direct comparison of unrelated local PathS.
5. At a true reversal, N remains highlighted until continuous stop hold and three correctly signed speed samples pass; waiting text is explicit.
6. Coarse, Local G2, current trajectory, vehicle pose, `s_end`, gear switch, and final goal are visually distinct.
7. Closing/slow-loading the browser does not stop observation.
8. Stopping MovementTest releases HTTP/SSE, port, and any Painter; source/log audit shows no hardware writes.
If any item cannot be executed safely, record it as not run. Do not claim overall vehicle acceptance.
- [ ] **Step 5: Write the vehicle handoff from fresh evidence**
If every vehicle item passes, create `phase-08-vehicle.md` with status “完成”, environment prerequisites, execution time, per-item results, logs/screenshots, warnings, and conclusion. If a regression appears, create the same file with status “阻塞”, the first valid failure, exact reproduction, and safety impact; do not expand the fix in the vehicle session.
- [ ] **Step 6: Commit docs and, only when available, vehicle evidence separately**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/README.md ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md ClumsyPilot/TrajectoryPlanningVisualization/README.md
git commit -m "docs: explain full-direction EM observation"
```
After actual supervised acceptance only:
```powershell
git add -- docs/superpowers/handoffs/em-observation-web/phase-08-vehicle.md
git commit -m "docs: record EM visualization vehicle acceptance"
```
Do not create or commit the vehicle handoff as “完成” from automated or synthetic evidence alone.