param([string]$AssemblyPath = (Join-Path $PSScriptRoot '..\bin\Debug\netstandard2.0\ClumsyPilot.dll')) $ErrorActionPreference = 'Stop' $newtonsoftPath = Join-Path $env:USERPROFILE '.nuget\packages\newtonsoft.json\13.0.4\lib\netstandard2.0\Newtonsoft.Json.dll' if (Test-Path -LiteralPath $newtonsoftPath) { [Reflection.Assembly]::LoadFrom($newtonsoftPath) | Out-Null } $assembly = [Reflection.Assembly]::LoadFrom((Resolve-Path $AssemblyPath)) function Assert-Equal($Expected, $Actual, [string]$Message) { if ($Expected -ne $Actual) { throw "$Message Expected=$Expected Actual=$Actual" } } function Assert-True($Actual, [string]$Message) { if (-not $Actual) { throw $Message } } function Assert-Near([double]$Expected, [double]$Actual, [string]$Message) { if ([Math]::Abs($Expected - $Actual) -gt 0.000000001) { throw "$Message Expected=$Expected Actual=$Actual" } } function Get-RequiredType([string]$Name) { return $assembly.GetType($Name, $true) } function Text-FromCodePoints([int[]]$CodePoints) { return (($CodePoints | ForEach-Object { [Char]::ConvertFromUtf32($_) }) -join '') } $visualization = 'MultiWheelC.TrajectoryPlanning.PathSmoothing.Visualization.' $styleType = Get-RequiredType ($visualization + 'IeeeFigureStyle') foreach ($expectation in @( @{ Name = 'FigureWidthPoints'; Value = 515.52 }, @{ Name = 'FigureHeightPoints'; Value = 374.4 }, @{ Name = 'RawColor'; Value = '#4D4D4D' }, @{ Name = 'BSplineColor'; Value = '#0072B2' }, @{ Name = 'BezierColor'; Value = '#D55E00' }, @{ Name = 'QuinticColor'; Value = '#009E73' }, @{ Name = 'LimitColor'; Value = '#CC79A7' } )) { $field = $styleType.GetField($expectation.Name) if ($null -eq $field) { throw "IeeeFigureStyle must expose $($expectation.Name)." } if ($expectation.Value -is [double]) { Assert-Near $expectation.Value $field.GetValue($null) "IeeeFigureStyle $($expectation.Name) must match the fixed report style." } else { Assert-Equal $expectation.Value $field.GetValue($null) "IeeeFigureStyle $($expectation.Name) must match the fixed report style." } } $root = 'MultiWheelC.TrajectoryPlanning.PathSmoothing.' $comparison = $root + 'Comparison.' $facade = $root + 'Facade.' $coarse = 'MultiWheelC.TrajectoryPlanning.CoarsePath.' $mapping = 'MultiWheelC.TrajectoryPlanning.Mapping.' $test = $root + 'Test.' $modelType = Get-RequiredType ($visualization + 'SmoothingFigureModel') $builderType = Get-RequiredType ($visualization + 'SmoothingFigureModelBuilder') $figureDefinitionType = Get-RequiredType ($visualization + 'SmoothingFigureDefinition') $figureSetBuilderType = Get-RequiredType ($visualization + 'SmoothingFigureSetBuilder') $rendererType = Get-RequiredType ($visualization + 'SmoothingSvgRenderer') $csvWriterType = Get-RequiredType ($visualization + 'SmoothingCsvWriter') $comparisonResultType = Get-RequiredType ($comparison + 'PathSmoothingComparisonResult') $comparisonRequestType = Get-RequiredType ($comparison + 'PathSmoothingComparisonRequest') $comparisonServiceType = Get-RequiredType ($facade + 'PathSmoothingComparisonService') $scenarioFactoryType = Get-RequiredType ($test + 'SmoothingScenarioFactory') $gridMapType = Get-RequiredType ($mapping + 'PlanningGridMap') $poseType = Get-RequiredType ($coarse + 'Pose2D') $buildMethod = $builderType.GetMethod('Build', [Type[]]@($comparisonResultType, $gridMapType, $poseType, $poseType, [string], [string])) if ($null -eq $buildMethod) { throw 'SmoothingFigureModelBuilder must expose Build(comparison, map, start, goal, scenarioId, scenarioLabel).' } Assert-Equal $modelType $buildMethod.ReturnType 'SmoothingFigureModelBuilder.Build must return the immutable figure model.' $figureSetBuildMethod = $figureSetBuilderType.GetMethod('Build', [Type[]]@($modelType)) if ($null -eq $figureSetBuildMethod) { throw 'SmoothingFigureSetBuilder must expose Build(SmoothingFigureModel).' } $renderMethod = $rendererType.GetMethod('Render', [Type[]]@($figureDefinitionType)) if ($null -eq $renderMethod) { throw 'SmoothingSvgRenderer must expose Render(SmoothingFigureDefinition).' } $writeMethod = $csvWriterType.GetMethod('Write', [Type[]]@($modelType)) if ($null -eq $writeMethod) { throw 'SmoothingCsvWriter must expose Write(SmoothingFigureModel).' } $fixturePath = Join-Path $PSScriptRoot '..\ParkrobTrajplanner\PathSmoothing\Test\Fixtures\path-smoothing-fixtures.json' $fixtureRequests = $scenarioFactoryType.GetMethod('CreateFixtureRequests', [Type[]]@([string])).Invoke($null, @((Resolve-Path $fixturePath).Path)) $comparisonRequest = $fixtureRequests[0] Assert-Equal $comparisonRequestType $comparisonRequest.GetType() 'The figure test must consume a real immutable comparison request.' $comparisonResult = [Activator]::CreateInstance($comparisonServiceType).Compare($comparisonRequest, [Threading.CancellationToken]::None) $coarsePath = $comparisonRequest.SmoothingRequest.CoarsePath $start = [Activator]::CreateInstance($poseType, @($coarsePath[0].X, $coarsePath[0].Y, $coarsePath[0].Heading)) $last = $coarsePath[$coarsePath.Count - 1] $goal = [Activator]::CreateInstance($poseType, @($last.X, $last.Y, $last.Heading)) $builder = [Activator]::CreateInstance($builderType) $rawLabel = Text-FromCodePoints @(0x539F, 0x59CB, 0x7C97, 0x8DEF, 0x5F84) $bSplineLabel = (Text-FromCodePoints @(0x4E09, 0x6B21)) + ' B ' + (Text-FromCodePoints @(0x6837, 0x6761)) $bezierLabel = (Text-FromCodePoints @(0x5C40, 0x90E8, 0x4E09, 0x6B21)) + ' B' + [Char]::ConvertFromUtf32(0x00E9) + 'zier' $quinticLabel = Text-FromCodePoints @(0x5206, 0x6BB5, 0x4E94, 0x6B21) $scenarioId = (Text-FromCodePoints @(0x573A, 0x666F)) + '-escape' $scenarioLabel = (Text-FromCodePoints @(0x8DEF, 0x5F84)) + ' ' $model = $buildMethod.Invoke($builder, @($comparisonResult, $comparisonRequest.SmoothingRequest.Map, $start, $goal, $scenarioId, $scenarioLabel)) $figureSet = $figureSetBuildMethod.Invoke([Activator]::CreateInstance($figureSetBuilderType), @($model)) $figures = @($figureSet.Figures) $expectedStems = @('01-coarse-path-overview', '02-all-paths-comparison', '03-cubic-bspline-overview', '04-local-cubic-bezier-overview', '05-piecewise-quintic-overview', '06-curvature-comparison') Assert-Equal ($expectedStems -join ',') (($figures | ForEach-Object { $_.FileStem }) -join ',') 'Figure set must publish the stable six-file order.' Assert-True (-not $figures[1].ShowsMapContext) 'All-path comparison must exclude obstacles and endpoint decorations.' Assert-True ($figures[2].ShowsMapContext -and $figures[2].Series[0].Opacity -lt 1) 'Individual smoother views must retain faded coarse-path context.' Assert-Near (($figures[0].WorldXMaxMeters - $figures[0].WorldXMinMeters) / ($figures[0].WorldYMaxMeters - $figures[0].WorldYMinMeters)) ($figures[0].PlotWidthPoints / $figures[0].PlotHeightPoints) 'Overhead figures must preserve equal X/Y scale.' $renderer = [Activator]::CreateInstance($rendererType) $svgByStem = @{} foreach ($figure in $figures) { $svgByStem[$figure.FileStem] = $renderMethod.Invoke($renderer, @($figure)) } $svg = (($expectedStems | ForEach-Object { $svgByStem[$_] }) -join "`n") Assert-True ($svg -match ']+viewBox="0 0 515\.52 374\.4"') 'SVG must use the fixed physical view box in typographic points.' Assert-True $svg.Contains(($scenarioLabel -replace '&', '&' -replace '<', '<' -replace '>', '>')) 'SVG text must XML-escape scenario labels.' Assert-True $svg.Contains('font-family="SimSun"') 'SVG must emit explicit SimSun Chinese text runs.' Assert-True $svg.Contains('font-family="Times New Roman"') 'SVG must emit explicit Times New Roman Latin/math text runs.' foreach ($color in @('#4D4D4D', '#0072B2', '#D55E00', '#009E73', '#CC79A7')) { Assert-True $svg.Contains($color) "SVG must retain the fixed color $color." } Assert-True $svg.Contains('class="trajectory-point"') 'All visible trajectory samples must render as point markers.' Assert-True (-not $svg.Contains('stroke-dasharray')) 'Trajectory reports must not use dashed path styles.' Assert-True (-not $svg.Contains('