Files
VoxelForge/Source/VoxelForge/Private/Tests/VoxelForgePassageOpenSpaceTest.cpp
T
Fr0zkaandClaude Opus 5.5 bc0f9ee791 wip(step3): 12.5 cm voxels, fall-through fixed, brick evaluator in game, organic tunnels -- speed gate FAILS
Stopped by the owner's decision (in-game lit captures were eating the round); he judges the look in
game instead.
1. Fall-through with the research field: fixed. Deep drop, gate on: 2 holds (0.50 s), min foot
   clearance +43 cm; gate off: penetrates (-278 cm). Stress fixture offsets converted to metres.
2. VOXEL_SIZE 25 -> 12.5 cm. Physical sizes kept in metres (player, steps, gate, insets, research
   field sizes, dig radii, test thresholds); class defaults that count chunks/voxels doubled
   (ClipRadius 6, RenderDistanceChunks 1536, ViewDistance*, StrateViewMargin 6,
   PawnCollisionRadius 2, SkirtCells 4, EdgeSealThickness 128, decoration spacings, density volume
   res 256). Asset values override these defaults: the owner must double his own.
   Ring scale, six workers, idle, step-2f field before the look changes: 64 m cube (519^3) 4.55 s;
   32 m cube (259^3) 0.95 s.
3. In game: tiles go through the brick evaluator (proved by a log marker); profile switch rebuilds
   the loaded world (~5,800 tiles); full-detail fill 12.4 s after spawn in the visible-window test.
4. Look: round winding tunnels, per-column ellipsoid/lattice caches. All step-2f geometry gates pass
   at 12.5 cm (0 crumbs, 1 solid component per profile, solid share 69-87%). 259^3 ring, six workers:
   1.08-2.02 s (worst HyperComplex) -- the 600 ms gate FAILS. Lit captures failed (hidden/off-screen
   window, then blank frames); not pursued.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
2026-10-01 21:21:45 +02:00

1526 lines
71 KiB
C++

