60 KiB
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.
FullDirectionSegmentplans 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_endfrom actual Local G2PathSand derivesT_end;DistanceHorizonMetersandTimeHorizonSecondsdo not truncate full mode. - Forward desired/hard maximum speed is
1.0 m/s; reverse desired/hard maximum speed is0.5 m/s. - Terminal world-position error must be at most
0.03 m; normalized yaw error must be at most5°. - Published trajectory sampling is
0.1 sfor MovementTest and is independent of adaptive optimization knots. - Jerk contains exactly
N-1interval values forNtrajectory 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: derivesT_end, adaptive knots, and reference progress/speed from Local G2PathSand 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.RollingHorizonandEmPlanningScope.FullDirectionSegment. -
Produces:
EmPlanningRequest.PlanningScopeandEmTrajectoryMetadata.PlanningScope. -
Produces configuration properties used by Tasks 2-5:
DesiredForwardSpeedMetersPerSecond,DesiredReverseSpeedMetersPerSecond,MaximumOptimizationTimeStepSeconds,MaximumOptimizationSpatialStepMeters,MaximumOptimizationKnotCount,MaximumPublishedSampleCount,TerminalPositionToleranceMeters, andTerminalYawToleranceRadians. -
Produces status values
NoProgress,TerminalPoseMismatch, andFullSegmentResourceLimitExceeded. -
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:
EmPlanningScopefrom Task 1. -
Produces:
PlanningHorizonSelector.Select(..., EmPlanningScope planningScope, ...). -
Guarantees: full mode returns the real
GoalorGearSwitchboundary withExactStopAtBoundary; 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
LongitudinalKnotSchedulewithKnotTimes,ReferencePathS,ReferenceSpeedMetersPerSecond,TotalDurationSeconds, andIsAdaptive. -
Produces
FullDirectionSegmentScheduleBuilder.TryBuild(...)returningEmPlanningStatus.SuccessorFullSegmentResourceLimitExceeded. -
LongitudinalPlanningInput.PlanningScopecarries the request scope into the independent validator. -
LongitudinalPlanningInput.KnotSchedulebecomes 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:
- Start with the hard maximum-speed array.
- Forward-propagate a jerk-limited reachable speed from
v0/a0, never exceeding desired speed or the hard PathS envelope. - Backward-propagate the jerk-limited stopping envelope to exact
(s_end, v=0, a=0)usingJerkLimitedStoppingMathfor every remaining-distance check. - Intersect forward, backward, curvature, and desired-speed limits.
- Integrate
dt = 2*ds/(v0+v1)on nonzero-speed intervals; use the jerk-limited acceleration/deceleration phase duration when either endpoint speed is zero. - Add exact breakpoints for startup, speed-limit changes, brake onset, terminal stop, and the zero-speed hold.
- Refine any interval exceeding either configured maximum
dtor maximumds. - If refinement needs more than
MaximumOptimizationKnotCount, returnFullSegmentResourceLimitExceeded.
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 = 0when the segment is feasible. -
Produces: exact
NoProgressfailure 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.TerminalPoseMismatchand sample overflow toFullSegmentResourceLimitExceeded. -
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 toFullDirectionSegment. -
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:
VisualizationChartAnnotationfor verticals_endlines and labeled points. -
Produces marker kinds
plan-start,gear-switch-end, andfinal-goal. -
Produces distinct polyline kinds
coarse,local-g2,active-segment,previous, andcurrentwith no duplicatecurrent-horizonin 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
jsdom29.1.1; no npm package is copied into the production plugin. -
Test hook: when
window.__TRAJECTORY_VISUALIZATION_TEST__ === true,app.jsexposes 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, andfullscreen. -
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 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
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 ist (s)versusPathS (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, andOBSERVE_ONLYsafety 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:
-
FullDirectionSegmentis the MovementTest default and performs one optimization per active direction segment. -
DistanceHorizonMetersandTimeHorizonSecondsapply only toRollingHorizontruncation. -
s_endcomes from actual Local G2PathS;T_endis 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-1jerk 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:
- Web enabled, Native Painter disabled by default, and UI/console/page show
OBSERVE_ONLY. - 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.
- ST
PathSincreases; jerk hasN-1intervals and no terminal successor or falseJerkLimitExceeded. - World handoff displays
DeltaPosition, shared-directionDeltaReferenceS,DeltaV, andDeltaA, never a direct comparison of unrelated local PathS. - At a true reversal, N remains highlighted until continuous stop hold and three correctly signed speed samples pass; waiting text is explicit.
- Coarse, Local G2, current trajectory, vehicle pose,
s_end, gear switch, and final goal are visually distinct. - Closing/slow-loading the browser does not stop observation.
- 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.