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

60 KiB
Raw Blame History

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 .
  • 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:

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
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:

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:

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
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:

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
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:

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
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
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:

// 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
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:

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:

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:

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:

LongitudinalCandidate.CreateKnotTimes(
    input.Configuration.Scheduling.TimeHorizonSeconds,
    input.Configuration.Scheduling.OutputTimeStepSeconds)

with:

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
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
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:

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
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:

double targetSpeed = Math.Min(input.InitialProgressSpeedMetersPerSecond, speedLimitAtS);

with:

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:

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
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
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
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:

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
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
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
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:

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:

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:

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
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
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:

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
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:

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
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:

{
  "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:

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
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:

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:

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:

.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:

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
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
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
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:

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
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
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
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:

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
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation

Expected: PASS trajectory-observation.

  • Step 6: Commit Painter correctness
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

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
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
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:

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.