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:
Fr0zkaandClaude Opus 5.5 committed 2026-09-25 04:54:00 +02:00
1 parent 85a803b873
commit a49725d365
3 files changed
+169 -52

No files matched your search

+157 -51
View File
@@ -1899,6 +1899,7 @@ void AVoxelWorld::ConfigureHeadlessStreamingTest()
HeadlessCollisionGateStressTestGateStartSeconds = 0.0; HeadlessCollisionGateStressTestGateStartSeconds = 0.0;
HeadlessCollisionGateStressTestFullStopStartSeconds = 0.0; HeadlessCollisionGateStressTestFullStopStartSeconds = 0.0;
HeadlessCollisionGateStressTestDistanceCm = 0.0; HeadlessCollisionGateStressTestDistanceCm = 0.0;
HeadlessCollisionGateStressTestGateDistanceCm = 0.0;
HeadlessCollisionGateStressTestTotalGateDurationSeconds = 0.0; HeadlessCollisionGateStressTestTotalGateDurationSeconds = 0.0;
HeadlessCollisionGateStressTestMaxGateDurationSeconds = 0.0; HeadlessCollisionGateStressTestMaxGateDurationSeconds = 0.0;
HeadlessCollisionGateStressTestFullStopDurationSeconds = 0.0; HeadlessCollisionGateStressTestFullStopDurationSeconds = 0.0;
@@ -3044,6 +3045,10 @@ void AVoxelWorld::ObserveHeadlessCollisionGateStressTest(
HeadlessCollisionGateStressTestLastActualPosition = PlayerPosition; HeadlessCollisionGateStressTestLastActualPosition = PlayerPosition;
const bool bGateEngaged = bPawnCollisionGateEngaged; const bool bGateEngaged = bPawnCollisionGateEngaged;
if (bGateEngaged || bHeadlessCollisionGateStressTestGateWasEngaged)
{
HeadlessCollisionGateStressTestGateDistanceCm += HorizontalDelta;
}
if (bGateEngaged && !bHeadlessCollisionGateStressTestGateWasEngaged) if (bGateEngaged && !bHeadlessCollisionGateStressTestGateWasEngaged)
{ {
++HeadlessCollisionGateStressTestGateHoldCount; ++HeadlessCollisionGateStressTestGateHoldCount;
@@ -3449,18 +3454,32 @@ void AVoxelWorld::MaybeFinishHeadlessCollisionGateStressTest()
&& Now - HeadlessCollisionGateStressTestStartSeconds && Now - HeadlessCollisionGateStressTestStartSeconds
>= static_cast<double>(HeadlessCollisionGateStressTestDurationSeconds)) >= static_cast<double>(HeadlessCollisionGateStressTestDurationSeconds))
{ {
const double ExpectedDistanceCm = // Total travel also reflects authored terrain along this deterministic path. Keep it as
static_cast<double>(HeadlessCollisionGateStressTestSpeedCmPerSecond) // diagnostic output; judge the gate on support readiness and progress during a real hold.
* static_cast<double>(HeadlessCollisionGateStressTestDurationSeconds); if (!bStartupCollisionGateTimingCaptured)
if (HeadlessCollisionGateStressTestFullStopCount > 0)
{ {
bHeadlessCollisionGateStressTestFailed = true; 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; 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 else
{ {
@@ -3502,11 +3521,23 @@ void AVoxelWorld::MaybeFinishHeadlessCollisionGateStressTest()
const double ExpectedDistanceCm = const double ExpectedDistanceCm =
static_cast<double>(HeadlessCollisionGateStressTestSpeedCmPerSecond) static_cast<double>(HeadlessCollisionGateStressTestSpeedCmPerSecond)
* static_cast<double>(HeadlessCollisionGateStressTestDurationSeconds); * 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, UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCollisionGateStressTest] result=%s reason=%s elapsed_s=%.6f " TEXT("[VoxelForgeCollisionGateStressTest] result=%s reason=%s elapsed_s=%.6f "
"distance_m=%.6f expected_distance_m=%.6f speed_cm_s=%.1f " "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 " "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)"), "final_position=(%.1f,%.1f,%.1f)"),
bPassed ? TEXT("PASS") : TEXT("FAIL"), bPassed ? TEXT("PASS") : TEXT("FAIL"),
bPassed ? TEXT("continuous_horizontal_progress") bPassed ? TEXT("continuous_horizontal_progress")
@@ -3518,9 +3549,15 @@ void AVoxelWorld::MaybeFinishHeadlessCollisionGateStressTest()
HeadlessCollisionGateStressTestGateHoldCount, HeadlessCollisionGateStressTestGateHoldCount,
HeadlessCollisionGateStressTestTotalGateDurationSeconds, HeadlessCollisionGateStressTestTotalGateDurationSeconds,
HeadlessCollisionGateStressTestMaxGateDurationSeconds, HeadlessCollisionGateStressTestMaxGateDurationSeconds,
HeadlessCollisionGateStressTestGateDistanceCm / 100.0,
HeadlessCollisionGateStressTestFullStopCount, HeadlessCollisionGateStressTestFullStopCount,
HeadlessCollisionGateStressTestFullStopDurationSeconds, HeadlessCollisionGateStressTestFullStopDurationSeconds,
bCollisionGateEnabled ? 1 : 0, bCollisionGateEnabled ? 1 : 0,
StartupCollisionGateSupportTile.Coord.X,
StartupCollisionGateSupportTile.Coord.Y,
StartupCollisionGateSupportTile.Coord.Z,
SupportSubmitLatencySeconds,
SupportReadyLatencySeconds,
HeadlessCollisionGateStressTestLastActualPosition.X, HeadlessCollisionGateStressTestLastActualPosition.X,
HeadlessCollisionGateStressTestLastActualPosition.Y, HeadlessCollisionGateStressTestLastActualPosition.Y,
HeadlessCollisionGateStressTestLastActualPosition.Z); HeadlessCollisionGateStressTestLastActualPosition.Z);
@@ -4505,6 +4542,14 @@ void AVoxelWorld::RequestCollisionGateSupportTile(const FVoxelTileKey& SupportTi
return; return;
} }
const double RequestCheckSeconds = FPlatformTime::Seconds();
if (!bStartupCollisionGateTimingCaptured)
{
StartupCollisionGateSupportTile = SupportTile;
bStartupCollisionGateTimingCaptured = true;
StartupCollisionGateFirstCheckSeconds = RequestCheckSeconds;
}
if (!bPawnGateSupportRequestReported if (!bPawnGateSupportRequestReported
|| PawnGateRequestedSupportTile.Coord != SupportTile.Coord || PawnGateRequestedSupportTile.Coord != SupportTile.Coord
|| PawnGateRequestedSupportTile.Level != SupportTile.Level) || PawnGateRequestedSupportTile.Level != SupportTile.Level)
@@ -4513,38 +4558,51 @@ void AVoxelWorld::RequestCollisionGateSupportTile(const FVoxelTileKey& SupportTi
bPawnGateSupportRequestReported = false; bPawnGateSupportRequestReported = false;
} }
// The gate runs before UpdateChunksAroundPosition. If the floor has just moved outside the // A gate check can run in BeginPlay, before the first desired set, and before the rebuild on a
// old desired stamp, ask that normal rebuild to make it the critical floor request rather than // teleport/crossing tick. Submit the level-0 collision tile immediately; the desired rebuild
// generating an orphan tile that the streaming policy could immediately cull. // below adopts the same key through PendingTiles and IsDesired, so it is neither duplicated nor
if (DesiredStamp == 0 || !IsDesired(SupportTile)) // cancelled when the full clipmap arrives.
const bool bDesiredAtRequest = DesiredStamp != 0 && IsDesired(SupportTile);
if (!bDesiredAtRequest)
{ {
bForceDesiredRebuild = true; 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 // Preserve the established critical floor ordering when it already belongs to this desired
// immediate game-thread request when no mesh/cook is already in flight. The critical work // epoch. Before the first build there is no array to promote into; LoadTile below still uses
// class uses the established BackgroundHigh streaming priority. // the same CollisionCritical work class and priority queue.
if (bDesiredAtRequest)
{
CriticalDesiredTiles.Remove(SupportTile); CriticalDesiredTiles.Remove(SupportTile);
CriticalDesiredTiles.Insert(SupportTile, 0); CriticalDesiredTiles.Insert(SupportTile, 0);
}
if (PendingTiles.Contains(SupportTile) || LoadedTiles.Contains(SupportTile)) 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) if (!bPawnGateSupportRequestReported)
{ {
UE_LOG(LogTemp, Display, UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCollisionGate] support_request_present tile=(%d,%d,%d) " 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, SupportTile.Coord.X, SupportTile.Coord.Y, SupportTile.Coord.Z,
PendingTiles.Contains(SupportTile) ? 1 : 0, 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; bPawnGateSupportRequestReported = true;
} }
return; return;
@@ -4552,17 +4610,28 @@ void AVoxelWorld::RequestCollisionGateSupportTile(const FVoxelTileKey& SupportTi
const int32 PendingBefore = PendingTiles.Num(); const int32 PendingBefore = PendingTiles.Num();
LoadTile(SupportTile, EVoxelTileWorkPriority::CollisionCritical); LoadTile(SupportTile, EVoxelTileWorkPriority::CollisionCritical);
if (PendingTiles.Num() > PendingBefore) const bool bSubmitted = PendingTiles.Num() > PendingBefore
|| PendingTiles.Contains(SupportTile);
if (bSubmitted)
{ {
bAllChunksLoaded = false; bAllChunksLoaded = false;
} }
if (!bPawnGateSupportRequestReported) if (!bPawnGateSupportRequestReported || bSubmitted)
{ {
UE_LOG(LogTemp, Display, UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCollisionGate] support_request tile=(%d,%d,%d) " 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, 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; bPawnGateSupportRequestReported = true;
} }
} }
@@ -4658,12 +4727,23 @@ void AVoxelWorld::UpdatePawnCollisionGate(APawn* Pawn, const FVector& PlayerPosi
bPawnGateUnavailableReported = false; bPawnGateUnavailableReported = false;
} }
if (!bCurrentSupportReady && !bStartupCollisionGateTimingCaptured)
{
StartupCollisionGateSupportTile = CurrentSupportTile;
bStartupCollisionGateTimingCaptured = true;
StartupCollisionGateFirstCheckSeconds = FPlatformTime::Seconds();
}
if (bCurrentSupportReady) if (bCurrentSupportReady)
{ {
ReleasePawnCollisionGate(); ReleasePawnCollisionGate();
return; 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; // 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. // only a downward/standing pawn needs its fall cancelled until the current support resolves.
if (const ACharacter* Character = Cast<ACharacter>(Pawn)) 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) if (!bPawnCollisionGateEngaged)
{ {
UE_LOG(LogTemp, Display, UE_LOG(LogTemp, Display,
@@ -5137,6 +5196,30 @@ void AVoxelWorld::HandleTileCollisionCookComplete(
if (bBodyInstalled) if (bBodyInstalled)
{ {
CollisionReadyTiles.Add(Tile); 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 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 // 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. // worker owns no mutable world state; it only observes its request token in GenerateTileResult.
CancelObsoleteTileWork(); CancelObsoleteTileWork();
@@ -6709,6 +6808,13 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
return; // Budget full — wait for a task to finish. return; // Budget full — wait for a task to finish.
} }
PendingTiles.Add(Tile); 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 uint64 RequestStartCycles = FPlatformTime::Cycles64();
const FIntVector OriginVoxels = Tile.OriginVoxels(); // min corner, voxel coords const FIntVector OriginVoxels = Tile.OriginVoxels(); // min corner, voxel coords
+9
View File
@@ -1118,6 +1118,7 @@ public:
double HeadlessCollisionGateStressTestGateStartSeconds = 0.0; double HeadlessCollisionGateStressTestGateStartSeconds = 0.0;
double HeadlessCollisionGateStressTestFullStopStartSeconds = 0.0; double HeadlessCollisionGateStressTestFullStopStartSeconds = 0.0;
double HeadlessCollisionGateStressTestDistanceCm = 0.0; double HeadlessCollisionGateStressTestDistanceCm = 0.0;
double HeadlessCollisionGateStressTestGateDistanceCm = 0.0;
double HeadlessCollisionGateStressTestTotalGateDurationSeconds = 0.0; double HeadlessCollisionGateStressTestTotalGateDurationSeconds = 0.0;
double HeadlessCollisionGateStressTestMaxGateDurationSeconds = 0.0; double HeadlessCollisionGateStressTestMaxGateDurationSeconds = 0.0;
double HeadlessCollisionGateStressTestFullStopDurationSeconds = 0.0; double HeadlessCollisionGateStressTestFullStopDurationSeconds = 0.0;
@@ -1226,6 +1227,14 @@ public:
bool bPawnGateUnsupportedReported = false; bool bPawnGateUnsupportedReported = false;
FVoxelTileKey PawnGateRequestedSupportTile; FVoxelTileKey PawnGateRequestedSupportTile;
bool bPawnGateSupportRequestReported = false; 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. // 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 // BuildDesiredTiles upserte le stamp courant puis balaie la map UNE fois : les entrées à stamp
+3 -1
View File
@@ -992,7 +992,9 @@ function Get-CollisionGateStressMetrics([string]$LogPath) {
if ($result.Success -and $result.Groups['v'].Value -eq 'PASS') { $metrics.status = 'passed' } 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', foreach ($key in @('reason', 'elapsed_s', 'distance_m', 'expected_distance_m', 'speed_cm_s',
'gate_holds', 'gate_total_s', 'max_gate_s', 'full_stops', '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)) $match = [regex]::Match($line, ('{0}=(?<v>[^ ]+)' -f $key))
if ($match.Success) { $metrics[$key] = $match.Groups['v'].Value } if ($match.Success) { $metrics[$key] = $match.Groups['v'].Value }
} }