fix(streaming): the player can walk from one strate into the next

Owner playtest: he could not walk from strate A into B. Forcing the pawn through in the editor
showed the junction tunnel (and his edits to the lower strate) were there.

Cause: UVoxelSettings::StrateContentCutMinLevel defaulted to 0, and DA_Settings does not override
it, so the strate content band (the PAWN's strate) also cut LOD0, the collision tiles. While the
player stood in A, the passage below A's seal, the gap and strate B had no LOD0 geometry, and the
collision gate correctly refused to step onto missing collision. The band only moves once the pawn
is inside B: a deadlock. bClampViewToStrate is a different setting, which is why toggling it did
not help. Some historical fall-throughs were likely this, before the gate turned "fall" into
"blocked".

Fix (Codex; reviewed by me):
- StrateContentCutMinLevel default 0 -> 1, ClampMin 1, and
  UVoxelSettings::GetEffectiveStrateContentCutMinLevel() clamps to [1,9] at every runtime
  consumer, so a legacy asset serialising 0 cannot bring the deadlock back. LOD1+ stay banded
  inside strates and go full in the gap.
- A regression test, -voxel.TestStrateCrossing=1 (off unless passed): it walks the real manager
  passage control points with AddMovementInput and normal swept character movement, with limits
  of 10 s max gate, 120 s total and a 2-voxel floor tolerance. Old code: FAIL
  collision_gate_timeout at route 5/9 (10.0 s gated). Fixed: PASS reached_lower_strate, strate
  0 -> 1, max gate 0.092 s. The trace shows band [-7..0] -> full in the gap -> [-17..-10].

Field unchanged: canonical export b3e5f4c3..9b377, capability 20,830 / 10,909. In game, the LOD0
change in the crossing scene is 11 transition-gap tiles going from 0 to 22,390 triangles; no LOD0
interior tile changed.

COST, stated plainly (two interleaved runs, baseline vs fixed DLL):
  static  generation CPU 34.1 -> 44.1 s (+29%), density calls +23%, LOD0 request p95 +8.9%
  moving  generation CPU 78.8 -> 91.1 s (+16%), density calls +26%, LOD0 request p95 +3.1%
  applied triangles identical (669,834 / 1,331,482): the extra work is LOD0 tiles in sealed rock
  that the old cut skipped. The next round proves them solid instead of sampling them.

Visual caveat: the old default of 0 was a deliberate July choice, because LOD0/1 tiles straddling
a strate boundary mixed both strates' materials. With LOD0 never cut, that mixing may reappear
near seals. The headless runs are -nullrhi, so this still needs checking in the editor.

Launches: all serialized and waited by PID. Failures: one malformed first baseline attempt, one
over-strict early harness assertion, and one run against stale staging (it logged CutMinLevel=0),
all superseded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
This commit is contained in:
Fr0zkaandClaude Opus 5 committed 2026-09-14 19:46:35 +02:00
1 parent 7ece3ceafc
commit 4c8d06fd8f
5 files changed
+735 -27

No files matched your search

+18 -5
View File
@@ -95,13 +95,26 @@ public:
// both strates' materials. From this clip level UP, the mesher only meshes cells inside the
// PLAYER's strate Z-band (the other strates are sealed/enclosed ⇒ invisible from here anyway);
// loaded coarse tiles re-queue automatically when the band changes (strate transition).
// Default 0 = cut at EVERY level (tested verdict 2026-07-05: level 0/1 straddler tiles were
// the visible mixers — a higher floor left them mixing and looked like "no improvement").
// Raise only if the descent/passage transition needs full tiles near the player. 9 = off.
// Default 1 = cut LOD1 and coarser. LOD0 is deliberately never content-cut because it owns pawn
// collision: cutting the passage, gap, or next-strate support tile would deadlock a walk at the
// current strate boundary. The runtime clamps legacy/hand-authored values below 1 to 1 as well.
// Raise only if a broader near-field view needs full coarse tiles. 9 = off.
// At ultra-coarse levels where one CELL is taller than the band itself, the tile is skipped
// entirely (see LoadTile) — cell-granular cutting there could only render garbage.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Streaming", meta = (ClampMin = "0", ClampMax = "9"))
int32 StrateContentCutMinLevel = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Streaming", meta = (ClampMin = "1", ClampMax = "9"))
int32 StrateContentCutMinLevel = 1;
/**
* Return the effective content-cut level used by runtime streaming.
*
* Existing data assets can contain a serialized legacy value of 0 even though the property now
* documents 1 as the minimum. Keep the safety rule in code, at the consumer boundary: level 0
* tiles are the collision tiles and must never receive a strate content band.
*/
int32 GetEffectiveStrateContentCutMinLevel() const
{
return FMath::Clamp(FMath::Max(StrateContentCutMinLevel, 1), 1, 9);
}
// Open-world SKY reach: the sky-cap ceiling of an open strate (SurfaceWorld / FloatingIslands)
// is FAR, so the ceiling BAND is streamed across a wider horizontal radius = ViewDistanceXY ×