# 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 segment’s 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 -- ` 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 knotTimes, IReadOnlyList referencePathS, IReadOnlyList referenceSpeedMetersPerSecond, bool isAdaptive); public IReadOnlyList KnotTimes { get; } public IReadOnlyList ReferencePathS { get; } public IReadOnlyList 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 pointer’s 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 chart’s 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.