wip(gate): support column kept through rebuilds, hold only on swept contact -- NEVER COMPILED OR RUN
Stopped mid-round: the PC ran out of memory (another app held 11 GB), new processes failed to start (0xC0000142), and the round ended without verifying anything after the edit. Confirmed in code (performance review finding 1): a support request outside the stamped desired set forces a rebuild that cancels it, the gate asks for one unresolved tile at a time, and it holds before the pending tile is anywhere near the pawn's next step. Uncompiled edits: a short collision-only support column retained through desired rebuilds and prioritised; hold only when the swept capsule is about to enter a pending tile; downward speed restored on release; opt-in deep-drop fixture (carved shaft) in the stress harness. Evidence so far: shallow 10-voxel fall, gate on, before the edit: 1 hold of 1.51 s, clearance +120.6 cm. The 80/160-voxel attempts spawned OUTSIDE the strate layout (support unavailable, zero holds) and failed the penetration check at -11.1 cm: not the reviewed loop, but worth a look. Compile risks: GetGravityZ(), TSet pruning in the rebuild, the fixture's carve/log arguments. 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
0b8fbc5591
commit
116b57f01e
4 files changed
+178
-16
No files matched your search
+4
-2
@@ -308,8 +308,10 @@ casse cette arithmetique *structurellement* — `BeginPlay` loggue une **Error**
|
||||
| `RecordTileHash` / `WriteTileHashDump` | — | Headless `-voxel.TileHashDump=<absolute path>` parity instrument. Records the exact `FVoxelMeshData` used by the streaming result, including empty/classified tiles, then writes sorted absolute tile keys, LOD step/cells, content-band limits, settings digest context, and the shared `VoxelGeometryHash` at `EndPlay`. |
|
||||
| `SyncRemeshTile` | — | **INSTANT DIG**: level-0 same-frame re-mesh on the game thread (`GenerateTileResult` + `ApplyTileResult` inline, Cells=CHUNK_SIZE/Step=1 + strate band, no capture). Used for the tile under the brush centre so a carve is visible THIS frame; one full-res gen on the game thread. |
|
||||
| `UpdateChunksAroundPosition` | 6870 | Builds desired set, cancels pending keys that left it, sorts from the **true pawn position**, submits the occupied/support LOD0 tile and the next tile along the pawn heading as an absolute `BackgroundHigh` prefix, then loads/unloads and handles LOD changes. **Delta cull**: `BuildDesiredTiles` returns the LEAVERS (stamped `DesiredStamped` map, one sweep) — only those + `TransitionHold` are considered per crossing, not every loaded tile. `BuildDesiredTiles` also applies `RenderDistanceChunks`: the outermost shell widens to cover the distance as level-MaxClipLevel MC tiles (`VF_OuterShell` shared with `IsTileInClipRange` so the cull sees the same horizon), dz pre-clamped to the vertical band. **§9.3 anchors:** also prunes dead `StreamingAnchors` + detects their chunk crossings → rebuilds the desired set when an anchor moves/(un)registers (`bAnchorsMoved`/`bForceDesiredRebuild`), same cadence as player movement. §8.10. |
|
||||
| `AdvanceHeadlessCollisionGateStressTest` / `ObserveHeadlessCollisionGateStressTest` / `MaybeFinishHeadlessCollisionGateStressTest` | — | Explicit `-voxel.TestCollisionGateStress=1` harness only. The `GetDensityAt` column at capsule feet defines the local air/solid surface; each frame records actor position, velocity, direct foot tile and gate support readiness, gate state, signed feet clearance, and a ≤0.5-voxel sweep between positions. PASS requires no measured clearance below −5 cm; support submission/readiness, travel, and holds are diagnostics. Result logs include the terminal density column and the first crossing below spawn height. |
|
||||
| `BuildDesiredTiles` | 6482 | Builds `DesiredSorted`+`DesiredStamped` (player clipmap shells) then `AddAnchorDesiredTiles()` before the leaver sweep; it orders from the true local pawn position and builds `CriticalDesiredTiles` for the occupied/support and heading tiles. Also rebuilds `DesiredTransitionDescendants` (ancestor → desired descendants) for the reconcile pass. |
|
||||
| `AdvanceHeadlessCollisionGateStressTest` / `ObserveHeadlessCollisionGateStressTest` / `MaybeFinishHeadlessCollisionGateStressTest` | — | Explicit `-voxel.TestCollisionGateStress=1` harness only. `voxel.TestCollisionGateStressSpawnHeightVoxels` sets the drop above the measured density surface (default 10 voxels); optional `voxel.TestCollisionGateStressDeepDrop=1` carves a process-local shaft for a fall deeper than the level-0 ring. The `GetDensityAt` column at capsule feet defines the local air/solid surface. Each frame records actor position, velocity, direct foot tile and gate support readiness, gate state, signed feet clearance, and a ≤0.5-voxel sweep between positions. PASS requires no measured clearance below −5 cm; support submission/readiness, travel, and holds are diagnostics. Result logs include the terminal density column and first crossing below spawn height. |
|
||||
| `BuildDesiredTiles` | 6482 | Builds `DesiredSorted`+`DesiredStamped` (player clipmap shells), then adds streaming anchors and the pending player's collision-only support column before the leaver sweep. It orders from the true local pawn position and builds `CriticalDesiredTiles` for the unresolved support target, occupied/support and heading tiles. Also rebuilds `DesiredTransitionDescendants` (ancestor → desired descendants) for the reconcile pass. |
|
||||
| `CollisionGateDesiredTiles` / `RequestCollisionGateSupportTile` | 5062 | Retains an unresolved support target and up to three level-0 tiles below it across desired-set rebuilds. The additional keys are hidden collision-only tiles, queued ahead of ordinary streaming, and pruned once resolved or outside the active column. |
|
||||
| `IsPawnCapsuleOverlappingPendingFloor` / `UpdatePawnCollisionGate` | 4987 / 5195 | Uses the current-to-next-step swept capsule bounds to start a hold only when the pending support tile is about to intersect the pawn. A hold preserves horizontal input and restores downward speed after collision becomes ready. |
|
||||
| `VoxelClipmapDesiredTiles::Build` / `AlignedRingBounds` (`Private/VoxelClipmapDesiredTiles.h`) | — | The pure clipmap selector. **Every finer ring snaps outward to a child-pair boundary** (`AlignedRingBounds`, alignment 2; the outermost ring needs none), so a coarser tile is wholly inside or wholly outside the finer ring: **no two desired tiles of different levels overlap** (an unaligned 7-wide odd ring leaves one half-covered coarse tile per axis side, drawn over the fine one: 635 pairs at the owner config). Cost: LOD0 ring 343 → 512 tiles. Guarded by `VoxelForge.Streaming.ClipmapDesiredTilesNoOverlapCoverage`. Anchor tiles appended later are outside this guarantee. |
|
||||
| `ReconcileReadyTransitionVisibility` | — | Visibility-only pass while streaming is unsettled: hides a retired tile once all desired tiles over its footprint are loaded, reveals new tiles once no visible retired tile overlaps them. **Indexed** (dyadic ancestors + `DesiredTransitionDescendants`, visible retired tiles collected once per pass): moving p95 32 ms → 0.31 ms. `voxel.MeasureReadyTransitionVisibility=1` logs per-frame samples. |
|
||||
| `RequestCollisionGateSupportTile` / `UpdatePawnCollisionGate` | — | The gate submits the pawn's level-0 support tile **immediately** as `CollisionCritical`, even before the first desired set exists (the desired rebuild then adopts the same pending key). The gate holds downward Z only, with **no timeout** (a 2 s release would fire at every startup, while the game thread applies nothing for ~2.2 s). |
|
||||
|
||||
@@ -585,6 +585,7 @@ void AVoxelWorld::RegenerateAllChunks()
|
||||
// §9.4 collision-only tracking references destroyed tiles — clear (rebuilt on the next crossing).
|
||||
CollisionOnlyTiles.Empty();
|
||||
PrevCollisionOnlyTiles.Empty();
|
||||
CollisionGateDesiredTiles.Empty();
|
||||
|
||||
// Reset streaming state so the next Tick rebuilds the desired set and reloads.
|
||||
LastUpdateCenter = FIntVector(INT32_MAX, INT32_MAX, INT32_MAX);
|
||||
@@ -1883,6 +1884,8 @@ void AVoxelWorld::ConfigureHeadlessStreamingTest()
|
||||
bHeadlessCollisionGateStressTestTerrainMeasurementMissing = false;
|
||||
bHeadlessCollisionGateStressTestLanded = false;
|
||||
bHeadlessCollisionGateStressTestLaunchSupportTimingCaptured = false;
|
||||
bHeadlessCollisionGateStressDeepDrop = false;
|
||||
bHeadlessCollisionGateStressDeepDropPrepared = false;
|
||||
HeadlessCollisionGateStressTestSpeedCmPerSecond = 6000.0f;
|
||||
HeadlessCollisionGateStressTestDurationSeconds = 2.0f;
|
||||
HeadlessCollisionGateStressTestTimeoutSeconds = 15.0f;
|
||||
@@ -2192,15 +2195,23 @@ void AVoxelWorld::ConfigureHeadlessStreamingTest()
|
||||
*HeadlessCollisionGateStressTestFailureReason);
|
||||
}
|
||||
float StressSpeed = HeadlessCollisionGateStressTestSpeedCmPerSecond;
|
||||
float StressSpawnHeight = HeadlessCollisionGateStressTestSpawnHeightVoxels;
|
||||
float StressDuration = HeadlessCollisionGateStressTestDurationSeconds;
|
||||
float StressTimeout = HeadlessCollisionGateStressTestTimeoutSeconds;
|
||||
int32 StressDeepDrop = 0;
|
||||
FParse::Value(CommandLine, TEXT("voxel.TestCollisionGateStressSpeedCmPerSecond="),
|
||||
StressSpeed);
|
||||
FParse::Value(CommandLine, TEXT("voxel.TestCollisionGateStressSpawnHeightVoxels="),
|
||||
StressSpawnHeight);
|
||||
FParse::Value(CommandLine, TEXT("voxel.TestCollisionGateStressDeepDrop="),
|
||||
StressDeepDrop);
|
||||
FParse::Value(CommandLine, TEXT("voxel.TestCollisionGateStressDurationSeconds="),
|
||||
StressDuration);
|
||||
FParse::Value(CommandLine, TEXT("voxel.TestCollisionGateStressTimeoutSeconds="),
|
||||
StressTimeout);
|
||||
HeadlessCollisionGateStressTestSpeedCmPerSecond = FMath::Max(StressSpeed, 1.0f);
|
||||
HeadlessCollisionGateStressTestSpawnHeightVoxels = FMath::Max(StressSpawnHeight, 1.0f);
|
||||
bHeadlessCollisionGateStressDeepDrop = StressDeepDrop != 0;
|
||||
HeadlessCollisionGateStressTestDurationSeconds = FMath::Max(StressDuration, 0.25f);
|
||||
HeadlessCollisionGateStressTestTimeoutSeconds = FMath::Max(StressTimeout, 1.0f);
|
||||
// The stress path owns movement and keeps the streamer centred at the launch tile. Do
|
||||
@@ -2210,8 +2221,10 @@ void AVoxelWorld::ConfigureHeadlessStreamingTest()
|
||||
HeadlessCollisionGateStressTestBeginSeconds = 0.0;
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCollisionGateStressTest] configured=1 speed_cm_s=%.1f "
|
||||
"duration_after_landing_s=%.3f timeout_s=%.3f"),
|
||||
"spawn_height_vox=%.2f deep_drop=%d duration_after_landing_s=%.3f timeout_s=%.3f"),
|
||||
HeadlessCollisionGateStressTestSpeedCmPerSecond,
|
||||
HeadlessCollisionGateStressTestSpawnHeightVoxels,
|
||||
bHeadlessCollisionGateStressDeepDrop ? 1 : 0,
|
||||
HeadlessCollisionGateStressTestDurationSeconds,
|
||||
HeadlessCollisionGateStressTestTimeoutSeconds);
|
||||
}
|
||||
@@ -2725,7 +2738,7 @@ void AVoxelWorld::ObserveHeadlessStrateCrossingTest(
|
||||
const FVector LocalPosition = WorldToLocalVoxel(PlayerPosition);
|
||||
FVoxelTileKey PendingFloorTile;
|
||||
if (IsPawnCapsuleOverlappingPendingFloor(
|
||||
PlayerPawn, PlayerPosition, &PendingFloorTile))
|
||||
PlayerPawn, PlayerPosition, PlayerPosition, &PendingFloorTile))
|
||||
{
|
||||
bHeadlessStrateCrossingTestPendingFloorOverlap = true;
|
||||
bHeadlessStrateCrossingTestFailed = true;
|
||||
@@ -3002,6 +3015,21 @@ void AVoxelWorld::AdvanceHeadlessCollisionGateStressTest(
|
||||
const double Now = FPlatformTime::Seconds();
|
||||
if (!bHeadlessCollisionGateStressTestStartPlaced)
|
||||
{
|
||||
if (bHeadlessCollisionGateStressDeepDrop && !bHeadlessCollisionGateStressDeepDropPrepared)
|
||||
{
|
||||
// Headless regression fixture: carve a narrow shaft through several level-0 tiles so
|
||||
// the pawn falls through known-empty tiles before reaching an unresolved floor tile.
|
||||
// This is process-local test data; no asset or save file is touched.
|
||||
const FVector ShaftTopWorld = LocalVoxelToWorld(FVector(18.2f, 0.0f, 40.0f));
|
||||
const FVector ShaftBottomWorld = LocalVoxelToWorld(FVector(18.2f, 0.0f, -90.0f));
|
||||
CarveCapsule(ShaftTopWorld, ShaftBottomWorld, 7.0f, 24.0f);
|
||||
bHeadlessCollisionGateStressDeepDropPrepared = true;
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCollisionGateStressTest] deep_drop_fixture=carved "
|
||||
"local_vox_top=(18.20,0.00,40.00) local_vox_bottom=(18.20,0.00,-90.00) "
|
||||
"radius_vox=7.00"));
|
||||
}
|
||||
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCollisionGateStressTest] owner_settings clip_radius=%d "
|
||||
"max_clip_level=%d render_distance_chunks=%d"),
|
||||
@@ -3022,7 +3050,8 @@ void AVoxelWorld::AdvanceHeadlessCollisionGateStressTest(
|
||||
// must fall onto a freshly requested level-0 tile before the horizontal diagnostic.
|
||||
constexpr float LaunchLocalVoxelX = 18.2f;
|
||||
constexpr float LaunchLocalVoxelY = 0.0f;
|
||||
constexpr float LaunchHeightAboveGroundVoxels = 10.0f;
|
||||
const float LaunchHeightAboveGroundVoxels =
|
||||
HeadlessCollisionGateStressTestSpawnHeightVoxels;
|
||||
const FVector SurfaceProbeWorld = LocalVoxelToWorld(
|
||||
FVector(LaunchLocalVoxelX, LaunchLocalVoxelY, 0.0f));
|
||||
float ProbeFeetDensity = 0.0f;
|
||||
@@ -4032,7 +4061,7 @@ void AVoxelWorld::MaybeFinishHeadlessCollisionGateStressTest()
|
||||
|
||||
UE_LOG(LogTemp, Display,
|
||||
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 spawn_height_vox=%.2f deep_drop=%d "
|
||||
"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 "
|
||||
@@ -4051,6 +4080,8 @@ void AVoxelWorld::MaybeFinishHeadlessCollisionGateStressTest()
|
||||
HeadlessCollisionGateStressTestDistanceCm / 100.0,
|
||||
ExpectedDistanceCm / 100.0,
|
||||
HeadlessCollisionGateStressTestSpeedCmPerSecond,
|
||||
HeadlessCollisionGateStressTestSpawnHeightVoxels,
|
||||
bHeadlessCollisionGateStressDeepDrop ? 1 : 0,
|
||||
HeadlessCollisionGateStressTestGateHoldCount,
|
||||
HeadlessCollisionGateStressTestTotalGateDurationSeconds,
|
||||
HeadlessCollisionGateStressTestMaxGateDurationSeconds,
|
||||
@@ -4978,7 +5009,8 @@ bool AVoxelWorld::IsPlayerSupportCollisionReady(APawn* Pawn, const FVector& Play
|
||||
}
|
||||
|
||||
bool AVoxelWorld::IsPawnCapsuleOverlappingPendingFloor(
|
||||
APawn* Pawn, const FVector& PlayerPosition, FVoxelTileKey* OutPendingTile) const
|
||||
APawn* Pawn, const FVector& PlayerPosition, const FVector& NextPlayerPosition,
|
||||
FVoxelTileKey* OutPendingTile) const
|
||||
{
|
||||
if (OutPendingTile != nullptr)
|
||||
{
|
||||
@@ -5025,6 +5057,7 @@ bool AVoxelWorld::IsPawnCapsuleOverlappingPendingFloor(
|
||||
}
|
||||
|
||||
const FVector LocalPosition = WorldToLocalVoxel(PlayerPosition);
|
||||
const FVector LocalNextPosition = WorldToLocalVoxel(NextPlayerPosition);
|
||||
const float HalfHeightVoxels = HalfHeightCm / VOXEL_SIZE;
|
||||
const float RadiusVoxels = RadiusCm / VOXEL_SIZE;
|
||||
const float TileMinX = static_cast<float>(SupportTile.Coord.X * CHUNK_SIZE);
|
||||
@@ -5033,12 +5066,14 @@ bool AVoxelWorld::IsPawnCapsuleOverlappingPendingFloor(
|
||||
const float TileMaxY = static_cast<float>((SupportTile.Coord.Y + 1) * CHUNK_SIZE);
|
||||
const float TileMinZ = static_cast<float>(SupportTile.Coord.Z * CHUNK_SIZE);
|
||||
const float TileMaxZ = static_cast<float>((SupportTile.Coord.Z + 1) * CHUNK_SIZE);
|
||||
const float CapsuleMinZ = LocalPosition.Z - HalfHeightVoxels;
|
||||
const float CapsuleMaxZ = LocalPosition.Z + HalfHeightVoxels;
|
||||
const bool bHorizontalOverlap = LocalPosition.X + RadiusVoxels > TileMinX
|
||||
&& LocalPosition.X - RadiusVoxels < TileMaxX
|
||||
&& LocalPosition.Y + RadiusVoxels > TileMinY
|
||||
&& LocalPosition.Y - RadiusVoxels < TileMaxY;
|
||||
const float CapsuleMinZ = FMath::Min(LocalPosition.Z, LocalNextPosition.Z) - HalfHeightVoxels;
|
||||
const float CapsuleMaxZ = FMath::Max(LocalPosition.Z, LocalNextPosition.Z) + HalfHeightVoxels;
|
||||
const float CapsuleMinX = FMath::Min(LocalPosition.X, LocalNextPosition.X) - RadiusVoxels;
|
||||
const float CapsuleMaxX = FMath::Max(LocalPosition.X, LocalNextPosition.X) + RadiusVoxels;
|
||||
const float CapsuleMinY = FMath::Min(LocalPosition.Y, LocalNextPosition.Y) - RadiusVoxels;
|
||||
const float CapsuleMaxY = FMath::Max(LocalPosition.Y, LocalNextPosition.Y) + RadiusVoxels;
|
||||
const bool bHorizontalOverlap = CapsuleMaxX > TileMinX && CapsuleMinX < TileMaxX
|
||||
&& CapsuleMaxY > TileMinY && CapsuleMinY < TileMaxY;
|
||||
const bool bVerticalOverlap = CapsuleMaxZ > TileMinZ && CapsuleMinZ < TileMaxZ;
|
||||
if (!bHorizontalOverlap || !bVerticalOverlap)
|
||||
{
|
||||
@@ -5101,10 +5136,38 @@ void AVoxelWorld::RequestCollisionGateSupportTile(const FVoxelTileKey& SupportTi
|
||||
// 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)
|
||||
bool bNeedsDesiredRebuild = !bDesiredAtRequest;
|
||||
constexpr int32 CollisionGatePrefetchDownTiles = 4;
|
||||
for (auto It = CollisionGateDesiredTiles.CreateIterator(); It; ++It)
|
||||
{
|
||||
bForceDesiredRebuild = true;
|
||||
const FVoxelTileKey& ExistingTile = *It;
|
||||
const int32 RelativeZ = SupportTile.Coord.Z - ExistingTile.Coord.Z;
|
||||
if (ExistingTile.Coord.X != SupportTile.Coord.X
|
||||
|| ExistingTile.Coord.Y != SupportTile.Coord.Y
|
||||
|| RelativeZ < 0 || RelativeZ >= CollisionGatePrefetchDownTiles)
|
||||
{
|
||||
bNeedsDesiredRebuild |= IsDesired(ExistingTile);
|
||||
It.RemoveCurrent();
|
||||
}
|
||||
}
|
||||
for (int32 Offset = 0; Offset < CollisionGatePrefetchDownTiles; ++Offset)
|
||||
{
|
||||
FVoxelTileKey ColumnTile = SupportTile;
|
||||
ColumnTile.Coord.Z -= Offset;
|
||||
const TCHAR* UnavailableReason = nullptr;
|
||||
if (IsTileCollisionReady(ColumnTile)
|
||||
|| CollisionNotRequiredTiles.Contains(ColumnTile)
|
||||
|| IsCollisionTileUnavailable(ColumnTile, UnavailableReason))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!CollisionGateDesiredTiles.Contains(ColumnTile))
|
||||
{
|
||||
CollisionGateDesiredTiles.Add(ColumnTile);
|
||||
bNeedsDesiredRebuild |= !IsDesired(ColumnTile);
|
||||
}
|
||||
}
|
||||
bForceDesiredRebuild |= bNeedsDesiredRebuild;
|
||||
|
||||
// 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
|
||||
@@ -5188,7 +5251,6 @@ void AVoxelWorld::RequestCollisionGateSupportTile(const FVoxelTileKey& SupportTi
|
||||
void AVoxelWorld::UpdatePawnCollisionGate(APawn* Pawn, const FVector& PlayerPosition,
|
||||
float DeltaTime)
|
||||
{
|
||||
static_cast<void>(DeltaTime);
|
||||
if (!IsValid(Pawn) || bShuttingDown.load(std::memory_order_relaxed))
|
||||
{
|
||||
return;
|
||||
@@ -5307,6 +5369,35 @@ void AVoxelWorld::UpdatePawnCollisionGate(APawn* Pawn, const FVector& PlayerPosi
|
||||
}
|
||||
}
|
||||
|
||||
// Ask for support now, but do not stop the pawn while an unresolved tile is still below its
|
||||
// next physics step. The swept capsule bounds catch the step before movement can enter it.
|
||||
FVector NextPlayerPosition = PlayerPosition;
|
||||
if (DeltaTime > 0.0f)
|
||||
{
|
||||
FVector Velocity = Pawn->GetVelocity();
|
||||
float GravityZ = 0.0f;
|
||||
if (const ACharacter* Character = Cast<ACharacter>(Pawn))
|
||||
{
|
||||
if (const UCharacterMovementComponent* CharacterMovement = Character->GetCharacterMovement())
|
||||
{
|
||||
Velocity = CharacterMovement->Velocity;
|
||||
GravityZ = CharacterMovement->GetGravityZ();
|
||||
}
|
||||
}
|
||||
NextPlayerPosition += Velocity * DeltaTime;
|
||||
if (Velocity.Z <= KINDA_SMALL_NUMBER && GravityZ < 0.0f)
|
||||
{
|
||||
NextPlayerPosition.Z += 0.5f * GravityZ * DeltaTime * DeltaTime;
|
||||
}
|
||||
}
|
||||
FVoxelTileKey PendingFloorTile;
|
||||
if (!IsPawnCapsuleOverlappingPendingFloor(
|
||||
Pawn, PlayerPosition, NextPlayerPosition, &PendingFloorTile))
|
||||
{
|
||||
ReleasePawnCollisionGate();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!bPawnCollisionGateEngaged)
|
||||
{
|
||||
UE_LOG(LogTemp, Display,
|
||||
@@ -5335,6 +5426,8 @@ void AVoxelWorld::EngagePawnCollisionGate(APawn* Pawn)
|
||||
FVector Velocity = CharacterMovement->Velocity;
|
||||
if (Velocity.Z < 0.0f)
|
||||
{
|
||||
SavedGateVerticalVelocity = Velocity.Z;
|
||||
bSavedGateVerticalVelocity = true;
|
||||
Velocity.Z = 0.0f;
|
||||
CharacterMovement->Velocity = Velocity;
|
||||
CharacterMovement->UpdateComponentVelocity();
|
||||
@@ -5345,6 +5438,8 @@ void AVoxelWorld::EngagePawnCollisionGate(APawn* Pawn)
|
||||
FVector Velocity = PawnMovement->Velocity;
|
||||
if (Velocity.Z < 0.0f)
|
||||
{
|
||||
SavedGateVerticalVelocity = Velocity.Z;
|
||||
bSavedGateVerticalVelocity = true;
|
||||
Velocity.Z = 0.0f;
|
||||
PawnMovement->Velocity = Velocity;
|
||||
PawnMovement->UpdateComponentVelocity();
|
||||
@@ -5366,6 +5461,8 @@ void AVoxelWorld::EngagePawnCollisionGate(APawn* Pawn)
|
||||
FVector Velocity = CharacterMovement->Velocity;
|
||||
if (Velocity.Z < 0.0f)
|
||||
{
|
||||
SavedGateVerticalVelocity = Velocity.Z;
|
||||
bSavedGateVerticalVelocity = true;
|
||||
Velocity.Z = 0.0f;
|
||||
CharacterMovement->Velocity = Velocity;
|
||||
}
|
||||
@@ -5383,6 +5480,8 @@ void AVoxelWorld::EngagePawnCollisionGate(APawn* Pawn)
|
||||
FVector Velocity = PawnMovement->Velocity;
|
||||
if (Velocity.Z < 0.0f)
|
||||
{
|
||||
SavedGateVerticalVelocity = Velocity.Z;
|
||||
bSavedGateVerticalVelocity = true;
|
||||
Velocity.Z = 0.0f;
|
||||
PawnMovement->Velocity = Velocity;
|
||||
PawnMovement->UpdateComponentVelocity();
|
||||
@@ -5410,6 +5509,8 @@ void AVoxelWorld::ReleasePawnCollisionGate()
|
||||
CollisionGatedPawnMovement.Reset();
|
||||
CollisionGatedCharacterMovement.Reset();
|
||||
bSavedCharacterGravityScale = false;
|
||||
bSavedGateVerticalVelocity = false;
|
||||
SavedGateVerticalVelocity = 0.0f;
|
||||
bPawnGateSupportRequestReported = false;
|
||||
PawnGateEngagedAtSeconds = 0.0;
|
||||
return;
|
||||
@@ -5420,8 +5521,20 @@ void AVoxelWorld::ReleasePawnCollisionGate()
|
||||
if (bSavedCharacterGravityScale)
|
||||
{
|
||||
CharacterMovement->GravityScale = SavedCharacterGravityScale;
|
||||
}
|
||||
if (bSavedGateVerticalVelocity && CharacterMovement->Velocity.Z <= KINDA_SMALL_NUMBER)
|
||||
{
|
||||
CharacterMovement->Velocity.Z = SavedGateVerticalVelocity;
|
||||
}
|
||||
CharacterMovement->UpdateComponentVelocity();
|
||||
}
|
||||
else if (UPawnMovementComponent* PawnMovement = CollisionGatedPawnMovement.Get())
|
||||
{
|
||||
if (bSavedGateVerticalVelocity && PawnMovement->Velocity.Z <= KINDA_SMALL_NUMBER)
|
||||
{
|
||||
PawnMovement->Velocity.Z = SavedGateVerticalVelocity;
|
||||
}
|
||||
PawnMovement->UpdateComponentVelocity();
|
||||
}
|
||||
|
||||
if (APawn* Pawn = CollisionGatedPawn.Get())
|
||||
@@ -5437,6 +5550,8 @@ void AVoxelWorld::ReleasePawnCollisionGate()
|
||||
CollisionGatedPawnMovement.Reset();
|
||||
CollisionGatedCharacterMovement.Reset();
|
||||
bSavedCharacterGravityScale = false;
|
||||
bSavedGateVerticalVelocity = false;
|
||||
SavedGateVerticalVelocity = 0.0f;
|
||||
bPawnCollisionGateEngaged = false;
|
||||
PawnGateEngagedAtSeconds = 0.0;
|
||||
bPawnGateSupportRequestReported = false;
|
||||
@@ -6516,6 +6631,30 @@ void AVoxelWorld::BuildDesiredTiles(const FIntVector& Center, const FVector& Pla
|
||||
// once it moves away or is unregistered. No-op (zero cost) when there are no anchors.
|
||||
AddAnchorDesiredTiles();
|
||||
|
||||
// A pending support target and a few tiles below it are collision-only desired tiles. This
|
||||
// keeps an out-of-ring request alive across this rebuild and lets the worker/apply queues fill
|
||||
// the landing column while the pawn is still above it.
|
||||
for (auto It = CollisionGateDesiredTiles.CreateIterator(); It; ++It)
|
||||
{
|
||||
const FVoxelTileKey& Key = *It;
|
||||
const TCHAR* UnavailableReason = nullptr;
|
||||
if (IsTileCollisionReady(Key) || CollisionNotRequiredTiles.Contains(Key)
|
||||
|| IsCollisionTileUnavailable(Key, UnavailableReason))
|
||||
{
|
||||
It.RemoveCurrent();
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32& Stamp = DesiredStamped.FindOrAdd(Key);
|
||||
const bool bNew = Stamp != DesiredStamp;
|
||||
if (bNew)
|
||||
{
|
||||
Stamp = DesiredStamp;
|
||||
DesiredSorted.Add(Key);
|
||||
CollisionOnlyTiles.Add(Key);
|
||||
}
|
||||
}
|
||||
|
||||
// Balayage UNIQUE de la map : les entrées à stamp périmé viennent de quitter le desired set —
|
||||
// ce sont les seuls candidats au cull de ce crossing (avec la TransitionHold). On les retire
|
||||
// ici même (RemoveCurrent est sûr en itérant), la map reste donc == desired set courant.
|
||||
@@ -6585,6 +6724,17 @@ void AVoxelWorld::BuildDesiredTiles(const FIntVector& Center, const FVector& Pla
|
||||
CriticalDesiredTiles.Add(Key);
|
||||
}
|
||||
};
|
||||
if (CollisionGateDesiredTiles.Contains(PawnGateRequestedSupportTile))
|
||||
{
|
||||
AddCriticalTile(PawnGateRequestedSupportTile);
|
||||
}
|
||||
for (const FVoxelTileKey& Key : DesiredSorted)
|
||||
{
|
||||
if (CollisionGateDesiredTiles.Contains(Key))
|
||||
{
|
||||
AddCriticalTile(Key);
|
||||
}
|
||||
}
|
||||
AddCriticalTile(OccupiedTile);
|
||||
|
||||
FVector Heading = PlayerHeading;
|
||||
|
||||
@@ -868,6 +868,7 @@ public:
|
||||
FVoxelTileKey* OutSupportTile = nullptr,
|
||||
const TCHAR** OutUnavailableReason = nullptr) const;
|
||||
bool IsPawnCapsuleOverlappingPendingFloor(APawn* Pawn, const FVector& PlayerPosition,
|
||||
const FVector& NextPlayerPosition,
|
||||
FVoxelTileKey* OutPendingTile = nullptr) const;
|
||||
void RequestCollisionGateSupportTile(const FVoxelTileKey& SupportTile);
|
||||
|
||||
@@ -970,6 +971,9 @@ public:
|
||||
// without an O(loaded) scan. Both empty when there are no CollisionOnly anchors → zero cost.
|
||||
TSet<FVoxelTileKey> CollisionOnlyTiles;
|
||||
TSet<FVoxelTileKey> PrevCollisionOnlyTiles;
|
||||
// A short collision-only column below an unresolved player support tile. Keeping these keys
|
||||
// stamped prevents a critical request outside the render clipmap from being canceled on rebuild.
|
||||
TSet<FVoxelTileKey> CollisionGateDesiredTiles;
|
||||
void ReconcileAnchorTileVisibility();
|
||||
|
||||
// --- Streaming work-avoidance (perf) ---
|
||||
@@ -1105,7 +1109,10 @@ public:
|
||||
bool bHeadlessCollisionGateStressTestTerrainMeasurementMissing = false;
|
||||
bool bHeadlessCollisionGateStressTestLanded = false;
|
||||
bool bHeadlessCollisionGateStressTestLaunchSupportTimingCaptured = false;
|
||||
bool bHeadlessCollisionGateStressDeepDrop = false;
|
||||
bool bHeadlessCollisionGateStressDeepDropPrepared = false;
|
||||
float HeadlessCollisionGateStressTestSpeedCmPerSecond = 6000.0f;
|
||||
float HeadlessCollisionGateStressTestSpawnHeightVoxels = 10.0f;
|
||||
float HeadlessCollisionGateStressTestDurationSeconds = 2.0f;
|
||||
float HeadlessCollisionGateStressTestTimeoutSeconds = 15.0f;
|
||||
double HeadlessCollisionGateStressTestBeginSeconds = 0.0;
|
||||
@@ -1228,6 +1235,8 @@ public:
|
||||
bool bCollisionGateEnabled = true;
|
||||
float SavedCharacterGravityScale = 1.0f;
|
||||
bool bSavedCharacterGravityScale = false;
|
||||
float SavedGateVerticalVelocity = 0.0f;
|
||||
bool bSavedGateVerticalVelocity = false;
|
||||
bool bPawnCollisionGateEngaged = false;
|
||||
double PawnGateEngagedAtSeconds = 0.0;
|
||||
bool bPawnGateUnavailableReported = false;
|
||||
|
||||
@@ -989,6 +989,7 @@ function Get-CollisionGateStressMetrics([string]$LogPath) {
|
||||
$result = [regex]::Match($line, 'result=(?<v>PASS|FAIL)')
|
||||
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',
|
||||
'spawn_height_vox', 'deep_drop',
|
||||
'gate_holds', 'gate_total_s', 'max_gate_s', 'full_stops',
|
||||
'full_stop_total_s', 'collision_gate', 'gate_progress_m', 'support_tile',
|
||||
'support_first_check_to_submit_s',
|
||||
|
||||
Reference in new issue
Block a user