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
+171 -54

No files matched your search

+159 -53
View File
@@ -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
+9
View File
@@ -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