// VoxelForgePassageOpenSpaceTest.cpp
// Vérifie que les bouches basses des passages ciblées tombent dans l'air de leur strate destination.
// Verifies that targeted lower passage mouths land in air in their destination strate.
#if WITH_DEV_AUTOMATION_TESTS
#include "HAL/PlatformTime.h"
#include "Misc/AutomationTest.h"
#include "VoxelCaveMorphology.h"
#include "VoxelDensityOpStack.h"
#include "VoxelDensityPrimitives.h"
#include "VoxelForgeTestFixture.h"
#include "VoxelPassageGeometry.h"
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FVoxelForgePassageLandsInOpenSpaceTest,
"VoxelForge.Determinism.PassageLandsInOpenSpace",
EAutomationTestFlags_ApplicationContextMask | EAutomationTestFlags::EngineFilter)
namespace
{
// The query and production density path use different seed identities for room graphs versus
// the world-seeded lattice/grid/blob sources. Keep the test's oracle explicit so a future seed
// change cannot silently inspect a different layout. / La requête et la densité utilisent des
// identités de seed différentes pour les graphes de salles et les sources monde ; garder
// l'oracle explicite évite une dérive silencieuse.
int32 OpenPointSeedFor(
const UVoxelStrateDefinition& Definition,
const FStrateSlot& Destination,
int32 WorldSeed)
{
const bool bUsesCaveRooms =
Definition.GeneratorType == ECaveGeneratorType::TunnelNetwork
|| Definition.GeneratorType == ECaveGeneratorType::Underwater;
return bUsesCaveRooms
? static_cast<int32>(VoxelCaveMorphology::MakeStrateSeed(
static_cast<uint32>(WorldSeed), Destination.StrateIndex))
: WorldSeed;
}
FString ArchetypeName(ECaveGeneratorType Archetype)
{
if (const UEnum* ArchetypeEnum = StaticEnum<ECaveGeneratorType>())
{
return ArchetypeEnum->GetNameStringByValue(static_cast<int64>(Archetype));
}
return FString::Printf(TEXT("Value_%d"), static_cast<int32>(Archetype));
}
float MaxLateralSnapFor(const UVoxelStrateDefinition& Definition)
{
switch (Definition.GeneratorType)
{
case ECaveGeneratorType::Maze:
return FMath::Max(Definition.MazeParams.CellSize, 1.0f);
case ECaveGeneratorType::VerticalShafts:
// A 0.6 shaft density does not guarantee an occupied immediate cell. The production
// query therefore permits a bounded two-cell local search and still returns the
// selected site's deterministic feature core/ledge-side pose.
return FMath::Max(2.0f * Definition.VerticalShaftParams.ShaftSpacing, 1.0f);
case ECaveGeneratorType::FloatingIslands:
return FMath::Max(Definition.FloatingIslandParams.IslandSpacing, 1.0f);
case ECaveGeneratorType::TunnelNetwork:
case ECaveGeneratorType::Underwater:
return FMath::Max(Definition.GenerationParams.RoomSpacing, 1.0f);
default:
return 0.0f;
}
}
float BoundarySealFor(const UVoxelStrateDefinition& Definition)
{
switch (Definition.GeneratorType)
{
case ECaveGeneratorType::TunnelNetwork:
case ECaveGeneratorType::Underwater:
return Definition.GenerationParams.BoundarySealThickness;
case ECaveGeneratorType::FlatPlain:
case ECaveGeneratorType::CrystalChamber:
return Definition.SlabParams.BoundarySealThickness;
case ECaveGeneratorType::Maze:
return Definition.MazeParams.BoundarySealThickness;
case ECaveGeneratorType::SurfaceWorld:
return Definition.SurfaceParams.BoundarySealThickness;
case ECaveGeneratorType::VerticalShafts:
return Definition.VerticalShaftParams.BoundarySealThickness;
case ECaveGeneratorType::FloatingIslands:
return Definition.FloatingIslandParams.BoundarySealThickness;
default:
return 0.0f;
}
}
bool FindFinalDensityFloor(
const UVoxelGenerator& Generator,
const FVector& Centreline,
float ExpectedFloorZ,
float& OutFloorZ)
{
constexpr float SampleStep = VoxelPassageGeometry::MetersToVoxels(0.0625f);
const float ExpectedAt = Generator.GetDensityAt(
Centreline.X, Centreline.Y, ExpectedFloorZ);
const float ExpectedBelow = Generator.GetDensityAt(
Centreline.X, Centreline.Y, ExpectedFloorZ
- VoxelPassageGeometry::MetersToVoxels(0.1875f));
const float ExpectedAbove = Generator.GetDensityAt(
Centreline.X, Centreline.Y, ExpectedFloorZ
+ VoxelPassageGeometry::MetersToVoxels(0.1875f));
// Search around the authored tunnel floor, not from an arbitrary lower surface. A source
// archetype may contain another cave floor below the tunnel; that is not the floor carried
// by this passage and must not hijack the audit.
const float ProbeHalfRange = VoxelPassageGeometry::MetersToVoxels(1.0f);
const float StartZ = ExpectedFloorZ - ProbeHalfRange;
const float EndZ = ExpectedFloorZ + ProbeHalfRange;
const int32 NumSteps = FMath::Max(
1, FMath::CeilToInt((EndZ - StartZ) / SampleStep));
float PreviousZ = StartZ;
float PreviousDensity = Generator.GetDensityAt(
Centreline.X, Centreline.Y, PreviousZ);
if (!FMath::IsFinite(PreviousDensity))
{
return false;
}
float BestDistance = FLT_MAX;
float BestCrossingZ = 0.0f;
for (int32 Step = 1; Step <= NumSteps; ++Step)
{
const float Alpha = static_cast<float>(Step)
/ static_cast<float>(NumSteps);
const float CurrentZ = FMath::Lerp(StartZ, EndZ, Alpha);
const float CurrentDensity = Generator.GetDensityAt(
Centreline.X, Centreline.Y, CurrentZ);
if (!FMath::IsFinite(CurrentDensity))
{
return false;
}
// MC-facing density is negative solid and positive air. The first solid->air
// crossing from below is the floor of this swept tube column.
if (PreviousDensity <= 0.0f && CurrentDensity > 0.0f)
{
const float Denominator = CurrentDensity - PreviousDensity;
const float CrossAlpha = Denominator > 0.0f
? FMath::Clamp(-PreviousDensity / Denominator, 0.0f, 1.0f)
: 0.0f;
const float CrossingZ = FMath::Lerp(
PreviousZ, CurrentZ, CrossAlpha);
const float Distance = FMath::Abs(CrossingZ - ExpectedFloorZ);
if (Distance < BestDistance)
{
BestDistance = Distance;
BestCrossingZ = CrossingZ;
}
}
PreviousZ = CurrentZ;
PreviousDensity = CurrentDensity;
}
if (BestDistance == FLT_MAX
|| BestDistance > VoxelPassageGeometry::LandingFloorThicknessVoxels
+ VoxelPassageGeometry::MetersToVoxels(0.25f))
{
return false;
}
const float Below = Generator.GetDensityAt(
Centreline.X, Centreline.Y, BestCrossingZ
- VoxelPassageGeometry::MetersToVoxels(0.1875f));
const float Above = Generator.GetDensityAt(
Centreline.X, Centreline.Y, BestCrossingZ
+ VoxelPassageGeometry::MetersToVoxels(0.1875f));
if (!FMath::IsFinite(Below) || !FMath::IsFinite(Above)
|| !(Below < 0.0f) || !(Above > 0.0f))
{
return false;
}
// Use the final-density crossing itself. At an overlap the deterministic support rule
// may merge two nearby tunnel floors into one shared walking surface; measuring that
// surface is the correct contract, whereas insisting on the authored plane would call a
// valid low step a floor failure.
OutFloorZ = BestCrossingZ;
return true;
}
class FRoomOwnedFloorOracle
{
struct FCacheEntry;
public:
explicit FRoomOwnedFloorOracle(const UVoxelStrateManager& InManager)
: Manager(InManager)
{
}
bool IsRoomOwnedFloor(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;
}
FCacheEntry* Entry = CacheFor(ChunkCoord);
if (Entry == nullptr)
{
return false;
}
// Rooms own their interiors in the current tunnel contract. The old graph support
// column was detached when the authored tunnel floor was removed, so it must not be
// used to excuse a solid sample here.
FVector RoomQueryPosition = Point;
if (Entry->Params.VerticalScale != 1.0f
&& Entry->Params.VerticalScale > 0.0f)
{
RoomQueryPosition.Z = Point.Z / Entry->Params.VerticalScale;
}
RoomQueryPosition = VoxelCaveMorphology::ApplyCaveWarp(
RoomQueryPosition, Entry->Params,
VoxelCaveMorphology::MakeStrateSeed(
static_cast<uint32>(Manager.GetWorldSeed()), Entry->StrateIndex));
const FTunnelCoreWorldEvaluation Evaluation =
VoxelCaveMorphology::EvaluateTunnelCoreWorld(
Point.X, Point.Y, Point.Z, Entry->Cache, nullptr,
/*bUseSpatialIndex=*/false, &RoomQueryPosition);
return Evaluation.bRoomFloor;
}
private:
struct FCacheEntry
{
FIntVector Chunk = FIntVector::ZeroValue;
FChunkSDFCache Cache;
FStrateGenerationParams Params;
int32 StrateIndex = INDEX_NONE;
};
FCacheEntry* CacheFor(const FIntVector& ChunkCoord)
{
for (FCacheEntry& Entry : Caches)
{
if (Entry.Chunk == ChunkCoord)
{
return &Entry;
}
}
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;
NewEntry.Params = Params;
NewEntry.StrateIndex = StrateIndex;
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;
}
const UVoxelStrateManager& Manager;
TArray<FCacheEntry> Caches;
};
bool FinalDensityCapsuleFits(
const UVoxelGenerator& Generator,
const FVector& Centreline,
float SupportFloorZ,
FRoomOwnedFloorOracle* RoomFloorOracle,
int32& OutRoomFloorSamples,
FString* OutFirstFailure = nullptr)
{
OutRoomFloorSamples = 0;
if (OutFirstFailure != nullptr)
{
OutFirstFailure->Empty();
}
constexpr float Radius = VoxelPassageGeometry::PlayerRadiusVoxels;
constexpr float HalfHeight = VoxelPassageGeometry::PlayerHalfHeightVoxels;
constexpr int32 MaxHorizontalOffset = 2;
constexpr int32 NumRows = 8; // ceil(2 * 3.52 voxel capsule half-height)
for (int32 Row = 0; Row < NumRows; ++Row)
{
const float RelativeZ = static_cast<float>(Row) + 0.5f - HalfHeight;
const float AxisHalfLength = FMath::Max(0.0f, HalfHeight - Radius);
const float DistanceToAxis = FMath::Max(
FMath::Abs(RelativeZ) - AxisHalfLength, 0.0f);
bool bSampledRow = false;
for (int32 OffsetY = -MaxHorizontalOffset;
OffsetY <= MaxHorizontalOffset; ++OffsetY)
{
for (int32 OffsetX = -MaxHorizontalOffset;
OffsetX <= MaxHorizontalOffset; ++OffsetX)
{
const float DistanceSquared = static_cast<float>(
OffsetX * OffsetX + OffsetY * OffsetY)
+ DistanceToAxis * DistanceToAxis;
if (DistanceSquared > Radius * Radius + KINDA_SMALL_NUMBER)
{
continue;
}
bSampledRow = true;
const float Density = Generator.GetDensityAt(
Centreline.X + static_cast<float>(OffsetX),
Centreline.Y + static_cast<float>(OffsetY),
SupportFloorZ + static_cast<float>(Row) + 0.5f);
if (!FMath::IsFinite(Density))
{
return false;
}
if (!(Density > 0.0f))
{
const FVector SamplePoint(
Centreline.X + static_cast<float>(OffsetX),
Centreline.Y + static_cast<float>(OffsetY),
SupportFloorZ + static_cast<float>(Row) + 0.5f);
const bool bRoomFloor = RoomFloorOracle != nullptr
&& RoomFloorOracle->IsRoomOwnedFloor(SamplePoint);
if (!bRoomFloor)
{
if (OutFirstFailure != nullptr && OutFirstFailure->IsEmpty())
{
*OutFirstFailure = FString::Printf(
TEXT("capsule solid sample at (%.2f,%.2f,%.2f), density %.9g, "
"support floor %.3f row %d; no room-owned floor"),
SamplePoint.X, SamplePoint.Y, SamplePoint.Z, Density,
SupportFloorZ, Row);
}
return false;
}
++OutRoomFloorSamples;
}
}
}
// Match the measurement stencil's defensive centre sample for the top partial row.
if (!bSampledRow)
{
const float Density = Generator.GetDensityAt(
Centreline.X, Centreline.Y,
SupportFloorZ + static_cast<float>(Row) + 0.5f);
if (!FMath::IsFinite(Density))
{
return false;
}
if (!(Density > 0.0f))
{
const FVector SamplePoint(
Centreline.X, Centreline.Y,
SupportFloorZ + static_cast<float>(Row) + 0.5f);
const bool bRoomFloor = RoomFloorOracle != nullptr
&& RoomFloorOracle->IsRoomOwnedFloor(SamplePoint);
if (!bRoomFloor)
{
if (OutFirstFailure != nullptr && OutFirstFailure->IsEmpty())
{
*OutFirstFailure = FString::Printf(
TEXT("capsule centre sample at (%.2f,%.2f,%.2f), density %.9g, "
"support floor %.3f row %d; no room-owned floor"),
SamplePoint.X, SamplePoint.Y, SamplePoint.Z, Density,
SupportFloorZ, Row);
}
return false;
}
++OutRoomFloorSamples;
}
}
}
return true;
}
struct FWalkableTunnelAudit
{
int32 Contracts = 0;
int32 Segments = 0;
int32 FloorSamples = 0;
int32 CapsuleSamples = 0;
int32 GradientFailures = 0;
int32 GeometricGradientFailures = 0;
int32 RouteGradientFailures = 0;
int32 FloorFailures = 0;
int32 CapsuleFailures = 0;
int32 RoomOwnedFloorSamples = 0;
float WorstGradient = 0.0f;
float WorstGeometricGradient = 0.0f;
float WorstPatchGradient = 0.0f;
float WorstRouteGradient = 0.0f;
FString FirstFailure;
};
void RecordTunnelFailure(FWalkableTunnelAudit& Audit, const FString& Message)
{
if (Audit.FirstFailure.IsEmpty())
{
Audit.FirstFailure = Message;
}
}
void AuditWalkableTunnel(
const UVoxelGenerator& Generator,
const FVoxelPassage& Passage,
FRoomOwnedFloorOracle& RoomFloorOracle,
FWalkableTunnelAudit& InOutAudit)
{
++InOutAudit.Contracts;
if (Passage.ControlPoints.Num() < 2
|| Passage.ControlRadii.Num() != Passage.ControlPoints.Num())
{
++InOutAudit.GradientFailures;
RecordTunnelFailure(InOutAudit,
TEXT("walkable passage has no complete control-point/radius chain"));
return;
}
FVector PreviousCentre = FVector::ZeroVector;
float PreviousExpectedFloor = 0.0f;
bool bHavePrevious = false;
for (int32 SegmentIndex = 0;
SegmentIndex + 1 < Passage.ControlPoints.Num();
++SegmentIndex)
{
++InOutAudit.Segments;
const FVector& Start = Passage.ControlPoints[SegmentIndex];
const FVector& End = Passage.ControlPoints[SegmentIndex + 1];
const float StartRadius = Passage.ControlRadii[SegmentIndex];
const float EndRadius = Passage.ControlRadii[SegmentIndex + 1];
const float GeometricGradient = VoxelPassageGeometry::TunnelFloorGradient(
Start, StartRadius, End, EndRadius);
if (!FMath::IsFinite(GeometricGradient)
|| GeometricGradient > VoxelPassageGeometry::WalkableTunnelMaxGradient
+ KINDA_SMALL_NUMBER)
{
++InOutAudit.GradientFailures;
++InOutAudit.GeometricGradientFailures;
RecordTunnelFailure(InOutAudit, FString::Printf(
TEXT("control segment %d floor gradient %.6f exceeds %.6f"),
SegmentIndex,
GeometricGradient,
VoxelPassageGeometry::WalkableTunnelMaxGradient));
}
InOutAudit.WorstGeometricGradient = FMath::Max(
InOutAudit.WorstGeometricGradient, GeometricGradient);
InOutAudit.WorstGradient = FMath::Max(
InOutAudit.WorstGradient, GeometricGradient);
const int32 NumSamples = FMath::Max(
1, FMath::CeilToInt(FVector::Dist(Start, End)));
for (int32 SampleIndex = 0; SampleIndex <= NumSamples; ++SampleIndex)
{
const float T = static_cast<float>(SampleIndex)
/ static_cast<float>(NumSamples);
const FVector Centre = FMath::Lerp(Start, End, T);
const FVector2D SupportOffsets[] = {
FVector2D::ZeroVector,
FVector2D(1.0f, 0.0f), FVector2D(-1.0f, 0.0f),
FVector2D(0.0f, 1.0f), FVector2D(0.0f, -1.0f) };
float SupportFloors[UE_ARRAY_COUNT(SupportOffsets)] = {};
float ExpectedFloors[UE_ARRAY_COUNT(SupportOffsets)] = {};
bool bFloorPassed = true;
bool bAllSupportFloorsPassed = true;
for (int32 OffsetIndex = 0;
OffsetIndex < UE_ARRAY_COUNT(SupportOffsets);
++OffsetIndex)
{
const FVector Probe = Centre + FVector(
SupportOffsets[OffsetIndex].X,
SupportOffsets[OffsetIndex].Y,
0.0f);
float ExpectedFloorZ = 0.0f;
float ExpectedSupportRadius = 0.0f;
const bool bProjected = VoxelPassageGeometry::ProjectWalkableTunnelFloor(
Passage.ControlPoints, Passage.ControlRadii, Probe,
ExpectedFloorZ, ExpectedSupportRadius);
const bool bFoundFloor = bProjected
&& FindFinalDensityFloor(
Generator, Probe, ExpectedFloorZ,
SupportFloors[OffsetIndex]);
if (!bFoundFloor)
{
bAllSupportFloorsPassed = false;
// The contract sample is the route centreline. Keep a deterministic
// support value for the capsule/diagnostic patch calculation when a
// neighbouring floor owns only this lateral probe.
SupportFloors[OffsetIndex] = ExpectedFloorZ;
if (OffsetIndex == 0)
{
bFloorPassed = false;
}
}
ExpectedFloors[OffsetIndex] = ExpectedFloorZ;
}
++InOutAudit.FloorSamples;
if (!bFloorPassed)
{
++InOutAudit.FloorFailures;
if (InOutAudit.FirstFailure.IsEmpty())
{
InOutAudit.FirstFailure = FString::Printf(
TEXT("final-density floor bracket failed at passage %d->%d segment %d sample %d centre (%.2f,%.2f,%.2f)"),
Passage.UpperStrateIndex, Passage.LowerStrateIndex,
SegmentIndex, SampleIndex, Centre.X, Centre.Y, Centre.Z);
}
bHavePrevious = false;
continue;
}
float PatchGradient = 0.0f;
if (bAllSupportFloorsPassed)
{
for (int32 First = 0;
First < UE_ARRAY_COUNT(SupportOffsets);
++First)
{
for (int32 Second = First + 1;
Second < UE_ARRAY_COUNT(SupportOffsets);
++Second)
{
const float Distance = FVector2D(
SupportOffsets[Second] - SupportOffsets[First]).Size();
if (Distance > KINDA_SMALL_NUMBER)
{
PatchGradient = FMath::Max(PatchGradient,
FMath::Abs(SupportFloors[Second] - SupportFloors[First])
/ Distance);
}
}
}
}
InOutAudit.WorstPatchGradient = FMath::Max(
InOutAudit.WorstPatchGradient, PatchGradient);
const float SupportFloor = FMath::Max3(
SupportFloors[0], SupportFloors[1], SupportFloors[2]);
const float SupportFloorWithY = FMath::Max(
SupportFloors[3], SupportFloors[4]);
const float FinalSupportFloor = FMath::Max(
SupportFloor, SupportFloorWithY);
++InOutAudit.CapsuleSamples;
int32 RoomFloorSamples = 0;
FString CapsuleFailure;
if (!FinalDensityCapsuleFits(
Generator, Centre, FinalSupportFloor,
&RoomFloorOracle, RoomFloorSamples, &CapsuleFailure))
{
++InOutAudit.CapsuleFailures;
RecordTunnelFailure(InOutAudit, FString::Printf(
TEXT("final-density player capsule failed at passage %d->%d segment %d sample %d: %s"),
Passage.UpperStrateIndex, Passage.LowerStrateIndex,
SegmentIndex, SampleIndex, *CapsuleFailure));
}
InOutAudit.RoomOwnedFloorSamples += RoomFloorSamples;
if (bHavePrevious)
{
const float RouteHorizontal = FVector2D(
Centre.X - PreviousCentre.X,
Centre.Y - PreviousCentre.Y).Size();
// The final-density check above proves the support plane around this sample;
// use the constructed plane for route slope rather than a nearby source/SDF
// sign crossing, which can move within that proved bracket at overlaps.
const float RouteFloor = ExpectedFloors[0];
const float RouteGradient = RouteHorizontal > KINDA_SMALL_NUMBER
? FMath::Abs(RouteFloor - PreviousExpectedFloor)
/ RouteHorizontal
: 0.0f;
if (!FMath::IsFinite(RouteGradient)
|| RouteGradient > VoxelPassageGeometry::WalkableTunnelMaxGradient
+ KINDA_SMALL_NUMBER)
{
++InOutAudit.GradientFailures;
++InOutAudit.RouteGradientFailures;
RecordTunnelFailure(InOutAudit, FString::Printf(
TEXT("final-density route gradient %.6f exceeds %.6f at passage %d->%d segment %d sample %d prev(%.2f,%.2f,%.2f) floor %.3f current(%.2f,%.2f,%.2f) floor %.3f expected %.3f"),
RouteGradient,
VoxelPassageGeometry::WalkableTunnelMaxGradient,
Passage.UpperStrateIndex, Passage.LowerStrateIndex,
SegmentIndex, SampleIndex,
PreviousCentre.X, PreviousCentre.Y, PreviousCentre.Z,
PreviousExpectedFloor,
Centre.X, Centre.Y, Centre.Z,
RouteFloor, ExpectedFloors[0]));
}
InOutAudit.WorstRouteGradient = FMath::Max(
InOutAudit.WorstRouteGradient, RouteGradient);
InOutAudit.WorstGradient = FMath::Max(
InOutAudit.WorstGradient, RouteGradient);
}
PreviousCentre = Centre;
PreviousExpectedFloor = ExpectedFloors[0];
bHavePrevious = true;
}
}
}
}
bool FVoxelForgePassageLandsInOpenSpaceTest::RunTest(const FString& Parameters)
{
using namespace VoxelForgeTest;
FTestWorld World;
// Seed 12345 deliberately puts the passage mouths over a Maze corridor, a VerticalShafts
// shaft, and a FloatingIslands blob. SurfaceWorld remains a deliberate query refusal because
// its production height can be biome/context-selected by the manager. This fixture seed was
// repinned after the Maze topology/radius change; it is coverage selection, not generation
// tuning.
World.Build(/*InSeed=*/12345);
if (!World.IsValid())
{
AddError(World.WhyInvalid());
return false;
}
const TArray<FStrateSlot>& Layout = World.StrateManager->GetLayout();
const TArray<FVoxelPassage>& Passages = World.StrateManager->GetPassages();
const int32 WorldSeed = World.Settings->Seed;
int32 NumInterStratePassages = 0;
int32 NumChecked = 0;
int32 NumUpperChecked = 0;
int32 NumUpperQueryFalse = 0;
int32 NumQueryFalse = 0;
int32 NumUnsupported = 0;
int32 NumSupportedWithoutPoint = 0;
int32 NumFootingChecked = 0;
int32 NumRingAirSamples = 0;
int32 NumRingSamples = 0;
TSet<uint8> FalseArchetypes;
TSet<uint8> AnsweredArchetypes;
bool bAllAnswerableMouthRingsHaveAir = true;
bool bAllAnswerableFootingsAreValid = true;
bool bAllAnswerableEndpointsMatchQuery = true;
int32 NumLandingEnds = 0;
int32 NumLandingFloorsPassed = 0;
int32 NumLandingFloorFailures = 0;
int32 NumLandingSealFailures = 0;
int32 NumLandingFloorBoxes = 0;
int32 NumLandingBoxProofs = 0;
int32 NumLandingBoxAllSolid = 0;
int32 NumLandingBoxAllAir = 0;
int32 NumLandingBoxViolations = 0;
int32 NumLandingSourceFit = 0;
int32 NumLandingCapsuleChecks = 0;
int32 NumLandingCapsuleFailures = 0;
float WorstLandingFloorGradient = 0.0f;
FString FirstLandingFailure;
FString FirstLandingCapsuleFailure;
FString LandingSlopeReport;
constexpr float LandingWalkableAngleDegrees =
VoxelPassageGeometry::WalkableTunnelMaxGradientDegrees;
for (const FVoxelPassage& Passage : Passages)
{
// The surface entry is a same-strate shaft and is not an inter-strate destination query.
// Le puits de surface relie la même strate ; ce n'est pas un passage inter-strates à tester.
if (Passage.UpperStrateIndex == Passage.LowerStrateIndex)
{
continue;
}
++NumInterStratePassages;
if (!Layout.IsValidIndex(Passage.LowerStrateIndex))
{
AddError(FString::Printf(
TEXT("Passage lower endpoint references invalid destination strate index %d."),
Passage.LowerStrateIndex));
continue;
}
const FStrateSlot& Destination = Layout[Passage.LowerStrateIndex];
const UVoxelStrateDefinition* Definition = Destination.Definition;
if (!Definition)
{
AddError(FString::Printf(
TEXT("Passage destination strate %d has no definition."),
Destination.StrateIndex));
continue;
}
if (!Layout.IsValidIndex(Passage.UpperStrateIndex))
{
AddError(FString::Printf(
TEXT("Passage to strate %d references invalid source strate index %d."),
Destination.StrateIndex,
Passage.UpperStrateIndex));
continue;
}
const FStrateSlot& Source = Layout[Passage.UpperStrateIndex];
const UVoxelStrateDefinition* SourceDefinition = Source.Definition;
if (!SourceDefinition)
{
AddError(FString::Printf(
TEXT("Passage source strate %d has no definition."),
Source.StrateIndex));
continue;
}
// The upper mouth has its own source query too. This is deliberately evaluated from the
// upper strate's definition, room seed, production world/warp seed, bounds, and requested
// 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 SourceBottomZ = (float)Source.BottomChunkZ * CHUNK_SIZE;
const int32 SourceQuerySeed = OpenPointSeedFor(
*SourceDefinition, Source, WorldSeed);
const float SourceMaxLateralSnap = MaxLateralSnapFor(*SourceDefinition);
FVector SuggestedUpperPoint = FVector::ZeroVector;
const bool bCanAnswerUpper = VF_SuggestLandingPoint(
SourceDefinition->GeneratorType,
SourceDefinition->GenerationParams,
SourceDefinition->SlabParams,
SourceDefinition->MazeParams,
SourceDefinition->VerticalShaftParams,
SourceDefinition->FloatingIslandParams,
SourceQuerySeed,
SourceTopZ,
SourceBottomZ,
Passage.RequestedUpperPoint.X,
Passage.RequestedUpperPoint.Y,
SourceMaxLateralSnap,
SuggestedUpperPoint,
WorldSeed);
if (!bCanAnswerUpper)
{
++NumUpperQueryFalse;
FalseArchetypes.Add(static_cast<uint8>(SourceDefinition->GeneratorType));
}
else
{
++NumUpperChecked;
const float UpperSnapDX = Passage.UpperPoint.X
- Passage.RequestedUpperPoint.X;
const float UpperSnapDY = Passage.UpperPoint.Y
- Passage.RequestedUpperPoint.Y;
const float UpperSnapDistance = FMath::Sqrt(
UpperSnapDX * UpperSnapDX + UpperSnapDY * UpperSnapDY);
if (!FMath::IsNearlyEqual(Passage.UpperPoint.X, SuggestedUpperPoint.X, 0.01f)
|| !FMath::IsNearlyEqual(Passage.UpperPoint.Y, SuggestedUpperPoint.Y, 0.01f)
|| !FMath::IsNearlyEqual(Passage.UpperPoint.Z, SuggestedUpperPoint.Z, 0.01f))
{
bAllAnswerableEndpointsMatchQuery = false;
AddError(FString::Printf(
TEXT("Passage from strate %d (%s) missed its upper player-fit landing point: endpoint (%.9g, %.9g, %.9g), query (%.9g, %.9g, %.9g)."),
Source.StrateIndex,
*ArchetypeName(SourceDefinition->GeneratorType),
Passage.UpperPoint.X,
Passage.UpperPoint.Y,
Passage.UpperPoint.Z,
SuggestedUpperPoint.X,
SuggestedUpperPoint.Y,
SuggestedUpperPoint.Z));
}
if (!FMath::IsFinite(UpperSnapDistance)
|| UpperSnapDistance > SourceMaxLateralSnap + 0.01f)
{
bAllAnswerableEndpointsMatchQuery = false;
AddError(FString::Printf(
TEXT("Passage from strate %d (%s) exceeded its upper lateral snap bound: %.6f > %.6f from requested (%.3f, %.3f)."),
Source.StrateIndex,
*ArchetypeName(SourceDefinition->GeneratorType),
UpperSnapDistance,
SourceMaxLateralSnap,
Passage.RequestedUpperPoint.X,
Passage.RequestedUpperPoint.Y));
}
}
const FStratePassageConfig& PassageConfig = SourceDefinition->PassageConfig;
if (!FMath::IsFinite(PassageConfig.MouthRadius) || PassageConfig.MouthRadius <= 0.0f)
{
AddError(FString::Printf(
TEXT("Passage to strate %d (%s) has invalid mouth radius %.9g."),
Destination.StrateIndex,
*ArchetypeName(Definition->GeneratorType),
PassageConfig.MouthRadius));
continue;
}
const float StrateTopZ = (float)(Destination.TopChunkZ + 1) * CHUNK_SIZE;
const float StrateBottomZ = (float)Destination.BottomChunkZ * CHUNK_SIZE;
const int32 QuerySeed = OpenPointSeedFor(*Definition, Destination, WorldSeed);
const FVector DesiredPoint = Passage.RequestedLowerPoint;
const float MaxLateralSnap = MaxLateralSnapFor(*Definition);
FVector SuggestedPoint = FVector::ZeroVector;
const bool bCanAnswer = VF_SuggestLandingPoint(
Definition->GeneratorType,
Definition->GenerationParams,
Definition->SlabParams,
Definition->MazeParams,
Definition->VerticalShaftParams,
Definition->FloatingIslandParams,
QuerySeed,
StrateTopZ,
StrateBottomZ,
DesiredPoint.X,
DesiredPoint.Y,
MaxLateralSnap,
SuggestedPoint,
WorldSeed);
if (!bCanAnswer)
{
++NumQueryFalse;
FalseArchetypes.Add(static_cast<uint8>(Definition->GeneratorType));
const bool bExpectedToBeSupported =
Definition->GeneratorType == ECaveGeneratorType::TunnelNetwork
|| Definition->GeneratorType == ECaveGeneratorType::Underwater
|| Definition->GeneratorType == ECaveGeneratorType::FlatPlain
|| Definition->GeneratorType == ECaveGeneratorType::CrystalChamber
|| Definition->GeneratorType == ECaveGeneratorType::Maze
|| Definition->GeneratorType == ECaveGeneratorType::VerticalShafts
|| Definition->GeneratorType == ECaveGeneratorType::FloatingIslands;
if (bExpectedToBeSupported)
{
++NumSupportedWithoutPoint;
}
else
{
++NumUnsupported;
}
continue;
}
++NumChecked;
AnsweredArchetypes.Add(static_cast<uint8>(Definition->GeneratorType));
// Standing endpoints remain pinned to the complete query result, including any lateral
// snap. The tube endpoint is now the landing door, so this intentionally compares the
// public standing anchor rather than the first/last control point.
if (!FMath::IsNearlyEqual(Passage.LowerPoint.X, SuggestedPoint.X, 0.01f)
|| !FMath::IsNearlyEqual(Passage.LowerPoint.Y, SuggestedPoint.Y, 0.01f)
|| !FMath::IsNearlyEqual(Passage.LowerPoint.Z, SuggestedPoint.Z, 0.01f))
{
bAllAnswerableEndpointsMatchQuery = false;
AddError(FString::Printf(
TEXT("Passage to strate %d (%s) missed its suggested landing point: endpoint (%.9g, %.9g, %.9g), query (%.9g, %.9g, %.9g)."),
Destination.StrateIndex,
*ArchetypeName(Definition->GeneratorType),
Passage.LowerPoint.X,
Passage.LowerPoint.Y,
Passage.LowerPoint.Z,
SuggestedPoint.X,
SuggestedPoint.Y,
SuggestedPoint.Z));
}
const float SnapDX = Passage.LowerPoint.X - DesiredPoint.X;
const float SnapDY = Passage.LowerPoint.Y - DesiredPoint.Y;
const float SnapDistance = FMath::Sqrt(SnapDX * SnapDX + SnapDY * SnapDY);
if (!FMath::IsFinite(SnapDistance) || SnapDistance > MaxLateralSnap + 0.01f)
{
bAllAnswerableEndpointsMatchQuery = false;
AddError(FString::Printf(
TEXT("Passage to strate %d (%s) exceeded its lateral snap bound: %.6f > %.6f from requested (%.3f, %.3f)."),
Destination.StrateIndex,
*ArchetypeName(Definition->GeneratorType),
SnapDistance,
MaxLateralSnap,
DesiredPoint.X,
DesiredPoint.Y));
}
// A center sample is VACUOUS: VF_ApplyPassageCarving deliberately makes the mouth air,
// even when the destination archetype is solid there. Thin lattice/shaft/blob sources
// need a vertical footing probe against the source with the manager detached; their
// open circumference is not represented by the generic ring below.
const bool bNeedsFootingProbe =
Definition->GeneratorType == ECaveGeneratorType::Maze
|| Definition->GeneratorType == ECaveGeneratorType::VerticalShafts
|| Definition->GeneratorType == ECaveGeneratorType::FloatingIslands;
if (bNeedsFootingProbe)
{
++NumFootingChecked;
// The live world's passage modifier is intentionally absent here. The endpoint is
// already known to be passage-carved, so the archetype's own stack, built without a
// strate manager (hence without passage carving), checks the source itself: air at
// the landing and solid immediately below it.
const UVoxelGenerator& LiveGenerator = *World.Generator;
FVoxelOpStack SourceStack;
FVoxelOpContext SourceContext;
SourceContext.Seed = (uint32)LiveGenerator.Seed;
SourceContext.LayoutVersion = World.StrateManager->GetLayoutVersion();
SourceContext.StrateTopWorldZ = StrateTopZ;
SourceContext.StrateBottomWorldZ = StrateBottomZ;
SourceContext.WorldRadiusVoxels = LiveGenerator.WorldRadiusVoxels;
SourceContext.EdgeSealThickness = LiveGenerator.EdgeSealThickness;
// The generator resets the shaft-connector hand-off around every density query; a
// direct stack evaluation must model that boundary too.
auto EvalSource = [&SourceStack](float QueryX, float QueryY, float QueryZ)
{
VoxelPassageGeometry::ResetVerticalShaftConnectorAirMarker();
const float SourceDensity = SourceStack.EvalMC(QueryX, QueryY, QueryZ);
VoxelPassageGeometry::ResetVerticalShaftConnectorAirMarker();
return SourceDensity;
};
float LandingDensity = 0.0f;
float FootingDensity = 0.0f;
switch (Definition->GeneratorType)
{
case ECaveGeneratorType::Maze:
{
FMazeGenerationParams P = Definition->MazeParams;
P.StrateTopWorldZ = StrateTopZ;
P.StrateBottomWorldZ = StrateBottomZ;
VoxelDensityOps::BuildMazeStack(SourceStack, P, LiveGenerator.Seed,
LiveGenerator.OriginSpineRadius, /*StrateManager*/nullptr);
SourceStack.PrepareChunk(SourceContext);
LandingDensity = EvalSource(
SuggestedPoint.X, SuggestedPoint.Y, SuggestedPoint.Z);
const float FloorProbeZ = SuggestedPoint.Z
- FMath::Max(P.CorridorRadius, 0.5f)
- P.SurfaceRoughness * VOXEL_NOISE_SCALE * 1.5f - 3.0f;
FootingDensity = EvalSource(
SuggestedPoint.X, SuggestedPoint.Y, FloorProbeZ);
break;
}
case ECaveGeneratorType::VerticalShafts:
{
FVerticalShaftParams P = Definition->VerticalShaftParams;
P.StrateTopWorldZ = StrateTopZ;
P.StrateBottomWorldZ = StrateBottomZ;
VoxelDensityOps::BuildVerticalShaftStack(SourceStack, P, LiveGenerator.Seed,
LiveGenerator.OriginSpineRadius, /*StrateManager*/nullptr);
SourceStack.PrepareChunk(SourceContext);
LandingDensity = EvalSource(
SuggestedPoint.X, SuggestedPoint.Y, SuggestedPoint.Z);
const float FloorProbeZ = StrateBottomZ + P.BoundarySealThickness * 0.5f;
FootingDensity = EvalSource(
SuggestedPoint.X, SuggestedPoint.Y, FloorProbeZ);
break;
}
case ECaveGeneratorType::FloatingIslands:
{
FFloatingIslandParams P = Definition->FloatingIslandParams;
P.StrateTopWorldZ = StrateTopZ;
P.StrateBottomWorldZ = StrateBottomZ;
VoxelDensityOps::BuildFloatingIslandStack(SourceStack, P, LiveGenerator.Seed,
LiveGenerator.OriginSpineRadius, /*StrateManager*/nullptr);
SourceStack.PrepareChunk(SourceContext);
LandingDensity = EvalSource(
SuggestedPoint.X, SuggestedPoint.Y, SuggestedPoint.Z);
FootingDensity = EvalSource(
SuggestedPoint.X, SuggestedPoint.Y, SuggestedPoint.Z - 1.0f);
break;
}
default:
break;
}
if (!FMath::IsFinite(LandingDensity) || !FMath::IsFinite(FootingDensity)
|| LandingDensity < 0.0f || FootingDensity >= 0.0f)
{
bAllAnswerableFootingsAreValid = false;
AddError(FString::Printf(
TEXT("Passage to strate %d (%s) did not bracket source footing: landing density %.9g, below density %.9g at (%.3f, %.3f, %.3f)."),
Destination.StrateIndex,
*ArchetypeName(Definition->GeneratorType),
LandingDensity,
FootingDensity,
Passage.LowerPoint.X,
Passage.LowerPoint.Y,
SuggestedPoint.Z));
}
continue;
}
// A center sample is VACUOUS: VF_ApplyPassageCarving deliberately makes the mouth air,
// even when the destination archetype is solid there. Probe a lateral ring for the
// room/slab sources, where an open circumference is a meaningful footprint.
// The ring is Cfg.MouthRadius + two complete PassageBlend bands (4 voxels each): the
// first clears the passage's carve/blend reach, and the second is a safety margin beyond
// EvaluateModifierSDF's 3-voxel junction smoothing. At this radius the passage itself
// cannot supply the air being counted; a nearby destination room/void must do so.
// Require 8 of 16 samples (50%): that is a meaningful open circumference for a mouth,
// while a mouth in bedrock has zero air once its own tube is outside the ring. This is a
// connectivity PROXY, not proof that the mouth is connected to the room: a disconnected
// pocket can pass it. A real proof needs a flood fill (Tier 2).
// Un échantillon au centre serait VACU : le carve structurel force la bouche à l'air.
// L'anneau latéral doit donc trouver de l'air dans la strate de destination elle-même.
constexpr int32 RingSampleCount = 16;
constexpr int32 MinimumAirSamples = 8;
const float RingRadius = PassageConfig.MouthRadius
+ 2.0f * VoxelDensityReach::PassageBlend;
int32 AirSamples = 0;
for (int32 RingSample = 0; RingSample < RingSampleCount; ++RingSample)
{
const float Angle = 2.0f * PI * (float)RingSample / (float)RingSampleCount;
float SinAngle = 0.0f, CosAngle = 0.0f;
VoxelMath::DetSinCos(SinAngle, CosAngle, Angle);
const float SampleX = Passage.LowerPoint.X + CosAngle * RingRadius;
const float SampleY = Passage.LowerPoint.Y + SinAngle * RingRadius;
const float Density = World.Generator->GetDensityAt(
SampleX,
SampleY,
Passage.LowerPoint.Z);
++NumRingSamples;
if (Density >= 0.0f)
{
++AirSamples;
++NumRingAirSamples;
}
}
if (AirSamples < MinimumAirSamples)
{
bAllAnswerableMouthRingsHaveAir = false;
AddError(FString::Printf(
TEXT("Passage to strate %d (%s) has too little destination air around its mouth: %d/%d ring samples at radius %.3f are air; mouth (%.3f, %.3f, %.3f)."),
Destination.StrateIndex,
*ArchetypeName(Definition->GeneratorType),
AirSamples,
RingSampleCount,
RingRadius,
Passage.LowerPoint.X,
Passage.LowerPoint.Y,
Passage.LowerPoint.Z));
}
}
//==========================================================================
// LANDING GEOMETRY — the mouth is now measured as a room, not as a tube.
//==========================================================================
// Use the final production density, including the legacy structural post and any
// disturbance backstop. Five points on every floor give a small numerical walkability
// stencil; the floor writer itself is a plane, but the test must inspect the resulting field.
for (const FVoxelPassage& Passage : Passages)
{
if (Passage.UpperStrateIndex == Passage.LowerStrateIndex) continue;
const FVoxelPassageLanding* Landings[] = {
&Passage.UpperLanding, &Passage.LowerLanding };
const int32 StrateIndices[] = {
Passage.UpperStrateIndex, Passage.LowerStrateIndex };
for (int32 EndIndex = 0; EndIndex < 2; ++EndIndex)
{
++NumLandingEnds;
const FVoxelPassageLanding& Landing = *Landings[EndIndex];
const int32 StrateIndex = StrateIndices[EndIndex];
const UVoxelStrateDefinition* Definition = Layout.IsValidIndex(StrateIndex)
? Layout[StrateIndex].Definition : nullptr;
auto RecordFailure = [&](const FString& Message)
{
if (FirstLandingFailure.IsEmpty()) FirstLandingFailure = Message;
};
if (Definition == nullptr
|| Landing.HalfWidth <= 0.0f
|| Landing.CeilingZ <= Landing.FloorZ
|| !FMath::IsFinite(Landing.FloorZ)
|| !FMath::IsFinite(Landing.CeilingZ)
|| !FMath::IsFinite(Landing.HalfWidth))
{
++NumLandingFloorFailures;
RecordFailure(FString::Printf(
TEXT("invalid landing descriptor at passage strate end %d/%d"),
Passage.UpperStrateIndex, Passage.LowerStrateIndex));
continue;
}
const float StrateTopZ = (float)(Layout[StrateIndex].TopChunkZ + 1) * CHUNK_SIZE;
const float StrateBottomZ = (float)Layout[StrateIndex].BottomChunkZ * CHUNK_SIZE;
const float Seal = BoundarySealFor(*Definition);
if (!(Landing.FloorZ > StrateBottomZ + Seal)
|| !(Landing.CeilingZ < StrateTopZ - Seal))
{
++NumLandingSealFailures;
RecordFailure(FString::Printf(
TEXT("landing at strate %d reaches its seal (floor %.3f, ceiling %.3f, interior %.3f..%.3f)"),
StrateIndex, Landing.FloorZ, Landing.CeilingZ,
StrateBottomZ + Seal, StrateTopZ - Seal));
}
if ((Landing.StandingPoint - (EndIndex == 0
? Passage.UpperPoint : Passage.LowerPoint)).SizeSquared() > 0.0001f)
{
++NumLandingFloorFailures;
RecordFailure(FString::Printf(
TEXT("landing standing anchor does not match passage %s point at strate %d"),
EndIndex == 0 ? TEXT("upper") : TEXT("lower"), StrateIndex));
}
const float ThreeMetresVoxels = VoxelPassageGeometry::MetersToVoxels(3.0f);
const bool bWidthAndHeadroom = Landing.HalfWidth * 2.0f >= ThreeMetresVoxels - 0.01f
&& Landing.CeilingZ - Landing.FloorZ >= ThreeMetresVoxels - 0.01f;
const bool bDoorTangent = FMath::IsNearlyEqual(
Landing.DoorPoint.Z - Landing.FloorZ,
Passage.Radius >= 0.0f ? FMath::Max(
Passage.ControlRadii.Num() > 0
? Passage.ControlRadii[0] : Passage.Radius,
1.0f) : 1.0f,
0.01f);
if (!bWidthAndHeadroom || !bDoorTangent)
{
++NumLandingFloorFailures;
RecordFailure(FString::Printf(
TEXT("landing at strate %d failed body/door arithmetic (width %.3f, height %.3f, door rise %.3f)"),
StrateIndex, Landing.HalfWidth * 2.0f,
Landing.CeilingZ - Landing.FloorZ,
Landing.DoorPoint.Z - Landing.FloorZ));
}
const FVector FloorSamples[] = {
Landing.StandingPoint,
Landing.StandingPoint + FVector(VoxelPassageGeometry::MetersToVoxels(0.5f), 0.0f, 0.0f),
Landing.StandingPoint + FVector(-VoxelPassageGeometry::MetersToVoxels(0.5f), 0.0f, 0.0f),
Landing.StandingPoint + FVector(0.0f, VoxelPassageGeometry::MetersToVoxels(0.5f), 0.0f),
Landing.StandingPoint + FVector(0.0f, -VoxelPassageGeometry::MetersToVoxels(0.5f), 0.0f) };
float SurfaceHeights[UE_ARRAY_COUNT(FloorSamples)] = {};
bool bFloorPassed = true;
float LandingGradient = -1.0f;
for (int32 SampleIndex = 0; SampleIndex < UE_ARRAY_COUNT(FloorSamples); ++SampleIndex)
{
const FVector& Sample = FloorSamples[SampleIndex];
const float Below = World.Generator->GetDensityAt(
Sample.X, Sample.Y, Landing.FloorZ
- VoxelPassageGeometry::MetersToVoxels(0.25f));
const float Above = World.Generator->GetDensityAt(
Sample.X, Sample.Y, Landing.FloorZ
+ VoxelPassageGeometry::MetersToVoxels(0.25f));
const float Denominator = Above - Below;
if (!FMath::IsFinite(Below) || !FMath::IsFinite(Above)
|| !(Below < 0.0f) || !(Above >= 0.0f)
|| !(Denominator > 0.0f))
{
bFloorPassed = false;
continue;
}
const float FloorProbeHalfRange = VoxelPassageGeometry::MetersToVoxels(0.25f);
SurfaceHeights[SampleIndex] = Landing.FloorZ - FloorProbeHalfRange
+ FMath::Clamp(-Below / Denominator, 0.0f, 1.0f)
* 2.0f * FloorProbeHalfRange;
}
if (bFloorPassed)
{
float MaxGradient = 0.0f;
for (int32 SampleIndex = 1; SampleIndex < UE_ARRAY_COUNT(FloorSamples); ++SampleIndex)
{
MaxGradient = FMath::Max(MaxGradient,
FMath::Abs(SurfaceHeights[SampleIndex] - SurfaceHeights[0])
/ VoxelPassageGeometry::MetersToVoxels(0.5f));
}
LandingGradient = MaxGradient;
WorstLandingFloorGradient = FMath::Max(WorstLandingFloorGradient, MaxGradient);
const float NormalZ = 1.0f / FMath::Sqrt(1.0f + MaxGradient * MaxGradient);
bFloorPassed = NormalZ + KINDA_SMALL_NUMBER >= VoxelMath::DetCos(
FMath::DegreesToRadians(LandingWalkableAngleDegrees));
}
// Landing rooms are intentionally local now. Their flat floors are the only landing
// contract; the removed radial root connectors were the source of the straight roads
// through unrelated rooms.
const bool bLandingSlopePassed = bFloorPassed;
if (bLandingSlopePassed)
{
++NumLandingFloorsPassed;
}
else
{
++NumLandingFloorFailures;
FString LandingDensityDetails;
for (int32 DetailIndex = 0;
DetailIndex < UE_ARRAY_COUNT(FloorSamples);
++DetailIndex)
{
const FVector& DetailSample = FloorSamples[DetailIndex];
LandingDensityDetails += FString::Printf(
TEXT(" sample%d(%.2f,%.2f below%.2f at%.2f above%.2f)"),
DetailIndex, DetailSample.X, DetailSample.Y,
World.Generator->GetDensityAt(
DetailSample.X, DetailSample.Y, Landing.FloorZ - 1.0f),
World.Generator->GetDensityAt(
DetailSample.X, DetailSample.Y, Landing.FloorZ),
World.Generator->GetDensityAt(
DetailSample.X, DetailSample.Y, Landing.FloorZ + 1.0f));
}
RecordFailure(FString::Printf(
TEXT("landing at strate %d failed its final-density flat-floor slope bracket:%s"),
StrateIndex, *LandingDensityDetails));
}
// The old route audit used the detached graph support-floor result to excuse solid
// capsule samples. That result disappeared with the authored tunnel floor. Keep the
// walkability property at the place it matters: each authored landing must admit the
// current player capsule above its final-density room-owned floor, with no fallback
// oracle and no reserved support slab.
++NumLandingCapsuleChecks;
int32 IgnoredRoomFloorSamples = 0;
FString LandingCapsuleFailure;
if (!FinalDensityCapsuleFits(
*World.Generator, Landing.StandingPoint, Landing.FloorZ,
nullptr, IgnoredRoomFloorSamples, &LandingCapsuleFailure))
{
++NumLandingCapsuleFailures;
if (FirstLandingCapsuleFailure.IsEmpty())
{
FirstLandingCapsuleFailure = FString::Printf(
TEXT("landing %d/%d at strate %d: %s"),
Passage.UpperStrateIndex, Passage.LowerStrateIndex,
StrateIndex, *LandingCapsuleFailure);
}
}
if (Landing.bSourcePlayerFit)
{
++NumLandingSourceFit;
}
// Exercise the same box shortcut that can otherwise erase a support slab. A uniform
// verdict is allowed only if every exact ClassifyTile lattice sample agrees with the
// final density; a floor-bearing landing should normally force Mixed via the
// bidirectional passage effect. This is one aggregate audit, not per-sample logging.
const FIntVector LandingBoxOrigin(
FMath::FloorToInt(Landing.StandingPoint.X) - 4,
FMath::FloorToInt(Landing.StandingPoint.Y) - 4,
FMath::FloorToInt(Landing.FloorZ) - 3);
constexpr int32 LandingBoxCells = 8;
constexpr int32 LandingBoxStep = 1;
const EVoxelTileClass LandingBoxVerdict = World.Generator->ClassifyTile(
LandingBoxOrigin, LandingBoxStep, LandingBoxCells);
++NumLandingFloorBoxes;
if (LandingBoxVerdict != EVoxelTileClass::Mixed)
{
++NumLandingBoxProofs;
if (LandingBoxVerdict == EVoxelTileClass::AllSolid) ++NumLandingBoxAllSolid;
if (LandingBoxVerdict == EVoxelTileClass::AllAir) ++NumLandingBoxAllAir;
bool bBoxAgrees = true;
const bool bClaimsSolid = LandingBoxVerdict == EVoxelTileClass::AllSolid;
for (int32 GZ = -1; GZ <= LandingBoxCells + 1 && bBoxAgrees; ++GZ)
{
for (int32 GY = -1; GY <= LandingBoxCells + 1 && bBoxAgrees; ++GY)
{
for (int32 GX = -1; GX <= LandingBoxCells + 1; ++GX)
{
const float Density = World.Generator->GetDensityAt(
(float)(LandingBoxOrigin.X + GX * LandingBoxStep),
(float)(LandingBoxOrigin.Y + GY * LandingBoxStep),
(float)(LandingBoxOrigin.Z + GZ * LandingBoxStep));
const bool bMatches = bClaimsSolid
? Density < 0.0f : Density >= 0.0f;
if (!FMath::IsFinite(Density) || !bMatches)
{
bBoxAgrees = false;
break;
}
}
}
}
if (!bBoxAgrees)
{
++NumLandingBoxViolations;
if (NumLandingBoxViolations == 1)
{
AddError(FString::Printf(
TEXT("Landing box verdict %d was not sound at (%d,%d,%d): the uniform %s verdict disagreed with final density."),
NumLandingBoxProofs,
LandingBoxOrigin.X,
LandingBoxOrigin.Y,
LandingBoxOrigin.Z,
bClaimsSolid ? TEXT("AllSolid") : TEXT("AllAir")));
}
}
}
const FString LandingArchetype = ArchetypeName(Definition->GeneratorType);
LandingSlopeReport += FString::Printf(
TEXT("%s[%d:%s]=flat-floor:%s(%.6f) "),
*LandingArchetype,
StrateIndex,
EndIndex == 0 ? TEXT("upper") : TEXT("lower"),
bFloorPassed ? TEXT("PASS") : TEXT("FAIL"),
LandingGradient);
}
}
// The former route-wide audit is intentionally retired. It walked a sampled tunnel against
// BuildTunnelSupportFloorColumn/its graph support-floor result, which is no longer emitted
// after authored tunnel-floor removal. Keeping that audit would assert detached geometry and
// would hide the property this test actually protects. The landing loop above now checks the
// final-density capsule at every authored endpoint without a support-floor exemption; the
// ring, footing, floor bracket, slope, and box checks continue to cover its surrounding air.
// The passage modifier is called once per density sample, but shortlist construction is
// keyed by (manager, layout version, chunk) in thread_local storage. Measure the hot hit and
// the deliberately alternating two-chunk rebuild separately so the report exposes the cache
// contract without adding a log line for any individual sample.
const FVoxelPassage* TimingPassage = nullptr;
for (const FVoxelPassage& Passage : Passages)
{
if (Passage.UpperStrateIndex != Passage.LowerStrateIndex)
{
TimingPassage = &Passage;
break;
}
}
if (TimingPassage != nullptr)
{
const FVector TimingPoint = TimingPassage->UpperLanding.StandingPoint
+ FVector(0.0f, 0.0f, 2.0f);
constexpr int32 HotCalls = 8192;
constexpr int32 RebuildCalls = 96;
volatile float TimingSink = 0.0f;
double StartSeconds = FPlatformTime::Seconds();
for (int32 Call = 0; Call < HotCalls; ++Call)
{
float Density = 8.0f;
World.StrateManager->ApplyPassageModifier(
Density, TimingPoint.X, TimingPoint.Y, TimingPoint.Z, 8.0f, 0.0f);
TimingSink += Density;
}
const double HotSeconds = FPlatformTime::Seconds() - StartSeconds;
StartSeconds = FPlatformTime::Seconds();
for (int32 Call = 0; Call < RebuildCalls; ++Call)
{
const FVector Point = TimingPoint
+ ((Call & 1) != 0 ? FVector((float)CHUNK_SIZE, 0.0f, 0.0f)
: FVector::ZeroVector);
float Density = 8.0f;
World.StrateManager->ApplyPassageModifier(
Density, Point.X, Point.Y, Point.Z, 8.0f, 0.0f);
TimingSink += Density;
}
const double RebuildSeconds = FPlatformTime::Seconds() - StartSeconds;
AddInfo(FString::Printf(
TEXT("Passage TLS cache timing: rebuild %.3f us/call over %d alternating chunks; hot %.3f us/call over %d same-chunk calls (sink %.3f)."),
RebuildSeconds * 1000000.0 / (double)RebuildCalls,
RebuildCalls,
HotSeconds * 1000000.0 / (double)HotCalls,
HotCalls,
(float)TimingSink));
}
AddInfo(FString::Printf(
TEXT("Passage player-fit check: %d inter-strate passages, upper %d checked (%d false), lower %d checked (%d footing checks), %d/%d room/slab ring samples air, lower %d query-false (%d unique archetypes; %d unsupported, %d supported-but-no-point)."),
NumInterStratePassages,
NumUpperChecked,
NumUpperQueryFalse,
NumChecked,
NumFootingChecked,
NumRingAirSamples,
NumRingSamples,
NumQueryFalse,
FalseArchetypes.Num(),
NumUnsupported,
NumSupportedWithoutPoint));
AddInfo(FString::Printf(
TEXT("Landing geometry: %d/%d ends passed the final-density flat-floor test (worst gradient %.6f), source-fit anchors %d/%d, seal violations %d, geometry/floor failures %d."),
NumLandingFloorsPassed,
NumLandingEnds,
WorstLandingFloorGradient,
NumLandingSourceFit,
NumLandingEnds,
NumLandingSealFailures,
NumLandingFloorFailures));
if (!LandingSlopeReport.IsEmpty())
{
AddInfo(FString::Printf(TEXT("Landing slope tests per end: %s"), *LandingSlopeReport));
}
AddInfo(FString::Printf(
TEXT("Landing floor box verdicts: %d boxes probed, %d uniform proofs (AllSolid %d, AllAir %d), %d violations."),
NumLandingFloorBoxes,
NumLandingBoxProofs,
NumLandingBoxAllSolid,
NumLandingBoxAllAir,
NumLandingBoxViolations));
if (!FirstLandingFailure.IsEmpty())
{
AddError(FString::Printf(TEXT("First landing geometry failure: %s"), *FirstLandingFailure));
}
AddInfo(FString::Printf(
TEXT("Landing capsule air: %d/%d authored endpoint capsules fit above the final-density "
"room floor; failures %d."),
NumLandingCapsuleChecks - NumLandingCapsuleFailures,
NumLandingCapsuleChecks,
NumLandingCapsuleFailures));
if (!FirstLandingCapsuleFailure.IsEmpty())
{
AddError(FString::Printf(
TEXT("First landing capsule failure: %s"),
*FirstLandingCapsuleFailure));
}
// A test that inspected nothing is not evidence of the invariant. Fail loudly in both cases.
// Un test qui n'a rien inspecté ne prouve pas l'invariant : échouer explicitement dans les deux cas.
if (NumInterStratePassages == 0)
{
AddError(TEXT("VACUOUS: the fixture generated zero inter-strate passages."));
}
if (NumChecked == 0)
{
AddError(TEXT("VACUOUS: zero answerable passage mouths were checked; the passage invariant was not exercised."));
}
if (NumUpperChecked == 0)
{
AddError(TEXT("VACUOUS: zero answerable upper passage mouths were checked; independent upper-mouth aiming was not exercised."));
}
if (NumFootingChecked == 0)
{
AddError(TEXT("VACUOUS: no Maze/VerticalShafts/FloatingIslands footing was checked."));
}
if (NumLandingCapsuleChecks == 0)
{
AddError(TEXT("VACUOUS: no authored landing capsule was checked for open space."));
}
const bool bAllNewFootingArchetypesAnswered =
AnsweredArchetypes.Contains(static_cast<uint8>(ECaveGeneratorType::Maze))
&& AnsweredArchetypes.Contains(static_cast<uint8>(ECaveGeneratorType::VerticalShafts))
&& AnsweredArchetypes.Contains(static_cast<uint8>(ECaveGeneratorType::FloatingIslands));
if (!bAllNewFootingArchetypesAnswered)
{
AddError(TEXT("The fixture did not exercise all three source-footing queries newly covered by Tier 1."));
}
// ⚠️ MESURE, PAS CONTRAT. Quels archétypes répondent DANS CE FIXTURE dépend de l'endroit où ses
// 7 passages tombent : une requête supportée a le DROIT de décliner quand le site le plus proche
// dépasse son budget latéral. C'est le comportement voulu — un refus honnête vaut mieux qu'une
// réponse fausse et confiante. Assertion supprimée le 2026-08-30 : elle affirmait une propriété
// de l'ÉCHANTILLON, pas du code, et échouait sur une CORRECTION (le budget salle passant de 0 à
// RoomSpacing a rendu un atterrissage hors-salle honnêtement refusé).
//
// MEASUREMENT, NOT CONTRACT. Which archetypes answer IN THIS FIXTURE depends on where its 7
// passages happen to fall: a supported query is ALLOWED to decline when the nearest site exceeds
// its lateral budget. That is the intended behaviour — an honest refusal beats a confident wrong
// answer. This assertion was removed 2026-08-30: it asserted a property of the SAMPLE rather
// than of the code, and it failed on a FIX (raising the room budget from 0 to RoomSpacing turned
// a previously-wrong outside-the-room landing into an honest refusal).
//
// The real risks remain hard failures: the vacuity guards below (zero passages, zero checks,
// no footing exercised) and the per-mouth ring/footing assertions above. Same discipline the
// codebase applies to box-verdict PROVED counts: read the count as a measurement, assert only
// that nothing it reports is WRONG.
TArray<FString> SupportedButUnexercised;
const TPair<ECaveGeneratorType, const TCHAR*> PreviouslyCovered[] = {
{ ECaveGeneratorType::TunnelNetwork, TEXT("TunnelNetwork") },
{ ECaveGeneratorType::Underwater, TEXT("Underwater") },
{ ECaveGeneratorType::FlatPlain, TEXT("FlatPlain") },
{ ECaveGeneratorType::CrystalChamber, TEXT("CrystalChamber") },
};
for (const TPair<ECaveGeneratorType, const TCHAR*>& Entry : PreviouslyCovered)
{
if (!AnsweredArchetypes.Contains(static_cast<uint8>(Entry.Key)))
{
SupportedButUnexercised.Add(FString(Entry.Value));
}
}
if (SupportedButUnexercised.Num() > 0)
{
AddInfo(FString::Printf(
TEXT("Supported but unexercised at this fixture seed (declined on lateral budget, not a ")
TEXT("failure): %s."),
*FString::Join(SupportedButUnexercised, TEXT(", "))));
}
const bool bSurfaceIntentionallyRefused =
FalseArchetypes.Contains(static_cast<uint8>(ECaveGeneratorType::SurfaceWorld));
if (!bSurfaceIntentionallyRefused)
{
AddError(TEXT("SurfaceWorld unexpectedly answered a pure footing query; keep it refused until its biome/context contract is explicit."));
}
return NumInterStratePassages > 0
&& NumLandingEnds == NumInterStratePassages * 2
&& NumLandingFloorsPassed == NumLandingEnds
&& NumLandingSealFailures == 0
&& NumLandingFloorFailures == 0
&& NumLandingBoxViolations == 0
&& NumLandingCapsuleChecks > 0
&& NumLandingCapsuleFailures == 0
&& NumChecked > 0
&& NumUpperChecked > 0
&& NumFootingChecked > 0
&& bAllNewFootingArchetypesAnswered
// (fixture coverage of the room/slab archetypes is reported, not asserted — see above)
&& bSurfaceIntentionallyRefused
&& bAllAnswerableFootingsAreValid
&& bAllAnswerableMouthRingsHaveAir
&& bAllAnswerableEndpointsMatchQuery;
}
#endif // WITH_DEV_AUTOMATION_TESTS