feat: validate EM terminal world pose

This commit is contained in:
梁薄云
2026-08-07 08:01:18 +08:00
parent 0bba8d7e61
commit e844fe00a8
6 changed files with 249 additions and 11 deletions
@@ -124,15 +124,22 @@ public sealed class EmPlanningService : IEmPlanningService
request.Map.SnapshotId, request.ReferencePathId, request.VehicleState.SequenceId, request.PreviousTrajectoryId, request.Map.SnapshotId, request.ReferencePathId, request.VehicleState.SequenceId, request.PreviousTrajectoryId,
segment.SegmentIndex, segment.Direction, horizon.TerminalType, horizon.LongitudinalMode, segment.SegmentIndex, segment.Direction, horizon.TerminalType, horizon.LongitudinalMode,
request.PlanningScope); request.PlanningScope);
EmTrajectory trajectory = new EmTrajectoryAssembler(configuration).Assemble(lateral.Path, longitudinal, metadata); EmPlanningStatus assemblyStatus = new EmTrajectoryAssembler(configuration).TryAssemble(lateral.Path,
longitudinal, metadata, out EmTrajectory trajectory, out string assemblyFailure);
if (assemblyStatus != EmPlanningStatus.Success)
return Failure(assemblyStatus, request, assemblyFailure);
EmitDebug(request, "trajectory assembly succeeded"); EmitDebug(request, "trajectory assembly succeeded");
EmBoundaryType terminalBoundary = slice.TerminalBoundary.BoundaryType;
Pose2D terminalPose = IsRealTerminalBoundary(terminalBoundary)
? TerminalPose(lateral.Path)
: null;
EmTrajectoryValidationResult publication = new EmTrajectoryValidator().Validate(trajectory, request.Map, EmTrajectoryValidationResult publication = new EmTrajectoryValidator().Validate(trajectory, request.Map,
request.Vehicle, configuration, segment.SegmentIndex, longitudinalInput.PathUpperBoundS, request.Vehicle, configuration, segment.SegmentIndex, longitudinalInput.PathUpperBoundS,
slice.TerminalBoundary.BoundaryType); terminalPose, terminalBoundary);
if (!publication.IsValid) if (!publication.IsValid)
{ {
return Failure(EmPlanningStatus.ValidationFailed, request, return Failure(MapPublicationFailure(publication.Failure), request,
publication.Failure + " at point " + publication.PointIndex + ": " + publication.Message); publication.Failure + " at point " + publication.PointIndex + ": " + publication.Message);
} }
EmitDebug(request, "world-space publication validation succeeded"); EmitDebug(request, "world-space publication validation succeeded");
@@ -225,6 +232,24 @@ public sealed class EmPlanningService : IEmPlanningService
return status == EmPlanningStatus.Success || status == EmPlanningStatus.SuccessWithFallback; return status == EmPlanningStatus.Success || status == EmPlanningStatus.SuccessWithFallback;
} }
internal static EmPlanningStatus MapPublicationFailure(EmTrajectoryValidationFailure failure)
{
return failure == EmTrajectoryValidationFailure.TerminalPoseMismatch
? EmPlanningStatus.TerminalPoseMismatch
: EmPlanningStatus.ValidationFailed;
}
private static bool IsRealTerminalBoundary(EmBoundaryType boundaryType)
{
return boundaryType == EmBoundaryType.Goal || boundaryType == EmBoundaryType.GearSwitchApproach;
}
private static Pose2D TerminalPose(LateralPath path)
{
LateralPathPoint terminal = path.Points[path.Points.Count - 1];
return new Pose2D(terminal.X, terminal.Y, terminal.VehicleYaw);
}
private static EmPlanningResult Failure(EmPlanningStatus status, EmPlanningRequest request, string reason) private static EmPlanningResult Failure(EmPlanningStatus status, EmPlanningRequest request, string reason)
{ {
return new EmPlanningResult(status, null, DiagnosticsPrefix(request) + ";reason=" + (reason ?? string.Empty)); return new EmPlanningResult(status, null, DiagnosticsPrefix(request) + ";reason=" + (reason ?? string.Empty));
@@ -9,6 +9,7 @@ public sealed class EmTrajectoryAssembler
{ {
private readonly double outputTimeStepSeconds; private readonly double outputTimeStepSeconds;
private readonly double zeroSpeedHoldSeconds; private readonly double zeroSpeedHoldSeconds;
private readonly int maximumPublishedSampleCount;
public EmTrajectoryAssembler() public EmTrajectoryAssembler()
: this(EmPlannerConfiguration.CreateDefault()) : this(EmPlannerConfiguration.CreateDefault())
@@ -21,8 +22,9 @@ public sealed class EmTrajectoryAssembler
throw new ArgumentNullException(nameof(configuration)); throw new ArgumentNullException(nameof(configuration));
outputTimeStepSeconds = configuration.Scheduling.OutputTimeStepSeconds; outputTimeStepSeconds = configuration.Scheduling.OutputTimeStepSeconds;
zeroSpeedHoldSeconds = configuration.Longitudinal.ZeroSpeedHoldSeconds; zeroSpeedHoldSeconds = configuration.Longitudinal.ZeroSpeedHoldSeconds;
maximumPublishedSampleCount = configuration.Scheduling.MaximumPublishedSampleCount;
if (!IsFinite(outputTimeStepSeconds) || outputTimeStepSeconds <= 0d || !IsFinite(zeroSpeedHoldSeconds) || if (!IsFinite(outputTimeStepSeconds) || outputTimeStepSeconds <= 0d || !IsFinite(zeroSpeedHoldSeconds) ||
zeroSpeedHoldSeconds < 0d) zeroSpeedHoldSeconds < 0d || maximumPublishedSampleCount < 2)
{ {
throw new ArgumentOutOfRangeException(nameof(configuration)); throw new ArgumentOutOfRangeException(nameof(configuration));
} }
@@ -30,6 +32,18 @@ public sealed class EmTrajectoryAssembler
public EmTrajectory Assemble(LateralPath path, LongitudinalPlanningResult longitudinal, EmTrajectoryMetadata metadata) public EmTrajectory Assemble(LateralPath path, LongitudinalPlanningResult longitudinal, EmTrajectoryMetadata metadata)
{ {
EmPlanningStatus status = TryAssemble(path, longitudinal, metadata, out EmTrajectory trajectory,
out string failureReason);
if (status != EmPlanningStatus.Success)
throw new ArgumentException(failureReason, nameof(longitudinal));
return trajectory;
}
public EmPlanningStatus TryAssemble(LateralPath path, LongitudinalPlanningResult longitudinal,
EmTrajectoryMetadata metadata, out EmTrajectory trajectory, out string failureReason)
{
trajectory = null;
failureReason = string.Empty;
if (longitudinal == null || longitudinal.Candidate == null || if (longitudinal == null || longitudinal.Candidate == null ||
(longitudinal.Status != EmPlanningStatus.Success && longitudinal.Status != EmPlanningStatus.SuccessWithFallback)) (longitudinal.Status != EmPlanningStatus.Success && longitudinal.Status != EmPlanningStatus.SuccessWithFallback))
{ {
@@ -40,8 +54,17 @@ public sealed class EmTrajectoryAssembler
var interpolator = new LateralPathInterpolator(path); var interpolator = new LateralPathInterpolator(path);
bool isFullDirectionSegment = metadata.PlanningScope == EmPlanningScope.FullDirectionSegment; bool isFullDirectionSegment = metadata.PlanningScope == EmPlanningScope.FullDirectionSegment;
var schedule = new TrajectorySampleSchedule(longitudinal.Candidate, outputTimeStepSeconds, double holdDurationSeconds = isFullDirectionSegment ? 0d : zeroSpeedHoldSeconds;
isFullDirectionSegment ? 0d : zeroSpeedHoldSeconds, metadata.LongitudinalMode, isFullDirectionSegment); if (TrajectorySampleSchedule.ExceedsMaximumSampleCount(longitudinal.Candidate, outputTimeStepSeconds,
holdDurationSeconds, metadata.LongitudinalMode, isFullDirectionSegment, maximumPublishedSampleCount,
out int requiredSampleCount))
{
failureReason = "FullSegmentResourceLimitExceeded: MaximumPublishedSampleCount=" +
maximumPublishedSampleCount + ";required-at-least=" + requiredSampleCount;
return EmPlanningStatus.FullSegmentResourceLimitExceeded;
}
var schedule = new TrajectorySampleSchedule(longitudinal.Candidate, outputTimeStepSeconds, holdDurationSeconds,
metadata.LongitudinalMode, isFullDirectionSegment);
double terminalPathS = path.Points[path.Points.Count - 1].PathS; double terminalPathS = path.Points[path.Points.Count - 1].PathS;
var points = new List<EmTrajectoryPoint>(schedule.Samples.Count); var points = new List<EmTrajectoryPoint>(schedule.Samples.Count);
double directionSign = metadata.Direction == TravelDirection.Forward ? 1d : -1d; double directionSign = metadata.Direction == TravelDirection.Forward ? 1d : -1d;
@@ -62,7 +85,8 @@ public sealed class EmTrajectoryAssembler
boundaryType, sample.Acceleration, sample.Jerk)); boundaryType, sample.Acceleration, sample.Jerk));
} }
return new EmTrajectory(metadata, points); trajectory = new EmTrajectory(metadata, points);
return EmPlanningStatus.Success;
} }
private static EmBoundaryType ToBoundaryType(EmTerminalType terminalType) private static EmBoundaryType ToBoundaryType(EmTerminalType terminalType)
@@ -86,6 +86,50 @@ internal sealed class TrajectorySampleSchedule
public IReadOnlyList<TrajectorySample> Samples { get; } public IReadOnlyList<TrajectorySample> Samples { get; }
public int TerminalAnchorSampleIndex { get; } public int TerminalAnchorSampleIndex { get; }
internal static bool ExceedsMaximumSampleCount(LongitudinalCandidate candidate, double outputTimeStepSeconds,
double holdDurationSeconds, EmLongitudinalMode mode, bool resampleMotion, int maximumSampleCount,
out int requiredSampleCount)
{
requiredSampleCount = 0;
if (candidate == null)
throw new ArgumentNullException(nameof(candidate));
if (maximumSampleCount < 1)
throw new ArgumentOutOfRangeException(nameof(maximumSampleCount));
if (resampleMotion)
{
double finalTime = candidate.KnotTimes[candidate.KnotTimes.Count - 1];
for (double sampleTime = 0d; sampleTime < finalTime - ZeroTolerance;
sampleTime += outputTimeStepSeconds)
{
if (IncrementAndExceedsMaximum(ref requiredSampleCount, maximumSampleCount))
return true;
}
if (IncrementAndExceedsMaximum(ref requiredSampleCount, maximumSampleCount))
return true;
}
else
{
for (int index = 0; index < candidate.KnotTimes.Count; index++)
{
if (IncrementAndExceedsMaximum(ref requiredSampleCount, maximumSampleCount))
return true;
}
}
if (mode != EmLongitudinalMode.ExactStopAtBoundary)
return false;
double holdElapsed = 0d;
while (holdElapsed < holdDurationSeconds - ZeroTolerance)
{
holdElapsed = Math.Min(holdDurationSeconds, holdElapsed + outputTimeStepSeconds);
if (IncrementAndExceedsMaximum(ref requiredSampleCount, maximumSampleCount))
return true;
}
return false;
}
private static void AddSample(TrajectorySample sample, ICollection<TrajectorySample> samples, private static void AddSample(TrajectorySample sample, ICollection<TrajectorySample> samples,
ref double previousPathS, LongitudinalCandidate candidate) ref double previousPathS, LongitudinalCandidate candidate)
{ {
@@ -97,6 +141,12 @@ internal sealed class TrajectorySampleSchedule
previousPathS = sample.PathS; previousPathS = sample.PathS;
} }
private static bool IncrementAndExceedsMaximum(ref int sampleCount, int maximumSampleCount)
{
checked { sampleCount++; }
return sampleCount > maximumSampleCount;
}
private static TrajectorySample Interpolate(LongitudinalCandidate candidate, double sampleTime, ref int sourceInterval) private static TrajectorySample Interpolate(LongitudinalCandidate candidate, double sampleTime, ref int sourceInterval)
{ {
int lastKnot = candidate.KnotTimes.Count - 1; int lastKnot = candidate.KnotTimes.Count - 1;
@@ -18,6 +18,7 @@ public enum EmTrajectoryValidationFailure
TerminalSpeedNotZero, TerminalSpeedNotZero,
TerminalAccelerationNotZero, TerminalAccelerationNotZero,
TerminalYawRateNotZero, TerminalYawRateNotZero,
TerminalPoseMismatch,
DirectionMismatch, DirectionMismatch,
DirectionSignMismatch, DirectionSignMismatch,
RedundantSpeedMismatch, RedundantSpeedMismatch,
@@ -79,6 +80,22 @@ public sealed class EmTrajectoryValidator
public EmTrajectoryValidationResult Validate(EmTrajectory trajectory, PlanningGridMap map, VehicleParameters vehicle, public EmTrajectoryValidationResult Validate(EmTrajectory trajectory, PlanningGridMap map, VehicleParameters vehicle,
EmPlannerConfiguration configuration, int segmentIndex, double pathUpperBoundS, EmBoundaryType terminalBoundary) EmPlannerConfiguration configuration, int segmentIndex, double pathUpperBoundS, EmBoundaryType terminalBoundary)
{
return ValidateCore(trajectory, map, vehicle, configuration, segmentIndex, pathUpperBoundS, null,
terminalBoundary, false);
}
public EmTrajectoryValidationResult Validate(EmTrajectory trajectory, PlanningGridMap map, VehicleParameters vehicle,
EmPlannerConfiguration configuration, int segmentIndex, double pathUpperBoundS, Pose2D terminalPose,
EmBoundaryType terminalBoundary)
{
return ValidateCore(trajectory, map, vehicle, configuration, segmentIndex, pathUpperBoundS, terminalPose,
terminalBoundary, true);
}
private EmTrajectoryValidationResult ValidateCore(EmTrajectory trajectory, PlanningGridMap map, VehicleParameters vehicle,
EmPlannerConfiguration configuration, int segmentIndex, double pathUpperBoundS, Pose2D terminalPose,
EmBoundaryType terminalBoundary, bool requiresTerminalPose)
{ {
if (trajectory == null || map == null || vehicle == null || configuration == null || configuration.Validation == null || if (trajectory == null || map == null || vehicle == null || configuration == null || configuration.Validation == null ||
configuration.Longitudinal == null || configuration.Corridor == null || segmentIndex < 0 || configuration.Longitudinal == null || configuration.Corridor == null || segmentIndex < 0 ||
@@ -151,6 +168,31 @@ public sealed class EmTrajectoryValidator
return Reject(EmTrajectoryValidationFailure.TerminalAccelerationNotZero, index, return Reject(EmTrajectoryValidationFailure.TerminalAccelerationNotZero, index,
"Exact-stop tail stored acceleration must remain zero."); "Exact-stop tail stored acceleration must remain zero.");
} }
if (IsRealTerminalBoundary(terminalBoundary))
{
if (requiresTerminalPose && terminalPose == null)
{
return Reject(EmTrajectoryValidationFailure.InvalidInput, terminalIndex,
"A real terminal anchor requires its expected world pose.");
}
if (terminalPose != null)
{
if (!IsFinite(terminalPose.X) || !IsFinite(terminalPose.Y) || !IsFinite(terminalPose.Heading))
{
return Reject(EmTrajectoryValidationFailure.InvalidInput, terminalIndex,
"The expected terminal world pose is non-finite.");
}
double dx = terminal.X - terminalPose.X;
double dy = terminal.Y - terminalPose.Y;
double positionError = Math.Sqrt(dx * dx + dy * dy);
double yawError = Math.Abs(NormalizeAngle(terminal.Yaw - terminalPose.Heading));
if (positionError > limits.TerminalPositionTolerance || yawError > limits.TerminalYawTolerance)
{
return Reject(EmTrajectoryValidationFailure.TerminalPoseMismatch, terminalIndex,
"TerminalPoseMismatch: position=" + Format(positionError) + ";yaw=" + Format(yawError));
}
}
}
} }
double directionSign = trajectory.Metadata.Direction == TravelDirection.Forward ? 1d : -1d; double directionSign = trajectory.Metadata.Direction == TravelDirection.Forward ? 1d : -1d;
@@ -268,6 +310,18 @@ public sealed class EmTrajectoryValidator
return -1; return -1;
} }
private static bool IsRealTerminalBoundary(EmBoundaryType boundaryType)
{
return boundaryType == EmBoundaryType.Goal || boundaryType == EmBoundaryType.GearSwitchApproach;
}
private static double NormalizeAngle(double angle)
{
while (angle > Math.PI) angle -= 2d * Math.PI;
while (angle < -Math.PI) angle += 2d * Math.PI;
return angle;
}
private static int FindFirstTerminalPathIndex(EmTrajectory trajectory, double terminalPathS, double spatialTolerance) private static int FindFirstTerminalPathIndex(EmTrajectory trajectory, double terminalPathS, double spatialTolerance)
{ {
for (int index = 0; index < trajectory.Points.Count; index++) for (int index = 0; index < trajectory.Points.Count; index++)
@@ -315,6 +369,10 @@ public sealed class EmTrajectoryValidator
configuration.Longitudinal.MaximumCurvatureRatePerMeterPerSecond <= 0d || configuration.Longitudinal.MaximumCurvatureRatePerMeterPerSecond <= 0d ||
!IsFinite(configuration.Validation.SpatialToleranceMeters) || configuration.Validation.SpatialToleranceMeters < 0d || !IsFinite(configuration.Validation.SpatialToleranceMeters) || configuration.Validation.SpatialToleranceMeters < 0d ||
!IsFinite(configuration.Validation.KinematicTolerance) || configuration.Validation.KinematicTolerance < 0d || !IsFinite(configuration.Validation.KinematicTolerance) || configuration.Validation.KinematicTolerance < 0d ||
!IsFinite(configuration.Validation.TerminalPositionToleranceMeters) ||
configuration.Validation.TerminalPositionToleranceMeters < 0d ||
!IsFinite(configuration.Validation.TerminalYawToleranceRadians) ||
configuration.Validation.TerminalYawToleranceRadians < 0d ||
!IsFinite(configuration.Corridor.MaximumCollisionCheckStepMeters) || !IsFinite(configuration.Corridor.MaximumCollisionCheckStepMeters) ||
configuration.Corridor.MaximumCollisionCheckStepMeters <= 0d) configuration.Corridor.MaximumCollisionCheckStepMeters <= 0d)
{ {
@@ -327,6 +385,8 @@ public sealed class EmTrajectoryValidator
configuration.Longitudinal.MaximumJerkMetersPerSecondCubed, configuration.Longitudinal.MaximumJerkMetersPerSecondCubed,
configuration.Longitudinal.MaximumCurvatureRatePerMeterPerSecond, configuration.Longitudinal.MaximumCurvatureRatePerMeterPerSecond,
configuration.Validation.SpatialToleranceMeters, configuration.Validation.KinematicTolerance, configuration.Validation.SpatialToleranceMeters, configuration.Validation.KinematicTolerance,
configuration.Validation.TerminalPositionToleranceMeters,
configuration.Validation.TerminalYawToleranceRadians,
configuration.Corridor.MaximumCollisionCheckStepMeters); configuration.Corridor.MaximumCollisionCheckStepMeters);
return true; return true;
} }
@@ -356,7 +416,8 @@ public sealed class EmTrajectoryValidator
{ {
public ValidationLimits(double maximumSpeed, double maximumCurvature, double maximumAcceleration, public ValidationLimits(double maximumSpeed, double maximumCurvature, double maximumAcceleration,
double maximumDeceleration, double maximumJerk, double maximumCurvatureRate, double spatialTolerance, double maximumDeceleration, double maximumJerk, double maximumCurvatureRate, double spatialTolerance,
double kinematicTolerance, double configuredCollisionStepMeters) double kinematicTolerance, double terminalPositionTolerance, double terminalYawTolerance,
double configuredCollisionStepMeters)
{ {
MaximumSpeed = maximumSpeed; MaximumSpeed = maximumSpeed;
MaximumCurvature = maximumCurvature; MaximumCurvature = maximumCurvature;
@@ -366,6 +427,8 @@ public sealed class EmTrajectoryValidator
MaximumCurvatureRate = maximumCurvatureRate; MaximumCurvatureRate = maximumCurvatureRate;
SpatialTolerance = spatialTolerance; SpatialTolerance = spatialTolerance;
KinematicTolerance = kinematicTolerance; KinematicTolerance = kinematicTolerance;
TerminalPositionTolerance = terminalPositionTolerance;
TerminalYawTolerance = terminalYawTolerance;
ConfiguredCollisionStepMeters = configuredCollisionStepMeters; ConfiguredCollisionStepMeters = configuredCollisionStepMeters;
} }
@@ -377,6 +440,8 @@ public sealed class EmTrajectoryValidator
public double MaximumCurvatureRate { get; } public double MaximumCurvatureRate { get; }
public double SpatialTolerance { get; } public double SpatialTolerance { get; }
public double KinematicTolerance { get; } public double KinematicTolerance { get; }
public double TerminalPositionTolerance { get; }
public double TerminalYawTolerance { get; }
public double ConfiguredCollisionStepMeters { get; } public double ConfiguredCollisionStepMeters { get; }
} }
} }
@@ -22,6 +22,7 @@ internal static class EmPlanningServiceChecks
VerifiesNoProgressPublishesNoTrajectory(); VerifiesNoProgressPublishesNoTrajectory();
VerifiesTimeoutFallbackAndCancellationSemantics(); VerifiesTimeoutFallbackAndCancellationSemantics();
VerifiesPublicationFailureAndDebugIsolation(); VerifiesPublicationFailureAndDebugIsolation();
VerifiesPublicationGateStatusMappings();
} }
private static void VerifiesForwardReverseAndBoundarySuccessesAreDeterministic() private static void VerifiesForwardReverseAndBoundarySuccessesAreDeterministic()
@@ -427,6 +428,20 @@ internal static class EmPlanningServiceChecks
VerifySuccess(debugIsolated, debugRequest, EmTerminalType.Goal, "debug-sink isolation"); VerifySuccess(debugIsolated, debugRequest, EmTerminalType.Goal, "debug-sink isolation");
} }
private static void VerifiesPublicationGateStatusMappings()
{
Verification.Equal(EmPlanningStatus.TerminalPoseMismatch,
EmPlanningService.MapPublicationFailure(EmTrajectoryValidationFailure.TerminalPoseMismatch),
"terminal-pose validator failure has its precise service status");
EmPlanningRequest sampleLimited = CreateRequest(TravelDirection.Forward, 0d, false, false);
sampleLimited.Configuration.Scheduling.MaximumPublishedSampleCount = 2;
EmPlanningResult sampleLimitResult = new EmPlanningService(new ScriptedPipelineSolver(PipelineSolverMode.Success)).Plan(
sampleLimited, CancellationToken.None);
VerifyFailure(sampleLimitResult, EmPlanningStatus.FullSegmentResourceLimitExceeded,
"sample-limit publication failure");
}
private static void VerifiesPreviousTrajectoryIsALongitudinalSoftReference() private static void VerifiesPreviousTrajectoryIsALongitudinalSoftReference()
{ {
EmPlanningRequest request = CreateRequest(TravelDirection.Forward, 0d, false, false); EmPlanningRequest request = CreateRequest(TravelDirection.Forward, 0d, false, false);
@@ -17,6 +17,7 @@ internal static class TrajectoryChecks
VerifiesRollingTrajectoryHasNoSyntheticStopTail(); VerifiesRollingTrajectoryHasNoSyntheticStopTail();
VerifiesExactStopHoldHasNoJerkDiscontinuity(); VerifiesExactStopHoldHasNoJerkDiscontinuity();
VerifiesWorldSpacePublicationMutationsAreRejected(); VerifiesWorldSpacePublicationMutationsAreRejected();
VerifiesTerminalPoseAndSampleLimitPublicationGates();
VerifiesReverseSpeedLimitUsesReverseConfiguration(); VerifiesReverseSpeedLimitUsesReverseConfiguration();
} }
@@ -184,6 +185,64 @@ internal static class TrajectoryChecks
Verification.Equal(4, beyondSegment.PointIndex, "segment-boundary first point"); Verification.Equal(4, beyondSegment.PointIndex, "segment-boundary first point");
} }
private static void VerifiesTerminalPoseAndSampleLimitPublicationGates()
{
ValidationContext context = CreateValidationContext();
EmTrajectory valid = CreateValidationTrajectory(TravelDirection.Forward);
int terminalIndex = LongitudinalTerminalSchedule.GetStabilizationStartIndex(
CreateValidationCandidate().KnotTimes, context.Configuration.Scheduling.OutputTimeStepSeconds);
EmTrajectoryPoint terminal = valid.Points[terminalIndex];
var validator = new EmTrajectoryValidator();
EmTrajectoryValidationResult insidePosition = validator.Validate(valid, context.EmptyMap, context.Vehicle,
context.Configuration, 2, 0.0055d, new Pose2D(terminal.X + 0.029d, terminal.Y, terminal.Yaw),
EmBoundaryType.Goal);
Verification.True(insidePosition.IsValid, "2.9-centimetre terminal position error is accepted");
EmTrajectoryValidationResult outsidePosition = validator.Validate(valid, context.EmptyMap, context.Vehicle,
context.Configuration, 2, 0.0055d, new Pose2D(terminal.X + 0.031d, terminal.Y, terminal.Yaw),
EmBoundaryType.Goal);
Verification.Equal(EmTrajectoryValidationFailure.TerminalPoseMismatch, outsidePosition.Failure,
"3.1-centimetre terminal position error is rejected");
double degrees = Math.PI / 180d;
EmTrajectory wrappedYaw = Replace(valid, terminalIndex, Clone(terminal, yaw: 179d * degrees));
EmTrajectoryValidationResult wrapped = validator.Validate(wrappedYaw, context.EmptyMap, context.Vehicle,
context.Configuration, 2, 0.0055d, new Pose2D(terminal.X, terminal.Y, -179d * degrees),
EmBoundaryType.Goal);
Verification.True(wrapped.IsValid, "179 and -179 degrees use normalized yaw error");
EmTrajectoryValidationResult insideYaw = validator.Validate(valid, context.EmptyMap, context.Vehicle,
context.Configuration, 2, 0.0055d, new Pose2D(terminal.X, terminal.Y, -4.9d * degrees),
EmBoundaryType.Goal);
Verification.True(insideYaw.IsValid, "4.9-degree terminal yaw error is accepted");
EmTrajectoryValidationResult outsideYaw = validator.Validate(valid, context.EmptyMap, context.Vehicle,
context.Configuration, 2, 0.0055d, new Pose2D(terminal.X, terminal.Y, -5.1d * degrees),
EmBoundaryType.Goal);
Verification.Equal(EmTrajectoryValidationFailure.TerminalPoseMismatch, outsideYaw.Failure,
"5.1-degree terminal yaw error is rejected");
EmTrajectory rolling = new EmTrajectoryAssembler().Assemble(
CreatePath(TravelDirection.Forward, 0d, 0d), CreateRollingLongitudinalResult(),
CreateMetadata(TravelDirection.Forward, EmTerminalType.RollingSafetyStop,
EmLongitudinalMode.RollingContinuation));
EmTrajectoryValidationResult rollingResult = validator.Validate(rolling, context.EmptyMap, context.Vehicle,
context.Configuration, 2, 0.08d, new Pose2D(99d, 99d, Math.PI), EmBoundaryType.RollingSafetyStop);
Verification.True(rollingResult.IsValid, "rolling-safety window end does not use the terminal-pose gate");
EmPlannerConfiguration limitedConfiguration = EmPlannerConfiguration.CreateDefault();
limitedConfiguration.Scheduling.MaximumPublishedSampleCount = 8;
EmPlanningStatus assemblyStatus = new EmTrajectoryAssembler(limitedConfiguration).TryAssemble(
CreatePath(TravelDirection.Forward, 0d, 0d), CreateLongitudinalResult(),
CreateMetadata(TravelDirection.Forward, EmTerminalType.Goal), out EmTrajectory limitedTrajectory,
out string assemblyFailure);
Verification.Equal(EmPlanningStatus.FullSegmentResourceLimitExceeded, assemblyStatus,
"sample schedule one above the limit is rejected");
Verification.True(limitedTrajectory == null, "sample-limit rejection does not assemble a partial trajectory");
Verification.True(assemblyFailure.IndexOf("MaximumPublishedSampleCount", StringComparison.Ordinal) >= 0,
"sample-limit rejection preserves a diagnostic");
Verification.True(assemblyFailure.IndexOf("required-at-least=9", StringComparison.Ordinal) >= 0,
"sample-limit fixture exceeds the cap by exactly one sample");
}
private static void VerifiesReverseSpeedLimitUsesReverseConfiguration() private static void VerifiesReverseSpeedLimitUsesReverseConfiguration()
{ {
ValidationContext context = CreateValidationContext(); ValidationContext context = CreateValidationContext();
@@ -284,11 +343,11 @@ internal static class TrajectoryChecks
return Replace(trajectory, index, replacement); return Replace(trajectory, index, replacement);
} }
private static EmTrajectoryPoint Clone(EmTrajectoryPoint point, double? x = null, double? timeFromStart = null, private static EmTrajectoryPoint Clone(EmTrajectoryPoint point, double? x = null, double? y = null, double? yaw = null,
double? signedSpeed = null, double? pathS = null, double? vehicleCurvature = null, double? timeFromStart = null, double? signedSpeed = null, double? pathS = null, double? vehicleCurvature = null,
EmBoundaryType? boundaryType = null) EmBoundaryType? boundaryType = null)
{ {
return new EmTrajectoryPoint(x ?? point.X, point.Y, point.Yaw, signedSpeed ?? point.SignedLongitudinalVelocity, return new EmTrajectoryPoint(x ?? point.X, y ?? point.Y, yaw ?? point.Yaw, signedSpeed ?? point.SignedLongitudinalVelocity,
timeFromStart ?? point.TimeFromStart, vehicleCurvature ?? point.VehicleCurvature, point.SegmentIndex, timeFromStart ?? point.TimeFromStart, vehicleCurvature ?? point.VehicleCurvature, point.SegmentIndex,
pathS ?? point.SegmentLocalS, pathS ?? point.PathS, point.Direction, boundaryType ?? point.BoundaryType, pathS ?? point.SegmentLocalS, pathS ?? point.PathS, point.Direction, boundaryType ?? point.BoundaryType,
0d, 0d); 0d, 0d);