perf(streaming): prove sealed rock solid instead of sampling it -- crossing fix's cost recovered
4c8d06f stopped content-cutting LOD0 so the player can cross strates, at a cost of +29% static
worker CPU: LOD0 tiles in the sealed rock between strates, which the old cut skipped by accident,
were sampled in full and found solid. This proves those tiles solid instead (Codex; reviewed by me).
- UVoxelGenerator::TryProveSealedSolidTile: AllSolid only for a tile whose whole lattice lies in a
gap/seal region. It rejects player mods (HasAnyModInChunkRange), passages plus carve pads, the
origin spine/landing, disturbances, recipes and overrides. It is narrower than the old
ClassifyTile, which stays off in streaming. CollisionSolidTiles / CollisionNotRequiredTiles
bookkeeping is preserved, so the collision gate's support search still resolves.
- TryProveOutOfLayoutAirTile: a separate fail-closed constant-air proof for the zero-triangle tiles
outside the strate layout. It never supplies collision support.
- voxel.SealedSolidProof (default 1). Mining a proven tile invalidates and re-meshes it.
- A soundness test that can fail (VoxelForgeSealedSolidProofTest): 22 candidates, 22 skips, 0
non-solid lattice samples; a deliberately weakened diff guard produces 437 non-solid samples
and fails.
Verified: mining tile (14,-18,-8) went from proof=1 / 0 triangles to proof=0 / 8 triangles and
re-meshed; strate-crossing test PASS (max gate 0.098 s); off/on trace 899/899 identical; export
b3e5f4c3..9b377; capability 20,830 / 10,909.
Two interleaved rounds, serialized (owner-editor count 0 before each launch):
baseline crossing-fixed + proof
static worker 35.5 s 41.9 s 36.0 s (+1.4% vs baseline)
moving worker 78.1 s 93.2 s 78.2 s (+0.1%)
mesher calls static 18.078M 22.280M 18.078M
Applied triangles identical: 669,834 static / 1,331,482 moving.
Runs: one early automation probe exited 3 with no log (the corrected harness passed); one
exploratory moving run was interrupted and excluded.
Note: HasAnyModInChunkRange scans every modified chunk per candidate (cold audit #21). That is cheap
today (~20 candidates), but it grows with mining; it is queued in the correctness round.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
This commit is contained in:
1 parent
481e7bbaee
commit
39c306b524
7 files changed
+1067
-15
No files matched your search
@@ -0,0 +1,179 @@
|
||||
// Sealed-solid proof soundness and invalidation.
|
||||
//
|
||||
// The production shortcut is intentionally one-sided: it may return false too often, but a true
|
||||
// result skips both meshing and level-0 collision generation. This test therefore brute-forces
|
||||
// every tile that the fixture presents to the proof and treats any non-solid lattice sample as a
|
||||
// hard failure. The second half deliberately disables the diff guard; a carve must then make the
|
||||
// weakened proof look true while the same oracle finds an air sample.
|
||||
|
||||
#if WITH_DEV_AUTOMATION_TESTS
|
||||
|
||||
#include "Misc/AutomationTest.h"
|
||||
|
||||
#include "VoxelForgeTestFixture.h"
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FVoxelForgeSealedSolidProofTest,
|
||||
"VoxelForge.Determinism.SealedSolidProofSoundness",
|
||||
EAutomationTestFlags_ApplicationContextMask | EAutomationTestFlags::EngineFilter)
|
||||
|
||||
namespace
|
||||
{
|
||||
struct FTileSpec
|
||||
{
|
||||
FIntVector Origin = FIntVector::ZeroValue;
|
||||
int32 Step = 1;
|
||||
int32 Cells = CHUNK_SIZE;
|
||||
};
|
||||
|
||||
int32 CountNonSolidSamples(const UVoxelGenerator& Generator, const FTileSpec& Tile)
|
||||
{
|
||||
const int32 Cells = FMath::Clamp(Tile.Cells, 2, CHUNK_SIZE);
|
||||
int32 NonSolid = 0;
|
||||
for (int32 Z = 0; Z <= Cells; ++Z)
|
||||
for (int32 Y = 0; Y <= Cells; ++Y)
|
||||
for (int32 X = 0; X <= Cells; ++X)
|
||||
{
|
||||
const float Density = Generator.GetDensityAt(
|
||||
static_cast<float>(Tile.Origin.X + X * Tile.Step),
|
||||
static_cast<float>(Tile.Origin.Y + Y * Tile.Step),
|
||||
static_cast<float>(Tile.Origin.Z + Z * Tile.Step));
|
||||
if (!VoxelMath::IsFinite(Density) || !(Density < 0.0f))
|
||||
{
|
||||
++NonSolid;
|
||||
}
|
||||
}
|
||||
return NonSolid;
|
||||
}
|
||||
|
||||
void SetSealThickness(UVoxelStrateDefinition& Definition, float Thickness)
|
||||
{
|
||||
Definition.GenerationParams.BoundarySealThickness = Thickness;
|
||||
Definition.SlabParams.BoundarySealThickness = Thickness;
|
||||
Definition.MazeParams.BoundarySealThickness = Thickness;
|
||||
Definition.SurfaceParams.BoundarySealThickness = Thickness;
|
||||
Definition.VerticalShaftParams.BoundarySealThickness = Thickness;
|
||||
Definition.FloatingIslandParams.BoundarySealThickness = Thickness;
|
||||
}
|
||||
}
|
||||
|
||||
bool FVoxelForgeSealedSolidProofTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
using namespace VoxelForgeTest;
|
||||
|
||||
// Forty voxels makes a complete 32-cell level-0 tile fit inside a bottom boundary seal. The
|
||||
// production asset remains untouched; this is a transient proof-only fixture configuration.
|
||||
FTestWorld World;
|
||||
World.Build(/*Seed*/1337, /*GapChunks*/2, /*bUseOperatorStack*/false,
|
||||
/*InStrateHeightInChunks*/8);
|
||||
if (!World.IsValid())
|
||||
{
|
||||
AddError(World.WhyInvalid());
|
||||
return false;
|
||||
}
|
||||
for (const TStrongObjectPtr<UVoxelStrateDefinition>& Definition : World.Definitions)
|
||||
{
|
||||
if (Definition.IsValid())
|
||||
{
|
||||
SetSealThickness(*Definition.Get(), 40.0f);
|
||||
}
|
||||
}
|
||||
World.Reinitialize();
|
||||
|
||||
const UVoxelGenerator* Generator = World.Generator.Get();
|
||||
const FIntVector FarXY(4096, 4096, 0);
|
||||
TArray<FTileSpec> CandidateTiles;
|
||||
|
||||
// Enumerate every gap chunk and every strate's bottom-seal tile in this fixture. No sample
|
||||
// is selected after looking at the proof result: all candidates are offered to it first.
|
||||
for (int32 ChunkZ = World.BottomChunkZ; ChunkZ <= World.TopChunkZ; ++ChunkZ)
|
||||
{
|
||||
if (World.StrateManager->IsGapChunk(FIntVector(0, 0, ChunkZ)))
|
||||
{
|
||||
CandidateTiles.Add({FIntVector(FarXY.X, FarXY.Y, ChunkZ * CHUNK_SIZE), 1, CHUNK_SIZE});
|
||||
}
|
||||
}
|
||||
for (const FStrateSlot& Slot : World.StrateManager->GetLayout())
|
||||
{
|
||||
CandidateTiles.Add({
|
||||
FIntVector(FarXY.X, FarXY.Y, Slot.BottomChunkZ * CHUNK_SIZE),
|
||||
1, CHUNK_SIZE});
|
||||
}
|
||||
|
||||
int32 NumProofSkips = 0;
|
||||
int32 NumNonSolidSamples = 0;
|
||||
int32 FirstProofTile = INDEX_NONE;
|
||||
for (int32 Index = 0; Index < CandidateTiles.Num(); ++Index)
|
||||
{
|
||||
const FTileSpec& Tile = CandidateTiles[Index];
|
||||
if (!Generator->TryProveSealedSolidTile(Tile.Origin, Tile.Step, Tile.Cells))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
++NumProofSkips;
|
||||
if (FirstProofTile == INDEX_NONE)
|
||||
{
|
||||
FirstProofTile = Index;
|
||||
}
|
||||
const int32 TileNonSolid = CountNonSolidSamples(*Generator, Tile);
|
||||
NumNonSolidSamples += TileNonSolid;
|
||||
if (TileNonSolid != 0)
|
||||
{
|
||||
AddError(FString::Printf(
|
||||
TEXT("FALSE ALL-SOLID PROOF: candidate tile (%d,%d,%d) returned true but has "
|
||||
"%d non-solid core-lattice samples."),
|
||||
Tile.Origin.X, Tile.Origin.Y, Tile.Origin.Z, TileNonSolid));
|
||||
}
|
||||
}
|
||||
|
||||
AddInfo(FString::Printf(
|
||||
TEXT("Sealed-solid proof brute force: %d candidate tiles, %d proof skips, %d non-solid "
|
||||
"core-lattice samples."),
|
||||
CandidateTiles.Num(), NumProofSkips, NumNonSolidSamples));
|
||||
TestTrue(TEXT("the fixture exercised at least one sealed-solid proof skip"),
|
||||
NumProofSkips > 0);
|
||||
TestEqual(TEXT("every skipped tile has zero non-solid core-lattice samples"),
|
||||
NumNonSolidSamples, 0);
|
||||
if (FirstProofTile == INDEX_NONE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// A carve in the proven region is the live-edit case that must invalidate the proof. The
|
||||
// diff layer stores the modification in the tile's chunk, so this is also the exact guard used
|
||||
// by the worker before it decides to skip meshing.
|
||||
const FTileSpec& ProofTile = CandidateTiles[FirstProofTile];
|
||||
const FVector CarveCenter(
|
||||
static_cast<float>(ProofTile.Origin.X + 16),
|
||||
static_cast<float>(ProofTile.Origin.Y + 16),
|
||||
static_cast<float>(ProofTile.Origin.Z + 16));
|
||||
FVoxelModification Carve;
|
||||
Carve.Center = CarveCenter;
|
||||
Carve.Radius = 6.0f;
|
||||
Carve.Strength = -64.0f;
|
||||
Carve.Shape = EVoxelBrushShape::Sphere;
|
||||
TestTrue(TEXT("the adversarial carve was registered"),
|
||||
World.DiffLayer->ApplyModification(Carve).Num() > 0);
|
||||
|
||||
TestFalse(TEXT("a player carve kills the sealed-solid proof"),
|
||||
Generator->TryProveSealedSolidTile(
|
||||
ProofTile.Origin, ProofTile.Step, ProofTile.Cells));
|
||||
|
||||
const uint8 DiffGuardDisabled = static_cast<uint8>(
|
||||
EVoxelSealedSolidProofGuard::DiffLayer);
|
||||
TestTrue(TEXT("the deliberately weakened proof still exposes the geometric candidate"),
|
||||
Generator->TryProveSealedSolidTileForTest(
|
||||
ProofTile.Origin, ProofTile.Step, ProofTile.Cells, DiffGuardDisabled));
|
||||
|
||||
const int32 WeakenedNonSolidSamples = CountNonSolidSamples(*Generator, ProofTile);
|
||||
AddInfo(FString::Printf(
|
||||
TEXT("Weakened proof (diff guard disabled): %d non-solid core-lattice samples after the "
|
||||
"carve."),
|
||||
WeakenedNonSolidSamples));
|
||||
TestTrue(TEXT("the brute-force oracle fails the deliberately weakened guard"),
|
||||
WeakenedNonSolidSamples > 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // WITH_DEV_AUTOMATION_TESTS
|
||||
@@ -1600,8 +1600,624 @@ static FORCEINLINE bool VF_AnyDisturbanceCanTouchBox(
|
||||
|| VF_AnyRidgeCanTouchBox(Box, D, Seed);
|
||||
}
|
||||
|
||||
static FORCEINLINE bool VF_SealedSolidProofGuardDisabled(
|
||||
uint8 DisabledGuards, EVoxelSealedSolidProofGuard Guard)
|
||||
{
|
||||
return (DisabledGuards & static_cast<uint8>(Guard)) != 0;
|
||||
}
|
||||
|
||||
static FORCEINLINE bool VF_IsSafeBoundarySealLatticeZ(
|
||||
float WorldZ, float StrateTopZ, float StrateBottomZ,
|
||||
float SealThickness, float BaseDensity)
|
||||
{
|
||||
// ApplyBoundarySeal is a positive internal-density lower bound. At an exact lattice point,
|
||||
// the original inequalities are sufficient: a strictly positive seal factor times a finite,
|
||||
// positive base density makes the final MC density negative. Do not widen the band here;
|
||||
// false Mixed costs time, while the brute-force test below is the authority for this shortcut.
|
||||
if (!VoxelMath::IsFinite(WorldZ)
|
||||
|| !VoxelMath::IsFinite(StrateTopZ)
|
||||
|| !VoxelMath::IsFinite(StrateBottomZ)
|
||||
|| !VoxelMath::IsFinite(SealThickness)
|
||||
|| !VoxelMath::IsFinite(BaseDensity)
|
||||
|| !(StrateTopZ > StrateBottomZ)
|
||||
|| !(SealThickness > 0.0f)
|
||||
|| !(BaseDensity > 0.0f))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const float DistTop = StrateTopZ - WorldZ;
|
||||
const float DistBottom = WorldZ - StrateBottomZ;
|
||||
return (DistTop >= 0.0f && DistTop < SealThickness)
|
||||
|| (DistBottom >= 0.0f && DistBottom < SealThickness);
|
||||
}
|
||||
|
||||
static bool VF_SealedSolidProofPassageInputsAreValid(const UVoxelStrateManager& Manager)
|
||||
{
|
||||
// AnyPassageNearBox is intentionally a cheap bounding-sphere test. Its normal runtime inputs
|
||||
// are generated data, but a malformed bound must fail closed: NaN distance comparisons would
|
||||
// otherwise turn an unknown passage into a false "no passage" result.
|
||||
for (const FVoxelPassage& Passage : Manager.GetPassages())
|
||||
{
|
||||
if (!VoxelMath::IsFinite(Passage.BoundCenter.X)
|
||||
|| !VoxelMath::IsFinite(Passage.BoundCenter.Y)
|
||||
|| !VoxelMath::IsFinite(Passage.BoundCenter.Z)
|
||||
|| !VoxelMath::IsFinite(Passage.BoundRadius)
|
||||
|| Passage.BoundRadius < 0.0f
|
||||
|| !VoxelMath::IsFinite(Passage.Radius)
|
||||
|| Passage.Radius < 0.0f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (const FVector& Point : Passage.ControlPoints)
|
||||
{
|
||||
if (!VoxelMath::IsFinite(Point.X)
|
||||
|| !VoxelMath::IsFinite(Point.Y)
|
||||
|| !VoxelMath::IsFinite(Point.Z))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const float Radius : Passage.ControlRadii)
|
||||
{
|
||||
if (!VoxelMath::IsFinite(Radius) || Radius < 0.0f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const FVoxelPassageLanding* Landings[] = {
|
||||
&Passage.UpperLanding, &Passage.LowerLanding };
|
||||
for (const FVoxelPassageLanding* Landing : Landings)
|
||||
{
|
||||
if (!VoxelMath::IsFinite(Landing->StandingPoint.X)
|
||||
|| !VoxelMath::IsFinite(Landing->StandingPoint.Y)
|
||||
|| !VoxelMath::IsFinite(Landing->StandingPoint.Z)
|
||||
|| !VoxelMath::IsFinite(Landing->DoorPoint.X)
|
||||
|| !VoxelMath::IsFinite(Landing->DoorPoint.Y)
|
||||
|| !VoxelMath::IsFinite(Landing->DoorPoint.Z)
|
||||
|| !VoxelMath::IsFinite(Landing->FloorZ)
|
||||
|| !VoxelMath::IsFinite(Landing->CeilingZ)
|
||||
|| !VoxelMath::IsFinite(Landing->HalfWidth)
|
||||
|| !VoxelMath::IsFinite(Landing->FloorThickness)
|
||||
|| Landing->HalfWidth < 0.0f
|
||||
|| Landing->FloorThickness < 0.0f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (Passage.bWalkableTunnelContract
|
||||
&& (Passage.ControlPoints.Num() < 2
|
||||
|| Passage.ControlRadii.Num() != Passage.ControlPoints.Num()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool VF_TryProveSealedSolidTile(
|
||||
const UVoxelGenerator& Generator, const FIntVector& OriginVoxels,
|
||||
int32 Step, int32 CellsPerAxis, uint8 DisabledGuards)
|
||||
{
|
||||
const UVoxelStrateManager* Manager = Generator.StrateManager;
|
||||
if (Manager == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Step = FMath::Max(1, Step);
|
||||
const int32 CPA = FMath::Clamp(CellsPerAxis, 2, CHUNK_SIZE);
|
||||
|
||||
// Keep the integer arithmetic that defines the MC lattice identical to ClassifyTile, but
|
||||
// reject a caller whose endpoint would overflow before any floor division or FBox creation.
|
||||
const int64 MaxX64 = static_cast<int64>(OriginVoxels.X)
|
||||
+ static_cast<int64>(CPA) * static_cast<int64>(Step);
|
||||
const int64 MaxY64 = static_cast<int64>(OriginVoxels.Y)
|
||||
+ static_cast<int64>(CPA) * static_cast<int64>(Step);
|
||||
const int64 MaxZ64 = static_cast<int64>(OriginVoxels.Z)
|
||||
+ static_cast<int64>(CPA) * static_cast<int64>(Step);
|
||||
if (MaxX64 < MIN_int32 || MaxX64 > MAX_int32
|
||||
|| MaxY64 < MIN_int32 || MaxY64 > MAX_int32
|
||||
|| MaxZ64 < MIN_int32 || MaxZ64 > MAX_int32)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const int32 MinX = OriginVoxels.X;
|
||||
const int32 MinY = OriginVoxels.Y;
|
||||
const int32 MinZ = OriginVoxels.Z;
|
||||
const int32 MaxX = static_cast<int32>(MaxX64);
|
||||
const int32 MaxY = static_cast<int32>(MaxY64);
|
||||
const int32 MaxZ = static_cast<int32>(MaxZ64);
|
||||
const FBox TileVoxelBox(
|
||||
FVector(static_cast<float>(MinX), static_cast<float>(MinY), static_cast<float>(MinZ)),
|
||||
FVector(static_cast<float>(MaxX), static_cast<float>(MaxY), static_cast<float>(MaxZ)));
|
||||
|
||||
auto FloorDivC = [](int32 A, int32 B) -> int32
|
||||
{
|
||||
const int32 Q = A / B;
|
||||
const int32 R = A % B;
|
||||
return (R != 0 && ((R < 0) != (B < 0))) ? Q - 1 : Q;
|
||||
};
|
||||
|
||||
// First pass is deliberately Z-only. Most streaming tiles are neither gap nor seal, so they
|
||||
// leave in this cheap loop without taking a diff lock, scanning passages, or copying params.
|
||||
enum : uint8 { GapRow = 1u, SealRow = 2u };
|
||||
int32 SampleChunkZ[CHUNK_SIZE + 1];
|
||||
uint8 SampleKind[CHUNK_SIZE + 1];
|
||||
bool bSawSeal = false;
|
||||
bool bSawSolidRegion = false;
|
||||
for (int32 G = 0; G <= CPA; ++G)
|
||||
{
|
||||
const int64 Z64 = static_cast<int64>(OriginVoxels.Z)
|
||||
+ static_cast<int64>(G) * static_cast<int64>(Step);
|
||||
const int32 Z = static_cast<int32>(Z64);
|
||||
const int32 ChunkZ = FloorDivC(Z, CHUNK_SIZE);
|
||||
SampleChunkZ[G] = ChunkZ;
|
||||
if (Manager->IsGapChunk(FIntVector(0, 0, ChunkZ)))
|
||||
{
|
||||
SampleKind[G] = GapRow;
|
||||
bSawSolidRegion = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
int32 UnusedTopChunkZ = 0;
|
||||
int32 UnusedBottomChunkZ = 0;
|
||||
if (!Manager->GetStrateChunkZBounds(
|
||||
ChunkZ, UnusedTopChunkZ, UnusedBottomChunkZ))
|
||||
{
|
||||
// Above/below the stack is open air, not a solid gap. A tile that touches it is not
|
||||
// a sealed-solid candidate even if another row happens to be bedrock.
|
||||
return false;
|
||||
}
|
||||
SampleKind[G] = SealRow;
|
||||
bSawSeal = true;
|
||||
bSawSolidRegion = true;
|
||||
}
|
||||
if (!bSawSolidRegion)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (bSawSeal && VoxelDensityAblation::IsBoundarySealOff())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Player edits are the final writer and can carve a tile that was structurally solid. This
|
||||
// is intentionally checked only after the Z pre-pass: ordinary mixed tiles avoid the lock.
|
||||
const int32 MinChunkX = FloorDivC(MinX, CHUNK_SIZE);
|
||||
const int32 MaxChunkX = FloorDivC(MaxX, CHUNK_SIZE);
|
||||
const int32 MinChunkY = FloorDivC(MinY, CHUNK_SIZE);
|
||||
const int32 MaxChunkY = FloorDivC(MaxY, CHUNK_SIZE);
|
||||
const int32 MinChunkZ = FloorDivC(MinZ, CHUNK_SIZE);
|
||||
const int32 MaxChunkZ = FloorDivC(MaxZ, CHUNK_SIZE);
|
||||
if (!VF_SealedSolidProofGuardDisabled(
|
||||
DisabledGuards, EVoxelSealedSolidProofGuard::DiffLayer)
|
||||
&& Generator.DiffLayer != nullptr
|
||||
&& Generator.DiffLayer->HasAnyMods())
|
||||
{
|
||||
if (Generator.DiffLayer->HasAnyModInChunkRange(
|
||||
FIntVector(MinChunkX, MinChunkY, MinChunkZ),
|
||||
FIntVector(MaxChunkX, MaxChunkY, MaxChunkZ)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// PassageCarving's public box guard includes the 4-voxel carve blend pad on top of each
|
||||
// generated passage bound. Landing and structural-post air are also handled conservatively:
|
||||
// if carving is ablated but another passage air writer remains active, reject any passage data
|
||||
// rather than trusting an API whose historical early-out is specific to carving.
|
||||
if (!VF_SealedSolidProofGuardDisabled(
|
||||
DisabledGuards, EVoxelSealedSolidProofGuard::Passages))
|
||||
{
|
||||
const bool bPassageAirWriterActive =
|
||||
!VoxelDensityAblation::IsPassageCarvingOff()
|
||||
|| !VoxelDensityAblation::IsLandingPostsOff()
|
||||
|| !VoxelDensityAblation::IsPassageStructuralPostsOff();
|
||||
if (bPassageAirWriterActive)
|
||||
{
|
||||
if (!VF_SealedSolidProofPassageInputsAreValid(*Manager))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!VoxelDensityAblation::IsPassageCarvingOff())
|
||||
{
|
||||
if (Manager->AnyPassageNearBox(
|
||||
TileVoxelBox.Min, TileVoxelBox.Max))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (Manager->GetPassages().Num() > 0)
|
||||
{
|
||||
// AnyPassageNearBox deliberately returns false when the carve stage is off. The
|
||||
// remaining landing/tunnel posts have no equivalent box API, so this is the
|
||||
// capability-first answer under that development ablation.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The origin landing is the (0,0) spine's current room-shaped air writer. Its box guard is
|
||||
// broader than the exact lattice guard used by ClassifyTile; false Mixed is cheap here.
|
||||
if (!VF_SealedSolidProofGuardDisabled(
|
||||
DisabledGuards, EVoxelSealedSolidProofGuard::OriginLanding)
|
||||
&& (!VoxelDensityAblation::IsOriginSpineOff()
|
||||
|| !VoxelDensityAblation::IsLandingPostsOff())
|
||||
&& Manager->AnyOriginLandingNearBox(TileVoxelBox.Min, TileVoxelBox.Max))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const int64 XYChunkCount =
|
||||
(static_cast<int64>(MaxChunkX) - static_cast<int64>(MinChunkX) + 1)
|
||||
* (static_cast<int64>(MaxChunkY) - static_cast<int64>(MinChunkY) + 1);
|
||||
const int64 ZChunkCount = static_cast<int64>(MaxChunkZ) - static_cast<int64>(MinChunkZ) + 1;
|
||||
// This is a per-tile proof, not a second coarse mesher. A normal LOD0/LOD1 tile touches at
|
||||
// most a handful of chunks. Refuse pathological caller boxes before a nested loop can turn
|
||||
// the cheap path into a large param walk.
|
||||
constexpr int64 MaxProofChunkCoordinates = 65536;
|
||||
if (XYChunkCount <= 0 || ZChunkCount <= 0
|
||||
|| XYChunkCount > MaxProofChunkCoordinates
|
||||
|| XYChunkCount * ZChunkCount > MaxProofChunkCoordinates)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto ResolveSealFields = [&](const FIntVector& ChunkCoord,
|
||||
float& OutTop, float& OutBottom,
|
||||
float& OutThickness, float& OutBase) -> bool
|
||||
{
|
||||
switch (Manager->GetGeneratorTypeForChunk(ChunkCoord))
|
||||
{
|
||||
case ECaveGeneratorType::FlatPlain:
|
||||
case ECaveGeneratorType::CrystalChamber:
|
||||
{
|
||||
const FSlabGenerationParams P = Manager->GetSlabParamsForChunk(ChunkCoord);
|
||||
OutTop = P.StrateTopWorldZ;
|
||||
OutBottom = P.StrateBottomWorldZ;
|
||||
OutThickness = P.BoundarySealThickness;
|
||||
OutBase = P.BaseDensity;
|
||||
return true;
|
||||
}
|
||||
case ECaveGeneratorType::Maze:
|
||||
{
|
||||
const FMazeGenerationParams P = Manager->GetMazeParamsForChunk(ChunkCoord);
|
||||
OutTop = P.StrateTopWorldZ;
|
||||
OutBottom = P.StrateBottomWorldZ;
|
||||
OutThickness = P.BoundarySealThickness;
|
||||
OutBase = P.BaseDensity;
|
||||
return true;
|
||||
}
|
||||
case ECaveGeneratorType::SurfaceWorld:
|
||||
{
|
||||
const FSurfaceGenerationParams P = Manager->GetSurfaceParamsForChunk(ChunkCoord);
|
||||
OutTop = P.StrateTopWorldZ;
|
||||
OutBottom = P.StrateBottomWorldZ;
|
||||
OutThickness = P.BoundarySealThickness;
|
||||
OutBase = P.BaseDensity;
|
||||
return true;
|
||||
}
|
||||
case ECaveGeneratorType::VerticalShafts:
|
||||
{
|
||||
const FVerticalShaftParams P = Manager->GetVerticalShaftParamsForChunk(ChunkCoord);
|
||||
OutTop = P.StrateTopWorldZ;
|
||||
OutBottom = P.StrateBottomWorldZ;
|
||||
OutThickness = P.BoundarySealThickness;
|
||||
OutBase = P.BaseDensity;
|
||||
return true;
|
||||
}
|
||||
case ECaveGeneratorType::FloatingIslands:
|
||||
{
|
||||
const FFloatingIslandParams P = Manager->GetFloatingIslandParamsForChunk(ChunkCoord);
|
||||
OutTop = P.StrateTopWorldZ;
|
||||
OutBottom = P.StrateBottomWorldZ;
|
||||
OutThickness = P.BoundarySealThickness;
|
||||
OutBase = P.BaseDensity;
|
||||
return true;
|
||||
}
|
||||
case ECaveGeneratorType::TunnelNetwork:
|
||||
case ECaveGeneratorType::Underwater:
|
||||
{
|
||||
const FStrateGenerationParams P = Manager->GetGenerationParams(ChunkCoord);
|
||||
OutTop = P.StrateTopWorldZ;
|
||||
OutBottom = P.StrateBottomWorldZ;
|
||||
OutThickness = P.BoundarySealThickness;
|
||||
OutBase = P.BaseDensity;
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
for (int32 ChunkZ = MinChunkZ; ChunkZ <= MaxChunkZ; ++ChunkZ)
|
||||
{
|
||||
bool bThisChunkHasSealRow = false;
|
||||
for (int32 G = 0; G <= CPA; ++G)
|
||||
{
|
||||
if (SampleKind[G] == SealRow && SampleChunkZ[G] == ChunkZ)
|
||||
{
|
||||
bThisChunkHasSealRow = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!bThisChunkHasSealRow)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int32 ChunkY = MinChunkY; ChunkY <= MaxChunkY; ++ChunkY)
|
||||
for (int32 ChunkX = MinChunkX; ChunkX <= MaxChunkX; ++ChunkX)
|
||||
{
|
||||
const FIntVector ChunkCoord(ChunkX, ChunkY, ChunkZ);
|
||||
|
||||
#if WITH_EDITOR
|
||||
// A plain composer override changes the archetype/params, while a region override
|
||||
// changes the lateral field. Both must remain on the ordinary mesher/classifier path.
|
||||
int32 ComposerSeed = 0;
|
||||
ECaveGeneratorType ComposerArchetype = ECaveGeneratorType::TunnelNetwork;
|
||||
FVoxelStrateArchetypeParams ComposerParams;
|
||||
bool bComposerRecipe = false;
|
||||
FVoxelOpStackRecipe ComposerRecipe;
|
||||
if (Manager->GetComposerOverrideForChunk(
|
||||
ChunkCoord, ComposerSeed, ComposerArchetype, ComposerParams,
|
||||
bComposerRecipe, ComposerRecipe))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
FVoxelStrateRegionManifest ComposerRegions;
|
||||
if (Manager->GetComposerRegionOverrideForChunk(ChunkCoord, ComposerRegions))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Cooked seasons can materialise a custom recipe for a chunk. The cheap proof only
|
||||
// knows the native structural seal contract, so selected recipes fail closed.
|
||||
int32 RecipeSeed = 0;
|
||||
ECaveGeneratorType RecipeArchetype = ECaveGeneratorType::TunnelNetwork;
|
||||
FVoxelStrateArchetypeParams RecipeParams;
|
||||
FVoxelOpStackRecipe Recipe;
|
||||
if (Manager->GetRecipeForChunk(
|
||||
ChunkCoord, RecipeSeed, RecipeArchetype, RecipeParams, Recipe))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
float Top = 0.0f;
|
||||
float Bottom = 0.0f;
|
||||
float Thickness = 0.0f;
|
||||
float BaseDensity = 0.0f;
|
||||
if (!ResolveSealFields(ChunkCoord, Top, Bottom, Thickness, BaseDensity))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!VF_SealedSolidProofGuardDisabled(
|
||||
DisabledGuards, EVoxelSealedSolidProofGuard::DisturbanceChasms)
|
||||
&& !VoxelDensityAblation::IsDisturbancesOff())
|
||||
{
|
||||
const FStrateDisturbanceParams D =
|
||||
Manager->GetDisturbanceParamsForChunk(ChunkCoord);
|
||||
if (VF_AnyChasmCanTouchBox(TileVoxelBox, D, static_cast<uint32>(Generator.Seed)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (int32 G = 0; G <= CPA; ++G)
|
||||
{
|
||||
if (SampleKind[G] != SealRow || SampleChunkZ[G] != ChunkZ)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const float Z = static_cast<float>(
|
||||
static_cast<int64>(OriginVoxels.Z)
|
||||
+ static_cast<int64>(G) * static_cast<int64>(Step));
|
||||
if (!VF_IsSafeBoundarySealLatticeZ(
|
||||
Z, Top, Bottom, Thickness, BaseDensity))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool VF_TryProveOutOfLayoutAirTile(
|
||||
const UVoxelGenerator& Generator, const FIntVector& OriginVoxels,
|
||||
int32 Step, int32 CellsPerAxis)
|
||||
{
|
||||
const UVoxelStrateManager* Manager = Generator.StrateManager;
|
||||
if (Manager == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Step = FMath::Max(1, Step);
|
||||
const int32 CPA = FMath::Clamp(CellsPerAxis, 2, CHUNK_SIZE);
|
||||
|
||||
const int64 MaxX64 = static_cast<int64>(OriginVoxels.X)
|
||||
+ static_cast<int64>(CPA) * static_cast<int64>(Step);
|
||||
const int64 MaxY64 = static_cast<int64>(OriginVoxels.Y)
|
||||
+ static_cast<int64>(CPA) * static_cast<int64>(Step);
|
||||
const int64 MaxZ64 = static_cast<int64>(OriginVoxels.Z)
|
||||
+ static_cast<int64>(CPA) * static_cast<int64>(Step);
|
||||
if (MaxX64 < MIN_int32 || MaxX64 > MAX_int32
|
||||
|| MaxY64 < MIN_int32 || MaxY64 > MAX_int32
|
||||
|| MaxZ64 < MIN_int32 || MaxZ64 > MAX_int32)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const int32 MinX = OriginVoxels.X;
|
||||
const int32 MinY = OriginVoxels.Y;
|
||||
const int32 MinZ = OriginVoxels.Z;
|
||||
const int32 MaxX = static_cast<int32>(MaxX64);
|
||||
const int32 MaxY = static_cast<int32>(MaxY64);
|
||||
const int32 MaxZ = static_cast<int32>(MaxZ64);
|
||||
const FBox TileVoxelBox(
|
||||
FVector(static_cast<float>(MinX), static_cast<float>(MinY), static_cast<float>(MinZ)),
|
||||
FVector(static_cast<float>(MaxX), static_cast<float>(MaxY), static_cast<float>(MaxZ)));
|
||||
|
||||
auto FloorDivC = [](int32 A, int32 B) -> int32
|
||||
{
|
||||
const int32 Q = A / B;
|
||||
const int32 R = A % B;
|
||||
return (R != 0 && ((R < 0) != (B < 0))) ? Q - 1 : Q;
|
||||
};
|
||||
|
||||
// All core lattice rows must be outside the layout. A gap or a real strate row means that
|
||||
// this is no longer the constant-air case, even when another row is open air.
|
||||
for (int32 G = 0; G <= CPA; ++G)
|
||||
{
|
||||
const int64 Z64 = static_cast<int64>(OriginVoxels.Z)
|
||||
+ static_cast<int64>(G) * static_cast<int64>(Step);
|
||||
const int32 ChunkZ = FloorDivC(static_cast<int32>(Z64), CHUNK_SIZE);
|
||||
if (Manager->IsGapChunk(FIntVector(0, 0, ChunkZ)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int32 UnusedTopChunkZ = 0;
|
||||
int32 UnusedBottomChunkZ = 0;
|
||||
if (Manager->GetStrateChunkZBounds(
|
||||
ChunkZ, UnusedTopChunkZ, UnusedBottomChunkZ))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const int32 MinChunkX = FloorDivC(MinX, CHUNK_SIZE);
|
||||
const int32 MaxChunkX = FloorDivC(MaxX, CHUNK_SIZE);
|
||||
const int32 MinChunkY = FloorDivC(MinY, CHUNK_SIZE);
|
||||
const int32 MaxChunkY = FloorDivC(MaxY, CHUNK_SIZE);
|
||||
const int32 MinChunkZ = FloorDivC(MinZ, CHUNK_SIZE);
|
||||
const int32 MaxChunkZ = FloorDivC(MaxZ, CHUNK_SIZE);
|
||||
if (Generator.DiffLayer != nullptr
|
||||
&& Generator.DiffLayer->HasAnyMods()
|
||||
&& Generator.DiffLayer->HasAnyModInChunkRange(
|
||||
FIntVector(MinChunkX, MinChunkY, MinChunkZ),
|
||||
FIntVector(MaxChunkX, MaxChunkY, MaxChunkZ)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// The final XY pass can add rock to open air. Radius == 0 (and other non-positive inputs) is
|
||||
// the documented no-op, so only validate and inspect the band when that writer is live.
|
||||
if (!VoxelDensityAblation::IsXYEdgeSealOff()
|
||||
&& Generator.WorldRadiusVoxels > 0.0f
|
||||
&& Generator.EdgeSealThickness > 0.0f)
|
||||
{
|
||||
if (!VF_IsValidXYEdgeSealProofInput(
|
||||
TileVoxelBox, Generator.WorldRadiusVoxels,
|
||||
Generator.EdgeSealThickness, 1.0f))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use double arithmetic here: the production helper is float-based, while this proof
|
||||
// must not turn a large finite coordinate into an overflowed squared-distance false
|
||||
// negative. Equality is safe because the production inner test is <= as well.
|
||||
const double FarthestX = FMath::Max(
|
||||
FMath::Abs(static_cast<double>(TileVoxelBox.Min.X)),
|
||||
FMath::Abs(static_cast<double>(TileVoxelBox.Max.X)));
|
||||
const double FarthestY = FMath::Max(
|
||||
FMath::Abs(static_cast<double>(TileVoxelBox.Min.Y)),
|
||||
FMath::Abs(static_cast<double>(TileVoxelBox.Max.Y)));
|
||||
const double InnerRadius = static_cast<double>(Generator.WorldRadiusVoxels)
|
||||
- static_cast<double>(Generator.EdgeSealThickness);
|
||||
if (!(InnerRadius > 0.0)
|
||||
|| FarthestX * FarthestX + FarthestY * FarthestY
|
||||
> InnerRadius * InnerRadius)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (Manager->GetPassages().Num() > 0)
|
||||
{
|
||||
if (!VF_SealedSolidProofPassageInputsAreValid(*Manager))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Passage carving is air-monotone and therefore cannot invalidate an AllAir proof. Only
|
||||
// the solid floor/support writers need a guard here. Use the same exact lattice floor
|
||||
// predicate as ClassifyTile: its padded AABBs stay cheap, but do not reject an open-air
|
||||
// tile merely because a harmless carve bound passes near it.
|
||||
const bool bPassageFloorWriterActive =
|
||||
!VoxelDensityAblation::IsPassageStructuralPostsOff()
|
||||
|| !VoxelDensityAblation::IsNativeFloorOff()
|
||||
|| !VoxelDensityAblation::IsLandingPostsOff();
|
||||
if (bPassageFloorWriterActive
|
||||
&& Manager->AnyPassageLandingFloorNearLattice(
|
||||
TileVoxelBox, OriginVoxels, Step))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Origin rooms only carve open air, but their landing floors reassert solid. The manager's
|
||||
// floor box includes every per-strate room; this is intentionally broader than the exact
|
||||
// lattice query used by ClassifyTile because a false Mixed is cheaper than a dropped floor.
|
||||
if (!VoxelDensityAblation::IsOriginSpineOff()
|
||||
|| !VoxelDensityAblation::IsLandingPostsOff())
|
||||
{
|
||||
if (!VoxelMath::IsFinite(Generator.OriginSpineRadius)
|
||||
|| Generator.OriginSpineRadius <= 0.0f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (Manager->AnyOriginLandingFloorNearLattice(
|
||||
TileVoxelBox, OriginVoxels, Step))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!VoxelDensityAblation::IsDisturbancesOff())
|
||||
{
|
||||
const int64 XYChunkCount =
|
||||
(static_cast<int64>(MaxChunkX) - static_cast<int64>(MinChunkX) + 1)
|
||||
* (static_cast<int64>(MaxChunkY) - static_cast<int64>(MinChunkY) + 1);
|
||||
const int64 ZChunkCount =
|
||||
static_cast<int64>(MaxChunkZ) - static_cast<int64>(MinChunkZ) + 1;
|
||||
constexpr int64 MaxProofChunkCoordinates = 65536;
|
||||
if (XYChunkCount <= 0 || ZChunkCount <= 0
|
||||
|| XYChunkCount > MaxProofChunkCoordinates
|
||||
|| XYChunkCount * ZChunkCount > MaxProofChunkCoordinates)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Out-of-layout chunks normally return disabled disturbance params. Still ask the exact
|
||||
// bridge/ridge predicates for every touched chunk so a future out-of-layout decoration
|
||||
// cannot silently turn this air proof into an invisible solid patch.
|
||||
for (int32 ChunkZ = MinChunkZ; ChunkZ <= MaxChunkZ; ++ChunkZ)
|
||||
for (int32 ChunkY = MinChunkY; ChunkY <= MaxChunkY; ++ChunkY)
|
||||
for (int32 ChunkX = MinChunkX; ChunkX <= MaxChunkX; ++ChunkX)
|
||||
{
|
||||
const FStrateDisturbanceParams D =
|
||||
Manager->GetDisturbanceParamsForChunk(FIntVector(ChunkX, ChunkY, ChunkZ));
|
||||
if (VF_AnyBridgeCanTouchBox(TileVoxelBox, D, static_cast<uint32>(Generator.Seed))
|
||||
|| VF_AnyRidgeCanTouchBox(TileVoxelBox, D, static_cast<uint32>(Generator.Seed)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// LE MAPPING « ARCHÉTYPE → PILE D'OPÉRATEURS » — UNE SEULE DÉFINITION
|
||||
// LE MAPPING « ARCHÉTYPE → PILE D’OPÉRATEURS » — UNE SEULE DÉFINITION
|
||||
//=============================================================================
|
||||
// ⚠️ EXTRAIT DE `GetDensityAt` PARCE QUE `ClassifyTile` EN A BESOIN AUSSI, ET QU'UNE DEUXIÈME COPIE
|
||||
// SERAIT LA PIRE FORME DE BUG DISPONIBLE ICI.
|
||||
@@ -5713,6 +6329,29 @@ EVoxelTileClass UVoxelGenerator::ClassifyTile(const FIntVector& OriginVoxels,
|
||||
return ClassifyTile(OriginVoxels, Step, CellsPerAxis, nullptr);
|
||||
}
|
||||
|
||||
bool UVoxelGenerator::TryProveSealedSolidTile(
|
||||
const FIntVector& OriginVoxels, int32 Step, int32 CellsPerAxis) const
|
||||
{
|
||||
return VF_TryProveSealedSolidTile(
|
||||
*this, OriginVoxels, Step, CellsPerAxis, /*DisabledGuards=*/0);
|
||||
}
|
||||
|
||||
bool UVoxelGenerator::TryProveOutOfLayoutAirTile(
|
||||
const FIntVector& OriginVoxels, int32 Step, int32 CellsPerAxis) const
|
||||
{
|
||||
return VF_TryProveOutOfLayoutAirTile(*this, OriginVoxels, Step, CellsPerAxis);
|
||||
}
|
||||
|
||||
#if WITH_DEV_AUTOMATION_TESTS
|
||||
bool UVoxelGenerator::TryProveSealedSolidTileForTest(
|
||||
const FIntVector& OriginVoxels, int32 Step, int32 CellsPerAxis,
|
||||
uint8 DisabledGuards) const
|
||||
{
|
||||
return VF_TryProveSealedSolidTile(
|
||||
*this, OriginVoxels, Step, CellsPerAxis, DisabledGuards);
|
||||
}
|
||||
#endif
|
||||
|
||||
EVoxelTileClass UVoxelGenerator::ClassifyTile(const FIntVector& OriginVoxels, int32 Step,
|
||||
int32 CellsPerAxis,
|
||||
FVoxelTileClassificationStats* OutStats) const
|
||||
|
||||
@@ -260,11 +260,12 @@ void RecordTile(const FTileSample& Sample)
|
||||
GState->Samples.Add(Sample);
|
||||
++GState->TileRecordsWritten;
|
||||
WriteEventLocked(*GState, TEXT("tile"), FString::Printf(
|
||||
TEXT("\"tile\":[%d,%d,%d],\"level\":%d,\"verdict\":%d,\"empty\":%d,\"triangles\":%d,"
|
||||
TEXT("\"tile\":[%d,%d,%d],\"level\":%d,\"verdict\":%d,\"proof\":%d,\"empty\":%d,\"triangles\":%d,"
|
||||
"\"request_to_apply_s\":%s,\"queue_wait_s\":%s,\"worker_queue_s\":%s,\"result_queue_s\":%s,"
|
||||
"\"generation_s\":%s,\"classify_s\":%s,\"mesh_s\":%s,\"stream_s\":%s,\"apply_s\":%s"),
|
||||
Sample.TileX, Sample.TileY, Sample.TileZ,
|
||||
Sample.Level, Sample.Verdict, Sample.bEmpty ? 1 : 0, Sample.Triangles,
|
||||
Sample.Level, Sample.Verdict, Sample.bSealedSolidProof ? 1 : 0,
|
||||
Sample.bEmpty ? 1 : 0, Sample.Triangles,
|
||||
*Number(Sample.RequestToApplySeconds), *Number(Sample.QueueWaitSeconds),
|
||||
*Number(Sample.WorkerQueueSeconds), *Number(Sample.ResultQueueSeconds),
|
||||
*Number(Sample.GenerationSeconds), *Number(Sample.ClassifySeconds),
|
||||
|
||||
@@ -97,6 +97,15 @@ namespace
|
||||
GVoxelForgeOuterClassifierMode,
|
||||
TEXT("Outer tile classifier mode: 0 off, 1 discarded exact validation."));
|
||||
|
||||
// Cheap structural proofs for LOD tiles whose exact core lattice lies entirely in a gap/seal
|
||||
// (AllSolid) or constant out-of-layout air (AllAir). They are independent of the discarded
|
||||
// full classifier and default on; 0 is retained for per-tile identity/A-B traces.
|
||||
int32 GVoxelForgeSealedSolidProof = 1;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeSealedSolidProof(
|
||||
TEXT("voxel.SealedSolidProof"),
|
||||
GVoxelForgeSealedSolidProof,
|
||||
TEXT("Skip meshing only when an exact gap/seal or constant-air proof holds: 0 off, 1 on."));
|
||||
|
||||
// Startup command-line cvars can arrive before the game world has spawned AVoxelWorld (or
|
||||
// before its first tiles have become visible). Keep the diagnostic edit pending until Tick has
|
||||
// a loaded tile; otherwise a startup measurement silently becomes a no-op or edits the wrong
|
||||
@@ -104,6 +113,7 @@ namespace
|
||||
int32 GVoxelForgePendingTestModification = 0;
|
||||
float GVoxelForgePendingModificationRadius = 3.0f;
|
||||
float GVoxelForgePendingModificationStrength = 10.0f;
|
||||
bool GVoxelForgeTestModificationLoadedProofTarget = false;
|
||||
bool GVoxelForgeTestModificationCommandLineConsumed = false;
|
||||
bool GVoxelForgeStreamingBudgetReported = false;
|
||||
bool GVoxelForgeGenerationCapHitReported = false;
|
||||
@@ -1105,6 +1115,9 @@ void AVoxelWorld::BeginPlay()
|
||||
TotalObsoleteWorkerTasks.store(0, std::memory_order_relaxed);
|
||||
TotalValidationDensityCalls.store(0, std::memory_order_relaxed);
|
||||
TotalMesherDensityCalls.store(0, std::memory_order_relaxed);
|
||||
TotalSealedSolidProofCandidates.store(0, std::memory_order_relaxed);
|
||||
TotalSealedSolidProofSkips.store(0, std::memory_order_relaxed);
|
||||
TotalOutOfLayoutAirProofSkips.store(0, std::memory_order_relaxed);
|
||||
AppliedTileCount = 0;
|
||||
AppliedVisibleTileCount = 0;
|
||||
AppliedTriangleCount = 0;
|
||||
@@ -1133,6 +1146,7 @@ void AVoxelWorld::BeginPlay()
|
||||
int32 CommandLineProfileTileGeneration = GVoxelForgeProfileTileGeneration;
|
||||
int32 CommandLineUseBlockEarlyOut = GVoxelForgeUseBlockEarlyOut;
|
||||
int32 CommandLineOuterClassifierMode = GVoxelForgeOuterClassifierMode;
|
||||
int32 CommandLineSealedSolidProof = GVoxelForgeSealedSolidProof;
|
||||
int32 CommandLineSampleStacks = GVoxelForgeSampleStacks;
|
||||
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.ProfileDensity="), CommandLineProfile))
|
||||
{
|
||||
@@ -1161,6 +1175,10 @@ void AVoxelWorld::BeginPlay()
|
||||
{
|
||||
GVoxelForgeOuterClassifierMode = CommandLineOuterClassifierMode;
|
||||
}
|
||||
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.SealedSolidProof="), CommandLineSealedSolidProof))
|
||||
{
|
||||
GVoxelForgeSealedSolidProof = CommandLineSealedSolidProof;
|
||||
}
|
||||
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.SampleStacks="), CommandLineSampleStacks))
|
||||
{
|
||||
GVoxelForgeSampleStacks = CommandLineSampleStacks;
|
||||
@@ -1168,6 +1186,7 @@ void AVoxelWorld::BeginPlay()
|
||||
GVoxelForgeProfileTileGeneration = GVoxelForgeProfileTileGeneration != 0 ? 1 : 0;
|
||||
GVoxelForgeUseBlockEarlyOut = GVoxelForgeUseBlockEarlyOut != 0 ? 1 : 0;
|
||||
GVoxelForgeOuterClassifierMode = FMath::Clamp(GVoxelForgeOuterClassifierMode, 0, 1);
|
||||
GVoxelForgeSealedSolidProof = GVoxelForgeSealedSolidProof != 0 ? 1 : 0;
|
||||
GVoxelForgeSampleStacks = FMath::Clamp(
|
||||
GVoxelForgeSampleStacks,
|
||||
0,
|
||||
@@ -1281,8 +1300,9 @@ void AVoxelWorld::BeginPlay()
|
||||
Mesher->bUseBlockEarlyOut = GVoxelForgeUseBlockEarlyOut != 0;
|
||||
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeClassifierConfig] outer_mode=%d nested_block_early_out=%d"),
|
||||
GVoxelForgeOuterClassifierMode, Mesher->bUseBlockEarlyOut ? 1 : 0);
|
||||
TEXT("[VoxelForgeClassifierConfig] outer_mode=%d sealed_solid_proof=%d nested_block_early_out=%d"),
|
||||
GVoxelForgeOuterClassifierMode, GVoxelForgeSealedSolidProof,
|
||||
Mesher->bUseBlockEarlyOut ? 1 : 0);
|
||||
|
||||
VoxelForgeStartupTrace::RecordEvent(TEXT("generator_mesher_constructed"), FString::Printf(
|
||||
TEXT("\"generator\":1,\"mesher\":1,\"generate_skirts\":%d,\"full_res_clip_levels\":%d,\"coarse_tile_cells\":%d"),
|
||||
@@ -1377,6 +1397,9 @@ void AVoxelWorld::ConfigureHeadlessStreamingTest()
|
||||
{
|
||||
bHeadlessStreamingTestMovement = false;
|
||||
bHeadlessStreamingTestExitRequested = false;
|
||||
bStartupTraceThroughCrossing = false;
|
||||
bHeadlessStreamingTestCenterOverride = false;
|
||||
HeadlessStreamingTestCenterVoxel = FVector::ZeroVector;
|
||||
HeadlessStreamingTestMoveAttempts = 0;
|
||||
HeadlessStreamingTestDistanceCm = 0.0;
|
||||
HeadlessStreamingTestSpeedCmPerSecond = 0.0f;
|
||||
@@ -1472,6 +1495,33 @@ void AVoxelWorld::ConfigureHeadlessStreamingTest()
|
||||
HeadlessStrateCrossingTestRequestedPassageIndex);
|
||||
}
|
||||
|
||||
int32 TraceThroughCrossingValue = 0;
|
||||
if (FParse::Value(
|
||||
CommandLine,
|
||||
TEXT("voxel.StartupTraceThroughCrossing="),
|
||||
TraceThroughCrossingValue))
|
||||
{
|
||||
bStartupTraceThroughCrossing = TraceThroughCrossingValue != 0;
|
||||
}
|
||||
|
||||
float CenterX = 0.0f;
|
||||
float CenterY = 0.0f;
|
||||
float CenterZ = 0.0f;
|
||||
const bool bCenterX = FParse::Value(CommandLine, TEXT("voxel.StreamingTestCenterX="), CenterX);
|
||||
const bool bCenterY = FParse::Value(CommandLine, TEXT("voxel.StreamingTestCenterY="), CenterY);
|
||||
const bool bCenterZ = FParse::Value(CommandLine, TEXT("voxel.StreamingTestCenterZ="), CenterZ);
|
||||
if (bCenterX && bCenterY && bCenterZ
|
||||
&& VoxelMath::IsFinite(CenterX)
|
||||
&& VoxelMath::IsFinite(CenterY)
|
||||
&& VoxelMath::IsFinite(CenterZ))
|
||||
{
|
||||
bHeadlessStreamingTestCenterOverride = true;
|
||||
HeadlessStreamingTestCenterVoxel = FVector(CenterX, CenterY, CenterZ);
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeStreamingTest] center_override=1 local_vox=(%.2f,%.2f,%.2f)"),
|
||||
CenterX, CenterY, CenterZ);
|
||||
}
|
||||
|
||||
if (bMoveRequested && !bHeadlessStrateCrossingTest)
|
||||
{
|
||||
FString DirectionText;
|
||||
@@ -2000,6 +2050,11 @@ void AVoxelWorld::MaybeFinishHeadlessStrateCrossingTest()
|
||||
HeadlessStrateCrossingTestLastActualPosition.X,
|
||||
HeadlessStrateCrossingTestLastActualPosition.Y,
|
||||
HeadlessStrateCrossingTestLastActualPosition.Z);
|
||||
if (bStartupTraceThroughCrossing && VoxelForgeStartupTrace::IsActive())
|
||||
{
|
||||
VoxelForgeStartupTrace::Finish(
|
||||
bPassed ? TEXT("strate_crossing_passed") : TEXT("strate_crossing_failed"));
|
||||
}
|
||||
FPlatformMisc::RequestExitWithStatus(
|
||||
false, bPassed ? 0 : 1, TEXT("VoxelForge strate crossing test complete"));
|
||||
}
|
||||
@@ -2110,7 +2165,10 @@ void AVoxelWorld::Tick(float DeltaTime)
|
||||
PeakObservedPawnSpeedCmPerSecond, PlayerHeading.Size2D());
|
||||
UpdatePawnCollisionGate(PlayerPawn, PlayerLastPos, DeltaTime);
|
||||
ObserveHeadlessStrateCrossingTest(PlayerLastPos, PlayerPawn);
|
||||
UpdateChunksAroundPosition(PlayerLastPos, PlayerPawn, PlayerHeading);
|
||||
const FVector StreamingCenterPosition = bHeadlessStreamingTestCenterOverride
|
||||
? LocalVoxelToWorld(HeadlessStreamingTestCenterVoxel)
|
||||
: PlayerLastPos;
|
||||
UpdateChunksAroundPosition(StreamingCenterPosition, PlayerPawn, PlayerHeading);
|
||||
if (AtmosphereManager)
|
||||
{
|
||||
AtmosphereManager->UpdateForPlayer(PlayerLastPos);
|
||||
@@ -2165,10 +2223,20 @@ void AVoxelWorld::Tick(float DeltaTime)
|
||||
{
|
||||
GVoxelForgePendingModificationRadius = 3.0f;
|
||||
GVoxelForgePendingModificationStrength = 10.0f;
|
||||
FString ModificationTarget;
|
||||
if (FParse::Value(
|
||||
*CommandLine,
|
||||
TEXT("voxel.TestModificationTarget="),
|
||||
ModificationTarget)
|
||||
&& ModificationTarget.Equals(TEXT("loaded-proof"), ESearchCase::IgnoreCase))
|
||||
{
|
||||
GVoxelForgeTestModificationLoadedProofTarget = true;
|
||||
}
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeTestModification] queued radius=%.2f strength=%.2f"),
|
||||
TEXT("[VoxelForgeTestModification] queued radius=%.2f strength=%.2f target=%s"),
|
||||
GVoxelForgePendingModificationRadius,
|
||||
GVoxelForgePendingModificationStrength);
|
||||
GVoxelForgePendingModificationStrength,
|
||||
GVoxelForgeTestModificationLoadedProofTarget ? TEXT("loaded-proof") : TEXT("player"));
|
||||
GVoxelForgePendingTestModification = 1;
|
||||
}
|
||||
}
|
||||
@@ -2183,10 +2251,16 @@ void AVoxelWorld::Tick(float DeltaTime)
|
||||
&& PendingTiles.Num() == 0
|
||||
&& bStartupTraceDesiredRecorded
|
||||
&& PendingCollisionCooks.Num() == 0
|
||||
&& (!bStartupTraceThroughCrossing
|
||||
|| !bHeadlessStrateCrossingTest
|
||||
|| bHeadlessStrateCrossingTestPassed
|
||||
|| bHeadlessStrateCrossingTestFailed)
|
||||
// The desired set being applied is not the player-ready point: the feet tile must have
|
||||
// received its completed LOD0 RMC body first. If no pawn exists, there is no gate to wait
|
||||
// for and the headless trace keeps its old completion behavior.
|
||||
&& (!bHasPlayer || IsPlayerSupportCollisionReady(PlayerPawn, PlayerLastPos)))
|
||||
&& (!bHasPlayer
|
||||
|| bHeadlessStreamingTestCenterOverride
|
||||
|| IsPlayerSupportCollisionReady(PlayerPawn, PlayerLastPos)))
|
||||
{
|
||||
TMap<int32, int32> SteadyLevelCounts;
|
||||
TMap<int32, FIntVector> SteadyLevelMins;
|
||||
@@ -2244,22 +2318,75 @@ void AVoxelWorld::Tick(float DeltaTime)
|
||||
// No pawn is a distinct state from a pawn at (0,0,0); keep the diagnostic fallback for
|
||||
// headless/editor use without making the world-origin pawn skip streaming.
|
||||
FVector ModificationPosition = bHasPlayer ? PlayerLastPos : GetActorLocation();
|
||||
const FVoxelTileKey ModificationCenterTile(
|
||||
FVoxelTileKey ModificationCenterTile(
|
||||
WorldToChunkCoord(WorldToLocalCm(ModificationPosition)), 0);
|
||||
bool bModificationTargetSelected = false;
|
||||
if (GVoxelForgePendingTestModification != 0
|
||||
&& GVoxelForgeTestModificationLoadedProofTarget
|
||||
&& Generator)
|
||||
{
|
||||
// The diagnostic target is deliberately selected from the applied set. That makes the
|
||||
// edit exercise the normal loaded-tile synchronous remesh, while the proof query here is
|
||||
// the same public predicate used by GenerateTileResult before the diff exists.
|
||||
for (const FVoxelTileKey& CandidateTile : LoadedTiles)
|
||||
{
|
||||
if (CandidateTile.Level != 0
|
||||
|| PendingTiles.Contains(CandidateTile))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const FIntVector CandidateOrigin(
|
||||
CandidateTile.Coord.X * CHUNK_SIZE,
|
||||
CandidateTile.Coord.Y * CHUNK_SIZE,
|
||||
CandidateTile.Coord.Z * CHUNK_SIZE);
|
||||
if (!Generator->TryProveSealedSolidTile(CandidateOrigin, 1, CHUNK_SIZE))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const FIntVector CandidateCenter(
|
||||
CandidateOrigin.X + CHUNK_SIZE / 2,
|
||||
CandidateOrigin.Y + CHUNK_SIZE / 2,
|
||||
CandidateOrigin.Z + CHUNK_SIZE / 2);
|
||||
ModificationPosition = LocalVoxelToWorld(FVector(CandidateCenter));
|
||||
ModificationCenterTile = CandidateTile;
|
||||
bModificationTargetSelected = true;
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeTestModification] player_mine_target tile=(%d,%d,%d) "
|
||||
"local_center=(%d,%d,%d) before_proof=1"),
|
||||
CandidateTile.Coord.X, CandidateTile.Coord.Y, CandidateTile.Coord.Z,
|
||||
CandidateCenter.X, CandidateCenter.Y, CandidateCenter.Z);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (GVoxelForgePendingTestModification != 0
|
||||
&& DiffLayer
|
||||
&& (!GVoxelForgeTestModificationLoadedProofTarget || bModificationTargetSelected)
|
||||
&& LoadedTiles.Contains(ModificationCenterTile)
|
||||
&& !PendingTiles.Contains(ModificationCenterTile))
|
||||
{
|
||||
GVoxelForgePendingTestModification = 0;
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeTestModification] applying position=(%.2f,%.2f,%.2f) radius=%.2f strength=%.2f"),
|
||||
TEXT("[VoxelForgeTestModification] applying player_mine=1 tile=(%d,%d,%d) "
|
||||
"position=(%.2f,%.2f,%.2f) radius=%.2f strength=%.2f"),
|
||||
ModificationCenterTile.Coord.X, ModificationCenterTile.Coord.Y, ModificationCenterTile.Coord.Z,
|
||||
ModificationPosition.X, ModificationPosition.Y, ModificationPosition.Z,
|
||||
GVoxelForgePendingModificationRadius, GVoxelForgePendingModificationStrength);
|
||||
CarveAtPosition(
|
||||
ModificationPosition,
|
||||
GVoxelForgePendingModificationRadius,
|
||||
GVoxelForgePendingModificationStrength);
|
||||
const FIntVector ModifiedOrigin(
|
||||
ModificationCenterTile.Coord.X * CHUNK_SIZE,
|
||||
ModificationCenterTile.Coord.Y * CHUNK_SIZE,
|
||||
ModificationCenterTile.Coord.Z * CHUNK_SIZE);
|
||||
const bool bProofSurvivedCarve = Generator
|
||||
&& Generator->TryProveSealedSolidTile(ModifiedOrigin, 1, CHUNK_SIZE);
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeTestModification] player_mine_complete tile=(%d,%d,%d) "
|
||||
"after_proof=%d"),
|
||||
ModificationCenterTile.Coord.X, ModificationCenterTile.Coord.Y,
|
||||
ModificationCenterTile.Coord.Z, bProofSurvivedCarve ? 1 : 0);
|
||||
}
|
||||
ProcessUnloadQueue();
|
||||
MaybeFinishHeadlessStreamingTest();
|
||||
@@ -2849,6 +2976,12 @@ void AVoxelWorld::LogStreamingLatencySummary() const
|
||||
TotalWorkerGenerationCycles.load(std::memory_order_relaxed);
|
||||
const uint64 ObsoleteWorkerCycles =
|
||||
TotalObsoleteWorkerCycles.load(std::memory_order_relaxed);
|
||||
const uint64 SealedSolidProofCandidates =
|
||||
TotalSealedSolidProofCandidates.load(std::memory_order_relaxed);
|
||||
const uint64 SealedSolidProofSkips =
|
||||
TotalSealedSolidProofSkips.load(std::memory_order_relaxed);
|
||||
const uint64 OutOfLayoutAirProofSkips =
|
||||
TotalOutOfLayoutAirProofSkips.load(std::memory_order_relaxed);
|
||||
const bool bDiagnosticsEnabled = GVoxelForgeProfileTileGeneration != 0
|
||||
|| VoxelForgeStartupTrace::IsActive()
|
||||
|| VoxelDensityProfile::GetMode() != VoxelDensityProfile::EMode::Disabled;
|
||||
@@ -2863,6 +2996,8 @@ void AVoxelWorld::LogStreamingLatencySummary() const
|
||||
"applied_tiles=%llu applied_visible_tiles=%llu applied_triangles=%llu "
|
||||
"worker_generation_tasks=%llu worker_generation_sum_s=%.6f "
|
||||
"validation_density_calls=%llu mesher_density_calls=%llu "
|
||||
"sealed_solid_proof_candidates=%llu sealed_solid_proof_skips=%llu "
|
||||
"out_of_layout_air_proof_skips=%llu "
|
||||
"obsolete_worker_tasks=%llu obsolete_worker_sum_s=%.6f "
|
||||
"outer_classifier_verdicts=%s applied_level_summary=%s band_level_summary=%s"),
|
||||
bDiagnosticsEnabled ? TEXT("profiled") : TEXT("clean"),
|
||||
@@ -2881,6 +3016,9 @@ void AVoxelWorld::LogStreamingLatencySummary() const
|
||||
FPlatformTime::ToSeconds64(WorkerGenerationCycles),
|
||||
static_cast<unsigned long long>(TotalValidationDensityCalls.load(std::memory_order_relaxed)),
|
||||
static_cast<unsigned long long>(TotalMesherDensityCalls.load(std::memory_order_relaxed)),
|
||||
static_cast<unsigned long long>(SealedSolidProofCandidates),
|
||||
static_cast<unsigned long long>(SealedSolidProofSkips),
|
||||
static_cast<unsigned long long>(OutOfLayoutAirProofSkips),
|
||||
static_cast<unsigned long long>(TotalObsoleteWorkerTasks.load(std::memory_order_relaxed)),
|
||||
FPlatformTime::ToSeconds64(ObsoleteWorkerCycles),
|
||||
*ClassifierVerdictSummary,
|
||||
@@ -3191,6 +3329,7 @@ bool AVoxelWorld::ApplyTileResult(FChunkResult& Result)
|
||||
Sample.TileZ = Result.Tile.Coord.Z;
|
||||
Sample.Level = Result.Tile.Level;
|
||||
Sample.Verdict = Result.ClassifyVerdict;
|
||||
Sample.bSealedSolidProof = Result.bSealedSolidProof;
|
||||
Sample.bEmpty = bResultEmpty;
|
||||
Sample.Triangles = Result.NumTriangles;
|
||||
Sample.RequestToApplySeconds = RequestToApply;
|
||||
@@ -4523,7 +4662,7 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeTileProfile] tile=(%d,%d,%d) level=%d step=%d cells=%d sheet=%d aborted=%d empty=%d triangles=%d "
|
||||
"band=(%d,%d) "
|
||||
"verdict=%d classify=%.6f mesh=%.6f streams=%.6f seconds=%.6f "
|
||||
"verdict=%d proof=%d air_proof=%d classify=%.6f mesh=%.6f streams=%.6f seconds=%.6f "
|
||||
"cache_build=%.6f evaluation=%.6f "
|
||||
"refine=%u stack=%u core_samples=%u final_samples=%u core_hits=%u final_hits=%u "
|
||||
"core_leaves=%u final_leaves=%u tail_queries=%u tail_eval=%u "
|
||||
@@ -4543,7 +4682,9 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
|
||||
Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z, Tile.Level, Step, Cells,
|
||||
bSheetTile ? 1 : 0, Result.bAborted ? 1 : 0,
|
||||
Result.bEmpty ? 1 : 0, Result.NumTriangles, BandChunkLo, BandChunkHi,
|
||||
ClassifyVerdict, ClassifySeconds, MeshSeconds, StreamSeconds,
|
||||
ClassifyVerdict, Result.bSealedSolidProof ? 1 : 0,
|
||||
Result.bOutOfLayoutAirProof ? 1 : 0,
|
||||
ClassifySeconds, MeshSeconds, StreamSeconds,
|
||||
TileSeconds, CacheBuildSeconds, EvaluationSeconds,
|
||||
ClassifierStats.RefineNodes, ClassifierStats.StackBoxCalls,
|
||||
ClassifierStats.ExactCoreSamples, ClassifierStats.ExactFinalSamples,
|
||||
@@ -4637,6 +4778,8 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
|
||||
Result.bObsolete = false;
|
||||
Result.bEmpty = true;
|
||||
Result.ClassifyVerdict = -1;
|
||||
Result.bSealedSolidProof = false;
|
||||
Result.bOutOfLayoutAirProof = false;
|
||||
Result.ClassifySeconds = 0.0;
|
||||
Result.MeshSeconds = 0.0;
|
||||
Result.StreamSeconds = 0.0;
|
||||
@@ -4674,8 +4817,49 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
|
||||
// même pour les cellules uniformes, et ces tuiles sont rares (fenêtre d'ombre).
|
||||
// (Gate IsoLevel == 0 : les verdicts du classifieur supposent l'iso MC à zéro exactement.)
|
||||
bool bTrivialEmpty = false;
|
||||
if (GVoxelForgeSealedSolidProof != 0
|
||||
&& !bSheetTile && !bWantCapture && Generator && Mesher
|
||||
&& Mesher->IsoLevel == 0.0f)
|
||||
{
|
||||
TotalSealedSolidProofCandidates.fetch_add(1, std::memory_order_relaxed);
|
||||
const double ProofStartSeconds = bMeasureTile ? FPlatformTime::Seconds() : 0.0;
|
||||
if (Generator->TryProveSealedSolidTile(OriginVoxels, Step, Cells))
|
||||
{
|
||||
ClassifySeconds = bMeasureTile
|
||||
? FPlatformTime::Seconds() - ProofStartSeconds : 0.0;
|
||||
ClassifyVerdict = static_cast<int32>(EVoxelTileClass::AllSolid);
|
||||
Result.ClassifyVerdict = ClassifyVerdict;
|
||||
Result.bSealedSolidProof = true;
|
||||
TotalSealedSolidProofSkips.fetch_add(1, std::memory_order_relaxed);
|
||||
INC_DWORD_STAT(STAT_VoxelForgeTilesSkippedAllSolid);
|
||||
bTrivialEmpty = true;
|
||||
}
|
||||
else if (Generator->TryProveOutOfLayoutAirTile(OriginVoxels, Step, Cells))
|
||||
{
|
||||
ClassifySeconds = bMeasureTile
|
||||
? FPlatformTime::Seconds() - ProofStartSeconds : 0.0;
|
||||
ClassifyVerdict = static_cast<int32>(EVoxelTileClass::AllAir);
|
||||
Result.ClassifyVerdict = ClassifyVerdict;
|
||||
Result.bOutOfLayoutAirProof = true;
|
||||
TotalOutOfLayoutAirProofSkips.fetch_add(1, std::memory_order_relaxed);
|
||||
INC_DWORD_STAT(STAT_VoxelForgeTilesSkippedAllAir);
|
||||
bTrivialEmpty = true;
|
||||
}
|
||||
else if (bMeasureTile)
|
||||
{
|
||||
// Keep the measured proof cost visible on a tile that continues through the normal
|
||||
// path. The clean summary remains counter-only and pays no timer overhead.
|
||||
ClassifySeconds = FPlatformTime::Seconds() - ProofStartSeconds;
|
||||
}
|
||||
}
|
||||
if (ShouldAbortWork(ObsoleteFlag))
|
||||
{
|
||||
AbortResult();
|
||||
EmitTileProfile();
|
||||
return;
|
||||
}
|
||||
const int32 OuterClassifierMode = FMath::Clamp(GVoxelForgeOuterClassifierMode, 0, 1);
|
||||
if (OuterClassifierMode != 0
|
||||
if (!bTrivialEmpty && OuterClassifierMode != 0
|
||||
&& !bSheetTile && !bWantCapture && Generator && Mesher && Mesher->IsoLevel == 0.0f)
|
||||
{
|
||||
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_ClassifyTile);
|
||||
@@ -4754,7 +4938,7 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
|
||||
EmitTileProfile();
|
||||
return;
|
||||
}
|
||||
ClassifySeconds = bMeasureTile ? FPlatformTime::Seconds() - ClassifyStartSeconds : 0.0;
|
||||
ClassifySeconds += bMeasureTile ? FPlatformTime::Seconds() - ClassifyStartSeconds : 0.0;
|
||||
ClassifyVerdict = static_cast<int32>(ValidatedVerdict);
|
||||
Result.ClassifyVerdict = ClassifyVerdict;
|
||||
const int32 TrackedLOD = FMath::Clamp(Tile.Level, 0, TrackedClassifierLODCount - 1);
|
||||
|
||||
@@ -22,6 +22,16 @@ class UVoxelDiffLayer;
|
||||
class UVoxelBiomeDefinition;
|
||||
struct FTunnelCoreWorldEvaluation;
|
||||
|
||||
/** Guards which may be disabled only by the sealed-solid proof's soundness tests. */
|
||||
enum class EVoxelSealedSolidProofGuard : uint8
|
||||
{
|
||||
None = 0,
|
||||
DiffLayer = 1u << 0,
|
||||
Passages = 1u << 1,
|
||||
OriginLanding = 1u << 2,
|
||||
DisturbanceChasms = 1u << 3,
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// LOD-AWARE OCTAVE REDUCTION (T2.b)
|
||||
//=============================================================================
|
||||
@@ -166,6 +176,27 @@ public:
|
||||
int32 SizeX, int32 SizeY, int32 SizeZ) const;
|
||||
void EndDensityBlock() const;
|
||||
|
||||
/**
|
||||
* Prove that the mesher's core lattice is entirely solid from the cheap, structural rules
|
||||
* that own inter-strate gaps and boundary seals. This never samples density. It is intended
|
||||
* for an empty-tile fast path; a false answer is harmless, while a true answer must make every
|
||||
* core lattice vertex negative in the final MC field.
|
||||
*
|
||||
* The proof is deliberately narrower than ClassifyTile: it accepts only a tile made entirely
|
||||
* of gap/seal lattice rows and rejects recipes/composer overrides plus every known air-writing
|
||||
* structural feature. The mesher's +/-1 normal halo is not part of the cell lattice, matching
|
||||
* ClassifyTile and GenerateMesh.
|
||||
*/
|
||||
bool TryProveSealedSolidTile(const FIntVector& OriginVoxels, int32 Step,
|
||||
int32 CellsPerAxis) const;
|
||||
|
||||
#if WITH_DEV_AUTOMATION_TESTS
|
||||
/** Test hook: disable named proof guards and require the brute-force oracle to catch it. */
|
||||
bool TryProveSealedSolidTileForTest(
|
||||
const FIntVector& OriginVoxels, int32 Step, int32 CellsPerAxis,
|
||||
uint8 DisabledGuards) const;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Densité pour une strate TunnelNetwork (rooms + tunnels + worm noise).
|
||||
* Utilisée en interne par GetDensityAt quand la strate est de ce type.
|
||||
@@ -385,6 +416,15 @@ public:
|
||||
int32 CellsPerAxis,
|
||||
struct FVoxelTileClassificationStats* OutStats) const;
|
||||
|
||||
/**
|
||||
* Prove the exact constant-air field above/below the strate layout for a mesher lattice.
|
||||
* This is separate from TryProveSealedSolidTile: it never supplies collision support and its
|
||||
* caller records AllAir, not AllSolid. It shares the layout/diff/structural guards with the
|
||||
* production classifier and returns false for every mixed or uncertain box.
|
||||
*/
|
||||
bool TryProveOutOfLayoutAirTile(const FIntVector& OriginVoxels, int32 Step,
|
||||
int32 CellsPerAxis) const;
|
||||
|
||||
private:
|
||||
/** Identité process-unique du propriétaire des caches `CP_*` thread_local.
|
||||
* Process-unique owner identity for the `CP_*` thread-local cache key. */
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace VoxelForgeStartupTrace
|
||||
int32 TileZ = 0;
|
||||
int32 Level = -1;
|
||||
int32 Verdict = -1; // Mixed=0, AllSolid=1, AllAir=2, -1=not classified (sheet/early path)
|
||||
bool bSealedSolidProof = false;
|
||||
bool bEmpty = true;
|
||||
int32 Triangles = 0;
|
||||
// This is worker-result -> game-thread mesh submission. Collision readiness is a later
|
||||
|
||||
@@ -107,6 +107,8 @@ struct FChunkResult
|
||||
uint64 GenerationEndCycles = 0;
|
||||
uint64 ApplyStartCycles = 0;
|
||||
int32 ClassifyVerdict = -1; double ClassifySeconds = 0.0; double MeshSeconds = 0.0; double StreamSeconds = 0.0; int32 NumTriangles = 0;
|
||||
bool bSealedSolidProof = false; // true when the cheap gap/seal proof skipped meshing
|
||||
bool bOutOfLayoutAirProof = false; // true when the exact constant-air guard skipped meshing
|
||||
// Clean, low-overhead work counters. These are carried with the result so the worker can
|
||||
// count exact validation and mesher density calls locally, then publish one aggregate update.
|
||||
int64 ValidationDensityCalls = 0;
|
||||
@@ -934,6 +936,9 @@ public:
|
||||
std::atomic<uint64> TotalObsoleteWorkerTasks{0};
|
||||
std::atomic<uint64> TotalValidationDensityCalls{0};
|
||||
std::atomic<uint64> TotalMesherDensityCalls{0};
|
||||
std::atomic<uint64> TotalSealedSolidProofCandidates{0};
|
||||
std::atomic<uint64> TotalSealedSolidProofSkips{0};
|
||||
std::atomic<uint64> TotalOutOfLayoutAirProofSkips{0};
|
||||
std::atomic<uint64> OuterClassifierCallsByLOD[TrackedClassifierLODCount]{};
|
||||
std::atomic<uint64> OuterClassifierVerdictsByLOD[TrackedClassifierLODCount][3]{};
|
||||
|
||||
@@ -959,6 +964,9 @@ public:
|
||||
// by explicit command-line switches; they do not alter ordinary player movement or streaming.
|
||||
bool bHeadlessStreamingTestMovement = false;
|
||||
bool bHeadlessStreamingTestExitRequested = false;
|
||||
bool bStartupTraceThroughCrossing = false;
|
||||
bool bHeadlessStreamingTestCenterOverride = false;
|
||||
FVector HeadlessStreamingTestCenterVoxel = FVector::ZeroVector;
|
||||
int32 HeadlessStreamingTestMoveAttempts = 0;
|
||||
double HeadlessStreamingTestDistanceCm = 0.0;
|
||||
float HeadlessStreamingTestSpeedCmPerSecond = 0.0f;
|
||||
|
||||
Reference in new issue
Block a user