fix(mesher): stop forcing the nested block classifier on -- but I cannot reproduce the 3.5x

The code contradicted its own documentation. VoxelMarchingCubesMesher.h:150 says to leave
the optional 8^3-block refinement DISABLED in streaming, "repeating a proof on ..." --
and VoxelWorld.cpp forced `Mesher->bUseBlockEarlyOut = true` regardless.

It is now off by default, behind `voxel.UseBlockEarlyOut` (CVar + command line), so the
capability stays available for the focused diagnostics where it was measured to help.

⚠️ THE HEADLINE NUMBER DOES NOT REPRODUCE, AND I AM NOT CLAIMING IT
Sol's second pass read a 3.5x from the attribution table:
    classifier disabled    587.991 ms
    classifier enabled   2,056.596 ms
My own clean A/B on this flag, quiet machine, 3 runs each:
    nested OFF   0.597 / 0.613 / 0.607 s   mean 0.606
    nested ON    0.637 / 0.599 / 0.605 s   mean 0.614
Identical within noise.

So either the 2,056 ms control exercised more than this flag does, or the 3.5x lives on
a path the 128^3 export does not take. PERF-ATTRIBUTION.md describes that figure as "the
separate classifier control", which is not obviously the same lever. Until the game-path
A/B is run, treat the 3.5x as UNCONFIRMED.

What this commit is worth on its own: the code now does what its documentation says, the
behaviour is switchable rather than hardcoded, and geometry is unchanged --
SHA-256 b3e5f4c3..9b377 identical with the flag on, off, and against bb8ec51.

⚠️ An earlier measurement pass in this round was contaminated: a local llama-server was
using 5 GB and competing for cores. Those runs came out 0.67-0.78 s with the on/off
ranges overlapping. Discarded and re-measured on a quiet machine; only the clean numbers
above are reported. Worth recording as a reminder that the machine is part of the
instrument.

NOT DONE -- the round was OOM-killed partway (same llama-server):
- the three-way outer-classifier A/B (no outer / current+discarded validation / ⭐ outer
  with validation samples REUSED as the mesher core grid). That third mode is the
  interesting one: exact validation currently costs 33^3 = 35,937 density calls whose
  results are then DISCARDED, while the mesher then fills 35^3 = 42,875. A successful
  uniform verdict saves at most 16.18%, and a Mixed verdict saves nothing.
- game-path measurement of any of it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aheo2crrZmHuYtPpdY2Cb
This commit is contained in:
Fr0zkaandClaude Opus 5 committed 2026-09-12 13:19:42 +02:00
1 parent bb8ec51939
commit f2044135ea
4 files changed
+224 -15

No files matched your search

@@ -37,12 +37,15 @@ namespace
FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels, int32 Step, int32 InCellsPerAxis,
TArray<uint8>* OutCaptureGrid,
int32 BandZMinVox, int32 BandZMaxVox)
int32 BandZMinVox, int32 BandZMaxVox,
const FVoxelMesherCoreDensityGrid* ReusableCoreGrid,
int64* OutDensitySampleCount)
{
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_MesherGenerateMesh);
VF_ParseOperatorBlockSwitch();
FVoxelMeshData MeshData;
if (OutCaptureGrid) { OutCaptureGrid->Reset(); }
if (OutDensitySampleCount) { *OutDensitySampleCount = 0; }
if (!Generator) return MeshData;
VoxelDensityProfile::FScopedTimer MesherProfileTimer(
VoxelDensityProfile::EBucket::MesherGenerateMesh);
@@ -227,6 +230,12 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
const int32 CellsPerAxis = FMath::Clamp(InCellsPerAxis, 2, CHUNK_SIZE); // coarse tiles use fewer
const int32 GridDim = CellsPerAxis + 1;
const int32 MDim = GridDim + 2; // +1 marge de chaque côté
const bool bUseReusableCoreGrid = !OutCaptureGrid
&& ReusableCoreGrid
&& ReusableCoreGrid->OriginVoxels == OriginVoxels
&& ReusableCoreGrid->Step == Step
&& ReusableCoreGrid->CellsPerAxis == CellsPerAxis
&& ReusableCoreGrid->Samples.Num() == GridDim * GridDim * GridDim;
// COUPE DE CONTENU PAR STRATE — restreint le maillage (et l'échantillonnage) aux cellules
// dont l'intervalle Z chevauche la bande [BandZMinVox, BandZMaxVox] (voxels inclusifs).
@@ -532,7 +541,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
// gradient) sont échantillonnées — le reste du tampon reste non initialisé et non lu.
VoxelDensityProfile::FScopedTimer MesherDensityGridTimer(
VoxelDensityProfile::EBucket::MesherDensityGrid);
const bool bUseOperatorBlock = !bUseSharedDensityGrid
const bool bUseOperatorBlock = !bUseSharedDensityGrid && !bUseReusableCoreGrid
&& GVoxelForgeUseOperatorBlock != 0;
if (bUseOperatorBlock)
{
@@ -567,6 +576,14 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
DensityGrid[LocalIndex] = ReuseGrid->Samples[
(SharedZ * ReuseGrid->Dim + SharedY) * ReuseGrid->Dim + SharedX];
}
else if (bUseReusableCoreGrid
&& gx >= 0 && gx < GridDim
&& gy >= 0 && gy < GridDim
&& gz >= 0 && gz < GridDim)
{
DensityGrid[LocalIndex] = ReusableCoreGrid->Samples[
(gz * GridDim + gy) * GridDim + gx];
}
else
{
// World voxel = tile origin + grid offset scaled by the cell size (Step).
@@ -574,6 +591,10 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
OriginVoxels.X + gx * Step,
OriginVoxels.Y + gy * Step,
OriginVoxels.Z + gz * Step);
if (OutDensitySampleCount)
{
++(*OutDensitySampleCount);
}
}
}
}