test(reach): automation test guarding the per-tile reach proofs; merge closed as load-bearing

VoxelForge.Correctness.TilePostReachProof (VoxelForgeTilePostReachTest.cpp) runs the real mesher
twice in one process: at full reach it compares the skipped posts' FINAL field (356,946
comparisons, 0 differences), and at a deliberately shrunk reach it must find differences (774
found). Native/fused sample counts are identical (137,180). So a future tunnel or passage geometry
change that breaks a reach proof (ce4d878, a668833) fails loudly instead of silently making holes.
VoxelGenLOD::ResetTilePostReachDiagnostics resets every per-kind counter between the two runs, so
the proof cannot pass vacuously.

Test fixture fix: the strate connectivity test's density-sign/coarse-lie controls use boxes up to
256 voxels, which the DiffLayer budget added in 8879045 rejected. The fixture now uses an unlimited
budget; the test measures the field, not the gameplay brush.

The tunnel SDF/core merge was attempted and NOT shipped (Codex; reviewed by me):
- The world-space core is load-bearing. WorldControlPoints keep the authored room-floor anchors and
  walkable tunnel routes before cave warp (8e42d6d, against warped floors becoming vertical
  breaks), and cf2b2ad made the world chain authoritative for option B winding/ledge walkability.
  EvaluateTunnelCoreWorld also supplies the support/room-floor ownership the tail uses.
- Replacing the world shape: player-fit 20,830 -> 19,684, walk 10,909 -> 9,934, components
  261 -> 220. Raw world evaluation without floor handling also failed (20,348 / 10,713): a
  coordinate-frame / junction mismatch, not only floor projection.
- The exact shared-segment variant was SLOWER than the committed tip: static worker +5.6%, moving
  +12.6%, LOD0 p95 +6% / +13%.
- A 0.0001-0.01% world-chain blend that merely passed the gate was rejected as cosmetic.
All merge source was reverted. This build is bit-identical to the committed tip (311CA42C, 75,482
triangles, capability 20,830 / 10,909); connectivity 6/6 + 16/16, passage open-space 14/14 and
sealed proof 22/22 pass. Every launch was serialized.

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-15 17:42:25 +02:00
1 parent cfd81b5ce9
commit 8ef1a3b579
3 files changed
+270

No files matched your search

