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
+169
-52
No files matched your search
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user