fix(passages): D-floors now exist inside the inter-strate gap; two stale tests corrected
Investigation of the two tests that had been failing for days (Codex; the fix reviewed and its field claims re-run by me). Real bug, introduced byadb34df: gap chunks take the compact bedrock path in GetDensityAt, which applied the passage modifier but never the build-authored native D-floor. So the walkable passage floor vanished exactly at the gap/strate seam, in the passages the player uses to cross strates. Now gap chunks set Result = -Density, then ApplyPassageNativeFloorMC, then VF_ApplyXYEdgeSealMC. The seal runs on the MC value, in the same order as before. Stale tests, not code bugs: - PassageLandsInOpenSpace: sinced97373cproduction passes the cached world/warp seed to VF_SuggestLandingPoint, while the test still queried with the default seed. Both queries are fixed; the test now allows only exact graph-owned support-floor samples (the production bProtectAuthoredTunnelFloor contract), and anything else still fails. 14/14 landing ends, 5,774/5,774 floor and capsule samples. - ClassifyTileSoundness: sinced38d4caClassifyTile covers the cell vertices g=0..CPA, but the test still sampled the mesher's outer normal halo. Both "holes" were halo-only (g = CPA+1). The test now asserts the current contract. TryProveSealedSolidTile already uses the exact core lattice, so it is not affected. Verified: crossing test PASS (max gate 0.129 s); sealed-proof test 22/22; both classifier variants pass (600 tiles each). Re-run by the orchestrator against this build: export OBJ SHA-256 b3e5f4c3..9b377 unchanged; capability 20,830 / 10,909 / walk-only 10,909. Ledger: one DDC startup fatal (crash folder 43101DC8, 04:38) and a set of expected diagnostic failures while locating the gap-branch omission; one malformed ablation argument (not a valid ablation). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
This commit is contained in:
1 parent
cf2b2add20
commit
92607751b1
3 files changed
+185
-29
No files matched your search
@@ -19,13 +19,15 @@
|
|||||||
// Cette fonction a DÉJÀ produit cette panne : la v1 de T1.d a été revertée le 2026-06-26 pour une
|
// Cette fonction a DÉJÀ produit cette panne : la v1 de T1.d a été revertée le 2026-06-26 pour une
|
||||||
// borne de plafond pas assez conservative. Jusqu'ici elle n'est validée que par raisonnement.
|
// borne de plafond pas assez conservative. Jusqu'ici elle n'est validée que par raisonnement.
|
||||||
// Ce test la valide par la force brute : pour chaque tuile jugée non-Mixed, on échantillonne le
|
// Ce test la valide par la force brute : pour chaque tuile jugée non-Mixed, on échantillonne le
|
||||||
// treillis EXACT que le mesher aurait échantillonné (marge ±1 incluse) et on vérifie que chaque
|
// treillis des sommets de cellules que ClassifyTile couvre réellement (g = 0..CPA) et on vérifie
|
||||||
// point est bien du côté annoncé.
|
// que chaque point est bien du côté annoncé. La marge extérieure du mesher sert aux normales, pas
|
||||||
|
// au verdict uniforme.
|
||||||
//
|
//
|
||||||
// This function has ALREADY produced that failure: T1.d v1 was reverted on 2026-06-26 over a
|
// This function has ALREADY produced that failure: T1.d v1 was reverted on 2026-06-26 over a
|
||||||
// non-conservative ceiling bound. Until now it was validated by reasoning only. This test
|
// non-conservative ceiling bound. Until now it was validated by reasoning only. This test
|
||||||
// validates it by brute force: for every tile judged non-Mixed, sample the EXACT lattice the
|
// validates it by brute force: for every tile judged non-Mixed, sample the exact cell-vertex
|
||||||
// mesher would have sampled (±1 margin included) and assert every point is on the claimed side.
|
// lattice covered by ClassifyTile (g = 0..CPA) and assert every point is on the claimed side. The
|
||||||
|
// mesher's outer normal halo is deliberately outside the classifier's uniformity contract.
|
||||||
//
|
//
|
||||||
// CONVENTION (VoxelMarchingCubesMesher.cpp:309, IsoLevel == 0):
|
// CONVENTION (VoxelMarchingCubesMesher.cpp:309, IsoLevel == 0):
|
||||||
// D >= 0 ⇒ côté AIR / air side
|
// D >= 0 ⇒ côté AIR / air side
|
||||||
@@ -166,8 +168,10 @@ bool FVoxelForgeClassifyTileTest::RunTest(const FString& Parameters)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Vérification par force brute, sur le treillis EXACT du mesher. ──
|
// ── Vérification par force brute du contrat de ClassifyTile. ──
|
||||||
// Les bornes reproduisent ClassifyTile / GenerateMesh : g ∈ [-1, Cells+1] par axe.
|
// ClassifyTile covers actual cell vertices only: g ∈ [0, CPA]. GenerateMesh may sample a
|
||||||
|
// one-vertex outer halo for central-difference normals, but that halo cannot create a cell and
|
||||||
|
// is intentionally not part of the uniform verdict.
|
||||||
int32 NumHoles = 0;
|
int32 NumHoles = 0;
|
||||||
for (const FTileSpec& Spec : ToVerify)
|
for (const FTileSpec& Spec : ToVerify)
|
||||||
{
|
{
|
||||||
@@ -175,13 +179,13 @@ bool FVoxelForgeClassifyTileTest::RunTest(const FString& Parameters)
|
|||||||
if (Verdict == EVoxelTileClass::Mixed) { continue; } // verdict instable ⇒ rien à prouver
|
if (Verdict == EVoxelTileClass::Mixed) { continue; } // verdict instable ⇒ rien à prouver
|
||||||
|
|
||||||
const int32 CPA = FMath::Clamp(Spec.Cells, 2, CHUNK_SIZE);
|
const int32 CPA = FMath::Clamp(Spec.Cells, 2, CHUNK_SIZE);
|
||||||
const int32 GridDim = CPA + 1;
|
const int32 VertexCount = CPA + 1;
|
||||||
const bool bClaimsSolid = (Verdict == EVoxelTileClass::AllSolid);
|
const bool bClaimsSolid = (Verdict == EVoxelTileClass::AllSolid);
|
||||||
|
|
||||||
bool bTileBad = false;
|
bool bTileBad = false;
|
||||||
for (int32 gz = -1; gz <= GridDim && !bTileBad; ++gz)
|
for (int32 gz = 0; gz < VertexCount && !bTileBad; ++gz)
|
||||||
for (int32 gy = -1; gy <= GridDim && !bTileBad; ++gy)
|
for (int32 gy = 0; gy < VertexCount && !bTileBad; ++gy)
|
||||||
for (int32 gx = -1; gx <= GridDim && !bTileBad; ++gx)
|
for (int32 gx = 0; gx < VertexCount && !bTileBad; ++gx)
|
||||||
{
|
{
|
||||||
const float X = (float)(Spec.Origin.X + gx * Spec.Step);
|
const float X = (float)(Spec.Origin.X + gx * Spec.Step);
|
||||||
const float Y = (float)(Spec.Origin.Y + gy * Spec.Step);
|
const float Y = (float)(Spec.Origin.Y + gy * Spec.Step);
|
||||||
@@ -199,8 +203,8 @@ bool FVoxelForgeClassifyTileTest::RunTest(const FString& Parameters)
|
|||||||
TEXT("HOLE: ClassifyTile said %s for tile origin (%d,%d,%d) Step=%d Cells=%d, ")
|
TEXT("HOLE: ClassifyTile said %s for tile origin (%d,%d,%d) Step=%d Cells=%d, ")
|
||||||
TEXT("but GetDensityAt(%.0f, %.0f, %.0f) = %.6g is on the %s side. This tile ")
|
TEXT("but GetDensityAt(%.0f, %.0f, %.0f) = %.6g is on the %s side. This tile ")
|
||||||
TEXT("would be skipped by the mesher: no triangles and NO COLLISION where there ")
|
TEXT("would be skipped by the mesher: no triangles and NO COLLISION where there ")
|
||||||
TEXT("should be a surface. Find which guard in ClassifyTile failed to fire for ")
|
TEXT("should be a surface. This point is inside ClassifyTile's actual cell ")
|
||||||
TEXT("the feature at that point."),
|
TEXT("lattice, so find which classifier guard failed to fire for the feature."),
|
||||||
bClaimsSolid ? TEXT("AllSolid") : TEXT("AllAir"),
|
bClaimsSolid ? TEXT("AllSolid") : TEXT("AllAir"),
|
||||||
Spec.Origin.X, Spec.Origin.Y, Spec.Origin.Z, Spec.Step, Spec.Cells,
|
Spec.Origin.X, Spec.Origin.Y, Spec.Origin.Z, Spec.Step, Spec.Cells,
|
||||||
X, Y, Z, D, (D >= 0.0f) ? TEXT("AIR") : TEXT("SOLID")));
|
X, Y, Z, D, (D >= 0.0f) ? TEXT("AIR") : TEXT("SOLID")));
|
||||||
@@ -336,13 +340,13 @@ bool FVoxelForgeOpStackClassifyTileTest::RunTest(const FString& Parameters)
|
|||||||
if (Verdict == EVoxelTileClass::Mixed) { continue; }
|
if (Verdict == EVoxelTileClass::Mixed) { continue; }
|
||||||
|
|
||||||
const int32 CPA = FMath::Clamp(Spec.Cells, 2, CHUNK_SIZE);
|
const int32 CPA = FMath::Clamp(Spec.Cells, 2, CHUNK_SIZE);
|
||||||
const int32 GridDim = CPA + 1;
|
const int32 VertexCount = CPA + 1;
|
||||||
const bool bClaimsSolid = (Verdict == EVoxelTileClass::AllSolid);
|
const bool bClaimsSolid = (Verdict == EVoxelTileClass::AllSolid);
|
||||||
|
|
||||||
bool bTileBad = false;
|
bool bTileBad = false;
|
||||||
for (int32 gz = -1; gz <= GridDim && !bTileBad; ++gz)
|
for (int32 gz = 0; gz < VertexCount && !bTileBad; ++gz)
|
||||||
for (int32 gy = -1; gy <= GridDim && !bTileBad; ++gy)
|
for (int32 gy = 0; gy < VertexCount && !bTileBad; ++gy)
|
||||||
for (int32 gx = -1; gx <= GridDim && !bTileBad; ++gx)
|
for (int32 gx = 0; gx < VertexCount && !bTileBad; ++gx)
|
||||||
{
|
{
|
||||||
const float X = (float)(Spec.Origin.X + gx * Spec.Step);
|
const float X = (float)(Spec.Origin.X + gx * Spec.Step);
|
||||||
const float Y = (float)(Spec.Origin.Y + gy * Spec.Step);
|
const float Y = (float)(Spec.Origin.Y + gy * Spec.Step);
|
||||||
|
|||||||
@@ -178,11 +178,119 @@ namespace
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class FGraphReservedFloorOracle
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit FGraphReservedFloorOracle(const UVoxelStrateManager& InManager)
|
||||||
|
: Manager(InManager)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsReservedFloor(const FVector& Point)
|
||||||
|
{
|
||||||
|
const FIntVector ChunkCoord(
|
||||||
|
FMath::FloorToInt(Point.X / static_cast<float>(CHUNK_SIZE)),
|
||||||
|
FMath::FloorToInt(Point.Y / static_cast<float>(CHUNK_SIZE)),
|
||||||
|
FMath::FloorToInt(Point.Z / static_cast<float>(CHUNK_SIZE)));
|
||||||
|
if (Manager.IsGapChunk(ChunkCoord))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ECaveGeneratorType GeneratorType =
|
||||||
|
Manager.GetGeneratorTypeForChunk(ChunkCoord);
|
||||||
|
if (GeneratorType != ECaveGeneratorType::TunnelNetwork
|
||||||
|
&& GeneratorType != ECaveGeneratorType::Underwater)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FChunkSDFCache* Cache = CacheFor(ChunkCoord);
|
||||||
|
if (Cache == nullptr)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FTunnelSupportFloorColumn SupportColumn;
|
||||||
|
const FTunnelSupportFloorColumn* SupportColumnPtr = nullptr;
|
||||||
|
const bool bIntegerXY =
|
||||||
|
Point.X == FMath::FloorToFloat(Point.X)
|
||||||
|
&& Point.Y == FMath::FloorToFloat(Point.Y);
|
||||||
|
if (bIntegerXY)
|
||||||
|
{
|
||||||
|
VoxelCaveMorphology::BuildTunnelSupportFloorColumn(
|
||||||
|
Point.X, Point.Y, *Cache, SupportColumn, /*bUseSpatialIndex=*/false);
|
||||||
|
SupportColumnPtr = &SupportColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the same public morphology query used by the generator's structural
|
||||||
|
// protection bit. A graph-owned room/tunnel floor is a reserved walking route: the
|
||||||
|
// inter-strate passage may join it, but must not carve its finite support band away.
|
||||||
|
const FTunnelCoreWorldEvaluation Evaluation =
|
||||||
|
VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
||||||
|
Point.X, Point.Y, Point.Z, *Cache, SupportColumnPtr,
|
||||||
|
/*bUseSpatialIndex=*/false);
|
||||||
|
return Evaluation.bSupportFloor || Evaluation.bRoomFloor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct FCacheEntry
|
||||||
|
{
|
||||||
|
FIntVector Chunk = FIntVector::ZeroValue;
|
||||||
|
FChunkSDFCache Cache;
|
||||||
|
};
|
||||||
|
|
||||||
|
FChunkSDFCache* CacheFor(const FIntVector& ChunkCoord)
|
||||||
|
{
|
||||||
|
for (FCacheEntry& Entry : Caches)
|
||||||
|
{
|
||||||
|
if (Entry.Chunk == ChunkCoord)
|
||||||
|
{
|
||||||
|
return &Entry.Cache;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UVoxelStrateDefinition* Definition = Manager.GetStrateForChunk(ChunkCoord);
|
||||||
|
if (Definition == nullptr)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FStrateGenerationParams Params = Manager.GetGenerationParams(ChunkCoord);
|
||||||
|
const float ChunkMinX = static_cast<float>(ChunkCoord.X * CHUNK_SIZE);
|
||||||
|
const float ChunkMinY = static_cast<float>(ChunkCoord.Y * CHUNK_SIZE);
|
||||||
|
const float ChunkMaxX = ChunkMinX + static_cast<float>(CHUNK_SIZE);
|
||||||
|
const float ChunkMaxY = ChunkMinY + static_cast<float>(CHUNK_SIZE);
|
||||||
|
const float Expansion = FMath::Abs(Params.CaveWarpStrength)
|
||||||
|
* VOXEL_NOISE_SCALE * 1.5f + 2.0f;
|
||||||
|
const int32 StrateIndex = Manager.GetStrateIndex(
|
||||||
|
(static_cast<float>(ChunkCoord.Z) + 0.5f)
|
||||||
|
* static_cast<float>(CHUNK_SIZE) * VOXEL_SIZE);
|
||||||
|
|
||||||
|
FCacheEntry& NewEntry = Caches.Emplace_GetRef();
|
||||||
|
NewEntry.Chunk = ChunkCoord;
|
||||||
|
VoxelCaveMorphology::BuildChunkCache(
|
||||||
|
NewEntry.Cache,
|
||||||
|
ChunkMinX - Expansion, ChunkMinY - Expansion,
|
||||||
|
ChunkMaxX + Expansion, ChunkMaxY + Expansion,
|
||||||
|
Params, static_cast<uint32>(Manager.GetWorldSeed()), StrateIndex,
|
||||||
|
&Definition->TerrainOperations,
|
||||||
|
ERoomGraphBuildSite::GeneratorTunnelCore);
|
||||||
|
return &NewEntry.Cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
const UVoxelStrateManager& Manager;
|
||||||
|
TArray<FCacheEntry> Caches;
|
||||||
|
};
|
||||||
|
|
||||||
bool FinalDensityCapsuleFits(
|
bool FinalDensityCapsuleFits(
|
||||||
const UVoxelGenerator& Generator,
|
const UVoxelGenerator& Generator,
|
||||||
const FVector& Centreline,
|
const FVector& Centreline,
|
||||||
float SupportFloorZ)
|
float SupportFloorZ,
|
||||||
|
FGraphReservedFloorOracle* GraphFloorOracle,
|
||||||
|
int32& OutReservedGraphFloorSamples)
|
||||||
{
|
{
|
||||||
|
OutReservedGraphFloorSamples = 0;
|
||||||
constexpr float Radius = VoxelPassageGeometry::PlayerRadiusVoxels;
|
constexpr float Radius = VoxelPassageGeometry::PlayerRadiusVoxels;
|
||||||
constexpr float HalfHeight = VoxelPassageGeometry::PlayerHalfHeightVoxels;
|
constexpr float HalfHeight = VoxelPassageGeometry::PlayerHalfHeightVoxels;
|
||||||
constexpr int32 MaxHorizontalOffset = 2;
|
constexpr int32 MaxHorizontalOffset = 2;
|
||||||
@@ -213,10 +321,23 @@ namespace
|
|||||||
Centreline.X + static_cast<float>(OffsetX),
|
Centreline.X + static_cast<float>(OffsetX),
|
||||||
Centreline.Y + static_cast<float>(OffsetY),
|
Centreline.Y + static_cast<float>(OffsetY),
|
||||||
SupportFloorZ + static_cast<float>(Row) + 0.5f);
|
SupportFloorZ + static_cast<float>(Row) + 0.5f);
|
||||||
if (!FMath::IsFinite(Density) || !(Density > 0.0f))
|
if (!FMath::IsFinite(Density))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!(Density > 0.0f))
|
||||||
|
{
|
||||||
|
const bool bReservedGraphFloor = GraphFloorOracle != nullptr
|
||||||
|
&& GraphFloorOracle->IsReservedFloor(FVector(
|
||||||
|
Centreline.X + static_cast<float>(OffsetX),
|
||||||
|
Centreline.Y + static_cast<float>(OffsetY),
|
||||||
|
SupportFloorZ + static_cast<float>(Row) + 0.5f));
|
||||||
|
if (!bReservedGraphFloor)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
++OutReservedGraphFloorSamples;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,10 +347,22 @@ namespace
|
|||||||
const float Density = Generator.GetDensityAt(
|
const float Density = Generator.GetDensityAt(
|
||||||
Centreline.X, Centreline.Y,
|
Centreline.X, Centreline.Y,
|
||||||
SupportFloorZ + static_cast<float>(Row) + 0.5f);
|
SupportFloorZ + static_cast<float>(Row) + 0.5f);
|
||||||
if (!FMath::IsFinite(Density) || !(Density > 0.0f))
|
if (!FMath::IsFinite(Density))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!(Density > 0.0f))
|
||||||
|
{
|
||||||
|
const bool bReservedGraphFloor = GraphFloorOracle != nullptr
|
||||||
|
&& GraphFloorOracle->IsReservedFloor(FVector(
|
||||||
|
Centreline.X, Centreline.Y,
|
||||||
|
SupportFloorZ + static_cast<float>(Row) + 0.5f));
|
||||||
|
if (!bReservedGraphFloor)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
++OutReservedGraphFloorSamples;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -246,6 +379,7 @@ namespace
|
|||||||
int32 RouteGradientFailures = 0;
|
int32 RouteGradientFailures = 0;
|
||||||
int32 FloorFailures = 0;
|
int32 FloorFailures = 0;
|
||||||
int32 CapsuleFailures = 0;
|
int32 CapsuleFailures = 0;
|
||||||
|
int32 ReservedGraphFloorSamples = 0;
|
||||||
float WorstGradient = 0.0f;
|
float WorstGradient = 0.0f;
|
||||||
float WorstGeometricGradient = 0.0f;
|
float WorstGeometricGradient = 0.0f;
|
||||||
float WorstPatchGradient = 0.0f;
|
float WorstPatchGradient = 0.0f;
|
||||||
@@ -264,6 +398,7 @@ namespace
|
|||||||
void AuditWalkableTunnel(
|
void AuditWalkableTunnel(
|
||||||
const UVoxelGenerator& Generator,
|
const UVoxelGenerator& Generator,
|
||||||
const FVoxelPassage& Passage,
|
const FVoxelPassage& Passage,
|
||||||
|
FGraphReservedFloorOracle& GraphFloorOracle,
|
||||||
FWalkableTunnelAudit& InOutAudit)
|
FWalkableTunnelAudit& InOutAudit)
|
||||||
{
|
{
|
||||||
++InOutAudit.Contracts;
|
++InOutAudit.Contracts;
|
||||||
@@ -400,8 +535,10 @@ namespace
|
|||||||
const float FinalSupportFloor = FMath::Max(
|
const float FinalSupportFloor = FMath::Max(
|
||||||
SupportFloor, SupportFloorWithY);
|
SupportFloor, SupportFloorWithY);
|
||||||
++InOutAudit.CapsuleSamples;
|
++InOutAudit.CapsuleSamples;
|
||||||
|
int32 ReservedGraphFloorSamples = 0;
|
||||||
if (!FinalDensityCapsuleFits(
|
if (!FinalDensityCapsuleFits(
|
||||||
Generator, Centre, FinalSupportFloor))
|
Generator, Centre, FinalSupportFloor,
|
||||||
|
&GraphFloorOracle, ReservedGraphFloorSamples))
|
||||||
{
|
{
|
||||||
++InOutAudit.CapsuleFailures;
|
++InOutAudit.CapsuleFailures;
|
||||||
RecordTunnelFailure(InOutAudit, FString::Printf(
|
RecordTunnelFailure(InOutAudit, FString::Printf(
|
||||||
@@ -409,6 +546,7 @@ namespace
|
|||||||
Passage.UpperStrateIndex, Passage.LowerStrateIndex,
|
Passage.UpperStrateIndex, Passage.LowerStrateIndex,
|
||||||
SegmentIndex, SampleIndex));
|
SegmentIndex, SampleIndex));
|
||||||
}
|
}
|
||||||
|
InOutAudit.ReservedGraphFloorSamples += ReservedGraphFloorSamples;
|
||||||
if (bHavePrevious)
|
if (bHavePrevious)
|
||||||
{
|
{
|
||||||
const float RouteHorizontal = FVector2D(
|
const float RouteHorizontal = FVector2D(
|
||||||
@@ -554,8 +692,8 @@ bool FVoxelForgePassageLandsInOpenSpaceTest::RunTest(const FString& Parameters)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The upper mouth has its own source query too. This is deliberately evaluated from the
|
// The upper mouth has its own source query too. This is deliberately evaluated from the
|
||||||
// upper strate's definition, seed, bounds, and requested XY; it must never inherit the
|
// upper strate's definition, room seed, production world/warp seed, bounds, and requested
|
||||||
// lower mouth's answer or become an implicit passage chain.
|
// XY; it must never inherit the lower mouth's answer or become an implicit passage chain.
|
||||||
const float SourceTopZ = (float)(Source.TopChunkZ + 1) * CHUNK_SIZE;
|
const float SourceTopZ = (float)(Source.TopChunkZ + 1) * CHUNK_SIZE;
|
||||||
const float SourceBottomZ = (float)Source.BottomChunkZ * CHUNK_SIZE;
|
const float SourceBottomZ = (float)Source.BottomChunkZ * CHUNK_SIZE;
|
||||||
const int32 SourceQuerySeed = OpenPointSeedFor(
|
const int32 SourceQuerySeed = OpenPointSeedFor(
|
||||||
@@ -575,7 +713,8 @@ bool FVoxelForgePassageLandsInOpenSpaceTest::RunTest(const FString& Parameters)
|
|||||||
Passage.RequestedUpperPoint.X,
|
Passage.RequestedUpperPoint.X,
|
||||||
Passage.RequestedUpperPoint.Y,
|
Passage.RequestedUpperPoint.Y,
|
||||||
SourceMaxLateralSnap,
|
SourceMaxLateralSnap,
|
||||||
SuggestedUpperPoint);
|
SuggestedUpperPoint,
|
||||||
|
WorldSeed);
|
||||||
if (!bCanAnswerUpper)
|
if (!bCanAnswerUpper)
|
||||||
{
|
{
|
||||||
++NumUpperQueryFalse;
|
++NumUpperQueryFalse;
|
||||||
@@ -652,7 +791,8 @@ bool FVoxelForgePassageLandsInOpenSpaceTest::RunTest(const FString& Parameters)
|
|||||||
DesiredPoint.X,
|
DesiredPoint.X,
|
||||||
DesiredPoint.Y,
|
DesiredPoint.Y,
|
||||||
MaxLateralSnap,
|
MaxLateralSnap,
|
||||||
SuggestedPoint);
|
SuggestedPoint,
|
||||||
|
WorldSeed);
|
||||||
|
|
||||||
if (!bCanAnswer)
|
if (!bCanAnswer)
|
||||||
{
|
{
|
||||||
@@ -1087,14 +1227,19 @@ bool FVoxelForgePassageLandsInOpenSpaceTest::RunTest(const FString& Parameters)
|
|||||||
// The landing checks above prove the two endpoints. This separate audit proves the default
|
// The landing checks above prove the two endpoints. This separate audit proves the default
|
||||||
// inter-strate connection itself: every final-density floor sample is bracketed, the exact
|
// inter-strate connection itself: every final-density floor sample is bracketed, the exact
|
||||||
// player capsule stencil fits above it, and the support path never exceeds the named gentle
|
// player capsule stencil fits above it, and the support path never exceeds the named gentle
|
||||||
// gradient. It intentionally audits only the default contract; legacy exotic styles retain
|
// gradient. A capsule sample may overlap the graph's finite, build-authored support floor;
|
||||||
// their descriptors without receiving a false walkability promise.
|
// that is an intentional reserved-walk hand-off, not a passage hole. The passage is forbidden
|
||||||
|
// to cut that graph floor, so those exact morphology-owned samples are allowed below while any
|
||||||
|
// other obstruction remains a failure. Legacy exotic styles retain their descriptors without
|
||||||
|
// receiving a false walkability promise.
|
||||||
|
FGraphReservedFloorOracle GraphFloorOracle(*World.StrateManager);
|
||||||
for (const FVoxelPassage& Passage : Passages)
|
for (const FVoxelPassage& Passage : Passages)
|
||||||
{
|
{
|
||||||
if (Passage.UpperStrateIndex != Passage.LowerStrateIndex
|
if (Passage.UpperStrateIndex != Passage.LowerStrateIndex
|
||||||
&& Passage.bWalkableTunnelContract)
|
&& Passage.bWalkableTunnelContract)
|
||||||
{
|
{
|
||||||
AuditWalkableTunnel(*World.Generator, Passage, TunnelAudit);
|
AuditWalkableTunnel(
|
||||||
|
*World.Generator, Passage, GraphFloorOracle, TunnelAudit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1187,7 +1332,7 @@ bool FVoxelForgePassageLandsInOpenSpaceTest::RunTest(const FString& Parameters)
|
|||||||
AddError(FString::Printf(TEXT("First landing geometry failure: %s"), *FirstLandingFailure));
|
AddError(FString::Printf(TEXT("First landing geometry failure: %s"), *FirstLandingFailure));
|
||||||
}
|
}
|
||||||
AddInfo(FString::Printf(
|
AddInfo(FString::Printf(
|
||||||
TEXT("Walkable tunnel contract: %d passages, %d segments, %d final floor samples, %d player-capsule samples; gradient failures %d (geometric %d, route %d), floor failures %d, capsule failures %d, worst gradient %.6f (geometric %.6f, route %.6f; cross-section diagnostic %.6f; limit %.6f / %.1f degrees)."),
|
TEXT("Walkable tunnel contract: %d passages, %d segments, %d final floor samples, %d player-capsule samples; gradient failures %d (geometric %d, route %d), floor failures %d, capsule failures %d, graph-reserved capsule samples allowed %d, worst gradient %.6f (geometric %.6f, route %.6f; cross-section diagnostic %.6f; limit %.6f / %.1f degrees)."),
|
||||||
TunnelAudit.Contracts,
|
TunnelAudit.Contracts,
|
||||||
TunnelAudit.Segments,
|
TunnelAudit.Segments,
|
||||||
TunnelAudit.FloorSamples,
|
TunnelAudit.FloorSamples,
|
||||||
@@ -1197,6 +1342,7 @@ bool FVoxelForgePassageLandsInOpenSpaceTest::RunTest(const FString& Parameters)
|
|||||||
TunnelAudit.RouteGradientFailures,
|
TunnelAudit.RouteGradientFailures,
|
||||||
TunnelAudit.FloorFailures,
|
TunnelAudit.FloorFailures,
|
||||||
TunnelAudit.CapsuleFailures,
|
TunnelAudit.CapsuleFailures,
|
||||||
|
TunnelAudit.ReservedGraphFloorSamples,
|
||||||
TunnelAudit.WorstGradient,
|
TunnelAudit.WorstGradient,
|
||||||
TunnelAudit.WorstGeometricGradient,
|
TunnelAudit.WorstGeometricGradient,
|
||||||
TunnelAudit.WorstRouteGradient,
|
TunnelAudit.WorstRouteGradient,
|
||||||
|
|||||||
@@ -2760,8 +2760,14 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
float Density = 8.0f; // bedrock solidity (positive = solid)
|
float Density = 8.0f; // bedrock solidity (positive = solid)
|
||||||
StrateManager->ApplyPassageModifier(
|
StrateManager->ApplyPassageModifier(
|
||||||
Density, WorldX, WorldY, WorldZ, 8.0f, 0.0f);
|
Density, WorldX, WorldY, WorldZ, 8.0f, 0.0f);
|
||||||
VF_ApplyXYEdgeSeal(Density, WorldX, WorldY, WorldRadiusVoxels, EdgeSealThickness, 8.0f);
|
// Gap chunks use the compact bedrock path above instead of the ordinary strate tail.
|
||||||
|
// Native walkable passages still cross those chunks, so compose their build-authored
|
||||||
|
// D-floor here as well; otherwise the floor disappears exactly at the gap/strate seam.
|
||||||
Result = -Density;
|
Result = -Density;
|
||||||
|
StrateManager->ApplyPassageNativeFloorMC(
|
||||||
|
Result, WorldX, WorldY, WorldZ, 8.0f);
|
||||||
|
VF_ApplyXYEdgeSealMC(
|
||||||
|
Result, WorldX, WorldY, WorldRadiusVoxels, EdgeSealThickness, 8.0f);
|
||||||
}
|
}
|
||||||
else if (StrateManager)
|
else if (StrateManager)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in new issue
Block a user