measure(step2b): per-brick setup rebuilds the chunk caches -- 10x slower; live cost attributed

Editor-only brick API (<= 8^3 points per call, TunnelNetwork/Underwater op-stack routes, 1.16%
scalar fallback). Exact: 0 Q16/sign disagreements on the full 259^3 ring and the five-seed probe
set, same hash on 1 and 6 workers, reach shrink still caught (18,107 Q16 / 735 sign).
Speed: 69.19 s wall, 17.6 us/sample vs live 7.04 s, 1.65 us/sample. Stack sampler (inclusive):
BuildChunkCache 93.5%, PropagateSdfOverBox 92.5%, BuildActiveOpList 92.6% -- each brick rebuilds
the cache windows and active-op lists; EvalBlock itself is 3.6%.
Live path attribution (inclusive, 68,934 stacks): op-stack RoomGraph EvalBlock 54.4%, SDF and
spatial candidate scans ~28%, tunnel core 18.4%, passage floor segment 15.0%, structural posts
14.6%, noise octaves 2.6%, disturbances 0.6%, diff layer 0.3%.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-09-30 03:10:22 +02:00
1 parent 41a9265ebd
commit fd9f809c1c
7 files changed
+749 -97

No files matched your search

+310 -69
View File
@@ -641,6 +641,8 @@ thread_local int32 VoxelGenLOD::OctaveBias = 0;
struct FVoxelDensityBlockSession
{
bool bActive = false;
bool bBatchRequested = false;
bool bBatchSucceeded = false;
FIntVector Origin = FIntVector::ZeroValue;
int32 Step = 1;
int32 SizeX = 0;
@@ -650,6 +652,7 @@ struct FVoxelDensityBlockSession
TArray<uint8> Valid;
TArray<FVoxelOpSample> BlockScratch;
TArray<int32> ActiveOps;
TArray<float> BatchResults;
void Begin(FIntVector InOrigin, int32 InStep, int32 InSizeX, int32 InSizeY, int32 InSizeZ)
{
@@ -665,6 +668,9 @@ struct FVoxelDensityBlockSession
Valid.Reset();
BlockScratch.Reset();
ActiveOps.Reset();
BatchResults.Reset();
bBatchRequested = false;
bBatchSucceeded = false;
return;
}
@@ -672,6 +678,9 @@ struct FVoxelDensityBlockSession
// (gaps, out-of-layout, degenerate strates) use the ordinary scalar path and should not
// pay for a block scratch lattice merely because the mesher opened the scoped hand-off.
BlockScratch.Reset();
BatchResults.Reset();
bBatchRequested = false;
bBatchSucceeded = false;
}
void End()
@@ -683,6 +692,9 @@ struct FVoxelDensityBlockSession
Valid.Reset();
BlockScratch.Reset();
ActiveOps.Reset();
BatchResults.Reset();
bBatchRequested = false;
bBatchSucceeded = false;
}
int32 Index(int32 X, int32 Y, int32 Z) const
@@ -2709,6 +2721,77 @@ void UVoxelGenerator::EndDensityBlock() const
GVoxelDensityBlockSession.End();
}
#if WITH_EDITOR
bool UVoxelGenerator::EvaluateDensityBrick(
FIntVector OriginVoxels, int32 SizeX, int32 SizeY, int32 SizeZ,
TArray<float>& OutDensities, int32& OutFallbackSamples) const
{
OutDensities.Reset();
OutFallbackSamples = 0;
const int64 SampleCount64 = static_cast<int64>(SizeX) * SizeY * SizeZ;
if (SizeX <= 0 || SizeY <= 0 || SizeZ <= 0
|| SizeX > 8 || SizeY > 8 || SizeZ > 8 || SampleCount64 > 512)
{
return false;
}
const FIntVector Last = OriginVoxels + FIntVector(SizeX - 1, SizeY - 1, SizeZ - 1);
const auto SameCell = [](int32 A, int32 B, int32 CellSize)
{
return FMath::FloorToInt(static_cast<float>(A) / static_cast<float>(CellSize))
== FMath::FloorToInt(static_cast<float>(B) / static_cast<float>(CellSize));
};
if (!SameCell(OriginVoxels.X, Last.X, 8)
|| !SameCell(OriginVoxels.Y, Last.Y, 8)
|| !SameCell(OriginVoxels.Z, Last.Z, 8)
|| !SameCell(OriginVoxels.X, Last.X, CHUNK_SIZE)
|| !SameCell(OriginVoxels.Y, Last.Y, CHUNK_SIZE)
|| !SameCell(OriginVoxels.Z, Last.Z, CHUNK_SIZE))
{
return false;
}
const int32 SampleCount = static_cast<int32>(SampleCount64);
BeginDensityBlock(OriginVoxels, 1, SizeX, SizeY, SizeZ);
GVoxelDensityBlockSession.bBatchRequested = true;
GVoxelDensityBlockSession.bBatchSucceeded = false;
GVoxelDensityBlockSession.BatchResults.SetNumUninitialized(SampleCount);
const float FirstDensity = GetDensityAt(
static_cast<float>(OriginVoxels.X),
static_cast<float>(OriginVoxels.Y),
static_cast<float>(OriginVoxels.Z));
if (GVoxelDensityBlockSession.bBatchSucceeded)
{
OutDensities.SetNumUninitialized(SampleCount);
FMemory::Memcpy(
OutDensities.GetData(), GVoxelDensityBlockSession.BatchResults.GetData(),
static_cast<SIZE_T>(SampleCount) * sizeof(float));
EndDensityBlock();
return OutDensities.Num() == SampleCount;
}
// Recipes without the editor brick port retain the canonical point evaluator. The first
// call already produced point zero; the remaining samples are explicit counted fallbacks.
GVoxelDensityBlockSession.bBatchRequested = false;
OutDensities.SetNumUninitialized(SampleCount);
OutDensities[0] = FirstDensity;
for (int32 Index = 1; Index < SampleCount; ++Index)
{
const int32 X = Index % SizeX;
const int32 Y = (Index / SizeX) % SizeY;
const int32 Z = Index / (SizeX * SizeY);
OutDensities[Index] = GetDensityAt(
static_cast<float>(OriginVoxels.X + X),
static_cast<float>(OriginVoxels.Y + Y),
static_cast<float>(OriginVoxels.Z + Z));
}
OutFallbackSamples = SampleCount;
EndDensityBlock();
return true;
}
#endif
float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ,
float* OutPreEditDensity) const
{
@@ -2789,6 +2872,12 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ,
bIsGapChunk = StrateManager->IsGapChunk(ChunkCoord);
}
#if WITH_EDITOR
bool bEvaluateDensityBatch = false;
int32 BatchSampleCount = 0;
int32 DensityBatchCount = 1;
bool bBrickHasDiffMods = false;
#endif
if (StrateManager && bIsGapChunk)
{
// SOLID BEDROCK gap between two strates. The auto-carved passages still tunnel
@@ -3361,6 +3450,160 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ,
}
DensityCoreTimer.End();
#if WITH_EDITOR
// The editor region program asks this one routed sample to fill every point in the
// already-evaluated operator block. Its bricks are contained by one 8-voxel research
// bin and one 32-voxel chunk, so the stack, params, route, and cache key above are shared.
BatchSampleCount = GVoxelDensityBlockSession.SizeX
* GVoxelDensityBlockSession.SizeY * GVoxelDensityBlockSession.SizeZ;
bEvaluateDensityBatch = GVoxelDensityBlockSession.bBatchRequested
&& bUsedOpBlockSample && !bUsedFusedEvaluator && CP_UseOpStack
&& !bIsGapChunk && StrateManager != nullptr
&& (CP_GenType == ECaveGeneratorType::TunnelNetwork
|| CP_GenType == ECaveGeneratorType::Underwater)
&& GVoxelDensityBlockSession.Samples.Num() == BatchSampleCount
&& GVoxelDensityBlockSession.BatchResults.Num() == BatchSampleCount;
DensityBatchCount = bEvaluateDensityBatch ? BatchSampleCount : 1;
bool bBrickHasDisturbanceCandidate = true;
bool bBrickHasPassagePostCandidate = true;
if (bEvaluateDensityBatch)
{
const FIntVector BrickLast = GVoxelDensityBlockSession.Origin + FIntVector(
(GVoxelDensityBlockSession.SizeX - 1) * GVoxelDensityBlockSession.Step,
(GVoxelDensityBlockSession.SizeY - 1) * GVoxelDensityBlockSession.Step,
(GVoxelDensityBlockSession.SizeZ - 1) * GVoxelDensityBlockSession.Step);
const FBox BrickBox(
FVector(GVoxelDensityBlockSession.Origin.X,
GVoxelDensityBlockSession.Origin.Y,
GVoxelDensityBlockSession.Origin.Z),
FVector(BrickLast.X, BrickLast.Y, BrickLast.Z));
bBrickHasDisturbanceCandidate = !VoxelDensityAblation::IsDisturbancesOff()
&& VF_AnyDisturbanceCanTouchBox(
BrickBox, CP_Dist, static_cast<uint32>(Seed));
const bool bAllPassagePostsOff =
VoxelDensityAblation::IsPassageStructuralPostsOff()
&& VoxelDensityAblation::IsNativeFloorOff()
&& VoxelDensityAblation::IsLandingPostsOff();
if (bAllPassagePostsOff)
{
bBrickHasPassagePostCandidate = false;
}
else if (!VoxelDensityAblation::IsPassageCarvingOff())
{
bBrickHasPassagePostCandidate = StrateManager->AnyPassageNearBox(
BrickBox.Min, BrickBox.Max);
}
bBrickHasDiffMods = DiffLayer != nullptr && DiffLayer->HasAnyMods();
}
const bool bApplyBrickDisturbances =
!bEvaluateDensityBatch || bBrickHasDisturbanceCandidate;
const bool bApplyBrickPassagePosts =
!bEvaluateDensityBatch || bBrickHasPassagePostCandidate;
#else
const bool bApplyBrickDisturbances = true;
const bool bApplyBrickPassagePosts = true;
#endif
// Chunk parameters and composer/custom recipe structure are fixed by the route above.
// Resolve the landing writer's base once for the whole brick, including edge bricks.
float BrickLandingBaseDensity = CP_Dist.BaseDensity;
#if WITH_EDITOR
if (CP_UseComposerRegions && CP_ComposerRegions.bHasGlobalStructuralParams)
{
BrickLandingBaseDensity = CP_ComposerRegions.BaseDensity;
}
else
#endif
if (CP_UseCustomRecipe)
{
switch (CP_CustomRecipe.StructuralParamBlock)
{
case EVoxelStrateParamBlock::TunnelNetwork:
BrickLandingBaseDensity = CP_CustomParams.TunnelNetworkParams.BaseDensity;
break;
case EVoxelStrateParamBlock::Slab:
BrickLandingBaseDensity = CP_CustomParams.SlabParams.BaseDensity;
break;
case EVoxelStrateParamBlock::Maze:
BrickLandingBaseDensity = CP_CustomParams.MazeParams.BaseDensity;
break;
case EVoxelStrateParamBlock::Surface:
BrickLandingBaseDensity = CP_CustomParams.SurfaceParams.BaseDensity;
break;
case EVoxelStrateParamBlock::VerticalShaft:
BrickLandingBaseDensity = CP_CustomParams.VerticalShaftParams.BaseDensity;
break;
case EVoxelStrateParamBlock::FloatingIsland:
BrickLandingBaseDensity = CP_CustomParams.FloatingIslandParams.BaseDensity;
break;
default:
break;
}
}
else
{
switch (CP_GenType)
{
case ECaveGeneratorType::FlatPlain:
case ECaveGeneratorType::CrystalChamber:
BrickLandingBaseDensity = CP_Slab.BaseDensity;
break;
case ECaveGeneratorType::Maze:
BrickLandingBaseDensity = CP_Maze.BaseDensity;
break;
case ECaveGeneratorType::SurfaceWorld:
BrickLandingBaseDensity = CP_Surface.BaseDensity;
break;
case ECaveGeneratorType::VerticalShafts:
BrickLandingBaseDensity = CP_Vert.BaseDensity;
break;
case ECaveGeneratorType::FloatingIslands:
BrickLandingBaseDensity = CP_Float.BaseDensity;
break;
case ECaveGeneratorType::TunnelNetwork:
case ECaveGeneratorType::Underwater:
default:
BrickLandingBaseDensity = CP_Tunnel.BaseDensity;
break;
}
}
for (int32 DensityBatchIndex = 0;
#if WITH_EDITOR
DensityBatchIndex < DensityBatchCount;
#else
DensityBatchIndex < 1;
#endif
++DensityBatchIndex)
{
#if WITH_EDITOR
if (bEvaluateDensityBatch)
{
const int32 BatchX = DensityBatchIndex % GVoxelDensityBlockSession.SizeX;
const int32 BatchY = (DensityBatchIndex / GVoxelDensityBlockSession.SizeX)
% GVoxelDensityBlockSession.SizeY;
const int32 BatchZ = DensityBatchIndex
/ (GVoxelDensityBlockSession.SizeX * GVoxelDensityBlockSession.SizeY);
WorldX = static_cast<float>(GVoxelDensityBlockSession.Origin.X
+ BatchX * GVoxelDensityBlockSession.Step);
WorldY = static_cast<float>(GVoxelDensityBlockSession.Origin.Y
+ BatchY * GVoxelDensityBlockSession.Step);
WorldZ = static_cast<float>(GVoxelDensityBlockSession.Origin.Z
+ BatchZ * GVoxelDensityBlockSession.Step);
const FVoxelOpSample& BatchSample =
GVoxelDensityBlockSession.Samples[DensityBatchIndex];
Result = -BatchSample.Density;
bUsedOpBlockSample = true;
bBlockHasTunnelCore = BatchSample.bHasTunnelCoreWorldEvaluation;
BlockTunnelCoreSDF = BatchSample.TunnelCoreWorldSDF;
bBlockTunnelCoreRoomFloor = BatchSample.bTunnelCoreRoomFloor;
bBlockHasTunnelCoreSweptFloor = BatchSample.bHasTunnelCoreSweptFloor;
BlockTunnelCoreSweptFloorZ = BatchSample.TunnelCoreSweptFloorZ;
BlockTunnelCoreSweptFloorRadius = BatchSample.TunnelCoreSweptFloorRadius;
bUsedFusedEvaluator = false;
bRoomOwnsBottomValid = false;
}
#endif
// Resolve the graph's authored floor before the disturbance pass. The operator-stack
// source has already computed this exact result while publishing its sample; native
// tunnel generation uses the prepared immutable cache. Keeping the result here means the
@@ -3420,8 +3663,11 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ,
VoxelDensityProfile::EBucket::DensityDisturbances);
const bool bProtectVerticalShaftAir =
VoxelPassageGeometry::VerticalShaftConnectorAirMarker();
ApplyDisturbances(Result, WorldX, WorldY, WorldZ, CP_Dist, (uint32)Seed,
bProtectVerticalShaftAir);
if (bApplyBrickDisturbances)
{
ApplyDisturbances(Result, WorldX, WorldY, WorldZ, CP_Dist, (uint32)Seed,
bProtectVerticalShaftAir);
}
DensityDisturbancesTimer.End();
// A disturbance is allowed to add visual detail, but it must not refill the landing's
@@ -3432,71 +3678,7 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ,
VoxelDensityProfile::EBucket::DensityStructuralPosts);
VoxelDensityProfile::FScopedTimer StructuralTailTimer(
VoxelDensityProfile::EBucket::StructuralTail);
float LandingBaseDensity = CP_Dist.BaseDensity;
#if WITH_EDITOR
// A composer parent can deliberately use a different structural base than the authored
// definition. The landing floor was already applied by PassageCarveOp with that parent
// base; use the same value for this post-disturbance MC backstop or live-vs-direct stack
// evaluation would differ exactly on floor voxels.
if (CP_UseComposerRegions && CP_ComposerRegions.bHasGlobalStructuralParams)
{
LandingBaseDensity = CP_ComposerRegions.BaseDensity;
}
else
#endif
if (CP_UseCustomRecipe)
{
switch (CP_CustomRecipe.StructuralParamBlock)
{
case EVoxelStrateParamBlock::TunnelNetwork:
LandingBaseDensity = CP_CustomParams.TunnelNetworkParams.BaseDensity;
break;
case EVoxelStrateParamBlock::Slab:
LandingBaseDensity = CP_CustomParams.SlabParams.BaseDensity;
break;
case EVoxelStrateParamBlock::Maze:
LandingBaseDensity = CP_CustomParams.MazeParams.BaseDensity;
break;
case EVoxelStrateParamBlock::Surface:
LandingBaseDensity = CP_CustomParams.SurfaceParams.BaseDensity;
break;
case EVoxelStrateParamBlock::VerticalShaft:
LandingBaseDensity = CP_CustomParams.VerticalShaftParams.BaseDensity;
break;
case EVoxelStrateParamBlock::FloatingIsland:
LandingBaseDensity = CP_CustomParams.FloatingIslandParams.BaseDensity;
break;
default:
break;
}
}
else
{
switch (CP_GenType)
{
case ECaveGeneratorType::FlatPlain:
case ECaveGeneratorType::CrystalChamber:
LandingBaseDensity = CP_Slab.BaseDensity;
break;
case ECaveGeneratorType::Maze:
LandingBaseDensity = CP_Maze.BaseDensity;
break;
case ECaveGeneratorType::SurfaceWorld:
LandingBaseDensity = CP_Surface.BaseDensity;
break;
case ECaveGeneratorType::VerticalShafts:
LandingBaseDensity = CP_Vert.BaseDensity;
break;
case ECaveGeneratorType::FloatingIslands:
LandingBaseDensity = CP_Float.BaseDensity;
break;
case ECaveGeneratorType::TunnelNetwork:
case ECaveGeneratorType::Underwater:
default:
LandingBaseDensity = CP_Tunnel.BaseDensity;
break;
}
}
const float LandingBaseDensity = BrickLandingBaseDensity;
auto ApplyTunnelCoreShape = [&](const FTunnelCoreWorldEvaluation& Core)
{
if (!VoxelMath::IsFinite(Core.SDF))
@@ -3538,6 +3720,7 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ,
CP_Dist.StrateTopWorldZ, CP_Dist.StrateBottomWorldZ,
CP_Dist.BoundarySealThickness, LandingBaseDensity, OriginSpineRadius);
}
if (bApplyBrickPassagePosts)
{
VoxelDensityProfile::FScopedTimer ProfileTimer(
VoxelDensityProfile::EBucket::PassageStructuralPosts);
@@ -3567,12 +3750,14 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ,
CP_Dist.StrateTopWorldZ, CP_Dist.StrateBottomWorldZ,
CP_Dist.BoundarySealThickness, LandingBaseDensity, OriginSpineRadius);
}
if (bApplyBrickPassagePosts)
{
VoxelDensityProfile::FScopedTimer ProfileTimer(
VoxelDensityProfile::EBucket::PassageLandingFloor);
StrateManager->ApplyPassageLandingFloorMC(
Result, WorldX, WorldY, WorldZ, LandingBaseDensity);
}
if (bApplyBrickPassagePosts)
{
VoxelDensityProfile::FScopedTimer ProfileTimer(
VoxelDensityProfile::EBucket::PassageLandingRoomFloor);
@@ -3647,6 +3832,7 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ,
// composed from the immutable construction-time profile after generic writers and the
// graph overlap, so the legacy passage support slab can be disabled without losing the
// floor to a disturbance refill.
if (bApplyBrickPassagePosts)
{
VoxelDensityProfile::FScopedTimer ProfileTimer(
VoxelDensityProfile::EBucket::PassageLandingFloor);
@@ -3655,6 +3841,13 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ,
}
StructuralTailTimer.End();
DensityStructuralPostsTimer.End();
#if WITH_EDITOR
if (bEvaluateDensityBatch)
{
GVoxelDensityBlockSession.BatchResults[DensityBatchIndex] = Result;
}
#endif
}
}
else
{
@@ -3673,6 +3866,33 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ,
DensityCoreTimer.End();
}
#if WITH_EDITOR
const int32 FinalDensityBatchCount = bEvaluateDensityBatch ? BatchSampleCount : 1;
#else
const int32 FinalDensityBatchCount = 1;
#endif
for (int32 FinalDensityBatchIndex = 0;
FinalDensityBatchIndex < FinalDensityBatchCount;
++FinalDensityBatchIndex)
{
#if WITH_EDITOR
if (bEvaluateDensityBatch)
{
const int32 BatchX = FinalDensityBatchIndex % GVoxelDensityBlockSession.SizeX;
const int32 BatchY = (FinalDensityBatchIndex / GVoxelDensityBlockSession.SizeX)
% GVoxelDensityBlockSession.SizeY;
const int32 BatchZ = FinalDensityBatchIndex
/ (GVoxelDensityBlockSession.SizeX * GVoxelDensityBlockSession.SizeY);
WorldX = static_cast<float>(GVoxelDensityBlockSession.Origin.X
+ BatchX * GVoxelDensityBlockSession.Step);
WorldY = static_cast<float>(GVoxelDensityBlockSession.Origin.Y
+ BatchY * GVoxelDensityBlockSession.Step);
WorldZ = static_cast<float>(GVoxelDensityBlockSession.Origin.Z
+ BatchZ * GVoxelDensityBlockSession.Step);
Result = GVoxelDensityBlockSession.BatchResults[FinalDensityBatchIndex];
VoxelPassageGeometry::ResetVerticalShaftConnectorAirMarker();
}
#endif
// The edge is the final generated structural invariant. Passage carving is ordered before it
// in every archetype/stack, and this MC-facing pass also covers out-of-layout air plus any
// disturbance that might otherwise carve back into the shell. Player edits remain separate
@@ -3704,7 +3924,13 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ,
{
*OutPreEditDensity = Result;
}
if (DiffLayer && DiffLayer->HasAnyMods())
const bool bDiffLayerHasModsForSample = DiffLayer != nullptr &&
#if WITH_EDITOR
(bEvaluateDensityBatch ? bBrickHasDiffMods : DiffLayer->HasAnyMods());
#else
DiffLayer->HasAnyMods();
#endif
if (DiffLayer && bDiffLayerHasModsForSample)
{
// ── PER-CHUNK MOD SNAPSHOT ──
// The old HasModifications + GetDensityOffset pair took the diff layer's RWLock + a TMap find
@@ -3737,8 +3963,23 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ,
{
Result -= UVoxelDiffLayer::EvaluateMods(Slot.Mods, WorldX, WorldY, WorldZ);
}
}
DensityDiffLayerTimer.End();
#if WITH_EDITOR
if (bEvaluateDensityBatch)
{
GVoxelDensityBlockSession.BatchResults[FinalDensityBatchIndex] = Result;
}
#endif
}
DensityDiffLayerTimer.End();
#if WITH_EDITOR
if (bEvaluateDensityBatch)
{
GVoxelDensityBlockSession.bBatchSucceeded = true;
}
#endif
VoxelDensityProfile::FScopedTimer DensityTailTimer(
VoxelDensityProfile::EBucket::DensityTail);
@@ -180,6 +180,13 @@ public:
int32 SizeX, int32 SizeY, int32 SizeZ) const;
void EndDensityBlock() const;
#if WITH_EDITOR
/** Editor-only region-program entry point; unsupported routes report live point fallbacks. */
bool EvaluateDensityBrick(FIntVector OriginVoxels, int32 SizeX, int32 SizeY, int32 SizeZ,
TArray<float>& OutDensities,
int32& OutFallbackSamples) const;
#endif
/**
* 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