Files
ParkingRobot/docs/superpowers/plans/2026-07-22-workstation-bounded-trap-map.md
T

13 KiB

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:

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

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:

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

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:

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:

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:

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:

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

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

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
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
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 -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
git add ClumsyPilot/MovementTest.Trapmaptest.cs ClumsyPilot/tests/verify_trapmap_inputs.ps1
git commit -m "feat: expose workstation trap map inputs"