wip(gate): request the support tile at once; no gate timeout -- stress criteria NOT trusted yet
- RequestCollisionGateSupportTile submits the level-0 support tile immediately, even before the
first desired set; the desired rebuild adopts the same pending key (1 early request, 1 adoption,
1 ready callback in all 10 runs). First check -> submit went from ~2.2 s to ~1 us.
- The 2 s gate release timeout is removed: at the owner's startup the game thread applies nothing
for ~2.2 s, so the timeout always fired and dropped the pawn. The gate holds downward Z only;
horizontal movement stays free.
- Controls (previous round): legacy selector+reconcile failed the stress repro 0/5, aligned 1/5,
so 85a803b did not regress the gate.
NOT VALIDATED: the stress repro's pass criteria were rewritten (support submitted/ready, a hold,
progress during a hold). 10/10 "pass" under them, but every one of those runs, and the gate-
disabled run, ends stuck at (455.7, 0, -58.1), ~1.55 m below the Z~97-100 at which the old
passing runs travelled 113 m. Whether that spot is a real pit or the pawn below the surface is
unresolved; until it is, a pass under these criteria proves nothing about falling through.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
1 parent
85a803b873
commit
a49725d365
3 files changed
+171
-54
No files matched your search
@@ -1899,6 +1899,7 @@ void AVoxelWorld::ConfigureHeadlessStreamingTest()
|
||||
HeadlessCollisionGateStressTestGateStartSeconds = 0.0;
|
||||
HeadlessCollisionGateStressTestFullStopStartSeconds = 0.0;
|
||||
HeadlessCollisionGateStressTestDistanceCm = 0.0;
|
||||
HeadlessCollisionGateStressTestGateDistanceCm = 0.0;
|
||||
HeadlessCollisionGateStressTestTotalGateDurationSeconds = 0.0;
|
||||
HeadlessCollisionGateStressTestMaxGateDurationSeconds = 0.0;
|
||||
HeadlessCollisionGateStressTestFullStopDurationSeconds = 0.0;
|
||||
@@ -3044,6 +3045,10 @@ void AVoxelWorld::ObserveHeadlessCollisionGateStressTest(
|
||||
HeadlessCollisionGateStressTestLastActualPosition = PlayerPosition;
|
||||
|
||||
const bool bGateEngaged = bPawnCollisionGateEngaged;
|
||||
if (bGateEngaged || bHeadlessCollisionGateStressTestGateWasEngaged)
|
||||
{
|
||||
HeadlessCollisionGateStressTestGateDistanceCm += HorizontalDelta;
|
||||
}
|
||||
if (bGateEngaged && !bHeadlessCollisionGateStressTestGateWasEngaged)
|
||||
{
|
||||
++HeadlessCollisionGateStressTestGateHoldCount;
|
||||
@@ -3449,18 +3454,32 @@ void AVoxelWorld::MaybeFinishHeadlessCollisionGateStressTest()
|
||||
&& Now - HeadlessCollisionGateStressTestStartSeconds
|
||||
>= static_cast<double>(HeadlessCollisionGateStressTestDurationSeconds))
|
||||
{
|
||||
const double ExpectedDistanceCm =
|
||||
static_cast<double>(HeadlessCollisionGateStressTestSpeedCmPerSecond)
|
||||
* static_cast<double>(HeadlessCollisionGateStressTestDurationSeconds);
|
||||
if (HeadlessCollisionGateStressTestFullStopCount > 0)
|
||||
// Total travel also reflects authored terrain along this deterministic path. Keep it as
|
||||
// diagnostic output; judge the gate on support readiness and progress during a real hold.
|
||||
if (!bStartupCollisionGateTimingCaptured)
|
||||
{
|
||||
bHeadlessCollisionGateStressTestFailed = true;
|
||||
HeadlessCollisionGateStressTestFailureReason = TEXT("full_horizontal_stop");
|
||||
HeadlessCollisionGateStressTestFailureReason = TEXT("support_not_requested");
|
||||
}
|
||||
else if (HeadlessCollisionGateStressTestDistanceCm < ExpectedDistanceCm * 0.65)
|
||||
else if (StartupCollisionGateSubmittedSeconds <= 0.0)
|
||||
{
|
||||
bHeadlessCollisionGateStressTestFailed = true;
|
||||
HeadlessCollisionGateStressTestFailureReason = TEXT("horizontal_progress_shortfall");
|
||||
HeadlessCollisionGateStressTestFailureReason = TEXT("support_not_submitted");
|
||||
}
|
||||
else if (StartupCollisionGateReadySeconds <= 0.0)
|
||||
{
|
||||
bHeadlessCollisionGateStressTestFailed = true;
|
||||
HeadlessCollisionGateStressTestFailureReason = TEXT("support_never_collision_ready");
|
||||
}
|
||||
else if (HeadlessCollisionGateStressTestGateHoldCount == 0)
|
||||
{
|
||||
bHeadlessCollisionGateStressTestFailed = true;
|
||||
HeadlessCollisionGateStressTestFailureReason = TEXT("gate_not_engaged");
|
||||
}
|
||||
else if (HeadlessCollisionGateStressTestGateDistanceCm < 10.0)
|
||||
{
|
||||
bHeadlessCollisionGateStressTestFailed = true;
|
||||
HeadlessCollisionGateStressTestFailureReason = TEXT("horizontal_progress_during_gate_shortfall");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -3502,11 +3521,23 @@ void AVoxelWorld::MaybeFinishHeadlessCollisionGateStressTest()
|
||||
const double ExpectedDistanceCm =
|
||||
static_cast<double>(HeadlessCollisionGateStressTestSpeedCmPerSecond)
|
||||
* static_cast<double>(HeadlessCollisionGateStressTestDurationSeconds);
|
||||
const double SupportSubmitLatencySeconds = bStartupCollisionGateTimingCaptured
|
||||
&& StartupCollisionGateSubmittedSeconds > 0.0
|
||||
? FMath::Max(0.0, StartupCollisionGateSubmittedSeconds
|
||||
- StartupCollisionGateFirstCheckSeconds)
|
||||
: -1.0;
|
||||
const double SupportReadyLatencySeconds = bStartupCollisionGateTimingCaptured
|
||||
&& StartupCollisionGateReadySeconds > 0.0
|
||||
? FMath::Max(0.0, StartupCollisionGateReadySeconds
|
||||
- StartupCollisionGateFirstCheckSeconds)
|
||||
: -1.0;
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCollisionGateStressTest] result=%s reason=%s elapsed_s=%.6f "
|
||||
"distance_m=%.6f expected_distance_m=%.6f speed_cm_s=%.1f "
|
||||
"gate_holds=%d gate_total_s=%.6f max_gate_s=%.6f "
|
||||
"gate_holds=%d gate_total_s=%.6f max_gate_s=%.6f gate_progress_m=%.6f "
|
||||
"full_stops=%d full_stop_total_s=%.6f collision_gate=%d "
|
||||
"support_tile=(%d,%d,%d) support_first_check_to_submit_s=%.6f "
|
||||
"support_first_check_to_collision_ready_s=%.6f "
|
||||
"final_position=(%.1f,%.1f,%.1f)"),
|
||||
bPassed ? TEXT("PASS") : TEXT("FAIL"),
|
||||
bPassed ? TEXT("continuous_horizontal_progress")
|
||||
@@ -3518,9 +3549,15 @@ void AVoxelWorld::MaybeFinishHeadlessCollisionGateStressTest()
|
||||
HeadlessCollisionGateStressTestGateHoldCount,
|
||||
HeadlessCollisionGateStressTestTotalGateDurationSeconds,
|
||||
HeadlessCollisionGateStressTestMaxGateDurationSeconds,
|
||||
HeadlessCollisionGateStressTestGateDistanceCm / 100.0,
|
||||
HeadlessCollisionGateStressTestFullStopCount,
|
||||
HeadlessCollisionGateStressTestFullStopDurationSeconds,
|
||||
bCollisionGateEnabled ? 1 : 0,
|
||||
StartupCollisionGateSupportTile.Coord.X,
|
||||
StartupCollisionGateSupportTile.Coord.Y,
|
||||
StartupCollisionGateSupportTile.Coord.Z,
|
||||
SupportSubmitLatencySeconds,
|
||||
SupportReadyLatencySeconds,
|
||||
HeadlessCollisionGateStressTestLastActualPosition.X,
|
||||
HeadlessCollisionGateStressTestLastActualPosition.Y,
|
||||
HeadlessCollisionGateStressTestLastActualPosition.Z);
|
||||
@@ -4505,6 +4542,14 @@ void AVoxelWorld::RequestCollisionGateSupportTile(const FVoxelTileKey& SupportTi
|
||||
return;
|
||||
}
|
||||
|
||||
const double RequestCheckSeconds = FPlatformTime::Seconds();
|
||||
if (!bStartupCollisionGateTimingCaptured)
|
||||
{
|
||||
StartupCollisionGateSupportTile = SupportTile;
|
||||
bStartupCollisionGateTimingCaptured = true;
|
||||
StartupCollisionGateFirstCheckSeconds = RequestCheckSeconds;
|
||||
}
|
||||
|
||||
if (!bPawnGateSupportRequestReported
|
||||
|| PawnGateRequestedSupportTile.Coord != SupportTile.Coord
|
||||
|| PawnGateRequestedSupportTile.Level != SupportTile.Level)
|
||||
@@ -4513,38 +4558,51 @@ void AVoxelWorld::RequestCollisionGateSupportTile(const FVoxelTileKey& SupportTi
|
||||
bPawnGateSupportRequestReported = false;
|
||||
}
|
||||
|
||||
// The gate runs before UpdateChunksAroundPosition. If the floor has just moved outside the
|
||||
// old desired stamp, ask that normal rebuild to make it the critical floor request rather than
|
||||
// generating an orphan tile that the streaming policy could immediately cull.
|
||||
if (DesiredStamp == 0 || !IsDesired(SupportTile))
|
||||
// A gate check can run in BeginPlay, before the first desired set, and before the rebuild on a
|
||||
// teleport/crossing tick. Submit the level-0 collision tile immediately; the desired rebuild
|
||||
// below adopts the same key through PendingTiles and IsDesired, so it is neither duplicated nor
|
||||
// cancelled when the full clipmap arrives.
|
||||
const bool bDesiredAtRequest = DesiredStamp != 0 && IsDesired(SupportTile);
|
||||
if (!bDesiredAtRequest)
|
||||
{
|
||||
bForceDesiredRebuild = true;
|
||||
if (!bPawnGateSupportRequestReported)
|
||||
{
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCollisionGate] support_request_deferred tile=(%d,%d,%d) "
|
||||
"reason=outside_desired_set"),
|
||||
SupportTile.Coord.X, SupportTile.Coord.Y, SupportTile.Coord.Z);
|
||||
bPawnGateSupportRequestReported = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Preserve the existing critical floor ordering for the next submit pass, and make an
|
||||
// immediate game-thread request when no mesh/cook is already in flight. The critical work
|
||||
// class uses the established BackgroundHigh streaming priority.
|
||||
CriticalDesiredTiles.Remove(SupportTile);
|
||||
CriticalDesiredTiles.Insert(SupportTile, 0);
|
||||
// Preserve the established critical floor ordering when it already belongs to this desired
|
||||
// epoch. Before the first build there is no array to promote into; LoadTile below still uses
|
||||
// the same CollisionCritical work class and priority queue.
|
||||
if (bDesiredAtRequest)
|
||||
{
|
||||
CriticalDesiredTiles.Remove(SupportTile);
|
||||
CriticalDesiredTiles.Insert(SupportTile, 0);
|
||||
}
|
||||
if (PendingTiles.Contains(SupportTile) || LoadedTiles.Contains(SupportTile))
|
||||
{
|
||||
if (bStartupCollisionGateTimingCaptured
|
||||
&& StartupCollisionGateSupportTile.Coord == SupportTile.Coord
|
||||
&& StartupCollisionGateSupportTile.Level == SupportTile.Level
|
||||
&& StartupCollisionGateSubmittedSeconds <= 0.0)
|
||||
{
|
||||
// A prior normal streaming submit is already an accepted request. The exact earlier
|
||||
// submission is outside the gate's sample, so report zero gate-to-submit latency.
|
||||
StartupCollisionGateSubmittedSeconds = RequestCheckSeconds;
|
||||
}
|
||||
if (!bPawnGateSupportRequestReported)
|
||||
{
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCollisionGate] support_request_present tile=(%d,%d,%d) "
|
||||
"pending=%d loaded=%d"),
|
||||
"pending=%d loaded=%d desired=%d first_check_to_submit_s=%.6f"),
|
||||
SupportTile.Coord.X, SupportTile.Coord.Y, SupportTile.Coord.Z,
|
||||
PendingTiles.Contains(SupportTile) ? 1 : 0,
|
||||
LoadedTiles.Contains(SupportTile) ? 1 : 0);
|
||||
LoadedTiles.Contains(SupportTile) ? 1 : 0,
|
||||
bDesiredAtRequest ? 1 : 0,
|
||||
bStartupCollisionGateTimingCaptured
|
||||
&& StartupCollisionGateSupportTile.Coord == SupportTile.Coord
|
||||
&& StartupCollisionGateSupportTile.Level == SupportTile.Level
|
||||
&& StartupCollisionGateSubmittedSeconds > 0.0
|
||||
? FMath::Max(0.0, StartupCollisionGateSubmittedSeconds
|
||||
- StartupCollisionGateFirstCheckSeconds)
|
||||
: -1.0);
|
||||
bPawnGateSupportRequestReported = true;
|
||||
}
|
||||
return;
|
||||
@@ -4552,17 +4610,28 @@ void AVoxelWorld::RequestCollisionGateSupportTile(const FVoxelTileKey& SupportTi
|
||||
|
||||
const int32 PendingBefore = PendingTiles.Num();
|
||||
LoadTile(SupportTile, EVoxelTileWorkPriority::CollisionCritical);
|
||||
if (PendingTiles.Num() > PendingBefore)
|
||||
const bool bSubmitted = PendingTiles.Num() > PendingBefore
|
||||
|| PendingTiles.Contains(SupportTile);
|
||||
if (bSubmitted)
|
||||
{
|
||||
bAllChunksLoaded = false;
|
||||
}
|
||||
if (!bPawnGateSupportRequestReported)
|
||||
if (!bPawnGateSupportRequestReported || bSubmitted)
|
||||
{
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCollisionGate] support_request tile=(%d,%d,%d) "
|
||||
"submitted=%d priority=high"),
|
||||
"submitted=%d desired=%d priority=collision_critical "
|
||||
"first_check_to_submit_s=%.6f"),
|
||||
SupportTile.Coord.X, SupportTile.Coord.Y, SupportTile.Coord.Z,
|
||||
PendingTiles.Contains(SupportTile) ? 1 : 0);
|
||||
bSubmitted ? 1 : 0,
|
||||
bDesiredAtRequest ? 1 : 0,
|
||||
bStartupCollisionGateTimingCaptured
|
||||
&& StartupCollisionGateSupportTile.Coord == SupportTile.Coord
|
||||
&& StartupCollisionGateSupportTile.Level == SupportTile.Level
|
||||
&& StartupCollisionGateSubmittedSeconds > 0.0
|
||||
? FMath::Max(0.0, StartupCollisionGateSubmittedSeconds
|
||||
- StartupCollisionGateFirstCheckSeconds)
|
||||
: -1.0);
|
||||
bPawnGateSupportRequestReported = true;
|
||||
}
|
||||
}
|
||||
@@ -4658,12 +4727,23 @@ void AVoxelWorld::UpdatePawnCollisionGate(APawn* Pawn, const FVector& PlayerPosi
|
||||
bPawnGateUnavailableReported = false;
|
||||
}
|
||||
|
||||
if (!bCurrentSupportReady && !bStartupCollisionGateTimingCaptured)
|
||||
{
|
||||
StartupCollisionGateSupportTile = CurrentSupportTile;
|
||||
bStartupCollisionGateTimingCaptured = true;
|
||||
StartupCollisionGateFirstCheckSeconds = FPlatformTime::Seconds();
|
||||
}
|
||||
|
||||
if (bCurrentSupportReady)
|
||||
{
|
||||
ReleasePawnCollisionGate();
|
||||
return;
|
||||
}
|
||||
|
||||
// Request collision even when the pawn is moving up and the gate will not hold it. A spawn or
|
||||
// teleport should not have to wait for downward velocity before the current floor is queued.
|
||||
RequestCollisionGateSupportTile(CurrentSupportTile);
|
||||
|
||||
// An unresolved tile above the pawn is not a landing hazard. Let upward movement continue;
|
||||
// only a downward/standing pawn needs its fall cancelled until the current support resolves.
|
||||
if (const ACharacter* Character = Cast<ACharacter>(Pawn))
|
||||
@@ -4678,27 +4758,6 @@ void AVoxelWorld::UpdatePawnCollisionGate(APawn* Pawn, const FVector& PlayerPosi
|
||||
}
|
||||
}
|
||||
|
||||
RequestCollisionGateSupportTile(CurrentSupportTile);
|
||||
|
||||
constexpr double MaxCollisionGateWaitSeconds = 2.0;
|
||||
if (bPawnCollisionGateEngaged && PawnGateEngagedAtSeconds > 0.0)
|
||||
{
|
||||
const double WaitSeconds = FPlatformTime::Seconds() - PawnGateEngagedAtSeconds;
|
||||
if (WaitSeconds >= MaxCollisionGateWaitSeconds)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning,
|
||||
TEXT("[VoxelForgeCollisionGate] wait_timeout pawn=%s waited_s=%.3f "
|
||||
"current_ready=%d support=(%d,%d,%d); releasing safety gate"),
|
||||
*Pawn->GetName(),
|
||||
WaitSeconds,
|
||||
bCurrentSupportReady ? 1 : 0,
|
||||
CurrentSupportTile.Coord.X, CurrentSupportTile.Coord.Y,
|
||||
CurrentSupportTile.Coord.Z);
|
||||
ReleasePawnCollisionGate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bPawnCollisionGateEngaged)
|
||||
{
|
||||
UE_LOG(LogTemp, Display,
|
||||
@@ -5137,6 +5196,30 @@ void AVoxelWorld::HandleTileCollisionCookComplete(
|
||||
if (bBodyInstalled)
|
||||
{
|
||||
CollisionReadyTiles.Add(Tile);
|
||||
if (bStartupCollisionGateTimingCaptured
|
||||
&& StartupCollisionGateSupportTile.Coord == Tile.Coord
|
||||
&& StartupCollisionGateSupportTile.Level == Tile.Level
|
||||
&& StartupCollisionGateReadySeconds <= 0.0)
|
||||
{
|
||||
StartupCollisionGateReadySeconds = FPlatformTime::Seconds();
|
||||
const double FirstCheckToSubmitSeconds = StartupCollisionGateSubmittedSeconds > 0.0
|
||||
? FMath::Max(0.0, StartupCollisionGateSubmittedSeconds
|
||||
- StartupCollisionGateFirstCheckSeconds)
|
||||
: -1.0;
|
||||
const double FirstCheckToReadySeconds = FMath::Max(
|
||||
0.0, StartupCollisionGateReadySeconds
|
||||
- StartupCollisionGateFirstCheckSeconds);
|
||||
const double SubmitToReadySeconds = StartupCollisionGateSubmittedSeconds > 0.0
|
||||
? FMath::Max(0.0, StartupCollisionGateReadySeconds
|
||||
- StartupCollisionGateSubmittedSeconds)
|
||||
: -1.0;
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCollisionGate] support_collision_ready tile=(%d,%d,%d) "
|
||||
"first_check_to_submit_s=%.6f first_check_to_collision_ready_s=%.6f "
|
||||
"submit_to_ready_s=%.6f"),
|
||||
Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z,
|
||||
FirstCheckToSubmitSeconds, FirstCheckToReadySeconds, SubmitToReadySeconds);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -5914,6 +5997,22 @@ void AVoxelWorld::BuildDesiredTiles(const FIntVector& Center, const FVector& Pla
|
||||
}
|
||||
}
|
||||
|
||||
if (bStartupCollisionGateTimingCaptured
|
||||
&& !bStartupCollisionGateAdoptionReported
|
||||
&& PendingTiles.Contains(StartupCollisionGateSupportTile))
|
||||
{
|
||||
const bool bSupportAdopted = IsDesired(StartupCollisionGateSupportTile);
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCollisionGate] support_request_desired_reconcile tile=(%d,%d,%d) "
|
||||
"adopted=%d desired_epoch=%u pending=%d"),
|
||||
StartupCollisionGateSupportTile.Coord.X,
|
||||
StartupCollisionGateSupportTile.Coord.Y,
|
||||
StartupCollisionGateSupportTile.Coord.Z,
|
||||
bSupportAdopted ? 1 : 0, DesiredEpoch,
|
||||
PendingTiles.Contains(StartupCollisionGateSupportTile) ? 1 : 0);
|
||||
bStartupCollisionGateAdoptionReported = true;
|
||||
}
|
||||
|
||||
// Cancel work that no longer belongs to this desired set before scheduling replacements. A
|
||||
// worker owns no mutable world state; it only observes its request token in GenerateTileResult.
|
||||
CancelObsoleteTileWork();
|
||||
@@ -6709,6 +6808,13 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
||||
return; // Budget full — wait for a task to finish.
|
||||
}
|
||||
PendingTiles.Add(Tile);
|
||||
if (bStartupCollisionGateTimingCaptured
|
||||
&& StartupCollisionGateSupportTile.Coord == Tile.Coord
|
||||
&& StartupCollisionGateSupportTile.Level == Tile.Level
|
||||
&& StartupCollisionGateSubmittedSeconds <= 0.0)
|
||||
{
|
||||
StartupCollisionGateSubmittedSeconds = FPlatformTime::Seconds();
|
||||
}
|
||||
const uint64 RequestStartCycles = FPlatformTime::Cycles64();
|
||||
|
||||
const FIntVector OriginVoxels = Tile.OriginVoxels(); // min corner, voxel coords
|
||||
|
||||
@@ -1118,6 +1118,7 @@ public:
|
||||
double HeadlessCollisionGateStressTestGateStartSeconds = 0.0;
|
||||
double HeadlessCollisionGateStressTestFullStopStartSeconds = 0.0;
|
||||
double HeadlessCollisionGateStressTestDistanceCm = 0.0;
|
||||
double HeadlessCollisionGateStressTestGateDistanceCm = 0.0;
|
||||
double HeadlessCollisionGateStressTestTotalGateDurationSeconds = 0.0;
|
||||
double HeadlessCollisionGateStressTestMaxGateDurationSeconds = 0.0;
|
||||
double HeadlessCollisionGateStressTestFullStopDurationSeconds = 0.0;
|
||||
@@ -1226,6 +1227,14 @@ public:
|
||||
bool bPawnGateUnsupportedReported = false;
|
||||
FVoxelTileKey PawnGateRequestedSupportTile;
|
||||
bool bPawnGateSupportRequestReported = false;
|
||||
// Keep the first unresolved support request's timing for the startup gate repro. This is
|
||||
// intentionally retained after release so the stress-test result can report the bootstrap path.
|
||||
FVoxelTileKey StartupCollisionGateSupportTile;
|
||||
bool bStartupCollisionGateTimingCaptured = false;
|
||||
bool bStartupCollisionGateAdoptionReported = false;
|
||||
double StartupCollisionGateFirstCheckSeconds = 0.0;
|
||||
double StartupCollisionGateSubmittedSeconds = 0.0;
|
||||
double StartupCollisionGateReadySeconds = 0.0;
|
||||
|
||||
// Desired-set membership STAMPÉE : clé → numéro du dernier crossing où la tuile était désirée.
|
||||
// BuildDesiredTiles upserte le stamp courant puis balaie la map UNE fois : les entrées à stamp
|
||||
|
||||
@@ -992,7 +992,9 @@ function Get-CollisionGateStressMetrics([string]$LogPath) {
|
||||
if ($result.Success -and $result.Groups['v'].Value -eq 'PASS') { $metrics.status = 'passed' }
|
||||
foreach ($key in @('reason', 'elapsed_s', 'distance_m', 'expected_distance_m', 'speed_cm_s',
|
||||
'gate_holds', 'gate_total_s', 'max_gate_s', 'full_stops',
|
||||
'full_stop_total_s', 'collision_gate')) {
|
||||
'full_stop_total_s', 'collision_gate', 'gate_progress_m', 'support_tile',
|
||||
'support_first_check_to_submit_s',
|
||||
'support_first_check_to_collision_ready_s')) {
|
||||
$match = [regex]::Match($line, ('{0}=(?<v>[^ ]+)' -f $key))
|
||||
if ($match.Success) { $metrics[$key] = $match.Groups['v'].Value }
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user