@@ -1387,6 +1387,12 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
return false; return false;
} }
// The two density-sign/coarse-lie controls below deliberately use boxes up to 256 voxels
// wide. That is larger than the player-facing DiffLayer default radius cap; this test is
// validating the measurement field, not the gameplay brush budget. Keep the fixture budget
// unlimited so a rejected control cannot masquerade as a density or connectivity failure.
World.DiffLayer->SetBudget(/*MaxMods=*/0, /*MaxRadius=*/0.0f, /*MaxVolume=*/0.0f);
FVoxelStrateMeasureSettings Settings; FVoxelStrateMeasureSettings Settings;
Settings.SampleStep = 4; Settings.SampleStep = 4;
Settings.RadiusInVoxels = 256; Settings.RadiusInVoxels = 256;
@@ -0,0 +1,226 @@
// The tile-reach proof must fail loudly when its envelope is deliberately made too small.
#if WITH_DEV_AUTOMATION_TESTS
#include "Misc/AutomationTest.h"
#include "HAL/IConsoleManager.h"
#include "VoxelForgeTestFixture.h"
#include "VoxelMarchingCubesMesher.h"
#include "../../Public/VoxelTilePostReach.h"
namespace
{
using namespace VoxelForgeTest;
struct FScopedReachTestCVars
{
IConsoleVariable* Debug = nullptr;
IConsoleVariable* Scale = nullptr;
int32 OldDebug = 0;
float OldScale = 1.0f;
bool bValid = false;
FScopedReachTestCVars()
{
IConsoleManager& Console = IConsoleManager::Get();
Debug = Console.FindConsoleVariable(TEXT("voxel.TilePostReachDebug"));
Scale = Console.FindConsoleVariable(TEXT("voxel.TilePostReachScale"));
if (Debug == nullptr || Scale == nullptr)
{
return;
}
OldDebug = Debug->GetInt();
OldScale = Scale->GetFloat();
bValid = true;
Debug->Set(1, ECVF_SetByCode);
}
~FScopedReachTestCVars()
{
if (!bValid)
{
return;
}
Debug->Set(OldDebug, ECVF_SetByCode);
Scale->Set(OldScale, ECVF_SetByCode);
}
void SetScale(float InScale)
{
if (Scale != nullptr)
{
Scale->Set(InScale, ECVF_SetByCode);
}
}
};
struct FReachSnapshot
{
uint64 Tiles = 0;
uint64 FinalComparisons = 0;
uint64 FinalDifferences = 0;
uint64 AllComparisons = 0;
uint64 AllDifferences = 0;
int64 DensitySamples = 0;
int32 MeshedTiles = 0;
};
FReachSnapshot RunReachTiles(
UVoxelMarchingCubesMesher& Mesher,
const TArray<FIntVector>& Origins)
{
FReachSnapshot Out;
const uint64 TileCountBefore =
VoxelGenLOD::GTilePostReachTileCount.load(std::memory_order_relaxed);
for (const FIntVector& Origin : Origins)
{
int64 Samples = 0;
Mesher.GenerateMesh(
Origin, 8, 16, nullptr, INT32_MIN, INT32_MAX, &Samples);
Out.DensitySamples += Samples;
Out.MeshedTiles += Samples > 0 ? 1 : 0;
}
const uint64 TileCountAfter =
VoxelGenLOD::GTilePostReachTileCount.load(std::memory_order_relaxed);
Out.Tiles = TileCountAfter - TileCountBefore;
Out.FinalComparisons = VoxelGenLOD::GSkippedPostComparisonsByKind[
static_cast<uint8>(VoxelGenLOD::ETilePostComparisonKind::FinalField)]
.load(std::memory_order_relaxed);
Out.FinalDifferences = VoxelGenLOD::GSkippedPostDifferencesByKind[
static_cast<uint8>(VoxelGenLOD::ETilePostComparisonKind::FinalField)]
.load(std::memory_order_relaxed);
Out.AllComparisons = VoxelGenLOD::GSkippedPostComparisons.load(
std::memory_order_relaxed);
Out.AllDifferences = VoxelGenLOD::GSkippedPostDifferences.load(
std::memory_order_relaxed);
return Out;
}
int32 FloorAlign(int32 Value, int32 Alignment)
{
return FMath::FloorToInt(static_cast<float>(Value) / static_cast<float>(Alignment))
* Alignment;
}
void BuildReachTileOrigins(
const FTestWorld& World,
int32 Slot,
TArray<FIntVector>& OutOrigins)
{
int32 SlotTop = 0;
int32 SlotBottom = 0;
if (!World.GetSlotVoxelZRange(Slot, SlotTop, SlotBottom))
{
return;
}
const int32 TileExtent = 16 * 8;
const int32 TileZ = FloorAlign(SlotBottom, TileExtent);
// Nine XY tiles around the origin cover the guaranteed spine, winding tunnel mouths,
// and feature-free graph regions. Include one distant tile to prove the skip side is
// exercised rather than relying only on near-tunnel tiles.
for (int32 Y = -TileExtent; Y <= TileExtent; Y += TileExtent)
{
for (int32 X = -TileExtent; X <= TileExtent; X += TileExtent)
{
OutOrigins.Add(FIntVector(X, Y, TileZ));
}
}
OutOrigins.Add(FIntVector(4 * TileExtent, -3 * TileExtent, TileZ));
}
void AppendWorldReachTiles(
const FTestWorld& World,
TArray<FIntVector>& OutOrigins)
{
BuildReachTileOrigins(World, FTestWorld::SlotTunnelNetwork, OutOrigins);
BuildReachTileOrigins(World, FTestWorld::SlotUnderwater, OutOrigins);
}
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FVoxelForgeTilePostReachProofTest,
"VoxelForge.Correctness.TilePostReachProof",
EAutomationTestFlags_ApplicationContextMask | EAutomationTestFlags::EngineFilter)
bool FVoxelForgeTilePostReachProofTest::RunTest(const FString& Parameters)
{
FScopedReachTestCVars CVarGuard;
if (!CVarGuard.bValid)
{
AddError(TEXT("Tile-post reach test could not find its two console variables."));
return false;
}
FTestWorld NativeWorld;
NativeWorld.Build(/*Seed*/1337, /*GapChunks*/2, /*bUseOperatorStack*/false);
FTestWorld FusedWorld;
FusedWorld.Build(/*Seed*/1337, /*GapChunks*/2, /*bUseOperatorStack*/true);
if (!NativeWorld.IsValid() || !FusedWorld.IsValid())
{
AddError(NativeWorld.IsValid() && FusedWorld.IsValid()
? TEXT("Unexpected invalid reach-proof fixture")
: (!NativeWorld.IsValid() ? NativeWorld.WhyInvalid() : FusedWorld.WhyInvalid()));
return false;
}
TArray<FIntVector> NativeOrigins;
TArray<FIntVector> FusedOrigins;
AppendWorldReachTiles(NativeWorld, NativeOrigins);
AppendWorldReachTiles(FusedWorld, FusedOrigins);
TestTrue(TEXT("the representative native tile set is non-empty"), NativeOrigins.Num() > 0);
TestTrue(TEXT("the representative fused tile set is non-empty"), FusedOrigins.Num() > 0);
TStrongObjectPtr<UVoxelMarchingCubesMesher> NativeMesher(
NewObject<UVoxelMarchingCubesMesher>(GetTransientPackage(), NAME_None, RF_Transient));
TStrongObjectPtr<UVoxelMarchingCubesMesher> FusedMesher(
NewObject<UVoxelMarchingCubesMesher>(GetTransientPackage(), NAME_None, RF_Transient));
NativeMesher->SetGenerator(NativeWorld.Generator.Get());
FusedMesher->SetGenerator(FusedWorld.Generator.Get());
VoxelGenLOD::ResetTilePostReachDiagnostics();
CVarGuard.SetScale(1.0f);
const FReachSnapshot NativeFull = RunReachTiles(*NativeMesher, NativeOrigins);
const FReachSnapshot FusedFull = RunReachTiles(*FusedMesher, FusedOrigins);
const uint64 FullFinalComparisons = NativeFull.FinalComparisons + FusedFull.FinalComparisons;
const uint64 FullFinalDifferences = NativeFull.FinalDifferences + FusedFull.FinalDifferences;
TestTrue(TEXT("normal reach compares final fields on the representative tiles"),
FullFinalComparisons > 0);
TestEqual(TEXT("normal reach has no final-field differences"), FullFinalDifferences, 0ull);
TestEqual(TEXT("normal reach has no skipped-post differences"),
NativeFull.AllDifferences + FusedFull.AllDifferences, 0ull);
VoxelGenLOD::ResetTilePostReachDiagnostics();
// Use a deliberately tiny but valid scale so at least one envelope is rejected while the same
// final-field samples remain exercised; this is the negative control, not a supported setting.
CVarGuard.SetScale(0.01f);
const FReachSnapshot NativeShrunk = RunReachTiles(*NativeMesher, NativeOrigins);
const FReachSnapshot FusedShrunk = RunReachTiles(*FusedMesher, FusedOrigins);
const uint64 ShrunkFinalComparisons =
NativeShrunk.FinalComparisons + FusedShrunk.FinalComparisons;
const uint64 ShrunkFinalDifferences =
NativeShrunk.FinalDifferences + FusedShrunk.FinalDifferences;
TestTrue(TEXT("shrunk reach still executes final-field comparisons"),
ShrunkFinalComparisons > 0);
TestTrue(TEXT("shrunk reach demonstrably fails the final-field proof"),
ShrunkFinalDifferences > 0);
AddInfo(FString::Printf(
TEXT("tile_post_reach_proof: native_tiles=%llu fused_tiles=%llu full_final=%llu/%llu "
"shrunk_final=%llu/%llu native_samples=%lld/%lld fused_samples=%lld/%lld."),
static_cast<unsigned long long>(NativeFull.Tiles),
static_cast<unsigned long long>(FusedFull.Tiles),
static_cast<unsigned long long>(FullFinalComparisons),
static_cast<unsigned long long>(FullFinalDifferences),
static_cast<unsigned long long>(ShrunkFinalComparisons),
static_cast<unsigned long long>(ShrunkFinalDifferences),
static_cast<long long>(NativeFull.DensitySamples),
static_cast<long long>(NativeShrunk.DensitySamples),
static_cast<long long>(FusedFull.DensitySamples),
static_cast<long long>(FusedShrunk.DensitySamples)));
return true;
}
#endif // WITH_DEV_AUTOMATION_TESTS
@@ -129,6 +129,44 @@ namespace VoxelGenLOD
inline std::atomic<uint64> GTileReachBlockCostCycles[ inline std::atomic<uint64> GTileReachBlockCostCycles[
static_cast<uint8>(ETileReachCostKind::Count)][2]{}; static_cast<uint8>(ETileReachCostKind::Count)][2]{};
// Automation tests deliberately run the real mesher twice (full reach and a shrunken reach)
// in one process. Keep the reset in the reach owner so a test cannot accidentally omit one
// of the per-kind counters and accept a vacuous proof.
FORCEINLINE void ResetTilePostReachDiagnostics()
{
GTilePostReachDebugEnabled.store(false, std::memory_order_relaxed);
GTilePostReachTileCount.store(0, std::memory_order_relaxed);
GOriginLandingReachableTiles.store(0, std::memory_order_relaxed);
GPassageLandingReachableTiles.store(0, std::memory_order_relaxed);
GPassageStructuralReachableTiles.store(0, std::memory_order_relaxed);
GOriginLandingSkippedTiles.store(0, std::memory_order_relaxed);
GPassageLandingSkippedTiles.store(0, std::memory_order_relaxed);
GPassageStructuralSkippedTiles.store(0, std::memory_order_relaxed);
GTunnelCoreReachableTiles.store(0, std::memory_order_relaxed);
GTunnelCoreSkippedTiles.store(0, std::memory_order_relaxed);
GPassageCarvingReachableTiles.store(0, std::memory_order_relaxed);
GPassageCarvingSkippedTiles.store(0, std::memory_order_relaxed);
GSkippedPostComparisons.store(0, std::memory_order_relaxed);
GSkippedPostDifferences.store(0, std::memory_order_relaxed);
for (uint8 Index = 0;
Index < static_cast<uint8>(ETilePostComparisonKind::Count); ++Index)
{
GSkippedPostComparisonsByKind[Index].store(0, std::memory_order_relaxed);
GSkippedPostDifferencesByKind[Index].store(0, std::memory_order_relaxed);
}
GTileReachCostDiagnosticsEnabled.store(false, std::memory_order_relaxed);
for (uint8 Kind = 0; Kind < static_cast<uint8>(ETileReachCostKind::Count); ++Kind)
{
for (uint8 Reach = 0; Reach < 2; ++Reach)
{
GTileReachCostCalls[Kind][Reach].store(0, std::memory_order_relaxed);
GTileReachCostCycles[Kind][Reach].store(0, std::memory_order_relaxed);
GTileReachBlockCostCalls[Kind][Reach].store(0, std::memory_order_relaxed);
GTileReachBlockCostCycles[Kind][Reach].store(0, std::memory_order_relaxed);
}
}
}
FORCEINLINE bool IsTilePostReachable(uint8 Bit) FORCEINLINE bool IsTilePostReachable(uint8 Bit)
{ {
return (TilePostReachFlags & Bit) != 0; return (TilePostReachFlags & Bit) != 0;