feat: 发布 EM 轨迹规划首个版本

This commit is contained in:
2026-08-11 20:35:59 +08:00
parent 569de5f13c
commit 1903e71fc1
522 changed files with 4188 additions and 119188 deletions
@@ -1,95 +0,0 @@
# EM Longitudinal Rolling Planning — Final Acceptance Handoff
## Scope and commits
- Phase 4 start baseline: `6cfbaf6` (`feat: reuse prior trajectory in longitudinal planning`)
- Task 8: `eb050b1` (`test: cover rolling-to-stop EM planning flow`)
- Task 9: `302798b` (`docs: explain rolling longitudinal planning diagnostics`)
- Phase 4 code/documentation end: `302798b`
Only original-plan Tasks 8, 9, and 10 were executed. No fifth phase is defined or created.
## Delivered behavior
### Task 8 — service-level rolling-to-stop regressions
- Added service coverage for `RollingContinuation -> ApproachStopBoundary -> ExactStopAtBoundary`.
- The rolling case uses `DistanceHorizonMeters=5m`, `TimeHorizonSeconds=2s`, `OutputTimeStepSeconds=0.1s`, and
`MaximumForwardSpeedMetersPerSecond=0.2m/s`; it publishes 21 points, stops short of 5m, and retains nonzero terminal
speed.
- Goal and `CreateGearPairReferencePath()` exact-stop cases verify the true boundary anchor, following QP stationary
point, zero speed, and the absence of points from the next direction segment.
- The parameterized gear-pair fixture retains its existing 2m default and supports a short first segment for exact-stop
service tests.
- The existing rolling integration regression now explicitly proves the 21-knot / 5m-LS / 2s-ST semantics.
### Task 9 — observation diagnostics and documentation
- The one-time configuration diagnostic reports the worst forward/reverse jerk-limited stopping capability:
`maximumJerkLimitedStopDistance`, `maximumJerkLimitedStopDuration`, and `requiredDistanceHorizon`.
- Published trajectory summaries now report `longitudinalMode`, `terminalSpeed`, and `terminalAcceleration`.
- Failure summaries preserve every upstream value and explicitly mark any missing longitudinal context as `unavailable`;
the required fields are `longitudinalMode`, `remainingToBoundary`, `minimumStoppingDistance`,
`minimumStoppingDuration`, and `maximumStoppedReachableDistance`.
- EMPlanner and MovementTest documentation now distinguish the L-S distance window from the S-T duration and state that
rolling observation remains `OBSERVE_ONLY`.
## TDD and debugging record
Task 8 began with new service assertions and an `em-core-all` red run. The first failure was an undersized ±3m fixture
map for a 10m rolling path. Subsequent red runs established that a scripted solver warm start is not a validated exact
stop solution. The final fixture uses the pre-existing, discretely feasible exact-stop service scenario while the
separate 5m/2s case covers rolling semantics. No acceleration, jerk, solver residual, or publication-validation limit
was changed.
Task 9 began with new required-field assertions. `trajectory-observation` failed because the configuration diagnostic
did not report jerk-limited stopping capability; the minimal implementation then made that test pass.
## Verification evidence
All commands below were run from the repository root.
| Command | Exit | Key output |
| --- | ---: | --- |
| `dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-core-all` (Task 8 red) | 1 | New fixture exposed map-boundary and unvalidated exact-stop warm-start failures. |
| `dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-integration` | 0 | `PASS longitudinal-integration` after reverting an unsuccessful seed experiment. |
| `dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-core-all` (Task 8 green) | 0 | `PASS longitudinal-model`, `PASS longitudinal-integration`, `PASS trajectory`, `PASS em-planning-service`. |
| `dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation` (Task 9 red) | 1 | Missing `maximumJerkLimitedStopDistance=` assertion. |
| `dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation` (Task 9 green) | 0 | `PASS trajectory-observation`. |
| `dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-core-all` | 0 | All four core PASS lines. |
| `dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- coordinator` | 0 | `PASS coordinator`. |
| `dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- executor` | 0 | `PASS executor`. |
| `dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation` | 0 | `PASS trajectory-observation`. |
| `dotnet build ClumsyPilot/ClumsyPilot.csproj -p:ExcludeLegacyAutoAvoidance=true` | 0 | Build succeeded with 0 warnings and 0 errors. |
| `dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-real-osqp-probe` | 0 | `PASS longitudinal-real-osqp`. |
| `git diff --check` | 0 | No whitespace errors; only CRLF notices from unrelated user edits. |
| `git status --short` / `git log --oneline -12` | 0 | Task commits are present; unrelated user work remains unstaged. |
The automated C# runs emitted the pre-existing obsolete-API warnings in `MovementTests.TireFollowing.cs` and
`TireFollowing.cs`; the Task 10 build with `ExcludeLegacyAutoAvoidance=true` itself reported 0 warnings.
## OSQP and observation-only target verification
The real OSQP probe succeeded on this Windows workspace host, so the native DLL was loadable for the probe. The manual
`TrajectoryObservationMovementTest` observation-only run remains pending on the deployed vehicle/MDCS host: it is a
vehicle-UI MovementTest that requires live localization and chassis-speed read interfaces and has no standalone
non-interactive launcher in this workspace. It must confirm the startup configuration prints once, the
`RollingContinuation -> ApproachStopBoundary -> ExactStopAtBoundary` progression, no point-21
`JerkLimitExceeded`, and continuing `OBSERVE_ONLY: no chassis command is sent.` output.
## Preserved user worktree and safety constraints
The branch remains `trajplanner`. Unrelated modified, deleted, and untracked user work in PathSmoothing, CoarsePath,
Map, reports, configuration, MovementTest, and planning/spec documents remains untouched and unstaged. No `reset`,
`checkout`, `clean`, `stash`, broad `git add`, or worktree cleanup was used.
No chassis command, trajectory follower, or watchdog was added. `OBSERVE_ONLY` remains unchanged.
## Retained invariants
- `DistanceHorizonMeters` remains the L-S reference window; `TimeHorizonSeconds` remains one S-T output duration.
- Only real Goal/GearSwitch boundaries use exact zero-speed terminals and an in-QP stationary S/U/A tail.
- `RollingContinuation` and `ApproachStopBoundary` may publish nonzero terminal speed and do not gain a synthetic hold.
- `ZeroSpeedHoldSeconds` remains outside the QP horizon.
- Previous-trajectory PathS/speed arrays remain objective soft references, never terminal hard constraints.
- No jerk, acceleration, solver, or publication-validation threshold was relaxed.
@@ -1,72 +0,0 @@
# EM Longitudinal Rolling Planning — Phase 1 Handoff
## Scope and commits
- Phase start baseline: `6c2f406` (`docs: split EM rolling plan into four phases`)
- Task 1 commit: `e56220f` (`feat: add complete jerk-limited stopping math`)
- Task 2 commit / phase code end: `21b20d0` (`feat: separate rolling horizons from stop boundaries`)
This phase implemented only original-plan Tasks 1 and 2. It did not implement the Task 3 speed-envelope behavior, Task 4 conditional QP constraints, Task 5 optimizer seeding, trajectory publication behavior, or any chassis command. `OBSERVE_ONLY` remains unchanged.
## Delivered changes
Task 1 added `JerkLimitedStoppingMath` and `JerkLimitedStoppingProfile` in `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/JerkLimitedStoppingMath.cs`. The public API is:
```csharp
JerkLimitedStoppingMath.TryCalculate(double speed, double acceleration,
double maximumDeceleration, double maximumJerk,
out JerkLimitedStoppingProfile profile, out string failureReason)
JerkLimitedStoppingMath.MaximumInitialSpeedForDistance(double availableDistance,
double conservativeInitialAcceleration, double maximumDeceleration,
double maximumJerk, double directionMaximumSpeed)
JerkLimitedStoppingMath.CalculateMaximumStoppedDistance(double initialSpeed,
double initialAcceleration, double maximumSpeed, double maximumAcceleration,
double maximumDeceleration, double maximumJerk, double timeHorizon)
```
The profile exposes `DistanceMeters`, `DurationSeconds`, `FinalSpeedMetersPerSecond`, and `FinalAccelerationMetersPerSecondSquared`. The model uses the ST constant-jerk integration equations and releases both speed and acceleration to zero. `PathSpeedLimitBuilder` now uses it for its existing stopping precheck; the old duplicate stopping classes were removed.
Task 1 files were `JerkLimitedStoppingMath.cs`, `PathSpeedLimitBuilder.cs`, and `LongitudinalModelChecks.cs`. Two existing direct callers of the removed internal types also required minimal compilation adaptation: `PlanningHorizonSelector.cs` and `SequentialLongitudinalOptimizer.cs`. They now call the new math but otherwise preserve their pre-Task-2 behavior.
Task 2 added `EmLongitudinalMode` (`RollingContinuation`, `ApproachStopBoundary`, `ExactStopAtBoundary`) and `LongitudinalTerminalSchedule.GetStabilizationStartIndex(IReadOnlyList<double>, double)`.
`PlanningHorizonSelection` now records `WindowEndReferenceS`, `WindowEndBoundaryType`, `TerminalType`, `LongitudinalMode`, `StopBoundaryReferenceS`, and `HasStopBoundary`. `TerminalReferenceS` remains as a read-only compatibility alias for the window end.
`PlanningHorizonSelector` now:
- rejects an already insufficient real segment stopping distance using the complete jerk model;
- sets the LS window with only `DistanceHorizonMeters`;
- uses only real `Goal` and `GearSwitchApproach` segment ends as stop boundaries;
- selects rolling when the boundary is outside the window, approach when visible but not time-reachable with the mandatory internal stabilization step, and exact stop otherwise;
- does not deduct `ZeroSpeedHoldSeconds` from the ST horizon.
`LongitudinalPlanningInput` now accepts `EmLongitudinalMode` and exposes `PathUpperBoundS`, `HasStopBoundary`, `StopBoundaryPathS`, `StopBoundaryType`, and `Mode`. Its original constructor remains as a compatibility overload, mapping rolling terminals to `RollingContinuation` and Goal/GearSwitch terminals to `ExactStopAtBoundary`; new code must use the mode-explicit constructor. `TerminalPathS` remains a compatibility alias for now, because Tasks 35 must migrate its upper-bound and stop-boundary uses deliberately.
`EmPlanningRequestValidator` checks the worst forward/reverse full jerk-limited stop at configured maximum positive acceleration plus one replan-period reserve against `DistanceHorizonMeters`, and checks that the ST knot horizon can reserve a full internal stabilization interval. `EmPlanningService` slices with `WindowEndReferenceS` and passes the selected longitudinal mode into the ST input.
Task 2 files were `EmLongitudinalMode.cs`, `LongitudinalTerminalSchedule.cs`, `PlanningHorizonSelector.cs`, `LongitudinalPlanningInput.cs`, `EmPlanningRequestValidator.cs`, `EmPlanningService.cs`, `LongitudinalModelChecks.cs`, and `FoundationChecks.cs`. `LongitudinalIntegrationChecks.cs` was additionally updated only to pass `Mode` at every direct `LongitudinalPlanningInput` construction.
## Verification evidence
The phase-end commands were run from commit `21b20d0`; each exited `0`:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- foundation
# PASS foundation
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-model
# PASS longitudinal-model
git diff --check
# exit 0
```
The .NET commands still print two pre-existing obsolete warnings from `MovementTests.TireFollowing.cs` and `TireFollowing.cs`; phase 1 added no warning source. The staged-file checks before each commit contained only the listed phase files and the explicitly noted direct-caller adaptations.
## Remaining work and constraints for phase 2
Phase 2 is original-plan Tasks 35 only. It must make speed envelopes, QP terminal constraints, solution validation, and optimizer seeds conditional on `LongitudinalPlanningInput.Mode`. It must replace legacy `TerminalPathS` semantics with `PathUpperBoundS` versus `StopBoundaryPathS` at the relevant use sites, without changing trajectory publication (Task 6+) or adding any chassis command.
Do not loosen jerk, acceleration, velocity, QP residual, or trajectory-validation tolerances. `DistanceHorizonMeters` remains the LS spatial look-ahead; `TimeHorizonSeconds` remains one ST solve duration. A rolling window end is not a stop boundary. Exact stops must reserve at least one full `OutputTimeStepSeconds` static interval inside the QP horizon. `ZeroSpeedHoldSeconds` stays outside that horizon and is not phase-2 work.
The working tree still contains extensive unrelated user PathSmoothing, CoarsePath, Map, configuration, report, and untracked-file changes. Preserve them; do not reset, clean, broadly stage, or commit them. No target-machine OSQP probe was run in phase 1; it belongs to the later roadmap phase.
@@ -1,60 +0,0 @@
# EM Longitudinal Rolling Planning — Phase 2 Handoff
## Scope and commits
- Phase start baseline: `21b20d0` (`feat: separate rolling horizons from stop boundaries`)
- Task 3: `94a9be9` (`feat: keep rolling speed envelopes open`)
- Task 4: `efa03c1` (`feat: apply ST stop constraints only at real boundaries`)
- Task 3 compatibility fixup: `dbc7b7c`
- Task 4 initial-state/envelope fixup: `26bd822`
- Task 5 / phase code end: `59d13e5` (`feat: seed rolling and exact-stop ST profiles`)
Only original-plan Tasks 3, 4, and 5 were implemented. The two fixups could not be autosquashed because the shared worktree has unrelated unstaged user changes and Git refused rebase. They are intentionally retained as separate local commits; their changes belong to Tasks 3 and 4 respectively.
No trajectory publication code, service-flow work, external hold behavior, chassis command, or Task 6+ production code was started. `OBSERVE_ONLY` remains unchanged.
## Delivered behavior
`PathSpeedLimit` now exposes `HasStopBoundary` and `PathUpperBoundS`; `TerminalPathS` remains a warning-free compatibility alias. `PathSpeedLimitBuilder` gives `RollingContinuation` a nonzero open end and no stop boundary. `ApproachStopBoundary` and `ExactStopAtBoundary` use `JerkLimitedStoppingMath.MaximumInitialSpeedForDistance` at every PathS sample against `StopBoundaryPathS`; only the actual stop-boundary sample is exactly zero. The old square-root limit and discrete deceleration tail approximation were removed.
`LongitudinalConstraintBuilder` applies no terminal S/U/A equality to Rolling or Approach. Exact mode uses `LongitudinalTerminalSchedule.GetStabilizationStartIndex` and pins every internal tail knot to `StopBoundaryPathS`, zero speed, and zero acceleration. Its row count is `8*K-2+3*M`; the other modes remain `8*K-2`.
`LongitudinalSolutionValidator` always checks knot layout, finite values, PathS monotonicity/bounds, jerk, and exact constant-jerk dynamics. Approach and Exact independently test each knot against its complete jerk-limited stopping distance; Exact also canonicalizes the entire internal static tail. The Task 4 fixup preserves the supplied physical state at knot zero: the conservative maximum-positive-acceleration envelope applies from the following knot, while the actual-state stopping check still applies at knot zero.
`SequentialLongitudinalOptimizer` dispatches seed construction by `EmLongitudinalMode`. Rolling forward-integrates without forcing the LS path end. Approach uses the stopping-speed envelope while producing only this ST time prefix. Exact uses the stabilization schedule, tries a cruise-plus-jerk-brake profile, and otherwise constructs a terminal-equality seed before filling the QP-internal static tail. Exact-only outer iteration pins tail progress; ordinary knots use `PathUpperBoundS`. The final-knot forward-probe index guard remains, without restoring an unconditional terminal-position pin.
`LongitudinalObjectiveBuilder` uses `PathUpperBoundS` for normalization and omits the terminal-acceleration soft residual only in Exact mode. Integration fixtures now explicitly distinguish generic Approach OSQP scenarios from the independent Exact-stop tail scenario; this avoids incorrectly demanding an exact stop for a distance that cannot fit inside the ST motion horizon.
## Interfaces the next phase must preserve
- `JerkLimitedStoppingMath`, `JerkLimitedStoppingProfile`
- `EmLongitudinalMode`: `RollingContinuation`, `ApproachStopBoundary`, `ExactStopAtBoundary`
- `LongitudinalTerminalSchedule.GetStabilizationStartIndex`
- `PlanningHorizonSelection.WindowEndReferenceS`, `WindowEndBoundaryType`, `LongitudinalMode`, `StopBoundaryReferenceS`, `HasStopBoundary`
- Mode-explicit `LongitudinalPlanningInput`, `PathUpperBoundS`, `StopBoundaryPathS`, `HasStopBoundary`
- `PathSpeedLimit.HasStopBoundary` and `PathSpeedLimit.PathUpperBoundS`
`TerminalPathS` is compatibility-only. Use `PathUpperBoundS` for every path/window upper bound and `StopBoundaryPathS` only for a real Goal/GearSwitch stop.
## Verification evidence
The following were rerun at phase end and exited `0`:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-model
# PASS longitudinal-model
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-integration
# PASS longitudinal-integration
git diff --check
# exit 0
```
The .NET runs report two pre-existing obsolete warnings from `MovementTests.TireFollowing.cs` and `TireFollowing.cs`; phase 2 introduced none. `git diff --check` also prints CRLF notices for unrelated pre-existing dirty files but returned success.
`git log -5 --oneline` ended with `59d13e5`, `26bd822`, `dbc7b7c`, `efa03c1`, and `94a9be9`. The worktree still contains extensive unrelated modified, deleted, and untracked PathSmoothing, CoarsePath, Map, report, and configuration files. Preserve them; never reset, clean, broadly stage, or commit them.
## Phase 3 boundary
Phase 3 is only original-plan Tasks 6 and 7: mode-aware trajectory sampling/publication validation, followed by previous-trajectory longitudinal soft references. It must not begin Task 8, 9, or 10. `ZeroSpeedHoldSeconds` remains outside the QP horizon and becomes an external hold only for Exact mode. Rolling and Approach must publish their ST horizon unchanged, with no synthetic terminal stop or boundary marker.
@@ -1,110 +0,0 @@
# EM Longitudinal Rolling Planning — Phase 3 Handoff
## Scope and commits
- Phase 3 implementation baseline: `59d13e5` (`feat: seed rolling and exact-stop ST profiles`)
- Task 6: `4159ae0` (`feat: publish rolling trajectories without stop tails`)
- Task 7 / phase 3 code end: `6cfbaf6` (`feat: reuse prior trajectory in longitudinal planning`)
Only original-plan Tasks 6 and 7 were implemented. Task 8, Task 9, and Task 10 have not been started. This handoff and the companion phase-4 prompt are documentation-only follow-up artifacts; they do not change the phase-3 code end above.
The preceding Task 3 and Task 4 fixups remain separate commits, `dbc7b7c` and `26bd822`, because the shared dirty worktree prevented autosquash. Preserve them as part of the actual history; do not rebase, reset, clean, or stash user files merely to rewrite it.
## Delivered behavior
### Task 6: mode-aware publication and validation
`EmTrajectoryMetadata` now carries `EmLongitudinalMode`, supplied by `EmPlanningService`. `TrajectorySampleSchedule` is mode-aware:
- `RollingContinuation` and `ApproachStopBoundary` publish exactly the ST candidate knots, without an artificial zero-speed hold or a terminal boundary anchor.
- `ExactStopAtBoundary` locates the internal stable tail with `LongitudinalTerminalSchedule.GetStabilizationStartIndex`, requires its constant `PathS`, `U=0`, `A=0`, and zero tail jerk, marks only its first point as the boundary anchor, then appends `ZeroSpeedHoldSeconds` after the QP horizon with strictly increasing timestamps.
`EmTrajectoryAssembler` marks an anchor only for the first exact stable-tail knot. `EmTrajectoryValidator` retains finite-value, ordinary kinematics, jerk, and collision checks for every mode, but applies terminal-anchor, zero terminal speed/acceleration/yaw, and static-tail requirements only to `ExactStopAtBoundary`. It now reports `TerminalAccelerationNotZero` when appropriate. No validation, solver, acceleration, or jerk threshold was relaxed, and `OBSERVE_ONLY` behavior is unchanged.
Task 6 changed only:
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Contracts/EmTrajectoryMetadata.cs`
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Trajectory/TrajectorySampleSchedule.cs`
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Trajectory/EmTrajectoryAssembler.cs`
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Validation/EmTrajectoryValidator.cs`
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Facade/EmPlanningService.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryChecks.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/ExecutorChecks.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/CoordinatorChecks.cs`
### Task 7: previous-trajectory longitudinal soft reference
`LongitudinalPreviousTrajectorySeed` and `LongitudinalPreviousTrajectorySeedBuilder` were added. The builder samples a compatible previous `EmTrajectory` at `newEffectiveAtUtc + knotTime`, linearly interpolates world position and signed speed, monotonically projects the position onto the current `LateralPath`, and publishes non-negative progress speed. Missing trajectories, incompatible segment/direction, malformed input, and out-of-range sampling return `LongitudinalPreviousTrajectorySeed.Empty`; none of those conditions throw into the service publication path.
After lateral planning succeeds, `EmPlanningService` creates the current ST knot times, builds the seed with the prior trajectory/current path/effective time/segment/direction, and passes its `PathS` and `ProgressSpeedMetersPerSecond` arrays into the existing explicit-mode `LongitudinalPlanningInput`. These arrays activate the existing previous-S and previous-U objective terms only as soft references. They are not terminal constraints. LS slicing remains based on `WindowEndReferenceS`.
Task 7 changed only:
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalPreviousTrajectorySeedBuilder.cs` (new)
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Facade/EmPlanningService.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalModelChecks.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/EmPlanningServiceChecks.cs`
The service test fixture now captures the emitted ST quadratic program to prove that valid seeds add nonzero previous-S/previous-U objective terms, while an invalid seed adds neither and planning still succeeds. Its scripted solver now returns the supplied warm start for normal longitudinal QPs, allowing phase-1/2 constraints and the phase-3 soft reference to be exercised without fabricating an incompatible primal solution.
## Interfaces the next phase must preserve
- `EmLongitudinalMode`: `RollingContinuation`, `ApproachStopBoundary`, `ExactStopAtBoundary`
- `LongitudinalTerminalSchedule.GetStabilizationStartIndex`
- Explicit-mode `LongitudinalPlanningInput`, including `PreviousPathS` and `PreviousProgressSpeedMetersPerSecond`
- `PathUpperBoundS`, `StopBoundaryPathS`, and `PathSpeedLimit.HasStopBoundary`
- `EmTrajectoryMetadata.LongitudinalMode`
- `LongitudinalPreviousTrajectorySeed.PathS`, `ProgressSpeedMetersPerSecond`, and `Empty`
- `LongitudinalPreviousTrajectorySeedBuilder.Build(EmTrajectory, LateralPath, DateTimeOffset, IReadOnlyList<double>, int, TravelDirection)`
`TerminalPathS` remains compatibility-only. Use `PathUpperBoundS` for the LS/path-window upper bound; use `StopBoundaryPathS` only for an actual Goal or GearSwitch stop boundary.
## Verification evidence
All commands below exited `0` at the stated checkpoint:
```powershell
# Before Task 6 commit 4159ae0
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory
# PASS trajectory
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- coordinator
# PASS coordinator
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- executor
# PASS executor
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
# PASS trajectory-observation
# Before Task 7 commit 6cfbaf6
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-model
# PASS longitudinal-model
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-planning-service
# PASS em-planning-service
# Fresh phase-end checks after Task 7
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory
# PASS trajectory
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-planning-service
# PASS em-planning-service
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- coordinator
# PASS coordinator
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- executor
# PASS executor
git diff --check
# exit 0
```
The fresh `trajectory` run emitted two pre-existing obsolete-API warnings from `MovementTests.TireFollowing.cs` and `TireFollowing.cs`; no failure resulted. `git diff --check` returned success but printed CRLF conversion notices for extensive unrelated dirty user files.
At phase end, `git log -5 --oneline` began with `6cfbaf6`, `4159ae0`, `72592f0`, `59d13e5`, and `26bd822`. The worktree still contains many unrelated modified, deleted, and untracked files in PathSmoothing, CoarsePath, Map, reports, configuration, and other user work. They were not staged or committed. Preserve them exactly: do not run reset, checkout, clean, stash, `git add .`, or `git add -A`.
## Phase 4 boundary
Phase 4 is original-plan Tasks 8, 9, and 10 only:
1. Add service-level multi-cycle regressions for `RollingContinuation -> ApproachStopBoundary -> ExactStopAtBoundary`, including Goal/GearSwitch and independent 5 m / 2 s horizon semantics.
2. Add observation diagnostics and documentation for mode, terminal state, and jerk-limited stopping capability while retaining `OBSERVE_ONLY`.
3. Run the complete regression/build set and the target-machine real-OSQP probe. If the current machine lacks the native dependency, record that as pending target-machine verification rather than changing longitudinal logic or thresholds.
Do not alter the phase-3 behavior to make the new Task 8 fixtures easier. In particular, rolling and approach are allowed to end with nonzero speed, only exact stops carry a boundary anchor/static tail, `ZeroSpeedHoldSeconds` is outside QP time, and previous-trajectory references remain soft only.
@@ -1,84 +0,0 @@
# EM 完整方向段规划与可视化修复:阶段 01 交接
## 状态
完成(此前阻塞的构造调用传播已获用户明确授权扩展范围)。
## 环境、入口与前置核验
- 执行时间:2026-08-06T20:56:08.7268822+08:00Windows / PowerShell / .NET SDK 10.0.302。
- 入口分支与 HEAD`trajplanner` / `57ea36b8595c621783aba844e18d9969bccea378`
- 恢复交接祖先:`fcf7df17d54d84904278e993104e131afe8e405d`
- 以下提交均以 `git merge-base --is-ancestor` 返回 0`c354f113060a5f95aa3b06c6f2b0be2d0c7d5da1``dff223c33a439d545537c803cb3531a673c7f2a3``46d5f9762d3d9972daea918c9fb43562ce25ce3b``57ea36b8595c621783aba844e18d9969bccea378``fcf7df17d54d84904278e993104e131afe8e405d`
## 实现提交
- Task 1`f4e89b4b4fe67f4924420ba8b3e06dea1d4ccc8b``feat: define full-direction EM planning scope`
- Task 2`048b4f618e2237cd0cb3d257bf6ee04a1d13670b``feat: select complete EM direction segments`
## 接口与实际修改
- 新增 `EmPlanningScope.RollingHorizon``FullDirectionSegment`request 与 metadata 均保存不可变 scope。
- 新增 desired speed、优化结点/发布上限、终端位置/yaw 容差、复制和交叉校验;默认值为 1.0/0.5 m/s、0.20 s、0.10 m、401、5001、0.03 m、5°。
- 新增 `NoProgress``TerminalPoseMismatch``FullSegmentResourceLimitExceeded` 状态。
- `PlanningHorizonSelector.Select` 显式接收 scopefull 模式仅接受 Goal/GearSwitch,选择真实方向段末端并使用 `ExactStopAtBoundary`rolling 分支保持原有窗口/approach/exact 行为。
- `EmPlanningService` 传递 scopemetadata 与 diagnostics 均携带 scope。
- 用户随后授权的必需构造调用传播:`TrajectoryObservationPipeline.cs` 及 EM 验证宿主中的 coordinator/executor/trajectory/observation 构造夹具;所有旧调用显式使用 `RollingHorizon`,没有改变 MovementTest 的规划行为。
实际修改文件为两个实现提交的 `git show --stat` 所列 21 个文件;Task 2 没有修改 `EmTrajectoryAssembler.cs`,因为现有 assembler 已基于 metadata 保留终端边界,完整段选择无需额外 assembly 行为。
## 新鲜 TDD 与回归证据
### Task 1
RED
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- foundation
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-planning-service
```
首次失败为缺少 `EmPlanningScope`CS0246/CS0103)。同次曾出现 `TrajectoryPlanningVisualization.dll` 瞬时写锁;单独复现后锁未持续,已按 systematic-debugging 认定为外部构建竞争,未作为功能 RED。之后完整构造调用传播获授权。
GREEN:相同两个命令均退出 0,输出:`PASS foundation``PASS em-planning-service`
### Task 2
RED
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-model
```
失败为 `PlanningHorizonSelector.Select` 没有接收新增 scope 参数(CS1501),符合预期。
GREEN
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-model
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-planning-service
```
均退出 0,输出:`PASS longitudinal-model``PASS em-planning-service`。测试覆盖 10 m/投影 3 m/legacy 1 m 的 full 真实末端、rolling 4 m 截断及 GearSwitch 边界;服务测试覆盖 scope metadata 和发布起止边界。
阶段回归:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-core-all
```
退出 0,完整摘要:`PASS longitudinal-model``PASS longitudinal-integration``PASS trajectory``PASS em-planning-service`。首次回归时 `TrajectoryChecks` 的速度夹具受批准的硬上限变更影响,先触发加速度而非速度;夹具改为 1.10 m/s 以继续隔离速度上限,之后回归通过。
## 脏工作区与范围保护
- `EmPlannerConfiguration.cs` 的用户 `DistanceHorizonMeters = 500d` 和两条中文注释先保存 diff、始终未暂存;提交前以 index-only staging 核验其不在两个实现提交中。
- `MovementTest.TrajectoryObservationTest.cs` 未编辑、未暂存、未提交。
- 既有大量脏修改/未跟踪项均未清理;暂存区在本交接创建前为空。
- 未使用 `git stash``git reset --hard``git checkout --``git add .``git add -A`
- `git diff --check 57ea36b8595c621783aba844e18d9969bccea378..048b4f618e2237cd0cb3d257bf6ee04a1d13670b` 通过。
## 未执行内容与阶段 02 输入
阶段 02–09 均未实现。尤其没有实现自适应 knot/T_end、静止起步、NoProgress 发布门禁、终端世界位姿门禁、MovementTest one-shot、Web 或 Painter。
阶段 02 从 `048b4f618e2237cd0cb3d257bf6ee04a1d13670b` 开始,消费本阶段的 scope/configuration。只允许总计划 Task 3 列出的 Longitudinal 文件、`EmPlanningService.cs``LongitudinalModelChecks.cs``LongitudinalIntegrationChecks.cs``phase-02.md`;不得触及静止起步、终端位姿或任何 MovementTest/UI。先运行 Task 3 RED `longitudinal-model`,再运行 `longitudinal-model``longitudinal-integration` GREEN,提交 `feat: derive adaptive full-segment ST schedule`,运行 `em-core-all`,最后单独提交 `phase-02.md`
@@ -1,34 +0,0 @@
# EM Complete Direction Visualization — Phase 02 Handoff
## Status
Complete on 2026-08-06.
Implementation commit: `3269d55``feat: derive adaptive full-segment ST schedule`
## Delivered scope
- Full-direction ST optimization knots and `T_end` are derived from the actual Local G2 `PathS` speed/stopping envelope and physical jerk/acceleration reachability.
- `LongitudinalKnotSchedule` is an optimization-only contract: knot times, `PathS`/speed references, and an explicit terminal-hold start index. It intentionally contains no reference candidate.
- Full exact stops first execute a bounded deterministic S/U/A/J feasibility QP through the injected `IQpSolver`. It uses nonuniform exact dynamics, actual-`PathS` speed-envelope relinearization, initial/terminal/hold equalities, hard acceleration/jerk bounds, and a shared solver budget.
- Only a strictly validated candidate can become a fallback. A timeout before one exists returns `SolverTimedOut`; a later timeout returns `SuccessWithFallback`.
- Full-scope publication resamples the optimized candidate and does not append the rolling zero-speed hold twice. Rolling-horizon publication behavior remains unchanged.
## Tests
The following commands were run sequentially with exit code 0:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-model
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-integration
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-core-all
```
`em-core-all` was rerun after the implementation commit and passed. The only warnings were existing obsolete API warnings for `Lidar2dDetect2LegTray.LegWidth` and `MultiWheelChassis.GetSteerWheels()`.
## Worktree safeguards
The existing user changes in `EmPlannerConfiguration.cs` and `MovementTest.TrajectoryObservationTest.cs`, together with unrelated dirty changes, were not edited or staged.
Phase 03+ work (static start, NoProgress, terminal world pose, MovementTest, Web, Painter) remains out of scope.
@@ -1,153 +0,0 @@
# EM 完整方向段可视化修复 — Phase 03 交接
## 状态
完成,2026-08-07。
启动分支为 `trajplanner`,启动 HEAD 为 `162f1a2`Phase 02 handoff)。已确认 `3269d55``162f1a2` 均为本阶段提交的祖先。
本阶段实现提交:
- `0bba8d7``fix: accelerate EM trajectories from rest`
- `e844fe0``feat: validate EM terminal world pose`
本交接文件单独提交;其真实提交哈希应由提交后的 Phase 03 最终答复提供给 Phase 04。
## 目标与非目标
完成主计划 Task 4–5:完整方向段可从静止起步,非终端全零候选被精确拒绝,并且在发布前验证真实终端位姿和样本数量上限。
未进入 MovementTest、UI、Web、Native Painter、跨段衔接或任何后续阶段;未新增执行器或硬件写输出。
## Task 4:静止起步与 NoProgress
- 完整方向段的纵向目标跟踪自适应 knot schedule 的参考速度,而不是在每个结点只追踪当前速度上限;滚动窗口保持原有速度上限目标。
- 静止、零加速度的完整段优先构造并严格验证可达的 S/U/A/J 精确停车种子,因此清晰 5 m 段能够起步、加速、推进并在边界停车。
- 停车包络使用实际初始加速度。此前预计算阶段按最大正加速度计算,过早将可用速度压为零;现在服务层的预计算和纵向输入使用同一实际状态。
- 自适应 schedule 在加速→巡航、巡航→减速和换向的转折点保留站点;时间可达性计算保持原始请求时间,不会因原地改写时间而产生非递增结点。
- `LongitudinalSolutionValidator` 对路径长度超过终端位置容差的 `FullDirectionSegment` 检查实际推进量。若不超过空间容差,返回精确的 `EmPlanningStatus.NoProgress` 与诊断;合法的终端容差内保持不误判。
- 初始可行投影把 `NoProgress` 直接传回服务层;服务结果不包含 trajectory。
Task 4 修改:
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/SequentialLongitudinalOptimizer.cs`
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalObjectiveBuilder.cs`
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalSolutionValidator.cs`
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/PathSpeedLimitBuilder.cs`
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/FullDirectionSegmentScheduleBuilder.cs`
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Facade/EmPlanningService.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalModelChecks.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalIntegrationChecks.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/EmPlanningServiceChecks.cs`
`PathSpeedLimitBuilder.cs``FullDirectionSegmentScheduleBuilder.cs` 是为复现并修复静止起步包络/结点前提而获用户明确授权纳入的最小附加范围;`EmPlannerConfiguration.cs` 未修改。
## Task 5:终端位姿、样本上限与发布门禁
- `EmTrajectoryValidator` 新增接收 `Pose2D terminalPose` 的验证重载。只有真正的 `Goal``GearSwitchApproach` 精确终端锚点才检查世界位置和航向;滚动安全窗终点不进入此门禁。
- 位置误差使用欧氏距离并受 `TerminalPositionToleranceMeters`3 cm)约束;航向误差归一化到 `[-π, π]`,再受 `TerminalYawToleranceRadians`5°)约束。失败码为 `EmTrajectoryValidationFailure.TerminalPoseMismatch`
- 服务层把 Local G2 的最后一个 `LateralPathPoint` 作为期望终端世界位姿,并把该验证失败映射为 `EmPlanningStatus.TerminalPoseMismatch`;失败不发布 trajectory。
- `TrajectorySampleSchedule.ExceedsMaximumSampleCount` 在组装点列表前按实际采样规则预检:包含终端锚点与停止保持样本。超过 `MaximumPublishedSampleCount``EmTrajectoryAssembler.TryAssemble` 返回 `FullSegmentResourceLimitExceeded`trajectory 为 `null`,不会截断、放宽采样周期或返回半成品。
- 原有时间严格递增、终端锚点、N-1 jerk、S/U/A/J 停止尾和世界碰撞验证保持不变。
Task 5 修改:
- `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`
新增测试覆盖 2.9 cm/3.1 cm、4.9°/5.1°、179°/-179° yaw 绕回、滚动安全窗豁免、恰好多一个发布样本、组装拒绝无部分 trajectory,以及服务状态映射。
## RED / GREEN / 回归证据
Task 4 的静止起步集成测试先暴露了全零参考/不可行的停止包络;修复后以下命令均以退出码 0 通过:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-model
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-integration
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-planning-service
```
Task 5 的 RED 是新增测试后编译失败,原因精确为缺少带终端 `Pose2D` 的验证重载、`TerminalPoseMismatch` 失败码和拒绝式 `TryAssemble` 接口。实现后以下命令均以退出码 0 通过:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-planning-service
```
最终新鲜回归:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-core-all
git diff --check
```
`em-core-all` 退出码为 0,输出依次为 `PASS longitudinal-model``PASS longitudinal-integration``PASS trajectory``PASS em-planning-service``git diff --check` 通过。
已知且保留的两条编译 warning:过时的 `Lidar2dDetect2LegTray.LegWidth``MultiWheelChassis.GetSteerWheels()`
## 工作区保护
保留并未编辑/暂存用户原有的脏工作区内容,包括 `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Configuration/EmPlannerConfiguration.cs``ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs`、PathSmoothing、CoarsePath、Map、文档和未跟踪报告等改动。未使用 `git reset --hard``git checkout --``git clean``git add .``git add -A`
## Phase 04 完整启动提示词
```text
执行 EM 全方向段可视化修复的 Phase 04MovementTest 每个方向段仅规划一次。
仓库:D:\Users\Desktop\项目\prakrobot\ParkingRobot
预期分支:trajplanner
Phase 03 实现提交必须可达:0bba8d7、e844fe0。
Phase 03 handoffdocs/superpowers/handoffs/em-full-direction-visualization/phase-03.md;同时要求调用方提供并验证该 handoff 的提交哈希可达。
只执行主计划 Task 6;不得进入 Task 7+、Web、Painter、跨段规划扩展或实车操作。MovementTest 必须保持 OBSERVE_ONLY,严禁新增底盘、转向、制动、电机或换挡写输出。不得派生子智能体,也不要重新 brainstorming 或重设计已批准行为。
开始时必须:
1. 读取 executing-plans、test-driven-development、verification-before-completion;仅在真实失败或意外行为后读取 systematic-debugging。
2. 完整阅读功能设计、主计划 Task 6、分阶段执行计划 Phase 04、阶段执行设计及本 Phase 03 handoff;发生矛盾则停止并写阻塞交接,请求用户裁决。
3. 运行并记录:
git branch --show-current
git rev-parse HEAD
git log -5 --oneline
git status --short
git diff --cached --name-only
git merge-base --is-ancestor 3269d55 HEAD
git merge-base --is-ancestor 162f1a2 HEAD
git merge-base --is-ancestor 0bba8d7 HEAD
git merge-base --is-ancestor e844fe0 HEAD
以及调用方提供的 Phase 03 handoff 哈希。
4. 保留所有既有脏文件;修改已脏目标前先记录 diff 并逐 hunk 保留用户改动。严禁 git reset --hard、git checkout --、git clean、git add .、git add -A。
本阶段唯一允许修改/新增:
- 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
- docs/superpowers/handoffs/em-full-direction-visualization/phase-04.md
按 TDD 先编写 Task 6 指定的失败测试,然后运行:
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
确认首个有效 RED 是完整方向段仍按协调器节拍重复开始计划周期。
实现且仅实现:TrajectoryObservationSettings.PlanningScope(默认 FullDirectionSegment)、经过验证快照/请求的 scope 传播、批准的观察默认值,以及每个活动方向段最多一次计划尝试。尝试标志必须在启动异步计划前置位;只有 TryAdvanceSegment 确认 N→N+1 后才复位。失败的完整段计划保持可见但不自动滚动重试。保留已有停车保持和三条带符号速度样本握手、显式等待文案及 active segment 高亮。
GREEN 后运行:
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
rg -n "SendXYThSpeed|SendMotion|DriveStop|PredefinedDriveStop|AccumulateSpeed|SetGear|SetBrake" ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest
git diff --check
期望 trajectory-observation PASS,且观察运行时源中没有执行器调用。
只显式暂存本阶段白名单实现/测试文件,并提交:
git commit -m "feat: observe one full EM direction segment"
随后仅用 apply_patch 创建 phase-04.md,记录入口 HEAD、实现提交、RED/GREEN、执行器审计、保留脏工作区、warning、未运行项及 Phase 05 事实;只提交该 handoff
git commit -m "docs: record EM full-direction phase 04 handoff"
成功门禁:完整范围由显式设置启用;每个方向段仅计划一次;观察继续、停车/换向握手保持;无硬件写输出。若被阻塞,创建阻塞 handoff 和同阶段恢复提示词,不得进入 Phase 05。
```
@@ -1,102 +0,0 @@
# EM 完整方向段可视化修复 — Phase 04 交接
## 状态
完成,2026-08-07。
启动分支为 `trajplanner`,启动 HEAD 为 `57d8abb`Phase 03 handoff)。已确认 `3269d55``162f1a2``0bba8d7``e844fe0``57d8abb` 均为本阶段提交的祖先。
本阶段实现提交:
- `ab3204b``feat: observe one full EM direction segment`
本交接文件单独提交;其真实提交哈希由提交后的最终答复提供给 Phase 05。
## 目标与非目标
完成主计划 Task 6MovementTest 每个活动方向段最多一次完整规划,换向确认后才允许下一段的新计划。
未进入 Task 7+、Web、Native Painter、跨段规划扩展或实车操作。MovementTest 保持 `OBSERVE_ONLY`,未新增任何底盘、转向、制动、电机或换挡写输出。
## 实现摘要
- `TrajectoryObservationSettings.PlanningScope` 默认 `EmPlanningScope.FullDirectionSegment`,经过 `CreateValidatedSnapshot` 复制并验证。
- 批准的观察默认值:Web 默认启用、Native Painter 默认关闭、`OutputTimeStepSeconds` 保持 `0.1 s``TimeHorizonSeconds` 仅作为滚动兼容字段标注。
- `TrajectoryObservationController` 保存经过验证的设置快照,并把 `PlanningScope` 传播到每个 `EmPlanningRequest`
- 控制器新增 `plannedSegmentIndex``planAttemptedForActiveSegment`:完整模式下 `ShouldStartCycle` 只允许每个活动方向段一次;`StartCycle` 在启动异步计划前置位;只有 `TryAdvanceSegment` 确认 N 到 N+1 后才复位。
- 完整段规划失败保持可见且不自动滚动重试。
- 保留已有停车保持、三个正确带符号速度样本、显式等待文案和 active segment 高亮。
- 静态快照配置组新增 `PlanningScope`
## 修改文件
- `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`
`TrajectoryObservationSegmentChecks.cs` 保持白名单内但本阶段无改动。
## 新增或改变的接口
- `TrajectoryObservationSettings.PlanningScope`,默认 `FullDirectionSegment`,快照复制并在 `Validate` 中拒绝未定义枚举值。
- `TrajectoryObservationMovementTest.UseFullDirectionSegmentPlanning`,默认 `true`,映射为 settings 的 `PlanningScope`
- `TrajectoryObservationController.ShouldStartCycle`:完整模式不再委托协调器节拍。
- `TrajectoryObservationController.StartCycle`:使用已验证 scope,并在启动异步任务前设置 one-shot 标志。
- `TrajectoryObservationController.TryAdvanceSegment`:确认换向后复位 one-shot 标志。
## RED / GREEN / 回归证据
首次 RED 被既有用户脏配置 `EmPlannerConfiguration.cs``DistanceHorizonMeters = 500d` 拦住:原测试硬编码 `distanceHorizon=5.00m`。未修改该脏文件;改为从 `CreateEffectiveConfigurationSnapshot` 动态读取实际距离视界后断言。
随后确认 Task 6 有效 RED
```text
full direction segment is planned only once per active segment expected 1 but was 4.
```
即当前完整方向段仍按协调器节拍重复开始计划。
GREEN
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
```
退出码 0,输出 `PASS trajectory-observation`
执行器源审计:
```powershell
rg -n "SendXYThSpeed|SendMotion|DriveStop|PredefinedDriveStop|AccumulateSpeed|SetGear|SetBrake" ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest
```
无匹配。
`git diff --check` 退出码 0;输出仅有既有 LF/CRLF 提示,无空白错误。
## 工作区保护
保留并未回退用户既有脏工作区,包括但不限于 `EmPlannerConfiguration.cs``MovementTest.TrajectoryObservationTest.cs` 中用户原有的 `TimeHorizonSeconds = 20d``EnableWebVisualization = true``EnableNativePainterVisualization = true`,以及 PathSmoothing、CoarsePath、Map、文档和未跟踪报告等改动。
本阶段只修改白名单实现/测试文件;未使用 `git reset --hard``git checkout --``git clean``git add .``git add -A`
## 已知警告与偏差
- 既有两条编译 warning 保留:`Lidar2dDetect2LegTray.LegWidth``MultiWheelChassis.GetSteerWheels()`
- 既有诊断测试因用户脏配置从硬编码 `5.00m` 改为读取实际生效配置;未改写用户脏文件。
- MovementTest 入口的 Native Painter 保持用户脏配置 `true``TrajectoryObservationSettings` 类的批准默认值仍为 `false`
## 未运行项
- 未进入 Phase 05 或后续任何阶段。
- 未执行 Web、Painter、跨段扩展或实车操作。
- 未运行 `em-core-all`;本阶段验证命令按任务要求仅执行 `trajectory-observation`、执行器审计和 `git diff --check`
## Phase 05 事实
对应主计划 Task 7,阶段执行计划 Task 5:统一快照、路径图层、曲线单位、`s_end`、车辆与换向语义。
Phase 05 的 Phase 04 实现提交为 `ab3204b`,上一交接文件为本文件;调用方需在最终答复中提供本交接提交的真实哈希并验证可达。
@@ -1,142 +0,0 @@
# EM 完整方向段可视化修复 — Phase 05 交接
## 状态
完成,2026-08-07。
启动分支为 `trajplanner`,启动 HEAD 为 `5d1c875`Phase 04 handoff)。已确认 `ab3204b``5d1c875` 均为本阶段提交的祖先。
本阶段实现提交:
- `d33257c``fix: publish distinct EM observation semantics`
本交接文件单独提交;其真实提交哈希由提交后的最终答复提供给 Phase 06。
## 目标与非目标
完成主计划 Task 7:统一快照、路径图层、曲线单位、`s_end`、车辆与换向语义。
未进入 Task 8+、Web DOM、Native Painter、跨段规划扩展或实车操作。MovementTest 保持 `OBSERVE_ONLY`,未新增任何底盘、转向、制动、电机或换挡写输出。
## 实现摘要
- 新增 `VisualizationChartAnnotation``VisualizationChart` 增加不可变 `Annotations`,旧构造函数保持空注解兼容。
- 静态与动态快照使用 `gear-switch-end` / `final-goal` 标记,并补充车辆、计划起点标记;完整段不再重复发布 `current-horizon`
- LS/ST/世界均标记 `s_end`;换向边界使用“换向点 N / s_end”,最终目标使用“终点 / s_end”。
- 动态快照构建器接收 `VehicleParameters`,曲率硬上限改为真实车辆 `MaximumCurvaturePerMeter`,横摆角速度轴改为 `ω (rad/s)`
- MovementTest 网页发布回调把 `bootstrap.Vehicle` 传入动态快照构建器。
## 修改文件
实际修改:
- `ClumsyPilot/TrajectoryPlanningVisualization/Contracts/VisualizationCharts.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`
白名单内未改动:`VisualizationGeometry.cs`。现有折线/标记/方向段几何契约已满足 Task 7 语义,无需重复修改。
## 新增或改变的接口
- `VisualizationChartAnnotation(id, kind, labelChinese, x, double? y = null)`
- `VisualizationChart.Annotations`;旧 6 参数构造器委托到带空注解的新构造器。
- `TrajectoryObservationDynamicSnapshotBuilder.Build(..., VehicleParameters vehicle, EmPlannerConfiguration configuration)`
- `TrajectoryObservationKinematicChartBuilder.Build(..., VehicleParameters vehicle, EmPlannerConfiguration configuration)`
- 曲线硬上限取 `VehicleParameters.MaximumCurvaturePerMeter`;没有直接曲率时回退到 `MinimumTurningRadiusMeters`
## RED / GREEN / 回归证据
RED
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
```
首个有效失败为 `static snapshot marks gear-switch s_end was false`,可视化宿主为 `chart exposes immutable annotations`。测试集还包含同一 current 不重复发布为 `current-horizon`、曲率上限必须来自车辆几何的断言;`Verification` 在首个失败处停止,因此未单独打印后续断言。
GREEN(串行运行,避免共享 `obj` DLL 锁):
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
```
退出码 0,输出 `PASS trajectory-observation`
```powershell
dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
```
退出码 0,输出 `PASS trajectory-planning-visualization`
## 执行器审计与清理
```powershell
rg -n "SendXYThSpeed|SendMotion|DriveStop|PredefinedDriveStop|AccumulateSpeed|SetGear|SetBrake" ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest
```
无匹配(rg 退出码 1,符合“无匹配”预期)。
`git diff --check` 与暂存后 `git diff --cached --check` 均退出码 0,仅有既有 LF/CRLF 提示,无新空白错误。
## 工作区保护
保留并未回退用户既有脏工作区与未跟踪内容,包括但不限于 `.gitignore``EmPlannerConfiguration.cs`、PathSmoothing/CoarsePath/Map 相关改动、文档和未跟踪报告。
实现提交只显式暂存本阶段白名单文件;提交后 `git diff --cached --name-only` 为空,未使用 `git reset --hard``git checkout --``git clean``git add .``git add -A`
## 已知警告与偏差
- 既有两条编译 warning 保留:`Lidar2dDetect2LegTray.LegWidth``MultiWheelChassis.GetSteerWheels()`
- `git diff --check` 持续输出仓库既有 LF/CRLF 提示,不影响空白检查结果。
- `VisualizationGeometry.cs` 本阶段未改动;如果后续阶段发现几何契约不足,应在 Phase 08 Painter 范围内处理,而不是本阶段扩展。
## 未运行项
- 未进入 Phase 06 或后续任何阶段。
- 未执行 Web DOM/jsdom、Native Painter、跨段规划扩展或实车操作。
- 未运行 `em-core-all`;本阶段验证命令按任务要求仅执行两个宿主、执行器审计和 `git diff --check`
## Phase 06 事实
对应主计划 Task 8,阶段执行计划 Task 6:真实 DOM/SVG 测试与 Web 渲染修复。
Phase 06 允许创建/修改:
- 创建:`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`
- 交接:`docs/superpowers/handoffs/em-full-direction-visualization/phase-06.md`
接口与验证要求:
- 使用 Node 24 + `jsdom` 29.1.1 的开发专用依赖验证真实 DOM/SVG。
- 修复分页隐藏、空白总览、y 轴刻度/单位、稳定数据域、语义图层顺序、车辆/边界标记和论文风细线。
- 测试钩子 `window.__TRAJECTORY_VISUALIZATION_TEST__ === true` 只暴露不可变输入渲染钩子,不启动 fetch/SSE。
必须执行:
```powershell
npm install --package-lock-only --prefix ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom
npm test --prefix ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom
dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
```
期望 RED 是 hidden-section CSS、y ticks、语义类或 empty-state 断言失败;GREEN 是全部 DOM 测试通过且 .NET 宿主打印 `PASS trajectory-planning-visualization`
实现提交信息:`fix: repair EM observation web charts`
交接文件单独提交信息:`docs: record EM full-direction phase 06 handoff`
Phase 06 退出门禁:现有四页布局不重做,正确渲染数据/轴/图层;本阶段不实现缩放。
Phase 06 的 Phase 05 实现提交为 `d33257c`,上一交接文件为本文件;调用方需在最终答复中提供本交接提交的真实哈希并验证可达。
@@ -1,144 +0,0 @@
# EM 完整方向段可视化修复 — Phase 06 交接
## 状态
完成,2026-08-07。
启动分支为 `trajplanner`,启动 HEAD 为 `8b82467618759294c4ccf810bf0699ca804171b8`Phase 05 handoff)。已确认 `d33257c``8b82467` 均为本阶段启动 HEAD 的祖先。
本阶段实现提交:
- `05cfd673f504d5ecee000ab9b85c7346436f2076``fix: repair EM observation web charts`
本交接文件单独提交;其真实提交哈希由提交后的最终答复提供给 Phase 07。
## 目标与非目标
完成主计划 Task 8、阶段执行计划 Task 6:真实 DOM/SVG 测试与 Web 渲染修复。
未进入 Task 9+、Native Painter、跨段规划扩展或实车操作,未实现缩放。MovementTest 保持 `OBSERVE_ONLY`,本阶段未触碰任何底盘、转向、制动、电机或换挡写输出。
## 修改文件
实际修改:
- 创建:`ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom/package.json`
- 创建:`ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom/package-lock.json`
- 创建:`ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom/dashboard.dom.test.mjs`
- 修改:`ClumsyPilot/TrajectoryPlanningVisualization/Web/app.css`
- 修改:`ClumsyPilot/TrajectoryPlanningVisualization/Web/app.js`
- 修改:`ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/WebAssetChecks.cs`
白名单内 `index.html` 未改动:现有四页结构已满足 Task 8,无需重做布局。
## 新增或改变的接口
- 新增测试专用 Node 包,锁定 `jsdom` 29.1.1`engines.node >= 24`,脚本为 `node --test dashboard.dom.test.mjs`
- `window.__TRAJECTORY_VISUALIZATION_TEST__ === true` 时,`app.js` 暴露冻结的 `window.__trajectoryVisualizationTestHooks`
`setBootstrap``receiveFrame``renderActiveTab``installTabs`;不暴露内部 `state`,不启动 fetch/SSE。生产路径仍调用 `boot()`
- `app.css` 使用 `main > section[hidden] { display: none !important; }` 修复分页隐藏覆盖,并增加 y 轴刻度、语义路径、车辆/边界标记和空状态类。
- `app.js` 新增 `niceDomain``formatTick`,渲染 x/y 双轴刻度及 `.axis-label-x` / `.axis-label-y`;非零常数域围绕实际值生成可读对称范围。
- 世界图层按 coarse、Local G2、方向段、previous、current 的语义顺序绘制,并渲染车辆小轮廓、细朝向线、计划起点、换向点和最终目标标记。
- 路径总览无可绘制路径时显示“未收到 Local G2 路径”,不留下无解释空白画布。
## RED / GREEN / 回归证据
RED
```powershell
npm install --package-lock-only --prefix ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom
npm test --prefix ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom
dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
```
首个有效 DOM 失败为分页隐藏:点击 `ls-st``getComputedStyle(overview).display` 仍为 `grid`,预期为 `none`;随后依次失败于 y ticks、语义路径/标记、empty-state 和测试钩子。.NET 宿主首个有效失败为 `hidden sections cannot be overridden by grid rules`
为运行 DOM 测试,lockfile 生成后补充执行了 `npm install --prefix ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom` 安装 jsdom;验证完成后删除 `node_modules`,未提交任何依赖目录。
GREEN
```powershell
npm test --prefix ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom
```
退出码 0,6 个 DOM 测试全部通过,0 失败。
```powershell
dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
```
退出码 0,输出 `PASS trajectory-planning-visualization`
```powershell
git diff --check
```
退出码 0,仅有仓库既有 LF/CRLF 提示,无新空白错误。
## 执行器审计与清理
```powershell
rg -n "SendXYThSpeed|SendMotion|DriveStop|PredefinedDriveStop|AccumulateSpeed|SetGear|SetBrake" ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest
```
无匹配(rg 退出码 1,符合“无匹配”预期)。
```powershell
rg -n "TrajectoryPlanningVisualizationWebDom|jsdom" ClumsyPilot/TrajectoryPlanningVisualization -g "*.csproj" -g "*.cs" -g "*.html" -g "*.css" -g "*.js"
```
无匹配(rg 退出码 1),确认 npm 测试包不进入生产 Web/插件输出。
## 工作区保护
保留并未回退用户既有脏工作区与未跟踪内容,包括但不限于 `.gitignore``EmPlannerConfiguration.cs`、PathSmoothing/CoarsePath/Map 相关改动、文档和未跟踪报告。
实现提交只显式暂存本阶段白名单文件;提交后 `git diff --cached --name-only` 为空,未使用 `git reset --hard``git checkout --``git clean``git add .``git add -A`
## 已知警告与偏差
- `npm install` 输出 npm 12 升级提示,不影响依赖锁定。
- `git diff --check` 持续输出仓库既有 LF/CRLF 提示,不影响空白检查结果。
- 仓库 `.gitignore` 未忽略 `node_modules/`;本阶段临时生成的 `node_modules` 已删除,避免污染工作区与提交范围。
- `index.html` 在本次白名单内但未改动,现有四页布局未重做。
## 未运行项
- 未进入 Phase 07 或后续任何阶段。
- 未实现图表框选、滚轮缩放、重置、全屏或任何缩放功能。
- 未执行 Native Painter、跨段规划扩展、实车操作或 `em-core-all`
- 本阶段验证命令按任务要求仅执行 jsdom 测试、.NET 可视化宿主、源审计和 `git diff --check`
## Phase 07 事实
对应主计划 Task 9,阶段执行计划 Task 7:每张图独立视域缩放,不改变规划数据。
Phase 07 允许创建/修改:
- 修改:`ClumsyPilot/TrajectoryPlanningVisualization/Web/index.html`
- 修改:`ClumsyPilot/TrajectoryPlanningVisualization/Web/app.css`
- 修改:`ClumsyPilot/TrajectoryPlanningVisualization/Web/app.js`
- 修改:`ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom/dashboard.dom.test.mjs`
- 交接:`docs/superpowers/handoffs/em-full-direction-visualization/phase-07.md`
接口与验证要求:
- 产生每张图的 `chartViewports[id]`,仅包含可见 x/y 数据域。
- 产生框选放大、滚轮缩放、重置和全屏;缩放前后输入快照数组必须保持字节不变。
- SSE 新周期到达时使用仍有效的本地视域重绘,不改写原始数据,也不向规划器发送参数。
必须执行:
```powershell
npm test --prefix ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom
dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
git diff --check
```
实现提交信息:`feat: add observation chart viewport zoom`
交接文件单独提交信息:`docs: record EM full-direction phase 07 handoff`
Phase 07 退出门禁:每张图都有独立局部导航且不修改规划数据;不包含 Native Painter 改动。
Phase 07 的 Phase 06 实现提交为 `05cfd673f504d5ecee000ab9b85c7346436f2076`,上一交接文件为本文件;调用方需在最终答复中提供本交接提交的真实哈希并验证可达。
@@ -1,105 +0,0 @@
# EM 完整方向段可视化修复 - Phase 07 交接
## 状态
完成,2026-08-07。
## 目标与非目标
完成主计划 Task 9、分阶段执行计划 Task 7:每张图独立视域缩放,仅修改浏览器本地视图,不修改规划数据。
未进入 Task 10+、Native Painter、跨段规划扩展、实车操作或 Phase 06 已批准 Web 渲染修复的回退/重做。MovementTest 保持 `OBSERVE_ONLY`,本阶段未触碰任何底盘、转向、制动、电机或换挡写输出。
## 启动事实
- 分支:`trajplanner`
- 入口 HEAD`b7772370dca119d8ff36ed6a690274e460d489ca`Phase 06 handoff
- 上一阶段实现提交:`05cfd673f504d5ecee000ab9b85c7346436f2076`
- 上一阶段 handoff`docs/superpowers/handoffs/em-full-direction-visualization/phase-06.md`
- 祖先检查:`05cfd67``b777237` 均为入口 HEAD 可达祖先
## 实现提交
`8a51629edbe682de8a0fd30a82894e0420ac6c86` - `feat: add observation chart viewport zoom`
仅提交白名单内实际修改文件:
- 修改:`ClumsyPilot/TrajectoryPlanningVisualization/Web/app.css`
- 修改:`ClumsyPilot/TrajectoryPlanningVisualization/Web/app.js`
- 修改:`ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom/dashboard.dom.test.mjs`
`index.html` 在白名单内但未修改,现有 chart host 结构已足够,工具条由 `renderChart` 动态创建。
## 新增接口与语义
- `state.chartViewports[id]` 仅保存当前图表的可见 `x0/x1/y0/y1` 数据域。
- 框选放大:`pointerdown``pointerup` 距离小于 4 CSS 像素时忽略。
- 滚轮缩放:以指针在图表数据坐标中的位置为锚点。
- 重置:只删除当前图表 `id` 的视域。
- 全屏:只请求当前图表 host 的 `requestFullscreen()`,并附加 `is-fullscreen` CSS 回退。
- SSE 新周期到达:`receiveFrame` 重绘时继续使用仍有限且有效的 `chartViewports[id]`;不修改快照数组,不发送规划器参数。
- 缩放不进行数据重采样、不回写快照、不修改服务端参数。
## RED 证据
先写入冻结输入测试,再执行:
```powershell
npm install --prefix ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom
npm test --prefix ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom
```
`npm test` 退出码 1;11 个测试中 8 个通过、3 个失败。
首个有效失败为 `wheel and box zoom change ST ticks and path without mutating frozen snapshot`:框选后 ST 轴刻度仍为 `-0.5|0.25|1|1.75|2.5`,路径 `d` 未变化,确认是缩放控件与 `chartViewports` 状态缺失。`reset``fullscreen` 测试随后因 `button[data-action]` 不存在而失败。
## GREEN / 回归证据
最终代码状态执行:
```powershell
npm test --prefix ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom
dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
git diff --check
```
- `npm test`:退出码 0,11 个 DOM 测试全部通过,0 失败。
- .NET 宿主:退出码 0,输出 `PASS trajectory-planning-visualization`
- `git diff --check`:退出码 0;仅有仓库既有 LF/CRLF 提示,无新空白错误。
- `node --check app.js``node --check dashboard.dom.test.mjs`:均通过。
## 执行器审计与清理
```powershell
rg -n "SendXYThSpeed|SendMotion|DriveStop|PredefinedDriveStop|AccumulateSpeed|SetGear|SetBrake" ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest
```
无匹配,确认未新增 MovementTest 硬件写输出。
```powershell
rg -n "TrajectoryPlanningVisualizationWebDom|jsdom" ClumsyPilot/TrajectoryPlanningVisualization -g "*.csproj" -g "*.cs" -g "*.html" -g "*.css" -g "*.js"
```
无匹配,npm 测试包不进入生产 Web/插件输出。
临时 `node_modules` 在测试完成后已安全删除并确认不存在;未提交依赖目录。
## 工作区保护
保留入口时所有既有脏文件、删除和未跟踪内容,未执行 `git reset --hard``git checkout --``git clean``git add .``git add -A`。实现提交仅显式暂存并提交上述 3 个白名单文件;提交后 `git diff --cached --name-only` 为空。
## 已知警告与偏差
- `git diff --check` 持续输出仓库既有 LF/CRLF 提示,不影响本阶段空白检查结论。
- `index.html` 未修改,因此实现提交实际文件数少于阶段白名单可允许数量。
- 本阶段未运行浏览器截图或真实设备烟雾;DOM 行为由 jsdom 覆盖。
## 未运行项
- 未进入 Phase 08 Native Painter 修复。
- 未进入 Phase 09 文档、全量自动验收或实车验收。
- 未执行 `em-core-all``--smoke-seconds`、浏览器人工操作或实车操作。
## Phase 08 事实
- 阶段:Phase 08,对应主计划 Task 10、分阶段执行计划 Task 8。
- 仓库:`D:\Users\Desktop\项目\prakrobot\ParkingRobot`
- 预期分支:`trajplanner`
- 本阶段实现提交:`8a51629edbe682de8a0fd30a82894e0420ac6c86`
- 本 handoff 真实哈希由 Phase 08 调用方在最终回答中提供。
- 允许修改:
- `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPresentation.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationVisualizationChecks.cs`
- 交接:`docs/superpowers/handoffs/em-full-direction-visualization/phase-08.md`
- 实现提交信息:`fix: correct EM observation painter geometry`
- 交接提交信息:`docs: record EM full-direction phase 08 handoff`
- 核心接口:世界路径 x/y 等比例、LS 横轴 `ReferenceS (m)`、ST 纵轴真实 `PathS (m)`、小车轮廊与细朝向线、`s_end`/换向/最终目标语义;不使用大黑三角。
- RED/GREEN 焦点命令:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
```
- 回归/审计命令:同一 `trajectory-observation` 宿主、MovementTest 执行器 `rg`、`git diff --check`。
- Phase 08 禁止:进入 Task 11、实车操作、跨段规划扩展、重新设计 Web 或 Phase 07 视域缩放。
@@ -1,82 +0,0 @@
# EM 完整方向段可视化修复 - Phase 08 交接
## 状态
完成(2026-08-07)。
## 目标与非目标
完成主计划 Task 10、分阶段执行计划 Task 8Native Painter 几何与语义修复。
实现世界路径 x/y 等比例、LS 横轴 `ReferenceS (m)`、ST 纵轴真实 `PathS (m)`、小车轮廓与细朝向线、`s_end`/换向/最终目标语义。
未进入 Task 11 或 Phase 09;未修改 Web 或规划器;未做跨段规划扩展、实车操作或 Painter 重设计。
MovementTest 保持 `OBSERVE_ONLY`,本阶段未新增底盘、转向、制动、电机或换挡写输出。
## 入口事实
- 分支:`trajplanner`
- 入口 HEAD`d019b080746a083fc3c655e21b229e082051706a`Phase 07 handoff
- 上一阶段实现提交:`8a51629edbe682de8a0fd30a82894e0420ac6c86`
- 祖先检查:`8a51629``d019b08` 均为入口 HEAD 可追溯祖先
- 入口暂存区为空;既有脏工作区与未跟踪内容全部保留
## 实现提交
`37c27bdb1b41502ad23783a041a42512b3a49648` - `fix: correct EM observation painter geometry`
仅提交白名单文件:
- `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPresentation.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationVisualizationChecks.cs`
## 新接口与语义
- `TrajectoryObservationLsPresentationModel.HorizontalAxisLabel``ReferenceS (m)``VerticalAxisLabel``l (m)`
- `DrawPose` 接受 `VehicleParameters`,按车辆长宽绘制四角旋转轮廓,并用 `endArrow: false` 绘制长度不超过半车长的细朝向线。
- 世界路径使用同一 `MillimetersPerMeter` 转换 x/y;粗路径、Local G2、当前轨迹线宽收窄。
- `DrawBoundaryMarkers` 绘制换向点 `换向点 N / s_end`(小菱形)与最终目标 `终点 / s_end`(小叉形),车辆标记为 `车辆`,计划起点为 `计划起点`
- ST Painter 增加 `t (s)``PathS (m)` 轴标签。
- 未新增 Painter 图层、主题或生命周期;三个 Painter 窗口与清理路径保持原状。
## RED 证据
先写入 Task 10 失败测试,再执行:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
```
首次有效失败:
`observer painter removes the large terminal arrow was false`
位置:`TrajectoryObservationChecks.VerifiesPainterPoseGeometryReplacesLargeArrow`
确认 RED 是当前 Painter 仍使用旧的大终端箭头,而不是环境或测试自身错误。
期间测试新增的 `using System.Text` 编译缺失已修复,修复后重新取得上述行为 RED。
## GREEN / 回归证据
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
rg -n "SendXYThSpeed|SendMotion|DriveStop|PredefinedDriveStop|AccumulateSpeed|SetGear|SetBrake" ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest
git diff --check
```
- `trajectory-observation`:退出码 0,输出 `PASS trajectory-observation`
- 执行器审计:无匹配(命令无输出,退出码 1)
- `git diff --check`:退出码 0,仅仓库既有 LF/CRLF 提示,无新空白错误
## 执行器审计与清理
MovementTest/observer 运行时源未出现任何指定执行器写调用。
实现提交仅显式暂存上述 3 个白名单文件;提交后 `git diff --cached --name-only` 为空。
未执行 `git reset --hard``git checkout --``git clean``git add .``git add -A`
## 保留的脏工作区
入口时已存在的 `.gitignore``ClumsyPilot.csproj``EmPlannerConfiguration.cs`、PathSmoothing/CoarsePath 修改或删除、未跟踪目录及报告等全部保留,本阶段未触碰。
## 已知警告与偏差
- 构建输出保留两个既有 obsolete 警告:`Lidar2dDetect2LegTray.LegWidth``MultiWheelChassis.GetSteerWheels()`
- `git diff --check` 持续输出仓库既有 LF/CRLF 提示,不影响本阶段空白检查结论。
- 本阶段未做真实 Painter 窗口截图或浏览器人工验证;自动化验证为源码/模型检查。
## 未运行项
- 未进入 Phase 09 文档、全量自动化、本地 Web smoke 或实车验收。
- 未运行 `em-core-all``em-all`、Node DOM 测试或可视化验证宿主。
- 未重跑 Phase 07 Web 验证;本阶段未修改 Web 文件。
## Phase 09 事实
- 下一阶段:Phase 09,对应主计划 Task 11、分阶段执行计划 Task 9。
- 允许修改:MovementTest/EMPlanner/Web 三个 README;创建 `docs/superpowers/handoffs/em-full-direction-visualization/phase-09.md`;实车验收通过后才创建 `phase-09-vehicle.md`
- Phase 09 自动验收命令包括 Node DOM 测试、可视化宿主、`trajectory-observation``em-all`、完整 build、执行器审计和 `git diff --check`
- 无监督安全环境时,Phase 09 自动窗口状态写 `自动验收完成但实车待验`,不得声称整体完成。
- 本 handoff 提交的真实哈希由 Phase 09 调用方在最终回答中提供。
@@ -1,51 +0,0 @@
# EM 完整方向段可视化修复 - Phase 09 实车续验记录
## 状态
未运行(实车待验,2026-08-07)。
本次续验窗口未找到可确认的有监督安全车辆环境,因此主计划 Task 11 Step 4 的八项实车清单全部记录为未运行;自动化、DOM 或合成 smoke 证据不作为实车验收。
## 入口与祖先检查
- 分支:`trajplanner`
- 入口 HEAD`b76de20fafe1d54723d4d1271e0b14b5900c95bd`
- 文档实现提交:`88a66b8b2a1a4b548d5733d21e94f2fad73df1fe`
- `git merge-base --is-ancestor 88a66b8b2a1a4b548d5733d21e94f2fad73df1fe HEAD`:通过
- `git merge-base --is-ancestor b76de20fafe1d54723d4d1271e0b14b5900c95bd HEAD`:通过
- 工作区既有脏修改与未跟踪内容全部保留,未修改规划器、MovementTest、Web、Painter 或任何实现文件。
## 自动化复核证据
入口工作区存在既有相关代码修改,因此按 phase-09 记录重跑受影响自动化:
| 命令 | 结果 |
| --- | --- |
| `npm ci --prefix ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom` | 39 个包安装成功 |
| `npm test --prefix ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom` | 11/11 通过 |
| `dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj` | `PASS trajectory-planning-visualization` |
| `dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation` | `PASS trajectory-observation`,仅保留两个既有 obsolete 警告 |
| `dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-all` | 20 个组件全部 `PASS` |
| `dotnet build ClumsyPilot/ClumsyPilot.csproj -p:ExcludeLegacyAutoAvoidance=true` | 0 警告、0 错误 |
| `rg -n "SendXYThSpeed\|SendMotion\|DriveStop\|PredefinedDriveStop\|AccumulateSpeed\|SetGear\|SetBrake" ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest` | 无匹配(退出码 1 |
| `git diff --check` | 退出码 0,仅仓库既有 LF/CRLF 提示 |
| 本地 smoke `--smoke-seconds 60` | 退出码 0,输出 token URL;结束后 `2192``1403` 均无监听 |
本地 smoke 仅使用合成快照,作为当前工作区回归证据,不作为实车验收。
## 实车清单
未提供可确认的有监督安全车辆环境:没有受监督安全区域确认,没有运行中的 MovementTest/底盘会话,没有实车日志或截图可引用,也未声明人工监督条件。按安全边界不启动实车观察,也不通过操作真实底盘促成观察条件。
1. Web 开启、Native Painter 默认关闭、UI/控制台/页面显示 `OBSERVE_ONLY`:未运行
2. 当前完整方向段产生完整 LS/ST;静态起步加速、达到可行巡航速度、按包络开始制动并在真实边界停止:未运行
3. ST `PathS` 递增;jerk 为 `N-1` 个区间且无终点后继或假 `JerkLimitExceeded`:未运行
4. 换向展示 `DeltaPosition`、同方向 `DeltaReferenceS``DeltaV``DeltaA`,不做无关局部 `PathS` 直接比较:未运行
5. 真换向时 N 保持高亮至连续停车保持和三个带符号速度样本通过,等待文案明确:未运行
6. 粗路径、Local G2、当前轨迹、车辆位姿、`s_end`、换向点和最终目标视觉可区分:未运行
7. 关闭或慢加载浏览器不影响观察:未运行
8. 停止 MovementTest 释放 HTTP/SSE、端口和 Painter;源码/日志审计无硬件写:未运行
## 结论
Phase 09 整体验收不能视为完成。下一次续验窗口必须在可确认的有监督安全车辆环境中按八项清单执行;全部通过后才写入“完成”并提交 `docs: record EM visualization vehicle acceptance`,出现真实回归才写入“阻塞”。
@@ -1,83 +0,0 @@
# EM 完整方向段可视化修复 - Phase 09 交接
## 状态
自动验收完成但实车待验(2026-08-07)。本窗口完成了主计划 Task 11 和分阶段执行计划 Task 9 的文档、全量自动化与本地确定性 smoke;受监督实车清单未执行,`phase-09-vehicle.md` 未创建,整体验收不能视为完成。
## 目标与非目标
- 更新 MovementTest、EMPlanner、Web 三份 README,记录 full/rolling 开关、`s_end/T_end` 来源、速度与终端容差、图表语义、缩放、停止清理和 `OBSERVE_ONLY` 边界。
- 提交三份 README 后运行 Node DOM、可视化宿主、`trajectory-observation``em-all`、完整 build、执行器审计、`git diff --check` 和 60 秒本地 smoke。
- 未修改 Web/Painter 实现,未扩展规划功能,未进入 Phase 08 之前实现,未派生子智能体,未重新 brainstorming。
- 未执行实车观察;未把自动化或合成 smoke 证据写成实车验收。
## 入口事实
- 分支:`trajplanner`
- 入口 HEAD`26b6246360dd2ac7e3d54ba57207a753b9bc70a7`
- 上一阶段实现提交:`37c27bdb1b41502ad23783a041a42512b3a49648`
- 上一阶段交接提交:`26b6246360dd2ac7e3d54ba57207a753b9bc70a7`
- 祖先检查:`37c27bd``26b6246` 均为入口 HEAD 的可追溯祖先
- 本阶段文档实现提交:`88a66b8b2a1a4b548d5733d21e94f2fad73df1fe``docs: explain full-direction EM observation`
## 修改文件
- `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/README.md`
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md`
- `ClumsyPilot/TrajectoryPlanningVisualization/README.md`
## 文档事实
- `UseFullDirectionSegmentPlanning=true` 是 MovementTest 默认值,映射到 `EmPlanningScope.FullDirectionSegment`;每个活动方向段只发起一次冻结优化,随后重复发布同一轨迹与新鲜观察状态。
- `DistanceHorizonMeters``TimeHorizonSeconds` 只用于 `RollingHorizon` 截断;full 模式保留兼容字段但不使用它们截断。
- `s_end` 来自实际 Local G2 `PathS``T_end` 由可行加速度、巡航、jerk 限速停止和零速 hold 推导。
- 前进期望/硬上限为 `1.0 m/s`,倒车期望/硬上限为 `0.5 m/s`
- 真实边界成功需要终端停止、世界位置误差不超过 `0.03 m`、归一化 yaw 误差不超过 `5 deg``5 * PI / 180 rad`)。
- Web 是主观察界面;Native Painter 仅作为可选正确性/审计输出。
- README 记录了各图轴/单位、jerk `N-1` 规则、语义图层、边界标记和视口缩放;缩放只改变浏览器本地视口,不修改快照或向规划器发送参数。
- 关闭或断开浏览器不会停止观察;MovementTest 停止会回收 HTTP/SSE、端口和可选 Painter。
- MovementTest 不写底盘、转向、制动、电机或挡位命令,全程保持 `OBSERVE_ONLY`
## 自动化证据
| 命令 | 结果 |
| --- | --- |
| `npm test --prefix ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom` | 11/11 通过 |
| `dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/...` | `PASS trajectory-planning-visualization` |
| `dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/... -- trajectory-observation` | `PASS trajectory-observation`,仅两个既有 obsolete 警告 |
| `dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/... -- em-all` | 20 个组件全部 `PASS` |
| `dotnet build ClumsyPilot/ClumsyPilot.csproj -p:ExcludeLegacyAutoAvoidance=true` | 0 警告、0 错误 |
| `rg -n "SendXYThSpeed\|SendMotion\|DriveStop\|PredefinedDriveStop\|AccumulateSpeed\|SetGear\|SetBrake" ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest` | 无匹配,退出码 1 |
| `git diff --check` | 退出码 0,无新空白错误;仅仓库既有 LF/CRLF 提示 |
| 本地 smoke `--smoke-seconds 60` | 退出码 0,输出带 token 的 `http://127.0.0.1:1403/...`;结束后 `1403` 无监听 |
测试依赖 `jsdom` 使用 `npm ci` 按锁文件安装,运行后已删除生成的 `node_modules``package.json``package-lock.json` 未改动。
## 警告与已知偏差
- MovementTest UI 字段 `EnableNativePainterVisualization` 当前默认值为 `true`,而 `TrajectoryObservationSettings` 契约默认值为 `false`。文档已如实记录,并要求受监督车辆清单在实车会话前显式设为 `false`,除非另行审查。
- MovementTest UI 字段 `TimeHorizonSeconds` 当前默认值为 `20.0`,而设置契约默认值为 `2.0`full 模式推导 `T_end`,该值仅保留为滚动兼容输入。
- 未进行人工浏览器截图或真实 Painter 窗口检查;自动化证据来自 DOM 测试、C# 契约宿主和确定性 smoke 宿主。
- `trajectory-observation` 保留两个既有 obsolete 警告(`Lidar2dDetect2LegTray.LegWidth``MultiWheelChassis.GetSteerWheels()`),非本阶段引入。
## 保留的脏工作区与暂存内容
入口时已有的 `.gitignore``ClumsyPilot.csproj``EmPlannerConfiguration.cs`、PathSmoothing/CoarsePath 相关修改或删除、未跟踪目录及报告等全部保留,未回退或覆盖。本阶段未修改这些内容;提交 README 后暂存区为空。除本次 `phase-09.md` 外没有新增应跟踪文件。
## 未运行项与原因
- 受监督实车清单未运行:当前无可确认的受监督安全车辆环境。
- `phase-09-vehicle.md` 未创建:设计明确禁止用自动化或合成证据替代实车验收。
- 未进行人工浏览器/截图检查:smoke 仅作为确定性本机宿主证据。
## Phase 09 实车续验事实
下一窗口是 Phase 09 的实车续验窗口,不是新阶段:
- 仓库根目录:`D:\Users\Desktop\项目\prakrobot\ParkingRobot`
- 期望分支:`trajplanner`
- 启动时核对 `88a66b8b2a1a4b548d5733d21e94f2fad73df1fe` 与 Phase 09 handoff 提交均为祖先。
- 必读:本 handoff、主计划 Task 11 Step 4-6、分阶段执行计划 Task 9 Step 5-6;如相关代码在 handoff 后变化,先重跑受影响的自动化。
- 只允许创建 `docs/superpowers/handoffs/em-full-direction-visualization/phase-09-vehicle.md`,不得修改规划、Web、Painter、MovementTest 或 README。
- 按主计划八项清单执行;MovementTest 必须保持 `OBSERVE_ONLY`,不得通过操作真实底盘促成观察条件。
- 全部通过时状态写“完成”,提交消息 `docs: record EM visualization vehicle acceptance`;出现真实回归时状态写“阻塞”,提交消息 `docs: record blocked EM visualization vehicle acceptance`,并只输出同阶段恢复提示词。
@@ -1,58 +0,0 @@
# EM 观察网页可视化阶段 01 交接
状态:完成
阶段目标:完成可复用 `netstandard2.0` 可视化类库的基础:不可变通用契约、防御性复制、紧凑占用 bitset、容量一帧交换、有界周期历史、已验证选项快照和稳定 JSON;不包含 HTTP/SSE、网页、EM Adapter 或 MovementTest 集成。
基线提交:`fa4938b3fc60620bee2c1694e232a5c2b35ca392``b0b79e5` 已确认是祖先)。
实现提交:
- `55d8e1eebf592b03caff28dfee28745959e41e09``feat: add planning visualization contracts`
- `ab8e4010f8d42dd8ddcd6066b82eaba7c163fc28``feat: add bounded visualization snapshots`
修改文件:
- `ClumsyPilot/ClumsyPilot.csproj`
- `ClumsyPilot/TrajectoryPlanningVisualization/TrajectoryPlanningVisualization.csproj`
- `ClumsyPilot/TrajectoryPlanningVisualization/Contracts/VisualizationPrimitives.cs`
- `ClumsyPilot/TrajectoryPlanningVisualization/Contracts/VisualizationGeometry.cs`
- `ClumsyPilot/TrajectoryPlanningVisualization/Contracts/VisualizationCharts.cs`
- `ClumsyPilot/TrajectoryPlanningVisualization/Contracts/PlanningVisualizationSnapshots.cs`
- `ClumsyPilot/TrajectoryPlanningVisualization/Runtime/PlanningVisualizationOptions.cs`
- `ClumsyPilot/TrajectoryPlanningVisualization/Runtime/LatestVisualizationFrameStore.cs`
- `ClumsyPilot/TrajectoryPlanningVisualization/Runtime/BoundedCycleHistory.cs`
- `ClumsyPilot/TrajectoryPlanningVisualization/Runtime/VisualizationJson.cs`
- `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj`
- `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/Program.cs`
- `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/Verification.cs`
- `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/ContractChecks.cs`
- `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/RuntimeChecks.cs`
新增或确认的接口:
- 不可变契约:`VisualizationPoint``VisualizationPose``VisualizationBounds``VisualizationValue``VisualizationConfigurationGroup``VisualizationPolyline``VisualizationMarker``VisualizationDirectionSegment``VisualizationOccupancyGrid``VisualizationSeries``VisualizationChart``PlanningVisualizationStaticSnapshot``PlanningVisualizationDynamicSnapshot``VisualizationCycleSummary`。所有携带集合的构造函数拒绝 null 元素并复制为 `ReadOnlyCollection<T>`;数值字段拒绝 NaN/Infinity。
- `VisualizationOccupancyGrid` 严格验证 `ceil(rows * columns / 8)` 字节长度,防御性复制 row-major、LSB-first bitset,并只暴露 `OccupancyBitsBase64`
- `PlanningVisualizationOptions.CreateValidatedSnapshot()` 返回不可变 `PlanningVisualizationOptionsSnapshot`;默认端口 0、刷新 10 Hz、历史 60、固定最多 2 客户端。
- `LatestVisualizationFrameStore.Publish` 使用 `Interlocked.Exchange` 发布最新一帧;`TryReadAfter` 只返回较新 store version。
- `BoundedCycleHistory` 有界、按 `CycleVersion` 去重,且 `Snapshot()` 返回新只读副本;`VisualizationJson.Serialize` 以不变文化、ISO 日期、紧凑 camelCase 输出。
验证命令与结果:
1. `git merge-base --is-ancestor b0b79e5 HEAD` — 退出码 0。
2. `dotnet build ClumsyPilot/ClumsyPilot.csproj -p:ExcludeLegacyAutoAvoidance=true`(实现前基线)— 退出码 0;2 条既有弃用警告。
3. `dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj`Task 1 RED)— 退出码 1;首个有效错误为缺少 `TrajectoryPlanningVisualization` 命名空间/契约类型。
4. 同一宿主(Task 1 GREEN)— 退出码 0,输出 `PASS trajectory-planning-visualization`;随后主项目构建退出码 0。
5. 同一宿主(Task 2 RED)— 退出码 1;首个有效错误为缺少 `LatestVisualizationFrameStore`,其余运行时类型同样缺失。
6. 同一宿主连续两次(Task 2 GREEN)— 两次退出码均为 0,均输出 `PASS trajectory-planning-visualization`
7. `dotnet build ClumsyPilot/TrajectoryPlanningVisualization/TrajectoryPlanningVisualization.csproj` — 退出码 0。
8. 阶段退出:`dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj``dotnet build ClumsyPilot/TrajectoryPlanningVisualization/TrajectoryPlanningVisualization.csproj``dotnet build ClumsyPilot/ClumsyPilot.csproj -p:ExcludeLegacyAutoAvoidance=true` — 全部退出码 0;宿主输出 PASS,类库构建 0 警告,主项目构建保留 2 条既有弃用警告。
9. `git diff --check -- <阶段文件路径>``git diff --check 55d8e1e^..ab8e401 -- <阶段文件路径>` — 均退出码 0;已用 `git diff-tree --no-commit-id --name-status -r` 分别核对两个实现提交的实际文件树。
未运行的验证及原因:无。
已知警告或遗留问题:主项目构建持续报告两条实现前已存在的弃用警告:`MovementTests.TireFollowing.cs(70)``Lidar2dDetect2LegTray.LegWidth``TireFollowing.cs(243)``MultiWheelChassis.GetSteerWheels()`;与本阶段无关。
与原计划的偏差:为使 Task 1 的验证宿主能以“契约类型不存在”而非“被引用项目不存在”进入 RED,在 RED 前先创建了不含源码的最小类库项目文件;契约与项目依赖内容仍在 GREEN 阶段完成,未改变功能或文件范围。
下一阶段注意事项:阶段 2 只能实现原类库 Task 3 的 HTTP/SSE 和会话门面。先核对以上两项实现提交与本交接状态,并重新运行独立验证宿主;不得实现网页资源、EM/MovementTest 适配或任何执行器接口。`PlanningVisualizationOptionsSnapshot` 是应传入服务的冻结配置,动态帧完整轨迹只应从 `LatestVisualizationFrameStore` 获取,周期历史只能保存 `VisualizationCycleSummary`
@@ -1,42 +0,0 @@
# EM 观察网页可视化阶段 02 交接
状态:完成
阶段目标:完成独立 `netstandard2.0` 可视化类库的受限 Loopback HTTP/1.1 GET/SSE 传输层与幂等会话门面;不包含网页资源、EM Adapter 或 MovementTest 集成。
基线提交:`106201e87b6144fe7cbff80f2994a6539cf15b98`(阶段 01 交接;`b0b79e5` 已核对为祖先)。
实现提交:`fa1a266``feat: serve planning snapshots on loopback`
修改文件:
- `ClumsyPilot/TrajectoryPlanningVisualization/Runtime/LoopbackHttpRequestReader.cs`
- `ClumsyPilot/TrajectoryPlanningVisualization/Runtime/SseClientConnection.cs`
- `ClumsyPilot/TrajectoryPlanningVisualization/Runtime/LoopbackVisualizationServer.cs`
- `ClumsyPilot/TrajectoryPlanningVisualization/PlanningVisualizationSession.cs`
- `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/Program.cs`
- `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/ServerChecks.cs`
新增或确认的接口:
- `PlanningVisualizationSession.Start(PlanningVisualizationStaticSnapshot)` 返回含 `http://127.0.0.1:<port>/?token=<64-lowercase-hex>``PlanningVisualizationSessionInfo``Publish``Stop``Dispose` 和重复 `Start`/`Stop` 均具备本阶段规定的幂等边界。
- 服务器只以 `TcpListener(IPAddress.Loopback, port)` 监听;可用路由为带 token 的 `/api/bootstrap``/api/events`。请求仅接受 HTTP/1.1 `GET`,读取总时限为 2 秒、头部上限 16 KiB。
- `/api/events` 最多两个客户端;每个客户端以容量一发送槽和 1 秒写超时隔离慢客户端。单 dispatcher 在无浏览器时仍收集有界周期摘要,且只在有 SSE 客户端时序列化帧。
- `Stop` 最多提供 100 ms 发送 `event: end``会话已结束`),然后关闭 socket 和 listener;服务内部故障不从规划发布调用逸出。
验证命令与结果:
1. `git merge-base --is-ancestor b0b79e5 HEAD` — 退出码 0。
2. `git show --stat 55d8e1eebf592b03caff28dfee28745959e41e09``git show --stat ab8e4010f8d42dd8ddcd6066b82eaba7c163fc28` — 文件树与阶段 01 交接一致。
3. `dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj` — 退出码 0,输出 `PASS trajectory-planning-visualization`;提交后重新执行结果相同。
4. `dotnet build ClumsyPilot/TrajectoryPlanningVisualization/TrajectoryPlanningVisualization.csproj` — 退出码 0,0 警告、0 错误。
5. 真实 `TcpClient` 检查已覆盖:403(缺 token)、404(授权未知路由)、405POST)、400HTTP/1.0 与超过 16 KiB 头)、503(第三 SSE 客户端)、UTF-8 `Content-Length`、256-bit token、慢 SSE 客户端下 10,000 次 `Publish` 在 1 秒内完成、无客户端时三条周期摘要、`event:end` 正常停止和端口重绑。
6. `git diff --check fa1a266^ fa1a266 -- <阶段 02 transport 文件>` — 退出码 0`git diff-tree --no-commit-id --name-status -r fa1a266` 仅列出上述六个 Task 3 文件。
未运行的验证及原因:无。
已知警告或遗留问题:工作区仍有 130 个阶段范围外脏项;启动时和提交过程中均未展开、暂存、重置或修改它们。本阶段库构建无警告。
与原计划的偏差:无。为使超限头测试在 Windows 上可靠收到 400,解析器在拒绝其 16 KiB 上限后,仍在同一 2 秒总读取预算内丢弃至 `\r\n\r\n`;超限请求不被解析或接受。
下一阶段注意事项:阶段 03 仅实现原类库 Task 4–5 的嵌入中文网页、独立验证、冒烟模式和 README。它应使用本阶段 `PlanningVisualizationSession``/api/bootstrap``/api/events`,并维持所有路由 token 鉴权;不得引入 EM、MDCS、Painter 或 MovementTest 类型。
@@ -1,51 +0,0 @@
# EM 观察网页可视化阶段 03 交接
状态:完成
阶段目标:完成独立 `netstandard2.0` 可视化类库的嵌入式中文科研风网页、程序集资源路由、确定性合成冒烟模式和独立类库文档;不引入 EM、MDCS、Painter 或 MovementTest 类型。
基线提交:`00501055eb924e8704def7bc7b419248a755ede9`(阶段 02 交接;`b0b79e5` 已核对为祖先)。
实现提交:
- `1ad324ca64d1139d34b2f2848b71f4fd90d4dedb``feat: add scientific planning dashboard`
- `8181f0532fb60067287c4d21d25e43c90758a88a``docs: document planning visualization library`
修改文件:
- `ClumsyPilot/TrajectoryPlanningVisualization/Runtime/EmbeddedWebAssets.cs`
- `ClumsyPilot/TrajectoryPlanningVisualization/Runtime/LoopbackVisualizationServer.cs`
- `ClumsyPilot/TrajectoryPlanningVisualization/TrajectoryPlanningVisualization.csproj`
- `ClumsyPilot/TrajectoryPlanningVisualization/Web/index.html`
- `ClumsyPilot/TrajectoryPlanningVisualization/Web/app.css`
- `ClumsyPilot/TrajectoryPlanningVisualization/Web/app.js`
- `ClumsyPilot/TrajectoryPlanningVisualization/README.md`
- `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/Program.cs`
- `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/SampleSnapshotFactory.cs`
- `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/WebAssetChecks.cs`
- `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/ContractChecks.cs`
新增或确认的接口:
- `EmbeddedWebAssets.ReadText(string)` 从程序集读取 `index.html``app.css``app.js`;静态资源不使用 CDN、Node.js 或网页运行时文件查找。
- 受既有会话 token 保护的 `/?token=…``/app.css?token=…``/app.js?token=…``/api/bootstrap` 返回静态快照和有效刷新率,`/api/events` 保持既有 SSE 契约。
- 页面固定页签 ID 为 `overview``ls-st``kinematics``history-config`,固定 chart ID 为 `ls``st``curvature-s``curvature-t``velocity-t``acceleration-t``jerk-t``yaw-rate-t`
- `SampleSnapshotFactory` 只使用通用可视化契约,生成两段方向、换向标记、冻结配置和八张图;`--smoke-seconds N` 以 10 Hz 发布 N 秒并打印一次完整 URI。
验证命令与结果:
1. `git merge-base --is-ancestor b0b79e5 HEAD` — 退出码 0。
2. `dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj` — 退出码 0,输出 `PASS trajectory-planning-visualization`
3. `dotnet build ClumsyPilot/TrajectoryPlanningVisualization/TrajectoryPlanningVisualization.csproj` — 退出码 0,0 警告,0 错误。
4. `dotnet build ClumsyPilot/ClumsyPilot.csproj -p:ExcludeLegacyAutoAvoidance=true` — 退出码 0,0 错误;保留两个既有 obsolete 警告,位于 `MovementTests.TireFollowing.cs``TireFollowing.cs`,均不在本阶段文件范围。
5. `Resolve-Path` 后从 `$env:TEMP` 执行验证宿主 — 退出码 0,输出 `PASS trajectory-planning-visualization`,证明网页资源来自程序集而非当前工作目录。
6. `dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj -- --smoke-seconds 1` — 退出码 0,恰输出一条 `http://127.0.0.1:<port>/?token=<64-lowercase-hex>` URI 后退出。
7. `git diff --check 0050105..HEAD -- <阶段 03 文件>` — 退出码 0,无空白错误;`git diff-tree --no-commit-id --name-status -r 1ad324c` 仅列出 Task 4 的九个文件,`git diff-tree --no-commit-id --name-status -r 8181f05` 仅列出 Task 5 的两个文件。
未运行的验证及原因:无。
已知警告或遗留问题:启动时工作区有 130 项阶段范围外脏项,暂存区为空;本阶段未展开、暂存、重置、覆盖或提交它们。主项目构建的两个 obsolete 警告同样不属于本阶段。
与原计划的偏差:`WebAssetChecks.cs` 在基线中不存在,虽在 Task 4 文件清单标为 Modify,实际按该任务的“Create WebAssetChecks”步骤新建;其余实现范围和两个独立提交均按 Task 4–5 执行。
下一阶段注意事项:阶段 04 只实施集成计划 Task 1–2 的设置同步与纯换向段状态机。可复用类库已经完成,但不得向其引入 EM、MDCS、Painter 或 MovementTest 类型;继续保持 `OBSERVE_ONLY`,并保护 MovementTest 中用户已经设置的 5.0 s、100000、2.0 s。
@@ -1,44 +0,0 @@
# EM 观察网页可视化阶段 04 交接
状态:完成
阶段目标:完成集成计划 Task 1–2 的观察参数同步、网页/Painter 冻结开关,以及纯顺序方向段换向确认状态机;不实现活动段控制器、图表适配、网页托管或插件发布。
基线提交:`0bc8df9d5ecabd259dbd4d5db8594e590fbd0012`(阶段 03 交接)。`b0b79e5` 已核对为祖先。
实现提交:
- `c5ea4f6` `feat: configure observation visualization`
- `0a9c34d` `feat: track observed direction segments`
修改文件:
- `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationContracts.cs`
- `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs`
- `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/README.md`
- `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationSegmentTracker.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationSettingsChecks.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationSegmentChecks.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs`
新增或确认的接口:
- `TrajectoryObservationSettings` 的默认求解器超时、OSQP 迭代和 ST 时域分别冻结为 `5.0 s``100000``2.0 s`;新增网页/Painter、端口、10 Hz、60 轮历史及换向确认参数,端口仅允许 `0``1024..65535`
- MovementTest 的公共字段与上述设置一对一复制,然后通过 `CreateValidatedSnapshot()` 冻结;网页与 Painter 默认关闭,网页自动打开默认开启。
- `TrajectoryObservationSegmentTracker``TrajectoryObservationSegmentState``TrajectoryObservationSegmentUpdate``TrajectoryObservationSegmentPhase` 为纯状态机接口。它仅按 `N -> N+1` 推进,并要求匹配的已到绝对末点 GearSwitch 轨迹、双段连接点投影、连续真实停车保持和三个严格递增的同向速度样本。错误方向、零速、重复序列、投影超限、非 GearSwitch 与时间倒退均重置确认;末段进入 `Completed`
验证命令与结果:
1. `git merge-base --is-ancestor b0b79e5 HEAD`:退出码 `0`
2. `dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation`:退出码 `0`,输出 `PASS trajectory-observation`。Task 2 的 GREEN 连续运行两次均通过;最终退出验证再次通过。
3. `dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj`:退出码 `0`,输出 `PASS trajectory-planning-visualization`
4. `rg -n "SendXYThSpeed|SendMotion|DriveStop|PredefinedDriveStop|AccumulateSpeed" ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest`:退出码 `1`,无匹配;观察运行源码没有这些接口调用。`TrajectoryObservationChecks.cs` 中存在同名字符串,仅用于读取运行时源文件并执行禁止调用审计,不是运行时调用。
5. `git diff --check c5ea4f6^..0a9c34d -- <阶段 04 文件>`:退出码 `0`,无空白错误。`git diff-tree --no-commit-id --name-status -r c5ea4f6` 仅列出 Task 1 的五个文件;同命令对 `0a9c34d` 仅列出 Task 2 的三个文件。
未运行的验证及原因:无。
已知警告或遗留问题:主项目构建保留两个既有 obsolete 警告,位于 `MovementTests.TireFollowing.cs``TireFollowing.cs`,均不属于本阶段文件。启动时工作区有 130 项脏变更、暂存区为空;本阶段只精确暂存和提交了 Task 1–2 文件,交接前其余 129 项仍未展开、重置或提交。
与原计划的偏差:首次基线构建因本次命令遗留的 MSBuild 节点短暂锁定 `TrajectoryPlanningVisualization.dll` 而失败(CS2012)。确认 PID 的启动时间和命令行后仅终止该本次失败命令的孤立节点,随后原样重跑两个基线入口并通过;没有代码偏差。
下一阶段注意事项:阶段 05 仅实施集成计划 Task 3。必须使用本阶段 tracker 的不可变状态作为唯一活动方向段来源;同段保留当前协调器轨迹作为 seed,跨方向时更换协调器且新段第一次请求的 `PreviousTrajectory` 必须为 `null`。继续保持 `OBSERVE_ONLY`,不得引入执行器写调用。
@@ -1,43 +0,0 @@
# EM 观察网页可视化阶段 05 交接
状态:完成
阶段目标:完成集成计划 Task 3 的活动方向段滚动规划控制器和异步观察循环;保持 `OBSERVE_ONLY`,不实现静态/动态图表 Adapter、网页生命周期或插件发布。
基线提交:`e141a76fdbef33f20821d6b69cad684d438eef43`(阶段 04 交接)。`b0b79e5` 已核对为祖先。
实现提交:
- `51f744b` `feat: observe EM planning across gear segments`
修改文件:
- `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPipeline.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationSegmentChecks.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs`
新增或确认的接口:
- `TrajectoryObservationController.ActiveSegment``SegmentState``PreviousTrajectoryForVisualization` 由 tracker 的不可变状态统一驱动;`TryAdvanceSegment(...)` 仅在 tracker 确认 `N -> N+1` 后替换 coordinator/executor。
- 同段请求使用当前 coordinator 的 `PublishedTrajectory` 作为 previous seed;确认换向时旧轨迹只保留在 `PreviousTrajectoryForVisualization`,新 coordinator 首次请求的 `PreviousTrajectory``null`
- 会话 trajectory ID 在 coordinator 替换后继续单调增长;`CreateEffectiveConfigurationSnapshot()` 返回 `configuration.Copy()`
- 等待换向时 executor 使用当前确认方向、下一段期望方向和 `directionConfirmed=false`;没有伪造档位读数。
- `TrajectoryObservationLoopTick` 暴露 `SegmentAdvanced` 与 tracker 的不可变 `SegmentState`。loop 仅在已消费完成任务且无飞行任务时调用 `TryAdvanceSegment`,不会为切段取消规划。
验证命令与结果:
1. `git merge-base --is-ancestor b0b79e5 HEAD`:退出码 `0`
2. RED`dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation`:退出码 `1`,首个断言为 `active observation segment is not hardcoded to zero was false`,准确捕获既有 `segmentIndex = 0`
3. GREEN 与最终回归:`dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation`:退出码 `0`,输出 `PASS trajectory-observation`
4. `dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- coordinator`:退出码 `0`,输出 `PASS coordinator`
5. `dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- executor`:退出码 `0`,输出 `PASS executor`
6. `rg -n "segmentIndex = 0|SendXYThSpeed|SendMotion|DriveStop|PredefinedDriveStop|AccumulateSpeed" ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest`:退出码 `1`,无匹配;运行源码既无硬编码活动段,也无执行器写调用。
7. `git diff --check 51f744b^..51f744b -- <Task 3 三个文件>`:退出码 `0``git diff-tree --no-commit-id --name-status -r 51f744b` 只列出这三个 Task 3 文件。
未运行的验证及原因:无。
已知警告或遗留问题:退出验证中的主项目编译保留两条既有 obsolete 警告,位于 `MovementTests.TireFollowing.cs``Lidar2dDetect2LegTray.LegWidth``TireFollowing.cs``MultiWheelChassis.GetSteerWheels()`;均不属于本阶段文件。启动时 `trajectory-observation` 首次构建曾因 `TrajectoryPlanningVisualization.dll` 的一次性写锁报 CS2012(退出码 `1`);经进程与目标检查后单独重跑同一入口通过,未修改源码处理该环境锁。实现提交后仍有 129 项无关工作区脏项,暂存区为空,均未展开、重置或提交。
与原计划的偏差:无代码范围偏差;Task 3 的三文件实现提交保持独立。阶段文件按阶段协议单独提交。
下一阶段注意事项:阶段 06 只实现集成计划 Task 4–5 的静态/动态快照、图表和 handoff 指标。它必须消费 `ActiveSegment``SegmentState``PublishedTrajectory``PreviousTrajectoryForVisualization`,不能恢复跨方向 previous seed,也不能改变 `OBSERVE_ONLY` 或引入网页生命周期。
@@ -1,46 +0,0 @@
# EM 观察网页可视化阶段 06 交接
状态:完成
阶段目标:完成集成计划 Task 4–5 的 EM 静态/动态数据可视化适配,在不启动网页会话、不改变 Painter 生命周期或插件发布的前提下,为冻结配置、地图/路径、运动学图表和周期交接提供可验证的不可变快照。
基线提交:`a7dde0f5e4c37438f775b706e3886ace00fe0e04`;启动检查确认 `b0b79e5` 是祖先,分支为 `trajplanner`
实现提交:
- `0a1cbe9` `feat: export EM observation configuration`
- `a7dde0f` `feat: visualize EM rolling kinematics`
- `cc6b0f1` `fix: complete EM observation visualization contracts`
修改文件:
- `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationStaticSnapshotBuilder.cs`
- `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationKinematicChartBuilder.cs`
- `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationHandoffAnalyzer.cs`
- `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationDynamicSnapshotBuilder.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationVisualizationChecks.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs`
新增或确认的接口:
- `TrajectoryObservationStaticSnapshotBuilder.Build(...)` 按固定十组顺序导出冻结生效配置、车辆/地图和网页/换向确认字段;占据图为 row-major、LSB-first 紧凑 bitset,静态数据包含粗路径、完整 Local G2、方向段与换向标记。
- `TrajectoryObservationKinematicChartBuilder.Build(...)` 固定导出 `ls``st``curvature-s``curvature-t``velocity-t``acceleration-t``jerk-t``yaw-rate-t`jerk 仅有 `N-1` 个真实区间,LS 使用完整活动段的共享 `ReferenceS`ST 保留局部 `PathS`
- `TrajectoryObservationHandoffAnalyzer.Analyze(...)` 使用 `EffectiveAtUtc + TimeFromStart` 采样,保留世界位置、速度、加速度差;仅在任一投影失败时使 `DeltaReferenceSMeters` 不可用,不比较独立局部 `PathS`
- `TrajectoryObservationDynamicSnapshotBuilder.Build(...)` 产生当前/上一轨迹、活动段/当前视界、八图、滚动或精确停车中文语义、真实终端 `v/a`、换向确认和交接状态;`VisualizationCycleSummary` 不含完整点数组。
验证命令与结果:
1. RED`dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation`,退出码 `1`;首个有效断言为 `static configuration group count expected 10 but was 4`,证明原验证仅检查类型存在且静态导出未满足契约。
2. `dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation`,退出码 `0`,输出 `PASS trajectory-observation`
3. `dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-core-all`,退出码 `0`,输出 `PASS longitudinal-model``PASS longitudinal-integration``PASS trajectory``PASS em-planning-service`
4. `dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj`,退出码 `0`,输出 `PASS trajectory-planning-visualization`
5. `rg -n "rolling publishes the two-second ST knot count|cycle 1 has nonzero terminal speed|jerk has N-1 intervals" ...` 确认 rolling service 的 21 knot、非零末速断言和本阶段 jerk 20 区间断言均已纳入通过的入口。
6. `git diff --check 0a1cbe9^..cc6b0f1 -- <Task 45 路径>`,退出码 `0`;已核对 `0a1cbe9``a7dde0f``cc6b0f1` 的精确文件树。
未运行的验证及原因:无。
已知警告或遗留问题:主项目构建仍报告两条既有 obsolete 警告,位于 `MovementTests.TireFollowing.cs``Lidar2dDetect2LegTray.LegWidth``TireFollowing.cs``MultiWheelChassis.GetSteerWheels()`,均不属于本阶段文件。一次并发启动三个验证宿主触发共享 DLL 写锁(CS2012/MSB3026);根因是并发构建,随后按串行方式重跑三条入口均通过,未修改代码规避该环境锁。
与原计划的偏差:原 Task 4–5 实现提交已存在但验证仅检查适配器类型可构造。本阶段以 TDD 增补真实行为断言并用 `cc6b0f1` 精确修正既有阶段文件;未扩大到 Task 6–7,也未启动网页会话、修改 Painter 生命周期或插件发布。
下一阶段注意事项:阶段 07 只执行集成计划 Task 6–7。应消费本阶段静态/动态快照构建器,添加网页发布熔断与可选 Painter 生命周期、插件 DLL 打包和操作文档;继续保持 `OBSERVE_ONLY`,不得把观察循环变为控制输出。
@@ -1,52 +0,0 @@
# EM 观察网页可视化阶段 07 交接
状态:完成
阶段目标:完成集成计划 Task 67:MovementTest 的回环网页会话生命周期、一次性故障隔离、可选原生 Painter、插件 DLL 原子打包和操作文档;保持 `OBSERVE_ONLY`,不进行实车验收。
基线提交:`819ff7324b4242ba9fad158618b49a49d76e93f2`(启动时分支 `trajplanner`,批准设计 `b0b79e5` 与 phase-06 实现提交均为祖先)。
实现提交:
- `fbd3ba6` `feat: host EM observation dashboard`
- `6034568` `docs: package EM observation dashboard`
修改文件:
- `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationVisualizationPublisher.cs`
- `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationVisualizationChecks.cs`
- `ClumsyPilot/scripts/Publish-ClumsyPilotPlugin.ps1`
- `ClumsyPilot/tests/EMPlannerVerificationHost/PluginPackagingChecks.cs`
- `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/README.md`
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md`
新增或确认的接口:
- 内部 `ITrajectoryObservationVisualizationSink` 提供 `Start``Publish``Stop`;生产 sink 包装一个 `PlanningVisualizationSession`,验证宿主注入记录/抛错 sink,不模拟网页类库。
- `TrajectoryObservationVisualizationPublisher` 仅在启用且到达 `WebRefreshRateHz` 门控点时构建动态快照;第一次网页快照、服务或适配器异常会停止 sink、仅记录一次中文原因并永久熔断本会话网页输出,观察循环继续。
- `EnableWebVisualization=false` 不创建网页静态/动态快照或历史,也不启动服务;成功 bootstrap 后才建立静态快照、记录完整 token URI,并把浏览器启动失败与服务生命周期隔离。
- 原生 Painter 不再静态预创建;只在 `EnableNativePainterVisualization=true` 的会话内创建。`TestStop()`、会话替换和当前会话故障会幂等停止网页、释放端口并清理既有 Painter。
- 实际 tick 使用 `controller.ActiveSegment`、当前/上一轨迹和当前循环信息创建网页快照;不在观察循环等待网页序列化或客户端。
- 发布脚本只从显式 `ManagedDll` 同级目录解析 `TrajectoryPlanningVisualization.dll`,先将其复制进 staging,再保留既有 root 防护、OSQP hash、原子交换与恢复逻辑。插件精确树现为 `ClumsyPilot.dll``TrajectoryPlanningVisualization.dll``osqp.dll` 和三份 OSQP 许可证。
验证命令与结果:
1. RED`dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation`,退出 `1`;首个有效错误为缺少 `ITrajectoryObservationVisualizationSink`,确认新 seam 尚未实现。
2. GREEN:同一 `trajectory-observation` 命令,退出 `0`,输出 `PASS trajectory-observation`;验证 throwing sink 单次熔断、20 Hz tick 下 10 Hz 最多 11 帧、禁用网页不建快照、Painter 源码惰性创建。
3. Task 7 RED`dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- plugin-package`,退出 `1`;精确包树缺少 `plugins/TrajectoryPlanningVisualization.dll`
4. 最终 `trajectory-observation`,退出 `0`,输出 `PASS trajectory-observation`
5. 最终 `plugin-package` 连续两次均退出 `0`,均输出 `PASS plugin-package`;验证精确包树、程序集名 `TrajectoryPlanningVisualization` 和无 staging/backup 残留。
6. `dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj`,退出 `0`,输出 `PASS trajectory-planning-visualization`
7. `dotnet build ClumsyPilot/ClumsyPilot.csproj -p:ExcludeLegacyAutoAvoidance=true`,退出 `0`,输出 `0 个警告 / 0 个错误`
8. `rg -n "SendXYThSpeed|SendMotion|DriveStop|PredefinedDriveStop|AccumulateSpeed" ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest` 无匹配,确认运行观察源码没有执行器调用。
9. `git diff --check fbd3ba6^..6034568 -- <Task 67 文件>`,退出 `0`;并以 `git diff-tree` 核对两个实现提交的精确文件树和插件输出契约。
未运行的验证及原因:未执行 Task 8 的 30 秒网页烟雾会话或任何实车观察/验收;本阶段授权范围严格限于 Task 6–7。
已知警告或遗留问题:`trajectory-observation` 编译时仍可见 phase-06 已记录的两条非本阶段 obsolete 警告:`MovementTests.TireFollowing.cs``Lidar2dDetect2LegTray.LegWidth``TireFollowing.cs``MultiWheelChassis.GetSteerWheels()`。Task 7 首次 GREEN 尝试曾在 staging `Directory.Move` 处遇到瞬时 Windows 拒绝访问;随后以同一脚本在独立临时目录连续两次原子交换成功,且最终 `plugin-package` 连续两次通过,未修改既有交换/恢复机制。
与原计划的偏差:无功能偏差。计划文件列出的 `TrajectoryObservationPresentation.cs` 无需修改:其 Painter 获取仍在实例字段初始化中,而实例本身已被 MovementTest 的会话级开关完全隔离,避免无关重写。
下一阶段注意事项:仅可在新窗口执行 Task 8 自动化与受监督实车观察;必须保持 `OBSERVE_ONLY`,不得把网页或轨迹输出接入任何车辆写接口。先运行完整自动化入口,再按清单记录网页烟雾和实车结果;若实车不可用,必须明确标为待验收。
@@ -1,41 +0,0 @@
# EM 观察网页可视化阶段 08 交接
状态:自动验收完成但实车待验
阶段目标:完成集成计划 Task 8 的全量自动化、确定性本地网页烟雾和实车观察验收边界记录;不扩展功能,不修改运行时代码,并持续保持 `OBSERVE_ONLY`
基线提交:`b45ec357b641ad3e855d76b1f675db2aeb155647`(分支 `trajplanner``b0b79e5` 已确认是祖先)。
实现提交:无。本阶段未发现回归,未作源码或测试修改。
修改文件:
- `docs/superpowers/handoffs/em-observation-web/phase-08.md`
新增或确认的接口:无新运行时接口。确认两个验证宿主、`em-all`、插件包验证、主项目构建和确定性网页烟雾入口仍可用。
验证命令与结果:
1. 启动检查:phase-01.md 至 phase-07.md 全部存在且状态为“完成”;抽查并确认 `55d8e1e``ab8e401``fa1a266``1ad324c``8181f05``c5ea4f6``0a9c34d``51f744b``0a1cbe9``a7dde0f``cc6b0f1``fbd3ba6``6034568` 都可由 HEAD 到达。
2. `dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj`:退出 `0`,输出 `PASS trajectory-planning-visualization`
3. `dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation`:退出 `0`,输出 `PASS trajectory-observation`
4. `dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-all`:退出 `0`;所有组件均输出 `PASS`,包括 `plugin-package``trajectory-observation``rolling-end-to-end``rolling-execution-tail`
5. `dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- plugin-package`:退出 `0`,输出 `PASS plugin-package`
6. `dotnet build ClumsyPilot/ClumsyPilot.csproj -p:ExcludeLegacyAutoAvoidance=true`:退出 `0`,输出 `0 个警告 / 0 个错误`
7. `git diff --check 55d8e1e^..6034568`:退出 `0`,本功能自 phase-01 起至 phase-07 实现收口范围内无空白错误。
8. `dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj -- --smoke-seconds 30`:确定性合成快照烟雾宿主退出 `0`;第二次会话输出 token URI `http://127.0.0.1:5465/?token=67b53e5a28508927b766e78923720a8a855a96803be41ff23c7b68d8be42e452`,自然退出后检查确认端口 `5465` 不再处于监听状态。
未运行的验证及原因:
- 当前工具没有可用的浏览器控制连接(浏览器枚举为空)。已在 30 秒会话窗口中向操作者提供 token URI,但没有取得人工页面观察反馈;因此中文标题/状态、英文或数学坐标、科研细线白底、四页签、八图表、Canvas 栅格、SVG 路径覆盖、活动段/horizon 高亮、生效配置和过期状态的肉眼核对未记录为通过。独立验证宿主已通过嵌入资源、中文内容、Canvas/SVG、页签和图表契约检查,但这不替代本次肉眼结果。
- 未执行任何实车观察。当前窗口没有可确认的“有人监督且环境安全”的实车条件,故未开启 MovementTest,未连接车辆写接口,也未尝试操作底盘。
- 因此以下实车项待续验:网页开启且 Painter 关闭时的 `OBSERVE_ONLY`rolling、approach、exact-stop 语义;jerk 的 `N-1` 区间与无伪造终点后区间;`DeltaPosition``DeltaReferenceS``DeltaV``DeltaA`;真实换向停车保持加三个带符号速度样本后的 `N -> N+1`;关闭浏览器后规划继续;停止 MovementTest 后网页端口/Painter 回收和全过程无执行器输出。
已知警告或遗留问题:
- `trajectory-observation` 的编译输出仍出现两个既有且与本功能无关的 obsolete 警告:`MovementTests.TireFollowing.cs(70)``Lidar2dDetect2LegTray.LegWidth`,以及 `TireFollowing.cs(243)``MultiWheelChassis.GetSteerWheels()`。同一轮 `dotnet build` 最终输出为 `0 个警告 / 0 个错误`;未将前述既有警告归因于本功能。
- 启动检查统计工作区有 129 项既有未暂存改动,暂存区为空;本阶段未展开、修改、暂存或提交它们。
与原计划的偏差:无功能或源码偏差。浏览器控制能力不可用时,按 Task 8 要求明确请求操作者协助,不伪造肉眼页面结果;该项保留给续验窗口。
下一阶段注意事项:仅在有人监督且环境安全时使用执行手册的“Stage 8 Vehicle Continuation Prompt”继续实车观察。先核对本交接的自动化证据;若 phase-08 后有相关代码变动,先重新运行受影响自动验证。不得通过操作真实底盘帮助观察条件成立,MovementTest 始终保持 `OBSERVE_ONLY`
@@ -1,266 +0,0 @@
# TrapMap Image Export and Console Logging 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 optional 300 DPI full-map PNG export with hard pixel/file limits and an independent switch for TrapMap terminal diagnostics.
**Architecture:** A new `TrapMapImageExporter` renders the completed in-memory grid without depending on CycleGUI/Painter state. `TrapMapTest` owns the two manual switches and calls the exporter only after successful map construction. A shared `TrapMapLog` always writes `DLog` and conditionally mirrors the same message to `Console.WriteLine`.
**Tech Stack:** C# 10, .NET Standard 2.0, internal pure-C# RGBA rasterizer, exact `StbImageWriteSharp` 1.16.7 managed PNG encoder, BCL-only PowerShell PNG parser, existing TrapMap tests.
## Global Constraints
- Do not inspect or modify `TrajPlanner`.
- Do not commit or stage any file.
- Keep `UI.GetPainter("TrapMapTest")` as the world-coordinate Painter; do not reintroduce `false`.
- Keep `UI.GetPainter("MultiWheelTwoLegDetect.Filter", false)` as the car-coordinate ROI Painter.
- Defaults: `_saveFullMapImage = true`, `_enableTerminalDebugLog = true`.
- PNG: 300 DPI, 4 pixels per cell, maximum edge 4000 pixels, maximum final size `50 * 1024 * 1024` bytes.
- Output: `<Environment.CurrentDirectory>\TrapMapExports\TrapMap_yyyyMMdd_HHmmss_fff.png`.
- Export failure never changes `TrapMapBuilder.Succeeded` or clears `GridMap`.
- Do not capture the Clumsy viewport or add point-cloud/motion behavior.
---
### Task 1: Export contract, dependency, and pre-allocation limits
**Files:**
- Modify: `ClumsyPilot/ClumsyPilot.csproj`
- Create: `ClumsyPilot/TrapMapImageExporter.cs`
- Create: `ClumsyPilot/tests/verify_trapmap_image.ps1`
**Interfaces:**
- Consumes: `GridMapData`, `TrapMapVehiclePose`, workstation `Vector2`, tire metadata, output root.
- Produces: `TrapMapImageExportRequest`, `TrapMapImageExportResult`, and `TrapMapImageExporter.ExportIfEnabled(bool, TrapMapImageExportRequest)`.
- [ ] **Step 1: Add a failing image-export reflection test**
Create `verify_trapmap_image.ps1`. Load `ClumsyPilot/bin/Debug/netstandard2.0/ClumsyPilot.dll`; require types `MultiWheelC.TrapMapImageExporter`, `TrapMapImageExportRequest`, and `TrapMapImageExportResult`, and assert the assembly/output no longer contains a platform drawing dependency. Create a temporary directory under `$env:TEMP`, invoke the disabled path with an output directory that does not exist, and assert `Saved=false`, `Skipped=true`, and that no directory was created. Invoke an oversized request using a `1000×1` grid at 50mm so the four-pixels-per-cell canvas plus padding exceeds 4000, and assert rejection before any PNG/temp file exists.
Use these assertion helpers and cleanup guard:
```powershell
function Assert-Equal($expected, $actual, [string]$message) {
if ($expected -ne $actual) { throw "$message Expected=$expected Actual=$actual" }
}
$testRoot = Join-Path $env:TEMP ("trapmap-image-test-" + [guid]::NewGuid().ToString('N'))
try {
# reflection setup and assertions
} finally {
if (Test-Path -LiteralPath $testRoot) {
Remove-Item -LiteralPath $testRoot -Recurse -Force
}
}
```
- [ ] **Step 2: Run RED verification**
```powershell
dotnet restore ClumsyPilot\ClumsyPilot.csproj
dotnet build ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File ClumsyPilot\tests\verify_trapmap_image.ps1
```
Expected: compilation succeeds and the image script fails because `TrapMapImageExporter` is absent.
- [ ] **Step 3: Add the managed PNG dependency**
Add the exact managed encoder package inside the package `ItemGroup` in `ClumsyPilot.csproj`. Keep the target framework unchanged, expose the package path, and use one explicit build target that copies only its single managed `netstandard2.0` runtime asset. No drawing-runtime or native asset is required:
```xml
<ItemGroup>
<PackageReference Include="StbImageWriteSharp" Version="1.16.7"
GeneratePathProperty="true" />
</ItemGroup>
<Target Name="DeployManagedPngRuntime" AfterTargets="Build">
<Copy SourceFiles="$(PkgStbImageWriteSharp)\lib\netstandard2.0\StbImageWriteSharp.dll"
DestinationFiles="$(TargetDir)StbImageWriteSharp.dll" />
</Target>
```
Do not change the target framework.
- [ ] **Step 4: Implement request/result types and dimension validation**
Create `TrapMapImageExporter.cs` in namespace `MultiWheelC`. Use exact constants:
```csharp
public const int PixelsPerCell = 4;
public const int MaximumImageEdgePixels = 4000;
public const long MaximumFileSizeBytes = 50L * 1024L * 1024L;
public const float OutputDpi = 300f;
public const int OuterPaddingPixels = 24;
public const int HeaderHeightPixels = 140;
```
Request properties must include `GridMap`, `VehiclePose`, `WorkstationWorld`, `TireLayerStatus`, `TireLayerMessage`, `InputSource`, and `OutputRootDirectory`. Result properties must include `Saved`, `Skipped`, `FilePath`, `Message`, `FileSizeBytes`, `PixelWidth`, and `PixelHeight`.
`ExportIfEnabled(false, request)` returns a skipped result before validating the request or touching the filesystem. Enabled export validates non-null map/pose, finite workstation, then computes with `long`:
```csharp
long pixelWidth = 2L * OuterPaddingPixels + (long)grid.Cols * PixelsPerCell;
long pixelHeight = HeaderHeightPixels + 2L * OuterPaddingPixels
+ (long)grid.Rows * PixelsPerCell;
```
Reject non-positive or over-4000 dimensions before allocating the RGBA surface. Add `public static bool IsFileSizeAllowed(long byteCount)` returning `byteCount >= 0 && byteCount <= MaximumFileSizeBytes`; the actual save path must call this same function.
- [ ] **Step 5: Run GREEN contract checks**
Run the Task 1 commands. Expected: disabled/oversized assertions pass, no output directory exists for disabled export, and no RGBA buffer is allocated for oversized export.
### Task 2: Complete 300 DPI PNG rendering and atomic 50MB save
**Files:**
- Modify: `ClumsyPilot/TrapMapImageExporter.cs`
- Modify: `ClumsyPilot/tests/verify_trapmap_image.ps1`
**Interfaces:**
- Consumes: validated Task 1 request.
- Produces: a complete PNG or a failure result with no final/temporary file.
- [ ] **Step 1: Add failing PNG behavior assertions**
Extend the test with a `2×2` 50mm grid, mark one known occupied cell, set a finite vehicle/workstation, and export twice. Assert:
```powershell
Assert-Equal $true $result.Saved 'Small map must save.'
Assert-Equal $false $result.Skipped 'Enabled successful export is not skipped.'
if (-not (Test-Path -LiteralPath $result.FilePath)) { throw 'PNG file missing.' }
if ((Get-Item -LiteralPath $result.FilePath).Length -gt 50MB) { throw 'PNG exceeds 50MB.' }
if ([IO.Path]::GetExtension($result.FilePath) -ne '.png') { throw 'Output is not PNG.' }
if ((Split-Path $result.FilePath -Leaf) -notmatch '^TrapMap_\d{8}_\d{6}_\d{3}(_\d+)?\.png$') {
throw 'Timestamp filename is invalid.'
}
$image = Read-PngRgba $result.FilePath
Assert-Equal $result.PixelWidth $image.Width 'PNG width mismatch.'
Assert-Equal $result.PixelHeight $image.Height 'PNG height mismatch.'
Assert-Equal 11811 $image.PixelsPerMetreX 'PNG horizontal pHYs mismatch.'
Assert-Equal 11811 $image.PixelsPerMetreY 'PNG vertical pHYs mismatch.'
```
Assert the two file paths differ. Assert `IsFileSizeAllowed(50MB)` is true and `IsFileSizeAllowed(50MB + 1)` is false. Assert no `*.tmp` remains.
- [ ] **Step 2: Run RED behavior test**
Run the image test. Expected: it fails because enabled rendering/save is not implemented.
- [ ] **Step 3: Render the full grid**
After validation, create an internal RGBA8 surface and draw cells, grid, overlays, and 5×7 bitmap text with clipped integer primitives. Encode the buffer with `StbImageWriteSharp`, then insert a CRC-protected `pHYs` chunk containing `11811,11811,1` immediately after `IHDR`.
Exact layout and colors:
```text
Canvas background: White
Map top-left: (OuterPaddingPixels, HeaderHeightPixels + OuterPaddingPixels)
Unmarked cell interior: White
Grid lines: LightGray, 1px
Occupied cell interior: Red
Map border: Black, 2px
Vehicle outline/center/heading: Blue
Workstation circle/cross/text: LimeGreen
Header text: Black
```
For cell `(col,row)`, invert Y:
```csharp
int imageCol = col;
int imageRow = grid.Rows - 1 - row;
int x = mapLeft + imageCol * PixelsPerCell;
int y = mapTop + imageRow * PixelsPerCell;
```
Fill occupied interiors before drawing all vertical/horizontal grid lines. Convert vehicle rectangle corners and workstation through a shared world-to-pixel helper using `(worldX - XMin) / ResolutionMm` for X and `((YMin + Rows * ResolutionMm) - worldY) / ResolutionMm` for Y. The Y expression uses the discrete raster's actual upper edge, so overlays stay aligned when the requested world bounds are not an exact multiple of the resolution. Draw title strings for bounds, resolution, rows/cols, occupancy, obstacle count, tire status/message, and input source.
- [ ] **Step 4: Implement collision-safe atomic save and cleanup**
Create `<OutputRootDirectory>\TrapMapExports` only after all request/dimension validation. Select the millisecond timestamp name; if it exists, append `_1`, `_2`, etc. Encode into the exclusively reserved `finalPath + ".tmp"` stream, read `FileInfo.Length`, call `IsFileSizeAllowed`, delete the temp on rejection, then `File.Move(tempPath, finalPath)`.
Wrap rendering/saving in `try/catch/finally`; `finally` deletes only the current temp path if present. Never delete an existing final PNG. Return failure messages instead of throwing into the test runner.
- [ ] **Step 5: Run image and existing behavior tests**
```powershell
dotnet build ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File ClumsyPilot\tests\verify_trapmap_image.ps1
powershell -ExecutionPolicy Bypass -File ClumsyPilot\tests\verify_trapmap_grid.ps1
powershell -ExecutionPolicy Bypass -File ClumsyPilot\tests\verify_trapmap_lifecycle.ps1
```
Expected: PNG assertions pass and existing behavior scripts retain their passing messages.
### Task 3: Terminal switch and successful-map export integration
**Files:**
- Modify: `ClumsyPilot/MovementTest.Trapmaptest.cs`
- Modify: `ClumsyPilot/tests/verify_trapmap_inputs.ps1`
- Modify: `ClumsyPilot/tests/verify_trapmap_image.ps1`
**Interfaces:**
- Consumes: Task 2 exporter.
- Produces: `_saveFullMapImage`, `_enableTerminalDebugLog`, shared dual-channel logging, and post-success export.
- [ ] **Step 1: Add failing source/integration contracts**
Require exact defaults, `EnableTerminalDebugLog` builder wiring, `TrapMapLog.Write`, and `TrapMapImageExporter.ExportIfEnabled`. Assert source still contains both world-Painter calls without `false`, retains the filter Painter with `false`, and export invocation occurs only after `_builder.Succeeded` is checked.
Add source assertions:
```powershell
if ($source -notmatch '_saveFullMapImage\s*=\s*true') { $failures.Add('Image switch default missing.') }
if ($source -notmatch '_enableTerminalDebugLog\s*=\s*true') { $failures.Add('Terminal switch default missing.') }
if ($source -notmatch 'TrapMapLog\.Write\(') { $failures.Add('Shared TrapMap logger missing.') }
if ($source -notmatch 'Console\.WriteLine\(') { $failures.Add('Terminal mirror missing.') }
if ($source -match 'GetPainter\("TrapMapTest",\s*false\)') { $failures.Add('TrapMap Painter regressed to local coordinates.') }
if ($source -notmatch 'GetPainter\("MultiWheelTwoLegDetect\.Filter",\s*false\)') { $failures.Add('Filter Painter lost local coordinates.') }
```
- [ ] **Step 2: Run RED contract test**
Run `verify_trapmap_inputs.ps1`. Expected: new switch/logger/export assertions fail.
- [ ] **Step 3: Implement the shared logger and switches**
Add a `TrapMapLog` static class with:
```csharp
public static void Write(string message, bool enableTerminal)
{
DLog.Log(message, "TrapMapTest");
if (enableTerminal)
Console.WriteLine($"[TrapMapTest] {message}");
}
```
Add `public bool EnableTerminalDebugLog = true` to the builder. Replace each TrapMap-owned two-argument `DLog.Log` call whose category is exactly `"TrapMapTest"` with `TrapMapLog.Write(message, EnableTerminalDebugLog)` in the builder and with the const switch in `TrapMapTest`. Do not replace unrelated log categories or `Hedingben.ToastText`.
Add the exact two constants to the test manual-edit section and pass terminal configuration into the builder.
- [ ] **Step 4: Invoke image export after successful map construction**
After the `_builder.Succeeded` failure return and after retrieving `grid`, construct the request from `_builder.GridMap`, `VehiclePose`, `WorkstationWorld`, tire status/message/input source, and `Environment.CurrentDirectory`. Call:
```csharp
var export = TrapMapImageExporter.ExportIfEnabled(_saveFullMapImage, request);
TrapMapLog.Write(export.Message, _enableTerminalDebugLog);
if (export.Saved)
Hedingben.ToastText($"栅格图片已保存: {export.FilePath}", "TrapMapTest");
```
Do not alter builder success when export fails/skips.
- [ ] **Step 5: Run full fresh verification**
```powershell
powershell -ExecutionPolicy Bypass -File ClumsyPilot\tests\verify_trapmap_inputs.ps1
dotnet build ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File ClumsyPilot\tests\verify_trapmap_grid.ps1
powershell -ExecutionPolicy Bypass -File ClumsyPilot\tests\verify_trapmap_lifecycle.ps1
powershell -ExecutionPolicy Bypass -File ClumsyPilot\tests\verify_trapmap_image.ps1
git diff --check
$staged = git diff --cached --name-only; if ($staged) { throw "Unexpected staged files: $staged" }
```
Expected: source, compile, grid, lifecycle, and image tests pass; no whitespace errors; no staged files. Confirm an exporter-generated test PNG reports 300 DPI and never exceeds 50MB before test cleanup.
@@ -1,279 +0,0 @@
# Workstation-Bounded Layered Trap Map 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:** Build a task-level world-coordinate grid bounded by the current vehicle, an adjustable workstation, and independent X/Y margins; tire detection remains an optional layer.
**Architecture:** `TrapMapBuilder` validates required inputs, reads the Detour pose, calculates the vehicle/workstation rectangle, creates the base grid, and marks the self layer before attempting tire detection. Tire acquisition records layer health and optionally adds occupancy without controlling map success. `GridMapData` remains the merged output so a future point-cloud layer can rasterize into the same map through a separate step.
**Tech Stack:** C# 10, .NET Standard 2.0, ClumsyCore/ClumsyDance, PowerShell reflection/contract tests, `dotnet msbuild`.
## Global Constraints
- Do not inspect or modify `TrajPlanner`.
- Workstation coordinates use the Detour world frame and millimetres.
- Defaults: `WorkstationX=10000f`, `WorkstationY=0f`, `MapMarginX=3000f`, `MapMarginY=3000f`.
- `Free` means “not marked by an integrated source,” not sensor-confirmed obstacle-free.
- Do not invent a point-cloud API or send chassis commands.
- Reject maps above `4_000_000` cells before allocating `byte[,]`.
---
### Task 1: Executable task-bound calculation
**Files:**
- Modify: `ClumsyPilot/MovementTest.Trapmaptest.cs`
- Modify: `ClumsyPilot/tests/verify_trapmap_grid.ps1`
**Interfaces:**
- Consumes: vehicle/workstation coordinates, X/Y margins, resolution, cell limit.
- Produces: `TrapMapBounds.TryCreate(float carX, float carY, float workstationX, float workstationY, float marginX, float marginY, float resolutionMm, int maxCellCount, out TrapMapBounds bounds, out string failureReason)` plus `XMin`, `XMax`, `YMin`, `YMax`, `Rows`, `Cols`, `CellCount`.
- [ ] **Step 1: Write failing reflection tests**
Append to `verify_trapmap_grid.ps1`:
```powershell
$boundsType = $assembly.GetType('MultiWheelC.TrapMapBounds', $true)
$tryCreate = $boundsType.GetMethod('TryCreate')
function Invoke-Bounds([single]$carX, [single]$carY, [single]$stationX, [single]$stationY,
[single]$marginX, [single]$marginY, [single]$resolution, [int]$maxCells) {
$args = @($carX, $carY, $stationX, $stationY, $marginX, $marginY,
$resolution, $maxCells, $null, $null)
$ok = $tryCreate.Invoke($null, $args)
[pscustomobject]@{ Ok=$ok; Bounds=$args[8]; Reason=$args[9] }
}
$result = Invoke-Bounds 2000 -1000 10000 0 3000 4000 50 4000000
Assert-Equal $true $result.Ok 'Valid bounds must succeed.'
Assert-Equal ([single]-1000) $result.Bounds.XMin 'Wrong XMin.'
Assert-Equal ([single]13000) $result.Bounds.XMax 'Wrong XMax.'
Assert-Equal ([single]-5000) $result.Bounds.YMin 'Wrong YMin.'
Assert-Equal ([single]4000) $result.Bounds.YMax 'Wrong YMax.'
Assert-Equal 280 $result.Bounds.Cols 'Wrong column count.'
Assert-Equal 180 $result.Bounds.Rows 'Wrong row count.'
Assert-Equal $false (Invoke-Bounds 0 0 100000 100000 0 0 20 4000000).Ok 'Oversized map must fail.'
Assert-Equal $false (Invoke-Bounds 0 0 10000 0 -1 3000 50 4000000).Ok 'Negative margin must fail.'
```
- [ ] **Step 2: Prove the new test fails**
Run:
```powershell
dotnet msbuild ClumsyPilot\ClumsyPilot.csproj /t:Compile /p:RestoreIgnoreFailedSources=true /v:minimal
powershell -ExecutionPolicy Bypass -File ClumsyPilot\tests\verify_trapmap_grid.ps1
```
Expected: compile succeeds; the script fails because `MultiWheelC.TrapMapBounds` is absent.
- [ ] **Step 3: Implement the bounds object**
Add `TrapMapBounds` beside the input models. `TryCreate` must validate every float with `TrapMapValue.IsFinite`, require margins `>=0`, resolution and limit `>0`, calculate:
```csharp
float xMin = Math.Min(carX, workstationX) - marginX;
float xMax = Math.Max(carX, workstationX) + marginX;
float yMin = Math.Min(carY, workstationY) - marginY;
float yMax = Math.Max(carY, workstationY) + marginY;
int cols = (int)Math.Ceiling((xMax - xMin) / resolutionMm);
int rows = (int)Math.Ceiling((yMax - yMin) / resolutionMm);
long cellCount = (long)rows * cols;
```
Reject non-finite/degenerate boundaries and `cellCount > maxCellCount` before returning a populated immutable-result object. Return a concrete Chinese reason for every rejection.
- [ ] **Step 4: Recompile and run behavior tests**
Run the Step 2 commands. Expected final line: `TrapMap GridMapData behavior checks passed.`
- [ ] **Step 5: Commit**
```powershell
git add ClumsyPilot/MovementTest.Trapmaptest.cs ClumsyPilot/tests/verify_trapmap_grid.ps1
git commit -m "feat: calculate workstation-bounded trap map"
```
### Task 2: Base-map-first lifecycle and optional tire layer
**Files:**
- Modify: `ClumsyPilot/MovementTest.Trapmaptest.cs`
- Modify: `ClumsyPilot/tests/verify_trapmap_inputs.ps1`
**Interfaces:**
- Consumes: Task 1 `TrapMapBounds.TryCreate` with its declared ten parameters, Detour pose, car dimensions, optional `TwoLegDetect` output.
- Produces: `WorkstationX`, `WorkstationY`, `MapMarginX`, `MapMarginY`, `WorkstationWorld`, `TrapMapTireLayerStatus TireLayerStatus`, and `string TireLayerMessage`.
- [ ] **Step 1: Add failing lifecycle contracts**
Add to `verify_trapmap_inputs.ps1`:
```powershell
if ($source -match '\bMapHalfSizeMm\b') { $failures.Add('Legacy MapHalfSizeMm remains.') }
foreach ($name in 'WorkstationX','WorkstationY','MapMarginX','MapMarginY','TrapMapBounds','TireLayerStatus','TireLayerMessage') {
if ($source -notmatch "\b$name\b") { $failures.Add("Missing layered-map member: $name") }
}
if ($source -notmatch 'WorkstationX\s*=\s*10000f' -or $source -notmatch 'WorkstationY\s*=\s*0f' -or
$source -notmatch 'MapMarginX\s*=\s*3000f' -or $source -notmatch 'MapMarginY\s*=\s*3000f') {
$failures.Add('Required task-map defaults are missing.')
}
$createIndex = $source.IndexOf('TryCreateGridMap(')
$detectIndex = $source.IndexOf('ReadTireLayer(')
if ($createIndex -lt 0 -or $detectIndex -lt 0 -or $createIndex -gt $detectIndex) {
$failures.Add('Base map must precede optional tire acquisition.')
}
if ($source -notmatch 'TRAPMAP_FREE_SEMANTICS') { $failures.Add('Free semantics are undocumented.') }
```
- [ ] **Step 2: Run the contract and confirm failure**
Run `powershell -ExecutionPolicy Bypass -File ClumsyPilot\tests\verify_trapmap_inputs.ps1`.
Expected: legacy half-size, missing members, and ordering assertions fail.
- [ ] **Step 3: Add task-map configuration and status types**
Replace `MapHalfSizeMm` with:
```csharp
public float WorkstationX { get; set; } = 10000f;
public float WorkstationY { get; set; } = 0f;
public float MapMarginX { get; set; } = 3000f;
public float MapMarginY { get; set; } = 3000f;
public const int MaxCellCount = 4_000_000;
public Vector2 WorkstationWorld { get; private set; }
public TrapMapTireLayerStatus TireLayerStatus { get; private set; }
public string TireLayerMessage { get; private set; }
```
Define enum values `NotAttempted`, `Populated`, `NoDetection`, `Unavailable`, `Simulated`. Validate finite workstation coordinates and non-negative finite margins.
- [ ] **Step 4: Reorder `Get()` and split population**
Implement this required sequence:
```csharp
VehiclePose = vehiclePose;
WorkstationWorld = new Vector2(WorkstationX, WorkstationY);
if (!TryCreateGridMap(out var gridMap, out failureReason) ||
!TryPopulateSelfLayer(gridMap, out failureReason)) {
Fail(failureReason);
yield break;
}
ReadTireLayer(self);
TryPopulateTireLayer(gridMap);
```
`TryCreateGridMap` calls Task 1's ten-parameter `TrapMapBounds.TryCreate` and constructs `GridMapData` from the returned limits. The self method marks only the vehicle. Tire population may log a rasterization problem but must not clear or suppress the valid base map.
- [ ] **Step 5: Make tire acquisition non-blocking**
Rename the required-input method to `ReadTireLayer`. Every exit assigns an empty or populated list plus status/message. Use:
```csharp
private void SetEmptyTireLayer(TrapMapTireLayerStatus status, string message)
{
DetectedObstacles = new List<TrapMapObstacle>();
TireLayerStatus = status;
TireLayerMessage = message;
InputSource = "无轮胎障碍输入";
DLog.Log($"轮胎层为空: status={status}, reason={message}", "TrapMapTest");
}
```
Real-mode empty lidar/config errors/exceptions/illegal coordinates become `Unavailable`; `detected == null` becomes `NoDetection`; valid endpoints become `Populated`. Ghost simulation remains `Simulated` and is never a real-mode fallback.
Add beside base-map creation:
```csharp
// TRAPMAP_FREE_SEMANTICS: 当前 Free 仅表示尚未被已接入层标记,
// 不表示传感器确认现实中无障碍。未来实时点云应作为独立障碍层;
// 引入 Unknown 后,未观测栅格不应默认允许规划通行。
```
- [ ] **Step 6: Verify and commit**
Run `powershell -ExecutionPolicy Bypass -File ClumsyPilot\tests\verify_trapmap_inputs.ps1`, `dotnet msbuild ClumsyPilot\ClumsyPilot.csproj /t:Compile /p:RestoreIgnoreFailedSources=true /v:minimal`, and `powershell -ExecutionPolicy Bypass -File ClumsyPilot\tests\verify_trapmap_grid.ps1`. Expected: both scripts exit 0 and compile has no TrapMap errors. Then:
```powershell
git add ClumsyPilot/MovementTest.Trapmaptest.cs ClumsyPilot/tests/verify_trapmap_inputs.ps1
git commit -m "fix: build trap map before optional tire layer"
```
### Task 3: Adjustable test entry and workstation visualization
**Files:**
- Modify: `ClumsyPilot/MovementTest.Trapmaptest.cs`
- Modify: `ClumsyPilot/tests/verify_trapmap_inputs.ps1`
**Interfaces:**
- Consumes: Task 2 builder configuration/status.
- Produces: editable test defaults, non-occupying target marker, exact bounds/layer logs.
- [ ] **Step 1: Add failing UI/source contracts**
```powershell
if ($source -notmatch '_workstationX\s*=\s*10000f' -or $source -notmatch '_workstationY\s*=\s*0f' -or
$source -notmatch '_mapMarginX\s*=\s*3000f' -or $source -notmatch '_mapMarginY\s*=\s*3000f') {
$failures.Add('MovementTest adjustable defaults are missing.')
}
if ($source -notmatch 'DrawWorkstationMarker\(') { $failures.Add('Target visualization is missing.') }
if ($source -match 'MarkObstacle\(Workstation|MarkOccupied\([^\r\n]*Workstation') {
$failures.Add('Workstation must not become occupancy.')
}
```
- [ ] **Step 2: Prove the contract fails**
Run the input script. Expected: missing test defaults and target visualization are reported.
- [ ] **Step 3: Replace manual-edit fields and pass them to builder**
```csharp
private const float _gridResolutionMm = 50f;
private const float _workstationX = 10000f;
private const float _workstationY = 0f;
private const float _mapMarginX = 3000f;
private const float _mapMarginY = 3000f;
private const float _safetyMarginMm = 300f;
```
Pass all values into `TrapMapBuilder`. Log vehicle, workstation, limits, dimensions, cells, `TireLayerStatus`, and `TireLayerMessage`. Remove all half-size/20m-window documentation.
- [ ] **Step 4: Draw the workstation without marking occupancy**
```csharp
private void DrawWorkstationMarker(Painter painter)
{
const float radius = 250f;
painter.DrawCircle(Color.Lime, WorkstationWorld.X, WorkstationWorld.Y, radius);
painter.DrawLine(Color.Lime, WorkstationWorld.X - radius, WorkstationWorld.Y,
WorkstationWorld.X + radius, WorkstationWorld.Y, width: 3);
painter.DrawLine(Color.Lime, WorkstationWorld.X, WorkstationWorld.Y - radius,
WorkstationWorld.X, WorkstationWorld.Y + radius, width: 3);
painter.DrawText(Color.Lime, "Workstation",
WorkstationWorld.X + radius, WorkstationWorld.Y + radius);
}
```
Call it from visualization; never pass `WorkstationWorld` to occupancy methods.
- [ ] **Step 5: Update comments and run final verification**
Document formulas, defaults, task-level “global” meaning, layer order, optional tire behavior, and future independent point-cloud layer. Run:
```powershell
powershell -ExecutionPolicy Bypass -File ClumsyPilot\tests\verify_trapmap_inputs.ps1
dotnet msbuild ClumsyPilot\ClumsyPilot.csproj /t:Compile /p:RestoreIgnoreFailedSources=true /v:minimal
powershell -ExecutionPolicy Bypass -File ClumsyPilot\tests\verify_trapmap_grid.ps1
rg -n "MapHalfSizeMm|GetSensor\(|GetPointCloud\(|PredefinedDriveStop\(|SendMotion\(|SendRotateMotion\(" ClumsyPilot\MovementTest.Trapmaptest.cs
git diff --check
```
Expected: scripts pass, compile has no TrapMap errors, forbidden search has no matches, and diff check is clean.
- [ ] **Step 6: Commit**
```powershell
git add ClumsyPilot/MovementTest.Trapmaptest.cs ClumsyPilot/tests/verify_trapmap_inputs.ps1
git commit -m "feat: expose workstation trap map inputs"
```
@@ -1,125 +0,0 @@
# TrapMap Managed PNG 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:** Remove TrapMap's platform-specific drawing dependency and produce the same bounded 300 DPI PNG with a managed encoder that runs inside Clumsy.
**Architecture:** Keep `TrapMapImageExporter.ExportIfEnabled` and its file-reservation/publication behavior. Replace only the renderer with an internal RGBA raster surface, primitive drawing functions, a compact embedded bitmap font, and `StbImageWriteSharp` for PNG encoding; insert the 300 DPI `pHYs` chunk after encoding.
**Tech Stack:** C# 10, .NET Standard 2.0, `StbImageWriteSharp` 1.16.7, PowerShell contract/PNG parsing tests.
## Global Constraints
- Do not inspect or modify `TrajPlanner`.
- Do not commit or stage any file.
- Do not change grid construction, Painter behavior, movement behavior, switches, output location, naming, 300 DPI, 4 pixels per cell, 4000-pixel edge limit, or 50 MiB limit.
- The only new image package is `StbImageWriteSharp` version 1.16.7; do not add native assets or another graphics package.
- `TrapMapImageExporter` must have no runtime reference to `System.Drawing.Common` or `System.Drawing`.
- Preserve collision-safe temporary-file reservation, encoded-size validation, atomic publication, and contained export failures.
---
### Task 1: Replace System.Drawing rendering with a managed Stb PNG encoder
**Files:**
- Modify: `ClumsyPilot/ClumsyPilot.csproj`
- Modify: `ClumsyPilot/TrapMapImageExporter.cs`
- Modify: `ClumsyPilot/tests/verify_trapmap_image.ps1`
- Modify: `docs/superpowers/specs/2026-07-22-trap-map-image-and-console-design.md`
- Modify: `docs/superpowers/plans/2026-07-22-trap-map-image-and-console.md`
**Interfaces:**
- Preserve: `TrapMapImageExporter.ExportIfEnabled(bool, TrapMapImageExportRequest)` and all public request/result properties and constants.
- Add only private implementation units: `RgbaSurface`, integer drawing helpers, bitmap-font helpers, and `PngWriter`.
- `RenderToTemporaryPng` continues to consume the existing request/dimensions and write to the already exclusively reserved stream.
- [ ] **Step 1: Add dependency-removal and PNG-structure assertions**
Update `verify_trapmap_image.ps1` before production code. Require that:
```powershell
if ($project.PackageReference.Include -contains 'System.Drawing.Common') {
throw 'TrapMap must not depend on System.Drawing.Common.'
}
if ($project.Target.Name -contains 'DeployFrameworkDrawingRuntime') {
throw 'Legacy drawing-runtime deployment target remains.'
}
if (-not ($project.PackageReference | Where-Object {
$_.Include -eq 'StbImageWriteSharp' -and $_.Version -eq '1.16.7'
})) { throw 'Exact managed PNG package is missing.' }
if ($exporterSource -match 'System\.Drawing|\bBitmap\b|\bGraphics\b|ImageFormat') {
throw 'Exporter still uses the external drawing API.'
}
```
Parse the generated PNG without loading a drawing assembly. Verify signature, one `IHDR`, one `pHYs`, one or more `IDAT`, and `IEND`; verify every chunk CRC. Assert `IHDR` width/height and RGBA8 fields and `pHYs` values `11811,11811,1`. Decode representative pixels with a test-only PNG decoder or the Stb package and reuse the existing color/Y-inversion assertions.
Add a clean-output assertion after build:
```powershell
$drawingDll = Join-Path (Split-Path -Parent $AssemblyPath) 'System.Drawing.Common.dll'
if (Test-Path -LiteralPath $drawingDll) {
throw 'System.Drawing.Common.dll must not be deployed for TrapMap.'
}
```
- [ ] **Step 2: Run RED verification**
```powershell
dotnet build ClumsyPilot\ClumsyPilot.csproj --no-restore -v:minimal
powershell -ExecutionPolicy Bypass -File ClumsyPilot\tests\verify_trapmap_image.ps1
```
Expected: the image test fails because the package, deployment target, `using System.Drawing`, and renderer still exist.
- [ ] **Step 3: Remove the drawing package and deployment target**
Delete the `System.Drawing.Common` `PackageReference` and the entire `DeployFrameworkDrawingRuntime` target. Add `<PackageReference Include="StbImageWriteSharp" Version="1.16.7" />`. Do not change the target framework or other references. Ensure a clean build cannot retain the old DLL: the verification command must remove only `ClumsyPilot/bin/Debug/netstandard2.0/System.Drawing.Common.dll` before rebuilding, after resolving and validating that exact path is under the project output directory.
- [ ] **Step 4: Implement the RGBA raster surface**
Replace drawing types with a private surface backed by `byte[]` in RGBA order. Required primitives and semantics:
```csharp
SetPixel(int x, int y, byte r, byte g, byte b, byte a = 255);
FillRectangle(int x, int y, int width, int height, Color32 color);
DrawLine(int x0, int y0, int x1, int y1, Color32 color, int thickness);
DrawRectangle(int x, int y, int width, int height, Color32 color, int thickness);
DrawCircle(int centerX, int centerY, int radius, Color32 color, int thickness);
FillCircle(int centerX, int centerY, int radius, Color32 color);
FillPolygon(PointD[] points, Color32 color);
```
Clip every primitive to the surface. Use pre-clipped Bresenham lines, scale-normalized scanline polygon filling, and a canvas-clipped bounded circle scan whose work is proportional to visible rows/columns rather than radius. Preserve exact white, red, LightGray `(211,211,211)`, black, blue, and LimeGreen `(0,255,0)` colors. Convert vehicle/workstation world positions using the existing discrete-grid-aligned transform.
- [ ] **Step 5: Implement deterministic bitmap text**
Embed a private 5×7 ASCII glyph table for code points 32126. Draw scaled glyphs using integer pixels; unsupported characters render as `?`. Use a 2× scale for header text and 1× scale for the workstation label. Keep all five header baselines inside `HeaderHeightPixels=140` with fixed non-overlapping line boxes. Continue building the same five metadata lines, including occupancy rate; sanitize only the exported header text, not logs.
- [ ] **Step 6: Encode PNG and add 300 DPI metadata**
Use `StbImageWriteSharp.ImageWriter.WritePng` to encode the RGBA buffer. Then insert:
```text
pHYs: X=11811, Y=11811, unit=1
```
Encode into a temporary `MemoryStream`, validate the PNG signature and first `IHDR` chunk, then copy the signature+IHDR, append the 13-byte `pHYs` chunk, and copy the remaining encoded chunks. Use big-endian integers and standard CRC-32 over `pHYs`+data. Do not close the caller-owned reserved stream before the existing file-size/atomic-move flow finishes.
- [ ] **Step 7: Update documentation and run GREEN verification**
Remove all claims that TrapMap deploys or requires `System.Drawing.Common`; document the pure C# encoder and BCL-only runtime.
Run from a clean output state:
```powershell
powershell -ExecutionPolicy Bypass -File ClumsyPilot\tests\verify_trapmap_inputs.ps1
dotnet build ClumsyPilot\ClumsyPilot.csproj --no-restore -v:minimal
powershell -ExecutionPolicy Bypass -File ClumsyPilot\tests\verify_trapmap_grid.ps1
powershell -ExecutionPolicy Bypass -File ClumsyPilot\tests\verify_trapmap_lifecycle.ps1
powershell -ExecutionPolicy Bypass -File ClumsyPilot\tests\verify_trapmap_image.ps1
git diff --check
$staged = git diff --cached --name-only; if ($staged) { throw "Unexpected staged files: $staged" }
```
Expected: build succeeds without a drawing DLL in output; all tests pass; PNG parser reports correct dimensions, CRCs, 300 DPI metadata, RGBA pixels, unique filenames, file size at or below 50 MiB, and no temporary files.
@@ -1,427 +0,0 @@
# Hybrid A* P0 规划核心 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:subagent-driven-development`(推荐)或 `superpowers:executing-plans`,按任务顺序实施,并使用 `- [ ]` 更新执行状态。
**Goal:** 在既有 `PlanningGridMap` 之上提供可复用、可验证且不依赖 UI 的 Hybrid A* 粗路径规划服务。
**Architecture:** P0 先固定 m/rad/1/m 数据契约和连续车辆碰撞边界,再在该边界上实现恒曲率原语、二维启发式、确定性 Hybrid A*、路径重建和最终复核。`CoarsePathPlanningService` 是业务的唯一组合入口;`HybridAStarPlanner` 是只消费已建地图的下层门面。
**Tech Stack:** C# 10、.NET Standard 2.0、现有 `PlanningGridMap`、PowerShell 反射契约测试、`CancellationToken`
## Global Constraints
- 所有运行时代码位于 `ClumsyPilot/ParkrobTrajplanner/CoarsePath/`,命名空间为 `MultiWheelC.TrajectoryPlanning.CoarsePath` 或其子命名空间。
- Map 只保存外部障碍物;安全余量只在连续车辆碰撞检查时扩张车辆矩形,绝不写入 Map。
- 地图输入和障碍几何使用 mm;CoarsePath 的位置使用 m、航向使用 rad、曲率使用 1/m。
- 规划器只能接受 `PlanningGridMap`,不得引用 TwoLeg、定位、Painter、UI 或系统时间。
- 所有公开类型、构造函数、属性和方法使用中文 XML 文档,明确参数单位、边界以及返回或失败语义;内部几何/搜索不变量使用简短中文注释。
- `netstandard2.0` 禁止直接使用 `PriorityQueue``Math.Clamp``double.IsFinite``record``init`
- 固定约束:原语最大长度 0.50 m;积分最大步长 0.05 m;碰撞中心步长不超过 `min(0.025 m, Map.ResolutionMeters / 2)`;默认终点容差为 0.15 m、5°。
- 终点候选必须进入 Open List,只有作为最佳有效条目出队时才能成功;地图外始终按占据处理。
- 本计划不实现 P1 的 Clumsy `MovementTest`、Painter 绘制、Release 性能基准或旧 TrapMap 入口退役。
- 按用户现有约束,不执行 Git 自检、暂存、提交或推送。
---
## 文件结构
```text
ClumsyPilot/ParkrobTrajplanner/
├── CoarsePath/
│ ├── Contracts/ # 请求、结果、枚举和值对象
│ ├── Vehicle/ # 扩大车辆几何和连续碰撞
│ ├── Search/ # 原语、堆、启发式和 Hybrid A* 搜索
│ ├── Output/ # 回溯、稠密路径装配和最终验证
│ ├── Facade/ # 一次调用编排和调试旁路契约
│ ├── HybridAStarPlanner.cs
│ └── README.md # 粗规划调用方文档;链接至 ../Map/README.md
└── tests/
├── verify_coarse_path_collision.ps1
├── verify_coarse_path_search.ps1
└── verify_coarse_path_integration.ps1
```
## Task 1: 固定公共契约、状态与默认配置
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Contracts/Pose2D.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Contracts/TravelDirection.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Contracts/GoalDirectionConstraint.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Contracts/VehicleParameters.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Contracts/HybridAStarConfiguration.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Contracts/PlanningRequest.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Contracts/PlanningStatus.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Contracts/PlanningDiagnostics.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Contracts/CoarsePathPoint.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Contracts/CoarsePathPointSource.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Contracts/PathSegment.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Contracts/PlanningResult.cs`
- Modify: `ClumsyPilot/tests/verify_planning_utils.ps1`
**Produces:**
```csharp
public sealed class Pose2D
{
public Pose2D(double xMeters, double yMeters, double headingRadians);
public double X { get; }
public double Y { get; }
public double Heading { get; }
}
public sealed class PlanningRequest
{
public PlanningGridMap Map { get; set; }
public Pose2D Start { get; set; }
public Pose2D Goal { get; set; }
public VehicleParameters Vehicle { get; set; }
public HybridAStarConfiguration Configuration { get; set; }
public double StartVehicleCurvature { get; set; }
public TravelDirection? StartDirection { get; set; }
public GoalDirectionConstraint GoalDirection { get; set; }
}
```
- [ ] **Step 1: 写失败的公共契约测试。**`verify_planning_utils.ps1` 载入程序集后添加反射断言,检查 `Pose2D` 构造函数、三个枚举、`PlanningRequest` 属性和每个默认值。默认配置断言如下:
```powershell
$config = New-Object MultiWheelC.TrajectoryPlanning.CoarsePath.HybridAStarConfiguration
Assert-Equal 0.50 $config.PrimitiveLengthMeters '原语最大长度'
Assert-Equal 0.05 $config.IntegrationStepMeters '积分步长'
Assert-Equal 0.025 $config.MaximumCollisionCheckStepMeters '碰撞步长'
Assert-Equal 5 $config.CurvatureLevelCount '曲率等级数'
Assert-Equal 200000 $config.MaximumExpandedNodes '节点上限'
```
- [ ] **Step 2: 运行测试并确认 RED。**
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_utils.ps1
```
预期:脚本因 `CoarsePath` 类型尚不存在而以非零退出。
- [ ] **Step 3: 实现最小契约。** `TravelDirection` 仅含 `Forward``Reverse``GoalDirectionConstraint` 仅含 `Any``Forward``Reverse``CoarsePathPointSource` 仅含 `Start``MotionPrimitive``GoalTruncation``PlanningStatus` 必须包含 `Success``Cancelled``InvalidRequest``InvalidMap``MapNotReady``InvalidVehicleParameters``InvalidCurvatureConfiguration``StartOutsideMap``StartInCollision``GoalOutsideMap``GoalInCollision``SearchTimeout``SearchNodeLimitExceeded``NoFeasiblePath``BacktrackingFailed``FinalValidationFailed``InternalError`
`HybridAStarConfiguration` 的构造默认值必须是:`Math.PI / 36d` 航向/终点航向容差、5 秒超时、`HeuristicWeight=1d``ReverseCostMultiplier=1.5d``GearSwitchPenaltyMeters=1d``CurvatureMagnitudeWeight=0.10d``CurvatureChangePenaltyMetersPerLevel=0.05d``ClearanceCostWeight=0.20d``ClearanceCostDistanceMeters=0.50d`
`PlanningResult` 只允许成功结果携带非空路径与分段;所有失败工厂方法返回空只读集合并保留诊断。`PlanningDiagnostics` 固定记录扩展、生成、重开、陈旧堆条目、Open List 峰值、路径长度、最小保守净空、耗时和终止原因。
- [ ] **Step 4: 重新运行工具契约测试并确认 GREEN。**
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_utils.ps1
```
预期:退出码为 0,既有 Utils/Map 契约仍可加载。
## Task 2: 实现扩大车辆足迹与连续碰撞检查
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Vehicle/VehicleKinematics.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Vehicle/VehicleFootprint.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Vehicle/OrientedRectangleCellIntersection.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Vehicle/FootprintCollisionChecker.cs`
- Create: `ClumsyPilot/tests/verify_coarse_path_collision.ps1`
**Consumes:** `PlanningGridMap``Pose2D``VehicleParameters`
**Produces:**
```csharp
public sealed class FootprintCollisionChecker
{
public bool IsPoseCollisionFree(
Pose2D pose, PlanningGridMap map, VehicleParameters vehicle,
double additionalMarginMeters, out double bodyClearanceMeters);
public bool IsSweptMotionCollisionFree(
Pose2D from, Pose2D to, PlanningGridMap map, VehicleParameters vehicle,
double maximumCenterStepMeters, out double minimumBodyClearanceMeters);
}
```
- [ ] **Step 1: 写失败的连续碰撞测试。** 脚本通过 `PlanningMapFactory` 创建 50 mm 地图和单个薄矩形障碍,验证下面三种行为:车辆与障碍格擦边返回碰撞、距离场净空严格大于外接圆半径时返回安全、两个端点安全但中间穿过障碍时扫掠检查返回碰撞。
```powershell
$checker = New-Object MultiWheelC.TrajectoryPlanning.CoarsePath.Vehicle.FootprintCollisionChecker
$clearance = 0.0
$safe = $checker.IsPoseCollisionFree($pose, $map, $vehicle, 0.0, [ref]$clearance)
Assert-False $safe '矩形擦边必须视为碰撞'
```
- [ ] **Step 2: 运行碰撞脚本并确认 RED。**
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_collision.ps1
```
预期:因车辆命名空间和碰撞检查器不存在而失败。
- [ ] **Step 3: 实现最小连续几何。** `VehicleKinematics` 在最大曲率与最小转弯半径都存在时取 `Math.Min(maximumCurvature, 1d / minimumRadius)``VehicleFootprint``LengthMeters + 2 * SafetyMarginMeters``WidthMeters + 2 * SafetyMarginMeters` 构造以 `Pose2D` 为几何中心的旋转矩形、AABB 与外接圆。
`OrientedRectangleCellIntersection` 使用 SAT:矩形的两个单位轴和格子的世界 X/Y 轴都作为投影轴;任一轴存在严格分离才是不相交,投影接触算相交。`FootprintCollisionChecker` 依次验证四角均在地图内、用严格 `distance > radius + additionalMargin` 快速放行、遍历 AABB 内占据格并执行 SAT。扫掠检查将中心位移切分到 `min(maximumCenterStepMeters, map.ResolutionMeters / 2d)`,每一段的临时边距为 `0.5d * (centerDisplacement + circumscribedRadius * Math.Abs(headingDelta))`
- [ ] **Step 4: 扩展碰撞测试并运行 GREEN。** 加入 0°、45°、任意航向、栅格中心/亚栅格中心、薄障碍、边界外和扫掠场景;随后运行:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_collision.ps1
```
预期:构建与脚本退出码均为 0。
## Task 3: 实现原语积分、目标容差与内部截断
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Search/MotionPrimitive.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Search/MotionPrimitiveGenerator.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Search/GoalToleranceChecker.cs`
- Create: `ClumsyPilot/tests/verify_coarse_path_search.ps1`
**Consumes:** `Pose2D`、方向、车辆最大曲率、`HybridAStarConfiguration``FootprintCollisionChecker`
**Produces:** 含方向、曲率、实际长度和内部积分点的不可变 `MotionPrimitive`;目标检查器只判定位置、航向和目标进入方向。
- [ ] **Step 1: 写失败的原语测试。** 验证直行、圆弧、倒车、0.50 m 上限、积分点间距上限,以及目标在 0.30 m 处时原语恰好截断到第一个满足条件的内部点。
```powershell
$primitive = $generator.Generate($start, $curvature, $direction, $config, $map)
Assert-True ($primitive.Points.Count -ge 1) '原语必须产生内部积分点'
Assert-Equal 0.30 $truncated.ActualLengthMeters '0.30m 目标必须在原语内部截断'
```
- [ ] **Step 2: 运行搜索脚本并确认 RED。**
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_search.ps1
```
预期:因原语类型尚不存在而失败。
- [ ] **Step 3: 实现解析积分和检查顺序。** 单步积分使用:
```csharp
double signedDistance = direction == TravelDirection.Forward ? step : -step;
double nextHeading = AngleMath.NormalizeRadians(heading + curvature * signedDistance);
if (Math.Abs(curvature) < 1e-12)
{
nextX = x + signedDistance * Math.Cos(heading);
nextY = y + signedDistance * Math.Sin(heading);
}
else
{
nextX = x + (Math.Sin(nextHeading) - Math.Sin(heading)) / curvature;
nextY = y - (Math.Cos(nextHeading) - Math.Cos(heading)) / curvature;
}
```
原语点步长不得超过 `min(IntegrationStepMeters, MaximumCollisionCheckStepMeters, Map.ResolutionMeters / 2d)`。每个内部点严格按“有限数值 → 从前一点的扫掠碰撞 → 终点容差”执行;命中目标即截断,并标记 `GoalTruncation`。起点已满足目标时生成零长度终点候选,不生成运动原语。
- [ ] **Step 4: 运行原语测试并确认 GREEN。** 加入五个曲率等级、曲率相邻变化最多一级和 `±π` 航向容差的断言;再运行 `verify_coarse_path_search.ps1`,预期退出码为 0。
## Task 4: 实现确定性 Open List、代价与二维启发式
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Search/BinaryMinHeap.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Search/SearchCostCalculator.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Search/GridDijkstraHeuristic.cs`
- Modify: `ClumsyPilot/tests/verify_coarse_path_search.ps1`
**Produces:** 内部二叉最小堆、等效米代价计算器和目标反向八邻域距离启发式。
- [ ] **Step 1: 写失败的堆、代价和启发式测试。** 验证堆的排序优先级为 `F``H`、较大 `G`、插入序号;验证八邻域斜向代价和禁止切过两个正交障碍的对角夹角;验证倒车、换向、曲率和净空代价项。
```powershell
Assert-Equal 'node-b' $heap.Pop().Id '相同 F 时应先选较小 H'
Assert-Throws { $calculator.Calculate($invalidInput) } '负权重必须拒绝'
Assert-True ([double]::IsPositiveInfinity($heuristic.GetCost($blockedRow, $blockedCol))) '二维不可达应为无穷'
```
- [ ] **Step 2: 运行搜索测试并确认 RED。**
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_search.ps1
```
预期:因堆、代价或启发式类型不存在而失败。
- [ ] **Step 3: 实现最小支持结构。** `BinaryMinHeap` 使用 `List<T>`,比较器严格按 `F``H`、反向 `G`、插入序号。代价必须实现:
```text
length * directionMultiplier *
(1 + curvatureMagnitudeWeight * abs(curvature / maximumCurvature)
+ clearanceCostWeight * max(0, 1 - clearance / clearanceCostDistance))
+ gearSwitchPenalty
+ curvatureChangePenalty * abs(curvatureLevelDelta)
```
`GridDijkstraHeuristic` 从目标格反向传播四邻域 1 倍格长和对角 `sqrt(2)` 倍格长;对角移动前确认两个正交邻格均未占据。
- [ ] **Step 4: 运行搜索测试并确认 GREEN。** 重复构造同一输入两次,断言出队顺序相同;运行 `verify_coarse_path_search.ps1`,预期退出码为 0。
## Task 5: 实现 Hybrid A* 节点、重开与终点候选管理
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Search/HybridAStarNode.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Search/HybridAStarNodeKey.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Search/HybridAStarSearch.cs`
- Modify: `ClumsyPilot/tests/verify_coarse_path_search.ps1`
**Consumes:** Task 2–4 的碰撞、原语、堆、代价与启发式。
**Produces:** 接收已验证请求并返回成功节点索引或明确搜索失败状态的内部搜索器。
- [ ] **Step 1: 写失败的搜索测试。** 覆盖空图前进、单矩形绕行、允许倒车的狭窄场景、起始曲率、目标方向、无解、取消、超时、节点上限、较小 `G` 重开和终点候选出队顺序。
```powershell
$result = $search.Search($request, [Threading.CancellationToken]::None)
Assert-Equal 'Success' $result.Status '空图应规划成功'
Assert-True $result.ReopenedNodeCount -gt 0 '更小 G 到达同键时必须允许重开'
```
- [ ] **Step 2: 运行搜索脚本并确认 RED。**
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_search.ps1
```
预期:因 `HybridAStarSearch` 不存在而失败。
- [ ] **Step 3: 实现离散键与搜索循环。** `HybridAStarNodeKey` 固定包含位置行列、航向索引、方向和曲率等级。普通状态的最佳 `G` 保存在 `Dictionary<HybridAStarNodeKey, double>`;发现严格更小的 `G` 时压入新条目,旧条目在弹出时丢弃。循环在扩展前检查 `CancellationToken`、配置超时和最大扩展数。
终点候选压入同一 Open List,但不放入普通键的去重表;它只能在作为当前最佳有效条目弹出、重新验证终点条件与末段碰撞后成功。Open List 耗尽返回 `NoFeasiblePath`
- [ ] **Step 4: 运行全量搜索场景并确认 GREEN。** 对每个固定场景重复运行两次并断言状态、路径代价和节点扩展顺序一致;运行 `verify_coarse_path_search.ps1`,预期退出码为 0。
## Task 6: 回溯、路径装配、最终验证与下层门面
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Output/PathBacktracker.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Output/CoarsePathAssembler.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Output/CoarsePathValidator.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/HybridAStarPlanner.cs`
- Create: `ClumsyPilot/tests/verify_coarse_path_integration.ps1`
**Produces:**
```csharp
public sealed class HybridAStarPlanner
{
public PlanningResult Plan(
PlanningRequest request,
CancellationToken cancellationToken = default(CancellationToken));
}
```
- [ ] **Step 1: 写失败的路径输出测试。** 验证首点弧长为 0、弧长不递减、展开航向连续、终点截断来源、相邻重复点只允许作为换向对、分段的包含式索引覆盖全部路径。
```powershell
Assert-Equal 0.0 $result.Path[0].ArcLength '起点弧长必须为零'
Assert-True ($result.Segments[-1].EndIndex -eq ($result.Path.Count - 1)) '分段必须覆盖尾点'
Assert-Equal 'FinalValidationFailed' $invalid.Status '最终复核失败不能发布部分路径'
```
- [ ] **Step 2: 运行集成脚本并确认 RED。**
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1
```
预期:因 `HybridAStarPlanner` 与输出类型不存在而失败。
- [ ] **Step 3: 实现回溯和最终复核。** 搜索节点只保留父索引和原语描述;`PathBacktracker` 在成功后使用同一解析积分公式重建内部点。`CoarsePathAssembler` 累计弧长,保持换向处两个相同位姿/弧长而方向不同的点,并让新方向点设置 `IsGearSwitchPoint=true`
`CoarsePathValidator` 使用与搜索相同的 `FootprintCollisionChecker` 和扫掠规则,检查有限数、曲率上限、起终点容差/方向、弧长单调性、换向对与分段覆盖。验证失败返回 `FinalValidationFailed`,路径和分段均为空。
`HybridAStarPlanner` 在调用搜索前映射空请求、Map 未就绪、车辆无效、曲率配置无效、起终点越界及起终点碰撞;其余异常收敛为 `InternalError` 并记录诊断。
- [ ] **Step 4: 运行碰撞、搜索和集成脚本并确认 GREEN。**
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_collision.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_search.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1
```
预期:三个脚本均退出 0。
## Task 7: 一次调用服务、调试旁路契约与 README
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Facade/CoarsePathPlanningJob.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Facade/CoarsePathPlanningJobResult.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Facade/PlanningDebugOptions.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Facade/IPlanningDebugSink.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Facade/CoarsePathPlanningService.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/README.md`
- Modify: `ClumsyPilot/tests/verify_coarse_path_integration.ps1`
**Produces:**
```csharp
public sealed class CoarsePathPlanningService
{
public CoarsePathPlanningJobResult Plan(
CoarsePathPlanningJob job,
CancellationToken cancellationToken = default(CancellationToken));
}
```
- [ ] **Step 1: 写失败的一次调用测试。** 断言服务先建图、地图失败时不搜索、成功时同时返回 `PlanningMapBuildResult``PlanningResult`;同一服务实例两次使用相同地图请求时第二次是 `Input` 缓存命中。
```powershell
$service = New-Object MultiWheelC.TrajectoryPlanning.CoarsePath.Facade.CoarsePathPlanningService
$first = $service.Plan($job, [Threading.CancellationToken]::None)
$second = $service.Plan($job, [Threading.CancellationToken]::None)
Assert-Equal 'Input' $second.MapResult.CacheHit.ToString() '服务必须长期持有地图工厂'
```
- [ ] **Step 2: 运行集成脚本并确认 RED。**
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1
```
预期:因门面类型与 README 不存在而失败。
- [ ] **Step 3: 实现服务和文档。** `CoarsePathPlanningService` 构造时创建一个长期 `PlanningMapFactory` 与一个 `HybridAStarPlanner`,计划调用顺序固定为:
```text
PlanningMapFactory.Create(job.MapRequest)
-> 地图失败:包装 MapResult,返回空 PlanningResult
-> 地图成功:HybridAStarPlanner.Plan(job 转换的 PlanningRequest)
-> 仅依 Debug 选项向 IPlanningDebugSink 发布旁路数据
```
默认 sink 为空实现。任何 sink 异常只追加调试诊断,绝不改变地图哈希、规划状态、路径或分段。
README 必须包含以下小节:模块范围;Map 与 CoarsePath 的职责表;`CoarsePathPlanningService.Plan` 的可编译调用示例;mm/m/rad/1/m 单位表;`SourceVersion` 与缓存规则;`PlanningStatus` 处理示例;路径点和方向分段含义;第一版不支持的平滑、速度规划、控制和横移能力;到 `../Map/README.md` 的链接。
- [ ] **Step 4: 运行完整 P0 验收。**
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_utils.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_factory.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_adapter.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_collision.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_search.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1
```
预期:构建和全部七个脚本退出码均为 0;不执行 P1 UI 或性能工作。
## Plan Self-Review
- 覆盖性:Task 1 覆盖公共契约;Task 2 覆盖连续车辆碰撞;Task 3–5 覆盖原语、代价、启发式、确定性搜索与终点候选;Task 6 覆盖输出与最终复核;Task 7 覆盖一次调用门面、文档和全量验收。
- 类型一致性:所有搜索和门面输入均以 `PlanningGridMap``PlanningRequest``CoarsePathPlanningJob` 为唯一跨层契约;Map 构建只存在于 Task 7 的服务门面。
- 范围:没有包含 Clumsy UI、Painter、性能基准或旧 TrapMap 迁移,这些均为 P1。
@@ -1,242 +0,0 @@
# Map 模块文档与注释实施计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use `executing-plans` to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 为 Map 模块提供根目录结构说明,并为所有公共 API 提供中文、Python docstring 风格的调用说明。
**Architecture:** `Map/README.md` 只说明模块结构、数据流、单位与入口;`.cs` 中的 `/// <summary>` 是参数、返回和约束的唯一 API 文档来源。注释不得改变方法签名、建图算法、缓存键或任何运行时行为。
**Tech Stack:** C# 10、netstandard2.0、PowerShell 验证脚本、Markdown。
## Global Constraints
- 所有新增说明使用中文。
- 公共 API 文档使用可被 C# IDE 识别的 `///`,内容顺序为“功能、参数、返回、注意”。
- 参数说明必须给出单位、坐标系、可空性或输入约束中的适用项。
- 返回说明必须给出结果数据的业务意义;`bool` 说明其 true/false 语义。
- README 不复制逐个属性的完整参数表。
- 不修改运行逻辑,不执行 Git 自检、暂存、提交或重置。
---
### Task 1: 建立 Map README 与文档存在性检查
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/Map/README.md`
- Create: `ClumsyPilot/tests/verify_planning_map_documentation.ps1`
**Interfaces:**
- Consumes: `PlanningMapFactory.Create(PlanningMapRequest request)`、Map 现有目录结构。
- Produces: Map 模块入口说明和可重复运行的文档检查。
- [ ] **Step 1: 写入失败检查**
创建 PowerShell 脚本,读取 `Map/README.md`,断言它不存在时抛出异常;创建后继续断言包含以下固定标题:`# Map 模块说明``## 文件结构``## 建图数据流``## 坐标与单位``## 最小调用示例``## 缓存与版本``## 测试与调试`
- [ ] **Step 2: 运行检查确认失败**
Run: `powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_documentation.ps1`
Expected: 因 `Map/README.md` 不存在而失败。
- [ ] **Step 3: 创建 README**
写入当前 `Core``Obstacles``Sources``Planning``Test``Test/Visualization` 的目录树;每个 `.cs` 文件后写一句职责。说明数据流为 `PlanningMapRequest → IMapObstacleSource → EnvironmentMapBuilder/MapObstacleRasterizer → EnvironmentGridMap → PlanningMapAdapter/ObstacleDistanceField → PlanningGridMap`。说明环境图使用世界 mm、规划查询使用 m、范围采用左闭右开;示例只经长期持有的 `PlanningMapFactory.Create` 调用;说明 `SourceVersion` 变化与两级缓存的关系;明确 PNG 为可选调试、旧 TrapMap 不属于新运行时入口。
- [ ] **Step 4: 运行检查确认通过**
Run: `powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_documentation.ps1`
Expected: `Planning map documentation checks passed.`
### Task 2: 注释公共建图入口与结果契约
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/PlanningMapFactory.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/PlanningMapRequest.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/PlanningMapBuildResult.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Core/MapBuildRequest.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Core/EnvironmentMapBuildResult.cs`
**Interfaces:**
- Consumes: 外部调用者提供的地图范围、分辨率、障碍物来源。
- Produces: 建图请求、构建结果、缓存命中状态的中文 API 契约。
- [ ] **Step 1: 扩展失败检查**
`verify_planning_map_documentation.ps1` 中对上述文件断言:`PlanningMapFactory.Create``PlanningMapRequest.Bounds``PlanningMapBuildResult.Map` 前方紧邻中文 `///` 注释,且包含 `参数:``返回:``单位:``注意:` 中适用的说明。
- [ ] **Step 2: 运行检查确认失败**
Run: `powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_documentation.ps1`
Expected: 失败并指出缺失的入口契约说明。
- [ ] **Step 3: 添加入口契约注释**
`PlanningMapFactory`、构造和 `Create` 写明长期复用要求、请求输入、结果与三种缓存命中语义。为请求与结果的每个公共属性写明单位、可空性和失败/空图语义。为 `PlanningMapCacheHit` 的每个枚举值写明 `None``Input``Occupancy` 的实际含义。为内部 Map 构建请求和结果的 public 成员补充相同层级说明。
- [ ] **Step 4: 运行入口检查与编译**
Run: `powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_documentation.ps1`
Run: `dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore`
Expected: 文档检查通过;编译 0 error。
### Task 3: 注释地图边界、环境栅格与障碍物契约
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Core/MapBoundsMm.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Core/EnvironmentGridMap.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Core/EnvironmentMapBuilder.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Obstacles/IMapObstacle.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Obstacles/CircleObstacle.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Obstacles/AxisAlignedRectangleObstacle.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Obstacles/MapObstacleRasterizer.cs`
**Interfaces:**
- Consumes: 世界坐标毫米几何、有效栅格范围。
- Produces: 环境占据图以及几何到栅格的公开行为说明。
- [ ] **Step 1: 扩展失败检查**
`MapBoundsMm` 构造函数、`Contains``GetDimensions``EnvironmentGridMap` 构造函数和世界/栅格查询方法,以及两种障碍物构造函数与属性,断言有中文 `///`。脚本还断言 `MapObstacleRasterizer.Rasterize` 注释包含其是唯一写栅格入口的约束。
- [ ] **Step 2: 运行检查确认失败**
Run: `powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_documentation.ps1`
Expected: 失败并显示尚未文档化的公共几何/栅格 API。
- [ ] **Step 3: 添加边界与几何注释**
为范围、行列、世界 mm 坐标、左闭右开边界、越界 `false`/占据行为、`out row/col` 的失败值写明说明。为圆和矩形的坐标、半径与 `IsValid` 写明单位和 true/false 条件。为环境构建器 `Build` 写明必需来源失败会整体失败、可选来源只记录状态的规则。
- [ ] **Step 4: 运行检查与 Map 适配器脚本**
Run: `powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_documentation.ps1`
Run: `powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_adapter.ps1`
Expected: 两个脚本通过。
### Task 4: 注释障碍物来源与 TwoLeg 投影契约
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Sources/IMapObstacleSource.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Sources/ManualObstacleSource.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Sources/TwoLegProjectionInput.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Sources/TwoLegObstacleSource.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Sources/TwoLegObstacleProjector.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Sources/ObstacleProjectionResult.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Sources/ObstacleSourceStatus.cs`
**Interfaces:**
- Consumes: 纯检测快照和外部障碍物几何。
- Produces: 世界 mm 几何、来源状态和诊断信息。
- [ ] **Step 1: 扩展失败检查**
对来源接口的 ID、版本、必需性和 `ProjectToWorld`,TwoLeg 输入构造函数/属性,以及投影结果工厂方法和状态枚举值断言中文 API 说明。
- [ ] **Step 2: 运行检查确认失败**
Run: `powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_documentation.ps1`
Expected: 失败并指出缺失的来源或 TwoLeg 契约说明。
- [ ] **Step 3: 添加来源注释**
明确 `ProjectToWorld` 不得读传感器、定位、UI、时钟;`SourceVersion` 必须在快照内容变化时递增;`IsRequired` 的失败语义;TwoLeg 检测时世界位姿和两腿局部 mm 坐标、航向弧度、半径单位;`Applied/Empty/Unavailable/Invalid` 的规划含义。
- [ ] **Step 4: 运行来源工厂验证**
Run: `powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_factory.ps1`
Expected: `Planning map factory checks passed.`
### Task 5: 注释规划快照、距离场与缓存契约
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Planning/PlanningGridMap.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Planning/PlanningMapAdapter.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Planning/ObstacleDistanceField.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Planning/EuclideanDistanceTransform.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Planning/PlanningMapCache.cs`
**Interfaces:**
- Consumes: 环境占据栅格和建图输入/占据哈希。
- Produces: 不可变规划快照、保守距离和缓存复用行为说明。
- [ ] **Step 1: 扩展失败检查**
断言 `PlanningGridMap` 的公共属性与查询方法、适配器/距离场/EDT 的公共静态方法、缓存公共方法均有中文 `///`;检查 `PlanningGridMap` 注释含 m 与 mm 的单位区分。
- [ ] **Step 2: 运行检查确认失败**
Run: `powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_documentation.ps1`
Expected: 失败并报告缺失的规划或缓存说明。
- [ ] **Step 3: 添加规划与缓存注释**
说明 `PlanningGridMap` 不可变、规划世界查询使用 m、越界视为占据/零净距、距离是保守下界;说明适配器从 mm 环境图转为 m 规划图;说明 EDT 输出平方距离;说明缓存容量为四、输入命中返回同一快照、占据命中共享数组但颁发新快照元数据。
- [ ] **Step 4: 运行适配器和工厂验证**
Run: `powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_adapter.ps1`
Run: `powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_factory.ps1`
Expected: 两个脚本通过。
### Task 6: 注释测试、PNG 调试公共 API并完成总验证
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Test/MovementTest.MapTest.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Test/Visualization/PlanningMapImageExportRequest.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Test/Visualization/PlanningMapImageExportResult.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Test/Visualization/PlanningMapImageExporter.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Test/Visualization/PlanningMapImageRenderer.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Test/Visualization/ValidatedPngWriter.cs`
**Interfaces:**
- Consumes: 只读 `PlanningGridMap` 和可选 PNG 输出目录。
- Produces: 清晰的 MapTest 配置/日志语义和 PNG 导出结果说明。
- [ ] **Step 1: 扩展失败检查**
`PlanningMapTest.Test/TestStop`、PNG 请求/结果的每个属性、导出器常量与 `ExportIfEnabled`、渲染器和 PNG 写入器公共方法断言中文 `///`
- [ ] **Step 2: 运行检查确认失败**
Run: `powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_documentation.ps1`
Expected: 失败并列出测试或可视化公共成员。
- [ ] **Step 3: 添加测试与 PNG 注释**
说明 MapTest 是手工 Clumsy 入口,日志/PNG 开关仅影响调试;说明 PNG 不参与建图和缓存;说明输出目录、像素尺寸、字节大小、Saved/Skipped 的语义;说明 `ValidatedPngWriter.Write` 输入是 RGBA 行主序字节及其宽高。
- [ ] **Step 4: 完整验证**
Run:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_documentation.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_utils.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_factory.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_adapter.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_image.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_test_config.ps1
```
Expected: 所有脚本通过;编译 0 error。现有过时 API 警告若仍来自 `MovementTests.TireFollowing.cs``TireFollowing.cs`,记录为非本任务引入。
## Plan Self-Review
- Spec coverage: Task 1 覆盖 README 和目录结构;Task 2 至 Task 6 覆盖全部 public API 分层;Task 6 覆盖完整验证。
- Placeholder scan: 本计划没有 TODO、TBD 或未指定的验证命令。
- Type consistency: 文中使用的类型和方法名均来自当前 Map 源码;不引入新运行时接口。
@@ -1,534 +0,0 @@
# P1 粗路径 Clumsy UI 集成 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:** 交付可在 Clumsy 中后台运行的七个粗路径测试入口,显示真实规划栅格快照和完整路径信息,并支持传入 AMR 世界位姿与手动目标位姿。
**Architecture:** `CoarsePathScenarioFactory` 保持无 UI 的纯输入构造职责,提供六个可重复的回归场景及一个显式标注为空图演示的“AMR 位姿 + 手动终点”请求创建入口。`MovementTest.CoarsePathTest.cs` 只作为 UI 适配层:把 AMR/目标 mm+deg 转为核心所需的 m+rad,使用一个共享门面在 `Task.Run` 后台运行,并从不可变 `CoarsePathPlanningJobResult` 绘制地图快照和路径。
**Tech Stack:** C# / `netstandard2.0`、现有 Clumsy `MovementTest`/`Painter``CoarsePathPlanningService`、PowerShell 反射验证脚本。
## Global Constraints
- `PlanningMapRequest` 的地图、障碍物、AMR 输入和手动目标 X/Y 均为世界 mm;`Pose2D` 和路径 X/Y 为世界 m;核心航向为 rad。
- 项目上游的 AMR `th` 输入按 deg 适配为 `th * Math.PI / 180d`;不得沿用直接将该值传给 `Math.Cos/Sin` 的旧写法。
- AMR 起点必须是车辆几何中心;传感器安装点必须由上游先按外参转换。
- UI 和测试只能调用 `CoarsePathPlanningService.Plan(job, token)`;不得直接实例化 `PlanningMapFactory``HybridAStarPlanner`、栅格化器、碰撞器、原语生成器或搜索节点。
- 所有七个 MovementTest 都不得引用 `BasicPilotBase.Chassis``SendMotion``DriveTask` 或任何底盘控制 API。
- `Test` 不得等待后台任务或读取 `Task.Result``TestStop` 先取消令牌,再使运行编号失效、解绑任务并清空 Painter。
- 只在 `PlanningStatus.Success` 绘制路径、方向箭头、换向点和扩大车体检查框;失败、取消和超时只显示地图、起点、终点和状态。
- 代码兼容 `netstandard2.0`,不引入新 NuGet 包;公开类型/成员写中文 XML 文档,复杂单位与并发逻辑写简短中文行注释。
- 不改动 TrapMap 文件或旧 TrapMap 验证脚本;不执行 Git 状态、差异、提交或重置操作。
---
## 文件结构
| 文件 | 变更职责 |
| --- | --- |
| `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/CoarsePathScenarioFactory.cs` | 新建纯场景工厂、六场景枚举、AMR/目标位姿 mm+deg 到核心 `Pose2D` 的转换,以及空图演示手动目标请求。 |
| `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/MovementTest.CoarsePathTest.cs` | 新建七个 UI 入口、共享会话执行器、任务取消、Painter 地图/路径/图例绘制与手动输入解析。 |
| `ClumsyPilot/tests/verify_coarse_path_integration.ps1` | 为工厂行为、单位转换、缓存/换向/无解、UI 源码边界与 README 内容新增真实程序集和文本断言。 |
| `ClumsyPilot/ParkrobTrajplanner/CoarsePath/README.md` | 记录 P1 测试入口、输入单位、空图演示限制、图例、停止语义及无底盘命令边界。 |
### 固定接口
```csharp
namespace MultiWheelC.TrajectoryPlanning.CoarsePath.Test;
public enum CoarsePathTestScenario
{
ExplicitEmpty,
RectangleDetour,
ManualAndTwoLeg,
CacheHit,
ReverseGearSwitch,
NoFeasiblePath,
}
public static class CoarsePathScenarioFactory
{
public static CoarsePathPlanningJob Create(CoarsePathTestScenario scenario);
public static CoarsePathPlanningJob CreateManualGoalDemo(
double startXMillimeters, double startYMillimeters, double startHeadingDegrees,
double goalXMillimeters, double goalYMillimeters, double goalHeadingDegrees);
}
```
`CreateManualGoalDemo` 只构造带 2,000 mm 边缘留白的显式空图演示请求,并在 README/测试名称中明确其不代表真实环境安全。未来现场入口必须提供真实 `IMapObstacleSource` 快照,而不是修改此方法的语义。
### Task 1: 工厂契约与失败测试
**Files:**
- Modify: `ClumsyPilot/tests/verify_coarse_path_integration.ps1`
- Create later in Task 2: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/CoarsePathScenarioFactory.cs`
**Consumes:** 已有 `CoarsePathPlanningService.Plan(CoarsePathPlanningJob, CancellationToken)``Find-Method``Assert-True``Assert-Equal` 与程序集加载逻辑。
**Produces:**`CoarsePathTestScenario``CoarsePathScenarioFactory` 的反射行为约束;Task 2 的最小实现必须使这些断言通过。
- [ ] **Step 1: 在集成脚本加入工厂反射测试**
在现有 facade 检查后、最终输出前插入以下 PowerShell。它要求类型和两个公开方法都存在,因此在工厂未创建时失败。
```powershell
$testNamespace = $coarsePath + 'Test.'
$scenarioEnumType = $assembly.GetType($testNamespace + 'CoarsePathTestScenario', $false)
$scenarioFactoryType = $assembly.GetType($testNamespace + 'CoarsePathScenarioFactory', $false)
Assert-True ($scenarioEnumType -ne $null) 'P1 scenario enum must exist.'
Assert-True ($scenarioFactoryType -ne $null) 'P1 scenario factory must exist.'
$factoryCreate = Find-Method $scenarioFactoryType 'Create' @($scenarioEnumType)
$factoryManual = Find-Method $scenarioFactoryType 'CreateManualGoalDemo' @(
[double], [double], [double], [double], [double], [double])
Assert-True ($factoryCreate -ne $null) 'P1 scenario factory must expose Create(scenario).'
Assert-True ($factoryManual -ne $null) 'P1 scenario factory must expose CreateManualGoalDemo with six doubles.'
$scenarioNames = @('ExplicitEmpty', 'RectangleDetour', 'ManualAndTwoLeg', 'CacheHit', 'ReverseGearSwitch', 'NoFeasiblePath')
foreach ($scenarioName in $scenarioNames) {
$scenario = [Enum]::Parse($scenarioEnumType, $scenarioName)
$jobA = $factoryCreate.Invoke($null, @($scenario))
$jobB = $factoryCreate.Invoke($null, @($scenario))
Assert-True ($jobA -ne $null) "Scenario $scenarioName must return a job."
Assert-False ([object]::ReferenceEquals($jobA, $jobB)) "Scenario $scenarioName must return a new job per call."
}
$manualJob = $factoryManual.Invoke($null, @(1000.0, 2000.0, 90.0, 4000.0, 2000.0, 0.0))
Assert-Near 1.0 $manualJob.Start.X 'Manual AMR X must convert mm to m.'
Assert-Near 2.0 $manualJob.Start.Y 'Manual AMR Y must convert mm to m.'
Assert-Near ([Math]::PI / 2.0) $manualJob.Start.Heading 'Manual AMR heading must convert degrees to radians.'
Assert-Near 4.0 $manualJob.Goal.X 'Manual goal X must convert mm to m.'
Assert-Near 0.0 $manualJob.Goal.Heading 'Manual goal heading must convert degrees to radians.'
Assert-True $manualJob.MapRequest.AllowExplicitEmptyMap 'Manual goal demo must declare its empty map explicitly.'
```
- [ ] **Step 2: 运行脚本确认失败**
Run:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1
```
Expected: 构建成功;脚本因 `P1 scenario enum must exist.` 失败。
### Task 2: 实现纯场景工厂并通过行为测试
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/CoarsePathScenarioFactory.cs`
- Test: `ClumsyPilot/tests/verify_coarse_path_integration.ps1`
**Consumes:** Task 1 的枚举和两个公开工厂方法;`MapBoundsMm``ManualObstacleSource``TwoLegObstacleSource``Pose2D``VehicleParameters``HybridAStarConfiguration`
**Produces:** 六个可重复 job 与一个空图演示手动 job,供 UI 入口和后续脚本行为断言共同使用。
- [ ] **Step 1: 先建立最小的公共类型和转换辅助函数**
创建工厂文件并定义以下枚举、转换函数和公共入口。所有输入先做有限值检查;非有限输入抛出 `ArgumentOutOfRangeException`,避免伪造核心请求。
```csharp
public enum CoarsePathTestScenario
{
ExplicitEmpty,
RectangleDetour,
ManualAndTwoLeg,
CacheHit,
ReverseGearSwitch,
NoFeasiblePath,
}
public static class CoarsePathScenarioFactory
{
private const double MillimetersPerMeter = 1000d;
private const double DegreesToRadians = Math.PI / 180d;
private const float ResolutionMillimeters = 50f;
private const double ManualMapPaddingMillimeters = 2000d;
public static CoarsePathPlanningJob Create(CoarsePathTestScenario scenario)
{
switch (scenario)
{
case CoarsePathTestScenario.ExplicitEmpty: return CreateExplicitEmpty();
case CoarsePathTestScenario.RectangleDetour: return CreateRectangleDetour();
case CoarsePathTestScenario.ManualAndTwoLeg: return CreateManualAndTwoLeg();
case CoarsePathTestScenario.CacheHit: return CreateRectangleDetour();
case CoarsePathTestScenario.ReverseGearSwitch: return CreateReverseGearSwitch();
case CoarsePathTestScenario.NoFeasiblePath: return CreateNoFeasiblePath();
default: throw new ArgumentOutOfRangeException(nameof(scenario));
}
}
public static CoarsePathPlanningJob CreateManualGoalDemo(
double startXMillimeters, double startYMillimeters, double startHeadingDegrees,
double goalXMillimeters, double goalYMillimeters, double goalHeadingDegrees)
{
EnsureFinite(startXMillimeters, nameof(startXMillimeters));
EnsureFinite(startYMillimeters, nameof(startYMillimeters));
EnsureFinite(startHeadingDegrees, nameof(startHeadingDegrees));
EnsureFinite(goalXMillimeters, nameof(goalXMillimeters));
EnsureFinite(goalYMillimeters, nameof(goalYMillimeters));
EnsureFinite(goalHeadingDegrees, nameof(goalHeadingDegrees));
return CreateJob(CreateManualDemoMap(startXMillimeters, startYMillimeters, goalXMillimeters, goalYMillimeters),
ToPose(startXMillimeters, startYMillimeters, startHeadingDegrees),
ToPose(goalXMillimeters, goalYMillimeters, goalHeadingDegrees), null, GoalDirectionConstraint.Any);
}
private static Pose2D ToPose(double xMillimeters, double yMillimeters, double headingDegrees)
=> new Pose2D(xMillimeters / MillimetersPerMeter, yMillimeters / MillimetersPerMeter,
headingDegrees * DegreesToRadians);
}
```
- [ ] **Step 2: 实现统一请求模板和六个固定场景**
使用统一的车辆和配置,避免场景间无意改变安全或搜索语义。模板必须是新对象:
```csharp
private static CoarsePathPlanningJob CreateJob(PlanningMapRequest mapRequest, Pose2D start, Pose2D goal,
TravelDirection? startDirection, GoalDirectionConstraint goalDirection)
{
return new CoarsePathPlanningJob
{
MapRequest = mapRequest,
Start = start,
Goal = goal,
Vehicle = new VehicleParameters
{
LengthMeters = 0.80d,
WidthMeters = 0.60d,
SafetyMarginMeters = 0.05d,
MaximumCurvaturePerMeter = 1d / 1.20d,
},
Configuration = new HybridAStarConfiguration(),
StartDirection = startDirection,
GoalDirection = goalDirection,
};
}
```
固定地图均使用 `new MapBoundsMm(0f, 6000f, 0f, 4000f)`、50 mm 分辨率。以下代码固定各场景的障碍来源和世界位姿,所有 `ManualObstacleSource` 版本为 `1L`、所有必需来源为 `true`
```csharp
private static CoarsePathPlanningJob CreateExplicitEmpty()
=> CreateJob(CreateMap(true, Array.Empty<IMapObstacleSource>()),
new Pose2D(1d, 2d, 0d), new Pose2D(5d, 2d, 0d), null, GoalDirectionConstraint.Forward);
private static CoarsePathPlanningJob CreateRectangleDetour()
=> CreateJob(CreateMap(false, new IMapObstacleSource[]
{
new ManualObstacleSource("manual", 1L, true, new IMapObstacle[]
{ new AxisAlignedRectangleObstacle(2700f, 3300f, 1200f, 2800f) }),
}), new Pose2D(1d, 2d, 0d), new Pose2D(5d, 2d, 0d), null, GoalDirectionConstraint.Forward);
private static CoarsePathPlanningJob CreateManualAndTwoLeg()
=> CreateJob(CreateMap(false, new IMapObstacleSource[]
{
new ManualObstacleSource("manual", 1L, true, new IMapObstacle[]
{
new CircleObstacle(2400f, 1300f, 220f),
new AxisAlignedRectangleObstacle(3000f, 3600f, 2000f, 2600f),
}),
new TwoLegObstacleSource("two-leg", 1L, true,
new TwoLegProjectionInput(true, 3900f, 2500f, 0d,
-180f, -180f, -180f, 180f, 140f, "P1 fixed TwoLeg snapshot.")),
}), new Pose2D(1d, 1d, 0d), new Pose2D(5d, 3d, 0d), null, GoalDirectionConstraint.Forward);
private static CoarsePathPlanningJob CreateNoFeasiblePath()
=> CreateJob(CreateMap(false, new IMapObstacleSource[]
{
new ManualObstacleSource("manual", 1L, true, new IMapObstacle[]
{ new AxisAlignedRectangleObstacle(2900f, 3100f, 0f, 4000f) }),
}), new Pose2D(1d, 2d, 0d), new Pose2D(5d, 2d, 0d), null, GoalDirectionConstraint.Forward);
```
`CreateMap` 返回新的 `PlanningMapRequest`,固定写入地图边界、分辨率、给定来源和 `AllowExplicitEmptyMap`。倒车换向场景使用空图、起点 `(1,2,0)`、终点 `(4,2,0)``StartDirection=Forward``GoalDirection=Reverse`,使路径必须以至少一次换向结束。
倒车换向场景使用空图、起点 `(1,2,0)`、终点 `(4,2,0)``StartDirection=Forward``GoalDirection=Reverse`,使路径必须以至少一次换向结束。若 P0 的离散搜索在此几何下无法稳定得到成功,只允许调整此场景的目标距离或障碍布局,且测试必须继续要求 `IsGearSwitchPoint=true`
`CreateManualDemoMap` 用起终点 X/Y 的最小/最大值各扩展 `ManualMapPaddingMillimeters`,按 50 mm 向外取整,并明确设置 `AllowExplicitEmptyMap=true` 和空的 `ObstacleSources`
- [ ] **Step 3: 扩展行为断言以覆盖所有场景的实际状态**
在 Task 1 的反射代码之后增加服务执行测试。它不引用任何 Painter 或 MovementTest
```powershell
$scenarioService = [Activator]::CreateInstance($serviceType)
foreach ($scenarioName in @('ExplicitEmpty', 'RectangleDetour', 'ManualAndTwoLeg', 'ReverseGearSwitch')) {
$job = $factoryCreate.Invoke($null, @([Enum]::Parse($scenarioEnumType, $scenarioName)))
$result = $servicePlan.Invoke($scenarioService, @($job, [Threading.CancellationToken]::None))
Assert-Equal 'Success' $result.PlanningResult.Status.ToString() "Scenario $scenarioName must succeed."
}
$reverseJob = $factoryCreate.Invoke($null, @([Enum]::Parse($scenarioEnumType, 'ReverseGearSwitch')))
$reverseResult = $servicePlan.Invoke($scenarioService, @($reverseJob, [Threading.CancellationToken]::None))
Assert-True (($reverseResult.PlanningResult.Path | Where-Object { $_.IsGearSwitchPoint }).Count -ge 1) 'Reverse scenario must expose a gear-switch point.'
$noPathJob = $factoryCreate.Invoke($null, @([Enum]::Parse($scenarioEnumType, 'NoFeasiblePath')))
$noPathResult = $servicePlan.Invoke($scenarioService, @($noPathJob, [Threading.CancellationToken]::None))
Assert-Equal 'NoFeasiblePath' $noPathResult.PlanningResult.Status.ToString() 'Barrier scenario must be infeasible.'
Assert-Equal 0 $noPathResult.PlanningResult.Path.Count 'Infeasible scenario must not publish a path.'
$cacheJobA = $factoryCreate.Invoke($null, @([Enum]::Parse($scenarioEnumType, 'CacheHit')))
$cacheJobB = $factoryCreate.Invoke($null, @([Enum]::Parse($scenarioEnumType, 'CacheHit')))
$cacheFirst = $servicePlan.Invoke($scenarioService, @($cacheJobA, [Threading.CancellationToken]::None))
$cacheSecond = $servicePlan.Invoke($scenarioService, @($cacheJobB, [Threading.CancellationToken]::None))
Assert-Equal 'Input' $cacheSecond.MapResult.CacheHit.ToString() 'Cache-hit scenario must reuse the complete map input.'
Assert-Equal $cacheFirst.PlanningResult.Status $cacheSecond.PlanningResult.Status 'Map cache reuse must not change planning status.'
```
- [ ] **Step 4: 运行测试并固定数值场景**
Run:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1
```
Expected: 工厂、单位转换、六个固定场景、缓存与无解断言通过;此时尚未加入 MovementTest 源码检查,因此脚本整体通过。
### Task 3: MovementTest 后台会话与完整 Painter 绘制
**Files:**
- Modify: `ClumsyPilot/tests/verify_coarse_path_integration.ps1`
- Create: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/MovementTest.CoarsePathTest.cs`
**Consumes:** Task 2 的 `CoarsePathScenarioFactory.Create``CreateManualGoalDemo``CoarsePathPlanningService``CoarsePathPlanningJobResult``PlanningGridMap``Painter`
**Produces:** 六个固定场景入口与一个“AMR 位姿 + 手动终点(空图演示)”入口;所有入口通过同一个后台执行器运行和绘制。
- [ ] **Step 1: 写入失败的 UI 源码结构断言**
在 PowerShell 脚本中加入下列纯文本检查,避免在自动化测试中实例化外部 UI:
```powershell
$movementTestPath = Join-Path $plannerRoot 'CoarsePath\Test\MovementTest.CoarsePathTest.cs'
if (-not (Test-Path -LiteralPath $movementTestPath -PathType Leaf)) {
throw 'P1 coarse-path MovementTest source file must exist.'
}
$movementTestContent = Get-Content -LiteralPath $movementTestPath -Raw
foreach ($required in @(
'[MovementTest(name = "粗路径-显式空图")]',
'[MovementTest(name = "粗路径-矩形绕行")]',
'[MovementTest(name = "粗路径-多来源障碍")]',
'[MovementTest(name = "粗路径-缓存命中")]',
'[MovementTest(name = "粗路径-倒车换向")]',
'[MovementTest(name = "粗路径-无解")]',
'[MovementTest(name = "粗路径-AMR起点手动终点(空图演示)")]',
'Task.Run', 'CancellationTokenSource', 'CoarsePathPlanningService',
'PlanningGridMap', 'IsOccupied', 'ResolutionMm', 'SnapshotId', '图例', 'IsGearSwitchPoint')) {
Assert-True $movementTestContent.Contains($required) "MovementTest must contain: $required"
}
foreach ($forbidden in @('PlanningMapFactory', 'HybridAStarPlanner', 'MapObstacleRasterizer',
'FootprintCollisionChecker', 'MotionPrimitiveGenerator', 'HybridAStarSearch',
'BasicPilotBase.Chassis', 'SendMotion', 'DriveTask', '.Wait()', '.Result')) {
Assert-False $movementTestContent.Contains($forbidden) "MovementTest must not depend on: $forbidden"
}
```
- [ ] **Step 2: 运行脚本确认 UI 结构检查失败**
Run:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1
```
Expected: 在工厂断言通过后,因 `P1 coarse-path MovementTest source file must exist.` 失败。
- [ ] **Step 3: 实现共享会话执行器与七个薄入口**
在新文件中使用 `namespace MultiWheelC;`,引用 `System.Threading``System.Threading.Tasks``System.Drawing``System.Numerics``ClumsyCore``MDCSToolBox.Clumsy.Movements` 和 Map/CoarsePath 命名空间。定义一个内部静态执行器,核心形状如下:
```csharp
internal static class CoarsePathMovementTestRunner
{
private static readonly object SyncRoot = new object();
private static readonly CoarsePathPlanningService Service = new CoarsePathPlanningService();
private static readonly Painter Painter = UI.GetPainter("CoarsePathPlanningV1", true);
private static long _nextRunId;
private static long _activeRunId;
private static CancellationTokenSource _activeCancellation;
private static Task _activeTask;
internal static void Start(string displayName, CoarsePathPlanningJob job)
{
CancellationTokenSource previous;
long runId;
var cancellation = new CancellationTokenSource();
lock (SyncRoot)
{
previous = _activeCancellation;
runId = ++_nextRunId;
_activeRunId = runId;
_activeCancellation = cancellation;
Painter.Clear();
_activeTask = Task.Run(() => Service.Plan(job, cancellation.Token));
_activeTask.ContinueWith(task => Complete(runId, displayName, job, cancellation, task),
CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default);
}
previous?.Cancel();
}
internal static void Stop()
{
CancellationTokenSource cancellation;
lock (SyncRoot)
{
cancellation = _activeCancellation;
}
cancellation?.Cancel();
lock (SyncRoot)
{
if (!ReferenceEquals(_activeCancellation, cancellation)) return;
_activeCancellation = null;
_activeTask = null;
_activeRunId = ++_nextRunId;
}
Painter.Clear();
}
}
```
`Complete` 必须捕获 `task.Exception`,但正常情况下只接受 `CoarsePathPlanningJobResult`。在锁内确认 `runId == _activeRunId`、任务未取消并且 `task.Status == TaskStatus.RanToCompletion` 后才绘制;无论绘制与否都在 finally 中释放该任务专用 `CancellationTokenSource`。不可在锁内等待任务。
添加一个抽象 `CoarsePathScenarioMovementTest`,其 `Test` 调用 `CoarsePathMovementTestRunner.Start(DisplayName, CoarsePathScenarioFactory.Create(Scenario))`,其 `TestStop` 调用 `Stop()`。实现六个带固定属性名称的密封子类。第七个类在 `Test` 中只读取一次六个 UI 输入:AMR 起点 X/Y/航向和目标 X/Y/航向(分别为 mm/mm/deg),调用 `CreateManualGoalDemo` 后启动;输入解析失败时仅记录错误并不启动任务。
- [ ] **Step 4: 实现确定的地图和结果绘制辅助方法**
在同一执行器内只消费 `job``CoarsePathPlanningJobResult`,按固定顺序调用以下辅助方法:
```csharp
private static void DrawMap(PlanningGridMap map);
private static void DrawPose(Color color, string label, Pose2D pose);
private static void DrawGoalTolerance(Pose2D goal, HybridAStarConfiguration configuration);
private static void DrawSuccessfulPath(PlanningResult result, VehicleParameters vehicle);
private static void DrawLegendAndStatus(string displayName, CoarsePathPlanningJobResult result, int gridStride);
```
`DrawMap``[Bounds.XMin, Bounds.XMax) × [Bounds.YMin, Bounds.YMax)` 的粗边界和 X/Y 参考。`gridStride = Max(1, Ceiling(Max(Rows, Cols) / 100d))`;每 `gridStride` 个真实栅格画一条线,状态文字写入 `分辨率=...mm,显示每...格`。遍历 `row/col`,仅对 `map.IsOccupied(row,col)` 为 true 的单元以四条边线画深色格框,确保显示的是最终快照而非原始几何。
`DrawPose` 将 m 转 mm,以圆、朝向短线和标签分别绘制绿色起点、橙色终点。`DrawGoalTolerance` 将位置容差 m 转 mm,绘制橙色容差圆。`DrawSuccessfulPath` 仅在 `result.Status == PlanningStatus.Success` 时运行:相邻路径点按当前点 `Direction` 使用青色(前进)或蓝色(倒车)连线;每隔 10 点画短箭头;`IsGearSwitchPoint` 画紫色圆与“换向”;首、末、换向和每 20 点调用旋转矩形绘制,半长/半宽严格按车辆长宽加安全余量。`DrawLegendAndStatus` 在边界左上方显示边界、占据格、起点、终点、前进、倒车、换向和扩大车体颜色说明,另显示 `SnapshotId``MapResult.Status``CacheHit``PlanningResult.Status``Elapsed` 和终止原因。
- [ ] **Step 5: 运行构建与集成脚本**
Run:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1
```
Expected: 构建成功;工厂行为、七入口结构、后台取消约束和 Painter 数据来源检查全部通过。
### Task 4: README 与文档断言
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/README.md`
- Modify: `ClumsyPilot/tests/verify_coarse_path_integration.ps1`
**Consumes:** Task 2 的 `CreateManualGoalDemo` 单位契约和 Task 3 的七个入口名称、图例颜色和停止行为。
**Produces:** 可独立使用的 P1 UI 说明,以及对其关键安全声明的自动化保护。
- [ ] **Step 1: 为 README 写失败断言**
在现有 README 检查后加入:
```powershell
foreach ($requiredReadmeText in @(
'## P1Clumsy 手动测试与可视化',
'粗路径-AMR起点手动终点(空图演示)',
'AMR 位姿输入:X/Y 使用世界 mmth 使用 deg',
'Pose2DX/Y 使用 m,航向使用 rad',
'显式空图只能用于演示',
'不会发送底盘运动命令',
'TestStop',
'栅格边界',
'占据格',
'换向')) {
Assert-True $coarsePathReadmeContent.Contains($requiredReadmeText) "CoarsePath README must document: $requiredReadmeText"
}
```
- [ ] **Step 2: 运行脚本确认 README 检查失败**
Run:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1
```
Expected: 所有代码检查通过;脚本因 `CoarsePath README must document: ## P1Clumsy 手动测试与可视化` 失败。
- [ ] **Step 3: 在 README 增加 P1 专节**
在“第一版限制”之前增加 `## P1Clumsy 手动测试与可视化`,逐项写明:
1. 七个 MovementTest 名称及对应场景;缓存测试连续运行两次,第二次展示 `Input` 命中。
2. AMR/手动目标输入契约:世界 `X/Y(mm)``th(deg)`,转换成 `Pose2D` 的 m/rad;起点是车辆几何中心。
3. 空图手动目标入口只能演示坐标、路径和取消流程;现场必须提供真实障碍物快照。
4. 可视化图例:边界、抽稀格线、占据格、起点、终点及容差、前进、倒车、换向和扩大车体检查框;失败不显示部分路径。
5. `Test` 在后台规划,`TestStop` 取消令牌并清空图层;测试只显示结果,绝不发送底盘运动命令或执行路径跟踪。
- [ ] **Step 4: 运行 README 与集成检查**
Run:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1
```
Expected: 构建成功;README 和全部 P1 集成检查通过。
### Task 5: 全量回归与手动核验说明
**Files:**
- Verify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/CoarsePathScenarioFactory.cs`
- Verify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/MovementTest.CoarsePathTest.cs`
- Verify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/README.md`
- Verify: `ClumsyPilot/tests/verify_coarse_path_integration.ps1`
**Consumes:** Tasks 1–4 的代码、文档和脚本。
**Produces:** 通过 Debug 回归的 P1 UI 集成首个交付;不进入 Release 性能基准。
- [ ] **Step 1: 执行 Debug 构建和所有现存 P0/P1 功能脚本**
Run:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_factory.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_adapter.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_image.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_documentation.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_test_config.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_collision.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_search.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1
```
Expected: 构建 0 errors;每个存在的脚本返回 0 并输出其 `passed` 消息。
- [ ] **Step 2: 手动 Clumsy 验收**
在 Clumsy 的 MovementTest 列表依次运行“粗路径-矩形绕行”和“粗路径-AMR起点手动终点(空图演示)”。检查:测试启动后界面仍可操作;图层拥有边界、格线、占据格、图例、起终点与状态;成功案例有方向区分路径和扩大车体框;点击停止后图层清空且没有任何底盘运动命令。
- [ ] **Step 3: 记录交付边界**
在完成报告中明确:P1 UI 集成已完成;下一 P1 子项目是 Release 性能、资源和确定性基准;TrapMap 迁移/清理继续排除;没有执行 Git 操作。
## 自检
- 覆盖性:Task 2 交付纯场景与单位转换;Task 3 交付后台七入口和完整视觉要素;Task 4 交付 README;Task 5 交付自动化与手动验收。规格中的空图限制、实际占据快照、停止语义、无底盘命令和不显示部分路径均有对应任务。
- 占位符:已检查任务不含未决占位、延后实现或泛化错误处理类措辞;每个实现任务均给出文件、接口、测试、命令和具体代码形状。
- 类型一致性:所有任务统一使用 `CoarsePathTestScenario``CoarsePathScenarioFactory.Create``CreateManualGoalDemo``CoarsePathPlanningJob``CoarsePathPlanningJobResult``PlanningGridMap`;AMR 输入始终是 mm+deg,核心位姿始终是 m+rad。
@@ -1,369 +0,0 @@
# 规划操作预算与诊断收尾实施计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 让一次粗规划调用在建图、距离场、Dijkstra 和 Hybrid A* 阶段共用可取消的总超时预算,并发布真实的 Open List 诊断计数。
**Architecture:**`Utils` 新增内部 `PlanningOperationBudget`,它不依赖 Map 或 CoarsePath,只报告继续、取消、超时。Map 与搜索分别将该中立结果映射为自己的结果;`CoarsePathPlanningService` 创建唯一预算并传递给下层。原有公开的无预算入口保持兼容,门面使用内部带预算入口。
**Tech Stack:** C# 10、.NET Standard 2.0、PowerShell 反射回归脚本、`Stopwatch``CancellationToken`
## Global Constraints
- 位置使用 m、地图输入使用 mm、航向使用 rad、曲率使用 1/m;不得改变现有单位边界。
- 取消或超时必须返回空路径和空方向分段,绝不发布部分路径或部分 `PlanningGridMap`
- 地图不得依赖 CoarsePath;共享预算只能放在 `ParkrobTrajplanner/Utils`
- 每 256 个或更少循环工作单元检查一次预算;外部 `IMapObstacleSource.ProjectToWorld()` 是调用方提供的同步快照接口,只能在调用前后检查,不能强制抢占其内部执行。
- 不改变碰撞保守性、运动原语、代价公式、目标候选保护或 Open List 排序。
- 保留 `PlanningMapFactory.Create(request)``HybridAStarPlanner.Plan(request, token)``HybridAStarSearch.Search(request, token)``GridDijkstraHeuristic(map,row,col)` 的兼容入口。
- 不执行 Git 添加、提交、重置或工作区清理。
---
## 文件结构
| 文件 | 职责 |
| --- | --- |
| `Utils/PlanningOperationBudget.cs` | 内部单调计时、取消检查和统一停止原因。 |
| `Map/PlanningMapBuildResult.cs` | 地图构建状态:成功、失败、取消、超时。 |
| `Map/Core/EnvironmentMapBuilder.cs``EnvironmentMapBuildResult.cs` | 将预算传入来源处理与栅格化,并保留停止原因。 |
| `Map/Obstacles/MapObstacleRasterizer.cs` | 在圆形/矩形逐格写入期间定期停止。 |
| `Map/Planning/{PlanningMapAdapter,ObstacleDistanceField,EuclideanDistanceTransform}.cs` | 在占据复制、EDT 和距离换算期间定期停止且不产出快照。 |
| `Map/PlanningMapFactory.cs` | 可取消地等待创建锁、检查缓存、建图、哈希和写缓存。 |
| `CoarsePath/Search/{GridDijkstraHeuristic,HybridAStarSearch}.cs` | 为 Dijkstra 和 Open List 使用共享预算,记录陈旧条目和峰值。 |
| `CoarsePath/{HybridAStarPlanner,Contracts/PlanningDiagnostics}.cs` | 将搜索统计传给最终结果,并让公开 Planner 包装兼容预算。 |
| `CoarsePath/Facade/CoarsePathPlanningService.cs` | 创建一次调用唯一预算,映射地图阶段停止状态。 |
| `Map/README.md``CoarsePath/README.md` | 分别说明地图构建状态,以及粗规划总超时和取消状态。 |
| `tests/verify_planning_map_factory.ps1``verify_planning_map_adapter.ps1``verify_coarse_path_search.ps1``verify_coarse_path_integration.ps1` | 预算与诊断的反射回归覆盖。 |
### Task 1: 建立独立的操作预算与地图终止契约
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/Utils/PlanningOperationBudget.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/PlanningMapBuildResult.cs`
- Modify: `ClumsyPilot/tests/verify_planning_map_factory.ps1`
**Consumes:** `System.Diagnostics.Stopwatch``System.Threading.CancellationToken`
**Produces:** `PlanningOperationStopReason``PlanningOperationBudget``PlanningMapBuildStatus`;下游 Map/CoarsePath 均只通过这些类型传递停止信息。
- [ ] **Step 1: 写失败测试,锁定新的地图状态与预算公开反射形状。**
在现存的 `verify_planning_map_factory.ps1` 断言存在内部 `MultiWheelC.TrajectoryPlanning.Utils.PlanningOperationBudget` 与三值 `PlanningOperationStopReason`,并断言 `PlanningMapBuildResult.Status` 存在;已取消、已超时结果的 `Succeeded` 必须为 `false``Map``$null``CacheHit``None`
```powershell
$statusType = $assembly.GetType($ns + 'PlanningMapBuildStatus', $true)
Assert-True ($statusType.GetEnumNames() -contains 'Cancelled') 'Map build status must expose cancellation.'
Assert-True ($statusType.GetEnumNames() -contains 'TimedOut') 'Map build status must expose timeout.'
Assert-True ($resultType.GetProperty('Status') -ne $null) 'Map result must expose an explicit status.'
```
- [ ] **Step 2: 运行两个脚本,确认因类型或属性不存在而失败。**
Run:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_factory.ps1
```
Expected: 断言报告 `PlanningOperationBudget` 或 `PlanningMapBuildStatus` 缺失。
- [ ] **Step 3: 实现最小共享预算和显式地图状态。**
`PlanningOperationBudget` 的核心接口固定如下;超时使用构造时启动的单调 `Stopwatch`,取消优先于超时:
```csharp
internal enum PlanningOperationStopReason { None, Cancelled, TimedOut }
internal sealed class PlanningOperationBudget
{
internal PlanningOperationBudget(CancellationToken cancellationToken, TimeSpan timeout);
internal static PlanningOperationBudget Unlimited(CancellationToken cancellationToken);
internal TimeSpan Elapsed { get; }
internal PlanningOperationStopReason GetStopReason();
internal PlanningOperationStopReason CheckEvery(ref int workItemCount);
}
```
`CheckEvery` 在第一次工作单元以及每 256 个工作单元检查;`Unlimited` 不启用时间限制但仍响应取消。将地图结果从布尔构造改为状态构造:
```csharp
public enum PlanningMapBuildStatus { Success, Failed, Cancelled, TimedOut }
public PlanningMapBuildStatus Status { get; }
public bool Succeeded { get { return Status == PlanningMapBuildStatus.Success; } }
internal static PlanningMapBuildResult Stopped(PlanningOperationStopReason reason,
IReadOnlyList<ObstacleProjectionResult> sourceResults)
{
return new PlanningMapBuildResult(
reason == PlanningOperationStopReason.Cancelled
? PlanningMapBuildStatus.Cancelled
: PlanningMapBuildStatus.TimedOut,
reason == PlanningOperationStopReason.Cancelled ? "地图创建已取消。" : "地图创建已超时。",
sourceResults, PlanningMapCacheHit.None, null);
}
```
- [ ] **Step 4: 重跑两个脚本,确认新契约通过且旧地图缓存断言未回归。**
Run: 与 Step 2 相同。
Expected: 地图工厂脚本输出 `Planning map factory checks passed.`。
### Task 2: 让地图创建、栅格化和距离场遵守预算
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Core/EnvironmentMapBuildResult.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Core/EnvironmentMapBuilder.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Obstacles/MapObstacleRasterizer.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Planning/PlanningMapAdapter.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Planning/ObstacleDistanceField.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/Planning/EuclideanDistanceTransform.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/PlanningMapFactory.cs`
- Modify: `ClumsyPilot/tests/verify_planning_map_factory.ps1`
- Modify: `ClumsyPilot/tests/verify_planning_map_adapter.ps1`
**Consumes:** Task 1 的 `PlanningOperationBudget` 与 `PlanningOperationStopReason`。
**Produces:** `PlanningMapFactory` 的内部 `Create(PlanningMapRequest, PlanningOperationBudget)`;它在取消/超时时返回 `PlanningMapBuildResult.Stopped`,不会写入 LRU 缓存。
- [ ] **Step 1: 写失败测试,覆盖预先取消、EDT 中超时与缓存不污染。**
在工厂脚本创建一个已取消的 `CancellationTokenSource`,通过反射调用新的内部带预算 `Create`,断言:
```powershell
Assert-Equal 'Cancelled' $cancelledMapResult.Status.ToString() 'Cancelled map construction must report cancellation.'
Assert-False $cancelledMapResult.Succeeded 'Cancelled map construction must not succeed.'
Assert-Null $cancelledMapResult.Map 'Cancelled map construction must not publish a map.'
Assert-Equal 'None' $cancelledMapResult.CacheHit.ToString() 'Stopped construction must not publish a cache hit.'
```
在适配器脚本为含障碍的大栅格创建 `PlanningOperationBudget`,使用零超时调用内部 `TryCreate`,断言返回 `TimedOut` 且输出 `PlanningGridMap` 为 `$null`。随后用无预算入口再次创建相同地图,断言距离场仍可用,以证明停止时没有污染输入或缓存。
- [ ] **Step 2: 运行地图工厂和适配器脚本,确认新增反射入口缺失而失败。**
Run:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_factory.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_adapter.ps1
```
Expected: 新带预算 `Create` 或 `TryCreate` 反射查找失败。
- [ ] **Step 3: 在地图管线的全部长循环中传递并检查预算。**
实现以下内部接口,所有 `Try*` 在停止时返回 `false` 并把 `stopReason` 设为非 `None`;普通参数错误仍按现有失败原因或异常处理。
```csharp
internal EnvironmentMapBuildResult Build(MapBuildRequest request, PlanningOperationBudget budget);
internal static bool TryRasterize(EnvironmentGridMap map, IMapObstacle obstacle,
PlanningOperationBudget budget, out PlanningOperationStopReason stopReason);
internal static bool TryCreate(EnvironmentGridMap environmentMap, PlanningOperationBudget budget,
out PlanningGridMap map, out PlanningOperationStopReason stopReason);
internal static bool TryCreate(byte[] occupied, int rows, int cols, double resolutionMeters,
PlanningOperationBudget budget, out ObstacleDistanceField field,
out PlanningOperationStopReason stopReason);
internal static bool TryComputeSquaredDistances(byte[] occupied, int rows, int cols,
PlanningOperationBudget budget, out double[] squared,
out PlanningOperationStopReason stopReason);
internal PlanningMapBuildResult Create(PlanningMapRequest request, PlanningOperationBudget budget);
```
保留现有公开 `Build`、`Rasterize`、`PlanningMapAdapter.Create`、`ObstacleDistanceField.Create` 与 `ComputeSquaredDistances`,让它们以 `PlanningOperationBudget.Unlimited(CancellationToken.None)` 包装新入口。对圆形/矩形逐格循环、EDT 两遍扫描、`Transform1D` 两个 `q` 循环、距离场扫描均调用 `budget.CheckEvery(ref workItemCount)`。
工厂以 `Monitor.TryEnter(_createGate, 16)` 循环等待创建锁;每次失败后检查预算。拿到锁后立刻再检查预算,随后在缓存读、来源处理、适配、占据哈希和每次缓存写入前检查。将 SHA-256 改为每 4096 字节调用 `TransformBlock` 的增量哈希,并在块间检查预算。任一非 `None` 停止原因直接返回 `PlanningMapBuildResult.Stopped`,且不会执行 `_cache.AddOccupancy` 或 `_cache.AddInput`。
- [ ] **Step 4: 重跑地图脚本,确认普通建图、两级缓存和新停止结果同时通过。**
Run: 与 Step 2 相同。
Expected: `Planning map factory checks passed.` 与 `Planning map adapter checks passed.`。
### Task 3: 为 Dijkstra 与 Hybrid A* 使用同一预算并记录 Open List 统计
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Search/GridDijkstraHeuristic.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Search/HybridAStarSearch.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/HybridAStarPlanner.cs`
- Modify: `ClumsyPilot/tests/verify_coarse_path_search.ps1`
- Modify: `ClumsyPilot/tests/verify_coarse_path_integration.ps1`
**Consumes:** Task 1 的预算和 Task 2 不可变地图;现有 `BinaryMinHeap<int>`。
**Produces:** `GridDijkstraHeuristic.TryCreate`、带共享预算的内部搜索/规划入口,以及 `HybridAStarSearchResult.StaleOpenListEntryCount` 和 `PeakOpenListCount`。
- [ ] **Step 1: 写失败测试,覆盖 Dijkstra 中取消、总超时和统计透传。**
在搜索脚本上创建至少 500×500 格的已就绪空地图,在地图创建完成后启动 `CancellationTokenSource.CancelAfter(1)` 并调用搜索。断言返回 `Cancelled`、`SuccessNodeIndex` 为 `$null`、运行时间小于 2 秒。再用同一地图和 `SearchTimeout = TimeSpan.Zero` 断言 `SearchTimeout`,以证明启发式创建前即尊重总预算。
反射断言搜索结果的新属性存在,并让直接路径搜索至少满足:
```powershell
Assert-True ($searchResultType.GetProperty('StaleOpenListEntryCount') -ne $null) 'Search result must expose stale Open List entries.'
Assert-True ($searchResultType.GetProperty('PeakOpenListCount') -ne $null) 'Search result must expose Open List peak size.'
Assert-True ($searchResult.PeakOpenListCount -ge 1) 'A successful search must retain at least one Open List entry.'
Assert-True ($searchResult.StaleOpenListEntryCount -ge 0) 'Stale Open List entry count must never be negative.'
```
在集成脚本断言 `PlanningResult.Diagnostics` 中的两项值与搜索结果一致;对产生重开/失效条目的固定障碍场景断言陈旧条目数大于零。
- [ ] **Step 2: 运行搜索和集成脚本,确认新增属性、带预算入口或中途取消断言失败。**
Run:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_search.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1
```
Expected: 新属性或 Dijkstra 中止行为缺失导致失败;现有“搜索前取消”检查不应被视为通过中途取消测试。
- [ ] **Step 3: 实现预算感知的 Dijkstra、搜索与真实诊断。**
`GridDijkstraHeuristic` 保留当前公开构造函数,并增加内部可失败构建:
```csharp
internal static bool TryCreate(PlanningGridMap map, int goalRow, int goalCol,
PlanningOperationBudget budget, out GridDijkstraHeuristic heuristic,
out PlanningOperationStopReason stopReason);
```
`Build` 的每次出堆与每 256 个邻居检查预算;停止时不返回部分启发式。`HybridAStarSearch.Search(request, token)` 从 `request.Configuration.SearchTimeout` 创建预算作为兼容包装,新增内部:
```csharp
internal HybridAStarSearchResult Search(PlanningRequest request, PlanningOperationBudget budget);
```
删除搜索器内部新建的 `Stopwatch` 与 `IsTimedOut`,所有原有取消/超时位置改为读取 `budget.GetStopReason()` 并精确映射为 `PlanningStatus.Cancelled` 或 `PlanningStatus.SearchTimeout`。Dijkstra 返回停止原因时立即返回对应搜索状态;节点上限仍只在预算检查之后、真正扩展之前检查。
扩展搜索结果构造函数与只读属性:
```csharp
public int StaleOpenListEntryCount { get; }
public int PeakOpenListCount { get; }
```
每次 `openList.Push` 后执行 `peakOpenListCount = Math.Max(peakOpenListCount, openList.Count)`。普通节点出堆后因 best-G 已更新、节点索引不匹配或已关闭而跳过时递增 `staleOpenListEntryCount`;目标候选的出队复核失败不算陈旧条目。所有 `CreateResult` 调用传递两项计数。
`HybridAStarPlanner` 的公开 `Plan` 保持签名并建立自己的预算;新增内部 `Plan(request, budget)` 供门面调用。诊断使用 `budget.Elapsed`,并把两个搜索统计填入原本为零的构造参数:
```csharp
searchResult == null ? 0 : searchResult.StaleOpenListEntryCount,
searchResult == null ? 0 : searchResult.PeakOpenListCount,
```
- [ ] **Step 4: 重跑搜索和集成脚本,确认取消、超时、目标候选与统计均通过。**
Run: 与 Step 2 相同。
Expected: `Coarse path search primitive checks passed.`、`Coarse path Hybrid A star search checks passed.`、`Coarse path integration checks passed.` 和 `Coarse path facade checks passed.`。
### Task 4: 让业务门面映射地图阶段终止状态并更新调用文档
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Facade/CoarsePathPlanningService.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Map/README.md`
- Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/README.md`
- Modify: `ClumsyPilot/tests/verify_coarse_path_integration.ps1`
**Consumes:** Task 1 的地图状态、Task 2 的带预算工厂入口、Task 3 的内部 Planner 入口。
**Produces:** 一次 `CoarsePathPlanningService.Plan` 的统一预算和对调用方稳定的 `PlanningStatus` 映射。
- [ ] **Step 1: 写失败测试,锁定门面状态映射和空结果。**
在集成脚本使用已取消 Token 调用门面,断言:
```powershell
Assert-Equal 'Cancelled' $facadeResult.MapResult.Status.ToString() 'Facade must retain a cancelled map result.'
Assert-Equal 'Cancelled' $facadeResult.PlanningResult.Status.ToString() 'Facade must map map-stage cancellation to planning cancellation.'
Assert-Equal 0 $facadeResult.PlanningResult.Path.Count 'Cancelled facade planning must publish no path.'
Assert-Equal 0 $facadeResult.PlanningResult.Segments.Count 'Cancelled facade planning must publish no segments.'
```
对 `SearchTimeout = TimeSpan.Zero` 的有效 job,断言地图结果和规划结果分别为 `TimedOut`、`SearchTimeout`,且调试 sink 不会把已停止操作改写为成功。
- [ ] **Step 2: 运行集成脚本,确认当前门面把地图阶段停止误报为 `InvalidMap` 或继续建图。**
Run:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1
```
Expected: 地图状态属性或正确的 `Cancelled`/`SearchTimeout` 映射不存在。
- [ ] **Step 3: 让门面创建并传递唯一预算,随后更新 README。**
门面从有效 `job.Configuration.SearchTimeout` 创建 `PlanningOperationBudget`;配置为空或时间值非法时使用无超时预算,让既有 Planner 预检继续返回原有无效配置状态。依次调用:
```csharp
PlanningMapBuildResult mapResult = _mapFactory.Create(job == null ? null : job.MapRequest, budget);
if (!mapResult.Succeeded)
{
PlanningStatus status = mapResult.Status == PlanningMapBuildStatus.Cancelled
? PlanningStatus.Cancelled
: mapResult.Status == PlanningMapBuildStatus.TimedOut
? PlanningStatus.SearchTimeout
: PlanningStatus.InvalidMap;
return PublishDebug(job, mapResult, PlanningResult.Failure(status, diagnostics));
}
PlanningResult planningResult = _planner.Plan(request, budget);
```
`Map/README.md` 在 `PlanningMapBuildResult` 的说明处增加 `Status``Success`、`Failed`、`Cancelled`、`TimedOut`;后两种不提供地图也不会进入缓存。`CoarsePath/README.md` 增加“总预算与取消”小节:`SearchTimeout` 是从门面开始的总预算,覆盖建图、距离场、Dijkstra 与 Hybrid A*`Cancelled`/`SearchTimeout` 一律无路径;不应以 `InvalidMap` 重试用户主动取消。
- [ ] **Step 4: 重跑集成脚本,确认门面状态映射、缓存复用和 debug 旁路隔离均通过。**
Run: 与 Step 2 相同。
Expected: `Coarse path integration checks passed.` 和 `Coarse path facade checks passed.`。
### Task 5: 全量回归与验收记录
**Files:**
- Modify only if a command reveals a concrete regression: the exact responsible source or test file from Tasks 14.
- [ ] **Step 1: 执行 Debug 构建。**
Run:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
```
Expected: `0 个警告`、`0 个错误`。
- [ ] **Step 2: 执行全部现行 P0 地图与粗规划回归。**
Run:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_factory.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_adapter.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_image.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_documentation.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_test_config.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_collision.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_search.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1
```
Expected: 每个脚本退出码为 0 并输出 `passed`。
- [ ] **Step 3: 对照设计完成验收。**
逐项检查:预先取消和中途 Dijkstra 取消均返回 `Cancelled`;零总超时返回 `SearchTimeout`;地图停止不创建快照或缓存条目;正常输入的路径与缓存行为不变;诊断两项不再硬编码为零;README 已说明总预算语义。
## 自检
- 规格覆盖:Task 1 定义共享预算和显式地图状态;Task 2 覆盖地图、EDT、锁和缓存;Task 3 覆盖 Dijkstra、Hybrid A*、统计和 PlannerTask 4 覆盖门面映射与文档;Task 5 覆盖完整回归。
- 类型一致性:所有耗时组件仅接收 `PlanningOperationBudget` 并输出 `PlanningOperationStopReason`Map 使用 `PlanningMapBuildStatus`,粗规划使用既有 `PlanningStatus`。
- 范围:不触及 UI、Painter、场景工厂、Release 基准、运动模型或旧 TrapMap 脚本。
@@ -1,436 +0,0 @@
# 固定粗路径案例使用实时 AMR 位姿 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 让全部 `粗路径规划-*` 固定 MovementTest 从一次冻结的当前 AMR 世界坐标和航向开始规划,并把案例地图、目标和障碍平移到 AMR 附近。
**Architecture:** `CoarsePathScenarioFactory` 保留确定性的基准案例入口,并新增接受 AMR mm/deg 位姿的入口。一个仅属于固定场景工厂的平移对象将基准案例的起点映射为 AMR 起点,平移地图和障碍,并以起终点基准航向差计算新的终点航向。MovementTest 运行器在前台读取一次 `DetourInterface.getCartLocation()`,校验并冻结快照后才提交已有后台规划流程。
**Tech Stack:** C# / .NET Standard 2.0、Clumsy `MovementTest` 与 Painter、现有 `CoarsePathPlanningService`、PowerShell 反射验证脚本。
## Global Constraints
- 保留 `CoarsePathScenarioFactory.Create(CoarsePathTestScenario)` 的现有确定性行为,自动化离线测试继续使用它。
- 新实时入口的 X/Y 单位为世界 mm、航向单位为 deg;核心 `Pose2D` 仍为 m/rad。
- 固定场景仅做位置平移;TwoLeg 的 `DetectionHeadingRadians` 不得因 AMR 航向改变。
- 起点航向必须等于冻结 AMR 航向;终点航向必须保持基准案例的起终点航向差,并规范化到 `[-pi, pi]`
- 不改变 Hybrid A*、车辆参数、碰撞模型、地图缓存实现、手动 `粗路径规划` 的终点/障碍/超时输入流程,或任何底盘控制行为。
- 实时位姿为空、读取抛异常或 X/Y/航向不是有限数时,不得创建后台任务;必须向用户报告以 `AMR 位姿不可用` 开头的原因。
- 不暂存工作区中的无关改动;每次提交均明确列出文件路径。
## File Structure
- Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/CoarsePathScenarioFactory.cs`
- 固定场景的基准坐标定义、实时 AMR 工厂重载、平移与航向转换的唯一实现位置。
- Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/MovementTest.CoarsePathTest.cs`
- 固定场景的 AMR 快照读取、输入失败提示,以及冻结位姿的 Painter 状态显示。
- Modify: `ClumsyPilot/tests/verify_coarse_path_integration.ps1`
- 对新工厂重载、平移几何、航向规则、缓存和基准入口不回归的程序集级验证。
- Modify: `ClumsyPilot/tests/verify_coarse_path_ui.ps1`
- 对运行器只读一次实时位姿、传入实时工厂入口及 AMR 失败/状态文案的源级验证。
- Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/README.md`
- 说明六个固定案例的实时锚定规则、缓存命中条件和定位异常行为。
---
### Task 1: 为固定案例建立可验证的实时 AMR 平移工厂
**Files:**
- Modify: `ClumsyPilot/tests/verify_coarse_path_integration.ps1:303-399`
- Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/CoarsePathScenarioFactory.cs:115-284`
**Interfaces:**
- Consumes: `CoarsePathTestScenario``PlanningMapRequest``MapBoundsMm``ManualObstacleSource``TwoLegObstacleSource``TwoLegProjectionInput`
- Produces: `public static CoarsePathPlanningJob Create(CoarsePathTestScenario scenario, double amrXMillimeters, double amrYMillimeters, double amrHeadingDegrees)`
- Produces: 每个固定案例以 `FixedScenarioTransform` 统一转换的起点、终点、地图边界和来源快照;旧 `Create(scenario)` 仍创建原始基准几何。
- [ ] **Step 1: 在程序集级验证脚本中写出失败测试**
`$factoryCreate` 定义之后加入实时重载查找;在基准案例循环之后加入以下断言。测试锚点 `(12000, -3000, 90)` 对单矩形场景的偏移应为 `(+11000, -5000)` mm;多来源锚点 `(7000, 8000, 45)` 对该基准场景的偏移应为 `(+6000, +7000)` mm。
```powershell
$factoryCreateAtAmr = Find-Method $scenarioFactoryType 'Create' @(
$scenarioEnumType, [double], [double], [double])
Assert-True ($factoryCreateAtAmr -ne $null) 'Scenario factory must expose Create(scenario, amrX, amrY, amrHeading).'
foreach ($scenarioName in $scenarioNames) {
$scenario = [Enum]::Parse($scenarioEnumType, $scenarioName)
$liveJob = $factoryCreateAtAmr.Invoke($null, @($scenario, 12345.0, -6789.0, 135.0))
Assert-Near 12.345 $liveJob.Start.X "Live $scenarioName start X must equal the AMR X."
Assert-Near -6.789 $liveJob.Start.Y "Live $scenarioName start Y must equal the AMR Y."
Assert-Near (3.0 * [Math]::PI / 4.0) $liveJob.Start.Heading "Live $scenarioName heading must equal the AMR heading."
}
$rectangleScenario = [Enum]::Parse($scenarioEnumType, 'RectangleDetour')
$liveRectangle = $factoryCreateAtAmr.Invoke($null, @($rectangleScenario, 12000.0, -3000.0, 90.0))
Assert-Near 12.0 $liveRectangle.Start.X 'Live rectangle start X must equal the AMR X.'
Assert-Near -3.0 $liveRectangle.Start.Y 'Live rectangle start Y must equal the AMR Y.'
Assert-Near ([Math]::PI / 2.0) $liveRectangle.Start.Heading 'Live rectangle start heading must equal the AMR heading.'
Assert-Near 16.0 $liveRectangle.Goal.X 'Live rectangle goal X must preserve the four-metre relative offset.'
Assert-Near -3.0 $liveRectangle.Goal.Y 'Live rectangle goal Y must preserve the relative offset.'
Assert-Near ([Math]::PI / 2.0) $liveRectangle.Goal.Heading 'Live rectangle goal heading must preserve the zero baseline heading delta.'
Assert-Near 11000.0 $liveRectangle.MapRequest.Bounds.XMin 'Live rectangle map X minimum must translate with the AMR.'
Assert-Near -1000.0 $liveRectangle.MapRequest.Bounds.YMax 'Live rectangle map Y maximum must translate with the AMR.'
$rectangleProjection = $liveRectangle.MapRequest.ObstacleSources[0].ProjectToWorld()
$rectangleObstacle = $rectangleProjection.Obstacles[0]
Assert-Near 13700.0 $rectangleObstacle.XMin 'Live rectangle obstacle X minimum must translate with the AMR.'
Assert-Near -2200.0 $rectangleObstacle.YMax 'Live rectangle obstacle Y maximum must translate with the AMR.'
$multiScenario = [Enum]::Parse($scenarioEnumType, 'ManualAndTwoLeg')
$baselineMulti = $factoryCreate.Invoke($null, @($multiScenario))
$liveMulti = $factoryCreateAtAmr.Invoke($null, @($multiScenario, 7000.0, 8000.0, 45.0))
Assert-Near 7.0 $liveMulti.Start.X 'Live multi-source start X must equal AMR X.'
Assert-Near 8.0 $liveMulti.Start.Y 'Live multi-source start Y must equal AMR Y.'
Assert-Near ([Math]::PI / 4.0) $liveMulti.Goal.Heading 'Live multi-source goal heading must follow AMR heading.'
$baselineTwoLeg = $baselineMulti.MapRequest.ObstacleSources[1].ProjectToWorld().Obstacles
$liveTwoLeg = $liveMulti.MapRequest.ObstacleSources[1].ProjectToWorld().Obstacles
Assert-Near ($baselineTwoLeg[0].CenterX + 6000.0) $liveTwoLeg[0].CenterX 'TwoLeg X must translate without rotation.'
Assert-Near ($baselineTwoLeg[0].CenterY + 7000.0) $liveTwoLeg[0].CenterY 'TwoLeg Y must translate without rotation.'
$noPathScenario = [Enum]::Parse($scenarioEnumType, 'NoFeasiblePath')
$liveNoPath = $factoryCreateAtAmr.Invoke($null, @($noPathScenario, 9000.0, -1000.0, -180.0))
Assert-Near 9000.0 $liveNoPath.Start.X 'Live infeasible scenario must use AMR X.'
Assert-Near -1.0 $liveNoPath.Start.Y 'Live infeasible scenario must use AMR Y.'
Assert-Near 8000.0 $liveNoPath.MapRequest.Bounds.XMin 'Live infeasible map must translate with its baseline start.'
$invalidLivePoseRejected = $false
try { $null = $factoryCreateAtAmr.Invoke($null, @($rectangleScenario, [double]::NaN, 0.0, 0.0)) }
catch [Reflection.TargetInvocationException] {
$invalidLivePoseRejected = $_.Exception.InnerException -is [ArgumentOutOfRangeException]
}
Assert-True $invalidLivePoseRejected 'Live factory must reject non-finite AMR coordinates.'
```
- [ ] **Step 2: 运行该测试,确认它因缺少实时工厂重载而失败**
Run:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1
```
Expected: build succeeds; the script fails at `Scenario factory must expose Create(scenario, amrX, amrY, amrHeading).`
- [ ] **Step 3: 以单一平移对象实现实时工厂重载**
在 `CoarsePathScenarioFactory` 中保留一参 `Create`,并用一个私有锚点和变换对象驱动同一个 switch。使用以下接口形状;`CreateCore` 的每个 case 都先以该案例的基准起点创建 `FixedScenarioTransform`,再传给对应的场景构造函数。
```csharp
public static CoarsePathPlanningJob Create(CoarsePathTestScenario scenario)
{
return CreateCore(scenario, null);
}
public static CoarsePathPlanningJob Create(CoarsePathTestScenario scenario,
double amrXMillimeters, double amrYMillimeters, double amrHeadingDegrees)
{
return CreateCore(scenario,
new FixedScenarioAnchor(amrXMillimeters, amrYMillimeters, amrHeadingDegrees));
}
private static CoarsePathPlanningJob CreateCore(CoarsePathTestScenario scenario,
FixedScenarioAnchor anchor)
{
switch (scenario)
{
case CoarsePathTestScenario.ExplicitEmpty:
return CreateExplicitEmpty(FixedScenarioTransform.From(1000d, 2000d, 0d, anchor));
case CoarsePathTestScenario.RectangleDetour:
case CoarsePathTestScenario.CacheHit:
return CreateRectangleDetour(FixedScenarioTransform.From(1000d, 2000d, 0d, anchor));
case CoarsePathTestScenario.ManualAndTwoLeg:
return CreateManualAndTwoLeg(FixedScenarioTransform.From(1000d, 1000d, 0d, anchor));
case CoarsePathTestScenario.ReverseGearSwitch:
return CreateReverseGearSwitch(FixedScenarioTransform.From(1000d, 2000d, 0d, anchor));
case CoarsePathTestScenario.NoFeasiblePath:
return CreateNoFeasiblePath(FixedScenarioTransform.From(1000d, 2000d, 0d, anchor));
default:
throw new ArgumentOutOfRangeException(nameof(scenario));
}
}
```
Implement the two private classes in the same file. They must validate all public live inputs with existing `EnsureFinite`, keep the identity transform when `anchor` is null, convert fixed mm to metres only when creating `Pose2D`, and normalize headings without changing a position.
```csharp
private sealed class FixedScenarioAnchor
{
public FixedScenarioAnchor(double xMillimeters, double yMillimeters, double headingDegrees)
{
EnsureFinite(xMillimeters, nameof(xMillimeters));
EnsureFinite(yMillimeters, nameof(yMillimeters));
EnsureFinite(headingDegrees, nameof(headingDegrees));
XMillimeters = xMillimeters;
YMillimeters = yMillimeters;
HeadingRadians = NormalizeRadians((headingDegrees % 360d) * DegreesToRadians);
}
public double XMillimeters { get; }
public double YMillimeters { get; }
public double HeadingRadians { get; }
}
private sealed class FixedScenarioTransform
{
private FixedScenarioTransform(double deltaXMillimeters, double deltaYMillimeters, double headingDeltaRadians)
{
DeltaXMillimeters = deltaXMillimeters;
DeltaYMillimeters = deltaYMillimeters;
HeadingDeltaRadians = headingDeltaRadians;
}
public double DeltaXMillimeters { get; }
public double DeltaYMillimeters { get; }
public double HeadingDeltaRadians { get; }
public static FixedScenarioTransform From(double baselineStartXMillimeters,
double baselineStartYMillimeters, double baselineStartHeadingRadians, FixedScenarioAnchor anchor)
{
if (anchor == null) return new FixedScenarioTransform(0d, 0d, 0d);
return new FixedScenarioTransform(anchor.XMillimeters - baselineStartXMillimeters,
anchor.YMillimeters - baselineStartYMillimeters,
NormalizeRadians(anchor.HeadingRadians - baselineStartHeadingRadians));
}
public float X(float value) { return ToFiniteFloat(value + DeltaXMillimeters, nameof(value)); }
public float Y(float value) { return ToFiniteFloat(value + DeltaYMillimeters, nameof(value)); }
public Pose2D Pose(double xMeters, double yMeters, double headingRadians)
{
return new Pose2D((xMeters * MillimetersPerMeter + DeltaXMillimeters) / MillimetersPerMeter,
(yMeters * MillimetersPerMeter + DeltaYMillimeters) / MillimetersPerMeter,
NormalizeRadians(headingRadians + HeadingDeltaRadians));
}
}
private static double NormalizeRadians(double angle)
{
double normalized = angle % (2d * Math.PI);
if (normalized <= -Math.PI) return normalized + 2d * Math.PI;
return normalized > Math.PI ? normalized - 2d * Math.PI : normalized;
}
```
Update every fixed builder to accept `FixedScenarioTransform transform` and use `transform.Pose`, `transform.X` and `transform.Y` for all coordinates:
```csharp
new AxisAlignedRectangleObstacle(transform.X(2700f), transform.X(3300f),
transform.Y(1200f), transform.Y(2800f));
new CircleObstacle(transform.X(2400f), transform.Y(1300f), 220f);
new TwoLegProjectionInput(true, transform.X(3900f), transform.Y(2500f), 0d,
-180f, -180f, -180f, 180f, 140f, "P1 fixed TwoLeg snapshot.");
```
Change `CreateMap` to accept the transform and translate all four `MapBoundsMm` limits with the correct axis. It must not change resolution, source IDs, source versions, required flags, empty-map flags, vehicle parameters, direction constraints or existing timeout assignments.
- [ ] **Step 4: 运行工厂和回归验证,确认新旧入口都通过**
Run:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1
```
Expected: build has zero errors; script ends with `Coarse path P1 scenario checks passed.` Existing assertions for the one-argument factory must continue to pass.
- [ ] **Step 5: 提交仅包含工厂与程序集级测试的可审查变更**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/CoarsePathScenarioFactory.cs ClumsyPilot/tests/verify_coarse_path_integration.ps1
git commit -m "feat: anchor coarse path scenarios to AMR pose"
```
### Task 2: 让固定 MovementTest 冻结 AMR 快照并显示诊断
**Files:**
- Modify: `ClumsyPilot/tests/verify_coarse_path_ui.ps1:15-52`
- Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/MovementTest.CoarsePathTest.cs:221-371,512-540`
**Interfaces:**
- Consumes: 新的 `CoarsePathScenarioFactory.Create(scenario, xMm, yMm, headingDeg)`、`DetourInterface.getCartLocation()` 和现有 `Run(string, CoarsePathPlanningJob)`。
- Produces: `RunScenario` 只读取一次位姿,创建已冻结的实时 job;手动入口继续调用现有两参 `Run`。
- Produces: 私有 `AmrPoseSnapshot`,仅保存有限 X/Y/deg 和格式化后的状态文本,不向规划后台读取定位。
- [ ] **Step 1: 在 UI 源级验证中加入失败断言**
在现有 `$source` 断言后追加以下代码。使用 ASCII 的方法名和 `AMR` 文本,避免 Windows PowerShell 5 对中文脚本字符串的编码歧义。
```powershell
$runnerStart = $source.IndexOf('internal static class CoarsePathPlanningTestRunner')
Assert-True ($runnerStart -ge 0) 'Shared runner source must exist.'
$runnerSource = $source.Substring($runnerStart)
Assert-Match $runnerSource 'RunScenario[\s\S]*getCartLocation\s*\(' 'Fixed scenarios must read the current AMR pose.'
Assert-Match $runnerSource 'CoarsePathScenarioFactory\.Create\s*\(\s*scenario\s*,' 'Fixed scenarios must use the AMR-aware factory overload.'
Assert-Match $runnerSource 'AMR' 'The runner must expose AMR pose diagnostics.'
Assert-Match $runnerSource 'ArgumentException\("AMR' 'Invalid AMR input must be reported without starting planning.'
Assert-Match $runnerSource 'double\.IsNaN|double\.IsInfinity' 'The runner must reject non-finite AMR coordinates.'
Assert-Match $runnerSource 'DrawStatus\s*\([\s\S]*AmrPoseSnapshot' 'Result status must receive the frozen AMR snapshot.'
```
- [ ] **Step 2: 运行 UI 脚本,确认新增实时位姿约束尚未满足**
Run:
```powershell
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_ui.ps1
```
Expected: script fails at `Fixed scenarios must read the current AMR pose.` or `Fixed scenarios must use the AMR-aware factory overload.`
- [ ] **Step 3: 实现单次 AMR 读取、校验、冻结与绘制传递**
在 `CoarsePathPlanningTestRunner` 内加入以下私有快照类型和工厂调用路径。任何读取或校验异常都在前台转换为 `AMR 位姿不可用:...`,因此不会进入 `Task.Run`。
```csharp
private sealed class AmrPoseSnapshot
{
public AmrPoseSnapshot(double xMillimeters, double yMillimeters, double headingDegrees)
{
EnsureFiniteAmrValue(xMillimeters, "X");
EnsureFiniteAmrValue(yMillimeters, "Y");
EnsureFiniteAmrValue(headingDegrees, "航向");
XMillimeters = xMillimeters;
YMillimeters = yMillimeters;
HeadingDegrees = headingDegrees;
}
public double XMillimeters { get; }
public double YMillimeters { get; }
public double HeadingDegrees { get; }
public string DisplayText
{
get
{
return "AMR 起点:X=" + XMillimeters.ToString("F0", CultureInfo.InvariantCulture) +
" mmY=" + YMillimeters.ToString("F0", CultureInfo.InvariantCulture) +
" mm,航向=" + HeadingDegrees.ToString("F1", CultureInfo.InvariantCulture) + " deg";
}
}
}
private static void EnsureFiniteAmrValue(double value, string name)
{
if (double.IsNaN(value) || double.IsInfinity(value))
throw new ArgumentException(name + " 必须是有限数。");
}
internal static void RunScenario(CoarsePathTestScenario scenario, string scenarioName)
{
try
{
var pose = DetourInterface.getCartLocation();
var snapshot = new AmrPoseSnapshot(pose.x, pose.y, pose.th);
CoarsePathPlanningJob job = CoarsePathScenarioFactory.Create(scenario,
snapshot.XMillimeters, snapshot.YMillimeters, snapshot.HeadingDegrees);
Run(scenarioName, job, snapshot);
}
catch (Exception exception)
{
ShowInputFailure(new ArgumentException("AMR 位姿不可用:" + exception.Message, exception));
}
}
```
Keep the existing `internal static void Run(string scenarioName, CoarsePathPlanningJob job)` as a thin compatibility overload that calls a new private `Run(string, CoarsePathPlanningJob, AmrPoseSnapshot)`. Thread the snapshot through `DrawPending`, the `ContinueWith` lambda, `Finish`, `DrawResult`, and `DrawStatus`. In `DrawStatus`, render `snapshot.DisplayText` after the scene line only when the snapshot is non-null; increase subsequent Y offsets consistently so vehicle and termination-reason text do not overlap. Manual `CoarsePathPlanningTest.Test()` must keep its present `Run(..., job)` call and user-entered timeout behavior.
- [ ] **Step 4: 编译并运行 UI 验证,确认后台边界未回归**
Run:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_ui.ps1
```
Expected: build has zero errors; script ends with `Coarse path UI source checks passed.` Existing assertions still confirm one shared service, background `Task.Run`, cancellation and no blocking `Task.Result`/`Wait`.
- [ ] **Step 5: 提交仅包含运行器与 UI 验证的可审查变更**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/MovementTest.CoarsePathTest.cs ClumsyPilot/tests/verify_coarse_path_ui.ps1
git commit -m "feat: use live AMR pose in coarse path tests"
```
### Task 3: 记录实时固定场景语义并执行完整回归
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/README.md:289-347`
- Modify: `ClumsyPilot/tests/verify_coarse_path_ui.ps1:54-100`
**Interfaces:**
- Consumes: 已实现的实时工厂入口和 MovementTest 状态文本。
- Produces: 可供现场使用者理解的固定案例锚定、缓存和定位异常说明;文档验证继续只检查稳定 ASCII 标识符。
- [ ] **Step 1: 在 README 源级验证中加入失败断言**
在 `$readmeStructure` 数组中加入以下 ASCII 项,并在数组后加入 `Contains` 断言:
```powershell
foreach ($requiredLiveAmrText in @(
'Create(CoarsePathTestScenario scenario, double amrXMillimeters',
'AMR',
'Input',
'TwoLeg')) {
Assert-True ($readme.Contains($requiredLiveAmrText)) "P1 README must document live fixed scenarios: $requiredLiveAmrText."
}
```
- [ ] **Step 2: 运行 UI 文档验证,确认文档尚未描述实时固定案例**
Run:
```powershell
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_ui.ps1
```
Expected: script fails because README does not yet contain the new `Create(CoarsePathTestScenario scenario, double amrXMillimeters` signature.
- [ ] **Step 3: 更新 P1 手动测试与可视化章节**
在固定案例表之前新增“固定案例的实时 AMR 锚点”小节,逐条说明:六个 `粗路径规划-*` 入口读取一次 `getCartLocation`;起点等于这份冻结的世界 mm/deg 位姿;地图边界、目标、圆形/轴对齐矩形和 TwoLeg 检测原点统一平移;终点航向保持和基准起点的航向差;TwoLeg 朝向不旋转;手动 `粗路径规划` 的输入流程不变。
在同一小节以单行 inline code 明确列出公开签名:`Create(CoarsePathTestScenario scenario, double amrXMillimeters, double amrYMillimeters, double amrHeadingDegrees)`。
加入公开签名示例:
```csharp
CoarsePathPlanningJob job = CoarsePathScenarioFactory.Create(
CoarsePathTestScenario.RectangleDetour,
amrXMillimeters, amrYMillimeters, amrHeadingDegrees);
```
将缓存命中表项改为“相同 AMR X/Y 下的重复完整地图输入”;明确 AMR 位置已移动时未命中是正常的 `None`/非 `Input` 缓存结果,且仅航向变化不改变地图输入、仍可命中。补充运行器在定位读取、空值访问或有限数校验失败时显示 `AMR 位姿不可用` 且不启动后台规划;状态区会显示该次冻结的 AMR 起点。保留关于 `getCartLocation` 可能阻塞和必须在定位准备完成后运行的既有警告。
- [ ] **Step 4: 运行完整粗路径与地图回归集**
Run:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_adapter.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_factory.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_documentation.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_image.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_test_config.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_collision.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_search.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_ui.ps1
```
Expected: build reports zero warnings and zero errors. Every verification script exits 0; integration ends with `Coarse path P1 scenario checks passed.` and UI ends with `Coarse path UI source checks passed.`
- [ ] **Step 5: 提交文档与最后验证脚本变更**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/CoarsePath/README.md ClumsyPilot/tests/verify_coarse_path_ui.ps1
git commit -m "docs: explain live AMR coarse path scenarios"
```
@@ -1,256 +0,0 @@
# Coarse Path Search Elapsed Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use `executing-plans` to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 在保留总耗时和总超时语义的前提下,记录并显示地图就绪后生成最终粗路径的独立耗时。
**Architecture:** `PlanningDiagnostics` 增加兼容的 `PathSearchElapsed` 只读字段。`HybridAStarPlanner` 在调用 `HybridAStarSearch.Search` 前启动本地秒表,并把搜索、回溯、装配和最终复核的耗时传入诊断对象;门面总预算和 `Elapsed` 不改变。MovementTest 图层和 Toast 同时显示总耗时与路径搜索耗时。
**Tech Stack:** C# / .NET Standard 2.0、`System.Diagnostics.Stopwatch`、PowerShell 回归脚本、Clumsy `MovementTest` Painter。
## Global Constraints
- `PlanningDiagnostics.Elapsed` 继续表示从 `CoarsePathPlanningService.Plan` 入口开始的总耗时。
- `PathSearchElapsed` 不包括地图来源读取、缓存、栅格化和距离场构建。
- `PathSearchElapsed` 包含二维启发式、Hybrid A*、回溯、装配、方向分段和最终复核。
- 搜索开始前失败时 `PathSearchElapsed` 必须为 `TimeSpan.Zero`;搜索阶段失败时保留已消耗时间。
- 新构造函数参数必须放在现有参数之后并提供默认值,保持现有位置参数调用的兼容性。
- 不修改 `PlanningOperationBudget`、取消机制、超时预算或地图缓存行为。
- 不执行 Git 操作。
## 文件结构
- Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Contracts/PlanningDiagnostics.cs` — 公开独立路径搜索耗时。
- Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/HybridAStarPlanner.cs` — 在地图就绪后的路径产出阶段计时,并写入诊断对象。
- Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/MovementTest.CoarsePathTest.cs` — 在状态图层和 Toast 显示两种耗时。
- Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/README.md` — 说明两个耗时的计时边界。
- Modify: `ClumsyPilot/tests/verify_coarse_path_integration.ps1` — 验证真实路径搜索耗时和地图阶段失败的零值。
- Modify: `ClumsyPilot/tests/verify_coarse_path_ui.ps1` — 验证 UI/README 使用新字段。
---
### Task 1: 路径搜索耗时诊断契约
**Files:**
- Modify: `ClumsyPilot/tests/verify_coarse_path_integration.ps1:100-108,199-204`
- Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Contracts/PlanningDiagnostics.cs:10-60`
- Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/HybridAStarPlanner.cs:1-105,185-210`
**Interfaces:**
- Consumes: `PlanningResult.Diagnostics.Elapsed``HybridAStarPlanner.Plan(PlanningRequest, PlanningOperationBudget)`
- Produces: `PlanningDiagnostics.PathSearchElapsed : TimeSpan`
- Contract: 成功结果满足 `TimeSpan.Zero <= PathSearchElapsed <= Elapsed`;地图阶段取消的结果为 `TimeSpan.Zero`
- [ ] **Step 1: 在集成脚本写入失败断言**
在空地图成功规划的现有诊断断言之后加入:
```powershell
Assert-True ($result.Diagnostics.PathSearchElapsed -ge [TimeSpan]::Zero) `
'Planner diagnostics must retain a non-negative path-search elapsed time.'
Assert-True ($result.Diagnostics.PathSearchElapsed -le $result.Diagnostics.Elapsed) `
'Path-search elapsed time must not exceed total planning elapsed time.'
```
`$cancelledFacadeResult` 的现有断言之后加入:
```powershell
Assert-Equal ([TimeSpan]::Zero) $cancelledFacadeResult.PlanningResult.Diagnostics.PathSearchElapsed `
'Map-stage cancellation must not report path-search time.'
```
- [ ] **Step 2: 运行集成脚本并确认红灯**
Run: `powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1`
Expected: FAIL,提示 `PathSearchElapsed` 不存在或无法通过新增的路径搜索耗时断言;此前的地图和门面检查仍先通过。
- [ ] **Step 3: 以兼容形式扩展诊断对象**
`PlanningDiagnostics` 构造函数的最后一个参数之后增加:
```csharp
TimeSpan pathSearchElapsed = default(TimeSpan)
```
并在构造函数中加入:
```csharp
PathSearchElapsed = pathSearchElapsed;
```
`Elapsed` 属性之后加入:
```csharp
/// <summary>
/// 地图就绪后搜索、回溯、装配和最终复核得到最终粗路径的耗时;不含建图。搜索开始前失败时为零。
/// </summary>
public TimeSpan PathSearchElapsed { get; }
```
同时更新构造函数 XML 注释,明确 `elapsed` 是总耗时而 `pathSearchElapsed` 是不含建图的路径产出耗时。
- [ ] **Step 4: 在规划器的正确边界计时**
`HybridAStarPlanner.cs` 顶部加入:
```csharp
using System.Diagnostics;
```
`Plan(PlanningRequest request, PlanningOperationBudget budget)``try` 外部声明:
```csharp
Stopwatch pathSearchStopwatch = null;
```
在第二次 `budget.GetStopReason()` 通过、且紧接 `_search.Search(request, budget)` 前写入:
```csharp
pathSearchStopwatch = Stopwatch.StartNew();
HybridAStarSearchResult searchResult = _search.Search(request, budget);
```
`Failure` 签名扩展为:
```csharp
private static PlanningResult Failure(PlanningStatus status, PlanningOperationBudget budget, string reason,
HybridAStarSearchResult searchResult, Stopwatch pathSearchStopwatch = null)
```
并在内部取得:
```csharp
TimeSpan pathSearchElapsed = pathSearchStopwatch == null ? TimeSpan.Zero : pathSearchStopwatch.Elapsed;
return PlanningResult.Failure(status, CreateDiagnostics(searchResult, budget.Elapsed, 0d, 0d, reason,
pathSearchElapsed));
```
`_search.Search` 之后的每个失败返回和 `catch` 都传入 `pathSearchStopwatch`。成功结果调用改为:
```csharp
return PlanningResult.Success(path, segments, CreateDiagnostics(searchResult, budget.Elapsed, pathLengthMeters,
minimumClearanceMeters, string.Empty, pathSearchStopwatch.Elapsed));
```
最后将 `CreateDiagnostics` 扩展为接收最后一个 `TimeSpan pathSearchElapsed` 参数,并将其作为 `PlanningDiagnostics` 的最后一个实参传入。
- [ ] **Step 5: 运行集成脚本并确认绿灯**
Run: `dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore`
Expected: exit code 0;只允许项目既有的过时 API 警告。
Run: `powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1`
Expected: `Coarse path integration checks passed.``Coarse path facade checks passed.``Coarse path P1 scenario checks passed.`
### Task 2: 图层、Toast 与 README 展示
**Files:**
- Modify: `ClumsyPilot/tests/verify_coarse_path_ui.ps1:15-70`
- Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/MovementTest.CoarsePathTest.cs:494-513`
- Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/README.md:285-337`
**Interfaces:**
- Consumes: `result.PlanningResult.Diagnostics.Elapsed` 和 Task 1 提供的 `PathSearchElapsed`
- Produces: 状态图层与 Toast 中的“总耗时”和“路径搜索”文本,以及对应 README 说明。
- Contract: 展示值均使用毫秒、`InvariantCulture``F0` 格式;不改变后台任务、取消或 Painter 图层名称。
- [ ] **Step 1: 为 UI 和 README 写入失败检查**
在 UI 源码断言区域加入:
```powershell
Assert-True (([regex]::Matches($source, 'PathSearchElapsed')).Count -ge 2) `
'The status layer and Toast must both show path-search elapsed time.'
Assert-Match $source '路径搜索' 'The UI must label the independent path-search elapsed time.'
```
`$readmeStructure` 数组加入:
```powershell
'PathSearchElapsed',
'路径搜索耗时',
```
- [ ] **Step 2: 运行 UI 脚本并确认红灯**
Run: `powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_ui.ps1`
Expected: FAIL,提示状态图层与 Toast 尚未显示 `PathSearchElapsed`,或 README 尚未说明该字段。
- [ ] **Step 3: 同时更新状态图层和 Toast**
`DrawStatus` 中将单一耗时文本替换为:
```csharp
Painter.DrawText(Color.White, "栅格:" + resolution + ",规划:" + result.PlanningResult.Status + ",总耗时:" +
result.PlanningResult.Diagnostics.Elapsed.TotalMilliseconds.ToString("F0", CultureInfo.InvariantCulture) +
" ms,路径搜索:" + result.PlanningResult.Diagnostics.PathSearchElapsed.TotalMilliseconds.ToString("F0", CultureInfo.InvariantCulture) +
" ms", x, y + 240f);
```
`BuildToastMessage` 中将返回字符串替换为:
```csharp
return "粗路径[" + scenarioName + "]:地图=" + result.MapResult.Status + ",规划=" +
result.PlanningResult.Status + ",总耗时=" +
result.PlanningResult.Diagnostics.Elapsed.TotalMilliseconds.ToString("F0", CultureInfo.InvariantCulture) +
"ms,路径搜索=" + result.PlanningResult.Diagnostics.PathSearchElapsed.TotalMilliseconds.ToString("F0", CultureInfo.InvariantCulture) +
"ms。";
```
- [ ] **Step 4: 补充 README 的耗时定义**
在 “P1 手动测试与可视化” 的 “后台执行、停止与图层” 小节,在当前 `PlanningGridMap` 说明之后插入:
```markdown
状态图层和 Toast 同时显示总耗时与 `PathSearchElapsed`(路径搜索耗时)。总耗时从
`CoarsePathPlanningService.Plan` 入口开始,包含地图创建;路径搜索耗时从地图和起终点
预检通过、即将进入 Hybrid A* 时开始,包含二维启发式、Hybrid A*、回溯、装配与最终复核,
不包含建图。搜索开始前即失败时该值为 0 ms。
```
- [ ] **Step 5: 运行 UI 检查并确认绿灯**
Run: `powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_ui.ps1`
Expected: `Coarse path P1 UI source checks passed.`
### Task 3: 最终回归与验收
**Files:**
- Verify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Contracts/PlanningDiagnostics.cs`
- Verify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/HybridAStarPlanner.cs`
- Verify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/MovementTest.CoarsePathTest.cs`
- Verify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/README.md`
**Interfaces:**
- Consumes: 任务 1 和任务 2 的已完成代码与脚本。
- Produces: 已验证的构建、集成回归和 UI/README 检查结果。
- [ ] **Step 1: 重新阅读计时边界**
确认 `PathSearchElapsed` 只在 `_search.Search` 前启动;所有搜索后成功和失败路径均使用同一秒表;任何搜索前返回保持零值;总预算仍由 `PlanningOperationBudget` 控制。
- [ ] **Step 2: 运行最终构建**
Run: `dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore`
Expected: exit code 0。
- [ ] **Step 3: 运行最终自动化验证**
Run: `powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_ui.ps1`
Expected: `Coarse path P1 UI source checks passed.`
Run: `powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1`
Expected: `Coarse path integration checks passed.``Coarse path facade checks passed.``Coarse path P1 scenario checks passed.`
- [ ] **Step 4: 进行手动界面验收**
在可用 Clumsy 界面运行“粗路径规划-显式空图”或“粗路径规划-单矩形绕行”,确认状态图层和 Toast 都包含 `总耗时``路径搜索` 两个毫秒值,且点击停止仍能取消当前任务。
@@ -1,837 +0,0 @@
# Coarse Path Manual Test Diagnostics 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:**`[MovementTest(name = "粗路径规划")]` 支持每次输入正数秒总预算,并在失败时显示真实终止原因、搜索统计、总耗时和路径搜索耗时。
**Architecture:** `HybridAStarSearchResult` 在搜索边界保留原始终止原因,`HybridAStarPlanner` 负责把原因、资源限制和节点统计装配成公开诊断。`PlanningDiagnostics` 增加兼容的 `PathSearchElapsed`MovementTest 只覆盖本次任务的超时配置并在图层和 Toast 中显示诊断;搜索、安全和路径发布规则保持不变。
**Tech Stack:** C# / .NET Standard 2.0、`System.Diagnostics.Stopwatch`、PowerShell 反射回归脚本、Clumsy `MovementTest`/Painter。
## Global Constraints
- 普通规划失败继续通过 `PlanningResult` 返回,不把超时、无解、碰撞或资源上限改成异常。
- `PlanningDiagnostics.Elapsed` 继续表示从 `CoarsePathPlanningService.Plan` 开始、包含建图的总耗时。
- `PlanningDiagnostics.PathSearchElapsed` 从搜索前预检通过后开始,包含二维启发式、Hybrid A*、回溯、装配和最终复核,不包含建图。
- 搜索前失败的 `PathSearchElapsed``TimeSpan.Zero`;搜索后的所有出口满足 `TimeSpan.Zero <= PathSearchElapsed <= Elapsed`
- 手动超时只接受 `TimeSpan` 可表示范围内的有限正数秒;`0` 不表示不限时。
- 不修改碰撞步长、终点容差、Open List 排序、地图边界策略、倒车开关或成功路径发布条件。
- 不实现 Reeds-Shepp/Dubins 精确连接、横移、蟹行、原地旋转、平滑、速度规划或控制。
- 手动入口继续使用长 `0.80 m`、宽 `0.60 m`、安全余量 `0.05 m`、最小转弯半径 `1.20 m` 的固定演示车辆参数。
- 工作区已有未提交内容;每次提交只能暂存任务中明确列出的文件,不得暂存其他路径。
## File Structure
- Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Search/HybridAStarSearch.cs` — 搜索结果保留发生位置一致的原始终止原因。
- Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Contracts/PlanningDiagnostics.cs` — 公开地图就绪后的独立路径搜索耗时。
- Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/HybridAStarPlanner.cs` — 计时搜索/发布阶段并组合原因、资源上限和节点统计。
- Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/MovementTest.CoarsePathTest.cs` — 读取单次超时并在图层/Toast 显示诊断。
- Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/README.md` — 记录手动预算、演示车辆、两类耗时和第一版能力边界。
- Modify: `ClumsyPilot/tests/verify_coarse_path_search.ps1` — 验证搜索原始原因和内部异常不再静默。
- Modify: `ClumsyPilot/tests/verify_coarse_path_integration.ps1` — 验证规划器诊断、路径搜索耗时和慢可行场景。
- Modify: `ClumsyPilot/tests/verify_coarse_path_ui.ps1` — 验证手动超时、图层/Toast 和 README 文本。
---
### Task 1: Preserve Search Termination Reasons
**Files:**
- Modify: `ClumsyPilot/tests/verify_coarse_path_search.ps1:302-391`
- Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Search/HybridAStarSearch.cs:15-318`
**Interfaces:**
- Consumes: `HybridAStarSearch.Search(PlanningRequest, CancellationToken)` 和内部 `Search(PlanningRequest, PlanningOperationBudget)`
- Produces: `HybridAStarSearchResult.TerminationReason : string`,成功时为空,所有失败状态非空。
- Produces: `CreateResult(..., int? successNodeIndex, string terminationReason = null)`,未显式提供原因时按状态生成稳定中文原因。
- [ ] **Step 1: Add failing search-result reason assertions**
在搜索结果属性断言后加入:
```powershell
Assert-True ($searchResultType.GetProperty('TerminationReason') -ne $null) `
'Search result must expose its original termination reason.'
```
在取消、节点上限和超时状态断言后分别加入:
```powershell
Assert-True (-not [string]::IsNullOrWhiteSpace($cancelledResult.TerminationReason)) `
'Cancelled search must retain a non-empty reason.'
Assert-True (-not [string]::IsNullOrWhiteSpace($limitedResult.TerminationReason)) `
'Node-limited search must retain a non-empty reason.'
Assert-True (-not [string]::IsNullOrWhiteSpace($timedOutResult.TerminationReason)) `
'Timed-out search must retain a non-empty reason.'
Assert-False ($cancelledResult.TerminationReason -eq $limitedResult.TerminationReason) `
'Cancelled and node-limited searches must retain different reasons.'
Assert-False ($limitedResult.TerminationReason -eq $timedOutResult.TerminationReason) `
'Node-limited and timed-out searches must retain different reasons.'
```
在脚本末尾通过内部预算重载制造一个可重复的未预期错误,并断言异常类型没有被吞掉:
```powershell
$internalSearchMethod = $searchType.GetMethods([Reflection.BindingFlags]'Instance,NonPublic') |
Where-Object {
$_.Name -eq 'Search' -and
$_.GetParameters().Length -eq 2 -and
$_.GetParameters()[1].ParameterType -eq $operationBudgetType
} |
Select-Object -First 1
Assert-True ($internalSearchMethod -ne $null) `
'Search must retain its internal shared-budget overload.'
$internalErrorResult = $internalSearchMethod.Invoke($searcher, @($searchRequest, $null))
Assert-Equal 'InternalError' $internalErrorResult.Status.ToString() `
'A missing internal budget must be mapped to InternalError.'
Assert-True ($internalErrorResult.TerminationReason.Contains('ArgumentNullException')) `
'Internal search errors must retain the exception type.'
```
- [ ] **Step 2: Run the search script and verify RED**
Run:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_search.ps1
```
Expected: build succeeds; the script fails first with `Search result must expose its original termination reason.`
- [ ] **Step 3: Extend `HybridAStarSearchResult`**
Add the final constructor parameter, assignment, and public property:
```csharp
internal HybridAStarSearchResult(
PlanningStatus status,
IEnumerable<HybridAStarNode> nodes,
int expandedNodeCount,
int generatedNodeCount,
int reopenedNodeCount,
int staleOpenListEntryCount,
int peakOpenListCount,
int? successNodeIndex,
string terminationReason)
{
Status = status;
Nodes = new ReadOnlyCollection<HybridAStarNode>(
new List<HybridAStarNode>(nodes ?? Array.Empty<HybridAStarNode>()));
ExpandedNodeCount = expandedNodeCount;
GeneratedNodeCount = generatedNodeCount;
ReopenedNodeCount = reopenedNodeCount;
StaleOpenListEntryCount = staleOpenListEntryCount;
PeakOpenListCount = peakOpenListCount;
SuccessNodeIndex = status == PlanningStatus.Success ? successNodeIndex : null;
TerminationReason = status == PlanningStatus.Success
? string.Empty
: terminationReason ?? string.Empty;
}
/// <summary>搜索边界记录的原始终止原因;成功时为空字符串,失败时非空。</summary>
public string TerminationReason { get; }
```
- [ ] **Step 4: Centralize default reasons and retain exception details**
Replace `CreateResult` with:
```csharp
private static HybridAStarSearchResult CreateResult(
PlanningStatus status,
IEnumerable<HybridAStarNode> nodes,
int expandedNodeCount,
int generatedNodeCount,
int reopenedNodeCount,
int staleOpenListEntryCount,
int peakOpenListCount,
int? successNodeIndex,
string terminationReason = null)
{
string reason = status == PlanningStatus.Success
? string.Empty
: terminationReason ?? GetDefaultTerminationReason(status);
return new HybridAStarSearchResult(status, nodes, expandedNodeCount, generatedNodeCount, reopenedNodeCount,
staleOpenListEntryCount, peakOpenListCount, successNodeIndex, reason);
}
private static string GetDefaultTerminationReason(PlanningStatus status)
{
switch (status)
{
case PlanningStatus.Cancelled:
return "Hybrid A* 搜索已取消。";
case PlanningStatus.InvalidRequest:
return "Hybrid A* 搜索请求缺少必要对象或包含非法数值。";
case PlanningStatus.InvalidMap:
return "Hybrid A* 搜索地图结构无效。";
case PlanningStatus.MapNotReady:
return "Hybrid A* 搜索地图尚未准备好。";
case PlanningStatus.InvalidVehicleParameters:
return "Hybrid A* 搜索车辆参数无效。";
case PlanningStatus.InvalidCurvatureConfiguration:
return "Hybrid A* 搜索曲率、离散、代价或资源配置无效。";
case PlanningStatus.StartOutsideMap:
return "Hybrid A* 搜索起始扩大车体不完全位于地图内。";
case PlanningStatus.StartInCollision:
return "Hybrid A* 搜索起始扩大车体与障碍物相交或擦边。";
case PlanningStatus.GoalOutsideMap:
return "Hybrid A* 搜索目标扩大车体不完全位于地图内。";
case PlanningStatus.GoalInCollision:
return "Hybrid A* 搜索目标扩大车体与障碍物相交或擦边。";
case PlanningStatus.SearchTimeout:
return "Hybrid A* 搜索使用的总规划预算已耗尽。";
case PlanningStatus.SearchNodeLimitExceeded:
return "Hybrid A* 搜索达到扩展节点上限。";
case PlanningStatus.NoFeasiblePath:
return "Hybrid A* 搜索的 Open List 已耗尽,未找到满足运动和碰撞约束的路径。";
case PlanningStatus.BacktrackingFailed:
return "Hybrid A* 成功节点无法回溯为完整父链。";
case PlanningStatus.FinalValidationFailed:
return "Hybrid A* 路径未通过最终复核。";
case PlanningStatus.InternalError:
return "Hybrid A* 搜索发生未预期内部错误。";
default:
return "Hybrid A* 搜索以未识别状态终止:" + status + "。";
}
}
```
Change the two `NoFeasiblePath` exits so the caller can distinguish their location:
```csharp
if (openList.Count == 0)
return CreateResult(PlanningStatus.NoFeasiblePath, nodes, expandedNodeCount, generatedNodeCount,
reopenedNodeCount, staleOpenListEntryCount, peakOpenListCount, null,
"二维启发式标记起点不可达目标,或起始方向无法进入 Open List。");
```
```csharp
return CreateResult(PlanningStatus.NoFeasiblePath, nodes, expandedNodeCount, generatedNodeCount,
reopenedNodeCount, staleOpenListEntryCount, peakOpenListCount, null,
"Hybrid A* 搜索的 Open List 已耗尽,未找到满足运动和碰撞约束的路径。");
```
Replace the catch block with:
```csharp
catch (Exception exception)
{
string reason = "Hybrid A* 搜索内部错误:" + exception.GetType().Name +
(string.IsNullOrEmpty(exception.Message) ? "。" : "。" + exception.Message);
return CreateResult(PlanningStatus.InternalError, nodes, expandedNodeCount, generatedNodeCount,
reopenedNodeCount, staleOpenListEntryCount, peakOpenListCount, null, reason);
}
```
- [ ] **Step 5: Run the search script and verify GREEN**
Run:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_search.ps1
```
Expected:
```text
Coarse path search primitive checks passed.
Coarse path Hybrid A star search checks passed.
```
- [ ] **Step 6: Commit the search reason contract**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/CoarsePath/Search/HybridAStarSearch.cs ClumsyPilot/tests/verify_coarse_path_search.ps1
git commit -m "fix: retain coarse path search failure reasons"
```
### Task 2: Add Planner-Level Diagnostics and Path Search Timing
**Files:**
- Modify: `ClumsyPilot/tests/verify_coarse_path_integration.ps1:75-135,189-210,357-360`
- Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Contracts/PlanningDiagnostics.cs:6-60`
- Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/HybridAStarPlanner.cs:1-226`
**Interfaces:**
- Consumes: Task 1 `HybridAStarSearchResult.TerminationReason`.
- Produces: `PlanningDiagnostics.PathSearchElapsed : TimeSpan`.
- Produces: `BuildSearchFailureReason(HybridAStarSearchResult, HybridAStarConfiguration) : string`.
- Contract: planner success and search-stage failure satisfy `0 <= PathSearchElapsed <= Elapsed`; map/preflight failure remains zero.
- [ ] **Step 1: Add failing planner-diagnostic assertions**
After the existing successful planner diagnostic assertions add:
```powershell
Assert-True ($result.Diagnostics.GetType().GetProperty('PathSearchElapsed') -ne $null) `
'Planner diagnostics must expose path-search elapsed time.'
Assert-True ($result.Diagnostics.PathSearchElapsed -ge [TimeSpan]::Zero) `
'Successful planning must retain non-negative path-search time.'
Assert-True ($result.Diagnostics.PathSearchElapsed -le $result.Diagnostics.Elapsed) `
'Path-search time must not exceed total elapsed time.'
```
After the invalid-map result assertions add:
```powershell
Assert-Equal ([TimeSpan]::Zero) $mapFailureResult.PlanningResult.Diagnostics.PathSearchElapsed `
'Map failure must report zero path-search time.'
```
After creating a valid planner request, add a search-stage node-limit case:
```powershell
$nodeLimitedRequest = [Activator]::CreateInstance($requestType)
$nodeLimitedRequest.Map = $request.Map
$nodeLimitedRequest.Start = $request.Start
$nodeLimitedRequest.Goal = $request.Goal
$nodeLimitedRequest.Vehicle = $request.Vehicle
$nodeLimitedRequest.Configuration = [Activator]::CreateInstance($configurationType)
$nodeLimitedRequest.Configuration.MaximumExpandedNodes = 0
$nodeLimitedRequest.Configuration.SearchTimeout = [TimeSpan]::FromSeconds(2)
$nodeLimitedRequest.Configuration.GoalPositionToleranceMeters = 0.001
$nodeLimitedRequest.Configuration.GoalHeadingToleranceRadians = 0.001
$nodeLimitedRequest.Configuration.AllowReverse = $false
$nodeLimitedRequest.GoalDirection = [Enum]::Parse($goalDirectionType, 'Forward')
$nodeLimitedResult = $plan.Invoke($planner, @($nodeLimitedRequest, [Threading.CancellationToken]::None))
Assert-Equal 'SearchNodeLimitExceeded' $nodeLimitedResult.Status.ToString() `
'A zero node limit must fail after planner preflight.'
Assert-True ($nodeLimitedResult.Diagnostics.PathSearchElapsed -ge [TimeSpan]::Zero) `
'Search-stage node-limit failure must retain path-search time.'
Assert-True ($nodeLimitedResult.Diagnostics.PathSearchElapsed -le $nodeLimitedResult.Diagnostics.Elapsed) `
'Failed path-search time must not exceed total elapsed time.'
Assert-True (-not [string]::IsNullOrWhiteSpace($nodeLimitedResult.Diagnostics.TerminationReason)) `
'Planner diagnostics must retain a node-limit reason.'
```
Extend the no-path scenario assertions:
```powershell
Assert-True ($noPathResult.PlanningResult.Diagnostics.TerminationReason.Contains('Open List')) `
'No-path diagnostics must retain the exact search exhaustion reason.'
Assert-True ($noPathResult.PlanningResult.Diagnostics.TerminationReason.Contains('扩展=')) `
'No-path diagnostics must include search statistics.'
```
- [ ] **Step 2: Run integration checks and verify RED**
Run:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1
```
Expected: build succeeds; the script fails first with `Planner diagnostics must expose path-search elapsed time.`
- [ ] **Step 3: Extend `PlanningDiagnostics` compatibly**
Append the optional constructor parameter:
```csharp
TimeSpan pathSearchElapsed = default(TimeSpan)
```
Assign it after `Elapsed`:
```csharp
Elapsed = elapsed;
PathSearchElapsed = pathSearchElapsed;
TerminationReason = terminationReason ?? string.Empty;
```
Add the property:
```csharp
/// <summary>
/// 地图和起终点预检通过后,二维启发式、Hybrid A*、回溯、装配和最终复核的耗时;
/// 不含建图,搜索前失败时为零。
/// </summary>
public TimeSpan PathSearchElapsed { get; }
```
Update the constructor XML comment so `elapsed` is described as total service elapsed time and `pathSearchElapsed` as the map-ready path-production elapsed time.
- [ ] **Step 4: Start the path-search stopwatch at the exact boundary**
Add:
```csharp
using System.Diagnostics;
using System.Globalization;
```
Declare the stopwatch before the `try`:
```csharp
Stopwatch pathSearchStopwatch = null;
```
Start it immediately before invoking the search:
```csharp
pathSearchStopwatch = Stopwatch.StartNew();
HybridAStarSearchResult searchResult = _search.Search(request, budget);
```
For `searchResult == null`, search failure, backtracking failure, assembly failure and final validation failure, pass `pathSearchStopwatch` into `Failure`. On success, pass its elapsed value:
```csharp
return PlanningResult.Success(path, segments, CreateDiagnostics(searchResult, budget.Elapsed,
pathLengthMeters, minimumClearanceMeters, string.Empty, pathSearchStopwatch.Elapsed));
```
Replace the planner catch block with:
```csharp
catch (Exception exception)
{
string reason = "规划内部错误:" + exception.GetType().Name +
(string.IsNullOrEmpty(exception.Message) ? "。" : "。" + exception.Message);
return Failure(PlanningStatus.InternalError,
budget ?? PlanningOperationBudget.Unlimited(CancellationToken.None),
reason, null, pathSearchStopwatch);
}
```
- [ ] **Step 5: Compose actionable search diagnostics**
For non-success search results, use:
```csharp
if (searchResult.Status != PlanningStatus.Success)
return Failure(searchResult.Status, budget,
BuildSearchFailureReason(searchResult, request.Configuration),
searchResult, pathSearchStopwatch);
```
Add:
```csharp
private static string BuildSearchFailureReason(HybridAStarSearchResult searchResult,
HybridAStarConfiguration configuration)
{
string reason = string.IsNullOrEmpty(searchResult.TerminationReason)
? "Hybrid A* 搜索以 " + searchResult.Status + " 状态终止。"
: searchResult.TerminationReason;
string limit = string.Empty;
if (searchResult.Status == PlanningStatus.SearchTimeout)
{
limit = "总预算=" + configuration.SearchTimeout.TotalSeconds.ToString(
"F3", CultureInfo.InvariantCulture) + "s";
}
else if (searchResult.Status == PlanningStatus.SearchNodeLimitExceeded)
{
limit = "节点上限=" + configuration.MaximumExpandedNodes.ToString(
CultureInfo.InvariantCulture) + "";
}
return reason + limit +
"扩展=" + searchResult.ExpandedNodeCount.ToString(CultureInfo.InvariantCulture) + "" +
"生成=" + searchResult.GeneratedNodeCount.ToString(CultureInfo.InvariantCulture) + "" +
"重开=" + searchResult.ReopenedNodeCount.ToString(CultureInfo.InvariantCulture) + "" +
"陈旧条目=" + searchResult.StaleOpenListEntryCount.ToString(CultureInfo.InvariantCulture) + "" +
"Open List峰值=" + searchResult.PeakOpenListCount.ToString(CultureInfo.InvariantCulture) + "。";
}
```
Replace the two helper signatures and bodies:
```csharp
private static PlanningResult Failure(PlanningStatus status, PlanningOperationBudget budget, string reason,
HybridAStarSearchResult searchResult, Stopwatch pathSearchStopwatch = null)
{
TimeSpan pathSearchElapsed = pathSearchStopwatch == null
? TimeSpan.Zero
: pathSearchStopwatch.Elapsed;
return PlanningResult.Failure(status, CreateDiagnostics(searchResult, budget.Elapsed, 0d, 0d,
reason, pathSearchElapsed));
}
private static PlanningDiagnostics CreateDiagnostics(HybridAStarSearchResult searchResult, TimeSpan elapsed,
double pathLengthMeters, double minimumClearanceMeters, string reason, TimeSpan pathSearchElapsed)
{
return new PlanningDiagnostics(
searchResult == null ? 0 : searchResult.ExpandedNodeCount,
searchResult == null ? 0 : searchResult.GeneratedNodeCount,
searchResult == null ? 0 : searchResult.ReopenedNodeCount,
searchResult == null ? 0 : searchResult.StaleOpenListEntryCount,
searchResult == null ? 0 : searchResult.PeakOpenListCount,
pathLengthMeters,
minimumClearanceMeters,
elapsed,
reason,
pathSearchElapsed);
}
```
All search-preflight `Failure(...)` calls continue omitting the optional stopwatch and therefore report zero. All calls after `_search.Search` pass the running stopwatch.
- [ ] **Step 6: Run integration checks and verify GREEN**
Run:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1
```
Expected:
```text
Coarse path integration checks passed.
Coarse path facade checks passed.
Coarse path P1 scenario checks passed.
```
- [ ] **Step 7: Commit planner diagnostics**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/CoarsePath/Contracts/PlanningDiagnostics.cs ClumsyPilot/ParkrobTrajplanner/CoarsePath/HybridAStarPlanner.cs ClumsyPilot/tests/verify_coarse_path_integration.ps1
git commit -m "feat: add actionable coarse path diagnostics"
```
### Task 3: Add Manual Timeout Input, UI Diagnostics, and Documentation
**Files:**
- Modify: `ClumsyPilot/tests/verify_coarse_path_ui.ps1:19-92`
- Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/MovementTest.CoarsePathTest.cs:104-190,340-519`
- Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/README.md:114-116,285-359`
**Interfaces:**
- Consumes: Task 2 `PlanningDiagnostics.PathSearchElapsed`.
- Produces: `ReadPositiveTimeoutInput(string) : TimeSpan`.
- Produces: `DrawStatus(string, CoarsePathPlanningJob, CoarsePathPlanningJobResult, PlanningGridMap)`.
- Contract: only the manual `[MovementTest(name = "粗路径规划")]` prompts for and overrides its request timeout; fixed scenarios keep their existing budgets.
- [ ] **Step 1: Add failing UI and README source checks**
After the current manual input assertions add:
```powershell
Assert-Match $source 'ReadPositiveTimeoutInput\s*\(' `
'The manual UI must read a finite positive timeout.'
Assert-Match $source 'Configuration\.SearchTimeout\s*=\s*searchTimeout' `
'The manual UI must apply the timeout to the current job.'
Assert-Match $source 'TimeSpan\.FromSeconds\s*\(' `
'The manual timeout must convert seconds to TimeSpan.'
Assert-Match $source 'timeoutSeconds\s*<=\s*0' `
'The manual timeout must reject zero and negative values.'
Assert-True (([regex]::Matches($source, 'PathSearchElapsed')).Count -ge 2) `
'The status layer and Toast must both show path-search elapsed time.'
Assert-Match $source 'BuildToastMessage[\s\S]*TerminationReason' `
'The failure Toast must include the termination reason.'
Assert-Match $source 'ExpandedNodeCount' `
'The status layer must show expanded-node statistics.'
Assert-Match $source 'VehicleKinematics\.TryGetMaximumCurvaturePerMeter' `
'The status layer must show the effective turning radius.'
```
Append these entries to `$readmeStructure`:
```powershell
'PathSearchElapsed',
'1.20 m',
'Reeds-Shepp',
```
- [ ] **Step 2: Run UI checks and verify RED**
Run:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_ui.ps1
```
Expected: FAIL with `The manual UI must read a finite positive timeout.`
- [ ] **Step 3: Read and apply one-shot manual timeout**
Add:
```csharp
using MultiWheelC.TrajectoryPlanning.CoarsePath.Vehicle;
```
In `CoarsePathPlanningTest.Test()`, read the timeout after goal heading and apply it after creating the job:
```csharp
double goalHeadingDeg = ReadFiniteInput("粗路径终点航向(世界 deg");
TimeSpan searchTimeout = ReadPositiveTimeoutInput("粗路径规划总超时(秒,必须大于 0)");
IReadOnlyList<ManualCoarsePathObstacle> obstacles = ReadManualObstacles();
long snapshotVersion = obstacles.Count == 0 ? 0L :
Interlocked.Increment(ref _nextManualObstacleSnapshotVersion);
CoarsePathPlanningJob job = CoarsePathScenarioFactory.CreateManualObstacleDemo(
amrPose.x, amrPose.y, amrPose.th, goalXmm, goalYmm, goalHeadingDeg,
obstacles, snapshotVersion);
job.Configuration.SearchTimeout = searchTimeout;
CoarsePathPlanningTestRunner.Run("AMR 位姿 + 手动终点 + 手动障碍物", job);
```
Add:
```csharp
private static TimeSpan ReadPositiveTimeoutInput(string prompt)
{
double timeoutSeconds = ReadFiniteInput(prompt);
if (timeoutSeconds <= 0d)
throw new ArgumentOutOfRangeException(nameof(prompt), "输入必须为正数:" + prompt);
try
{
return TimeSpan.FromSeconds(timeoutSeconds);
}
catch (OverflowException)
{
throw new ArgumentOutOfRangeException(nameof(prompt), "输入超出允许范围:" + prompt);
}
}
```
- [ ] **Step 4: Show timings, search counts, vehicle configuration, and failure reason**
Change the call site to:
```csharp
DrawStatus(scenarioName, job, result, map);
```
Replace `DrawStatus` with:
```csharp
private static void DrawStatus(string scenarioName, CoarsePathPlanningJob job,
CoarsePathPlanningJobResult result, PlanningGridMap map)
{
float x = map == null ? 0f : map.Bounds.XMin + 150f;
float y = map == null ? -250f : map.Bounds.YMin + 150f;
string snapshot = map == null ? "无" : map.SnapshotId.ToString(CultureInfo.InvariantCulture);
string resolution = map == null ? "无" :
map.ResolutionMm.ToString("F0", CultureInfo.InvariantCulture) + " mm";
PlanningDiagnostics diagnostics = result.PlanningResult.Diagnostics;
string reason = diagnostics.TerminationReason ?? string.Empty;
string turningRadius = "无";
if (job != null && VehicleKinematics.TryGetMaximumCurvaturePerMeter(
job.Vehicle, out double maximumCurvaturePerMeter))
{
turningRadius = (1d / maximumCurvaturePerMeter).ToString(
"F2", CultureInfo.InvariantCulture) + " m";
}
Painter.DrawText(Color.White, "场景:" + scenarioName, x, y);
Painter.DrawText(Color.White, "地图:" + result.MapResult.Status + ",缓存:" +
result.MapResult.CacheHit + ",快照:" + snapshot, x, y + 120f);
Painter.DrawText(Color.White, "栅格:" + resolution + ",规划:" +
result.PlanningResult.Status + ",总耗时:" +
diagnostics.Elapsed.TotalMilliseconds.ToString("F0", CultureInfo.InvariantCulture) +
" ms,路径搜索:" +
diagnostics.PathSearchElapsed.TotalMilliseconds.ToString("F0", CultureInfo.InvariantCulture) +
" ms", x, y + 240f);
Painter.DrawText(Color.White, "节点:扩展=" +
diagnostics.ExpandedNodeCount.ToString(CultureInfo.InvariantCulture) + ",生成=" +
diagnostics.GeneratedNodeCount.ToString(CultureInfo.InvariantCulture) + "Open List峰值=" +
diagnostics.PeakOpenListCount.ToString(CultureInfo.InvariantCulture), x, y + 360f);
if (job != null && job.Vehicle != null)
{
Painter.DrawText(Color.White, "演示车辆:长=" +
job.Vehicle.LengthMeters.ToString("F2", CultureInfo.InvariantCulture) + " m,宽=" +
job.Vehicle.WidthMeters.ToString("F2", CultureInfo.InvariantCulture) + " m,余量=" +
job.Vehicle.SafetyMarginMeters.ToString("F2", CultureInfo.InvariantCulture) +
" m,最小转弯半径=" + turningRadius, x, y + 480f);
}
if (!string.IsNullOrEmpty(reason))
Painter.DrawText(Color.LightYellow, "原因:" + reason, x, y + 600f);
}
```
Replace `BuildToastMessage` with:
```csharp
private static string BuildToastMessage(string scenarioName, CoarsePathPlanningJobResult result)
{
PlanningDiagnostics diagnostics = result.PlanningResult.Diagnostics;
string message = "粗路径[" + scenarioName + "]:地图=" + result.MapResult.Status +
",规划=" + result.PlanningResult.Status + ",总耗时=" +
diagnostics.Elapsed.TotalMilliseconds.ToString("F0", CultureInfo.InvariantCulture) +
"ms,路径搜索=" +
diagnostics.PathSearchElapsed.TotalMilliseconds.ToString("F0", CultureInfo.InvariantCulture) + "ms";
if (result.PlanningResult.Status != PlanningStatus.Success &&
!string.IsNullOrEmpty(diagnostics.TerminationReason))
{
message += ",原因=" + diagnostics.TerminationReason;
}
return message + "。";
}
```
- [ ] **Step 5: Document the exact manual-test boundaries**
After “总预算与取消” add:
```markdown
### 总耗时与路径搜索耗时
`PlanningDiagnostics.Elapsed` 是从 `CoarsePathPlanningService.Plan` 开始的总耗时,包含
地图来源、缓存、栅格化、距离场和路径规划。`PathSearchElapsed` 是地图和起终点预检
通过后的路径搜索耗时,包含二维启发式、Hybrid A*、回溯、装配、方向分段和最终复核;
搜索开始前失败时为零。
```
In “AMR 位姿、手动终点与障碍物”, add:
```markdown
手动入口还要求输入一次“粗路径规划总超时”,单位为秒,只接受 `TimeSpan` 可表示范围内
的有限正数秒;`0`、负数、NaN、Infinity 或溢出值都会在启动规划前拒绝。该值只覆盖
本次 `CoarsePathPlanningJob.Configuration.SearchTimeout`,不会改变固定场景或全局默认值。
此入口使用固定演示车辆:长 `0.80 m`、宽 `0.60 m`、四周安全余量 `0.05 m`、最小转弯
半径 `1.20 m`。这些值不是从现场 AMR 配置读取的,判断现场可行性前必须确认车辆参数一致。
```
In “后台执行、停止与图层”, add:
```markdown
状态图层显示规划状态、总耗时、`PathSearchElapsed`(路径搜索耗时)、扩展/生成节点数、
Open List 峰值、失败原因和固定演示车辆参数。Toast 同时显示两种耗时,并在失败时附加
`TerminationReason`,因此超时、节点上限、无解、碰撞和内部错误不会只显示成泛化失败。
```
Extend “第一版限制” with:
```markdown
- Reeds-Shepp 或 Dubins 精确终点连接;
- 原地旋转。
当前只生成汽车式恒曲率前进/倒车原语,并允许在原语边界换向;未实现的 Reeds-Shepp、
横移、蟹行和原地旋转是整个粗规划核心的第一版能力边界,不是 MovementTest 单独关闭。
```
- [ ] **Step 6: Run UI checks and verify GREEN**
Run:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_ui.ps1
```
Expected:
```text
Coarse path P1 UI source checks passed.
```
- [ ] **Step 7: Commit the manual-test UX**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/MovementTest.CoarsePathTest.cs ClumsyPilot/ParkrobTrajplanner/CoarsePath/README.md ClumsyPilot/tests/verify_coarse_path_ui.ps1
git commit -m "feat: expose coarse path test diagnostics"
```
### Task 4: Regress the Slow Feasible Case and Run the Full Suite
**Files:**
- Modify: `ClumsyPilot/tests/verify_coarse_path_integration.ps1:273-369`
- Verify: all files modified by Tasks 1-3
**Interfaces:**
- Consumes: Task 3 manual scenario factory and configurable `SearchTimeout`.
- Produces: regression proof that the formerly 5-second-limited feasible pose succeeds under a caller-selected longer budget.
- Contract: the regression uses a 30 秒 upper bound without asserting an exact wall-clock duration.
- [ ] **Step 1: Add the slow feasible-case regression**
After the manual empty-map factory assertions, add:
```powershell
$slowFeasibleJob = $factoryManual.Invoke($null, @(
1000.0, 2000.0, 0.0, 1500.0, 2500.0, 90.0))
$slowFeasibleJob.Configuration.SearchTimeout = [TimeSpan]::FromSeconds(30)
$slowFeasibleJob.Configuration.MaximumExpandedNodes = 1000000
$slowFeasibleService = [Activator]::CreateInstance($serviceType)
$slowFeasibleResult = $servicePlan.Invoke(
$slowFeasibleService, @($slowFeasibleJob, [Threading.CancellationToken]::None))
Assert-Equal 'Success' $slowFeasibleResult.PlanningResult.Status.ToString() `
'The previously five-second-limited feasible pose must succeed with a caller-selected longer budget.'
Assert-True ($slowFeasibleResult.PlanningResult.Diagnostics.PathSearchElapsed -le
$slowFeasibleResult.PlanningResult.Diagnostics.Elapsed) `
'Slow feasible path-search time must remain within total elapsed time.'
```
- [ ] **Step 2: Run the integration script and confirm the regression passes**
Run:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1
```
Expected:
```text
Coarse path integration checks passed.
Coarse path facade checks passed.
Coarse path P1 scenario checks passed.
```
The slow case is allowed up to 30 seconds and should normally complete near the observed 10-second baseline; do not assert an exact wall-clock value.
- [ ] **Step 3: Run all coarse-path and map regressions**
Run:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_adapter.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_factory.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_documentation.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_image.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_planning_map_test_config.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_collision.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_search.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_ui.ps1
```
Expected: build exits 0 and every script prints its existing `passed` summary without an unhandled exception.
- [ ] **Step 4: Inspect the final diff for scope and accidental edits**
Run:
```powershell
git diff -- ClumsyPilot/ParkrobTrajplanner/CoarsePath/Search/HybridAStarSearch.cs ClumsyPilot/ParkrobTrajplanner/CoarsePath/Contracts/PlanningDiagnostics.cs ClumsyPilot/ParkrobTrajplanner/CoarsePath/HybridAStarPlanner.cs ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/MovementTest.CoarsePathTest.cs ClumsyPilot/ParkrobTrajplanner/CoarsePath/README.md ClumsyPilot/tests/verify_coarse_path_search.ps1 ClumsyPilot/tests/verify_coarse_path_integration.ps1 ClumsyPilot/tests/verify_coarse_path_ui.ps1
```
Expected: only the approved reason propagation, timing, manual timeout, UI, documentation and tests are present; collision/search semantics and unrelated worktree files are unchanged.
- [ ] **Step 5: Perform the Clumsy UI acceptance**
Run `[MovementTest(name = "粗路径规划")]` with:
```text
Start from current AMR pose corresponding to: 1000 mm, 2000 mm, 0 deg
Goal X: 1500 mm
Goal Y: 2500 mm
Goal heading: 90 deg
Timeout: 30 seconds
Manual obstacle count: 0
```
Confirm:
```text
Planning status: Success
Toast and status layer both show total elapsed and path-search elapsed
Status layer shows expanded/generated/Open List peak and the fixed demo vehicle parameters
```
Then run an intentionally impossible barrier or a deliberately short positive timeout and confirm the Toast includes a non-empty reason.
- [ ] **Step 6: Commit the regression coverage**
```powershell
git add -- ClumsyPilot/tests/verify_coarse_path_integration.ps1
git commit -m "test: cover slow feasible coarse path planning"
```
@@ -1,185 +0,0 @@
# CoarsePath README 结构化重构 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:** 将 CoarsePath README 重构为与 Map README 相同的“结构—数据流—契约—最小示例—分步指南—常见错误”说明方式,同时保留准确的 P0/P1 边界。
**Architecture:** 保持所有生产代码不变。先为 README 的结构性事实增加稳定的 ASCII 文本断言,再将现有 README 的正确内容重组为面向调用者的模块说明,最后运行文档、构建与集成回归,证明这只是文档交付。
**Tech Stack:** Markdown、PowerShell、.NET `netstandard2.0` Debug 构建、现有 CoarsePath 验证脚本。
## Global Constraints
- 只修改 `CoarsePath/README.md` 与其文档断言;不得改动 Map、CoarsePath、P1 UI 或测试场景的运行行为。
- README 只陈述当前已实现并经自动化验证的 P0/P1 能力;实际 Clumsy 的人工视觉验收仍要明确为待执行。
- 业务调用示例固定使用 `CoarsePathPlanningService.Plan(job, cancellationToken)`;不得鼓励 UI 或调用方直接拼接搜索组件。
- Map 障碍物投影、栅格化和缓存细节只链接到 `../Map/README.md`,不复制为 CoarsePath 实现说明。
- 坐标说明必须保持:Map 输入为 mm,核心位姿/路径为 m,核心航向为 rad;P1 UI 的 AMR 输入航向为 deg 并在边界转换。
- 显式空图只能描述为 P1 单位/可视化演示,不能描述为真实作业地图。
- 不恢复、清理或迁移 TrapMap 文件或旧 TrapMap 验证脚本;不执行 Git 状态、差异、提交或重置操作。
---
## 文件结构
| 文件 | 修改职责 |
| --- | --- |
| `ClumsyPilot/ParkrobTrajplanner/CoarsePath/README.md` | 重组现有 P0/P1 内容,加入实际目录树、规划数据流、分步指南与常见错误。 |
| `ClumsyPilot/tests/verify_coarse_path_ui.ps1` | 用 ASCII 关键字保护 README 的结构、核心边界和 P1 说明。 |
### Task 1: 为 README 重构建立失败的结构断言
**Files:**
- Modify: `ClumsyPilot/tests/verify_coarse_path_ui.ps1`
- Verify later: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/README.md`
**Consumes:** 现有 `$readmePath``$readme``Assert-True` 及 P1 UI 源码检查。
**Produces:** 文档结构保护;README 缺少新的 Map 风格章节或 P1 边界时脚本失败。
- [ ] **Step 1: 在现有 README 断言后加入目标结构的失败检查**
在当前 `$requiredText` 循环之后插入以下 PowerShell。所有匹配项保持 ASCII,避免 Windows PowerShell 无 BOM 脚本中的中文编码差异:
```powershell
$readmeStructure = @(
'File Structure',
'Planning Data Flow',
'Build Status and Stop',
'Coordinates and Units',
'Minimal Call Example',
'Cache and SourceVersion',
'Detailed Usage Guide',
'P1 Manual Tests and Visualization',
'Common Errors',
'First-Version Limits',
'CoarsePathPlanningService.Plan(job, cancellationToken)',
'CoarsePathPlanningJob',
'PlanningGridMap',
'SourceVersion',
'CoarsePathPlanningV1',
'CancellationTokenSource',
'NoFeasiblePath',
'IsGearSwitchPoint',
'../Map/README.md'
)
foreach ($requiredText in $readmeStructure) {
Assert-True ($readme.Contains($requiredText)) "Restructured CoarsePath README must document $requiredText."
}
```
- [ ] **Step 2: 运行脚本确认 README 仍缺少新结构**
Run:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_ui.ps1
```
Expected: `Restructured CoarsePath README must document File Structure.`;源码 UI 断言仍通过。
### Task 2: 重构 CoarsePath README 的模块说明与调用文档
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/README.md`
- Test: `ClumsyPilot/tests/verify_coarse_path_ui.ps1`
**Consumes:** Map README 的组织方式;现有 CoarsePath README 的真实 P0/P1 契约;`CoarsePathPlanningService.Plan(job, cancellationToken)`
**Produces:** 一份可从零开始阅读的 CoarsePath 模块说明,内容与当前实现一致。
- [ ] **Step 1: 用 Map 风格的顶层章节替换现有 README 的章节顺序**
保留 README 标题 `# CoarsePath 粗路径规划(P0/P1`,然后按以下顺序重新组织内容;将每个二级标题同时写为中文说明和括号中的 ASCII 稳定标识,例如 `## 文件结构(File Structure`,使人类读者与 Task 1 断言都能使用:
1. `## 模块说明(Module Overview`:说明 Map 提供只读快照,CoarsePath 输出已复核的粗路径;唯一业务入口是 `CoarsePathPlanningService.Plan(job, cancellationToken)`;列出不负责的控制、速度、实时重规划等职责。
2. `## 文件结构(File Structure`:使用 `text` 目录树列出实际 `Contracts/``Vehicle/``Search/``Output/``Facade/``Test/` 文件,逐项写出与当前目录对应的职责。
3. `## 规划数据流(Planning Data Flow`:画出 `CoarsePathPlanningJob -> CoarsePathPlanningService -> PlanningMapFactory.Create -> PlanningGridMap -> HybridAStarPlanner -> PlanningResult -> CoarsePathPlanningJobResult`;在失败分支注明地图失败不启动搜索。
4. `## 构建状态与停止(Build Status and Stop`:说明 `MapResult``PlanningResult` 必须一起处理,解释 `Success``Cancelled``SearchTimeout``NoFeasiblePath` 与空路径规则。
5. `## 坐标与单位(Coordinates and Units`:用表格列出地图 mm、`Pose2D`/路径 m、核心航向 rad、P1 AMR 输入 deg;明确起点为车身几何中心和安全余量由 `VehicleParameters.SafetyMarginMeters` 表达。
6. `## 最小调用示例(Minimal Call Example`:保留并精简当前服务调用示例;包含 `PlanningMapRequest``Pose2D``VehicleParameters``HybridAStarConfiguration``MapResult``PlanningResult` 的失败处理。
7. `## 缓存与 SourceVersionCache and SourceVersion`:说明服务长期存活、`Input`/`Occupancy`/`None` 缓存层级,及来源内容变更必须递增 `SourceVersion`
8. `## 详细使用指南(Detailed Usage Guide`:用六步小节解释长期服务、准备地图请求、填写起终点、填写车辆、调整搜索配置、调用及消费路径/方向段;链接 `../Map/README.md` 说明障碍物来源和栅格化。
9. `## P1 手动测试与可视化(P1 Manual Tests and Visualization`:包含七个 MovementTest 的场景表、`CoarsePathPlanningTest``getCartLocation`/手动目标转换、`CancellationTokenSource`/`Task.Run`/`TestStop` 停止语义、`CoarsePathPlanningV1` 图层及颜色图例。明确人工视觉验收尚待在实际 Clumsy 中执行。
10. `## 常见错误(Common Errors`:以“现象 / 原因 / 处理”表格写入:mm 当作 m、deg 当作 rad、`SourceVersion` 未递增、隐式空图、未处理非成功结果、把粗路径当作底盘可执行轨迹。
11. `## 第一版限制(First-Version Limits`:保留并归并路径平滑、速度/时间轨迹、底盘控制、实时重规划、真实作业地图、Release 基准等明确非目标。
- [ ] **Step 2: 对照实际目录和 P1 实现,校验每个文件树项与说明的真实性**
确认目录树只引用下列已存在组件:
```text
Contracts/: Pose2D, PlanningRequest, PlanningResult, PlanningStatus,
CoarsePathPoint, PathSegment, VehicleParameters, HybridAStarConfiguration
Vehicle/: VehicleKinematics, VehicleFootprint, FootprintCollisionChecker,
OrientedRectangleCellIntersection
Search/: BinaryMinHeap, GridDijkstraHeuristic, GoalToleranceChecker,
MotionPrimitive, MotionPrimitiveGenerator, SearchCostCalculator,
HybridAStarNode, HybridAStarNodeKey, HybridAStarSearch
Output/: PathBacktracker, CoarsePathAssembler, CoarsePathValidator
Facade/: CoarsePathPlanningJob, CoarsePathPlanningJobResult,
CoarsePathPlanningService, PlanningDebugOptions, IPlanningDebugSink
Test/: CoarsePathScenarioFactory, MovementTest.CoarsePathTest
```
不要在 README 中承诺不存在的平滑器、控制器、实时数据源或 Release 基准。
- [ ] **Step 3: 运行文档结构检查确认通过**
Run:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_ui.ps1
```
Expected: `Coarse path P1 UI source checks passed.`
### Task 3: 验证文档重构没有影响 P0/P1 行为
**Files:**
- Verify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/README.md`
- Verify: `ClumsyPilot/tests/verify_coarse_path_ui.ps1`
- Verify: `ClumsyPilot/tests/verify_coarse_path_integration.ps1`
**Consumes:** Tasks 12 的 README 与断言。
**Produces:** 从最终工作区获得的文档、构建和集成验证证据。
- [ ] **Step 1: 构建项目**
Run:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
```
Expected: `0 个错误`;允许项目已有的两条过时 API 警告。
- [ ] **Step 2: 运行 P1 文档/UI 结构检查**
Run:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_ui.ps1
```
Expected: `Coarse path P1 UI source checks passed.`
- [ ] **Step 3: 运行粗路径集成回归**
Run:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1
```
Expected: 依次输出 `Coarse path integration checks passed.``Coarse path facade checks passed.``Coarse path P1 scenario checks passed.`
## 自检
- **规格覆盖:** Task 2 覆盖设计中的十个 README 章节、P0/P1 已完成边界、Map 链接、单位、空图限制与人工验收状态;Task 1 保护可自动检查的结构事实;Task 3 给出最终证据。
- **完整性检查:** 本计划不含未决实现、泛化错误处理或未命名的验证步骤;每项改动均有文件路径、具体内容与命令。
- **一致性:** 所有调用名、状态名、场景工厂、P1 图层和坐标单位均与现有 CoarsePath 代码一致;计划不引入新 C# 接口或依赖。
@@ -1,365 +0,0 @@
# P1 手动障碍物输入 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:** 让“粗路径规划”MovementTest 支持一次输入最多 20 个圆形或轴对齐矩形障碍物,并通过既有门面规划、快照绘制和取消流程验证结果。
**Architecture:** 纯几何输入和 Map 请求构造保留在 `CoarsePathScenarioFactory`,UI 只读取、验证和冻结操作者输入。每次含障碍物的手动运行由共享执行器颁发单调递增快照版本,保证 Map 缓存不会错误复用旧障碍物;Painter 继续只读取最终 `PlanningGridMap`
**Tech Stack:** C# / `netstandard2.0`、现有 `ManualObstacleSource`、Clumsy `MovementTest`/`UI.GetInput`、PowerShell 反射与源码验证脚本。
## Global Constraints
- 不改变 `CoarsePathPlanningService.Plan(job, token)` 作为唯一业务规划入口的边界;UI 不得直接创建地图工厂、搜索器、碰撞器或原语。
- 手动输入的 X/Y、圆半径和矩形长宽全部使用世界 mm;AMR/目标航向输入使用 deg;核心 `Pose2D` 使用 m/rad。
- 障碍物数量范围固定为 0–20;圆半径、矩形 X 长度和 Y 宽度必须是有限正数;矩形始终与世界坐标轴平行。
- 有障碍物时使用必需的 `ManualObstacleSource("manual-user-input", version, true, ...)` 且关闭显式空图;零障碍物时才允许显式空图。
- 手动地图边界必须覆盖起点、终点及每个障碍物完整外轮廓,再保留 2000 mm 留白并按 50 mm 向外取整。
- 含障碍物手动提交必须使用单调递增快照版本;固定场景的缓存命中行为不得改变。
- 保留后台 `Task.Run``CancellationTokenSource``TestStop`、结果快照绘制和无底盘命令边界。
- 不支持旋转矩形、多边形、文件导入、拖拽编辑或运行中修改障碍物;不执行 Git 操作。
---
## 文件结构
| 文件 | 修改职责 |
| --- | --- |
| `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/CoarsePathScenarioFactory.cs` | 新增手动障碍物纯数据类型、工厂方法、几何校验、动态边界和来源快照构造。 |
| `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/MovementTest.CoarsePathTest.cs` | 为“粗路径规划”读取数量、类型、中心和尺寸,生成单调来源版本并提交工厂请求。 |
| `ClumsyPilot/tests/verify_coarse_path_integration.ps1` | 通过程序集反射验证工厂、障碍来源、空图分支、几何边界和无效尺寸。 |
| `ClumsyPilot/tests/verify_coarse_path_ui.ps1` | 验证 UI 入口包含手动障碍物输入与工厂调用,同时保持无直接地图/搜索依赖。 |
| `ClumsyPilot/ParkrobTrajplanner/CoarsePath/README.md` | 补充手动障碍物的输入顺序、单位、上限、矩形方向和空图限制。 |
### Task 1: 手动障碍物工厂契约与行为验证
**Files:**
- Modify: `ClumsyPilot/tests/verify_coarse_path_integration.ps1`
- Modify later: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/CoarsePathScenarioFactory.cs`
**Consumes:** 现有 `$assembly``$testNamespace``$scenarioFactoryType``Find-Method``Assert-True``Assert-Equal``Assert-Near`
**Produces:** `ManualCoarsePathObstacleKind``ManualCoarsePathObstacle``CreateManualObstacleDemo` 的反射/行为契约。
- [ ] **Step 1: 在 P1 工厂断言后加入失败的手动障碍物检查**
`$manualJob` 的现有断言之后插入下面代码。它使用数组传入 `IReadOnlyList<ManualCoarsePathObstacle>`,并检查请求尚未存在时的类型/方法失败。
```powershell
$manualObstacleKindType = $assembly.GetType($testNamespace + 'ManualCoarsePathObstacleKind', $false)
$manualObstacleType = $assembly.GetType($testNamespace + 'ManualCoarsePathObstacle', $false)
Assert-True ($manualObstacleKindType -ne $null) 'Manual obstacle kind enum must exist.'
Assert-True ($manualObstacleType -ne $null) 'Manual obstacle value type must exist.'
$manualCircle = Find-Method $manualObstacleType 'Circle' @([double], [double], [double])
$manualRectangle = Find-Method $manualObstacleType 'AxisAlignedRectangle' @([double], [double], [double], [double])
$manualObstacleFactory = $scenarioFactoryType.GetMethods() | Where-Object {
$_.Name -eq 'CreateManualObstacleDemo' -and $_.GetParameters().Length -eq 8
} | Select-Object -First 1
Assert-True ($manualCircle -ne $null) 'Manual obstacle type must create circles from center and radius.'
Assert-True ($manualRectangle -ne $null) 'Manual obstacle type must create rectangles from center and X/Y dimensions.'
Assert-True ($manualObstacleFactory -ne $null) 'Scenario factory must expose CreateManualObstacleDemo with six poses, obstacles and version.'
$manualObstacles = [Array]::CreateInstance($manualObstacleType, 2)
$manualObstacles.SetValue($manualCircle.Invoke($null, @([double]6500, [double]2000, [double]200)), 0)
$manualObstacles.SetValue($manualRectangle.Invoke($null, @([double]-2000, [double]500, [double]600, [double]400)), 1)
$manualObstacleJob = $manualObstacleFactory.Invoke($null, @(
1000.0, 2000.0, 0.0, 4000.0, 2000.0, 0.0, $manualObstacles, [long]77))
Assert-False $manualObstacleJob.MapRequest.AllowExplicitEmptyMap 'Manual obstacles must disable the explicit-empty-map mode.'
Assert-Equal 1 $manualObstacleJob.MapRequest.ObstacleSources.Count 'Manual obstacles must create one unified source.'
Assert-Equal 'manual-user-input' $manualObstacleJob.MapRequest.ObstacleSources[0].SourceId 'Manual source ID must be stable.'
Assert-Equal 77 $manualObstacleJob.MapRequest.ObstacleSources[0].SourceVersion 'Manual source version must be preserved.'
Assert-True ($manualObstacleJob.MapRequest.Bounds.XMin -le -4300.0) 'Manual map must include the rectangle outline and padding.'
Assert-True ($manualObstacleJob.MapRequest.Bounds.XMax -ge 8700.0) 'Manual map must include the circle outline and padding.'
$emptyManualObstacles = [Array]::CreateInstance($manualObstacleType, 0)
$emptyManualJob = $manualObstacleFactory.Invoke($null, @(
1000.0, 2000.0, 0.0, 4000.0, 2000.0, 0.0, $emptyManualObstacles, [long]0))
Assert-True $emptyManualJob.MapRequest.AllowExplicitEmptyMap 'Zero manual obstacles must retain explicit empty-map mode.'
Assert-Equal 0 $emptyManualJob.MapRequest.ObstacleSources.Count 'Zero manual obstacles must not create a fake source.'
try {
$null = $manualCircle.Invoke($null, @([double]1000, [double]1000, [double]0))
throw 'Zero-radius manual circle must be rejected.'
}
catch [Reflection.TargetInvocationException] {
Assert-True ($_.Exception.InnerException -is [ArgumentOutOfRangeException]) 'Invalid manual geometry must report argument range.'
}
```
- [ ] **Step 2: 构建并运行脚本确认新契约失败**
Run:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1
```
Expected: 构建成功;脚本报出 `Manual obstacle kind enum must exist.`
- [ ] **Step 3: 在场景工厂实现不可变手动障碍物类型**
`CoarsePathScenarioFactory.cs` 的固定场景枚举之后加入如下公共类型。构造函数保持私有,强制圆形与矩形分别通过语义明确的静态工厂创建;所有几何输入均为 mm。
```csharp
/// <summary>手动障碍物的支持几何类型。</summary>
public enum ManualCoarsePathObstacleKind
{
/// <summary>由圆心和半径定义的圆形障碍物。</summary>
Circle,
/// <summary>由几何中心、X 方向长度和 Y 方向宽度定义的轴对齐矩形障碍物。</summary>
AxisAlignedRectangle,
}
/// <summary>手动粗路径测试的不可变障碍物输入;全部几何数据使用世界 mm。</summary>
public sealed class ManualCoarsePathObstacle
{
private ManualCoarsePathObstacle(ManualCoarsePathObstacleKind kind, double centerXMillimeters,
double centerYMillimeters, double sizeXMillimeters, double sizeYMillimeters)
{
Kind = kind; CenterXMillimeters = centerXMillimeters; CenterYMillimeters = centerYMillimeters;
SizeXMillimeters = sizeXMillimeters; SizeYMillimeters = sizeYMillimeters;
}
public ManualCoarsePathObstacleKind Kind { get; }
public double CenterXMillimeters { get; }
public double CenterYMillimeters { get; }
public double SizeXMillimeters { get; }
public double SizeYMillimeters { get; }
public static ManualCoarsePathObstacle Circle(double centerXMillimeters, double centerYMillimeters,
double radiusMillimeters)
{
EnsureFinite(centerXMillimeters, nameof(centerXMillimeters));
EnsureFinite(centerYMillimeters, nameof(centerYMillimeters));
EnsurePositiveFinite(radiusMillimeters, nameof(radiusMillimeters));
return new ManualCoarsePathObstacle(ManualCoarsePathObstacleKind.Circle, centerXMillimeters,
centerYMillimeters, radiusMillimeters, radiusMillimeters);
}
public static ManualCoarsePathObstacle AxisAlignedRectangle(double centerXMillimeters,
double centerYMillimeters, double lengthXMillimeters, double widthYMillimeters)
{
EnsureFinite(centerXMillimeters, nameof(centerXMillimeters));
EnsureFinite(centerYMillimeters, nameof(centerYMillimeters));
EnsurePositiveFinite(lengthXMillimeters, nameof(lengthXMillimeters));
EnsurePositiveFinite(widthYMillimeters, nameof(widthYMillimeters));
return new ManualCoarsePathObstacle(ManualCoarsePathObstacleKind.AxisAlignedRectangle,
centerXMillimeters, centerYMillimeters, lengthXMillimeters, widthYMillimeters);
}
}
```
`EnsureFinite` 与新增 `EnsurePositiveFinite` 定义为可被同一命名空间类型调用的内部静态校验辅助方法,或在 `ManualCoarsePathObstacle` 中实现等价私有辅助方法;无效值必须抛出 `ArgumentOutOfRangeException`
- [ ] **Step 4: 实现手动障碍物请求和动态边界**
`CoarsePathScenarioFactory` 加入下面公共方法,并让现有 `CreateManualGoalDemo` 调用它的零障碍物分支,以保留当前空图契约:
```csharp
public static CoarsePathPlanningJob CreateManualObstacleDemo(
double startXMillimeters, double startYMillimeters, double startHeadingDegrees,
double goalXMillimeters, double goalYMillimeters, double goalHeadingDegrees,
IReadOnlyList<ManualCoarsePathObstacle> obstacles, long obstacleSnapshotVersion)
{
ValidateManualPoseInputs(startXMillimeters, startYMillimeters, startHeadingDegrees,
goalXMillimeters, goalYMillimeters, goalHeadingDegrees);
IReadOnlyList<ManualCoarsePathObstacle> items = obstacles ??
throw new ArgumentNullException(nameof(obstacles));
if (items.Count > MaximumManualObstacleCount)
throw new ArgumentOutOfRangeException(nameof(obstacles));
if (items.Count == 0)
return CreateJob(CreateManualDemoMap(startXMillimeters, startYMillimeters,
goalXMillimeters, goalYMillimeters, Array.Empty<ManualCoarsePathObstacle>()),
ToPose(startXMillimeters, startYMillimeters, startHeadingDegrees),
ToPose(goalXMillimeters, goalYMillimeters, goalHeadingDegrees), null, GoalDirectionConstraint.Any);
if (obstacleSnapshotVersion <= 0)
throw new ArgumentOutOfRangeException(nameof(obstacleSnapshotVersion));
IMapObstacle[] mapObstacles = ConvertManualObstacles(items);
IMapObstacleSource[] sources =
{
new ManualObstacleSource("manual-user-input", obstacleSnapshotVersion, true, mapObstacles),
};
return CreateJob(CreateManualDemoMap(startXMillimeters, startYMillimeters,
goalXMillimeters, goalYMillimeters, items),
ToPose(startXMillimeters, startYMillimeters, startHeadingDegrees),
ToPose(goalXMillimeters, goalYMillimeters, goalHeadingDegrees), null, GoalDirectionConstraint.Any);
}
```
Use `CreateManualMapRequest(items, sources)` rather than leaving the above source array unused: it must create a `PlanningMapRequest` with the dynamic bounds, `ResolutionMm = 50f`, those sources and `AllowExplicitEmptyMap = false`. `ConvertManualObstacles` must map a circle to `new CircleObstacle(centerX, centerY, radius)` and a rectangle to `new AxisAlignedRectangleObstacle(centerX - lengthX / 2, centerX + lengthX / 2, centerY - widthY / 2, centerY + widthY / 2)` after range-safe float conversion.
Refactor `CreateManualDemoMap` to accept an obstacle collection and include its circle/rectangle extents before adding 2000 mm padding and applying `ToGridLowerBound`/`ToGridUpperBound`. Zero obstacles must retain `Array.Empty<IMapObstacleSource>()` and `AllowExplicitEmptyMap = true`.
- [ ] **Step 5: 运行工厂行为检查确认通过**
Run:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1
```
Expected: 构建成功;输出既有三行集成通过信息,且手动圆/矩形、空障碍物和无效半径断言均通过。
### Task 2: MovementTest 逐项输入与来源版本
**Files:**
- Modify: `ClumsyPilot/tests/verify_coarse_path_ui.ps1`
- Modify later: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/MovementTest.CoarsePathTest.cs`
**Consumes:** Task 1 的 `ManualCoarsePathObstacle.Circle``ManualCoarsePathObstacle.AxisAlignedRectangle``CoarsePathScenarioFactory.CreateManualObstacleDemo`
**Produces:** `CoarsePathPlanningTest` 在启动规划前读取并冻结最多 20 个手动障碍物,随后使用递增版本提交给工厂。
- [ ] **Step 1: 加入失败的 UI 源码边界断言**
在现有手动工厂断言后加入:
```powershell
Assert-Match $source 'ManualCoarsePathObstacle' 'The manual UI must construct typed manual obstacles.'
Assert-Match $source 'CreateManualObstacleDemo\s*\(' 'The manual UI must submit obstacles through the factory.'
Assert-Match $source 'MaximumManualObstacleCount\s*=\s*20' 'The manual UI must bound obstacle input to 20.'
Assert-Match $source 'ReadManualObstacles\s*\(' 'The manual UI must read the requested obstacle sequence.'
Assert-Match $source 'Interlocked\.Increment\s*\(' 'The manual UI must issue a fresh obstacle snapshot version.'
Assert-Match $source 'Circle\s*\(' 'The manual UI must support circle input.'
Assert-Match $source 'AxisAlignedRectangle\s*\(' 'The manual UI must support axis-aligned rectangle input.'
```
- [ ] **Step 2: 运行 UI 脚本确认新断言失败**
Run:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_ui.ps1
```
Expected: `The manual UI must construct typed manual obstacles.`
- [ ] **Step 3: 实现输入辅助方法与提交逻辑**
`CoarsePathPlanningTest` 中新增:
```csharp
private const int MaximumManualObstacleCount = 20;
private static long _nextManualObstacleSnapshotVersion;
private static IReadOnlyList<ManualCoarsePathObstacle> ReadManualObstacles()
{
int count = ReadBoundedIntegerInput("手动障碍物数量(0-20", 0, MaximumManualObstacleCount);
var obstacles = new List<ManualCoarsePathObstacle>(count);
for (int index = 0; index < count; index++)
{
int kind = ReadBoundedIntegerInput("障碍物 " + (index + 1) + " 类型(1圆形,2矩形)", 1, 2);
double centerX = ReadFiniteInput("障碍物 " + (index + 1) + " 中心 X(世界 mm");
double centerY = ReadFiniteInput("障碍物 " + (index + 1) + " 中心 Y(世界 mm");
if (kind == 1)
{
double radius = ReadPositiveFiniteInput("障碍物 " + (index + 1) + " 半径 rmm");
obstacles.Add(ManualCoarsePathObstacle.Circle(centerX, centerY, radius));
}
else
{
double lengthX = ReadPositiveFiniteInput("障碍物 " + (index + 1) + " X方向长度(mm");
double widthY = ReadPositiveFiniteInput("障碍物 " + (index + 1) + " Y方向宽度(mm");
obstacles.Add(ManualCoarsePathObstacle.AxisAlignedRectangle(centerX, centerY, lengthX, widthY));
}
}
return obstacles;
}
```
`ReadBoundedIntegerInput` 复用 `UI.GetInput` 和当前文化/InvariantCulture 解析,拒绝非整数或超出 `[minimum, maximum]` 的输入;`ReadPositiveFiniteInput``ReadFiniteInput` 返回后拒绝 `<= 0d`。所有失败继续由现有 `ShowInputFailure` 显示。
`Test()` 中的终点读取后调用 `ReadManualObstacles()`。当集合非空时,用 `Interlocked.Increment(ref _nextManualObstacleSnapshotVersion)` 取得版本;集合为空时使用 `0L`。随后替换现有工厂调用:
```csharp
IReadOnlyList<ManualCoarsePathObstacle> obstacles = ReadManualObstacles();
long snapshotVersion = obstacles.Count == 0 ? 0L :
Interlocked.Increment(ref _nextManualObstacleSnapshotVersion);
CoarsePathPlanningJob job = CoarsePathScenarioFactory.CreateManualObstacleDemo(
amrPose.x, amrPose.y, amrPose.th, goalXmm, goalYmm, goalHeadingDeg,
obstacles, snapshotVersion);
CoarsePathPlanningTestRunner.Run("AMR 位姿 + 手动终点 + 手动障碍物", job);
```
保留 `TestStop``Run`、Painter 和底盘禁止边界,不在 UI 内构造 `ManualObstacleSource``PlanningMapRequest` 或搜索对象。
- [ ] **Step 4: 运行 UI 结构检查确认通过**
Run:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_ui.ps1
```
Expected: `Coarse path P1 UI source checks passed.`
### Task 3: README 输入说明与最终回归
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/CoarsePath/README.md`
- Modify: `ClumsyPilot/tests/verify_coarse_path_ui.ps1`
- Verify: `ClumsyPilot/tests/verify_coarse_path_integration.ps1`
**Consumes:** Tasks 1–2 的工厂与 UI 输入契约。
**Produces:** README 中与实际输入顺序一致的手动障碍物说明,以及最终的构建、UI 和集成证据。
- [ ] **Step 1: 为 README 增加失败的 ASCII 文档断言**
`$readmeStructure` 的数组中加入:
```powershell
'CreateManualObstacleDemo',
'ManualCoarsePathObstacle',
'manual-user-input',
'0-20',
'AxisAlignedRectangle',
```
- [ ] **Step 2: 运行 UI 脚本确认 README 断言失败**
Run:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_ui.ps1
```
Expected: `Restructured CoarsePath README must document CreateManualObstacleDemo.`
- [ ] **Step 3: 更新 README 的 P1 手动测试段落**
`## P1 手动测试与可视化(P1 Manual Tests and Visualization` 的“AMR 位姿与手动终点”小节中,替换“空图入口”的单一说明,加入以下事实:
1. 目标输入之后先输入 `0-20` 的障碍物数量;
2. 每项输入 `1` 圆形或 `2` 矩形、中心 X/Y(mm),圆形半径或矩形 X 长度/Y 宽度(mm);
3. 矩形是 `AxisAlignedRectangle`,不支持旋转;尺寸必须为正;
4. `CreateManualObstacleDemo` 将它们包装为 `manual-user-input` 快照,有障碍物时关闭显式空图;
5. 零障碍物才是坐标/取消演示的显式空图;真实作业仍必须提供真实障碍物来源;
6. 地图边界自动覆盖起终点和障碍物完整外轮廓,保留 2000 mm 留白并按 50 mm 对齐;
7. 每次含障碍物提交使用新版本,Painter 仍显示最终 `PlanningGridMap` 占据格而不是原始几何。
- [ ] **Step 4: 运行完整验证**
Run:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_ui.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_coarse_path_integration.ps1
```
Expected: 构建为 `0 个错误`UI 脚本输出 `Coarse path P1 UI source checks passed.`;集成脚本依次输出既有三行 `passed` 消息。
## 自检
- **规格覆盖:** Task 1 覆盖几何类型、非空/空地图、动态边界、版本和无效尺寸;Task 2 覆盖 0–20 输入、形状输入、版本和后台门面边界;Task 3 覆盖 README 与回归。
- **完整性检查:** 每个实现步骤指定了文件、调用签名、验证规则和命令;不引入未命名接口或外部依赖。
- **一致性:** `ManualCoarsePathObstacle``CreateManualObstacleDemo``manual-user-input``obstacleSnapshotVersion` 在所有任务中使用相同名称和单位定义。
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,73 +0,0 @@
# Path smoothing six-figure report implementation plan
> **Execution:** Implement in this workspace without staging or committing. The worktree contains unrelated user changes; touch only the path-smoothing report code, its tests, and its documentation.
**Goal:** Replace each scenario's legacy composite `comparison.svg/png` output with six focused SVG/PNG figures and one CSV, using discrete trajectory samples only (no path-connecting strokes).
**Architecture:** Keep `SmoothingFigureModel` as the immutable source data extracted from comparison results. Add a figure-set layer that selects series, camera bounds, map decorations, axis configuration, and title/legend per output figure. Both renderers consume that same figure definition, so SVG and PNG communicate exactly the same data. The exporter creates all twelve images and the CSV in temporary sibling files, then publishes the completed set and removes legacy composite images.
**Technology:** C#/.NET 10 (`System.Drawing.Common` for PNG); hand-authored SVG; existing PowerShell verification harness and `PathSmoothingPngVerificationHost`.
---
## Task 1: Define six figure views from the common report model
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureModel.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureModelBuilder.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureKind.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureDefinition.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureSetBuilder.cs`
- Modify: `ClumsyPilot/tests/PathSmoothingPngVerificationHost/Program.cs`
1. Extend the C# verification host first with assertions for six ordered figure kinds/stems, selected series, equal-scale world bounds, labels with units, and point-only series metadata. Run the host and confirm it fails because no figure set exists.
2. Remove `DashArray` as a trajectory styling contract from `SmoothingFigureSeries` and legend entries. Preserve source points, status, colors, raw baseline flag, violations, map obstacles, start, goal, and metric rows.
3. Implement immutable figure definitions with fixed stems:
- `01-coarse-path-overview`: raw only; map obstacles and start/goal.
- `02-all-paths-comparison`: raw plus all three smoothing methods; paths and axes/legend only.
- `03-cubic-bspline-overview`, `04-local-cubic-bezier-overview`, `05-piecewise-quintic-overview`: faded raw reference plus the named method; map obstacles and start/goal.
- `06-curvature-comparison`: raw plus all smoother curvature samples.
4. Compute a trajectory-driven world view for each overhead figure: union only visible series points plus its relevant start/goal, add 10% padding with a 0.25 m minimum extent, and expand the smaller world dimension so projected X and Y scale are equal. Do not use full map bounds to zoom out a figure.
5. Include deterministic “nice” axis ticks/labels in metres for overhead figures and arc length/curvature units for the final figure. Preserve failed/infeasible method labels in legends even when their geometry has no points.
6. Rerun the host checks; expected result: it passes definition-level checks while renderer-output checks remain to be updated in Tasks 23.
## Task 2: Render six focused point-cloud figures and publish the set
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingSvgRenderer.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingPngRenderer.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingReportExporter.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingReportExportResult.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/PathSmoothingComparisonDemo.cs`
- Modify: `ClumsyPilot/tests/PathSmoothingPngVerificationHost/Program.cs`
1. Add output-level tests in the verification host for the twelve exact image names, the single CSV, non-empty parseable SVGs, readable 600 dpi PNGs, and absence of temporary files. Run them and confirm the legacy one-image exporter fails these expectations.
2. Refactor the SVG renderer to render one figure definition at a time. Draw axes, ticks, numeric labels, unit labels, legend point swatches, map rectangles (when requested), and start/goal markers. Draw every trajectory and curvature sample as a small marker; do not emit a trajectory `<path>`, polyline, dash array, or line segment.
3. Apply the identical layout semantics in the PNG renderer. Draw points rather than calling a line-drawing API for path samples; give raw reference samples a reduced alpha in individual smoother figures. Keep 600 dpi metadata and the existing required-font behavior.
4. Refactor the exporter to build the six definitions and write twelve temporary image files plus the CSV before publishing. Return collections of SVG and PNG paths with the one CSV path. Delete `comparison.svg/png` after a successful new-set publish; on failure, clean temporary files and retain existing published outputs.
5. Update demo/host call sites from singular `SvgPath`/`PngPath` to the path collections. Run the verification host; expected result: six SVGs, six PNGs, and CSV are all present and valid.
## Task 3: Update external verification, runner documentation, and visually inspect outputs
**Files:**
- Modify: `ClumsyPilot/tests/verify_path_smoothing_svg_csv.ps1`
- Modify: `ClumsyPilot/tests/verify_path_smoothing_png.ps1`
- Modify: `ClumsyPilot/tests/verify_path_smoothing_documentation.ps1`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/README.md`
- Modify: `ClumsyPilot/tests/run_path_smoothing_comparison.ps1` (only if it states/assumes legacy filenames)
- Modify: `docs/superpowers/specs/2026-07-30-path-smoothing-six-figure-report-design.md` (only if implementation exposes a necessary clarified contract)
1. Update PowerShell tests to assert exactly six SVG + six PNG filenames, one CSV, no legacy composite output, required unit labels, marker-based trajectory rendering, and no trajectory dash/line styles. Ensure test source uses safe UTF-8 handling rather than brittle localized literal matching.
2. Update the README to document the six filenames, marker-only semantics, method statuses, coordinate units, and the one-command runner output structure.
3. Run the focused report verification scripts and the PNG host. Regenerate at least one fixture report with the current 0.025 m smoothing output sampling.
4. Render/open representative PNGs for visual QA: raw overview, all-path overlay, each individual smoother, and curvature. Check that curves fill the frame, coordinates/units are legible, all points are visible, individual figures retain context, and there are no joined path lines.
5. Run `dotnet build ClumsyPilot/ClumsyPilot.csproj --no-restore` and relevant contract/service/integration tests. Confirm `git diff --check` and report exact files changed; do not stage or commit.
## Acceptance checklist
- Each scenario produces exactly `01` through `06` SVGs and corresponding PNGs plus one CSV.
- Raw and smoothed trajectories use every sampled point and zero connecting lines.
- Overhead figures use equal X/Y scale, trajectory-focused bounds, numeric axes, and metre units.
- Curvature uses `s (m)` and `κ (m⁻¹)` axes with a complete legend and statuses.
- SVG and PNG agree on the six figure contents, fonts, units, colors, and point-only semantics.
- Every PNG is 600 dpi; failed export leaves no temporary files or partial newly generated set.
@@ -1,249 +0,0 @@
# Local G2 Dailywork Reports 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:**`dailywork_report/` 中交付两份清晰、可追溯的 Local G2 五次 Hermite 中文报告,并为每份提供一个可离线打开的 HTML 可视化附录。
**Architecture:** 报告按“算法事实”和“问题证据”拆分,避免把候选层能力误写成已完成的端到端功能。每份 HTML 均为独立单文件,以内嵌 CSS、SVG 和少量原生 JavaScript 将 Markdown 的核心结构可视化;不引入构建工具或外部资源。
**Tech Stack:** Markdown、HTML5、内嵌 CSS、内嵌 SVG、原生 JavaScript、PowerShell 验证。
## Global Constraints
- 目录根为 `dailywork_report/`,大小写和下划线必须保持一致。
- 建立 `Map_rep/``coarsepath_rep/` 作为空的未来报告入口;本次不填充其业务内容。
- 本次四份正式内容只放在 `dailywork_report/pathsmoothing_rep/`
- 所有文字使用中文;首次出现的英文技术术语必须有中文解释或可由相邻中文短语理解。
- 明确区分:专项测试通过、已复现故障、静态分析确认的逻辑缺口、待验证集成风险。
- 不修改任何路径平滑、地图、粗路径或测试实现。
- HTML 不使用 CDN、网络请求、第三方库、外部图片或构建步骤。
- 工作区已有无关改动;本任务不暂存、不提交。
---
### Task 1: 建立稳定的日报目录边界
**Files:**
- Create: `dailywork_report/Map_rep/.gitkeep`
- Create: `dailywork_report/coarsepath_rep/.gitkeep`
- Create: `dailywork_report/pathsmoothing_rep/`(由后续两个任务创建内容)
**Interfaces:**
- Consumes: 已批准的 `docs/superpowers/specs/2026-07-31-local-g2-dailywork-reports-design.md`
- Produces: 可承载地图、粗路径和路径平滑报告的稳定目录边界。
- [ ] **Step 1: 创建两个空模块目录的保留文件**
使用 `apply_patch` 创建两个空的 `.gitkeep` 文件,内容保持为空:
```text
dailywork_report/Map_rep/.gitkeep
dailywork_report/coarsepath_rep/.gitkeep
```
- [ ] **Step 2: 验证目录边界**
运行:
```powershell
$paths = @(
'dailywork_report/Map_rep/.gitkeep',
'dailywork_report/coarsepath_rep/.gitkeep'
)
foreach ($path in $paths) {
if (-not (Test-Path -LiteralPath $path)) { throw "Missing report directory marker: $path" }
}
Write-Output 'Dailywork report directory checks passed.'
```
预期:输出 `Dailywork report directory checks passed.`
---
### Task 2: 编写 Local G2 算法主报告及流程可视化附录
**Files:**
- Create: `dailywork_report/pathsmoothing_rep/01-local-g2-quintic-hermite-algorithm-report.md`
- Create: `dailywork_report/pathsmoothing_rep/01-local-g2-quintic-hermite-algorithm-visualization.html`
**Interfaces:**
- Consumes: `docs/superpowers/plans/2026-07-30-local-g2-path-presmoothing.md``docs/superpowers/specs/2026-07-30-local-g2-path-presmoothing-design.md``ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/` 下的实现。
- Produces: 一份说明 Local G2 候选层工作方式、输入、输出、约束和当前接入边界的报告;一份与该报告事实一致的可视化附录。
- [ ] **Step 1: 写入 Markdown 主报告的固定章节**
按下列一级标题顺序撰写,并在每节中给出可核对的事实:
```markdown
# Local G2 五次 Hermite 路径平滑算法说明
## 1. 目标、位置与非目标
## 2. 输入:进入算法前必须具备什么
## 3. 模块架构:每个模块负责什么
## 4. 数据流:从粗路径到候选安全路径
## 5. 输出:路径、段、指标、区域报告与状态
## 6. 安全与质量门
## 7. 当前实现进度与边界
```
必须写清:输入为成功的 Hybrid A* 粗路径、路径方向段、地图、车辆参数、`LocalG2QuinticOptions`、取消令牌;距离单位为米、航向为弧度、曲率为 `1/m`。数据流必须依次解释预处理、曲率跳变检测、窗口规划、五次 Hermite 候选构造、局部拼接、统一几何分析、完整车体验证、质量评价。输出必须解释 `SmoothedPathPoint``SmoothedPathSegment`、曲率 `κ`、曲率导数 `dκ/ds`、区域报告和诊断。
“当前实现进度与边界”必须明确:任务 1–7 已达到候选构造与评价层;`LocalG2PreSmoothingPipeline` 与服务分派尚未实现,因此不能声称目前可正式发布完整的 Local G2 SQP 初始路径。
- [ ] **Step 2: 写入单文件 HTML 算法附录**
HTML 必须包含 `<main>`、一个“输入”卡片区、一个按顺序排列的 SVG 流程图、一个“输出”卡片区和一个“当前边界”提示区。SVG 流程节点必须使用以下稳定文字:
```text
Hybrid A* 粗路径
预处理与方向分段
曲率跳变检测
窗口规划
五次 Hermite 候选
拼接与统一几何分析
完整车体安全与质量评价
计划中的发布流水线(尚未接入)
```
用绿色标记已实现的候选层节点,用琥珀色标记“尚未接入”的发布流水线节点。HTML 中的“输入”和“输出”文字必须与 Markdown 报告一致,且页面顶部必须写明“离线静态可视化附录”。
- [ ] **Step 3: 校验主报告与 HTML 的算法事实**
运行:
```powershell
$markdown = Get-Content -Raw -Encoding UTF8 'dailywork_report/pathsmoothing_rep/01-local-g2-quintic-hermite-algorithm-report.md'
$html = Get-Content -Raw -Encoding UTF8 'dailywork_report/pathsmoothing_rep/01-local-g2-quintic-hermite-algorithm-visualization.html'
$markdownTerms = @('输入:进入算法前必须具备什么', '输出:路径、段、指标、区域报告与状态', 'dκ/ds', '尚未实现')
$htmlTerms = @('<main', '<svg', 'Hybrid A* 粗路径', '五次 Hermite 候选', '尚未接入', '离线静态可视化附录')
foreach ($term in $markdownTerms) { if (-not $markdown.Contains($term)) { throw "Algorithm report missing: $term" } }
foreach ($term in $htmlTerms) { if (-not $html.Contains($term)) { throw "Algorithm visualization missing: $term" } }
Write-Output 'Algorithm report checks passed.'
```
预期:输出 `Algorithm report checks passed.`
---
### Task 3: 编写问题分析报告及风险可视化附录
**Files:**
- Create: `dailywork_report/pathsmoothing_rep/02-local-g2-issues-and-next-actions-report.md`
- Create: `dailywork_report/pathsmoothing_rep/02-local-g2-issues-and-next-actions-visualization.html`
**Interfaces:**
- Consumes: 本轮已运行的构建与专项测试结果、`verify_path_smoothing_integration.ps1``RectangleDetour` 失败、`LocalG2WindowPlanner.cs``LocalG2PathSplicer.cs``LocalG2CandidateEvaluator.cs`
- Produces: 三个问题的证据分级、可理解的场景例子、成因、影响与下一步验证/修复措施;一份与主报告一致的风险可视化。
- [ ] **Step 1: 写入 Markdown 问题报告的固定章节和证据分类**
使用下列一级标题:
```markdown
# Local G2 路径平滑问题分析与后续措施
## 1. 阅读本报告前:证据等级说明
## 2. 问题一:RectangleDetour 原始基线复验失败
## 3. 问题二:窗口合并范围与候选长度上限不一致
## 4. 问题三:连续处理多个区域时的弧长定位风险
## 5. 进入任务 8 前的行动顺序与验收条件
```
每个问题必须按“现象 → 生动例子 → 为什么发生 → 影响 → 证据等级 → 下一步措施 → 验收条件”顺序写作。
问题一必须标记为“已复现故障”,引用如下实际结果,不增添未验证的数值原因:
```text
Raw baseline RectangleDetour must remain a feasible, verified copy of the coarse path.
Expected=Success Actual=InvalidInput
```
问题二必须标记为“静态分析确认的逻辑缺口”,说明默认 `0.8 m` 候选总长度上限与 `event ± 0.8 m` 合并包络的差异;使用“相距 1.0 m 的两个弯被合并后无法装进 0.8 m 窗口”的例子。
问题三必须标记为“待验证集成风险”,说明一次拼接会重算局部弧长,后续区域仍可能使用旧起止弧长;不得写成已经复现的线上故障。
- [ ] **Step 2: 写入单文件 HTML 问题附录**
HTML 顶部必须显示三种证据徽章:`已复现故障``静态分析确认``待验证风险`。页面主体必须提供三个编号问题卡片,每张卡片含“现象”“例子”“成因”“措施”四个短区块。使用内嵌 SVG 表达:
```text
问题一:粗路径成功 → 原始基线复验 InvalidInput → G2 尚未开始
问题二:两个相距 1.0 m 的事件 → 被合并 → 0.8 m 窗口无候选
问题三:先平滑区域 A → 弧长重算 → 区域 B 使用旧坐标
```
页面末尾必须列出行动优先级:先定位问题一、再为问题二添加窗口边界回归、最后为问题三添加双区域顺序替换回归。
- [ ] **Step 3: 校验问题分类、现象和行动顺序**
运行:
```powershell
$markdown = Get-Content -Raw -Encoding UTF8 'dailywork_report/pathsmoothing_rep/02-local-g2-issues-and-next-actions-report.md'
$html = Get-Content -Raw -Encoding UTF8 'dailywork_report/pathsmoothing_rep/02-local-g2-issues-and-next-actions-visualization.html'
$markdownTerms = @('已复现故障', 'Expected=Success Actual=InvalidInput', '静态分析确认的逻辑缺口', '待验证集成风险', '相距 1.0 m', '任务 8')
$htmlTerms = @('<main', '<svg', '已复现故障', '静态分析确认', '待验证风险', '区域 A', '区域 B')
foreach ($term in $markdownTerms) { if (-not $markdown.Contains($term)) { throw "Issue report missing: $term" } }
foreach ($term in $htmlTerms) { if (-not $html.Contains($term)) { throw "Issue visualization missing: $term" } }
Write-Output 'Issue report checks passed.'
```
预期:输出 `Issue report checks passed.`
---
### Task 4: 做离线交付检查和可视化人工审阅
**Files:**
- Verify: `dailywork_report/Map_rep/.gitkeep`
- Verify: `dailywork_report/coarsepath_rep/.gitkeep`
- Verify: `dailywork_report/pathsmoothing_rep/01-local-g2-quintic-hermite-algorithm-report.md`
- Verify: `dailywork_report/pathsmoothing_rep/01-local-g2-quintic-hermite-algorithm-visualization.html`
- Verify: `dailywork_report/pathsmoothing_rep/02-local-g2-issues-and-next-actions-report.md`
- Verify: `dailywork_report/pathsmoothing_rep/02-local-g2-issues-and-next-actions-visualization.html`
**Interfaces:**
- Consumes: 前三项任务的六个文件。
- Produces: 可离线打开、层级明确、相互一致的报告包。
- [ ] **Step 1: 验证完整文件集与禁止外部依赖**
运行:
```powershell
$files = @(
'dailywork_report/Map_rep/.gitkeep',
'dailywork_report/coarsepath_rep/.gitkeep',
'dailywork_report/pathsmoothing_rep/01-local-g2-quintic-hermite-algorithm-report.md',
'dailywork_report/pathsmoothing_rep/01-local-g2-quintic-hermite-algorithm-visualization.html',
'dailywork_report/pathsmoothing_rep/02-local-g2-issues-and-next-actions-report.md',
'dailywork_report/pathsmoothing_rep/02-local-g2-issues-and-next-actions-visualization.html'
)
foreach ($file in $files) { if (-not (Test-Path -LiteralPath $file)) { throw "Missing deliverable: $file" } }
$html = @(
Get-Content -Raw -Encoding UTF8 'dailywork_report/pathsmoothing_rep/01-local-g2-quintic-hermite-algorithm-visualization.html'
Get-Content -Raw -Encoding UTF8 'dailywork_report/pathsmoothing_rep/02-local-g2-issues-and-next-actions-visualization.html'
) -join "`n"
if ($html -match 'https?://' -or $html -match '<script[^>]+src=') { throw 'HTML appendices must be self-contained.' }
Write-Output 'Dailywork report package checks passed.'
```
预期:输出 `Dailywork report package checks passed.`
- [ ] **Step 2: 在本地浏览器进行人工可读性审阅**
依次打开两个 HTML 文件,检查以下具体条件:
```text
算法附录:流程从左到右或从上到下可顺序阅读;绿色已实现节点与琥珀色未接入节点容易区分;输入和输出没有被流程图遮挡。
问题附录:三类证据徽章颜色和文字均可分辨;三个问题卡片的例子、成因、措施没有被截断;行动顺序位于页面末尾且与 Markdown 一致。
```
- [ ] **Step 3: 检查工作区改动范围**
运行:
```powershell
git diff --check
git status --short -- dailywork_report docs/superpowers/specs/2026-07-31-local-g2-dailywork-reports-design.md docs/superpowers/plans/2026-07-31-local-g2-dailywork-reports.md
```
预期:无空白错误;改动只包含本计划的设计、计划和日报交付文件。
@@ -1,404 +0,0 @@
# Local G2 Interactive Visualization Redesign 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:** 将两份 Local G2 HTML 报告附录从流程卡片重做为可逐步查看路径变化、并能对照错误与正确预期的离线交互式可视化。
**Architecture:** 两页均继续为独立 HTML 文件。算法页使用一个固定坐标系的 SVG 和六个可切换状态,曲线由内嵌 JavaScript 的五次 Hermite 基函数计算并绘制;问题页使用三个可切换的 SVG 场景,每个场景同时呈现“实际发生 / 正确应有 / 差异原因”。所有教学几何固定标为典型示例,真实测试结论只以已知状态与原始文本呈现。
**Tech Stack:** HTML5、CSS、内嵌 SVG、原生 JavaScript、PowerShell 静态验证。
## Global Constraints
- 仅在 `dailywork_report/pathsmoothing_rep/` 下两个指定 HTML 路径重建内容;它们在本任务基线提交中尚未跟踪,因此 Git 可将首次纳入版本控制的重建页面显示为新增文件。不修改路径平滑算法、测试、地图、粗路径或两份 Markdown 报告的事实内容。
- 不使用任何外部资源:不使用 CDN、网络请求、外部图片、外部字体、第三方库或构建步骤。
- 算法页必须具备六个可访问步骤:粗路径、局部窗口、五次 Hermite、局部替换、曲率—弧长、安全与质量门。
- 问题页必须具备三个可访问问题场景;每个场景同时可见“实际发生”“正确应有”“差异原因”。
- 所有典型坐标、曲率图形和车辆示意均必须明确标注为机制解释,不得暗示为项目运行时实测结果。
- `RectangleDetour` 只陈述已复现的 `Success → InvalidInput`;失败点仍标记“待定位”,不得画出伪造的具体坏样本或根因。
- 用户已授权整体删除并从头重建两份旧 HTML;工作区存在无关改动,每个任务的提交只能包含其对应的一份 HTML,不得带入其他文件。
## File Structure
| 文件 | 职责 |
|---|---|
| `dailywork_report/pathsmoothing_rep/01-local-g2-quintic-hermite-algorithm-visualization.html` | 六步 Local G2 路径几何演示、Hermite 曲线生成、键盘/按钮步骤导航与安全质量门示意。 |
| `dailywork_report/pathsmoothing_rep/02-local-g2-issues-and-next-actions-visualization.html` | 三个问题的实际/正确对照、场景切换与证据边界标注。 |
---
### Task 1: 重做算法页为六步路径几何演示
**Files:**
- Modify: `dailywork_report/pathsmoothing_rep/01-local-g2-quintic-hermite-algorithm-visualization.html`
**Interfaces:**
- Consumes: 无运行时数据;仅使用固定、标为典型示例的二维点和已确认的 G2 术语/门限。
- Produces: `setStep(index)``buildQuinticPath()``stepButtons``#algorithm-diagram``#step-title``#step-description``#step-status``#prev-step``#next-step`,供 HTML 初次渲染、按钮和左右方向键共用。
- [ ] **Step 1: 先运行会失败的结构验证**
Run:
```powershell
$file = 'dailywork_report\pathsmoothing_rep\01-local-g2-quintic-hermite-algorithm-visualization.html'
$html = Get-Content -Raw -Encoding UTF8 $file
$required = @('id="algorithm-diagram"', 'data-step="0"', 'data-step="5"', 'id="prev-step"', 'id="next-step"', 'function buildQuinticPath', 'function setStep')
$missing = @($required | Where-Object { -not $html.Contains($_) })
if ($missing.Count -gt 0) { throw "Algorithm visual contract missing: $($missing -join ', ')" }
```
Expected: FAIL because the current static flow page has no interactive step contract.
- [ ] **Step 2: 整体删除旧卡片页面并从头重建,让主 SVG 成为视觉中心**
Use `apply_patch` to整体删除当前文件内容并添加一个响应式新文档,结构如下:
```html
<main id="local-g2-algorithm-demo" data-step="0">
<header>…候选层已实现、正式发布流水线尚未接入…</header>
<p class="evidence-note">典型示例:用于解释机制,不代表某次测试的精确坐标。</p>
<nav class="stepper" aria-label="Local G2 平滑步骤">
<button type="button" class="step-button" data-step="0" aria-pressed="true">0 粗路径</button>
<button type="button" class="step-button" data-step="1" aria-pressed="false">1 局部窗口</button>
<button type="button" class="step-button" data-step="2" aria-pressed="false">2 Hermite 约束</button>
<button type="button" class="step-button" data-step="3" aria-pressed="false">3 局部替换</button>
<button type="button" class="step-button" data-step="4" aria-pressed="false">4 连续性效果</button>
<button type="button" class="step-button" data-step="5" aria-pressed="false">5 安全质量门</button>
</nav>
<section class="diagram-shell" aria-live="polite">
<div class="step-copy"><span id="step-status"></span><h2 id="step-title"></h2><p id="step-description"></p></div>
<svg id="algorithm-diagram" viewBox="0 0 1200 720" role="img" aria-labelledby="algorithm-svg-title algorithm-svg-desc">
<title id="algorithm-svg-title">Local G2 五次 Hermite 局部路径平滑步骤</title>
<desc id="algorithm-svg-desc">典型粗路径在局部曲率跳变处被五次 Hermite 曲线安全替换的六步示意。</desc>
<!-- 始终可见的坐标、粗路径和步骤图层 -->
</svg>
</section>
<div class="step-controls"><button id="prev-step" type="button">上一步</button><button id="next-step" type="button">下一步</button></div>
</main>
```
CSS requirements:
- `svg { width: 100%; height: auto; }`,不再使用 `min-width` 和横向滚动容器;窄屏按 `viewBox` 等比缩放。
- 用同一组语义颜色稳定表达:灰色原始粗路径、蓝色窗口/约束、绿色已接受候选、琥珀色发布边界、红色拒绝或阻断;同时配合实线/虚线、文字和符号。
- `.scene-layer` 默认淡出,`[data-step="N"] .scene-N` 显示;`prefers-reduced-motion: reduce` 时禁用转场。
- 不设置固定视口高度、不设置内部滚动,并保证按钮触摸目标和焦点状态清晰。
- [ ] **Step 3: 用真实五次 Hermite 基函数绘制典型候选曲线**
In the page script, define the fixed example endpoints and use the six quintic Hermite basis functions—not an SVG cubic Bézier substitute—to sample the visual candidate:
```javascript
const hermite = {
p0: { x: 290, y: 462 }, p1: { x: 690, y: 258 },
d0: { x: 180, y: 0 }, d1: { x: 210, y: -135 },
a0: { x: 0, y: -18 }, a1: { x: 22, y: -12 }
};
function quinticBasis(t) {
const t2 = t * t, t3 = t2 * t, t4 = t3 * t, t5 = t4 * t;
return [
1 - 10 * t3 + 15 * t4 - 6 * t5,
t - 6 * t3 + 8 * t4 - 3 * t5,
0.5 * (t2 - 3 * t3 + 3 * t4 - t5),
10 * t3 - 15 * t4 + 6 * t5,
-4 * t3 + 7 * t4 - 3 * t5,
0.5 * (t3 - 2 * t4 + t5)
];
}
function buildQuinticPath() {
const points = [];
for (let i = 0; i <= 48; i += 1) {
const [h00, h10, h20, h01, h11, h21] = quinticBasis(i / 48);
points.push({
x: h00 * hermite.p0.x + h10 * hermite.d0.x + h20 * hermite.a0.x + h01 * hermite.p1.x + h11 * hermite.d1.x + h21 * hermite.a1.x,
y: h00 * hermite.p0.y + h10 * hermite.d0.y + h20 * hermite.a0.y + h01 * hermite.p1.y + h11 * hermite.d1.y + h21 * hermite.a1.y
});
}
return points.map((point, index) => `${index === 0 ? 'M' : 'L'} ${point.x.toFixed(1)} ${point.y.toFixed(1)}`).join(' ');
}
```
Set the generated string on `#quintic-candidate`. Draw a separate raw polyline that shares the same window endpoints but has a visible heading/curvature break in its interior. Add persistent labels for start, end, direction, window boundary and `κ` jump; do not attach real-world units or claim these fixed coordinates are measured data.
- [ ] **Step 4: 实现六个可读状态的 SVG 图层**
Create six SVG groups, each carrying both `scene-layer` and `scene-0` through `scene-5` as appropriate. They must communicate these exact visual effects:
```text
scene-0: 原始离散点、方向箭头、突变点,候选曲线隐藏。
scene-1: 左右窗口边界和淡蓝色局部带高亮,其余粗路径降低不透明度。
scene-2: 两端切向箭头、二阶趋势弧线、虚线 quintic-candidate 可见。
scene-3: 灰色原折线与绿色候选曲线叠加,接缝用“替换开始/结束”标记。
scene-4: 上方替换后路径;下方 κ—s 趋势示意显示原始跳变与候选连续过渡,并标“趋势示意,非实测数据”。
scene-5: 三个车辆轮廓沿候选曲线放置;净空带、通过标记和“碰撞 / 净空 / 曲率 / 偏移”四个质量门可见。
```
Use `<path>`, `<circle>`, `<line>`, `<text>`, `<marker>` and simple `<g transform>` vehicle rectangles; do not use raster images. Put the existing “正式发布流水线尚未接入”的事实边界 below the visual, outside the six state layers.
- [ ] **Step 5: 接入状态更新与键盘操作**
Use one state function and no inline event handlers:
```javascript
const steps = [
['0 / 5', '原始 Hybrid A* 粗路径', '可行离散路径在局部接口处仍可能有曲率跳变。'],
['1 / 5', '检测并框定局部窗口', '只处理跳变附近,不重新搜索整条路径。'],
['2 / 5', '由端点约束构造五次 Hermite 候选', '位置、切向和曲率趋势共同确定局部曲线。'],
['3 / 5', '替换窗口内部的原始几何', '窗口外路径保持不变,接缝需要连续。'],
['4 / 5', '观察曲率—弧长连续性', '目标是消除接口处的趋势跳变,而不是只让外形更圆。'],
['5 / 5', '经安全与质量门决定接受或回退', '候选必须同时满足车体安全、净空、曲率和偏移约束。']
];
function setStep(index) {
const next = Math.max(0, Math.min(steps.length - 1, index));
const [status, title, description] = steps[next];
document.getElementById('local-g2-algorithm-demo').dataset.step = String(next);
document.getElementById('step-status').textContent = `步骤 ${status}`;
document.getElementById('step-title').textContent = title;
document.getElementById('step-description').textContent = description;
stepButtons.forEach((button) => button.setAttribute('aria-pressed', String(Number(button.dataset.step) === next)));
document.getElementById('prev-step').disabled = next === 0;
document.getElementById('next-step').disabled = next === steps.length - 1;
}
```
Declare `stepButtons` before `setStep`, register each button, register `#prev-step`/`#next-step`, handle only unmodified `ArrowLeft` and `ArrowRight` key presses, then call `setStep(0)`. Do not override keyboard interaction when the event target is a form control.
- [ ] **Step 6: 重新运行算法页验证,确认由失败转为通过**
Run the Step 1 command again, then run:
```powershell
$file = 'dailywork_report\pathsmoothing_rep\01-local-g2-quintic-hermite-algorithm-visualization.html'
$html = Get-Content -Raw -Encoding UTF8 $file
$terms = @('典型示例:用于解释机制', '曲率—弧长', '趋势示意,非实测数据', '完整车体安全', '正式发布流水线尚未接入')
$missing = @($terms | Where-Object { -not $html.Contains($_) })
if ($missing.Count -gt 0) { throw "Algorithm visual content missing: $($missing -join ', ')" }
Write-Output 'Algorithm interactive visualization checks passed.'
```
Expected: `Algorithm interactive visualization checks passed.`
---
### Task 2: 重做问题页为错误与正确预期的几何对照
**Files:**
- Modify: `dailywork_report/pathsmoothing_rep/02-local-g2-issues-and-next-actions-visualization.html`
**Interfaces:**
- Consumes: 已确认的三类证据、`RectangleDetour` 端到端输出、窗口约束数值和弧长陈旧风险的事实边界。
- Produces: `selectIssue(issueId)``issueButtons``#issue-visual``#issue-evidence``#issue-title``#issue-actual``#issue-expected``#issue-cause``#issue-action``#prev-issue``#next-issue`
- [ ] **Step 1: 先运行会失败的结构验证**
Run:
```powershell
$file = 'dailywork_report\pathsmoothing_rep\02-local-g2-issues-and-next-actions-visualization.html'
$html = Get-Content -Raw -Encoding UTF8 $file
$required = @('id="issue-visual"', 'data-issue="baseline"', 'data-issue="window"', 'data-issue="arclength"', 'function selectIssue', 'id="issue-actual"', 'id="issue-expected"', 'id="issue-cause"')
$missing = @($required | Where-Object { -not $html.Contains($_) })
if ($missing.Count -gt 0) { throw "Issue visual contract missing: $($missing -join ', ')" }
```
Expected: FAIL because the current page has only static issue cards and one causal flow diagram.
- [ ] **Step 2: 整体删除旧问题卡片页面并构建共享选择器与三栏事实说明**
Use `apply_patch` to整体删除当前文件内容并添加一个新文档,结构如下:
```html
<main id="local-g2-issue-demo" data-issue="baseline">
<header>…三种证据等级…</header>
<nav class="issue-selector" aria-label="选择要查看的 Local G2 问题">
<button type="button" class="issue-button" data-issue="baseline" aria-pressed="true">问题一:基线失败</button>
<button type="button" class="issue-button" data-issue="window" aria-pressed="false">问题二:窗口约束</button>
<button type="button" class="issue-button" data-issue="arclength" aria-pressed="false">问题三:弧长错位</button>
</nav>
<section class="issue-stage" aria-live="polite">
<div class="issue-copy"><span id="issue-evidence"></span><h2 id="issue-title"></h2></div>
<svg id="issue-visual" viewBox="0 0 1200 700" role="img" aria-labelledby="issue-svg-title issue-svg-desc"></svg>
<div class="compare-copy">
<article><h3>实际发生</h3><p id="issue-actual"></p></article>
<article><h3>正确应有</h3><p id="issue-expected"></p></article>
<article><h3>差异原因</h3><p id="issue-cause"></p></article>
</div>
<p class="next-action"><strong>下一步:</strong><span id="issue-action"></span></p>
</section>
<div class="issue-controls"><button id="prev-issue" type="button">上一个问题</button><button id="next-issue" type="button">下一个问题</button></div>
</main>
```
Use a single shared scale and distinct, labelled SVG lanes rather than three textual cards. Keep the priority/action order in a compact section below the interactive visual, not above it.
- [ ] **Step 3: 画出三个“实际 / 正确”对照场景,并保持证据边界**
Create `.issue-scene` SVG groups and make the selected group visible using `[data-issue="…"]` CSS. Each scene must implement these marks:
```text
baseline:
- 上方:概念性 RectangleDetour 绕障路径、障碍物、灰色原始路径;红色问号标“首次失败样本待定位”。
- 中间:实际链路 Hybrid A* Success → 原始基线复验 InvalidInput ⛔ → G2 候选未开始。
- 下方:正确链路 Hybrid A* Success → 原始基线复验 Success → G2 候选评价。
- 固定脚注:概念性几何,不代表尚未定位的实际坏样本。
window:
- 上方:同一条典型路径上的事件 A/B 和弧长标尺,中心距离直接标为 1.0 m。
- 中间左侧“实际”:两个 ±0.8 m 影响范围重叠后合并,合并总区间标“> 0.8 m”,红色叉号和“无候选”。
- 中间右侧“正确”:可行的拆分窗口或一致的长度策略,绿色窗口 A/B 和“可评价候选”。
- 下方:明确标“静态分析确认的逻辑缺口”。
arclength:
- 上方:替换前路径的 A、B 两个局部窗口和原始弧长标尺。
- 下方左侧“实际风险”:A 替换后路径长度改变,B-old 仍按旧弧长落在偏早位置;用虚线箭头表达旧映射。
- 下方右侧“正确”:重算/稳定锚点后 B-new 落在预期局部;用实线箭头表达新映射。
- 固定脚注:待正式多区域流水线接入后通过回归测试验证。
```
Do not draw a red collision marker or concrete bad curvature sample in `baseline`; only the question marker is allowed there. Pair every colored status with text (`实际`, `正确`, `待定位`, `无候选`, `重定位`) and a different line style or marker.
- [ ] **Step 4: 接入问题状态数据、导航和可访问性**
In the page script, use immutable descriptive data and one update function:
```javascript
const issues = {
baseline: {
evidence: '已复现故障 · RectangleDetour',
title: '原始基线在 Local G2 开始前被拒绝',
actual: 'Hybrid A* 粗路径规划成功,但原始基线统一复验返回 InvalidInput;候选生成没有开始。',
expected: '同一条成功规划的粗路径应先作为可行基线通过统一复验,再进入候选评价。',
cause: '首次非法数值或超限曲率样本尚未定位;不能把该失败归因于 G2 候选。',
action: '记录首次异常的方向段、样本、曲率、净空和验证结果,在不放松安全门的前提下定位源头。'
},
window: {
evidence: '静态分析确认的逻辑缺口',
title: '合并范围比可用候选窗口更宽',
actual: '相距 1.0 m 的事件被 ±0.8 m 范围合并,但候选总长度不能超过 0.8 m,结果没有候选。',
expected: '窗口合并和最大总长度应采用一致语义,或在不满足时拆分为可行局部窗口。',
cause: '影响范围的合并规则与候选总长度约束没有共同的可行性判断。',
action: '加入 1.0 m 间距回归,统一窗口长度语义并验证候选仍可生成。'
},
arclength: {
evidence: '待验证集成风险',
title: '区域 A 替换后,区域 B 的弧长坐标可能陈旧',
actual: 'A 拼接并重算弧长后,B 若仍使用替换前坐标,可能指向错误局部。',
expected: '处理 B 前应按当前路径重定位,或由稳定锚点映射恢复其原始语义位置。',
cause: '候选记录的原始弧长与每次拼接后重新计算的当前弧长处于不同坐标系。',
action: '构造 A 改变长度、B 仍准确定位的双区域回归,再接入正式流水线。'
}
};
function selectIssue(issueId) {
const issue = issues[issueId];
if (!issue) return;
document.getElementById('local-g2-issue-demo').dataset.issue = issueId;
document.getElementById('issue-evidence').textContent = issue.evidence;
document.getElementById('issue-title').textContent = issue.title;
document.getElementById('issue-actual').textContent = issue.actual;
document.getElementById('issue-expected').textContent = issue.expected;
document.getElementById('issue-cause').textContent = issue.cause;
document.getElementById('issue-action').textContent = issue.action;
issueButtons.forEach((button) => button.setAttribute('aria-pressed', String(button.dataset.issue === issueId)));
}
```
Declare an ordered `issueIds = ['baseline', 'window', 'arclength']`, implement previous/next by index, register button clicks, then call `selectIssue('baseline')`. Make the initial baseline state useful without JavaScript by placing its copy in the HTML before the script runs, then allow JavaScript to overwrite it with the same factually equivalent text.
- [ ] **Step 5: 重新运行问题页验证,确认由失败转为通过**
Run the Step 1 command again, then run:
```powershell
$file = 'dailywork_report\pathsmoothing_rep\02-local-g2-issues-and-next-actions-visualization.html'
$html = Get-Content -Raw -Encoding UTF8 $file
$terms = @('Success → InvalidInput', '首次失败样本待定位', '1.0 m', '±0.8 m', '> 0.8 m', '弧长重算', '实际发生', '正确应有', '差异原因')
$missing = @($terms | Where-Object { -not $html.Contains($_) })
if ($missing.Count -gt 0) { throw "Issue visual content missing: $($missing -join ', ')" }
Write-Output 'Issue interactive visualization checks passed.'
```
Expected: `Issue interactive visualization checks passed.`
---
### Task 3: 离线完整性、交互契约和可视化验收
**Files:**
- Verify: `dailywork_report/pathsmoothing_rep/01-local-g2-quintic-hermite-algorithm-visualization.html`
- Verify: `dailywork_report/pathsmoothing_rep/02-local-g2-issues-and-next-actions-visualization.html`
**Interfaces:**
- Consumes: 两页已实现的 DOM id、数据属性、函数名和离线资源限制。
- Produces: 可复查的 PowerShell 验证输出,以及在浏览器可用时的人工交互验收结论。
- [ ] **Step 1: 验证离线性、文件结构和静态交互契约**
Run:
```powershell
$files = @(
'dailywork_report\pathsmoothing_rep\01-local-g2-quintic-hermite-algorithm-visualization.html',
'dailywork_report\pathsmoothing_rep\02-local-g2-issues-and-next-actions-visualization.html'
)
$contracts = @{
$files[0] = @('<!doctype html>', '<svg', '<script>', 'function buildQuinticPath', 'function setStep', 'id="algorithm-diagram"', 'data-step="5"')
$files[1] = @('<!doctype html>', '<svg', '<script>', 'function selectIssue', 'id="issue-visual"', 'data-issue="arclength"', 'id="issue-cause"')
}
foreach ($file in $files) {
$html = Get-Content -Raw -Encoding UTF8 $file
$missing = @($contracts[$file] | Where-Object { -not $html.Contains($_) })
if ($missing.Count -gt 0) { throw "$file missing: $($missing -join ', ')" }
if ($html -match 'https?://|<script[^>]+\bsrc\s*=|<img[^>]+\bsrc\s*=') { throw "$file must not depend on external resources." }
if (($html -split '<svg').Count -lt 2) { throw "$file must contain a main SVG." }
}
Write-Output 'Offline HTML and interaction contracts passed.'
```
Expected: `Offline HTML and interaction contracts passed.`
- [ ] **Step 2: 验证 JavaScript 所查询的 DOM 节点均存在**
Run:
```powershell
$checks = @{
'dailywork_report\pathsmoothing_rep\01-local-g2-quintic-hermite-algorithm-visualization.html' = @('local-g2-algorithm-demo','step-status','step-title','step-description','prev-step','next-step','quintic-candidate')
'dailywork_report\pathsmoothing_rep\02-local-g2-issues-and-next-actions-visualization.html' = @('local-g2-issue-demo','issue-evidence','issue-title','issue-actual','issue-expected','issue-cause','issue-action','prev-issue','next-issue')
}
foreach ($entry in $checks.GetEnumerator()) {
$html = Get-Content -Raw -Encoding UTF8 $entry.Key
$missing = @($entry.Value | Where-Object { -not $html.Contains("id=`"$_`"") })
if ($missing.Count -gt 0) { throw "$($entry.Key) queried ids missing: $($missing -join ', ')" }
}
Write-Output 'DOM query targets passed.'
```
Expected: `DOM query targets passed.`
- [ ] **Step 3: 进行浏览器交互和窄屏人工验收,或如实记录不可用状态**
When an in-app browser is available, open each local HTML and verify:
```text
算法页:初始第 0 步可见;连续点六个步骤均可切换;上/下一步禁用状态正确;左右键切换;窄宽度下标签、曲线和按钮不重叠。
问题页:三个问题均可切换;每个场景同时可看到实际发生、正确应有、差异原因;问题一没有伪造坏样本;窄宽度下文本可读。
```
If no browser is available, do not substitute unapproved browser tooling or claim visual QA passed. Record that offline/static checks pass but browser-based visual inspection remains unavailable.
- [ ] **Step 4: 检查改动范围与空白字符**
Run:
```powershell
git diff --check -- dailywork_report/pathsmoothing_rep/01-local-g2-quintic-hermite-algorithm-visualization.html dailywork_report/pathsmoothing_rep/02-local-g2-issues-and-next-actions-visualization.html
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
git status --short -- dailywork_report/pathsmoothing_rep/
```
Expected: no whitespace errors; each任务提交仅包含其对应 HTML 文件,工作区状态不出现由本次工作带入的其他文件。
File diff suppressed because it is too large Load Diff
@@ -1,971 +0,0 @@
# Local G2 Soft-Anchor Candidate Recovery 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:** Prove and implement a bounded soft-anchor Local G2 candidate family that makes the real `SingleTurn` route pass the unchanged safety/quality evaluator, then finish the dedicated Task 8 service pipeline.
**Architecture:** First preserve the already-proven evaluator seam correction, then make window generation cover representative total lengths before asymmetric variants consume the budget. A disposable-copy feasibility gate evaluates the exact soft-anchor family before the shared production builder changes; only a successful non-zero-offset candidate permits the builder TDD and Task 8 service integration to continue.
**Tech Stack:** C# 10, .NET Standard 2.0, PowerShell reflection verification, existing `PathGeometryAnalyzer`, `SmoothedPathValidator`, full-body collision checks, Git.
## Global Constraints
- `MinimumWindowLengthMeters = 0.20`, `PreferredWindowLengthMeters = 0.50`, and `MaximumWindowLengthMeters = 0.80` are total left-plus-right lengths.
- Path start, path end, gear switches, and outer window endpoints remain hard position anchors.
- Only an internal primitive-boundary position may move; its vehicle heading and distance-weighted shared curvature remain hard boundary values.
- `softOffset = min(0.05 m, 0.5 × MaximumDeviationMeters)`; if it is at most `1e-10 m`, emit no non-zero profile.
- Per selected representative window, profile order is exact anchor, `+softOffset`, `-softOffset`, all with derivative multiplier `1.00`.
- The configured candidate limit remains authoritative and is capped by the existing hard maximum of 12.
- Do not modify vehicle curvature limits, the validator `1e-6` tolerance, raw curvature-range tolerance, collision/clearance gates, `0.10 m` default maximum deviation, 20 percent peak-gradient improvement, or 2 percent variation-cost tolerance.
- Do not change Hybrid A*, SQP, legacy smoothing algorithms, or `PathSmoothingComparisonRequest.DefaultMethods`.
- Detector report order remains immutable ascending order; processing uses `LocalG2RegionWorkOrder`.
- Use TDD. Do not change `LocalG2CandidateBuilder` in the shared tree until the disposable feasibility gate finds an accepted non-zero-offset candidate under the unchanged evaluator.
- Preserve unrelated dirty worktree files. Stage only the exact files listed by each task.
- The untracked `ClumsyPilot/ParkrobTrajplanner/auto_avoidance` tree currently requires unavailable external assemblies. Never edit or delete it; when it blocks the normal build, verify this work from a disposable copy that excludes only that directory.
---
## File Structure
- `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/LocalG2CandidateEvaluator.cs`
- Preserve the analyzer-compatible `1e-10 m` boundary-point de-duplication already proven by RED/GREEN evidence.
- `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/LocalG2WindowPlanner.cs`
- Expose preferred/minimum/maximum balanced targets before asymmetric variants and keep deterministic target coverage.
- `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/LocalG2CandidateBuilder.cs`
- Select representative windows, apply the bounded normal soft-anchor profiles, and emit at most 12 deterministic geometries.
- `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/LocalG2PreSmoothingPipeline.cs`
- Consume work order, evaluate candidates, roll back failed global combinations, and publish raw baseline when no accepted replacement survives.
- `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Facade/PathSmoothingService.cs`
- Dispatch only `LocalG2Quintic` to the dedicated pipeline while preserving legacy routes.
- `ClumsyPilot/tests/verify_path_smoothing_local_g2_detection.ps1`
- Prove target coverage, total-window semantics, deterministic order, and configured caps.
- `ClumsyPilot/tests/verify_path_smoothing_local_g2_candidates.ps1`
- Prove the evaluator seam, feasibility tuple, soft-anchor geometry, candidate budget, G2, direction, and determinism.
- `ClumsyPilot/tests/verify_path_smoothing_local_g2_integration.ps1`
- Prove public service statuses, safe complete-path publication, two-region order, rollback, and cancellation.
- `ClumsyPilot/tests/verify_path_smoothing_service.ps1`
- Prove dedicated Local G2 dispatch without disturbing legacy method registration.
---
### Task 1: Preserve the evaluator window-boundary correction
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/LocalG2CandidateEvaluator.cs`
- Modify: `ClumsyPilot/tests/verify_path_smoothing_local_g2_candidates.ps1`
- Evidence: `.superpowers/sdd/local-g2-task-8-integration-report.md`
**Interfaces:**
- Consumes: interpolated window start/end points and source points from one `PreparedDirectionSegment`.
- Produces: `TryExtractWindow(...)` output with exact interpolated endpoints and no consecutive positions within `1e-10 m`.
- [ ] **Step 1: Separate the completed evaluator regression from the still-RED candidate-family assertion**
Keep the real SingleTurn seam and this assertion:
```powershell
Assert-Equal 0 $duplicateFailures.Count `
'SingleTurn builder candidates must not fail evaluator window analysis due to duplicate or degenerate points.'
```
Remove only the current `$acceptedCandidates` collection and the assertion requiring an accepted candidate. Task 3 reintroduces the accepted non-zero-offset requirement after the feasibility gate.
- [ ] **Step 2: Confirm the recorded RED/GREEN evidence is complete**
Read `.superpowers/sdd/local-g2-task-8-integration-report.md` and require both entries:
```text
RED: Expected=0 Actual=6 duplicate/degenerate evaluator failures
GREEN: Path smoothing Local G2 candidate checks passed.
```
The production correction must remain exactly:
```csharp
private const double WindowPointToleranceMeters = 1e-10d;
private static bool SamePosition(SmoothingPoint2D left, SmoothingPoint2D right)
{
if (left == null || right == null) return false;
double x = right.X - left.X;
double y = right.Y - left.Y;
return x * x + y * y <=
WindowPointToleranceMeters * WindowPointToleranceMeters;
}
```
and `TryExtractWindow` must preserve the exact interpolated end:
```csharp
if (point.ArcLength > startArcLength &&
point.ArcLength < endArcLength &&
!SamePosition(points[points.Count - 1], point))
{
points.Add(point);
}
if (SamePosition(points[points.Count - 1], end))
points[points.Count - 1] = end;
else
points.Add(end);
```
- [ ] **Step 3: Build and run the focused GREEN check**
Run the normal commands first:
```powershell
dotnet build ClumsyPilot/ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File ClumsyPilot/tests/verify_path_smoothing_local_g2_candidates.ps1
```
If the build fails only because of untracked `auto_avoidance` dependencies, create a disposable verification copy:
```powershell
$verificationRoot = Join-Path $env:TEMP ('parkingrobot-local-g2-' + [Guid]::NewGuid().ToString('N'))
New-Item -ItemType Directory -Path $verificationRoot | Out-Null
robocopy 'ClumsyPilot' (Join-Path $verificationRoot 'ClumsyPilot') /E /XD 'ClumsyPilot\ParkrobTrajplanner\auto_avoidance'
if ($LASTEXITCODE -gt 7) { throw "robocopy failed with $LASTEXITCODE" }
dotnet build (Join-Path $verificationRoot 'ClumsyPilot\ClumsyPilot.csproj') --no-restore
powershell -ExecutionPolicy Bypass -File (Join-Path $verificationRoot 'ClumsyPilot\tests\verify_path_smoothing_local_g2_candidates.ps1')
```
Expected: build has zero errors; candidate checks pass without an accepted-candidate requirement.
- [ ] **Step 4: Commit only the evaluator seam**
```powershell
git add -- `
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/LocalG2CandidateEvaluator.cs `
ClumsyPilot/tests/verify_path_smoothing_local_g2_candidates.ps1
git diff --cached --check
git diff --cached --name-only
git commit -m "fix: deduplicate Local G2 evaluator windows"
```
Expected staged names: exactly the two files above.
---
### Task 2: Guarantee representative window-target coverage
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/LocalG2WindowPlanner.cs`
- Modify: `ClumsyPilot/tests/verify_path_smoothing_local_g2_detection.ps1`
**Interfaces:**
- Consumes: `LocalG2OptionsSnapshot` total-window bounds and `MaximumCandidatesPerRegion`.
- Produces: ordered `WindowVariants` whose balanced target pass precedes 40/60 and 60/40 passes.
- [ ] **Step 1: Add the failing coverage scenario**
Add this case to `LocalG2WindowPlanner.TestHooks.Execute`:
```csharp
case "InteriorCoverage":
transitions = new[] { Transition(1d, 0) };
segmentLength = 2d;
break;
```
Extend `WindowPlanningTestSnapshot` with:
```csharp
public int FirstRegionVariantCount { get; }
public bool RepresentativeTargetsFirst { get; }
public bool HasAsymmetricVariant { get; }
```
Append the three values to its internal constructor and assign them exactly:
```csharp
internal WindowPlanningTestSnapshot(
int regionCount,
string transitionCounts,
double maximumWindowLength,
bool exactEnvelope,
double firstLeftLength,
double firstRightLength,
string signature,
int firstRegionVariantCount,
bool representativeTargetsFirst,
bool hasAsymmetricVariant)
{
RegionCount = regionCount;
TransitionCounts = transitionCounts;
MaximumWindowLength = maximumWindowLength;
ExactEnvelope = exactEnvelope;
FirstLeftLength = firstLeftLength;
FirstRightLength = firstRightLength;
Signature = signature;
FirstRegionVariantCount = firstRegionVariantCount;
RepresentativeTargetsFirst = representativeTargetsFirst;
HasAsymmetricVariant = hasAsymmetricVariant;
}
```
Compute the values before returning the snapshot:
```csharp
IReadOnlyList<LocalG2WindowVariant> firstVariants = regions[0].WindowVariants;
bool representativeTargetsFirst =
firstVariants.Count >= 3 &&
Math.Abs(WindowLength(firstVariants[0]) - 0.50d) <= 1e-9d &&
Math.Abs(WindowLength(firstVariants[1]) - 0.20d) <= 1e-9d &&
Math.Abs(WindowLength(firstVariants[2]) - 0.80d) <= 1e-9d;
bool hasAsymmetricVariant = false;
for (int index = 0; index < firstVariants.Count; index++)
{
if (Math.Abs(
firstVariants[index].LeftWindowLengthMeters -
firstVariants[index].RightWindowLengthMeters) > 1e-9d)
{
hasAsymmetricVariant = true;
break;
}
}
```
Add the helper:
```csharp
private static double WindowLength(LocalG2WindowVariant variant) =>
variant.EndArcLengthMeters - variant.StartArcLengthMeters;
```
Pass the three values through the snapshot constructor. In the PowerShell verifier add:
```powershell
$coverage = $executeMethod.Invoke($null, @('InteriorCoverage'))
Assert-True $coverage.RepresentativeTargetsFirst `
'Preferred, minimum, and maximum balanced targets must precede asymmetric variants.'
Assert-True $coverage.HasAsymmetricVariant `
'Default window planning must retain a legal asymmetric variant after balanced coverage.'
Assert-True ($coverage.FirstRegionVariantCount -le 12) `
'Window planning must obey the configured default cap.'
```
- [ ] **Step 2: Run the detection verifier to prove RED**
Run:
```powershell
dotnet build ClumsyPilot/ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File ClumsyPilot/tests/verify_path_smoothing_local_g2_detection.ps1
```
Use the disposable-copy command from Task 1 only if the normal build is blocked by `auto_avoidance`.
Expected RED: `RepresentativeTargetsFirst` is false because the current order begins `0.50, 0.50, 0.50` for balanced/asymmetric splits of one target.
- [ ] **Step 3: Implement coverage-pass enumeration**
Replace `BuildVariants` with:
```csharp
private static IReadOnlyList<LocalG2WindowVariant> BuildVariants(
IReadOnlyList<CurvatureTransition> transitions,
double segmentLength,
LocalG2OptionsSnapshot options)
{
var variants = new List<LocalG2WindowVariant>();
double firstEvent = transitions[0].LocalArcLengthMeters;
double lastEvent = transitions[transitions.Count - 1].LocalArcLengthMeters;
double anchor = (firstEvent + lastEvent) / 2d;
IReadOnlyList<double> targets = BuildTargets(options, segmentLength);
double[] ratios = { 0.5d, 0.4d, 0.6d };
for (int ratioIndex = 0; ratioIndex < ratios.Length; ratioIndex++)
{
for (int targetIndex = 0; targetIndex < targets.Count; targetIndex++)
{
if (variants.Count >= options.MaximumCandidatesPerRegion)
return new ReadOnlyCollection<LocalG2WindowVariant>(variants);
AddIfLegal(
variants,
targets[targetIndex],
ratios[ratioIndex],
anchor,
firstEvent,
lastEvent,
segmentLength,
ratioIndex == 0,
options);
}
}
return new ReadOnlyCollection<LocalG2WindowVariant>(variants);
}
```
Replace the `requested` array in `BuildTargets` with:
```csharp
double[] requested =
{
options.PreferredWindowLengthMeters,
options.MinimumWindowLengthMeters,
options.MaximumWindowLengthMeters,
0.75d * options.PreferredWindowLengthMeters,
1.25d * options.PreferredWindowLengthMeters,
};
```
Do not alter `AddIfLegal`, total-length validation, grouping, or region-envelope calculation.
- [ ] **Step 4: Verify GREEN and determinism**
Run twice:
```powershell
powershell -ExecutionPolicy Bypass -File ClumsyPilot/tests/verify_path_smoothing_local_g2_detection.ps1
powershell -ExecutionPolicy Bypass -File ClumsyPilot/tests/verify_path_smoothing_local_g2_detection.ps1
powershell -ExecutionPolicy Bypass -File ClumsyPilot/tests/verify_path_smoothing_local_g2_candidates.ps1
```
Expected: all pass; both detection runs publish identical signatures.
- [ ] **Step 5: Commit the planner coverage change**
```powershell
git add -- `
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/LocalG2WindowPlanner.cs `
ClumsyPilot/tests/verify_path_smoothing_local_g2_detection.ps1
git diff --cached --check
git diff --cached --name-only
git commit -m "fix: cover Local G2 window targets before splits"
```
---
### Task 3: Prove and implement the soft-anchor candidate family
**Files:**
- Modify after feasibility GREEN: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/LocalG2CandidateBuilder.cs`
- Modify: `ClumsyPilot/tests/verify_path_smoothing_local_g2_candidates.ps1`
- Evidence: `.superpowers/sdd/local-g2-soft-anchor-feasibility-report.md`
**Interfaces:**
- Consumes: stabilized `WindowVariants`, internal `CurvatureTransition` anchors, travel direction, and `MaximumDeviationMeters`.
- Produces: profile-major deterministic candidates with exact outer states and bounded normal movement at internal anchors.
- [ ] **Step 1: Reintroduce the permanent RED acceptance seam**
Before the real-candidate loop initialize:
```powershell
$acceptedSoftCandidates = @()
$candidateSignatures = @()
```
For each real candidate, find its point at the first transition arc within
`1e-9 m`, then compute:
```powershell
$transition = (Get-InternalProperty $region 'Transitions')[0]
$travelHeading = [double](Get-InternalProperty $transition 'VehicleHeadingRadians')
$normalX = -[Math]::Sin($travelHeading)
$normalY = [Math]::Cos($travelHeading)
$softOffset = 0.0
foreach ($point in (Get-InternalProperty $realCandidate 'RegionPoints')) {
if ([Math]::Abs($point.ArcLength - (Get-InternalProperty $transition 'LocalArcLengthMeters')) -le 1e-9) {
$softOffset = ($point.X - (Get-InternalProperty $transition 'X')) * $normalX +
($point.Y - (Get-InternalProperty $transition 'Y')) * $normalY
break
}
}
if ((Get-InternalProperty $evaluation 'Accepted') -and [Math]::Abs($softOffset) -gt 1e-10) {
$acceptedSoftCandidates += $realCandidate
}
```
Add:
```powershell
Assert-True ($acceptedSoftCandidates.Count -gt 0) `
'SingleTurn must produce an accepted non-zero soft-anchor candidate under unchanged gates.'
```
Run the candidate verifier against the current builder. Expected RED: no non-zero soft-anchor candidate exists.
- [ ] **Step 2: Run the disposable feasibility gate before shared production edits**
Create a new disposable verification copy using Task 1's command. In that copy only, apply the complete builder changes from Steps 3 and 4 below with `apply_patch`. Add temporary output inside the candidate evaluation loop:
```powershell
$candidateIndex = Get-InternalProperty $realCandidate 'CandidateIndex'
$startArc = Get-InternalProperty $realCandidate 'StartArcLengthMeters'
$endArc = Get-InternalProperty $realCandidate 'EndArcLengthMeters'
$accepted = Get-InternalProperty $evaluation 'Accepted'
$failure = Get-InternalProperty $evaluation 'FailureReason'
$rawPeak = Get-InternalProperty $evaluation 'RawPeakCurvatureDerivativePerSquareMeter'
$resultPeak = Get-InternalProperty $evaluation 'ResultPeakCurvatureDerivativePerSquareMeter'
$maximumCurvature = Get-InternalProperty $evaluation 'MaximumAbsoluteVehicleCurvaturePerMeter'
$maximumDeviation = Get-InternalProperty $evaluation 'MaximumDeviationMeters'
$minimumClearance = Get-InternalProperty $evaluation 'MinimumBodyClearanceMeters'
Write-Output ("soft-probe candidate=$candidateIndex window=$startArc..$endArc offset=$softOffset accepted=$accepted failure=$failure rawPeak=$rawPeak resultPeak=$resultPeak maxCurvature=$maximumCurvature deviation=$maximumDeviation clearance=$minimumClearance")
```
In the disposable verifier, also calculate the raw and candidate curvature
ranges with the evaluator's unchanged private analysis path:
```powershell
function Get-AnalysisCurvatureRange($Analysis) {
$minimum = [double]::PositiveInfinity
$maximum = [double]::NegativeInfinity
foreach ($point in (Get-InternalProperty $Analysis 'Path')) {
$minimum = [Math]::Min($minimum, [double]$point.VehicleCurvature)
$maximum = [Math]::Max($maximum, [double]$point.VehicleCurvature)
}
return [PSCustomObject]@{ Minimum = $minimum; Maximum = $maximum }
}
$extractMethod = Get-InternalMethod $evaluatorType 'TryExtractWindow'
$analyzeMethod = Get-InternalMethod $evaluatorType 'TryAnalyzeWindow'
$extractArgs = [object[]]@($preparedSegment, [double]$startArc, [double]$endArc, $null, $null)
Assert-True ($extractMethod.Invoke($realEvaluator, $extractArgs)) `
'The feasibility probe must extract the unchanged raw window.'
$rawWindow = $extractArgs[3]
$rawAnalyzeArgs = [object[]]@(
$preparedSegment, $rawWindow, [double]$configuration.OutputSpacingMeters, $null, $null)
Assert-True ($analyzeMethod.Invoke($realEvaluator, $rawAnalyzeArgs)) `
'The feasibility probe must analyze the unchanged raw window.'
$candidateAnalyzeArgs = [object[]]@(
$preparedSegment,
(Get-InternalProperty $realCandidate 'RegionPoints'),
[double]$configuration.OutputSpacingMeters,
$null,
$null)
Assert-True ($analyzeMethod.Invoke($realEvaluator, $candidateAnalyzeArgs)) `
'The feasibility probe must analyze the soft-anchor candidate window.'
$rawRange = Get-AnalysisCurvatureRange $rawAnalyzeArgs[3]
$candidateRange = Get-AnalysisCurvatureRange $candidateAnalyzeArgs[3]
Write-Output ("soft-probe ranges candidate=$candidateIndex raw=$($rawRange.Minimum)..$($rawRange.Maximum) candidate=$($candidateRange.Minimum)..$($candidateRange.Maximum)")
```
Run:
```powershell
dotnet build (Join-Path $verificationRoot 'ClumsyPilot\ClumsyPilot.csproj') --no-restore
powershell -ExecutionPolicy Bypass -File (Join-Path $verificationRoot 'ClumsyPilot\tests\verify_path_smoothing_local_g2_candidates.ps1')
```
Write the exact command, every tuple line, and the final accepted tuple to `.superpowers/sdd/local-g2-soft-anchor-feasibility-report.md` using `apply_patch`.
Gate result:
- GREEN: at least one line has `accepted=True`, `abs(offset) > 1e-10`, result peak at most 80 percent of raw peak, and all unchanged evaluator gates pass. Continue.
- RED: no such line exists. Stop this plan, leave the shared builder unchanged, and report the design as blocked. Do not change expected fixture statuses or any threshold.
- [ ] **Step 3: Implement representative-window selection after feasibility GREEN**
Replace the current window-first/scale-second loop in `Build` with:
```csharp
IReadOnlyList<LocalG2WindowVariant> windows =
SelectRepresentativeWindows(region.WindowVariants);
double softOffset = Math.Min(0.05d, 0.5d * options.MaximumDeviationMeters);
double[] offsets = softOffset <= 1e-10d
? new[] { 0d }
: new[] { 0d, softOffset, -softOffset };
var candidates = new List<LocalG2CandidateGeometry>();
int limit = Math.Min(options.MaximumCandidatesPerRegion, 12);
for (int profileIndex = 0; profileIndex < offsets.Length; profileIndex++)
{
for (int windowIndex = 0; windowIndex < windows.Count; windowIndex++)
{
cancellationToken.ThrowIfCancellationRequested();
if (candidates.Count >= limit) return ReadOnly(candidates);
if (TryBuildCandidate(
candidates.Count,
originalSegment,
region,
windows[windowIndex],
1d,
offsets[profileIndex],
outputSpacingMeters,
cancellationToken,
out LocalG2CandidateGeometry candidate))
{
candidates.Add(candidate);
}
}
}
return ReadOnly(candidates);
```
Add these helpers next to `SameArc`:
```csharp
private static IReadOnlyList<LocalG2WindowVariant> SelectRepresentativeWindows(
IReadOnlyList<LocalG2WindowVariant> variants)
{
var selected = new List<LocalG2WindowVariant>(4);
if (variants == null || variants.Count == 0)
return new ReadOnlyCollection<LocalG2WindowVariant>(selected);
AddDistinctWindow(selected, variants[0]);
LocalG2WindowVariant minimum = variants[0];
LocalG2WindowVariant maximum = variants[0];
LocalG2WindowVariant asymmetric = variants[0];
for (int index = 1; index < variants.Count; index++)
{
LocalG2WindowVariant candidate = variants[index];
if (IsShorter(candidate, minimum)) minimum = candidate;
if (IsLonger(candidate, maximum)) maximum = candidate;
if (IsMoreAsymmetric(candidate, asymmetric)) asymmetric = candidate;
}
AddDistinctWindow(selected, minimum);
AddDistinctWindow(selected, maximum);
AddDistinctWindow(selected, asymmetric);
return new ReadOnlyCollection<LocalG2WindowVariant>(selected);
}
private static bool IsShorter(
LocalG2WindowVariant candidate,
LocalG2WindowVariant current)
{
double candidateLength =
candidate.EndArcLengthMeters - candidate.StartArcLengthMeters;
double currentLength =
current.EndArcLengthMeters - current.StartArcLengthMeters;
return candidateLength < currentLength - 1e-10d ||
(Math.Abs(candidateLength - currentLength) <= 1e-10d &&
candidate.CandidateIndex < current.CandidateIndex);
}
private static bool IsLonger(
LocalG2WindowVariant candidate,
LocalG2WindowVariant current)
{
double candidateLength =
candidate.EndArcLengthMeters - candidate.StartArcLengthMeters;
double currentLength =
current.EndArcLengthMeters - current.StartArcLengthMeters;
return candidateLength > currentLength + 1e-10d ||
(Math.Abs(candidateLength - currentLength) <= 1e-10d &&
candidate.CandidateIndex < current.CandidateIndex);
}
private static bool IsMoreAsymmetric(
LocalG2WindowVariant candidate,
LocalG2WindowVariant current)
{
double candidateValue = Math.Abs(
candidate.LeftWindowLengthMeters - candidate.RightWindowLengthMeters);
double currentValue = Math.Abs(
current.LeftWindowLengthMeters - current.RightWindowLengthMeters);
return candidateValue > currentValue + 1e-10d ||
(Math.Abs(candidateValue - currentValue) <= 1e-10d &&
candidate.CandidateIndex < current.CandidateIndex);
}
private static void AddDistinctWindow(
List<LocalG2WindowVariant> selected,
LocalG2WindowVariant candidate)
{
for (int index = 0; index < selected.Count; index++)
{
if (SameArc(selected[index].StartArcLengthMeters, candidate.StartArcLengthMeters) &&
SameArc(selected[index].EndArcLengthMeters, candidate.EndArcLengthMeters))
{
return;
}
}
selected.Add(candidate);
}
```
- [ ] **Step 4: Implement bounded internal-anchor movement**
Add `double normalOffsetMeters` to `TryBuildCandidate` immediately after `double multiplier`.
Replace internal node construction with:
```csharp
double travelHeading = segment.Direction == TravelDirection.Forward
? transition.VehicleHeadingRadians
: transition.VehicleHeadingRadians - Math.PI;
double normalX = -Math.Sin(travelHeading);
double normalY = Math.Cos(travelHeading);
double nodeX = transition.X + normalOffsetMeters * normalX;
double nodeY = transition.Y + normalOffsetMeters * normalY;
if (!NumericGuard.IsFinite(nodeX) || !NumericGuard.IsFinite(nodeY))
return false;
nodes.Add(new BoundaryNode(
transition.LocalArcLengthMeters,
nodeX,
nodeY,
transition.VehicleHeadingRadians,
sharedCurvature));
```
Declare this immediately before the transition loop:
```csharp
int internalNodeCount = 0;
```
Increment it immediately after adding each moved internal node. Before
`TryAssignDerivativeScales` add:
```csharp
if (internalNodeCount == 0 && Math.Abs(normalOffsetMeters) > 1e-10d)
return false;
```
Keep the original interpolated outer nodes, distance-weighted `sharedCurvature`, derivative-scale calculation, derivative certification, sampling, direction sign, and candidate endpoint fields unchanged.
Delete the now-unused `DerivativeScaleMultipliers` field.
- [ ] **Step 5: Complete permanent soft-anchor and budget assertions**
Add assertions that:
```powershell
Assert-True ($acceptedSoftCandidates.Count -gt 0) `
'SingleTurn must publish an accepted non-zero soft-anchor candidate.'
Assert-True ($realCandidates.Count -le $configuration.LocalG2Quintic.MaximumCandidatesPerRegion) `
'Builder output must obey the configured candidate cap.'
```
Run the real builder twice and compare, for every candidate, candidate index, start/end arc, point count, every X/Y/Heading/Source value, and the inferred signed internal offset. Loop candidate limits from `1` through `12`; rebuild options and assert output count never exceeds the configured limit. Set `MaximumDeviationMeters = 0.02` in one request and assert every inferred anchor offset is at most `0.010000001 m`.
Extend the existing cluster and reverse TestHook cases so that one non-zero profile proves:
```text
outer endpoint position error <= 1e-9 m
outer endpoint heading error <= 1e-9 rad
outer endpoint curvature error <= 1e-8 1/m
internal left/right tangent error <= 1e-8
internal left/right curvature error <= 1e-8 1/m
```
- [ ] **Step 6: Verify candidate GREEN and integration progress**
Run:
```powershell
dotnet build ClumsyPilot/ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File ClumsyPilot/tests/verify_path_smoothing_local_g2_detection.ps1
powershell -ExecutionPolicy Bypass -File ClumsyPilot/tests/verify_path_smoothing_local_g2_candidates.ps1
powershell -ExecutionPolicy Bypass -File ClumsyPilot/tests/verify_path_smoothing_local_g2_integration.ps1
```
Use the disposable copy when the normal build is blocked only by `auto_avoidance`.
Expected: detection and candidate scripts pass; service integration advances past `single-turn must publish its required Local G2 status`. Any later Task 8 failure is handled in Task 4 without modifying candidate gates.
- [ ] **Step 7: Commit the soft-anchor candidate family**
```powershell
git add -- `
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/LocalG2CandidateBuilder.cs `
ClumsyPilot/tests/verify_path_smoothing_local_g2_candidates.ps1
git diff --cached --check
git diff --cached --name-only
git commit -m "fix: generate bounded Local G2 soft anchors"
```
---
### Task 4: Finish the dedicated Task 8 pipeline and service publication
**Files:**
- Create from the existing untracked prototype: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/LocalG2PreSmoothingPipeline.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Facade/PathSmoothingService.cs`
- Create from the existing untracked prototype: `ClumsyPilot/tests/verify_path_smoothing_local_g2_integration.ps1`
- Modify: `ClumsyPilot/tests/verify_path_smoothing_service.ps1`
**Interfaces:**
- Consumes: verified `RawPathBaseline`, immutable prepared path, detector `reportOrder`, `LocalG2RegionWorkOrder`, candidate builder/evaluator.
- Produces:
```csharp
internal PathSmoothingResult Smooth(
PathSmoothingRequest request,
PreparedPath preparedPath,
RawPathBaseline rawBaseline,
CancellationToken cancellationToken);
```
- Guarantees: every valid non-cancelled request publishes a complete verified path; invalid raw input and cancellation publish no partial path.
- [ ] **Step 1: Run the public integration and service scripts as RED**
Run:
```powershell
dotnet build ClumsyPilot/ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File ClumsyPilot/tests/verify_path_smoothing_local_g2_integration.ps1
powershell -ExecutionPolicy Bypass -File ClumsyPilot/tests/verify_path_smoothing_service.ps1
```
Expected before the final pipeline correction: at least one mandatory fixture/status, raw-baseline publication, rollback, or report assertion fails. Record the first expected failure in `.superpowers/sdd/local-g2-task-8-integration-report.md`.
- [ ] **Step 2: Preserve immutable report order and work-order processing**
Keep this exact flow in `Smooth`:
```csharp
IReadOnlyList<LocalG2SmoothingRegion> reportOrder =
new ReadOnlyCollection<LocalG2SmoothingRegion>(
new List<LocalG2SmoothingRegion>(regions));
if (!_workOrder.TryCreate(
reportOrder,
out IReadOnlyList<LocalG2SmoothingRegion> workRegions,
out string orderReason))
{
return Failure(PathSmoothingStatus.Failed, stopwatch, orderReason);
}
foreach (LocalG2SmoothingRegion region in workRegions)
{
cancellationToken.ThrowIfCancellationRequested();
// Build from preparedPath; evaluate against current.
}
var reports = new List<PathSmoothingRegionReport>(reportOrder.Count);
for (int reportIndex = 0; reportIndex < reportOrder.Count; reportIndex++)
reports.Add(reportsByRegion[reportOrder[reportIndex]]);
```
Candidate construction must use `preparedPath.Segments[region.SegmentIndex]`; evaluation must receive both `preparedPath` and the evolving `current` path.
- [ ] **Step 3: Publish the verified raw baseline without reconstructing it**
Before regional processing, if `transitions.Count == 0`, publish:
```csharp
return Publish(
PathSmoothingStatus.NotNeeded,
rawBaseline,
stopwatch,
new List<PathSmoothingRegionReport>());
```
Add:
```csharp
private static PathSmoothingResult Publish(
PathSmoothingStatus status,
RawPathBaseline baseline,
Stopwatch stopwatch,
IReadOnlyList<PathSmoothingRegionReport> reports) =>
PathSmoothingResult.PublishLocalG2(
status,
baseline.Path,
baseline.Segments,
new PathSmoothingDiagnostics(
baseline.Metrics,
stopwatch.Elapsed,
0,
0d,
string.Empty),
reports);
```
After regional evaluation and global rollback, if `improvedCount == 0`, publish the same `rawBaseline` as `Unchanged` with detector-order reports. Do not call `PathGeometryAnalyzer` again for this raw fallback; the pre-Task-8 trusted-curvature gate already produced and fully validated it.
- [ ] **Step 4: Make accepted and retained reports describe the actual candidate**
For every region retain:
```csharp
IReadOnlyList<LocalG2CandidateGeometry> candidates
LocalG2CandidateEvaluation best
LocalG2CandidateGeometry selectedCandidate
```
Find the selected geometry deterministically:
```csharp
LocalG2CandidateGeometry selectedCandidate = null;
for (int index = 0; index < candidates.Count; index++)
{
if (candidates[index].CandidateIndex == best.CandidateIndex)
{
selectedCandidate = candidates[index];
break;
}
}
```
When `best.Accepted`, require `selectedCandidate != null`, store it in `AcceptedRegion`, and report its start/end/left/right lengths. When no candidate is accepted, use candidate index `-1`, the first window only as planned-window diagnostics, and the stable failure reason from `best`.
Extend `AcceptedRegion` with:
```csharp
internal int CandidateCount { get; }
internal LocalG2CandidateGeometry Candidate { get; }
```
Rollback reports use the stored candidate count and selected candidate rather than `region.WindowVariants.Count`.
- [ ] **Step 5: Preserve global rollback and exact status derivation**
After candidate processing, validate `current`. If it fails, roll accepted regions back in reverse acceptance order. After each rollback, re-run full validation. Mark every removed region `GlobalValidationRollback`.
Derive status only after rollback:
```csharp
if (improvedCount == 0)
status = PathSmoothingStatus.Unchanged;
else if (improvedCount == regions.Count)
status = PathSmoothingStatus.Complete;
else
status = PathSmoothingStatus.PartialImprovement;
```
`Complete` and `PartialImprovement` must have at least one `Improved` report. If every accepted region is rolled back, publish the verified `rawBaseline` as `Unchanged`.
- [ ] **Step 6: Keep service dispatch isolated**
In `PathSmoothingService` retain the dedicated branch after common validation, preparation, and raw-baseline creation:
```csharp
if (configuration.Method == SmoothingMethod.LocalG2Quintic)
return _localG2Pipeline.Smooth(
request,
preparedPath,
rawBaseline,
cancellationToken);
```
Validate `LocalG2QuinticOptions` only for that method; legacy methods continue through `Resolve(...)` and `_runner`. Do not add `LocalG2Quintic` to comparison defaults.
- [ ] **Step 7: Complete integration assertions**
The integration script must run every fixture twice and require:
```text
Straight -> NotNeeded
SingleTurn -> Complete
LargeHeadingChange -> Complete
SBend -> Complete or PartialImprovement
RectangleDetour -> Complete or PartialImprovement
MultiObstacleDetour -> Complete or PartialImprovement
ReverseGearSwitch -> Complete, PartialImprovement, or NotNeeded
```
For every published path require complete segment coverage, feasible diagnostics, configured clearance, deterministic status/coordinates/reports, and at least one `Improved` report for `Complete`/`PartialImprovement`. Keep the strict `0.99` improvement request as `Unchanged`, cancellation as empty-path `Cancelled`, and the same-direction two-region case as back-to-front processing with detector-order reports.
- [ ] **Step 8: Run focused and legacy GREEN checks**
Run:
```powershell
dotnet build ClumsyPilot/ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File ClumsyPilot/tests/verify_path_smoothing_local_g2_integration.ps1
powershell -ExecutionPolicy Bypass -File ClumsyPilot/tests/verify_path_smoothing_service.ps1
powershell -ExecutionPolicy Bypass -File ClumsyPilot/tests/verify_path_smoothing_runner.ps1
powershell -ExecutionPolicy Bypass -File ClumsyPilot/tests/verify_path_smoothing_integration.ps1
powershell -ExecutionPolicy Bypass -File ClumsyPilot/tests/verify_path_smoothing_local_g2_candidates.ps1
powershell -ExecutionPolicy Bypass -File ClumsyPilot/tests/verify_path_smoothing_local_g2_detection.ps1
```
Expected: all pass. Use the disposable copy only for the unrelated `auto_avoidance` build blocker.
- [ ] **Step 9: Commit only Task 8 publication files**
```powershell
git add -- `
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/LocalG2PreSmoothingPipeline.cs `
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Facade/PathSmoothingService.cs `
ClumsyPilot/tests/verify_path_smoothing_local_g2_integration.ps1 `
ClumsyPilot/tests/verify_path_smoothing_service.ps1
git diff --cached --check
git diff --cached --name-only
git commit -m "feat: publish Local G2 presmoothing results"
```
Expected staged names: exactly the four files above. If the service file contains unrelated user hunks, stage the Task 8 patch into the index without staging those hunks and verify with `git diff --cached` before committing.
---
### Task 5: Run the complete recovery and Task 8 verification gate
**Files:**
- Verify: all files committed by Tasks 14
- Verify: all `ClumsyPilot/tests/verify_path_smoothing_*.ps1`
- Record: `.superpowers/sdd/local-g2-soft-anchor-task-8-final-report.md`
**Interfaces:**
- Consumes: the evaluator seam, window scheduling, proven soft-anchor family, and dedicated pipeline.
- Produces: evidence that Task 8 is complete without modifying Task 9 documentation.
- [ ] **Step 1: Build the final source**
```powershell
dotnet build ClumsyPilot/ClumsyPilot.csproj --no-restore
```
Expected: zero errors. The two pre-existing obsolete API warnings may remain. If unrelated `auto_avoidance` dependencies still block the shared build, also record that exact failure and run the same build in the disposable copy.
- [ ] **Step 2: Run every PathSmoothing verification script**
```powershell
$tests = @(
'verify_path_smoothing_algorithm_input.ps1',
'verify_path_smoothing_bezier.ps1',
'verify_path_smoothing_bspline.ps1',
'verify_path_smoothing_comparison.ps1',
'verify_path_smoothing_contracts.ps1',
'verify_path_smoothing_documentation.ps1',
'verify_path_smoothing_fixtures.ps1',
'verify_path_smoothing_geometry.ps1',
'verify_path_smoothing_integration.ps1',
'verify_path_smoothing_local_g2_candidates.ps1',
'verify_path_smoothing_local_g2_curve.ps1',
'verify_path_smoothing_local_g2_detection.ps1',
'verify_path_smoothing_local_g2_integration.ps1',
'verify_path_smoothing_png.ps1',
'verify_path_smoothing_quintic.ps1',
'verify_path_smoothing_runner.ps1',
'verify_path_smoothing_service.ps1',
'verify_path_smoothing_svg_csv.ps1',
'verify_path_smoothing_validation.ps1'
)
foreach ($test in $tests) {
& powershell -ExecutionPolicy Bypass -File (Join-Path 'ClumsyPilot/tests' $test)
if ($LASTEXITCODE -ne 0) { throw "$test failed with exit code $LASTEXITCODE" }
}
```
Expected: all 19 scripts pass.
- [ ] **Step 3: Audit scope, safety thresholds, and Task 9 boundary**
```powershell
git diff --check
git diff --cached --check
git status --short
git log -6 --oneline
rg -n "MaximumCurvaturePerMeter|CurvatureRangeTolerance|MinimumClearanceReserveMeters|MinimumPeakGradientImprovementRatio|MaximumVariationCostRegressionRatio" `
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2 `
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Facade/PathSmoothingService.cs
```
Confirm no safety value changed, no staged files remain, unrelated dirty files are untouched, comparison defaults exclude Local G2, and Task 9 README/documentation work has not started.
- [ ] **Step 4: Record final evidence without an empty commit**
Write `.superpowers/sdd/local-g2-soft-anchor-task-8-final-report.md` with:
```text
Soft-anchor feasibility gate: PASS with exact accepted tuple and unchanged evaluator metrics.
Window target coverage: PASS for preferred/minimum/maximum before asymmetric variants.
SingleTurn: Complete with at least one accepted non-zero soft anchor.
Task 8 service publication: PASS including raw fallback, rollback, work/report order, and cancellation.
Full PathSmoothing verification: 19/19 scripts PASS (or shared-build limitation plus isolated-copy evidence explicitly recorded).
Task 9 documentation remains pending.
```
Do not create a verification-only commit.
File diff suppressed because it is too large Load Diff
@@ -1,558 +0,0 @@
# Local G2 Diagnostic Visualization 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:** Export a factual Local G2 visualization that shows the ordinary strict `Unchanged` result and the recorded clearance-rejected diagnostic candidate without publishing or recommending that candidate.
**Architecture:** The existing comparison service remains the source for normal Local G2 status and output. A test/demo-only evidence loader reads a compact immutable record of `single-turn/s0/r0/w5/seed2`, reconstructs a visual-only spliced path with the existing preprocessor/splicer/analyzer, and appends it to an immutable figure model. The existing SVG/PNG/CSV exporter then produces the six standard figures plus a seventh diagnostic figure only for that augmented model.
**Tech Stack:** C# 10 targeting `netstandard2.0`, Newtonsoft.Json 13.0.4 already referenced by `ClumsyPilot.csproj`, existing `System.Drawing` PNG renderer, PowerShell verification scripts, .NET 10 Windows verification host.
## Global Constraints
- Do not modify `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/`, evaluator, validator, publication, or recommendation logic.
- The normal comparison must retain the actual Local G2 `PathSmoothingStatus`; a diagnostic candidate must never create a `PathSmoothingResult` or a recommendation.
- Evidence is fixed to fixture SHA-256 `3d05daee5a211b3e7aa0b77193423b5fa07d3135e241a4413be3518fc7efe563`, batch SHA-256 `ac8166828813d85bf6f8b58f985839e5b2a049e75cb94186ed04f59d540e4eed`, and stable key `single-turn/s0/r0/w5/seed2`.
- Preserve normal-export file stems `01-coarse-path-overview` through `06-curvature-comparison` and its six-file contract.
- The diagnostic candidate must be labelled `净空拒绝,未发布`; do not render a collision cross because the evidence records a clearance rejection, not an occupied-cell collision.
- Keep trajectories point-only. Do not add SVG paths or dashed stroke rendering.
- Place generated artifacts only below `ClumsyPilot/obj/path_smoothing_reports`.
- Use targeted `git add -- <paths>` and `git commit --only -- <paths>`; do not include unrelated worktree changes.
---
## File Structure
| Path | Responsibility |
| --- | --- |
| `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Comparison/PathSmoothingComparisonRequest.cs` | Adds normal Local G2 to the immutable default offline comparison order. |
| `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/IeeeFigureStyle.cs` | Holds normal Local G2 and diagnostic-candidate colors. |
| `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureModelBuilder.cs` | Adds normal Local G2 series and CSV metric row. |
| `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureModel.cs` | Creates an immutable model copy with one added diagnostic series. |
| `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureDefinition.cs` | Moves a five-entry legend upward enough to remain inside the fixed figure height. |
| `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureKind.cs` | Names the optional seventh figure. |
| `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureSetBuilder.cs` | Adds Local G2 to normal comparison figures and conditionally creates figure 07. |
| `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/Fixtures/local-g2-diagnostic-single-turn.json` | Immutable compact evidence extract used by the visual-only route. |
| `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/LocalG2DiagnosticEvidenceLoader.cs` | Parses and verifies evidence identity, geometry, and rejection state. |
| `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/LocalG2DiagnosticVisualizationDemo.cs` | Reconstructs renderable candidate geometry and invokes the existing exporter. |
| `ClumsyPilot/tests/PathSmoothingPngVerificationHost/Program.cs` | Verifies and exports the augmented seven-file report. |
| `ClumsyPilot/tests/verify_path_smoothing_comparison.ps1` | Checks Local G2 default order and normal comparison semantics. |
| `ClumsyPilot/tests/verify_path_smoothing_svg_csv.ps1` | Checks normal Local G2 figure/CSV content and visual layout contracts. |
| `ClumsyPilot/tests/verify_path_smoothing_local_g2_diagnostic_evidence.ps1` | Checks evidence parsing and deterministic rejection validation. |
| `ClumsyPilot/tests/verify_path_smoothing_local_g2_diagnostic_visualization.ps1` | Calls the verification host for the seven-file diagnostic report. |
| `ClumsyPilot/tests/run_local_g2_diagnostic_visualization.ps1` | Builds and exports the user-facing Local G2 diagnostic image. |
## Task 1: Add Normal Local G2 To Existing Comparison Reports
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Comparison/PathSmoothingComparisonRequest.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/IeeeFigureStyle.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureModelBuilder.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureDefinition.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureSetBuilder.cs`
- Modify: `ClumsyPilot/tests/verify_path_smoothing_comparison.ps1`
- Modify: `ClumsyPilot/tests/verify_path_smoothing_svg_csv.ps1`
- Modify: `ClumsyPilot/tests/PathSmoothingPngVerificationHost/Program.cs`
**Interfaces:**
- Consumes: `PathSmoothingComparisonRequest(PathSmoothingRequest, IReadOnlyList<SmoothingMethod> methods = null)` and `SmoothingFigureModelBuilder.Build(PathSmoothingComparisonResult, PlanningGridMap, Pose2D, Pose2D, string, string)`.
- Produces: A default method order of `CubicBSpline`, `LocalCubicBezier`, `PiecewiseQuintic`, `LocalG2Quintic`; figure key `local-g2`; CSV method `LocalG2Quintic`; unchanged standard six-figure export count.
- [ ] **Step 1: Write the failing default-order and standard-model assertions**
In `verify_path_smoothing_comparison.ps1`, parse the Local G2 enum and construct a request without the optional methods list. Add these assertions after the explicit three-method request assertions:
```powershell
$localG2 = [Enum]::Parse($methodType, 'LocalG2Quintic')
$defaultComparisonRequest = [Activator]::CreateInstance($comparisonRequestType, @((New-SmoothingRequest), $null))
Assert-Equal 4 $defaultComparisonRequest.Methods.Count 'Default comparison must include Local G2.'
Assert-Equal 'CubicBSpline,LocalCubicBezier,PiecewiseQuintic,LocalG2Quintic' (($defaultComparisonRequest.Methods | ForEach-Object ToString) -join ',') 'Default comparison order must be stable.'
```
In `verify_path_smoothing_svg_csv.ps1`, add `#56B4E9` to the expected normal SVG colors, assert that `$model.Series` includes a series whose `Key` is `local-g2`, and assert that the generated CSV contains `LocalG2Quintic,Unchanged` for the frozen `single-turn` request. Add assertions that the standard figure set still contains only the six existing stems.
In `Program.cs`, add a `VerifyNormalLocalG2Series(SmoothingFigureModel model)` call immediately after `VerifySixFigureDefinitionContract(model)`. It must require exactly one `local-g2` series and a `PathSmoothingStatus` value defined by the enum. If its strict output path is visible, require that it has at least two samples. Do not hard-code `Unchanged` for this artificial high-curvature host fixture; the frozen `single-turn` evidence assertion in the SVG/CSV test owns that requirement.
- [ ] **Step 2: Run the focused checks and confirm they fail before implementation**
Run:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_comparison.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_svg_csv.ps1
```
Expected: the comparison assertion reports three default methods and the SVG/CSV check cannot find `local-g2` or `#56B4E9`.
- [ ] **Step 3: Implement the smallest normal-comparison extension**
Append the enum in the existing default array, preserving all existing order:
```csharp
private static readonly SmoothingMethod[] DefaultMethods =
{
SmoothingMethod.CubicBSpline,
SmoothingMethod.LocalCubicBezier,
SmoothingMethod.PiecewiseQuintic,
SmoothingMethod.LocalG2Quintic,
};
```
Add these fixed colors to `IeeeFigureStyle`:
```csharp
public const string LocalG2Color = "#56B4E9";
public const string LocalG2DiagnosticColor = "#B1373E";
```
In `SmoothingFigureModelBuilder`, append the normal series and metric row after the existing piecewise-quintic entries. Use the exact stable key and label:
```csharp
series.Add(CreateSeries(
Find(comparison, SmoothingMethod.LocalG2Quintic),
SmoothingMethod.LocalG2Quintic,
"local-g2",
"局部 G2",
IeeeFigureStyle.LocalG2Color,
string.Empty,
false,
map));
CreateRow(Find(comparison, SmoothingMethod.LocalG2Quintic), "LocalG2Quintic", "局部 G2")
```
In `SmoothingFigureSetBuilder.Build`, resolve `local-g2` with the other standard series and include it in only the existing all-path and curvature comparisons:
```csharp
SmoothingFigureSeries localG2 = Find(model, "local-g2");
// Add View(localG2, 1d) after View(quintic, 1d) in figures 02 and 06.
```
Keep individual figures `03` through `05` unchanged. In `SmoothingFigureDefinition`, use a five-entry-safe legend origin:
```csharp
public double LegendYPoints => LegendEntries.Count > 4 ? 332d : 340d;
```
Do not special-case `Unchanged`: `PathSmoothingComparisonService` already supplies its strict path and status. Do not modify that service or the ranker.
- [ ] **Step 4: Run the focused checks and confirm they pass**
Run:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_comparison.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_svg_csv.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_png.ps1
```
Expected: all three scripts exit `0`; normal exports retain exactly six figures; the Local G2 row is present with its actual `Unchanged` status.
- [ ] **Step 5: Commit only Task 1 files**
```powershell
git add -- `
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Comparison/PathSmoothingComparisonRequest.cs `
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/IeeeFigureStyle.cs `
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureModelBuilder.cs `
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureDefinition.cs `
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureSetBuilder.cs `
ClumsyPilot/tests/verify_path_smoothing_comparison.ps1 `
ClumsyPilot/tests/verify_path_smoothing_svg_csv.ps1 `
ClumsyPilot/tests/PathSmoothingPngVerificationHost/Program.cs
git -c core.autocrlf=false diff --cached --check
git commit --only -m "feat: show Local G2 in smoothing comparisons" -- `
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Comparison/PathSmoothingComparisonRequest.cs `
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/IeeeFigureStyle.cs `
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureModelBuilder.cs `
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureDefinition.cs `
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureSetBuilder.cs `
ClumsyPilot/tests/verify_path_smoothing_comparison.ps1 `
ClumsyPilot/tests/verify_path_smoothing_svg_csv.ps1 `
ClumsyPilot/tests/PathSmoothingPngVerificationHost/Program.cs
```
### Task 2: Freeze And Validate The Diagnostic Evidence Extract
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/Fixtures/local-g2-diagnostic-single-turn.json`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/LocalG2DiagnosticEvidenceLoader.cs`
- Create: `ClumsyPilot/tests/verify_path_smoothing_local_g2_diagnostic_evidence.ps1`
**Interfaces:**
- Produces: `public sealed class LocalG2DiagnosticEvidenceLoader` with `public LocalG2DiagnosticEvidence LoadAndVerify(string evidencePath)`.
- Produces: `LocalG2DiagnosticEvidence` properties `ScenarioId`, `FixtureSha256`, `CandidateStableKey`, `CandidateSha256`, `CandidateIndex`, `SegmentIndex`, `WindowStartArcLengthMeters`, `WindowEndArcLengthMeters`, start/end curvatures, `CandidatePoints`, `EvaluatorResult`, `StopGate`, and `PublishedStatus`.
- Consumed later by: `LocalG2DiagnosticVisualizationDemo.Export(string fixturePath, string evidencePath, string outputDirectory, CancellationToken cancellationToken = default)`.
- [ ] **Step 1: Write the failing evidence-loader verification script**
Create `verify_path_smoothing_local_g2_diagnostic_evidence.ps1`. Load `ClumsyPilot.dll`, resolve `MultiWheelC.TrajectoryPlanning.PathSmoothing.Test.LocalG2DiagnosticEvidenceLoader`, and invoke `LoadAndVerify` with the new fixture path. Assert all of these exact values:
```powershell
Assert-Equal 'single-turn' $evidence.ScenarioId 'Diagnostic evidence scenario must be stable.'
Assert-Equal 'single-turn/s0/r0/w5/seed2' $evidence.CandidateStableKey 'Diagnostic evidence key must be stable.'
Assert-Equal 5 $evidence.CandidateIndex 'Diagnostic evidence candidate index must be stable.'
Assert-Equal 10 $evidence.CandidatePoints.Count 'Diagnostic evidence must retain all ten recorded samples.'
Assert-Equal 'InsufficientClearance' $evidence.EvaluatorResult 'Diagnostic evidence must retain the observed evaluator result.'
Assert-Equal 'Clearance' $evidence.StopGate 'Diagnostic evidence must retain the observed stop gate.'
Assert-Equal 'Unchanged' $evidence.PublishedStatus 'Diagnostic evidence must retain the strict final status.'
```
Copy the JSON to a uniquely named temp path, replace only `"StopGate": "Clearance"` with `"StopGate": "Collision"`, and require that `LoadAndVerify` throws. Delete the temp copy in `finally`.
- [ ] **Step 2: Run the evidence check and confirm it fails before implementation**
Run:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_local_g2_diagnostic_evidence.ps1
```
Expected: failure because the loader type and evidence file do not exist.
- [ ] **Step 3: Add the immutable compact evidence extract and loader**
Create the JSON file with this exact top-level contract and the ten recorded `CandidatePoints`. Preserve the displayed IEEE-754 decimal values; they are the evidence values, not rounded drawing inputs:
```json
{
"SourceMeasurementBatchSha256": "ac8166828813d85bf6f8b58f985839e5b2a049e75cb94186ed04f59d540e4eed",
"FixtureSha256": "3d05daee5a211b3e7aa0b77193423b5fa07d3135e241a4413be3518fc7efe563",
"ScenarioId": "single-turn",
"CandidateStableKey": "single-turn/s0/r0/w5/seed2",
"CandidateSha256": "7cefb76c77e48a49bf3212e7a2472e23036c3899daa94b5c6b68fa5db29a3392",
"CandidateIndex": 5,
"SegmentIndex": 0,
"WindowStartArcLengthMeters": 1.9199999999999982,
"WindowEndArcLengthMeters": 2.1199999999999983,
"StartGeometricCurvaturePerMeter": 0.41666666666666663,
"EndGeometricCurvaturePerMeter": 0.0,
"StartVehicleCurvaturePerMeter": 0.41666666666666663,
"EndVehicleCurvaturePerMeter": 0.0,
"EvaluatorResult": "InsufficientClearance",
"StopGate": "Clearance",
"PublishedStatus": "Unchanged",
"CandidatePoints": [
{ "X": 2.7216397035494579, "Y": 1.7279184433567971, "ReferenceArcLengthMeters": 1.9199999999999982, "HeadingRadians": 0.799999999999999, "UnwrappedHeadingRadians": 0.799999999999999, "Source": 4 },
{ "X": 2.7355188205174521, "Y": 1.7423187041196302, "ReferenceArcLengthMeters": 1.9399999999999982, "HeadingRadians": 0.80751185024996908, "UnwrappedHeadingRadians": 0.80751185024996908, "Source": 4 },
{ "X": 2.7492871654421882, "Y": 1.7568248978050907, "ReferenceArcLengthMeters": 1.9599999999999982, "HeadingRadians": 0.8157618125921976, "UnwrappedHeadingRadians": 0.8157618125921976, "Source": 4 },
{ "X": 2.7629234692741469, "Y": 1.7714552535786874, "ReferenceArcLengthMeters": 1.9799999999999982, "HeadingRadians": 0.82542852165703573, "UnwrappedHeadingRadians": 0.82542852165703573, "Source": 4 },
{ "X": 2.7764244476704873, "Y": 1.786210614200662, "ReferenceArcLengthMeters": 1.9999999999999982, "HeadingRadians": 0.83333333333333237, "UnwrappedHeadingRadians": 0.83333333333333237, "Source": 4 },
{ "X": 2.7925350453990334, "Y": 1.803999635249544, "ReferenceArcLengthMeters": 2.0239999999999982, "HeadingRadians": 0.835253334801853, "UnwrappedHeadingRadians": 0.835253334801853, "Source": 4 },
{ "X": 2.80865418003672, "Y": 1.8217809211927092, "ReferenceArcLengthMeters": 2.0479999999999983, "HeadingRadians": 0.83333333355178374, "UnwrappedHeadingRadians": 0.83333333355178374, "Source": 4 },
{ "X": 2.8248074262445737, "Y": 1.8395312269498194, "ReferenceArcLengthMeters": 2.0719999999999983, "HeadingRadians": 0.8318933325232104, "UnwrappedHeadingRadians": 0.8318933325232104, "Source": 4 },
{ "X": 2.8409691920174533, "Y": 1.8572737784943611, "ReferenceArcLengthMeters": 2.0959999999999983, "HeadingRadians": 0.83237333274470626, "UnwrappedHeadingRadians": 0.83237333274470626, "Source": 4 },
{ "X": 2.8571139169604551, "Y": 1.8750318365841865, "ReferenceArcLengthMeters": 2.1199999999999983, "HeadingRadians": 0.83333333333333237, "UnwrappedHeadingRadians": 0.83333333333333237, "Source": 4 }
]
}
```
Implement the public loader in the test namespace using `Newtonsoft.Json.JsonConvert.DeserializeObject<LocalG2DiagnosticEvidence>(File.ReadAllText(evidencePath))`. Use `InvalidDataException` for every rejected input. The validation must require the three exact SHA/key constants, `ScenarioId == "single-turn"`, `CandidateIndex == 5`, `SegmentIndex == 0`, `EvaluatorResult == "InsufficientClearance"`, `StopGate == "Clearance"`, and `PublishedStatus == "Unchanged"`.
Validate the two window values and all point coordinates/headings/reference arc lengths with this helper:
```csharp
private static bool IsFinite(double value) => !double.IsNaN(value) && !double.IsInfinity(value);
```
Require exactly ten points, first/last reference arcs equal the window endpoints within `1e-8d`, strictly increasing reference arcs, `Source == (int)SmoothedPathPointSource.LocalG2Transition`, and non-null start/end curvature values. Return only after all checks pass.
- [ ] **Step 4: Run the evidence verification and confirm it passes**
Run:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_local_g2_diagnostic_evidence.ps1
```
Expected: exit `0`; the valid extract loads and the altered stop gate is rejected.
- [ ] **Step 5: Commit only Task 2 files**
```powershell
git add -- `
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/Fixtures/local-g2-diagnostic-single-turn.json `
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/LocalG2DiagnosticEvidenceLoader.cs `
ClumsyPilot/tests/verify_path_smoothing_local_g2_diagnostic_evidence.ps1
git -c core.autocrlf=false diff --cached --check
git commit --only -m "test: freeze Local G2 diagnostic evidence" -- `
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/Fixtures/local-g2-diagnostic-single-turn.json `
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/LocalG2DiagnosticEvidenceLoader.cs `
ClumsyPilot/tests/verify_path_smoothing_local_g2_diagnostic_evidence.ps1
```
### Task 3: Build The Visual-Only Candidate And Optional Figure 07
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureModel.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureKind.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureSetBuilder.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/LocalG2DiagnosticVisualizationDemo.cs`
- Modify: `ClumsyPilot/tests/PathSmoothingPngVerificationHost/Program.cs`
- Create: `ClumsyPilot/tests/verify_path_smoothing_local_g2_diagnostic_visualization.ps1`
**Interfaces:**
- Produces: `internal SmoothingFigureModel WithAdditionalSeries(SmoothingFigureSeries series)` that preserves all existing model layout, metric rows, endpoints, and scales.
- Produces: `SmoothingFigureKind.LocalG2DiagnosticCandidate` and optional stem `07-local-g2-diagnostic-candidate`.
- Produces: `public SmoothingReportExportResult LocalG2DiagnosticVisualizationDemo.Export(string fixturePath, string evidencePath, string outputDirectory, CancellationToken cancellationToken = default)`.
- Produces: a host verification command that accepts a fixture path and evidence path, plus an export command that accepts fixture, evidence, and output-directory paths.
- [ ] **Step 1: Write the failing seven-file verification host branch and wrapper script**
In `Program.Main`, add command dispatch before the existing fixture/export cases:
```csharp
if (arguments.Length == 3 && arguments[0] == "--verify-local-g2-diagnostic")
{
VerifyLocalG2Diagnostic(arguments[1], arguments[2]);
Console.WriteLine("Local G2 diagnostic visualization verification completed.");
return 0;
}
if (arguments.Length == 4 && arguments[0] == "--export-local-g2-diagnostic")
{
ExportLocalG2Diagnostic(arguments[1], arguments[2], arguments[3]);
return 0;
}
```
Create the wrapper script to resolve the fixture and evidence paths and run this command:
```powershell
& dotnet run --project $hostProject --no-restore -- --verify-local-g2-diagnostic $resolvedFixturePath $resolvedEvidencePath
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
```
Run it before the demo/model implementation. Expected: build error because `VerifyLocalG2Diagnostic` and `ExportLocalG2Diagnostic` do not yet exist.
- [ ] **Step 2: Add immutable model augmentation and the optional figure definition**
Add this internal method to `SmoothingFigureModel`:
```csharp
internal SmoothingFigureModel WithAdditionalSeries(SmoothingFigureSeries series)
{
if (series == null) throw new ArgumentNullException(nameof(series));
var combined = new List<SmoothingFigureSeries>(Series.Count + 1);
for (int index = 0; index < Series.Count; index++)
{
if (Series[index].Key == series.Key)
throw new ArgumentException("Figure series keys must be unique.", nameof(series));
combined.Add(Series[index]);
}
combined.Add(series);
var copy = new SmoothingFigureModel(
ScenarioId, ScenarioLabel, WorldXMinMeters, WorldXMaxMeters, WorldYMinMeters, WorldYMaxMeters,
PathPanelX, PathPanelY, PathPanelWidth, PathPanelHeight,
CurvaturePanelX, CurvaturePanelY, CurvaturePanelWidth, CurvaturePanelHeight,
MetricsPanelX, MetricsPanelY, MetricsPanelWidth, MetricsPanelHeight,
Obstacles, combined, MetricRows, Start, Goal)
{
PathScaleX = PathScaleX,
PathScaleY = PathScaleY,
};
return copy;
}
```
Add `LocalG2DiagnosticCandidate` to `SmoothingFigureKind`. In `SmoothingFigureSetBuilder.Build`, retain the six normal definitions first. Use a non-throwing `TryFind` helper for key `local-g2-diagnostic`; when it succeeds, append exactly this overhead figure:
```csharp
BuildOverhead(
SmoothingFigureKind.LocalG2DiagnosticCandidate,
"07-local-g2-diagnostic-candidate",
"G2 诊断候选:净空拒绝,未发布;严格输出=原始路径",
model,
true,
View(raw, 0.45d),
View(diagnostic, 1d))
```
The diagnostic series must carry key `local-g2-diagnostic`, status `PathSmoothingStatus.Infeasible`, color `IeeeFigureStyle.LocalG2DiagnosticColor`, and an empty `ViolationMarkers` list. Do not reuse `SmoothingFigureModelBuilder.CreateSeries`, because its generic `Infeasible` behavior synthesizes a violation cross when it cannot identify an occupied point.
- [ ] **Step 3: Implement `LocalG2DiagnosticVisualizationDemo` with the existing geometry components**
The class stays in namespace `MultiWheelC.TrajectoryPlanning.PathSmoothing.Test` and creates no `PathSmoothingResult`. Its `Export` method must execute this exact sequence:
```csharp
LocalG2DiagnosticEvidence evidence = _evidenceLoader.LoadAndVerify(evidencePath);
PathSmoothingComparisonRequest request = FindFixtureRequest(fixturePath, evidence.ScenarioId);
PathSmoothingComparisonResult comparison = _comparisonService.Compare(request, cancellationToken);
PathSmoothingComparisonEntry localG2 = FindEntry(comparison, SmoothingMethod.LocalG2Quintic);
Require(localG2 != null && localG2.Status == PathSmoothingStatus.Unchanged,
"Strict Local G2 result must be Unchanged for the diagnostic evidence.");
RequireSameGeometry(comparison.RawPathBaseline.Path, localG2.Path);
_preprocessor.TryPrepare(request.SmoothingRequest, out PreparedPath prepared, out string reason);
LocalG2CandidateGeometry candidate = CreateCandidate(evidence);
_splicer.TryReplace(prepared, candidate, out PreparedPath spliced, out reason);
_analyzer.TryAnalyze(spliced.Segments, request.SmoothingRequest.Configuration.OutputSpacingMeters, out PathGeometryAnalysis analysis, out reason);
CoarsePathPoint first = request.SmoothingRequest.CoarsePath[0];
CoarsePathPoint last = request.SmoothingRequest.CoarsePath[request.SmoothingRequest.CoarsePath.Count - 1];
SmoothingFigureModel normal = _figureBuilder.Build(
comparison,
request.SmoothingRequest.Map,
new Pose2D(first.X, first.Y, first.Heading),
new Pose2D(last.X, last.Y, last.Heading),
evidence.ScenarioId,
evidence.ScenarioId);
SmoothingFigureModel augmented = normal.WithAdditionalSeries(CreateDiagnosticSeries(analysis.Path));
return _exporter.Export(new SmoothingReportExportRequest { Model = augmented, OutputDirectory = outputDirectory, FileStem = "comparison" });
```
`FindFixtureRequest` must call `SmoothingScenarioFactory.CreateFixtureRequests(fixturePath)`, locate exactly one request by the matching fixture index from `SmoothingScenarioFixtureLoader.LoadAndVerify(fixturePath)`, and reject missing or duplicate `single-turn` IDs. `CreateCandidate` must turn every evidence point into:
```csharp
new SmoothingPoint2D(
point.X, point.Y, point.ReferenceArcLengthMeters,
point.HeadingRadians, point.UnwrappedHeadingRadians,
0d, false, SmoothedPathPointSource.LocalG2Transition)
```
Construct `LocalG2CandidateGeometry` with evidence index/window/curvatures, `0d` left and right lengths, the converted point list, and `true` for `internalConnectionsAreG2`. The zero clearance exists only to satisfy geometry-analysis input validity; it must not be fed to a validator or a metric row.
`CreateDiagnosticSeries` must convert `analysis.Path` to `SmoothingFigurePoint` values, use label `G2 候选(净空拒绝,未发布)`, and supply `Array.Empty<SmoothingFigurePoint>()` as violation markers. Its path can be visualized but is not a published/safe path.
- [ ] **Step 4: Implement host verification and run it**
`VerifyLocalG2Diagnostic` must create a fresh temp output directory, invoke the demo, and require all of the following before deleting the directory in `finally`:
```csharp
Require(report.Status == SmoothingReportExportStatus.Success, "Diagnostic report export failed: " + report.Reason);
Require(report.SvgPaths.Count == 7 && report.PngPaths.Count == 7 && File.Exists(report.CsvPath), "Diagnostic export must publish seven SVGs, seven PNGs and one CSV.");
Require(Path.GetFileName(report.SvgPaths[6]) == "07-local-g2-diagnostic-candidate.svg", "Diagnostic SVG stem is incorrect.");
Require(Path.GetFileName(report.PngPaths[6]) == "07-local-g2-diagnostic-candidate.png", "Diagnostic PNG stem is incorrect.");
string diagnosticSvg = File.ReadAllText(report.SvgPaths[6]);
Require(diagnosticSvg.Contains("data-series=\"raw\"") && diagnosticSvg.Contains("data-series=\"local-g2-diagnostic\""), "Diagnostic SVG must contain raw and diagnostic samples.");
Require(diagnosticSvg.Contains("净空拒绝") && diagnosticSvg.Contains("未发布"), "Diagnostic SVG must disclose rejection and publication state.");
Require(!diagnosticSvg.Contains("violation-cross"), "Clearance rejection must not be drawn as an obstacle collision.");
Require(File.ReadAllText(report.CsvPath).Contains("LocalG2Quintic,Unchanged"), "CSV must retain the normal strict Local G2 row.");
```
Run `VerifyPng(File.ReadAllBytes(path))` for every returned PNG and use the existing `ContainsTemporaryFiles` helper to assert atomic publication. Also copy the evidence file to a temp file, alter the fixture hash, call the demo with a separate empty output path, require an exception, and require that the output directory was never created.
Run:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_local_g2_diagnostic_visualization.ps1
```
Expected: exit `0`, seven valid figures, raw and diagnostic series in `07`, no fabricated collision cross, and bad evidence rejected atomically.
- [ ] **Step 5: Commit only Task 3 files**
```powershell
git add -- `
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureModel.cs `
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureKind.cs `
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureSetBuilder.cs `
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/LocalG2DiagnosticVisualizationDemo.cs `
ClumsyPilot/tests/PathSmoothingPngVerificationHost/Program.cs `
ClumsyPilot/tests/verify_path_smoothing_local_g2_diagnostic_visualization.ps1
git -c core.autocrlf=false diff --cached --check
git commit --only -m "feat: export Local G2 diagnostic candidate" -- `
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureModel.cs `
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureKind.cs `
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/SmoothingFigureSetBuilder.cs `
ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/LocalG2DiagnosticVisualizationDemo.cs `
ClumsyPilot/tests/PathSmoothingPngVerificationHost/Program.cs `
ClumsyPilot/tests/verify_path_smoothing_local_g2_diagnostic_visualization.ps1
```
### Task 4: Add The User-Facing Export Script And Perform Full Acceptance
**Files:**
- Create: `ClumsyPilot/tests/run_local_g2_diagnostic_visualization.ps1`
- Modify: `ClumsyPilot/tests/verify_path_smoothing_png.ps1`
**Interfaces:**
- Consumes: the diagnostic host export command, its fixture path, its evidence path, and its bounded output directory.
- Produces: `ClumsyPilot/obj/path_smoothing_reports/local-g2-single-turn/07-local-g2-diagnostic-candidate.png` and six companion standard figures.
- [ ] **Step 1: Write the failing runner assertion in the PNG smoke test**
In `verify_path_smoothing_png.ps1`, add a call to the not-yet-created runner with an explicit output directory below `obj/path_smoothing_reports`, followed by an existence assertion for its primary PNG. Its default runner paths must be:
```powershell
[string]$FixturePath = (Join-Path $PSScriptRoot '..\ParkrobTrajplanner\PathSmoothing\Test\Fixtures\path-smoothing-fixtures.json'),
[string]$EvidencePath = (Join-Path $PSScriptRoot '..\ParkrobTrajplanner\PathSmoothing\Test\Fixtures\local-g2-diagnostic-single-turn.json'),
[string]$OutputDirectory = (Join-Path $PSScriptRoot '..\obj\path_smoothing_reports\local-g2-single-turn')
```
Add this smoke-test block after the existing host invocation, changing no unrelated test behavior:
```powershell
$runnerPath = Join-Path $PSScriptRoot 'run_local_g2_diagnostic_visualization.ps1'
$runnerOutput = Join-Path $PSScriptRoot '..\obj\path_smoothing_reports\local-g2-png-smoke'
& powershell -ExecutionPolicy Bypass -File $runnerPath -OutputDirectory $runnerOutput
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
$primaryPng = Join-Path $runnerOutput '07-local-g2-diagnostic-candidate.png'
if (-not (Test-Path -LiteralPath $primaryPng)) {
throw "Local G2 diagnostic runner did not publish $primaryPng"
}
```
Run the PNG smoke test. Expected: PowerShell reports that `run_local_g2_diagnostic_visualization.ps1` does not exist, so the runner acceptance assertion fails before implementation.
- [ ] **Step 2: Implement the bounded export script and extend the PNG smoke test**
Follow the existing `run_path_smoothing_comparison.ps1` root validation exactly: resolve `$clumsyPilotRoot`, require `$OutputDirectory` to equal or be below `$clumsyPilotRoot\obj\path_smoothing_reports`, resolve both input files, build `ClumsyPilot.csproj`, then execute:
```powershell
& dotnet run --project $hostProject --no-restore -- --export-local-g2-diagnostic $resolvedFixturePath $resolvedEvidencePath $resolvedOutputDirectory
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
Write-Output "Local G2 diagnostic visualization written below $resolvedOutputDirectory"
```
In `verify_path_smoothing_png.ps1`, call the new diagnostic verification wrapper after the existing host verification so both standard six-file and diagnostic seven-file image contracts run in the normal PNG check. Retain the runner call and primary-PNG existence assertion added in Step 1; use a `local-g2-png-smoke` output subdirectory below the allowed report root.
- [ ] **Step 3: Run the full automated acceptance sequence**
Run these commands in order and inspect every exit code:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_comparison.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_svg_csv.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_local_g2_diagnostic_evidence.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_local_g2_diagnostic_visualization.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_path_smoothing_png.ps1
powershell -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\run_local_g2_diagnostic_visualization.ps1
```
Expected: every command exits `0`; the primary PNG, matching SVG, and `comparison.csv` exist under `ClumsyPilot/obj/path_smoothing_reports/local-g2-single-turn`.
- [ ] **Step 4: Inspect the generated image and report the factual result**
Open the primary file with the local image viewer:
```text
ClumsyPilot/obj/path_smoothing_reports/local-g2-single-turn/07-local-g2-diagnostic-candidate.png
```
Confirm visually that the map is nonblank, the gray raw/final path and red diagnostic candidate are both visible, the legend/title disclose `净空拒绝` and `未发布`, there is no collision cross, and text remains inside the 4296-by-3120 PNG frame. Also open `02-all-paths-comparison.png` and `06-curvature-comparison.png` from the same directory to confirm that normal Local G2 appears with `Unchanged` alongside the existing methods.
- [ ] **Step 5: Commit only Task 4 source/test files**
Do not commit generated `obj` artifacts. Commit only the runner and PNG smoke-test changes:
```powershell
git add -- `
ClumsyPilot/tests/run_local_g2_diagnostic_visualization.ps1 `
ClumsyPilot/tests/verify_path_smoothing_png.ps1
git -c core.autocrlf=false diff --cached --check
git commit --only -m "test: add Local G2 diagnostic visualization runner" -- `
ClumsyPilot/tests/run_local_g2_diagnostic_visualization.ps1 `
ClumsyPilot/tests/verify_path_smoothing_png.ps1
```
## Final Verification Checklist
- [ ] Re-read [`2026-08-02-local-g2-diagnostic-visualization-design.md`](../specs/2026-08-02-local-g2-diagnostic-visualization-design.md) and map every acceptance criterion to a passing command or visual check above.
- [ ] Run `git -c core.autocrlf=false diff --check` only on the files changed by these tasks.
- [ ] Verify that no `PathSmoothing/LocalG2/` implementation, evaluator, validator, publishing, or ranking file changed.
- [ ] Verify each task commit contains only its listed paths.
- [ ] Report the primary image path and state plainly that the red curve is a rejected diagnostic candidate, while the strict Local G2 result remains `Unchanged`.
@@ -1,915 +0,0 @@
# Daily Summary Job Domain Visualization Upgrade Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use `subagent-driven-development` (recommended) or `executing-plans` to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Upgrade `daily-summary-job` so it understands the current task, renders the function or algorithm's real domain effect, and connects visible objects to the current problem, cause, consequence, correction, expected result, and task-matched verification evidence.
**Architecture:** Extend the normalized report facts with optional algorithm views and task-specific validation facts. Keep one diagnostic interaction shell, but render its central canvas through a declarative adapter registry selected from the current task's semantics. Split maintainable CSS and JavaScript assets during skill development, then inline them into the final self-contained HTML during rendering.
**Tech Stack:** Python 3.12 standard library, `unittest`, HTML5, CSS, vanilla JavaScript, SVG/DOM, Node syntax checks.
## Global Constraints
- Implement the approved design in `docs/superpowers/specs/2026-08-03-daily-summary-job-domain-visualization-upgrade-design.md`.
- Modify the personal skill at `C:\Users\admin\.codex\skills\daily-summary-job`; request filesystem approval when the execution environment requires it.
- Do not hard-code trajectory planning as the meaning of algorithm visualization. Select the view from the current task's purpose, observable business objects, inputs, outputs, and correctness constraints.
- A generic flow diagram may assist navigation, but it must not replace a domain effect view when spatial, numeric, temporal, state, search, or structured-data evidence exists.
- Distinguish actual observation, static reconstruction, conceptual preview, verified result, and conflicting evidence in both data and presentation.
- Build validation scenarios from the current task and its correctness constraints. Do not substitute an unrelated fixed test matrix.
- Preserve reports that omit the new optional fields.
- Final HTML must contain no external resource or network dependency.
- Do not modify business code, run expensive tests by default, stage changes, or create Git commits.
- Preserve the current UTF-8, date/module classification, checkpoint limits, stable issue IDs, and Markdown/HTML pairing behavior.
## User-Approved Scope Adjustment
This implementation ships only the generic declarative `composite-scene` renderer and the unified diagnosis shell. Do not implement dedicated `spatial-scene`, `cartesian-series`, `graph-network`, `state-machine`, or `data-flow` renderers in this round. Keep the adapter registry as an extension point, so future task-specific work can add those renderers without changing the report contract.
---
## File Structure
**Modify**
- `C:\Users\admin\.codex\skills\daily-summary-job\SKILL.md` — task understanding, visualization-brief generation, domain-view selection, and task-matched validation workflow.
- `C:\Users\admin\.codex\skills\daily-summary-job\references\report-schema.md` — optional algorithm-view, issue-target, solution-preview, verified-result, and task-validation contracts.
- `C:\Users\admin\.codex\skills\daily-summary-job\scripts\prepare_report.py` — validate new facts, render the Markdown algorithm section, bundle assets, and validate rendered links.
- `C:\Users\admin\.codex\skills\daily-summary-job\scripts\test_prepare_report.py` — schema, backward compatibility, Markdown, asset bundling, adapter, interaction, and CLI regression tests.
- `C:\Users\admin\.codex\skills\daily-summary-job\assets\interactive-report-template.html` — diagnostic-shell markup and asset placeholders.
- `C:\Users\admin\.codex\skills\daily-summary-job\agents\openai.yaml` — UI description and default prompt for task-matched domain visualization.
**Create**
- `C:\Users\admin\.codex\skills\daily-summary-job\assets\visualization-styles.css` — domain canvas, view modes, diagnostic drawer, evidence states, responsiveness, and reduced-motion styles.
- `C:\Users\admin\.codex\skills\daily-summary-job\assets\visualization-adapters.js` — declarative scene-object renderer registry and built-in layout strategies.
- `C:\Users\admin\.codex\skills\daily-summary-job\assets\visualization-runtime.js` — report state, selectors, view switching, target highlighting, diagnosis rendering, and keyboard interaction.
The three development assets are embedded into every rendered report. They must never remain as runtime `<link>` or `<script src>` dependencies.
---
### Task 1: Extend the normalized fact contract without breaking old reports
**Files:**
- Modify: `C:\Users\admin\.codex\skills\daily-summary-job\scripts\test_prepare_report.py:152-365`
- Modify: `C:\Users\admin\.codex\skills\daily-summary-job\scripts\prepare_report.py:32-340`
**Interfaces:**
- Consumes: existing `validate_report_data(data: dict[str, Any]) -> None`.
- Produces: optional `algorithm_views: list[dict]`, optional `task_validation: dict`, optional issue visualization fields, and `visual_target_index(data) -> dict[str, set[str]]`.
- [ ] **Step 1: Add a complete task-matched visualization fixture**
Add this helper to `ReportRenderingTests` and use it only in new visualization tests so the existing `sample_data()` remains a legacy-format fixture:
```python
def visualization_data(self):
data = self.sample_data()
data["algorithm_views"] = [
{
"id": "local-g2-smoother",
"name": "Local G2 路径平滑",
"purpose": "把粗路径转换为满足连续性、曲率和安全约束的可执行路径。",
"domain": "geometry-smoothing",
"adapter": "spatial-scene",
"evidence_state": "actual",
"inputs": [
{"id": "coarse-path", "label": "粗路径", "detail": "离散位姿序列", "source_ref": "tests/input.json"}
],
"outputs": [
{"id": "smooth-path", "label": "平滑路径", "detail": "连续候选轨迹", "source_ref": "tests/output.json"}
],
"constraints": [
{"id": "curvature-limit", "label": "曲率上限", "detail": "abs(kappa) <= 0.2", "status": "失败", "source_ref": "tests/output.json"}
],
"stages": [
{
"id": "candidate-evaluation",
"label": "候选评价",
"detail": "比较连续性、曲率和碰撞约束。",
"function_refs": ["PathSmoothing/CandidateEvaluator.cs"],
"target_ids": ["current-path", "curvature-peak"],
}
],
"scene": {
"coordinate_system": "cartesian",
"objects": [
{
"id": "current-path",
"kind": "polyline",
"label": "当前路径",
"evidence_state": "actual",
"source_ref": "tests/output.json",
"data": {"points": [[0, 0], [1, 0.4], [2, 1.1]]},
},
{
"id": "curvature-peak",
"kind": "annotation",
"label": "曲率峰值",
"evidence_state": "actual",
"source_ref": "tests/output.json",
"data": {"x": 1, "y": 0.4, "value": 0.31},
},
{
"id": "preview-path",
"kind": "polyline",
"label": "候选修正路径",
"evidence_state": "conceptual",
"source_ref": "docs/solution.md",
"data": {"points": [[0, 0], [1, 0.3], [2, 1.1]]},
},
],
"layers": [
{"id": "baseline", "label": "正常机制", "mode": "baseline", "object_ids": ["current-path"]},
{"id": "current", "label": "当前问题", "mode": "current", "object_ids": ["current-path", "curvature-peak"]},
{"id": "proposed", "label": "修正预演", "mode": "proposed", "object_ids": ["preview-path"]},
],
},
"source_refs": ["tests/input.json", "tests/output.json"],
}
]
data["issues"][0].update(
{
"algorithm_view_id": "local-g2-smoother",
"target_ids": ["curvature-peak"],
"effect_target_ids": ["current-path"],
"solution_preview": {
"summary": "重新约束连接段导数。",
"expected_result": "曲率峰值回到上限内。",
"evidence_state": "conceptual",
"target_ids": ["preview-path"],
},
}
)
data["task_validation"] = {
"task": "验证 Local G2 平滑候选是否满足当前路径约束。",
"correctness_criteria": [
{"id": "criterion-curvature", "statement": "全路径曲率不超过 0.2。", "source_ref": "tests/output.json"}
],
"checks": [
{
"id": "check-curvature",
"name": "曲率扫描",
"status": "失败",
"criterion_ids": ["criterion-curvature"],
"command": "verify_path_smoothing.ps1",
"result": "max_abs_curvature=0.31",
"evidence_ref": "tests/output.json",
}
],
"missing_evidence": [
{
"criterion_id": "criterion-curvature",
"needed": "修正后的相同输入扫描结果",
"suggested_check": "对同一输入重新运行曲率扫描。",
}
],
}
return data
```
- [ ] **Step 2: Write failing contract and compatibility tests**
Add tests with these exact assertions:
```python
def test_accepts_legacy_report_without_algorithm_views(self):
self.require_target().validate_report_data(self.sample_data())
def test_accepts_linked_algorithm_view_and_task_validation(self):
target = self.require_target()
data = self.visualization_data()
target.validate_report_data(data)
self.assertEqual(
{"current-path", "curvature-peak", "preview-path", "candidate-evaluation"},
target.visual_target_index(data)["local-g2-smoother"],
)
def test_rejects_unknown_visual_target(self):
data = self.visualization_data()
data["issues"][0]["target_ids"] = ["missing-target"]
with self.assertRaisesRegex(ValueError, "unknown visual target"):
self.require_target().validate_report_data(data)
def test_verified_result_requires_verified_state_and_validation_reference(self):
data = self.visualization_data()
data["issues"][0]["verified_result"] = {
"summary": "看起来已经改善。",
"evidence_state": "conceptual",
"target_ids": ["preview-path"],
"validation_refs": [],
}
with self.assertRaisesRegex(ValueError, "verified_result"):
self.require_target().validate_report_data(data)
def test_task_validation_rejects_unknown_criterion(self):
data = self.visualization_data()
data["task_validation"]["checks"][0]["criterion_ids"] = ["criterion-missing"]
with self.assertRaisesRegex(ValueError, "unknown correctness criterion"):
self.require_target().validate_report_data(data)
```
- [ ] **Step 3: Run the focused tests and confirm RED**
Run:
```powershell
python -X utf8 C:\Users\admin\.codex\skills\daily-summary-job\scripts\test_prepare_report.py ReportRenderingTests
```
Expected: new tests fail because `visual_target_index` and visualization validation do not exist; existing legacy tests remain green.
- [ ] **Step 4: Add validation constants and helpers**
Add near the existing constants:
```python
VISUAL_EVIDENCE_STATES = {"actual", "static", "conceptual", "verified", "conflict"}
VIEW_MODES = {"baseline", "current", "proposed", "verified"}
VISUAL_ID = re.compile(r"[a-z0-9][a-z0-9-]{1,63}")
```
Add helpers before `validate_report_data`:
```python
def _require_visual_id(value: Any, field: str) -> str:
text = _require_text(value, field)
if not VISUAL_ID.fullmatch(text):
raise ValueError(f"invalid {field}: {text}")
return text
def _require_text_list(value: Any, field: str) -> list[str]:
return [_require_text(item, f"{field} item") for item in _require_list(value, field)]
def _validate_named_fact(item: Any, field: str, required: tuple[str, ...]) -> None:
if not isinstance(item, dict):
raise ValueError(f"{field} item must be an object")
for key in required:
_require_text(item.get(key), f"{field}.{key}")
def visual_target_index(data: dict[str, Any]) -> dict[str, set[str]]:
result: dict[str, set[str]] = {}
for view in data.get("algorithm_views", []):
targets = {stage["id"] for stage in view["stages"]}
targets.update(obj["id"] for obj in view["scene"]["objects"])
result[view["id"]] = targets
return result
```
- [ ] **Step 5: Validate algorithm views, issue links, and task criteria**
Implement `_validate_algorithm_views(data)` and `_validate_task_validation(data)` and call them from `validate_report_data` before issue-link validation. Require the exact fields used by `visualization_data()`, unique view/stage/object/layer IDs, valid evidence states, valid layer modes, stage target references, and layer object references. Require every scene object's `data` to be an object. Require `actual` and `verified` scene objects to carry a non-empty `source_ref`; `static` and `conceptual` objects may reference source or design evidence but must retain their explicit state. Permit any safe adapter slug so future tasks are not restricted to a fixed domain list.
For each issue, validate optional fields only when present:
```python
view_id = issue.get("algorithm_view_id")
if view_id is not None:
view_id = _require_visual_id(view_id, "issue.algorithm_view_id")
if view_id not in targets_by_view:
raise ValueError(f"unknown algorithm view: {view_id}")
for field in ("target_ids", "effect_target_ids"):
for target_id in _require_text_list(issue.get(field, []), f"issue.{field}"):
if target_id not in targets_by_view[view_id]:
raise ValueError(f"unknown visual target: {target_id}")
```
Require `solution_preview.evidence_state` to be `conceptual` or `static`. Require `verified_result.evidence_state == "verified"` and at least one non-empty `validation_refs` item.
- [ ] **Step 6: Run focused and full tests and confirm GREEN**
Run:
```powershell
python -X utf8 C:\Users\admin\.codex\skills\daily-summary-job\scripts\test_prepare_report.py ReportRenderingTests
python -X utf8 C:\Users\admin\.codex\skills\daily-summary-job\scripts\test_prepare_report.py
```
Expected: focused contract tests pass; the full suite retains all existing passes plus the new tests.
- [ ] **Step 7: Review the scoped diff without staging or committing**
Run:
```powershell
git diff --no-index -- NUL C:\Users\admin\.codex\skills\daily-summary-job\scripts\prepare_report.py
```
Expected: only the intended contract helpers and validation paths are present. Do not run `git add` or `git commit`.
---
### Task 2: Render algorithm purpose and task-matched validation in Markdown
**Files:**
- Modify: `C:\Users\admin\.codex\skills\daily-summary-job\scripts\test_prepare_report.py:249-285`
- Modify: `C:\Users\admin\.codex\skills\daily-summary-job\scripts\prepare_report.py:342-452`
**Interfaces:**
- Consumes: validated `algorithm_views` and `task_validation` from Task 1.
- Produces: `_render_algorithm_markdown(data: dict[str, Any]) -> list[str]` and `_render_task_validation_markdown(data: dict[str, Any]) -> list[str]`.
- [ ] **Step 1: Write failing Markdown assertions**
```python
def test_renders_algorithm_function_domain_effect_and_task_validation(self):
markdown = self.require_target().render_markdown(self.visualization_data())
for expected in (
"## 2. 当前函数与算法功能",
"Local G2 路径平滑",
"把粗路径转换为满足连续性、曲率和安全约束的可执行路径",
"候选评价",
"PathSmoothing/CandidateEvaluator.cs",
"## 8. 当前任务匹配的验证",
"全路径曲率不超过 0.2",
"max_abs_curvature=0.31",
"修正后的相同输入扫描结果",
):
self.assertIn(expected, markdown)
def test_legacy_markdown_keeps_original_section_numbers(self):
markdown = self.require_target().render_markdown(self.sample_data())
self.assertIn("## 2. 今日完成的工作", markdown)
self.assertIn("## 7. 证据索引", markdown)
self.assertNotIn("当前函数与算法功能", markdown)
```
- [ ] **Step 2: Run the two tests and confirm RED**
Run:
```powershell
python -X utf8 C:\Users\admin\.codex\skills\daily-summary-job\scripts\test_prepare_report.py ReportRenderingTests.test_renders_algorithm_function_domain_effect_and_task_validation ReportRenderingTests.test_legacy_markdown_keeps_original_section_numbers
```
Expected: the visualization-aware test fails; the legacy numbering test passes.
- [ ] **Step 3: Add deterministic Markdown helpers**
Implement `_render_algorithm_markdown` so each view shows purpose, domain, evidence state, inputs, outputs, constraints, stages, function references, and source references. Implement `_render_task_validation_markdown` so criteria, executed checks, and missing evidence are separate lists. Do not infer pass/fail or substitute generic tests.
Use this section order only when `algorithm_views` is non-empty:
```text
1. 今日结论摘要
2. 当前函数与算法功能
3. 今日完成的工作
4. 今日发现的问题
5. 问题如何被发现及证据
6. 已采取的改善和验证结果
7. 尚未解决的风险与下一步
8. 当前任务匹配的验证
9. 证据索引
```
Keep the current seven-section output byte-compatible in structure when `algorithm_views` is absent.
- [ ] **Step 4: Run focused and full tests and confirm GREEN**
Run the commands from Step 2, then:
```powershell
python -X utf8 C:\Users\admin\.codex\skills\daily-summary-job\scripts\test_prepare_report.py
```
Expected: all Markdown and legacy tests pass.
- [ ] **Step 5: Inspect a rendered Markdown sample**
Run:
```powershell
python -X utf8 -c "import importlib.util; from pathlib import Path; p=Path(r'C:\Users\admin\.codex\skills\daily-summary-job\scripts\prepare_report.py'); s=importlib.util.spec_from_file_location('daily',p); m=importlib.util.module_from_spec(s); s.loader.exec_module(m); print(m.render_markdown(__import__('json').loads(Path('sample-visual-report.json').read_text(encoding='utf-8'))))"
```
Before running, create `sample-visual-report.json` in a temporary directory from `visualization_data()` through the test helper or CLI fixture, then remove only that temporary file. Expected: algorithm function, task-specific criteria, run checks, and missing evidence are visibly separated.
---
### Task 3: Split development assets and inline them into the final HTML
**Files:**
- Create: `C:\Users\admin\.codex\skills\daily-summary-job\assets\visualization-styles.css`
- Create: `C:\Users\admin\.codex\skills\daily-summary-job\assets\visualization-adapters.js`
- Create: `C:\Users\admin\.codex\skills\daily-summary-job\assets\visualization-runtime.js`
- Modify: `C:\Users\admin\.codex\skills\daily-summary-job\assets\interactive-report-template.html`
- Modify: `C:\Users\admin\.codex\skills\daily-summary-job\scripts\prepare_report.py:453-470,624-660`
- Modify: `C:\Users\admin\.codex\skills\daily-summary-job\scripts\test_prepare_report.py:286-342`
**Interfaces:**
- Produces: `load_visual_assets(asset_dir: Path | None = None) -> dict[str, str]`.
- Changes: `render_html(data, template, visual_assets=None) -> str` while preserving existing two-argument callers.
- [ ] **Step 1: Write failing asset-bundling tests**
```python
def test_inlines_visual_assets_without_runtime_dependencies(self):
target = self.require_html_target()
html = target.render_html(
self.visualization_data(), TEMPLATE_PATH.read_text(encoding="utf-8")
)
self.assertNotIn("__VISUAL_STYLES__", html)
self.assertNotIn("__VISUAL_ADAPTERS__", html)
self.assertNotIn("__VISUAL_RUNTIME__", html)
self.assertIn("DailySummaryVisuals", html)
self.assertNotRegex(html, r"<link\b|<script[^>]+src=|https?://")
def test_rejects_missing_or_duplicate_asset_placeholder(self):
target = self.require_html_target()
template = TEMPLATE_PATH.read_text(encoding="utf-8").replace("__VISUAL_RUNTIME__", "")
with self.assertRaisesRegex(ValueError, "visual asset placeholder"):
target.render_html(self.visualization_data(), template)
```
- [ ] **Step 2: Run the focused tests and confirm RED**
Run:
```powershell
python -X utf8 C:\Users\admin\.codex\skills\daily-summary-job\scripts\test_prepare_report.py HtmlRenderingTests.test_inlines_visual_assets_without_runtime_dependencies HtmlRenderingTests.test_rejects_missing_or_duplicate_asset_placeholder
```
Expected: failures because the template and loader do not contain the new placeholders.
- [ ] **Step 3: Move styles and scripts into focused development files**
Move the current `<style>` content to `visualization-styles.css` and the current inline behavior to `visualization-runtime.js`. Initialize `visualization-adapters.js` with this stable public namespace:
```javascript
'use strict';
globalThis.DailySummaryVisuals = (() => {
const registry = new Map();
function register(name, renderer) {
if (!/^[a-z0-9][a-z0-9-]+$/.test(name) || typeof renderer !== 'function') {
throw new TypeError('invalid visualization adapter');
}
registry.set(name, renderer);
}
function select(name) {
return registry.get(name) || registry.get('composite-scene');
}
function render(view, root, context) {
const renderer = select(view.adapter);
if (!renderer) throw new Error('composite-scene adapter is not registered');
return renderer(view, root, context);
}
return { register, select, render };
})();
```
Replace template bodies with exact single placeholders:
```html
<style>__VISUAL_STYLES__</style>
...
<script id="report-data" type="application/json">__REPORT_DATA__</script>
<script>__VISUAL_ADAPTERS__</script>
<script>__VISUAL_RUNTIME__</script>
```
- [ ] **Step 4: Implement the asset loader and renderer replacement**
```python
VISUAL_ASSET_FILES = {
"__VISUAL_STYLES__": "visualization-styles.css",
"__VISUAL_ADAPTERS__": "visualization-adapters.js",
"__VISUAL_RUNTIME__": "visualization-runtime.js",
}
def load_visual_assets(asset_dir: Path | None = None) -> dict[str, str]:
root = Path(asset_dir or Path(__file__).parent.parent / "assets")
return {
placeholder: (root / filename).read_text(encoding="utf-8")
for placeholder, filename in VISUAL_ASSET_FILES.items()
}
def render_html(
data: dict[str, Any],
template: str,
visual_assets: dict[str, str] | None = None,
) -> str:
validate_report_data(data)
replacements = {
"__REPORT_DATA__": safe_json_for_html(data),
**(visual_assets or load_visual_assets()),
}
rendered = template
for placeholder, value in replacements.items():
if rendered.count(placeholder) != 1:
label = "report data placeholder" if placeholder == "__REPORT_DATA__" else "visual asset placeholder"
raise ValueError(f"template must contain exactly one {label}: {placeholder}")
rendered = rendered.replace(placeholder, value)
return rendered
```
When `--template` points to a custom template, continue loading the trusted bundled assets from the skill's `assets` directory unless a future explicit CLI option changes that contract.
- [ ] **Step 5: Run bundling tests, full tests, and syntax checks**
```powershell
python -X utf8 C:\Users\admin\.codex\skills\daily-summary-job\scripts\test_prepare_report.py
node --check C:\Users\admin\.codex\skills\daily-summary-job\assets\visualization-adapters.js
node --check C:\Users\admin\.codex\skills\daily-summary-job\assets\visualization-runtime.js
```
Expected: Python suite passes; both Node checks exit 0.
---
### Task 4: Implement the generic declarative domain-effect renderer
**Files:**
- Modify: `C:\Users\admin\.codex\skills\daily-summary-job\assets\visualization-adapters.js`
- Modify: `C:\Users\admin\.codex\skills\daily-summary-job\scripts\test_prepare_report.py`
**Interfaces:**
- Consumes: `algorithm_views[].scene.objects`, `scene.layers`, and the selected view mode.
- Produces: SVG/DOM elements carrying `data-target-id`, `data-evidence-state`, and accessible labels.
- Public JS API: `DailySummaryVisuals.register(name, renderer)`, `.select(name)`, and `.render(view, root, context)`.
- [ ] **Step 1: Add task-derived adapter assertions**
Use `visualization_data()` as the business fixture. Add static and generated-HTML assertions:
```python
def test_domain_adapter_renders_task_objects_not_fixed_demo_content(self):
html = self.require_html_target().render_html(
self.visualization_data(), TEMPLATE_PATH.read_text(encoding="utf-8")
)
for value in ("local-g2-smoother", "current-path", "curvature-peak", "preview-path"):
self.assertIn(value, html)
self.assertNotIn("固定轨迹示例", html)
def test_unknown_safe_adapter_has_composite_fallback(self):
data = self.visualization_data()
data["algorithm_views"][0]["adapter"] = "custom-business-domain"
html = self.require_html_target().render_html(
data, TEMPLATE_PATH.read_text(encoding="utf-8")
)
self.assertIn("custom-business-domain", html)
self.assertIn("composite-scene", html)
```
- [ ] **Step 2: Run the focused tests and confirm RED**
Expected: the fallback or declarative object hooks are missing.
- [ ] **Step 3: Add safe DOM/SVG construction helpers**
Implement helpers that assign text through `textContent` and SVG attributes through `setAttribute`; never concatenate untrusted labels into `innerHTML`:
```javascript
const SVG_NS = 'http://www.w3.org/2000/svg';
function element(name, attrs = {}, text = '') {
const node = document.createElement(name);
Object.entries(attrs).forEach(([key, value]) => node.setAttribute(key, String(value)));
if (text) node.textContent = text;
return node;
}
function svgElement(name, attrs = {}) {
const node = document.createElementNS(SVG_NS, name);
Object.entries(attrs).forEach(([key, value]) => node.setAttribute(key, String(value)));
return node;
}
function markTarget(node, object) {
node.dataset.targetId = object.id;
node.dataset.evidenceState = object.evidence_state;
node.setAttribute('tabindex', '0');
node.setAttribute('role', 'button');
node.setAttribute('aria-label', `${object.label}${object.evidence_state}`);
return node;
}
```
- [ ] **Step 4: Implement adapter strategies over shared primitives**
Register these layout strategies, while keeping their data task-driven:
- `composite-scene`: render supplied points, polylines, curves, regions, nodes, edges, state blocks, data items, annotations, and clear unsupported-kind cards for the remainder.
The generic renderer must filter visible objects from the selected `scene.layers[].object_ids`; it must not invent domain samples. Unknown adapter names must select `composite-scene`. Do not add dedicated renderer implementations in this task.
- [ ] **Step 5: Add evidence-state and target CSS**
In `visualization-styles.css`, use line style, icon/text, and color together:
```css
[data-evidence-state="actual"] { --state-color: var(--blue); }
[data-evidence-state="static"] { --state-color: var(--amber); }
[data-evidence-state="conceptual"] { --state-color: var(--amber); stroke-dasharray: 8 6; opacity: .82; }
[data-evidence-state="verified"] { --state-color: var(--green); }
[data-evidence-state="conflict"] { --state-color: var(--red); stroke-dasharray: 3 4; }
[data-target-id].is-highlighted { filter: drop-shadow(0 0 5px var(--state-color)); }
[data-target-id]:focus-visible { outline: 3px solid #e7a628; outline-offset: 3px; }
```
- [ ] **Step 6: Run Python tests and Node syntax checks**
Use the commands from Task 3 Step 5. Expected: all pass, and no test claims business correctness beyond the current fixture's actual evidence.
---
### Task 5: Build the four-mode interactive diagnosis shell
**Files:**
- Modify: `C:\Users\admin\.codex\skills\daily-summary-job\assets\interactive-report-template.html`
- Modify: `C:\Users\admin\.codex\skills\daily-summary-job\assets\visualization-runtime.js`
- Modify: `C:\Users\admin\.codex\skills\daily-summary-job\assets\visualization-styles.css`
- Modify: `C:\Users\admin\.codex\skills\daily-summary-job\scripts\test_prepare_report.py`
**Interfaces:**
- State: `{ algorithmId, issueId, mode, solutionIndex }`.
- Modes: `baseline`, `current`, `proposed`, `verified`.
- Consumes: Task 1 issue links and Task 4 adapter API.
- [ ] **Step 1: Write failing interaction-hook tests**
```python
def test_renders_algorithm_selector_four_modes_domain_canvas_and_diagnosis_card(self):
html = self.require_html_target().render_html(
self.visualization_data(), TEMPLATE_PATH.read_text(encoding="utf-8")
)
for hook in (
'id="algorithm-selector"',
'data-view-mode="baseline"',
'data-view-mode="current"',
'data-view-mode="proposed"',
'data-view-mode="verified"',
'id="domain-canvas"',
'id="diagnosis-current"',
'id="diagnosis-cause"',
'id="diagnosis-impact"',
'id="diagnosis-solution"',
'id="diagnosis-expected"',
'id="task-validation"',
):
self.assertIn(hook, html)
def test_verified_mode_is_guarded_by_verified_result(self):
runtime = (TEMPLATE_PATH.parent / "visualization-runtime.js").read_text(encoding="utf-8")
self.assertIn("hasVerifiedResult", runtime)
self.assertIn("button.disabled", runtime)
self.assertIn("尚无修正后的匹配验证证据", runtime)
```
- [ ] **Step 2: Run the focused tests and confirm RED**
Expected: the new shell hooks and verified-result guard are absent.
- [ ] **Step 3: Replace the two-column issue workbench with the approved shell**
Add:
- algorithm and issue selectors;
- four mode buttons with `aria-pressed`;
- layer toggles;
- central `#domain-canvas`;
- algorithm-stage navigation;
- object diagnosis card;
- expandable source/test evidence;
- existing solution steps, validation gates, and roadmap below the canvas.
When `algorithm_views` is absent, hide the algorithm controls and retain the legacy text diagnosis behavior.
- [ ] **Step 4: Implement one state-driven render path**
In `visualization-runtime.js`, use one render function so selectors, modes, canvas, diagnosis, validation, and buttons never drift:
```javascript
const report = JSON.parse(document.getElementById('report-data').textContent);
const views = Array.isArray(report.algorithm_views) ? report.algorithm_views : [];
const issues = Array.isArray(report.issues) ? report.issues : [];
const state = {
algorithmId: views[0]?.id || '',
issueId: issues[0]?.id || '',
mode: views.length ? 'baseline' : 'current',
solutionIndex: 0,
};
function currentView() {
return views.find((view) => view.id === state.algorithmId) || null;
}
function currentIssue() {
return issues.find((issue) => issue.id === state.issueId) || null;
}
function hasVerifiedResult(issue) {
return Boolean(issue?.verified_result?.evidence_state === 'verified' && issue.verified_result.validation_refs?.length);
}
function renderApp() {
const view = currentView();
const issue = currentIssue();
renderSelectors(view, issue);
renderModeButtons(issue);
renderDomainCanvas(view, issue);
renderDiagnosis(issue);
renderTaskValidation(report.task_validation);
renderExistingReportSections(issue);
}
```
- [ ] **Step 5: Implement mode-to-layer and diagnosis behavior**
- `baseline`: show the normal algorithm layer and purpose/input/output/constraints.
- `current`: show current layer, highlight `target_ids`, then `effect_target_ids` in propagation order.
- `proposed`: show `solution_preview.target_ids`, expected result, and conceptual/static label.
- `verified`: enable only when `hasVerifiedResult(issue)`; show verified targets and validation references.
Clicking or pressing Enter/Space on a visual target must select the linked issue. Arrow keys change issues only when focus is not in a form control; mode buttons and targets retain visible focus.
- [ ] **Step 6: Add responsive and reduced-motion behavior**
At desktop width use mode rail + canvas + diagnosis drawer. Under 900px stack the drawer below the canvas. Under 560px use single-column selectors and controls. When `prefers-reduced-motion: reduce` is active, reveal the complete impact path immediately rather than animating it.
- [ ] **Step 7: Extend rendered-pair validation to cover algorithm facts**
Update `_validate_rendered_text` so every algorithm view ID, view evidence state, linked issue target ID, correctness criterion ID, and executed check ID exists in the generated HTML. Require the view name, purpose, criterion statement, and check result in Markdown. Keep the existing issue ID/evidence checks and external-resource rejection.
Add a negative test that removes `curvature-peak` from rendered HTML and expects `validate` to fail with `HTML is missing visual target: curvature-peak`.
- [ ] **Step 8: Run the full suite and generated-HTML validation**
```powershell
python -X utf8 C:\Users\admin\.codex\skills\daily-summary-job\scripts\test_prepare_report.py
node --check C:\Users\admin\.codex\skills\daily-summary-job\assets\visualization-adapters.js
node --check C:\Users\admin\.codex\skills\daily-summary-job\assets\visualization-runtime.js
```
Expected: all tests and syntax checks pass; generated HTML contains no external resource.
---
### Task 6: Teach the skill the task-understanding and dynamic-validation workflow
**Files:**
- Modify: `C:\Users\admin\.codex\skills\daily-summary-job\SKILL.md`
- Modify: `C:\Users\admin\.codex\skills\daily-summary-job\references\report-schema.md`
- Modify: `C:\Users\admin\.codex\skills\daily-summary-job\agents\openai.yaml`
**Interfaces:**
- Consumes: data contract and renderer from Tasks 1-5.
- Produces: repeatable Agent instructions that create task-matched views and validation facts without requiring the user to fill JSON manually.
- [ ] **Step 1: Add the mandatory understanding sequence to SKILL.md**
Insert a concise workflow before report JSON construction:
```markdown
## Build a task-matched algorithm view
When today's work changes, diagnoses, or discusses a function or algorithm:
1. Identify its business purpose, inputs, outputs, stages, observable objects, and correctness constraints from current evidence.
2. Decide what domain effect lets a reader see the algorithm working. Prefer spatial scenes, numeric plots, search/state structures, timelines, or transformed data over a generic flowchart when the evidence supports them.
3. Build one `algorithm_views` entry from actual run/test data when available. Label source reconstruction as `static` and solution prediction as `conceptual`.
4. Link every visual issue to existing stage/object IDs. Show current targets, effect propagation, candidate changes, and verified results as separate states.
5. If the evidence cannot support a credible domain view, list the missing evidence and omit the invented scene.
```
- [ ] **Step 2: Replace generic validation wording with task-matched validation**
```markdown
## Match validation to the current task
Derive correctness criteria from the selected function or algorithm, then locate only tests, commands, samples, and runtime evidence that directly evaluate those criteria. Record checks actually run, their exact results, and missing evidence separately. If no matching test exists, propose a task-specific check and keep the conclusion unverified. Never claim coverage from an unrelated fixed scenario.
```
Retain the existing safety rule against expensive tests by default.
- [ ] **Step 3: Document the complete schema and one non-prescriptive example**
In `references/report-schema.md`, document every Task 1 field, allowed evidence states, object/layer linking rules, task-validation shape, verified-result requirements, and legacy behavior. Use one example only to illustrate the contract, and state explicitly that its domain does not constrain adapter selection.
- [ ] **Step 4: Regenerate UI metadata from the updated skill**
Run:
```powershell
python -X utf8 C:\Users\admin\.codex\skills\.system\skill-creator\scripts\generate_openai_yaml.py C:\Users\admin\.codex\skills\daily-summary-job --interface 'display_name=Daily Summary Job' --interface 'short_description=按当前任务生成带领域算法诊断的交互日报' --interface 'default_prompt=使用 $daily-summary-job 理解当前任务和算法,以匹配的领域效果图展示正常机制、问题、影响、修正方案与验证结果,并更新今日日报。'
```
Expected: `agents/openai.yaml` contains only the interface block with the three supplied values and valid UTF-8 Chinese.
- [ ] **Step 5: Validate skill structure and concise loading behavior**
```powershell
python -X utf8 C:\Users\admin\.codex\skills\.system\skill-creator\scripts\quick_validate.py C:\Users\admin\.codex\skills\daily-summary-job
```
Expected: `Skill is valid!`. Confirm `SKILL.md` stays under 500 lines and keeps detailed field definitions in `references/report-schema.md`.
---
### Task 7: End-to-end verification on the current task and backward compatibility
**Files:**
- Test: all files under `C:\Users\admin\.codex\skills\daily-summary-job`
- Generate temporary outputs only under a verified temporary directory or this project's `dailywork_report` when explicitly updating the real report.
**Interfaces:**
- Consumes: completed skill from Tasks 1-6.
- Produces: fresh verification evidence for schema, rendering, interaction hooks, self-containment, task matching, and legacy reports.
- [ ] **Step 1: Run the complete automated suite**
```powershell
python -X utf8 C:\Users\admin\.codex\skills\daily-summary-job\scripts\test_prepare_report.py
```
Expected: every test passes with zero failures. Record the actual test count; do not reuse the previous count of 31.
- [ ] **Step 2: Run skill and JavaScript validation**
```powershell
python -X utf8 C:\Users\admin\.codex\skills\.system\skill-creator\scripts\quick_validate.py C:\Users\admin\.codex\skills\daily-summary-job
node --check C:\Users\admin\.codex\skills\daily-summary-job\assets\visualization-adapters.js
node --check C:\Users\admin\.codex\skills\daily-summary-job\assets\visualization-runtime.js
```
Expected: skill valid; both JavaScript files exit 0.
- [ ] **Step 3: Run a temporary legacy report round trip**
Use the existing `sample_data()` shape without `algorithm_views`. Run `render`, `validate`, and `render --update` in a new temporary project. Expected: Markdown and HTML are created, validate returns `valid: true`, and update reuses the same pair.
- [ ] **Step 4: Run a temporary current-task domain-view round trip**
Use the Task 1 `visualization_data()` facts, which match the current path-smoothing work rather than an unrelated generic test. Run `render`, then `validate`. Assert:
- `valid` is `true`;
- HTML contains the task's actual object IDs and values;
- current, proposed, and verified controls are present;
- verified mode is disabled because this fixture has no verified result;
- Markdown contains the task-specific criterion and missing evidence;
- no external URL, `<link>`, or `<script src>` exists.
- [ ] **Step 5: Verify failure gates with mutations**
Starting from the same task facts, independently mutate and reject:
- an unknown `target_id`;
- a proposed view marked `verified` without validation references;
- a check referencing an unknown correctness criterion;
- a layer referencing an unknown object;
- a rendered HTML file containing an external URL.
Expected: each mutation returns a non-zero CLI status and an error naming the violated contract.
- [ ] **Step 6: Perform live visual and interaction QA when a browser runtime is available**
Open the generated domain-view HTML and verify:
- algorithm and issue selection;
- all four mode controls;
- task-specific domain objects, not a fixed demo;
- click/keyboard target selection;
- cause and effect highlighting;
- solution-step preview;
- verified-mode guard;
- desktop and narrow-screen layout;
- reduced-motion behavior.
If the browser runtime is unavailable, record this exact check as `待验证风险`; source inspection and syntax checks do not replace visual QA.
- [ ] **Step 7: Review only skill and report artifacts; do not commit**
Run scoped file listings and diffs. Confirm no business source file, staging index, or Git commit was changed. Report created/modified skill files, verification commands, exact pass counts, and any remaining visual-QA risk.
---
## Plan Completion Criteria
- All seven tasks satisfy their focused tests before the next task begins.
- The full suite passes after each task that changes Python or JavaScript behavior.
- A legacy report and a task-matched domain report both pass CLI validation.
- The task-matched report visibly connects domain objects to problem, cause, consequence, solution preview, expected result, and available verification evidence.
- No fixed domain example is presented as a universal validation scenario.
- No external dependency, business-code edit, Git staging, or Git commit is introduced.
@@ -1,432 +0,0 @@
# Daily Summary Job Personal Skill Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Install a personal `daily-summary-job` skill that records compact development checkpoints and generates or updates evidence-grounded Markdown reports with self-contained interactive HTML visualizations.
**Architecture:** A concise `SKILL.md` orchestrates context/Git evidence collection and semantic classification. A standard-library Python helper validates the normalized JSON fact source, selects safe module/date/topic paths, and renders both deliverables from one source; an HTML asset provides all offline interaction.
**Tech Stack:** Markdown, YAML, Python 3.12 standard library, HTML5, CSS, inline SVG, native JavaScript, `unittest`, PowerShell verification.
## Global Constraints
- Install to `C:\Users\admin\.codex\skills\daily-summary-job`.
- Use the normalized skill name `daily-summary-job`; do not use `dailySummary_job` as a folder or YAML name.
- Trigger on demand from explicit `$daily-summary-job` invocations or clear natural-language daily progress/report intents; never run in the background.
- Never copy full conversations or full logs into checkpoints.
- Limit one checkpoint to 5 achievements, 5 issues, and 3 next steps; descriptions should be at most 120 Chinese characters where practical.
- Prefer existing `<module>_rep` naming; otherwise use a normalized module, `cross-module_rep`, or `general_rep`.
- Store final files under `dailywork_report/<module>_rep/YYYY-MM-DD/`.
- Generate Markdown and HTML from the same normalized JSON source.
- HTML must be a single offline file with no CDN, network request, third-party library, or external image.
- Do not modify ParkingRobot business code, stage files, or create Git commits.
---
### Task 1: Initialize the personal skill scaffold
**Files:**
- Create: `C:\Users\admin\.codex\skills\daily-summary-job\SKILL.md`
- Create: `C:\Users\admin\.codex\skills\daily-summary-job\agents\openai.yaml`
- Create directories: `scripts`, `references`, `assets`
**Interfaces:**
- Consumes: `skill-creator/scripts/init_skill.py` and the approved design.
- Produces: A discoverable personal skill skeleton with UI metadata.
- [ ] **Step 1: Confirm the target does not already exist**
Run:
```powershell
$target = 'C:\Users\admin\.codex\skills\daily-summary-job'
if (Test-Path -LiteralPath $target) { throw "Skill already exists: $target" }
```
Expected: no output.
- [ ] **Step 2: Initialize the skill with required resource folders**
Run with approval for writing outside the workspace:
```powershell
python 'C:\Users\admin\.codex\skills\.system\skill-creator\scripts\init_skill.py' daily-summary-job `
--path 'C:\Users\admin\.codex\skills' `
--resources scripts,references,assets `
--interface 'display_name=Daily Summary Job' `
--interface 'short_description=按需记录、分类并生成带证据与交互可视化的开发工作日报' `
--interface 'default_prompt=使用 $daily-summary-job 记录当前开发进展,并生成今日 Markdown 与交互式 HTML 日报。'
```
Expected: `daily-summary-job` is created and `agents/openai.yaml` contains the three interface values.
- [ ] **Step 3: Inspect only the new scaffold**
Run:
```powershell
Get-ChildItem -LiteralPath 'C:\Users\admin\.codex\skills\daily-summary-job' -Recurse
```
Expected: `SKILL.md`, `agents/openai.yaml`, and the three resource directories are present.
---
### Task 2: Implement deterministic path planning and checkpoint budgets with tests first
**Files:**
- Create: `C:\Users\admin\.codex\skills\daily-summary-job\scripts\test_prepare_report.py`
- Create: `C:\Users\admin\.codex\skills\daily-summary-job\scripts\prepare_report.py`
**Interfaces:**
- Produces: `find_project_root(start: Path) -> Path`, `normalize_slug(value: str, fallback: str) -> str`, `infer_module(changed_paths: list[str], report_root: Path, explicit: str | None) -> str`, `plan_paths(...) -> ReportPaths`, and `validate_checkpoint_budget(data: dict) -> None`.
- `ReportPaths` exposes `module_dir`, `date_dir`, `state_file`, `markdown_file`, and `html_file` as `Path` values.
- [ ] **Step 1: Write failing standard-library tests**
Create tests covering exact behavior:
```python
def test_prefers_existing_module_folder(self):
(self.root / "dailywork_report" / "pathsmoothing_rep").mkdir(parents=True)
module = target.infer_module(
["src/PathSmoothing/LocalG2/Pipeline.cs"],
self.root / "dailywork_report",
None,
)
self.assertEqual("pathsmoothing_rep", module)
def test_multiple_existing_modules_become_cross_module(self):
report_root = self.root / "dailywork_report"
(report_root / "Map_rep").mkdir(parents=True)
(report_root / "coarsepath_rep").mkdir()
module = target.infer_module(
["src/Map/Grid.cs", "src/CoarsePath/Search.cs"], report_root, None
)
self.assertEqual("cross-module_rep", module)
def test_unknown_scope_becomes_general(self):
self.assertEqual(
"general_rep",
target.infer_module(["README.md"], self.root / "dailywork_report", None),
)
def test_rejects_checkpoint_over_budget(self):
data = {"achievements": [{"title": str(i)} for i in range(6)], "issues": [], "next_steps": []}
with self.assertRaisesRegex(ValueError, "at most 5 achievements"):
target.validate_checkpoint_budget(data)
```
- [ ] **Step 2: Run the tests and confirm the expected import failure**
Run:
```powershell
python 'C:\Users\admin\.codex\skills\daily-summary-job\scripts\test_prepare_report.py'
```
Expected: FAIL because `prepare_report.py` does not yet provide the tested API.
- [ ] **Step 3: Implement safe normalization, module inference, and path planning**
Use a frozen dataclass and reject traversal:
```python
@dataclass(frozen=True)
class ReportPaths:
module_dir: Path
date_dir: Path
state_file: Path
markdown_file: Path
html_file: Path
def normalize_slug(value: str, fallback: str) -> str:
normalized = unicodedata.normalize("NFKD", value).encode("ascii", "ignore").decode("ascii")
normalized = re.sub(r"[^a-zA-Z0-9]+", "-", normalized).strip("-").lower()
if not normalized or normalized in {".", ".."}:
normalized = fallback
return normalized[:64].rstrip("-") or fallback
```
Implement existing-folder matching before generic path inference. Preserve an existing folder's exact spelling, use `cross-module_rep` for more than one matched module, and `general_rep` when only generic files such as `README.md` are available.
`plan_paths` must reuse an existing state file with the same date/module/topic in update mode and otherwise choose the next two-digit sequence.
- [ ] **Step 4: Implement and enforce checkpoint budgets**
```python
def validate_checkpoint_budget(data: dict[str, Any]) -> None:
limits = {"achievements": 5, "issues": 5, "next_steps": 3}
for key, limit in limits.items():
values = data.get(key, [])
if not isinstance(values, list):
raise ValueError(f"{key} must be a list")
if len(values) > limit:
raise ValueError(f"checkpoint allows at most {limit} {key}")
```
- [ ] **Step 5: Run the focused tests**
Run the same test command.
Expected: all path, classification, update, traversal, and budget tests pass.
---
### Task 3: Define and validate the normalized fact source
**Files:**
- Modify: `C:\Users\admin\.codex\skills\daily-summary-job\scripts\test_prepare_report.py`
- Modify: `C:\Users\admin\.codex\skills\daily-summary-job\scripts\prepare_report.py`
- Create: `C:\Users\admin\.codex\skills\daily-summary-job\references\report-schema.md`
**Interfaces:**
- Produces: `validate_report_data(data: dict) -> None`, `render_markdown(data: dict) -> str`, and a documented JSON schema shared by checkpoints, generation, and update mode.
- [ ] **Step 1: Add failing schema and Markdown tests**
The fixture must include one issue for each evidence level and assert stable issue identifiers appear in Markdown:
```python
self.assertRaisesRegex(ValueError, "unsupported evidence level", target.validate_report_data, bad_data)
markdown = target.render_markdown(self.sample_data())
self.assertIn("## 3. 今日发现的问题", markdown)
self.assertIn("issue-baseline", markdown)
self.assertIn("待验证风险", markdown)
```
- [ ] **Step 2: Run tests and confirm the new API fails**
Expected: FAIL because validation and Markdown rendering are not implemented.
- [ ] **Step 3: Implement strict schema validation**
Require top-level fields `date`, `title`, `summary`, `modules`, `achievements`, `issues`, `validations`, `next_steps`, and `sources`. Require each issue to contain `id`, `title`, `module`, `evidence_level`, `discovery`, `actual`, `expected`, `cause`, `impact`, `improvements`, `validation`, `next_steps`, and `evidence`. Accept only these labels:
```python
EVIDENCE_LEVELS = {"已验证", "静态分析", "对话发现", "待验证风险", "结论冲突"}
```
Reject duplicate issue identifiers and non-list collection fields.
- [ ] **Step 4: Implement Markdown rendering from the validated data**
Render the approved seven main sections. Every issue heading includes its stable identifier and evidence level. Evidence is rendered as a compact table containing label, reference, and result; empty optional collections render as “无已记录项” rather than invented content.
- [ ] **Step 5: Document the exact schema and evidence rules**
`report-schema.md` must contain the complete JSON example, field table, five evidence labels, checkpoint budget, merge-by-issue-id rule, conflict behavior, and safe-language examples distinguishing verified facts from risks.
- [ ] **Step 6: Run the focused tests**
Expected: schema and Markdown tests pass.
---
### Task 4: Build the self-contained interactive HTML renderer
**Files:**
- Modify: `C:\Users\admin\.codex\skills\daily-summary-job\scripts\test_prepare_report.py`
- Modify: `C:\Users\admin\.codex\skills\daily-summary-job\scripts\prepare_report.py`
- Create: `C:\Users\admin\.codex\skills\daily-summary-job\assets\interactive-report-template.html`
**Interfaces:**
- Produces: `render_html(data: dict, template: str) -> str` and UI hooks `issue-button`, `evidence-filter`, `cause-node`, `solution-step`, `before-after-toggle`, `validation-gate`, and `roadmap-item`.
- [ ] **Step 1: Add failing HTML safety and interaction tests**
```python
html = target.render_html(self.sample_data(), template_text)
self.assertIn('id="daily-summary-app"', html)
self.assertIn('class="issue-button"', html)
self.assertIn('class="before-after-toggle"', html)
self.assertIn('@media (prefers-reduced-motion: reduce)', html)
self.assertNotRegex(html, r'https?://|<script[^>]+src=')
self.assertNotIn("</script><script>alert", html)
for issue in self.sample_data()["issues"]:
self.assertIn(issue["id"], html)
```
- [ ] **Step 2: Run tests and confirm rendering fails**
Expected: FAIL because the template and renderer do not exist.
- [ ] **Step 3: Create the offline data-driven template**
The template must contain:
```html
<main id="daily-summary-app" data-selected-issue="">
<header class="hero">...</header>
<nav class="filters" aria-label="筛选问题证据等级">...</nav>
<section class="overview" aria-label="今日工作总览">...</section>
<section class="problem-lab" aria-live="polite">...</section>
<section class="validation-funnel">...</section>
<section class="roadmap">...</section>
</main>
<script id="report-data" type="application/json">__REPORT_DATA__</script>
<script>/* native rendering and keyboard navigation */</script>
```
Use text and icons together for status; do not rely on color alone. Provide visible focus states, arrow-key issue navigation, responsive single-column fallbacks, and a no-animation media query. Display “概念示意” whenever a problem lacks numeric evidence.
- [ ] **Step 4: Implement safe JSON embedding and rendering**
```python
def safe_json_for_html(data: dict[str, Any]) -> str:
raw = json.dumps(data, ensure_ascii=False, separators=(",", ":"))
return raw.replace("<", "\\u003c").replace(">", "\\u003e").replace("&", "\\u0026")
def render_html(data: dict[str, Any], template: str) -> str:
validate_report_data(data)
if template.count("__REPORT_DATA__") != 1:
raise ValueError("template must contain exactly one report data placeholder")
return template.replace("__REPORT_DATA__", safe_json_for_html(data))
```
- [ ] **Step 5: Run the focused tests**
Expected: HTML safety, interaction-hook, evidence-consistency, and accessibility-source tests pass.
---
### Task 5: Add checkpoint, render, update, and validate CLI workflows
**Files:**
- Modify: `C:\Users\admin\.codex\skills\daily-summary-job\scripts\test_prepare_report.py`
- Modify: `C:\Users\admin\.codex\skills\daily-summary-job\scripts\prepare_report.py`
**Interfaces:**
- Produces CLI subcommands `inspect`, `checkpoint`, `render`, and `validate`.
- All successful commands emit compact JSON to stdout; failures return nonzero with a specific message on stderr.
- [ ] **Step 1: Add failing end-to-end CLI tests**
Use `tempfile.TemporaryDirectory` to verify:
1. `checkpoint` creates one compact JSON under `.daily-summary-job/YYYY-MM-DD/checkpoints`.
2. `render` creates canonical state plus a Markdown/HTML pair under `<module>_rep/YYYY-MM-DD`.
3. `render --update` preserves the original sequence and paths.
4. A second topic receives the next sequence.
5. `validate` rejects mismatched issue identifiers or an external URL in HTML.
- [ ] **Step 2: Run tests and confirm CLI failures**
Expected: FAIL because the subcommands are not wired.
- [ ] **Step 3: Implement the four subcommands**
- `inspect`: report project root, local date, changed paths, existing report modules, inferred module, and evidence file candidates without writing.
- `checkpoint`: validate compact input, create the checkpoint directory, and write UTF-8 JSON atomically.
- `render`: validate full input, plan or reuse paths, render both outputs to temporary siblings, validate them, atomically replace the pair, and persist canonical state.
- `validate`: compare issue identifiers and evidence levels across canonical JSON, Markdown, and HTML; reject external resources.
Use `tempfile.NamedTemporaryFile(delete=False, dir=target.parent)` and `Path.replace` only after both staged files pass validation. Clean up staged files in `finally` without deleting existing deliverables.
- [ ] **Step 4: Run all script tests**
Run:
```powershell
python 'C:\Users\admin\.codex\skills\daily-summary-job\scripts\test_prepare_report.py' -v
```
Expected: all tests pass.
---
### Task 6: Write the concise skill workflow and metadata-aligned instructions
**Files:**
- Modify: `C:\Users\admin\.codex\skills\daily-summary-job\SKILL.md`
- Verify: `C:\Users\admin\.codex\skills\daily-summary-job\agents\openai.yaml`
**Interfaces:**
- Consumes: `scripts/prepare_report.py`, `references/report-schema.md`, and `assets/interactive-report-template.html`.
- Produces: A skill another Codex instance can invoke for record, generate, or update intents without loading unrelated history.
- [ ] **Step 1: Replace scaffold placeholders with final frontmatter**
Use only the required YAML keys:
```yaml
---
name: daily-summary-job
description: Record compact development checkpoints and generate or update evidence-grounded daily work reports with paired Markdown and self-contained interactive HTML. Use when the user asks to record current development progress, summarize today's coding work, organize problems and improvements, visualize problem/solution reasoning, or update an existing daily development report.
---
```
- [ ] **Step 2: Write the imperative workflow**
The body must tell the invoking agent to:
1. Determine record/generate/update intent without requiring fixed wording.
2. Read only current context and today's relevant evidence.
3. Run `inspect` before any write.
4. Preserve evidence boundaries and conflicts.
5. Create the normalized JSON using `report-schema.md`.
6. Use `checkpoint` for compact progress capture.
7. Use `render` for new reports and `render --update` for exact-topic updates.
8. Run `validate` and report precise paths.
9. Never fix business code, run Git commit, fabricate evidence, or read historical days by default.
- [ ] **Step 3: Verify interface metadata remains aligned**
`agents/openai.yaml` must show `Daily Summary Job`, the approved Chinese short description, and a default prompt explicitly containing `$daily-summary-job`. Do not add icons, colors, dependencies, or policy fields.
---
### Task 7: Validate the installed skill and run a disposable full workflow
**Files:**
- Verify only: `C:\Users\admin\.codex\skills\daily-summary-job\**`
- Create and remove only: a dedicated directory under the system temporary directory.
**Interfaces:**
- Produces: Validation evidence for skill structure, unit behavior, report generation, update stability, and offline HTML constraints.
- [ ] **Step 1: Run skill structure validation**
```powershell
python 'C:\Users\admin\.codex\skills\.system\skill-creator\scripts\quick_validate.py' 'C:\Users\admin\.codex\skills\daily-summary-job'
```
Expected: validation succeeds.
- [ ] **Step 2: Run the full script test suite**
```powershell
python 'C:\Users\admin\.codex\skills\daily-summary-job\scripts\test_prepare_report.py' -v
```
Expected: all tests pass.
- [ ] **Step 3: Create a disposable simulated project**
Create one explicit temporary project containing `src/Map`, `src/PathSmoothing`, and an existing `dailywork_report/pathsmoothing_rep`. Feed a checkpoint and a full report fixture containing achievements, two evidence levels, an improvement, validation results, and next steps.
- [ ] **Step 4: Run record, generate, update, and validation commands**
Expected:
- checkpoint path is date-scoped;
- multi-module input selects `cross-module_rep` unless explicitly overridden;
- generation creates one paired report;
- update keeps the same pair;
- every issue identifier appears in normalized JSON, Markdown, and HTML;
- HTML contains no `http://`, `https://`, external script, or external image reference.
- [ ] **Step 5: Inspect the final installed file set and repository scope**
Run:
```powershell
Get-ChildItem -LiteralPath 'C:\Users\admin\.codex\skills\daily-summary-job' -Recurse -File | Select-Object FullName,Length
git status --short -- 'docs/superpowers/specs/2026-08-03-daily-summary-job-skill-design.md' 'docs/superpowers/plans/2026-08-03-daily-summary-job-skill.md'
```
Expected: only the new skill files exist in the personal directory; the repository shows the two uncommitted documentation files and no task-caused business-code changes.
## Execution Choice
The user requested immediate execution without Git commits. Execute this plan inline with `superpowers:executing-plans`; do not dispatch subagents and do not pause for a separate execution-choice prompt.
@@ -1,557 +0,0 @@
# EM Planner Foundation 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:** Build the immutable EM Planner contracts, validated configuration, direction-segment boundary model, reverse-safe Frenet transforms, and topology-preserving static lateral corridor.
**Architecture:** Keep the planner core request-driven and independent from hardware, UI, clocks, and solver details. Adapt one consumable `PathSmoothingResult` into an immutable current-direction reference window, then project only within that segment and build a collision-checked connected lateral interval around the selected seed.
**Tech Stack:** C# 10, .NET Standard 2.0 library, existing `PlanningGridMap`, `FootprintCollisionChecker`, and a `net10.0-windows` console verification host.
## Global Constraints
- Namespace for new production types: `MultiWheelC.TrajectoryPlanning.EMPlanner` plus responsibility-specific child namespaces.
- First release supports ordinary nonholonomic forward/reverse motion only; lateral translation, crab motion, and in-place rotation are rejected.
- World units are metres, seconds, radians, metres/second, radians/second, and inverse metres.
- Vehicle body X points forward, body Y points left, and positive yaw is counter-clockwise.
- `TravelYaw = VehicleYaw` forward and `TravelYaw = Normalize(VehicleYaw + π)` reverse.
- Frenet `s` always increases in actual travel direction; positive `l` is left of travel, including reverse.
- A planning window never crosses a gear-switch boundary and never drops an exact terminal anchor.
- Static-corridor selection preserves the topology chosen by Hybrid A*; it never jumps to another disconnected free interval.
- Initial corridor spacings are `0.10 m` longitudinal and `0.025 m` lateral; maximum lateral offset is `0.30 m`.
- Existing user changes in `ClumsyPilot.csproj`, Map, CoarsePath, and PathSmoothing must be preserved.
- Dynamic-obstacle trajectory prediction and dynamic space-time boundaries are outside this plan.
---
## Locked File Structure
```text
ClumsyPilot/ParkrobTrajplanner/EMPlanner/
├── Contracts/
│ ├── EmBoundaryType.cs
│ ├── EmMotionModel.cs
│ ├── EmPlanningRequest.cs
│ ├── EmPlanningResult.cs
│ ├── EmPlanningStatus.cs
│ ├── EmTerminalType.cs
│ ├── EmTrajectory.cs
│ ├── EmTrajectoryMetadata.cs
│ ├── EmTrajectoryPoint.cs
│ └── VehicleMotionState.cs
├── Configuration/
│ ├── CorridorConfiguration.cs
│ ├── EmPlannerConfiguration.cs
│ ├── FrenetConfiguration.cs
│ ├── LateralConfiguration.cs
│ ├── LateralWeights.cs
│ ├── LongitudinalConfiguration.cs
│ ├── LongitudinalWeights.cs
│ ├── SchedulingConfiguration.cs
│ ├── SolverConfiguration.cs
│ └── ValidationConfiguration.cs
├── Diagnostics/
│ ├── EmPlannerDebugOptions.cs
│ ├── EmPlanningDiagnostics.cs
│ └── IEmPlannerDebugSink.cs
├── Segmentation/
│ ├── DirectionSegmentView.cs
│ ├── ReferenceBoundary.cs
│ ├── ReferenceHorizonSlicer.cs
│ └── ReferencePathSegmenter.cs
├── Frenet/
│ ├── FrenetProjection.cs
│ ├── FrenetProjector.cs
│ ├── FrenetReferencePoint.cs
│ ├── FrenetTransform.cs
│ └── ReferencePathInterpolator.cs
├── Corridor/
│ ├── LateralInterval.cs
│ ├── StaticCorridor.cs
│ └── StaticCorridorBuilder.cs
└── Validation/
└── EmPlanningRequestValidator.cs
ClumsyPilot/tests/EMPlannerVerificationHost/
├── EMPlannerVerificationHost.csproj
├── Program.cs
├── Verification.cs
├── EmFixtureFactory.cs
├── FoundationChecks.cs
├── SegmentationChecks.cs
├── FrenetChecks.cs
└── CorridorChecks.cs
```
## Shared Public Interfaces
```csharp
public sealed class VehicleMotionState
{
public VehicleMotionState(Pose2D pose, double signedLongitudinalSpeedMetersPerSecond,
double? longitudinalAccelerationMetersPerSecondSquared,
DateTimeOffset capturedAtUtc, long sequenceId);
public Pose2D Pose { get; }
public double SignedLongitudinalSpeedMetersPerSecond { get; }
public double? LongitudinalAccelerationMetersPerSecondSquared { get; }
public DateTimeOffset CapturedAtUtc { get; }
public long SequenceId { get; }
}
public sealed class EmPlanningRequest
{
public EmPlanningRequest(PathSmoothingResult referencePath, PlanningGridMap map,
VehicleParameters vehicle, VehicleMotionState vehicleState,
EmPlannerConfiguration configuration, int segmentIndex,
EmTrajectory previousTrajectory, DateTimeOffset requestedAtUtc,
DateTimeOffset effectiveAtUtc, string outputTrajectoryId,
string referencePathId, string previousTrajectoryId,
EmMotionModel motionModel);
}
public sealed class DirectionSegmentView
{
public int SegmentIndex { get; }
public TravelDirection Direction { get; }
public IReadOnlyList<SmoothedPathPoint> Points { get; }
public ReferenceBoundary StartBoundary { get; }
public ReferenceBoundary EndBoundary { get; }
}
public sealed class FrenetProjector
{
public bool TryProject(Pose2D worldPose, DirectionSegmentView segment,
double minimumReferenceS, double maximumReferenceS,
double maximumDistanceMeters, out FrenetProjection projection);
}
public sealed class StaticCorridorBuilder
{
public bool TryBuild(DirectionSegmentView segment, double startReferenceS,
double endReferenceS, IReadOnlyList<FrenetProjection> seed,
PlanningGridMap map, VehicleParameters vehicle,
CorridorConfiguration configuration, out StaticCorridor corridor,
out string failureReason);
}
```
### Task 1: Verification Host and Immutable Contracts
**Files:**
- Modify: `ClumsyPilot/ClumsyPilot.csproj`
- Create: `ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj`
- Create: `ClumsyPilot/tests/EMPlannerVerificationHost/Program.cs`
- Create: `ClumsyPilot/tests/EMPlannerVerificationHost/Verification.cs`
- Create: `ClumsyPilot/tests/EMPlannerVerificationHost/FoundationChecks.cs`
- Create: all files under `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Contracts/`
**Interfaces:**
- Consumes: existing `Pose2D`, `TravelDirection`, `PathSmoothingResult`, `PlanningGridMap`, and `VehicleParameters`.
- Produces: the contracts shown in “Shared Public Interfaces”, `EmPlanningStatus`, `EmTerminalType`, `EmBoundaryType`, and immutable trajectory containers.
- [ ] **Step 1: Add the isolated verification host and a failing contract check**
Add these exact MSBuild rules without rewriting surrounding user changes:
```xml
<Compile Remove="tests\EMPlannerVerificationHost\**\*.cs" />
<Compile Remove="ParkrobTrajplanner\auto_avoidance\**\*.cs"
Condition="'$(ExcludeLegacyAutoAvoidance)' == 'true'" />
```
Create the host project with:
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0-windows</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\ClumsyPilot.csproj"
AdditionalProperties="ExcludeLegacyAutoAvoidance=true" />
</ItemGroup>
</Project>
```
`FoundationChecks.Run()` must construct a reverse `VehicleMotionState`, assert that its signed speed remains negative, construct an `EmTrajectoryPoint`, and assert all constructor-supplied values are unchanged. `Program.Main` accepts `foundation`, runs the check, prints `PASS foundation`, and returns `1` after printing an exception when a check fails.
- [ ] **Step 2: Run the host and verify the contracts are absent**
Run:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- foundation
```
Expected: build failure naming `VehicleMotionState` or `EmTrajectoryPoint`.
- [ ] **Step 3: Implement immutable contracts with constructor validation**
Use `ReadOnlyCollection<T>` copies for every published list. `EmTrajectoryPoint` has exactly these public properties:
```csharp
public double X { get; }
public double Y { get; }
public double Yaw { get; }
public double SignedLongitudinalVelocity { get; }
public double Speed { get; }
public double VelocityX { get; }
public double VelocityY { get; }
public double YawRate { get; }
public double TimeFromStart { get; }
public double VehicleCurvature { get; }
public int SegmentIndex { get; }
public double SegmentLocalS { get; }
public double PathS { get; }
public TravelDirection Direction { get; }
public EmBoundaryType BoundaryType { get; }
```
Its constructor accepts the authoritative `signedLongitudinalVelocity` and derives, rather than accepts, the redundant fields:
```csharp
Speed = Math.Abs(signedLongitudinalVelocity);
VelocityX = signedLongitudinalVelocity * Math.Cos(yaw);
VelocityY = signedLongitudinalVelocity * Math.Sin(yaw);
YawRate = signedLongitudinalVelocity * vehicleCurvature;
```
Define these exact status values:
```text
Success
SuccessWithFallback
InvalidInput
UnsupportedMotionMode
StaleVehicleState
StateDirectionMismatch
InvalidReferencePath
ProjectionFailed
CorridorInfeasible
LateralInfeasible
LongitudinalInfeasible
StoppingDistanceInsufficient
SolverUnavailable
SolverTimedOut
Cancelled
ValidationFailed
Superseded
Failed
```
`EmMotionModel` has one supported value, `NonholonomicForwardReverse`, plus explicit `CrabTranslation` and `InPlaceRotation` values that validation maps to `UnsupportedMotionMode`. Only the two success statuses may construct a result with a non-empty trajectory. Define `EmBoundaryType` as `None`, `RollingSafetyStop`, `GearSwitchApproach`, `GearSwitchDeparture`, and `Goal`; define `EmTerminalType` as `RollingSafetyStop`, `GearSwitch`, and `Goal`.
`EmTrajectoryMetadata` contains `TrajectoryId`, `GeneratedAtUtc`, `EffectiveAtUtc`, `MapSnapshotId`, `ReferencePathId`, `VehicleStateSequenceId`, `PreviousTrajectoryId`, `SegmentIndex`, `Direction`, and `TerminalType`; `EmTrajectory` exposes that metadata plus immutable points. `GeneratedAtUtc` is copied from request `RequestedAtUtc`, and `EffectiveAtUtc` plus `OutputTrajectoryId` are caller-supplied so the pure planner never reads a clock or creates a random ID. `EmTrajectoryPoint` retains internal longitudinal acceleration and jerk values for validation but derives all public redundant velocity fields from signed longitudinal velocity.
- [ ] **Step 4: Run the foundation check**
Run the same command. Expected: `PASS foundation` and exit code `0`.
- [ ] **Step 5: Commit the host and contracts**
```powershell
git add ClumsyPilot/ClumsyPilot.csproj ClumsyPilot/tests/EMPlannerVerificationHost ClumsyPilot/ParkrobTrajplanner/EMPlanner/Contracts
git commit -m "feat: add EM planner contracts"
```
### Task 2: Configuration, Diagnostics, and Request Validation
**Files:**
- Create: all files under `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Configuration/`
- Create: all files under `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Diagnostics/`
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Validation/EmPlanningRequestValidator.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/FoundationChecks.cs`
**Interfaces:**
- Consumes: Task 1 contracts.
- Produces: `EmPlannerConfiguration.CreateDefault()`, `EmPlanningRequestValidator.Validate(EmPlanningRequest)`, debug options, and diagnostics.
- [ ] **Step 1: Write failing checks for exact defaults and rejection rules**
Add assertions for these exact defaults:
```text
ReplanPeriodSeconds=0.20
TimeHorizonSeconds=6.0
DistanceHorizonMeters=5.0
OutputTimeStepSeconds=0.05
SolverTimeoutSeconds=0.10
HandoffLookaheadSeconds=0.30
MaximumVehicleStateAgeSeconds=0.20
LongitudinalSampleSpacingMeters=0.10
LateralSampleSpacingMeters=0.025
MaximumLateralOffsetMeters=0.30
AdditionalClearanceReserveMeters=0.02
MaximumCollisionCheckStepMeters=0.025
MaximumProjectionDistanceMeters=0.50
MinimumFrenetDenominator=0.20
BoundaryAnchorToleranceMeters=1e-8
MaximumLateralStepPerIterationMeters=0.05
MaximumLateralSlope=0.50
MaximumLateralSecondDerivativePerMeter=1.00
MaximumLateralThirdDerivativePerSquareMeter=2.00
MaximumForwardSpeedMetersPerSecond=0.20
MaximumReverseSpeedMetersPerSecond=0.20
MaximumAccelerationMetersPerSecondSquared=0.20
MaximumDecelerationMetersPerSecondSquared=0.30
MaximumJerkMetersPerSecondCubed=0.50
MaximumLateralAccelerationMetersPerSecondSquared=0.20
MaximumCurvatureRatePerMeterPerSecond=0.50
StopSpeedToleranceMetersPerSecond=0.01
ZeroSpeedHoldSeconds=0.20
MaximumOuterIterations=5
MaximumOsqpIterations=4000
AbsoluteTolerance=1e-5
RelativeTolerance=1e-5
StrictResidualTolerance=1e-5
WarmStart=true
Polish=true
NativeVerbose=false
```
Assert LS weights `10,1,5,10,5,20,5,10` in reference/heading/second/third/curvature/curvature-variation/previous/rolling-terminal order, and ST weights `10,1,10,5,1` in speed/acceleration/jerk/previous/terminal-acceleration order.
Also assert validation rejects: null request members, a map with `PlanningReady=false`, a smoothing failure status, an out-of-range segment index, a non-finite speed, negative sequence ID, stale state, and vehicle geometry without a valid curvature limit.
Assert `CrabTranslation` and `InPlaceRotation` return `UnsupportedMotionMode` before projection.
- [ ] **Step 2: Run and verify the configuration types are absent**
Run the foundation group. Expected: build failure naming `EmPlannerConfiguration`.
- [ ] **Step 3: Implement configuration snapshots and validation**
Use settable configuration DTOs only at the request boundary, then make `EmPlanningRequestValidator` return a copied internal snapshot. Validate every double with `NumericGuard`; enforce positive sample spacings, `0 < MinimumFrenetDenominator < 1`, non-negative margins, and positive horizons. Treat only `Complete`, `PartialImprovement`, `NotNeeded`, and `Unchanged` smoothing statuses as consumable.
`LateralConfiguration` owns trust-region and derivative hard limits plus `LateralWeights`; `LongitudinalConfiguration` owns direction speed, acceleration, deceleration, jerk, lateral-acceleration, curvature-rate, stop tolerance, hold duration, and `LongitudinalWeights`; `ValidationConfiguration` owns absolute spatial/kinematic consistency tolerances. Configuration validation requires every weight to be finite and non-negative and every normalization scale to be finite and positive.
`EmPlannerDebugOptions` exposes only flags and a sink:
```csharp
EnableSummary
EnableProjectionTrace
EnableCorridorTrace
EnableLateralSolverTrace
EnableLongitudinalSolverTrace
EnableTrajectoryDump
EnableVisualization
Sink
```
Debug-sink exceptions are caught and recorded in diagnostics; they never escape the planner.
- [ ] **Step 4: Run foundation checks and confirm all rejection messages are deterministic**
Expected: `PASS foundation`; run twice and compare stdout exactly.
- [ ] **Step 5: Commit configuration and validation**
```powershell
git add ClumsyPilot/ParkrobTrajplanner/EMPlanner/Configuration ClumsyPilot/ParkrobTrajplanner/EMPlanner/Diagnostics ClumsyPilot/ParkrobTrajplanner/EMPlanner/Validation ClumsyPilot/tests/EMPlannerVerificationHost/FoundationChecks.cs
git commit -m "feat: validate EM planner requests"
```
### Task 3: Direction Segmentation and Exact Boundary Anchors
**Files:**
- Create: all files under `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Segmentation/`
- Create: `ClumsyPilot/tests/EMPlannerVerificationHost/SegmentationChecks.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/Program.cs`
- Create: `ClumsyPilot/tests/EMPlannerVerificationHost/EmFixtureFactory.cs`
**Interfaces:**
- Consumes: `PathSmoothingResult.Path`, `PathSmoothingResult.Segments`, and Task 1 boundary enums.
- Produces: `DirectionSegmentView`, `ReferenceBoundary`, and exact horizon slices.
- [ ] **Step 1: Write failing gear-pair and horizon checks**
Build a fixture whose forward segment ends at `(2,0,0,s=2)`, whose reverse segment starts with a duplicated pose at the same source arc length, and whose direction changes only on the second member of the pair. Assert:
```text
segment 0 end boundary = GearSwitchApproach
segment 1 start boundary = GearSwitchDeparture
segment identity differs even when pose and source arc length match
horizon 1.95 injects an interpolated RollingSafetyStop at exactly 1.95
horizon 2.05 for segment 0 still ends at exactly 2.00 and never includes segment 1
```
- [ ] **Step 2: Run the segmentation group and verify failure**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- segmentation
```
Expected: build failure naming `ReferencePathSegmenter`.
- [ ] **Step 3: Implement segment views and exact slicing**
`ReferenceBoundary` identity is the tuple `(SegmentIndex, SegmentLocalS, BoundaryType)`. Rebase every selected segment to `SegmentLocalS=0` without changing the source point. `ReferenceHorizonSlicer.Slice` must interpolate X, Y, unwrapped vehicle yaw, geometric curvature, vehicle curvature, curvature derivative, and clearance at the exact terminal S, then label the anchor `RollingSafetyStop`, `GearSwitch`, or `Goal`.
Never implement horizon selection by filtering `point.ArcLength <= end`; always append or replace with the exact anchor after bracketing interpolation.
- [ ] **Step 4: Run segmentation checks**
Expected: `PASS segmentation`, with assertions covering exact equality within `1e-8 m`.
- [ ] **Step 5: Commit segmentation**
```powershell
git add ClumsyPilot/ParkrobTrajplanner/EMPlanner/Segmentation ClumsyPilot/tests/EMPlannerVerificationHost
git commit -m "feat: preserve EM planner segment boundaries"
```
### Task 4: Reverse-Safe Frenet Projection and Reconstruction
**Files:**
- Create: all files under `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Frenet/`
- Create: `ClumsyPilot/tests/EMPlannerVerificationHost/FrenetChecks.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/Program.cs`
**Interfaces:**
- Consumes: `DirectionSegmentView` from Task 3.
- Produces: interpolation, bounded projection, world/Frenet conversion, and optimized-yaw reconstruction.
- [ ] **Step 1: Write failing forward, reverse, wraparound, and loop checks**
Cover these equations directly:
```text
travelYaw = forward ? vehicleYaw : Normalize(vehicleYaw + PI)
deltaS = dx*cos(travelYaw) + dy*sin(travelYaw)
l = -dx*sin(travelYaw) + dy*cos(travelYaw)
x = referenceX - l*sin(travelYaw)
y = referenceY + l*cos(travelYaw)
optimizedTravelYaw = referenceTravelYaw + atan2(dl, 1-referenceK*l)
optimizedVehicleYaw = reverse ? Normalize(optimizedTravelYaw + PI) : optimizedTravelYaw
```
Use a reverse reference whose vehicle yaw is near `-π`, points on both sides of the wrap, and a U-shaped segment with two spatially close branches. Assert the projector remains inside the supplied S interval and chooses the locally nearest branch, then reconstructs the original world point within `1e-8 m`.
- [ ] **Step 2: Run the Frenet group and verify failure**
Expected: build failure naming `FrenetProjector`.
- [ ] **Step 3: Implement deterministic bounded projection**
Use segment-line projection for the coarse candidate, clamp the interpolation fraction to `[0,1]`, then compare squared world distance. Resolve equal-distance ties by smaller absolute delta from the seed/reference S and then smaller S. Reject projections farther than `MaximumProjectionDistanceMeters`; never search another direction segment.
`FrenetTransform` rejects reconstruction when `1-referenceK*l < MinimumFrenetDenominator`. Angles used for interpolation are unwrapped; only public yaw is normalized with `AngleMath.NormalizeRadians`.
- [ ] **Step 4: Run Frenet checks**
Expected: `PASS frenet`, including reverse `l>0` being left of motion and therefore body-right.
- [ ] **Step 5: Commit Frenet support**
```powershell
git add ClumsyPilot/ParkrobTrajplanner/EMPlanner/Frenet ClumsyPilot/tests/EMPlannerVerificationHost
git commit -m "feat: add reverse-safe Frenet transforms"
```
### Task 5: Topology-Preserving Static Corridor
**Files:**
- Create: all files under `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Corridor/`
- Create: `ClumsyPilot/tests/EMPlannerVerificationHost/CorridorChecks.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/Program.cs`
**Interfaces:**
- Consumes: Task 4 interpolation/transforms, `PlanningGridMap`, `VehicleParameters`, and `FootprintCollisionChecker`.
- Produces: immutable `StaticCorridor` stations containing `ReferenceS`, `MinimumL`, `MaximumL`, and seed L.
- [ ] **Step 1: Write failing corridor connectivity checks**
Create maps through `PlanningMapFactory`: an empty map, a static rectangle narrowing the left side of a straight path, and an obstacle splitting lateral samples into disconnected left/right intervals. Assert:
```text
empty map corridor = [-0.30, +0.30] at interior stations
every accepted lateral sample passes exact rotated-footprint collision checking
prior-trajectory seed is used before l=0
the chosen interval contains the seed at every station
a disappearing seed-connected interval returns false instead of switching sides
first and last stations match exact requested ReferenceS anchors
```
- [ ] **Step 2: Run the corridor group and verify failure**
Expected: build failure naming `StaticCorridorBuilder`.
- [ ] **Step 3: Implement broad-phase plus exact-footprint sampling**
At each `0.10 m` S station, sample L in `0.025 m` increments from `-0.30` to `+0.30`, always injecting the exact seed L and exact offset limits. Use the distance field only to accept obviously clear samples; all remaining samples go through `FootprintCollisionChecker.IsPoseCollisionFree` with `AdditionalClearanceReserveMeters=0.02`. Group adjacent free samples, choose only the group containing the seed, and propagate overlap with the previously chosen interval.
When propagation loses overlap, return failure with the first failed S. Do not select another free group, invert L for reverse, or infer a new obstacle side.
- [ ] **Step 4: Run all foundation-plan checks**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- all-foundation
```
Expected output:
```text
PASS foundation
PASS segmentation
PASS frenet
PASS corridor
```
- [ ] **Step 5: Commit corridor support**
```powershell
git add ClumsyPilot/ParkrobTrajplanner/EMPlanner/Corridor ClumsyPilot/tests/EMPlannerVerificationHost
git commit -m "feat: build static EM lateral corridors"
```
### Task 6: Foundation Documentation and Gate
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/Program.cs`
**Interfaces:**
- Consumes: all preceding tasks.
- Produces: a documented, repeatable foundation verification command for later plans.
- [ ] **Step 1: Add README contract examples**
Document the pipeline, coordinate formulas, reverse sign example, exact gear-pair behavior, configuration units, and the command used to run `all-foundation`. State explicitly that the module does not yet solve LS or ST at this gate.
- [ ] **Step 2: Run format and placeholder checks**
```powershell
git diff --check
rg -n "NotImplementedException|throw new Exception\(\)" ClumsyPilot/ParkrobTrajplanner/EMPlanner ClumsyPilot/tests/EMPlannerVerificationHost
```
Expected: `git diff --check` returns no diagnostics and the incomplete-implementation scan returns no matches.
- [ ] **Step 3: Run the complete foundation gate twice**
Run `all-foundation` twice. Expected: identical four PASS lines and exit code `0` both times.
- [ ] **Step 4: Confirm the normal project baseline separately**
```powershell
dotnet build ClumsyPilot/ClumsyPilot.csproj --no-restore
```
Expected at this repository baseline: only the already-documented legacy `auto_avoidance/MultiWheelAutoAvoidance.cs` missing-reference errors may remain. Any new error under `EMPlanner` fails this gate.
- [ ] **Step 5: Commit documentation**
```powershell
git add ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md ClumsyPilot/tests/EMPlannerVerificationHost/Program.cs
git commit -m "docs: describe EM planner foundation"
```
## Completion Gate
- `all-foundation` passes twice with deterministic output.
- Forward and reverse projection/reconstruction agree within `1e-8 m`.
- No segment, projection, horizon, or corridor crosses a gear boundary.
- Corridor samples pass exact body collision checks and never change disconnected topology.
- No solver, scheduler, hardware access, dynamic prediction, or trajectory publication logic has leaked into this phase.
@@ -1,334 +0,0 @@
# EM Planner Lateral LS 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:** Optimize a collision-free, curvature-feasible lateral path inside the selected static corridor using sequential convex programming over OSQP QPs.
**Architecture:** Discretize `l, dl, ddl, dddl` over exact reference-S stations, build normalized quadratic costs and linear integration/corridor constraints, and linearize nonlinear vehicle curvature inside an outer trust-region loop. Reconstruct each accepted candidate in world coordinates, recompute true path arc length, and independently validate it before exposing it to ST.
**Tech Stack:** C# 10, .NET Standard 2.0, foundation Frenet/corridor types, solver-neutral `IQpSolver`, OSQP backend for integration checks.
## Global Constraints
- This plan depends on completion of the foundation and OSQP-backend plans.
- LS runs on exactly one current direction segment and uses `ReferenceS` as its independent variable.
- `l>0` is left of travel for both forward and reverse; do not reinterpret it as body-left in reverse.
- Corridor bounds, maximum lateral offset, trust region, start state, terminal event, Frenet denominator, and vehicle curvature are hard constraints.
- Initial derivative bounds: `|Δl|<=0.05 m` per SQP iteration, `|dl|<=0.50`, `|ddl|<=1.00 1/m`, `|dddl|<=2.00 1/m²`.
- Enforce `1-referenceK*l >= 0.20` at every knot.
- SQP outer-iteration limit is `5`; OSQP iteration limit is `4000`; absolute/relative tolerances are `1e-5`.
- Cost weights: reference `10`, heading `1`, second derivative `5`, third derivative `10`, curvature `5`, curvature variation `20`, previous trajectory `5`, rolling terminal `10`.
- Every cost term is divided by the square of its physical scale before its weight is applied.
- LS scales are maximum lateral offset for L, maximum slope for DL, maximum second derivative for DDL, maximum third derivative for DDDL, vehicle maximum curvature for curvature, and `max(1, reference max |dk/ds|)` for curvature variation.
- Gear-switch and goal terminals require `l=0` and `dl=0`; a rolling safety terminal uses a soft terminal penalty.
- Only the last independently validated feasible candidate may survive a later timeout or failed outer iteration.
- The output world path is re-parameterized by actual `PathS`; later ST code must not use `ReferenceS` as traveled distance.
---
## Locked File Structure
```text
ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral/
├── LateralCandidate.cs
├── LateralConstraintBuilder.cs
├── LateralGeometryEvaluator.cs
├── LateralObjectiveBuilder.cs
├── LateralPath.cs
├── LateralPathPoint.cs
├── LateralPlanner.cs
├── LateralPlanningInput.cs
├── LateralPlanningResult.cs
├── LateralSolutionValidator.cs
├── LateralVariableLayout.cs
└── SequentialConvexOptimizer.cs
ClumsyPilot/tests/EMPlannerVerificationHost/
├── FakeQpSolver.cs
├── LateralModelChecks.cs
└── LateralIntegrationChecks.cs
```
## Shared Interfaces
```csharp
public sealed class LateralPlanningInput
{
public LateralPlanningInput(DirectionSegmentView referenceSegment,
StaticCorridor corridor, FrenetProjection startProjection,
EmTerminalType terminalType, VehicleParameters vehicle,
EmPlannerConfiguration configuration,
IReadOnlyList<FrenetProjection> previousTrajectorySeed);
}
public sealed class LateralPlanner
{
public LateralPlanner(IQpSolver qpSolver);
public LateralPlanningResult Plan(LateralPlanningInput input,
CancellationToken cancellationToken);
}
public sealed class LateralPathPoint
{
public double ReferenceS { get; }
public double PathS { get; }
public double L { get; }
public double DL { get; }
public double DDL { get; }
public double DDDL { get; }
public double X { get; }
public double Y { get; }
public double VehicleYaw { get; }
public double GeometricCurvature { get; }
public double VehicleCurvature { get; }
public double VehicleCurvatureDerivative { get; }
}
```
### Task 1: Variable Layout and Exact Discrete Lateral Dynamics
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral/LateralVariableLayout.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral/LateralPlanningInput.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral/LateralCandidate.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral/LateralPathPoint.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral/LateralPath.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral/LateralPlanningResult.cs`
- Create: `ClumsyPilot/tests/EMPlannerVerificationHost/LateralModelChecks.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/Program.cs`
**Interfaces:**
- Consumes: foundation corridor, reference, configuration, and terminal types.
- Produces: deterministic variable indices and immutable lateral inputs/results.
- [ ] **Step 1: Write failing layout and dynamics checks**
For `N=4`, assert disjoint contiguous ranges for `l[0..3]`, `dl[0..3]`, `ddl[0..3]`, and `dddl[0..2]`, with total variable count `4*N-1`. For unequal S gaps, verify the integration equations:
```text
ddl[i+1] = ddl[i] + ds*dddl[i]
dl[i+1] = dl[i] + ds*ddl[i] + 0.5*ds^2*dddl[i]
l[i+1] = l[i] + ds*dl[i] + 0.5*ds^2*ddl[i] + (ds^3/6)*dddl[i]
```
Reject fewer than two stations, non-increasing S, corridor/input station mismatch, and a start projection outside the first hard interval.
- [ ] **Step 2: Run the lateral-model group and verify failure**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- lateral-model
```
Expected: build failure naming `LateralVariableLayout`.
- [ ] **Step 3: Implement layouts and immutable model types**
Expose index methods `L(i)`, `DL(i)`, `DDL(i)`, and `DDDL(i)` that range-check every input. Copy all station and seed lists. A failed result has no candidate; success and fallback results require a non-empty independently validated `LateralPath`.
- [ ] **Step 4: Run the model checks**
Expected: `PASS lateral-model`.
- [ ] **Step 5: Commit lateral model types**
```powershell
git add ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral ClumsyPilot/tests/EMPlannerVerificationHost
git commit -m "feat: add lateral optimization model"
```
### Task 2: Normalized Objective and Linear Hard Constraints
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral/LateralObjectiveBuilder.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral/LateralConstraintBuilder.cs`
- Create: `ClumsyPilot/tests/EMPlannerVerificationHost/FakeQpSolver.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/LateralModelChecks.cs`
**Interfaces:**
- Consumes: Task 1 layout, `SparseTripletBuilder`, corridor bounds, a linearization candidate, and approved LS weights.
- Produces: a validated `QuadraticProgram` for one SQP iteration.
- [ ] **Step 1: Write failing coefficient-level QP checks**
For a three-station straight reference with unit scales, inspect P, q, A, lower, and upper arrays and assert:
```text
reference cost adds 2*w_l to P(l_i,l_i)
jerk cost adds 2*w_dddl to P(dddl_i,dddl_i)
previous-seed cost adds 2*w_previous and -2*w_previous*l_previous
every integration equality appears once with equal lower/upper bounds
corridor, derivative, trust-region, and Frenet-denominator rows use hard finite bounds
gear/goal terminal rows force l_N=0 and dl_N=0
rolling terminal adds objective terms but no zero terminal equalities
```
The test must also show every weight is applied after division by its named scale squared.
- [ ] **Step 2: Run and verify builders are absent**
Expected: build failure naming `LateralObjectiveBuilder`.
- [ ] **Step 3: Implement objective and hard-row assembly**
Build the OSQP objective convention `0.5*x'Px + q'x`, so a squared residual `w*((x-target)/scale)^2` contributes `2w/scale²` to P and `-2w*target/scale²` to q. Assemble integration rows exactly from Task 1. Intersect corridor bounds with maximum offset, trust region, and linearized denominator bounds before adding each L row; return infeasible before calling the solver when an intersection is empty.
Use `FakeQpSolver` only in the verification host. It records the last problem/settings/warm start and returns a caller-supplied `QpSolveResult`.
- [ ] **Step 4: Run coefficient-level checks**
Expected: `PASS lateral-model`; no coefficient comparison tolerance larger than `1e-10`.
- [ ] **Step 5: Commit QP assembly**
```powershell
git add ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral ClumsyPilot/tests/EMPlannerVerificationHost
git commit -m "feat: assemble lateral LS quadratic programs"
```
### Task 3: Nonlinear Geometry Evaluation and Independent Validation
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral/LateralGeometryEvaluator.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral/LateralSolutionValidator.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/LateralModelChecks.cs`
**Interfaces:**
- Consumes: solved `l/dl/ddl/dddl`, Frenet interpolation, direction, and vehicle curvature limit.
- Produces: world-space `LateralPath` with recomputed `PathS`, curvature, and validation residuals.
- [ ] **Step 1: Write failing reconstruction and curvature checks**
Cover straight and constant-curvature references in both directions. Assert:
```text
world X/Y use x_ref-l*sin(travelYaw), y_ref+l*cos(travelYaw)
vehicle yaw adds PI only for reverse
PathS[0]=0 and increments by actual reconstructed chord/geometry length
PathS is strictly increasing even when ReferenceS gaps vary
VehicleCurvature = directionSign*GeometricCurvature
yawRate identity remains valid for a signed test speed
denominator below 0.20 is rejected
curvature beyond vehicle limit is rejected
non-finite values are rejected
```
- [ ] **Step 2: Run and verify geometry evaluator is absent**
Expected: build failure naming `LateralGeometryEvaluator`.
- [ ] **Step 3: Implement evaluation and strict validation**
Evaluate geometry from the full Frenet derivative formulas used by the design, not a small-angle replacement. Compute unwrapped travel yaw first, derive geometric curvature with respect to actual path direction, convert to vehicle curvature using direction sign, and compute curvature derivative over actual `PathS`. Use centred differences internally and one-sided endpoints.
The validator independently recomputes corridor membership, start/terminal residuals, derivative limits, denominator, curvature limit, finite values, and strictly increasing S. It does not trust solver residuals or reuse the QP constraint matrix as its only proof.
- [ ] **Step 4: Run geometry checks**
Expected: `PASS lateral-model`, including forward/reverse mirrored cases.
- [ ] **Step 5: Commit nonlinear evaluation**
```powershell
git add ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral ClumsyPilot/tests/EMPlannerVerificationHost/LateralModelChecks.cs
git commit -m "feat: validate lateral path geometry"
```
### Task 4: Sequential Convex Outer Loop and Feasible-Candidate Fallback
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral/SequentialConvexOptimizer.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral/LateralPlanner.cs`
- Create: `ClumsyPilot/tests/EMPlannerVerificationHost/LateralIntegrationChecks.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/Program.cs`
**Interfaces:**
- Consumes: Tasks 13, `IQpSolver`, warm starts, cancellation, and timeout settings.
- Produces: `LateralPlanningResult` with the last strict feasible path or an explicit failure.
- [ ] **Step 1: Write failing SQP state-machine checks with `FakeQpSolver`**
Script solver outcomes and assert:
```text
first solved candidate is validated before becoming fallback
second timeout returns first candidate as SuccessWithFallback
an invalid solved vector never replaces the fallback
SolvedInaccurate requires QP residual <=1e-5 and full lateral validation
trust region is centred on the previous iterate and never exceeds 0.05 m
outer loop stops after at most 5 calls
cancellation before a call returns Cancelled
no feasible candidate plus timeout returns SolverTimedOut with no path
```
- [ ] **Step 2: Run the lateral-integration group and verify failure**
Expected: build failure naming `SequentialConvexOptimizer`.
- [ ] **Step 3: Implement the outer loop**
Initialize from the previous trajectory seed when it covers all stations; otherwise use the corridor-clamped zero-offset seed. Per iteration: linearize geometry, assemble the QP, solve with the remaining time budget, evaluate world geometry, validate independently, store a deep copy if feasible, and test convergence using max absolute L change plus objective improvement. Warm-start the next QP with the complete previous primal vector.
Return the most specific status. A timeout/cancellation after a validated candidate maps to fallback success; infeasible corridor/QP with no candidate maps to lateral infeasible.
- [ ] **Step 4: Run scripted SQP checks**
Expected: `PASS lateral-integration`.
- [ ] **Step 5: Commit SQP orchestration**
```powershell
git add ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral ClumsyPilot/tests/EMPlannerVerificationHost
git commit -m "feat: optimize lateral paths with SQP"
```
### Task 5: Real-OSQP Lateral Scenarios and Gate
**Files:**
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/LateralIntegrationChecks.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md`
**Interfaces:**
- Consumes: `OsqpNativeSolver`, foundation fixtures, and complete LS pipeline.
- Produces: a verified lateral path contract ready for ST.
- [ ] **Step 1: Add fixed real-solver scenarios**
Run: straight empty map forward, straight empty map reverse, gentle curve, static obstacle narrowing the existing corridor, gear-switch terminal, and rolling terminal. Assert every result is solved or documented fallback, stays in corridor, respects curvature, and ends at the exact ReferenceS anchor.
- [ ] **Step 2: Add determinism and topology assertions**
Run each scenario twice with identical inputs. Compare status, point count, and every numeric output within `1e-10`; assert the obstacle case remains in the seed-connected interval and does not cross to the disconnected side.
- [ ] **Step 3: Run the complete lateral gate**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- lateral-all
git diff --check
```
Expected output:
```text
PASS lateral-model
PASS lateral-integration
PASS lateral-real-osqp
```
- [ ] **Step 4: Document LS variables, hard constraints, costs, and fallback**
Add the exact equations, normalization scales, terminal differences, `ReferenceS`/`PathS` boundary, and last-feasible publication rule to the README.
- [ ] **Step 5: Commit lateral integration evidence**
```powershell
git add ClumsyPilot/tests/EMPlannerVerificationHost/LateralIntegrationChecks.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md
git commit -m "test: verify lateral LS scenarios"
```
## Completion Gate
- Coefficient-level tests prove the intended normalized QP, not merely a plausible output path.
- Forward and reverse reconstructed geometry obey the same world-coordinate convention.
- Exact gear/goal terminal L conditions and rolling soft terminal behavior are distinct.
- No candidate outside hard corridor, denominator, derivative, curvature, or boundary constraints is published.
- The published lateral path has actual strictly increasing `PathS` ready for longitudinal optimization.
@@ -1,394 +0,0 @@
# EM Planner Longitudinal ST and Publication 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 curvature-aware longitudinal optimization, assemble the complete immutable trajectory, independently validate it, and expose a pure one-shot `EmPlanningService.Plan` pipeline.
**Architecture:** Build a speed envelope over the LS results actual `PathS`, then optimize time-knot `s,u,a,j` variables with exact constant-jerk integration and hard stop boundaries. Convert the validated longitudinal/lateral pair into redundant but consistent public trajectory fields, run an independent world-space and kinematic publication validator, and only then return success.
**Tech Stack:** C# 10, .NET Standard 2.0, LS path types, solver-neutral QP layer, OSQP, existing map and swept-footprint collision checker.
## Global Constraints
- This plan depends on completion of foundation, OSQP backend, and lateral LS plans.
- ST uses actual optimized `PathS`; it never receives or publishes `ReferenceS` as traveled distance.
- Internal speed `u>=0`; public signed longitudinal velocity is `directionSign*u`.
- Initial limits: forward/reverse speed `0.20 m/s`, acceleration `0.20 m/s²`, deceleration `0.30 m/s²`, jerk `0.50 m/s³`, lateral acceleration `0.20 m/s²`, curvature-rate limit `0.50 1/(m*s)`.
- Default output time step is `0.05 s`, time horizon `6.0 s`, distance horizon `5.0 m`, stop tolerance `0.01 m/s`, and zero-speed hold `0.20 s`.
- Every terminal is hard bounded by `s_N=terminalS` and `u_N=0`; terminal acceleration is soft.
- Objective weights: speed reference `10`, acceleration `1`, jerk `10`, previous trajectory `5`, terminal acceleration `1`.
- ST scales are direction maximum speed for U, `max(MaximumAcceleration,MaximumDeceleration)` for A, maximum jerk for J, and terminal S for S-tracking; zero terminal S uses scale `1` only for the degenerate stopped result.
- There is no progress cost when terminal S is fixed.
- Speed limit is the minimum of direction maximum, lateral-acceleration limit, curvature-rate limit, and stopping envelope.
- Output contains `X,Y,Yaw,SignedLongitudinalVelocity,Speed,VelocityX,VelocityY,YawRate,TimeFromStart,VehicleCurvature`.
- `SignedLongitudinalVelocity` is authoritative; all redundant speed fields are constructor-derived and independently rechecked.
- Full rotated-body pose and swept-motion collision checks run after optimization.
- A non-success result never contains a non-empty trajectory.
- Dynamic obstacle prediction, time-varying obstacle boundaries, and dynamic behavior decisions remain outside this plan.
---
## Locked File Structure
```text
ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/
├── LongitudinalCandidate.cs
├── LongitudinalConstraintBuilder.cs
├── LongitudinalObjectiveBuilder.cs
├── LongitudinalPlanner.cs
├── LongitudinalPlanningInput.cs
├── LongitudinalPlanningResult.cs
├── LongitudinalSolutionValidator.cs
├── LongitudinalVariableLayout.cs
├── PathSpeedLimit.cs
├── PathSpeedLimitBuilder.cs
└── SequentialLongitudinalOptimizer.cs
ClumsyPilot/ParkrobTrajplanner/EMPlanner/Segmentation/
└── PlanningHorizonSelector.cs
ClumsyPilot/ParkrobTrajplanner/EMPlanner/Trajectory/
├── EmTrajectoryAssembler.cs
├── LateralPathInterpolator.cs
└── TrajectorySampleSchedule.cs
ClumsyPilot/ParkrobTrajplanner/EMPlanner/Validation/
└── EmTrajectoryValidator.cs
ClumsyPilot/ParkrobTrajplanner/EMPlanner/Facade/
├── IEmPlanningService.cs
└── EmPlanningService.cs
ClumsyPilot/tests/EMPlannerVerificationHost/
├── LongitudinalModelChecks.cs
├── LongitudinalIntegrationChecks.cs
├── TrajectoryChecks.cs
└── EmPlanningServiceChecks.cs
```
## Shared Interfaces
```csharp
public sealed class LongitudinalPlanner
{
public LongitudinalPlanner(IQpSolver qpSolver);
public LongitudinalPlanningResult Plan(LongitudinalPlanningInput input,
CancellationToken cancellationToken);
}
public sealed class EmTrajectoryAssembler
{
public EmTrajectory Assemble(LateralPath path,
LongitudinalPlanningResult longitudinal,
EmTrajectoryMetadata metadata);
}
public interface IEmPlanningService
{
EmPlanningResult Plan(EmPlanningRequest request,
CancellationToken cancellationToken);
}
public sealed class EmPlanningService : IEmPlanningService
{
public EmPlanningService(IQpSolver qpSolver,
IEmPlannerDebugSink defaultDebugSink = null);
public EmPlanningResult Plan(EmPlanningRequest request,
CancellationToken cancellationToken);
}
```
### Task 1: Speed Envelope over Actual PathS
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Segmentation/PlanningHorizonSelector.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/PathSpeedLimit.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/PathSpeedLimitBuilder.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalPlanningInput.cs`
- Create: `ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalModelChecks.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/Program.cs`
**Interfaces:**
- Consumes: validated `LateralPath`, direction, current speed/acceleration, terminal S/type, and longitudinal configuration.
- Produces: a finite piecewise-linear maximum-speed envelope indexed only by `PathS`.
- [ ] **Step 1: Write failing speed-limit checks**
For fixed path points, assert:
```text
direction limit = 0.20 m/s
if |k|=2 1/m, lateral limit = sqrt(0.20/2)
if |dk/ds|=4 1/m², curvature-rate limit = 0.50/4
zero curvature and zero derivative do not divide by zero
combined limit is the minimum finite non-negative value
stopping limit at path position s = sqrt(2*MaximumDeceleration*(terminalS-s))
speed envelope is interpolated by PathS, not ReferenceS
terminal speed is exactly zero
```
Add a start state whose jerk/deceleration-limited stopping distance exceeds the available terminal distance; expect `StoppingDistanceInsufficient` before a QP call.
- [ ] **Step 2: Run and verify the envelope types are absent**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-model
```
Expected: build failure naming `PathSpeedLimitBuilder`.
- [ ] **Step 3: Implement finite limits and stopping precheck**
Use a curvature epsilon of `1e-10`. Clamp every computed limit to the direction maximum and to `sqrt(2*MaximumDeceleration*(terminalS-s))`; the terminal value is exactly zero. Compute a conservative jerk/deceleration stop by first ramping acceleration down at maximum negative jerk until maximum deceleration or zero speed, integrating distance exactly for constant jerk, then adding constant-deceleration distance. Reject when this distance plus `1e-8 m` exceeds terminal distance.
`PlanningHorizonSelector` computes `TerminalReferenceS` before LS as the minimum of remaining current-segment reference length, `DistanceHorizonMeters`, and the reference distance reachable within `TimeHorizonSeconds` while reserving the same jerk-limited stop tail and zero-speed hold. It returns `GearSwitch` or `Goal` when the exact segment boundary wins and `RollingSafetyStop` otherwise; it never returns a reference S past the current segment end. After LS reconstruction, ST discards that numeric reference distance and uses the last lateral point's actual `PathS` as `terminalS`; stopping feasibility is checked again against that actual distance.
- [ ] **Step 4: Run speed-envelope checks**
Expected: `PASS longitudinal-model`.
- [ ] **Step 5: Commit speed envelope**
```powershell
git add ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal ClumsyPilot/tests/EMPlannerVerificationHost
git commit -m "feat: build EM path speed limits"
```
### Task 2: Time-Knot Layout, Dynamics, Objective, and Hard Constraints
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalVariableLayout.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalCandidate.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalObjectiveBuilder.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalConstraintBuilder.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalPlanningResult.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalModelChecks.cs`
**Interfaces:**
- Consumes: Task 1 envelope, a current S iterate, previous-trajectory samples, and QP sparse builders.
- Produces: one convex time-domain QP and immutable candidate/result types.
- [ ] **Step 1: Write failing variable and coefficient checks**
For `N=5`, assert ranges for `s[0..4]`, `u[0..4]`, `a[0..4]`, and `j[0..3]`, total `4*N-1`, and exact constant-jerk equations:
```text
a[i+1] = a[i] + dt*j[i]
u[i+1] = u[i] + dt*a[i] + 0.5*dt^2*j[i]
s[i+1] = s[i] + dt*u[i] + 0.5*dt^2*a[i] + (dt^3/6)*j[i]
```
Inspect the QP and prove: `u>=0`, monotonic S, acceleration/deceleration bounds, jerk bounds, speed-envelope bounds at the current S iterate, exact start state, `s_N=terminalS`, `u_N=0`, and no objective coefficient rewards fixed terminal progress.
- [ ] **Step 2: Run and verify ST builders are absent**
Expected: build failure naming `LongitudinalVariableLayout`.
- [ ] **Step 3: Implement normalized ST QP assembly**
Use the same `0.5*x'Px+q'x` convention. Apply squared-residual coefficients for reference speed, acceleration, jerk, previous S/U, and terminal acceleration after division by their physical scales squared. Determine knot count as `ceil(TimeHorizonSeconds/OutputTimeStepSeconds)+1`; include exact zero and exact horizon times.
At each outer iteration, read the piecewise speed envelope at the current candidate S. Bound S to `[0,terminalS]` and enforce `s[i+1]>=s[i]`. Add exact start S/U/A and terminal S/U equalities.
- [ ] **Step 4: Run coefficient-level ST checks**
Expected: `PASS longitudinal-model`, coefficient tolerance `1e-10`.
- [ ] **Step 5: Commit ST model assembly**
```powershell
git add ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalModelChecks.cs
git commit -m "feat: assemble longitudinal ST quadratic programs"
```
### Task 3: Longitudinal Outer Loop and Strict Validation
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalSolutionValidator.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/SequentialLongitudinalOptimizer.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalPlanner.cs`
- Create: `ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalIntegrationChecks.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/Program.cs`
**Interfaces:**
- Consumes: Tasks 12 and `IQpSolver`.
- Produces: a validated `s,u,a,j` time profile with last-feasible fallback semantics.
- [ ] **Step 1: Write failing fake-solver state-machine checks**
Script solved, inaccurate, timeout, infeasible, and invalid-vector outcomes. Assert the same last-feasible rules as LS, maximum five envelope iterations, warm start, cancellation behavior, and that no candidate violating monotonic S, speed limit, acceleration, jerk, terminal S, or terminal zero speed can become fallback.
- [ ] **Step 2: Run and verify longitudinal optimizer is absent**
Expected: build failure naming `SequentialLongitudinalOptimizer`.
- [ ] **Step 3: Implement iteration and independent checks**
Seed S by simulating the requested speed while reserving a conservative stop tail; seed U/A/J consistently. Each iteration rebuilds the speed bounds at candidate S, solves with remaining budget, validates directly in physical units, and stores a deep copy only when strict. Converge on maximum S/U change and objective improvement; do not accept a vector merely because OSQP reports solved.
- [ ] **Step 4: Run scripted and real-OSQP longitudinal checks**
Cover forward, reverse, curvature-limited, jerk-limited stop, short segment, and zero-start-speed cases. Expected: `PASS longitudinal-integration`.
- [ ] **Step 5: Commit longitudinal optimizer**
```powershell
git add ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal ClumsyPilot/tests/EMPlannerVerificationHost
git commit -m "feat: optimize longitudinal ST profiles"
```
### Task 4: Complete Trajectory Assembly and Redundant-Field Consistency
**Files:**
- Create: all files under `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Trajectory/`
- Create: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryChecks.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/Program.cs`
**Interfaces:**
- Consumes: validated `LateralPath` and longitudinal profile.
- Produces: immutable public `EmTrajectory` with exact terminal event and zero-speed hold.
- [ ] **Step 1: Write failing forward/reverse trajectory-field checks**
At every time knot assert:
```text
signedV = directionSign*u
speed = abs(signedV)
vx = signedV*cos(yaw)
vy = signedV*sin(yaw)
yawRate = signedV*VehicleCurvature
TimeFromStart strictly increases
PathS never decreases
```
For reverse, prove the world velocity points along travel rather than vehicle yaw. Assert the exact gear/goal/rolling terminal point is present, has zero signed speed and zero yaw rate, and is followed by `0.20 s` of identical-pose zero-speed hold samples at `0.05 s` spacing.
- [ ] **Step 2: Run and verify assembler is absent**
Expected: build failure naming `EmTrajectoryAssembler`.
- [ ] **Step 3: Implement interpolation and immutable assembly**
Interpolate lateral geometry by `PathS` with unwrapped yaw, then normalize public yaw. Construct `EmTrajectoryPoint` only from authoritative signed speed and vehicle curvature so redundant fields cannot diverge. Inject the exact terminal time/position before the hold tail if it is not already a regular knot; never drop it through list filtering.
- [ ] **Step 4: Run trajectory checks**
Expected: `PASS trajectory`, including reverse and exact-boundary fixtures.
- [ ] **Step 5: Commit trajectory assembly**
```powershell
git add ClumsyPilot/ParkrobTrajplanner/EMPlanner/Trajectory ClumsyPilot/tests/EMPlannerVerificationHost
git commit -m "feat: assemble complete EM trajectories"
```
### Task 5: Independent World-Space Publication Validator
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Validation/EmTrajectoryValidator.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryChecks.cs`
**Interfaces:**
- Consumes: assembled trajectory, map, vehicle, configuration, current segment, and terminal boundary.
- Produces: a strict validation report used as the only publication gate.
- [ ] **Step 1: Write failing mutation-style validation checks**
Start from a valid trajectory, create altered copies, and assert rejection for: NaN, non-increasing time, decreasing PathS, wrong direction sign, inconsistent speed components, inconsistent yaw rate, excessive speed/acceleration/jerk/curvature/curvature-rate, skipped terminal anchor, nonzero terminal speed, pose collision, swept collision, and a point beyond segment end.
- [ ] **Step 2: Run and verify validator is absent**
Expected: build failure naming `EmTrajectoryValidator`.
- [ ] **Step 3: Implement independent publication validation**
Recompute finite differences in time for acceleration and jerk, recompute yaw-rate identity, check every point through `FootprintCollisionChecker.IsPoseCollisionFree`, and check each adjacent pair through `IsSweptMotionCollisionFree` with maximum step `0.025 m`. Use absolute/relative numeric tolerances from configuration; do not reuse solver status as evidence.
- [ ] **Step 4: Run all mutation checks**
Expected: `PASS trajectory`; every altered trajectory has a deterministic first failure code and point index.
- [ ] **Step 5: Commit publication validator**
```powershell
git add ClumsyPilot/ParkrobTrajplanner/EMPlanner/Validation/EmTrajectoryValidator.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryChecks.cs
git commit -m "feat: validate published EM trajectories"
```
### Task 6: Pure One-Shot EmPlanningService
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Facade/IEmPlanningService.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Facade/EmPlanningService.cs`
- Create: `ClumsyPilot/tests/EMPlannerVerificationHost/EmPlanningServiceChecks.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/Program.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md`
**Interfaces:**
- Consumes: every completed core component.
- Produces: the approved pure `Plan(request, cancellationToken)` API.
- [ ] **Step 1: Write failing end-to-end status and dataflow checks**
Cover success forward, success reverse, invalid smoothing status, stale state, direction mismatch, projection failure, corridor infeasible, lateral infeasible, stopping-distance insufficient, longitudinal infeasible, solver unavailable, timeout with and without fallback, cancellation, validation failure, gear switch, goal, and rolling stop. Assert input map ID, reference path ID, state sequence ID, prior trajectory ID, and segment index appear unchanged in result diagnostics.
- [ ] **Step 2: Run and verify facade is absent**
Expected: build failure naming `EmPlanningService`.
- [ ] **Step 3: Implement deterministic pipeline orchestration**
Call stages in this exact order:
```text
request/config validation
direction-segment selection
bounded ego projection
exact horizon and terminal selection through PlanningHorizonSelector
previous-trajectory seed projection
static connected corridor
LS optimization and validation
PathS speed envelope
ST optimization and validation
trajectory assembly
world-space publication validation
immutable result publication
```
Use the request timestamp rather than reading the system clock. Invoke debug sinks only behind flags and catch sink exceptions. Never expose a partial solver vector or mutate request-owned lists.
- [ ] **Step 4: Run the complete core gate twice**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-core-all
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-core-all
git diff --check
```
Expected output both times:
```text
PASS longitudinal-model
PASS longitudinal-integration
PASS trajectory
PASS em-planning-service
```
- [ ] **Step 5: Document and commit the one-shot API**
Document request construction, all output fields/units/signs, statuses, terminal types, and a minimal forward/reverse usage example, then commit:
```powershell
git add ClumsyPilot/ParkrobTrajplanner/EMPlanner/Facade ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md ClumsyPilot/tests/EMPlannerVerificationHost
git commit -m "feat: publish one-shot EM trajectories"
```
## Completion Gate
- ST coefficient tests prove exact time dynamics, bounds, normalized costs, and fixed-terminal semantics.
- Every successful trajectory ends at an exact zero-speed terminal and contains a safe hold tail.
- Redundant output fields satisfy their authoritative formulas for every point.
- Full pose and swept collision checks pass in world coordinates.
- Every failure status publishes an empty trajectory and deterministic diagnostics.
- Two identical one-shot requests produce identical statuses and numeric trajectories.
@@ -1,382 +0,0 @@
# EM Planner OSQP Backend 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:** Provide a solver-independent convex-QP contract and a pinned Windows x64 OSQP 1.0.0 backend that loads `osqp.dll` safely from the plugin directory.
**Architecture:** Mathematical planners build validated immutable CSC problems against `IQpSolver`; the OSQP adapter owns all native memory and maps native outcomes into planner-neutral statuses. The upstream shared library is built with a fixed ABI configuration, preloaded by absolute path, version-checked, and never allowed to crash the host when absent or incompatible.
**Tech Stack:** C# 10, .NET Standard 2.0, P/Invoke with Cdecl, OSQP 1.0.0 C API, builtin QDLDL algebra, CMake 3.18+, Visual Studio x64 compiler.
## Global Constraints
- This plan depends on `2026-08-03-em-planner-foundation-implementation.md` Task 1 and its verification host.
- Pin upstream source tag `v1.0.0`; do not bind the incompatible 0.6 API.
- Build Windows x64, double precision, 32-bit indices, unpacked settings, builtin algebra, shared library, no MKL or CUDA.
- Required build switches: `OSQP_USE_FLOAT=OFF`, `OSQP_USE_LONG=OFF`, `OSQP_PACK_SETTINGS=OFF`, `OSQP_ALGEBRA_BACKEND=builtin`, `OSQP_BUILD_SHARED_LIB=ON`.
- Native library filename in source and deployed plugin is exactly `osqp.dll`.
- `ClumsyPilot.dll` locates and preloads the sibling DLL from `Assembly.Location`; current directory and system `PATH` are not inputs.
- Every native entry point uses `CallingConvention.Cdecl`.
- All pinned arrays, CSC wrappers, settings, and solver handles are released in reverse acquisition order.
- `SolvedInaccurate` is publishable only after independent strict residual and domain validation.
- Missing DLL, wrong architecture, version mismatch, invalid exports, and setup failure return structured solver outcomes.
- Native verbose output is disabled.
---
## Locked File Structure
```text
ClumsyPilot/ParkrobTrajplanner/EMPlanner/Optimization/
├── IQpSolver.cs
├── QpSolveResult.cs
├── QpSolveStatus.cs
├── QpSolverSettings.cs
├── QuadraticProgram.cs
├── SparseCscMatrix.cs
├── SparseTripletBuilder.cs
└── Osqp/
├── OsqpNativeLoader.cs
├── OsqpNativeMethods.cs
├── OsqpNativeSolver.cs
├── OsqpNativeStructures.cs
└── OsqpStatusMapper.cs
ClumsyPilot/ThirdParty/OSQP/
├── build-win-x64.ps1
├── LICENSE
├── NOTICE
├── VERSION
├── SHA256SUMS
└── win-x64/osqp.dll
ClumsyPilot/tests/EMPlannerVerificationHost/
├── OptimizationChecks.cs
└── OsqpChecks.cs
```
## Shared Interfaces
```csharp
public interface IQpSolver
{
QpSolveResult Solve(QuadraticProgram problem, QpSolverSettings settings,
IReadOnlyList<double> warmStart, CancellationToken cancellationToken);
}
public sealed class QuadraticProgram
{
public QuadraticProgram(SparseCscMatrix upperTriangularP, IReadOnlyList<double> q,
SparseCscMatrix a, IReadOnlyList<double> lowerBounds,
IReadOnlyList<double> upperBounds);
public int VariableCount { get; }
public int ConstraintCount { get; }
}
public sealed class QpSolveResult
{
public QpSolveStatus Status { get; }
public IReadOnlyList<double> Primal { get; }
public double Objective { get; }
public double PrimalResidual { get; }
public double DualResidual { get; }
public int Iterations { get; }
public TimeSpan SolveTime { get; }
public string NativeStatus { get; }
public string Diagnostic { get; }
}
```
Official references used to lock this ABI:
- `https://osqp.org/docs/interfaces/C.html`
- `https://osqp.org/docs/get_started/migration_guide.html`
- `https://github.com/osqp/osqp/tree/v1.0.0`
### Task 1: Solver-Neutral Sparse QP Contracts
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Optimization/IQpSolver.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Optimization/QpSolveResult.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Optimization/QpSolveStatus.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Optimization/QpSolverSettings.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Optimization/QuadraticProgram.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Optimization/SparseCscMatrix.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Optimization/SparseTripletBuilder.cs`
- Create: `ClumsyPilot/tests/EMPlannerVerificationHost/OptimizationChecks.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/Program.cs`
**Interfaces:**
- Consumes: `NumericGuard` and cancellation tokens.
- Produces: the shared interfaces above and deterministic sparse-matrix assembly used by LS and ST.
- [ ] **Step 1: Write failing CSC canonicalization checks**
Build triplets in shuffled order with duplicate coordinates and assert the resulting CSC matrix:
```text
has ColumnPointers length ColumnCount+1
sorts row indices ascending inside each column
sums duplicate coordinates
drops exact zero sums
rejects NaN, infinity, negative indices, and out-of-range indices
stores only the upper triangle for P
```
Also construct the micro problem `min 0.5*x^2 - 2*x` subject to `0 <= x <= 1` and assert its immutable arrays cannot be changed through the source lists.
- [ ] **Step 2: Run and verify solver contracts are absent**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- optimization
```
Expected: build failure naming `SparseTripletBuilder`.
- [ ] **Step 3: Implement canonical CSC and QP validation**
`SparseCscMatrix` stores copied arrays `Values`, `RowIndices`, and `ColumnPointers`. Validate monotonic pointers, `ColumnPointers[0]==0`, final pointer equals nonzero count, and all rows are in range. `QuadraticProgram` enforces square P, matching variable dimensions, matching constraint dimensions, `lower<=upper`, finite coefficients, and bounds limited to `±1e30` rather than CLR infinity.
Use these exact statuses:
```csharp
public enum QpSolveStatus
{
Solved,
SolvedInaccurate,
PrimalInfeasible,
DualInfeasible,
MaximumIterations,
TimeLimit,
Cancelled,
SolverUnavailable,
InvalidProblem,
NativeError
}
```
- [ ] **Step 4: Run optimization checks**
Expected: `PASS optimization`.
- [ ] **Step 5: Commit QP contracts**
```powershell
git add ClumsyPilot/ParkrobTrajplanner/EMPlanner/Optimization ClumsyPilot/tests/EMPlannerVerificationHost
git commit -m "feat: add solver-neutral QP contracts"
```
### Task 2: Reproducible OSQP 1.0.0 Native Package
**Files:**
- Create: all files under `ClumsyPilot/ThirdParty/OSQP/`
**Interfaces:**
- Consumes: Git, CMake 3.18+, and a Visual Studio x64 compiler.
- Produces: a versioned `win-x64/osqp.dll` with a recorded SHA-256 and matching license files.
- [ ] **Step 1: Write the native build script**
`build-win-x64.ps1` must create a unique temporary directory, clone only tag `v1.0.0`, configure with this exact command shape, and remove the temporary directory in `finally`:
```powershell
cmake -S $sourceRoot -B $buildRoot -A x64 `
-DOSQP_ALGEBRA_BACKEND=builtin `
-DOSQP_BUILD_SHARED_LIB=ON `
-DOSQP_BUILD_STATIC_LIB=OFF `
-DOSQP_BUILD_DEMO_EXE=OFF `
-DOSQP_BUILD_UNITTESTS=OFF `
-DOSQP_USE_FLOAT=OFF `
-DOSQP_USE_LONG=OFF `
-DOSQP_PACK_SETTINGS=OFF `
-DOSQP_ENABLE_PRINTING=OFF `
-DOSQP_CODEGEN=OFF `
-DOSQP_ENABLE_DERIVATIVES=OFF
cmake --build $buildRoot --config Release --target osqp
```
The script resolves the generated DLL explicitly, verifies exactly one match, copies upstream `LICENSE` and `NOTICE`, writes `VERSION` with tag and build flags, computes `Get-FileHash -Algorithm SHA256`, and writes `SHA256SUMS` using a lowercase hexadecimal digest.
- [ ] **Step 2: Execute the build script**
```powershell
powershell -ExecutionPolicy Bypass -File ClumsyPilot/ThirdParty/OSQP/build-win-x64.ps1
```
Expected: `win-x64/osqp.dll`, `LICENSE`, `NOTICE`, `VERSION`, and `SHA256SUMS` exist; the script prints `OSQP v1.0.0 win-x64 package ready`.
- [ ] **Step 3: Verify architecture, exports, and hash**
Use `dumpbin /headers` to assert machine `x64`, `dumpbin /exports` to assert `osqp_version`, `osqp_setup`, `osqp_solve`, and `osqp_cleanup`, then recompute SHA-256 and compare with `SHA256SUMS`. A missing tool is a failed packaging gate, not a skipped check.
- [ ] **Step 4: Verify license contents came from the pinned tag**
Compare bytes against the tag checkout before the temporary checkout is removed. Expected: exact equality for both files.
- [ ] **Step 5: Commit the reproducible native package**
```powershell
git add ClumsyPilot/ThirdParty/OSQP
git commit -m "build: pin OSQP 1.0.0 win-x64"
```
### Task 3: Absolute-Path Native Loader and ABI Structures
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Optimization/Osqp/OsqpNativeLoader.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Optimization/Osqp/OsqpNativeMethods.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Optimization/Osqp/OsqpNativeStructures.cs`
- Create: `ClumsyPilot/tests/EMPlannerVerificationHost/OsqpChecks.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/Program.cs`
**Interfaces:**
- Consumes: the pinned DLL and Task 1 QP contracts.
- Produces: a process-lifetime loader plus exact OSQP 1.0.0 double/int32 structures.
- [ ] **Step 1: Write failing loader checks**
Copy the verification host output to a temporary `plugins` directory with `ClumsyPilot.dll` and, in separate cases, no native DLL, a text file named `osqp.dll`, and the real DLL. Assert the first two return `SolverUnavailable` diagnostics without `BadImageFormatException` escaping; the real DLL reports version `1.0.0`. Start 16 parallel first-use calls and assert a single stable module handle.
- [ ] **Step 2: Run the OSQP group without loader implementation**
Expected: build failure naming `OsqpNativeLoader`.
- [ ] **Step 3: Implement loader and ABI definitions**
Use Windows `LoadLibraryW`, `GetProcAddress`, and `FreeLibrary` from `kernel32`; resolve the plugin directory from `typeof(OsqpNativeLoader).Assembly.Location`. Reject `IntPtr.Size != 8`. Preload the absolute sibling path and retain the handle for process lifetime.
Define `OSQPInt` as C# `int` and `OSQPFloat` as C# `double`, matching the pinned build. Define sequential layouts for `OSQPCscMatrix`, `OSQPSettings`, `OSQPInfo`, `OSQPSolution`, and the four-pointer prefix of `OSQPSolver` exactly as the v1.0.0 public headers specify. Add an internal layout check for expected offsets and total sizes before the first solve.
Declare only these native functions initially:
```text
osqp_version
osqp_set_default_settings
osqp_setup
osqp_warm_start
osqp_solve
osqp_cleanup
```
Do not depend on `OSQPCscMatrix_new`, `OSQPCscMatrix_free`, `OSQPSettings_new`, or `OSQPSettings_free`: those helpers are not marked with the public export macro in the pinned header. Allocate the two CSC structures and settings block with `Marshal.AllocHGlobal`, initialize settings through `osqp_set_default_settings`, and release those managed-owned blocks with `Marshal.FreeHGlobal`.
- [ ] **Step 4: Run missing, corrupt, real, and concurrent loader checks**
Expected: `PASS osqp-loader` and no process crash.
- [ ] **Step 5: Commit loader and structures**
```powershell
git add ClumsyPilot/ParkrobTrajplanner/EMPlanner/Optimization/Osqp ClumsyPilot/tests/EMPlannerVerificationHost
git commit -m "feat: load pinned OSQP native library"
```
### Task 4: OSQP Solve Lifecycle and Status Mapping
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Optimization/Osqp/OsqpNativeSolver.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Optimization/Osqp/OsqpStatusMapper.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/OsqpChecks.cs`
**Interfaces:**
- Consumes: `IQpSolver`, Task 3 native functions, and `QpSolverSettings`.
- Produces: `OsqpNativeSolver : IQpSolver`.
- [ ] **Step 1: Write failing solve/status checks**
Test three fixed QPs:
```text
bounded optimum: min 0.5*x^2 - 2*x, 0<=x<=1, expected x=1
equality optimum: min x^2+y^2, x+y=1, expected x=y=0.5
infeasible: x>=1 and x<=0, expected PrimalInfeasible
```
Assert residuals, iteration count, objective, native status, and solve time are populated. Add a `1e-9 second` time-limit case that maps only to `TimeLimit` or a valid solved status; no native status may be silently treated as solved.
- [ ] **Step 2: Run and verify `OsqpNativeSolver` is absent**
Expected: build failure naming `OsqpNativeSolver`.
- [ ] **Step 3: Implement one-shot native ownership**
Pin P/Q/A/L/U and optional warm-start arrays; allocate and populate P/A `OSQPCscMatrix` blocks; allocate settings and initialize it through `osqp_set_default_settings`; overwrite `verbose=0`, `warm_starting`, `polishing`, `max_iter`, `eps_abs`, `eps_rel`, and `time_limit`; call setup, optional warm start, solve, then marshal solution and info. Copy all result values before cleanup. Release the solver through `osqp_cleanup`, then settings/matrix blocks through `Marshal.FreeHGlobal`, then array pins in reverse order inside `finally`.
Map native status values exactly:
```text
1 Solved
2 SolvedInaccurate
3/4 PrimalInfeasible
5/6 DualInfeasible
7 MaximumIterations
8 TimeLimit
9/10/11 NativeError
```
Cancellation is checked before native setup and after solve. OSQP's configured time limit is the bound for a solve already inside native code.
- [ ] **Step 4: Run all OSQP checks repeatedly**
```powershell
1..20 | ForEach-Object {
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- osqp
if ($LASTEXITCODE -ne 0) { throw "OSQP verification failed on iteration $_" }
}
```
Expected: every iteration prints `PASS osqp-loader` and `PASS osqp-solve`.
- [ ] **Step 5: Commit solver lifecycle**
```powershell
git add ClumsyPilot/ParkrobTrajplanner/EMPlanner/Optimization/Osqp ClumsyPilot/tests/EMPlannerVerificationHost/OsqpChecks.cs
git commit -m "feat: solve QPs through OSQP"
```
### Task 5: Backend Completion Gate
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/Program.cs`
**Interfaces:**
- Consumes: all tasks in this plan.
- Produces: the stable `IQpSolver` boundary required by LS and ST plans.
- [ ] **Step 1: Document native deployment and diagnostics**
Add the exact source/deployment layouts, pinned version, build flags, license placement, absolute loading rule, and solver status mapping to the README.
- [ ] **Step 2: Run optimization and OSQP gates**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- optimization
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- osqp
git diff --check
```
Expected: both groups pass and Git reports no whitespace errors.
- [ ] **Step 3: Verify the DLL is self-contained**
Run a dependency inspection on `win-x64/osqp.dll`. Expected: only Windows system/runtime DLLs; no MKL, CUDA, or separately deployed QDLDL DLL.
- [ ] **Step 4: Verify clean plugin-directory loading**
Copy only `ClumsyPilot.dll` and `osqp.dll` to a fresh directory, copy the verification host executable beside them, and run the micro QP with the working directory set elsewhere. Expected: solved result, proving loading does not depend on current directory.
- [ ] **Step 5: Commit backend documentation**
```powershell
git add ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md ClumsyPilot/tests/EMPlannerVerificationHost/Program.cs
git commit -m "docs: describe OSQP plugin deployment"
```
## Completion Gate
- `IQpSolver` contains no OSQP-specific type.
- Pinned native metadata, license, notice, hash, and DLL agree with OSQP v1.0.0.
- Loader failures are structured and never terminate the host.
- Fixed feasible and infeasible QPs map to the correct statuses with finite diagnostics.
- Twenty repeated solve/cleanup cycles pass without handle growth or access violations.
@@ -1,375 +0,0 @@
# EM Planner Rolling Execution and Plugin Deployment 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 version-safe rolling replanning, trajectory handoff, gear-switch execution states, controller adaptation, and deterministic packaging of `ClumsyPilot.dll` with `osqp.dll` and licenses.
**Architecture:** Keep `EmPlanningService` pure and place scheduling, cancellation, stale-result suppression, previous-trajectory reuse, and command generation in a sibling `TrajectoryExecution` module. The coordinator publishes only a fully validated current-version trajectory; the executor samples that immutable trajectory, manages zero-speed gear transitions, and converts its fields into a generic controller command without coupling EM optimization to the hardware API.
**Tech Stack:** C# 10, .NET Standard 2.0, `Task`/`CancellationToken`, immutable EM trajectories, PowerShell packaging, Windows x64 plugin layout.
## Global Constraints
- This plan depends on all four preceding implementation plans.
- Default replan period is `0.20 s`; time horizon `6.0 s`; distance horizon `5.0 m`; handoff lookahead `0.30 s`.
- The coordinator accepts captured `VehicleMotionState`; it does not read localization, wheel speed, UI, or hardware directly.
- Each cycle binds `MapSnapshotId`, `ReferencePathId`, `VehicleState.SequenceId`, `PreviousTrajectoryId`, and `SegmentIndex`.
- A result is publishable only if all bound identities still match the latest cycle and the cycle version is current.
- Normal replans hand off from the previous trajectory only when tracking error and age are within configuration limits and no gear boundary is crossed.
- If a replan fails, the previously published trajectory remains executable and ends in its own zero-speed safety tail.
- Gear change occurs only after measured speed remains below `0.01 m/s` for at least `0.20 s`.
- The executor never commands lateral body velocity or in-place rotation.
- The generic control command uses signed longitudinal velocity and yaw rate; world `vx/vy`, speed, curvature, and pose remain available for monitoring.
- No dynamic-obstacle prediction or dynamic behavior state is introduced.
- Plugin runtime layout is `plugins/ClumsyPilot.dll`, `plugins/osqp.dll`, and `plugins/licenses/*`.
- Deployment refuses a non-x64 host/runtime package or an OSQP hash mismatch.
---
## Locked File Structure
```text
ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/
├── EmPlanningCoordinator.cs
├── GearSwitchStateMachine.cs
├── GearSwitchState.cs
├── IEmPlanningCycleSink.cs
├── IVehicleStateProvider.cs
├── PlanningCycleIdentity.cs
├── PlanningCycleInput.cs
├── PlanningCycleResult.cs
├── TrajectoryControlAdapter.cs
├── TrajectoryControlCommand.cs
├── TrajectoryExecutionState.cs
├── TrajectoryExecutor.cs
├── TrajectoryHandoffSelector.cs
└── TrajectorySampler.cs
ClumsyPilot/scripts/
└── Publish-ClumsyPilotPlugin.ps1
ClumsyPilot/tests/EMPlannerVerificationHost/
├── CoordinatorChecks.cs
├── ExecutorChecks.cs
└── PluginPackagingChecks.cs
```
## Shared Interfaces
```csharp
public interface IVehicleStateProvider
{
VehicleMotionState Capture();
}
public sealed class EmPlanningCoordinator
{
public EmPlanningCoordinator(IEmPlanningService planningService,
IEmPlanningCycleSink sink = null);
public Task<PlanningCycleResult> PlanLatestAsync(PlanningCycleInput input,
CancellationToken cancellationToken);
public EmTrajectory PublishedTrajectory { get; }
}
public sealed class TrajectoryExecutor
{
public TrajectoryExecutionState State { get; }
public TrajectoryControlCommand Update(DateTimeOffset now,
VehicleMotionState measuredState, EmTrajectory trajectory);
}
public sealed class TrajectoryControlCommand
{
public double SignedLongitudinalVelocity { get; }
public double YawRate { get; }
public TravelDirection Direction { get; }
public bool RequestDirectionChange { get; }
public bool HoldBrake { get; }
public bool IsTrajectoryComplete { get; }
}
```
### Task 1: Cycle Identity, Scheduling Decision, and Stale-Result Suppression
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/PlanningCycleIdentity.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/PlanningCycleInput.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/PlanningCycleResult.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/IEmPlanningCycleSink.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/EmPlanningCoordinator.cs`
- Create: `ClumsyPilot/tests/EMPlannerVerificationHost/CoordinatorChecks.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/Program.cs`
**Interfaces:**
- Consumes: pure `IEmPlanningService`, captured request inputs, and configuration scheduling values.
- Produces: latest-wins asynchronous planning and a read-only published trajectory.
- [ ] **Step 1: Write failing coordinator concurrency checks**
Use a controllable fake planning service to start cycle A, then cycle B before A completes. Complete B first with success and A later with success. Assert B alone is published and A returns `Superseded`. Add checks for map ID, reference ID, state sequence, segment, and prior trajectory ID changes invalidating an otherwise successful result.
Also assert `ShouldStartCycle(now)` is false before `0.20 s` and true at exactly `0.20 s`; this decision uses caller-supplied time.
- [ ] **Step 2: Run and verify coordinator types are absent**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- coordinator
```
Expected: build failure naming `EmPlanningCoordinator`.
- [ ] **Step 3: Implement latest-wins coordination**
Use an incrementing `long` cycle version, a private lock only around publication state, and a per-cycle linked cancellation source. Starting a newer cycle cancels the prior source. After planning, compare the complete identity and version again under the publication lock; map stale success to `Superseded` without exposing its trajectory.
Sink exceptions are caught and reported in the cycle diagnostic. Never hold the publication lock while running the planner or invoking a sink.
- [ ] **Step 4: Run concurrency checks 100 times**
```powershell
1..100 | ForEach-Object {
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- coordinator
if ($LASTEXITCODE -ne 0) { throw "Coordinator verification failed on iteration $_" }
}
```
Expected: every iteration prints `PASS coordinator`.
- [ ] **Step 5: Commit coordinator identity logic**
```powershell
git add ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution ClumsyPilot/tests/EMPlannerVerificationHost
git commit -m "feat: coordinate rolling EM replans"
```
### Task 2: Safe Previous-Trajectory Handoff
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/TrajectorySampler.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/TrajectoryHandoffSelector.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/EmPlanningCoordinator.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/CoordinatorChecks.cs`
**Interfaces:**
- Consumes: current published trajectory, measured state, latest segment identity, and `0.30 s` handoff lookahead.
- Produces: either a future trajectory-derived start state/seed or a measured-state start with no seed.
- [ ] **Step 1: Write failing handoff acceptance/rejection checks**
Assert acceptance only when trajectory age is valid, position/yaw/speed tracking errors are inside configured tolerances, the future sample remains on the same segment/direction, and the interval contains no gear boundary. Assert rejection for stale trajectory, large error, terminal proximity, segment mismatch, direction mismatch, and a handoff time beyond the trajectory.
- [ ] **Step 2: Run and verify selector is absent**
Expected: build failure naming `TrajectoryHandoffSelector`.
- [ ] **Step 3: Implement time interpolation and selection**
Binary-search `TimeFromStart`, interpolate X/Y, unwrapped yaw, signed speed, curvature, and PathS, then derive redundant fields through the trajectory-point constructor. Never interpolate across different boundary types, segment indices, or directions. Return a result object that states `PreviousTrajectory` or `MeasuredState` and includes a deterministic rejection reason.
- [ ] **Step 4: Run handoff checks**
Expected: `PASS coordinator`, including a reverse same-segment handoff.
- [ ] **Step 5: Commit handoff logic**
```powershell
git add ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution ClumsyPilot/tests/EMPlannerVerificationHost/CoordinatorChecks.cs
git commit -m "feat: select safe EM trajectory handoffs"
```
### Task 3: Gear-Switch State Machine and Trajectory Executor
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/GearSwitchState.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/GearSwitchStateMachine.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/TrajectoryExecutionState.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/TrajectoryExecutor.cs`
- Create: `ClumsyPilot/tests/EMPlannerVerificationHost/ExecutorChecks.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/Program.cs`
**Interfaces:**
- Consumes: measured state, current time, sampled trajectory, and exact boundary types.
- Produces: zero-speed holding, one-shot direction-change request, confirmed departure, and completion states.
- [ ] **Step 1: Write failing state-transition checks**
Cover this exact sequence:
```text
Following -> ApproachingGearSwitch
ApproachingGearSwitch -> HoldingZero when command reaches boundary
HoldingZero remains while |measured speed| >= 0.01 m/s
HoldingZero timer resets if speed rises above tolerance
HoldingZero -> RequestingDirectionChange after continuous 0.20 s below tolerance
RequestingDirectionChange emits exactly one request
AwaitingDirectionConfirmation holds zero
confirmed direction -> Following next segment
goal/rolling terminal -> Completed while holding zero
```
Assert no transition can output nonzero signed speed during holding or direction confirmation.
- [ ] **Step 2: Run and verify executor types are absent**
Expected: build failure naming `GearSwitchStateMachine`.
- [ ] **Step 3: Implement explicit state and event inputs**
The state machine receives caller-supplied `now`, measured signed speed, desired/current directions, and a boolean direction-confirmation input. It does not call hardware. `TrajectoryExecutor` samples the trajectory, delegates boundary behavior to the state machine, and returns an immutable execution state containing the selected point and reason.
- [ ] **Step 4: Run executor state checks**
Expected: `PASS executor`, including forward-to-reverse and reverse-to-forward sequences.
- [ ] **Step 5: Commit executor state machine**
```powershell
git add ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution ClumsyPilot/tests/EMPlannerVerificationHost/ExecutorChecks.cs
git commit -m "feat: execute EM gear-switch boundaries"
```
### Task 4: Generic Control Adapter
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/TrajectoryControlCommand.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/TrajectoryControlAdapter.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/IVehicleStateProvider.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/TrajectoryExecutor.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/ExecutorChecks.cs`
**Interfaces:**
- Consumes: an `EmTrajectoryPoint` and gear-switch execution state.
- Produces: signed linear velocity, yaw rate, direction-change request, brake hold, and completion flags.
- [ ] **Step 1: Write failing command-mapping checks**
For forward and reverse points assert command signed velocity and yaw rate equal the point fields exactly. Assert `Speed`, world `vx/vy`, pose, and curvature remain available in execution telemetry but are not reinterpreted as body lateral velocity. Assert holding states always override both command velocities to zero.
- [ ] **Step 2: Run and verify adapter is absent**
Expected: build failure naming `TrajectoryControlAdapter`.
- [ ] **Step 3: Implement adapter without hardware coupling**
`TrajectoryControlAdapter.CreateCommand` copies signed longitudinal velocity and yaw rate in normal following. For hold, switch, invalid, or completed states it sets both to zero and sets the corresponding flags. Do not reference `MultiVehicleScriptVx`, `MultiVehicleScriptVy`, or `MultiVehicleScriptVth`; a later hardware-specific adapter may map this generic command after the existing controller field semantics are confirmed.
- [ ] **Step 4: Run adapter and executor checks**
Expected: `PASS executor`; add an invariant that ordinary following never requests nonzero body lateral velocity or `v=0, yawRate!=0`.
- [ ] **Step 5: Commit control adaptation**
```powershell
git add ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution ClumsyPilot/tests/EMPlannerVerificationHost/ExecutorChecks.cs
git commit -m "feat: adapt EM trajectories to control commands"
```
### Task 5: Plugin Output and License Packaging
**Files:**
- Modify: `ClumsyPilot/ClumsyPilot.csproj`
- Create: `ClumsyPilot/scripts/Publish-ClumsyPilotPlugin.ps1`
- Create: `ClumsyPilot/tests/EMPlannerVerificationHost/PluginPackagingChecks.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/Program.cs`
**Interfaces:**
- Consumes: a successfully built `ClumsyPilot.dll` plus the pinned OSQP package.
- Produces: the exact deployable `plugins` tree.
- [ ] **Step 1: Write failing packaging checks**
Create a temporary output directory, invoke the future script, and assert exactly:
```text
plugins/ClumsyPilot.dll
plugins/osqp.dll
plugins/licenses/OSQP-LICENSE.txt
plugins/licenses/OSQP-NOTICE.txt
plugins/licenses/OSQP-VERSION.txt
```
Assert the deployed native hash equals `SHA256SUMS`, `ClumsyPilot.dll` is a managed assembly, OSQP is x64, and rerunning packaging replaces files without leaving stale temporary files.
- [ ] **Step 2: Run and verify packaging script is absent**
Expected: `PluginPackagingChecks` fails because `Publish-ClumsyPilotPlugin.ps1` does not exist.
- [ ] **Step 3: Add build-output metadata and transactional publish script**
Add these items to `ClumsyPilot.csproj` without rewriting existing targets:
```xml
<None Include="ThirdParty\OSQP\win-x64\osqp.dll"
Link="osqp.dll" CopyToOutputDirectory="PreserveNewest" />
<None Include="ThirdParty\OSQP\LICENSE"
Link="licenses\OSQP-LICENSE.txt" CopyToOutputDirectory="PreserveNewest" />
<None Include="ThirdParty\OSQP\NOTICE"
Link="licenses\OSQP-NOTICE.txt" CopyToOutputDirectory="PreserveNewest" />
<None Include="ThirdParty\OSQP\VERSION"
Link="licenses\OSQP-VERSION.txt" CopyToOutputDirectory="PreserveNewest" />
```
The publish script takes mandatory `-ManagedDll` and `-OutputDirectory`, resolves both absolute paths, validates inputs and hash, stages the five files in a unique sibling temporary directory, then renames the completed `plugins` directory into place. It refuses to operate when the resolved output is a drive root or workspace root.
- [ ] **Step 4: Run packaging checks against a fresh directory**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- plugin-package
```
Expected: `PASS plugin-package`; no files exist outside the temporary test root.
- [ ] **Step 5: Commit packaging**
```powershell
git add ClumsyPilot/ClumsyPilot.csproj ClumsyPilot/scripts/Publish-ClumsyPilotPlugin.ps1 ClumsyPilot/tests/EMPlannerVerificationHost
git commit -m "build: package ClumsyPilot with OSQP"
```
### Task 6: Rolling End-to-End and Final Gate
**Files:**
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/CoordinatorChecks.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/ExecutorChecks.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md`
**Interfaces:**
- Consumes: all core, coordinator, executor, and packaging components.
- Produces: a complete first-version EM Planner workflow with safe rolling fallback.
- [ ] **Step 1: Add deterministic rolling scenarios**
Simulate caller-supplied time and measured states for: normal repeated forward replans, reverse replans, a solver failure with old-trajectory continuation, a superseded slow cycle, tracking-error reset to measured state, forward/reverse gear switch, and final goal stop. Assert every executed command comes from a currently published validated trajectory or is a zero hold.
- [ ] **Step 2: Add failure-tail assertions**
Force all new plans to fail after a successful publication. Advance time through the old trajectory and assert it reaches its exact zero-speed terminal and stays zero; no extrapolated nonzero command is allowed after its final point.
- [ ] **Step 3: Run the full first-version gate**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-all
git diff --check
```
Expected groups include foundation, OSQP, lateral, longitudinal, trajectory, facade, coordinator, executor, and plugin packaging, all with PASS output.
- [ ] **Step 4: Update README with ownership and deployment**
Document the pure-planner/coordinator/executor boundary, caller responsibilities, update cadence, handoff rules, gear state sequence, all trajectory fields, generic controller command, OSQP files, packaging command, and explicitly deferred dynamic-obstacle scope.
- [ ] **Step 5: Commit rolling integration evidence**
```powershell
git add ClumsyPilot/tests/EMPlannerVerificationHost ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md
git commit -m "test: verify rolling EM execution"
```
## Completion Gate
- One slow cycle can never overwrite a newer published trajectory.
- All handoffs remain within one segment/direction and use measured state when tracking is unsafe.
- Failed replans leave a complete prior trajectory that terminates safely at zero.
- Gear changes require measured zero-speed dwell and emit one explicit request.
- Generic control output never invents crab or in-place-rotation behavior.
- Packaging produces the exact DLL/license tree and validates the pinned native hash.
@@ -1,247 +0,0 @@
# EM Planner Windowed Execution Bootstrap 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:** Initialize the single cross-window EM Planner progress file with verified protocol state and a complete Stage 1 startup prompt.
**Architecture:** Keep all durable cross-window state in one committed Markdown file. The file points to the approved design and implementation plans, records that no implementation stage has started, preserves the known dirty-worktree baseline, and gives the next Codex window enough exact instructions to execute only Foundation Tasks 13.
**Tech Stack:** Markdown, Git, PowerShell validation, existing EM Planner design and implementation plans.
## Global Constraints
- Follow `docs/superpowers/specs/2026-08-03-em-planner-windowed-execution-design.md` exactly.
- Create only `docs/superpowers/progress/em-planner-progress.md` during this bootstrap.
- Stage 1 begins in `NotStarted`; no EM production or test code is implemented in this bootstrap.
- The branch is `trajplanner` and the protocol design baseline is commit `554c84f`.
- The five EM implementation plans are anchored by commit `8dd8ff0`.
- Existing unrelated worktree changes remain untouched and unstaged.
- Never use `git add .`, `git add -A`, destructive reset, checkout restoration, or untracked-file cleanup.
- The initial next-window prompt must prohibit subagents and restrict execution to Foundation Tasks 13.
- The progress checkpoint is committed separately from every later functionality commit.
---
## Locked File Structure
```text
docs/superpowers/progress/
└── em-planner-progress.md
```
### Task 1: Initialize the Cross-Window Progress Checkpoint
**Files:**
- Create: `docs/superpowers/progress/em-planner-progress.md`
**Interfaces:**
- Consumes: design commit `554c84f`, plan commit `8dd8ff0`, the ten-stage mapping, and the existing dirty-worktree baseline.
- Produces: the durable Stage 1 entry state and a complete prompt for a fresh Codex window.
- [ ] **Step 1: Run the failing precondition check**
Run:
```powershell
$progress = 'docs/superpowers/progress/em-planner-progress.md'
if (Test-Path -LiteralPath $progress) {
throw "Progress file already exists; inspect it instead of overwriting it."
}
throw "Expected bootstrap failure: progress file does not exist."
```
Expected: nonzero exit with `Expected bootstrap failure: progress file does not exist.` If the file already exists, stop and reconcile it with the design rather than replacing user state.
- [ ] **Step 2: Verify protocol and plan baselines before creating state**
Run:
```powershell
git cat-file -e '554c84f^{commit}'
if ($LASTEXITCODE -ne 0) { throw 'Windowed-execution design commit is missing.' }
git cat-file -e '8dd8ff0^{commit}'
if ($LASTEXITCODE -ne 0) { throw 'EM implementation-plan commit is missing.' }
if ((git branch --show-current) -ne 'trajplanner') { throw 'Expected trajplanner branch.' }
$staged = @(git diff --cached --name-only)
if ($staged.Count -ne 0) { throw "Unexpected staged files: $($staged -join ', ')" }
Write-Output 'PASS windowed-execution bootstrap prerequisites'
```
Expected: `PASS windowed-execution bootstrap prerequisites` and exit code `0`.
- [ ] **Step 3: Create the exact initial progress document**
Create the file with this complete content:
````markdown
# EM Planner Execution Progress
## Current State
- Current stage: 1 — Foundation contracts and boundaries
- Stage status: NotStarted
- Current branch: `trajplanner`
- Last checkpoint commit: None; Stage 1 has not started
- Protocol design baseline: `554c84f`
- Implementation-plan baseline: `8dd8ff0`
## Completed Tasks
| Stage | Plan | Tasks | Commits | Verification |
|---:|---|---|---|---|
| — | — | — | — | No implementation task has started |
## Current Verification
- Commands:
- `git cat-file -e 554c84f^{commit}`
- `git cat-file -e 8dd8ff0^{commit}`
- `git branch --show-current`
- `git diff --cached --name-only`
- Result: Bootstrap prerequisites passed; Stage 1 entry is ready
- Verified commit: `554c84f` for protocol design and `8dd8ff0` for implementation plans
## Preserved Workspace State
- The workspace already contains many unrelated Map, CoarsePath, PathSmoothing, project-file, report, and documentation changes. They belong to the user and must remain untouched and unstaged.
- Use explicit file paths for every `git add`; never stage the whole workspace.
- Inspect the existing diff before modifying `ClumsyPilot/ClumsyPilot.csproj`, then append only the Stage 1 rules required by the Foundation plan.
- The normal `ClumsyPilot.csproj` build can be blocked by legacy `auto_avoidance/MultiWheelAutoAvoidance.cs` references to `NetTopologySuite` and `OpenCvSharp`.
- Stage 1 must use the isolated `EMPlannerVerificationHost` rule from the Foundation plan. Do not delete or rewrite legacy functionality to hide the baseline build failure.
## Decisions Needed
- None. The Stage 1 scope and contracts are approved.
## Next Stage
- Stage: 1 — Foundation contracts and boundaries
- Plan: `docs/superpowers/plans/2026-08-03-em-planner-foundation-implementation.md`
- Tasks:
- Task 1 — Verification Host and Immutable Contracts
- Task 2 — Configuration, Diagnostics, and Request Validation
- Task 3 — Direction Segmentation and Exact Boundary Anchors
- Entry checks:
- Confirm branch `trajplanner`.
- Confirm commits `554c84f` and `8dd8ff0` exist.
- Confirm the staging area is empty.
- Capture `git status --short` before editing.
- Confirm no prior Stage 1 functionality commit is recorded; if EM files already exist, inspect and reconcile them before continuing.
- Exit gate:
- Each of Foundation Tasks 13 has its own passing verification and independent commit.
- The Foundation verification host passes `foundation` and `segmentation` groups required through Task 3.
- Exact gear-switch approach/departure identities and exact horizon anchors are verified.
- `git diff --check` reports no new whitespace errors.
- The staging area is empty after all task commits.
- This progress file is updated to mark Stage 1 completed and Stage 2 ready, then committed alone.
## Next-Window Prompt
```text
请继续 ParkingRobot 仓库的 EM Planner 多窗口实施。
工作目录:D:\Users\Desktop\项目\prakrobot\ParkingRobot
本窗口是阶段 1:Foundation 契约与边界,只执行 Foundation 实施计划的 Task 13,不得提前执行 Task 4 或后续阶段。
开始工作前必须完整读取:
1. docs/superpowers/specs/2026-08-03-em-planner-ls-st-design.md
2. docs/superpowers/specs/2026-08-03-em-planner-windowed-execution-design.md
3. docs/superpowers/progress/em-planner-progress.md
4. docs/superpowers/plans/2026-08-03-em-planner-foundation-implementation.md
执行时使用 executing-plans、test-driven-development 和 verification-before-completion 技能,并完整读取对应 SKILL.md。不要使用子代理。
开始前:
- 确认工作目录和分支 trajplanner
- 确认提交 554c84f 与 8dd8ff0 存在;
- 检查 git status --short 和暂存区;
- 保存开始时的工作区状态用于结束时对照;
- 当前工作区存在大量用户修改,全部保留,不得清理、覆盖或顺带提交;
- 如果进度文件、Git 或实际文件不一致,先诊断并报告,不要直接覆盖。
阶段范围:
- Task 1Verification Host and Immutable Contracts
- Task 2Configuration, Diagnostics, and Request Validation
- Task 3Direction Segmentation and Exact Boundary Anchors。
执行要求:
- 严格按 Foundation 计划逐任务进行 TDD
- 每个任务先运行计划指定的失败验证,再实现最小内容,再运行通过验证;
- 每个任务形成独立提交,只用显式路径 git add;
- 修改 ClumsyPilot/ClumsyPilot.csproj 前先查看现有差异,只追加本阶段要求;
- 不修改无关 Map、CoarsePath、PathSmoothing 和旧 auto_avoidance 文件;
- 不实现 Frenet、静态走廊、OSQP、LS、ST、滚动执行或动态障碍功能;
- 主项目既有 auto_avoidance 依赖失败不能通过删除旧功能规避;使用计划中的隔离验证宿主;
- 任何完成声明前运行计划指定验证和 git diff --check。
阶段结束时:
- 确认 Task 1–3 各有独立提交和测试证据;
- 运行截至 Task 3 的 Foundation 阶段出口验证;
- 核对每个提交只包含计划文件,暂存区为空;
- 更新 docs/superpowers/progress/em-planner-progress.md:记录提交、验证命令和结果,将阶段 1 标为 Completed,将阶段 2 标为 ready
- 只暂存进度文件并单独提交阶段检查点;
- 返回本阶段完成内容、提交列表、测试证据、遗留问题;
- 给出阶段 2 可直接复制的新窗口提示词。
```
````
- [ ] **Step 4: Validate structure, task scope, and prompt completeness**
Run:
```powershell
$progress = 'docs/superpowers/progress/em-planner-progress.md'
$content = Get-Content -LiteralPath $progress -Raw -Encoding UTF8
$required = @(
'Stage status: NotStarted',
'Protocol design baseline: `554c84f`',
'Implementation-plan baseline: `8dd8ff0`',
'Task 1 — Verification Host and Immutable Contracts',
'Task 2 — Configuration, Diagnostics, and Request Validation',
'Task 3 — Direction Segmentation and Exact Boundary Anchors',
'不要使用子代理',
'不得提前执行 Task 4',
'只暂存进度文件并单独提交阶段检查点'
)
foreach ($term in $required) {
if (-not $content.Contains($term)) { throw "Missing progress item: $term" }
}
if ($content.Contains('Task 4 — Reverse-Safe')) { throw 'Stage 1 prompt includes Stage 2 implementation.' }
$fences = ([regex]::Matches($content, '```')).Count
if (($fences % 2) -ne 0) { throw "Unbalanced code fences: $fences" }
git diff --check -- $progress
if ($LASTEXITCODE -ne 0) { throw 'Progress-file diff check failed.' }
Write-Output 'PASS initial EM Planner progress checkpoint'
```
Expected: `PASS initial EM Planner progress checkpoint` and exit code `0`.
- [ ] **Step 5: Commit only the initialized progress checkpoint**
Run:
```powershell
$progress = 'docs/superpowers/progress/em-planner-progress.md'
$before = @(git diff --cached --name-only)
if ($before.Count -ne 0) { throw "Unexpected staged files: $($before -join ', ')" }
git add -- $progress
$staged = @(git diff --cached --name-only)
if ($staged.Count -ne 1 -or $staged[0] -ne $progress) {
throw "Staged scope is not progress-only: $($staged -join ', ')"
}
git diff --cached --check
if ($LASTEXITCODE -ne 0) { throw 'Staged diff check failed.' }
git commit -m "docs: initialize EM planner execution progress"
```
Expected: one commit containing only `docs/superpowers/progress/em-planner-progress.md`; the staging area is empty afterward.
## Completion Gate
- The progress file exists and names Stage 1 as `NotStarted`.
- It references the approved design and implementation-plan commits.
- It contains Foundation Tasks 13 exactly and excludes Stage 2 implementation.
- It records dirty-worktree and legacy-build protections.
- Its Stage 1 prompt is directly copyable into a fresh Codex window.
- The bootstrap commit contains only the progress file.
@@ -1,106 +0,0 @@
# LocalG2-Only PathSmoothing Reorganization Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:subagent-driven-development` or `superpowers:executing-plans` to execute this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Convert `PathSmoothing` into a LocalG2-only module, remove the three legacy smoothing algorithms, preserve LocalG2 visualization and fixture workflows, and organize the source tree and README using the established `CoarsePath` module pattern.
**Architecture:** The production facade always runs the LocalG2 pipeline. Shared path preparation and validation remain intact; B-spline, local Bezier, and piecewise quintic implementations and their configuration are removed. Offline reports remain a factual comparison of raw coarse path versus LocalG2 only, with visualization sources placed below an `Output` layer like `CoarsePath`.
**Tech Stack:** C# 10, .NET SDK, Newtonsoft.Json, existing System.Drawing/StbImageWriteSharp report exporter, PowerShell verification hosts.
## Global Constraints
- Do not read, search, enumerate, copy, modify, delete, stage, or commit `ClumsyPilot/ParkrobTrajplanner/auto_avoidance`; do not enumerate `ClumsyPilot/ParkrobTrajplanner` as a parent.
- Preserve LocalG2 candidate construction, validation, publication statuses, fixture data, diagnostic candidate visualization, and generated report artifacts below `ClumsyPilot/obj/path_smoothing_reports`.
- Remove all production references to `CubicBSpline`, `LocalCubicBezier`, and `PiecewiseQuintic` smoothing.
- Retain the raw-path baseline in reports. Normal reports must contain only raw and LocalG2 series and four figures; diagnostic reports may append the already-rejected LocalG2 candidate as a fifth figure.
- Maintain current default `MinimumClearanceReserveMeters = 0d`.
- Do not delete unrelated user work or generated report directories.
---
### Task 1: Establish a LocalG2-only verification contract
**Files:**
- Modify: `ClumsyPilot/tests/verify_path_smoothing_comparison.ps1`
- Modify: `ClumsyPilot/tests/verify_path_smoothing_svg_csv.ps1`
- Modify: `ClumsyPilot/tests/PathSmoothingPngVerificationHost/Program.cs`
**Interfaces:**
- The comparison request exposes exactly one requested method: `SmoothingMethod.LocalG2Quintic`.
- A normal report has a raw baseline plus one LocalG2 row/series; the diagnostic report retains its optional rejected candidate figure.
- [ ] Add failing assertions that reject the three removed enum names, require one requested comparison method, require two normal figure series, and require exactly two CSV rows after the header.
- [ ] Run the focused PowerShell checks and confirm they fail against the four-algorithm implementation.
- [ ] Update host assertions for the new two-series normal report while retaining the seven-file diagnostic contract.
- [ ] Re-run the focused checks after Tasks 2 and 3 and record the exit codes.
### Task 2: Remove legacy algorithms and simplify the production facade
**Files:**
- Delete: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Algorithms/`
- Delete: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/CubicBSplineOptions.cs`
- Delete: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/LocalCubicBezierOptions.cs`
- Delete: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/PiecewiseQuinticOptions.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/SmoothingMethod.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/PathSmoothingConfiguration.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/PathSmoothingRequest.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/PathSmoothingResult.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Facade/PathSmoothingService.cs`
**Interfaces:**
- `SmoothingMethod` retains only `LocalG2Quintic`.
- `PathSmoothingConfiguration` defaults `Method` to `LocalG2Quintic` and exposes only shared safety/sampling fields and `LocalG2Quintic` options.
- `PathSmoothingService.Smooth(request, cancellationToken)` directly validates/prepares/builds the raw baseline and invokes `LocalG2PreSmoothingPipeline`.
- [ ] Delete legacy source files only after their callers are removed.
- [ ] Remove legacy smoothness/retry configuration and cloning code; preserve output spacing, collision step, clearance reserve, and LocalG2 options.
- [ ] Replace the multi-method resolver and fallback path in `PathSmoothingService` with its LocalG2-only route.
- [ ] Compile the isolated PathSmoothing host and confirm no source references to the removed methods remain in allowed paths.
### Task 3: Reorganize report sources into an Output layer and reduce the report model
**Files:**
- Move: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Comparison/` to `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Output/Comparison/`
- Move: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Visualization/` to `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Output/Visualization/`
- Delete: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Output/Comparison/SmoothingMethodRanker.cs`
- Modify: moved comparison request/result/service consumers and all moved visualization files.
**Interfaces:**
- `PathSmoothingComparisonRequest` owns one immutable LocalG2 request rather than a caller-selectable method list.
- `PathSmoothingComparisonResult` contains a raw baseline and exactly one LocalG2 entry.
- Normal figure and CSV builders emit `RawPath` and `LocalG2Quintic` only.
- [ ] Move source directories with their namespaces changed from `PathSmoothing.Comparison` and `PathSmoothing.Visualization` to `PathSmoothing.Output.Comparison` and `PathSmoothing.Output.Visualization`.
- [ ] Simplify comparison execution to warm up and measure LocalG2 only; retain deterministic timing/digest behavior for its sole entry.
- [ ] Remove visual style colors, legend rows, labels, metric rows, and all source references for the three deleted algorithms.
- [ ] Publish four normal figures with stable stems `01-coarse-path-overview`, `02-all-paths-comparison`, `03-local-g2-overview`, and `04-curvature-comparison`; append `05-local-g2-diagnostic-candidate` only to an augmented diagnostic model.
- [ ] Update all allowed source/test imports to the new `Output` namespaces.
### Task 4: Align test/demo entry points and document the module
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/PathSmoothingComparisonDemo.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/SmoothingScenarioFactory.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Test/LocalG2DiagnosticVisualizationDemo.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/README.md`
**Interfaces:**
- Fixture reports use the LocalG2-only comparison request and retain all eight fixtures.
- The README mirrors the `CoarsePath/README.md` information architecture for LocalG2 inputs, safety gates, result statuses, report output, and known limitations.
- [ ] Update test/demo imports and expected report shapes for the Output namespaces and LocalG2-only model.
- [ ] Create `README.md` with the following ordered sections: Module Overview, File Structure, Smoothing Data Flow, Result Status and Publication Rules, Coordinates and Units, Minimal Call Example, Detailed Usage Guide, Fixture Reports and Visualization, Common Errors, and First-Version Limits.
- [ ] State explicitly that a candidate passing collision validation may still be retained when its quality gate fails, and that `0 m` reserve removes only the additional clearance reserve, not collision or curvature checks.
### Task 5: Verify source layout and retain visualization artifacts
**Files:**
- Verify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/`
- Verify: `ClumsyPilot/obj/path_smoothing_reports/`
- [ ] Build and run the isolated current-source LocalG2 visualization host against all eight fixture scenarios.
- [ ] Confirm all normal report directories contain the expected four PNG/SVG figures and CSV, and that `02-all-paths-comparison.png` presents raw plus LocalG2 only.
- [ ] Run the focused comparison/SVG/diagnostic verification scripts where their dependencies are available; report any root-build limitation separately.
- [ ] Inspect at least the `single-turn` normal report and `05-local-g2-diagnostic-candidate.png` to confirm LocalG2 labels, nonblank rendering, and retained diagnostic semantics.
- [ ] Update `.superpowers/sdd/progress.md` with the actual cleanup results and verification evidence.
@@ -1,457 +0,0 @@
# EM Observation MovementTest 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:** Build a real-localization, observe-only MovementTest that creates a configurable start/goal map, plans Hybrid A* → Local G2 → EM trajectories, and shows world, LS, and ST diagnostics without sending a chassis command.
**Architecture:** Put map construction, planning bootstrap, rolling EM requests, trajectory observation, and LS/ST derivation in pure, testable classes. Keep MDCS reads, prompts, painters, background timing, and cancellation in one thin MovementTest host. The host may only read DetourInterface and BasicPilotBase.Chassis; its only control output is a displayed TrajectoryControlCommand.
**Tech Stack:** C# 10, netstandard2.0, existing Clumsy MovementTest/Painter UI, MDCS localization and chassis read APIs, Hybrid A*, Local G2, EM planner, OSQP, and EMPlannerVerificationHost.
## Global Constraints
- All map geometry and UI world coordinates are mm; Pose2D, velocities, and EM geometry are m, m/s, and rad.
- Bounds are exactly the start/goal axis-aligned rectangle expanded by MapPaddingMeters on all sides. Obstacles must fit these bounds; they must not enlarge them.
- Default settings are: padding 2.0 m, resolution 50 mm, replan 0.20 s, observer period 0.05 s.
- Capture world pose through DetourInterface.getCartLocation() and signed body-longitudinal velocity from BasicPilotBase.Chassis.GetCarSpeed(true).Vx. Create a monotonically increasing state sequence id.
- Output is TrajectoryControlCommand for display only. Do not invoke SendXYThSpeed, SendMotion, SendTh, AccumulateSpeed, ComputeWheelsGeometrically, brake/wheel adapter methods, or a geometric controller.
- Keep runtime source under ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest. Do not change the existing coarse-path factory, whose unrelated manual demo uses an 8 m expansion.
- Stop/cancel must cancel worker activity and clear the World, LS, and ST painter layers.
---
## File structure
| File | Responsibility |
| --- | --- |
| ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationContracts.cs | Settings, manual-obstacle DTOs, validation, exact map-job construction. |
| ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPipeline.cs | Hybrid A* + Local G2 bootstrap, rolling EM requests, time observation, LS/ST models. |
| ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPresentation.cs | Three painter layers and presentation text; no MDCS/hardware use. |
| ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs | Discoverable test, MDCS state reader, prompts, background session, console, cancellation. |
| ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/README.md | Operator configuration, layer interpretation, unit and safety guidance. |
| ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs | Deterministic regression checks and an actuator-call source audit. |
| ClumsyPilot/tests/EMPlannerVerificationHost/Program.cs | Adds the trajectory-observation command. |
### Task 1: Configuration and exact rectangle-map inputs
**Files:**
- Create: ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationContracts.cs
- Create: ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs
- Modify: ClumsyPilot/tests/EMPlannerVerificationHost/Program.cs
**Interfaces:**
- Consumes: Pose2D, VehicleParameters, PlanningMapRequest, MapBoundsMm, ManualObstacleSource, CircleObstacle, AxisAlignedRectangleObstacle.
- Produces: TrajectoryObservationSettings.Validate(), TrajectoryObservationObstacle.Circle(double, double, double), TrajectoryObservationObstacle.Rectangle(double, double, double, double), and TrajectoryObservationSetupFactory.CreateBootstrapJob(Pose2D, Pose2D, TrajectoryObservationSettings, IReadOnlyList<TrajectoryObservationObstacle>, long).
- [ ] **Step 1: Write failing map-bounds and obstacle checks**
Create the verification host class and invoke it with a new trajectory-observation argument:
~~~csharp
internal static class TrajectoryObservationChecks
{
public static void Run()
{
VerifiesStartGoalBoundsUseOnlyConfiguredPadding();
RejectsObstacleOutsideConfiguredBounds();
}
private static void VerifiesStartGoalBoundsUseOnlyConfiguredPadding()
{
var settings = new TrajectoryObservationSettings
{
MapPaddingMeters = 2d,
MapResolutionMillimeters = 50f,
};
CoarsePathPlanningJob job = TrajectoryObservationSetupFactory.CreateBootstrapJob(
new Pose2D(10d, -5d, 0d), new Pose2D(13d, -1d, 0d), settings,
Array.Empty<TrajectoryObservationObstacle>(), 17L);
Verification.NearlyEqual(8000d, job.MapRequest.Bounds.XMin, "observer map x min");
Verification.NearlyEqual(15000d, job.MapRequest.Bounds.XMax, "observer map x max");
Verification.NearlyEqual(-7000d, job.MapRequest.Bounds.YMin, "observer map y min");
Verification.NearlyEqual(1000d, job.MapRequest.Bounds.YMax, "observer map y max");
Verification.NearlyEqual(50d, job.MapRequest.ResolutionMm, "observer map resolution");
}
}
~~~
Modify Program.Main to accept trajectory-observation, call TrajectoryObservationChecks.Run(), then write PASS trajectory-observation. Add the same call to em-all.
- [ ] **Step 2: Run the new check to prove it fails**
Run:
~~~powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
~~~
Expected: compilation fails because TrajectoryObservationSettings and TrajectoryObservationSetupFactory do not exist.
- [ ] **Step 3: Implement the contracts and factory**
Create the editable configuration contract:
~~~csharp
public sealed class TrajectoryObservationSettings
{
public double MapPaddingMeters { get; set; } = 2d;
public float MapResolutionMillimeters { get; set; } = 50f;
public double ReplanPeriodSeconds { get; set; } = 0.20d;
public double ObserverPeriodSeconds { get; set; } = 0.05d;
public double VehicleLengthMeters { get; set; } = 0.80d;
public double VehicleWidthMeters { get; set; } = 0.60d;
public double SafetyMarginMeters { get; set; } = 0.05d;
public double MaximumCurvaturePerMeter { get; set; } = 1d / 1.20d;
public void Validate();
public VehicleParameters CreateVehicle();
}
~~~
Implement finite/positive validation. Implement the obstacle as world-mm circle or axis-aligned rectangle with GetBounds() and ToMapObstacle(). Build bounds with the following exact calculation, rounded outward to the configured grid:
~~~csharp
double padMm = settings.MapPaddingMeters * 1000d;
var bounds = new MapBoundsMm(
ToGridLower(Math.Min(start.X, goal.X) * 1000d - padMm, settings.MapResolutionMillimeters),
ToGridUpper(Math.Max(start.X, goal.X) * 1000d + padMm, settings.MapResolutionMillimeters),
ToGridLower(Math.Min(start.Y, goal.Y) * 1000d - padMm, settings.MapResolutionMillimeters),
ToGridUpper(Math.Max(start.Y, goal.Y) * 1000d + padMm, settings.MapResolutionMillimeters));
~~~
Reject an obstacle unless its full envelope is contained in bounds. With zero obstacles set AllowExplicitEmptyMap true. Otherwise construct exactly one required ManualObstacleSource named trajectory-observer-manual with the supplied positive snapshot version. Return a CoarsePathPlanningJob with new HybridAStarConfiguration, StartDirection = null, and GoalDirection = GoalDirectionConstraint.Any.
- [ ] **Step 4: Run focused and existing checks**
Run:
~~~powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- foundation
~~~
Expected: both exit 0 and print PASS trajectory-observation and PASS foundation.
- [ ] **Step 5: Commit the input layer**
~~~powershell
git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationContracts.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs ClumsyPilot/tests/EMPlannerVerificationHost/Program.cs
git commit -m "feat: add observation test map inputs"
~~~
### Task 2: Pure planning bootstrap, time observation, and LS/ST derivation
**Files:**
- Create: ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPipeline.cs
- Modify: ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs
**Interfaces:**
- Consumes: CoarsePathPlanningService, PathSmoothingService, EmPlanningCoordinator, TrajectoryExecutor, FrenetProjector, and caller-supplied VehicleMotionState.
- Produces: TrajectoryObservationBootstrapper.Bootstrap(CoarsePathPlanningJob, CancellationToken), TrajectoryObservationController.StartCycle(DateTimeOffset, VehicleMotionState, CancellationToken), TrajectoryObservationController.Observe(DateTimeOffset, VehicleMotionState), and TrajectoryObservationCharts.Build(EmTrajectory, DirectionSegmentView, double).
- [ ] **Step 1: Add failing chart and time-sampling checks**
Extend TrajectoryObservationChecks.Run() by adding VerifiesLsAndStUsePublishedTrajectoryData(). Use a fixed two-point EmTrajectory whose EffectiveAtUtc is 2026-08-04T00:00:00Z, with TimeFromStart values 0 and 1, PathS values 4 and 5, and known signed speeds. Assert that Build returns two ST samples (0,4) and (1,5), two speed samples, and the expected LS projection count. Call Observe at 00:00:00.500Z and assert that TrajectoryExecutor selected an interpolated point with TimeFromStart == 0.5d.
- [ ] **Step 2: Run the new check to prove it fails**
Run:
~~~powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
~~~
Expected: compilation fails because TrajectoryObservationCharts and TrajectoryObservationController do not exist.
- [ ] **Step 3: Implement the pipeline**
Bootstrap must use exactly this success gate:
~~~csharp
CoarsePathPlanningJobResult coarse = coarseService.Plan(job, cancellationToken);
if (coarse.PlanningResult.Status != PlanningStatus.Success)
return TrajectoryObservationBootstrapResult.FromFailure(
job, coarse, null, "Coarse planning status: " + coarse.PlanningResult.Status);
var smoothingRequest = new PathSmoothingRequest(
CopyFiniteClearance(coarse.PlanningResult.Path, coarse.MapResult.Map),
coarse.PlanningResult.Segments, coarse.MapResult.Map, job.Vehicle,
new PathSmoothingConfiguration());
PathSmoothingResult smooth = smoothingService.Smooth(smoothingRequest, cancellationToken);
if (!IsPublishedSmoothingStatus(smooth.Status))
return TrajectoryObservationBootstrapResult.FromFailure(
job, coarse, smooth, smooth.Diagnostics.TerminationReason);
return TrajectoryObservationBootstrapResult.Success(job, coarse, smooth, ReferencePathSegmenter.Create(smooth));
~~~
IsPublishedSmoothingStatus accepts only Complete, PartialImprovement, NotNeeded, and Unchanged. CopyFiniteClearance replaces a positive-infinite clearance with the finite map diagonal before copying each CoarsePathPoint.
TrajectoryObservationController owns EmPlanningCoordinator and TrajectoryExecutor. For a replan it creates:
~~~csharp
var request = new EmPlanningRequest(
bootstrap.SmoothedPath, bootstrap.Map, bootstrap.Job.Vehicle, state, configuration,
segmentIndex, coordinator.PublishedTrajectory, now, now,
sessionId + "-trajectory-" + cycleId, sessionId + "-reference",
coordinator.PublishedTrajectory?.Metadata.TrajectoryId ?? string.Empty,
EmMotionModel.NonholonomicForwardReverse);
return coordinator.PlanLatestAsync(new PlanningCycleInput(request, now), cancellationToken);
~~~
Set configuration.Scheduling.ReplanPeriodSeconds from settings. Initial observation mode always uses segmentIndex 0. Observe must use PublishedTrajectory only; when non-null call UpdateCommand(now, state, trajectory, trajectory.Metadata.Direction, trajectory.Metadata.Direction, true) and return the selected point, command, and executor state for display only.
Build LS/ST from published data alone:
~~~csharp
ls.Add(new TrajectoryObservationLsSample(
segment.SourceStartArcLength + projection.ReferenceS, projection.LateralOffset));
st.Add(new TrajectoryObservationStSample(point.TimeFromStart, point.PathS));
speed.Add(new TrajectoryObservationSpeedSample(point.TimeFromStart, point.SignedLongitudinalVelocity));
~~~
Use seeded FrenetProjector calls and count failed projections. No pipeline class may reference UI, DetourInterface, BasicPilotBase, or a hardware class.
- [ ] **Step 4: Run diagnostics and regression checks**
Run:
~~~powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- coordinator
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- executor
~~~
Expected: every command exits 0.
- [ ] **Step 5: Commit the pure pipeline**
~~~powershell
git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPipeline.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs
git commit -m "feat: add EM observation planning pipeline"
~~~
### Task 3: Presentation layers and observation text
**Files:**
- Create: ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPresentation.cs
- Modify: ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs
**Interfaces:**
- Consumes: bootstrap result, observation result, and chart data.
- Produces: TrajectoryObservationPresentation.DrawWorld(TrajectoryObservationBootstrapResult, TrajectoryObservationObservation), DrawLs(TrajectoryObservationCharts), DrawSt(TrajectoryObservationCharts), ClearAll(), and TrajectoryObservationPresentationText.Create(TrajectoryObservationObservation, TrajectoryObservationCharts).
- [ ] **Step 1: Add a failing presentation-text check**
Assert that TrajectoryObservationPresentationText.Create(observation, charts) contains the literal OBSERVE_ONLY: no chassis command is sent., selected point time/path-S, signed speed, yaw rate, and LS projection failure count. The check must not instantiate a Painter.
- [ ] **Step 2: Run the check to verify it fails**
Run:
~~~powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
~~~
Expected: compilation fails because TrajectoryObservationPresentationText does not exist.
- [ ] **Step 3: Implement the three painters**
Create exactly these named layers:
~~~csharp
worldPainter = UI.GetPainter("TrajectoryObserver.World", true);
lsPainter = UI.GetPainter("TrajectoryObserver.LS", true);
stPainter = UI.GetPainter("TrajectoryObserver.ST", true);
~~~
DrawWorld clears only worldPainter then draws map bounds/grid/occupied cells, start, goal, coarse path, Local G2 path, real pose, and latest EM path. Convert every planner position from m to mm before calling DrawLine, DrawCircle, or DrawText.
DrawLs draws axes plus s-l samples. DrawSt draws t-s and a vertically separated t-v series with a legend. A missing trajectory draws a status string instead of throwing. ClearAll invokes Clear on all three painters and performs no other action.
- [ ] **Step 4: Run visual-model and compile verification**
Run:
~~~powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
dotnet build ClumsyPilot/ClumsyPilot.csproj --no-restore
~~~
Expected: trajectory-observation passes and the project has zero compile errors.
- [ ] **Step 5: Commit presentation**
~~~powershell
git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPresentation.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs
git commit -m "feat: visualize EM observation diagnostics"
~~~
### Task 4: MDCS read-only MovementTest host
**Files:**
- Create: ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs
- Create: ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/README.md
- Modify: ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs
**Interfaces:**
- Consumes: DetourInterface.getCartLocation(), BasicPilotBase.Chassis.GetCarSpeed(true), setup/controller/presentation APIs.
- Produces: a [MovementTest(name = "EM轨迹规划观察闭环测试")] entry with Test() and TestStop().
- [ ] **Step 1: Write a failing actuator-free source audit**
Add VerifiesObservationSourceHasNoActuatorCalls() to TrajectoryObservationChecks.Run() and implement it in the verification host. It reads the observation runtime source files and fails on any of these tokens:
~~~csharp
new[]
{
".SendXYThSpeed(", ".SendMotion(", ".SendTh(", ".AccumulateSpeed(",
".ComputeWheelsGeometrically(", ".DriveStop(", ".PredefinedDriveStop("
}
~~~
The audit strings live only in the test host; none may appear in the new runtime observation files.
- [ ] **Step 2: Run the audit before the host exists**
Run:
~~~powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
~~~
Expected: the check fails because MovementTest.TrajectoryObservationTest.cs is missing.
- [ ] **Step 3: Implement the host and lifecycle**
Use this discoverable configuration:
~~~csharp
[MovementTest(name = "EM轨迹规划观察闭环测试")]
public sealed class TrajectoryObservationMovementTest : MovementTest
{
public double GoalXmm = double.NaN;
public double GoalYmm = double.NaN;
public double GoalYawDeg = 0d;
public double MapPaddingMeters = 2d;
public float MapResolutionMm = 50f;
public double ReplanPeriodSeconds = 0.20d;
public double ObserverPeriodSeconds = 0.05d;
public override void Test();
public override void TestStop();
}
~~~
When GoalXmm or GoalYmm is non-finite, prompt for all goal values with the same finite parser/UI.GetInput pattern as CoarsePathPlanningTest. Prompt for 020 manual obstacles (circle or rectangle) and freeze all inputs before Task.Run begins.
The MDCS reader must use only this read path:
~~~csharp
var location = DetourInterface.getCartLocation();
if (location == null) throw new InvalidOperationException("Live localization is unavailable.");
if (BasicPilotBase.Chassis == null) throw new InvalidOperationException("Live chassis read interface is unavailable.");
var speed = BasicPilotBase.Chassis.GetCarSpeed(true);
return new VehicleMotionState(
new Pose2D(location.x / 1000d, location.y / 1000d, location.th * Math.PI / 180d),
speed.Vx, null, DateTimeOffset.UtcNow, Interlocked.Increment(ref stateSequence));
~~~
Bootstrap once in a cancellable Task.Run. After success, run Task.Delay(TimeSpan.FromSeconds(ObserverPeriodSeconds), token) between ticks. At each tick capture exactly one state, start a cycle only when controller.ShouldStartCycle(now), observe the latest published trajectory, draw all layers, and print a throttled status. Construct the service as new EmPlanningService(new OsqpNativeSolver()).
Every status includes:
~~~text
OBSERVE_ONLY: no chassis command is sent.
~~~
When a GearSwitch trajectory reaches its final time, draw and print 等待真实档位/方向确认;观察模式不会推进下一方向段, leave segment index 0, and do not create a direction-change action. Goal and rolling-stop commands may only be logged.
Use a lock/session id pattern matching CoarsePathPlanningTestRunner: replace the active CancellationTokenSource, cancel the old source without waiting, and allow only the current session to draw or log. TestStop cancels, disposes after task completion, and calls presentation.ClearAll.
Write README.md with configuration fields/units, obstacle examples, default values, chart interpretations, the VelocityX/VelocityY world-frame warning, and the explicit no-driving limitation.
- [ ] **Step 4: Verify runner safety and integration build**
Run:
~~~powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
dotnet build ClumsyPilot/ClumsyPilot.csproj --no-restore
rg -n 'SendXYThSpeed\(|SendMotion\(|SendTh\(|AccumulateSpeed\(|ComputeWheelsGeometrically\(|DriveStop\(|PredefinedDriveStop\(' ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest -g '*.cs'
~~~
Expected: host and build exit 0. The rg command exits 1 because no runtime observation file calls a forbidden actuator method.
- [ ] **Step 5: Commit the MovementTest**
~~~powershell
git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/README.md ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs
git commit -m "feat: add read-only EM observation movement test"
~~~
### Task 5: End-to-end regression and operator handoff
**Files:**
- Modify: ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs
- Modify: ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/README.md
**Interfaces:**
- Consumes: completed observation-test components and existing EMPlannerVerificationHost checks.
- Produces: a reproducible all-up verification command and a launch/stop checklist.
- [ ] **Step 1: Add a failing bootstrap regression**
Use an empty-map setup with start (0.5, 0.5, 0) m and goal (3.5, 0.5, 0) m. Assert that bootstrap returns a successful map, PlanningStatus.Success, a publishable smoothing result, and at least one DirectionSegmentView. This check does not run native OSQP.
- [ ] **Step 2: Run the check to confirm its failure**
Run:
~~~powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
~~~
Expected: the assertion identifies a missing or incorrect bootstrap result.
- [ ] **Step 3: Make the smallest corrective change**
Correct only TrajectoryObservationSetupFactory or TrajectoryObservationBootstrapper so the empty-map request produces a planning-ready map and publishable Local G2 path. Preserve the exact bounds rule and do not add UI, MDCS, or hardware dependencies to pure classes.
- [ ] **Step 4: Run all required evidence checks**
Run:
~~~powershell
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 --no-restore
git diff --check
~~~
Expected: every command exits 0. In the vehicle UI, the entry appears as EM轨迹规划观察闭环测试 and starting/running/stopping it does not issue any chassis, motor, steering, or brake output.
- [ ] **Step 5: Commit final verification/documentation**
~~~powershell
git add -- ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/README.md
git commit -m "test: verify EM observation movement test"
~~~
## Plan self-review
**Spec coverage:** Task 1 provides configurable start/goal map bounds, vehicle settings, and manual obstacles. Task 2 covers Hybrid A*, Local G2, rolling EM, time sampling, and derived LS/ST. Task 3 creates World/LS/ST painters. Task 4 reads live MDCS state, prints observation diagnostics, handles gear-switch observation, and ensures cancellation/no-write behavior. Task 5 supplies an end-to-end fixture and final evidence.
**Placeholder scan:** Every task names concrete files, commands, interface names, inputs, expected behavior, and commit content; no deferred implementation markers remain.
**Type consistency:** Map code produces PlanningMapRequest and CoarsePathPlanningJob; bootstrap produces PathSmoothingResult and DirectionSegmentView; rolling planning consumes VehicleMotionState and EmPlanningRequest; UI consumes EmTrajectory, TrajectoryControlCommand, and chart samples without changing EM contracts.
@@ -1,262 +0,0 @@
# EM Planner 与 TrajectoryExecution README 实施计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use `executing-plans` to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 以 `CoarsePath/README.md` 的结构重写 EMPlanner 文档,并为 TrajectoryExecution 新增准确、可操作的模块 README。
**Architecture:** README 只描述现有的不可变请求、纯单次规划、滚动协调、轨迹执行和通用控制命令边界。两份文档均从上游空间路径到未来硬件适配器的单向数据流解释职责;不改变任何 C#、测试、项目文件或发布脚本。
**Tech Stack:** Markdown、PowerShell、Git、现有 `EMPlannerVerificationHost` 命令。
## Global Constraints
- 仅修改 `ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md` 和新建的 `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/README.md`
- 保留所有既有用户工作区改动;只显式暂存本任务指定的 README,绝不使用 `git add .``git add -A`
- 从仓库根目录运行所有检查;不改动 LS/ST/OSQP、测试夹具、发布脚本、`csproj`、UI 或硬件代码。
- `EmPlanningService.Plan(EmPlanningRequest, CancellationToken)` 是纯、同步、一次性的规划入口。
- 只有 `Success``SuccessWithFallback` 可携带可消费的 `EmTrajectory`;其余状态不可发布部分轨迹。
- 动态障碍物、时空占用、行为决策、UI/硬件集成、横移、蟹行和原地旋转均明确为未实现范围。
- 验证命令只引用当前的 `em-core-all``coordinator``executor``plugin-package``em-all`
---
### Task 1: 重写 EMPlanner 模块 README
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md`
**Interfaces:**
- Consumes: `PathSmoothingResult``PlanningGridMap``VehicleParameters``VehicleMotionState``EmPlannerConfiguration` 和可选上一条 `EmTrajectory` 快照。
- Produces: 文档化的 `IEmPlanningService.Plan(EmPlanningRequest request, CancellationToken cancellationToken)` 消费边界,以及仅在 `Success`/`SuccessWithFallback` 时可消费的 `EmTrajectory`
- [ ] **Step 1: 记录旧文档的 RED 基线**
从仓库根目录运行:
```powershell
$text = Get-Content ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md -Raw -Encoding UTF8
if ($text -notmatch '尚未实现 LS 横向优化、\s*ST 纵向优化、滚动协调、轨迹发布') {
throw 'Expected the stale Foundation-only scope marker.'
}
```
Expected: 找到一条 Foundation 阶段的旧描述,证明重写前 README 与当前实现不一致。
- [ ] **Step 2: 以 CoarsePath 风格重写 README**
使用 `apply_patch` 以 UTF-8 Markdown 整体替换文件。文档必须按以下顺序覆盖:
```text
模块说明(职责/非职责与唯一入口)
文件结构(Configuration、Contracts、Segmentation、Frenet、Corridor、Lateral、Longitudinal、Trajectory、Validation、Facade
规划数据流(校验 -> 方向段 -> 走廊 -> LS -> ST -> 装配 -> 独立验证)
结果、状态与停止
坐标、单位与前进/倒车符号
最小调用示例
详细使用指南
验证命令
常见错误
第一版限制
```
正文必须声明静态走廊、OSQP 通过 solver-neutral `IQpSolver`/`QuadraticProgram`、实际 `PathS`、终端零速安全尾段、世界空间独立复核和纯服务边界。示例只构造 `EmPlanningService` 和调用 `Plan`,不得包含 UI 或硬件对象。模块概览和首版限制都必须以相对链接指向 `../TrajectoryExecution/README.md`,说明滚动执行不属于纯规划服务。
- [ ] **Step 3: 运行 GREEN 文档契约检查**
从仓库根目录运行:
```powershell
$path = 'ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md'
$text = Get-Content $path -Raw -Encoding UTF8
$required = @(
'## 模块说明(Module Overview',
'## 文件结构(File Structure',
'## 规划数据流(Planning Data Flow',
'EmPlanningService.Plan',
'SuccessWithFallback',
'em-core-all',
'em-all',
'动态障碍物'
)
foreach ($item in $required) {
if (-not $text.Contains($item)) { throw "Missing README contract: $item" }
}
if ($text -match '尚未实现 LS 横向优化、\s*ST 纵向优化、滚动协调、轨迹发布') {
throw 'Stale Foundation-only scope remains.'
}
```
Expected: exit 0 without `Missing README contract` or stale-scope error.
- [ ] **Step 4: 检查差异并提交 Task 1**
```powershell
git diff --check
git add -- ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md
git diff --cached --check
git diff --cached --name-only
git commit -m "docs: rewrite EM Planner readme"
git diff-tree --no-commit-id --name-only -r HEAD
```
Expected: staged 和提交范围仅为 EMPlanner README;所有 diff 检查退出 0。
### Task 2: 新增 TrajectoryExecution 模块 README
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/README.md`
**Interfaces:**
- Consumes: `IEmPlanningService`、调用方冻结的 `PlanningCycleInput`、当前 `VehicleMotionState` 与已发布不可变 `EmTrajectory`
- Produces: `EmPlanningCoordinator.PlanLatestAsync` 的当前版本发布语义,以及 `TrajectoryExecutor.UpdateCommand` 返回的 `TrajectoryControlCommand`
- [ ] **Step 1: 记录 README 缺失的 RED 基线**
从仓库根目录运行:
```powershell
if (Test-Path ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/README.md) {
throw 'TrajectoryExecution README unexpectedly already exists.'
}
throw 'RED: TrajectoryExecution README is absent.'
```
Expected: command exits nonzero and prints the RED message.
- [ ] **Step 2: 新增 CoarsePath 风格 README**
使用 `apply_patch` 创建 UTF-8 Markdown。按以下顺序说明:
```text
模块说明(协调、交接、执行与控制适配职责)
文件结构(所有 .cs 文件)
滚动执行数据流
周期身份、latest-wins 与安全交接
换向状态机和停止语义
通用控制命令的字段、单位及硬件边界
最小调用示例
详细使用指南
验证命令与插件部署链接
常见错误
第一版限制
```
文件结构必须逐项列出当前文件,且只赋予现有职责:
```text
TrajectoryExecution/
├── EmPlanningCoordinator.cs
├── GearSwitchState.cs
├── GearSwitchStateMachine.cs
├── IEmPlanningCycleSink.cs
├── IVehicleStateProvider.cs
├── PlanningCycleIdentity.cs
├── PlanningCycleInput.cs
├── PlanningCycleResult.cs
├── TrajectoryControlAdapter.cs
├── TrajectoryControlCommand.cs
├── TrajectoryExecutionState.cs
├── TrajectoryExecutor.cs
├── TrajectoryHandoffSelector.cs
└── TrajectorySampler.cs
```
文档必须给出状态序列 `Following -> ApproachingGearSwitch -> HoldingZero -> RequestingDirectionChange -> AwaitingDirectionConfirmation -> Following`,说明失败重规划不覆盖上一条完整轨迹、轨迹末点不外推,以及 `TrajectoryControlCommand` 没有横向车体速度或原地旋转字段。插件章节必须指向 EMPlanner README 的 Windows x64 打包说明,不能重写发布脚本的细节。
- [ ] **Step 3: 运行 GREEN 文档契约检查**
从仓库根目录运行:
```powershell
$path = 'ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/README.md'
$text = Get-Content $path -Raw -Encoding UTF8
$required = @(
'## 模块说明(Module Overview',
'## 文件结构(File Structure',
'## 滚动执行数据流(Rolling Execution Data Flow',
'EmPlanningCoordinator.PlanLatestAsync',
'TrajectoryExecutor.UpdateCommand',
'Following -> ApproachingGearSwitch -> HoldingZero',
'TrajectoryControlCommand',
'../EMPlanner/README.md',
'em-all',
'动态障碍物'
)
foreach ($item in $required) {
if (-not $text.Contains($item)) { throw "Missing README contract: $item" }
}
```
Expected: exit 0 without `Missing README contract`.
- [ ] **Step 4: 执行文档关联与回归验证**
从仓库根目录运行:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-core-all
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- coordinator
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- executor
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- plugin-package
git diff --check
```
Expected: 四个验证组均退出 0 并输出对应 `PASS` 行;`git diff --check` 退出 0。
- [ ] **Step 5: 检查差异并提交 Task 2**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/README.md
git diff --cached --check
git diff --cached --name-only
git commit -m "docs: add trajectory execution readme"
git diff-tree --no-commit-id --name-only -r HEAD
```
Expected: staged 和提交范围仅为 TrajectoryExecution README;所有 diff 检查退出 0。
### Task 3: 最终 README 交叉核查
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md`(仅在发现失效相对链接或错误命令时修正)
- Modify: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/README.md`(仅在发现失效相对链接或错误命令时修正)
**Interfaces:**
- Consumes: Task 1 的单次规划边界和 Task 2 的执行边界。
- Produces: 两份边界互不矛盾、命令与仓库现状一致的交接文档。
- [ ] **Step 1: 运行最终文本与相对链接检查**
```powershell
$em = Get-Content ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md -Raw -Encoding UTF8
$execution = Get-Content ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/README.md -Raw -Encoding UTF8
if ($em -match '尚未实现 LS 横向优化') { throw 'EMPlanner retains stale completion state.' }
if ($em -notmatch 'TrajectoryExecution') { throw 'EMPlanner does not describe its execution boundary.' }
if ($execution -notmatch '../EMPlanner/README.md') { throw 'Execution README lacks EMPlanner link.' }
if ($execution -match 'MultiVehicleScriptVx|MultiVehicleScriptVy|MultiVehicleScriptVth') {
throw 'Execution README leaks a prohibited hardware/UI field.'
}
```
Expected: exit 0.
- [ ] **Step 2: 运行最终门禁并检查工作区范围**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-all
git diff --check
git status --short -- ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/README.md
```
Expected: `em-all` exit 0;无 whitespace 错误;两个 README 均已提交,因此范围状态为空。
- [ ] **Step 3: 如步骤 1 或 2 要求更正,显式暂存两份 README 并提交**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/README.md
git diff --cached --check
git commit -m "docs: verify EM Planner readme links"
git diff-tree --no-commit-id --name-only -r HEAD
```
Expected: 仅当发生文字修正时才创建提交;否则不执行此步骤。
@@ -1,295 +0,0 @@
# PathSmoothing 与 TrajectoryExecution 注释改造实施计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use `executing-plans` to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 为 PathSmoothing 生产规划核心与 TrajectoryExecution 全部生产代码补齐 CoarsePath 风格的中文 XML 文档注释。
**Architecture:** 只在现有声明之前增加或替换 `///` 文档注释,不改变任何命名、签名、数值、控制流或项目文件。公开 API 使用完整的职责、参数、返回值和失败语义;关键内部算法边界补充单位、数据所有权和不变量;私有小工具、测试、报告及可视化模块保持不动。
**Tech Stack:** C# XML documentation comments、PowerShell、现有 PathSmoothing 验证脚本、`EMPlannerVerificationHost`
## Global Constraints
- 只修改注释;禁止修改任何可执行 C# 语句、签名、命名空间、`using`、测试、`csproj`、UI、硬件或报告/可视化模块。
- 注释使用中文,并保留 C# 类型名;每个数值参数或属性都要标明现有单位(m、rad、`1/m`、m/s、m/s²、m/s³、s)或明确其为 ID、索引、布尔值、枚举、快照或集合。
- 每个公开构造函数和公开方法必须有 `<summary>`;有参数时逐项使用 `<param>`;非 `void` 方法使用 `<returns>``Try...` 方法明确 `true` / `false` 和每个 `out` 参数。
- 关键内部跨目录算法入口也使用相同规范;不为私有单行数学工具、测试、比较、报告或可视化代码增加注释。
- 路径、状态和结果的注释必须说明不可变性、集合顺序、失败时的空结果或 `false` 语义;不得宣称代码当前未实现的动态障碍物、UI、硬件、横移或原地旋转能力。
- 每个任务只能显式暂存该任务列出的文件;禁止 `git add .``git add -A`、清理命令或破坏性 Git 命令。
---
### Task 1: PathSmoothing 契约与公开门面注释
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/LocalG2QuinticOptions.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/PathQualityMetrics.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/PathSmoothingConfiguration.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/PathSmoothingDiagnostics.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/PathSmoothingRegionFailureReason.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/PathSmoothingRegionReport.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/PathSmoothingRegionStatus.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/PathSmoothingRequest.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/PathSmoothingResult.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/PathSmoothingStatus.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/SmoothedPathPoint.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/SmoothedPathPointSource.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/SmoothedPathSegment.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/SmoothingMethod.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Facade/PathSmoothingService.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Facade/PathSmoothingComparisonService.cs`
**Interfaces:**
- Consumes: `PlanningResult``PlanningGridMap``VehicleParameters`、不可变 `PathSmoothingRequest``PathSmoothingConfiguration`
- Produces: 只有成功状态才可消费的 `PathSmoothingResult`、严格递增弧长的 `SmoothedPathPoint` 序列,以及包含区域失败和性能信息的 `PathSmoothingDiagnostics`
- [ ] **Step 1: 记录缺失参数/返回值注释的 RED 基线**
```powershell
$path = 'ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Facade/PathSmoothingService.cs'
$text = Get-Content $path -Raw -Encoding UTF8
if ($text -notmatch 'public PathSmoothingResult Smooth\(') { throw 'Expected public Smooth entry point.' }
if ($text -match '<param name="request">') { throw 'RED baseline unexpectedly already documents request.' }
throw 'RED: PathSmoothingService.Smooth lacks parameter documentation.'
```
Expected: command exits nonzero with the RED message.
- [ ] **Step 2: 为契约类型、构造函数、属性和门面补齐 XML 文档**
使用 `apply_patch` 在每个公开类型前写职责和边界;在构造函数前写参数类型意义、单位、可空性、集合顺序和防御性复制语义;在关键属性前写数据含义和单位。为 `PathSmoothingService.Smooth`、比较服务入口和结果工厂写完整 `<param>` / `<returns>`
关键语义必须在注释中准确出现:`PathSmoothingRequest` 持有输入副本;`PathSmoothingResult` 仅在成功状态发布路径;`SmoothedPathPoint` 的位置为 m、航向 rad、曲率 `1/m`、弧长 m;失败结果不包含部分可消费路径。
- [ ] **Step 3: 运行 GREEN XML 契约检查**
```powershell
$paths = @(
'ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/PathSmoothingRequest.cs',
'ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/PathSmoothingResult.cs',
'ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/SmoothedPathPoint.cs',
'ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Facade/PathSmoothingService.cs'
)
$text = ($paths | ForEach-Object { Get-Content $_ -Raw -Encoding UTF8 }) -join "`n"
foreach ($marker in @('<summary>', '<param name="request">', '<returns>', 'm', 'rad', '1/m', '不可变')) {
if (-not $text.Contains($marker)) { throw "Missing XML documentation marker: $marker" }
}
if ($text -notmatch '失败.*部分|部分.*路径') { throw 'Missing partial-result failure semantics.' }
Write-Output 'PASS PathSmoothing contract XML documentation'
```
Expected: exit 0 and the PASS line.
- [ ] **Step 4: 检查纯注释差异并提交 Task 1**
```powershell
git diff --check
git diff -- ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Facade
git add -- ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/LocalG2QuinticOptions.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/PathQualityMetrics.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/PathSmoothingConfiguration.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/PathSmoothingDiagnostics.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/PathSmoothingRegionFailureReason.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/PathSmoothingRegionReport.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/PathSmoothingRegionStatus.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/PathSmoothingRequest.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/PathSmoothingResult.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/PathSmoothingStatus.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/SmoothedPathPoint.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/SmoothedPathPointSource.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/SmoothedPathSegment.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts/SmoothingMethod.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Facade/PathSmoothingService.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Facade/PathSmoothingComparisonService.cs
git diff --cached --check
git commit -m "docs: document path smoothing contracts"
git diff-tree --no-commit-id --name-only -r HEAD
```
Expected: cached diff and commit contain only the listed files, with no non-comment code changes.
### Task 2: PathSmoothing 处理、校验与 LocalG2 阶段注释
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Processing/ArcLengthResampler.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Processing/PathGeometryAnalysis.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Processing/PathGeometryAnalyzer.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Processing/PathReferenceInterpolator.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Processing/PathSmoothingPreprocessor.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Processing/PreparedDirectionSegment.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Processing/PreparedPath.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Processing/RawPathBaselineBuilder.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Processing/SmoothingPoint2D.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Validation/CurvatureLimitPolicy.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Validation/SmoothedPathValidator.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/CurvatureTransition.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/CurvatureTransitionDetector.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/LocalG2CandidateBuilder.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/LocalG2CandidateEvaluator.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/LocalG2CandidateGeometry.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/LocalG2OptionsSnapshot.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/LocalG2PathSplicer.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/LocalG2PreSmoothingPipeline.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/LocalG2RegionWorkOrder.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/LocalG2SmoothingRegion.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/LocalG2WindowPlanner.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/QuinticHermiteCurve2D.cs`
**Interfaces:**
- Consumes: 已准备的方向段、车辆约束、地图快照、局部 G2 配置和候选几何。
- Produces: 连续且严格递增弧长的平滑段、候选评估/拒绝原因、验证结果和不可变区域报告。
- [ ] **Step 1: 记录关键 `Try...` 入口缺失返回语义的 RED 基线**
```powershell
$path = 'ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Processing/PathSmoothingPreprocessor.cs'
$text = Get-Content $path -Raw -Encoding UTF8
if ($text -notmatch 'TryPrepare\(') { throw 'Expected TryPrepare entry point.' }
if ($text -match '<returns>.*true.*false') { throw 'RED baseline unexpectedly documents TryPrepare outcomes.' }
throw 'RED: TryPrepare lacks true/false and out-result documentation.'
```
Expected: command exits nonzero with the RED message.
- [ ] **Step 2: 补齐处理/验证/LocalG2 的阶段边界注释**
使用 `apply_patch` 为公开类型与关键内部入口增加 `<summary>``<param>``<returns>`。所有 `Try...` 方法必须说明成功/失败、`out` 值和原因字符串;几何方法必须说明世界坐标 m、航向 rad、曲率 `1/m`、弧长 m 和严格递增约束;校验器必须说明碰撞、曲率、换向锚点和无部分发布规则。
LocalG2 注释必须说明候选构建、预平滑、窗口选择、拼接和评估的输入/输出关系,不得将内部启发式误描述为动态障碍或控制功能。
- [ ] **Step 3: 运行 GREEN 内部算法注释检查**
```powershell
$paths = @(
'ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Processing/PathSmoothingPreprocessor.cs',
'ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Validation/SmoothedPathValidator.cs',
'ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/LocalG2WindowPlanner.cs',
'ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/LocalG2CandidateEvaluator.cs'
)
$text = ($paths | ForEach-Object { Get-Content $_ -Raw -Encoding UTF8 }) -join "`n"
foreach ($marker in @('<summary>', '<param', '<returns>', 'true', 'false', 'm', 'rad', '1/m')) {
if (-not $text.Contains($marker)) { throw "Missing algorithm XML marker: $marker" }
}
if ($text -notmatch '严格递增|不发布部分') { throw 'Missing geometric or publication invariant.' }
Write-Output 'PASS PathSmoothing algorithm XML documentation'
```
Expected: exit 0 and the PASS line.
- [ ] **Step 4: 运行既有 PathSmoothing 验证并提交 Task 2**
```powershell
& .\ClumsyPilot\tests\verify_path_smoothing_geometry.ps1
& .\ClumsyPilot\tests\verify_path_smoothing_validation.ps1
& .\ClumsyPilot\tests\verify_path_smoothing_local_g2_integration.ps1
git diff --check
git add -- ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Processing/ArcLengthResampler.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Processing/PathGeometryAnalysis.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Processing/PathGeometryAnalyzer.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Processing/PathReferenceInterpolator.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Processing/PathSmoothingPreprocessor.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Processing/PreparedDirectionSegment.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Processing/PreparedPath.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Processing/RawPathBaselineBuilder.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Processing/SmoothingPoint2D.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Validation/CurvatureLimitPolicy.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Validation/SmoothedPathValidator.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/CurvatureTransition.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/CurvatureTransitionDetector.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/LocalG2CandidateBuilder.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/LocalG2CandidateEvaluator.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/LocalG2CandidateGeometry.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/LocalG2OptionsSnapshot.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/LocalG2PathSplicer.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/LocalG2PreSmoothingPipeline.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/LocalG2RegionWorkOrder.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/LocalG2SmoothingRegion.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/LocalG2WindowPlanner.cs ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2/QuinticHermiteCurve2D.cs
git diff --cached --check
git commit -m "docs: document path smoothing pipeline"
git diff-tree --no-commit-id --name-only -r HEAD
```
Expected: three existing validation scripts exit 0; cached diff and commit contain only listed production files and comment-only changes.
### Task 3: TrajectoryExecution 全部生产边界注释
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/EmPlanningCoordinator.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/GearSwitchState.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/GearSwitchStateMachine.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/IEmPlanningCycleSink.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/IVehicleStateProvider.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/PlanningCycleIdentity.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/PlanningCycleInput.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/PlanningCycleResult.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/TrajectoryControlAdapter.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/TrajectoryControlCommand.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/TrajectoryExecutionState.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/TrajectoryExecutor.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/TrajectoryHandoffSelector.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/TrajectorySampler.cs`
**Interfaces:**
- Consumes: `IEmPlanningService`、调用方时钟与 `VehicleMotionState` 快照、已验证的不可变 `EmTrajectory`、方向确认状态和 `EmPlannerConfiguration`
- Produces: latest-wins `PlanningCycleResult`、安全交接选择、不可变 `TrajectoryExecutionState` 和控制器中立的 `TrajectoryControlCommand`
- [ ] **Step 1: 记录协调器方法缺失完整契约的 RED 基线**
```powershell
$path = 'ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/EmPlanningCoordinator.cs'
$text = Get-Content $path -Raw -Encoding UTF8
if ($text -notmatch 'PlanLatestAsync\(') { throw 'Expected PlanLatestAsync entry point.' }
if ($text -match '<param name="input">') { throw 'RED baseline unexpectedly already documents rolling input.' }
throw 'RED: PlanLatestAsync lacks caller-clocked input and cancellation documentation.'
```
Expected: command exits nonzero with the RED message.
- [ ] **Step 2: 补齐执行、交接与控制契约注释**
使用 `apply_patch` 为目录内所有公开类型、枚举、构造函数、属性和公开方法补齐 XML 文档;为 `CompleteCycle``SelectPoint`、交接/采样和换向状态机等跨职责内部入口补齐语义。
`EmPlanningCoordinator.PlanLatestAsync` 必须解释 caller-clocked `now`、取消旧周期、版本/身份检查和只发布完整成功轨迹。交接注释必须列出同段同方向、年龄、追踪、边界和末点拒绝。`UpdateCommand``TrajectoryControlCommand` 必须写明 m/s、rad/s、零速/制动、一次换向请求、无横向速度和无原地旋转。`TrySample` 必须明确区间外或跨边界时返回 `false`
- [ ] **Step 3: 运行 GREEN 执行层 XML 契约检查**
```powershell
$paths = @(
'ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/EmPlanningCoordinator.cs',
'ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/TrajectoryHandoffSelector.cs',
'ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/TrajectoryExecutor.cs',
'ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/GearSwitchStateMachine.cs',
'ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/TrajectoryControlCommand.cs'
)
$text = ($paths | ForEach-Object { Get-Content $_ -Raw -Encoding UTF8 }) -join "`n"
foreach ($marker in @('<summary>', '<param name="input">', '<returns>', 'm/s', 'rad/s', 'latest-wins', '零速', 'false')) {
if (-not $text.Contains($marker)) { throw "Missing execution XML marker: $marker" }
}
if ($text -match 'MultiVehicleScriptVx|MultiVehicleScriptVy|MultiVehicleScriptVth') {
throw 'Documentation must not leak UI/hardware command fields.'
}
Write-Output 'PASS TrajectoryExecution XML documentation'
```
Expected: exit 0 and the PASS line.
- [ ] **Step 4: 运行执行层回归并提交 Task 3**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- coordinator
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- executor
git diff --check
git add -- ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/EmPlanningCoordinator.cs ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/GearSwitchState.cs ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/GearSwitchStateMachine.cs ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/IEmPlanningCycleSink.cs ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/IVehicleStateProvider.cs ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/PlanningCycleIdentity.cs ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/PlanningCycleInput.cs ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/PlanningCycleResult.cs ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/TrajectoryControlAdapter.cs ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/TrajectoryControlCommand.cs ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/TrajectoryExecutionState.cs ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/TrajectoryExecutor.cs ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/TrajectoryHandoffSelector.cs ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/TrajectorySampler.cs
git diff --cached --check
git commit -m "docs: document trajectory execution contracts"
git diff-tree --no-commit-id --name-only -r HEAD
```
Expected: both execution gates exit 0; cached diff and commit contain only listed files and comment-only changes.
### Task 4: 最终注释审查与完整回归
**Files:**
- Modify: Task 1 至 Task 3 的文件(仅在发现缺失、错误单位或文档与实际行为矛盾时修正)。
**Interfaces:**
- Consumes: CoarsePath 风格的 XML 文档约定、PathSmoothing 的纯规划边界和 TrajectoryExecution 的 caller-clocked 执行边界。
- Produces: 与实际代码一致、可由 IDE XML 文档显示的中文 API 说明。
- [ ] **Step 1: 检查差异仅包含注释**
```powershell
$diff = git diff 297186a..HEAD -- ClumsyPilot/ParkrobTrajplanner/PathSmoothing ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution
$codeLines = $diff | Where-Object { $_ -match '^[+-](?![+-/\s])' }
if ($codeLines.Count -gt 0) { $codeLines; throw 'Non-comment C# changes detected.' }
Write-Output 'PASS comment-only diff review'
```
Expected: exit 0 and the PASS line. Ignore `+++` / `---` headers and `///` documentation lines.
- [ ] **Step 2: 运行最终构建与门禁**
```powershell
dotnet build ClumsyPilot/ClumsyPilot.csproj --no-restore
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-all
git diff --check
git status --short -- ClumsyPilot/ParkrobTrajplanner/PathSmoothing ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution
```
Expected: build 和 `em-all` 退出 0;无 whitespace 错误;两个目标目录没有未提交状态。
- [ ] **Step 3: 仅在最终审查发现需修正时,显式暂存和提交**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Contracts ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Facade ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Processing ClumsyPilot/ParkrobTrajplanner/PathSmoothing/Validation ClumsyPilot/ParkrobTrajplanner/PathSmoothing/LocalG2 ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution
git diff --cached --check
git commit -m "docs: verify planner XML comments"
git diff-tree --no-commit-id --name-only -r HEAD
```
Expected: 仅在文字修正发生时创建提交;否则不执行此步骤。
@@ -1,213 +0,0 @@
# EM 纵向滚动规划四阶段执行路线图
日期:2026-08-05
## 目标
把原有的 10 个实现任务拆成 4 个可以在不同 Codex 窗口中依次完成的阶段。每个阶段必须独立验证、记录实际结果,并根据真实代码状态生成下一阶段的精细提示词。
本路线图只负责阶段划分和跨窗口交接。技术设计与逐步实现细节仍以下列文件为准:
- 设计规格:`docs/superpowers/specs/2026-08-05-em-longitudinal-rolling-planning-design.md`
- 十任务实施计划:`docs/superpowers/plans/2026-08-05-em-longitudinal-rolling-planning.md`
如三份文档发生冲突,设计规格决定行为语义,十任务实施计划决定具体实现步骤,本路线图决定阶段边界和停止位置。
## 不可破坏的全局约束
- `DistanceHorizonMeters` 是 LS 空间前视范围,不是车辆在一个 ST 周期内必须走完的距离。
- `TimeHorizonSeconds` 是单次 ST 优化覆盖的未来时间,不是观察循环周期。
- 只有真实 `Goal``GearSwitchApproach` 边界允许触发精确停车。
- `RollingContinuation``ApproachStopBoundary` 不得被强制设置末速度为零。
- `ExactStopAtBoundary` 必须在 QP 时间范围结束前达到 `S=S_end、U=0、A=0`,并在 QP 内至少保留一个完整 `OutputTimeStepSeconds` 的静止稳定区间。
- 不通过放宽速度、加速度、jerk、QP 残差或轨迹发布验证阈值掩盖逻辑错误。
- `OBSERVE_ONLY` 必须保持不变。本路线图不授权增加底盘命令、轨迹跟踪器或超时看门狗。
- 工作树内已有且与本任务无关的修改全部视为用户资产。禁止清理、回退或提交这些修改。
- 每次只实施当前阶段;完成交接文件后立即停止,不得提前执行下一阶段。
## 阶段总览
| 阶段 | 原计划任务 | 核心结果 | 阶段验收 |
|---|---:|---|---|
| 1. 纵向基础与窗口语义 | 1–2 | jerk 受限停车数学、三种纵向模式、空间窗口与真实停车边界分离、输入与配置验证 | `foundation``longitudinal-model` |
| 2. ST 优化核心 | 3–5 | 模式化速度包络、条件化 QP 终端约束、独立解验证、按模式生成初始解 | `longitudinal-model``longitudinal-integration` |
| 3. 发布与跨周期连续性 | 6–7 | 条件化轨迹采样、停车锚点和稳定尾段、发布验证、上一周期轨迹软参考 | `trajectory``em-planning-service``coordinator``executor` |
| 4. 端到端集成与诊断 | 8–10 | 模式流转、多周期 Goal/GearSwitch 回归、MovementTest 诊断、全量回归和目标机 OSQP 验证 | `em-core-all`、观察测试、构建、目标机探针 |
## 阶段一:纵向基础与窗口语义
对应十任务计划中的 Task 1 和 Task 2。
### 实施范围
- 新建完整 jerk 受限停车数学,统一停车距离、停车时间、末速度和末加速度语义。
- 新建 `EmLongitudinalMode`
- `RollingContinuation`
- `ApproachStopBoundary`
- `ExactStopAtBoundary`
- 新建 `LongitudinalTerminalSchedule`,计算精确停车稳定尾段的起始 knot。
- 让 `PlanningHorizonSelector` 分离:
- LS 空间窗口末端;
- 真实 Goal/GearSwitch 停车边界;
- 当前纵向模式。
- 扩展 `LongitudinalPlanningInput` 和请求验证,拒绝不一致的模式、边界和稳定时间配置。
### 允许修改的主要文件
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Contracts/EmLongitudinalMode.cs`
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/JerkLimitedStoppingMath.cs`
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalTerminalSchedule.cs`
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/PathSpeedLimitBuilder.cs`
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Segmentation/PlanningHorizonSelector.cs`
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalPlanningInput.cs`
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Validation/EmPlanningRequestValidator.cs`
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Facade/EmPlanningService.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalModelChecks.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/FoundationChecks.cs`
若编译修复必须触及直接调用者,可以进行最小兼容修改,但必须在阶段交接中逐项解释。
### 完成条件
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- foundation
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-model
```
两项均须输出 `PASS`。阶段内按原十任务计划保留 Task 1、Task 2 两个独立提交。
## 阶段二:ST 优化核心
对应十任务计划中的 Task 3、Task 4 和 Task 5。
### 实施范围
- 按纵向模式构造 PathS 速度包络。
- Rolling 模式的局部路径末端不再出现人为零速边界。
- 仅为 `ExactStopAtBoundary` 添加停车位置、速度、加速度以及稳定尾段硬约束。
- 让 QP 行数、上下界和验证器随模式与稳定尾段 knot 数量变化。
- 使用与 QP 相同的恒 jerk 离散动力学做精确停车可达性验证。
- 为 Rolling、ApproachStop 和 ExactStop 构造各自合理的初始解和外层迭代行为。
### 阶段输入
- 阶段一完成后的提交。
- `docs/superpowers/handoffs/2026-08-05-em-longitudinal-rolling-phase-1.md`
- 阶段一生成的阶段二精细提示词。
### 完成条件
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-model
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-integration
```
两项均须输出 `PASS`,并保留 Task 3、Task 4、Task 5 的独立提交边界。
## 阶段三:轨迹发布与跨周期连续性
对应十任务计划中的 Task 6 和 Task 7。
### 实施范围
- 在轨迹元数据中保存纵向模式和真实停车边界身份。
- Rolling 和 ApproachStop 只发布时间范围内的运动轨迹,不追加虚假零速保持点。
- ExactStop 发布 QP 内部静止稳定段,并按配置追加外部零速保持点。
- 轨迹验证器按模式检查终端状态,同时保持普通运动学、碰撞和边界验证。
- 将上一条已发布轨迹按绝对时间重采样,并映射到新路径的 PathS,作为软参考而不是硬约束。
### 阶段输入
- 阶段二完成后的提交和交接文档。
- 阶段二根据实际结果生成的阶段三精细提示词。
### 完成条件
至少运行并通过:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-planning-service
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- coordinator
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- executor
```
保留 Task 6 和 Task 7 的独立提交边界。
## 阶段四:端到端集成与实测诊断
对应十任务计划中的 Task 8、Task 9 和 Task 10。
### 实施范围
- 完成 `RollingContinuation → ApproachStopBoundary → ExactStopAtBoundary` 的服务流转。
- 覆盖真实 Goal、GearSwitchApproach、多周期滚动和停车稳定尾段。
- 完善 MovementTest 终端/UI 诊断,但继续保持 `OBSERVE_ONLY`
- 更新 EMPlanner 文档。
- 完成全量回归、构建和目标运行环境中的 OSQP 探针。
### 阶段输入
- 阶段三完成后的提交和交接文档。
- 阶段三根据实际结果生成的阶段四精细提示词。
### 完成条件
严格执行十任务计划 Task 10 中列出的全部验证命令。至少包含:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-core-all
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- coordinator
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- executor
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
dotnet build ClumsyPilot/ClumsyPilot.csproj
```
目标机 OSQP 验证必须在实际部署/测试机器上运行;若当前窗口不在目标机,只能明确记录为“待目标机验证”,不得声称已通过。
## 跨窗口交接协议
### 每个阶段开始时
1. 阅读设计规格、十任务计划、本路线图、上一阶段交接文件和当前阶段提示词。
2. 运行 `git status --short``git log -8 --oneline`,记录阶段开始基线。
3. 确认上一阶段要求的提交存在;若不存在或测试不通过,先停止并报告,不得假设已完成。
4. 只执行当前阶段对应的原计划任务。
### 每个阶段结束时
1. 重新运行本阶段全部验收命令,不能只引用较早的结果。
2. 检查每个提交只包含当前任务文件,不得使用 `git add .``git add -A`
3. 创建交接文件:
`docs/superpowers/handoffs/2026-08-05-em-longitudinal-rolling-phase-N.md`
4. 交接文件必须记录:
- 阶段开始和结束提交;
- 每个任务的提交号;
- 实际修改和新增的文件;
- 新增或改变的公开/内部接口;
- 每条验证命令、退出码和关键输出;
- 未解决问题、目标机限制和与计划的偏差;
- 下一阶段必须继承的具体事实。
5. 根据交接文件和当前仓库状态创建下一阶段提示词:
`docs/superpowers/prompts/2026-08-05-em-longitudinal-rolling-phase-(N+1).md`
6. 下一阶段提示词不得只写“继续原计划”,必须包含实际提交号、实际接口、准确任务边界、允许修改文件、测试命令、提交边界和完成后的交接要求。
7. 提交交接文件与下一阶段提示词,然后停止,不执行下一阶段代码。
阶段四没有下一阶段提示词,改为生成最终验收报告:
`docs/superpowers/handoffs/2026-08-05-em-longitudinal-rolling-final.md`
## 阶段状态
- [ ] 阶段一:纵向基础与窗口语义
- [ ] 阶段二:ST 优化核心
- [ ] 阶段三:轨迹发布与跨周期连续性
- [ ] 阶段四:端到端集成与实测诊断
当前入口提示词:`docs/superpowers/prompts/2026-08-05-em-longitudinal-rolling-phase-1.md`
File diff suppressed because it is too large Load Diff
@@ -1,334 +0,0 @@
# EM Observation Diagnostics 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:** Make every EM planning-cycle failure visible in the host terminal and the World/L-S/S-T observation canvases without changing observation-only safety behavior.
**Architecture:** Add a pure diagnostic formatter that keeps `EmPlanningStatus` and the original `FailureReason` from `PlanningCycleResult`. Extend the observation-loop tick with explicit start/completion events so the runner writes one pending line and one completed-cycle line per cycle, while the painters receive the current diagnostic every tick.
**Tech Stack:** C# / .NET Standard 2.0 plugin, `Hedingben.ToastText`, `UI.GetPainter`, EM planner contracts, .NET verification host.
## Global Constraints
- The MovementTest remains observe-only: do not add any chassis, brake, wheel, or actuator call.
- Terminal output uses `Console.WriteLine` and starts with `[TrajectoryObserver]`.
- Every completed cycle reports raw `EmPlanningStatus`, `published`, version, elapsed time, and the original nonempty `FailureReason`.
- A 50 ms observer tick must not emit a duplicate terminal record.
- World, L-S, and S-T painters must show the diagnostic even when `PublishedTrajectory` is null.
---
### Task 1: Add a pure planning diagnostic formatter
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationDiagnostics.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs`
**Interfaces:**
- Consumes: `PlanningCycleResult`, `EmTrajectory`, `TimeSpan`, and the in-flight flag.
- Produces: `TrajectoryObservationDiagnostic.Text`, a compact multi-line operator string.
- [ ] **Step 1: Write the failing test**
Add `VerifiesPlanningDiagnosticsKeepRawFailureReason();` to `Run()`, then add:
```csharp
private static void VerifiesPlanningDiagnosticsKeepRawFailureReason()
{
var failed = new PlanningCycleResult(
4L,
new PlanningCycleIdentity(3L, "diagnostic-reference", 7L, string.Empty, 0),
new EmPlanningResult(EmPlanningStatus.CorridorInfeasible, null,
"map=3;reference=diagnostic-reference;state=7;previous=;segment=0;reason=no connected corridor"),
false,
"map=3;reference=diagnostic-reference;state=7;previous=;segment=0;reason=no connected corridor");
TrajectoryObservationDiagnostic diagnostic = TrajectoryObservationDiagnostics.Create(
failed, TimeSpan.FromMilliseconds(18d), false, null);
Verification.True(diagnostic.Text.Contains("cycle=4"), "diagnostic has cycle version");
Verification.True(diagnostic.Text.Contains("status=CorridorInfeasible"), "diagnostic preserves raw status");
Verification.True(diagnostic.Text.Contains("published=False"), "diagnostic preserves publish state");
Verification.True(diagnostic.Text.Contains("elapsed=18ms"), "diagnostic preserves elapsed time");
Verification.True(diagnostic.Text.Contains("reason=map=3;reference=diagnostic-reference"),
"diagnostic preserves planner failure reason");
TrajectoryObservationDiagnostic pending = TrajectoryObservationDiagnostics.Create(
null, TimeSpan.Zero, true, null);
Verification.Equal("planning status=pending", pending.Text, "diagnostic reports pending before completion");
}
```
- [ ] **Step 2: Run the test to verify RED**
Run:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
```
Expected: build failure because `TrajectoryObservationDiagnostic` and `TrajectoryObservationDiagnostics` do not exist.
- [ ] **Step 3: Write the minimal formatter**
Create `TrajectoryObservationDiagnostics.cs`:
```csharp
using System;
using System.Globalization;
using MultiWheelC.TrajectoryPlanning.EMPlanner;
namespace MultiWheelC.TrajectoryPlanning.TrajectoryObservation;
public sealed class TrajectoryObservationDiagnostic
{
public TrajectoryObservationDiagnostic(string text)
{
Text = text ?? string.Empty;
}
public string Text { get; }
}
public static class TrajectoryObservationDiagnostics
{
public static TrajectoryObservationDiagnostic Create(PlanningCycleResult latestCycle,
TimeSpan elapsed, bool planningInFlight, EmTrajectory publishedTrajectory)
{
if (latestCycle == null)
return new TrajectoryObservationDiagnostic(planningInFlight
? "planning status=pending"
: "planning status=not-started");
string text = "planning cycle=" + latestCycle.Version.ToString(CultureInfo.InvariantCulture) +
" status=" + latestCycle.Result.Status +
" published=" + latestCycle.Published +
" elapsed=" + Math.Max(0d, elapsed.TotalMilliseconds).ToString("F0", CultureInfo.InvariantCulture) + "ms";
if (planningInFlight)
text += "\nreplan=pending";
if (publishedTrajectory != null)
text += "\ntrajectory=" + publishedTrajectory.Metadata.TrajectoryId;
if (!string.IsNullOrWhiteSpace(latestCycle.Result.FailureReason))
text += "\nreason=" + latestCycle.Result.FailureReason;
return new TrajectoryObservationDiagnostic(text);
}
}
```
- [ ] **Step 4: Run the test to verify GREEN**
Run the Step 2 command.
Expected: `PASS trajectory-observation`.
- [ ] **Step 5: Commit**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationDiagnostics.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs
git commit -m "feat: format EM observation diagnostics"
```
### Task 2: Report once at the start and completion of every planning cycle
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPipeline.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs`
**Interfaces:**
- Produces: `TrajectoryObservationLoopTick.PlanningStarted` and `.PlanningCompleted`.
- Consumes: those flags in the MovementTest to issue one terminal/UI status record per lifecycle event.
- [ ] **Step 1: Write the failing test**
In `VerifiesObserverTicksWhilePlanningIsDelayed`, after the first tick, add:
```csharp
Verification.True(firstTick.PlanningStarted, "observer first tick reports a planning-cycle start");
Verification.True(!firstTick.PlanningCompleted, "observer first tick has no completed cycle");
```
After `finalTick` is created, add:
```csharp
Verification.True(finalTick.PlanningCompleted, "observer completion tick reports cycle completion");
```
In `VerifiesObservationSourceUsesRequiredOperatorText`, add:
```csharp
Verification.True(source.Contains("Console.WriteLine(\"[TrajectoryObserver] \" + text);"),
"observer status is mirrored to the host terminal");
```
- [ ] **Step 2: Run the test to verify RED**
Run the Task 1 test command.
Expected: assertions fail because lifecycle flags and terminal output do not exist.
- [ ] **Step 3: Implement lifecycle flags and output**
Change `TrajectoryObservationLoopTick` to accept and expose:
```csharp
bool planningStarted, bool planningCompleted
public bool PlanningStarted { get; }
public bool PlanningCompleted { get; }
```
In `TrajectoryObservationLoop.Tick`, use:
```csharp
bool planningCompleted = ConsumeCompletedPlanning(now);
bool planningStarted = false;
if (planningTask == null && controller.ShouldStartCycle(now))
{
planningStarted = true;
planningStartedAtUtc = now;
planningTask = controller.StartCycle(now, state, cancellationToken);
planningCompleted |= ConsumeCompletedPlanning(now);
}
```
Change `ConsumeCompletedPlanning` to return `false` when no completed Task is available and `true` after it assigns `latestCycle`, updates elapsed time, and clears `planningTask`. Pass both flags to the tick constructor.
In `RunSessionAsync`, after the tick is created, make one diagnostic and only log event records:
```csharp
TrajectoryObservationDiagnostic diagnostic = TrajectoryObservationDiagnostics.Create(
tick.LatestCycle, tick.LatestPlanningElapsed, tick.PlanningInFlight,
observation.PublishedTrajectory);
if (tick.PlanningStarted)
LogIfCurrent(sessionId, "planning status=pending");
if (tick.PlanningCompleted)
LogIfCurrent(sessionId, diagnostic.Text);
```
Remove the unconditional `if (tick.ShouldLog)` status call. Keep the existing session-start, stop, bootstrap-failure, and runtime-fault calls.
Change `PrintStatus` to:
```csharp
private static void PrintStatus(string message)
{
string text = ObserveOnlyNotice + "\n" + message;
Hedingben.ToastText(text, StatusChannel);
Console.WriteLine("[TrajectoryObserver] " + text);
}
```
- [ ] **Step 4: Run the test to verify GREEN**
Run the Task 1 test command.
Expected: `PASS trajectory-observation`; the delayed-planner test still proves the observer does not wait for planning.
- [ ] **Step 5: Commit**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPipeline.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs
git commit -m "feat: report EM observation planning cycles"
```
### Task 3: Persist planning diagnostics in all three painter layers
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPresentation.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs`
**Interfaces:**
- Consumes: `TrajectoryObservationDiagnostic.Text`.
- Produces: World/L-S/S-T empty states that show the precise planning status and reason.
- [ ] **Step 1: Write the failing test**
Add `VerifiesEmptyChartsReceivePersistentPlanningDiagnostic();` to `Run()` and add:
```csharp
private static void VerifiesEmptyChartsReceivePersistentPlanningDiagnostic()
{
string presentationPath = Path.Combine(Directory.GetCurrentDirectory(), "ClumsyPilot",
"ParkrobTrajplanner", "tarjplanner_movementtest", "TrajectoryObservationPresentation.cs");
string source = new UTF8Encoding(false, true).GetString(File.ReadAllBytes(presentationPath));
Verification.True(source.Contains("DrawLs(TrajectoryObservationCharts charts, string diagnosticText)"),
"LS painter accepts planning diagnostic input");
Verification.True(source.Contains("DrawSt(TrajectoryObservationCharts charts, string diagnosticText)"),
"ST painter accepts planning diagnostic input");
Verification.True(source.Contains("No published trajectory available for L-S chart.\n"),
"LS empty state includes diagnostic after chart label");
Verification.True(source.Contains("No published trajectory available for T-S/T-V charts.\n"),
"ST empty state includes diagnostic after chart label");
}
```
- [ ] **Step 2: Run the test to verify RED**
Run the Task 1 test command.
Expected: source checks fail because painter methods have no diagnostic parameter.
- [ ] **Step 3: Add painter parameters and wire the diagnostic**
Change signatures to:
```csharp
public void DrawWorld(TrajectoryObservationBootstrapResult bootstrap,
TrajectoryObservationObservation observation, TrajectoryObservationRuntimeState runtimeState,
string diagnosticText)
public void DrawLs(TrajectoryObservationCharts charts, string diagnosticText)
public void DrawSt(TrajectoryObservationCharts charts, string diagnosticText)
```
Add this helper in `TrajectoryObservationPresentation`:
```csharp
private static string EmptyChartMessage(string label, string diagnosticText)
{
return string.IsNullOrWhiteSpace(diagnosticText)
? label
: label + "\n" + diagnosticText;
}
```
For World, draw `EmptyChartMessage("No published trajectory available.", diagnosticText)` at `bootstrap.Map.Bounds.XMin + 100f, bootstrap.Map.Bounds.YMin + 300f` before returning from the empty trajectory path. For L-S and S-T, draw `EmptyChartMessage` with their existing label at `0f, 0f`.
Change `DrawIfCurrent` to accept `TrajectoryObservationDiagnostic diagnostic` and call:
```csharp
Presentation.DrawWorld(bootstrap, observation, runtimeState, diagnostic?.Text ?? string.Empty);
Presentation.DrawLs(charts, diagnostic?.Text ?? string.Empty);
Presentation.DrawSt(charts, diagnostic?.Text ?? string.Empty);
```
Pass the diagnostic created in Task 2 from `RunSessionAsync`. For the bootstrap-failure path, pass:
```csharp
new TrajectoryObservationDiagnostic("bootstrap failed: " + bootstrap.FailureReason)
```
- [ ] **Step 4: Verify focused test, build, and diff**
Run:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
dotnet build ClumsyPilot/ClumsyPilot.csproj --no-restore
git diff --check
```
Expected: `PASS trajectory-observation`, zero build errors, and no diff whitespace errors.
- [ ] **Step 5: Commit**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPresentation.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs
git commit -m "feat: show EM observation failure diagnostics"
```
@@ -1,296 +0,0 @@
# EM Observation Planning Diagnostics Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use executing-plans to implement this plan task-by-task. Steps use checkbox syntax for tracking.
**Goal:** Print one effective EM planning configuration snapshot at observation-session startup and make trajectory validation failures and successes numerically diagnosable without changing planning behavior.
**Architecture:** EmTrajectoryValidator enriches its existing jerk rejection reason with the values used in the decision. TrajectoryObservationDiagnostics owns pure invariant-culture formatters for configuration and successful trajectory metrics. The MovementTest runner prints the configuration formatter once after it creates the session-local controller.
**Tech Stack:** C#/.NET, existing EMPlannerVerificationHost, OSQP-backed EM planner.
## Global Constraints
- Keep the MovementTest OBSERVE_ONLY; do not add actuator calls.
- Do not change solver configuration, motion limits, QP formulation, horizon selection, or acceptance rules.
- Print configuration once per session; do not put it in the recurring UI diagnostic or every planning cycle.
- Format values with CultureInfo.InvariantCulture.
- Failed publications keep returning no EmTrajectory; their numeric evidence belongs in the failure reason.
## Preflight: Preserve manual MovementTest parameter tuning
`TrajectoryObservationMovementTest` exposes fields precisely so an operator can tune a test session. The verification host must not require those source literals to equal the defaults of `TrajectoryObservationSettings`; it must continue to verify that the fields exist, are copied into settings, and that `TrajectoryObservationSettings` itself retains its independently tested defaults.
- [ ] Run `dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation` and observe the current source-literal failure.
- [ ] In `TrajectoryObservationChecks.VerifiesMovementTestVehicleInputsAndSettingsSnapshot`, replace only the four source-text checks that require `0.50d`, `12000`, `6d`, and `0.10d` with checks for `public` fields named `SolverTimeoutSeconds`, `MaximumOsqpIterations`, `TimeHorizonSeconds`, and `OutputTimeStepSeconds`. Preserve the separate `new TrajectoryObservationSettings()` default assertions and all field-to-settings propagation checks.
- [ ] Re-run the same command and require exit code `0` before adding the Task 1 regression test.
---
### Task 1: Detail a jerk-limit rejection with computed evidence
**Files:**
- Modify: ClumsyPilot/ParkrobTrajplanner/EMPlanner/Validation/EmTrajectoryValidator.cs:132-199
- Modify: ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryChecks.cs:58-110
**Interfaces:**
- Consumes: the existing EmTrajectoryValidator.Validate(...) method.
- Produces: an unchanged JerkLimitExceeded result whose Message contains time, dt, previousAcceleration, acceleration, jerk, limit, excess, storedPreviousJerk, and storedCurrentJerk.
- [ ] **Step 1: Write the failing test**
In TrajectoryChecks.VerifiesWorldSpacePublicationMutationsAreRejected, retain the current jerkMutated trajectory and add:
EmTrajectoryValidationResult jerkResult = new EmTrajectoryValidator().Validate(
jerkMutated, context.EmptyMap, context.Vehicle, context.Configuration, 2, 0.0055d, EmBoundaryType.Goal);
Verification.Equal(EmTrajectoryValidationFailure.JerkLimitExceeded, jerkResult.Failure,
"jerk diagnostic failure code");
Verification.Equal(2, jerkResult.PointIndex, "jerk diagnostic point index");
foreach (string field in new[]
{
"time=", "dt=", "previousAcceleration=", "acceleration=", "jerk=", "limit=", "excess=",
"storedPreviousJerk=", "storedCurrentJerk=",
})
Verification.True(jerkResult.Message.Contains(field), "jerk diagnostic includes " + field);
- [ ] **Step 2: Run the test to confirm RED**
Run:
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory
Expected: the new assertion fails because the current message contains only Trajectory finite-difference jerk exceeds its limit.
- [ ] **Step 3: Implement the message enrichment**
Add using System.Globalization; to EmTrajectoryValidator.cs. Calculate finiteDifferenceJerk before the condition and preserve the existing inequality:
double finiteDifferenceJerk = (acceleration - previousAcceleration) / dt;
if (hasPreviousAcceleration && Math.Abs(finiteDifferenceJerk) > limits.MaximumJerk + limits.KinematicTolerance)
{
double storedPreviousJerk = index >= 2 ? trajectory.Points[index - 2].LongitudinalJerk : 0d;
double storedCurrentJerk = previous.LongitudinalJerk;
return Reject(EmTrajectoryValidationFailure.JerkLimitExceeded, index,
"Trajectory finite-difference jerk exceeds its limit" +
" (time=" + Format(point.TimeFromStart) + "s, dt=" + Format(dt) + "s" +
", previousAcceleration=" + Format(previousAcceleration) + "m/s2" +
", acceleration=" + Format(acceleration) + "m/s2" +
", jerk=" + Format(finiteDifferenceJerk) + "m/s3" +
", limit=" + Format(limits.MaximumJerk) + "m/s3" +
", excess=" + Format(Math.Abs(finiteDifferenceJerk) - limits.MaximumJerk) + "m/s3" +
", storedPreviousJerk=" + Format(storedPreviousJerk) + "m/s3" +
", storedCurrentJerk=" + Format(storedCurrentJerk) + "m/s3).");
}
Add this private method to the same class:
private static string Format(double value)
{
return value.ToString("G17", CultureInfo.InvariantCulture);
}
storedPreviousJerk and storedCurrentJerk are evidence only and do not influence the validation decision.
- [ ] **Step 4: Confirm GREEN and commit**
Run:
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory
Expected: host exit code 0.
Run:
git add -- ClumsyPilot/ParkrobTrajplanner/EMPlanner/Validation/EmTrajectoryValidator.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryChecks.cs
git commit -m "feat: detail trajectory jerk validation failures"
### Task 2: Format and print the effective session configuration once
**Files:**
- Modify: ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationDiagnostics.cs:17-39
- Modify: ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPipeline.cs:188-241
- Modify: ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs:269-305
- Modify: ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs:18-39,96-191
**Interfaces:**
- Consumes: the effective EmPlannerConfiguration held privately by TrajectoryObservationController.
- Produces: TrajectoryObservationDiagnostics.CreateConfiguration(EmPlannerConfiguration) and TrajectoryObservationController.CreateConfigurationDiagnostic().
- Produces: one terminal planning configuration block before the observation loop begins.
- [ ] **Step 1: Write failing configuration tests**
Add VerifiesPlanningConfigurationDiagnosticUsesEffectiveConfiguration() to TrajectoryObservationChecks.Run(). Bootstrap a controller with:
var settings = new TrajectoryObservationSettings
{
ReplanPeriodSeconds = 0.25d,
SolverTimeoutSeconds = 1.25d,
MaximumOsqpIterations = 54321,
TimeHorizonSeconds = 3.5d,
OutputTimeStepSeconds = 0.10d,
};
var controller = new TrajectoryObservationController(bootstrap, settings,
new FixedTrajectoryPlanningService(CreatePublishedTrajectory(effectiveAt)), "config-diagnostic");
string text = controller.CreateConfigurationDiagnostic().Text;
foreach (string expected in new[]
{
"planning configuration:", "timeHorizon=3.50s", "distanceHorizon=5.00m", "outputTimeStep=0.10s",
"outputFrequency=10.00Hz", "trajectoryKnots=36", "maximumOsqpIterations=54321",
"solverTimeout=1.25s", "replanPeriod=0.25s",
})
Verification.True(text.Contains(expected), "configuration diagnostic includes " + expected);
Source-read MovementTest.TrajectoryObservationTest.cs and assert the runner call is singular:
int firstCall = runnerSource.IndexOf("CreateConfigurationDiagnostic()", StringComparison.Ordinal);
Verification.True(firstCall >= 0, "runner prints configuration diagnostic");
Verification.Equal(-1, runnerSource.IndexOf("CreateConfigurationDiagnostic()", firstCall + 1,
StringComparison.Ordinal), "runner prints configuration diagnostic once");
- [ ] **Step 2: Run the test to confirm RED**
Run:
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
Expected: compilation fails because CreateConfigurationDiagnostic does not exist.
- [ ] **Step 3: Implement pure formatting and the one runner call**
Add to TrajectoryObservationDiagnostics:
public static TrajectoryObservationDiagnostic CreateConfiguration(EmPlannerConfiguration configuration)
{
if (configuration == null || configuration.Scheduling == null || configuration.Solver == null)
throw new ArgumentNullException(nameof(configuration));
double horizon = configuration.Scheduling.TimeHorizonSeconds;
double step = configuration.Scheduling.OutputTimeStepSeconds;
int knots = checked((int)Math.Ceiling(horizon / step) + 1);
return new TrajectoryObservationDiagnostic("planning configuration:\n" +
"timeHorizon=" + Format(horizon, "F2") + "s\n" +
"distanceHorizon=" + Format(configuration.Scheduling.DistanceHorizonMeters, "F2") + "m\n" +
"outputTimeStep=" + Format(step, "F2") + "s\n" +
"outputFrequency=" + Format(1d / step, "F2") + "Hz\n" +
"trajectoryKnots=" + knots.ToString(CultureInfo.InvariantCulture) + "\n" +
"maximumOsqpIterations=" + configuration.Solver.MaximumOsqpIterations.ToString(CultureInfo.InvariantCulture) + "\n" +
"solverTimeout=" + Format(configuration.Scheduling.SolverTimeoutSeconds, "F2") + "s\n" +
"replanPeriod=" + Format(configuration.Scheduling.ReplanPeriodSeconds, "F2") + "s");
}
private static string Format(double value, string format)
{
return value.ToString(format, CultureInfo.InvariantCulture);
}
Add to TrajectoryObservationController:
public TrajectoryObservationDiagnostic CreateConfigurationDiagnostic()
{
return TrajectoryObservationDiagnostics.CreateConfiguration(configuration);
}
Immediately after constructing controller in RunSessionAsync, before new TrajectoryObservationLoop(controller), add exactly:
LogIfCurrent(sessionId, controller.CreateConfigurationDiagnostic().Text);
Do not call this method from the while loop or the UI drawing methods.
- [ ] **Step 4: Confirm GREEN and commit**
Run:
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
Expected: host exit code 0.
Run:
git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationDiagnostics.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPipeline.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs
git commit -m "feat: log EM observation session planning configuration"
### Task 3: Summarize each successfully published trajectory
**Files:**
- Modify: ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationDiagnostics.cs:17-39
- Modify: ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs:538-561
**Interfaces:**
- Consumes: latestCycle.Result.Trajectory only when latestCycle.Published is true.
- Produces: a trajectory summary with ID, point count, duration, PathS length, max speed, max finite-difference acceleration, and max finite-difference jerk.
- [ ] **Step 1: Write the failing summary test**
Add VerifiesPublishedPlanningDiagnosticsIncludeTrajectorySummary() to TrajectoryObservationChecks.Run():
EmTrajectory trajectory = CreatePublishedTrajectory(effectiveAt);
var succeeded = new PlanningCycleResult(9L,
new PlanningCycleIdentity(1L, "summary-reference", 2L, string.Empty, 0),
new EmPlanningResult(EmPlanningStatus.Success, trajectory, string.Empty), true, string.Empty);
string text = TrajectoryObservationDiagnostics.Create(
succeeded, TimeSpan.FromMilliseconds(12d), false, trajectory).Text;
foreach (string expected in new[]
{
"trajectory summary:", "trajectoryId=observer-published", "points=2", "duration=1.000s",
"pathLength=1.000m", "maxSpeed=0.400m/s", "maxAcceleration=0.200m/s2", "maxJerk=0.000m/s3",
})
Verification.True(text.Contains(expected), "published diagnostic includes " + expected);
Also add this assertion to the existing failed-cycle test:
Verification.True(!diagnostic.Text.Contains("trajectory summary:"), "failed diagnostic has no stale trajectory summary");
- [ ] **Step 2: Run the test to confirm RED**
Run:
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
Expected: summary assertions fail because the formatter only writes trajectory=<id>.
- [ ] **Step 3: Implement summary formatting**
Replace the publishedTrajectory != null branch in TrajectoryObservationDiagnostics.Create(...) with a branch requiring latestCycle.Published && latestCycle.Result.Trajectory != null. Add CreateTrajectorySummary(EmTrajectory trajectory), which:
- reads immutable trajectory points;
- calculates maximum absolute signed speed;
- calculates acceleration and jerk by the same direction-aware finite-difference convention as EmTrajectoryValidator;
- ignores a nonpositive or non-finite dt only in logging;
- reports first and last PathS difference as actual pathLength;
- uses Format(value, "F3") and CultureInfo.InvariantCulture;
- returns exactly these two lines:
trajectory summary:
trajectoryId=<id>, points=<count>, duration=<last-time>s, pathLength=<last-pathS-first-pathS>m
maxSpeed=<absolute-signed-speed>m/s, maxAcceleration=<absolute-finite-difference>m/s2, maxJerk=<absolute-finite-difference>m/s3
Do not call this helper for an unsuccessful cycle, even when an older published trajectory is still observed.
- [ ] **Step 4: Confirm GREEN, inspect, and commit**
Run:
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
git diff --check
Expected: host exit code 0 and no whitespace errors.
Run:
git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationDiagnostics.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs
git commit -m "feat: summarize published EM observation trajectories"
## Final verification
- [ ] Run:
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
git diff --check
git log -3 --oneline
Expected: verification exits 0, no whitespace errors exist, and three focused diagnostics commits follow the already committed design document.
@@ -1,694 +0,0 @@
# EM Observation Web Visualization Integration Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Upgrade `TrajectoryObservationMovementTest` into a complete multi-segment, observe-only EM planning experiment that publishes effective configuration, global/active path geometry, LS/ST and kinematic evidence to the reusable local scientific dashboard.
**Architecture:** A pure segment tracker advances only after a real stop and stable signed-speed confirmation. The controller owns one coordinator per active direction segment, while pure snapshot builders convert existing EM/map/smoothing data into the generic visualization contracts. A thin MovementTest host conditionally owns web and Painter sessions and isolates every visualization failure from planning.
**Tech Stack:** C# 10, `netstandard2.0`, existing EMPlanner/TrajectoryExecution/Map/PathSmoothing modules, `TrajectoryPlanningVisualization`, OSQP, MDCS read interfaces, existing console verification host.
## Prerequisite
Complete and review `docs/superpowers/plans/2026-08-05-trajectory-planning-visualization-library.md` first. This plan consumes its exact `PlanningVisualizationSession`, snapshot, chart, geometry, and configuration contracts.
## Global Constraints
- Continue `OBSERVE_ONLY`; do not call chassis, motor, steering, brake, wheel, gear, or controller write APIs.
- Use actual MDCS pose and signed longitudinal speed only as read inputs.
- Preserve user defaults exactly: solver timeout `5.0 s`, OSQP maximum iterations `100000`, ST horizon `2.0 s`, output step `0.10 s`.
- Default web visualization is off; default native Painter visualization is off.
- Web refresh defaults to `10 Hz`; history defaults to `60`; port defaults to `0`.
- Segment transitions are strictly `N -> N+1`; never skip, infer at zero speed, or reuse a previous-direction trajectory as an EM seed.
- Current and previous cycles may compare world position and shared direction-segment `ReferenceS`; never compare their independent local `PathS` origins.
- `j[i]` represents `[t_i, t_i+1)`; publish exactly `trajectory.Points.Count - 1` jerk samples and no synthetic terminal sample.
- Static geometry/configuration is built once after successful bootstrap. Dynamic snapshots are built at most at `WebRefreshRateHz`.
- Visualization exceptions are logged once and disable only the web output.
- Preserve unrelated worktree changes and stage only task files.
---
## File Structure
| File | Responsibility |
| --- | --- |
| `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationContracts.cs` | Runtime, web and direction-confirmation settings with frozen validation. |
| `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationSegmentTracker.cs` | Pure stop/direction/projection state machine. |
| `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPipeline.cs` | Active-segment controller and asynchronous loop integration. |
| `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationStaticSnapshotBuilder.cs` | Map/global paths/direction segments/effective configuration. |
| `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationKinematicChartBuilder.cs` | LS/ST/curvature/v/a/j/yaw-rate generic charts. |
| `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationHandoffAnalyzer.cs` | Absolute-time interpolation and shared-segment handoff deltas. |
| `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationDynamicSnapshotBuilder.cs` | World overlays, status, charts and cycle summary. |
| `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationVisualizationPublisher.cs` | 10 Hz gating and exception fuse. |
| `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs` | Public fields, lifecycle, browser launch and conditional Painter/web ownership. |
| `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPresentation.cs` | Lazily created optional native Painter fallback. |
| `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/README.md` | Operator workflow and chart interpretation. |
| `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationSettingsChecks.cs` | Defaults, validation and frozen-setting checks. |
| `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationSegmentChecks.cs` | Pure multi-segment transition checks. |
| `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationVisualizationChecks.cs` | Static/dynamic adapter and web isolation checks. |
| `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs` | Existing regression entry; calls the new focused groups. |
| `ClumsyPilot/tests/EMPlannerVerificationHost/PluginPackagingChecks.cs` | New managed DLL packaging contract. |
| `ClumsyPilot/scripts/Publish-ClumsyPilotPlugin.ps1` | Copies the visualization class library beside `ClumsyPilot.dll`. |
| `ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md` | Updated plugin tree and observation link. |
---
### Task 1: Synchronize effective settings and visualization controls
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationContracts.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs`
- Create: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationSettingsChecks.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/README.md`
**Interfaces:**
- Extends `TrajectoryObservationSettings` with web/Painter and direction-confirmation properties.
- `CreateValidatedSnapshot()` freezes every new property.
- MovementTest public fields map one-to-one into settings before background work begins.
- [ ] **Step 1: Write failing defaults, copy, and validation checks**
Add `TrajectoryObservationSettingsChecks.Run()` and invoke it from `TrajectoryObservationChecks.Run()`.
```csharp
var defaults = new TrajectoryObservationSettings();
Verification.NearlyEqual(5d, defaults.SolverTimeoutSeconds, "observer solver timeout default");
Verification.Equal(100000, defaults.MaximumOsqpIterations, "observer OSQP default");
Verification.NearlyEqual(2d, defaults.TimeHorizonSeconds, "observer ST horizon default");
Verification.True(!defaults.EnableWebVisualization, "web defaults off");
Verification.True(!defaults.EnableNativePainterVisualization, "Painter defaults off");
Verification.Equal(0, defaults.WebVisualizationPort, "dynamic port default");
Verification.NearlyEqual(10d, defaults.WebRefreshRateHz, "web refresh default");
Verification.Equal(60, defaults.VisualizationHistoryCycleLimit, "history default");
Verification.NearlyEqual(0.02d, defaults.DirectionConfirmationSpeedMetersPerSecond,
"direction speed threshold");
Verification.Equal(3, defaults.DirectionConfirmationSamples, "direction sample default");
Verification.NearlyEqual(0.50d, defaults.GearSwitchProjectionToleranceMeters,
"gear projection default");
Verification.NearlyEqual(0.20d, defaults.GearSwitchStopHoldSeconds, "gear stop hold default");
```
Mutate all source values after `CreateValidatedSnapshot()` and assert the snapshot retains the originals. Add invalid cases for port `-1/1/1023/65536`, refresh `0`, history `0`, speed threshold `0`, sample count `0`, projection tolerance `0`, and hold `0`.
- [ ] **Step 2: Run RED**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
```
Expected: failure because settings still use `0.50 / 12000 / 6.0` and the new properties do not exist.
- [ ] **Step 3: Implement settings and MovementTest field mapping**
Add exact get/set defaults to settings and matching public fields to MovementTest:
```csharp
public bool EnableWebVisualization { get; set; } = false;
public bool AutoOpenWebVisualization { get; set; } = true;
public int WebVisualizationPort { get; set; } = 0;
public double WebRefreshRateHz { get; set; } = 10d;
public int VisualizationHistoryCycleLimit { get; set; } = 60;
public bool EnableNativePainterVisualization { get; set; } = false;
public double DirectionConfirmationSpeedMetersPerSecond { get; set; } = 0.02d;
public int DirectionConfirmationSamples { get; set; } = 3;
public double GearSwitchProjectionToleranceMeters { get; set; } = 0.50d;
public double GearSwitchStopHoldSeconds { get; set; } = 0.20d;
```
Use inclusive validation for port `0` and `1024..65535`. Update the Chinese XML summaries so the ST default says `2 s`, not `6 s`. Update the README configuration table with all solver, ST, web, Painter and direction-confirmation fields.
- [ ] **Step 4: Run GREEN**
Run `trajectory-observation`. Expected: PASS and no assertion still expects the superseded defaults.
- [ ] **Step 5: Commit settings**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationContracts.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/README.md ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationSettingsChecks.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs
git commit -m "feat: configure observation visualization"
```
---
### Task 2: Pure sequential direction-segment tracker
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationSegmentTracker.cs`
- Create: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationSegmentChecks.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs`
**Interfaces:**
- Produces: `TrajectoryObservationSegmentPhase`, `TrajectoryObservationSegmentState`, and `TrajectoryObservationSegmentTracker.Update(...)`.
- Consumes: frozen settings, ordered `DirectionSegmentView` list, EM stop tolerance, caller time, measured `VehicleMotionState`, and current published trajectory.
- [ ] **Step 1: Write failing state-machine checks**
Build a two-segment forward/reverse fixture with a duplicated world pose at the gear switch. Exercise this exact sequence:
```csharp
var tracker = new TrajectoryObservationSegmentTracker(segments, settings, stopSpeedTolerance: 0.01d);
Verification.Equal(0, tracker.State.ActiveSegmentIndex, "tracker begins on segment zero");
tracker.Update(t0, StateAtSwitch(0d, t0, 1L), GearTerminal(t0));
Verification.Equal(TrajectoryObservationSegmentPhase.WaitingForStop, tracker.State.Phase,
"first zero sample begins stop hold");
tracker.Update(t0.AddSeconds(0.21d), StateAtSwitch(0d, t0.AddSeconds(0.21d), 2L), GearTerminal(t0));
Verification.Equal(TrajectoryObservationSegmentPhase.WaitingForDirection, tracker.State.Phase,
"continuous stop arms next direction");
tracker.Update(t0.AddSeconds(0.25d), StateAtSwitch(-0.03d, t0.AddSeconds(0.25d), 3L), GearTerminal(t0));
tracker.Update(t0.AddSeconds(0.30d), StateAtSwitch(-0.03d, t0.AddSeconds(0.30d), 4L), GearTerminal(t0));
TrajectoryObservationSegmentUpdate advanced = tracker.Update(
t0.AddSeconds(0.35d), StateAtSwitch(-0.03d, t0.AddSeconds(0.35d), 5L), GearTerminal(t0));
Verification.True(advanced.Advanced && tracker.State.ActiveSegmentIndex == 1,
"three stable reverse samples advance exactly one segment");
```
Separate checks prove that wrong sign, a zero sample, a sequence-id repeat, excessive switch distance, a non-gear terminal, and a discontinuous timestamp reset confirmation. A one-segment path reaches `Completed` rather than indexing past the end.
- [ ] **Step 2: Run RED**
Run `trajectory-observation`. Expected: compile failure because tracker types do not exist.
- [ ] **Step 3: Implement the tracker**
Use these stable phases:
```csharp
public enum TrajectoryObservationSegmentPhase
{
Planning,
WaitingForStop,
WaitingForDirection,
Completed,
}
```
`Update` first validates strictly increasing `SequenceId` and nondecreasing caller time. It only leaves `Planning` when the current published trajectory has matching segment/direction, `TerminalType.GearSwitch`, and the caller time is at or beyond `trajectory.Metadata.EffectiveAtUtc + trajectory.Points[^1].TimeFromStart`. Use `FrenetProjector` to confirm the measured pose is within the configured tolerance of both the current-segment end and next-segment start.
The expected speed sign is positive for `Forward` and negative for `Reverse`. A successful update changes the active index once, resets counters/timers, and returns a Chinese transition diagnostic. It must not write to coordinator, browser, UI, or hardware.
- [ ] **Step 4: Run GREEN**
Run `trajectory-observation` twice. Expected: PASS both times with deterministic state transitions.
- [ ] **Step 5: Commit tracker**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationSegmentTracker.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationSegmentChecks.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs
git commit -m "feat: track observed direction segments"
```
---
### Task 3: Active-segment rolling controller and loop
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPipeline.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationSegmentChecks.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs`
**Interfaces:**
- `TrajectoryObservationController.ActiveSegment`, `SegmentState`, `PreviousTrajectoryForVisualization`, and `TryAdvanceSegment(...)` become the single active-segment source.
- `TrajectoryObservationController.CreateEffectiveConfigurationSnapshot()` returns `configuration.Copy()` so adapters never read mutable MovementTest fields or retain the controller's private configuration object.
- `StartCycle` builds a request for `ActiveSegment.SegmentIndex`.
- `TrajectoryObservationLoop.Tick` may call `TryAdvanceSegment` only when no planning task is in flight.
- [ ] **Step 1: Write failing controller integration checks**
Use a recording `IEmPlanningService` and two-segment bootstrap:
```csharp
controller.StartCycle(t0, forwardState, CancellationToken.None).GetAwaiter().GetResult();
Verification.Equal(0, service.Requests[0].SegmentIndex, "first request uses segment zero");
AdvanceTrackerThroughRealStopAndReverse(controller, gearTrajectory, t0);
controller.StartCycle(t0.AddSeconds(1d), reverseState, CancellationToken.None).GetAwaiter().GetResult();
Verification.Equal(1, service.Requests[1].SegmentIndex, "next request uses segment one");
Verification.True(service.Requests[1].PreviousTrajectory == null,
"new direction does not reuse old segment trajectory");
```
Also prove two cycles on the same segment retain the exact published previous trajectory reference and ID, and prove an in-flight task prevents a segment reset.
- [ ] **Step 2: Run RED**
Expected: checks fail because the controller hardcodes `segmentIndex = 0`.
- [ ] **Step 3: Refactor controller ownership**
Store the planning service and make coordinator/executor replaceable per segment:
```csharp
private readonly IEmPlanningService planningService;
private EmPlanningCoordinator coordinator;
private TrajectoryExecutor executor;
private readonly TrajectoryObservationSegmentTracker segmentTracker;
private EmTrajectory previousTrajectoryForVisualization;
```
Add:
```csharp
internal EmPlannerConfiguration CreateEffectiveConfigurationSnapshot()
{
return configuration.Copy();
}
```
On a confirmed transition, preserve the old published trajectory only in `previousTrajectoryForVisualization`, then construct a new coordinator and executor. Same-segment `StartCycle` uses `coordinator.PublishedTrajectory`; cross-segment starts with null. Continue monotonic session cycle IDs across coordinator replacement.
`Observe` supplies the tracker-confirmed current direction to `TrajectoryExecutor`. While waiting at a gear switch, desired direction is the next segment direction and `directionConfirmed=false`; after transition both current and desired are the new direction.
- [ ] **Step 4: Update the async loop**
In `Tick`, after consuming a completed task and before starting a new task:
```csharp
bool segmentAdvanced = planningTask == null && controller.TryAdvanceSegment(now, state);
if (segmentAdvanced)
latestCycle = null;
```
Expose `SegmentAdvanced` and the immutable segment state in `TrajectoryObservationLoopTick`. Never cancel a running planner solely to advance a segment.
- [ ] **Step 5: Run GREEN and existing coordinator/executor regressions**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- coordinator
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- executor
```
Expected: all three PASS.
- [ ] **Step 6: Commit controller integration**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPipeline.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationSegmentChecks.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs
git commit -m "feat: observe EM planning across gear segments"
```
---
### Task 4: Static world geometry and effective configuration adapter
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationStaticSnapshotBuilder.cs`
- Create: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationVisualizationChecks.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs`
**Interfaces:**
- Produces: `PlanningVisualizationStaticSnapshot Build(bootstrap, effectiveConfiguration, settings, obstacleSnapshotVersion)`.
- Static polylines use metres; map source bounds/resolution remain named with their original millimetre units in configuration values.
- [ ] **Step 1: Write failing static snapshot checks**
Assert a forward/reverse bootstrap produces every segment and switch marker:
```csharp
PlanningVisualizationStaticSnapshot snapshot = builder.Build(bootstrap, configuration, settings, 17L);
Verification.Equal(bootstrap.Segments.Count, snapshot.DirectionSegments.Count,
"all direction segments exported");
Verification.Equal("Forward", snapshot.DirectionSegments[0].Direction, "forward direction exported");
Verification.Equal("Reverse", snapshot.DirectionSegments[1].Direction, "reverse direction exported");
Verification.True(snapshot.StaticMarkers.Any(x => x.Kind == "gear-switch"),
"gear switch marker exported");
Verification.Equal((bootstrap.GridMap.Rows * bootstrap.GridMap.Cols + 7) / 8,
Convert.FromBase64String(snapshot.OccupancyGrid.OccupancyBitsBase64).Length,
"occupancy bitset has exact compact length");
Verification.True(IsOccupiedBitSet(snapshot.OccupancyGrid, occupiedRow, occupiedColumn),
"occupied map cell uses row-major least-significant-bit-first encoding");
```
Flatten configuration entries by raw field name and assert exact values for `TimeHorizonSeconds=2`, `DistanceHorizonMeters=5`, `MaximumOsqpIterations=100000`, all solver tolerances, all LS/ST weights, vehicle fields, map snapshot/resolution/bounds, web settings, and direction-confirmation settings.
- [ ] **Step 2: Run RED**
Expected: compile failure because builder does not exist.
- [ ] **Step 3: Implement static conversion**
Build configuration groups in this stable order: `调度`, `OSQP`, `车辆与安全`, `纵向限制`, `ST 权重`, `横向限制`, `LS 权重`, `走廊与投影`, `地图`, `可视化与换向确认`.
Use raw field names exactly as C# properties and invariant values. Encode `PlanningGridMap.IsOccupied(row, column)` into the generic row-major occupancy bitset using bit index `row * map.Cols + column`; do not create one DTO, SVG element, or polyline per occupied cell. Build global coarse and Local G2 polylines once. Direction segments must retain segment index, direction and switch topology; dynamic state decides completed/current/future highlighting.
- [ ] **Step 4: Run GREEN**
Run `trajectory-observation`. Expected: PASS with all effective values read from the controller's frozen `EmPlannerConfiguration`, not editable MovementTest fields.
- [ ] **Step 5: Commit static adapter**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationStaticSnapshotBuilder.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationVisualizationChecks.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs
git commit -m "feat: export EM observation configuration"
```
---
### Task 5: Kinematic charts, rolling semantics, and handoff evidence
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationKinematicChartBuilder.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationHandoffAnalyzer.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationDynamicSnapshotBuilder.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationVisualizationChecks.cs`
**Interfaces:**
- Produces chart IDs `ls`, `st`, `curvature-s`, `curvature-t`, `velocity-t`, `acceleration-t`, `jerk-t`, `yaw-rate-t`.
- Produces `TrajectoryObservationHandoffMetrics` with availability, `DeltaPositionMeters`, `DeltaReferenceSMeters`, `DeltaVelocityMetersPerSecond`, and `DeltaAccelerationMetersPerSecondSquared`.
- Produces one `PlanningVisualizationDynamicSnapshot` per accepted publication tick.
- [ ] **Step 1: Write failing jerk and chart checks**
For a 21-point rolling trajectory:
```csharp
IReadOnlyList<VisualizationChart> charts = chartBuilder.Build(trajectory, segment, configuration);
VisualizationChart jerk = FindChart(charts, "jerk-t");
Verification.Equal(20, jerk.Series[0].Points.Count, "jerk has one real sample per interval");
Verification.True(jerk.NoteChinese.Contains("末点后无时间区间"), "jerk explains terminal interval");
Verification.Equal(21, FindChart(charts, "velocity-t").Series[0].Points.Count,
"velocity remains knot based");
Verification.Equal("ReferenceS (m)", FindChart(charts, "ls").XAxisLabel,
"LS uses shared segment station");
Verification.Equal("PathS (m)", FindChart(charts, "st").YAxisLabel,
"ST uses local planned PathS");
```
Assert acceleration, jerk, curvature and signed-speed limit series are present with `VisualizationLineStyle.Limit`. Do not invent a yaw-rate limit: the effective configuration has no independent maximum yaw-rate field.
- [ ] **Step 2: Write failing rolling/exact-stop semantic checks**
Build dynamic snapshots for `RollingContinuation` and `ExactStopAtBoundary`. Assert Chinese status values respectively contain `滚动末端停车硬约束:未启用` and `精确停车锚点`, and that the displayed terminal speed/acceleration equal the actual final point.
- [ ] **Step 3: Write failing handoff coordinate checks**
Create two trajectories whose local `PathS` both start at zero but whose world points project to different positions on the same full segment. Align at `current.Metadata.EffectiveAtUtc`; assert:
```csharp
Verification.NearlyEqual(expectedWorldDelta, metrics.DeltaPositionMeters, "handoff world delta");
Verification.NearlyEqual(expectedReferenceDelta, metrics.DeltaReferenceSMeters,
"handoff compares shared ReferenceS");
Verification.True(expectedReferenceDelta != current.Points[0].PathS - previous.Points[0].PathS,
"handoff does not compare local PathS origins");
```
Projection failure must return unavailable `DeltaReferenceS` while preserving world/velocity/acceleration deltas.
- [ ] **Step 4: Run RED**
Expected: compilation failure because builders and metrics do not exist.
- [ ] **Step 5: Implement chart and handoff builders**
For jerk, iterate only while `index + 1 < trajectory.Points.Count` and use the interval start time/value. For LS, project every world pose to the complete active `DirectionSegmentView` and use `segment.SourceStartArcLength + projection.ReferenceS`. For ST, retain `point.TimeFromStart` and the plan-local `point.PathS`.
Use `TrajectorySampler.TrySample` at:
```csharp
double previousTime = (current.Metadata.EffectiveAtUtc - previous.Metadata.EffectiveAtUtc).TotalSeconds;
```
Compare the sampled previous point with current point zero. Project both to the same full segment for `DeltaReferenceS`; never subtract local `PathS`.
- [ ] **Step 6: Implement dynamic snapshot composition**
Dynamic world overlays contain real pose, current trajectory, previous trajectory, active-segment highlight and current projected horizon. Status values include active segment/direction, planning status, mode, terminal type, elapsed, trajectory age, projection failures, rolling constraint semantics, terminal `v/a`, handoff deltas, and unmet gear-confirmation condition. Cycle summary contains no full point arrays.
- [ ] **Step 7: Run GREEN**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-core-all
```
Expected: both commands PASS; the existing rolling service regression still has 21 knots and nonzero rolling terminal speed.
- [ ] **Step 8: Commit the visualization adapter**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationKinematicChartBuilder.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationHandoffAnalyzer.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationDynamicSnapshotBuilder.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationVisualizationChecks.cs
git commit -m "feat: visualize EM rolling kinematics"
```
---
### Task 6: MovementTest web lifecycle and optional Painter fallback
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationVisualizationPublisher.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPresentation.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationVisualizationChecks.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs`
**Interfaces:**
- `TrajectoryObservationVisualizationPublisher.Start(...)` creates the generic session and returns the URI.
- Internal `ITrajectoryObservationVisualizationSink` defines `Start`, `Publish`, and `Stop`; the production adapter wraps `PlanningVisualizationSession`, while tests inject a throwing sink without mocking the library.
- `TryPublish(...)` enforces the configured cadence and fuses on the first adapter/server fault.
- `Stop()` is idempotent.
- Native Painter objects are constructed only when `EnableNativePainterVisualization=true`.
- [ ] **Step 1: Write failing lifecycle/isolation checks**
Add a fake visualization sink whose `Publish` throws. Verify the wrapper logs/disables once, later calls are no-ops, and the `TrajectoryObservationLoop` continues producing ticks. Add a source audit asserting the MovementTest has no static eager `new TrajectoryObservationPresentation()` and conditionally creates it only under the native Painter flag.
Add a cadence check: observer ticks at 20 Hz with web refresh at 10 Hz produce no more than 11 snapshots over one second, including the initial snapshot.
- [ ] **Step 2: Run RED**
Expected: failures because the publisher does not exist and Painter creation is eager.
- [ ] **Step 3: Implement publisher cadence and fuse**
Use this internal seam and facade surface:
```csharp
internal interface ITrajectoryObservationVisualizationSink
{
PlanningVisualizationSessionInfo Start(PlanningVisualizationOptions options,
PlanningVisualizationStaticSnapshot snapshot);
void Publish(PlanningVisualizationDynamicSnapshot snapshot);
void Stop();
}
internal sealed class TrajectoryObservationVisualizationPublisher
{
internal TrajectoryObservationVisualizationPublisher(TrajectoryObservationSettings settings,
ITrajectoryObservationVisualizationSink sink, Action<string> log);
internal PlanningVisualizationSessionInfo Start(PlanningVisualizationStaticSnapshot snapshot);
internal bool TryPublish(DateTimeOffset now,
Func<PlanningVisualizationDynamicSnapshot> snapshotFactory);
internal void Stop();
internal string FaultReason { get; }
}
```
The production sink constructs and disposes one `PlanningVisualizationSession`; the fake sink records calls or throws.
The publisher stores `nextPublishAtUtc`. When enabled and healthy:
```csharp
if (now < nextPublishAtUtc) return false;
nextPublishAtUtc = now + TimeSpan.FromSeconds(1d / settings.WebRefreshRateHz);
session.Publish(dynamicBuilder.Build(...));
return true;
```
Catch any exception from build/start/publish, call `session.Stop()`, set one immutable Chinese fault reason, invoke the supplied log callback once, and return false thereafter.
- [ ] **Step 4: Integrate successful bootstrap and browser launch**
After bootstrap succeeds and before the loop begins, build the static snapshot and start the web session only when enabled. Log the full tokenized URI. If auto-open is enabled, call:
```csharp
Process.Start(new ProcessStartInfo
{
FileName = sessionInfo.Uri.AbsoluteUri,
UseShellExecute = true,
});
```
Catch browser-launch exceptions separately and keep the web session running.
- [ ] **Step 5: Make Painter lazy and conditional**
Remove static eager `Presentation`. Create a session-local presentation only when enabled, pass null otherwise, and guard all `DrawWorld/DrawLs/DrawSt/ClearAll` calls. Stopping or replacing a session stops its web publisher and clears only an existing Painter. Bootstrap failure still logs through UI/console even when both visualization modes are off.
- [ ] **Step 6: Publish dynamic snapshots from real ticks**
Use `controller.ActiveSegment` for LS/ST building and world highlight. Pass the completed cycle, elapsed time, in-flight flag, segment state, current/previous trajectories, live state and current sample into `TryPublish`. Do not serialize or wait in the observer loop.
- [ ] **Step 7: Run GREEN and source audit**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
rg -n "SendXYThSpeed|SendMotion|DriveStop|PredefinedDriveStop|AccumulateSpeed" ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest
```
Expected: check PASS and `rg` finds no actuator call in runtime observer sources.
- [ ] **Step 8: Commit MovementTest lifecycle**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationVisualizationPublisher.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPresentation.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationVisualizationChecks.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs
git commit -m "feat: host EM observation dashboard"
```
---
### Task 7: Plugin packaging and operator documentation
**Files:**
- Modify: `ClumsyPilot/scripts/Publish-ClumsyPilotPlugin.ps1`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/PluginPackagingChecks.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/README.md`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md`
**Interfaces:**
- Plugin output adds `plugins/TrajectoryPlanningVisualization.dll` beside `ClumsyPilot.dll`.
- The publisher resolves the visualization DLL only as a sibling of the explicit `ManagedDll`; it does not search `PATH` or current directory.
- [ ] **Step 1: Write failing package-tree check**
Change the expected sorted tree to:
```text
plugins/ClumsyPilot.dll|
plugins/TrajectoryPlanningVisualization.dll|
plugins/licenses/OSQP-LICENSE.txt|
plugins/licenses/OSQP-NOTICE.txt|
plugins/licenses/OSQP-VERSION.txt|
plugins/osqp.dll
```
Also use `AssemblyName.GetAssemblyName` to verify the new file is a managed assembly named `TrajectoryPlanningVisualization`.
- [ ] **Step 2: Run RED**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- plugin-package
```
Expected: failure because the publish script omits the visualization DLL.
- [ ] **Step 3: Update the atomic publisher**
Resolve:
```powershell
$visualizationDll = Join-Path ([System.IO.Path]::GetDirectoryName($managedDllPath)) 'TrajectoryPlanningVisualization.dll'
if (-not [System.IO.File]::Exists($visualizationDll)) {
throw "Visualization DLL does not exist beside managed DLL: $visualizationDll"
}
```
Copy it into staging before the atomic directory swap. Keep all existing drive-root/workspace-root protections, OSQP hash validation and recovery behavior unchanged.
- [ ] **Step 4: Run package GREEN**
Run `plugin-package` twice. Expected: PASS both times and no stale staging/backup directories.
- [ ] **Step 5: Update both READMEs**
The MovementTest README must document:
- how to enable web/Painter modes;
- localhost/token URL behavior;
- Chinese scientific chart conventions;
- `ReferenceS` versus local `PathS`;
- jerk interval semantics;
- rolling/approach/exact-stop labels;
- active/completed/future segment colors;
- direction-confirmation conditions;
- browser closure and TestStop behavior.
The EM README plugin tree must include `TrajectoryPlanningVisualization.dll` and link to the observation README.
- [ ] **Step 6: Commit packaging and docs**
```powershell
git add -- ClumsyPilot/scripts/Publish-ClumsyPilotPlugin.ps1 ClumsyPilot/tests/EMPlannerVerificationHost/PluginPackagingChecks.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/README.md ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md
git commit -m "docs: package EM observation dashboard"
```
---
### Task 8: Full automated and manual acceptance
**Files:**
- No planned source changes. If a command fails, return to the owning task, add a focused failing regression there, implement the minimal correction, and rerun this acceptance task from Step 1.
**Interfaces:**
- Consumes: both verification hosts, the packaged plugin output, the deterministic smoke mode, and the deployed MovementTest entry.
- Produces: fresh automated evidence plus an explicit completed-or-pending vehicle checklist; it does not introduce a new runtime API.
- [ ] **Step 1: Run the complete automated suite**
```powershell
dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-all
dotnet build ClumsyPilot/ClumsyPilot.csproj -p:ExcludeLegacyAutoAvoidance=true
git diff --check
```
Expected: visualization host PASS; every `em-all` component PASS; main build exits `0`; no new whitespace errors. Record any pre-existing warnings separately rather than claiming they were introduced here.
- [ ] **Step 2: Run a local dashboard smoke session**
Run the deterministic smoke mode delivered by Plan 1:
```powershell
dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj -- --smoke-seconds 30
```
Open the printed tokenized URI during the 30-second window and verify:
- Chinese titles/status and English/scientific axes;
- thin white-background scientific plots;
- all four tabs and all required charts;
- active segment/world horizon highlight;
- effective configuration values;
- stale-state display after sample publication stops;
- port release after the host exits.
This smoke command must publish synthetic snapshots only and must not reference MDCS or hardware.
- [ ] **Step 3: Perform the deployed vehicle observation checklist**
In a supervised safe environment:
1. Enable web and leave native Painter disabled.
2. Confirm `OBSERVE_ONLY` appears in UI/console/page.
3. Observe rolling, approach and exact-stop modes; confirm no forced rolling parking label.
4. Confirm jerk has `N-1` interval samples and no point-21 successor interval or `JerkLimitExceeded`.
5. Confirm current/previous handoff uses `Δposition/ΔReferenceS/Δv/Δa`.
6. At a real gear switch, confirm stop hold and three signed-speed samples precede `N -> N+1` highlight.
7. Close the browser and confirm planning cadence continues.
8. Stop MovementTest and confirm server/port/Painter cleanup and no actuator output.
- [ ] **Step 4: Commit only acceptance-driven fixes**
If Step 13 required code changes, commit each regression and fix with exact file paths. If no changes were required, do not create an empty commit.
Plan 2 is complete only after automated evidence is fresh and the vehicle-only checklist is explicitly reported as completed or, if the vehicle is unavailable, explicitly reported as pending rather than silently treated as passed.
@@ -1,110 +0,0 @@
# MovementTest OSQP Iteration Limit 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:** Expose a 12000-iteration OSQP cap for observation-only MovementTest sessions.
**Architecture:** TrajectoryObservationSettings owns an integer iteration limit, snapshots and validates it. The public MovementTest field maps into those settings, and the observation controller maps it into the session-local EM solver configuration. Production EM defaults remain unchanged.
**Tech Stack:** C# 10, .NET, EMPlannerVerificationHost.
## Global Constraints
- Default MaximumOsqpIterations is exactly 12000.
- SolverTimeoutSeconds remains at its existing 0.50d MovementTest default.
- AbsoluteTolerance, RelativeTolerance, and StrictResidualTolerance remain 1e-5d.
- Do not change production EM defaults, vehicle/map constraints, or observe-only command behavior.
---
### Task 1: Propagate the test iteration limit into an EM request
**Files:**
- Modify: ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs:95-176,384-416
- Modify: ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationContracts.cs:9-50
- Modify: ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs:25-71
- Modify: ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPipeline.cs:198-215
**Interfaces:**
- Consumes: TrajectoryObservationSettings.CreateValidatedSnapshot() and TrajectoryObservationController.StartCycle(...).
- Produces: TrajectoryObservationSettings.MaximumOsqpIterations, copied to EmPlanningRequest.Configuration.Solver.MaximumOsqpIterations.
- [x] **Step 1: Write the failing test**
Add assertions proving the public field uses the default and a custom setting reaches the request:
~~~csharp
Verification.True(source.Contains("public int MaximumOsqpIterations = 12000;"),
"observer MovementTest exposes the test OSQP iteration default");
var settings = new TrajectoryObservationSettings
{
MaximumOsqpIterations = 9000,
};
// Start one controller cycle.
Verification.Equal(9000, planningService.Requests[0].Configuration.Solver.MaximumOsqpIterations,
"observer configured OSQP iteration limit");
~~~
Assert 0 and negative iteration limits are rejected.
- [x] **Step 2: Run focused verification to verify it fails**
Run:
~~~powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
~~~
Expected: FAIL because MaximumOsqpIterations is absent.
- [x] **Step 3: Write the minimal implementation**
Add this setting and snapshot mapping:
~~~csharp
public int MaximumOsqpIterations { get; set; } = 12000;
~~~
Validate it with:
~~~csharp
if (MaximumOsqpIterations <= 0)
throw new ArgumentOutOfRangeException(nameof(MaximumOsqpIterations), "Value must be positive.");
~~~
Map it into the MovementTest settings and session configuration:
~~~csharp
configuration.Solver.MaximumOsqpIterations = settings.MaximumOsqpIterations;
~~~
- [x] **Step 4: Run focused verification to verify it passes**
Run:
~~~powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
~~~
Expected: PASS trajectory-observation.
- [x] **Step 5: Run full regression and build**
Run:
~~~powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-all
dotnet build ClumsyPilot/ClumsyPilot.csproj --no-restore
~~~
Expected: all suites pass and the build reports zero errors.
- [x] **Step 6: Commit implementation**
~~~powershell
git add ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationContracts.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPipeline.cs
git commit -m "feat: configure MovementTest OSQP iterations"
~~~
@@ -1,102 +0,0 @@
# MovementTest Solver Timeout 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:** Make the observation-only MovementTest expose a 0.50-second default OSQP time budget without relaxing solver accuracy.
**Architecture:** `TrajectoryObservationSettings` owns the test-facing time-budget value and validates/copies it. The MovementTest maps its public field into those settings, and `TrajectoryObservationController` maps the snapshot into the per-session EM configuration. The production EM defaults remain unchanged.
**Tech Stack:** C# 10, .NET, existing EMPlannerVerificationHost test host.
## Global Constraints
- Default test solver timeout is exactly `0.50d` seconds.
- `AbsoluteTolerance`, `RelativeTolerance`, and `StrictResidualTolerance` remain `1e-5d`.
- Do not change vehicle constraints, map construction, speed limits, or observe-only command behavior.
- Do not alter `EmPlannerConfiguration.CreateDefault()`; the override belongs only to the observation test.
---
### Task 1: Propagate the test solver budget into an EM request
**Files:**
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs:122-170,384-405`
- Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationContracts.cs:9-49`
- Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs:25-70`
- Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPipeline.cs:198-214`
**Interfaces:**
- Consumes: `TrajectoryObservationSettings.CreateValidatedSnapshot()` and `TrajectoryObservationController.StartCycle(...)`.
- Produces: `TrajectoryObservationSettings.SolverTimeoutSeconds`, copied to `EmPlanningRequest.Configuration.Scheduling.SolverTimeoutSeconds`.
- [x] **Step 1: Write the failing test**
In `VerifiesControllerBuildsSegmentZeroRequest`, set a non-default observation setting and assert it reaches the request:
```csharp
var settings = new TrajectoryObservationSettings
{
SolverTimeoutSeconds = 0.42d,
};
// Start one controller cycle.
Verification.NearlyEqual(0.42d, planningService.Requests[0].Configuration.Scheduling.SolverTimeoutSeconds,
"observer configured solver timeout");
```
Also extend `VerifiesSettingsSnapshotAndVehicle` to assert that a default snapshot retains `0.50d`, and extend `RejectsInvalidSettings` with `settings => settings.SolverTimeoutSeconds = 0d`.
- [x] **Step 2: Run the focused verification to verify it fails**
Run:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
```
Expected: FAIL because `SolverTimeoutSeconds` does not yet exist or its value is not propagated to the request.
- [x] **Step 3: Write the minimal implementation**
Add and copy/validate the settings property:
```csharp
public double SolverTimeoutSeconds { get; set; } = 0.50d;
```
Map it from the public MovementTest field, then set the session-local EM configuration:
```csharp
configuration.Scheduling.SolverTimeoutSeconds = settings.SolverTimeoutSeconds;
```
Do not alter the EM default configuration or any tolerance property.
- [x] **Step 4: Run focused verification to verify it passes**
Run:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
```
Expected: all trajectory-observation checks pass, including timeout propagation and invalid-setting rejection.
- [x] **Step 5: Run the related EM regression suite**
Run:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-all
dotnet build ClumsyPilot/ClumsyPilot.csproj --no-restore
```
Expected: both commands finish successfully with no errors.
- [x] **Step 6: Commit the implementation**
```powershell
git add ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationContracts.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPipeline.cs
git commit -m "feat: configure MovementTest solver timeout"
```
@@ -1,115 +0,0 @@
# MovementTest ST Time Controls 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:** Expose configurable ST horizon and trajectory timestamp spacing for observation-only MovementTest sessions.
**Architecture:** TrajectoryObservationSettings owns TimeHorizonSeconds and OutputTimeStepSeconds, preserves them in a validated snapshot, and enforces a time step no larger than the horizon. Public MovementTest fields feed those settings, then TrajectoryObservationController copies both values into its session-local EM schedule.
**Tech Stack:** C# 10, .NET, EMPlannerVerificationHost.
## Global Constraints
- TimeHorizonSeconds defaults to 6d.
- OutputTimeStepSeconds defaults to 0.10d.
- OutputTimeStepSeconds must be positive and no greater than TimeHorizonSeconds.
- ObserverPeriodSeconds stays 0.05d.
- SolverTimeoutSeconds stays 0.50d, MaximumOsqpIterations stays 12000, and all solver tolerances stay 1e-5d.
- Do not change production EM defaults, vehicle/map constraints, or observe-only command behavior.
---
### Task 1: Propagate ST time controls into an EM request
**Files:**
- Modify: ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs
- Modify: ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationContracts.cs
- Modify: ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs
- Modify: ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPipeline.cs
**Interfaces:**
- Consumes: TrajectoryObservationSettings.CreateValidatedSnapshot() and TrajectoryObservationController.StartCycle(...).
- Produces: TimeHorizonSeconds and OutputTimeStepSeconds in EmPlanningRequest.Configuration.Scheduling.
- [x] **Step 1: Write the failing test**
Add assertions for the visible defaults and request propagation:
~~~csharp
Verification.True(source.Contains("public double TimeHorizonSeconds = 6d;"),
"observer MovementTest exposes the ST time-horizon default");
Verification.True(source.Contains("public double OutputTimeStepSeconds = 0.10d;"),
"observer MovementTest exposes the ST timestamp-spacing default");
var settings = new TrajectoryObservationSettings
{
TimeHorizonSeconds = 4d,
OutputTimeStepSeconds = 0.20d,
};
// Start one controller cycle.
Verification.NearlyEqual(4d, request.Configuration.Scheduling.TimeHorizonSeconds,
"observer configured ST time horizon");
Verification.NearlyEqual(0.20d, request.Configuration.Scheduling.OutputTimeStepSeconds,
"observer configured ST timestamp spacing");
~~~
Assert zero horizon, zero step, and a step larger than the horizon are rejected.
- [x] **Step 2: Run focused verification to verify it fails**
Run:
~~~powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
~~~
Expected: FAIL because the ST time-control properties are absent.
- [x] **Step 3: Write the minimal implementation**
Add public settings and comments:
~~~csharp
/// <summary>单次 ST 轨迹覆盖的未来时长,单位 s;不等于观察循环周期。</summary>
public double TimeHorizonSeconds { get; set; } = 6d;
/// <summary>发布 Trajectory 相邻 TimeFromStart 时间戳的间隔,单位 s;不等于观察循环周期。</summary>
public double OutputTimeStepSeconds { get; set; } = 0.10d;
~~~
Validate both values and require OutputTimeStepSeconds to be no larger than TimeHorizonSeconds. Copy them through the MovementTest and use:
~~~csharp
configuration.Scheduling.TimeHorizonSeconds = settings.TimeHorizonSeconds;
configuration.Scheduling.OutputTimeStepSeconds = settings.OutputTimeStepSeconds;
~~~
- [x] **Step 4: Run focused verification to verify it passes**
Run:
~~~powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
~~~
Expected: PASS trajectory-observation.
- [x] **Step 5: Run full regression and build**
Run:
~~~powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-all
dotnet build ClumsyPilot/ClumsyPilot.csproj --no-restore
~~~
Expected: all suites pass and the build reports zero errors.
- [x] **Step 6: Commit implementation**
~~~powershell
git add ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationContracts.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPipeline.cs
git commit -m "feat: configure MovementTest ST time controls"
~~~
@@ -1,660 +0,0 @@
# Trajectory Planning Visualization Library 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:** Build a reusable `netstandard2.0` library that publishes immutable planning snapshots through a bounded, non-blocking loopback HTTP/SSE service and renders a self-contained Chinese scientific-style dashboard.
**Architecture:** The library owns generic visualization contracts, an atomic latest-frame exchange, bounded cycle summaries, a minimal `TcpListener` HTTP/1.1 server, embedded web assets, and one `PlanningVisualizationSession` facade. It has no dependency on EMPlanner, MDCS, MovementTest, ClumsyCore, Painter, ASP.NET Core, Node.js, or an external CDN.
**Tech Stack:** C# 10, `netstandard2.0`, `TcpListener`, Server-Sent Events, Newtonsoft.Json 13.0.4, embedded HTML/CSS/JavaScript, a `net10.0-windows` console verification host.
## Global Constraints
- Bind only `IPAddress.Loopback`; never bind `IPAddress.Any`, `0.0.0.0`, a LAN address, or a hostname prefix.
- Accept only HTTP `GET`; cap request headers at `16 KiB` and request reads at `2 s`.
- Require the per-session random token on the page, assets, bootstrap, and event endpoints.
- `Publish` may only perform validation plus `Interlocked.Exchange`; it must not serialize, write a socket, wait for a client, or mutate caller-owned collections.
- Latest dynamic-frame capacity is exactly `1`; full trajectory data exists only in that latest frame.
- Cycle history is bounded by `HistoryCycleLimit` and stores summaries only.
- Default refresh rate is `10 Hz`, default history limit is `60`, default port is `0`, and maximum clients is fixed at `2`.
- Web assets are embedded and self-contained; no CDN, package manager, web build, or runtime file lookup.
- Chinese is used for descriptions and state text; axis variables, SI units, and mathematical symbols retain scientific notation.
- Preserve all unrelated dirty-worktree changes and stage only files named by each task.
---
## File Structure
| File | Responsibility |
| --- | --- |
| `ClumsyPilot/TrajectoryPlanningVisualization/TrajectoryPlanningVisualization.csproj` | Standalone class library, Newtonsoft dependency, and (from Task 4 onward) embedded assets. |
| `ClumsyPilot/TrajectoryPlanningVisualization/Contracts/VisualizationPrimitives.cs` | Points, poses, bounds, key/value data and defensive collection-copy helpers. |
| `ClumsyPilot/TrajectoryPlanningVisualization/Contracts/VisualizationGeometry.cs` | World polylines, markers, segments and static world snapshot. |
| `ClumsyPilot/TrajectoryPlanningVisualization/Contracts/VisualizationCharts.cs` | Charts, axes, series and line-style contracts. |
| `ClumsyPilot/TrajectoryPlanningVisualization/Contracts/PlanningVisualizationSnapshots.cs` | Static snapshot, dynamic snapshot, cycle summary and session status. |
| `ClumsyPilot/TrajectoryPlanningVisualization/Runtime/PlanningVisualizationOptions.cs` | Validated transport and retention options. |
| `ClumsyPilot/TrajectoryPlanningVisualization/Runtime/LatestVisualizationFrameStore.cs` | Atomic capacity-one dynamic-frame exchange. |
| `ClumsyPilot/TrajectoryPlanningVisualization/Runtime/BoundedCycleHistory.cs` | Service-side deduplicated cycle-summary ring. |
| `ClumsyPilot/TrajectoryPlanningVisualization/Runtime/VisualizationJson.cs` | Stable camel-case invariant JSON serialization. |
| `ClumsyPilot/TrajectoryPlanningVisualization/Runtime/LoopbackHttpRequestReader.cs` | Bounded GET request parser. |
| `ClumsyPilot/TrajectoryPlanningVisualization/Runtime/SseClientConnection.cs` | Per-client capacity-one outbound payload and bounded writer. |
| `ClumsyPilot/TrajectoryPlanningVisualization/Runtime/LoopbackVisualizationServer.cs` | TCP accept loop, route authorization and SSE clients. |
| `ClumsyPilot/TrajectoryPlanningVisualization/PlanningVisualizationSession.cs` | Public `Start/Publish/Stop` facade and fault isolation. |
| `ClumsyPilot/TrajectoryPlanningVisualization/Web/index.html` | Chinese dashboard structure. |
| `ClumsyPilot/TrajectoryPlanningVisualization/Web/app.css` | Thin-line scientific visual system. |
| `ClumsyPilot/TrajectoryPlanningVisualization/Web/app.js` | Snapshot consumption, SVG charts, tabs and stale-state handling. |
| `ClumsyPilot/TrajectoryPlanningVisualization/README.md` | Public API, endpoints, resource guarantees and integration example. |
| `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj` | Independent executable verification host. |
| `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/Program.cs` | Adds each check group as its task lands and provides the final timed smoke mode. |
| `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/SampleSnapshotFactory.cs` | Deterministic synthetic dashboard sample for browser smoke testing. |
| `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/Verification.cs` | Minimal assertion helpers. |
| `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/ContractChecks.cs` | Contract immutability and validation checks. |
| `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/RuntimeChecks.cs` | Latest-frame, history and JSON checks. |
| `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/ServerChecks.cs` | Real loopback HTTP/SSE lifecycle checks. |
| `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/WebAssetChecks.cs` | Embedded Chinese scientific dashboard checks. |
| `ClumsyPilot/ClumsyPilot.csproj` | Excludes the new library/test sources from default recursive compilation and references the library project. |
---
### Task 1: Standalone project and immutable visualization contracts
**Files:**
- Create: `ClumsyPilot/TrajectoryPlanningVisualization/TrajectoryPlanningVisualization.csproj`
- Create: `ClumsyPilot/TrajectoryPlanningVisualization/Contracts/VisualizationPrimitives.cs`
- Create: `ClumsyPilot/TrajectoryPlanningVisualization/Contracts/VisualizationGeometry.cs`
- Create: `ClumsyPilot/TrajectoryPlanningVisualization/Contracts/VisualizationCharts.cs`
- Create: `ClumsyPilot/TrajectoryPlanningVisualization/Contracts/PlanningVisualizationSnapshots.cs`
- Create: `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj`
- Create: `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/Program.cs`
- Create: `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/Verification.cs`
- Create: `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/ContractChecks.cs`
- Modify: `ClumsyPilot/ClumsyPilot.csproj`
**Interfaces:**
- Produces: `VisualizationPoint`, `VisualizationPose`, `VisualizationBounds`, `VisualizationValue`, `VisualizationConfigurationGroup`, `VisualizationPolyline`, `VisualizationMarker`, `VisualizationDirectionSegment`, `VisualizationOccupancyGrid`, `VisualizationChart`, `VisualizationSeries`, `PlanningVisualizationStaticSnapshot`, `PlanningVisualizationDynamicSnapshot`, and `VisualizationCycleSummary`.
- Collection-bearing constructors must reject null elements and copy source collections into `ReadOnlyCollection<T>`.
- Numeric constructors must reject NaN/infinity. Timestamps must be carried as `DateTimeOffset`.
- [ ] **Step 1: Create the independent verification host and failing contract checks**
Add a host that initially calls only the contract group and returns `1` on exceptions:
```csharp
internal static class Program
{
private static int Main()
{
try
{
ContractChecks.Run();
Console.WriteLine("PASS trajectory-planning-visualization");
return 0;
}
catch (Exception exception)
{
Console.Error.WriteLine(exception);
return 1;
}
}
}
```
Start `ContractChecks.Run()` with a defensive-copy test:
```csharp
var points = new List<VisualizationPoint> { new VisualizationPoint(1d, 2d) };
var series = new VisualizationSeries("current", "当前轨迹", VisualizationLineStyle.Solid, points);
points[0] = new VisualizationPoint(9d, 9d);
Verification.NearlyEqual(1d, series.Points[0].X, "series copies points");
Verification.Throws<ArgumentOutOfRangeException>(
() => new VisualizationPoint(double.NaN, 0d), "point rejects NaN");
byte[] bits = { 0x01 };
var grid = new VisualizationOccupancyGrid(new VisualizationBounds(0d, 1d, 0d, 1d),
0.5d, rows: 2, columns: 2, bits);
bits[0] = 0x00;
Verification.Equal("AQ==", grid.OccupancyBitsBase64, "occupancy grid copies compact bits");
```
The verification host project must reference only the standalone visualization library:
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0-windows</TargetFramework>
<LangVersion>10</LangVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\TrajectoryPlanningVisualization\TrajectoryPlanningVisualization.csproj" />
</ItemGroup>
</Project>
```
- [ ] **Step 2: Run the host to verify RED**
Run:
```powershell
dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
```
Expected: compilation fails because `TrajectoryPlanningVisualization` contracts do not exist.
- [ ] **Step 3: Add project boundaries and contract implementations**
The library project must contain:
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>10</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
</ItemGroup>
</Project>
```
Update the main project so nested project/test sources are not compiled twice:
```xml
<Compile Remove="TrajectoryPlanningVisualization\**\*.cs" />
<Compile Remove="tests\TrajectoryPlanningVisualizationVerificationHost\**\*.cs" />
<ProjectReference Include="TrajectoryPlanningVisualization\TrajectoryPlanningVisualization.csproj" />
```
Define the stable chart contract exactly as follows:
```csharp
public enum VisualizationLineStyle { Solid, Dashed, Limit }
public sealed class VisualizationSeries
{
public VisualizationSeries(string id, string legend, VisualizationLineStyle lineStyle,
IReadOnlyList<VisualizationPoint> points);
public string Id { get; }
public string Legend { get; }
public VisualizationLineStyle LineStyle { get; }
public IReadOnlyList<VisualizationPoint> Points { get; }
}
public sealed class VisualizationChart
{
public VisualizationChart(string id, string chineseTitle, string xAxisLabel, string yAxisLabel,
IReadOnlyList<VisualizationSeries> series, string noteChinese = "");
public string Id { get; }
public string ChineseTitle { get; }
public string XAxisLabel { get; }
public string YAxisLabel { get; }
public IReadOnlyList<VisualizationSeries> Series { get; }
public string NoteChinese { get; }
}
```
The two top-level snapshots must expose no setters:
```csharp
public sealed class PlanningVisualizationStaticSnapshot
{
public PlanningVisualizationStaticSnapshot(string sessionNameChinese, VisualizationBounds worldBounds,
VisualizationOccupancyGrid occupancyGrid,
IReadOnlyList<VisualizationPolyline> staticPolylines,
IReadOnlyList<VisualizationMarker> staticMarkers,
IReadOnlyList<VisualizationDirectionSegment> directionSegments,
IReadOnlyList<VisualizationConfigurationGroup> configurationGroups);
public string SessionNameChinese { get; }
public VisualizationBounds WorldBounds { get; }
public VisualizationOccupancyGrid OccupancyGrid { get; }
public IReadOnlyList<VisualizationPolyline> StaticPolylines { get; }
public IReadOnlyList<VisualizationMarker> StaticMarkers { get; }
public IReadOnlyList<VisualizationDirectionSegment> DirectionSegments { get; }
public IReadOnlyList<VisualizationConfigurationGroup> ConfigurationGroups { get; }
}
public sealed class PlanningVisualizationDynamicSnapshot
{
public PlanningVisualizationDynamicSnapshot(long sequence, DateTimeOffset observedAtUtc,
string sessionStateChinese, int activeSegmentIndex, string activeDirection,
VisualizationPose vehiclePose, IReadOnlyList<VisualizationPolyline> dynamicPolylines,
IReadOnlyList<VisualizationMarker> dynamicMarkers, IReadOnlyList<VisualizationChart> charts,
IReadOnlyList<VisualizationValue> statusValues, VisualizationCycleSummary cycleSummary);
public long Sequence { get; }
public DateTimeOffset ObservedAtUtc { get; }
public string SessionStateChinese { get; }
public int ActiveSegmentIndex { get; }
public string ActiveDirection { get; }
public VisualizationPose VehiclePose { get; }
public IReadOnlyList<VisualizationPolyline> DynamicPolylines { get; }
public IReadOnlyList<VisualizationMarker> DynamicMarkers { get; }
public IReadOnlyList<VisualizationChart> Charts { get; }
public IReadOnlyList<VisualizationValue> StatusValues { get; }
public VisualizationCycleSummary CycleSummary { get; }
}
```
Use these exact primitive/geometry fields so later adapters do not invent parallel DTOs:
```text
VisualizationPoint: X, Y
VisualizationPose: X, Y, HeadingRadians
VisualizationBounds: XMin, XMax, YMin, YMax
VisualizationValue: ChineseName, RawName, Value, Unit, Severity
VisualizationConfigurationGroup: ChineseTitle, Entries
VisualizationPolyline: Id, LegendChinese, Kind, LineStyle, Points
VisualizationMarker: Id, Kind, LabelChinese, Position
VisualizationDirectionSegment: SegmentIndex, Direction, StartsAtGearSwitch, EndsAtGearSwitch, Points
VisualizationOccupancyGrid: Bounds, ResolutionMeters, Rows, Columns, OccupancyBitsBase64
VisualizationCycleSummary: CycleVersion, OccurredAtUtc, Status, Published,
PlanningElapsedMilliseconds, SegmentIndex, Direction, LongitudinalMode,
TerminalType, TerminalVelocity, TerminalAcceleration, FailureReason
```
`TerminalVelocity` and `TerminalAcceleration` are nullable doubles; all other numeric fields are non-null. `Severity` is one of `normal`, `notice`, or `failure`, validated as an exact string so the generic library does not depend on an EM enum.
`VisualizationOccupancyGrid` accepts a row-major bit array of exactly `ceil(Rows * Columns / 8)` bytes, makes a defensive copy, and exposes it as Base64 JSON through `OccupancyBitsBase64`. Bit index `row * Columns + column` uses the least-significant bit first within each byte. A null occupancy grid is allowed for non-map visualizations.
- [ ] **Step 4: Run the focused contract host GREEN**
Run:
```powershell
dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
dotnet build ClumsyPilot/ClumsyPilot.csproj -p:ExcludeLegacyAutoAvoidance=true
```
Expected: both exit `0`; the host prints `PASS trajectory-planning-visualization` and the main build has no duplicate-type errors.
- [ ] **Step 5: Commit the project and contracts**
```powershell
git add -- ClumsyPilot/TrajectoryPlanningVisualization ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost ClumsyPilot/ClumsyPilot.csproj
git commit -m "feat: add planning visualization contracts"
```
---
### Task 2: Capacity-one frame exchange, bounded history, and JSON
**Files:**
- Create: `ClumsyPilot/TrajectoryPlanningVisualization/Runtime/PlanningVisualizationOptions.cs`
- Create: `ClumsyPilot/TrajectoryPlanningVisualization/Runtime/LatestVisualizationFrameStore.cs`
- Create: `ClumsyPilot/TrajectoryPlanningVisualization/Runtime/BoundedCycleHistory.cs`
- Create: `ClumsyPilot/TrajectoryPlanningVisualization/Runtime/VisualizationJson.cs`
- Modify: `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/Program.cs`
- Modify: `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/RuntimeChecks.cs`
**Interfaces:**
- Produces: `PlanningVisualizationOptions.CreateValidatedSnapshot()`, `LatestVisualizationFrameStore.Publish(...)`, `TryReadAfter(...)`, `BoundedCycleHistory.Add(...)`, `Snapshot()`, and `VisualizationJson.Serialize(...)`.
- `VisualizationFrame` contains a monotonically increasing store version and one dynamic snapshot.
- [ ] **Step 1: Write failing store, history, and JSON checks**
Create `RuntimeChecks`, add `RuntimeChecks.Run()` immediately after `ContractChecks.Run()` in `Program`, and add these behaviors:
```csharp
var store = new LatestVisualizationFrameStore();
store.Publish(Snap(1));
store.Publish(Snap(2));
Verification.True(store.TryReadAfter(0, out VisualizationFrame frame), "latest frame exists");
Verification.Equal(2L, frame.Snapshot.Sequence, "latest frame replaces old frame");
Verification.True(!store.TryReadAfter(frame.Version, out _), "same frame is not replayed");
var history = new BoundedCycleHistory(2);
history.Add(Cycle(1)); history.Add(Cycle(2)); history.Add(Cycle(3)); history.Add(Cycle(3));
Verification.Equal("2|3", string.Join("|", history.Snapshot().Select(x => x.CycleVersion)),
"history is bounded and deduplicated");
string json = VisualizationJson.Serialize(Snap(2));
Verification.True(json.Contains("\"sessionStateChinese\"") && json.Contains("\"observedAtUtc\""),
"JSON uses stable camel case names");
```
Also verify invalid options: negative port, refresh rate `0`, history `0`, and port `65536` throw.
- [ ] **Step 2: Run to verify RED**
Run the visualization host. Expected: compilation fails because runtime types do not exist.
- [ ] **Step 3: Implement the non-blocking stores and options**
`LatestVisualizationFrameStore.Publish` must contain no lock or callback:
```csharp
public void Publish(PlanningVisualizationDynamicSnapshot snapshot)
{
if (snapshot == null) throw new ArgumentNullException(nameof(snapshot));
long version = Interlocked.Increment(ref nextVersion);
Interlocked.Exchange(ref latest, new VisualizationFrame(version, snapshot));
}
```
`TryReadAfter` reads with `Volatile.Read`, and the history uses a private lock only on the service-consumer thread. `BoundedCycleHistory.Add` ignores a repeated `CycleVersion`, removes from the head while count exceeds capacity, and `Snapshot` returns a fresh read-only copy.
Use Newtonsoft settings with `CamelCasePropertyNamesContractResolver`, `InvariantCulture`, `DateFormatHandling.IsoDateFormat`, and `Formatting.None`.
- [ ] **Step 4: Run to verify GREEN**
Run the visualization host twice. Expected: both runs print the PASS line and never depend on working-directory files.
- [ ] **Step 5: Commit runtime data handling**
```powershell
git add -- ClumsyPilot/TrajectoryPlanningVisualization/Runtime ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/Program.cs ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/RuntimeChecks.cs
git commit -m "feat: add bounded visualization snapshots"
```
---
### Task 3: Restricted loopback HTTP/SSE server and public facade
**Files:**
- Create: `ClumsyPilot/TrajectoryPlanningVisualization/Runtime/LoopbackHttpRequestReader.cs`
- Create: `ClumsyPilot/TrajectoryPlanningVisualization/Runtime/SseClientConnection.cs`
- Create: `ClumsyPilot/TrajectoryPlanningVisualization/Runtime/LoopbackVisualizationServer.cs`
- Create: `ClumsyPilot/TrajectoryPlanningVisualization/PlanningVisualizationSession.cs`
- Modify: `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/Program.cs`
- Modify: `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/ServerChecks.cs`
**Interfaces:**
- Produces: `PlanningVisualizationSession.Start(PlanningVisualizationStaticSnapshot)`, `Publish(PlanningVisualizationDynamicSnapshot)`, `Stop()`, `IsRunning`, and `SessionInfo.Uri`.
- This task produces `/api/bootstrap` and `/api/events`; Task 4 adds `/`, `/app.css`, and `/app.js`. Every available route requires `token=<session-token>`.
- Statuses: bad token `403`, unknown route `404`, non-GET `405`, oversized/malformed request `400`, third live SSE client `503`.
- [ ] **Step 1: Write failing real-socket checks**
Create `ServerChecks`, call it after `RuntimeChecks`, and use `TcpClient` rather than mocks:
```csharp
using (var session = new PlanningVisualizationSession(new PlanningVisualizationOptions { Port = 0 }))
{
PlanningVisualizationSessionInfo info = session.Start(StaticSnapshot());
Verification.Equal("127.0.0.1", info.Uri.Host, "server binds loopback");
Verification.Equal(403, SendStatus(info.Uri.Port, "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n"),
"missing token is forbidden");
Verification.Equal(405, SendStatus(info.Uri.Port,
"POST /?token=" + info.Token + " HTTP/1.1\r\nHost: localhost\r\n\r\n"),
"POST is rejected");
Verification.Equal(200, SendStatus(info.Uri.Port,
"GET /api/bootstrap?token=" + info.Token + " HTTP/1.1\r\nHost: localhost\r\n\r\n"),
"authorized bootstrap succeeds");
session.Stop();
}
Verification.True(CanBindReleasedPort(port), "stop releases port");
```
Also send an unknown authorized route, a malformed request line, and a header larger than `16 KiB`; assert `404`, `400`, and `400`. Hold two authorized SSE connections open and assert a third receives `503`. Read one normal JSON response as bytes and assert its `Content-Length` equals the UTF-8 body length.
Add a slow SSE client that stops reading, publish 10,000 tiny snapshots on a task, and assert the publish task completes within one second. The assertion is about `Publish`, not socket delivery.
Before connecting any SSE client, publish one distinct cycle summary, wait two refresh periods for the dispatcher, and repeat for three summaries. Then connect and assert the first event contains all three summaries. This respects the capacity-one latest-frame contract while proving history collection is service-owned and does not depend on an open browser.
- [ ] **Step 2: Run to verify RED**
Run the visualization host. Expected: compilation fails because the server and facade do not exist.
- [ ] **Step 3: Implement bounded request parsing and routing**
Use `TcpListener(IPAddress.Loopback, validated.Port)`. For port `0`, read the assigned port from `LocalEndpoint`. The request reader must:
```csharp
const int MaximumHeaderBytes = 16 * 1024;
static readonly TimeSpan RequestReadTimeout = TimeSpan.FromSeconds(2d);
```
Read until `\r\n\r\n`, reject a buffer that reaches the cap, split the first line into exactly method/target/version, allow only origin-form paths, and URL-decode only the `token` query value. Never accept a caller-provided filesystem path.
The server owns one dispatcher task in addition to the accept loop. At `1 / RefreshRateHz`, the dispatcher reads `LatestVisualizationFrameStore.TryReadAfter`, appends a new non-null cycle summary to `BoundedCycleHistory` even when no browser is connected, and serializes only when at least one SSE client exists. It offers the resulting payload to each client's capacity-one outbound slot with `Interlocked.Exchange`.
Each `SseClientConnection` owns its socket writer. A slow socket can block only its own writer; newer dispatcher payloads overwrite that client's unsent slot. Apply a one-second write timeout and disconnect the client on timeout. The writer emits:
```text
event: frame
data: {camelCase JSON containing snapshot and bounded history}
```
The SSE response uses `Content-Type: text/event-stream; charset=utf-8`, `Cache-Control: no-cache`, and `Connection: keep-alive`. Normal JSON responses use `application/json; charset=utf-8`; HTML/CSS/JavaScript use explicit UTF-8 content types and byte-accurate `Content-Length`.
All socket exceptions are caught inside the client task. A server-fatal exception sets `FaultReason`, cancels the server, and never escapes through `Publish`.
On normal `Stop`, offer one `event: end` payload with Chinese reason `会话已结束` to every client, allow at most `100 ms` for best-effort flush, then close sockets and the listener. Shutdown must never wait indefinitely for a browser.
- [ ] **Step 4: Implement idempotent facade lifecycle**
`PlanningVisualizationSession.Start` validates options and creates a 256-bit token using APIs available in `netstandard2.0`:
```csharp
byte[] tokenBytes = new byte[32];
using (RandomNumberGenerator random = RandomNumberGenerator.Create())
random.GetBytes(tokenBytes);
string token = BitConverter.ToString(tokenBytes).Replace("-", string.Empty).ToLowerInvariant();
```
It then starts the server and returns:
```csharp
new PlanningVisualizationSessionInfo(
new Uri("http://127.0.0.1:" + port + "/?token=" + token), token);
```
`Stop` swaps the server field to null under a lifecycle lock, then cancels and disposes outside the lock. `Dispose` calls `Stop`. Calling `Publish` before start or after stop is a no-op; passing null still throws.
- [ ] **Step 5: Run server checks GREEN**
Run:
```powershell
dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
```
Expected: exit `0`, PASS line, loopback ports released, no unobserved task exception.
- [ ] **Step 6: Commit the transport**
```powershell
git add -- ClumsyPilot/TrajectoryPlanningVisualization/Runtime/LoopbackHttpRequestReader.cs ClumsyPilot/TrajectoryPlanningVisualization/Runtime/SseClientConnection.cs ClumsyPilot/TrajectoryPlanningVisualization/Runtime/LoopbackVisualizationServer.cs ClumsyPilot/TrajectoryPlanningVisualization/PlanningVisualizationSession.cs ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/Program.cs ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/ServerChecks.cs
git commit -m "feat: serve planning snapshots on loopback"
```
---
### Task 4: Embedded Chinese scientific dashboard
**Files:**
- Create: `ClumsyPilot/TrajectoryPlanningVisualization/Web/index.html`
- Create: `ClumsyPilot/TrajectoryPlanningVisualization/Web/app.css`
- Create: `ClumsyPilot/TrajectoryPlanningVisualization/Web/app.js`
- Create: `ClumsyPilot/TrajectoryPlanningVisualization/Runtime/EmbeddedWebAssets.cs`
- Create: `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/SampleSnapshotFactory.cs`
- Modify: `ClumsyPilot/TrajectoryPlanningVisualization/TrajectoryPlanningVisualization.csproj`
- Modify: `ClumsyPilot/TrajectoryPlanningVisualization/Runtime/LoopbackVisualizationServer.cs`
- Modify: `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/Program.cs`
- Modify: `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/WebAssetChecks.cs`
**Interfaces:**
- Produces: four tabs with stable IDs `overview`, `ls-st`, `kinematics`, `history-config`.
- Required chart IDs: `ls`, `st`, `curvature-s`, `curvature-t`, `velocity-t`, `acceleration-t`, `jerk-t`, and `yaw-rate-t`.
- The client gets `/api/bootstrap` once and consumes `/api/events` through `EventSource`.
- [ ] **Step 1: Write failing embedded-resource and content checks**
Create `WebAssetChecks`, call it after `ServerChecks`, read resources through `EmbeddedWebAssets`, and assert:
```csharp
Verification.Contains(html, "路径总览", "overview Chinese title");
Verification.Contains(html, "LS / ST", "LS/ST tab");
Verification.Contains(html, "曲率与运动学", "kinematics tab");
Verification.Contains(html, "周期历史", "history tab");
Verification.Contains(html, "生效配置", "configuration panel");
Verification.Contains(css, "--current-trajectory: #1769aa", "scientific current color");
Verification.Contains(css, "stroke-width: 1.1", "thin scientific line");
Verification.Contains(js, "末点后无时间区间", "jerk terminal explanation");
Verification.Contains(html, "occupancy-grid", "single occupancy canvas exists");
Verification.Contains(html, "world-overlay", "SVG trajectory overlay exists");
Verification.Contains(js, "atob", "compact occupancy bitset is decoded in browser");
Verification.True(!html.Contains("http://") && !html.Contains("https://"), "page has no CDN URL");
```
- [ ] **Step 2: Run to verify RED**
Run the visualization host. Expected: failure because resources are absent.
- [ ] **Step 3: Implement page structure and scientific CSS**
Add the three web files to the library as embedded resources:
```xml
<ItemGroup>
<EmbeddedResource Include="Web\index.html" LogicalName="TrajectoryPlanningVisualization.Web.index.html" />
<EmbeddedResource Include="Web\app.css" LogicalName="TrajectoryPlanningVisualization.Web.app.css" />
<EmbeddedResource Include="Web\app.js" LogicalName="TrajectoryPlanningVisualization.Web.app.js" />
</ItemGroup>
```
The page must include:
```html
<header><h1>EM 轨迹规划观察台</h1><div id="live-state">等待数据</div></header>
<nav aria-label="图表页签">
<button data-tab="overview">路径总览</button>
<button data-tab="ls-st">LS / ST</button>
<button data-tab="kinematics">曲率与运动学</button>
<button data-tab="history-config">周期历史与生效配置</button>
</nav>
<main>
<section id="overview">
<div class="world-stack">
<canvas id="occupancy-grid" aria-label="占用栅格底图"></canvas>
<svg id="world-overlay" role="img" aria-label="全局路径与当前规划段"></svg>
</div>
</section>
<section id="ls-st" hidden></section>
<section id="kinematics" hidden></section>
<section id="history-config" hidden></section>
</main>
```
The HTML references `/app.css?token=__SESSION_TOKEN__` and `/app.js?token=__SESSION_TOKEN__`. When serving only `index.html`, replace that exact placeholder with the lowercase hexadecimal session token; the token alphabet requires no HTML escaping. `app.js` reads the token from `window.location.search` and appends `encodeURIComponent(token)` to `/api/bootstrap` and `/api/events`. This keeps every route authorized without cookies or custom EventSource headers.
CSS uses a white canvas, `#20252b` text, `#d9dde1` grid, `#1769aa` current line, `#8d959d` previous dashed line, `#d87918` handoff/gear marker, and `#b42318` only for failures/limits. SVG data lines are `1.1px`; axes are `0.8px`; grids are `0.55px`.
- [ ] **Step 4: Implement SVG rendering and stale-state behavior**
`app.js` must:
- preserve equal X/Y scale in the overhead plot;
- decode the row-major occupancy bitset once and draw occupied cells on one Canvas below the SVG overlay, without one DOM node per grid cell;
- derive plot bounds from finite values and add a 5% pad;
- render series as SVG paths without thick strokes or point-per-sample DOM nodes;
- label axes exactly from snapshot `xAxisLabel/yAxisLabel`;
- render configuration as Chinese name, raw field, invariant value, unit;
- render the active segment and current horizon above faded global segments;
- show “末点后无时间区间” beside `jerk-t` rather than adding a final sample;
- show “数据已过期” when no frame arrives for `2 / refreshRateHz` seconds;
- show “会话已结束” only after the explicit terminal `event: end`; on an ordinary `EventSource` error show “连接中断,正在重连” while the independent stale timer may show “数据已过期”;
- install `window.onerror` and `unhandledrejection` handlers that show “页面绘图异常” and stop only that tab's redraw loop;
- never evaluate HTML from snapshot strings; use `textContent` for labels and values.
- [ ] **Step 5: Run GREEN and build from a non-repository working directory**
Run the host from repository root, then:
```powershell
$visualizationHost = (Resolve-Path 'ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj').Path
Push-Location $env:TEMP
try {
dotnet run --project $visualizationHost
} finally { Pop-Location }
```
Expected: both invocations print PASS, proving assets come from the assembly rather than current directory.
- [ ] **Step 6: Add a deterministic timed smoke mode**
Add `SampleSnapshotFactory` that creates two direction segments, a gear marker, effective configuration groups and all required charts without referencing EMPlanner. Extend `Program` so no arguments runs checks, while `--smoke-seconds N` starts a session, publishes the sample at 10 Hz for exactly `N` seconds, prints the full URI once, then stops.
```csharp
if (args.Length == 2 && args[0] == "--smoke-seconds" &&
int.TryParse(args[1], out int seconds) && seconds > 0)
return RunSmoke(seconds);
```
Verify with:
```powershell
dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj -- --smoke-seconds 1
```
Expected: exit `0`, one `http://127.0.0.1:<port>/?token=<token>` line, and clean shutdown after one second.
- [ ] **Step 7: Commit the dashboard**
```powershell
git add -- ClumsyPilot/TrajectoryPlanningVisualization/Web ClumsyPilot/TrajectoryPlanningVisualization/TrajectoryPlanningVisualization.csproj ClumsyPilot/TrajectoryPlanningVisualization/Runtime/EmbeddedWebAssets.cs ClumsyPilot/TrajectoryPlanningVisualization/Runtime/LoopbackVisualizationServer.cs ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/Program.cs ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/SampleSnapshotFactory.cs ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/WebAssetChecks.cs
git commit -m "feat: add scientific planning dashboard"
```
---
### Task 5: Library documentation and final standalone verification
**Files:**
- Create: `ClumsyPilot/TrajectoryPlanningVisualization/README.md`
- Modify: `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/ContractChecks.cs`
**Interfaces:**
- Documents the exact facade, route/security boundary, performance model, known `localhost` scope, chart conventions, and adapter example.
- [ ] **Step 1: Add a failing documentation check**
Assert the README contains `OBSERVE_ONLY`, `127.0.0.1`, `capacity 1`, `60`, `10 Hz`, `j[i]`, `末点后无时间区间`, and the exact `Start/Publish/Stop` example.
- [ ] **Step 2: Run to verify RED**
Expected: host fails because README is absent.
- [ ] **Step 3: Write the README**
Include this minimal use sequence:
```csharp
using var visualization = new PlanningVisualizationSession(
new PlanningVisualizationOptions { Port = 0, RefreshRateHz = 10d, HistoryCycleLimit = 60 });
PlanningVisualizationSessionInfo info = visualization.Start(staticSnapshot);
visualization.Publish(dynamicSnapshot);
visualization.Stop();
```
Explicitly state that callers build all domain-specific charts, the server is read-only, `Publish` drops old frames, and no browser event can flow back into planning.
- [ ] **Step 4: Run complete standalone verification**
```powershell
dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
dotnet build ClumsyPilot/TrajectoryPlanningVisualization/TrajectoryPlanningVisualization.csproj
dotnet build ClumsyPilot/ClumsyPilot.csproj -p:ExcludeLegacyAutoAvoidance=true
git diff --check
```
Expected: all commands exit `0`; host prints one PASS line; builds report zero errors; `git diff --check` reports no whitespace errors in task files.
- [ ] **Step 5: Commit documentation**
```powershell
git add -- ClumsyPilot/TrajectoryPlanningVisualization/README.md ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/ContractChecks.cs
git commit -m "docs: document planning visualization library"
```
Plan 1 is complete when this standalone verification is green. Do not begin EM-specific data conversion before this boundary is reviewed.
@@ -1,588 +0,0 @@
# EM Full-Direction-Segment Staged Execution Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to execute only the current phase in a fresh window. Do not use subagents. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Execute the approved full-direction-segment planning and observation repair across nine bounded windows with fresh verification, independent handoffs, and copy-ready next-phase prompts.
**Architecture:** The functional design and 11-task master implementation plan remain authoritative. This plan groups those tasks into nine phase gates; each window reads only its assigned tasks plus the previous handoff, commits implementation in the master-plan boundaries, then commits a factual handoff separately. The final response generates the next prompt only after the handoff commit exists, so every copied prompt carries real hashes.
**Tech Stack:** Git on Windows/PowerShell, C#/.NET 8, existing EM/OSQP verification hosts, vanilla HTML/CSS/JavaScript/SVG/Canvas, Node.js 24 with test-only jsdom 29.1.1, Markdown handoffs.
## Global Constraints
- Repository root is `D:\Users\Desktop\项目\prakrobot\ParkingRobot` and expected branch is `trajplanner`.
- Functional design: `docs/superpowers/specs/2026-08-06-em-full-direction-segment-visualization-repair-design.md`.
- Master plan: `docs/superpowers/plans/2026-08-06-em-full-direction-segment-visualization-repair.md`.
- Staged-execution design: `docs/superpowers/specs/2026-08-06-em-full-direction-segment-staged-execution-design.md`.
- Every implementation window reads `executing-plans`, `test-driven-development`, and `verification-before-completion`; read `systematic-debugging` only after a real failure or unexpected result.
- Do not re-run brainstorming, redesign approved behavior, expand features, or spawn subagents.
- MovementTest remains `OBSERVE_ONLY`; never add chassis, steering, braking, motor, or gear writes.
- Keep all unrelated dirty and staged work intact. Before editing a dirty target file, record its diff and preserve unrelated hunks.
- Use `apply_patch` for edits and explicit `git add -- <paths>` commands. Never use `git add .` or `git add -A`.
- A phase may not implement work assigned to a subsequent phase. Minimal signature propagation is allowed only when the master plan lists that file in the current task.
- Each phase implementation commit follows the master plan. The handoff is a separate commit named `docs: record EM full-direction phase XX handoff`.
- A completed phase final response includes the real implementation and handoff hashes plus one copy-ready prompt for the next phase.
- A blocked phase creates a `阻塞` handoff and outputs a recovery prompt for the same phase, never a later-phase prompt.
- Phase 9 automated acceptance and supervised vehicle acceptance are separate checkpoints. Unsafe or unavailable vehicle work remains incomplete.
## Common Phase Start
Every phase prompt supplies the exact expected previous implementation/handoff hashes. At startup run:
```powershell
git branch --show-current
git rev-parse HEAD
git log -5 --oneline
git status --short
git diff --cached --name-only
```
Then run `git merge-base --is-ancestor` for every hash named by the prompt. Read the four authority documents listed above, the current master-plan Task, and the previous handoff in full. If related code changed after the previous handoff, rerun the affected previous-phase GREEN commands before editing.
## Common Phase Finish
Before any success claim or commit:
1. Run the phase RED and confirm the first failure is the intended missing behavior.
2. Run all phase GREEN/regression commands and read complete output.
3. Run `git diff --check` over the phase range and inspect `git diff --stat` plus `git diff --cached --name-only`.
4. Audit actuator calls whenever MovementTest or observer code is touched.
5. Commit only exact current-phase files.
6. Create the phase handoff with `apply_patch`, recording entry HEAD, implementation hashes, files, interfaces, RED/GREEN evidence, dirty-worktree preservation, warnings, omissions, and next-phase facts.
7. Commit only the handoff file.
8. Use the resulting handoff hash in the final answers next-phase prompt.
---
### Task 1: Phase 01 — Planning scope, configuration, and full-segment selection
**Master-plan scope:** Task 1 and Task 2 only.
**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`
- 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/FoundationChecks.cs`
- Test: `ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalModelChecks.cs`
- Test: `ClumsyPilot/tests/EMPlannerVerificationHost/EmPlanningServiceChecks.cs`
- Handoff: `docs/superpowers/handoffs/em-full-direction-visualization/phase-01.md`
**Interfaces:**
- Produces `EmPlanningScope.RollingHorizon` and `FullDirectionSegment`.
- Produces request/metadata scope, desired speeds, adaptive-resource configuration, terminal tolerances, and explicit `NoProgress`, `TerminalPoseMismatch`, and `FullSegmentResourceLimitExceeded` statuses.
- Produces scope-aware `PlanningHorizonSelector.Select` that keeps rolling behavior intact and selects the true direction boundary for full mode.
- [ ] **Step 1: Execute master Task 1 RED/GREEN/commit**
Follow every checkbox in master Task 1. Required focused commands:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- foundation
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-planning-service
```
Expected GREEN: `PASS foundation` and `PASS em-planning-service`.
Commit exactly as:
```powershell
git commit -m "feat: define full-direction EM planning scope"
```
- [ ] **Step 2: Execute master Task 2 RED/GREEN/commit**
Follow every checkbox in master Task 2. Required commands:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-model
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-planning-service
```
Expected GREEN: `PASS longitudinal-model` and `PASS em-planning-service`; full mode reaches the true Goal/GearSwitch boundary and rolling remains truncated by its configured window.
Commit exactly as:
```powershell
git commit -m "feat: select complete EM direction segments"
```
- [ ] **Step 3: Run phase regression and handoff**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-core-all
git diff --check
```
Expected: every `em-core-all` component PASS and no new whitespace errors.
Create `phase-01.md`, then commit only it:
```powershell
git add -- docs/superpowers/handoffs/em-full-direction-visualization/phase-01.md
git commit -m "docs: record EM full-direction phase 01 handoff"
```
**Exit gate:** Task 12 commits and handoff are reachable; no adaptive knot schedule or MovementTest/Web/Painter work exists.
---
### Task 2: Phase 02 — Adaptive full-segment ST schedule
**Master-plan scope:** Task 3 only.
**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`
- Handoff: `docs/superpowers/handoffs/em-full-direction-visualization/phase-02.md`
**Interfaces:**
- Consumes Phase 01 scope/configuration.
- Produces immutable `LongitudinalKnotSchedule` and `FullDirectionSegmentScheduleBuilder.TryBuild`.
- Makes `LongitudinalPlanningInput.KnotSchedule` the sole optimization-knot source while publication remains separately sampled.
- [ ] **Step 1: Execute master Task 3 RED/GREEN**
Run the exact model RED before implementation, then all Task 3 changes. Required GREEN:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-model
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-integration
```
Expected: both PASS, including short/long derived duration, curvature/stop breakpoints, knot cap failure, and publication-density independence.
- [ ] **Step 2: Commit and run regression**
```powershell
git commit -m "feat: derive adaptive full-segment ST schedule"
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-core-all
git diff --check
```
Expected: commit contains only Task 3 paths; `em-core-all` PASS.
- [ ] **Step 3: Write and commit Phase 02 handoff**
```powershell
git add -- docs/superpowers/handoffs/em-full-direction-visualization/phase-02.md
git commit -m "docs: record EM full-direction phase 02 handoff"
```
**Exit gate:** derived `T_end` and adaptive knots are validated; no static-start objective, terminal pose, MovementTest, or visualization work is included.
---
### Task 3: Phase 03 — Static start, no-progress rejection, and terminal publication gates
**Master-plan scope:** Task 4 and Task 5 only.
**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/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/LongitudinalModelChecks.cs`
- Test: `ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalIntegrationChecks.cs`
- Test: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryChecks.cs`
- Test: `ClumsyPilot/tests/EMPlannerVerificationHost/EmPlanningServiceChecks.cs`
- Handoff: `docs/superpowers/handoffs/em-full-direction-visualization/phase-03.md`
**Interfaces:**
- Consumes adaptive reference speed and scope.
- Produces nonzero motion from `v0=0`, `NoProgress`, terminal 3 cm/5° normalized-yaw validation, and publication sample cap.
- [ ] **Step 1: Execute master Task 4 with real RED evidence**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-integration
```
Expected RED: the clear 5 m static-start fixture remains all-zero or lacks measurable progress.
After minimal implementation run:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-model
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-integration
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-planning-service
```
Expected: all PASS; the explicit all-zero candidate returns `NoProgress` and publishes no trajectory.
Commit: `fix: accelerate EM trajectories from rest`.
- [ ] **Step 2: Execute master Task 5 RED/GREEN**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory
```
Expected RED: terminal world pose is not yet validated.
After implementation run `trajectory` and `em-planning-service`; expect yaw wrap, 3 cm/5° boundary, sample cap, terminal anchor, and `N-1` jerk checks PASS.
Commit: `feat: validate EM terminal world pose`.
- [ ] **Step 3: Run phase regression and handoff**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-core-all
git diff --check
```
Create/commit `phase-03.md` with message `docs: record EM full-direction phase 03 handoff`.
**Exit gate:** a full clear segment accelerates from rest and stops at a pose-valid terminal; invalid zero progress/pose/sample count cannot publish.
---
### Task 4: Phase 04 — One-shot MovementTest direction segments
**Master-plan scope:** Task 6 only.
**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`
- Handoff: `docs/superpowers/handoffs/em-full-direction-visualization/phase-04.md`
**Interfaces:**
- Produces `TrajectoryObservationSettings.PlanningScope`, full-mode defaults, and one plan attempt per active segment.
- Preserves stop hold plus three signed-speed samples before N+1 activation.
- [ ] **Step 1: Run Task 6 RED and implement one-shot state**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
```
Expected RED: current coordinator cadence starts more than one planning cycle in full scope.
Implement only Task 6 and rerun the same command. Expected GREEN: one plan for N, observation continues, and one new plan starts only after confirmed N→N+1.
- [ ] **Step 2: Audit read-only safety and commit**
```powershell
rg -n "SendXYThSpeed|SendMotion|DriveStop|PredefinedDriveStop|AccumulateSpeed|SetGear|SetBrake" ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest
git diff --check
```
Expected: no actuator call in observer runtime sources.
Commit: `feat: observe one full EM direction segment`.
- [ ] **Step 3: Write and commit Phase 04 handoff**
Commit `phase-04.md` with `docs: record EM full-direction phase 04 handoff`.
**Exit gate:** MovementTest is full-scope by explicit setting, plans once per direction segment, retains explicit waiting state, and remains read-only.
---
### Task 5: Phase 05 — Observation snapshot and chart semantics
**Master-plan scope:** Task 7 only.
**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`
- Handoff: `docs/superpowers/handoffs/em-full-direction-visualization/phase-05.md`
**Interfaces:**
- Produces `VisualizationChartAnnotation` and distinct coarse/Local G2/active/previous/current kinds.
- Produces real `s_end`, gear/final marker, vehicle/plan-start, correct LS/ST/kinematic axes, and real curvature limit.
- [ ] **Step 1: Run both Task 7 RED hosts**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
```
Expected RED: duplicate current horizon, missing annotations, or incorrect curvature-limit semantics.
- [ ] **Step 2: Implement Task 7 and run GREEN**
Run both commands again. Expected: both PASS; full mode contains exactly one current trajectory; jerk current series has `N-1` points; LS uses `ReferenceS`, ST uses `PathS`; curvature limit comes from vehicle geometry.
Commit: `fix: publish distinct EM observation semantics`.
- [ ] **Step 3: Regression, audit, and handoff**
Run `trajectory-observation`, the visualization host, actuator `rg`, and `git diff --check`. Commit `phase-05.md` as `docs: record EM full-direction phase 05 handoff`.
**Exit gate:** immutable snapshots carry all correct semantics before any Web or Painter rendering changes.
---
### Task 6: Phase 06 — DOM/SVG Web rendering repair
**Master-plan scope:** Task 8 only.
**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`
- Handoff: `docs/superpowers/handoffs/em-full-direction-visualization/phase-06.md`
**Interfaces:**
- Adds test-only Node/jsdom DOM verification.
- Repairs hidden tabs, empty overview, y ticks/units, stable domains, semantic layer order, vehicle/boundary markers, and thin paper style.
- [ ] **Step 1: Install locked test dependency and run RED**
Create the exact Task 8 `package.json`, run:
```powershell
npm install --package-lock-only --prefix ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom
npm test --prefix ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom
```
Expected RED: hidden-section CSS, y ticks, semantic classes, or empty-state assertions fail.
- [ ] **Step 2: Implement Task 8 and run GREEN**
```powershell
npm test --prefix ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom
dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
```
Expected: all DOM tests pass and .NET host prints `PASS trajectory-planning-visualization`.
Commit: `fix: repair EM observation web charts`.
- [ ] **Step 3: Verify production assets and handoff**
Confirm no npm package is copied into plugin output, run `git diff --check`, and commit `phase-06.md` as `docs: record EM full-direction phase 06 handoff`.
**Exit gate:** existing four-tab page renders correct data/axes/layers with no layout redesign; zoom is not yet implemented.
---
### Task 7: Phase 07 — Per-chart viewport zoom
**Master-plan scope:** Task 9 only.
**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`
- Handoff: `docs/superpowers/handoffs/em-full-direction-visualization/phase-07.md`
**Interfaces:**
- Produces per-chart viewport-only box zoom, wheel zoom, reset, and fullscreen.
- Never mutates snapshot arrays or sends planner configuration.
- [ ] **Step 1: Run frozen-data interaction RED**
```powershell
npm test --prefix ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom
```
Expected RED: zoom controls and viewport state are absent.
- [ ] **Step 2: Implement Task 9 and run GREEN**
```powershell
npm test --prefix ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom
dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
```
Expected: wheel/box/reset/fullscreen tests pass; serialized input snapshot remains byte-identical.
Commit: `feat: add observation chart viewport zoom`.
- [ ] **Step 3: Handoff**
Run `git diff --check`; commit `phase-07.md` as `docs: record EM full-direction phase 07 handoff`.
**Exit gate:** every chart has independent local navigation and no Native Painter changes.
---
### Task 8: Phase 08 — Native Painter correctness
**Master-plan scope:** Task 10 only.
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPresentation.cs`
- Test: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs`
- Test: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationVisualizationChecks.cs`
- Handoff: `docs/superpowers/handoffs/em-full-direction-visualization/phase-08.md`
**Interfaces:**
- Consumes Phase 05 semantics.
- Produces equal-scale world geometry, correct LS/ST axes, thin vehicle outline/heading ray, and correct `s_end`/gear/final markers.
- [ ] **Step 1: Run Painter geometry RED**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
```
Expected RED: the current pose uses a large `endArrow: true` marker or old axis semantics.
- [ ] **Step 2: Implement Task 10 and run GREEN**
Run the same command. Expected: `PASS trajectory-observation`; tests prove the large triangle is absent and x/y use one scale.
Commit: `fix: correct EM observation painter geometry`.
- [ ] **Step 3: Safety audit and handoff**
```powershell
rg -n "SendXYThSpeed|SendMotion|DriveStop|PredefinedDriveStop|AccumulateSpeed|SetGear|SetBrake" ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest
git diff --check
```
Commit `phase-08.md` as `docs: record EM full-direction phase 08 handoff`.
**Exit gate:** Native Painter is semantically correct but not redesigned; Web remains primary.
---
### Task 9: Phase 09 — Documentation, automated acceptance, and vehicle continuation
**Master-plan scope:** Task 11 only.
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/README.md`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md`
- Modify: `ClumsyPilot/TrajectoryPlanningVisualization/README.md`
- Create: `docs/superpowers/handoffs/em-full-direction-visualization/phase-09.md`
- Create only after supervised execution: `docs/superpowers/handoffs/em-full-direction-visualization/phase-09-vehicle.md`
**Interfaces:**
- Produces operator documentation and fresh automated evidence.
- Produces either final supervised vehicle acceptance or an exact continuation/recovery prompt without false completion.
- [ ] **Step 1: Update docs and commit**
Follow master Task 11 Step 1 exactly. Commit only the three READMEs as:
```powershell
git commit -m "docs: explain full-direction EM observation"
```
- [ ] **Step 2: Run the complete automated suite**
```powershell
npm test --prefix ClumsyPilot/tests/TrajectoryPlanningVisualizationWebDom
dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-all
dotnet build ClumsyPilot/ClumsyPilot.csproj -p:ExcludeLegacyAutoAvoidance=true
rg -n "SendXYThSpeed|SendMotion|DriveStop|PredefinedDriveStop|AccumulateSpeed|SetGear|SetBrake" ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest
git diff --check
```
Expected: Node tests pass; visualization/observation/em-all PASS; build exits 0; no observer actuator calls; no new whitespace errors.
- [ ] **Step 3: Run deterministic local smoke**
```powershell
dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj -- --smoke-seconds 60
```
Verify the tokenized page and port cleanup exactly as master Task 11. Record screenshots/log paths.
- [ ] **Step 4: Commit automated handoff**
Write `phase-09.md`. If supervision/safety is unavailable, set status `自动验收完成但实车待验` and include every pending vehicle item. Commit only it:
```powershell
git add -- docs/superpowers/handoffs/em-full-direction-visualization/phase-09.md
git commit -m "docs: record EM full-direction phase 09 handoff"
```
Output a full Phase 09 vehicle-continuation prompt using the real handoff hash.
- [ ] **Step 5: Execute vehicle continuation only when supervised and safe**
A new window verifies Phase 09 evidence reachability and reruns affected automation if related code changed. It then performs the eight-item vehicle checklist from master Task 11 without writing hardware commands.
- [ ] **Step 6: Record final vehicle status**
If all items pass, create `phase-09-vehicle.md` with status `完成` and commit:
```powershell
git add -- docs/superpowers/handoffs/em-full-direction-visualization/phase-09-vehicle.md
git commit -m "docs: record EM visualization vehicle acceptance"
```
If a real regression appears, write status `阻塞`, record the first valid failure/reproduction/safety impact, commit only the same file with message `docs: record blocked EM visualization vehicle acceptance`, and output a Phase 09 vehicle recovery prompt.
**Exit gate:** only fresh supervised vehicle evidence permits the statement that the overall work is complete. Final completion has no next-phase prompt.
---
## Phase Prompt Construction Rule
After each handoff commit, construct the next prompt from factual repository state. The prompt must include:
- absolute repository path and expected branch;
- exact current HEAD, previous implementation hashes, and handoff hash;
- exact required documents and current sections/tasks;
- startup ancestry/status/dirty-worktree checks;
- exact allowed files and forbidden later-phase work;
- exact RED/GREEN/regression/audit commands;
- implementation and handoff commit messages;
- completion output and blocked recovery behavior.
Never write an unresolved hash variable or depend on chat history. The prompt may instruct the next agent to read a file, but it must say why and identify the exact phase/task.
@@ -1,632 +0,0 @@
# EM Observation Web Visualization Staged Execution 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:** Execute the approved EM observe-only web visualization work across eight fresh Codex windows with bounded context, reproducible verification, exact Git ownership, and durable handoffs.
**Architecture:** The code-level work remains defined by the approved library and EM integration plans. This plan is the orchestration layer: each task is one isolated window, consumes the preceding handoff, executes a dependency-aligned slice of the original plans, records evidence, and emits the next task's complete prompt.
**Tech Stack:** Codex staged sessions, PowerShell, Git, C# 10, `netstandard2.0`, `net10.0-windows` verification hosts, Markdown handoff records.
## Global Constraints
- Repository root is `D:\Users\Desktop\项目\prakrobot\ParkingRobot`.
- The approved design commit `b0b79e5` must remain an ancestor of every stage baseline.
- Execute stages strictly in order; do not advance after a blocked or unverified stage.
- Use one primary agent per window and do not spawn subagents.
- Preserve all unrelated dirty-worktree and index changes; never reset, clean, restore, or include them in a stage commit.
- Read only the current stage's named plan sections and source files; locate additional symbols with targeted `rg` before opening files.
- Follow TDD order from the original plan and use systematic debugging for unexpected failures.
- Keep `OBSERVE_ONLY`; never add or call actuator write APIs.
- A success claim requires fresh evidence collected under `verification-before-completion`.
- Each completed stage creates and commits exactly one handoff file under `docs/superpowers/handoffs/em-observation-web/`.
- A blocked stage emits a same-stage recovery prompt, not the next-stage prompt.
- The final response ends with the next prompt copied verbatim from this plan, except after fully completed Stage 8.
## Exact Original-Task Ownership
| Stage | Original plan ownership |
| --- | --- |
| 1 | 类库 Task 12 |
| 2 | 类库 Task 3 |
| 3 | 类库 Task 45 |
| 4 | 集成计划 Task 12 |
| 5 | 集成计划 Task 3 |
| 6 | 集成计划 Task 45 |
| 7 | 集成计划 Task 67 |
| 8 | 集成计划 Task 8 |
---
## Shared Session Protocol
Every stage prompt below already contains the shared protocol. The agent must keep the original task-level commits specified by the code-level plans. After those commits and fresh verification, it creates its handoff file with actual facts under these headings:
```markdown
# EM 观察网页可视化阶段 NN 交接
状态:完成
阶段目标:本阶段实际完成的目标
基线提交:启动阶段时记录的提交哈希
实现提交:本阶段代码和文档提交哈希列表
修改文件:本阶段实际修改和创建的文件列表
新增或确认的接口:可供下一阶段依赖的接口和行为
验证命令与结果:逐条记录命令、退出码和关键 PASS 输出
未运行的验证及原因:没有未运行项时明确写“无”
已知警告或遗留问题:没有时明确写“无”
与原计划的偏差:没有时明确写“无”
下一阶段注意事项:下一阶段必须核查的事实
```
Handoff commits use `docs: record EM visualization phase NN handoff`. The handoff records implementation commits; it does not need to contain its own commit hash.
If a required command fails, the stage agent writes the same handoff fields with `状态:阻塞`, does not mark unchecked plan work complete, and returns a newly written recovery prompt that tells a fresh agent to resume the same stage from the recorded failure. It must include the exact failed command, exit code, first actionable error, current commits, relevant files, and remaining verification.
---
### Task 1: Stage 1 — Visualization library foundations
**Files:**
- Read: `docs/superpowers/specs/2026-08-06-em-observation-web-staged-execution-design.md`
- Read: `docs/superpowers/specs/2026-08-05-em-observation-web-visualization-design.md`
- Read: `docs/superpowers/plans/2026-08-05-trajectory-planning-visualization-library.md` Task 12
- Create/modify: every exact file listed by library Task 12
- Create: `docs/superpowers/handoffs/em-observation-web/phase-01.md`
**Interfaces:**
- Consumes: approved design commit `b0b79e5`, existing `ClumsyPilot.csproj`, Newtonsoft.Json 13.0.4.
- Produces: standalone visualization project, immutable contracts, `PlanningVisualizationOptions`, capacity-one latest frame, bounded cycle history, and stable JSON required by Stage 2.
- [ ] **Step 1: Open a fresh window and paste this prompt exactly**
```text
你正在执行 EM 观察网页可视化的阶段 1/8:可视化类库基础。只完成本阶段,不要提前实现 HTTP/SSE、网页、EM Adapter 或 MovementTest 集成,也不要派生子智能体。
仓库根目录:D:\Users\Desktop\项目\prakrobot\ParkingRobot
设计已经批准,不要重新进行 brainstorming。开始前完整阅读并遵守:
1. C:\Users\admin\.codex\skills\executing-plans\SKILL.md
2. C:\Users\admin\.codex\skills\test-driven-development\SKILL.md
3. C:\Users\admin\.codex\skills\verification-before-completion\SKILL.md
遇到任何非预期测试或构建失败时,再完整阅读 C:\Users\admin\.codex\skills\systematic-debugging\SKILL.md,然后先定位根因再修改。
必读资料:
- docs/superpowers/specs/2026-08-06-em-observation-web-staged-execution-design.md
- docs/superpowers/specs/2026-08-05-em-observation-web-visualization-design.md 的第 16、1012 节
- docs/superpowers/plans/2026-08-05-trajectory-planning-visualization-library.md 的标题、Global Constraints、File Structure、Task 1、Task 2
- ClumsyPilot/ClumsyPilot.csproj
启动检查:
- 执行 git branch --show-current、git rev-parse HEAD、git log -5 --oneline。
- 执行 git merge-base --is-ancestor b0b79e5 HEAD;若退出码非 0,停止并报告基线不包含已批准设计。
- 用 (git status --porcelain=v1 | Measure-Object).Count 统计全部脏项,但只展开本阶段文件路径的 git status --short 和 git diff。
- 执行 git diff --cached --name-status。不得重置、取消暂存、覆盖或提交任何既有无关改动。
- 运行 dotnet build ClumsyPilot/ClumsyPilot.csproj -p:ExcludeLegacyAutoAvoidance=true,记录基线退出码;若失败,先判断是否为本阶段开始前已存在的问题并在交接中保留证据。
实施范围是原类库计划 Task 1–2 的所有复选步骤,保持原顺序和 TDD:
- 建立 ClumsyPilot/TrajectoryPlanningVisualization 独立 netstandard2.0 项目;
- 建立独立 net10.0-windows 验证宿主;
- 实现不可变契约、防御性复制、有限数值验证和紧凑占用 bitset;
- 主项目排除嵌套源码并通过 ProjectReference 引用类库;
- 实现 PlanningVisualizationOptions、LatestVisualizationFrameStore、BoundedCycleHistory、VisualizationJson
- 严格执行原计划中的 RED、GREEN 和两个任务的独立提交。
只能修改原计划 Task 1–2 的 Files 清单。若当前阶段文件已有用户修改,先阅读 diff 并兼容;无法安全兼容时停止,不要覆盖。提交必须使用精确路径。若暂存区存在无关内容,使用仅包含当前阶段精确路径的提交方式,并在提交后用 git diff-tree --no-commit-id --name-status -r 实际提交哈希核对文件。
退出验证必须重新运行:
1. dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
2. dotnet build ClumsyPilot/TrajectoryPlanningVisualization/TrajectoryPlanningVisualization.csproj
3. dotnet build ClumsyPilot/ClumsyPilot.csproj -p:ExcludeLegacyAutoAvoidance=true
4. 对本阶段实际修改文件运行 git diff --check,并核对每个实现提交的文件树。
全部退出验证通过后,创建 docs/superpowers/handoffs/em-observation-web/phase-01.md。按照阶段执行设计第 8 节填写真实基线、实现提交、文件、接口、逐条命令/退出码、警告、偏差和下一阶段注意事项,不得把未运行写成通过。单独提交该文件,提交信息为 docs: record EM visualization phase 01 handoff,并核对该提交只含这一文件。
最终回答先用中文报告完成内容、实现/交接提交和新鲜验证证据。然后打开 docs/superpowers/plans/2026-08-06-em-observation-web-staged-execution.md,找到 Task 2 的提示词,并在最终回答最后一个 Markdown 代码块中逐字完整输出,供用户复制到新窗口。若本阶段阻塞,不得输出 Task 2;改为输出信息完整的阶段 1 恢复提示词。
```
- [ ] **Step 2: Accept only a verified Stage 1 handoff**
Expected: `phase-01.md` says `状态:完成`, names the contract/runtime commits, and records successful visualization-host and main-project builds.
---
### Task 2: Stage 2 — Restricted Loopback HTTP/SSE
**Files:**
- Read: Stage 1 handoff and library Task 3
- Create/modify: every exact file listed by library Task 3
- Create: `docs/superpowers/handoffs/em-observation-web/phase-02.md`
**Interfaces:**
- Consumes: Stage 1 immutable snapshots, options, frame store, history and JSON.
- Produces: authenticated loopback server, SSE connections and `PlanningVisualizationSession` facade required by Stage 3.
- [ ] **Step 1: Open a fresh window and paste this prompt exactly**
```text
你正在执行 EM 观察网页可视化的阶段 2/8:受限 Loopback HTTP/SSE。只完成本阶段,不要实现网页资源或任何 EM/MovementTest 适配,也不要派生子智能体。
仓库根目录:D:\Users\Desktop\项目\prakrobot\ParkingRobot
设计已经批准,不要重新进行 brainstorming。完整阅读并使用 executing-plans、test-driven-development、verification-before-completion;路径分别是:
- C:\Users\admin\.codex\skills\executing-plans\SKILL.md
- C:\Users\admin\.codex\skills\test-driven-development\SKILL.md
- C:\Users\admin\.codex\skills\verification-before-completion\SKILL.md
出现非预期失败时完整阅读并使用 C:\Users\admin\.codex\skills\systematic-debugging\SKILL.md。
必读资料:
- docs/superpowers/specs/2026-08-06-em-observation-web-staged-execution-design.md
- docs/superpowers/specs/2026-08-05-em-observation-web-visualization-design.md 的第 36、1012 节
- docs/superpowers/plans/2026-08-05-trajectory-planning-visualization-library.md 的 Global Constraints、File Structure、Task 3
- docs/superpowers/handoffs/em-observation-web/phase-01.md
- 阶段 1 新增的 Contracts、Runtime 和 PlanningVisualizationOptions 相关文件;只读取 Task 3 直接依赖的类型
启动检查:
- 记录当前分支、HEAD、最近 8 条提交。
- git merge-base --is-ancestor b0b79e5 HEAD 必须成功。
- phase-01.md 必须存在且状态为完成;逐一用 git show --stat 核对其中实现提交,并运行独立可视化验证宿主建立入口基线。
- 统计整个工作区脏项数量,只展开 Task 3 文件范围的状态/diff;检查暂存区并保护无关内容。
严格执行原类库计划 Task 3 的全部复选步骤和两个阶段:真实 socket RED 检查先行,再实现。必须落实:
- TcpListener 只绑定 IPAddress.Loopback
- 只接受受限 HTTP/1.1 GET16 KiB header 与 2 s 读取上限;
- 所有现有路由要求 256-bit 随机 token
- 最多两个 SSE 客户端,第三个返回 503;
- Publish 只做验证和 Interlocked.Exchange,不序列化、不等待 socket;
- 单 dispatcher 在无浏览器时仍收集有界周期摘要;
- 每客户端容量一发送槽与 1 s 写超时;
- Stop 最多等待 100 ms 发送 event:end 并可靠释放端口;
- Start/Publish/Stop/Dispose 幂等且服务故障不逃逸到规划发布调用。
只能修改 Task 3 的 Files 清单。不得绑定 0.0.0.0、IPAddress.Any 或局域网地址,不得改用 HttpListener、ASP.NET Core、Kestrel 或外部包。按原计划完成 transport 实现提交,提交后核对文件树。
退出验证必须重新运行:
1. dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
2. dotnet build ClumsyPilot/TrajectoryPlanningVisualization/TrajectoryPlanningVisualization.csproj
3. 确认真实 socket 检查覆盖 403/404/405/400/503、慢客户端非阻塞、无客户端历史、正常停止和端口重绑。
4. 对本阶段修改文件运行 git diff --check,并核对提交内容。
全部通过后创建 docs/superpowers/handoffs/em-observation-web/phase-02.md,按阶段设计第 8 节填写真实证据,单独提交为 docs: record EM visualization phase 02 handoff。最终回答先报告结果与提交,再从阶段执行手册 Task 3 复制完整提示词作为最后一个代码块。若阻塞,只输出阶段 2 恢复提示词,不得推进阶段 3。
```
- [ ] **Step 2: Accept only a verified Stage 2 handoff**
Expected: the handoff proves loopback-only binding, bounded request parsing, non-blocking publication and released ports with real-socket tests.
---
### Task 3: Stage 3 — Chinese scientific dashboard and library closure
**Files:**
- Read: Stage 2 handoff and library Task 45
- Create/modify: every exact file listed by library Task 45
- Create: `docs/superpowers/handoffs/em-observation-web/phase-03.md`
**Interfaces:**
- Consumes: Stage 2 session, bootstrap and SSE endpoints.
- Produces: embedded Chinese Canvas/SVG dashboard and documented, independently verified reusable library required by EM integration.
- [ ] **Step 1: Open a fresh window and paste this prompt exactly**
```text
你正在执行 EM 观察网页可视化的阶段 3/8:中文科研风网页与独立类库收口。只完成本阶段,不要开始 EM Adapter 或 MovementTest 集成,也不要派生子智能体。
仓库根目录:D:\Users\Desktop\项目\prakrobot\ParkingRobot
设计已经批准。完整阅读并使用 executing-plans、test-driven-development、verification-before-completion;发生非预期失败时再使用 systematic-debugging。对应 SKILL.md 均位于 C:\Users\admin\.codex\skills\ 下的同名目录。
必读资料:
- docs/superpowers/specs/2026-08-06-em-observation-web-staged-execution-design.md
- docs/superpowers/specs/2026-08-05-em-observation-web-visualization-design.md 的第 36、8、1012 节
- docs/superpowers/plans/2026-08-05-trajectory-planning-visualization-library.md 的 Global Constraints、File Structure、Task 4、Task 5
- docs/superpowers/handoffs/em-observation-web/phase-02.md
- Stage 2 的 PlanningVisualizationSession、LoopbackVisualizationServer 与契约类型;不要阅读无关 EM 源码
启动时记录分支、HEAD、最近提交;验证 b0b79e5 为祖先;核对 phase-02.md 状态与提交;运行独立可视化验证宿主作为入口基线。统计全部脏项但只展开本阶段文件范围,保护无关工作区和暂存区内容。
严格执行原类库计划 Task 4–5 的全部复选步骤:
- 先写嵌入资源和中文内容 RED 检查;
- 实现程序集内 index.html/app.css/app.js,不使用 CDN、Node.js 或运行时文件查找;
- 鸟瞰图使用单个 Canvas 解码行优先 LSB-first bitsetSVG 只覆盖路径/车辆/标记;
- X/Y 等比例,科研白底和 1.1 px 数据线,中文说明、英文/数学轴与 SI 单位;
- 四个页签和八个固定 chart ID 完整;
- EventSource 普通断线显示重连/过期,只有 event:end 显示会话已结束;前端异常只停止当前页面重绘;
- 快照文本一律 textContent,不执行 HTML;每条路由保留 token;
- 从非仓库工作目录验证嵌入资源;
- 实现 --smoke-seconds N 的确定性合成冒烟模式;
- 写独立类库 README 和文档检查;
- 保留原计划 Task 4 与 Task 5 的独立提交。
只能修改 Task 45 的 Files 清单。不得把 EM、MDCS、Painter 或 MovementTest 类型引入独立类库或验证宿主。
退出验证必须重新运行:
1. dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
2. dotnet build ClumsyPilot/TrajectoryPlanningVisualization/TrajectoryPlanningVisualization.csproj
3. dotnet build ClumsyPilot/ClumsyPilot.csproj -p:ExcludeLegacyAutoAvoidance=true
4. 先 Resolve-Path 得到验证宿主项目绝对路径,再 Push-Location $env:TEMP,从非仓库目录运行该宿主并返回。
5. dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj -- --smoke-seconds 1
6. 对本阶段文件运行 git diff --check 并核对提交文件树。
全部通过后创建并单独提交 docs/superpowers/handoffs/em-observation-web/phase-03.md,提交信息为 docs: record EM visualization phase 03 handoff。最终回答报告证据后,从本手册 Task 4 逐字复制下一阶段完整提示词作为最后一个代码块。阻塞时只输出阶段 3 恢复提示词。
```
- [ ] **Step 2: Accept only a verified Stage 3 handoff**
Expected: the reusable library passes from repository and temporary working directories, has embedded assets, and contains no EM/MDCS/Painter dependency.
---
### Task 4: Stage 4 — Observation settings and pure segment tracker
**Files:**
- Read: Stage 3 handoff and integration Task 12
- Create/modify: every exact file listed by integration Task 12
- Create: `docs/superpowers/handoffs/em-observation-web/phase-04.md`
**Interfaces:**
- Consumes: completed reusable library, current MovementTest observer and EM direction segments.
- Produces: frozen settings and pure sequential segment tracker required by Stage 5.
- [ ] **Step 1: Open a fresh window and paste this prompt exactly**
```text
你正在执行 EM 观察网页可视化的阶段 4/8:观察参数同步与纯换向段状态机。只完成集成计划 Task 1–2,不要修改活动段控制器、图表适配、网页托管或插件发布,也不要派生子智能体。
仓库根目录:D:\Users\Desktop\项目\prakrobot\ParkingRobot
设计已经批准。完整阅读并使用 executing-plans、test-driven-development、verification-before-completion;非预期失败时使用 systematic-debugging。技能文件位于 C:\Users\admin\.codex\skills\对应目录。
必读资料:
- docs/superpowers/specs/2026-08-06-em-observation-web-staged-execution-design.md
- docs/superpowers/specs/2026-08-05-em-observation-web-visualization-design.md 的第 13、67、1012 节
- docs/superpowers/plans/2026-08-05-em-observation-web-integration.md 的 Prerequisite、Global Constraints、File Structure、Task 1、Task 2
- docs/superpowers/handoffs/em-observation-web/phase-03.md
- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationContracts.cs
- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.TrajectoryObservationTest.cs
- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPipeline.cs 中与 settings/bootstrap/segment 直接相关部分
- DirectionSegmentView、VehicleMotionState、FrenetProjector、轨迹 metadata/terminal 契约
启动检查:记录分支/HEAD/最近提交,验证 b0b79e5 为祖先,核对 phase-03.md 完成及其提交。运行独立可视化验证宿主和 trajectory-observation 入口检查。统计全局脏项但只展开 Task 1–2 路径;特别保护 MovementTest 文件中用户已经设置的 5.0 s、100000、2.0 s,不得回退。
严格执行集成计划 Task 1–2 的所有 TDD 步骤和两个独立提交:
- 将 TrajectoryObservationSettings 默认值同步为 SolverTimeoutSeconds=5.0、MaximumOsqpIterations=100000、TimeHorizonSeconds=2.0
- 新增并冻结网页/Painter 开关、端口、10 Hz、60 轮历史和换向确认参数;
- 网页与 Painter 默认都关闭,网页自动打开默认开启,端口 0 或 1024..65535
- MovementTest 公共字段一对一映射到冻结 settings;
- 实现纯 TrajectoryObservationSegmentTracker,严格 N 到 N+1
- 只有匹配段/方向的 GearSwitch 轨迹已到绝对末点、测量位姿同时接近两段连接点、真实速度连续停车保持、随后 3 个严格递增序列的目标符号速度样本才切段;
- 错误方向、零速样本、重复序列、超投影距离、非 GearSwitch、时间不连续必须重置确认;
- 单段结束进入 Completed,不越界;状态机不得写 coordinator、UI、网页或硬件。
只能修改 Task 12 的 Files 清单。保持 OBSERVE_ONLY,不调用或新增任何底盘/运动写接口。
退出验证必须重新运行:
1. dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
2. dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
3. rg -n "SendXYThSpeed|SendMotion|DriveStop|PredefinedDriveStop|AccumulateSpeed" ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest,并确认运行观察源码没有调用这些接口;测试中的字符串审计本身需单独解释。
4. 对阶段文件运行 git diff --check,核对实现提交文件树。
全部通过后创建并单独提交 docs/superpowers/handoffs/em-observation-web/phase-04.md,提交信息为 docs: record EM visualization phase 04 handoff。最终回答报告提交和证据,再从本手册 Task 5 逐字复制下一阶段提示词作为最后一个代码块。阻塞时输出阶段 4 恢复提示词而不是推进。
```
- [ ] **Step 2: Accept only a verified Stage 4 handoff**
Expected: settings retain the approved defaults, the tracker passes deterministic transition checks, and source audit confirms observe-only behavior.
---
### Task 5: Stage 5 — Active-segment rolling controller
**Files:**
- Read: Stage 4 handoff and integration Task 3
- Modify: every exact file listed by integration Task 3
- Create: `docs/superpowers/handoffs/em-observation-web/phase-05.md`
**Interfaces:**
- Consumes: Stage 4 frozen settings and pure `TrajectoryObservationSegmentTracker`.
- Produces: active-segment controller/loop, per-segment coordinator replacement and visualization-only previous trajectory required by Stage 6.
- [ ] **Step 1: Open a fresh window and paste this prompt exactly**
```text
你正在执行 EM 观察网页可视化的阶段 5/8:活动方向段滚动规划控制器。只完成集成计划 Task 3,不要实现静态/动态图表 Adapter、网页生命周期或插件发布,也不要派生子智能体。
仓库根目录:D:\Users\Desktop\项目\prakrobot\ParkingRobot
设计已批准。完整阅读并使用 executing-plans、test-driven-development、verification-before-completion;发生非预期失败时完整阅读并使用 systematic-debugging。SKILL.md 位于 C:\Users\admin\.codex\skills\对应目录。
必读资料:
- docs/superpowers/specs/2026-08-06-em-observation-web-staged-execution-design.md
- docs/superpowers/specs/2026-08-05-em-observation-web-visualization-design.md 的第 3、7、912 节
- docs/superpowers/plans/2026-08-05-em-observation-web-integration.md 的 Global Constraints、File Structure、Task 3
- docs/superpowers/handoffs/em-observation-web/phase-04.md
- TrajectoryObservationPipeline.cs 全文
- TrajectoryObservationSegmentTracker.cs 及其检查
- EmPlanningCoordinator、TrajectoryExecutor、EmPlanningRequest、PlanningCycleResult 和 TrajectoryObservationLoop 直接相关契约
启动检查:记录分支、HEAD 和最近提交;验证 b0b79e5 为祖先;核对 phase-04.md 状态和实现提交。运行 trajectory-observation、coordinator、executor 三个入口基线。统计全局脏项但只展开 Task 3 路径,保护所有无关修改与暂存内容。
严格执行集成计划 Task 3 的 RED/GREEN 与提交:
- 移除硬编码 segmentIndex = 0,以 tracker 的 ActiveSegment 为唯一活动段来源;
- 保存 planningService,使 coordinator/executor 可在确认换向后按新段替换;
- 同段滚动周期继续使用 coordinator.PublishedTrajectory 作为 previous seed
- 跨方向段只把旧轨迹保留为 PreviousTrajectoryForVisualization,新 coordinator 的第一次请求 PreviousTrajectory 必须为 null
- 会话 cycle ID 在 coordinator 替换后继续单调增长;
- CreateEffectiveConfigurationSnapshot 返回 configuration.Copy,不泄露可变配置;
- 规划任务在飞行时不得切段或仅为切段取消;
- Tick 只在完成任务已消费且无任务在飞行时调用 TryAdvanceSegment
- 等待换向时区分当前方向、期望方向和 directionConfirmed,不能伪造档位读数;
- TrajectoryObservationLoopTick 暴露 SegmentAdvanced 与不可变段状态;
- 全程 OBSERVE_ONLY,不新增执行器写调用。
只能修改 Task 3 的 Files 清单。若为测试需要访问内部状态,沿用现有验证宿主/内部可见模式,不扩大公开 EM API。
退出验证必须重新运行:
1. dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
2. dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- coordinator
3. dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- executor
4. rg -n "segmentIndex = 0|SendXYThSpeed|SendMotion|DriveStop|PredefinedDriveStop|AccumulateSpeed" ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest,并解释任何命中,确认运行源码无硬编码活动段和执行器调用。
5. 对阶段文件运行 git diff --check 并核对实现提交文件树。
全部通过后创建并单独提交 docs/superpowers/handoffs/em-observation-web/phase-05.md,提交信息为 docs: record EM visualization phase 05 handoff。最终回答报告新鲜证据,然后从本手册 Task 6 逐字复制完整提示词作为最后一个代码块。阻塞时只输出阶段 5 恢复提示词。
```
- [ ] **Step 2: Accept only a verified Stage 5 handoff**
Expected: requests follow the tracker-confirmed active segment, same-segment seeding remains intact, cross-direction seeding is null, and coordinator/executor regressions pass.
---
### Task 6: Stage 6 — EM visualization adapters and handoff evidence
**Files:**
- Read: Stage 5 handoff and integration Task 45
- Create/modify: every exact file listed by integration Task 45
- Create: `docs/superpowers/handoffs/em-observation-web/phase-06.md`
**Interfaces:**
- Consumes: Stage 3 generic visualization contracts and Stage 5 active-segment controller state.
- Produces: static/dynamic snapshot builders, all required charts and shared-segment handoff metrics required by Stage 7.
- [ ] **Step 1: Open a fresh window and paste this prompt exactly**
```text
你正在执行 EM 观察网页可视化的阶段 6/8:EM 静态/动态数据可视化适配。只完成集成计划 Task 4–5,不要启动网页会话、修改 Painter 生命周期或插件发布,也不要派生子智能体。
仓库根目录:D:\Users\Desktop\项目\prakrobot\ParkingRobot
设计已批准。完整阅读并使用 executing-plans、test-driven-development、verification-before-completion;非预期失败时使用 systematic-debugging。技能文件位于 C:\Users\admin\.codex\skills\对应目录。
必读资料:
- docs/superpowers/specs/2026-08-06-em-observation-web-staged-execution-design.md
- docs/superpowers/specs/2026-08-05-em-observation-web-visualization-design.md 的第 46、812 节
- docs/superpowers/plans/2026-08-05-em-observation-web-integration.md 的 Global Constraints、File Structure、Task 4、Task 5
- docs/superpowers/handoffs/em-observation-web/phase-05.md
- Stage 3 的 Visualization contracts 与 SampleSnapshotFactory
- TrajectoryObservationPipeline、现有 TrajectoryObservationCharts/Diagnostics/Presentation
- PlanningGridMap、DirectionSegmentView、FrenetProjector、EmTrajectory、EmTrajectoryPoint、TrajectorySampler 和配置 Copy 契约
启动检查:记录分支/HEAD/最近提交;验证批准设计为祖先;核对 phase-05.md 完成与提交;运行 trajectory-observation 和 em-core-all 入口基线。统计全局脏项但只展开 Task 4–5 文件范围,保护无关修改和暂存内容。
严格执行集成计划 Task 4–5 的全部 TDD 步骤和两个独立提交:
- 静态快照仅会话构建一次,包含世界边界、行优先 LSB-first bitset 占用图、粗路径、Local G2、全部方向段、换向点和冻结的真实生效配置;
- bitset 字节长度严格为 (Rows*Cols+7)/8,不得为每格创建 DTO、SVG 或 Painter 对象;
- 配置按计划规定顺序分组,展示 raw C# 字段名、不变量格式值、中文名和单位;
- 动态快照包含实车位姿、当前/上一轨迹、活动段与当前 horizon 高亮、状态和摘要,历史摘要不得包含完整点数组;
- 生成 ls、st、curvature-s、curvature-t、velocity-t、acceleration-t、jerk-t、yaw-rate-t
- LS 使用完整活动方向段的 SourceStartArcLength + ReferenceSST 保持周期局部 PathS
- jerk 只发布 N-1 个真实区间样本,使用区间起点,不制造末点之后的值;
- 显示实际加速度/jerk/曲率/带符号速度限制,但没有独立 yaw-rate 配置时不得虚构限制线;
- RollingContinuation 与 ExactStopAtBoundary 的中文语义和真实终端 v/a 必须区分;
- 用绝对 EffectiveAtUtc 对齐当前点 0 与上一轨迹插值点;比较世界 DeltaPosition、共享完整方向段 DeltaReferenceS、DeltaV、DeltaA,绝不相减两轮局部 PathS;投影失败时只将 DeltaReferenceS 标为不可用。
只能修改 Task 45 的 Files 清单。EmTrajectoryPoint 的 internal a/j 可在同一 ClumsyPilot 程序集中读取,不要为了网页修改公共 EM API。
退出验证必须重新运行:
1. dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
2. dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-core-all
3. dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
4. 确认 rolling service 回归仍为 21 个 knot、滚动末速非零,jerk 图为 20 个区间样本。
5. 对阶段文件运行 git diff --check 并核对两个实现提交的文件树。
全部通过后创建并单独提交 docs/superpowers/handoffs/em-observation-web/phase-06.md,提交信息为 docs: record EM visualization phase 06 handoff。最终回答报告证据,再从本手册 Task 7 逐字复制下一阶段提示词作为最后一个代码块。阻塞时只输出阶段 6 恢复提示词。
```
- [ ] **Step 2: Accept only a verified Stage 6 handoff**
Expected: all generic snapshots are built from frozen EM/map data, jerk has no terminal fiction, and handoff deltas use world/shared-segment coordinates.
---
### Task 7: Stage 7 — MovementTest hosting, Painter isolation and packaging
**Files:**
- Read: Stage 6 handoff and integration Task 67
- Create/modify: every exact file listed by integration Task 67
- Create: `docs/superpowers/handoffs/em-observation-web/phase-07.md`
**Interfaces:**
- Consumes: Stage 3 reusable session and Stage 6 static/dynamic builders.
- Produces: optional web/Painter lifecycle, exception fuse, browser launch, published plugin DLL and operator documentation required by Stage 8.
- [ ] **Step 1: Open a fresh window and paste this prompt exactly**
```text
你正在执行 EM 观察网页可视化的阶段 7/8:MovementTest 网页托管、Painter 隔离、插件发布与操作文档。只完成集成计划 Task 6–7,不要进行最终实车验收,也不要派生子智能体。
仓库根目录:D:\Users\Desktop\项目\prakrobot\ParkingRobot
设计已批准。完整阅读并使用 executing-plans、test-driven-development、verification-before-completion;出现非预期失败时使用 systematic-debugging。技能文件位于 C:\Users\admin\.codex\skills\对应目录。
必读资料:
- docs/superpowers/specs/2026-08-06-em-observation-web-staged-execution-design.md
- docs/superpowers/specs/2026-08-05-em-observation-web-visualization-design.md 的第 26、8、1012 节
- docs/superpowers/plans/2026-08-05-em-observation-web-integration.md 的 Global Constraints、File Structure、Task 6、Task 7
- docs/superpowers/handoffs/em-observation-web/phase-06.md
- MovementTest.TrajectoryObservationTest.cs、TrajectoryObservationPresentation.cs、TrajectoryObservationPipeline.cs
- Stage 6 三个 snapshot/handoff/chart builder
- PlanningVisualizationSession 公开 API
- Publish-ClumsyPilotPlugin.ps1 与 PluginPackagingChecks.cs
- MovementTest README 与 EMPlanner README
启动检查:记录分支、HEAD、最近提交;验证批准设计为祖先;核对 phase-06.md 状态与提交。运行两个验证宿主的当前入口检查。统计全局脏项但只展开 Task 6–7 文件范围;这些文件可能有用户修改,必须逐项兼容且不得覆盖。检查暂存区并保护无关内容。
严格执行集成计划 Task 6–7 的所有 TDD 步骤和两个独立提交:
- 建立内部 ITrajectoryObservationVisualizationSink seam 与 TrajectoryObservationVisualizationPublisher
- publisher 在 10 Hz 门控点才调用 snapshotFactory,异常只记录一次、停止网页 sink 并永久熔断网页输出,规划 loop 继续;
- EnableWebVisualization=false 时不启动服务、不构建网页快照/历史;
- bootstrap 成功后按开关启动网页,记录完整 token URI;AutoOpen 失败只记录,不停止服务;
- 原生 Painter 默认关闭,移除静态 eager Presentation,仅在 EnableNativePainterVisualization=true 时会话内懒创建;
- Stop/替换会话幂等回收网页、端口与已存在 Painter;
- 实际 tick 使用 controller.ActiveSegment 及当前/上一轨迹发布,不在观察循环序列化或等待;
- 保持 OBSERVE_ONLY,网页关闭/慢客户端不改变规划周期;
- 插件发布从 ManagedDll 的同目录精确解析 TrajectoryPlanningVisualization.dll,放入 staging 后再原子交换;保留现有根目录保护、OSQP hash 与恢复机制;
- PluginPackagingChecks 验证精确包树和程序集名;
- 更新两个 README,完整说明开关、Token、科研风图、ReferenceS/PathS、N-1 jerk、滚动/停车、段高亮、换向确认和清理行为。
只能修改 Task 6–7 的 Files 清单。不得新增硬件写接口、局域网监听、自动循环重启网页服务或把独立网页依赖恢复到 Painter。
退出验证必须重新运行:
1. dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
2. dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- plugin-package
3. 再运行一次 plugin-package,确认无遗留 staging/backup。
4. dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
5. dotnet build ClumsyPilot/ClumsyPilot.csproj -p:ExcludeLegacyAutoAvoidance=true
6. rg -n "SendXYThSpeed|SendMotion|DriveStop|PredefinedDriveStop|AccumulateSpeed" ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest,确认运行源码无执行器调用。
7. 对阶段文件运行 git diff --check,核对两个实现提交和插件输出契约。
全部通过后创建并单独提交 docs/superpowers/handoffs/em-observation-web/phase-07.md,提交信息为 docs: record EM visualization phase 07 handoff。最终回答报告提交和证据,再从本手册 Task 8 逐字复制完整提示词作为最后一个代码块。阻塞时只输出阶段 7 恢复提示词。
```
- [ ] **Step 2: Accept only a verified Stage 7 handoff**
Expected: web and Painter are independently optional, visualization faults do not stop planning, and the plugin package contains the reusable visualization assembly.
---
### Task 8: Stage 8 — Full automated, browser and vehicle acceptance
**Files:**
- Read: Stage 7 handoff and integration Task 8
- Modify: no planned runtime source; acceptance-driven fixes return to the owning task's files and tests
- Create: `docs/superpowers/handoffs/em-observation-web/phase-08.md`
- Conditional create: `docs/superpowers/handoffs/em-observation-web/phase-08-vehicle.md`
**Interfaces:**
- Consumes: both verification hosts, main build, deterministic smoke mode, plugin output and deployed MovementTest.
- Produces: fresh end-to-end evidence and either full completion or an explicit vehicle-only continuation boundary.
- [ ] **Step 1: Open a fresh window and paste this prompt exactly**
```text
你正在执行 EM 观察网页可视化的阶段 8/8:全量自动化、网页冒烟与实车观察验收。本阶段以验证为主,不得顺手扩展功能,也不要派生子智能体。
仓库根目录:D:\Users\Desktop\项目\prakrobot\ParkingRobot
设计已批准。完整阅读并使用 executing-plans 与 verification-before-completion。只有发现回归时才使用 test-driven-development;出现非预期失败时完整阅读并使用 systematic-debugging。技能文件位于 C:\Users\admin\.codex\skills\对应目录。
必读资料:
- docs/superpowers/specs/2026-08-06-em-observation-web-staged-execution-design.md
- docs/superpowers/specs/2026-08-05-em-observation-web-visualization-design.md 的第 1012 节和所有完成标准
- docs/superpowers/plans/2026-08-05-em-observation-web-integration.md 的 Global Constraints 与 Task 8
- docs/superpowers/plans/2026-08-05-trajectory-planning-visualization-library.md 的 Global Constraints 与 Task 5 最终验证
- docs/superpowers/handoffs/em-observation-web/phase-07.md
- phase-01.md 至 phase-06.md 只读取状态、提交、验证和遗留问题字段,不重新加载全部实现推理
- 两个 README 中的操作与图表解释
启动检查:记录分支、HEAD、最近 20 条提交;验证 b0b79e5 为祖先;核对 phase-01 至 phase-07 全部存在且状态为完成,并抽查各阶段实现提交可达。统计全局脏项但不展开无关 diff;检查暂存区。阶段 8 没有预定源码修改,不能把无关脏项当成本功能失败。
先执行完整自动化:
1. dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
2. dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
3. dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-all
4. dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- plugin-package
5. dotnet build ClumsyPilot/ClumsyPilot.csproj -p:ExcludeLegacyAutoAvoidance=true
6. 对本功能自 phase-01 起的提交范围运行 whitespace 检查,并记录任何与本功能无关的既有警告,不能声称由本功能引入。
然后执行网页冒烟:
- 运行 dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj -- --smoke-seconds 30。
- 在 30 秒内打开输出的 token URI;如当前工具具有浏览器控制能力,可用浏览器检查,否则明确要求用户协助打开,不能伪造肉眼结果。
- 核对中文标题/状态、英文或数学坐标、科研细线白底、四页签、八图表、Canvas 栅格、SVG 路径覆盖、活动段/horizon 高亮、生效配置、过期状态和进程退出后端口释放。
最后执行实车观察清单,但只能在有人监督且环境安全时进行:
1. 开启网页、保持 Painter 关闭;确认 OBSERVE_ONLY。
2. 观察 rolling、approach、exact-stop;滚动模式不能标为强制停车。
3. 确认 jerk 为 N-1 区间且无终点下一点/JerkLimitExceeded 假象。
4. 确认当前/上一轮显示 DeltaPosition、DeltaReferenceS、DeltaV、DeltaA。
5. 真实换向点必须经过停车保持和 3 个带符号速度样本后才 N 到 N+1 高亮。
6. 关闭浏览器后规划周期继续;停止 MovementTest 后网页端口和 Painter 正确回收;全程没有执行器输出。
如果自动化或网页冒烟失败:不要创建“完成”交接。先定位归属阶段,补充聚焦失败测试,做最小修复,运行归属阶段验证后重新从完整自动化第 1 项开始。只提交回归测试和修复的精确文件,并记录偏差。
如果自动化和网页冒烟通过但当前没有受监督实车条件:创建 docs/superpowers/handoffs/em-observation-web/phase-08.md,状态必须写“自动验收完成但实车待验”,逐条记录自动证据和未运行的实车项;单独提交为 docs: record EM visualization phase 08 handoff。最终回答不得声称整体完成,最后一个代码块必须逐字复制本手册“Stage 8 Vehicle Continuation Prompt”。
只有自动化、网页冒烟和实车清单全部真实完成,phase-08.md 才能写“状态:完成”并单独提交。最终回答报告所有证据和提交,不再输出下一阶段提示词。
```
- [ ] **Step 2: Accept the correct Stage 8 terminal state**
Expected: either full verified completion, or `自动验收完成但实车待验` with the exact continuation prompt below. Automated-only evidence must never be labeled as full completion.
---
## Stage 8 Vehicle Continuation Prompt
Use this only when `phase-08.md` records successful automated/browser acceptance and explicitly leaves supervised vehicle observation pending.
```text
你正在续验 EM 观察网页可视化阶段 8 的实车观察部分。自动化与网页冒烟已经完成;本窗口只核查交接证据并在受监督安全环境中完成尚未执行的实车清单,不要重复开发、扩展功能或派生子智能体。
仓库根目录:D:\Users\Desktop\项目\prakrobot\ParkingRobot
完整阅读并使用 C:\Users\admin\.codex\skills\verification-before-completion\SKILL.md。只有发现真实回归时才阅读和使用 test-driven-development 与 systematic-debugging;设计已批准,不要重新 brainstorming。
必读资料:
- docs/superpowers/specs/2026-08-06-em-observation-web-staged-execution-design.md 的第 810 节
- docs/superpowers/specs/2026-08-05-em-observation-web-visualization-design.md 的第 1012 节
- docs/superpowers/plans/2026-08-05-em-observation-web-integration.md 的 Task 8
- docs/superpowers/handoffs/em-observation-web/phase-08.md
- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/README.md
启动时记录当前分支、HEAD、最近提交,验证 b0b79e5 为祖先,并确认 phase-08.md 的状态正是“自动验收完成但实车待验”。核对其中自动化提交和命令证据可达;如果代码在 phase-08 后发生相关变化,必须先重新运行受影响自动验证,不能直接沿用旧证据。保护所有无关脏工作区和暂存内容。
只有在有人监督且环境安全时执行:
1. 开启 EnableWebVisualization,关闭 EnableNativePainterVisualization;确认 UI、控制台和网页显示 OBSERVE_ONLY。
2. 观察 rolling、approach、exact-stop;确认滚动末端停车硬约束显示未启用,精确停车锚点只在真实边界出现。
3. 确认 jerk 为轨迹 N-1 个时间区间,没有末点之后的加加速度点或虚假 JerkLimitExceeded。
4. 确认周期交接展示世界 DeltaPosition、共享方向段 DeltaReferenceS、DeltaV、DeltaA,没有直接比较局部 PathS。
5. 在真实换向点确认连续停车保持和 3 个正确带符号速度样本后才从 N 高亮到 N+1,等待期间页面明确显示未满足条件。
6. 关闭浏览器,确认规划观察周期继续且不受慢客户端/关闭影响。
7. 停止 MovementTest,确认 HTTP/SSE、端口和可能存在的 Painter 全部回收。
8. 通过日志和源审计确认全过程没有底盘、转向、制动、电机或档位写输出。
不得通过操作真实底盘来“帮助”观察任务满足条件;MovementTest 本身必须保持只读观察。任何一项无法安全执行都应记录为未完成,不能声称整体完成。
全部实车项通过后创建 docs/superpowers/handoffs/em-observation-web/phase-08-vehicle.md,记录车辆/环境前提、执行时间、逐项结果、日志或截图位置、未运行项、警告和最终结论。单独提交该文件,提交信息为 docs: record EM visualization vehicle acceptance。最终回答列出新鲜实车证据和交接提交,明确整体完成,不再输出后续提示词。
若发现回归,创建 phase-08-vehicle.md 并写状态“阻塞”,记录首个有效失败、复现步骤和安全影响;不要现场扩大修改。最终回答输出一份新的阶段 8 实车恢复提示词,携带真实失败事实和允许继续的精确范围。
```
---
## Plan Completion Check
- [ ] All eight stage prompts are copied without truncation when handed to new windows.
- [ ] Every original library Task 15 is owned exactly once by Stages 13.
- [ ] Every original integration Task 18 is owned exactly once by Stages 48.
- [ ] Every stage has a distinct handoff path and terminal verification.
- [ ] Blocked stages resume themselves and never advance.
- [ ] Stage 8 distinguishes full vehicle completion from automated-only acceptance.
@@ -1,227 +0,0 @@
# EM FullDirection Correctness Fixes Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use test-driven-development for every task. Execute tasks serially because they share the EM solver pipeline.
**Goal:** Fix confirmed FullDirection projection, cancellation, curvature-constraint, timeout-budget, and trajectory-coordinate defects without replacing the existing LS/ST planner.
**Architecture:** Keep `IEmPlanningService`, `EmPlanningRequest`, `LateralPlanner`, `LongitudinalPlanner`, and trajectory contracts compatible. Apply local fixes where one component owns the invariant; introduce only a shared internal lateral-curvature affine model and internal explicit-budget overloads where the same invariant necessarily crosses components.
**Tech Stack:** C# 10, .NET Standard 2.0 production assembly, .NET 8 verification host, solver-neutral `IQpSolver` tests.
## Global Constraints
- `FullDirectionSegment` plans exactly one complete direction segment; `RollingHorizon` behavior is not redesigned in this plan.
- FullDirection ego admission is restricted to the segment-start prefix `[0, min(L, MaximumProjectionDistanceMeters)]`; it must not select a later U-shape/self-overlap branch.
- A start heading error with magnitude greater than or equal to `π/2` is rejected before `tan(headingError)` is evaluated.
- `Cancelled` always carries a null trajectory/path/candidate, even if a strict fallback candidate exists.
- `SolverTimeoutSeconds` is one combined LS+ST solve budget for a service call, not a fresh budget for each optimizer.
- Every LS QP has a finite linearized vehicle-curvature hard-bound row at every station; nonlinear validation remains authoritative.
- `SegmentLocalS` stores interpolated direction-segment reference S; `PathS` stores optimized lateral-path arc length.
- Do not edit or revert the user's existing `EmPlannerConfiguration.cs` change, PathSmoothing work, Map work, or `ClumsyPilot.csproj` changes.
- Do not add actuator calls, change public EM request/result signatures, or commit/stage files from the dirty shared worktree.
---
### Task 1: Anchor FullDirection start projection and reject folded headings
**Files:**
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/EmPlanningServiceChecks.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Facade/EmPlanningService.cs`
**Interfaces:**
- Consumes: existing `FrenetProjector.TryProject` bounded-window overload.
- Produces: service-local FullDirection start-prefix admission; Rolling continues using `[0,L]`.
- [ ] **Step 1: Write failing service tests.** Add a FullDirection U-shaped all-forward reference whose later arm is closer to the measured pose, and assert `ProjectionFailed` rather than accepting a later `ReferenceS`. Add a same-position start pose with yaw `π`, and assert `ProjectionFailed` with a null trajectory.
```csharp
EmPlanningResult wrongBranch = service.Plan(fullURequest, CancellationToken.None);
Verification.Equal(EmPlanningStatus.ProjectionFailed, wrongBranch.Status,
"FullDirection cannot enter through a later U branch");
EmPlanningResult reversedHeading = service.Plan(oppositeHeadingRequest, CancellationToken.None);
Verification.Equal(EmPlanningStatus.ProjectionFailed, reversedHeading.Status,
"opposite start heading is rejected before slope conversion");
```
- [ ] **Step 2: Run the focused test and verify RED.**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-planning-service
```
Expected: the later U branch and/or opposite-heading assertion fails under the current global `[0,L]`, seed-zero projection.
- [ ] **Step 3: Implement the local admission rule.** In `EmPlanningService.Plan`, choose the projection upper bound from scope and validate heading before constructing lateral input.
```csharp
double startProjectionUpperS = request.PlanningScope == EmPlanningScope.FullDirectionSegment
? Math.Min(segment.LengthMeters, configuration.Frenet.MaximumProjectionDistanceMeters)
: segment.LengthMeters;
if (!projector.TryProject(request.VehicleState.Pose, segment, 0d, startProjectionUpperS,
configuration.Frenet.MaximumProjectionDistanceMeters, 0d, out FrenetProjection startProjection) ||
Math.Abs(startProjection.HeadingError) >= Math.PI / 2d)
{
return Failure(EmPlanningStatus.ProjectionFailed, request,
"Vehicle pose is not an admissible start state for the selected direction segment.");
}
```
- [ ] **Step 4: Re-run `em-planning-service` and verify GREEN.** Existing Rolling projection behavior must remain green.
### Task 2: Make cancellation terminal and non-publishable
**Files:**
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/LateralIntegrationChecks.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalIntegrationChecks.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/EmPlanningServiceChecks.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral/SequentialConvexOptimizer.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/SequentialLongitudinalOptimizer.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Facade/EmPlanningService.cs`
**Interfaces:**
- Produces: `Cancelled` results with null candidate/path/trajectory at every layer.
- [ ] **Step 1: Write failing optimizer tests.** Use a solver that returns one valid candidate and cancels the supplied source before the next iteration. Assert both optimizers return `Cancelled`, not `SuccessWithFallback`, and expose no candidate.
```csharp
Verification.Equal(EmPlanningStatus.Cancelled, result.Status,
"cancellation is never converted to fallback success");
Verification.True(result.Path == null, "cancelled lateral result has no path");
```
- [ ] **Step 2: Verify RED with the focused groups.**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- lateral-integration
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-integration
```
Expected: at least one optimizer currently returns `SuccessWithFallback`.
- [ ] **Step 3: Implement minimal cancellation precedence.** Special-case cancellation in each `FallbackOrFailure`, and check the token after LS, after ST, after assembly, and immediately before service success publication.
```csharp
if (failureStatus == EmPlanningStatus.Cancelled)
return Failed(EmPlanningStatus.Cancelled, failureReason);
```
- [ ] **Step 4: Re-run both optimizer groups and `em-planning-service`; verify GREEN.**
### Task 3: Add shared linearized curvature hard constraints
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral/LateralCurvatureLinearization.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral/LateralObjectiveBuilder.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral/LateralConstraintBuilder.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/LateralModelChecks.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/EmPlanningServiceChecks.cs`
**Interfaces:**
- Produces: `LateralCurvatureLinearization.Create(input, layout, iterate)` returning immutable station affines with indices, gradient, and constant.
- Consumers: objective terms and hard constraints use the exact same affine coefficients.
- [ ] **Step 1: Write a failing QP-shape test.** For a straight reference and zero iterate, set vehicle maximum curvature to `0.25 1/m`; assert every station has a row equivalent to `-0.25 <= DDL(i) <= 0.25`. Also assert the total constraint count increases by the station count.
```csharp
Verification.Equal(expectedOldRows + layout.StationCount, problem.ConstraintCount,
"one curvature hard-bound row is emitted per station");
Verification.True(HasBound(problem,
new Dictionary<int, double> { { layout.DDL(station), 1d } }, -0.25d, 0.25d),
"straight-path curvature affine is hard bounded");
```
- [ ] **Step 2: Run `lateral-model` and verify RED.**
- [ ] **Step 3: Extract the existing affine calculation without changing its formula.** Move `CreateCurvatureAffines` and its value type from `LateralObjectiveBuilder` to the new internal file. Keep the nonlinear formula in `LateralGeometryEvaluator`/independent validator unchanged.
- [ ] **Step 4: Add one curvature row per station in `LateralConstraintBuilder`.** Bounds are `[-maximumVehicleCurvature, +maximumVehicleCurvature]` after subtracting the affine constant.
```csharp
AddRow(constraints, lower, upper, ref row,
-maximumCurvature - affine.Constant,
maximumCurvature - affine.Constant,
affine.Indices, affine.Gradient);
```
- [ ] **Step 5: Update test solver problem classification so the added lateral rows are not mistaken for ST rows, then run `lateral-model`, `lateral-integration`, and `em-planning-service` GREEN.**
### Task 4: Relinearize rejected solved vectors and report iteration exhaustion honestly
**Files:**
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/LateralIntegrationChecks.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral/SequentialConvexOptimizer.cs`
**Interfaces:**
- Produces: rejected, parseable solver candidates may advance only the SQP iterate/warm start; they never replace `lastValidatedPath`.
- [ ] **Step 1: Write failing tests.** Configure a low curvature limit so the first candidate is strict and the second parseable candidate fails nonlinear validation. Assert the third QP/warm start is based on the second candidate, while a later timeout still returns the first strict path. Change the outer-limit assertion from ordinary `Success` to `SuccessWithFallback` with a non-empty reason.
- [ ] **Step 2: Run `lateral-integration` and verify RED.**
- [ ] **Step 3: Move iterate/warm-start advancement to immediately after a parseable solved candidate, while updating `lastValidatedPath` only after independent validation.** Return `SuccessWithFallback` when the outer loop ends with a strict candidate but without satisfying convergence.
- [ ] **Step 4: Run `lateral-integration` and `lateral-real-osqp` GREEN.**
### Task 5: Share one LS/ST solver timeout budget
**Files:**
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/EmPlanningServiceChecks.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral/LateralPlanner.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Lateral/SequentialConvexOptimizer.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalPlanner.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/SequentialLongitudinalOptimizer.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Facade/EmPlanningService.cs`
**Interfaces:**
- Keeps: existing public `Plan(input, token)` and `Optimize(input, token)` entry points.
- Adds: internal overloads accepting a finite positive `TimeSpan solveBudget`.
- [ ] **Step 1: Write a failing service test.** Delay a lateral fake solve by at least 100 ms under a 2 s configured timeout, record all `QpSolverSettings.MaximumSolveDuration` values, and assert the first ST call receives less than 1.95 s rather than a fresh 2 s.
- [ ] **Step 2: Run `em-planning-service` and verify RED.**
- [ ] **Step 3: Add internal explicit-budget overloads.** Default public overloads continue deriving budget from configuration; service starts one monotonic `Stopwatch` immediately before LS and passes `configuredBudget - elapsed` to LS and then ST. Non-positive remaining time returns `SolverTimedOut` with no trajectory.
- [ ] **Step 4: Add a final elapsed/cancellation check before publication and run `lateral-integration`, `longitudinal-integration`, and `em-planning-service` GREEN.**
### Task 6: Preserve reference S separately from optimized PathS
**Files:**
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryChecks.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Trajectory/LateralPathInterpolator.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Trajectory/EmTrajectoryAssembler.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Validation/EmTrajectoryValidator.cs`
**Interfaces:**
- Extends internal `InterpolatedLateralPathPoint` with `ReferenceS`.
- Keeps public `EmTrajectoryPoint` shape unchanged.
- [ ] **Step 1: Write a failing curved/offset-path assembly test.** Construct a validated lateral path where reference-S and chord PathS differ; assert each output point's `SegmentLocalS` is the interpolated reference-S and `PathS` remains the ST progress value.
- [ ] **Step 2: Run `trajectory` and verify RED.** Current assembly writes `sample.PathS` into both fields.
- [ ] **Step 3: Interpolate `ReferenceS` using the same PathS bracket and pass `geometry.ReferenceS` as `SegmentLocalS`.** Update publication bounds so segment-local S is checked against direction-segment length/reference bound, while PathS is checked against the optimized path upper bound.
- [ ] **Step 4: Run `trajectory` and `em-core-all` GREEN.**
### Task 7: Core regression and diff hygiene
**Files:**
- Verify only; no broad formatting.
- [ ] **Step 1: Run the complete EM verification set.**
```powershell
dotnet build ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj --no-restore
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj --no-build -- em-all
```
- [ ] **Step 2: Check only scoped diffs.**
```powershell
git diff --check -- ClumsyPilot/ParkrobTrajplanner/EMPlanner ClumsyPilot/tests/EMPlannerVerificationHost
git status --short -- ClumsyPilot/ParkrobTrajplanner/EMPlanner ClumsyPilot/tests/EMPlannerVerificationHost
```
Expected: all groups pass; no whitespace errors; `EmPlannerConfiguration.cs` remains exactly the user's pre-existing modification.
@@ -1,694 +0,0 @@
# MovementTest Web Path Visualization 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:** Improve the MovementTest web path overview so coarse, smoothed, and EM paths remain distinguishable, while adding a metric coordinate system, a data-driven legend, fixed-size vehicle rendering, and explicit smoothed-path endpoints.
**Architecture:** Keep the existing Canvas occupancy layer and SVG geometry layer. Extend the SVG renderer with world-to-screen metrics, metric grid/axis rendering, fixed-pixel marker transforms, and deterministic path layering; add one HTML legend overlay and one static `smooth-start` marker without changing the visualization JSON contract or planner behavior.
**Tech Stack:** C# 10, .NET `netstandard2.0` visualization library, .NET 10 Windows verification hosts, embedded HTML/CSS/vanilla JavaScript, SVG, Canvas.
## Global Constraints
- Do not modify coarse-path, Local G2, EMPlanner, FullDirection, Rolling, or multi-segment state-machine behavior.
- Do not add actuator calls, trajectory tracking, or any execution closed loop; `OBSERVE_ONLY` remains unchanged.
- Do not introduce third-party JavaScript, CSS, map, chart, CDN, or NuGet dependencies.
- Do not replace or version the visualization JSON contract; only add `smooth-start` to the existing static marker collection.
- Keep X and Y at equal world scale and label coordinates in meters.
- Keep all path strokes and marker geometry visually stable across world extents.
- Do not add overview pan, zoom, fullscreen, or layer toggles.
- Preserve unrelated dirty-worktree changes; stage only the files named by the current task.
---
## File Structure
- `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationStaticSnapshotBuilder.cs`
- Owns MovementTest-to-generic-visualization static geometry conversion; it will add the smoothed-path start marker.
- `ClumsyPilot/TrajectoryPlanningVisualization/Web/index.html`
- Owns overview DOM structure; it will add the screen-space legend container.
- `ClumsyPilot/TrajectoryPlanningVisualization/Web/app.js`
- Owns browser rendering; it will add coordinate ticks, world/screen metrics, fixed-size markers, ordered paths, and legend population.
- `ClumsyPilot/TrajectoryPlanningVisualization/Web/app.css`
- Owns semantic visual styling; it will define coordinate, path, marker, and legend appearance.
- `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationVisualizationChecks.cs`
- Verifies MovementTest snapshot semantics, including the new `smooth-start` marker.
- `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/WebAssetChecks.cs`
- Verifies embedded web assets expose coordinate, fixed-marker, layer, and legend capabilities.
- `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/SampleSnapshotFactory.cs`
- Supplies deterministic overlapping coarse, smoothed, and EM geometry for the local browser smoke test.
## Task 1: Export the Smoothed-Path Start Marker
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationStaticSnapshotBuilder.cs:56-76`
- Test: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationVisualizationChecks.cs:82-110`
**Interfaces:**
- Consumes: `TrajectoryObservationBootstrapResult.SmoothedPath.Path`, whose first element supplies `X` and `Y`.
- Produces: one existing-contract `VisualizationMarker` with `Id = "smooth-start"`, `Kind = "smooth-start"`, and `LabelChinese = "平滑路径起点"` when the smoothed path is non-empty.
- [ ] **Step 1: Write the failing snapshot assertions**
Add these assertions immediately after the existing `local-g2-path` assertion in `VerifiesStaticSnapshotExportsFrozenConfigurationAndGeometry`:
```csharp
VisualizationMarker smoothStart = snapshot.StaticMarkers.Single(x => x.Kind == "smooth-start");
Verification.Equal("平滑路径起点", smoothStart.LabelChinese,
"static snapshot labels the smoothed-path start");
Verification.NearlyEqual(bootstrap.SmoothedPath.Path[0].X, smoothStart.Position.X,
"smoothed-path start marker X matches the first smoothed point");
Verification.NearlyEqual(bootstrap.SmoothedPath.Path[0].Y, smoothStart.Position.Y,
"smoothed-path start marker Y matches the first smoothed point");
```
- [ ] **Step 2: Run the focused test and verify failure**
Run:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
```
Expected: FAIL in `VerifiesStaticSnapshotExportsFrozenConfigurationAndGeometry` because no marker has `Kind == "smooth-start"`.
- [ ] **Step 3: Add the minimal static marker**
Insert this block at the start of `CreateMarkers`, after `var markers = new List<VisualizationMarker>();` and before iterating direction segments:
```csharp
if (bootstrap.SmoothedPath.Path.Count > 0)
{
var start = bootstrap.SmoothedPath.Path[0];
markers.Add(new VisualizationMarker("smooth-start", "smooth-start",
"平滑路径起点", new VisualizationPoint(start.X, start.Y)));
}
```
- [ ] **Step 4: Run the focused test and verify success**
Run:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
```
Expected: `PASS trajectory-observation`.
- [ ] **Step 5: Commit the marker contract change**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationStaticSnapshotBuilder.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationVisualizationChecks.cs
git commit -m "feat: mark smoothed path start in EM overview"
```
## Task 2: Add the Metric Coordinate System
**Files:**
- Modify: `ClumsyPilot/TrajectoryPlanningVisualization/Web/app.js:40-94`
- Modify: `ClumsyPilot/TrajectoryPlanningVisualization/Web/app.css:41-57`
- Test: `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/WebAssetChecks.cs:21-43`
**Interfaces:**
- Consumes: the existing `getBounds()` result and `#world-overlay.getBoundingClientRect()`.
- Produces: `niceWorldStep(span, targetTicks)`, `worldMetrics(bounds)`, and `renderWorldCoordinates(bounds, metrics)`; later tasks consume `metrics.unitsPerPixel` for fixed-size markers.
- [ ] **Step 1: Write failing embedded-asset checks**
Add the following assertions in `WebAssetChecks.Run` after the existing `.world-current` check:
```csharp
Verification.True(css.Contains(".world-coordinate-grid"),
"overview exposes a metric grid semantic class");
Verification.True(css.Contains(".world-axis-zero"),
"overview exposes a zero-axis semantic class");
Verification.True(css.Contains(".world-axis-tick"),
"overview exposes coordinate tick semantics");
Verification.True(css.Contains(".world-axis-label"),
"overview exposes coordinate axis-label semantics");
Verification.True(js.Contains("function niceWorldStep("),
"overview selects readable metric tick steps");
Verification.True(js.Contains("function worldMetrics("),
"overview derives stable world-to-screen metrics");
Verification.True(js.Contains("function renderWorldCoordinates("),
"overview renders its own metric coordinate system");
Verification.True(js.Contains("X (m)") && js.Contains("Y (m)"),
"overview labels both metric axes");
```
- [ ] **Step 2: Run the web asset host and verify failure**
Run:
```powershell
dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
```
Expected: FAIL with `overview exposes a metric grid semantic class`.
- [ ] **Step 3: Add world/screen metrics and coordinate rendering**
Insert the following functions after `drawOccupancy` and before `worldPath` in `app.js`:
```javascript
function niceWorldStep(span, targetTicks = 7) {
if (!finite(span) || span <= 0) return 1;
const rough = span / Math.max(2, targetTicks);
const magnitude = Math.pow(10, Math.floor(Math.log10(rough)));
const normalized = rough / magnitude;
const factor = normalized <= 1 ? 1 : normalized <= 2 ? 2 : normalized <= 5 ? 5 : 10;
return factor * magnitude;
}
function worldMetrics(bounds) {
const box = overlay.getBoundingClientRect();
const pixelWidth = box.width > 0 ? box.width : 800;
const pixelHeight = box.height > 0 ? box.height : 520;
const spanX = Math.max(bounds.xMax - bounds.xMin, 1e-9);
const spanY = Math.max(bounds.yMax - bounds.yMin, 1e-9);
return {
pixelWidth,
pixelHeight,
spanX,
spanY,
unitsPerPixel: Math.max(spanX / pixelWidth, spanY / pixelHeight, 1e-9)
};
}
function renderWorldCoordinates(bounds, metrics) {
const group = svg("g", { class: "world-coordinate-system", "aria-hidden": "true" });
const xStep = niceWorldStep(metrics.spanX, 8);
const yStep = niceWorldStep(metrics.spanY, 8);
const epsilon = Math.max(xStep, yStep) * 1e-9;
let count = 0;
for (let x = Math.ceil(bounds.xMin / xStep) * xStep; x <= bounds.xMax + epsilon && count < 100; x += xStep, count += 1) {
const normalized = Math.abs(x) < epsilon ? 0 : x;
group.append(svg("line", { x1: normalized, y1: -bounds.yMax, x2: normalized, y2: -bounds.yMin, class: "world-coordinate-grid" }));
const tick = svg("text", { x: normalized, y: -bounds.yMin - 7 * metrics.unitsPerPixel, class: "world-axis-tick", "font-size": 11 * metrics.unitsPerPixel, "text-anchor": "middle" });
tick.textContent = formatTick(normalized);
group.append(tick);
}
count = 0;
for (let y = Math.ceil(bounds.yMin / yStep) * yStep; y <= bounds.yMax + epsilon && count < 100; y += yStep, count += 1) {
const normalized = Math.abs(y) < epsilon ? 0 : y;
group.append(svg("line", { x1: bounds.xMin, y1: -normalized, x2: bounds.xMax, y2: -normalized, class: "world-coordinate-grid" }));
const tick = svg("text", { x: bounds.xMin + 6 * metrics.unitsPerPixel, y: -normalized - 3 * metrics.unitsPerPixel, class: "world-axis-tick", "font-size": 11 * metrics.unitsPerPixel, "text-anchor": "start" });
tick.textContent = formatTick(normalized);
group.append(tick);
}
if (bounds.xMin <= 0 && bounds.xMax >= 0)
group.append(svg("line", { x1: 0, y1: -bounds.yMax, x2: 0, y2: -bounds.yMin, class: "world-axis-zero" }));
if (bounds.yMin <= 0 && bounds.yMax >= 0)
group.append(svg("line", { x1: bounds.xMin, y1: 0, x2: bounds.xMax, y2: 0, class: "world-axis-zero" }));
const xLabel = svg("text", { x: bounds.xMax - 8 * metrics.unitsPerPixel, y: -bounds.yMin - 8 * metrics.unitsPerPixel, class: "world-axis-label", "font-size": 12 * metrics.unitsPerPixel, "text-anchor": "end" });
xLabel.textContent = "X (m)";
const yLabel = svg("text", { x: bounds.xMin + 8 * metrics.unitsPerPixel, y: -bounds.yMax + 16 * metrics.unitsPerPixel, class: "world-axis-label", "font-size": 12 * metrics.unitsPerPixel, "text-anchor": "start" });
yLabel.textContent = "Y (m)";
group.append(xLabel, yLabel);
overlay.append(group);
}
```
Replace the first statement sequence in `renderWorld` with:
```javascript
const bounds = getBounds();
const metrics = worldMetrics(bounds);
drawOccupancy(bounds);
overlay.replaceChildren();
overlay.setAttribute("viewBox", `${bounds.xMin} ${-bounds.yMax} ${bounds.xMax - bounds.xMin} ${bounds.yMax - bounds.yMin}`);
overlay.setAttribute("preserveAspectRatio", "xMidYMid meet");
renderWorldCoordinates(bounds, metrics);
```
Add the following CSS after `.world-line`:
```css
.world-coordinate-grid { stroke: #dfe4e8; stroke-opacity: .62; stroke-width: .7; vector-effect: non-scaling-stroke; }
.world-axis-zero { stroke: #7c8791; stroke-opacity: .72; stroke-width: 1.1; vector-effect: non-scaling-stroke; }
.world-axis-tick { fill: #68737d; font-family: ui-monospace, Consolas, monospace; }
.world-axis-label { fill: #48535d; font-family: "Microsoft YaHei", "Noto Sans CJK SC", sans-serif; font-weight: 600; }
```
- [ ] **Step 4: Run the web asset host and verify success**
Run:
```powershell
dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
```
Expected: `PASS trajectory-planning-visualization`.
- [ ] **Step 5: Commit the coordinate system**
```powershell
git add -- ClumsyPilot/TrajectoryPlanningVisualization/Web/app.js ClumsyPilot/TrajectoryPlanningVisualization/Web/app.css ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/WebAssetChecks.cs
git commit -m "feat: add metric axes to trajectory overview"
```
## Task 3: Fix Path Layering and Screen-Size Markers
**Files:**
- Modify: `ClumsyPilot/TrajectoryPlanningVisualization/Web/app.js:52-93`
- Modify: `ClumsyPilot/TrajectoryPlanningVisualization/Web/app.css:1-9,41-57`
- Test: `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/WebAssetChecks.cs:21-43`
**Interfaces:**
- Consumes: `worldMetrics(bounds).unitsPerPixel` from Task 2 and existing polyline/marker kinds.
- Produces: `lineKind(line)`, `markerTransform(position, unitsPerPixel, headingRadians)`, and `appendWorldMarker(marker, frame, metrics)`; the current EM trajectory is rendered after every static/reference layer.
- [ ] **Step 1: Add failing checks for fixed-size rendering and semantic layers**
Add these assertions after the coordinate checks in `WebAssetChecks.Run`:
```csharp
Verification.True(css.Contains("--world-smoothed-path"),
"overview defines a dedicated smoothed-path color");
Verification.True(css.Contains("--world-em-trajectory"),
"overview defines a dedicated EM-path color");
Verification.True(css.Contains(".world-marker-shape"),
"overview marker shapes use a shared screen-space class");
Verification.True(css.Contains(".marker-smooth-start"),
"overview styles the smoothed-path start marker");
Verification.True(js.Contains("function markerTransform("),
"overview anchors fixed-size markers in world coordinates");
Verification.True(js.Contains("function appendWorldMarker("),
"overview centralizes fixed-size marker rendering");
Verification.True(!js.Contains("const corners = [[.38, .18]"),
"vehicle marker no longer uses a large world-size rectangle");
Verification.True(!css.Contains(".marker-vehicle .vehicle-outline { fill: none; stroke: #20252b; stroke-width: .7; }"),
"vehicle outline no longer uses a world-scale heavy stroke");
Verification.True(!css.Contains(".marker-vehicle .vehicle-heading { stroke: #20252b; stroke-width: .8; }"),
"vehicle marker no longer uses world-scale heavy strokes");
```
- [ ] **Step 2: Run the web asset host and verify failure**
Run:
```powershell
dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
```
Expected: FAIL with `overview defines a dedicated smoothed-path color`.
- [ ] **Step 3: Define path and marker visual variables**
Add these variables to `:root` without changing the chart color variables:
```css
--world-coarse-path: #7e8790;
--world-smoothed-path: #168b83;
--world-active-segment: #3f78a8;
--world-em-trajectory: #df6b1f;
```
Replace the existing world path and marker CSS rules with:
```css
.world-line { fill: none; vector-effect: non-scaling-stroke; }
.world-static { stroke: #8d959d; stroke-opacity: .38; stroke-width: 1.4; }
.world-future { stroke: #b6bec6; stroke-dasharray: 5 4; stroke-width: 1.2; }
.world-active { stroke: var(--world-active-segment); stroke-width: 1.5; }
.world-previous { stroke: #887493; stroke-opacity: .66; stroke-dasharray: 7 5; stroke-width: 2; }
.world-coarse { stroke: var(--world-coarse-path); stroke-opacity: .40; stroke-dasharray: 7 5; stroke-width: 2; }
.world-local-g2 { stroke: var(--world-smoothed-path); stroke-opacity: .65; stroke-width: 4; }
.world-segment-active { stroke: var(--world-active-segment); stroke-opacity: .50; stroke-width: 1.5; }
.world-direction-inactive { stroke: #aab2b9; stroke-opacity: .30; stroke-dasharray: 5 5; stroke-width: 1.1; }
.world-current { stroke: var(--world-em-trajectory); stroke-opacity: .96; stroke-width: 2.4; }
.world-marker-shape { vector-effect: non-scaling-stroke; }
.world-marker-label { fill: #20252b; font: 11px/1.2 "Microsoft YaHei", "Noto Sans CJK SC", sans-serif; paint-order: stroke; stroke: rgba(255, 255, 255, .88); stroke-width: 3px; stroke-linejoin: round; }
.world-marker { fill: #d87918; stroke: #ffffff; stroke-width: 1.4; }
.marker-vehicle .vehicle-outline { fill: rgba(32, 37, 43, .16); stroke: #20252b; stroke-width: 1.5; }
.marker-vehicle .vehicle-heading { stroke: #20252b; stroke-width: 1.5; stroke-linecap: round; vector-effect: non-scaling-stroke; }
.marker-smooth-start { fill: #2f8f56; stroke: #ffffff; stroke-width: 1.5; }
.marker-plan-start { fill: #ffffff; stroke: #2f8f56; stroke-width: 2; }
.marker-gear-switch { fill: #d87918; stroke: #ffffff; stroke-width: 1.5; }
.marker-final-goal { fill: #1769aa; stroke: #ffffff; stroke-width: 1.5; }
.world-empty-state { fill: #66707a; font: 14px/1.2 "Microsoft YaHei", "Noto Sans CJK SC", sans-serif; }
```
- [ ] **Step 4: Replace world marker and layer rendering**
Add the following functions after `worldPath`:
```javascript
function lineKind(line) { return String(line && line.kind || "").toLowerCase(); }
function markerTransform(position, unitsPerPixel, headingRadians = 0) {
const headingDegrees = (finite(headingRadians) ? headingRadians : 0) * 180 / Math.PI;
return `translate(${position.x} ${-position.y}) rotate(${-headingDegrees}) scale(${unitsPerPixel})`;
}
function appendWorldMarker(marker, frame, metrics) {
if (!marker.position || !finite(marker.position.x) || !finite(marker.position.y)) return;
const kind = String(marker.kind || "").toLowerCase();
const pose = frame.vehiclePose;
const heading = kind === "vehicle" && pose && finite(pose.headingRadians) ? pose.headingRadians : 0;
const group = svg("g", {
class: `world-marker-group marker-${kind || "generic"}`,
transform: markerTransform(marker.position, metrics.unitsPerPixel, heading)
});
if (kind === "vehicle") {
group.append(svg("path", { d: "M -9 -5 L 5 -5 L 9 0 L 5 5 L -9 5 Z", class: "world-marker-shape vehicle-outline" }));
group.append(svg("line", { x1: -4, y1: 0, x2: 7, y2: 0, class: "vehicle-heading" }));
} else if (kind === "smooth-start") {
group.append(svg("circle", { cx: 0, cy: 0, r: 5, class: "world-marker-shape marker-smooth-start" }));
} else if (kind === "plan-start") {
group.append(svg("circle", { cx: 0, cy: 0, r: 5, class: "world-marker-shape marker-plan-start" }));
} else if (kind === "gear-switch" || kind === "gear-switch-end") {
group.append(svg("path", { d: "M 0 -6 L 6 0 L 0 6 L -6 0 Z", class: "world-marker-shape marker-gear-switch" }));
} else if (kind === "final-goal") {
group.append(svg("path", { d: "M -6 -6 L 6 -6 L 6 6 L -6 6 Z", class: "world-marker-shape marker-final-goal" }));
} else {
group.append(svg("path", { d: "M 0 -6 L 6 0 L 0 6 L -6 0 Z", class: "world-marker-shape world-marker" }));
}
overlay.append(group);
const labelY = kind === "plan-start" ? 17 : -9;
const labelGroup = svg("g", { transform: `translate(${marker.position.x} ${-marker.position.y}) scale(${metrics.unitsPerPixel})` });
const label = svg("text", { x: 10, y: labelY, class: "world-marker-label" });
label.textContent = marker.labelChinese || "";
labelGroup.append(label);
overlay.append(labelGroup);
}
```
Replace the complete `renderWorld` function with:
```javascript
function renderWorld() {
const bounds = getBounds();
const metrics = worldMetrics(bounds);
drawOccupancy(bounds);
overlay.replaceChildren();
overlay.setAttribute("viewBox", `${bounds.xMin} ${-bounds.yMax} ${bounds.xMax - bounds.xMin} ${bounds.yMax - bounds.yMin}`);
overlay.setAttribute("preserveAspectRatio", "xMidYMid meet");
renderWorldCoordinates(bounds, metrics);
const snapshot = state.staticSnapshot || {};
const frame = state.frame || {};
const addLine = (line, css) => {
const path = worldPath(line.points);
if (path) overlay.append(svg("path", { d: path, class: "world-line " + css }));
};
const staticLines = safeArray(snapshot.staticPolylines);
staticLines.filter(line => lineKind(line) === "global" || lineKind(line) === "coarse")
.forEach(line => addLine(line, "world-coarse"));
staticLines.filter(line => lineKind(line).includes("local-g2") || lineKind(line).includes("g2"))
.forEach(line => addLine(line, "world-local-g2"));
staticLines.filter(line => lineKind(line) !== "global" && lineKind(line) !== "coarse" && !lineKind(line).includes("local-g2") && !lineKind(line).includes("g2"))
.forEach(line => addLine(line, "world-static"));
safeArray(snapshot.directionSegments).forEach(segment => addLine(segment,
segment.segmentIndex === frame.activeSegmentIndex ? "world-segment-active" : "world-direction-inactive"));
const dynamicLines = safeArray(frame.dynamicPolylines);
dynamicLines.filter(line => lineKind(line) === "active-segment")
.forEach(line => addLine(line, "world-segment-active"));
dynamicLines.filter(line => lineKind(line) === "previous" || line.lineStyle === 1)
.forEach(line => addLine(line, "world-previous"));
dynamicLines.filter(line => lineKind(line) !== "active-segment" && lineKind(line) !== "previous" && line.lineStyle !== 1)
.forEach(line => addLine(line, "world-current"));
const allLines = staticLines.concat(safeArray(snapshot.directionSegments), dynamicLines);
const hasPath = allLines.some(line => safeArray(line && line.points)
.some(point => point && finite(point.x) && finite(point.y)));
safeArray(snapshot.staticMarkers).concat(safeArray(frame.dynamicMarkers))
.forEach(marker => appendWorldMarker(marker, frame, metrics));
if (!hasPath) {
const emptyGroup = svg("g", { transform: `translate(${(bounds.xMin + bounds.xMax) / 2} ${-((bounds.yMin + bounds.yMax) / 2)}) scale(${metrics.unitsPerPixel})` });
const empty = svg("text", { x: 0, y: 0, class: "world-empty-state", "text-anchor": "middle" });
empty.textContent = "未收到 Local G2 路径";
emptyGroup.append(empty);
overlay.append(emptyGroup);
}
}
```
Add resize-aware redraw beside the existing window error listeners:
```javascript
let overviewResizeFrame = 0;
window.addEventListener("resize", () => {
if (overviewResizeFrame || state.activeTab !== "overview" || !state.redrawEnabled.overview) return;
overviewResizeFrame = window.requestAnimationFrame(() => {
overviewResizeFrame = 0;
renderWorld();
});
});
```
- [ ] **Step 5: Run the web asset host and verify success**
Run:
```powershell
dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
```
Expected: `PASS trajectory-planning-visualization`.
- [ ] **Step 6: Commit fixed markers and path layering**
```powershell
git add -- ClumsyPilot/TrajectoryPlanningVisualization/Web/app.js ClumsyPilot/TrajectoryPlanningVisualization/Web/app.css ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/WebAssetChecks.cs
git commit -m "fix: clarify EM overview path layers"
```
## Task 4: Add a Data-Driven World Legend
**Files:**
- Modify: `ClumsyPilot/TrajectoryPlanningVisualization/Web/index.html:21-26`
- Modify: `ClumsyPilot/TrajectoryPlanningVisualization/Web/app.js:7-17,52-94`
- Modify: `ClumsyPilot/TrajectoryPlanningVisualization/Web/app.css:22-24,41-70`
- Test: `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/WebAssetChecks.cs:16-43`
- Test fixture: `ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/SampleSnapshotFactory.cs:11-33`
**Interfaces:**
- Consumes: existing `LegendChinese`, `Kind`, `staticPolylines`, `directionSegments`, `dynamicPolylines`, `staticMarkers`, and `dynamicMarkers`.
- Produces: `renderWorldLegend(snapshot, frame)` and `#world-legend`; entries are deduplicated by semantic key and only emitted when corresponding data exists.
- [ ] **Step 1: Write failing legend asset checks**
Add these assertions in `WebAssetChecks.Run` after the `world-overlay` assertion:
```csharp
Verification.True(html.Contains("id=\"world-legend\""),
"overview contains a screen-space legend host");
Verification.True(html.Contains("aria-label=\"路径图例\""),
"overview legend has a Chinese accessible name");
Verification.True(css.Contains(".world-legend"),
"overview legend has a screen-space visual container");
Verification.True(css.Contains(".world-legend-swatch"),
"overview legend exposes semantic line and marker samples");
Verification.True(js.Contains("function renderWorldLegend("),
"overview builds legend entries from current snapshot data");
Verification.True(js.Contains("legendChinese"),
"overview reuses exported Chinese polyline legends");
```
Add these assertions in `BuildsDeterministicSmokeSnapshots` after creating `staticSnapshot`:
```csharp
Verification.True(staticSnapshot.StaticPolylines.Any(line => line.Kind == "coarse"),
"smoke snapshot exposes the coarse comparison path");
Verification.True(staticSnapshot.StaticPolylines.Any(line => line.Kind == "local-g2"),
"smoke snapshot exposes the smoothed comparison path");
Verification.True(staticSnapshot.StaticMarkers.Any(marker => marker.Kind == "smooth-start"),
"smoke snapshot exposes the smoothed-path start marker");
```
- [ ] **Step 2: Run the web asset host and verify failure**
Run:
```powershell
dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
```
Expected: FAIL with `smoke snapshot exposes the coarse comparison path`; the legend assertions also remain red until Steps 36 are complete.
- [ ] **Step 3: Add the accessible legend host**
Add this element inside `.world-stack`, after `#world-overlay`:
```html
<aside id="world-legend" class="world-legend" aria-label="路径图例" hidden></aside>
```
- [ ] **Step 4: Expand the deterministic smoke snapshot**
Replace the single static `global` polyline in `SampleSnapshotFactory.CreateStaticSnapshot` with:
```csharp
new[]
{
new VisualizationPolyline("coarse-path", "粗路径", "coarse", VisualizationLineStyle.Dashed,
Points((0d, 1d), (3d, 1.2d), (6d, 1.85d), (8d, 4.2d), (11d, 6d))),
new VisualizationPolyline("local-g2-path", "完整 Local G2 路径", "local-g2", VisualizationLineStyle.Solid,
Points((0d, 1d), (3d, 1d), (6d, 2d), (8d, 4d), (11d, 6d)))
},
```
Add this marker as the first item in the static marker array:
```csharp
new VisualizationMarker("smooth-start", "smooth-start", "平滑路径起点", new VisualizationPoint(0d, 1d)),
```
- [ ] **Step 5: Implement data-driven legend population**
Add the legend reference after the existing overlay reference:
```javascript
const worldLegend = document.getElementById("world-legend");
```
Add this function after `appendWorldMarker`:
```javascript
function renderWorldLegend(snapshot, frame) {
if (!worldLegend) return;
worldLegend.replaceChildren();
const staticLines = safeArray(snapshot.staticPolylines);
const directionSegments = safeArray(snapshot.directionSegments);
const dynamicLines = safeArray(frame.dynamicPolylines);
const markers = safeArray(snapshot.staticMarkers).concat(safeArray(frame.dynamicMarkers));
const entries = [];
const seen = new Set();
const add = (key, label, swatchClass) => {
if (seen.has(key)) return;
seen.add(key);
entries.push({ label, swatchClass });
};
const coarse = staticLines.find(line => lineKind(line) === "global" || lineKind(line) === "coarse");
const smooth = staticLines.find(line => lineKind(line).includes("local-g2") || lineKind(line).includes("g2"));
const active = dynamicLines.find(line => lineKind(line) === "active-segment");
const previous = dynamicLines.find(line => lineKind(line) === "previous" || line.lineStyle === 1);
const current = dynamicLines.find(line => lineKind(line) !== "active-segment" && lineKind(line) !== "previous" && line.lineStyle !== 1);
if (coarse) add("coarse", coarse.legendChinese || "粗路径", "legend-line legend-coarse");
if (smooth) add("smooth", smooth.legendChinese || "完整 Local G2 路径", "legend-line legend-smooth");
if (active || directionSegments.length)
add("segment", active && active.legendChinese || "当前方向段", "legend-line legend-segment");
if (previous) add("previous", previous.legendChinese || "上一条 EM 轨迹", "legend-line legend-previous");
if (current) add("current", current.legendChinese || "当前 EM 轨迹", "legend-line legend-current");
if (markers.some(marker => lineKind(marker) === "smooth-start"))
add("smooth-start", "平滑路径起点", "legend-marker legend-smooth-start");
if (markers.some(marker => lineKind(marker) === "plan-start"))
add("plan-start", "EM 规划起点", "legend-marker legend-plan-start");
if (markers.some(marker => lineKind(marker) === "vehicle"))
add("vehicle", "当前车辆", "legend-marker legend-vehicle");
if (markers.some(marker => lineKind(marker) === "gear-switch" || lineKind(marker) === "gear-switch-end"))
add("gear", "换向点 / s_end", "legend-marker legend-gear");
if (markers.some(marker => lineKind(marker) === "final-goal"))
add("goal", "平滑路径终点", "legend-marker legend-goal");
entries.forEach(entry => {
const item = element("div", "world-legend-item");
item.append(element("span", "world-legend-swatch " + entry.swatchClass),
element("span", "world-legend-label", entry.label));
worldLegend.append(item);
});
worldLegend.hidden = entries.length === 0;
}
```
Add this call in `renderWorld` after all markers are appended and before the empty-state branch:
```javascript
renderWorldLegend(snapshot, frame);
```
- [ ] **Step 6: Style the legend with matching semantic samples**
Add this CSS after the world marker rules:
```css
.world-legend { position: absolute; top: 14px; right: 14px; z-index: 3; display: grid; gap: 6px; min-width: 154px; max-width: min(260px, calc(100% - 28px)); padding: 10px 12px; border: 1px solid rgba(112, 123, 133, .38); border-radius: 6px; background: rgba(255, 255, 255, .88); box-shadow: 0 2px 10px rgba(32, 37, 43, .08); color: #36404a; font-size: 12px; pointer-events: none; backdrop-filter: blur(2px); }
.world-legend[hidden] { display: none; }
.world-legend-item { display: grid; grid-template-columns: 30px minmax(0, 1fr); align-items: center; gap: 8px; min-height: 16px; }
.world-legend-swatch { display: block; justify-self: center; }
.legend-line { width: 28px; height: 0; border-top-style: solid; }
.legend-coarse { border-top: 2px dashed rgba(126, 135, 144, .62); }
.legend-smooth { border-top: 4px solid rgba(22, 139, 131, .65); }
.legend-segment { border-top: 2px solid rgba(63, 120, 168, .50); }
.legend-previous { border-top: 2px dashed rgba(136, 116, 147, .72); }
.legend-current { border-top: 3px solid rgba(223, 107, 31, .96); }
.legend-marker { width: 11px; height: 11px; border: 1.5px solid #ffffff; box-shadow: 0 0 0 1px rgba(32, 37, 43, .22); }
.legend-smooth-start { border-radius: 50%; background: #2f8f56; }
.legend-plan-start { border: 2px solid #2f8f56; border-radius: 50%; background: #ffffff; }
.legend-vehicle { width: 17px; height: 9px; border: 1.5px solid #20252b; border-radius: 2px; background: rgba(32, 37, 43, .16); }
.legend-gear { transform: rotate(45deg); background: #d87918; }
.legend-goal { background: #1769aa; }
.world-legend-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
```
Replace the existing mobile media query with the following complete rule:
```css
@media (max-width: 720px) {
header { align-items: start; flex-direction: column; gap: 10px; }
nav, main, header { padding-left: 14px; padding-right: 14px; }
.world-stack { min-height: 360px; }
.world-legend { top: 8px; right: 8px; min-width: 138px; padding: 8px 9px; font-size: 11px; }
.config-entry { grid-template-columns: 1fr 1fr; }
}
```
- [ ] **Step 7: Run focused and integration verification**
Run:
```powershell
dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
dotnet build ClumsyPilot/ClumsyPilot.csproj -p:ExcludeLegacyAutoAvoidance=true --no-restore
```
Expected output contains:
```text
PASS trajectory-planning-visualization
PASS trajectory-observation
Build succeeded.
```
- [ ] **Step 8: Run the local visual smoke test**
Run the sample server for 60 seconds:
```powershell
dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj -- --smoke-seconds 60
```
Open the printed loopback URL and verify every item:
- the vehicle is approximately 1820 screen pixels long and does not cover the route;
- X and Y retain equal spatial scale;
- metric ticks, grid, `X (m)`, and `Y (m)` are readable;
- coarse gray dashes, wide translucent teal smooth path, and narrow orange EM path remain visible where they overlap;
- the legend matches only elements visible in the snapshot;
- smoothed start, final goal, current vehicle, and EM plan-start markers are visible;
- no browser console error or “页面绘图异常” status appears.
- [ ] **Step 9: Check scoped diff hygiene and commit**
Run:
```powershell
git diff --check -- ClumsyPilot/TrajectoryPlanningVisualization/Web/index.html ClumsyPilot/TrajectoryPlanningVisualization/Web/app.js ClumsyPilot/TrajectoryPlanningVisualization/Web/app.css ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/WebAssetChecks.cs ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/SampleSnapshotFactory.cs
git status --short -- ClumsyPilot/TrajectoryPlanningVisualization/Web/index.html ClumsyPilot/TrajectoryPlanningVisualization/Web/app.js ClumsyPilot/TrajectoryPlanningVisualization/Web/app.css ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/WebAssetChecks.cs ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/SampleSnapshotFactory.cs
git add -- ClumsyPilot/TrajectoryPlanningVisualization/Web/index.html ClumsyPilot/TrajectoryPlanningVisualization/Web/app.js ClumsyPilot/TrajectoryPlanningVisualization/Web/app.css ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/WebAssetChecks.cs ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/SampleSnapshotFactory.cs
git commit -m "feat: add legend to EM path overview"
```
Expected: `git diff --check` emits no error, scoped status lists only the intended files before staging, and the commit contains only those five paths.
## Final Verification
After all task commits, run:
```powershell
dotnet run --project ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/TrajectoryPlanningVisualizationVerificationHost.csproj
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
dotnet build ClumsyPilot/ClumsyPilot.csproj -p:ExcludeLegacyAutoAvoidance=true --no-restore
git diff --check -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationStaticSnapshotBuilder.cs ClumsyPilot/TrajectoryPlanningVisualization/Web/index.html ClumsyPilot/TrajectoryPlanningVisualization/Web/app.js ClumsyPilot/TrajectoryPlanningVisualization/Web/app.css ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationVisualizationChecks.cs ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/WebAssetChecks.cs ClumsyPilot/tests/TrajectoryPlanningVisualizationVerificationHost/SampleSnapshotFactory.cs
```
Expected: both verification hosts print their PASS lines, the main project reports `Build succeeded.`, and the scoped diff check produces no errors.
@@ -1,136 +0,0 @@
# Trajplanner_output 真实轨迹输出 Demo Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use `executing-plans` to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 提供一个可配置、可运行、可导出且可供控制模块学习引用的真实 EM 轨迹序列 Demo。
**Architecture:** 独立 `net10.0-windows` 控制台项目以 `ProjectReference` 调用已有规划库,在固定演示场景中依次得到粗路径、平滑路径和真实 OSQP EM 轨迹。输出层只消费不可变 `EmTrajectory`,将其写入 CSV 并投影为控制模块 DTO,不包含硬件调用。
**Tech Stack:** .NET 10、C#、`ClumsyPilot.csproj`、OSQP Windows x64、CSV、Markdown。
## Global Constraints
- Demo 必须使用 `EmPlanningService(new OsqpNativeSolver())`,不得用假求解器伪造成功轨迹。
- Demo 配置全部集中于 `TrajectoryOutputDemoConfiguration.cs`;位置 m、航向 rad、速度 m/s、曲率 1/m、时间 s。
- 只接受 `Success``SuccessWithFallback` 的非空 `EmTrajectory`
- OSQP 或任一规划阶段失败时非零退出,不输出部分/伪造 CSV。
- 控制模块适配器只提供只读序列,不驱动、转向、制动或换向设备。
- 新增注释使用 CoarsePath 风格中文 XML 文档注释。
---
### Task 1: 创建可运行 Demo 项目和集中配置
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/Trajplanner_output/TrajectoryOutputDemo.csproj`
- Create: `ClumsyPilot/ParkrobTrajplanner/Trajplanner_output/Program.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/Trajplanner_output/TrajectoryOutputDemoConfiguration.cs`
- Test: `ClumsyPilot/ParkrobTrajplanner/Trajplanner_output/TrajectoryOutputDemo.csproj`
**Interfaces:**
- Consumes: `ClumsyPilot.csproj``CoarsePath``PathSmoothing``EMPlanner` 公共 API。
- Produces: 一份可复制、单文件可调的演示配置和标准退出码入口。
- [ ] **Step 1: 写入项目引用**
创建 `net10.0-windows` 控制台项目,关闭隐式 using/启用 nullable,并引用 `../../ClumsyPilot.csproj`,同时设定 `ExcludeLegacyAutoAvoidance=true`
- [ ] **Step 2: 写入默认演示配置**
配置包含 `MapBoundsMm(0, 6000, 0, 4000)``50 mm` 栅格、显式空地图、起点 `(1,1,0)`、终点 `(3,1,0)`、车辆 `0.80 m × 0.60 m``0.05 m` 安全余量、`1/1.20 1/m` 曲率上限、前进方向与 `output/trajectory.csv`
- [ ] **Step 3: 编写失败入口测试**
Run: `dotnet run --project ClumsyPilot/ParkrobTrajplanner/Trajplanner_output/TrajectoryOutputDemo.csproj -- --invalid-option`
Expected: 非零退出并打印使用说明;尚未实现时命令因项目不存在而失败。
### Task 2: 实现真实规划链路与成功/失败边界
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/Trajplanner_output/TrajectoryOutputDemoRunner.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Trajplanner_output/Program.cs`
- Test: `ClumsyPilot/ParkrobTrajplanner/Trajplanner_output/TrajectoryOutputDemo.csproj`
**Interfaces:**
- Consumes: `CoarsePathPlanningService.Plan``PathSmoothingService.Smooth``EmPlanningService.Plan`
- Produces: 成功时 `EmTrajectory`;失败时含阶段、状态和原因的非零结果。
- [ ] **Step 1: 构造冻结的 CoarsePath 与平滑请求**
使用配置创建 `CoarsePathPlanningJob``PathSmoothingRequest`,每一步仅在成功状态且输出非空时进入下一阶段;失败信息写入 Demo 结果。
- [ ] **Step 2: 构造真实 EM 请求**
以平滑路径、同一地图、车辆、`VehicleMotionState`、默认 `EmPlannerConfiguration`、方向段索引和唯一输出 ID 创建 `EmPlanningRequest`,并调用 `new EmPlanningService(new OsqpNativeSolver()).Plan(...)`
- [ ] **Step 3: 拒绝非完整输出**
仅当 `result.Status``Success``SuccessWithFallback``result.Trajectory` 非空且点数大于零时返回成功;其他状态返回非零并输出 `FailureReason`
- [ ] **Step 4: 运行真实链路**
Run: `dotnet run --project ClumsyPilot/ParkrobTrajplanner/Trajplanner_output/TrajectoryOutputDemo.csproj`
Expected: OSQP 可用时输出轨迹 ID、点数和 CSV 路径;不可用时输出明确 OSQP/规划诊断且不产生成功 CSV。
### Task 3: 导出轨迹序列和控制模块只读适配器
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/Trajplanner_output/TrajectorySequenceExporter.cs`
- Create: `ClumsyPilot/ParkrobTrajplanner/Trajplanner_output/ControlModuleTrajectoryAdapter.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Trajplanner_output/TrajectoryOutputDemoRunner.cs`
- Test: `ClumsyPilot/ParkrobTrajplanner/Trajplanner_output/TrajectoryOutputDemo.csproj`
**Interfaces:**
- Consumes: `EmTrajectory.Metadata``IReadOnlyList<EmTrajectoryPoint>`
- Produces: UTF-8 CSV,以及控制模块可枚举的只读 `ControlTrajectoryPoint` 序列。
- [ ] **Step 1: 实现 CSV 字段和原子写入**
首行固定为 `time_s,x_m,y_m,yaw_rad,signed_velocity_mps,yaw_rate_radps,curvature_per_m,direction,segment_index,path_s_m,boundary_type`。成功轨迹写入临时文件后原子替换目标 CSV,避免控制模块读到半文件。
- [ ] **Step 2: 实现控制 DTO**
`ControlTrajectoryPoint` 提供时间、位置、航向、带符号速度、yaw rate、曲率、方向和边界类型;`ControlModuleTrajectoryAdapter.Create(EmTrajectory)` 返回只读列表和元数据,不产生任何硬件调用。
- [ ] **Step 3: 输出摘要**
控制台打印轨迹 ID、生效时间、方向段、终端类型、点数、首末点和 CSV 绝对路径,不逐行刷屏。
- [ ] **Step 4: 验证文件内容**
Run: `Import-Csv ClumsyPilot/ParkrobTrajplanner/Trajplanner_output/output/trajectory.csv | Select-Object -First 1`
Expected: 首个数据行具有全部 11 个字段,时间字段为非负数。
### Task 4: 编写 README 和最终验证
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/Trajplanner_output/README.md`
- Modify: `ClumsyPilot/ParkrobTrajplanner/Trajplanner_output/*.cs`
- Test: `ClumsyPilot/ParkrobTrajplanner/Trajplanner_output/TrajectoryOutputDemo.csproj`
**Interfaces:**
- Consumes: 最终项目、配置、CSV 和控制 DTO。
- Produces: 对外可复现的运行、调参和控制模块引用说明。
- [ ] **Step 1: 以 CoarsePath 结构编写 README**
写入模块职责、文件结构、数据流、单位、运行命令、配置表、CSV 契约、控制模块 `ProjectReference` 示例、失败语义和“演示空地图不得用于真实作业”的限制。
- [ ] **Step 2: 完成 XML 注释**
每个公开类型、配置字段、运行阶段、导出边界和控制 DTO 都说明职责、单位与失败/只读语义;不写逐行翻译式注释。
- [ ] **Step 3: 运行格式与构建验证**
Run: `dotnet build ClumsyPilot/ParkrobTrajplanner/Trajplanner_output/TrajectoryOutputDemo.csproj; git diff --check`
Expected: 构建退出 0,格式检查退出 0;若现有 Visual Studio 锁定依赖 DLL,记录锁定文件和进程,不假称通过。
- [ ] **Step 4: 提交 Demo**
Run: `git add -- ClumsyPilot/ParkrobTrajplanner/Trajplanner_output docs/superpowers/plans/2026-08-09-trajplanner-output-demo.md; git commit -m "feat: add trajectory output demo"`
Expected: 本机提交只包含 Demo、README 与实施计划。
@@ -1,508 +0,0 @@
# EM Closed-Loop Movement Test 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 an `EM闭环测试` MovementTest that plans one complete EM direction segment once, freezes it, adapts it to `Trajectory2D`, and tracks it with the imported Stanley/PID/GCP controller until that segment stops.
**Architecture:** Keep the planning and controller models separate. Restore the reference controller's smallest portable runtime dependency set inside `ClumsyPilot`, add a pure `EmTrajectory`-to-`Trajectory2D` adapter, then make a session runner reuse the existing trajectory-observation bootstrap and one `TrajectoryObservationController.StartCycle` call before handing the frozen trajectory to `TrajectoryTrackingMovement`.
**Tech Stack:** C# 10, .NET Standard 2.0 plugin, .NET 10 Windows verification host, Clumsy `MovementTest`/`DriveTask`, existing EM planner and OSQP runtime.
## Global Constraints
- The MovementTest registration name is exactly `EM闭环测试`.
- Plan exactly once in `EmPlanningScope.FullDirectionSegment`; do not replan or replace the trajectory while driving.
- Execute only the first direction segment. A gear-switch terminal stops and ends this test.
- Shared/control boundaries use SI units: m, m/s, rad, rad/s; body X is forward, body Y is left, counter-clockwise is positive.
- Preserve signed longitudinal velocity: forward positive and reverse negative.
- Do not change velocity/steering signs, CAN IDs, remote mappings, mechanical limits, or mode-switch policy.
- Do not overwrite unrelated dirty files. Stage only the exact files named by each task.
- Real-vehicle validation must remain low-speed, short-distance, in an open area, with hardware emergency stop available.
---
## File Map
### Existing user files retained and brought under version control
- `ClumsyPilot/Control/**/*.cs` — Stanley/PID/GCP controller implementation already copied from the reference project.
- `ClumsyPilot/Trajectory/**/*.cs` — controller-side trajectory and projection implementation already copied from the reference project.
### Portable runtime dependencies copied from the reference project
- `ClumsyPilot/Shared/Mathematics/AngleMath.cs` — angle normalization and conversion.
- `ClumsyPilot/Shared/Mathematics/InterpolationMath.cs` — scalar interpolation.
- `ClumsyPilot/Shared/Mathematics/FrameTransform2D.cs` — world/body transforms.
- `ClumsyPilot/Shared/Models/ChassisCommand.cs``Pose2D`, `Twist2D`, and chassis command value types.
- `ClumsyPilot/Shared/Chassis/MultiWheelChassisAdapter.cs` — SI/GCP boundary over the existing `MultiWheelChassis`.
- `ClumsyPilot/StateEstimation/IVehicleStateProvider.cs` — controller state-source boundary.
- `ClumsyPilot/StateEstimation/VehicleState.cs` — immutable controller state.
- `ClumsyPilot/StateEstimation/FirstOrderLowPassFilter.cs` — velocity filter primitive.
- `ClumsyPilot/StateEstimation/VelocityEstimator2D.cs` — Detour velocity estimation.
- `ClumsyPilot/StateEstimation/DetourVehicleStateProvider.cs` — live Detour state source.
- `ClumsyPilot/Movements/TrajectoryTrackingMovement.cs``DriveTask` movement wrapper around the geometric controller.
### New EM closed-loop files
- `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/EmControlTrajectoryAdapter.cs` — pure model adapter.
- `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.EmClosedLoopTest.cs` — UI input, session ownership, one-shot planning, controller handoff, and stop behavior.
- `ClumsyPilot/tests/EMPlannerVerificationHost/EmControlTrajectoryAdapterChecks.cs` — adapter behavior checks.
- `ClumsyPilot/tests/verify_em_closed_loop_movement.ps1` — MovementTest registration and one-shot lifecycle checks.
- `ClumsyPilot/tests/EMPlannerVerificationHost/Program.cs` — add the `em-control-adapter` verification command.
- `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/README.md` — operator procedure and test boundary.
---
### Task 1: Restore the controller's portable runtime dependency chain
**Files:**
- Add and track: `ClumsyPilot/Control/**/*.cs`
- Add and track: `ClumsyPilot/Trajectory/**/*.cs`
- Create: `ClumsyPilot/Shared/Mathematics/AngleMath.cs`
- Create: `ClumsyPilot/Shared/Mathematics/InterpolationMath.cs`
- Create: `ClumsyPilot/Shared/Mathematics/FrameTransform2D.cs`
- Create: `ClumsyPilot/Shared/Models/ChassisCommand.cs`
- Create: `ClumsyPilot/Shared/Chassis/MultiWheelChassisAdapter.cs`
- Create: `ClumsyPilot/StateEstimation/IVehicleStateProvider.cs`
- Create: `ClumsyPilot/StateEstimation/VehicleState.cs`
- Create: `ClumsyPilot/StateEstimation/FirstOrderLowPassFilter.cs`
- Create: `ClumsyPilot/StateEstimation/VelocityEstimator2D.cs`
- Create: `ClumsyPilot/StateEstimation/DetourVehicleStateProvider.cs`
- Create: `ClumsyPilot/Movements/TrajectoryTrackingMovement.cs`
**Interfaces:**
- Produces: `MyParking.Shared.Pose2D`, `Twist2D`, `AngleMath`, `MultiWheelChassisAdapter`.
- Produces: `MultiWheelC.StateEstimation.IVehicleStateProvider` and `DetourVehicleStateProvider`.
- Produces: `MultiWheelC.Trajectory.Trajectory2D` and `TrajectoryPoint`.
- Produces: `MultiWheelC.TrajectoryTrackingMovement : MovementDefinition` with public `Trajectory`, `StateProvider`, `CycleObserver`, and safety-limit fields.
- [ ] **Step 1: Re-run the failing integration build**
Run:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
```
Expected: FAIL with missing `MyParking.Shared`, `MultiWheelC.StateEstimation`, `VehicleState`, and `MultiWheelChassisAdapter`. This proves the partial controller import is not silently excluded from the plugin build.
- [ ] **Step 2: Add the exact portable reference sources**
Read each source from:
```text
D:\Users\Desktop\项目\prakrobot\停车机器人-合并测试\parkr_shen\Shared\...
D:\Users\Desktop\项目\prakrobot\停车机器人-合并测试\parkr_shen\MultiWheelC\StateEstimation\...
D:\Users\Desktop\项目\prakrobot\停车机器人-合并测试\parkr_shen\MultiWheelC\Movements\TrajectoryTrackingMovement.cs
```
Add the files at the destination paths in this task without changing namespaces or logic. Do not import `FleetKinematics`, experiment recorders, composite movements, rotation movements, or wheel-feedback providers because `TrajectoryTrackingMovement` does not consume them.
- [ ] **Step 3: Prove existing controller/trajectory files still match the reference implementation**
Run:
```powershell
$reference = 'D:\Users\Desktop\项目\prakrobot\停车机器人-合并测试\parkr_shen\MultiWheelC'
$local = (Resolve-Path '.\ClumsyPilot').Path
Get-ChildItem '.\ClumsyPilot\Control','.\ClumsyPilot\Trajectory' -File -Recurse | ForEach-Object {
$relative = $_.FullName.Substring($local.Length + 1)
$source = Join-Path $reference $relative
if (-not (Test-Path $source) -or (Get-FileHash $_.FullName).Hash -ne (Get-FileHash $source).Hash) {
throw "Reference mismatch: $relative"
}
}
```
Expected: exit 0 and no mismatch.
- [ ] **Step 4: Run the build to verify the dependency chain compiles**
Run:
```powershell
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
```
Expected: PASS with zero errors. Record warnings verbatim; do not hide them.
- [ ] **Step 5: Commit only the controller runtime integration**
```powershell
git add -- ClumsyPilot/Control ClumsyPilot/Trajectory ClumsyPilot/Shared ClumsyPilot/StateEstimation ClumsyPilot/Movements/TrajectoryTrackingMovement.cs
git diff --cached --check
git commit -m "feat: integrate trajectory tracking controller runtime"
```
---
### Task 2: Adapt a frozen EM trajectory to `Trajectory2D`
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/EmControlTrajectoryAdapter.cs`
- Create: `ClumsyPilot/tests/EMPlannerVerificationHost/EmControlTrajectoryAdapterChecks.cs`
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/Program.cs`
**Interfaces:**
- Consumes: `EmTrajectory` with validated SI-unit `EmTrajectoryPoint` values.
- Produces: `public sealed class EmControlTrajectoryAdapter`.
- Produces: `public Trajectory2D Create(EmTrajectory trajectory)`.
- Duplicate-position threshold: `1e-6 m`, matching `Trajectory2D`'s minimum valid projection segment.
- [ ] **Step 1: Register the adapter check before implementing the adapter**
Extend `Program.cs` argument validation with `em-control-adapter`, and add:
```csharp
if (args[0] == "em-control-adapter" || args[0] == "em-all")
{
EmControlTrajectoryAdapterChecks.Run();
Console.WriteLine("PASS em-control-adapter");
}
```
Create `EmControlTrajectoryAdapterChecks.cs` with checks that build real `EmTrajectory` objects and assert:
```csharp
internal static void Run()
{
MapsFieldsAndRebuildsGeometricArcLength();
KeepsReverseVelocityNegative();
CollapsesTerminalHoldAndKeepsItsZeroSpeed();
RejectsFewerThanTwoDistinctPositions();
}
```
The forward mapping check must assert all of these exact relations:
```csharp
Trajectory2D actual = new EmControlTrajectoryAdapter().Create(source);
Require(actual.Count == 3, "adapter point count");
RequireClose(actual[0].ArcLengthMeters, 0d, "first arc");
RequireClose(actual[1].ArcLengthMeters, 0.5d, "second arc");
RequireClose(actual[2].ArcLengthMeters, 1.0d, "third arc");
RequireClose(actual[1].PoseInWorld.XMeters, source.Points[1].X, "x");
RequireClose(actual[1].PoseInWorld.YMeters, source.Points[1].Y, "y");
RequireClose(actual[1].PoseInWorld.YawRadians, source.Points[1].Yaw, "yaw");
RequireClose(actual[1].CurvaturePerMeter, source.Points[1].VehicleCurvature, "curvature");
RequireClose(actual[1].ReferenceSpeedMetersPerSecond,
source.Points[1].SignedLongitudinalVelocity, "signed speed");
```
For the terminal-hold case, provide two final EM samples at the same pose where the later sample has zero speed, then assert that the output contains one terminal position and `EndPoint.ReferenceSpeedMetersPerSecond == 0d`.
- [ ] **Step 2: Run the new check and verify RED**
Run:
```powershell
dotnet run --project .\ClumsyPilot\tests\EMPlannerVerificationHost\EMPlannerVerificationHost.csproj -- em-control-adapter
```
Expected: FAIL to compile because `EmControlTrajectoryAdapter` does not exist.
- [ ] **Step 3: Implement the minimum adapter**
Implement this public surface and algorithm:
```csharp
public sealed class EmControlTrajectoryAdapter
{
private const double MinimumSegmentLengthMeters = 1e-6;
public Trajectory2D Create(EmTrajectory trajectory)
{
if (trajectory == null)
throw new ArgumentNullException(nameof(trajectory));
var points = new List<TrajectoryPoint>(trajectory.Points.Count);
double arcLength = 0d;
for (int index = 0; index < trajectory.Points.Count; index++)
{
EmTrajectoryPoint source = trajectory.Points[index];
var pose = new MyParking.Shared.Pose2D(source.X, source.Y, source.Yaw);
var converted = new TrajectoryPoint(
arcLength, pose, source.VehicleCurvature, source.SignedLongitudinalVelocity);
if (points.Count == 0)
{
points.Add(converted);
continue;
}
TrajectoryPoint previous = points[points.Count - 1];
double dx = pose.XMeters - previous.PoseInWorld.XMeters;
double dy = pose.YMeters - previous.PoseInWorld.YMeters;
double distance = Math.Sqrt(dx * dx + dy * dy);
if (distance < MinimumSegmentLengthMeters)
{
points[points.Count - 1] = new TrajectoryPoint(
previous.ArcLengthMeters, pose, source.VehicleCurvature,
source.SignedLongitudinalVelocity);
continue;
}
arcLength += distance;
points.Add(new TrajectoryPoint(
arcLength, pose, source.VehicleCurvature,
source.SignedLongitudinalVelocity));
}
if (points.Count < 2)
throw new ArgumentException(
"EM轨迹至少需要包含两个不同位置的有效控制点。", nameof(trajectory));
return new Trajectory2D(points);
}
}
```
Include `/// <summary>` comments on the class and public method, explicitly stating the unit and signed-speed boundary.
- [ ] **Step 4: Run adapter checks and the existing EM suite**
Run:
```powershell
dotnet run --project .\ClumsyPilot\tests\EMPlannerVerificationHost\EMPlannerVerificationHost.csproj -- em-control-adapter
dotnet run --project .\ClumsyPilot\tests\EMPlannerVerificationHost\EMPlannerVerificationHost.csproj -- trajectory-observation
```
Expected: both commands PASS.
- [ ] **Step 5: Commit the adapter and checks**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/EmControlTrajectoryAdapter.cs ClumsyPilot/tests/EMPlannerVerificationHost/EmControlTrajectoryAdapterChecks.cs ClumsyPilot/tests/EMPlannerVerificationHost/Program.cs
git diff --cached --check
git commit -m "feat: adapt EM trajectory for geometric controller"
```
---
### Task 3: Add the one-shot, single-direction `EM闭环测试`
**Files:**
- Create: `ClumsyPilot/tests/verify_em_closed_loop_movement.ps1`
- Create: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.EmClosedLoopTest.cs`
**Interfaces:**
- Consumes: `TrajectoryObservationSetupFactory.CreateBootstrapJob(...)`.
- Consumes: `TrajectoryObservationBootstrapper.Bootstrap(...)`.
- Consumes exactly one call to `TrajectoryObservationController.StartCycle(...)`.
- Consumes: `EmControlTrajectoryAdapter.Create(...)`.
- Produces: `[MovementTest(name = "EM闭环测试")] public sealed class EmClosedLoopMovementTest : MovementTest`.
- Produces: `Test()` for session start and `TestStop()` for idempotent cancellation/stop.
- [ ] **Step 1: Write the structural/lifecycle verification first**
Create `verify_em_closed_loop_movement.ps1` so it reads the new source file and fails unless all conditions hold:
```powershell
$sourcePath = Join-Path $PSScriptRoot '..\ParkrobTrajplanner\tarjplanner_movementtest\MovementTest.EmClosedLoopTest.cs'
if (-not (Test-Path -LiteralPath $sourcePath)) { throw 'EM closed-loop MovementTest source is missing.' }
$source = Get-Content -Raw -Encoding UTF8 -LiteralPath $sourcePath
$required = @(
'\[MovementTest\(name = "EM闭环测试"\)\]',
'EmPlanningScope\.FullDirectionSegment',
'TrajectoryObservationBootstrapper',
'\.StartCycle\(',
'EmControlTrajectoryAdapter',
'TrajectoryTrackingMovement',
'public override void TestStop\(\)',
'\.Cancel\(\)',
'\.Stop\(\)'
)
foreach ($pattern in $required) {
if ($source -notmatch $pattern) { throw "Missing required pattern: $pattern" }
}
if ([regex]::Matches($source, '\.StartCycle\(').Count -ne 1) {
throw 'EM closed-loop test must contain exactly one planning-cycle call site.'
}
if ($source -match 'TrajectoryObservationLoop|ReplanPeriod|while\s*\(true\).*StartCycle') {
throw 'EM closed-loop test must not contain a replanning loop.'
}
Write-Output 'EM closed-loop MovementTest checks passed.'
```
- [ ] **Step 2: Run the verification and confirm RED**
Run:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_em_closed_loop_movement.ps1
```
Expected: FAIL with `EM closed-loop MovementTest source is missing.`
- [ ] **Step 3: Implement the MovementTest input surface**
Add `EmClosedLoopMovementTest` with the same goal, map, obstacle, vehicle, solver, and output-step defaults used by `TrajectoryObservationMovementTest`. Set control defaults explicitly:
```csharp
public double MaximumCommandSpeedMetersPerSecond = 0.20d;
public double MaximumDistanceToTrajectoryMeters = 0.30d;
public double ExecutionTimeoutSeconds = 120d;
public float WheelAlignmentToleranceDegrees = 2f;
```
`Test()` must validate inputs, create a validated settings snapshot with:
```csharp
PlanningScope = EmPlanningScope.FullDirectionSegment
```
and start one session through `EmClosedLoopMovementTestRunner.Start(...)`. `TestStop()` must call only:
```csharp
EmClosedLoopMovementTestRunner.Stop();
```
Use the existing observation test's culture-aware finite-number and bounded-obstacle input behavior. Keep those helpers private to the new test so the user's modified observation file is not edited.
- [ ] **Step 4: Implement idempotent session ownership and one-shot planning**
Use a static runner guarded by one lock. Store one `CancellationTokenSource`, one background `Task`, one active `DriveTask`, and a monotonically increasing session ID.
The planning body must follow this exact sequence:
```csharp
VehicleMotionState initialState = ReadPlanningState();
CoarsePathPlanningJob job = TrajectoryObservationSetupFactory.CreateBootstrapJob(
initialState.Pose, goal, settings, obstacles, obstacleSnapshotVersion);
TrajectoryObservationBootstrapResult bootstrap =
new TrajectoryObservationBootstrapper().Bootstrap(job, token);
if (!bootstrap.Succeeded)
throw new InvalidOperationException(bootstrap.FailureReason);
var controller = new TrajectoryObservationController(
bootstrap, settings, new EmPlanningService(new OsqpNativeSolver()),
"em-closed-loop-" + sessionId.ToString(CultureInfo.InvariantCulture));
PlanningCycleResult cycle = await controller.StartCycle(
initialState.CapturedAtUtc, initialState, token).ConfigureAwait(false);
if (!cycle.Published || controller.PublishedTrajectory == null)
throw new InvalidOperationException(
string.IsNullOrWhiteSpace(cycle.Result.FailureReason)
? cycle.Diagnostic
: cycle.Result.FailureReason);
Trajectory2D trajectory =
new EmControlTrajectoryAdapter().Create(controller.PublishedTrajectory);
```
There must be no observation loop and no second `StartCycle` call. Log the EM point count, controller point count, geometric length, segment index, direction, and terminal type before control starts.
Create `EmClosedLoopWheelSafety.AreWheelsForward(float toleranceDegrees)` in the same file using `MultiWheelChassisAdapter.AreParallelWheelsAligned(0d, toleranceRadians)`. If this check fails, throw before constructing `DriveTask`.
Create and run the controller movement as:
```csharp
var movement = new TrajectoryTrackingMovement
{
Trajectory = trajectory,
MaximumCommandSpeedMetersPerSecond = maximumCommandSpeedMetersPerSecond,
MaximumDistanceToTrajectoryMeters = maximumDistanceToTrajectoryMeters,
ExecutionTimeoutSeconds = executionTimeoutSeconds
};
var driveTask = new DriveTask(movement.Get());
```
Publish `driveTask` under the session lock only if the session is still current and not cancelled. Otherwise stop it immediately. For a current session call `driveTask.Wait()` on the background worker, then report that the first direction segment ended. A `GearSwitch` terminal is reported as “stopped at gear-switch boundary; next segment was not started.”
`Stop()` must atomically clear the active references, call `CancellationTokenSource.Cancel()`, and call `DriveTask.Stop()`. The completion continuation must clear only references that still belong to its own session.
- [ ] **Step 5: Run structural verification and compile**
Run:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_em_closed_loop_movement.ps1
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
```
Expected: structural check PASS; build PASS with zero errors.
- [ ] **Step 6: Commit the MovementTest**
```powershell
git add -- ClumsyPilot/tests/verify_em_closed_loop_movement.ps1 ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/MovementTest.EmClosedLoopTest.cs
git diff --cached --check
git commit -m "feat: add one-shot EM closed-loop movement test"
```
---
### Task 4: Document operation and run the complete non-hardware verification
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/README.md`
**Interfaces:**
- Documents: test selection, inputs, one-shot/single-direction boundary, `TestStop`, low-speed first-run procedure, and limits of automated validation.
- [ ] **Step 1: Add the operator-facing section**
Add a section named `EM闭环测试` containing these exact operational facts:
```text
- 启动时只规划一次,并冻结首次成功的完整方向段轨迹。
- 控制执行期间不重规划、不切换轨迹。
- 到达目标或换向边界后停车;本测试不启动下一方向段。
- 默认控制速度上限为 0.20 m/s。
- 规划、适配或车轮方向检查失败时不会启动底盘控制。
- TestStop 会同时取消规划并停止活动控制任务。
- 首次实车测试使用空旷环境、短距离单方向目标,并确保硬件急停可用。
```
- [ ] **Step 2: Run all targeted checks**
Run:
```powershell
dotnet run --project .\ClumsyPilot\tests\EMPlannerVerificationHost\EMPlannerVerificationHost.csproj -- em-control-adapter
dotnet run --project .\ClumsyPilot\tests\EMPlannerVerificationHost\EMPlannerVerificationHost.csproj -- trajectory-observation
powershell -NoProfile -ExecutionPolicy Bypass -File .\ClumsyPilot\tests\verify_em_closed_loop_movement.ps1
dotnet build .\ClumsyPilot\ClumsyPilot.csproj --no-restore
```
Expected: every command exits 0. Report warning counts from the build.
- [ ] **Step 3: Verify scope and source hygiene**
Run:
```powershell
git diff --check
git status --short
git diff --no-ext-diff -- ClumsyPilot/Control ClumsyPilot/Trajectory ClumsyPilot/Shared ClumsyPilot/StateEstimation ClumsyPilot/Movements ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest ClumsyPilot/tests
```
Expected: no whitespace errors; only files named by this plan are staged or changed by this work. Existing unrelated modifications remain untouched.
- [ ] **Step 4: Commit documentation**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/README.md
git diff --cached --check
git commit -m "docs: explain EM closed-loop test operation"
```
- [ ] **Step 5: Hand off real-vehicle validation without claiming it passed**
Report separately:
```text
Automated status: adapter checks, observation regressions, structural lifecycle check, and plugin build.
Hardware status: not run by automated verification; requires a low-speed, short, single-direction field test with emergency stop available.
```
@@ -1,166 +0,0 @@
# EM PathS Monotonic Resampling Fix 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:** Normalize solver-sized `PathS` residuals during full-direction EM trajectory publication while rejecting material regressions.
**Architecture:** `EmTrajectoryAssembler` passes `Validation.KinematicTolerance` into `TrajectorySampleSchedule`. Each output sample is normalized into `[previousPathS, terminalPathS]` only when the required correction is within that tolerance; larger violations retain fail-closed behavior with numeric diagnostics.
**Tech Stack:** C#, .NET 10 verification host, netstandard2.0 plugin, `EMPlannerVerificationHost`.
## Global Constraints
- Do not modify `ClumsyPilot/Control`, `Shared`, `ClumsyPilot/StateEstimation`, `ClumsyPilot/Trajectory`, or controller Movement sources.
- Reuse `Validation.KinematicTolerance`; add no independent tolerance setting.
- Published `PathS` must be nondecreasing and no greater than terminal `PathS`.
- Violations greater than tolerance must still be rejected.
---
### Task 1: Add PathS resampling regression coverage
**Files:**
- Modify: `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryChecks.cs`
**Interfaces:**
- Consumes: `EmTrajectoryAssembler.Assemble(...)` with `EmPlanningScope.FullDirectionSegment`.
- Produces: regression checks invoked by `TrajectoryChecks.Run()`.
- [ ] **Step 1: Write the failing tolerance-sized regression test**
Add `VerifiesFullScopeNormalizesToleranceSizedPathSResiduals()` to `Run()`. Construct four knots at `0.0, 0.1, 0.2, 0.3` seconds with `S = 0.0, 0.05, 0.05 - 0.5 * KinematicTolerance, 0.10`, use `RollingContinuation` plus `FullDirectionSegment`, assemble it, and assert every published `PathS` is at least its predecessor and no greater than `candidate.S[^1]`.
Use this metadata helper signature so existing calls remain valid:
```csharp
private static EmTrajectoryMetadata CreateMetadata(TravelDirection direction, EmTerminalType terminalType,
EmLongitudinalMode longitudinalMode = EmLongitudinalMode.ExactStopAtBoundary,
EmPlanningScope planningScope = EmPlanningScope.RollingHorizon)
{
return new EmTrajectoryMetadata("trajectory", DateTimeOffset.UnixEpoch, DateTimeOffset.UnixEpoch, 3L,
"reference", 4L, string.Empty, 2, direction, terminalType, longitudinalMode, planningScope);
}
```
- [ ] **Step 2: Write the material-regression protection test**
Add `VerifiesFullScopeRejectsMaterialPathSRegressionWithDiagnostics()`. Use the same time knots with `S = 0.0, 0.05, 0.04, 0.10`; assert assembly throws `ArgumentException` and its message contains `sampleIndex=2`, `previousPathS=`, `candidatePathS=`, and `difference=`.
- [ ] **Step 3: Run the focused test and verify RED**
Run:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory
```
Expected: FAIL with `Trajectory PathS cannot decrease. (Parameter 'candidate')` in the tolerance-sized regression test.
- [ ] **Step 4: Commit the red tests**
```powershell
git add -- ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryChecks.cs
git commit -m "test: reproduce EM PathS resampling regression"
```
### Task 2: Implement bounded monotonic normalization
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Trajectory/TrajectorySampleSchedule.cs`
- Modify: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Trajectory/EmTrajectoryAssembler.cs`
**Interfaces:**
- Consumes: `EmPlannerConfiguration.Validation.KinematicTolerance`.
- Produces: `TrajectorySampleSchedule(..., bool resampleMotion, double pathSTolerance)`.
- [ ] **Step 1: Pass the configured tolerance into the schedule**
In `EmTrajectoryAssembler`, store and validate:
```csharp
private readonly double pathSTolerance;
pathSTolerance = configuration.Validation.KinematicTolerance;
if (!IsFinite(pathSTolerance) || pathSTolerance < 0d)
throw new ArgumentOutOfRangeException(nameof(configuration));
```
Construct the schedule with:
```csharp
var schedule = new TrajectorySampleSchedule(longitudinal.Candidate, outputTimeStepSeconds,
holdDurationSeconds, metadata.LongitudinalMode, isFullDirectionSegment, pathSTolerance);
```
- [ ] **Step 2: Normalize only tolerance-sized deviations**
Add `using System.Globalization;`. Validate `pathSTolerance`, set `terminalPathS = candidate.S[candidate.S.Count - 1]`, and pass `samples.Count` into each `AddSample` call. Implement the core behavior:
```csharp
double lowerDifference = previousPathS - sample.PathS;
double upperDifference = sample.PathS - terminalPathS;
if (lowerDifference > pathSTolerance || upperDifference > pathSTolerance)
throw PathSFailure(sampleIndex, previousPathS, sample.PathS,
Math.Max(lowerDifference, upperDifference), candidate);
double normalizedPathS = Math.Min(terminalPathS, Math.Max(previousPathS, sample.PathS));
if (normalizedPathS != sample.PathS)
sample = new TrajectorySample(sample.TimeFromStart, normalizedPathS, sample.ProgressSpeed,
sample.Acceleration, sample.Jerk, sample.IsHoldSample);
```
Retain the existing negative progress-speed check. Create diagnostics with invariant round-trip formatting:
```csharp
private static ArgumentException PathSFailure(int sampleIndex, double previousPathS,
double candidatePathS, double difference, LongitudinalCandidate candidate)
{
return new ArgumentException("Trajectory PathS violates monotonic publication bounds: sampleIndex=" +
sampleIndex + ";previousPathS=" + previousPathS.ToString("R", CultureInfo.InvariantCulture) +
";candidatePathS=" + candidatePathS.ToString("R", CultureInfo.InvariantCulture) +
";difference=" + difference.ToString("R", CultureInfo.InvariantCulture) + ".", nameof(candidate));
}
```
- [ ] **Step 3: Run focused tests and verify GREEN**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory
```
Expected: `PASS trajectory`.
- [ ] **Step 4: Commit the minimal production fix**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/EMPlanner/Trajectory/TrajectorySampleSchedule.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Trajectory/EmTrajectoryAssembler.cs
git commit -m "fix: normalize EM PathS resampling residuals"
```
### Task 3: Run integration regression and scoped verification
**Files:**
- Verify only; no production edits expected.
**Interfaces:**
- Consumes: the normalized `EmTrajectory` publication behavior.
- Produces: evidence that planning, observation, adapter, and plugin compilation remain valid.
- [ ] **Step 1: Run EM integration checks**
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-planning-service
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-control-adapter
```
Expected: all three commands print `PASS`.
- [ ] **Step 2: Build and inspect only the scoped files**
```powershell
dotnet build ClumsyPilot/ClumsyPilot.csproj -c Release --no-restore
git diff --check -- ClumsyPilot/ParkrobTrajplanner/EMPlanner/Trajectory/TrajectorySampleSchedule.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Trajectory/EmTrajectoryAssembler.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryChecks.cs
```
Expected: zero build errors; only the two known obsolete-API warnings may remain; diff check emits no errors.
@@ -1,179 +0,0 @@
# Trajectory Planning Flow Demo 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:** Build an offline single-page Chinese interactive demo that explains the map-to-control trajectory-planning evidence chain.
**Architecture:** A standalone HTML file embeds coherent illustrative map, coarse-path, smoothing-path, EM-trajectory and control-command data. Inline CSS and JavaScript render a fixed-coordinate SVG map, stage-specific layers, time-based vehicle playback, data contracts and control-output semantics without network or project-runtime dependencies. A PowerShell verifier asserts required content before browser review.
**Tech Stack:** HTML5, inline CSS, vanilla JavaScript, inline SVG, PowerShell 7 verification.
## Global Constraints
- Create exactly `ClumsyPilot/ParkrobTrajplanner/Trajplanner_output/trajectory-planning-flow-demo.html`.
- Local-file operation only: no `http://`, `https://`, `fetch`, XHR or WebSocket.
- Use illustrative metric data; do not claim OSQP, localization, hardware or controller execution.
- Use existing `EmTrajectory` / `TrajectoryControlCommand` field names verbatim.
- Preserve and never stage unrelated user changes.
---
### Task 1: Add a deterministic web-asset contract verifier
**Files:**
- Create: `ClumsyPilot/tests/verify_trajectory_planning_flow_demo.ps1`
- Test: `ClumsyPilot/tests/verify_trajectory_planning_flow_demo.ps1`
**Interfaces:**
- Consumes UTF-8 text from `ClumsyPilot/ParkrobTrajplanner/Trajplanner_output/trajectory-planning-flow-demo.html`.
- Produces exit code `0` and `Trajectory planning flow demo checks passed.` only when all page contracts pass.
- [ ] **Step 1: Write the failing test**
```powershell
$ErrorActionPreference = 'Stop'
$page = Join-Path $PSScriptRoot '..\ParkrobTrajplanner\Trajplanner_output\trajectory-planning-flow-demo.html'
if (-not (Test-Path -LiteralPath $page)) { throw "Missing demo page: $page" }
$html = Get-Content -LiteralPath $page -Raw -Encoding UTF8
$required = @('轨迹规划全链路 Demo', 'data-stage="map"', 'data-stage="coarse"', 'data-stage="smooth"', 'data-stage="em"', 'data-stage="control"', 'id="planning-map"', 'id="timeline"', 'Hybrid A* 粗路径', 'Local G2 平滑路径', 'EmTrajectory', 'TrajectoryControlCommand', 'mapSnapshotId', 'referencePathId', 'SignedLongitudinalVelocity', 'RequestDirectionChange', 'HoldBrake', 'IsTrajectoryComplete', 'function render()', 'function controlCommandForPoint(', 'addEventListener')
foreach ($token in $required) { if (-not $html.Contains($token)) { throw "Missing required demo contract: $token" } }
foreach ($forbidden in @('http://', 'https://', 'fetch(', 'XMLHttpRequest', 'WebSocket')) { if ($html.Contains($forbidden)) { throw "Demo must remain offline: $forbidden" } }
Write-Output 'Trajectory planning flow demo checks passed.'
```
- [ ] **Step 2: Run the test and verify initial failure**
Run `pwsh -NoProfile -File ClumsyPilot/tests/verify_trajectory_planning_flow_demo.ps1`.
Expected: `Missing demo page`.
- [ ] **Step 3: Commit the test**
Run `git add -- ClumsyPilot/tests/verify_trajectory_planning_flow_demo.ps1` followed by `git commit -m "test: verify trajectory planning flow demo"`.
### Task 2: Implement the offline stage-and-playback page
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/Trajplanner_output/trajectory-planning-flow-demo.html`
- Test: `ClumsyPilot/tests/verify_trajectory_planning_flow_demo.ps1`
**Interfaces:**
- Consumes static `identity`, `map`, `coarsePath`, `smoothPath`, and `trajectory` constants.
- Produces `render()` which updates SVG layers, selected point, stage JSON, control command and boundary explanation.
- [ ] **Step 1: Implement the responsive semantic shell**
Use this exact control structure:
```html
<nav aria-label="规划阶段">
<button type="button" data-stage="map">1. 地图</button>
<button type="button" data-stage="coarse">2. 粗路径</button>
<button type="button" data-stage="smooth">3. 平滑路径</button>
<button type="button" data-stage="em" aria-pressed="true">4. EM 轨迹</button>
<button type="button" data-stage="control">5. 控制接口</button>
</nav>
<svg id="planning-map" viewBox="0 0 800 520" role="img" aria-label="停车场地图与轨迹"></svg>
<input id="timeline" type="range" min="0" value="0" aria-label="EM 轨迹时间点">
<pre id="data-sample"></pre>
<pre id="control-sample"></pre>
```
Inline CSS must make the map dominant, panels stack at narrow width, and identify every layer with text plus distinct marker/line pattern. Include visible units `m`, `rad`, `m/s`, `rad/s` and a label that data is illustrative. Do not import fonts, assets or libraries.
- [ ] **Step 2: Implement internally consistent data and the exact control adapter**
Create frozen `identity` fields: `mapSnapshotId`, `referencePathId`, `vehicleStateSequenceId`, `trajectoryId`, `effectiveAtUtc`. Use a 4 m by 3 m map with three obstacles; include a forward coarse/smooth segment, a shared gear-switch pose, and reverse parking into the Goal. Implement `trajectory` with six time samples at `0`, `1.0`, `2.0`, `2.2`, `3.2`, `4.3` seconds; samples `2.0` and `2.2` share the gear pose and Goal is the last sample. Each point contains `x`, `y`, `yaw`, `signedVelocity`, `acceleration`, `yawRate`, `direction`, `segmentIndex`, `boundaryType`.
Implement this declared contract exactly:
```javascript
function controlCommandForPoint(point) {
const holding = point.boundaryType === "GearSwitchApproach" ||
point.boundaryType === "GearSwitchDeparture" || point.boundaryType === "Goal";
return {
SignedLongitudinalVelocity: holding ? 0 : point.signedVelocity,
YawRate: holding ? 0 : point.yawRate,
Direction: point.direction,
RequestDirectionChange: point.boundaryType === "GearSwitchApproach",
HoldBrake: holding,
IsTrajectoryComplete: point.boundaryType === "Goal"
};
}
```
- [ ] **Step 3: Implement SVG render behavior**
Implement `function render()` using one world-to-screen transform. Always draw map boundary, three obstacles, start/goal and current vehicle. For `map`, show only map information; for `coarse`, add discrete `Hybrid A* 粗路径` nodes and direction arrows; for `smooth`, add the `Local G2 平滑路径`, segment labels and curvature text; for `em` and `control`, add timed `EmTrajectory` points, selected-point highlight and heading arrow. Update `#data-sample` with stage-specific JSON. Update `#control-sample` with `controlCommandForPoint(selectedPoint)` only in control stage, otherwise state that control does not consume the stage yet.
Every view must visibly state `Success` or `SuccessWithFallback` plus a complete trajectory is required for publication. Control view must state coarse/smoothed path is observability evidence, not a controller command.
- [ ] **Step 4: Implement local interactions**
Click handlers on `[data-stage]` update `aria-pressed`, visibility and `render()`. An `input` handler on `#timeline` selects the trajectory point and calls `render()`. Disable timeline in map/coarse/smooth; initialize stage `em`, selected point `0`, then call `render()` exactly once after event binding.
- [ ] **Step 5: Run test and perform browser acceptance checks**
Run `pwsh -NoProfile -File ClumsyPilot/tests/verify_trajectory_planning_flow_demo.ps1`.
Expected: `Trajectory planning flow demo checks passed.`
Open the local page. Verify all five stage buttons change layers/data; EM/control timeline updates vehicle pose; gear sample returns zero speed/yaw rate plus `RequestDirectionChange: true` and `HoldBrake: true`; Goal returns zero speed/yaw rate plus `HoldBrake: true` and `IsTrajectoryComplete: true`; mobile reflow has no horizontal clipping.
- [ ] **Step 6: Commit the page**
Run `git add -- ClumsyPilot/ParkrobTrajplanner/Trajplanner_output/trajectory-planning-flow-demo.html` followed by `git commit -m "feat: add trajectory planning flow demo"`.
### Task 3: Document the relationship to the executable output demo
**Files:**
- Modify: `ClumsyPilot/ParkrobTrajplanner/Trajplanner_output/README.md`
- Modify: `ClumsyPilot/tests/verify_trajectory_planning_flow_demo.ps1`
- Test: `ClumsyPilot/tests/verify_trajectory_planning_flow_demo.ps1`
**Interfaces:**
- Consumes the local HTML page and `TrajectoryOutputDemoRunner` documentation.
- Produces a README link that identifies the page as an illustrative observability/interface explainer, never a live planner.
- [ ] **Step 1: Extend the test with a failing README assertion**
Before final output, add:
```powershell
$readme = Get-Content -LiteralPath (Join-Path $PSScriptRoot '..\ParkrobTrajplanner\Trajplanner_output\README.md') -Raw -Encoding UTF8
if (-not $readme.Contains('trajectory-planning-flow-demo.html')) { throw 'README must link to the trajectory-planning-flow demo.' }
```
- [ ] **Step 2: Run the test and verify initial README failure**
Run `pwsh -NoProfile -File ClumsyPilot/tests/verify_trajectory_planning_flow_demo.ps1`.
Expected: `README must link`.
- [ ] **Step 3: Add the exact README content**
Add after the existing data-flow diagram:
```markdown
## 全链路可视化(Offline Flow Demo
双击打开 [trajectory-planning-flow-demo.html](trajectory-planning-flow-demo.html),可在同一米制地图中按阶段查看地图、Hybrid A* 粗路径、Local G2 平滑路径、`EmTrajectory` 和控制层采样命令。该页面使用内置说明数据,帮助理解版本身份与数据边界;它不调用 OSQP、定位、硬件或真实控制器。
控制器只消费已验证完整轨迹的采样结果或执行层导出的 `TrajectoryControlCommand`。粗路径、平滑路径、地图和规划诊断属于观测、回放和问题定位证据链,不是逐周期的底盘命令。
```
- [ ] **Step 4: Run final checks**
Run `pwsh -NoProfile -File ClumsyPilot/tests/verify_trajectory_planning_flow_demo.ps1` and `dotnet run --project ClumsyPilot/ParkrobTrajplanner/Trajplanner_output/Tests/TrajectoryOutputDemo.Tests.csproj`.
Expected: verifier passes and existing output-demo self-check succeeds.
- [ ] **Step 5: Commit documentation and final verifier**
Run `git add -- ClumsyPilot/ParkrobTrajplanner/Trajplanner_output/README.md ClumsyPilot/tests/verify_trajectory_planning_flow_demo.ps1` followed by `git commit -m "docs: explain trajectory planning flow demo"`.
## Plan self-review
- Spec coverage: Tasks 12 cover offline behavior, five-stage navigation, fixed-coordinate map, planning artifacts, version identity, playback, gear-switch behavior and control-command contract. Task 3 makes the Demo discoverable and separates observability data from controller inputs.
- Placeholder scan: no task relies on unspecified files, functions, test expectations or external services.
- Type consistency: JavaScript and verifier use exactly `SignedLongitudinalVelocity`, `YawRate`, `Direction`, `RequestDirectionChange`, `HoldBrake`, `IsTrajectoryComplete`; README names the same boundary.
@@ -1,499 +0,0 @@
# EM Planner Controller Handoff Guide 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 a detailed Chinese README and two fully annotated, copy-readable C# examples that explain the fixed offline planning pipeline and replace the controller test's artificial `Trajectory2D` with an EM-planned trajectory.
**Architecture:** The README owns concepts, configuration locations, units, output contracts, failure semantics, and the numbered walkthrough. `EmPlannerFullPipelineDemo.cs` mirrors those numbers as one continuous offline data-flow example. `EmPlannerTrajectoryReplacementExample.cs` narrows the same contract to the exact `TestTrajectoryFactory` replacement boundary used by the external closed-loop tests.
**Tech Stack:** Markdown, C# source examples, existing `MultiWheelC.TrajectoryPlanning` contracts, Hybrid A*, Local G2 smoothing, `EmPlanningService`, OSQP, `EmControlTrajectoryAdapter`, and `TrajectoryTrackingMovement`.
## Global Constraints
- Deliver copy-readable C# only; do not add a project file or claim the examples are standalone executables.
- Use a fixed offline start, goal, vehicle, and obstacle scene; do not read live localization or send chassis commands.
- Keep every planning step explicit and fully annotated in Chinese; do not use ellipses or placeholder APIs.
- Match current repository type names, constructor signatures, enum values, properties, units, and status semantics.
- Treat `EmPlanningService` as an in-process synchronous service returning `EmPlanningResult` from `Plan(request, cancellationToken)`.
- Publish to the controller only when status is `Success` or `SuccessWithFallback` and the result contains a complete non-null trajectory.
- Keep single-direction `Trajectory2D` consumption separate from multi-direction gear-switch orchestration.
- Preserve the existing `trajectory-planning-flow-demo.html`; reference it as an optional visual companion.
- Do not modify the external `停车机器人-合并测试` tree or unrelated dirty-worktree files.
---
### Task 1: Write the main Chinese handoff guide
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/Trajplanner_guide/README.md`
- Reference: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md`
- Reference: `ClumsyPilot/ParkrobTrajplanner/TrajectoryExecution/README.md`
- Reference: `D:/Users/Desktop/项目/prakrobot/停车机器人-合并测试/parkr_shen/MultiWheelC/Movements/TrajectoryTrackingMovement.cs`
- Reference: `D:/Users/Desktop/项目/prakrobot/停车机器人-合并测试/parkr_shen/MultiWheelC/Experiments/NewControllerTrackingTests.cs`
**Interfaces:**
- Consumes: the approved design in `docs/superpowers/specs/2026-08-11-em-planner-controller-handoff-guide-design.md` and current public planner/controller contracts.
- Produces: numbered sections `步骤 1` through `步骤 15`, configuration tables, output-field tables, status handling, and exact links to both C# examples.
- [ ] **Step 1: Record the failing documentation-presence check**
Run:
```powershell
Test-Path 'ClumsyPilot\ParkrobTrajplanner\Trajplanner_guide\README.md'
```
Expected before implementation: `False`.
- [ ] **Step 2: Create the README with the integration boundary first**
Start the document with this exact contract:
```markdown
# 轨迹规划服务调用与闭环控制器交接指南
> 本指南展示固定离线场景的完整调用顺序。代码只用于复制阅读和接口交接,不会读取实车定位,也不会向底盘发送命令。
| 边界 | 类型/方法 | 含义 |
| --- | --- | --- |
| EMplanner 正式输出 | `EmTrajectory` | 带时间、位姿、曲率、速度、加速度、jerk 与方向边界的不可变轨迹 |
| 闭环控制器正式输入 | `Trajectory2D` | 几何控制器消费的世界位姿、弧长、曲率和有符号参考速度 |
| 唯一适配入口 | `EmControlTrajectoryAdapter.Create(EmTrajectory)` | 将单方向 EM 轨迹转换为控制器轨迹 |
`EmPlanningService` 是进程内同步服务。调用方创建冻结的 `EmPlanningRequest`,调用 `Plan(request, cancellationToken)`,并仅从本次返回的成功 `EmPlanningResult` 中索取 `Trajectory`
```
- [ ] **Step 3: Add the complete numbered data flow**
Add sections `步骤 1` through `步骤 15` in the exact order from the approved design. Each section must include four labeled paragraphs: `本步输入``调用``本步输出``失败处理`. Link every step to the matching numbered region in `EmPlannerFullPipelineDemo.cs`.
The flow summary must be:
```text
固定场景
→ PlanningGridMap
→ PlanningRequest
→ HybridAStarPlanner.Plan
→ PathSmoothingRequest
→ PathSmoothingService.Smooth
→ 当前 DirectionSegmentView
→ EmPlanningRequest
→ EmPlanningService.Plan
→ EmPlanningResult.Trajectory
→ EmControlTrajectoryAdapter.Create
→ Trajectory2D
→ TrajectoryTrackingMovement
```
- [ ] **Step 4: Add configuration source and meaning tables**
Create four tables: `粗路径配置``Local G2 平滑配置``EM 配置``闭环控制配置`. Every row must include `参数``源码位置``单位``固定 demo 值/当前默认值``含义``调大/调小影响`.
At minimum cover:
```text
VehicleParameters.LengthMeters / WidthMeters / SafetyMarginMeters / MaximumCurvaturePerMeter
HybridAStarConfiguration primitive length, heading bins, goal tolerances, search limits
PathSmoothingConfiguration.OutputSpacingMeters / MaximumCollisionCheckStepMeters / MinimumClearanceReserveMeters
LocalG2QuinticOptions window lengths, deviation, curvature jump, improvement and candidate limits
EmPlannerConfiguration.Corridor / Frenet / Lateral / Longitudinal / Solver / Scheduling / Validation
TrajectoryTrackingMovement Stanley gains, PID gains, speed limit, finish tolerances, deviation limit, timeout
```
Add a dedicated unit table that explicitly distinguishes `m` vs `mm`, `rad` vs `deg`, `1/m`, `m/s`, `m/s²`, `m/s³`, `rad/s`, world frame, body frame, and signed longitudinal speed.
- [ ] **Step 5: Add complete result and field contracts**
Document `EmPlanningResult`, `EmTrajectoryMetadata`, and every public `EmTrajectoryPoint` field that affects downstream interpretation. Include this warning verbatim:
```markdown
`VelocityX``VelocityY` 是世界坐标系中的预测速度分量,不是底盘纵向/横向命令;闭环测试不得把它们直接发送给车辆。
```
Explain that `EmControlTrajectoryAdapter` maps `X/Y/Yaw`, `VehicleCurvature`, and `SignedLongitudinalVelocity`, while rebuilding controller arc length from world positions.
- [ ] **Step 6: Add service request, success, failure, and gear-switch guidance**
Show the exact retrieval pattern:
```csharp
var planningService = new EmPlanningService(new OsqpNativeSolver());
EmPlanningResult result = planningService.Plan(request, cancellationToken);
bool succeeded = result.Status == EmPlanningStatus.Success ||
result.Status == EmPlanningStatus.SuccessWithFallback;
if (!succeeded || result.Trajectory == null)
{
throw new InvalidOperationException(
"EM 规划未返回可交给控制器的完整轨迹:" + result.FailureReason);
}
EmTrajectory emTrajectory = result.Trajectory;
Trajectory2D controllerTrajectory =
new EmControlTrajectoryAdapter().Create(emTrajectory);
```
Add a status decision table covering invalid input, infeasible planning, cancellation, deadline/timeout, empty trajectory, direction mismatch, and adapter rejection. Explain that the fixed demo is single-direction; a gear-switch boundary requires a full stop and a separately confirmed next-direction plan rather than concatenation into one `Trajectory2D`.
- [ ] **Step 7: Add the external-test replacement map and reading order**
Identify the current artificial source as `TestTrajectoryFactory.CreateStraight4Meters` in `NewControllerTrackingTests.cs`, and the controller sink as `TrajectoryTrackingMovement.Trajectory`. State that only the trajectory-source block changes; the existing state provider and controller tuning remain owned by the test.
End with:
```markdown
## 推荐阅读顺序
1. 先完整阅读本 README。
2. 顺序阅读 `EmPlannerFullPipelineDemo.cs` 的步骤 115。
3. 最后复制 `EmPlannerTrajectoryReplacementExample.cs` 中与目标测试相符的替换方式。
4. 如需流程图,再打开 `trajectory-planning-flow-demo.html`HTML 不是接口规范。
```
- [ ] **Step 8: Run the README structural check**
Run:
```powershell
$guide = 'ClumsyPilot\ParkrobTrajplanner\Trajplanner_guide\README.md'
Test-Path $guide
rg -n "步骤 1|步骤 15|EmPlanningService|EmPlanningResult|EmTrajectoryPoint|Trajectory2D|TestTrajectoryFactory|VelocityX|换向|参数" $guide
```
Expected: `True`; every required concept has at least one match, and steps 1 and 15 are present.
- [ ] **Step 9: Commit the README**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/Trajplanner_guide/README.md
git commit -m "docs: add EM planner controller handoff guide"
```
### Task 2: Add the fully annotated fixed offline pipeline example
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/Trajplanner_guide/EmPlannerFullPipelineDemo.cs`
- Reference: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationPipeline.cs`
- Reference: `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/EmControlTrajectoryAdapter.cs`
**Interfaces:**
- Consumes: real coarse-path, Local G2, EM, and control-adapter APIs; README steps 115.
- Produces: `EmPlannerFullPipelineDemo.CreateFixedOfflineTrajectory(CancellationToken)` returning a `FixedOfflinePlanningOutput` that exposes both `EmTrajectory` and `Trajectory2D`.
- [ ] **Step 1: Record the failing example-presence check**
Run:
```powershell
Test-Path 'ClumsyPilot\ParkrobTrajplanner\Trajplanner_guide\EmPlannerFullPipelineDemo.cs'
```
Expected before implementation: `False`.
- [ ] **Step 2: Create the example shell and explicit output contract**
Use this public shape:
```csharp
public sealed class FixedOfflinePlanningOutput
{
public FixedOfflinePlanningOutput(
EmTrajectory emTrajectory,
Trajectory2D controllerTrajectory)
{
EmTrajectory = emTrajectory ??
throw new ArgumentNullException(nameof(emTrajectory));
ControllerTrajectory = controllerTrajectory ??
throw new ArgumentNullException(nameof(controllerTrajectory));
}
public EmTrajectory EmTrajectory { get; }
public Trajectory2D ControllerTrajectory { get; }
}
public static class EmPlannerFullPipelineDemo
{
public static FixedOfflinePlanningOutput CreateFixedOfflineTrajectory(
CancellationToken cancellationToken);
}
```
The declaration above defines the reader-facing signature. The final class must give that method the complete numbered body produced by steps 36; it must not remain declaration-only.
- [ ] **Step 3: Implement steps 15 with a complete fixed scene and coarse plan**
Use explicit SI-valued constants for start, goal, vehicle length/width, safety margin, curvature limit, map resolution, and at least one fixed obstacle. Construct the map with `MapBoundsMm`, `CircleObstacle` or `AxisAlignedRectangleObstacle`, `ManualObstacleSource`, `PlanningMapRequest`, and `new PlanningMapFactory().Create(mapRequest)`. Require `PlanningMapBuildResult.Succeeded`, `Map != null`, and `Map.PlanningReady`. Then construct `PlanningRequest` and call `new HybridAStarPlanner().Plan(request, cancellationToken)`.
Reject non-success with diagnostics:
```csharp
if (coarseResult.Status != PlanningStatus.Success)
{
throw new InvalidOperationException(
"Hybrid A* 粗路径失败:" +
coarseResult.Diagnostics.TerminationReason);
}
```
- [ ] **Step 4: Implement steps 67 with Local G2 and a single active direction segment**
Construct `PathSmoothingRequest` from the successful coarse path, segments, frozen map, vehicle, and explicit `PathSmoothingConfiguration`. Call `new PathSmoothingService().Smooth(smoothingRequest, cancellationToken)`. Accept only `Complete`, `PartialImprovement`, `NotNeeded`, or `Unchanged`, and reject empty path/segment results.
Call `ReferencePathSegmenter.Create(smoothingResult)`, select segment index `0`, and assert that the fixed scene remains one direction. If the result contains more than one direction segment, throw an exception explaining that this fixed controller demo intentionally does not cross a gear-switch boundary.
- [ ] **Step 5: Implement steps 811 with state, configuration, request, and service call**
Use zero initial signed speed/acceleration and the fixed start pose for `VehicleMotionState`. Create `EmPlannerConfiguration` with explicit demo overrides, copy the repository's request-construction order, set `EmPlanningScope.FullDirectionSegment`, and invoke:
```csharp
var planningService = new EmPlanningService(new OsqpNativeSolver());
EmPlanningResult emResult = planningService.Plan(
emRequest,
cancellationToken);
```
Every constructor argument must have an adjacent Chinese comment giving its source and unit where applicable.
- [ ] **Step 6: Implement steps 1215 with validation, field reading, adaptation, and return**
Use this publication gate:
```csharp
bool emSucceeded = emResult.Status == EmPlanningStatus.Success ||
emResult.Status == EmPlanningStatus.SuccessWithFallback;
if (!emSucceeded ||
emResult.Trajectory == null ||
emResult.Trajectory.Points.Count < 2)
{
throw new InvalidOperationException(
"EM 规划没有返回完整可控轨迹:" + emResult.FailureReason);
}
```
Iterate through the points and assign local variables for time, `X/Y/Yaw`, curvature, `PathS`, signed speed, world velocity components, acceleration, jerk, and yaw rate. The comments must explain that this is inspection, not command output.
Finish with:
```csharp
EmTrajectory emTrajectory = emResult.Trajectory;
Trajectory2D controllerTrajectory =
new EmControlTrajectoryAdapter().Create(emTrajectory);
return new FixedOfflinePlanningOutput(
emTrajectory,
controllerTrajectory);
```
- [ ] **Step 7: Verify numbered coverage and banned placeholders**
Run:
```powershell
$demo = 'ClumsyPilot\ParkrobTrajplanner\Trajplanner_guide\EmPlannerFullPipelineDemo.cs'
1..15 | ForEach-Object {
if (-not (Select-String -Path $demo -Pattern ("步骤 " + $_) -Quiet)) {
throw "缺少步骤 $_"
}
}
rg -n "TODO|TBD|[.][.][.]|NotImplementedException" $demo
```
Expected: all numbered checks complete without an exception; `rg` prints no matches and exits with code `1` because the banned patterns are absent.
- [ ] **Step 8: Cross-check every API token against source**
Run targeted searches for every constructed public type and method:
```powershell
rg -n "class (PlanningRequest|HybridAStarPlanner|PathSmoothingRequest|PathSmoothingService|EmPlanningRequest|EmPlanningService|EmControlTrajectoryAdapter)|Plan\(|Smooth\(|Create\(" ClumsyPilot/ParkrobTrajplanner -g '*.cs'
```
Expected: each type and call used by the example resolves to a current repository declaration; adjust the example to the source signature before continuing.
- [ ] **Step 9: Commit the full pipeline example**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/Trajplanner_guide/EmPlannerFullPipelineDemo.cs
git commit -m "docs: add annotated EM planning pipeline example"
```
### Task 3: Add the closed-loop test replacement example
**Files:**
- Create: `ClumsyPilot/ParkrobTrajplanner/Trajplanner_guide/EmPlannerTrajectoryReplacementExample.cs`
- Reference: `D:/Users/Desktop/项目/prakrobot/停车机器人-合并测试/parkr_shen/MultiWheelC/Experiments/NewControllerTrackingTests.cs`
- Reference: `D:/Users/Desktop/项目/prakrobot/停车机器人-合并测试/parkr_shen/MultiWheelC/Movements/TrajectoryTrackingMovement.cs`
- Reference: `D:/Users/Desktop/项目/prakrobot/停车机器人-合并测试/parkr_shen/MultiWheelC/Movements/MotionPlanExecutor.cs`
**Interfaces:**
- Consumes: `FixedOfflinePlanningOutput`, `EmTrajectory`, `EmControlTrajectoryAdapter`, `Trajectory2D`, and the external test's existing state-provider/controller setup.
- Produces: exact before/after source blocks plus direct `TrajectoryTrackingMovement` and `TrackMotionPlanSegment` consumption examples.
- [ ] **Step 1: Record the failing replacement-example presence check**
Run:
```powershell
Test-Path 'ClumsyPilot\ParkrobTrajplanner\Trajplanner_guide\EmPlannerTrajectoryReplacementExample.cs'
```
Expected before implementation: `False`.
- [ ] **Step 2: Add a fully parameterized before block**
Copy the current artificial trajectory call without abbreviating arguments:
```csharp
Trajectory2D trajectory =
TestTrajectoryFactory.CreateStraight4Meters(
trajectoryStartPose,
CruiseSpeedMetersPerSecond,
AccelerationMetersPerSecondSquared,
DecelerationMetersPerSecondSquared,
PointSpacingMeters);
```
Comment that this line generates controller test data and does not call the formal planner.
- [ ] **Step 3: Add the after block and explain how the test obtains the result**
Use:
```csharp
FixedOfflinePlanningOutput planningOutput =
EmPlannerFullPipelineDemo.CreateFixedOfflineTrajectory(
cancellationToken);
EmTrajectory emTrajectory = planningOutput.EmTrajectory;
Trajectory2D trajectory = planningOutput.ControllerTrajectory;
```
Explain that the full demo internally performs the synchronous `EmPlanningService.Plan` call and publication gate. Also show the equivalent direct retrieval block with `EmPlanningResult`, status check, `result.Trajectory`, and `EmControlTrajectoryAdapter.Create` so the reader understands how to extract the trajectory without using the demo wrapper.
- [ ] **Step 4: Add both controller consumption paths without changing tuning ownership**
Direct movement:
```csharp
var movement = new TrajectoryTrackingMovement
{
Trajectory = trajectory,
StateProvider = stateProvider
};
```
Composite movement:
```csharp
var segment = new TrackMotionPlanSegment(trajectory);
```
Comment that existing `ConfigureTrackingMovement`, finish tolerances, observers, state provider, and safety checks stay in the controller test; only the trajectory source changes.
- [ ] **Step 5: Add rejection examples for failure and gear switch**
Include explicit guards that reject null/failed EM results and state that a single `TrajectoryTrackingMovement` invocation consumes one direction segment. Show an exception for `EmTerminalType.GearSwitch` unless the surrounding test has implemented and safety-reviewed the stop-and-direction-confirmation sequence.
- [ ] **Step 6: Verify the replacement map**
Run:
```powershell
$example = 'ClumsyPilot\ParkrobTrajplanner\Trajplanner_guide\EmPlannerTrajectoryReplacementExample.cs'
rg -n "TestTrajectoryFactory.CreateStraight4Meters|EmPlanningService|EmPlanningResult|result.Trajectory|EmControlTrajectoryAdapter|TrajectoryTrackingMovement|TrackMotionPlanSegment|GearSwitch" $example
rg -n "TestTrajectoryFactory.CreateStraight4Meters|Trajectory = trajectory" 'D:\Users\Desktop\项目\prakrobot\停车机器人-合并测试\parkr_shen\MultiWheelC' -g '*.cs'
```
Expected: the guide contains every acquisition and consumption boundary; the external tree still shows its original test source and sink because it was not modified.
- [ ] **Step 7: Commit the replacement example**
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/Trajplanner_guide/EmPlannerTrajectoryReplacementExample.cs
git commit -m "docs: show EM trajectory replacement in controller tests"
```
### Task 4: Verify the complete guide as one handoff artifact
**Files:**
- Verify: `ClumsyPilot/ParkrobTrajplanner/Trajplanner_guide/README.md`
- Verify: `ClumsyPilot/ParkrobTrajplanner/Trajplanner_guide/EmPlannerFullPipelineDemo.cs`
- Verify: `ClumsyPilot/ParkrobTrajplanner/Trajplanner_guide/EmPlannerTrajectoryReplacementExample.cs`
- Preserve: `ClumsyPilot/ParkrobTrajplanner/Trajplanner_guide/trajectory-planning-flow-demo.html`
**Interfaces:**
- Consumes: all three guide deliverables and the real planner/controller sources.
- Produces: fresh evidence that the guide is complete, cross-linked, placeholder-free, and limited to the intended files.
- [ ] **Step 1: Verify all expected files and cross-links**
Run:
```powershell
$root = 'ClumsyPilot\ParkrobTrajplanner\Trajplanner_guide'
@(
"$root\README.md",
"$root\EmPlannerFullPipelineDemo.cs",
"$root\EmPlannerTrajectoryReplacementExample.cs",
"$root\trajectory-planning-flow-demo.html"
) | ForEach-Object {
if (-not (Test-Path $_)) { throw "缺少指南文件:$_" }
}
rg -n "EmPlannerFullPipelineDemo.cs|EmPlannerTrajectoryReplacementExample.cs|trajectory-planning-flow-demo.html" "$root\README.md"
```
Expected: all files exist and README links to both C# examples and the HTML companion.
- [ ] **Step 2: Verify completeness and placeholder absence**
Run:
```powershell
$root = 'ClumsyPilot\ParkrobTrajplanner\Trajplanner_guide'
rg -n "TODO|TBD|NotImplementedException|[.][.][.]" "$root\README.md" "$root\EmPlannerFullPipelineDemo.cs" "$root\EmPlannerTrajectoryReplacementExample.cs"
```
Expected: no matches; `rg` exits with code `1`.
- [ ] **Step 3: Verify documentation-source correspondence**
Run:
```powershell
$root = 'ClumsyPilot\ParkrobTrajplanner\Trajplanner_guide'
rg -n "PlanningGridMap|HybridAStarPlanner|PathSmoothingService|EmPlanningRequest|EmPlanningService|EmTrajectoryPoint|EmControlTrajectoryAdapter" "$root\README.md" "$root\EmPlannerFullPipelineDemo.cs"
rg -n "class (HybridAStarPlanner|PathSmoothingService|EmPlanningRequest|EmPlanningService|EmTrajectoryPoint|EmControlTrajectoryAdapter)" ClumsyPilot/ParkrobTrajplanner -g '*.cs'
```
Expected: every documented integration type is present in the guide and resolves to a real repository declaration.
- [ ] **Step 4: Run repository-level static checks**
Run:
```powershell
git diff --check
dotnet build ClumsyPilot/ClumsyPilot.csproj --no-restore
```
Expected: `git diff --check` exits `0`; build exits `0`. If the already-dirty worktree causes an unrelated build failure, record the exact failing project/file and separately report that the guide's static checks passed.
- [ ] **Step 5: Audit scope and final diff**
Run:
```powershell
git status --short
git diff --no-ext-diff -- ClumsyPilot/ParkrobTrajplanner/Trajplanner_guide docs/superpowers/plans/2026-08-11-em-planner-controller-handoff-guide.md
```
Expected: guide changes are limited to README and the two new C# examples; the existing HTML remains preserved. Unrelated pre-existing changes remain untouched.
- [ ] **Step 6: Commit any final guide-only corrections**
If verification required guide corrections, stage only the three guide deliverables and commit:
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/Trajplanner_guide/README.md ClumsyPilot/ParkrobTrajplanner/Trajplanner_guide/EmPlannerFullPipelineDemo.cs ClumsyPilot/ParkrobTrajplanner/Trajplanner_guide/EmPlannerTrajectoryReplacementExample.cs
git commit -m "docs: finalize EM planner trajectory handoff guide"
```
@@ -1,266 +0,0 @@
# EM Planner Execution Progress
## Current State
- Current stage: 10 - generic control adaptation, plugin packaging, and final rolling gate
- Stage status: Completed
- Current branch: `trajplanner`
- Last checkpoint commit: `4058230` (last Stage 10 functional commit; this file is the final Stage 10 checkpoint)
- Protocol design baseline: `554c84f`
- Implementation-plan baseline: `8dd8ff0`
## Historical Stage 8 Current State
- Current stage: 8 — complete trajectory publication and pure one-shot planning service
- Stage status: Completed
- Current branch: `trajplanner`
- Last checkpoint commit: `019b896` (last Stage 8 functional commit; this file is the Stage 8 checkpoint)
- Protocol design baseline: `554c84f`
- Implementation-plan baseline: `8dd8ff0`
## Completed Tasks
| Stage | Plan | Tasks | Commits | Verification |
|---:|---|---|---|---|
| 10 | `2026-08-03-em-planner-rolling-execution-implementation.md` | Task 4 - Generic Control Adapter | `aa51ae2` | RED: `executor` exited 1 with missing `TrajectoryControlAdapter` and `IVehicleStateProvider`. GREEN: `PASS executor` verifies exact forward/reverse signed-velocity and yaw-rate mapping, telemetry preservation, zero holds, one-shot direction requests, no lateral body command, and no in-place rotation. |
| 10 | `2026-08-03-em-planner-rolling-execution-implementation.md` | Task 5 - Plugin Output and License Packaging | `3c84e36` | RED: `plugin-package` exited 1 because `Publish-ClumsyPilotPlugin.ps1` was absent. GREEN: `PASS plugin-package` verifies build-output metadata, managed `ClumsyPilot.dll`, x64/hash-checked `osqp.dll`, the exact three-license tree, and repeat publication with no stale staging directory. |
| 10 | `2026-08-03-em-planner-rolling-execution-implementation.md` | Task 6 - Rolling End-to-End and Final Gate | `4058230` | RED: `em-all` exited 2 because its command route did not exist. GREEN: `PASS rolling-end-to-end` and `PASS rolling-execution-tail` cover forward/reverse publication, supersession, unsafe-tracking reset, gear dwell, goal stop, and failure continuation to a non-extrapolated zero-speed tail. |
| 9 | `2026-08-03-em-planner-rolling-execution-implementation.md` | Task 1 - Cycle Identity, Scheduling Decision, and Stale-Result Suppression | `d75380c` | RED: the authorized inherited coordinator draft threw `ObjectDisposedException` when a completed current cycle was followed by another cycle. GREEN: `coordinator` printed `PASS coordinator`; the exact 100-run planned loop completed with exit 0 and 100 PASS lines. |
| 9 | `2026-08-03-em-planner-rolling-execution-implementation.md` | Task 2 - Safe Previous-Trajectory Handoff | `55119de` | RED: `coordinator` exited 1 with missing `TrajectoryHandoffSelector` contracts. GREEN: `PASS coordinator` covers forward/reverse same-segment interpolation, age/tracking/terminal/segment/direction/beyond-trajectory/gear-boundary rejection, no cross-boundary interpolation, and coordinator consumption of its immutable published trajectory. |
| 9 | `2026-08-03-em-planner-rolling-execution-implementation.md` | Task 3 - Gear-Switch State Machine and Trajectory Executor | `de402e6` | RED: `executor` exited 1 with missing gear-switch and execution-state types. GREEN: `PASS executor` covers forward-to-reverse and reverse-to-forward zero-speed dwell, one-shot request, confirmation, and Goal/RollingSafetyStop completion. |
## Historical Completed Tasks
| Stage | Plan | Tasks | Commits | Verification |
|---:|---|---|---|---|
| 1 | `2026-08-03-em-planner-foundation-implementation.md` | Task 1 — Verification Host and Immutable Contracts | `e12eb31` | RED: `foundation` failed with missing `VehicleMotionState`/`EmTrajectoryPoint`; GREEN: `foundation` printed `PASS foundation` with exit 0. |
| 1 | `2026-08-03-em-planner-foundation-implementation.md` | Task 2 — Configuration, Diagnostics, and Request Validation | `b326431` | RED: `foundation` failed with missing configuration/validation types; GREEN: `foundation` printed `PASS foundation` twice with byte-identical stdout and exit 0. |
| 1 | `2026-08-03-em-planner-foundation-implementation.md` | Task 3 — Direction Segmentation and Exact Boundary Anchors | `e492e16` | RED: `segmentation` failed with missing `ReferencePathSegmenter`; GREEN: `foundation` and `segmentation` each printed their PASS line with exit 0. |
| 2 | `2026-08-03-em-planner-foundation-implementation.md` | Task 4 — Reverse-Safe Frenet Projection and Reconstruction | `06687e2` | RED: `frenet` failed with missing Frenet types including `FrenetProjector`; GREEN: `PASS frenet` with forward/reverse sign, yaw-wrap, bounded U-branch, singular-denominator, and reconstruction checks. |
| 2 | `2026-08-03-em-planner-foundation-implementation.md` | Task 5 — Topology-Preserving Static Corridor | `2d252ff` | RED: `corridor` failed with missing static-corridor types; GREEN: `PASS corridor`, followed by `all-foundation` with all four PASS lines. |
| 2 | `2026-08-03-em-planner-foundation-implementation.md` | Task 6 — Foundation Documentation and Gate | `aa62d6b` | RED: Foundation README was absent; GREEN: README contract scan passed. Placeholder/format scans passed, `all-foundation` passed twice with identical four-line stdout, and the normal-project baseline contained only legacy `auto_avoidance` errors. |
| 3 | `2026-08-03-em-planner-osqp-backend-implementation.md` | Task 1 — Solver-Neutral Sparse QP Contracts | `104081e` | RED: `optimization` failed with missing `SparseTripletBuilder`; GREEN: `PASS optimization` covered deterministic CSC canonicalization, duplicate summation, zero dropping, upper-triangular Hessians, QP validation, and defensive copies. |
| 3 | `2026-08-03-em-planner-osqp-backend-implementation.md` | Task 2 — Reproducible OSQP 1.0.0 Native Package | `2abb465` | RED: the planned package script was absent; GREEN: the fixed-tag build printed `OSQP v1.0.0 win-x64 package ready`. The package contains the pinned DLL, license files, version/build flags, and lowercase SHA-256 manifest; `dumpbin` verified x64 and the four required exports. |
| 3 | `2026-08-03-em-planner-osqp-backend-implementation.md` | Task 3 — Absolute-Path Native Loader and ABI Structures | `39c1708` | RED: `osqp-loader` failed with missing `OsqpNativeLoader`; GREEN: `PASS osqp-loader` verified missing/corrupt DLL diagnostics, real `1.0.0` loading from a copied plugin directory, and one stable handle across 16 parallel first-use calls. |
| 4 | `2026-08-03-em-planner-osqp-backend-implementation.md` | Task 4 — OSQP Solve Lifecycle and Status Mapping | `a957fda` | RED: `osqp-loader` failed with missing `OsqpNativeSolver`; GREEN: fixed bounded, equality, infeasible and one-tick time-limit QPs pass inside a copied clean-plugin process. Status values 111 map to the planner-neutral contract, with finite metrics and captured native status. Twenty exact `osqp` solve/cleanup cycles printed `PASS osqp-solve` and `PASS osqp-loader` on every iteration. |
| 4 | `2026-08-03-em-planner-osqp-backend-implementation.md` | Task 5 — Backend Completion Gate | `61dfa79` | RED: `osqp` was rejected by the verification-host command parser; GREEN: it runs the full solver/loader group. README now records the pinned package, deployment layout, absolute-load rule, ownership and status mapping. `dumpbin /dependents` found only Windows/runtime DLLs; an externally located working directory still loaded a copied clean plugin bundle and solved the micro QPs. |
| 5 | `2026-08-03-em-planner-lateral-ls-implementation.md` | Task 1 — Variable Layout and Exact Discrete Lateral Dynamics | `c225b17` | RED: `lateral-model` failed to build with missing `LateralVariableLayout`; GREEN: `PASS lateral-model` covered contiguous `4N-1` indices, range checks, unequal-S exact integration, station/corridor/start validation, defensive copies, and the lateral result publication contract. |
| 5 | `2026-08-03-em-planner-lateral-ls-implementation.md` | Task 2 — Normalized Objective and Linear Hard Constraints | `f703d41` | RED: `lateral-model` failed to build with missing `LateralObjectiveBuilder`; GREEN: `PASS lateral-model` inspected normalized P/q coefficients with `1e-12` comparisons, exact integration equalities, finite hard bounds, goal/gear versus rolling terminal behavior, empty-intersection early failure, and the solver-neutral fake-QP boundary. |
| 5 | `2026-08-03-em-planner-lateral-ls-implementation.md` | Task 3 — Nonlinear Geometry Evaluation and Independent Validation | `0c48a7d` | RED: `lateral-model` failed to build with missing `LateralGeometryEvaluator`; GREEN: `PASS lateral-model` covered straight and constant-curvature references in forward/reverse, full Frenet curvature, actual strictly increasing PathS, curvature/yaw-rate signs, and rejection of denominator, curvature, non-finite, and independently recomputed world-geometry violations. |
| 6 | `2026-08-03-em-planner-lateral-ls-implementation.md` | Task 4 — Sequential Convex Outer Loop and Feasible-Candidate Fallback | `f19df53` | RED: `lateral-integration` failed to build with missing `SequentialConvexOptimizer` and `LateralPlanner`; GREEN: `PASS lateral-integration` scripted validation-before-fallback, invalid-vector rejection, `SolvedInaccurate` residual/geometry rejection, 0.05 m trust centering, complete-primal warm starts, five-call cap, cancellation, timeout and facade behavior. |
| 6 | `2026-08-03-em-planner-lateral-ls-implementation.md` | Task 5 — Real-OSQP Lateral Scenarios and Gate | `4d83ed2` | RED: `lateral-all` reached the real forward scenario but rejected the empty initial warm start; the subsequent loader diagnosis confirmed the host output bundle intentionally lacks `osqp.dll`. GREEN: the solver-neutral full initial primal and a clean copied plugin-bundle probe produced `PASS lateral-model`, `PASS lateral-integration`, and `PASS lateral-real-osqp` for deterministic forward/reverse straight, gentle curve, seed-connected obstacle narrowing, gear-switch, and rolling scenarios. |
| 7 | `2026-08-03-em-planner-longitudinal-st-implementation.md` | Task 1 — Speed Envelope over Actual PathS | `62ea9db`, correction `25742ab` | RED: `longitudinal-model` first failed with missing longitudinal input/speed-envelope types; the sparse stopping chord and then the discrete jerk-release tail checks also failed before refinement. GREEN: `PASS longitudinal-model` proves actual strictly increasing LS `PathS` consumption, finite curvature/curvature-rate/stopping limits, stopping-distance precheck, dense actual-PathS stations, and discrete deceleration/jerk tail stations. |
| 7 | `2026-08-03-em-planner-longitudinal-st-implementation.md` | Task 2 — Time-Knot Layout, Dynamics, Objective, and Hard Constraints | `c01d0d5` | RED: `longitudinal-model` failed with missing ST candidate/layout/constraint types. GREEN: `PASS longitudinal-model` inspects normalized objective coefficients, solver-neutral `QuadraticProgram` assembly, exact constant-jerk dynamics, monotonic progress, finite physical bounds, exact start state, and hard terminal `s_N`/`u_N`. |
| 7 | `2026-08-03-em-planner-longitudinal-st-implementation.md` | Task 3 — Longitudinal Outer Loop and Strict Validation | `510bf97` | RED: `longitudinal-integration` failed first with missing optimizer/validator/facade types; focused checks then exposed invalid fallback acceptance, sparse envelope probes, noncanonical endpoints, and loss of a strictly validated initial seed on immediate timeout. GREEN: `PASS longitudinal-integration` covers scripted statuses, strict residual/physical validation, deep-copy last-feasible fallback, complete-primal warm starts, the five-iteration cap, cancellation, and deterministic real OSQP forward/reverse/curvature/jerk-stop/short/zero-start scenarios in a clean copied plugin bundle. |
| 8 | `2026-08-03-em-planner-longitudinal-st-implementation.md` | Task 4 — Complete Trajectory Assembly and Redundant-Field Consistency | `1d58864` | RED: `trajectory` exited 1 with CS0246 for missing `EmTrajectoryAssembler`. GREEN: `trajectory` exited 0 with `PASS trajectory`, checking forward/reverse signed speed, all redundant field identities, unwrapped yaw interpolation, exact terminal anchor, and the `0.20 s` / `0.05 s` hold tail. |
| 8 | `2026-08-03-em-planner-longitudinal-st-implementation.md` | Task 5 — Independent World-Space Publication Validator | `7410654` | RED: `trajectory` exited 1 with missing `EmTrajectoryValidationFailure` / validator contracts. GREEN: `trajectory` exited 0 with `PASS trajectory`, rejecting deterministic mutations for non-finite values, timing/PathS/direction/field inconsistencies, every configured physical limit, terminal anchor/speed/yaw-rate failures, pose and `0.025 m` swept collisions, segment overflow, and reverse-specific speed limits. |
| 8 | `2026-08-03-em-planner-longitudinal-st-implementation.md` | Task 6 — Pure One-Shot EmPlanningService | `019b896` | RED: `em-planning-service` exited 1 with CS0246 for missing `EmPlanningService`. GREEN: `em-core-all` exited 0 twice in the required four-line order; the service checks cover forward/reverse/gear/goal/rolling success, deterministic output, request IDs, invalid/stale/direction/projection/corridor/stopping/LS/ST/solver/timeout/cancellation/publication-validation failures, and debug-sink exception isolation. |
## Current Verification
- Commands: entry regressions `coordinator`, `executor`, `em-core-all`, `trajectory`, `lateral-all`, `optimization`, `osqp`, and `all-foundation`; Task 4 `executor`; Task 5 `plugin-package`; final `em-all` and `git diff --check`, all from the repository root.
- Result: every entry regression exited 0. The final post-commit `em-all` exited 0 with foundation, OSQP, lateral, longitudinal, trajectory, facade, coordinator, executor, plugin packaging, rolling end-to-end, and rolling-tail PASS lines; `git diff --check` exited 0.
- RED/GREEN: Task 4 RED `executor` exit 1 (missing generic adapter/provider), GREEN exit 0. Task 5 RED `plugin-package` exit 1 (missing script), GREEN exit 0. Task 6 RED `em-all` exit 2 (unregistered command), GREEN exit 0; the Task 5 output metadata required test-only clean-bundle copy fixtures to omit host-output `osqp.dll` before their existing explicit pinned-DLL copy.
- Verified commits: `aa51ae2`, `3c84e36`, `4058230`.
## Historical Current Verification
- Commands:
- `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 -- lateral-integration`
- `dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- lateral-all`
- `dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- lateral-model`
- `1..20 | ForEach-Object { dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- osqp; if ($LASTEXITCODE -ne 0) { throw "OSQP verification failed on iteration $_" } }`
- `dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- optimization`
- `dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- osqp-loader`
- `dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- all-foundation`
- `C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Tools\MSVC\14.44.35207\bin\Hostx64\x64\dumpbin.exe /dependents ClumsyPilot/ThirdParty/OSQP/win-x64/osqp.dll`
- Clean copied plugin bundle: run `EMPlannerVerificationHost.exe osqp-probe` from a separate working directory.
- `git diff --check`
- Result: The Stage 5 `lateral-model` gate exited 0 after all three Task commits. It proves the `4N-1` layout, exact unequal-station dynamics, immutable lateral inputs/results, normalized OSQP-convention P/q assembly, finite hard corridor/derivative/trust/denominator bounds, terminal distinction, full forward/reverse world reconstruction, actual PathS, and independent validation. `optimization`, `osqp`, and all four `all-foundation` groups exited 0. The dependency list remains `KERNEL32.dll`, `VCRUNTIME140.dll`, and API-set CRT DLLs only—no MKL, CUDA or external QDLDL. `git diff --check` had no whitespace diagnostics; immediately before this checkpoint update the staging area was empty and no Stage 5 scope files were uncommitted.
- Result: Stage 6 exit commands all exited 0 at `4d83ed2`: `lateral-integration`; `lateral-all` (model, scripted SQP, and clean-plugin real OSQP groups); `optimization`; `osqp`; `all-foundation`; and `git diff --check`. The real OSQP gate runs every fixed scenario twice and compares status, point count, and all lateral-path numeric fields within `1e-10`; the obstacle scenario stays in the negative, seed-connected corridor interval. The LS implementation remains dependent only on `IQpSolver`/`QuadraticProgram`; no LS P/Invoke, UI, hardware object, or current-working-directory dependency was added.
- Result: Stage 7 exit commands all exited 0 from the repository root at `510bf97`: `longitudinal-model`; `longitudinal-integration`; `lateral-integration`; `lateral-all`; `optimization`; `osqp`; `all-foundation`; and `git diff --check`. ST consumes only actual LS `PathS` and reaches OSQP only through `IQpSolver`/`QuadraticProgram`; no ST P/Invoke, UI, hardware object, or current-working-directory dependency was added. The jerk-stop case retains only the independently validated initial seed when OSQP reaches its 4000-iteration limit; the timed-out solver vector is not accepted.
- Baseline note: invoking the pre-existing `lateral-all` test from the `ClumsyPilot` subdirectory fails its pinned-DLL lookup because that Stage 6 test constructs the source path from `Directory.GetCurrentDirectory()`. Running the documented command from the repository root exits 0. Stage 7 does not modify or bypass the completed LS test.
- Normal-project baseline: the historical checkpoint recorded exit 1 from legacy `auto_avoidance/MultiWheelAutoAvoidance.cs` references to `NetTopologySuite` and `OpenCvSharp`. At the Stage 7 final read-only audit that source path is absent from both the working tree and the tracked `HEAD` tree, so `dotnet build ClumsyPilot/ClumsyPilot.csproj --no-restore` exits 0 with only two unrelated obsolete-API warnings. Stage 7 issued no delete/clean command and made no legacy-module change; preserve the observed workspace and do not recreate, delete, hide, or otherwise alter legacy functionality merely to change this baseline.
- Verified commits: `104081e`, `2abb465`, `39c1708`, `a957fda`, `61dfa79`, `c225b17`, `f703d41`, `0c48a7d`, `f5c69c2`, `f19df53`, `4d83ed2`, `62ea9db`, `c01d0d5`, `25742ab`, and `510bf97`
- Result: Stage 8 exit commands all exited 0 from the repository root at `019b896`: `em-core-all` twice (each printed `PASS longitudinal-model`, `PASS longitudinal-integration`, `PASS trajectory`, `PASS em-planning-service` in order); then `longitudinal-model`, `longitudinal-integration`, `trajectory`, `lateral-all`, `optimization`, `osqp`, `all-foundation`, and `git diff --check`. The trajectory gate independently verifies physical world-space publication and the service consumes only request snapshots through `IQpSolver` / `QuadraticProgram`; no Stage 8 production P/Invoke, UI, hardware, current-working-directory, dynamic-obstacle, rolling-execution, or controller dependency was added.
- Stage 8 RED commands and exits: Task 4 `trajectory` exit 1 (missing `EmTrajectoryAssembler`); Task 5 `trajectory` exit 1 (missing validator contracts); Task 6 `em-planning-service` exit 1 (missing `EmPlanningService`). Task 4/5 GREEN command was `trajectory` exit 0 / `PASS trajectory`; Task 6 GREEN command was `em-core-all` exit 0 twice with the required four PASS lines.
## Preserved Workspace State
- The initially untracked Task 1 coordinator draft was explicitly authorized for Stage 9 ownership and committed as `d75380c`.
- Every captured user-baseline status entry remains untouched and unstaged: the final `git status --short` set has the same 125 entries as Stage 10 entry, with no additions or losses.
- The `lateral-all` fixture still resolves its explicitly copied pinned OSQP DLL from the repository-root current directory. Build-output `osqp.dll` is omitted only from the temporary clean-bundle host copy before that existing explicit copy; no LS/ST/OSQP/facade production behavior changed.
- Existing baseline noise remains the two obsolete-API warnings and CRLF advisories on user-owned dirty files; `git diff --check` exits 0.
## Historical Preserved Workspace State
- The workspace continues to contain the same unrelated Map, CoarsePath, PathSmoothing, project-file, report, and documentation changes captured at Stage 6 entry. They belong to the user and remain untouched and unstaged.
- Use explicit file paths for every `git add`; never stage the whole workspace.
- The existing user hunk in `ClumsyPilot/ClumsyPilot.csproj` remains preserved. Do not clean, rewrite, or stage it.
- The historical legacy `auto_avoidance/MultiWheelAutoAvoidance.cs` dependency failure is not reproducible in the Stage 7 final workspace because that untracked/unversioned source path is absent. Do not recreate, delete, bypass, or otherwise alter legacy functionality to manufacture a different baseline.
- Remaining observed baseline noise: the first fresh Stage 8 core build printed the existing `Lidar2dDetect2LegTray.LegWidth` and `MultiWheelChassis.GetSteerWheels()` obsolete-API warnings; `git diff --check` emits CRLF conversion advisories for user-owned dirty files but exits 0. Neither is an EM Planner regression.
## Decisions Needed
- None. Stage 10 completed within scope; it is the final planned EM Planner stage.
## Historical Decisions Needed
- None. Stage 7 completed within scope.
## Prior Stage 7 Entry Record
- Stage: 7 — ST model and longitudinal optimization
- Readiness: Ready for Stage 7 entry checks after this checkpoint is committed.
- Plan: `docs/superpowers/plans/2026-08-03-em-planner-longitudinal-st-implementation.md`
- Tasks:
- Task 1 — Speed Envelope over Actual PathS
- Task 2 — Time-Knot Layout, Dynamics, Objective, and Hard Constraints
- Task 3 — Longitudinal Outer Loop and Strict Validation
- Entry checks:
- Confirm branch `trajplanner`, `a957fda`, `61dfa79`, `c225b17`, `f703d41`, `0c48a7d`, `f5c69c2`, `f19df53`, and `4d83ed2` are ancestors of `HEAD`, and the Stage 6 progress checkpoint is present.
- Capture `git status --short` before editing and preserve every unrelated user change.
- Read the two EM Planner design documents, this progress file, and the longitudinal-ST implementation plan in full.
- Run `lateral-integration`, `lateral-all`, `optimization`, `osqp`, and `all-foundation` before Task 1.
- ST must consume actual, strictly increasing LS `PathS` and keep the OSQP boundary solver-neutral through `IQpSolver`.
- Exit gate:
- Tasks 13 each have an independent commit and their prescribed RED/GREEN evidence.
- The actual-PathS speed envelope, stopping precheck, ST dynamics/QP, and strict longitudinal validation pass.
- The Stage 7 plan's full exit gate and `git diff --check` pass; the staging area is empty; a Stage 7 progress checkpoint is committed alone.
## Prior Stage 7 Window Prompt (Historical)
```text
请继续 ParkingRobot 仓库的 EM Planner 多窗口实施。
工作目录:D:\Users\Desktop\项目\prakrobot\ParkingRobot
本窗口只执行阶段 7:ST 模型与纵向优化。唯一授权范围是:
`docs/superpowers/plans/2026-08-03-em-planner-longitudinal-st-implementation.md` 的 Task 1、Task 2、Task 3。
不要开始轨迹发布、单次规划服务、滚动执行、控制适配、动态障碍物或任何后续阶段。
开始前必须完整读取:
1. `docs/superpowers/specs/2026-08-03-em-planner-ls-st-design.md`
2. `docs/superpowers/specs/2026-08-03-em-planner-windowed-execution-design.md`
3. `docs/superpowers/progress/em-planner-progress.md`
4. `docs/superpowers/plans/2026-08-03-em-planner-longitudinal-st-implementation.md`
使用 `executing-plans``test-driven-development``verification-before-completion`;不要使用子代理。只在 `trajplanner` 上工作。Stage 4–6 提交必须可从 `HEAD` 追溯:`a957fda``61dfa79``c225b17``f703d41``0c48a7d``f5c69c2``f19df53``4d83ed2`;随后是本进度文件的独立 Stage 6 checkpoint。若这些提交、进度记录或入口验证任一不一致,停止实施并先报告事实,不要猜测或重做已提交工作。
开始 Task 1 前:
- 保存完整 `git status --short` 基线,确认暂存区为空;工作区已有大量与 EM 无关的 Map、CoarsePath、PathSmoothing、报告、文档、项目文件和未跟踪修改,必须完整保留。
- 不得使用 `git add .``git add -A``git reset --hard``git checkout --` 或任何清理命令;每次只用显式路径暂存计划指定文件。
- 运行以下隔离入口验证,并确认全部退出码为 0:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- lateral-integration
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- lateral-all
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- optimization
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- osqp
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- all-foundation
```
- `ClumsyPilot.csproj` 中有既有用户 hunk;正常项目构建仍会因旧 `auto_avoidance/MultiWheelAutoAvoidance.cs` 缺少 `NetTopologySuite``OpenCvSharp` 失败。不要修改、删除、屏蔽或绕过该旧模块,也不要把它当成 EM 回归。
实施约束:
- 严格逐任务 TDD:先增加该任务的最小失败检查,观察其因缺少目标行为而失败,再写最小生产代码,并运行计划指定 GREEN 验证。
- Task 1、2、3 各自形成独立功能提交;提交前用 `git diff --cached --check``git diff-tree` 核对文件集合。
- ST 只能消费 LS 输出的实际、严格递增 `PathS`,并且只能通过既有 solver-neutral `IQpSolver`/`QuadraticProgram` 边界使用 OSQP;不能向 ST 引入 P/Invoke、当前工作目录依赖、UI 或硬件对象。
- 不修改无关的 Map、CoarsePath、PathSmoothing、旧 `auto_avoidance`、已完成 LS 或 OSQP 后端;不实现轨迹发布、滚动执行、控制适配或动态障碍物。
阶段结束条件:
- 仅在 Task 13 均有独立提交、各自 RED/GREEN 证据和该计划全部出口验证通过后,运行 Stage 7 规定的 LS/ST/优化/OSQP/Foundation 回归及 `git diff --check`
- 检查暂存区为空、用户的起始 `git status --short` 修改仍被保留;然后只更新并单独提交 `docs/superpowers/progress/em-planner-progress.md`
- 最终报告必须给出实际提交、命令/退出码、未处理基线问题,并提供阶段 8(仅轨迹发布与单次规划服务 Task 4–6)的精确下一窗口提示词。
```
## Next Stage
- None. Stage 10 is the final approved stage; no Stage 11 implementation work or prompt is generated.
## Historical Stage 9 Next Stage
- Stage: 9 — rolling coordination and gear-switch execution
- Readiness: Ready after this Stage 8 progress checkpoint is committed.
- Plan: `docs/superpowers/plans/2026-08-03-em-planner-rolling-execution-implementation.md`
- Authorized tasks only:
- Task 1 — Cycle Identity, Scheduling Decision, and Stale-Result Suppression
- Task 2 — Safe Previous-Trajectory Handoff
- Task 3 — Gear-Switch State Machine and Trajectory Executor
- Explicit exclusions: generic control adaptation, plugin packaging, dynamic obstacles, UI/hardware integration beyond the approved execution boundary, and Stage 10 work.
## Historical Stage 10 Window Prompt
```text
请继续 ParkingRobot 仓库的 EM Planner 多窗口实施。
工作目录:D:\Users\Desktop\项目\prakrobot\ParkingRobot
本窗口只执行阶段 10:通用控制适配、插件发布与最终滚动验收。唯一授权范围是:
docs/superpowers/plans/2026-08-03-em-planner-rolling-execution-implementation.md 的 Task 4、Task 5、Task 6。
不要开始动态障碍物、UI/硬件集成,或任何未在该计划中的后续工作。
开始前必须完整读取:
1. docs/superpowers/specs/2026-08-03-em-planner-ls-st-design.md
2. docs/superpowers/specs/2026-08-03-em-planner-windowed-execution-design.md
3. docs/superpowers/progress/em-planner-progress.md
4. docs/superpowers/plans/2026-08-03-em-planner-rolling-execution-implementation.md
使用 executing-plans、test-driven-development 和 verification-before-completion;不要使用子代理。只在 trajplanner 上工作。
开始 Task 4 前:
- 确认 d75380c、55119de、de402e6 以及紧随其后的 Stage 9 进度 checkpoint 均可从 HEAD 追溯。
- 保存完整 git status --short 基线,确认暂存区为空,并保留所有既有用户修改。
- 从仓库根目录运行并确认退出码为 0coordinator、executor、em-core-all、trajectory、lateral-all、optimization、osqp、all-foundation。
- 不使用 git add .、git add -A、清理命令或破坏性 Git 命令;每次仅显式暂存计划指定文件。
- Stage 6 lateral-all 测试夹具依赖仓库根目录的当前工作目录定位固定 OSQP DLL;不要修改已完成 LS/ST/OSQP/发布门面来改变该基线。
执行要求:
- 严格逐任务 TDD;Task 4-6 各自独立提交,提交前运行 git diff --cached --check,并用 git diff-tree 核对范围。
- Task 4 只能产生通用控制命令,不能引用 MultiVehicleScriptVx、MultiVehicleScriptVy、MultiVehicleScriptVth、UI 或硬件对象。
- Task 5 只能按计划输出并验证 ClumsyPilot.dll、osqp.dll 与许可证目录;不得重写现有 csproj 用户 hunk。
- Task 6 只添加计划要求的端到端证据和 README 所有权/部署说明;不实现动态障碍物、UI 或硬件集成。
阶段结束时:
- 仅在 Task 4-6 均有独立 RED/GREEN 证据和计划规定的最终出口验证后,运行 em-all 和 git diff --check。
- 确认暂存区为空、起始用户修改仍保留;只更新并单独提交 docs/superpowers/progress/em-planner-progress.md。
- 返回提交、RED/GREEN 证据、命令/退出码和未处理基线问题;Stage 10 是最终阶段,不生成 Stage 11 实施提示词。
```
## Historical Stage 9 Next-Window Prompt
```text
请继续 ParkingRobot 仓库的 EM Planner 多窗口实施。
工作目录:D:\Users\Desktop\项目\prakrobot\ParkingRobot
本窗口只执行阶段 9:滚动协调与换向执行。唯一授权范围是:
docs/superpowers/plans/2026-08-03-em-planner-rolling-execution-implementation.md 的 Task 1、Task 2、Task 3。
不要开始通用控制适配、插件最终发布、动态障碍物、UI/硬件集成或任何 Stage 10 工作。
开始前必须完整读取:
1. docs/superpowers/specs/2026-08-03-em-planner-ls-st-design.md
2. docs/superpowers/specs/2026-08-03-em-planner-windowed-execution-design.md
3. docs/superpowers/progress/em-planner-progress.md
4. docs/superpowers/plans/2026-08-03-em-planner-rolling-execution-implementation.md
使用 executing-plans、test-driven-development 和 verification-before-completion;不要使用子代理。只在 trajplanner 上工作。
开始 Task 1 前:
- 确认 1d58864、7410654、019b896 以及紧随其后的 Stage 8 进度 checkpoint 均可从 HEAD 追溯。
- 保存完整 git status --short 基线,确认暂存区为空,并保留所有既有用户修改。
- 从仓库根目录运行并确认退出码为 0em-core-all、trajectory、lateral-all、optimization、osqp、all-foundation。
- 不使用 git add .、git add -A、清理命令或破坏性 Git 命令;每次仅显式暂存计划指定文件。
- Stage 6 lateral-all 测试夹具依赖仓库根目录的当前工作目录定位固定 OSQP DLL;不要修改已完成 LS/ST/OSQP/发布门面来改变该基线。
执行要求:
- 严格逐任务 TDD;Task 1–3 各自独立提交,提交前运行 git diff --cached --check,并用 git diff-tree 核对范围。
- 保持单次 EmPlanningService 纯请求边界:协调器只能消费其不可变结果,不能把 UI、硬件或当前工作目录依赖回灌到核心。
- 不修改无关 Map、CoarsePath、PathSmoothing、旧 auto_avoidance、已完成 Foundation/OSQP/LS/ST/Trajectory/Validation/Facade 文件;不实现动态障碍或控制适配。
阶段结束时:
- 仅在 Task 13 均有独立 RED/GREEN 证据和阶段出口验证后,运行计划规定的回归和 git diff --check。
- 确认暂存区为空、起始用户修改仍保留;只更新并单独提交 docs/superpowers/progress/em-planner-progress.md。
- 返回提交、RED/GREEN 证据、命令/退出码、未处理基线问题,并给出 Stage 10 的精确下一窗口提示词。
```
@@ -1,235 +0,0 @@
# 新窗口执行提示词:EM 纵向滚动规划阶段一
请在一个新的 Codex 窗口中完整粘贴本文件内容,或明确要求 Codex 读取并执行本文件。
---
你现在要在仓库 `D:\Users\Desktop\项目\prakrobot\ParkingRobot` 中执行 EM 纵向滚动轨迹规划修复的**阶段一:纵向基础与窗口语义**。
这是一项代码实施任务,不是重新讨论方案。设计已经确认。本窗口只能完成阶段一,对应原十任务计划中的 Task 1 和 Task 2;完成阶段验收、交接文件和阶段二提示词后必须停止,不得继续实现 Task 3。
## 必须使用的工作方法
1. 首先使用 `executing-plans` skill 执行现有计划。
2. 实现每个 bugfix/feature 时使用 `test-driven-development`:先写失败测试、运行确认失败原因正确,再做最小实现、重新运行确认通过。
3. 遇到非预期编译错误、测试失败或行为不一致时,先使用 `systematic-debugging` 查明根因,不得盲目放宽阈值。
4. 声称阶段完成前必须使用 `verification-before-completion`,重新运行全部阶段验收命令。
5. 不需要重新 brainstorming;已经批准的设计规格是行为依据。
## 必读文件
开始实施前完整阅读:
- `docs/superpowers/specs/2026-08-05-em-longitudinal-rolling-planning-design.md`
- `docs/superpowers/plans/2026-08-05-em-longitudinal-rolling-planning.md`
- `docs/superpowers/plans/2026-08-05-em-longitudinal-rolling-four-phase-roadmap.md`
在十任务计划中,本窗口只能执行:
- Task 1:完整 jerk 受限停车数学
- Task 2:纵向模式、空间窗口和输入契约
计划中的代码片段是实施基线,但在应用前必须与当前仓库接口核对;若代码已发生变化,应做保持设计语义的最小适配,并在交接文件中记录差异。
## 开始前基线检查
依次执行并记录结果:
```powershell
git branch --show-current
git status --short
git log -8 --oneline
git show --stat --oneline e0ccc0c
git show --stat --oneline e608299
```
预期当前分支为 `trajplanner`,并且至少能找到:
- `e0ccc0c docs: plan EM longitudinal rolling implementation`
- `e608299 docs: require stabilized EM stop terminals`
仓库当前存在大量与本任务无关的用户修改。必须遵守:
- 不运行 `git reset --hard``git checkout -- .``git clean` 或任何等价清理命令。
- 不回退、不格式化、不移动与阶段一无关的文件。
- 不使用 `git add .``git add -A`
- 提交前使用 `git diff --cached --name-only` 核对暂存区。
- 如果阶段一目标文件本身已有未提交修改,先检查并保留用户内容,在其基础上最小集成;不能直接覆盖。
## 不可改变的行为语义
- `DistanceHorizonMeters` 只决定 LS 可以查看的空间长度。
- `TimeHorizonSeconds` 只决定一次 ST 优化的未来时间范围。
- 局部空间窗口末端不是停车点。
- 只有真实 `Goal``GearSwitchApproach` 才是停车边界。
- 纵向模式必须显式区分:
- `RollingContinuation`
- `ApproachStopBoundary`
- `ExactStopAtBoundary`
- 精确停车最终要支持在 QP 内达到 `S=S_end、U=0、A=0`,并至少保留一个完整 `OutputTimeStepSeconds` 的静止稳定区间。
- `ZeroSpeedHoldSeconds` 是 QP 时间范围之后的发布保持段,不能从 `TimeHorizonSeconds` 中扣除。
- 不得通过放宽 jerk、加速度、求解残差或轨迹验证阈值规避问题。
- 保持 `OBSERVE_ONLY`;不得增加任何底盘控制下发。
## Task 1:完整 jerk 受限停车数学
严格按照十任务计划 Task 1 的 TDD 步骤实施。
### 文件范围
- 新建:`ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/JerkLimitedStoppingMath.cs`
- 修改:`ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/PathSpeedLimitBuilder.cs`
- 测试:`ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalModelChecks.cs`
### 必须产生的接口
- `JerkLimitedStoppingMath.TryCalculate(double, double, double, double, out JerkLimitedStoppingProfile, out string)`
- `JerkLimitedStoppingMath.MaximumInitialSpeedForDistance(double, double, double, double, double)`
- `JerkLimitedStoppingMath.CalculateMaximumStoppedDistance(double, double, double, double, double, double, double)`
- `JerkLimitedStoppingProfile`,至少表达停车距离、持续时间、末速度和末加速度。
停车数学必须使用与 ST QP 一致的恒 jerk 离散/分段动力学,能够覆盖初始正加速度、初始负加速度、三角形和梯形减速度轮廓,最终同时释放到零速度和零加速度。
### 红灯和绿灯命令
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-model
```
先证明新增测试因缺少新能力而失败;实现后必须得到 `PASS longitudinal-model`
### Task 1 提交
只暂存 Task 1 文件,核对暂存区后提交:
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/JerkLimitedStoppingMath.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/PathSpeedLimitBuilder.cs ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalModelChecks.cs
git diff --cached --name-only
git commit -m "feat: add complete jerk-limited stopping math"
```
## Task 2:纵向模式、空间窗口和输入契约
Task 1 提交并通过测试后,严格按照十任务计划 Task 2 的 TDD 步骤实施。
### 文件范围
- 新建:`ClumsyPilot/ParkrobTrajplanner/EMPlanner/Contracts/EmLongitudinalMode.cs`
- 新建:`ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalTerminalSchedule.cs`
- 修改:`ClumsyPilot/ParkrobTrajplanner/EMPlanner/Segmentation/PlanningHorizonSelector.cs`
- 修改:`ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalPlanningInput.cs`
- 修改:`ClumsyPilot/ParkrobTrajplanner/EMPlanner/Validation/EmPlanningRequestValidator.cs`
- 修改:`ClumsyPilot/ParkrobTrajplanner/EMPlanner/Facade/EmPlanningService.cs`
- 测试:`ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalModelChecks.cs`
- 测试:`ClumsyPilot/tests/EMPlannerVerificationHost/FoundationChecks.cs`
### 必须产生的接口和行为
- `EmLongitudinalMode` 三种模式。
- `LongitudinalTerminalSchedule.GetStabilizationStartIndex(IReadOnlyList<double>, double)`
- `PlanningHorizonSelection` 显式保存:
- `WindowEndReferenceS`
- `WindowEndBoundaryType`
- `LongitudinalMode`
- `StopBoundaryReferenceS`
- `HasStopBoundary`
- `LongitudinalPlanningInput` 分离路径上界、真实停车边界和纵向模式。
- 选择顺序必须是:
1. 识别当前方向段的真实边界;
2. 用 Task 1 的完整 jerk 模型检查最小停车距离;
3. 只用 `DistanceHorizonMeters` 截取 LS 空间窗口;
4. 真实边界不在窗口内时选择 `RollingContinuation`
5. 边界已在窗口内但本次时间范围无法形成稳定停车时选择 `ApproachStopBoundary`
6. 能在时间范围内停车并留出内部稳定区间时选择 `ExactStopAtBoundary`
- `EmPlanningRequestValidator` 必须检查前视停车余量、时间步、时间范围和内部稳定区间组合,不允许无效配置进入求解器。
### 红灯和绿灯命令
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-model
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- foundation
```
先证明模式/属性/行为测试失败;实现并更新所有直接构造调用后,必须得到:
```text
PASS foundation
PASS longitudinal-model
```
### Task 2 提交
只暂存 Task 2 文件,核对暂存区后提交:
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/EMPlanner/Contracts/EmLongitudinalMode.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalTerminalSchedule.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Segmentation/PlanningHorizonSelector.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalPlanningInput.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Validation/EmPlanningRequestValidator.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Facade/EmPlanningService.cs ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalModelChecks.cs ClumsyPilot/tests/EMPlannerVerificationHost/FoundationChecks.cs
git diff --cached --name-only
git commit -m "feat: separate rolling horizons from stop boundaries"
```
若为修复编译必须改动直接调用者,使用明确路径单独加入,并在提交前解释原因;仍然禁止把其他脏文件带入提交。
## 阶段一最终验收
Task 1 和 Task 2 完成后,从当前代码状态重新运行,不得引用之前的测试输出:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- foundation
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-model
git diff --check
git log -5 --oneline
git status --short
```
验收时同时人工确认:
- 普通 5 m 局部窗口不会因为 `TimeHorizonSeconds=2s` 而被解释为必须在两秒内走完 5 m。
- Rolling 局部窗口末端不再具有“停车边界”身份。
- Goal/GearSwitch 边界进入窗口但暂时无法稳定停车时是 `ApproachStopBoundary`,不是失败,也不是强制零速。
- 精确停车模式为至少一个 QP 内部静止时间步保留了明确的调度信息。
## 阶段结束交接——必须执行
验收通过后使用 `writing-plans`,根据**实际提交和实际接口**完成以下两份文件:
1. 阶段一交接:
`docs/superpowers/handoffs/2026-08-05-em-longitudinal-rolling-phase-1.md`
2. 阶段二精细提示词:
`docs/superpowers/prompts/2026-08-05-em-longitudinal-rolling-phase-2.md`
阶段一交接必须包含:
- 开始基线提交和结束提交;
- Task 1、Task 2 各自的提交号;
- 实际修改/新增文件;
- 最终接口签名和与原计划不同之处;
- 全部验证命令、退出码和关键 PASS 输出;
- 遗留问题、未验证内容和目标机限制;
- 阶段二可依赖的明确事实。
阶段二提示词必须:
- 引用阶段一真实提交号和交接文件;
- 只覆盖原计划 Task 35
- 明确速度包络、QP 条件终端约束、解验证、初始解和外层迭代的文件范围;
- 要求 TDD,并列出 `longitudinal-model``longitudinal-integration` 验收命令;
- 保留 Task 3、Task 4、Task 5 的独立提交边界;
- 要求阶段二结束后生成阶段二交接和阶段三提示词;
- 重复工作树保护、全局行为语义和 `OBSERVE_ONLY` 限制。
只暂存这两份交接文档,并提交:
```powershell
git add -- docs/superpowers/handoffs/2026-08-05-em-longitudinal-rolling-phase-1.md docs/superpowers/prompts/2026-08-05-em-longitudinal-rolling-phase-2.md
git diff --cached --name-only
git commit -m "docs: hand off EM rolling planning phase one"
```
提交后输出一个简短阶段报告,包含两个功能提交、交接提交和两项验收结果,然后**立即停止**。不要开始 Task 3,也不要在本窗口实施阶段二。
---
以上内容是本窗口的完整授权范围。
@@ -1,72 +0,0 @@
# New Window Prompt: EM Longitudinal Rolling Planning — Phase 2
Work in `D:\Users\Desktop\项目\prakrobot\ParkingRobot`. This is an implementation task, not a design discussion. Read these files completely before editing:
- `docs/superpowers/specs/2026-08-05-em-longitudinal-rolling-planning-design.md`
- `docs/superpowers/plans/2026-08-05-em-longitudinal-rolling-planning.md`
- `docs/superpowers/plans/2026-08-05-em-longitudinal-rolling-four-phase-roadmap.md`
- `docs/superpowers/handoffs/2026-08-05-em-longitudinal-rolling-phase-1.md`
Phase 1 ended at `21b20d0` and consists of `e56220f` and `21b20d0`. Preserve their actual interfaces: `JerkLimitedStoppingMath`, `JerkLimitedStoppingProfile`, `EmLongitudinalMode`, `LongitudinalTerminalSchedule`, `PlanningHorizonSelection.WindowEndReferenceS`, `WindowEndBoundaryType`, `LongitudinalMode`, `StopBoundaryReferenceS`, `HasStopBoundary`, and the mode-explicit `LongitudinalPlanningInput` constructor/properties. `TerminalPathS` is only a temporary compatibility alias; use `PathUpperBoundS` for path bounds and `StopBoundaryPathS` for real stops.
Execute only original-plan Tasks 3, 4, and 5. Do not begin Task 6 or later. Use `executing-plans`, test-driven development for every behavior change, systematic debugging for every unexpected test/build failure, and verification-before-completion before each commit and before phase handoff. No new brainstorming is needed.
Start by recording:
```powershell
git branch --show-current
git status --short
git log -8 --oneline
git show --stat --oneline e56220f
git show --stat --oneline 21b20d0
```
The branch must be `trajplanner`; the worktree has substantial unrelated user changes. Never use reset, checkout, clean, `git add .`, or `git add -A`. Inspect any target file that is already dirty and preserve its user content. Explicitly stage only the current task files and run `git diff --cached --name-only` before every commit.
## Task 3 — mode-conditioned PathS speed envelope
Files: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/PathSpeedLimit.cs`, `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/PathSpeedLimitBuilder.cs`, and `ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalModelChecks.cs`.
Write and observe a failing `longitudinal-model` test first. Rolling must keep a nonzero speed allowance at `PathUpperBoundS` and report no stop boundary. For Approach and Exact modes, compute each stopping-speed limit by inverting `JerkLimitedStoppingMath.MaximumInitialSpeedForDistance(remaining, maximumAcceleration, maximumDeceleration, maximumJerk, directionMaximum)` against `StopBoundaryPathS`; only the real stop-boundary sample is exactly zero. Replace the old square-root and discrete-tail approximations. Add `PathSpeedLimit.HasStopBoundary` and retain any necessary compatibility alias without introducing warnings. Then run:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-model
```
Commit only these Task 3 files:
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/PathSpeedLimit.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/PathSpeedLimitBuilder.cs ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalModelChecks.cs
git diff --cached --name-only
git commit -m "feat: keep rolling speed envelopes open"
```
## Task 4 — conditional QP terminals and independent validation
Files: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalConstraintBuilder.cs`, `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalSolutionValidator.cs`, and `ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalModelChecks.cs`.
TDD requirements: Rolling and Approach have no terminal S/U/A equalities. Exact mode must use `LongitudinalTerminalSchedule.GetStabilizationStartIndex` and enforce `S=StopBoundaryPathS`, `U=0`, `A=0` at every knot in the internal stabilization tail. The exact mode row count is `8*K-2+3*M`; non-exact modes remain `8*K-2`. The validator always checks finite values, time layout, monotone PathS, bounds, jerk, and exact constant-jerk dynamics. For Approach and Exact, independently recompute every knot's complete jerk-limited stopping profile and reject if `S + stoppingDistance > StopBoundaryPathS + tolerance`; exact mode must also canonicalize its full stabilization tail. Run `longitudinal-model` red then green and commit only Task 4 files:
```powershell
git add -- ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalConstraintBuilder.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalSolutionValidator.cs ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalModelChecks.cs
git diff --cached --name-only
git commit -m "feat: apply ST stop constraints only at real boundaries"
```
## Task 5 — mode-specific seeds and outer iteration
Files: `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/SequentialLongitudinalOptimizer.cs`, `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalObjectiveBuilder.cs`, and `ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalIntegrationChecks.cs`.
Write failing `longitudinal-integration` tests first. A rolling two-second ST solve with a five-metre LS window must end before `PathUpperBoundS` at nonzero speed. Exact mode must have `S=StopBoundaryPathS`, `U=0`, `A=0` at every stabilization-tail knot. Dispatch seed creation by mode: rolling forward integration without a forced endpoint; approach as a cruise plus complete jerk-limited stop prefix limited to this ST horizon; exact stop reaches rest at the schedule anchor then fills the internal tail. In outer iterations, only Exact mode pins tail progress. Replace upper-bound uses with `PathUpperBoundS`; only real-stop endpoint uses may use `StopBoundaryPathS`. Keep terminal-acceleration soft objective only for non-exact modes. Verify both commands and commit only Task 5 files:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-model
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-integration
git add -- ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/SequentialLongitudinalOptimizer.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalObjectiveBuilder.cs ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalIntegrationChecks.cs
git diff --cached --name-only
git commit -m "feat: seed rolling and exact-stop ST profiles"
```
Global semantics are non-negotiable: `DistanceHorizonMeters` is LS look-ahead only; `TimeHorizonSeconds` is one ST horizon only; only Goal/GearSwitchApproach can request an exact zero-speed terminal; `ZeroSpeedHoldSeconds` is outside the QP horizon; do not loosen jerk/acceleration/solver/validation tolerances; and retain `OBSERVE_ONLY` with no chassis command.
At phase-2 end, rerun fresh `longitudinal-model` and `longitudinal-integration`, run `git diff --check`, inspect `git log -5 --oneline` and `git status --short`, then use `writing-plans` to create the phase-2 handoff and a phase-3 prompt. Commit only those two handoff documents and stop; do not implement phase 3.
@@ -1,86 +0,0 @@
# New Window Prompt: EM Longitudinal Rolling Planning — Phase 3
Work in `D:\Users\Desktop\项目\prakrobot\ParkingRobot`. This is an implementation task, not a design discussion. Read these files completely before editing:
- `docs/superpowers/specs/2026-08-05-em-longitudinal-rolling-planning-design.md`
- `docs/superpowers/plans/2026-08-05-em-longitudinal-rolling-planning.md`
- `docs/superpowers/plans/2026-08-05-em-longitudinal-rolling-four-phase-roadmap.md`
- `docs/superpowers/handoffs/2026-08-05-em-longitudinal-rolling-phase-2.md`
Phase 2 code ends at `59d13e5`; Task 3 and Task 4 also have local fixups `dbc7b7c` and `26bd822` because the dirty shared worktree prevented autosquash. Preserve all actual interfaces listed in the handoff, especially `EmLongitudinalMode`, `LongitudinalTerminalSchedule`, mode-explicit `LongitudinalPlanningInput`, `PathUpperBoundS`, `StopBoundaryPathS`, and `PathSpeedLimit.HasStopBoundary`.
Execute only original-plan Tasks 6 and 7. Do not begin Task 8 or later. Use `executing-plans`, TDD for every behavior change, `systematic-debugging` for any unexpected build/test failure, and `verification-before-completion` before every commit and before phase handoff. No brainstorming is needed.
Start by recording:
```powershell
git branch --show-current
git status --short
git log -10 --oneline
git show --stat --oneline 59d13e5
```
The branch is `trajplanner` and the worktree has substantial unrelated user changes. Never reset, checkout, clean, broadly stage, stash user files, use `git add .`, or use `git add -A`. Stage only the listed task files and run `git diff --cached --name-only` before every commit.
## Task 6 — mode-aware trajectory publication and validation
Files:
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Contracts/EmTrajectoryMetadata.cs`
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Trajectory/TrajectorySampleSchedule.cs`
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Trajectory/EmTrajectoryAssembler.cs`
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Validation/EmTrajectoryValidator.cs`
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Facade/EmPlanningService.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryChecks.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/ExecutorChecks.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/CoordinatorChecks.cs`
Write and observe failing trajectory tests first. Add `EmLongitudinalMode` to trajectory metadata and pass the mode from `EmPlanningService`. For Rolling and Approach, sample exactly the ST candidate knots: no synthetic zero-speed hold, no terminal boundary anchor, and a nonzero terminal state remains valid when ordinary limits are met.
For Exact mode, verify the complete internal static tail from `LongitudinalTerminalSchedule.GetStabilizationStartIndex`: every tail knot must hold the same PathS with `U=0`, `A=0`, and zero tail jerk. Mark only the first tail knot as the real Goal/GearSwitch boundary anchor; then append `ZeroSpeedHoldSeconds` only after the QP horizon, without duplicate timestamps. Add `TerminalAccelerationNotZero` to publication failures and make `EmTrajectoryValidator` always retain ordinary finite, kinematic, jerk, and collision checks while applying terminal-anchor/tail checks only to Exact mode. Do not weaken any threshold or add chassis behavior.
Required checks before the Task 6 commit:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- coordinator
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- executor
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
```
Commit only the Task 6 files with:
```powershell
git commit -m "feat: publish rolling trajectories without stop tails"
```
## Task 7 — previous-trajectory longitudinal soft reference
Files:
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Longitudinal/LongitudinalPreviousTrajectorySeedBuilder.cs` (new)
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/Facade/EmPlanningService.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalModelChecks.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/EmPlanningServiceChecks.cs`
Write and observe failing tests first. Create `LongitudinalPreviousTrajectorySeed` with `PathS`, `ProgressSpeedMetersPerSecond`, and `Empty`. Its builder must resample the previous `EmTrajectory` at `newEffectiveAtUtc + knotTime`, linearly interpolate world position and signed speed, project monotonically onto the current `LateralPath`, and publish absolute progress speed. Any incompatible direction, segment, missing trajectory, or out-of-range sample must return `Empty` without throwing into planning.
In `EmPlanningService`, construct current ST knot times after lateral planning, build this seed from the previous trajectory/current path/effective time/segment/direction, and pass the two reference arrays into the existing mode-explicit `LongitudinalPlanningInput`. Confirm the objective receives nonzero previous-S and previous-U soft-reference terms only for a valid seed; these remain soft terms, never terminal constraints. Keep LS slicing on `WindowEndReferenceS`.
Required checks before the Task 7 commit:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-model
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-planning-service
```
Commit only the Task 7 files with:
```powershell
git commit -m "feat: reuse prior trajectory in longitudinal planning"
```
Global semantics are non-negotiable: `DistanceHorizonMeters` is LS look-ahead only; `TimeHorizonSeconds` is one ST horizon only; only Goal/GearSwitchApproach may require Exact zero speed; `ZeroSpeedHoldSeconds` is outside the QP horizon; do not loosen jerk, acceleration, solver, or validation thresholds; and retain `OBSERVE_ONLY` with no chassis command.
At phase end, rerun `trajectory`, `em-planning-service`, `coordinator`, and `executor`; run `git diff --check`, `git log -5 --oneline`, and `git status --short`. Use `writing-plans` to create the phase-3 handoff and phase-4 prompt, commit only those two documents, and stop without implementing Task 8.
@@ -1,153 +0,0 @@
# New Window Prompt: EM Longitudinal Rolling Planning — Phase 4
Work in `D:\Users\Desktop\项目\prakrobot\ParkingRobot`. This is an implementation task, not a design discussion. Before editing, read these files completely:
- `docs/superpowers/specs/2026-08-05-em-longitudinal-rolling-planning-design.md`
- `docs/superpowers/plans/2026-08-05-em-longitudinal-rolling-planning.md`
- `docs/superpowers/plans/2026-08-05-em-longitudinal-rolling-four-phase-roadmap.md`
- `docs/superpowers/handoffs/2026-08-05-em-longitudinal-rolling-phase-3.md`
The phase-3 code end is `6cfbaf6` (`feat: reuse prior trajectory in longitudinal planning`); Task 6 is `4159ae0`. Earlier Task 3/4 fixups `dbc7b7c` and `26bd822` intentionally remain separate because the shared worktree was already dirty and Git refused autosquash. Keep the actual interfaces from the phase-3 handoff, particularly `EmLongitudinalMode`, `LongitudinalTerminalSchedule`, explicit-mode `LongitudinalPlanningInput`, `PathUpperBoundS`, `StopBoundaryPathS`, `PathSpeedLimit.HasStopBoundary`, `EmTrajectoryMetadata.LongitudinalMode`, and `LongitudinalPreviousTrajectorySeed`.
Execute only original-plan Tasks 8, 9, and 10. Use `executing-plans`, TDD for every behavior change, `systematic-debugging` for every unexpected build/test failure, and `verification-before-completion` before each commit and the final handoff. Do not re-run brainstorming. Do not create chassis behavior: `OBSERVE_ONLY` remains mandatory.
Start by recording:
```powershell
git branch --show-current
git status --short
git log -10 --oneline
git show --stat --oneline 6cfbaf6
```
The branch is `trajplanner` and the worktree contains substantial unrelated user changes. Never reset, checkout, clean, broadly stage, stash user files, use `git add .`, or use `git add -A`. Before every commit stage only that task's listed files, then run `git diff --cached --name-only` and `git diff --cached --check`.
## Task 8 — service mode flow, Goal/GearSwitch, and multi-cycle regression
Files:
- `ClumsyPilot/tests/EMPlannerVerificationHost/EmPlanningServiceChecks.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalIntegrationChecks.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/EmFixtureFactory.cs`
Write and observe the failing tests first. With a sufficiently long unobstructed straight path, call `EmPlanningService.Plan` across three physical states and assert the published metadata flows in this exact order:
```csharp
Verification.Equal(EmLongitudinalMode.RollingContinuation,
rolling.Trajectory.Metadata.LongitudinalMode, "cycle 1 rolls");
Verification.True(rolling.Trajectory.Points[rolling.Trajectory.Points.Count - 1]
.SignedLongitudinalVelocity != 0d, "cycle 1 has nonzero terminal speed");
Verification.Equal(EmLongitudinalMode.ApproachStopBoundary,
approach.Trajectory.Metadata.LongitudinalMode, "cycle 2 approaches");
Verification.Equal(EmLongitudinalMode.ExactStopAtBoundary,
exact.Trajectory.Metadata.LongitudinalMode, "cycle 3 stops");
Verification.NearlyEqual(0d, exact.Trajectory.Points[exactAnchor]
.SignedLongitudinalVelocity, "goal speed is zero");
Verification.True(exact.Trajectory.Points.Count > exactAnchor + 1,
"goal anchor is followed by a QP stabilization point");
Verification.NearlyEqual(exact.Trajectory.Points[exactAnchor].PathS,
exact.Trajectory.Points[exactAnchor + 1].PathS,
"goal stabilization keeps the stop position");
Verification.NearlyEqual(0d, exact.Trajectory.Points[exactAnchor + 1]
.SignedLongitudinalVelocity, "goal stabilization speed is zero");
```
Repeat the exact-stop assertion using `CreateGearPairReferencePath()` and verify that the published trajectory contains no point from the following direction segment. Add an independent semantic regression with:
```csharp
configuration.Scheduling.DistanceHorizonMeters = 5d;
configuration.Scheduling.TimeHorizonSeconds = 2d;
configuration.Scheduling.OutputTimeStepSeconds = 0.1d;
configuration.Longitudinal.MaximumForwardSpeedMetersPerSecond = 0.2d;
```
It must publish 21 points, end before `PathS=5m`, have nonzero terminal speed, and pass publication validation. This proves distance is the LS window and time is the single ST horizon.
Run the new test first:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-core-all
```
Before fixture work, at least one assertion may fail because an old fake solver assumes every trajectory stops. If a production failure is exposed, repair it only in the original owning Task 17 scope and rerun its targeted tests; Task 8 itself is fixture/regression work and must not weaken production validation. Update scripted fixtures by mode:
- rolling and approach return nonzero terminal velocity and no hold tail;
- exact returns the real boundary anchor and its QP-internal `S/U/A` static tail, with only a later optional external hold.
The phase-3 service fixture already uses a realistic longer direct path and captures the longitudinal QP for prior-trajectory soft-reference coverage. Preserve that coverage while adding the new mode-flow assertions.
Required checks and commit:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-core-all
git add -- ClumsyPilot/tests/EMPlannerVerificationHost/EmPlanningServiceChecks.cs ClumsyPilot/tests/EMPlannerVerificationHost/LongitudinalIntegrationChecks.cs ClumsyPilot/tests/EMPlannerVerificationHost/EmFixtureFactory.cs
git diff --cached --name-only
git diff --cached --check
git commit -m "test: cover rolling-to-stop EM planning flow"
```
Expected: `PASS longitudinal-model`, `PASS longitudinal-integration`, `PASS trajectory`, and `PASS em-planning-service` as part of `PASS em-core-all`.
## Task 9 — observation diagnostics and documentation
Files:
- `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationDiagnostics.cs`
- `ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs`
- `ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md`
- `ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/README.md`
Write failing observation tests first. The one-time configuration diagnostic must include:
```text
maximumJerkLimitedStopDistance=
maximumJerkLimitedStopDuration=
requiredDistanceHorizon=
```
Every successful trajectory summary must include:
```text
longitudinalMode=RollingContinuation
terminalSpeed=
terminalAcceleration=
```
Failure diagnostics must include at least `longitudinalMode=`, `remainingToBoundary=`, `minimumStoppingDistance=`, `minimumStoppingDuration=`, and `maximumStoppedReachableDistance=`. Use the worst forward/reverse speed to print the maximum jerk-limited stopping capability once per session, not inside a per-frame UI redraw block. Preserve `OBSERVE_ONLY` and do not issue a chassis command.
Update `EMPlanner/README.md` to state exactly that `DistanceHorizonMeters` controls the LS reference window, `TimeHorizonSeconds` controls ST output duration, only Goal/GearSwitchApproach may require an exact zero-speed terminal, and rolling/approach may publish nonzero terminal speed. Update the MovementTest README to state that rolling trajectory observation remains `OBSERVE_ONLY` and never commands the chassis.
Required checks and commit:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
git add -- ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/TrajectoryObservationDiagnostics.cs ClumsyPilot/tests/EMPlannerVerificationHost/TrajectoryObservationChecks.cs ClumsyPilot/ParkrobTrajplanner/EMPlanner/README.md ClumsyPilot/ParkrobTrajplanner/tarjplanner_movementtest/README.md
git diff --cached --name-only
git diff --cached --check
git commit -m "docs: explain rolling longitudinal planning diagnostics"
```
Expected: `PASS trajectory-observation`.
## Task 10 — complete regression and target-machine OSQP verification
Task 10 is verification-only: do not modify files and do not create an empty commit. Run:
```powershell
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- em-core-all
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- coordinator
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- executor
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- trajectory-observation
dotnet build ClumsyPilot/ClumsyPilot.csproj -p:ExcludeLegacyAutoAvoidance=true
dotnet run --project ClumsyPilot/tests/EMPlannerVerificationHost/EMPlannerVerificationHost.csproj -- longitudinal-real-osqp-probe
```
Then perform the manual `TrajectoryObservationMovementTest` observation-only probe with the current manual parameters. Confirm one startup configuration print, rolling with nonzero terminal speed and no hold when far from Goal, approach near Goal, exact stop with `U=0,A=0`, no point-21 `JerkLimitExceeded`, and recurring `OBSERVE_ONLY: no chassis command is sent.`
The real-OSQP probe must run on a machine with the required native dependency. If this window cannot load it, record the result precisely as pending target-machine verification; do not bypass it by modifying longitudinal code, solver residuals, or any validation threshold. Finish with `git diff --check`, `git status --short`, and `git log --oneline -12`, preserving all unrelated dirty work.
After Task 10, use `writing-plans` to create `docs/superpowers/handoffs/2026-08-05-em-longitudinal-rolling-final.md`, record all command results and target-machine status, stage and commit only that final handoff document, and stop.
Global semantics are non-negotiable: `DistanceHorizonMeters` is only an LS look-ahead window; `TimeHorizonSeconds` is only one ST horizon; only actual Goal/GearSwitch boundaries permit exact zero speed; `ZeroSpeedHoldSeconds` is outside QP time; previous-trajectory arrays remain objective soft references; never loosen jerk, acceleration, solver, or validation thresholds; and never add chassis commands.
@@ -1,111 +0,0 @@
# TrapMap完整图片导出与终端日志开关设计
## 目标
`MovementTest.Trapmaptest.cs`增加两个相互独立的测试开关:
- 成功建图后,将完整栅格地图独立渲染为300 DPI PNG,不依赖Clumsy当前视口、缩放或其他Painter。
- 控制TrapMap调试信息是否同步打印到宿主进程终端,同时始终保留`DLog`日志。
现有`TrapMapTest` Painter初始化和清理已经使用世界坐标调用`UI.GetPainter("TrapMapTest")`,本次不重复修改。两腿检测ROI继续使用车体局部Painter及`false`参数。
## 用户开关与默认值
`TrapMapTest`手动编辑区增加:
```csharp
private const bool _saveFullMapImage = true;
private const bool _enableTerminalDebugLog = true;
```
两个值传入`TrapMapBuilder`或共享日志/导出组件。关闭图片开关时不得创建目录、临时文件或PNG。关闭终端开关时只抑制`Console.WriteLine`,不得抑制`DLog`和必要的UI提示。
## 图片内容
图片从最终发布的`GridMapData`离屏渲染,包含完整地图边界而不是屏幕截图:
- 白色背景。
- 浅灰色完整栅格线,确保每个小格可见。
- 红色占用栅格。
- 蓝色车辆轮廓、几何中心和朝向。
- 绿色工作站目标标记。
- 黑色地图外边界。
- 标题/图例区域,显示世界边界、分辨率、行列数、占据率、障碍物数量、轮胎层状态和输入来源。
世界X轴在图片中向右;世界Y轴向上,因此从`Cells[row,col]`映射到位图时反转图像Y方向。工作站仅绘制标记,不写入占用数据。
## 图片尺寸与文件约束
- 每个栅格使用`4×4`像素。
- PNG水平和垂直DPI都设置为`300`
- 包含边距和标题后,任一图片边长不得超过`4000`像素。
- 最终PNG文件大小不得超过`50 * 1024 * 1024`字节。
尺寸在分配RGBA像素缓冲区前检查。若超过4000像素,跳过导出并报告明确原因。编码先写入同目录临时文件,完成后检查实际字节数;超过50MB时删除临时文件,不留下超限最终文件。只有所有检查通过后,才原子移动/重命名为最终PNG。
典型`327×139`地图的栅格主体约为`1308×556`像素,另加标题和边距。
## 保存位置与命名
输出根目录使用宿主进程当前工作目录:
```text
TrapMapExports\TrapMap_yyyyMMdd_HHmmss_fff.png
```
毫秒时间戳避免同一秒多次测试覆盖。目录只在图片开关打开且地图成功后创建。临时文件使用同目录、同文件名加`.tmp`后缀,以保证最终重命名不跨磁盘。
## 日志行为
引入TrapMap专用日志入口,其行为为:
```text
所有消息 -> DLog.Log(message, "TrapMapTest")
终端开关开启 -> 额外Console.WriteLine("[TrapMapTest] " + message)
```
至少覆盖测试开始、输入参数、Detour位姿、车辆尺寸、地图边界/尺寸、轮胎层状态、图片保存成功/跳过/失败、最终统计和测试停止。图片错误不得因终端开关关闭而静默,仍必须进入`DLog`
## 组件边界
图片导出放在独立文件`ClumsyPilot/TrapMapImageExporter.cs`,避免继续扩大已经较长的MovementTest文件。组件只消费不可变的导出请求数据:地图、车辆位姿、工作站、轮胎层元数据和目标文件路径;它不读取Detour、雷达或UI,也不修改栅格。
`MovementTest.Trapmaptest.cs`负责开关、调用时机、日志和错误降级。导出发生在地图成功生成之后;导出失败不改变`TrapMapBuilder.Succeeded``GridMap`
实现使用内部纯C# RGBA光栅器绘制栅格、车辆、工作站和5×7位图文字,再由精确版本`StbImageWriteSharp` 1.16.7编码PNG。编码后立即在`IHDR`后插入`pHYs=11811/11811/unit1`,以保留300 DPI元数据。运行时不依赖平台绘图程序集或原生图形资产;除单个托管Stb编码程序集外,光栅、元数据和文件流程均只使用BCL。
## 错误处理
以下情况只导致图片导出失败,不导致建图失败:
- 图片开关关闭。
- 图片尺寸超过4000像素。
- 输出目录创建失败。
- RGBA缓冲区创建、绘制或PNG编码异常。
- 临时文件超过50MB。
- 临时文件重命名失败。
异常路径必须尽力删除本次临时文件,不得删除已有的成功PNG。
## 验证要求
至少验证:
1. 图片开关关闭时不创建文件和目录。
2. 小型已知栅格导出的PNG由BCL测试解码器重新读取;所有chunk CRC有效,`IHDR`为RGBA8`pHYs`表示300 DPI,像素尺寸符合4像素/格及布局规则。
3. PNG中占用格、车辆和工作站采样位置颜色正确,Y轴没有上下颠倒。
4. 超过4000像素的请求在RGBA缓冲区分配前失败。
5. 最终路径使用毫秒时间戳且不覆盖旧文件。
6. 成功文件严格小于或等于50MB,超限临时文件被删除。
7. 终端开关开启时消息同时进入DLog和终端;关闭时仍进入DLog但不写终端。
8. 图片失败时地图仍为成功状态。
9. 现有源码契约、栅格行为、生命周期测试及`ClumsyPilot`编译继续通过。
## 非目标
- 不截取Clumsy/CycleGUI窗口。
- 不保存紫色UI背景、小车3D模型、绿色两腿ROI或橙色检测猜测线。
- 不改变栅格数据格式或地图边界计算。
- 不接入新的点云来源。
- 不修改`TrajPlanner`
- 不提交或暂存本次工作区改动。
@@ -1,110 +0,0 @@
# TrapMap 分层建图修订设计
## 目标
修正 `MovementTest.Trapmaptest.cs` 中“必须先检测到轮胎才能建立地图”的依赖倒置,并将随车固定窗口改为任务级世界坐标栅格。基础地图只依赖合法的车辆位姿、车辆尺寸、工作站世界坐标和地图参数;轮胎检测是可选障碍物输入,不再决定地图是否存在。
本次仍是一次性地图测试,不扩展 `TrajPlanner`,也不引入尚不存在的通用障碍物点云接口。该地图覆盖当前车辆到目标工作站的任务范围,不宣称覆盖整个停车场的永久 SLAM 全局地图。
## 地图范围
目标工作站使用与 `DetourInterface.getCartLocation()` 完全相同的绝对世界坐标系,单位为 mm。第一版默认可调参数为:
- `WorkstationX = 10000 mm`
- `WorkstationY = 0 mm`
- `MapMarginX = 3000 mm`
- `MapMarginY = 3000 mm`
读取车辆几何中心 `(CarX, CarY)` 后,地图边界按以下方式计算:
```text
XMin = min(CarX, WorkstationX) - MapMarginX
XMax = max(CarX, WorkstationX) + MapMarginX
YMin = min(CarY, WorkstationY) - MapMarginY
YMax = max(CarY, WorkstationY) + MapMarginY
```
工作站坐标和 X/Y 冗余必须可配置,不能继续用一个随车半边长参数控制地图。边界采用世界坐标轴对齐矩形,保证车辆点、工作站点及四周冗余都在地图内。
实现必须校验坐标、冗余、计算后的边界、行列数与总栅格数量,拒绝非有限值、负冗余、退化边界和可能造成异常内存分配的超大地图。
## 分层模型
地图按职责分为以下逻辑层:
1. 基础层:按车辆—工作站包围矩形及 X/Y 冗余创建任务级世界坐标栅格,当前所有单元初始化为 `Free`
2. 自车层:按照车辆长度、宽度、朝向标记本车占用区域。
3. 目标层:记录并可视化工作站位置;目标点不是障碍物,不写入占用栅格。
4. 轮胎层:调用现有 `TwoLegDetect`,成功时将两个端点转换至世界坐标并膨胀为占用栅格。
5. 点云障碍层:仅作为未来扩展边界,本次不实现、不虚构输入来源。
当前 `Free` 的准确含义是“此栅格没有被本次已接入的占用来源标记”,并不表示传感器已经确认该区域不存在其他障碍物。代码注释必须明确这一限制,以便未来引入点云及 `Unknown` 状态。
## 数据流与执行顺序
1. 校验地图分辨率、工作站坐标、X/Y 冗余及车辆尺寸。
2. 从 `DetourInterface.getCartLocation()` 读取车辆世界位姿。
3. 根据车辆点和工作站点计算包围矩形,校验最终尺寸和栅格总量。
4. 立即创建任务级世界坐标栅格地图。
5. 标记自车占用层并记录工作站目标层。
6. 尝试读取轮胎检测结果:
- 检测成功:转换并标记两个轮胎端点。
- 未检测到目标:轮胎层保持为空,地图仍然成功。
- 配置缺失、检测异常或坐标非法:轮胎层保持为空,地图仍然成功,但记录降级状态和具体原因。
7. 可视化并发布地图,同时输出边界、工作站、各层状态和统计信息。
地图的创建和发布不再等待轮胎输入。轮胎检测只影响轮胎层内容和传感器状态。
## 状态与错误语义
以下情况属于建图失败,不能发布地图:
- 地图分辨率、范围或安全距离非法。
- 工作站世界坐标或 X/Y 冗余非法。
- 计算后的行列数或总栅格数量超过安全上限。
- 车辆长度或宽度非法。
- 车辆世界位姿不是有限值。
- 基础地图创建、必要的自车标记或结果发布失败。
以下情况属于轮胎层降级,不导致建图失败:
- 未配置轮胎检测雷达。
- `TwoLegDetect` 没有检测到目标。
- 检测调用抛出异常。
- 返回的端点或转换后的世界坐标非法。
输出应区分:
- `Succeeded`:基础地图是否成功生成。
- `InputSource`:本次实际启用的障碍来源。
- 轮胎层状态:已标记、未检测到或检测异常。
- 轮胎层状态说明:未标记或异常的具体原因。
“未检测到目标”与“检测器异常”都产生空轮胎层,但日志语义不能混同:前者表示本次没有轮胎投影,后者表示轮胎信息不可用。
## 数据结构边界
本次可以继续使用最终合并后的 `GridMapData.Cells`,无需提前实现完整的多层栅格容器。分层通过独立的填充步骤、来源状态和统计实现,避免为尚未接入的点云设计过度复杂的数据模型。
未来点云接入时,应新增独立的点云转换/栅格化步骤,再合并到最终占用结果;不得把原始点云读取逻辑塞入轮胎检测函数。
## 测试要求
至少覆盖以下行为:
1. 位姿、工作站和地图参数合法、轮胎检测无目标时,地图成功且轮胎障碍数为零。
2. 轮胎检测配置缺失或调用异常时,地图仍成功,并记录降级原因。
3. 轮胎检测成功时,两个端点被转换并投影为占用栅格。
4. 边界严格由车辆点、工作站点及各轴冗余计算,并同时包含两个点。
5. 工作站可视化但不增加占用栅格数量。
6. 位姿、工作站、冗余、地图参数或总栅格数量非法时,地图失败且不发布半成品。
7. 源码契约确认建图发生在轮胎检测之前,且不存在实车模式模拟障碍回退。
8. 注释明确当前 `Free` 不等于传感器确认无障碍,并标明未来点云/`Unknown` 扩展方向。
## 非目标
- 不实现持续更新或历史地图融合。
- 不读取或声称生成完整停车场 SLAM 静态地图;本次“全局”仅指车辆和工作站共享的世界坐标任务范围。
- 不接入未经现有代码验证的点云 API。
- 不修改或展开 `TrajPlanner`
- 不发送车辆运动或停止指令。
@@ -1,52 +0,0 @@
# TrapMap Managed PNG Export Design
## Goal
Replace `System.Drawing.Common` in TrapMap image export so Clumsy can save the full grid PNG without depending on platform-specific drawing assemblies.
## Chosen approach
Use a small internal RGBA rasterizer for grid primitives and `StbImageWriteSharp` 1.16.7 for PNG encoding. Keep the public request/result API and the existing TrapMap call site unchanged.
Alternatives rejected:
- Copying `System.Drawing.Common.dll` beside the build output is unreliable because Clumsy performs its own dependency attachment and can load the incompatible `netstandard2.0` facade first.
- `SkiaSharp` adds native Windows assets and more deployment points.
- `ImageSharp` has a larger dependency/licensing surface and its current release does not target this project's `netstandard2.0` runtime.
`StbImageWriteSharp` is selected because its package contains one managed `netstandard2.0` implementation and declares no dependencies. It has no native assets or alternate platform facades for Clumsy to select incorrectly.
## Rasterization
- Allocate an in-memory 32-bit RGBA pixel buffer after the existing 4000-pixel edge validation.
- Preserve the current canvas dimensions, 4 pixels per cell, colors, Y inversion, vehicle/workstation geometry, header height, and 50 MiB final-file limit.
- Draw filled rectangles, 1/2-pixel lines, circles, crosses, and vehicle polygons with deterministic integer raster operations.
- Render header and workstation text with a small embedded ASCII bitmap font. Non-ASCII metadata is sanitized to a printable fallback for the PNG header only; original messages remain unchanged in DLog/Console.
- Keep header lines non-overlapping and include bounds, resolution, rows/columns, occupied count/rate, obstacle count, tire status/message, and input source.
## PNG encoding
- Pass the RGBA pixel buffer to `StbImageWriteSharp.ImageWriter.WritePng`.
- Insert a standard `pHYs` chunk immediately after `IHDR`, with X/Y both 11,811 pixels per metre and unit `1` (300 DPI).
- Calculate the inserted chunk's CRC-32 and write its integers in big-endian order.
- Validate the completed PNG before publication; encoding or metadata failures remain contained export failures.
- Preserve the current collision-safe temporary-file reservation, actual encoded-size check, atomic move, and best-effort cleanup behavior.
## Project and deployment changes
- Remove the `System.Drawing.Common` package reference and `DeployFrameworkDrawingRuntime` target from `ClumsyPilot.csproj`.
- Add `StbImageWriteSharp` version 1.16.7. No other new package is allowed.
- The final build output must not require or deploy `System.Drawing.Common.dll` for TrapMap; it may deploy the single managed `StbImageWriteSharp.dll`.
- Existing Clumsy references are not modified.
## Verification
- TDD first proves the current exporter/package still depends on `System.Drawing.Common`.
- Reflection/source contracts assert the old package, build target, `using System.Drawing`, and drawing types are absent, and the exact Stb package is present.
- Decode the generated PNG in the test with a test-only decoder or framework reader and verify dimensions, 300 DPI metadata, representative colors, Y inversion, header separation, unique concurrent filenames, and no temporary files.
- Run source, build, grid, lifecycle, and image tests; require zero build errors and no new warnings.
## Non-goals
- No changes to grid construction, obstacle/tire inputs, vehicle motion, Painter visualization, export switches, output directory, or file-size/pixel limits.
- No screenshot capture and no new general-purpose graphics framework.
@@ -1,544 +0,0 @@
# Hybrid A* 粗路径规划设计
## 目标与范围
在现有栅格地图能力之上,实现四舵轮 AMR 的 Hybrid A* 空间粗路径:支持前进、倒车、换向、静态/投影障碍绕行、车体碰撞检查、可配置的终点位置与航向容差,以及稠密路径与方向分段输出。
本阶段**不包含**曲线平滑、B 样条、Bezier、局部 QP、SQP、Reeds-Shepp 精确终点连接、速度/加速度/时间参数化或底盘舵角控制。第一版采用汽车式恒曲率模型,不支持蟹行、纯横移和原地旋转。它们由后续独立模块处理。
## 已确认的约束
- 地图与外部人工障碍输入继续使用现有世界坐标单位:mm。
- Hybrid A* 内部统一使用 m、rad、1/m;单位转换只能经过地图适配与 `Utils`,不能散落在搜索代码中。
- 人工障碍物第一版支持以几何中心放置的轴对齐矩形和圆形。
- `TwoLegProjectionInput` 是可选障碍物输入,不是规划地图是否可用的唯一条件;上层采集到的 `TwoLegDetect` 结果经 DTO 投影后与人工障碍物合并。
- 环境占据地图只保存外部障碍物,绝不写入 AMR 自身足迹。AMR 尺寸、当前位姿和安全余量只用于粗路径的车体碰撞检测。
- 障碍物不做安全膨胀;安全余量仅通过碰撞检查时的扩大车辆矩形应用,防止双重膨胀。
- 第一版的终点条件为可配置的位置容差和车头航向容差。默认建议为 0.15 m 与 5°,而非精确连接到目标位姿。
- `PrimitiveLength=0.50 m` 是单条原语的最大长度,不是终点只能出现的离散间隔。每条原语必须在内部积分点检查目标条件;该原语内首次满足条件时立即截断并建立终点候选节点。终点候选仍须进入 Open List,只有当它作为当前最优有效节点出队时,整个搜索才返回成功。
- 碰撞检测必须保守:不得因航向离散、车辆中心的亚栅格偏移、距离场误差或原语离散采样而发布可能碰撞的路径。
- `ObstacleDistanceField` 只能作为保守快速放行和代价估计,任何可能高估真实净空的近似都不得用于跳过精确车体碰撞检查。
- 测试是交付的一部分:纯逻辑契约测试与参照 `Map``MovementTest` 集成/可视化入口都必须提供。
- 每个文件只承担一个明确职责;对外调用通过模块门面类完成,不让调用者拼装搜索、地图和碰撞的内部对象。
- 推荐调用方只使用 `CoarsePathPlanningService` 一次完成建图、粗路径搜索和可选调试发布;`PlanningMapFactory``HybridAStarPlanner` 是可独立测试、复用的下层模块门面。请求、结果、枚举、障碍物 DTO 和值对象仍然是可公开构造的数据契约。
## 目录和命名空间
```text
ClumsyPilot/ParkrobTrajplanner/
├── Initial_plan/ ------ 已有路线与方案文档,不放运行时代码
├── Utils/ ------ 命名空间 MultiWheelC.TrajectoryPlanning.Utils
│ ├── AngleMath.cs ------ 角度归一化、最短角差和航向离散索引
│ ├── UnitConverter.cs ------ mm/m、deg/rad 和半径/曲率单位转换
│ ├── CoordinateTransform.cs ------ 车体坐标系与世界坐标系二维刚体变换
│ ├── NumericGuard.cs ------ 有限值、正值和参数范围校验
│ └── GridIndex.cs ------ 不可变行列索引值对象
├── Map/ ------ 命名空间 MultiWheelC.TrajectoryPlanning.Mapping
│ ├── Core/
│ │ ├── EnvironmentGridMap.cs ------ 只保存外部障碍物的 mm 单位占据栅格
│ │ ├── MapBoundsMm.cs ------ 有限、非退化的 mm 地图边界值对象
│ │ ├── MapBuildRequest.cs ------ 环境地图边界、分辨率和障碍物图层输入
│ │ ├── EnvironmentMapBuildResult.cs ------ 环境地图构建状态、来源摘要和失败原因
│ │ └── EnvironmentMapBuilder.cs ------ 校验并合并人工与投影障碍物图层
│ ├── Obstacles/
│ │ ├── IMapObstacle.cs ------ 人工和投影障碍物的公共几何契约
│ │ ├── AxisAlignedRectangleObstacle.cs ------ 世界轴对齐矩形障碍物 DTO
│ │ ├── CircleObstacle.cs ------ 世界坐标圆形障碍物 DTO
│ │ └── MapObstacleRasterizer.cs ------ 通过形状与格子相交测试写入占据栅格
│ ├── Sources/
│ │ ├── IMapObstacleSource.cs ------ 纯快照障碍来源统一投影接口
│ │ ├── ObstacleSourceStatus.cs ------ Applied、Empty、Unavailable、Invalid 来源状态
│ │ ├── ObstacleProjectionResult.cs ------ 来源版本、状态、诊断和世界障碍物集合
│ │ ├── ManualObstacleSource.cs ------ 把人工圆和矩形作为世界障碍物输出
│ │ ├── TwoLegProjectionInput.cs ------ 已验证两腿端点、检测状态和检测时车辆位姿 DTO
│ │ ├── TwoLegObstacleSource.cs ------ 把 TwoLeg 快照接入统一障碍来源接口
│ │ └── TwoLegObstacleProjector.cs ------ 把车体系两腿端点投影为世界坐标圆障碍
│ ├── Planning/
│ │ ├── PlanningGridMap.cs ------ 只读 m 单位占据图、距离场和规划可用状态
│ │ ├── EuclideanDistanceTransform.cs ------ 线性时间生成栅格中心精确欧氏距离
│ │ ├── ObstacleDistanceField.cs ------ 生成不高估障碍净空的保守欧氏距离下界
│ │ ├── PlanningMapCache.cs ------ 线程安全的输入指纹与占据哈希快照缓存
│ │ └── PlanningMapAdapter.cs ------ 深拷贝占据数据并完成 mm 到 m 的边界适配
│ ├── PlanningMapRequest.cs ------ 地图门面的统一输入契约
│ ├── PlanningMapBuildResult.cs ------ 地图门面的统一输出契约
│ ├── PlanningMapFactory.cs ------ Map 模块唯一公共行为入口
│ └── Test/
│ ├── MovementTest.MapTest.cs ------ Clumsy UI 地图构建与可视化测试入口
│ └── Visualization/
│ ├── PlanningMapImageExportRequest.cs ------ 规划快照、叠加层和输出选项 DTO
│ ├── PlanningMapImageExportResult.cs ------ PNG 导出状态、路径、大小和诊断
│ ├── PlanningMapImageExporter.cs ------ 校验请求、编排渲染并原子发布 PNG
│ ├── PlanningMapImageRenderer.cs ------ 把地图、起终点、车体和路径绘制到 RGBA
│ └── ValidatedPngWriter.cs ------ Stb PNG 编码、结构和 CRC 完整性校验
├── CoarsePath/ ------ 命名空间 MultiWheelC.TrajectoryPlanning.CoarsePath
│ ├── Contracts/
│ │ ├── Pose2D.cs ------ m/rad 单位的不可变二维位姿
│ │ ├── TravelDirection.cs ------ Forward 与 Reverse 运动方向枚举
│ │ ├── GoalDirectionConstraint.cs ------ Any、Forward、Reverse 目标进入方向约束
│ │ ├── VehicleParameters.cs ------ 车体尺寸、安全余量和最大曲率参数
│ │ ├── PlanningRequest.cs ------ 地图、起终点、起始曲率和方向约束
│ │ ├── HybridAStarConfiguration.cs ------ 原语、离散、代价、限额和容差配置
│ │ ├── PlanningResult.cs ------ 状态、诊断、稠密路径和方向分段
│ │ ├── PlanningStatus.cs ------ 输入、碰撞、搜索和验证结果枚举
│ │ ├── PlanningDiagnostics.cs ------ 节点、堆、耗时、路径和终止统计
│ │ ├── CoarsePathPoint.cs ------ 位姿、弧长、方向、曲率和保守净空
│ │ ├── CoarsePathPointSource.cs ------ 起点、普通原语和终点截断来源枚举
│ │ └── PathSegment.cs ------ 前进/倒车分段及其包含式索引范围
│ ├── Vehicle/
│ │ ├── VehicleKinematics.cs ------ 解析并校验车辆保守最大曲率
│ │ ├── VehicleFootprint.cs ------ 计算扩大车体矩形、包围盒和外接圆
│ │ ├── OrientedRectangleCellIntersection.cs ------ 精确判断旋转车体矩形与栅格矩形相交
│ │ └── FootprintCollisionChecker.cs ------ 边界、距离场、精确和扫掠碰撞检查
│ ├── Search/
│ │ ├── MotionPrimitive.cs ------ 单条恒曲率原语及其实际截断长度描述
│ │ ├── MotionPrimitiveGenerator.cs ------ 解析积分前进/倒车原语并保留内部点
│ │ ├── BinaryMinHeap.cs ------ netstandard2.0 兼容且确定性排序的 Open List
│ │ ├── SearchCostCalculator.cs ------ 统一计算长度、倒车、换向、曲率和净空代价
│ │ ├── HybridAStarNode.cs ------ 连续位姿、代价、父索引和原语描述
│ │ ├── HybridAStarNodeKey.cs ------ 位置格、航向格、方向和曲率离散键
│ │ ├── GoalToleranceChecker.cs ------ 位置、航向和目标进入方向容差判断
│ │ ├── GridDijkstraHeuristic.cs ------ 八邻域二维绕障距离启发
│ │ └── HybridAStarSearch.cs ------ 节点扩展、重开、限额和终点候选管理
│ ├── Output/
│ │ ├── PathBacktracker.cs ------ 按父索引确定性重建原语内部点
│ │ ├── CoarsePathAssembler.cs ------ 生成弧长、换向点和包含式方向分段
│ │ └── CoarsePathValidator.cs ------ 复核数值、碰撞、曲率、终点和分段
│ ├── HybridAStarPlanner.cs ------ 只消费 PlanningGridMap 的纯搜索下层门面
│ ├── Facade/
│ │ ├── CoarsePathPlanningJob.cs ------ 一次调用所需地图、起终点、车辆和调试选项
│ │ ├── CoarsePathPlanningJobResult.cs ------ 同时返回地图构建结果和粗路径结果
│ │ ├── PlanningDebugOptions.cs ------ 地图、路径和碰撞调试发布开关
│ │ ├── IPlanningDebugSink.cs ------ 不影响规划状态的调试结果消费接口
│ │ └── CoarsePathPlanningService.cs ------ 建图、缓存、搜索和调试编排的一次调用入口
│ └── Test/
│ ├── CoarsePathScenarioFactory.cs ------ 生成固定、可复现的地图和规划场景
│ └── MovementTest.CoarsePathTest.cs ------ 后台运行规划并在 Clumsy UI 绘制结果
│ └── README.md ------ 粗规划模块边界、公共调用入口和文档链接
ClumsyPilot/tests/
├── verify_planning_utils.ps1 ------ 单位、角度、坐标和数值守卫测试
├── verify_planning_map_factory.ps1 ------ 地图输入、图层事务、快照和版本测试
├── verify_planning_map_adapter.ps1 ------ 地图栅格化、图层、适配和距离场测试
├── verify_planning_map_image.ps1 ------ 只读快照 PNG 渲染、限制和原子发布测试
├── verify_coarse_path_collision.ps1 ------ 亚栅格、擦边和扫掠碰撞测试
├── verify_coarse_path_search.ps1 ------ 原语截断、搜索、方向、限额和重开测试
├── verify_coarse_path_integration.ps1 ------ Map 到最终路径验证的端到端测试
└── benchmark_coarse_path.ps1 ------ 参考与压力场景性能资源验收
```
`Occupancygird_Map/Map_test` 是当前原型位置。实施时会把其中仍然需要的地图能力按以上职责迁移到 `Map`,避免继续向两个现有大文件叠加功能;图片导出可以保留为独立地图测试辅助,不成为规划器依赖。
## Utils
`Utils` 只放无状态、确定性的通用计算,不依赖 Clumsy UI、地图、传感器或搜索器。
| 文件 | 唯一职责 |
| --- | --- |
| `AngleMath` | 角度归一化、最小有符号角差、度/弧度边界规则、航向离散索引。 |
| `UnitConverter` | mm↔m、deg↔rad、曲率/半径转换;不包含业务配置。 |
| `CoordinateTransform` | 车体系与世界系的二维刚体变换。 |
| `NumericGuard` | 有限值、正值和范围校验的可复用方法。 |
| `GridIndex` | 行列索引值对象与比较,不存储地图状态。 |
## Map 模块
### 对外调用门面
`HybridAStarPlanner` 不直接实例化任何地图对象;`CoarsePathPlanningService` 只持有 `PlanningMapFactory`,不接触 `EnvironmentMapBuilder``MapObstacleRasterizer`、来源投影器或 `PlanningMapAdapter``PlanningMapFactory``Map` 模块唯一的公共创建入口:
```csharp
public sealed class PlanningMapFactory
{
public PlanningMapBuildResult Create(PlanningMapRequest request);
}
```
下层模块独立调用时只依赖其稳定输出:
```csharp
var mapResult = new PlanningMapFactory().Create(mapRequest);
if (!mapResult.Succeeded)
return PlanningResult.FromMapFailure(mapResult);
var result = new HybridAStarPlanner().Plan(new PlanningRequest
{
Map = mapResult.Map,
Start = startPose,
Goal = goalPose,
Vehicle = vehicle,
Configuration = configuration,
});
```
`PlanningMapRequest` 集中地图边界/分辨率、`IReadOnlyList<IMapObstacleSource>` 和空旷地图声明;`PlanningMapBuildResult` 返回 `Succeeded`、失败原因、每个障碍物来源的摘要、缓存命中类型,以及成功时不可变的 `PlanningGridMap`。因此 A* 的 `PlanningRequest.Map` 始终是已完成校验、投影、栅格化、mm→m 适配和距离场生成的输入,规划器不需要了解地图构造细节。
### 统一障碍物来源与投影
所有人工、TwoLeg 和后续障碍物输入统一实现:
```csharp
public interface IMapObstacleSource
{
string SourceId { get; }
long SourceVersion { get; }
bool IsRequired { get; }
ObstacleProjectionResult ProjectToWorld();
}
```
`ProjectToWorld` 只能消费构造来源对象时已经取得的不可变快照,不得在内部读取传感器、定位、UI 或系统时间。它统一返回世界坐标 mm 几何体:
```text
ObstacleProjectionResult:
SourceId
SourceVersion
Status Applied/Empty/Unavailable/Invalid
Message
IReadOnlyList<IMapObstacle> Obstacles
```
必需来源返回 `Unavailable/Invalid` 时地图构建失败;可选来源返回上述状态时记录诊断并继续处理其他来源。多个来源产生重叠障碍物是合法的,占据写入具有幂等语义。
`ManualObstacleSource` 直接输出已经位于世界坐标系的圆和轴对齐矩形。`TwoLegProjectionInput` 是纯数据 DTO,明确包含检测状态、车体坐标系中的两个端点、端点半径、检测时刻的 AMR 世界位姿,以及 mm/deg 单位声明。`MovementTest` 或上层采集适配器负责调用现有 `TwoLegDetect`,随后把结果封装为快照;`TwoLegObstacleSource` 委托 `TwoLegObstacleProjector` 执行确定性车体到世界变换并输出零个或两个 `CircleObstacle`
新增障碍物来源只需投影为现有 `IMapObstacle` 几何体;如果需要新增多边形等几何类型,必须同时为 `MapObstacleRasterizer` 添加保守的形状-栅格相交实现和成功/边界/失败测试。任何来源都不得应用车辆安全余量。
### 环境地图与图层
`EnvironmentGridMap` 保存 mm 单位的地图边界、分辨率和仅含外部障碍物的占据单元。它不接受 `MarkVehicleFootprint` 一类接口。
`EnvironmentMapBuilder` 是由 `PlanningMapFactory` 使用的内部环境图构造器:
```csharp
public sealed class EnvironmentMapBuilder
{
public EnvironmentMapBuildResult Build(MapBuildRequest request);
}
```
它依次校验地图参数、按 `SourceId` 确定性排序来源、收集 `ObstacleProjectionResult`、合并所有成功投影的 `IMapObstacle` 并调用唯一栅格化器。任何可选来源失败都不会删除其他来源已成功构建的占据内容。
人工障碍物实现 `IMapObstacle``AxisAlignedRectangleObstacle` 使用 `(CenterXmm, CenterYmm, WidthMm, HeightMm)``CircleObstacle` 使用 `(CenterXmm, CenterYmm, RadiusMm)`。两者的尺寸必须为有限正数,矩形轴与世界 X/Y 轴对齐。`MapObstacleRasterizer` 是唯一直接写入环境栅格的类。
`MapObstacleRasterizer` 是唯一直接写入 `EnvironmentGridMap` 的类型;各来源和投影器都不能取得地图写入接口。`TwoLegObstacleProjector` 不负责地图边界、栅格化、缓存或规划可用性判断。
### 规划适配
`PlanningMapAdapter` 是地图与规划器之间唯一的单位/数据边界:
```csharp
public sealed class PlanningMapAdapter
{
public PlanningGridMap Adapt(EnvironmentGridMap map);
}
```
适配时验证边界和分辨率、深拷贝占据数据、将长度从 mm 转为 m、将边界外固定解释为占据,并生成 `ObstacleDistanceField``PlanningGridMap` 是不可变的规划输入,包含 m 单位边界、分辨率、行列、占据数据、距离场、来源版本摘要、快照标识与 `PlanningReady/PlanningBlockReason`
距离场使用精确的二维欧氏距离变换计算栅格中心到最近占据栅格中心的距离,再减去一个完整栅格对角线 `sqrt(2) * ResolutionMeters` 并截断到零,得到当前自由栅格内任意点到任意占据栅格矩形的保守下界。查询不得进行会抬高结果的插值;查询点使用其所在栅格的保守值。显式空旷地图的障碍物距离可以是正无穷,但车体边界检查仍必须先执行,地图外始终按占据处理。
距离场的用途受以下规则约束:
- 当保守距离严格大于扩大车体外接圆半径时,碰撞检查器可以快速放行。
- 当保守距离小于或等于外接圆半径时,必须执行精确的扩大车体矩形与占据栅格矩形相交检查。
- `BodyClearance` 发布 `max(0, ConservativeCenterClearance - ExpandedFootprintCircumscribedRadius)`,作为车体净空的保守下界,不得声称为精确几何净空。
- 多源距离场实现、空图语义、地图边界和最大栅格数都必须有自动化测试。
`PlanningReady` 的规则:至少一个成功来源提供有效障碍语义即可使地图用于测试/规划;所有来源均为空时,只有已明确配置 `AllowExplicitEmptyMap=true` 才可规划。必需来源失败或未明确空旷语义时,以 `PlanningBlockReason` 阻止规划,而不是暗中把未观测区域当作空闲。
### 现有地图优化与规划适配
本阶段不在旧 `GridMapData` 外再包一层长期兼容适配器,而是把其中经过测试的几何规则迁移为纯逻辑、静态快照式地图管线。迁移目标是消除 UI、传感器、车辆自身足迹和规划查询之间的职责耦合,同时降低 Hybrid A* 高频占据查询与距离查询的开销。
#### 现有职责拆分
| 现有类型/函数 | 处理方式 | 新职责位置 |
| --- | --- | --- |
| `TrapMapBounds.TryCreate` | 保留有限值、退化边界、分辨率和最大栅格数校验;移除“车辆到工作站”业务假设 | `MapBoundsMm``MapBuildRequest` |
| `GridMapData.WorldToGrid/GridToWorld` | 保留 X→列、Y→行和 XMax/YMax 排他规则;统一处理最后一个非完整栅格 | `EnvironmentGridMap``PlanningGridMap` |
| `GridMapData.Cells byte[,]` | 改为私有行优先 `byte[]`,索引固定为 `row * Cols + col`;不暴露可写数组 | 两类 GridMap 的内部存储 |
| `GridMapData.MarkObstacle/MarkObstacles` | 移除默认安全距离;保留候选包围盒裁剪和形状-格矩形相交 | `MapObstacleRasterizer` |
| `GridMapData.MarkVehicleFootprint` | 从地图模块删除,不提供兼容开关 | `FootprintCollisionChecker` 查询时处理 |
| `GridMapData.Copy` | 不再暴露可变副本;适配时只进行一次占据缓冲区深拷贝 | `PlanningMapAdapter` |
| `TrapMapLayerComposer.Compose` | 泛化为“可选来源失败不破坏其他成功来源”的事务语义 | `IMapObstacleSource``EnvironmentMapBuilder` |
| `TrapMapBuilder.Get` | 拆除 `MovementDefinition`、定位读取、TwoLeg 调用、Toast、Painter 和协程依赖 | 上层采集适配器 + `CoarsePathPlanningService` |
| `TrapMapImageExporter.cs` | 保留经过验证的纯托管 RGBA/PNG 能力,拆分请求、结果、渲染、发布和 PNG 校验职责;输入改为只读规划快照 | `Map/Test/Visualization` |
#### 统一地图输入与静态快照
`PlanningMapRequest` 必须显式包含:
```text
MapBoundsMm Bounds
float ResolutionMm
IReadOnlyList<IMapObstacleSource> ObstacleSources
bool AllowExplicitEmptyMap
```
`Bounds` 使用 `[XMin, XMax) × [YMin, YMax)``ResolutionMm` 必须是 20~200 mm 的有限正数。来源 `SourceId` 必须非空且在一次请求内唯一,`SourceVersion` 必须非负,并在对应来源快照内容变化时递增。
`PlanningMapFactory.Create` 返回与来源输入隔离的不可变静态快照,不实现增量栅格更新或距离场局部修补。`PlanningGridMap` 保存各来源版本摘要、`InputFingerprint``OccupancyHash` 和通过 `Interlocked.Increment` 生成的进程内单调 `SnapshotId`。该计数器只标识快照,不保存地图内容。规划开始后只读取同一快照;上层即使收到新障碍物,也不得修改正在使用的占据缓冲区。
#### 两级指纹与快照复用
`PlanningMapCache``PlanningMapFactory` 的线程安全、容量为 4 的最近使用缓存;长期存在的 `CoarsePathPlanningService` 持有同一个工厂实例,因此多次规划可以复用快照。
每次创建按以下顺序判断:
1. 调用纯快照来源的 `ProjectToWorld`,按 `SourceId` 排序,并对边界、分辨率、空图策略、来源状态/版本和规范化世界几何体计算 `InputFingerprint`
2. 若缓存中存在相同 `InputFingerprint`,直接返回同一不可变 `PlanningGridMap`;不重新栅格化或生成距离场。
3. 输入指纹不同时重新栅格化,并对最终连续占据 `byte[]` 计算 `OccupancyHash`
4. 若地图几何参数和 `OccupancyHash` 与缓存快照相同,复用占据缓冲区与距离场,只生成包含新来源摘要和新 `SnapshotId` 的轻量快照。
5. `OccupancyHash` 不同时才重新执行距离场变换并缓存完整新快照。
浮点几何按其 IEEE 位模式和固定字段顺序计算确定性指纹,不通过简单的“坐标除以分辨率取整”判断变化,避免圆或矩形在格边附近发生漏失效。缓存项同时保留规范化输入描述;`InputFingerprint` 命中后仍执行结构相等比较。`OccupancyHash` 命中后仍比较地图几何参数和连续占据缓冲区长度/内容,不能只依赖哈希值判等。
起点、终点、车辆尺寸、安全余量、Hybrid A* 参数和可视化开关不属于地图指纹。TwoLeg 的检测状态从有效变为无检测/不可用/过期时,其来源结果必须改变;上层采集适配器负责根据检测有效期构造正确的 `TwoLegProjectionInput`,地图来源接口本身不读取系统时间。
#### 存储、坐标和查询优化
- 占据数据使用私有连续 `byte[]`,避免公开 `byte[,]` 带来的可变性和多维数组索引开销。
- `IsOccupied(row,col)``IsOccupiedWorld(x,y)` 和保守距离查询保持无分配、常数复杂度;地图外直接返回占据或零净空。
- 世界坐标到格索引使用 `floor((value - min) / resolution)``XMax``YMax` 排他。最后一个格子的几何上界必须裁剪到实际地图上界。
- 构造阶段使用 `checked` 计算 `Rows * Cols`,继续采用 4,000,000 格绝对上限;任何溢出或超限在分配前返回失败结果。
- 圆形与矩形栅格化只遍历其裁剪后的格索引包围盒,不扫描全图。与地图完全不相交的合法障碍物被忽略并记录在来源摘要中,而不是使地图构建失败。
- `EnvironmentGridMap` 只有程序集内部的占据写入入口;`PlanningGridMap` 不提供任何写入入口,也不返回内部缓冲区引用。
#### 距离场优化
`EuclideanDistanceTransform` 使用两次一维平方距离变换完成精确二维栅格中心距离计算,时间复杂度为 `O(Rows × Cols)`,不得为每个自由格遍历全部障碍格。中间数组按行列最大长度复用,最终距离使用连续 `double[]` 保存。
`ObstacleDistanceField` 在精确中心距离上执行前述保守修正并封装查询,不允许调用方直接取得未经修正的中心距离用于碰撞放行。显式空图不运行无意义的变换,直接构造正无穷障碍距离场;地图边界仍由规划地图和车体碰撞检查独立约束。
#### 地图迁移顺序
1. 先建立 `MapBoundsMm`、新占据存储和纯栅格化测试,不修改旧 UI 入口。
2. 建立 `PlanningMapFactory`、静态快照、距离场和规划查询测试。
3. 将人工障碍、TwoLeg DTO 与现有 Ghost/固定场景迁移为统一 `IMapObstacleSource`
4. 建立两级指纹缓存与 `CoarsePathPlanningService` 一次调用入口。
5. 将旧 `TrapMapImageExporter.cs` 拆分为 `Map/Test/Visualization` 下的五个文件,并将 PNG 导出与 `MovementTest.MapTest` 改为只消费新快照。
6. 新旧地图回归结果一致后,退役旧地图构建、车体写入和图层合成入口,并更新或删除对应旧反射测试。
迁移期间不得让 Hybrid A* 同时支持新旧两种地图类型;规划器从第一天起只接受 `PlanningGridMap`
## CoarsePath 模块
### 一次调用编排门面
常规调用方只调用:
```csharp
public sealed class CoarsePathPlanningService
{
public CoarsePathPlanningJobResult Plan(
CoarsePathPlanningJob job,
CancellationToken cancellationToken = default);
}
```
`CoarsePathPlanningJob` 集中以下输入:
```text
PlanningMapRequest MapRequest
Pose2D Start
Pose2D Goal
VehicleParameters Vehicle
HybridAStarConfiguration Configuration
double StartVehicleCurvature
TravelDirection? StartDirection
GoalDirectionConstraint GoalDirection
PlanningDebugOptions Debug
```
推荐调用形式:
```csharp
var result = planningService.Plan(new CoarsePathPlanningJob
{
MapRequest = new PlanningMapRequest
{
Bounds = bounds,
ResolutionMm = 50f,
ObstacleSources = new IMapObstacleSource[]
{
new ManualObstacleSource(manualObstacles),
new TwoLegObstacleSource(twoLegSnapshot),
},
AllowExplicitEmptyMap = true,
},
Start = startPose,
Goal = goalPose,
Vehicle = vehicle,
Configuration = configuration,
Debug = new PlanningDebugOptions
{
VisualizeMap = true,
VisualizePath = true,
},
}, cancellationToken);
```
一次调用内部固定执行:
```text
PlanningMapFactory.Create(MapRequest)
→ 地图失败则生成 FromMapFailure 结果
→ HybridAStarPlanner.Plan(PlanningRequest, cancellationToken)
→ IPlanningDebugSink 按 Debug 开关发布地图、路径和诊断
```
`CoarsePathPlanningJobResult` 同时保留 `PlanningMapBuildResult MapResult``PlanningResult PlanningResult`,使调用方能够取得实际使用的 `PlanningGridMap` 快照、缓存命中情况和粗路径状态。地图失败时不启动搜索;调试发布失败只写入调试诊断,不改变地图或路径规划状态。
`PlanningDebugOptions` 只包含 `VisualizeMap``VisualizePath``VisualizeCollisionChecks` 等旁路开关。`IPlanningDebugSink` 由 Clumsy `MovementTest` 适配实现;核心服务默认使用空实现,因此无 UI 环境与自动化测试不加载 Painter。
### 纯搜索下层门面
需要复用已有地图快照或单独测试搜索时调用 `HybridAStarPlanner`
```csharp
public sealed class HybridAStarPlanner
{
public PlanningResult Plan(
PlanningRequest request,
CancellationToken cancellationToken = default);
}
```
`PlanningRequest` 组合以下不可变输入:
- `PlanningGridMap Map`
- `Pose2D Start``Pose2D Goal`
- `VehicleParameters Vehicle`
- `HybridAStarConfiguration Configuration`
- `double StartVehicleCurvature`,未提供时显式使用零曲率
- `TravelDirection? StartDirection``null` 表示起步方向不受约束
- `GoalDirectionConstraint GoalDirection`,取值为 `Any``Forward``Reverse`
它不接受地图构建器、UI 对象或传感器对象。起点曲率必须在车辆最大曲率内,并离散到最近的合法曲率等级;该索引作为起始搜索状态的一部分。
`VehicleParameters` 明确使用车辆几何中心为 `Pose2D` 参考点,并包含 `LengthMeters``WidthMeters``SafetyMarginMeters`、可选 `MaximumCurvaturePerMeter` 与可选 `MinimumTurningRadiusMeters`。最大曲率和最小转弯半径同时存在时使用更保守的限制;两者都未提供时请求无效。
`HybridAStarConfiguration` 集中所有可调参数,包括最大节点数、超时、航向分辨率、原语最大长度、积分步长、碰撞采样步长、曲率等级、代价权重、是否允许倒车、位置容差和航向容差。默认值为:
| 参数 | 默认值 |
| --- | --- |
| `PrimitiveLengthMeters` | 0.50 m,表示最大长度 |
| `IntegrationStepMeters` | 0.05 m |
| `MaximumCollisionCheckStepMeters` | 0.025 m,且运行时不得大于 `Map.ResolutionMeters / 2` |
| `HeadingResolutionRadians` | 5° |
| `CurvatureLevelCount` | 5 |
| `GoalPositionToleranceMeters` | 0.15 m |
| `GoalHeadingToleranceRadians` | 5° |
| `MaximumExpandedNodes` | 200,000 |
| `SearchTimeout` | 5 s |
| `HeuristicWeight` | 1.0 |
| `ReverseCostMultiplier` | 1.5 |
| `GearSwitchPenaltyMeters` | 1.0 |
| `CurvatureMagnitudeWeight` | 0.10 |
| `CurvatureChangePenaltyMetersPerLevel` | 0.05 |
| `ClearanceCostWeight` | 0.20 |
| `ClearanceCostDistanceMeters` | 0.50 m |
搜索代价全部以“等效米”为单位:
```text
primitiveCost =
lengthMeters
× directionMultiplier
× (1
+ CurvatureMagnitudeWeight × abs(curvature / maximumCurvature)
+ ClearanceCostWeight × max(0, 1 - clearance / ClearanceCostDistanceMeters))
+ gearSwitchPenalty
+ CurvatureChangePenaltyMetersPerLevel × abs(curvatureLevelDelta)
```
其中前进的 `directionMultiplier=1`,倒车使用 `ReverseCostMultiplier`;没有换向时 `gearSwitchPenalty=0`。所有权重必须为有限非负值。默认 `HeuristicWeight=1.0`;若调用方调大该值,只承诺更快地寻找可行解,不承诺离散图上的最低代价。
`PlanningResult` 始终返回明确 `PlanningStatus`、诊断信息、零或一条 `IReadOnlyList<CoarsePathPoint>``IReadOnlyList<PathSegment>`。成功结果中的点契约固定为:
```text
CoarsePathPoint:
X、Y m
Heading、UnwrappedHeading rad
ArcLength m,非负且不递减
Direction Forward/Reverse
VehicleCurvature 1/m
BodyClearance m,保守下界
IsGearSwitchPoint bool
Source Start/MotionPrimitive/GoalTruncation
```
`PathSegment` 固定包含 `SegmentIndex``Direction``StartIndex``EndIndex``StartsAtGearSwitch``EndsAtGearSwitch``StartIndex``EndIndex` 都是包含端点的索引,所有分段按索引顺序完整覆盖整条路径。换向时保留两个坐标和航向相同、弧长相同但方向不同的相邻点:前一点结束旧分段,后一点开始新分段并设置 `IsGearSwitchPoint=true`。除这种换向对外,装配器删除相邻重复点。粗路径不包含时间、速度、加速度、舵轮角或轮速。
### 车辆、碰撞和搜索
- `VehicleKinematics` 根据车辆参数提供最大曲率;直接最大曲率和最小转弯半径同时存在时采用更保守的值。
- `VehicleFootprint` 以连续位姿计算扩大车辆矩形的四角、轴对齐包围盒和外接圆。安全余量只在这里同时加到长度和宽度两侧,不写入地图。
- `OrientedRectangleCellIntersection` 使用分离轴定理判断连续位姿下的扩大车辆矩形是否与占据栅格矩形相交,不使用仅按离散航向和整数格偏移的模板,因此车辆中心的亚栅格偏移不会漏检。
- `FootprintCollisionChecker` 依次执行扩大车体边界检查、保守距离场快速放行和包围盒内占据栅格的精确相交检查;它不执行搜索,也不改变地图。
- `MotionPrimitiveGenerator` 仅生成恒曲率前进/倒车原语,以不大于 0.05 m 的步长保留输出积分点,并使用直线/圆弧解析公式更新位姿,不使用累计误差更大的显式欧拉积分。
- 相邻碰撞检查位姿的中心位移不得超过 `min(MaximumCollisionCheckStepMeters, Map.ResolutionMeters / 2)`。同时用 `0.5 × (中心位移 + 外接圆半径 × 航向变化绝对值)` 作为扫掠附加余量检查相邻区间端点,保守覆盖两个采样位姿之间的车体运动;该附加余量只用于区间碰撞验证,不写入输出车体尺寸。
- `GoalToleranceChecker` 只判断位置、航向和最后一段方向约束;位置和航向阈值来自 `HybridAStarConfiguration`
- 每条原语按积分点顺序执行数值合法性、碰撞和目标检查。如果某个内部积分点满足目标条件,当前原语在该点截断并生成终点候选;候选加入 Open List,只有当它作为最佳有效节点出队时才成功终止搜索。
- `GridDijkstraHeuristic` 从目标在占据图上生成八邻域二维绕障距离启发,禁止穿过两个对角相邻障碍物的夹角;它不处理车辆运动学。若目标在二维图上不可达,规划返回 `NoFeasiblePath`
- `SearchCostCalculator` 只实现本节定义的等效米代价公式,集中处理倒车、换向、曲率、曲率变化与保守净空代价,不管理节点或 Open List。
- `BinaryMinHeap` 是兼容 `netstandard2.0` 的内部最小堆,不依赖较新运行时的 `PriorityQueue`。排序依次使用 `F``H`、较大的 `G` 和单调递增插入序号,保证相同输入的搜索顺序可复现。
- `HybridAStarSearch` 使用 `Dictionary<HybridAStarNodeKey,double>` 保存每个离散键当前最佳 `G`。发现更小 `G` 时允许重新打开节点;Open List 中的旧条目通过比较最佳 `G` 惰性丢弃。Closed Set 键为位置格、航向格、方向和曲率等级。
- 搜索循环在扩展节点前检查取消、超时和节点上限。终点候选只有作为当前最佳有效节点出队时才返回成功。
- `PathBacktracker` 只存父节点索引和原语描述,在成功后确定性地重新生成内部积分点,避免为所有搜索节点长期保存稠密点。`CoarsePathAssembler` 按既定换向规则累计弧长并构造 `PathSegment`
- `CoarsePathValidator` 使用相同的连续位姿、扫掠余量和碰撞规则复核最终稠密输出,同时检查有限数值、曲率上限、目标容差、方向约束、弧长单调性、换向对和分段索引完整覆盖。
第一版允许前进、倒车和换向。换向只可发生在原语边界;相邻原语曲率等级最多变化一级。目标达到容差即成功,不尝试 Reeds-Shepp 精确连接。
## 状态与失败处理
`PlanningStatus` 至少区分:`Success``Cancelled``InvalidRequest``InvalidMap``MapNotReady``InvalidVehicleParameters``InvalidCurvatureConfiguration``StartOutsideMap``StartInCollision``GoalOutsideMap``GoalInCollision``SearchTimeout``SearchNodeLimitExceeded``NoFeasiblePath``BacktrackingFailed``FinalValidationFailed``InternalError`
所有输入错误在开始搜索前返回状态与可读原因;取消、超时和节点上限均返回空路径,不发布部分结果。除参数为空这类编程错误外,搜索或地图对象不能通过异常把部分路径发布给调用方。`PlanningDiagnostics` 记录扩展节点数、生成节点数、重新打开节点数、丢弃的陈旧堆条目数、Open List 峰值、总路径长度、最小保守净空、耗时和终止原因,供之后的性能优化使用。
## 测试设计
纯逻辑测试沿用当前 PowerShell 反射契约测试方式,确保在项目的 `netstandard2.0` 与 Clumsy 引用环境中验证真实程序集。
| 测试文件/入口 | 覆盖内容 |
| --- | --- |
| `verify_planning_utils.ps1` | mm/m、deg/rad、角度环绕、车体/世界坐标变换和非法数值。 |
| `verify_planning_map_factory.ps1` | 统一来源投影、必需/可选失败策略、来源确定性顺序、空图声明、输入指纹、占据哈希、完整/缓冲区缓存命中、来源版本摘要、静态快照隔离与并发访问。 |
| `verify_planning_map_adapter.ps1` | 圆与轴对齐矩形的包围盒裁剪和格矩形相交、AMR 自身不占据环境图、连续行优先存储、坐标边界、非完整末格、越界保守占据、mm→m 深拷贝、距离场不高估与 `PlanningReady`。 |
| `verify_planning_map_image.ps1` | 从 `PlanningGridMap` 渲染占据格、边界、起终点、车辆和路径叠加;覆盖关闭导出、非法尺寸、像素/文件上限、唯一命名、临时文件清理、PNG 结构与 CRC。 |
| `verify_coarse_path_collision.ps1` | 车体中心位于栅格中心和亚栅格位置时的正交/45°/任意航向,边角擦碰、薄障碍、地图边界、距离场快速放行与原语区间扫掠碰撞。 |
| `verify_coarse_path_search.ps1` | 无障碍前进、0.30 m 非整倍数终点截断、单障碍绕行、允许倒车的狭窄场景、起始曲率、目标进入方向、换向对、±π 航向容差、起点已满足目标、无解、取消、超时、节点上限、节点重新打开与确定性顺序。 |
| `verify_coarse_path_integration.ps1` | `CoarsePathPlanningService` 一次调用完成多来源建图、缓存复用、规划、回溯和最终验证;复核调试开关不改变地图指纹或规划结果。 |
| `benchmark_coarse_path.ps1` | Release 构建下的参考场景耗时、扩展节点数、Open List 峰值与托管内存增量。 |
| `MovementTest.MapTest` | 在 Clumsy UI 中显示人工与 TwoLeg 投影后的环境栅格;可选 PNG 导出只用于调试证据。 |
| `MovementTest.CoarsePathTest` | 使用固定可复现实例调用 `CoarsePathPlanningService`,绘制地图、起终点、扩大车体检查点和粗路径;`TestStop` 取消规划并清理 Painter,不向底盘发送运动命令。 |
每个新增公共契约均需有成功、边界和失败三类测试。测试场景由 `CoarsePathScenarioFactory` 统一生成,不在 `MovementTest` 中手写地图、原语或搜索细节。
`MovementTest.CoarsePathTest` 在后台任务中调用同步的 `CoarsePathPlanningService.Plan`,持有专用 `CancellationTokenSource``TestStop` 先取消规划,再清理任务引用和 Painter。UI 入口不得在界面线程上执行最长 5 s 的搜索,也不得调用任何底盘运动接口。
PowerShell 测试统一使用以下形式执行,绕过本机脚本执行策略差异,并在首个错误处停止:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\tests\<script>.ps1
```
每个脚本首行设置 `$ErrorActionPreference = 'Stop'`。测试先执行一次项目构建,之后加载同一个 `bin/Debug/netstandard2.0/ClumsyPilot.dll`,不得混用 `obj``bin` 中的程序集。`netstandard2.0` 实现不得直接使用 `PriorityQueue``Math.Clamp``double.IsFinite` 或缺少兼容类型时的 `record/init`
### 性能与资源验收
- 地图参考场景:20 m × 20 m、0.05 m 分辨率、160,000 格和 100 个圆/矩形障碍。Release 构建预热后连续构建 20 次,首次完整构建 P95 不超过 200 ms;完整快照缓存命中 P95 不超过 5 ms。
- 地图极限场景:4,000,000 格、100 个障碍。完整构建必须在 3 s 内成功或以明确状态失败;成功时单次托管内存增量不超过 160 MB,不得出现整数溢出或部分发布快照。
- 默认硬限制:`MaximumExpandedNodes=200000``SearchTimeout=5 s`;任一限制触发后必须在下一次循环检查点终止。
- 参考场景:12 m × 8 m、0.05 m 分辨率、一个阻断直线路径的矩形障碍、起终点距离至少 8 m。Release 构建预热后连续运行 20 次,P95 规划耗时不超过 2 s,单次托管内存增量不超过 256 MB。
- 压力场景:20 m × 20 m、0.05 m 分辨率、160,000 栅格。无论成功或无解,都必须在 5 s 与 200,000 扩展节点内返回,托管内存增量不超过 512 MB。
- 性能脚本输出地图规模、状态、耗时、扩展/生成/重开节点数、Open List 峰值和内存增量;超过阈值返回非零退出码。
## 非目标与迁移边界
- 不修改 `TrajPlanner` 下的 Python 原型,也不把它作为运行时依赖。
- 不在本阶段实现平滑、SQP、时间轨迹或控制接口;后续模块只消费 `PlanningResult` 中稳定的粗路径与方向分段。
- 不保留将车辆自身写入规划占据图的兼容开关;若调试可视化需要车辆图形,应作为渲染叠加层。
- 现有 `Occupancygird_Map/Map_test` 原型中的通用栅格化、TwoLeg 投影和 PNG 调试能力按以上职责迁移。迁移完成后,旧 `GridMapData``TrapMapBuilder``TrapMapLayerComposer` 与旧 `TrapMapTest` 不再作为公共运行时入口;旧反射测试必须更新到新命名空间和门面,或在等价覆盖后删除。
- PNG 导出器若保留,只能作为内部测试/可视化适配器消费只读 `PlanningGridMap`,不得重新拥有地图构建、障碍膨胀或车辆足迹写入逻辑。
- 迁移验收必须证明 `CoarsePathPlanningService` 是推荐的一次调用入口,`PlanningMapFactory``HybridAStarPlanner` 只作为下层模块门面;旧地图构建器、投影器、栅格化器和搜索内部类型不得成为额外公共服务。
@@ -1,94 +0,0 @@
# Hybrid A* P0 规划核心实施设计
## 目标
在已完成的 `PlanningGridMap` 静态地图能力之上,交付可复用、确定性且可验证的 Hybrid A* 粗路径规划核心。常规调用方通过 `CoarsePathPlanningService.Plan(job)` 一次完成建图和规划;纯算法测试可直接使用 `HybridAStarPlanner.Plan(request)`
本设计只覆盖 P0-PLAN。P1 的 Clumsy UI 后台任务、Painter 绘制、Release 性能基准和旧 TrapMap 运行时入口退役不在本次实现范围内。
## 既有边界
- `Map` 模块只保存外部障碍物;不得写入 AMR 自身足迹,也不得对障碍物做车辆安全膨胀。
- `PlanningGridMap` 是规划器唯一接受的地图类型,世界查询坐标为 m;越界位置视为占据且净空为 0。
- 规划核心不读取传感器、定位、UI 或系统时间。取消令牌是唯一允许的外部控制输入。
- 所有公共契约和核心逻辑兼容 `netstandard2.0`:不使用 `PriorityQueue``Math.Clamp``double.IsFinite``record``init`
- 注释延续已有模块风格:公开类型和成员写中文 XML 文档,说明单位、边界、返回/失败语义;内部复杂几何或搜索不变量保留简短中文行注释。
## 方案选择
采用“碰撞核心先行、搜索核心随后接入”的两段实现。
先建立公开数据契约、车辆扩大矩形和连续碰撞检查,使安全语义可以在不依赖搜索器的情况下通过自动化测试固定下来。随后在这些稳定边界上实现恒曲率运动原语、启发式、确定性 Open List、Hybrid A*、路径装配/复核和一次调用服务门面。此顺序避免 UI 或搜索状态掩盖车辆擦边、扫掠和地图边界错误。
## 架构与数据流
```text
CoarsePathPlanningJob
-> PlanningMapFactory.Create(MapRequest)
-> PlanningMapBuildResult / PlanningGridMap
-> HybridAStarPlanner.Plan(PlanningRequest)
-> FootprintCollisionChecker
-> MotionPrimitiveGenerator
-> GridDijkstraHeuristic + HybridAStarSearch
-> PathBacktracker + CoarsePathAssembler
-> CoarsePathValidator
-> CoarsePathPlanningJobResult
```
`Facade` 只编排地图与规划,不接触栅格化、车辆几何或搜索节点。`HybridAStarPlanner` 在搜索前完成请求、地图、车辆、起点和终点的有效性检查;搜索成功后必须经过 `CoarsePathValidator` 才能发布路径。
### 公共契约
`Contracts` 定义 m/rad/1/m 单位的值对象、车辆参数、配置、请求、状态、诊断、路径点和分段。默认配置固定为:0.50 m 原语最大长度、0.05 m 积分步长、5° 航向分辨率、0.15 m 位置容差、5° 航向容差、200,000 节点和 5 s 搜索上限。
`PlanningResult` 对所有结果提供明确 `PlanningStatus` 和可读诊断。输入错误、地图未就绪、碰撞、无解、取消、超时、节点上限、回溯失败和最终校验失败都返回空路径;不会通过异常发布部分路径。
### 车辆碰撞
`VehicleFootprint` 将以车辆几何中心为参考的长宽与安全余量转换为扩大矩形、AABB 和外接圆。`VehicleKinematics` 从最大曲率和最小转弯半径推导更保守的最大曲率。
`FootprintCollisionChecker` 固定按以下顺序检查连续位姿:
1. 扩大矩形是否完整位于地图边界内;
2. 使用保守距离场与外接圆进行严格大于关系的快速放行;
3. 对包围盒内的每个占据格,使用 SAT 检查旋转矩形与格矩形是否相交或擦边;
4. 对相邻采样位姿,以中心平移与航向变化构造扫掠附加余量,检查中间区间。
因此距离场只能加速安全放行,不能替代精确碰撞判定;安全余量只作用于车辆扩大矩形,绝不回写地图。
### 搜索与路径输出
第一版只生成前进、倒车和原语边界换向的恒曲率原语。运动积分使用直线/圆弧解析公式,积分点间距不超过配置步长;碰撞采样的中心位移不超过 `min(0.025 m, Map.ResolutionMeters / 2)`
搜索键由位置格、航向格、方向和曲率等级组成。确定性二叉最小堆按 `F``H`、较大 `G` 和插入序号排序;更小 `G` 的状态允许重新打开,旧堆条目延迟丢弃。二维八邻域 Dijkstra 启发式禁止穿越障碍的对角夹角。
原语的每个内部积分点依次进行数值、碰撞和终点容差检查。首次满足终点条件时截断原语并将候选压入 Open List;只有该候选作为当前最优有效节点出队时才宣布成功。成功后由回溯器重建稠密积分点,由装配器生成累计弧长和包含式方向分段;换向处保留一对位置、航向和弧长相同但方向不同的相邻点。
### 文档
新增 `ClumsyPilot/ParkrobTrajplanner/CoarsePath/README.md`,作为 P0 调用者文档。它说明模块边界、一次调用示例、输入单位、来源版本与地图缓存关系、状态处理方式、路径输出含义和第一版非目标。现有 `Map/README.md` 不复制粗路径内容,继续保留地图构建细节。
## 错误与取消语义
- `PlanningGridMap` 为空、不可规划或起终点不在地图内时,在搜索前返回对应状态。
- 起点/终点与扩大车辆矩形碰撞时,返回 `StartInCollision``GoalInCollision`
- 每次扩展节点前检查取消、超时和最大扩展数;触发后立即返回空路径和累计诊断。
- 任意内部不变量异常被收敛为 `InternalError`,不向调用方泄露部分路径。
- 地图构建失败时,服务直接包装 `PlanningMapBuildResult`,不启动 `HybridAStarPlanner`
## 测试策略
测试继续使用真实 `netstandard2.0` 程序集的 PowerShell 反射脚本,所有 P0 生产代码均遵循 Red-Green-Refactor:先在脚本中写可反射调用的失败断言并确认其因类型或行为缺失失败,再写最小实现,最后运行同一脚本确认通过。
1. `verify_coarse_path_collision.ps1`:参数边界、亚栅格位姿、任意航向、擦边、薄障碍、地图边界、距离场放行和扫掠碰撞。
2. `verify_coarse_path_search.ps1`:原语解析积分、非整倍数终点截断、方向约束、换向、启发式、堆确定性、重开、取消、超时、节点上限和无解。
3. `verify_coarse_path_integration.ps1`:从多来源 `PlanningMapRequest` 到最终路径的服务编排、地图缓存复用、最终复核和调试开关不改变规划结果。
每个脚本在测试前构建 `ClumsyPilot/ClumsyPilot.csproj`,随后只加载 `bin/Debug/netstandard2.0/ClumsyPilot.dll`。完成 P0 前必须重新运行构建与所有既有 Map 验证脚本,确保新规划代码没有破坏地图模块。
## P0 验收
- 固定静态场景可以经 `CoarsePathPlanningService.Plan` 得到连续、无碰撞、满足终点容差的稠密粗路径。
- 最终路径通过同一套扩大车体和扫掠规则复核;失败不发布部分路径。
- 路径点、方向分段、状态和诊断可由不依赖 Clumsy 的调用方直接消费。
- 使用说明可独立解释 Map 与 CoarsePath 边界、单位、调用方式及版本限制。
@@ -1,56 +0,0 @@
# Map 模块文档与注释设计
## 目标
让阅读 `ClumsyPilot/ParkrobTrajplanner/Map` 的开发者无需反查实现,即可理解模块文件职责、建图数据流和所有公共 API 的调用契约。
## 交付内容
### `Map/README.md`
README 是 Map 模块的入口说明,只保留不会由 IDE 自动展示的模块级信息:
- 当前目录树,以及每个文件的一句话职责;
- 从 `PlanningMapRequest``PlanningGridMap` 的建图数据流;
- 坐标系和单位约定;
- `PlanningMapFactory` 的最小调用示例;
- 缓存与 `SourceVersion` 的使用约束;
- 测试、PNG 调试和旧 TrapMap 的边界。
README 不复制逐个参数说明;参数的唯一权威说明位于声明处的代码注释。
### `.cs` 代码注释
覆盖 `Map` 内所有 `public` 类、接口、枚举、构造函数、方法和属性。注释使用可被 C# IDE 识别的 `///` XML 文档注释,但按 Python docstring 的阅读顺序组织:
1. 功能:该成员做什么;
2. 参数:名称、类型语义、单位、可空性或约束;
3. 返回:返回对象及字段的业务意义;
4. 注意:缓存、坐标转换、不可变性、线程安全或失败语义等调用者必须知道的约束。
不为纯私有实现逐项添加重复注释;复杂算法的私有方法只在其现有说明明显不足、且会妨碍维护时补充最小必要说明。
## 注释示例
```csharp
/// <summary>
/// 创建规划地图快照。
///
/// 参数:
/// - request:建图请求,包含世界范围、栅格分辨率和障碍物来源。
///
/// 返回:
/// - PlanningMapBuildResult:成功时含不可变地图、来源投影结果和缓存命中类型。
///
/// 注意:
/// - 应长期复用工厂实例,才能复用缓存。
/// </summary>
public PlanningMapBuildResult Create(PlanningMapRequest request)
```
## 验收
- `Map/README.md` 可独立说明文件结构、数据流和公共入口;
- 通过 `rg` 检查,所有 Map 公共 API 均有紧邻的中文 `///` 文档说明;
- 现有 Map Gate 与项目编译保持通过;
- 不修改 Map 的建图算法、缓存键或运行时行为。
@@ -1,114 +0,0 @@
# P1 粗路径 Clumsy UI 集成设计
## 目标
在不改变 P0 地图、Hybrid A* 与碰撞安全语义的前提下,为 Clumsy 增加可手动运行的粗路径场景测试。使用者能够从 MovementTest 列表启动固定场景,或传入 AMR 当前世界位姿并手动输入终点;界面显示栅格地图、起点、终点、连续路径、换向点和扩大车辆矩形检查点,并可随时停止正在进行的规划。
本设计只覆盖 P1 的首个交付:场景工厂、后台 MovementTest、Painter 可视化、自动化集成检查和模块 README。Release 性能基准属于 P1 的下一项交付;旧 TrapMap 迁移、旧验证脚本和旧入口的清理按已确认范围排除。
## 既有边界
- 业务入口仍唯一为 `CoarsePathPlanningService.Plan(job, cancellationToken)`MovementTest 不自行拼接 `PlanningMapFactory``HybridAStarPlanner`、栅格化器、碰撞器、原语或搜索节点。
- `PlanningMapRequest` 的边界、分辨率和障碍物几何使用 mm;`CoarsePathPlanningJob` 位姿、车辆尺寸和路径点使用 m,航向使用 rad。
- 项目传入的 AMR 位姿采用世界 `X/Y(mm)` 与航向 `th(deg)`。P1 只在 UI 边界将其一次性转换为 `Pose2D(X / 1000, Y / 1000, th × pi / 180)`;规划核心不接受度或 mm 位姿。当前车队路径代码将来自 `getCartLocation().th` 的姿态与度制角相加,并在调用三角函数前显式除以 180 再乘 pi,因此 P1 不沿用旧 `Movements.cs` 直接对 `.th` 调用 `Math.Cos/Sin` 的不一致写法。
- AMR 起点必须表示车辆几何中心。若上游定位的参考点是雷达、天线或其他安装点,上游必须先按外参转换到车辆几何中心;安全余量仍只由 `VehicleParameters` 表达。
- `PlanningResult` 只有 `Success` 才能携带完整路径;取消、超时、无解和失败不得在 UI 上表现为部分路径。
- `Painter` 在现有后台多车线程中已被调用,因此本设计允许规划任务完成后的后台回调操作该图层;规划核心本身始终不依赖 UI。
- 注释延续 P0 风格:公开类型与成员使用中文 XML 文档,说明单位、并发/停止语义和返回行为;会影响竞态的内部代码保留简短中文行注释。
## 方案选择
采用“六个薄 MovementTest 入口 + 共享后台执行器”的方案。
每个入口对应一个已命名的固定场景,便于在 Clumsy 的测试列表中直接运行;它们共用同一个静态 `CoarsePathPlanningService`,因此既能复用地图缓存,也不会让测试代码绕开门面。一个位于同一源文件内的执行器负责互斥会话、`Task` 生命周期、取消和绘制,避免六个入口复制并发逻辑。
不采用单一测试入口配合代码常量切换,因为手动验证需要反复改代码;也不采用运行时弹窗选项,因为这会增加 UI 输入状态和无法直接观察每个场景的可发现性。
## 文件与职责
| 文件 | 职责 |
| --- | --- |
| `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/CoarsePathScenarioFactory.cs` | 创建不读取 UI、传感器、定位或时钟的固定 `CoarsePathPlanningJob` 场景。每次创建均返回新请求对象。 |
| `ClumsyPilot/ParkrobTrajplanner/CoarsePath/Test/MovementTest.CoarsePathTest.cs` | 声明七个 MovementTest 入口,以及共享服务、后台执行、取消、结果日志、AMR 位姿/手动终点输入与 Painter 绘制。 |
| `ClumsyPilot/tests/verify_coarse_path_integration.ps1` | 通过真实程序集反射验证场景、门面调用约束、缓存、换向、无解、取消和测试代码结构。 |
| `ClumsyPilot/ParkrobTrajplanner/CoarsePath/README.md` | 补充 P1 手动测试方法、颜色图例、单位转换、停止语义和非目标。 |
所有 UI 辅助类型保留在 `CoarsePath/Test` 内;不会向 `Map``Search``Vehicle``Facade` 增加 UI 依赖。
## 场景工厂
`CoarsePathScenarioFactory` 公开一个场景枚举和按枚举创建请求的方法。工厂的职责仅是构造纯输入;它不持有服务、缓存、Painter 或取消源。每个请求采用同一组可验证的车辆和搜索默认值,再按场景覆盖障碍物、起终点和方向约束。
场景固定使用世界 mm 地图边界和分辨率,向 `Pose2D` 写入对应的 m 坐标。障碍物只通过 `ManualObstacleSource``TwoLegObstacleSource` 进入 `PlanningMapRequest`,并为内容变化提供固定且正确的 `SourceVersion`
| 场景 | 地图与预期 |
| --- | --- |
| 显式空图 | `AllowExplicitEmptyMap=true`,直达前进路径成功,用于检查最短调用链。 |
| 单矩形绕行 | 中央矩形阻断直线,路径成功且必须绕障。 |
| 手工圆、矩形与 TwoLeg | 同时使用手工圆形、手工矩形和有效 TwoLeg 快照,路径成功,证明多来源经过同一门面。 |
| 缓存命中 | 连续以新建但完全相同的输入调用同一服务两次;第二次 `MapResult.CacheHit` 必须为 `Input`。 |
| 倒车换向 | 起步方向限制为前进、终点进入方向限制为倒车;成功路径必须出现标记的换向点。 |
| 无解 | 完全贯穿地图的障碍带隔开起点和终点,返回 `NoFeasiblePath` 且无路径。 |
| AMR 位姿与手动终点 | 起点使用上层传入并冻结的 AMR 世界位姿;操作者输入同一世界系的终点 X/Y/航向。该入口仅使用明确提供的障碍物快照,显式空图只能作为演示,不能代表现场无障碍。 |
实现期间先用自动化断言固定每个场景的状态;若需为当前 P0 运动原语调整数值,只能调整场景几何或请求参数,不能放宽碰撞、目标或失败语义。
## 后台会话与取消
共享执行器持有一个静态、长期存活的 `CoarsePathPlanningService`。任一入口启动时会创建新的会话:运行编号、专用 `CancellationTokenSource`、场景描述和后台 `Task`。启动新会话前取消旧会话,以确保同时最多只有一个可绘制的规划结果。
AMR 位姿和手动终点在创建任务前被转换、有限值校验并冻结,随后只作为 `CoarsePathPlanningJob` 数据传给后台。MovementTest 不在规划后台持续读取定位;若未来接入可能阻塞的 `DetourInterface.getCartLocation()`,它必须位于独立的上游快照提供者,不能阻塞 UI 或绕过本设计的输入契约。
```text
MovementTest.Test
-> 生成场景的全新 CoarsePathPlanningJob
-> 创建运行编号和 CancellationTokenSource
-> Task.Run(() => service.Plan(job, token))
-> 完成回调:仅当运行编号仍为当前会话时记录并绘制结果
MovementTest.TestStop
-> 取消当前 CancellationTokenSource
-> 使当前运行编号失效并解绑 Task 引用
-> 清空专用 Painter 图层
-> 旧任务完成后只释放其 CancellationTokenSource,不再绘制
```
`Test` 绝不等待 `Task`、不读取 `Task.Result`,因此不会阻塞 Clumsy 界面。`TestStop` 不等待规划任务退出;P0 的共享预算会将令牌传递至建图、EDT、Dijkstra 和 Hybrid A*,任务在其检查点返回 `Cancelled`。运行编号检查可防止已取消的旧任务在新任务结果之后覆盖画面。
任务异常只记录清晰的测试诊断并释放资源,不伪造 `PlanningResult`。正常停止、超时、无解与输入失败均使用门面实际返回的状态。
所有七个入口只创建规划请求、任务和绘制;不得引用 `BasicPilotBase.Chassis``SendMotion``DriveTask` 或任何底盘控制 API。
## 绘制规则
使用独立的全局世界坐标 Painter 图层,例如 `CoarsePathPlanningV1`。Painter 输入为 mm,因此所有来自 `Pose2D``CoarsePathPoint` 的 X/Y 必须乘以 1000;航向仍以 rad 计算旋转矩形。不得混用 Map 的 mm 和 CoarsePath 的 m。所有地图输入、AMR 起点和手动终点均处于同一个世界坐标系。
- 先绘制地图 `[XMin, XMax) × [YMin, YMax)` 的粗外边界、世界 X/Y 参考和栅格网络。格线遵循真实 `ResolutionMm`;当格线数量超过显示上限时,按整数格距抽稀,并在状态文本中保留真实分辨率与显示步距。
- 从 `PlanningGridMap.IsOccupied(row, col)` 绘制占据格,而不是重新绘制原始障碍物几何;因此显示内容与实际规划快照一致。空闲格使用背景,不为每个空格增加填充。
- 起点为绿色圆、方向短线和“起点”标签;终点为橙色圆、方向短线、“终点”标签及目标位置容差圈。
- 成功路径逐段连接:前进与倒车使用不同颜色,并以固定间距绘制方向箭头;路径不成功时不绘制任何路径段。
- `IsGearSwitchPoint=true` 的点使用紫色标记和“换向”标签。
- 对首点、末点、每个换向点和固定间隔点绘制旋转矩形。矩形半长/半宽为 `Vehicle.LengthMeters / 2 + SafetyMarginMeters``Vehicle.WidthMeters / 2 + SafetyMarginMeters`,仅用于显示 P0 已采用的扩大车体,不参与碰撞判断。
- 在地图角落绘制固定图例:边界、占据格、起点、终点、前进、倒车、换向与扩大车体检查框的颜色含义。无论成功与否,绘制文本状态:场景名称、地图快照 ID、地图构建状态、缓存层级、规划状态、耗时和终止原因。停止或新会话开始时先清空旧图层。
绘制只消费 `CoarsePathPlanningJobResult` 的只读结果;不修改 `PlanningMapRequest`、地图快照、路径、调试开关或服务缓存。
## 测试与验收
按 Red-Green-Refactor 顺序扩展 `verify_coarse_path_integration.ps1`:先增加以下会失败的反射/行为断言,再实现最小代码,最后运行相同脚本。
1. 断言 `CoarsePath/Test` 中的场景工厂和 MovementTest 文件存在;工厂提供六类固定场景与一个 AMR 位姿/手动终点入口,且每次创建返回独立请求。
2. 使用同一 `CoarsePathPlanningService` 运行工厂场景:空图、矩形、多来源和倒车换向均成功;倒车换向路径含 `IsGearSwitchPoint`;无解结果为 `NoFeasiblePath` 且路径为空。
3. 对缓存场景连续调用两次,断言第二个地图结果为 `Input` 命中,且路径状态和点数不因缓存改变。
4. 断言 AMR `0 deg``90 deg` 的 UI 输入分别转换为 `0 rad``pi/2 rad`,同时 X/Y 由 mm 转为 m;手动目标与起点都使用相同转换与有限值校验。
5. 对预先取消的后台调用断言门面映射为 `Cancelled`、地图或路径不发布部分结果;结构检查确认 MovementTest 使用 `Task.Run``CancellationTokenSource`,且没有等待任务。
6. 结构检查确认测试入口只通过 `CoarsePathPlanningService` 进行规划,且不引用底盘命令、栅格化器、碰撞器、原语生成器或搜索节点;并检查绘制代码消费 `PlanningGridMap` 的边界、分辨率与占据状态,包含图例和成功路径保护。
7. 更新 README 断言,确认 P1 的 UI、AMR 位姿单位、手动终点、取消和“无底盘命令”边界可被调用方查阅。
完成后运行 Debug 构建及现有 P0 Map/CoarsePath 验证脚本(不恢复或改动已被排除的旧 TrapMap 验证脚本)。
## 非目标
- 不实现路径平滑、速度规划、跟踪控制、底盘命令、实时重规划或传感器采集。
- 不改变地图指纹、缓存键、障碍物栅格化、车辆碰撞、终点判定、搜索代价或资源上限。
- 不在本交付中实现 Release 性能基准,也不清理、迁移或恢复任何 TrapMap 文件与脚本。
@@ -1,59 +0,0 @@
# 规划操作预算与诊断收尾设计
## 目标
在进入 P1 的 UI 集成前,使一次 `CoarsePathPlanningService.Plan` 调用的取消与超时语义覆盖完整链路:地图创建、距离场构建、二维 Dijkstra 启发式和 Hybrid A* 搜索。同时让现有 `PlanningDiagnostics` 中的 Open List 陈旧条目数与峰值容量反映真实搜索数据。
成功、无解、输入无效和碰撞安全语义不改变;任何取消或超时结果均不得发布部分路径或部分地图。
## 方案选择
采用一个内部共享的、基于单调 `Stopwatch``PlanningOperationBudget`。它保存调用方的 `CancellationToken`、整次调用开始时刻与总超时,并在每个耗时循环中返回三态结果:继续、已取消、已超时。
不采用“只在 Dijkstra 前后检查”的方案,因为大图 Dijkstra 和 EDT 仍可能长时间无响应;也不替换 Dijkstra 启发式,避免在收尾阶段改变 Hybrid A* 的搜索特性。
## 边界与数据流
```text
CoarsePathPlanningService.Plan(job, token)
-> PlanningOperationBudget(token, job.Configuration.SearchTimeout)
-> PlanningMapFactory.Create(mapRequest, budget)
-> EnvironmentMapBuilder / PlanningMapAdapter / EDT
-> HybridAStarPlanner.Plan(planningRequest, budget)
-> GridDijkstraHeuristic
-> HybridAStarSearch Open List
-> PlanningResult + PlanningDiagnostics
```
`PlanningOperationBudget` 放在不依赖 `Map``CoarsePath` 的公共工具层,仅暴露中立的停止原因。Map 与粗规划分别把该原因映射到自己的结果类型,避免 `Map` 反向依赖 `CoarsePath`
地图构建结果增加明确的终止状态(成功、普通构建失败、取消、超时)。`CoarsePathPlanningService` 将地图阶段的取消映射为 `PlanningStatus.Cancelled`,地图阶段的超时映射为 `PlanningStatus.SearchTimeout`;两种结果都保留地图构建诊断但路径和分段为空。
现有不带预算参数的 `PlanningMapFactory.Create``HybridAStarPlanner.Plan``GridDijkstraHeuristic` 入口保持可用,作为不受取消限制的兼容包装;业务门面只使用带共享预算的内部入口。
## 响应与一致性规则
- 每个耗时循环在开始处及每处理最多 256 个工作单元后检查预算;检查不改变正常情况下的栅格、启发式或 Open List 排序。
- 等待地图工厂创建锁时使用可轮询的获取方式,以便取消和超时也能中断排队等待。
- 缓存命中仍立即返回原有不可变快照;预算已停止时优先返回取消/超时,不能借缓存绕过调用方停止请求。
- 已被取消或超时的地图构建不得写入任何缓存,也不得发布部分 `PlanningGridMap`
- 规划器总耗时从门面开始计时;搜索器不重新开始独立的 5 秒窗口。
## 诊断
`HybridAStarSearchResult` 增加陈旧 Open List 条目数和 Open List 峰值。每次丢弃失效的普通节点时递增陈旧计数;每次成功入堆后更新峰值。`HybridAStarPlanner` 原样将这两个统计写入 `PlanningDiagnostics`
目标候选仍保留其当前规则:不受普通离散键的 best-G 压制,出队时复核。它们占用 Open List 容量,因此计入峰值;不因候选自身而计为陈旧条目。
## 测试与验收
- 在大于一个检查批次的地图上,Dijkstra 预计算期间取消,断言返回 `Cancelled`、空路径和有限响应时间。
- 使用足以覆盖 Dijkstra 工作的极短总超时,断言返回 `SearchTimeout`、空路径,且总耗时不超出预算一个检查批次的合理余量。
- 在地图适配器/EDT 处理中取消和超时,断言地图结果带对应状态、没有地图快照且缓存未被污染。
- 使用产生失效 Open List 条目的场景,断言陈旧数大于零;任意正常搜索断言峰值至少为一,并与最终 `PlanningDiagnostics` 一致。
- 重新运行 Debug 构建、所有既有 P0 地图/粗规划检查,以及新增取消、超时和诊断检查。
## 非目标
- 不修改运动原语、碰撞保守性、代价公式、目标候选排序或路径装配。
- 不在本次收尾中实现 UI、Painter、场景工厂或 Release 性能门槛;这些仍属于 P1。
@@ -1,83 +0,0 @@
# 粗路径固定案例使用实时 AMR 位姿设计
## 背景
当前 `粗路径规划-显式空图``粗路径规划-单矩形绕行``粗路径规划-多来源障碍``粗路径规划-缓存命中``粗路径规划-倒车换向``粗路径规划-无解` 使用工厂中写死的起点、终点、地图边界和障碍物坐标。只有手动的 `粗路径规划` 测试读取当前 AMR 位姿。
这使固定案例无法从当前机器人位置和朝向验证规划,且 AMR 位于固定地图范围外时会直接得到无意义的越界失败。
## 目标
所有 `粗路径规划-*` 固定案例都以一次冻结的当前 AMR 位姿为起点。每个案例保留原有的相对地图、目标、障碍和方向约束,从而在现场位置验证相同的规划问题。
## 范围
本变更只调整 Clumsy MovementTest 中固定场景的输入创建方式、诊断显示、测试和文档。
不修改 Hybrid A* 搜索算法、车辆参数、碰撞模型、地图缓存实现或手动 `粗路径规划` 的交互流程。
## 方案选择
采用“固定案例模板化并整体平移”的方案。
不采用只替换起终点的方案,因为地图和障碍仍停留在原始世界坐标,AMR 可能位于地图外。
不为每个案例分别编写实时构造逻辑,避免重复坐标换算、地图边界和障碍创建代码。
## 工厂接口与坐标规则
保留现有的 `Create(CoarsePathTestScenario scenario)`,使离线和自动化测试仍可创建可重复的基准案例。
新增接受 AMR 世界位姿的工厂入口:
```csharp
Create(CoarsePathTestScenario scenario,
double amrXMillimeters,
double amrYMillimeters,
double amrHeadingDegrees)
```
工厂将基准案例的起点映射到传入 AMR 位姿:
- `deltaX = amrX - baselineStartX`
- `deltaY = amrY - baselineStartY`
- 起点位置为 `amrX/amrY`,起点航向为 `amrHeading`
- 目标位置、地图边界、圆形障碍、轴对齐矩形障碍和 TwoLeg 位置均加上 `deltaX/deltaY`
- 目标航向为 `amrHeading + (baselineGoalHeading - baselineStartHeading)`,再规范化到规划器采用的标准角度范围。
- TwoLeg 障碍自身的朝向保持基准值,只移动其位置;本变更是平移而非旋转场景几何。
所有固定案例都使用该规则,包含预期无解的障碍带案例。无解案例移动后仍必须保持无解。
## UI 运行流程与错误处理
`CoarsePathPlanningTestRunner.RunScenario` 在开始时从 `DetourInterface` 读取一次 AMR 位姿,并将这份快照传给工厂。后续后台规划只使用冻结后的请求,定位更新不会改变正在执行的规划输入。
读取失败、空位姿或任一 X/Y/航向值为 NaN 或无穷值时,不提交规划请求。测试通过 Toast 和画布状态区显示 `AMR 位姿不可用` 及具体原因。
成功创建请求时,状态区额外显示冻结的 AMR 起点(世界 mm 和 deg),以便现场核对。既有的规划状态、路径搜索耗时、节点统计和失败原因显示继续保留。
手动 `粗路径规划` 测试不改变:它继续读取一次 AMR 起点,并要求用户输入终点、障碍和总超时。
## 缓存语义
缓存键继续由完整的地图输入决定。由于地图边界和障碍随 AMR 坐标平移,两次固定案例冻结到相同的 AMR X/Y、从而形成相同的平移后地图输入时,`粗路径规划-缓存命中` 才会命中现有输入缓存。仅 AMR 航向变化不会改变地图输入,缓存仍可命中。
AMR 位置已移动时显示缓存未命中是正确结果,而不是规划失败。UI 继续显示实际的缓存状态。
## 验证
自动化测试应验证:
1. 新工厂入口将基准起点映射到实时 AMR 位姿,并正确保留目标相对位置与起终点航向差。
2. 单矩形、多来源和无解案例的地图边界、目标及障碍按相同 `deltaX/deltaY` 移动。
3. TwoLeg 的位置移动而朝向不变。
4. 既有 `Create(scenario)` 的基准案例及其成功/无解预期继续通过。
5. UI 运行器读取并冻结一次 AMR 位姿;无效位姿产生明确的未启动诊断。
6. 文档准确说明实时 AMR 锚点、缓存命中条件和无解场景预期。
## 验收标准
- 任一固定 MovementTest 从当前 AMR 坐标和航向开始规划,而不是从写死起点开始。
- 目标、地图和障碍在当前 AMR 附近平移,起终点的相对方向要求保持不变。
- 现场定位异常时用户可直接看到原因,且不会错误进入规划后台任务。
- 离线固定案例和现有规划诊断能力不回归。
@@ -1,66 +0,0 @@
# 粗路径搜索耗时设计
## 目标
为粗路径规划结果增加独立的“路径搜索耗时”。它用于回答:在规划地图已经可用后,从起点到终点得到可发布最终粗路径实际花费了多久。
现有 `PlanningDiagnostics.Elapsed` 保持不变,继续表示从 `CoarsePathPlanningService.Plan` 入口开始的总耗时。
## 计时边界
`PlanningDiagnostics.PathSearchElapsed` 的边界固定如下:
- 开始:`HybridAStarPlanner` 已完成输入、起终点和初始碰撞检查,即将调用 `HybridAStarSearch.Search`
- 包含:二维 Dijkstra 启发式预计算、Hybrid A* 节点扩展、路径回溯、路径装配、方向分段和最终碰撞复核。
- 结束:规划器准备返回对应的 `PlanningResult`
- 不包含:地图来源读取、地图缓存查询、障碍物栅格化、距离场构建,以及门面层在进入规划器前的工作。
因此,此字段表示“地图就绪后的路径求解与发布耗时”,而不是仅 Open List 循环的耗时。
## 数据契约
`PlanningDiagnostics` 新增只读 `TimeSpan PathSearchElapsed`
- 成功时记录完整路径搜索与发布阶段耗时。
- 搜索失败、无解、节点上限、超时、取消、回溯失败、装配失败或最终复核失败时,记录截至返回前已消耗的该阶段时间。
- 在进入搜索阶段前即失败(例如输入、起终点或初始碰撞检查失败)时为 `TimeSpan.Zero`
- 该字段必须为非负值,并且不超过总耗时 `Elapsed`
保持构造函数的现有调用兼容:新参数具有 `TimeSpan.Zero` 默认值。`HybridAStarPlanner` 是唯一写入实际计时值的边界。
## 实现方案
推荐方案是在 `HybridAStarPlanner.Plan` 中,于调用 `_search.Search` 前创建本地 `Stopwatch`,并在所有搜索后返回路径将要构造 `PlanningResult` 时读取其 `Elapsed``CreateDiagnostics` 接收这个独立耗时,并写入 `PlanningDiagnostics`
选择该方案的原因:
- 不修改门面的共享总预算和取消/超时语义。
- 不让 `HybridAStarSearch` 暴露计时实现细节。
- 计时覆盖用户定义的完整粗路径产出阶段,而非只覆盖节点扩展循环。
未采用的方案:
1. 直接复用 `PlanningOperationBudget.Elapsed`:会包含建图,不满足需求。
2. 仅在 `HybridAStarSearch` 内计时:会遗漏回溯、装配和最终复核,无法表示最终粗路径产出时间。
3. 为建图、启发式、搜索、复核分别公开多组指标:诊断更细,但超出当前需求。
## 可视化与文档
`MovementTest.CoarsePathTest` 的状态图层和 Toast 同时显示:
```text
总耗时:<Elapsed> ms,路径搜索:<PathSearchElapsed> ms
```
README 明确区分:总耗时覆盖建图和路径规划;路径搜索耗时仅覆盖地图就绪后的最终粗路径搜索、回溯、装配与复核。
## 验证
自动化验证应覆盖:
1. `PlanningDiagnostics` 默认搜索耗时为零,且新字段可由调用方读取。
2. 一个真实可行规划返回非负的路径搜索耗时,且不大于总耗时。
3. 既有总预算、取消、超时和路径状态断言不改变。
4. UI 源码检查确认图层和 Toast 读取并显示新字段。
5. README 包含新字段的计时边界说明。
@@ -1,160 +0,0 @@
# 粗路径手动测试诊断与耗时设计
## 目标
完善 `[MovementTest(name = "粗路径规划")]` 的手动测试体验,使调用者能够为每次测试指定正数秒总预算,并在失败时看到真实、可区分的终止原因、搜索统计、总耗时和路径搜索耗时。
本改动不放宽碰撞安全、终点判定或成功路径发布条件,也不把普通规划失败改成异常。普通失败继续通过 `PlanningResult` 返回;输入读取失败和未预期程序错误才按异常信息展示。
## 已确认的现状
手动测试使用正式的 `CoarsePathPlanningService``HybridAStarPlanner``HybridAStarSearch`,没有测试专用搜索器,也没有关闭倒车;`HybridAStarConfiguration.AllowReverse` 默认仍为 `true`
影响实际成功率和问题定位的限制如下:
- 手动入口未覆盖 `SearchTimeout`,因此沿用 5 秒总预算。
- 已复现一个空图可行案例:起点 `(1000 mm, 2000 mm, 0 deg)`、终点 `(1500 mm, 2500 mm, 90 deg)` 在 5 秒返回 `SearchTimeout`,将预算改为 30 秒后约 10.2 秒成功。
- 手动测试固定使用长 `0.80 m`、宽 `0.60 m`、安全余量 `0.05 m`、最小转弯半径 `1.20 m` 的演示车辆参数,不读取现场 AMR 几何参数。
- `HybridAStarPlanner` 将所有搜索阶段失败改写成同一句“Hybrid A* 搜索未找到可发布路径”,丢失了超时、节点上限、无解和内部错误的具体区别。
- 状态图层只在 `TerminationReason` 非空时绘制原因,而 Toast 不包含原因。
- 第一版算法只支持汽车式恒曲率前进、倒车和原语边界换向;明确不支持 Reeds-Shepp 精确连接、横移、蟹行和原地旋转。这是整个粗规划核心的版本边界,不是手动测试单独阉割。
## 范围
### 包含
- 手动测试增加单次总超时输入。
- 搜索结果保留原始终止原因。
- 面向调用者组合状态、原因、资源上限和节点统计。
- 状态图层与 Toast 展示原因、总耗时和路径搜索耗时。
- README 说明测试车辆参数和第一版运动能力限制。
- 自动化覆盖输入校验、失败分类、耗时边界和 UI 文本。
### 不包含
- Reeds-Shepp 或 Dubins 解析终点连接。
- 横移、蟹行或原地旋转原语。
- 路径平滑、速度规划或控制。
- 将固定演示车辆参数替换为尚未定义来源的现场 AMR 参数。
- 改变地图边界策略、碰撞规则、终点容差或 Open List 排序。
## 设计
### 手动超时输入
`CoarsePathPlanningTest.Test()` 在读取终点航向后读取“粗路径规划总超时(秒,必须大于 0)”。输入按当前文化和不变文化解析为有限 `double`,必须严格大于零,并且转换为 `TimeSpan` 后不溢出。
场景工厂仍负责创建完整业务请求;测试入口在取得 `CoarsePathPlanningJob` 后只覆盖本次请求的:
```csharp
job.Configuration.SearchTimeout = TimeSpan.FromSeconds(timeoutSeconds);
```
固定回归场景继续使用各自既有预算。手动输入不修改全局默认配置,也不影响下次测试。
输入 `0`、负数、NaN、Infinity、非数字或超出 `TimeSpan` 可表示范围时,本次规划不启动,并通过既有输入失败提示显示具体字段。
### 搜索原始终止原因
`HybridAStarSearchResult` 增加只读 `TerminationReason`。搜索的每个失败出口填写与发生位置一致的原因:
- `SearchTimeout`:共享总预算已经耗尽。
- `SearchNodeLimitExceeded`:扩展节点数达到配置上限。
- `NoFeasiblePath`:二维启发式不可达、起始状态无法进入 Open List,或 Open List 耗尽。
- `Cancelled`:收到取消请求。
- 输入、地图、车辆或配置状态:指出对应校验阶段。
- `InternalError`:保留异常类型和消息,不在 UI 展示完整堆栈。
`HybridAStarPlanner` 不再用同一句泛化说明覆盖搜索原因。它将原始原因与可操作统计组合为最终 `PlanningDiagnostics.TerminationReason`。例如:
```text
总预算 8.000 秒已耗尽;扩展 5,366,生成 21,014Open List 峰值 15,102。
```
节点上限原因包含配置的 `MaximumExpandedNodes`;无解原因包含扩展、生成和 Open List 峰值。起点、终点、碰撞和最终复核的现有明确原因保持不变。
### 异常语义
普通规划失败不抛异常,因为超时、无解、碰撞和资源上限是可预期业务结果。调用者继续通过 `PlanningStatus` 做稳定分支,并读取 `TerminationReason`
搜索或规划器捕获未预期异常时返回 `InternalError`,原因包含异常类型和非空消息。完整堆栈不进入 Toast,避免界面噪声;自动化测试验证异常不会再次被完全静默吞掉。
### 耗时
保留既有 `PlanningDiagnostics.Elapsed`,定义为从 `CoarsePathPlanningService.Plan` 入口开始的总耗时,包括建图、缓存查询、栅格化、距离场和路径规划。
新增只读 `PlanningDiagnostics.PathSearchElapsed`,边界为:
- 开始:规划请求、地图、车辆、配置、起点、终点和初始碰撞预检均已通过,即将进入 `HybridAStarSearch.Search`
- 包含:二维 Dijkstra 启发式、Hybrid A* 扩展、回溯、路径装配、方向分段和最终复核。
- 结束:`HybridAStarPlanner` 准备构造最终 `PlanningResult`
- 不包含:地图来源读取、地图缓存、栅格化、距离场构建和搜索前预检。
搜索开始前失败时为 `TimeSpan.Zero`;搜索开始后的成功、失败、取消、超时、节点上限、回溯失败或最终复核失败均保留截至返回时的路径搜索耗时。所有结果满足:
```text
TimeSpan.Zero <= PathSearchElapsed <= Elapsed
```
构造函数的新参数位于现有参数之后并具有 `TimeSpan.Zero` 默认值,保持现有调用兼容。
### MovementTest 展示
状态图层显示:
- 地图状态、缓存命中和快照。
- 规划状态。
- 总耗时与路径搜索耗时。
- 扩展节点数、生成节点数和 Open List 峰值。
- 非空失败原因。
- 当前测试采用的车辆长、宽、安全余量和最小转弯半径。
Toast 保持单行摘要,但成功和失败都显示两种耗时;失败时必须附加 `TerminationReason`。这样即使用户没有看到地图左下角的状态图层,也不会只收到一个无原因的失败状态。
### 文档
`CoarsePath/README.md` 增加以下说明:
- 手动入口要求输入有限正数秒总预算。
- 总耗时与路径搜索耗时的不同边界。
- 手动入口采用固定演示车辆参数,不代表现场 AMR。
- 当前算法支持汽车式恒曲率前进、倒车和换向,但不支持 Reeds-Shepp 精确连接、横移、蟹行或原地旋转。
## 测试策略
所有行为修改遵循 Red-Green-Refactor。
### 输入与 UI 源码检查
`verify_coarse_path_ui.ps1` 先增加失败断言,再实现:
- 手动入口读取并应用超时秒数。
- 超时输入使用正数有限值校验。
- 状态图层和 Toast 都读取 `PathSearchElapsed`
- Toast 读取 `TerminationReason`
- 状态图层显示节点统计和车辆参数。
- README 包含新的耗时边界、手动预算和能力限制说明。
### 搜索与集成行为
`verify_coarse_path_search.ps1``verify_coarse_path_integration.ps1` 先增加失败断言,再实现:
- 超时、节点上限、无解和内部错误返回不同的非空原因。
- `InternalError` 原因包含异常类型或消息。
- 搜索前失败的 `PathSearchElapsed` 为零。
- 一个真实成功规划的路径搜索耗时非负且不超过总耗时。
- 搜索阶段失败保留已消耗的路径搜索耗时。
- 5 秒会超时而更长手动预算可成功的近距离换向案例得到回归覆盖;测试应使用足够稳定的预算余量,避免仅依赖精确墙钟阈值形成脆弱测试。
### 最终回归
最终运行 Debug 构建,以及现有地图、碰撞、搜索、集成和 UI 脚本。既有固定成功场景、取消、总超时、节点上限、无解、地图缓存和最终碰撞复核语义不得改变。
## 验收标准
- 手动“粗路径规划”每次要求输入有限正数秒总预算,并应用到该次请求。
- 失败 Toast 能直接区分超时、节点上限、无解、碰撞和内部错误,并包含可操作原因。
- 状态图层同时显示两种耗时、搜索统计和演示车辆参数。
- 可行但超过默认 5 秒的复现场景可以通过更长的用户输入预算成功。
- 所有新旧自动化验证通过。
- 算法第一版能力限制在 README 中可见,不再被误认为测试入口暗中禁用完整算法。
@@ -1,46 +0,0 @@
# CoarsePath README 结构化重构设计
## 目标
`ClumsyPilot/ParkrobTrajplanner/CoarsePath/README.md` 重构为与 `Map/README.md` 一致的说明风格,使调用者能够从模块职责、文件位置和数据流开始,逐步理解粗路径的调用、状态处理、P1 手动测试与明确的非目标。
本次只重构文档内容与现有文档检查;不改变 `Map``CoarsePath`、P1 UI 或任何测试场景的运行行为。
## 当前事实
- `Map` 负责障碍物来源、栅格化、不可变 `PlanningGridMap` 与缓存;它是粗路径的输入依赖。
- `CoarsePath` 已具备 P0 核心:车辆扩大足迹碰撞、前进/倒车原语、Dijkstra 启发式、Hybrid A*、路径回溯、最终复核与业务门面。
- P1 已具备:六个固定场景、AMR 位姿与手动终点空图演示、后台取消、Painter 结果可视化与 UI 结构检查。
- 尚不包含平滑、速度/时间轨迹、底盘控制、实时重规划、真实作业障碍物接入与 Release 基准。
## README 目标结构
1. **模块说明**:定义 `CoarsePath` 的输入、输出、唯一业务入口与职责边界。
2. **文件结构**:按 `Contracts``Vehicle``Search``Output``Facade``Test` 列出实际文件及职责。
3. **规划数据流**:说明 `CoarsePathPlanningJob` 经服务、Map 快照、Hybrid A* 到 `PlanningResult` 的固定路径;明确地图失败不会启动搜索。
4. **状态、单位与安全边界**:集中说明 mm/m、deg/rad、车辆安全外扩、取消/超时和“非成功不发布部分路径”。
5. **最小调用示例**:沿用现有可编译门面调用,展示成功、地图失败和规划失败的处理方式。
6. **缓存与 SourceVersion**:解释长期持有服务、`Input`/`Occupancy`/`None` 缓存层级及版本递增责任。
7. **详细使用指南**:依次说明长期服务、准备地图请求、车辆和搜索参数、调用门面、消费路径与方向段。
8. **P1 测试与调试**:集中说明七个 MovementTest、AMR 手动终点单位边界、后台停止和 Painter 图例。
9. **常见错误**:用“现象 / 原因 / 处理”表格覆盖单位混用、遗漏 `SourceVersion`、隐式空图、错误处理失败结果、将粗路径当作控制轨迹等问题。
10. **第一版限制**:保留不属于 P0/P1 的能力清单。
## 内容约束
- 仅记录已实现且已验证的行为;不把 P1 计划或人工验收说成已完成能力。
- 固定使用 `CoarsePathPlanningService.Plan(job, cancellationToken)` 作为唯一业务调用示例;不鼓励 UI 直接组装搜索组件。
- 保留 `Map/README.md` 链接,避免复制地图障碍物和栅格化的详细说明。
- P1 手动终点必须明确是显式空图演示,不能代表现场无障碍;当前 AMR 位姿为车辆几何中心,输入在 UI 边界从 mm/deg 转为 m/rad。
- 使用中文说明、目录树、数据流图、参数表、代码示例和常见错误表,保持 Map README 的信息密度与顺序。
## 验证
- 扩展 `ClumsyPilot/tests/verify_coarse_path_ui.ps1`,以 ASCII 稳定标识检查 README 含有新的主要章节、核心门面、数据流、P1 入口、单位、停止语义、非部分路径和限制边界。
- 运行 README 的 P1 UI 检查,以及现有 Debug 构建和粗路径集成检查;文档改动不应影响生产代码或 P0 行为。
## 非目标
- 不重写或迁移 `Map/README.md`
- 不新增、删除或改名 C# 类型、场景、MovementTest 或测试脚本。
- 不恢复、清理或迁移 TrapMap 及其旧验证脚本。
@@ -1,71 +0,0 @@
# P1 手动障碍物输入设计
## 目标
扩展 `[MovementTest(name = "粗路径规划")]`,使操作者可在一次手动测试中输入多个圆形或轴对齐矩形障碍物的中心与尺寸。输入经纯场景工厂转换为 `ManualObstacleSource`,再由已有 `CoarsePathPlanningService` 创建地图和规划;不绕过门面,也不添加任何底盘控制。
## 输入流程
1. 读取一次 `DetourInterface.getCartLocation()`,冻结 AMR 车身几何中心的世界 `X/Y(mm)``th(deg)`
2. 输入目标世界 `X(mm)``Y(mm)` 与航向 `deg`
3. 输入障碍物数量,允许范围为 `0``20`
4. 对每个障碍物输入类型:`1` 为圆形,`2` 为矩形。
5. 输入障碍物几何中心世界 `X(mm)``Y(mm)`
- 圆形再输入半径 `r(mm)`
- 矩形再输入 X 方向长度与 Y 方向宽度(均为 mm)。矩形不提供旋转角,始终与世界坐标轴平行。
6. 将已冻结的 AMR 位姿、目标和障碍物集合提交给现有后台执行器。
输入必须是有限数字。数量、类型、半径、长度和宽度不合法时拒绝启动规划并显示输入失败信息;不会产生不完整的规划请求。
## 工厂契约
`CoarsePathScenarioFactory` 新增面向手动测试的纯数据类型与工厂方法:
```csharp
public enum ManualCoarsePathObstacleKind
{
Circle,
AxisAlignedRectangle,
}
public sealed class ManualCoarsePathObstacle
{
public static ManualCoarsePathObstacle Circle(
double centerXMillimeters, double centerYMillimeters, double radiusMillimeters);
public static ManualCoarsePathObstacle AxisAlignedRectangle(
double centerXMillimeters, double centerYMillimeters,
double lengthXMillimeters, double widthYMillimeters);
}
public static CoarsePathPlanningJob CreateManualObstacleDemo(
double startXMillimeters, double startYMillimeters, double startHeadingDegrees,
double goalXMillimeters, double goalYMillimeters, double goalHeadingDegrees,
IReadOnlyList<ManualCoarsePathObstacle> obstacles, long obstacleSnapshotVersion);
```
原有 `CreateManualGoalDemo` 保留不变,并委托到相同的边界/位姿转换逻辑和空障碍物路径,因此既有调用方与验证不受破坏。
工厂将中心/尺寸转换为 `CircleObstacle``AxisAlignedRectangleObstacle`。有障碍物时请求使用必需的 `ManualObstacleSource("manual-user-input", obstacleSnapshotVersion, true, ...)``AllowExplicitEmptyMap=false`;没有障碍物时使用空来源数组和 `AllowExplicitEmptyMap=true`。工厂校验障碍物列表、版本、几何数值与正尺寸,避免将缓存版本或无效几何交给 Map。
## 边界和缓存
手动地图边界的候选范围由起点、终点和每个障碍物的完整外轮廓共同决定:圆形使用中心 ± 半径,矩形使用中心 ± 半长/半宽。候选范围的每侧保留 2000 mm,随后按既有 50 mm 分辨率向外取整。
后台执行器为每次包含障碍物的手动提交生成单调递增的 `obstacleSnapshotVersion`,并在 UI 线程完成输入后冻结它。这样新输入绝不会复用旧障碍物地图;固定场景的缓存命中测试保持原样。空障碍物演示不需要障碍物来源版本。
## 可视化与停止
不新增 Painter 专用绘图分支。现有结果绘制已从 `PlanningGridMap.IsOccupied(row, col)` 消费占据格,因此新的手动障碍物会自动在同一 `CoarsePathPlanningV1` 图层显示为实际栅格快照。起点、终点、路径、换向、扩大车体检查框、状态和 `TestStop` 取消语义均保持不变。
## 验证与文档
- `verify_coarse_path_integration.ps1` 增加工厂反射与行为断言:新类型/方法存在;圆形和矩形输入生成非空手动来源、关闭显式空图、边界覆盖外轮廓;空障碍物仍保留显式空图;无效尺寸被拒绝。
- `verify_coarse_path_ui.ps1` 检查手动入口读取障碍物数量、类型、中心和尺寸,并调用 `CreateManualObstacleDemo`,同时仍不直接创建地图或搜索器。
- `CoarsePath/README.md` 的 P1 节说明输入顺序、单位、20 个上限、矩形无旋转、空图仅限零障碍物演示,以及可视化仍基于最终规划快照。
## 非目标
- 不支持旋转矩形、多边形、导入文件、拖拽编辑或实时编辑已运行任务。
- 不让手动障碍物直接跳过 `ManualObstacleSource`、Map 缓存或 `CoarsePathPlanningService`
- 不改变已有固定场景、车辆安全参数、搜索算法、P1 Painter 颜色或任何底盘控制边界。
@@ -1,704 +0,0 @@
# 粗路径平滑算法对比实验台设计
## 1. 背景
`ParkrobTrajplanner/CoarsePath` 已经通过 `CoarsePathPlanningService` 输出经过连续车体碰撞、曲率、终点和方向分段复核的 Hybrid A* 粗路径。成功的 `PlanningResult` 包含:
- 稠密的 `CoarsePathPoint`
- 覆盖整条路径的前进/倒车 `PathSegment`
- 累计弧长、车辆曲率、保守车体净空和换向标记;
- 地图、搜索和最终复核诊断。
粗路径由离散恒曲率运动原语拼接而成。它解决绕障拓扑、行驶方向和换向结构,但相邻原语之间的曲率可能跳变,不适合作为后续 SQP 的最终空间参考线。
本设计在 `CoarsePath` 之后增加独立的 `PathSmoothing` 模块。第一阶段先建设离线算法对比实验台,使用相同粗路径独立比较三次 B 样条、局部三次 Bézier 和分段五次多项式;这里的“独立”不表示并发执行。实验台统一执行几何分析、安全复核、指标排序和 IEEE 风格绘图。算法成熟后,正式规划流程只调用选定的默认平滑方法。
## 2. 目标
本阶段必须完成:
1. 定义稳定的粗路径平滑输入、输出、状态、配置和诊断契约。
2. 保持粗路径已经确定的绕障侧、前进/倒车顺序、换向次数和目标进入方向。
3. 使用三种互相独立的平滑方法处理同一条粗路径。
4. 用统一的几何分析器计算原粗路径和三种平滑路径的弧长、航向与曲率。
5. 对所有平滑候选重新执行完整车体碰撞、地图边界、最大车辆曲率、端点和方向分段复核。
6. 支持现有粗路径端到端案例和不重复运行 Hybrid A* 的快速粗路径夹具。
7. 为每个场景输出 IEEE 风格的四路径对比图、四曲率对比图和指标表。
8. 输出可供后续 SQP 使用的不可变平滑空间路径,不包含速度和时间参数。
## 3. 非目标
本阶段不包含:
- SQP、QP、Frenet 横向优化或速度规划;
- 时间戳、速度、加速度、角速度或 jerk;
- 改变 Hybrid A* 选定的绕障拓扑;
- 新增、删除或移动换向点;
- 现场实时重规划;
- 四舵轮角度、轮速和底盘跟踪控制;
- 把不可行平滑候选作为可执行路径发布;
- 用单一五次多项式覆盖任意长度的完整路径。
## 4. 总体架构
新增与 `CoarsePath` 平级的目录:
```text
ClumsyPilot/ParkrobTrajplanner/
├── CoarsePath/
└── PathSmoothing/
├── Contracts/
├── Processing/
├── Algorithms/
├── Validation/
├── Comparison/
├── Visualization/
├── Facade/
└── Test/
```
各目录职责如下:
| 目录 | 职责 |
| --- | --- |
| `Contracts` | 平滑请求、结果、点、方向段、配置、状态、指标和诊断 |
| `Processing` | 输入校验、按方向拆段、等弧长重采样、关键点保护和统一几何分析 |
| `Algorithms` | 三次 B 样条、局部三次 Bézier、分段五次多项式及内部算法契约 |
| `Validation` | 完整车体碰撞、地图边界、曲率、端点、换向和输出不变量复核 |
| `Comparison` | 同输入多算法运行、结果隔离、指标汇总、排序和推荐 |
| `Visualization` | 图形场景模型、IEEE 样式、SVG/PNG/CSV 导出和字体检查 |
| `Facade` | 正式单算法平滑入口和离线多算法比较入口 |
| `Test` | 固定夹具、端到端案例和报告生成入口 |
`PathSmoothing` 只消费 `CoarsePath``Map` 的公开不可变数据,不读取传感器、定位、UI 或底盘。它不得修改已有 `PlanningResult`
## 5. 数据流
```text
PlanningResult.Path + PlanningResult.Segments
PlanningGridMap + VehicleParameters + PathSmoothingConfiguration
输入契约和原粗路径安全复核
按 Forward/Reverse 独立拆段
等弧长重采样、固定端点和换向点
┌───────────┼───────────┐
▼ ▼ ▼
三次 B 样条 局部 Bézier 分段五次多项式
│ │ │
└───────────┼───────────┘
统一航向、弧长和曲率分析
车体碰撞、边界、曲率、端点和分段复核
指标汇总、方法排序和 IEEE 风格报告
```
三种方法不能串联。每种方法从同一份预处理输入开始,失败状态和重试次数分别记录。
## 6. 公共接口
### 6.1 正式单算法入口
```csharp
public sealed class PathSmoothingService
{
public PathSmoothingResult Smooth(
PathSmoothingRequest request,
CancellationToken cancellationToken = default);
}
```
`PathSmoothingRequest` 包含:
```text
CoarsePath IReadOnlyList<CoarsePathPoint>
Segments IReadOnlyList<PathSegment>
Map PlanningGridMap
Vehicle VehicleParameters
Configuration PathSmoothingConfiguration
```
调用方可以从成功的 `PlanningResult.Path``PlanningResult.Segments` 直接构造请求。请求仍需携带规划时使用的地图和车辆参数,因为平滑路径必须独立进行安全复核。
### 6.2 离线比较入口
```csharp
public sealed class PathSmoothingComparisonService
{
public PathSmoothingComparisonResult Compare(
PathSmoothingComparisonRequest request,
CancellationToken cancellationToken = default);
}
```
比较请求复用同一份粗路径、地图、车辆和公共配置,并明确列出需要运行的方法。第一版默认方法集合固定为:
```text
CubicBSpline
LocalCubicBezier
PiecewiseQuintic
```
比较服务不绘图。报告导出器只消费不可变比较结果,因此绘图失败不会改变算法状态或平滑路径。
## 7. 核心契约
### 7.1 平滑方法
```text
SmoothingMethod:
CubicBSpline
LocalCubicBezier
PiecewiseQuintic
```
### 7.2 平滑状态
```text
PathSmoothingStatus:
Success
FallbackToCoarsePath
InvalidInput
Infeasible
Failed
Cancelled
```
- `Success`:平滑路径通过所有复核。
- `FallbackToCoarsePath`:所有允许的平滑尝试失败,且原粗路径重新通过安全复核后,按显式配置返回原路径。
- `InvalidInput`:输入路径、方向段、地图、车辆或配置不符合契约。
- `Infeasible`:算法产生了有限数值候选,但候选未通过安全或运动学复核。
- `Failed`:算法发生数值退化或无法产生完整候选。
- `Cancelled`:调用被取消,不能发布部分路径。
`Success``FallbackToCoarsePath` 外,正式 `PathSmoothingResult.Path``Segments` 必须为空。
### 7.3 平滑路径点
`SmoothedPathPoint` 是不可变值,包含:
```text
X、Y 世界坐标,m
Heading 归一化车头航向,rad
UnwrappedHeading 连续展开车头航向,rad
ArcLength 从完整路径起点累计的非负弧长,m
Direction Forward 或 Reverse
GeometricCurvature 按行进弧长计算的有符号几何曲率,1/m
VehicleCurvature 车辆模型使用的有符号曲率,1/m
BodyClearance 扩大车体的保守净空下界,m
IsGearSwitchPoint 是否为新方向段开始的换向点
Source Anchor、Interpolated、GearSwitch 或 CoarsePathFallback
```
回退原粗路径时 `Source` 使用单独的 `CoarsePathFallback`,避免把未经平滑的普通粗路径点错误标记为算法插值点。
`directionSign` 在前进时为 `+1`,倒车时为 `-1`。每个方向段内部采用:
```text
GeometricCurvature = d(Heading) / ds
VehicleCurvature = directionSign × GeometricCurvature
```
曲率不得跨合法换向重复点求导。
### 7.4 平滑方向段
平滑后点数与粗路径不同,不能直接复用原 `PathSegment` 索引。`SmoothedPathSegment` 包含:
```text
SegmentIndex
Direction
StartIndex
EndIndex
StartsAtGearSwitch
EndsAtGearSwitch
```
其索引必须连续、无重叠并完整覆盖平滑路径。
### 7.5 平滑配置
公共配置至少包含:
```text
Method
OutputSpacingMeters 默认 0.05
MaximumCollisionCheckStepMeters 默认 0.025
MinimumClearanceReserveMeters 默认 0.02
AllowFallbackToCoarsePath 默认 true
SmoothingStrength 默认 1.00
RetryStrengthScales
```
`RetryStrengthScales` 是有限、严格递减的只读集合。第一版建议为:
```text
1.00, 0.75, 0.50, 0.25
```
每种算法拥有独立的强类型子配置,不能用无语义的字符串字典传参。第一版固定默认值如下:
```text
CubicBSpline.EndpointTangentScale 1/3
LocalCubicBezier.CornerHeadingThresholdRadians π/18
LocalCubicBezier.MaximumWindowLengthMeters 0.60
LocalCubicBezier.HandleLengthRatio 1/3
PiecewiseQuintic.KnotSpacingMeters 0.50
PiecewiseQuintic.MinimumKnotSpacingMeters 0.10
```
公共请求在构造时复制这些配置;进入数值算法前再次转换成仅含只读标量的内部快照。算法不得硬编码或回读调用方可变配置。
所有配置数值必须有限;`OutputSpacingMeters``MaximumCollisionCheckStepMeters``SmoothingStrength`、控制比例及所有窗口/结点长度必须为正,`MinimumClearanceReserveMeters` 必须非负,转角阈值必须位于 `(0, π]`,且 `KnotSpacingMeters >= MinimumKnotSpacingMeters`。不满足时返回 `InvalidInput`,不得进入有限重试。
`MaximumCollisionCheckStepMeters` 默认值为当前粗路径默认安全精度 `0.025 m`。若上游粗规划采用了更小的检查步长,调用方必须传入相同或更小的值;平滑模块不能从未携带的粗规划配置中猜测该参数。
## 8. 公共预处理
### 8.1 输入校验
处理前必须验证:
- 粗路径和方向段均非空;
- 所有数值有限;
- 弧长非负且不递减;
- 唯一允许的相邻重复位姿是合法换向对;
- 方向段完整覆盖粗路径;
- 换向点的位置、航向和弧长保持成对一致;
- 地图可用于规划;
- 车辆尺寸、安全余量和最大曲率有效;
- 原粗路径在当前地图和车辆参数下仍然安全。
原粗路径复核失败时不得执行平滑,也不得回退。
### 8.2 方向拆分
每个 `PathSegment` 独立处理。方向段首尾点全部是强制锚点。换向两侧的重复位姿分别属于相邻方向段,两个点都必须保留。
### 8.3 重采样
所有方向段先转换为统一的等弧长采样。默认间距为 `0.05 m`,最后一个不足整步的区间保留精确段尾。
重采样不能跨换向点,不能改变段首、段尾的位置和车头航向。
### 8.4 安全移动范围
预处理器根据原路径的保守车体净空和 `MinimumClearanceReserveMeters` 计算每个样点允许的最大移动范围。该范围只用于减少平滑曲线切弯进入障碍物的概率,不构成安全证明。最终安全性只能由完整车体碰撞和扫掠复核确认。
算法生成候选时,每个求值输出点还必须相对同一原始弧长位置的插值参考点复核该范围:`max(0, reference.BodyClearance - MinimumClearanceReserveMeters)`。参数映射统一使用方向段局部原始弧长:完整方向段的归一化参数 `u∈[0,1]` 映射为 `s_ref=u×L_original`;局部窗口参数 `t∈[0,1]` 映射为 `s_ref=s_entry+t×(s_exit-s_entry)`;分段五次区间采用相同的结点弧长插值。禁止使用原始点索引比例代替弧长映射。
求值点超限属于可重试的几何不可行:该次尝试不得发布候选几何,但运行器必须继续下一个较低强度。禁止把单个求值点投影或硬裁剪回允许范围,因为这会在原折点附近破坏曲线切线连续性。该候选可行性门槛不替代最终的完整车体碰撞和扫掠复核。
## 9. 三种平滑方法
### 9.1 三次 B 样条
每个方向段使用夹持三次 B 样条近似:
- 固定段首和段尾位置;
- 使用行进方向切向约束段首和段尾;
- 不把换向两侧合并成一条曲线;
- 通过平滑强度控制控制点对原粗路径的贴合程度;
- 输出后统一按弧长重采样。
该方法是普通长路径段的主要候选,优点是整体连续、控制点数量适中;风险是绕障拐角处可能切弯。
### 9.2 局部三次 Bézier
先从统一重采样路径中识别超过配置航向变化阈值的局部转折区域,再使用三次 Bézier 替换该区域:
- 区域外路径保持不变;
- 每个局部连接固定入口和出口位置;
- 控制柄沿入口和出口切向;
- 相邻局部区域重叠时先合并,避免重复处理;
- 直线和微小转折不创建无意义 Bézier 段。
该方法计算最快且局部可控,但只能保证所设计连接阶次内的连续性,整体曲率质量需要通过统一指标判断。
### 9.3 分段五次多项式
五次方法采用分段二维 Hermite 形式,而不是用单条五次多项式拟合完整方向段:
- 每个子段约束首尾位置;
- 首尾一阶导数由行进切向确定;
- 首尾二阶导数由相邻曲率估计并进行连续化;
- 共享内部结点的一阶和二阶导数保持一致;
- 固定完整方向段的首尾位置、航向和曲率约束。
该方法追求二阶连续,适合终端连接和曲率连续性要求较高的参考线,但参数敏感且更容易在短段上数值退化。
## 10. 几何分析
原粗路径、三种候选和平滑回退路径全部通过同一个 `PathGeometryAnalyzer`。分析器逐方向段计算,禁止跨换向点差分。
分析器负责:
1. 按位置重新计算累计弧长;
2. 根据行进切向和方向恢复车头航向;
3. 连续展开航向;
4. 计算几何曲率和车辆曲率;
5. 计算曲率总变化和曲率变化能量;
6. 生成完整路径和方向段索引。
`CoarsePathPoint.VehicleCurvature` 仍保留为粗规划诊断,但算法比较不直接拿它与重新计算的平滑曲率混用。
## 11. 平滑安全复核
`SmoothedPathValidator` 必须独立复核:
- 所有点和派生数值有限;
- 起点、终点位置和车头航向与粗路径对应端点一致;
- 每个换向点的位置和车头航向不变;
- 前进/倒车段数量、顺序和边界拓扑不变;
- 累计弧长严格增加,合法换向重复点除外;
- `abs(VehicleCurvature)` 不超过车辆最大曲率;
- 每个点的扩大车体无碰撞且位于地图内;
- 相邻点之间的完整车体扫掠无碰撞;
- 发布的 `BodyClearance` 不高于复核得到的保守净空;
- 平滑方向段完整覆盖输出。
验证器复用现有车辆足迹和碰撞语义,不能引入另一套障碍膨胀规则。
## 12. 有限安全回退
每种方法先按 `SmoothingStrength` 运行。候选不通过复核或算法内的可动范围门槛时,依次使用 `RetryStrengthScales` 降低平滑强度。内部算法结果必须区分:
- `Success`:产生完整有限候选,进入统一分析和安全复核;
- `RetryableInfeasible`:可动范围、局部几何约束或安全复核不满足,记录原因后尝试下一强度;
- `Failed`:非法数值、奇异系数、输入契约错误或无法构造完整候选,立即停止该方法。
```text
默认强度
↓ 可重试不可行
0.75 倍
↓ 可重试不可行
0.50 倍
↓ 可重试不可行
0.25 倍
↓ 可重试不可行
该方法标记 Infeasible
```
重试次数、采用强度和每次失败原因必须进入诊断。达到有限次数后必须停止,不能无限调参。算法返回 `Failed` 时不得用降低强度掩盖数值或契约错误。
正式单算法服务只有在 `AllowFallbackToCoarsePath = true` 且原粗路径重新通过完整安全复核时,才能返回 `FallbackToCoarsePath`。比较实验中的方法失败不能被回退结果伪装成该方法成功。
## 13. 质量指标
每条原始或平滑路径都计算:
```text
Feasible
PathLengthMeters
MaximumAbsoluteVehicleCurvaturePerMeter
RootMeanSquareVehicleCurvaturePerMeter
TotalAbsoluteCurvatureVariationPerMeter
CurvatureVariationEnergy
MinimumBodyClearanceMeters
ComputationElapsed
RetryCount
AcceptedStrength
```
曲率变化指标逐方向段计算后累加,不跨换向点产生虚假的曲率跳变。
正式单算法结果中的 `ComputationElapsed` 记录一次规范执行的耗时,不参与跨方法推荐。离线比较的耗时排序使用独立基准流程:每个场景和方法先预热 `1` 次,再计时 `5` 次;五次输出必须具有相同状态、路径点数和稳定几何摘要,否则该方法标记为非确定性诊断失败。稳定几何摘要按状态、方向段元数据和所有路径点字段的 IEEE 754 位模式顺序生成 SHA-256,不使用进程相关的 `GetHashCode()`。排名使用五次计时的中位数,预热和计时运行均不得改变正式比较结果。
比较条目另外保存 `MeasuredElapsedMilliseconds[5]``MedianElapsedMilliseconds``TimingSampleCount=5``IsDeterministic`CSV 和排序使用 `MedianElapsedMilliseconds`,不能把正式单次 `ComputationElapsed` 混作基准中位数。
每个平滑结果还报告相对于原粗路径的:
```text
LengthChangePercent
PeakCurvatureChangePercent
CurvatureVariationChangePercent
MinimumClearanceChangeMeters
```
任何分母接近零的相对指标必须使用明确定义的绝对值替代,不能产生 NaN 或无穷值。
## 14. 方法排序
方法选择采用可解释的字典序,不使用隐藏的加权总分。对全部必选场景按以下聚合值依次比较:
1. 安全可行场景数量更多;
2. 曲率变化能量的场景中位数更低;
3. 各场景最大曲率利用率中的最坏值更低;
4. 各场景最小净空损失中的最坏值更小;
5. 路径长度增量的场景中位数更小;
6. 计算耗时的场景中位数更短。
完全相同时使用稳定的方法枚举顺序打破平局,并在报告中显示所有原始指标。若没有方法通过必选场景,比较结果不推荐默认方法。
## 15. 场景体系
### 15.1 端到端场景
端到端组使用现有 `CoarsePathScenarioFactory` 的固定基准入口,不读取实时 AMR
| 场景 | 用途 |
| --- | --- |
| `ExplicitEmpty` | 直线不应被无意义扭曲 |
| `RectangleDetour` | 验证绕障转折和平滑切弯 |
| `ManualAndTwoLeg` | 验证多来源障碍和复杂路径 |
| `ReverseGearSwitch` | 验证方向分段和换向点保护 |
`CacheHit` 不增加新的平滑几何,不进入算法比较。`NoFeasiblePath` 没有成功粗路径,只保留为上游粗路径测试。
### 15.2 快速粗路径夹具
快速夹具覆盖:
```text
straight
single-turn
s-bend
large-heading-change
rectangle-detour
multi-obstacle-detour
narrow-corridor
forward-reverse-switch
```
每个夹具保存:
- 稳定的英文场景 ID 和版本;
- 对应的固定地图重建信息;
- 车辆参数;
- 粗路径点和方向段;
- 生成该路径时的粗规划配置指纹;
- 期望的起终点、方向段数和换向点数。
快速实验重建地图但不调用 `HybridAStarPlanner`。夹具指纹与当前预期不一致时返回“夹具已过期”,不能静默继续,也不能在普通测试中自动覆盖夹具。
夹具更新必须通过显式开发入口完成,并在更新后运行端到端复核。
## 16. 比较报告
### 16.1 四条曲线
正常场景同时比较:
1. 原始粗路径;
2. 三次 B 样条;
3. 局部三次 Bézier
4. 分段五次多项式。
路径图和曲率图使用相同的四方法顺序。某种方法产生完整但不可行的候选时,实验报告可以绘制其候选并在违规位置标记叉号,同时明确标注“不可行”。该候选只能存在于比较报告的只读调试数据中,不能进入正式 `PathSmoothingResult.Path`
算法内可行性门槛或数值构造失败导致没有完整候选时,保留图例和指标行,显示“无有效曲线”,不得伪造曲线。
### 16.2 IEEE 风格版式
每个场景输出一张双栏宽度图:
```text
物理尺寸:7.16 × 5.2 in
PNG4296 × 3120 px
分辨率:600 dpi
```
布局:
```text
┌─────────────────────┬───────────────┐
│ (a) 四条路径对比 │ (b) 四条曲率 │
│ 地图、障碍物、起终点 │ κ(s) 与限制线 │
│ ├───────────────┤
│ │ (c) 指标表 │
└─────────────────────┴───────────────┘
```
- 路径面板约占宽度的 60%
- 路径面板强制 X/Y 等比例;
- 曲率横轴使用各路径自身的累计弧长 `s (m)`
- 曲率面板显示车辆最大正、负曲率限制;
- 不放大标题,只使用紧凑的 `(a)``(b)``(c)`
- 路径面板不使用背景网格;
- 曲率面板只保留零线、必要刻度和曲率限制线;
- 指标表小数点对齐,失败项显示状态而非虚构数值;
- 图例在所有场景中位置、顺序、颜色和线型一致。
IEEE 官方图形指南建议使用颜色和线型共同编码、保持字体和字号一致,并优先使用高分辨率或矢量图:
- https://conferences.ieeeauthorcenter.ieee.org/write-your-paper/improve-your-graphics/
- https://journals.ieeeauthorcenter.ieee.org/create-your-ieee-journal-article/create-graphics-for-your-article/resolution-and-size/
### 16.3 固定色卡
采用色盲友好的科学绘图色卡:
| 内容 | 颜色 | 线型 |
| --- | --- | --- |
| 原始粗路径 | `#4D4D4D` 深灰 | 虚线 |
| 三次 B 样条 | `#0072B2` 深蓝 | 实线 |
| 局部三次 Bézier | `#D55E00` 朱红 | 点划线 |
| 分段五次多项式 | `#009E73` 蓝绿色 | 点线 |
| 曲率上下限 | `#CC79A7` 紫红 | 细虚线 |
| 障碍物 | `#D9D9D9` 填充、`#666666` 描边 | 实线 |
起点使用带黑色边框的黄色圆形,终点使用带黑色边框的紫色菱形。图中不使用渐变、阴影或依赖透明度才能辨认的叠色。
### 16.4 字体、字号和防乱码
用户指定:
```text
中文:SimSun(宋体)
英文、数字、希腊字母和公式:Times New Roman
```
绘图实现必须:
1. 在导出前按准确字体族名检查两种字体;
2. 将混合文本按中文和非中文字符片段拆分;
3. 使用字体度量统一片段基线;
4. 使用同一排版模型生成 SVG 和 PNG;
5. 字体缺失时返回明确的 `FontUnavailable` 导出失败,不能静默替换;
6. SVG 使用 UTF-8 XML
7. CSV 使用带 BOM 的 UTF-8
8. PNG 写入并验证对应 600 dpi 的物理分辨率元数据;
9. 使用稳定英文文件名,中文只出现在图内和 CSV 内容中。
最终物理尺寸下建议:
```text
坐标刻度:9 pt
坐标标题、图例和表格正文:9 pt
分图编号:10 pt
```
英文 Times New Roman 属于 IEEE 推荐字体。由于宋体不是 IEEE 通用英文字体清单的一部分,它只用于满足本项目中文图注需求;英文、数字和数学字符仍使用 Times New Roman。
字体检查只能保证生成机器正确渲染。文本型 SVG 是可编辑母版,仅保证在安装了 `SimSun``Times New Roman` 的环境中保持原排版;600 dpi PNG 是无需字体依赖的便携预览。若用于 IEEE 正式投稿,必须在模块外将已验证的 SVG 转换为 IEEE 接受的 PDF/EPS,并嵌入字体或将文字转换为轮廓;本阶段不把 SVG 声明为可直接投稿格式。
### 16.5 输出格式
每个场景输出:
```text
<scenario-id>-comparison.svg
<scenario-id>-comparison.png
<scenario-id>-metrics.csv
```
SVG 是可编辑矢量母版,600 dpi PNG 用于无需字体依赖的直接查看和现有工作流,CSV 保存每种方法的完整指标。报告输出属于生成产物,不提交到源码目录。IEEE 投稿用 PDF/EPS 的字体嵌入或轮廓化转换属于显式发布步骤,不在本模块中静默完成。
绘图使用独立的不可变 `SmoothingFigureModel`。SVG 和 PNG 渲染器都消费该模型,以保证面板范围、曲线、颜色、字体和文本一致。
## 17. 错误隔离
- 单个算法的预期失败只影响对应比较项;
- 单个算法发生数值异常时,比较服务记录算法名、异常类型和安全诊断,然后继续其他算法;
- 取消优先于继续比较,取消后不再启动后续算法;
- 报告导出失败不改变平滑比较结果;
- 某个场景所有算法失败时仍可生成只包含原粗路径、失败状态和诊断的报告;
- 正式服务不捕获并隐藏调用方编程错误,但所有输入契约错误必须转换为稳定状态和诊断;
- 所有错误消息包含场景、方法和处理阶段,不依赖堆栈才能定位。
## 18. 测试设计
### 18.1 契约测试
- 空路径、空方向段、空地图、空车辆和空配置;
- NaN、无穷值、负弧长和非法曲率;
- 非法重复点和不完整方向段;
- 成功和失败结果的只读集合;
- 失败状态不得携带可执行路径;
- 取消不得发布部分路径。
### 18.2 几何分析测试
- 直线的曲率为零;
- 已知半径圆弧的几何曲率和车辆曲率符合方向语义;
- 前进和倒车使用正确的曲率符号;
- 航向跨越 `-π/π` 时连续展开;
- 不跨换向重复点计算差分;
- 等弧长重采样保留精确段尾。
### 18.3 算法性质测试
三种方法分别验证:
- 直线路径不产生无意义横向偏移;
- 方向段首尾位置和航向保持;
- 换向点的位置、航向和顺序保持;
- 输出点数和间距符合配置;
- 重试强度严格递减且次数有限;
- 可动范围超限进入下一强度,数值失败立即停止;
- 非均匀原始采样仍按方向段局部弧长映射参考点;
- 修改请求构造后的外部配置不影响内部算法快照;
- 短段和退化控制点产生明确失败而非 NaN。
### 18.4 安全复核测试
- 平滑切入障碍物时拒绝;
- 相邻点之间扫掠碰撞时拒绝;
- 车体越过地图边界时拒绝;
- 最大车辆曲率超限时拒绝;
- 净空被高估时拒绝;
- 方向段或换向结构改变时拒绝;
- 原粗路径复核失败时禁止回退。
### 18.5 比较和夹具测试
- 三种方法接收同一份预处理输入;
- 单个方法失败不影响其他方法;
- 排序规则按固定字典序执行;
- 每个方法预热一次、计时五次并使用中位数,非确定性输出不得参与推荐;
- 没有可行方法时不推荐默认方法;
- 快速夹具路径不调用 Hybrid A*;
- 配置指纹变化会使夹具明确过期;
- 四个现有端到端场景保持预期成功和方向结构。
### 18.6 绘图测试
- 固定色值、线型、图例顺序和面板编号;
- 路径图 X/Y 比例相等;
- 曲率限制线数值正确;
- `SimSun``Times New Roman` 检测;
- `粗路径 κ(s) X (m) −π` 等代表性混合文本完整渲染;
- SVG 为有效 UTF-8,包含预期字体和所有四种方法;
- PNG 具有 `4296 × 3120` 像素、正确 PNG 结构、CRC 和 600 dpi 元数据;
- CSV 具有 UTF-8 BOM、稳定列顺序和不依赖区域设置的小数格式;
- 不使用依赖平台抗锯齿细节的脆弱逐像素金图测试;
- 至少对直线、绕障和换向三张代表图执行人工视觉检查,确认无乱码、遮挡、裁切和间距失衡。
## 19. 实施边界
第一轮实施只建立本设计中的接口、三种算法、统一验证、快速夹具和报告。正式 `MovementTest` 集成是后续独立工作,在默认方法通过离线比较并得到明确推荐后进行。
实验台不得让 `CoarsePath` 依赖 `PathSmoothing`。正式串联由更上层门面完成:
```text
CoarsePathPlanningService
↓ 成功 PlanningResult
PathSmoothingService
↓ 成功或明确回退
后续 SQP
```
## 20. 验收标准
设计实施完成需满足:
1. 成功粗路径能通过单一公共接口得到不可变平滑路径结果。
2. 三种方法对同一粗路径独立运行,不串联、不修改输入。
3. 起终点、方向段和换向结构在所有成功结果中保持。
4. 所有成功结果通过完整车体碰撞、边界、曲率和输出契约复核。
5. 不可行候选不会进入正式可执行路径。
6. 八个快速夹具无需运行 Hybrid A* 即可完成三算法比较。
7. 四个现有端到端场景能够从建图和 Hybrid A* 连接到平滑比较。
8. 每个场景生成包含四路径、四曲率和指标表的 SVG、600 dpi PNG 与 UTF-8 CSV,并明确 SVG/PDF 的投稿边界。
9. 图内中文使用宋体,英文、数字和数学字符使用 Times New Roman,代表性图无乱码、遮挡或裁切。
10. 比较结果使用公开的字典序规则推荐方法;没有合格方法时明确不推荐。
11. 输出可以作为后续 SQP 的空间参考路径输入,但不提前引入时间、速度或控制字段。
@@ -1,573 +0,0 @@
# 局部 G2 路径预平滑设计
**日期:** 2026-07-30
**状态:** 已完成方案评审;2026-08-01 固定锚点分离导数尺度修订等待书面复核
**范围:** Hybrid A* 粗路径与后续 SQP 路径优化之间的局部预平滑
## 1. 背景与问题
当前 Hybrid A* 使用离散曲率等级生成运动基元。单个运动基元是直线或恒曲率圆弧,路径在位置和航向上连续,但相邻运动基元的车辆曲率可能直接跳变。
当前典型配置为:
- 运动基元长度:`0.50 m`
- 路径点间距:通常不大于 `0.025 m`
- 最大车辆曲率:约 `0.8333 1/m`
- 相邻曲率等级的典型跳变量:约 `0.4167 1/m`
因此粗路径视觉上可能已经比较平缓,但仍可能要求车轮转角瞬间改变。当前 PathSmoothing 中的算法不能稳定解决该问题:
- 局部三次 Bézier 通过相邻线段夹角检测拐点。Hybrid A* 相邻采样点的航向变化很小,算法经常检测不到需要处理的运动基元边界。
- 三次 B 样条和旧分段五次曲线可能修改整段路径、切弯或产生曲率过冲。
- 当前运行器只检查候选是否安全,没有要求候选在平滑质量上优于粗路径。
- 无变化甚至更差的候选仍可能被报告为成功。
- 粗路径和平滑路径没有始终使用同一个几何分析器计算曲率,比较基线不公平。
## 2. 目标
新增一条单一主路线:使用局部、分段、二维参数化五次 Hermite 曲线,低成本地消除容易处理的曲率跳变,并将所得完整路径作为 SQP 的更好初值。
具体目标:
1. 直接检测同一行驶方向段内部的运动基元曲率跳变。
2. 只修改突变点附近的局部区域,保留本来已经平滑的恒曲率圆弧。
3. 在局部曲线与原粗路径连接处保证位置、切线和曲率连续,即达到 G2 连续。
4. 对重叠的局部窗口进行联合处理,避免独立平滑后再次产生接缝。
5. 对每个候选同时执行碰撞、车辆几何、偏移和质量验证。
6. 允许只完成部分区域;部分改善路径可以作为 SQP 初值,但不能伪装成全部完成。
7. 输出可靠的空间路径、曲率和曲率对弧长的导数,不输出速度或时间参数。
## 3. 非目标
本次工作不包含:
- 修改 Hybrid A* 的搜索模型、几何中心参考点或运动基元生成方法。
- 实现或修改 SQP 路径优化器。
- 实现速度规划。
- 计算四个车轮的轮速或转角。
- 根据最大转向角速度直接输出每点速度上限。
- 修复或重写现有三次 B 样条、局部三次 Bézier和旧分段五次算法。
- 将预平滑输出直接定义为最终控制路径。
- 第一版约束转向角加速度或要求 `dκ/ds` 自身连续。
现有三种平滑算法可以保留用于离线比较,但不参与本主路线向 SQP 发布的路径选择。
## 4. 系统定位与数据流
主数据流为:
```text
Hybrid A* 粗路径
输入标准化与行驶方向硬分段
曲率跳变检测
局部窗口规划与重叠窗口合并
局部 G2 五次 Hermite 候选生成
候选硬约束验证与质量比较
成功区域替换、失败区域保留粗路径
完整路径统一重采样与全局复验
Complete / PartialImprovement / NotNeeded / Unchanged / Failed
SQP 初始路径
最终验证、速度规划和控制
```
对于通过输入复核的有效粗路径,PathSmoothing 始终向下游提供一条完整路径。局部处理失败不能造成路径缺口。若输入粗路径本身无效,则返回 `Failed` 且不发布路径。
## 5. 输入契约
### 5.1 必需输入
每个粗路径点至少包含:
- 位置 `x, y`
- 车辆航向 `heading`
- 行驶方向 `TravelDirection`
- 累计弧长 `s`
- Hybrid A* 车辆曲率 `κ_vehicle`
- 换向点标记
- 车体净空或可供重新检查净空的地图上下文
请求还应包含:
- Hybrid A* 已生成的方向段集合
- 规划栅格地图
- 车辆外形、安全边距和最大曲率
- 局部 G2 预平滑配置
### 5.2 起点曲率修正
当前 `CoarsePathAssembler` 在存在运动基元时使用第一条运动基元曲率作为起点曲率,覆盖了回溯结果中的真实起始曲率。这会隐藏车辆当前转角与第一条运动基元之间的初始曲率跳变。
本设计要求做一个最小输入契约修正:
- 粗路径首点保留请求中的真实 `StartVehicleCurvature`
- 第一条运动基元仍保留自己的曲率。
- 如果二者不同,预平滑器将其识别为方向段起点处的单侧曲率过渡。
此修正不改变 Hybrid A* 搜索、参考点或运动基元。
## 6. 两层分段
### 6.1 第一层:行驶方向硬分段
前进段和倒车段独立处理:
```text
前进段 | 停车换向点 | 倒车段
```
规则:
- 不跨换向点生成平滑曲线。
- 换向点的位置和车辆航向保持不变。
- 换向点两侧的曲率不要求连续。
- 换向点两侧不计算跨段 `dκ/ds`
- 倒车时按照运动方向构造几何切线;不能直接把车辆航向当成沿路径前进方向的切线。
### 6.2 第二层:方向段内部的局部过渡窗口
在同一方向段内检测相邻运动基元的曲率跳变,并围绕每个跳变建立局部过渡窗口。窗口必须跨越跳变点,不能在跳变点将路径切开后分别平滑。
窗口外的粗路径保持不变。重叠窗口合并成一个联合平滑区域。
## 7. 曲率跳变检测
检测使用 Hybrid A* 提供的相邻运动基元车辆曲率,不使用相邻采样点夹角。
有效检测阈值为:
```text
max(
AbsoluteCurvatureJumpFloorPerMeter,
CurvatureJumpRatioOfMaximum * MaximumVehicleCurvaturePerMeter)
```
默认值:
- `AbsoluteCurvatureJumpFloorPerMeter = 0.001 1/m`
- `CurvatureJumpRatioOfMaximum = 0.05`
当最大曲率为 `0.8333 1/m` 时,有效阈值约为 `0.0417 1/m`。当前典型的 `0.4167 1/m` 跳变会被稳定检测,浮点噪声不会生成平滑事件。
换向点两侧的曲率差不参与检测。
## 8. 自适应过渡窗口
### 8.1 默认长度
单个曲率跳变事件使用以下总窗口长度:
- 最小:`0.20 m`
- 首选:`0.50 m`
- 最大:`0.80 m`
这里的长度是突变点左右合计的总长度,不是每一侧的长度。窗口可以左右不对称。
### 8.2 可用范围
窗口不得越过:
- 当前行驶方向段的硬边界
- 换向点
- 路径全局起点和终点
窗口可根据两侧可用路径长度、相邻事件和候选验证结果自动调整。单个事件的窗口不超过 `0.80 m`;多个事件的窗口合并后,联合区域总长度可以超过 `0.80 m`
### 8.3 优先级
窗口策略遵循:
1. 保证完整车体无碰撞和最低安全余量。
2. 保持粗路径拓扑、方向和硬锚点。
3. 达到最低平滑改善要求。
4. 在达到要求的候选中选择偏离粗路径最小的结果。
5. 空间不足时允许后续速度规划降速,不为了保持高速强行扩大窗口。
## 9. 局部 G2 五次 Hermite 技术
### 9.1 曲线表示
使用二维参数曲线:
```text
r(u) = (x(u), y(u)), u ∈ [0, 1]
```
`x(u)``y(u)` 分别为五次多项式。不能使用 `y = f(x)`,以免无法正确处理竖直、回转和倒车路径。
### 9.2 边界状态
一个 Hermite 片段的两端状态包含:
- 位置 `P`
- 沿运动方向的单位切线 `T`
- 法向 `N`
- 有符号几何曲率 `κ_geometric`
- 参数导数尺度 `λ`
端点导数按以下关系构造:
```text
r'(u_endpoint) = λ T
r''(u_endpoint) = λ² κ_geometric N
```
车辆曲率与几何曲率的符号转换必须统一考虑前进和倒车方向。端点位置、一阶导数和二阶导数确定后,五次 Hermite 系数唯一确定。
### 9.3 单个曲率事件
窗口外端点从稳定的原始圆弧读取位置、切线和曲率。原运动基元边界的位置和航向均为内部硬边界值,不允许生成位置偏移候选。跳变两侧的两个曲率值替换为一个共享的内部目标曲率。
内部目标曲率首先按照事件左右可用长度做距离加权插值:
```text
κ_shared =
κ_left + (κ_right - κ_left)
* left_available_length
/ (left_available_length + right_available_length)
```
该距离加权值作为固定的内部共享曲率。有限候选只改变窗口和导数尺度,不再同时改变共享曲率。窗口左片段和右片段在内部锚点共享位置、单位切线方向和曲率,使内部连接达到 G2。按照 `2026-08-01-local-g2-split-derivative-scale-recovery-design.md`,左片段的进入导数尺度与右片段的离开导数尺度可以不同;算法不额外要求参数速度连续或参数 C2。
方向段起点处的真实起始曲率跳变只有单侧可用空间,直接以真实起始状态为左边界、稳定运动基元状态为右边界生成候选。
### 9.4 多事件联合区域
当窗口重叠时:
- 合并为一个联合区域。
- 保留各运动基元边界的位置和航向硬约束,不生成内部位置偏移候选。
- 每个内部锚点只允许一个共享曲率值。
- 相邻五次 Hermite 片段共享位置、单位切线方向和几何曲率;左右参数导数长度可以不同。
- 内部共享曲率和分离的进入/离开导数尺度通过有限、确定性的候选组合选取。
联合处理避免先分别生成曲线再拼接所产生的新曲率接缝。
### 9.5 第一版连续性范围
第一版要求:
- 位置连续
- 航向连续
- 曲率连续
- `|dκ/ds|` 有限并得到量化改善
第一版不要求 `dκ/ds` 连续,因为当前车辆配置只提供最大转向角速度,没有提供最大转向角加速度。
## 10. 候选生成
每个独立或联合区域只进行有限、确定性的候选搜索,不运行新的通用优化器。
候选维度包括:
- 窗口总长度
- 左右窗口长度分配
- 端点和内部锚点的导数尺度
内部共享曲率使用第 9.3 节的距离加权值,不作为当前恢复候选维度。
约束:
- 每个区域最多评估 `12` 个候选。
- 候选顺序固定。
- 重复运行同一输入必须生成相同候选和相同结果。
- 达到候选上限仍无合格结果时,该区域保留粗路径并记录失败原因。
通用 `SmoothingStrength` 和旧 `RetryStrengthScales` 不控制新的局部 G2 算法。新算法使用语义明确的 `LocalG2QuinticOptions`
## 11. 候选硬约束验证
任何一项失败都直接拒绝候选:
1. 所有数值有限,无 NaN 或无穷大。
2. 曲线无尖点、回环或错误的局部运动方向。
3. 行驶方向和换向拓扑不变。
4. 起点、终点和换向硬锚点不移动。
5. 窗口外端点的位置、切线和曲率连接满足数值容差。
6. 完整车体扫掠无碰撞。
7. 碰撞检查步长不超过 `0.025 m`
8. 保留现有默认额外净空 `0.02 m`
9. 车辆曲率绝对值不超过车辆最大曲率加数值容差。
10. 候选曲率不超出该区域原始最小曲率和最大曲率范围加数值容差。
11. 任意候选点到原粗路径局部折线的最近距离不超过 `0.10 m`
最大偏移使用候选点到对应粗路径窗口折线的单向最大最近距离计算。完整车体碰撞检查仍是安全性的最终依据。
## 12. 统一几何分析与质量门槛
### 12.1 公平基线
粗路径和所有候选必须:
- 按同一个目标弧长间距重采样,默认 `0.025 m`
- 使用同一个几何分析器重新计算航向、几何曲率和车辆曲率
- 不跨换向点计算曲率变化
Hybrid A* 保存的曲率用于检测运动基元事件和提供边界条件,但不能单独作为粗路径质量统计,而候选使用几何重算结果。
### 12.2 主要指标
区域峰值曲率变化率:
```text
PeakCurvatureGradient =
max |Δκ_vehicle / Δs|
```
成功处理的区域要求:
```text
candidatePeak
<= rawPeak * (1 - MinimumPeakGradientImprovementRatio)
```
默认:
- `MinimumPeakGradientImprovementRatio = 0.20`
即峰值至少降低 `20%`
### 12.3 次要指标
“曲率变化代价”定义为:
```text
CurvatureVariationCost =
Σ (Δκ_vehicle / Δs)² Δs
```
它不是车辆能耗,用于惩罚整段路径中的快速变化和反复振荡。
成功处理的区域以及最终完整路径要求:
```text
candidateCost
<= rawCost * (1 + MaximumVariationCostRegressionRatio)
```
默认:
- `MaximumVariationCostRegressionRatio = 0.02`
即只允许 `2%` 的数值容差,不允许实质恶化。
### 12.4 局部与全局评价
`PartialImprovement` 可能保留未处理的原始突变,因此不能要求整条路径的峰值一定下降。评价规则为:
- 每个被替换区域独立达到峰值改善和变化代价门槛。
- 未处理区域保留粗路径并报告原因。
- 完整路径的曲率变化代价不得实质恶化。
- 至少一个区域真实改善,才允许返回 `PartialImprovement`
## 13. 候选选择
不能接受当前启用层中第一个通过安全检查的候选。当前层的所有有限候选评估完成后,按以下规则选择;固定锚点分离导数尺度设计允许第一级已有合格项时不启用第二级:
1. 仅保留通过全部硬约束的候选。
2. 仅保留达到最低质量改善门槛的候选。
3. 优先选择最大路径偏移最小的候选。
4. 偏移在数值容差内相同时,选择峰值 `|dκ/ds|` 更小的候选。
5. 峰值仍相同时,选择曲率变化代价更小的候选。
6. 仍相同时,选择路径长度变化更小的候选。
7. 完全相同时,按固定候选生成顺序选择。
该排序表达“达到足够平滑后,优先最小修改粗路径”。
## 14. 拼接、重采样与全局复验
区域处理完成后:
1. 成功区域替换为局部 G2 曲线。
2. 无合格候选的区域保留原粗路径。
3. 合并为完整路径,去除接缝重复点。
4. 按方向段分别以 `0.025 m` 目标间距重采样。
5. 统一重算航向、几何曲率、车辆曲率和 `dκ/ds`
6. 对完整路径重新执行数值、方向、曲率、偏移、碰撞和净空验证。
7. 如果某个替换区域导致最终全局复验失败,回退该区域并重新复验。
8. 如果无法得到通过全局复验的完整路径,返回经过复核的原粗路径并报告 `Failed`
## 15. 输出契约
### 15.1 完整空间参考路径
输出路径点至少包含:
- `X`
- `Y`
- `Heading`
- `TravelDirection`
- `AccumulatedArcLengthMeters`
- `GeometricCurvaturePerMeter`
- `VehicleCurvaturePerMeter`
- `VehicleCurvatureDerivativePerSquareMeter`
- 换向点或方向段边界标记
`dκ/ds` 只在单个方向段内部计算。换向点两侧分别使用单侧值或显式标记为段边界,绝不跨换向点做差分。
输出不包含:
- 每点最大速度
- 时间戳
- 线速度或加速度
- 轮速
- 车轮转角
速度规划根据最终 SQP 路径、实际最高速度、轴距和最大转向角速度重新计算速度约束。当前约定的最大转向角速度 `60°/s` 和当前最大线速度 `1 m/s` 不写入 PathSmoothing 几何算法。
### 15.2 总体状态
状态定义:
- `Complete`:所有检测到的同方向移动中曲率跳变均已处理。
- `PartialImprovement`:至少一个区域有效改善,至少一个区域保留粗路径;完整输出安全,可作为 SQP 初值。
- `NotNeeded`:不存在达到检测门槛的同方向曲率跳变。
- `Unchanged`:存在需要处理的曲率跳变,但没有任何区域获得安全且有效的改善;SQP 使用原粗路径。
- `Failed`:输入、数值计算或最终完整路径复验失败。原粗路径通过复核时回退到原粗路径;原粗路径无效时不发布路径。
`Complete``PartialImprovement` 都不能由无变化路径产生。
### 15.3 区域报告
每个检测事件或联合区域输出:
- 方向段编号
- 原始路径索引和弧长范围
- 原始曲率跳变列表
- 计划和实际窗口长度
- 左右窗口长度
- 生成候选数量
- 选中候选编号
- 区域状态
- 未处理或失败原因
- 原始与结果的峰值 `|dκ/ds|`
- 原始与结果的曲率变化代价
- 最大路径偏移
- 最小车体净空
- 最大车辆曲率
区域报告用于诊断和测试,不要求 SQP 依赖其内部结构。
## 16. 配置
保留现有通用安全配置:
- `OutputSpacingMeters = 0.025`
- `MaximumCollisionCheckStepMeters = 0.025`
- `MinimumClearanceReserveMeters = 0.02`
- `AllowFallbackToCoarsePath = true`
SQP 主链路要求 `AllowFallbackToCoarsePath = true`,以保证有效粗路径在局部预平滑没有改善时仍可继续作为 SQP 初值。该配置保留是为了兼容现有公共 API。
新增 `LocalG2QuinticOptions`
| 配置 | 默认值 | 含义 |
|---|---:|---|
| `MinimumWindowLengthMeters` | `0.20` | 单事件最小总窗口长度 |
| `PreferredWindowLengthMeters` | `0.50` | 单事件首选总窗口长度 |
| `MaximumWindowLengthMeters` | `0.80` | 单事件最大总窗口长度 |
| `MaximumDeviationMeters` | `0.10` | 候选相对粗路径的最大偏移 |
| `AbsoluteCurvatureJumpFloorPerMeter` | `0.001` | 曲率跳变检测绝对下限 |
| `CurvatureJumpRatioOfMaximum` | `0.05` | 相对车辆最大曲率的检测比例 |
| `MinimumPeakGradientImprovementRatio` | `0.20` | 区域峰值最小改善比例 |
| `MaximumVariationCostRegressionRatio` | `0.02` | 曲率变化代价数值容差 |
| `MaximumCandidatesPerRegion` | `12` | 单区域候选数量上限 |
所有配置必须有限且满足顺序和范围约束。无效配置在开始处理前返回明确错误,不能静默修正。
## 17. 错误处理与回退
- 单个候选失败:继续评估该区域其他候选。
- 单个区域无合格候选:保留该区域粗路径,记录原因。
- 部分区域成功:全局复验通过后返回 `PartialImprovement`
- 所有区域均无改善:返回 `Unchanged` 和原粗路径。
- 无需处理:返回 `NotNeeded` 和原粗路径。
- 输入或配置无效:返回 `Failed`,不运行候选生成。
- 完整路径复验失败:逐区域回退;仍失败则返回经过复核的原粗路径和 `Failed`
- 如果原粗路径本身未通过输入安全复核,不允许把它作为“安全回退”发布;返回 `Failed` 并说明原始路径无效。
失败原因使用稳定枚举,并可附带人类可读说明,便于测试和日志统计。
## 18. 测试与验收
### 18.1 单元测试
必须覆盖:
- 曲率跳变检测和浮点噪声过滤
- 不跨换向点检测
- 起点真实曲率与第一运动基元曲率之间的单侧过渡
- 对称和不对称窗口
- 窗口长度裁剪
- 重叠窗口合并
- 前进与倒车的切线和曲率符号
- 五次 Hermite 端点位置、切线和曲率
- 多片段内部 G2 连接
- 尖点、回环、过冲和错误方向拒绝
- 最大偏移计算
- 统一几何分析基线
- 峰值改善和变化代价门槛
- 候选确定性排序
- 局部失败与局部回退
- 拼接去重和完整路径段覆盖
- 换向点不跨段计算 `dκ/ds`
- NaN、无穷和无效配置
### 18.2 现有场景验收
- `Straight``NotNeeded`,输出几何与粗路径一致。
- `SingleTurn``Complete`,曲率突变全部处理。
- `LargeHeadingChange``Complete`,无曲率过冲。
- `SBend``Complete``PartialImprovement`,无新增曲率振荡。
- `RectangleDetour``Complete``PartialImprovement`,不切入障碍物。
- `MultiObstacleDetour``Complete``PartialImprovement`,满足净空要求。
- `ReverseGearSwitch`:不跨换向点平滑,换向位置和航向保持不变。
### 18.3 所有输出的共同断言
- 完整车体扫掠无碰撞。
- 最小净空满足配置。
- 最大车辆曲率不超限。
- 最大路径偏移不超过 `0.10 m`
- 每个成功区域的峰值 `|dκ/ds|` 至少降低 `20%`
- 曲率变化代价不实质恶化。
- 起点、终点、换向点和方向拓扑保持不变。
- 输出路径完整,没有重复接缝、NaN 或无穷值。
- 相同输入重复运行的状态、路径和报告一致。
- `Complete``PartialImprovement` 至少包含一个真实改善区域。
- 无变化或更差的候选不能报告成功。
### 18.4 构建与回归
实施完成后至少运行:
- PathSmoothing 新增单元验证脚本
- 现有 PathSmoothing 合同、安全、可视化和集成验证
- CoarsePath 起点曲率与输出合同验证
- `dotnet build ClumsyPilot/ClumsyPilot.csproj --no-restore`
旧对比算法允许继续保持原有结果;本任务不以提高旧算法成功率作为验收条件。
## 19. 设计决策摘要
1. Hybrid A* 粗路径保留几何中心模型。
2. 路径按前进/倒车硬分段,换向点允许停车调转角。
3. 曲率跳变是同一方向段内部的运动基元曲率变化,不是换向边界。
4. 主算法是局部、二维、分段五次 Hermite G2 预平滑。
5. 单事件窗口默认 `0.200.80 m`,首选 `0.50 m`,允许不对称。
6. 重叠窗口联合处理。
7. 优先安全和最小偏移,达到最低改善后不继续追求过度平滑。
8. 最大偏移默认 `0.10 m`
9. 区域峰值 `|dκ/ds|` 至少改善 `20%`,变化代价不得实质恶化。
10. 部分改善是合法的 SQP 初值结果,不是最终控制路径。
11. PathSmoothing 输出空间几何和 `κ、dκ/ds`,速度规划拥有全部速度约束。
12. 现有三种算法暂时保留但不参与本主路线。
@@ -1,38 +0,0 @@
# Path Smoothing Six-Figure Report Design
## Goal
Replace the current one-file, three-panel path-smoothing report with six focused, independent point-plot figures for every scenario. Preserve both SVG and 600 dpi PNG export, retain one CSV metrics file, and never connect trajectory samples with lines.
## Output contract
Every scenario directory contains exactly these six figures in both `.svg` and `.png` form:
1. `01-coarse-path-overview`: raw Hybrid A* samples, map obstacles, start and coarse-path endpoint.
2. `02-all-paths-comparison`: raw, B-spline, Bézier, and quintic samples together; no obstacles, start, or goal marker.
3. `03-cubic-bspline-overview`: faded raw samples, B-spline samples, relevant obstacles, start and endpoint.
4. `04-local-cubic-bezier-overview`: faded raw samples, Bézier samples, relevant obstacles, start and endpoint.
5. `05-piecewise-quintic-overview`: faded raw samples, quintic samples, relevant obstacles, start and endpoint.
6. `06-curvature-comparison`: raw and every available smoother's vehicle-curvature samples against arc length.
`comparison.csv` remains the single numerical report. The legacy composite `comparison.svg` and `comparison.png` are no longer emitted.
## Point-only rendering
Each `SmoothingFigurePoint` in a displayed series becomes one circular marker. SVG must not emit a trajectory polyline/path for any figure; PNG must not call a line-drawing API for trajectory samples. Marker size is fixed in report points so 0.025 m samples remain individually visible at 600 dpi. Start and endpoint remain distinct point markers only in figures 1, 3, 4, and 5.
Raw samples are dark gray, B-spline samples blue, Bézier samples orange, and quintic samples green. A method with no geometry has no markers but remains represented by an `Infeasible` or `Failed` status in that figure's legend.
## Framing and annotation
Every overhead figure derives its world bounds from the displayed path samples, then adds a fixed 10% padding with a 0.25 m minimum. The X/Y scales are equal. Obstacles are clipped by the panel rather than expanding the camera away from the path. Overhead axes show numeric ticks plus `X (m)` and `Y (m)` labels.
The curvature figure uses `s (m)` horizontally and `κ (m⁻¹)` vertically, with numeric ticks, zero axis, and displayed curvature limits. Every figure owns a compact legend describing its visible series and statuses.
## Export and compatibility
The existing shared, immutable comparison data remains the source of all six figures. SVG continues to use SimSun/Times New Roman family references. PNG continues to require exact SimSun and Times New Roman and returns `FontUnavailable` rather than falling back. The report exporter publishes all image files atomically and cleans any temporary files if one fails.
## Validation
Regression coverage verifies the six stable file stems, absence of trajectory line commands/styles, presence of all expected point markers and units, correct legends/statuses, valid PNG signature/CRC/600 dpi metadata, and no leftover `.tmp` files. Visual inspection covers straight, rectangle-detour, forward-reverse-switch, and an infeasible scenario.
@@ -1,62 +0,0 @@
# Local G2 日报式报告交付设计
## 目标
在仓库根目录新增 `dailywork_report/`,交付两份中文主报告及各自独立、可直接打开的 HTML 可视化附录。内容面向研发人员和需要快速理解进度/风险的项目协作者。
## 交付物
```text
dailywork_report/
├── Map_rep/ # 预留:地图模块报告
├── coarsepath_rep/ # 预留:粗路径模块报告
└── pathsmoothing_rep/ # 本次 Local G2 报告
├── 01-local-g2-quintic-hermite-algorithm-report.md
├── 01-local-g2-quintic-hermite-algorithm-visualization.html
├── 02-local-g2-issues-and-next-actions-report.md
└── 02-local-g2-issues-and-next-actions-visualization.html
```
HTML 文件为单文件附件:内嵌 CSS、SVG 和少量原生 JavaScript,不依赖网络、第三方 CDN 或构建步骤。
本次只在 `pathsmoothing_rep/` 中创建内容;`Map_rep/``coarsepath_rep/` 仅建立目录结构,供后续对应模块的日报式报告使用。
## 报告一:算法说明
主题为“Local G2 五次 Hermite 路径平滑算法说明”。内容按以下顺序组织:
1. 目标、适用位置与非目标:说明它位于 Hybrid A* 与后续 SQP 之间,只生成空间路径初值,不涉及速度、加速度或 SQP 求解。
2. 输入:成功的粗路径、方向段、地图、车辆参数、G2 配置和取消令牌;明确坐标/单位与有效性前提。
3. 模块架构:预处理、曲率跳变检测、窗口规划、五次 Hermite 候选构造、路径拼接、统一几何分析、安全/质量评价,以及计划中的专用发布流水线。
4. 数据流:以“输入 → 检测 → 局部候选 → 安全筛选 → 输出”的线性流程说明每一步的职责和边界。
5. 输出:说明路径点、方向段、曲率、曲率导数、区域报告、状态和诊断;明确当前任务 8 尚未接入,不能把候选层能力描述为已发布的主路径功能。
6. 约束与安全门:不跨换向点、窗口/偏移/净空/曲率限制、候选数量上限和确定性排序。
配套 HTML 使用模块卡片、输入/输出栏和 SVG 数据流箭头,分别标明“已实现”和“待接入”模块。
## 报告二:问题分析与后续措施
主题为“Local G2 路径平滑问题分析与后续措施”。开头先给出证据边界:专项测试通过不等于端到端功能已经完成。随后按固定结构分别描述三个问题:
1. **已复现故障**`RectangleDetour` 原始基线在平滑前的复验中变为 `InvalidInput`
2. **已确认的逻辑缺口**:窗口合并包络与候选总长度上限不一致,可能将本可分开处理的事件合并为没有合法候选的区域。
3. **待验证的集成风险**:连续处理同一方向段多个区域时,前一处替换重算弧长可能让后一处继续使用旧的窗口坐标。
每个问题都包含:现象、通俗例子、技术成因、影响范围、证据等级、建议验证/修复措施和进入任务 8 前的验收条件。报告不把风险说成已经发生的运行时故障。
配套 HTML 使用状态徽章、场景示意 SVG、因果链和“问题 → 验证 → 措施”流程,突出已复现故障与待验证风险的区别。
## 写作与证据原则
- 以中文撰写,术语首次出现时同时给出白话解释。
- 明确区分“已通过的专项测试”“已复现的失败”和“静态分析发现的风险”。
- 引用现有实现计划、测试脚本、关键实现文件和本次实际测试结果;不宣称尚未实现的任务 8/9 已完成。
- HTML 与 Markdown 的事实、术语和问题分级必须一致。
## 验收标准
- 四个文件均位于 `dailywork_report/`,命名稳定、无需外部资源即可阅读。
- 两份 Markdown 报告结构完整,能够单独解释算法和问题。
- 两份 HTML 附录在本地直接打开时内容可读、层级清楚、与主报告一致。
- 报告二准确表达三个问题的证据等级和下一步,不给出未经验证的结论。
- 本次工作只创建报告,不修改路径平滑算法或测试逻辑。
@@ -1,98 +0,0 @@
# Local G2 报告交互式可视化重做设计
## 背景与目标
现有两份 HTML 附录能够列出算法流程和问题结论,但它们以文字卡片和节点连线为主,无法让读者看到路径几何如何变化,也无法直观看到错误与正确预期之间的差异。
本次重做把“路径形状、局部窗口、曲率变化、替换结果和故障后果”作为主画面。目标读者打开单个 HTML 后,应当能在不阅读源代码的前提下理解:
1. Local G2 为什么只修改局部粗路径;
2. 五次 Hermite 曲线如何取代曲率跳变处的原始折线;
3. 安全/质量门如何决定候选曲线被接受还是回退;
4. 三个已记录问题实际发生在哪里、正确结果应当是什么、二者为何不同。
本次不改变路径平滑算法、测试、地图、粗路径或报告中的事实结论。
## 事实边界与图示约定
两类图形必须在视觉上明确区分,不能把教学示意误写成已运行结果:
| 图形类别 | 用途 | 标注规则 |
|---|---|---|
| 典型几何示例 | 解释局部曲线替换、窗口和弧长机制 | 主图附近固定标注“典型示例:用于解释机制,不代表某次测试的精确坐标” |
| 真实证据 | 表达已运行的测试结果、状态与文本 | 显示场景名、证据等级和原始状态,如 `RectangleDetour: Success → InvalidInput` |
当前尚未定位 `RectangleDetour` 的第一个非法数值或超限曲率样本。因此问题一不能画出虚构的具体碰撞点、具体坏采样点或确定根因;只可画出真实的失败链路,并把未定位部位标为“待定位”。
## 文件边界
仅重做以下离线单文件附件;Markdown 两份报告的事实文字保持不变。
```text
dailywork_report/pathsmoothing_rep/
├── 01-local-g2-quintic-hermite-algorithm-visualization.html
└── 02-local-g2-issues-and-next-actions-visualization.html
```
两份文件继续使用 HTML5、内嵌 CSS、内嵌 SVG 与原生 JavaScript;不使用 CDN、网络请求、外部图片、构建工具或第三方库。
## 算法页:逐步路径演示
算法页采用一个占据主要版面的可切换 SVG,而不是并排流程卡片。读者可通过“上一步 / 下一步”、步骤按钮或左右方向键在六个状态之间切换;切换时只更新当前主图、图例和一句解释,不堆叠多张重复图。
| 步骤 | 主画面 | 读者应看到的作用 |
|---|---|---|
| 0. 原始粗路径 | 离散点构成的粗路径、方向箭头、局部曲率跳变记号 | Hybrid A* 路径可行,但相邻基元接口可能不平滑 |
| 1. 发现局部事件 | 突变点与其局部处理窗口高亮,其余路径淡化 | 算法只改必要区域,不重做全局搜索 |
| 2. 端点约束 | 窗口两端的位置、切向箭头、曲率趋势和五次候选虚线 | 五次 Hermite 用两端位置、一阶/二阶趋势塑形 |
| 3. 局部替换 | 原局部折线以灰色保留,候选曲线覆盖,接缝位置标注 | 只替换窗口内部,窗口外的粗路径保持不变 |
| 4. 连续性效果 | 上方为替换后的路径,下方为“曲率—弧长”对比 | 原曲率在接口跳变,候选曲线使该区域过渡连续 |
| 5. 安全与质量门 | 车辆轮廓沿候选轨迹的几个姿态、净空区域和接受/回退门 | 几何更顺滑不等于可发布,必须通过碰撞、净空和质量检查 |
几何使用经过明确标注的典型前进路径:一段水平路径接一段斜向路径,在接口处设置曲率突变。每一步复用同一坐标系,以便读者能清楚比较变化前后。车辆图仅表达安全扫掠关系,不模拟完整动力学或真实车体精确尺寸。
## 问题页:错误与正确预期对照
问题页采用“问题选择器 + 同一主画布中的实际/正确对照”。读者选择三个问题中的任意一个后,主画布同步显示事实状态、典型机制和下一步措施。每个问题都固定包含“实际发生”“正确应有”“差异原因”三条可视化信息,不再只列文字风险卡片。
### 问题一:RectangleDetour 原始基线复验失败
- **实际发生**:流程轨迹显示 `Hybrid A* 粗路径 Success``原始基线统一复验 InvalidInput` 的红色中断;Local G2 候选生成位于中断之后且没有开始。
- **正确应有**:同一条已成功规划的粗路径应先以可行基线进入后续平滑比较和候选评价。
- **几何辅助**:用一个标记为“概念性 RectangleDetour 外形”的绕障路径,配合“待定位”的校验点,不声称该点就是实际失败样本。
### 问题二:窗口合并与最大长度约束不一致
- **实际发生**:弧长标尺上两个相距 `1.0 m` 的曲率事件,各自的 `±0.8 m` 影响范围相交,规划器将它们合并;合并区间总长度又超过 `0.8 m`,候选窗口消失。
- **正确应有**:在不满足单窗口总长度约束时,应选择可行的拆分窗口或采用与合并规则一致的长度策略。
- **几何辅助**:同一条典型路径上的两个局部高亮区,与下方时间轴式弧长标尺同步显示。
### 问题三:连续替换后的弧长坐标陈旧风险
- **实际发生**:先替换 A 窗口并重新计算弧长后,B 窗口仍采用替换前的旧弧长,箭头落到新路径上的错误位置。
- **正确应有**:处理 B 前按当前路径重新定位其窗口,或使用稳定的锚点映射,使 B 落在原意图的局部区域。
- **几何辅助**:上、下两条路径分别表示“旧弧长定位”和“重新定位”,用同一个 B 标签显示落点差异。图例注明这是尚待流水线接入后回归验证的风险示例。
## 交互、可读性与可访问性
- 初始打开即显示算法第 0 步或问题一,无须任何操作。
- 所有交互均使用原生 `<button>`,提供可见文字、`aria-pressed` / `aria-current` 状态与键盘可达性;算法页额外支持左右方向键。
- 主 SVG 含 `<title>``<desc>`;色彩只辅助区分状态,同时配合实线/虚线、叉号、标签和箭头。
- 主图在桌面宽度下优先呈现为宽幅画布,在窄屏下缩放或纵向重排;不依赖固定视口高度和内部滚动。
- 仅在步骤切换时做短暂、可禁用的几何过渡;`prefers-reduced-motion` 下关闭动画。
## 验证标准
完成后应进行以下检查:
1. 两个 HTML 均包含完整 HTML 文档、中文标题、嵌入式 CSS/SVG/JavaScript,且没有 `http://``https://`、外部脚本或图片引用。
2. 算法页包含六个可访问步骤及所有对应术语:粗路径、局部窗口、五次 Hermite、局部替换、曲率—弧长、安全与质量门。
3. 问题页包含三个可选择问题;每个问题可见“实际发生”“正确应有”“差异原因”,并保留 `Success → InvalidInput``1.0 m``±0.8 m``0.8 m`、弧长重算等事实标签。
4. 检查脚本验证 JavaScript 查询的节点都存在、按钮切换函数存在、默认状态可渲染,以及 HTML 无明显未闭合标签。
5. 若环境提供浏览器,逐一点击步骤/问题并在窄宽度下做视觉验收;若无浏览器,则如实记录为未完成的人工视觉检查项,不以静态检查替代。
## 非目标
- 不把 HTML 做成实时运行算法的仿真器,也不连接项目运行时或读取本地数据。
- 不新增任何关于 G2 已正式接入服务、已解决三项问题或已定位 `RectangleDetour` 根因的结论。
- 不将典型示例中的坐标、曲率数值写成项目真实测量结果。
@@ -1,255 +0,0 @@
# Local G2 任务 8 前稳定化设计
**日期:** 2026-07-31
**状态:** 已经用户确认,待实施计划
**范围:** 修复进入任务 8 前的 P0/P1 阻断,不实现任务 8 的正式发布流水线
## 1. 背景
Local G2 五次 Hermite 模块已经具备曲率事件检测、局部窗口规划、候选构造、拼接和质量评价能力,但在接入正式流水线前存在三个阻断:
1. `RectangleDetour` 的 Hybrid A* 粗路径规划成功,平滑模块重建原始基线时却返回 `InvalidInput`
2. `MaximumWindowLengthMeters = 0.80` 的语义应为左右窗口总长度,但当前事件合并阶段把它当成每侧都可扩展 `0.80 m`
3. 拼接一个上游区域后会重算整个方向段的局部弧长,后续区域若仍使用原始弧长,可能命中错误位置。
当前源码已经恢复可构建。设计审查时的基线为:
- `dotnet build ClumsyPilot/ClumsyPilot.csproj --no-restore`:成功,0 个错误;
- 路径几何、Local G2 检测和 Local G2 候选专项检查:通过;
- `verify_path_smoothing_integration.ps1`:稳定失败于 `RectangleDetour` 原始基线。
## 2. 目标与非目标
### 2.1 目标
- 消除合法极限曲率圆弧被离散弦长误判为超限的问题,且不削弱候选安全门。
- 统一 `0.80 m` 为局部替换窗口的最大总长度。
- 保证一个合并区域实际能够生成至少一个覆盖全部事件的合法窗口。
- 固定同一方向段内从后向前的区域处理顺序,为任务 8 提供不会发生弧长错位的工作顺序。
- 用失败先行的自动回归锁定三项行为。
### 2.2 非目标
- 不实现 `LocalG2PreSmoothingPipeline`
- 不把 `LocalG2Quintic` 接入 `PathSmoothingService`
- 不新增 RectangleDetour 碰撞位置、最小净空或可视化诊断输出。
- 不调整车辆最大曲率、碰撞门限、净空门限或验证器容差来换取测试通过。
- 不修改三种旧平滑算法的选择、重试或排序策略。
现有完整车体和扫掠验证仍然保留。RectangleDetour 的原始基线只有在现有验证全部通过后才能返回 `Success`
## 3. P0:曲率基线误判
### 3.1 已确认根因
粗路径运动基元携带真实车辆曲率和真实积分弧长。RectangleDetour 的最大车辆曲率为:
```text
0.833333333333333 1/m
```
该值等于车辆允许上限。`PathGeometryAnalyzer` 丢弃粗路径的逐点车辆曲率,以重采样点之间的欧氏弦长作为弧长,再用:
```text
Δheading / chord-length
```
恢复曲率。同一圆弧因此得到:
```text
0.833348403933077 1/m
```
误差大于验证器的 `1e-6` 数值容差,原始基线在进入碰撞循环前被拒绝。
### 3.2 首选修复:经过认证的共享离散曲率估计器
首选方案是在 `PathGeometryAnalyzer` 中使用带符号圆弧—弦长关系:
```text
kappa = 2 * sin(deltaHeading / 2) / chordLength
```
采样对定义如下:
- `deltaHeading` 使用沿路径连续展开后的带符号航向差,不直接对原始角度值相减;
- 首点:使用点 `0` 和点 `1` 的航向差与位置弦长;
- 末点:使用倒数第 `2` 点和末点的航向差与位置弦长;
- 内部点 `i`:使用点 `i-1` 和点 `i+1` 的航向差与位置弦长;
- 倒车段只在几何曲率生成后按现有方向符号转换为车辆曲率;
- 非有限弦长、退化弦长或无法唯一展开的转角继续返回失败。
该公式对恒定半径圆弧精确,能够消除本次由弦长短于圆弧长度引起的系统性高估。
### 3.3 安全认证门
共享估计器影响原始基线、旧平滑算法和 Local G2 候选,因此不得只凭 RectangleDetour 变绿就接受。实施必须先建立下列失败回归:
1. 最大允许曲率的前进与倒车圆弧,在首点、内部点和末点都不得超过真实曲率与数值容差之和。
2. 明确超出车辆曲率上限的圆弧仍必须被验证器拒绝。
3. 对带解析一、二阶导数的五次 Hermite 曲线进行稠密参考采样;新估计器对解析最大曲率的单侧低估量,不得大于旧估计器的单侧低估量加 `1e-6`
4. S 弯、曲率峰值靠近端点、前进、倒车和非均匀末段采样都必须覆盖。
5. 所有现有几何、验证、旧算法和 Local G2 候选检查必须保持通过。
单侧低估量定义为:
```text
max(0, analyticMaximumCurvature - estimatedMaximumCurvature)
```
若共享估计器不满足上述认证门,则禁止全局替换。
### 3.4 双条件门控的限定回退
仅在以下任一条件成立时,才允许改用原始基线专用方案:
1. 首选共享估计器不能通过第 3.3 节的安全认证;或
2. 原始基线经共享分析器重建后,未改动的验证器明确因非有限或超限的**重建曲率**而拒绝,同时对应的可信粗路径 `VehicleCurvature` 是有限值且不超过车辆曲率上限。
- 原始基线保留 `CoarsePathPoint.VehicleCurvature` 和粗路径真实 `ArcLength`
- 在每个方向段内直接由真实曲率序列计算 `dκ/ds`、总绝对曲率变化和变化代价;
- 使用原粗路径位姿执行现有完整车体和扫掠验证;
- 回退结果必须重新运行现有完整验证器;
- 候选路径继续使用共享几何分析器,不改变候选安全门。
该回退只允许用于原始粗路径,因为粗路径运动基元提供了可信的真实曲率;普通平滑候选没有同等来源的数据。它不放宽车辆曲率、碰撞、净空或验证器容差。
### 3.5 P0 验收
- 极限圆弧回归通过;
- 超限圆弧仍被拒绝;
- RectangleDetour 原始基线返回 `Success`
- 不修改车辆最大曲率和验证器容差;
- `verify_path_smoothing_integration.ps1` 不再在 RectangleDetour 原始基线处失败。
## 4. P1:窗口总长度和合并语义
### 4.1 唯一长度语义
以下三个配置始终表示一个候选替换窗口的总长度:
| 配置 | 默认值 | 唯一语义 |
|---|---:|---|
| `MinimumWindowLengthMeters` | `0.20` | 左窗口加右窗口的最小总长度 |
| `PreferredWindowLengthMeters` | `0.50` | 左窗口加右窗口的首选总长度 |
| `MaximumWindowLengthMeters` | `0.80` | 左窗口加右窗口的最大总长度 |
对称候选的 `0.80 m` 表示左右约各 `0.40 m`,不表示左右各 `0.80 m`。靠近方向段边界时允许把缺失长度转移到另一侧,但总长度不能超过 `0.80 m`
### 4.2 以候选可生成性决定合并
窗口规划器继续按方向段和事件弧长稳定排序。对同一方向段中的下一个事件,使用以下规则:
1. 将下一个事件加入临时事件组;
2. 用现有目标长度和左右比例为临时事件组构造窗口变体;
3. 只有临时事件组至少产生一个能够覆盖全部事件、总长度不超过最大值的窗口,才接受合并;
4. 如果没有合法窗口,结束当前区域,并从下一个事件建立新区域。
事件按弧长升序排列,因此一个更远事件不能加入当前组时,后续事件也不再尝试加入该组。
### 4.3 区域包络
`MaximumStartArcLengthMeters``MaximumEndArcLengthMeters` 当前没有运行时消费者,但任务 8 的区域报告可能使用它们。保留字段,并将语义改为:
- `MaximumStartArcLengthMeters`:该区域实际合法窗口中的最小起点;
- `MaximumEndArcLengthMeters`:该区域实际合法窗口中的最大终点。
不得继续保存 `event ± MaximumWindowLengthMeters` 的虚假包络。
### 4.4 窗口验收
- 相距 `1.0 m` 的两个事件在默认配置下形成两个独立区域;
- 能被同一 `0.80 m` 总窗口覆盖的相邻事件仍能合并;
- 三个事件只合并实际可被同一合法窗口覆盖的连续子组;
- 靠近方向段起点或终点的事件能够生成总长度受限的不对称窗口;
- 每个成功规划的区域至少包含一个合法窗口;
- 同一输入重复运行的区域分组、窗口顺序和候选编号一致。
## 5. P1:多区域弧长稳定性
### 5.1 处理顺序
任务 8 必须使用两个不同顺序:
- **工作顺序:** 方向段编号升序;同一方向段内按原始局部弧长降序;
- **报告顺序:** 保留检测器产生的原始方向段/弧长升序。
跨方向段的局部弧长彼此独立。对同一方向段,从后向前替换时,后方替换不会改变前方窗口之前的点或累计弧长,因此前方候选仍能使用原始坐标。
### 5.2 任务 8 前可完成的保障
在任务 8 前增加一个独立、确定性的区域工作顺序组件,并增加底层双窗口回归:
1. 在同一方向段创建两个互不重叠的区域 A、B,其中 B 位于 A 后方;
2. 先替换 B,并要求替换产生可观测的路径长度变化;
3. 验证 A 的原始起止弧长仍对应原始语义位置;
4. 再替换 A,验证两个替换都保留且路径拓扑完整;
5. 重复运行并比较工作顺序、点数和点坐标。
测试还必须证明相反顺序会使 B 的原始绝对弧长不再对应原始语义位置,从而锁定逆序规则存在的必要性。
### 5.3 任务 8 的硬性接入门
多区域问题在任务 8 前只能完成排序组件和底层证明,不能宣称端到端修复。任务 8 必须:
- 直接使用该区域工作顺序组件;
- 候选仍从不可变原始路径构造,评价和拼接作用于当前路径;
- 区域报告按原始顺序发布;
- 在两个区域都可能改善的真实流水线测试中验证第二个处理区域命中正确位置;
- 相同请求连续运行两次,状态、路径、候选编号和区域报告一致。
未满足这些条件时,`LocalG2Quintic` 不得接入正式服务。
## 6. 数据流
任务 8 前稳定化后的数据流为:
```text
CoarsePath
-> PathSmoothingPreprocessor
-> 原始基线曲率/安全复核
-> CurvatureTransitionDetector
-> LocalG2WindowPlanner(以合法候选决定合并)
-> RegionWorkOrder(同段从后向前)
-> 任务 8 接入门
```
本阶段不会调用不存在的正式流水线。
## 7. 错误处理
- 曲率估计产生非有限值、退化弦或模糊大转角:返回稳定失败,不发布路径。
- 单事件无法生成任何窗口:窗口规划失败并给出稳定原因,不静默丢弃事件。
- 临时联合事件组没有合法窗口:不视为全局失败,结束当前组并从下一个事件创建区域。
- 区域排序输入包含空区域、非法方向段或非有限弧长:拒绝排序。
- P0 首选估计器认证失败,或原始基线重建仅因非有限/超限曲率被验证器拒绝且可信粗路径曲率仍在车辆上限内:不放宽安全门,按第 3.4 节改用限定原始基线方案。
## 8. 实施边界与预期文件
实施计划可以修改或新增以下范围内的文件:
- `PathSmoothing/Processing/PathGeometryAnalyzer.cs`
- `PathSmoothing/Processing/RawPathBaselineBuilder.cs`(仅当启用限定回退)
- `PathSmoothing/LocalG2/LocalG2WindowPlanner.cs`
- `PathSmoothing/LocalG2/LocalG2SmoothingRegion.cs`
- 新的内部区域工作顺序组件
- `verify_path_smoothing_geometry.ps1`
- `verify_path_smoothing_integration.ps1`
- `verify_path_smoothing_local_g2_detection.ps1`
- `verify_path_smoothing_local_g2_candidates.ps1`
不得在本阶段创建 `LocalG2PreSmoothingPipeline.cs` 或修改服务分派。
## 9. 完成标准
本阶段只有同时满足以下条件才算完成:
1. 当前项目构建成功;
2. P0 曲率修复通过安全认证门,或明确采用并验证限定回退;
3. RectangleDetour 原始基线通过;
4. `0.80 m` 总窗口语义和 `1.0 m` 分组回归通过;
5. 双区域逆序处理底层回归通过;
6. 原有 PathSmoothing 几何、验证、服务、比较和 Local G2 专项检查不回归;
7. 任务 8 计划明确引用区域工作顺序组件和双区域端到端硬门。
本阶段完成只表示“具备进入任务 8 的条件”,不表示 Local G2 已经成为正式可用服务。

Some files were not shown because too many files have changed in this diff Show More