feat(tunnels): option B floors -- no staircases; steep tunnels wind or drop
Owner's playtest showed a staircase (dozens of <= 45 cm risers) on a steep tunnel into a room. He chose option B: "smooth passage and 'dramatic ledge' on tunnels that crossed the slope limit, so we both have serpenting tunnels and sometimes dramatic drops that needs to be escalated". Implementation (Codex; the edge rule reviewed by me): - VF_TunnelFloorNeedsTerrace and all tunnel-floor NumSteps quantisation are removed; new profiles always have NumSteps = 0. Gentle floors remain continuous ramps with bounded relief. - The room graph's bridges and a deterministic reserved walk spanning forest decide each steep edge. bGraphLedgeEligible = leaf || (redundant && !walk-forest edge). Eligible edges may receive a few large drops at control-point boundaries, never repeated small risers. Every other steep edge WINDS (a sinusoidal wander lengthens the WorldControlPoints route until its floor is walkable). Several drops chosen together cannot disconnect walking, because the forest stays walkable. - The collect margin includes the longer wound-route envelope (window invariance). Classifier interval bounds include both endpoint floor levels, covering a ledge riser. Verified: - default acceptance (slot 1, threshold 0.9657): player-fit 20,830, walk 10,909, arrival -> departure connected, TunnelFloorMultiStepEdges 0, mesh 311CA42C unchanged. NOTE: this config has 0 steep edges, so it does not exercise option B at all. - stress (threshold 0.2): arrival -> departure connected; player-fit 21,305 (winding adds volume), walk-only 10,787. Fewer walk-only cells are expected behind climbable ledges. Ledges 4-8 voxels: 52,826 observations; 8+: 215,880; below 4: 0. - determinism: two runs identical (164,126 triangles, 70F987AA, 0 per-tile differences). - performance vs baseline DLL: moving worker 93.6 -> 85.2 s; LOD0 request p95 static 196 -> 181 ms, moving 188 -> 164 ms. - the two already-failing tests fail identically before and after (ClassifyTileSoundness: 2 expected-uniform holes; PassageLandsInOpenSpace: upper landing endpoint mismatch). NOT verified visually: every render reused one fixed camera, never aimed at a changed tunnel. The arrival case was rendered at the default threshold (8/8 views identical); in the steep/wind cases the changed views did not show a tunnel clearly. The owner's in-game check is the real acceptance. Ledger: several intermediate capability gates failed before the final configuration passed; two malformed launches were aborted; no crash folders. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
This commit is contained in:
1 parent
8879045627
commit
cf2b2add20
6 files changed
+614
-298
No files matched your search
@@ -6410,7 +6410,7 @@ namespace
|
||||
uint8 Result = 0u;
|
||||
auto AddSegment = [&](const FVector& A, const FVector& BPoint,
|
||||
float RadiusA, float RadiusB,
|
||||
bool bWorldChain)
|
||||
bool bWorldChain, int32 SegmentIndex)
|
||||
{
|
||||
if (!Finite((float)A.X) || !Finite((float)A.Y)
|
||||
|| !Finite((float)A.Z) || !Finite((float)BPoint.X)
|
||||
@@ -6438,14 +6438,33 @@ namespace
|
||||
|
||||
if (bWorldChain)
|
||||
{
|
||||
const float FloorA = VoxelPassageGeometry::TunnelFloorZ(
|
||||
const float NaturalFloorA = VoxelPassageGeometry::TunnelFloorZ(
|
||||
A, SafeRadiusA);
|
||||
const float FloorB = VoxelPassageGeometry::TunnelFloorZ(
|
||||
const float NaturalFloorB = VoxelPassageGeometry::TunnelFloorZ(
|
||||
BPoint, SafeRadiusB);
|
||||
float FloorMin = FMath::Min(NaturalFloorA, NaturalFloorB);
|
||||
float FloorMax = FMath::Max(NaturalFloorA, NaturalFloorB);
|
||||
const TArray<FTunnelFloorSegmentProfile>* FloorProfiles =
|
||||
&Tunnel.WorldFloorProfiles;
|
||||
if (FloorProfiles->Num() == Tunnel.WorldControlPoints.Num() - 1
|
||||
&& SegmentIndex >= 0
|
||||
&& SegmentIndex < FloorProfiles->Num())
|
||||
{
|
||||
// The interval proof must include an explicit ledge's vertical
|
||||
// riser. The profile transition is evaluated at the segment
|
||||
// boundary, so its endpoint interval is a conservative superset
|
||||
// of both plateau sides and remains valid for the lattice proof.
|
||||
const FTunnelFloorSegmentProfile& Profile =
|
||||
(*FloorProfiles)[SegmentIndex];
|
||||
FloorMin = FMath::Min(FloorMin, FMath::Min(
|
||||
Profile.StartFloorZ, Profile.EndFloorZ));
|
||||
FloorMax = FMath::Max(FloorMax, FMath::Max(
|
||||
Profile.StartFloorZ, Profile.EndFloorZ));
|
||||
}
|
||||
const float SupportRadius = FMath::Max(
|
||||
FMath::Min(SafeRadiusA, SafeRadiusB) - 0.5f,
|
||||
VoxelPassageGeometry::PlayerRadiusVoxels);
|
||||
if (!Finite(FloorA) || !Finite(FloorB)
|
||||
if (!Finite(FloorMin) || !Finite(FloorMax)
|
||||
|| !Finite(SupportRadius))
|
||||
{
|
||||
Result = 3u;
|
||||
@@ -6456,10 +6475,10 @@ namespace
|
||||
FMath::Max(A.X, BPoint.X) + SupportRadius,
|
||||
FMath::Min(A.Y, BPoint.Y) - SupportRadius,
|
||||
FMath::Max(A.Y, BPoint.Y) + SupportRadius,
|
||||
FMath::Min(FloorA, FloorB)
|
||||
- VoxelPassageGeometry::LandingFloorThicknessVoxels,
|
||||
FMath::Max(FloorA, FloorB)
|
||||
+ VoxelPassageGeometry::WalkableTunnelFloorAirClearanceVoxels))
|
||||
FloorMin
|
||||
- VoxelPassageGeometry::LandingFloorThicknessVoxels,
|
||||
FloorMax
|
||||
+ VoxelPassageGeometry::WalkableTunnelFloorAirClearanceVoxels))
|
||||
{
|
||||
Result |= 2u;
|
||||
}
|
||||
@@ -6476,7 +6495,7 @@ namespace
|
||||
Tunnel.WorldControlPoints[SegmentIndex],
|
||||
Tunnel.WorldControlPoints[SegmentIndex + 1],
|
||||
Tunnel.WorldControlRadii[SegmentIndex],
|
||||
Tunnel.WorldControlRadii[SegmentIndex + 1], true);
|
||||
Tunnel.WorldControlRadii[SegmentIndex + 1], true, SegmentIndex);
|
||||
if (Result == 3u) { return Result; }
|
||||
}
|
||||
}
|
||||
@@ -6491,21 +6510,21 @@ namespace
|
||||
Tunnel.ControlPoints[SegmentIndex],
|
||||
Tunnel.ControlPoints[SegmentIndex + 1],
|
||||
Tunnel.ControlRadii[SegmentIndex],
|
||||
Tunnel.ControlRadii[SegmentIndex + 1], false);
|
||||
Tunnel.ControlRadii[SegmentIndex + 1], false, SegmentIndex);
|
||||
if (Result == 3u) { return Result; }
|
||||
}
|
||||
}
|
||||
else if (Tunnel.bHasMidpoint)
|
||||
{
|
||||
AddSegment(Tunnel.EndpointA, Tunnel.Midpoint,
|
||||
Tunnel.RadiusA, Tunnel.RadiusMid, false);
|
||||
Tunnel.RadiusA, Tunnel.RadiusMid, false, INDEX_NONE);
|
||||
AddSegment(Tunnel.Midpoint, Tunnel.EndpointB,
|
||||
Tunnel.RadiusMid, Tunnel.RadiusB, false);
|
||||
Tunnel.RadiusMid, Tunnel.RadiusB, false, INDEX_NONE);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddSegment(Tunnel.EndpointA, Tunnel.EndpointB,
|
||||
Tunnel.RadiusA, Tunnel.RadiusB, false);
|
||||
Tunnel.RadiusA, Tunnel.RadiusB, false, INDEX_NONE);
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user