perf(streaming): outer tile classifier off by default; delete mode 2
Owner-approved, on the A/B measured in 83a0e4d: the classifier plus exact validation cost more
than it saved on the game path. With it off, ready p95 is 182 vs 190-195 ms, generation p95 is
125-127 vs 139-141 ms, and density calls drop 18%.
- GVoxelForgeOuterClassifierMode defaults to 0. Mode 1 stays reachable via
voxel.OuterClassifierMode=1 as an A/B instrument; the clamp is now 0..1.
- Mode 2 (validation samples reused as the mesher core grid) is deleted: the reuse grid,
FVoxelMesherCoreDensityGrid, GenerateMesh's ReusableCoreGrid parameter, bValidationCoreReused,
TotalValidationCoreReuseTiles and its log field. It could not fire: it reused the grid only
when validation disproved a uniform verdict, which never happened, and Mixed tiles never
validate. The mode-1 validation loop exits early again on the first disagreeing sample.
- All three GenerateMesh call sites checked; the density-count pointer binds after BandVoxHi.
Verified by Codex, checked by me against the logs:
- build ok; the measured process loaded the staged DLL, SHA-256 1024D8CF..04D
- export OBJ SHA-256 unchanged: b3e5f4c3..9b377
- a run WITHOUT the override prints outer_classifier_mode=0 validation_density_calls=0,
proving the new default took effect
- 841/841 tiles have identical triangle counts between mode 0 and mode 1. The 394 tiles mode 1
skipped as uniform produce 0 triangles in mode 0, so the strate band cut (applied by the
mesher, not by validation) was not hiding geometry.
Docs: ARCHITECTURE 8.10, CODEMAP (GenerateTileResult, ClassifyTile) and fable-idea T1.d record
that streaming no longer classifies by default. WORK-NEXT item 1 records the owner's playtest:
he can no longer fall through the world.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZS94UH8bSsBeg5sZaxe2Y
This commit is contained in:
1 parent
83a0e4d85c
commit
c65b015f01
8 files changed
+33
-86
No files matched your search
@@ -794,6 +794,13 @@ driven by `EditorBrush*` props.
|
|||||||
shadow-casting tile emits a second shadow-pass draw; the far coarse tiles don't need it. NOTE: fps is
|
shadow-casting tile emits a second shadow-pass draw; the far coarse tiles don't need it. NOTE: fps is
|
||||||
RENDER-side (draws ≈ visible tile count × passes); generation cost (workers) and tile *resolution*
|
RENDER-side (draws ≈ visible tile count × passes); generation cost (workers) and tile *resolution*
|
||||||
(cuts triangles, not draws/components) don't move the game thread — tile COUNT does (hence the clipmap).
|
(cuts triangles, not draws/components) don't move the game thread — tile COUNT does (hence the clipmap).
|
||||||
|
- ⛔ **2026-09-13: streaming no longer calls `ClassifyTile` by default** (`voxel.OuterClassifierMode`
|
||||||
|
= 0; `=1` restores it as an A/B instrument). Measured on the headless game path with nested
|
||||||
|
refinement off, the classifier plus its exact validation cost more than they saved. Mode 0 had
|
||||||
|
ready p95 182 vs 190-195 ms, generation p95 125-127 vs 139-141 ms, and 18% fewer density calls,
|
||||||
|
with 841/841 tiles triangle-identical (WORK-NEXT.md). Proving a 33³ core uniform costs almost
|
||||||
|
as much as meshing 35³. The history below explains why it existed. Its soundness tests still
|
||||||
|
guard it.
|
||||||
- **Trivial-empty tile reject (T1.d) — v2 SHIPPED (2026-07-05); v1 was reverted 2026-06-26.**
|
- **Trivial-empty tile reject (T1.d) — v2 SHIPPED (2026-07-05); v1 was reverted 2026-06-26.**
|
||||||
`UVoxelGenerator::ClassifyTile(Origin, Step, Cells)` runs on the gen worker BEFORE the density
|
`UVoxelGenerator::ClassifyTile(Origin, Step, Cells)` runs on the gen worker BEFORE the density
|
||||||
pre-sample (`LoadTile` task, scope `VoxelForge_ClassifyTile`); `AllSolid`/`AllAir` ⇒ GenerateMesh is
|
pre-sample (`LoadTile` task, scope `VoxelForge_ClassifyTile`); `AllSolid`/`AllAir` ⇒ GenerateMesh is
|
||||||
|
|||||||
+2
-2
@@ -301,7 +301,7 @@ casse cette arithmetique *structurellement* — `BeginPlay` loggue une **Error**
|
|||||||
| `GetPlayerPosition` | 231 | Pawn position or zero. (`GetLODForChunk`/`LODToStep`/`IsChunkInRange` removed — dead since the clipmap.) |
|
| `GetPlayerPosition` | 231 | Pawn position or zero. (`GetLODForChunk`/`LODToStep`/`IsChunkInRange` removed — dead since the clipmap.) |
|
||||||
| `ProcessPendingChunks` | 301 | Drains ProcessQueue under per-frame budget; reads the per-request cancellation token before removing its map entry, drops obsolete/aborted results, then applies each live result via `ApplyTileResult`. |
|
| `ProcessPendingChunks` | 301 | Drains ProcessQueue under per-frame budget; reads the per-request cancellation token before removing its map entry, drops obsolete/aborted results, then applies each live result via `ApplyTileResult`. |
|
||||||
| `ApplyTileResult` | — | **Shared game-thread apply** for one `FChunkResult` (async drain + sync carve): discards stale generation epochs and keys no longer desired, marks loaded, ingests capture, EMPTY releases the tile's existing component (a re-gen can flip content→empty on band change — old geometry must not linger), else `ApplyMeshToTile`. Returns true iff a visible mesh uploaded (counts the budget). Doesn't touch `PendingTiles` (caller's). |
|
| `ApplyTileResult` | — | **Shared game-thread apply** for one `FChunkResult` (async drain + sync carve): discards stale generation epochs and keys no longer desired, marks loaded, ingests capture, EMPTY releases the tile's existing component (a re-gen can flip content→empty on band change — old geometry must not linger), else `ApplyMeshToTile`. Returns true iff a visible mesh uploaded (counts the budget). Doesn't touch `PendingTiles` (caller's). |
|
||||||
| `GenerateTileResult` | — | **Shared worker-side gen** for one tile (async `LoadTile` task + sync `SyncRemeshTile`): ClassifyTile (T1.d) → `GenerateMesh`/`GenerateSheetMesh` → `BuildTileStreamSet`. Reads Generator/Mesher only → safe on a worker or the game thread; fills `FChunkResult`, no enqueue. |
|
| `GenerateTileResult` | — | **Shared worker-side gen** for one tile (async `LoadTile` task + sync `SyncRemeshTile`): [ClassifyTile (T1.d) + exact validation, only when `voxel.OuterClassifierMode=1`; **default 0 since 2026-09-13**] → `GenerateMesh`/`GenerateSheetMesh` → `BuildTileStreamSet`. Reads Generator/Mesher only → safe on a worker or the game thread; fills `FChunkResult`, no enqueue. |
|
||||||
| `SyncRemeshTile` | — | **INSTANT DIG**: level-0 same-frame re-mesh on the game thread (`GenerateTileResult` + `ApplyTileResult` inline, Cells=CHUNK_SIZE/Step=1 + strate band, no capture). Used for the tile under the brush centre so a carve is visible THIS frame; one full-res gen on the game thread. |
|
| `SyncRemeshTile` | — | **INSTANT DIG**: level-0 same-frame re-mesh on the game thread (`GenerateTileResult` + `ApplyTileResult` inline, Cells=CHUNK_SIZE/Step=1 + strate band, no capture). Used for the tile under the brush centre so a carve is visible THIS frame; one full-res gen on the game thread. |
|
||||||
| `UpdateChunksAroundPosition` | 362 | Builds desired set, cancels pending keys that left it, sorts from the **true pawn position**, submits the occupied/support LOD0 tile and the next tile along the pawn heading as an absolute `BackgroundHigh` prefix, then loads/unloads and handles LOD changes. **Delta cull**: `BuildDesiredTiles` returns the LEAVERS (stamped `DesiredStamped` map, one sweep) — only those + `TransitionHold` are considered per crossing, not every loaded tile. `BuildDesiredTiles` also applies `RenderDistanceChunks`: the outermost shell widens to cover the distance — as level-MaxClipLevel MC tiles, or (F18 `bFarSheetRing`) as a SHEET ring at level MaxClipLevel+span (covered-check vs the MaxClipLevel box; `VF_OuterShell` shared with `IsTileInClipRange` so the cull sees the same horizon), dz pre-clamped to the vertical band. **§9.3 anchors:** also prunes dead `StreamingAnchors` + detects their chunk crossings → rebuilds the desired set when an anchor moves/(un)registers (`bAnchorsMoved`/`bForceDesiredRebuild`), same cadence as player movement. §8.10. |
|
| `UpdateChunksAroundPosition` | 362 | Builds desired set, cancels pending keys that left it, sorts from the **true pawn position**, submits the occupied/support LOD0 tile and the next tile along the pawn heading as an absolute `BackgroundHigh` prefix, then loads/unloads and handles LOD changes. **Delta cull**: `BuildDesiredTiles` returns the LEAVERS (stamped `DesiredStamped` map, one sweep) — only those + `TransitionHold` are considered per crossing, not every loaded tile. `BuildDesiredTiles` also applies `RenderDistanceChunks`: the outermost shell widens to cover the distance — as level-MaxClipLevel MC tiles, or (F18 `bFarSheetRing`) as a SHEET ring at level MaxClipLevel+span (covered-check vs the MaxClipLevel box; `VF_OuterShell` shared with `IsTileInClipRange` so the cull sees the same horizon), dz pre-clamped to the vertical band. **§9.3 anchors:** also prunes dead `StreamingAnchors` + detects their chunk crossings → rebuilds the desired set when an anchor moves/(un)registers (`bAnchorsMoved`/`bForceDesiredRebuild`), same cadence as player movement. §8.10. |
|
||||||
| `BuildDesiredTiles` | ~722 | Builds `DesiredSorted`+`DesiredStamped` (player clipmap shells + F18 sheet ring) then `AddAnchorDesiredTiles()` before the leaver sweep; it orders from the true local pawn position and builds `CriticalDesiredTiles` for the occupied/support and heading tiles. |
|
| `BuildDesiredTiles` | ~722 | Builds `DesiredSorted`+`DesiredStamped` (player clipmap shells + F18 sheet ring) then `AddAnchorDesiredTiles()` before the leaver sweep; it orders from the true local pawn position and builds `CriticalDesiredTiles` for the occupied/support and heading tiles. |
|
||||||
@@ -344,7 +344,7 @@ This is **where terrain shape lives.**
|
|||||||
| `SampleSurfaceStructuralZ` | — | **F20:** the RAW SurfaceWorld heightfield (continents+mountains+detail), BEFORE any terrain op; returns terrain Z + relief M. Cliff re-samples it at an XY offset for a cheap analytic slope. |
|
| `SampleSurfaceStructuralZ` | — | **F20:** the RAW SurfaceWorld heightfield (continents+mountains+detail), BEFORE any terrain op; returns terrain Z + relief M. Cliff re-samples it at an XY offset for a cheap analytic slope. |
|
||||||
| `ComputeSurfaceTerrainZ` / `GetSurfaceDensity` | — | SurfaceWorld heightfield → terrain Z, then density; biome **output-blend** lerps dominant/neighbour heights (`ParamsD`/`ParamsN`/weight). **F20 surface ops** (`FSurfaceGenerationParams`, biome-selected + slope/relief-conditioned, all default off): Cliff (slope-gated STEEPENING — push height from local mean where steep ⇒ sheer walls; 4 structural resamples only when on), Terrace (relief-gated + `TerraceHardness`), LayerLines (sedimentary shelves) — pure per-column height REMAPS applied here so the single height oracle stays consistent (MC/sheets/ClassifyTile/deco/BP bridge). **Phase 2 OVERHANG** (volumetric — real jutting shelves): in `SurfaceDensityFromColumn`, for AIR voxels in a window `(TerrainZ, TerrainZ+OverhangHeight]` above a steep slope, the heightfield is re-sampled UPHILL (toward the cliff) by a reach that GROWS with height (tiny low ⇒ air over the void, full high ⇒ borrows the far cliff rock) and unioned in ⇒ a shelf attached to the cliff, tapering out over the void with air beneath (the sketch). Per-column `OverhangAmp`(=strength·slope-gate) + unit uphill `(DirX,DirY)` resolved once in `ComputeSurfaceColumn` (gradient sampled at the REACH scale so a spot over the void can see the cliff), cached on `FSurfaceColumn`. Genuine 3D (per-voxel structural re-eval, gated to steep overhang columns). Off ⇒ byte-identical. §8.14. |
|
| `ComputeSurfaceTerrainZ` / `GetSurfaceDensity` | — | SurfaceWorld heightfield → terrain Z, then density; biome **output-blend** lerps dominant/neighbour heights (`ParamsD`/`ParamsN`/weight). **F20 surface ops** (`FSurfaceGenerationParams`, biome-selected + slope/relief-conditioned, all default off): Cliff (slope-gated STEEPENING — push height from local mean where steep ⇒ sheer walls; 4 structural resamples only when on), Terrace (relief-gated + `TerraceHardness`), LayerLines (sedimentary shelves) — pure per-column height REMAPS applied here so the single height oracle stays consistent (MC/sheets/ClassifyTile/deco/BP bridge). **Phase 2 OVERHANG** (volumetric — real jutting shelves): in `SurfaceDensityFromColumn`, for AIR voxels in a window `(TerrainZ, TerrainZ+OverhangHeight]` above a steep slope, the heightfield is re-sampled UPHILL (toward the cliff) by a reach that GROWS with height (tiny low ⇒ air over the void, full high ⇒ borrows the far cliff rock) and unioned in ⇒ a shelf attached to the cliff, tapering out over the void with air beneath (the sketch). Per-column `OverhangAmp`(=strength·slope-gate) + unit uphill `(DirX,DirY)` resolved once in `ComputeSurfaceColumn` (gradient sampled at the REACH scale so a spot over the void can see the cliff), cached on `FSurfaceColumn`. Genuine 3D (per-voxel structural re-eval, gated to steep overhang columns). Off ⇒ byte-identical. §8.14. |
|
||||||
| `VF_BuildOpStackForChunk` (file-static) | — | **The archetype → stack mapping, written down once.** `GetDensityAt` and `ClassifyTile` both call it; params are passed in, never fetched here. A second copy would be the worst bug available in this file — a tile skipped on the verdict of a stack that is not the one producing its density is a hole. Returns false (⇒ caller falls back to the `switch`) for an unported archetype, missing params, or a **degenerate strate**, since five archetype functions early-out to air there and the stack deliberately has no such early-out. `Refs.Surface == nullptr` makes it refuse SurfaceWorld, which is how `ClassifyTile` keeps its own exact-lattice proof. |
|
| `VF_BuildOpStackForChunk` (file-static) | — | **The archetype → stack mapping, written down once.** `GetDensityAt` and `ClassifyTile` both call it; params are passed in, never fetched here. A second copy would be the worst bug available in this file — a tile skipped on the verdict of a stack that is not the one producing its density is a hole. Returns false (⇒ caller falls back to the `switch`) for an unported archetype, missing params, or a **degenerate strate**, since five archetype functions early-out to air there and the stack deliberately has no such early-out. `Refs.Surface == nullptr` makes it refuse SurfaceWorld, which is how `ClassifyTile` keeps its own exact-lattice proof. |
|
||||||
| `ClassifyTile` | — | **T1.d trivial-tile reject.** Cooked/editor recipes now build the exact recipe stack and call its proved `ClassifyBox`; a tile crossing a slot/gap, any diff, or any disturbance returns `Mixed`. This replaces the old global `HasComposerRecipeOverride()` force-to-Mixed. Native cave/surface/gap proofs and all §8.10 caches remain unchanged. |
|
| `ClassifyTile` | — | **T1.d trivial-tile reject. ⛔ Not called by streaming by default since 2026-09-13** (`voxel.OuterClassifierMode=0`; net-negative in game, see ARCHITECTURE §8.10). Cooked/editor recipes now build the exact recipe stack and call its proved `ClassifyBox`; a tile crossing a slot/gap, any diff, or any disturbance returns `Mixed`. This replaces the old global `HasComposerRecipeOverride()` force-to-Mixed. Native cave/surface/gap proofs and all §8.10 caches remain unchanged. |
|
||||||
| `SampleRelief` / `SampleMoisture` | — | Climate fields (pure XY, [0,1]). Relief = shared source of truth for the relief map M. §8.14. |
|
| `SampleRelief` / `SampleMoisture` | — | Climate fields (pure XY, [0,1]). Relief = shared source of truth for the relief map M. §8.14. |
|
||||||
| `SampleBiomeAt` | — | Warped-Voronoi + climate biome query (dominant + neighbour + weight). Reference used by the preview bake + `GetDominantBiomeAt`. §8.14. |
|
| `SampleBiomeAt` | — | Warped-Voronoi + climate biome query (dominant + neighbour + weight). Reference used by the preview bake + `GetDominantBiomeAt`. §8.14. |
|
||||||
| `ResolveBiomeSampleAt` / `RebuildBiomeGrid` | — | Hot-path biome resolve (FBiomeSample) via a box-validated per-chunk cell-grid cache. Bit-identical to `SampleBiomeAt`. §8.14, §8.10. |
|
| `ResolveBiomeSampleAt` / `RebuildBiomeGrid` | — | Hot-path biome resolve (FBiomeSample) via a box-validated per-chunk cell-grid cache. Bit-identical to `SampleBiomeAt`. §8.14, §8.10. |
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ namespace
|
|||||||
FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels, int32 Step, int32 InCellsPerAxis,
|
FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels, int32 Step, int32 InCellsPerAxis,
|
||||||
TArray<uint8>* OutCaptureGrid,
|
TArray<uint8>* OutCaptureGrid,
|
||||||
int32 BandZMinVox, int32 BandZMaxVox,
|
int32 BandZMinVox, int32 BandZMaxVox,
|
||||||
const FVoxelMesherCoreDensityGrid* ReusableCoreGrid,
|
|
||||||
int64* OutDensitySampleCount)
|
int64* OutDensitySampleCount)
|
||||||
{
|
{
|
||||||
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_MesherGenerateMesh);
|
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_MesherGenerateMesh);
|
||||||
@@ -230,13 +229,6 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
const int32 CellsPerAxis = FMath::Clamp(InCellsPerAxis, 2, CHUNK_SIZE); // coarse tiles use fewer
|
const int32 CellsPerAxis = FMath::Clamp(InCellsPerAxis, 2, CHUNK_SIZE); // coarse tiles use fewer
|
||||||
const int32 GridDim = CellsPerAxis + 1;
|
const int32 GridDim = CellsPerAxis + 1;
|
||||||
const int32 MDim = GridDim + 2; // +1 marge de chaque côté
|
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
|
// COUPE DE CONTENU PAR STRATE — restreint le maillage (et l'échantillonnage) aux cellules
|
||||||
// dont l'intervalle Z chevauche la bande [BandZMinVox, BandZMaxVox] (voxels inclusifs).
|
// dont l'intervalle Z chevauche la bande [BandZMinVox, BandZMaxVox] (voxels inclusifs).
|
||||||
// Les tuiles à capture ne sont jamais bandées (niveau 0 — garde-fou ci-dessous).
|
// Les tuiles à capture ne sont jamais bandées (niveau 0 — garde-fou ci-dessous).
|
||||||
@@ -541,7 +533,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
// gradient) sont échantillonnées — le reste du tampon reste non initialisé et non lu.
|
// gradient) sont échantillonnées — le reste du tampon reste non initialisé et non lu.
|
||||||
VoxelDensityProfile::FScopedTimer MesherDensityGridTimer(
|
VoxelDensityProfile::FScopedTimer MesherDensityGridTimer(
|
||||||
VoxelDensityProfile::EBucket::MesherDensityGrid);
|
VoxelDensityProfile::EBucket::MesherDensityGrid);
|
||||||
const bool bUseOperatorBlock = !bUseSharedDensityGrid && !bUseReusableCoreGrid
|
const bool bUseOperatorBlock = !bUseSharedDensityGrid
|
||||||
&& GVoxelForgeUseOperatorBlock != 0;
|
&& GVoxelForgeUseOperatorBlock != 0;
|
||||||
if (bUseOperatorBlock)
|
if (bUseOperatorBlock)
|
||||||
{
|
{
|
||||||
@@ -576,14 +568,6 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
DensityGrid[LocalIndex] = ReuseGrid->Samples[
|
DensityGrid[LocalIndex] = ReuseGrid->Samples[
|
||||||
(SharedZ * ReuseGrid->Dim + SharedY) * ReuseGrid->Dim + SharedX];
|
(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
|
else
|
||||||
{
|
{
|
||||||
// World voxel = tile origin + grid offset scaled by the cell size (Step).
|
// World voxel = tile origin + grid offset scaled by the cell size (Step).
|
||||||
|
|||||||
@@ -82,13 +82,12 @@ namespace
|
|||||||
GVoxelForgeUseBlockEarlyOut,
|
GVoxelForgeUseBlockEarlyOut,
|
||||||
TEXT("Use the optional nested 8x8x8 mesher classifier in streaming."));
|
TEXT("Use the optional nested 8x8x8 mesher classifier in streaming."));
|
||||||
|
|
||||||
// Outer classifier benchmark modes: 0 = disabled, 1 = current discarded exact validation,
|
// Outer classifier benchmark modes: 0 = disabled, 1 = current discarded exact validation.
|
||||||
// 2 = exact validation core reused by the mesher when the candidate fails validation.
|
int32 GVoxelForgeOuterClassifierMode = 0;
|
||||||
int32 GVoxelForgeOuterClassifierMode = 1;
|
|
||||||
FAutoConsoleVariableRef CVarVoxelForgeOuterClassifierMode(
|
FAutoConsoleVariableRef CVarVoxelForgeOuterClassifierMode(
|
||||||
TEXT("voxel.OuterClassifierMode"),
|
TEXT("voxel.OuterClassifierMode"),
|
||||||
GVoxelForgeOuterClassifierMode,
|
GVoxelForgeOuterClassifierMode,
|
||||||
TEXT("Outer tile classifier mode: 0 off, 1 discarded validation, 2 validation-core reuse."));
|
TEXT("Outer tile classifier mode: 0 off, 1 discarded exact validation."));
|
||||||
|
|
||||||
// Startup command-line cvars can arrive before the game world has spawned AVoxelWorld (or
|
// 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
|
// before its first tiles have become visible). Keep the diagnostic edit pending until Tick has
|
||||||
@@ -1058,7 +1057,6 @@ void AVoxelWorld::BeginPlay()
|
|||||||
TotalObsoleteWorkerTasks.store(0, std::memory_order_relaxed);
|
TotalObsoleteWorkerTasks.store(0, std::memory_order_relaxed);
|
||||||
TotalValidationDensityCalls.store(0, std::memory_order_relaxed);
|
TotalValidationDensityCalls.store(0, std::memory_order_relaxed);
|
||||||
TotalMesherDensityCalls.store(0, std::memory_order_relaxed);
|
TotalMesherDensityCalls.store(0, std::memory_order_relaxed);
|
||||||
TotalValidationCoreReuseTiles.store(0, std::memory_order_relaxed);
|
|
||||||
for (int32 LOD = 0; LOD < TrackedClassifierLODCount; ++LOD)
|
for (int32 LOD = 0; LOD < TrackedClassifierLODCount; ++LOD)
|
||||||
{
|
{
|
||||||
OuterClassifierCallsByLOD[LOD].store(0, std::memory_order_relaxed);
|
OuterClassifierCallsByLOD[LOD].store(0, std::memory_order_relaxed);
|
||||||
@@ -1098,7 +1096,7 @@ void AVoxelWorld::BeginPlay()
|
|||||||
GVoxelForgeOuterClassifierMode = CommandLineOuterClassifierMode;
|
GVoxelForgeOuterClassifierMode = CommandLineOuterClassifierMode;
|
||||||
}
|
}
|
||||||
GVoxelForgeUseBlockEarlyOut = GVoxelForgeUseBlockEarlyOut != 0 ? 1 : 0;
|
GVoxelForgeUseBlockEarlyOut = GVoxelForgeUseBlockEarlyOut != 0 ? 1 : 0;
|
||||||
GVoxelForgeOuterClassifierMode = FMath::Clamp(GVoxelForgeOuterClassifierMode, 0, 2);
|
GVoxelForgeOuterClassifierMode = FMath::Clamp(GVoxelForgeOuterClassifierMode, 0, 1);
|
||||||
|
|
||||||
if (GVoxelForgePerfAttribution != 0)
|
if (GVoxelForgePerfAttribution != 0)
|
||||||
{
|
{
|
||||||
@@ -2181,7 +2179,6 @@ void AVoxelWorld::LogStreamingLatencySummary() const
|
|||||||
"outer_classifier_mode=%d nested_block_early_out=%d "
|
"outer_classifier_mode=%d nested_block_early_out=%d "
|
||||||
"worker_generation_tasks=%llu worker_generation_sum_s=%.6f "
|
"worker_generation_tasks=%llu worker_generation_sum_s=%.6f "
|
||||||
"validation_density_calls=%llu mesher_density_calls=%llu "
|
"validation_density_calls=%llu mesher_density_calls=%llu "
|
||||||
"validation_core_reuse_tiles=%llu "
|
|
||||||
"obsolete_worker_tasks=%llu obsolete_worker_sum_s=%.6f "
|
"obsolete_worker_tasks=%llu obsolete_worker_sum_s=%.6f "
|
||||||
"outer_classifier_verdicts=%s"),
|
"outer_classifier_verdicts=%s"),
|
||||||
bDiagnosticsEnabled ? TEXT("profiled") : TEXT("clean"),
|
bDiagnosticsEnabled ? TEXT("profiled") : TEXT("clean"),
|
||||||
@@ -2197,7 +2194,6 @@ void AVoxelWorld::LogStreamingLatencySummary() const
|
|||||||
FPlatformTime::ToSeconds64(WorkerGenerationCycles),
|
FPlatformTime::ToSeconds64(WorkerGenerationCycles),
|
||||||
static_cast<unsigned long long>(TotalValidationDensityCalls.load(std::memory_order_relaxed)),
|
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>(TotalMesherDensityCalls.load(std::memory_order_relaxed)),
|
||||||
static_cast<unsigned long long>(TotalValidationCoreReuseTiles.load(std::memory_order_relaxed)),
|
|
||||||
static_cast<unsigned long long>(TotalObsoleteWorkerTasks.load(std::memory_order_relaxed)),
|
static_cast<unsigned long long>(TotalObsoleteWorkerTasks.load(std::memory_order_relaxed)),
|
||||||
FPlatformTime::ToSeconds64(ObsoleteWorkerCycles),
|
FPlatformTime::ToSeconds64(ObsoleteWorkerCycles),
|
||||||
*ClassifierVerdictSummary);
|
*ClassifierVerdictSummary);
|
||||||
@@ -3885,7 +3881,6 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
|
|||||||
Result.NumTriangles = 0;
|
Result.NumTriangles = 0;
|
||||||
Result.ValidationDensityCalls = 0;
|
Result.ValidationDensityCalls = 0;
|
||||||
Result.MesherDensityCalls = 0;
|
Result.MesherDensityCalls = 0;
|
||||||
Result.bValidationCoreReused = false;
|
|
||||||
Result.Streams.Reset();
|
Result.Streams.Reset();
|
||||||
Result.CaptureGrid.Reset();
|
Result.CaptureGrid.Reset();
|
||||||
Result.BandChunkLo = BandChunkLo; // strate content cut (MIN/MAX = uncut)
|
Result.BandChunkLo = BandChunkLo; // strate content cut (MIN/MAX = uncut)
|
||||||
@@ -3917,10 +3912,7 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
|
|||||||
// même pour les cellules uniformes, et ces tuiles sont rares (fenêtre d'ombre).
|
// 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.)
|
// (Gate IsoLevel == 0 : les verdicts du classifieur supposent l'iso MC à zéro exactement.)
|
||||||
bool bTrivialEmpty = false;
|
bool bTrivialEmpty = false;
|
||||||
FVoxelMesherCoreDensityGrid ValidationCoreGrid;
|
const int32 OuterClassifierMode = FMath::Clamp(GVoxelForgeOuterClassifierMode, 0, 1);
|
||||||
const FVoxelMesherCoreDensityGrid* ReusableCoreGrid = nullptr;
|
|
||||||
const int32 OuterClassifierMode = FMath::Clamp(GVoxelForgeOuterClassifierMode, 0, 2);
|
|
||||||
const bool bUseValidationCoreReuse = OuterClassifierMode == 2;
|
|
||||||
if (OuterClassifierMode != 0
|
if (OuterClassifierMode != 0
|
||||||
&& !bSheetTile && !bWantCapture && Generator && Mesher && Mesher->IsoLevel == 0.0f)
|
&& !bSheetTile && !bWantCapture && Generator && Mesher && Mesher->IsoLevel == 0.0f)
|
||||||
{
|
{
|
||||||
@@ -3950,19 +3942,9 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
|
|||||||
TGuardValue<int32> ValidationSampleStepGuard(
|
TGuardValue<int32> ValidationSampleStepGuard(
|
||||||
VoxelGenLOD::SampleStep, ValidationStep);
|
VoxelGenLOD::SampleStep, ValidationStep);
|
||||||
|
|
||||||
if (bUseValidationCoreReuse)
|
|
||||||
{
|
|
||||||
ValidationCoreGrid.OriginVoxels = OriginVoxels;
|
|
||||||
ValidationCoreGrid.Step = ValidationStep;
|
|
||||||
ValidationCoreGrid.CellsPerAxis = ValidationCells;
|
|
||||||
const int32 ValidationDim = ValidationCells + 1;
|
|
||||||
ValidationCoreGrid.Samples.SetNumUninitialized(
|
|
||||||
ValidationDim * ValidationDim * ValidationDim);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool bExactUniform = true;
|
bool bExactUniform = true;
|
||||||
for (int32 Z = 0; Z <= ValidationCells
|
for (int32 Z = 0; Z <= ValidationCells
|
||||||
&& (bUseValidationCoreReuse || bExactUniform) && !bValidationAborted; ++Z)
|
&& bExactUniform && !bValidationAborted; ++Z)
|
||||||
{
|
{
|
||||||
if (ShouldAbortWork(ObsoleteFlag))
|
if (ShouldAbortWork(ObsoleteFlag))
|
||||||
{
|
{
|
||||||
@@ -3970,7 +3952,7 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
for (int32 Y = 0; Y <= ValidationCells
|
for (int32 Y = 0; Y <= ValidationCells
|
||||||
&& (bUseValidationCoreReuse || bExactUniform) && !bValidationAborted; ++Y)
|
&& bExactUniform && !bValidationAborted; ++Y)
|
||||||
{
|
{
|
||||||
for (int32 X = 0; X <= ValidationCells; ++X)
|
for (int32 X = 0; X <= ValidationCells; ++X)
|
||||||
{
|
{
|
||||||
@@ -3984,26 +3966,17 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
|
|||||||
OriginVoxels.Y + Y * ValidationStep,
|
OriginVoxels.Y + Y * ValidationStep,
|
||||||
OriginVoxels.Z + Z * ValidationStep);
|
OriginVoxels.Z + Z * ValidationStep);
|
||||||
++Result.ValidationDensityCalls;
|
++Result.ValidationDensityCalls;
|
||||||
if (bUseValidationCoreReuse)
|
|
||||||
{
|
|
||||||
const int32 ValidationDim = ValidationCells + 1;
|
|
||||||
ValidationCoreGrid.Samples[
|
|
||||||
(Z * ValidationDim + Y) * ValidationDim + X] = Density;
|
|
||||||
}
|
|
||||||
const bool bSampleAgrees = FMath::IsFinite(Density) && Density != 0.0f
|
const bool bSampleAgrees = FMath::IsFinite(Density) && Density != 0.0f
|
||||||
&& (Verdict != EVoxelTileClass::AllSolid || Density < 0.0f)
|
&& (Verdict != EVoxelTileClass::AllSolid || Density < 0.0f)
|
||||||
&& (Verdict != EVoxelTileClass::AllAir || Density > 0.0f);
|
&& (Verdict != EVoxelTileClass::AllAir || Density > 0.0f);
|
||||||
if (!bSampleAgrees)
|
if (!bSampleAgrees)
|
||||||
{
|
{
|
||||||
bExactUniform = false;
|
bExactUniform = false;
|
||||||
if (!bUseValidationCoreReuse)
|
|
||||||
{
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
TotalValidationDensityCalls.fetch_add(
|
TotalValidationDensityCalls.fetch_add(
|
||||||
static_cast<uint64>(Result.ValidationDensityCalls), std::memory_order_relaxed);
|
static_cast<uint64>(Result.ValidationDensityCalls), std::memory_order_relaxed);
|
||||||
if (!bExactUniform)
|
if (!bExactUniform)
|
||||||
@@ -4011,12 +3984,6 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
|
|||||||
// A false Mixed costs meshing; a false uniform verdict removes both render and
|
// A false Mixed costs meshing; a false uniform verdict removes both render and
|
||||||
// collision geometry. Fall back to the safe path whenever one exact vertex disagrees.
|
// collision geometry. Fall back to the safe path whenever one exact vertex disagrees.
|
||||||
ValidatedVerdict = EVoxelTileClass::Mixed;
|
ValidatedVerdict = EVoxelTileClass::Mixed;
|
||||||
if (bUseValidationCoreReuse && !bValidationAborted)
|
|
||||||
{
|
|
||||||
ReusableCoreGrid = &ValidationCoreGrid;
|
|
||||||
Result.bValidationCoreReused = true;
|
|
||||||
TotalValidationCoreReuseTiles.fetch_add(1, std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (bValidationAborted)
|
if (bValidationAborted)
|
||||||
@@ -4067,7 +4034,7 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
|
|||||||
HoleMinX, HoleMinY, HoleMaxX, HoleMaxY)
|
HoleMinX, HoleMinY, HoleMaxX, HoleMaxY)
|
||||||
: Mesher->GenerateMesh(OriginVoxels, Step, Cells,
|
: Mesher->GenerateMesh(OriginVoxels, Step, Cells,
|
||||||
bWantCapture ? &Result.CaptureGrid : nullptr,
|
bWantCapture ? &Result.CaptureGrid : nullptr,
|
||||||
BandVoxLo, BandVoxHi, ReusableCoreGrid,
|
BandVoxLo, BandVoxHi,
|
||||||
&Result.MesherDensityCalls);
|
&Result.MesherDensityCalls);
|
||||||
TotalMesherDensityCalls.fetch_add(
|
TotalMesherDensityCalls.fetch_add(
|
||||||
static_cast<uint64>(Result.MesherDensityCalls), std::memory_order_relaxed);
|
static_cast<uint64>(Result.MesherDensityCalls), std::memory_order_relaxed);
|
||||||
|
|||||||
@@ -38,8 +38,6 @@ struct FVoxelSharedDensityGrid
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FVoxelMesherCoreDensityGrid;
|
|
||||||
|
|
||||||
UCLASS(BlueprintType)
|
UCLASS(BlueprintType)
|
||||||
class VOXELFORGE_API UVoxelMarchingCubesMesher : public UObject
|
class VOXELFORGE_API UVoxelMarchingCubesMesher : public UObject
|
||||||
{
|
{
|
||||||
@@ -72,7 +70,6 @@ public:
|
|||||||
FVoxelMeshData GenerateMesh(FIntVector OriginVoxels, int32 Step = 1, int32 CellsPerAxis = CHUNK_SIZE,
|
FVoxelMeshData GenerateMesh(FIntVector OriginVoxels, int32 Step = 1, int32 CellsPerAxis = CHUNK_SIZE,
|
||||||
TArray<uint8>* OutCaptureGrid = nullptr,
|
TArray<uint8>* OutCaptureGrid = nullptr,
|
||||||
int32 BandZMinVox = INT32_MIN, int32 BandZMaxVox = INT32_MAX,
|
int32 BandZMinVox = INT32_MIN, int32 BandZMaxVox = INT32_MAX,
|
||||||
const FVoxelMesherCoreDensityGrid* ReusableCoreGrid = nullptr,
|
|
||||||
int64* OutDensitySampleCount = nullptr);
|
int64* OutDensitySampleCount = nullptr);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -166,22 +163,3 @@ public:
|
|||||||
// Réduction d'octaves sur les tuiles grossières ; 0 = désactivé.
|
// Réduction d'octaves sur les tuiles grossières ; 0 = désactivé.
|
||||||
int32 LODOctaveDrop = 0;
|
int32 LODOctaveDrop = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// A per-tile core lattice produced by the outer classifier's exact validation. Unlike
|
|
||||||
// FVoxelSharedDensityGrid this is intentionally local to one GenerateMesh call: validation can
|
|
||||||
// run concurrently on several streaming workers without mutating shared mesher state.
|
|
||||||
struct FVoxelMesherCoreDensityGrid
|
|
||||||
{
|
|
||||||
FIntVector OriginVoxels = FIntVector::ZeroValue;
|
|
||||||
int32 Step = 1;
|
|
||||||
int32 CellsPerAxis = 0;
|
|
||||||
TArray<float> Samples;
|
|
||||||
|
|
||||||
void Reset()
|
|
||||||
{
|
|
||||||
OriginVoxels = FIntVector::ZeroValue;
|
|
||||||
Step = 1;
|
|
||||||
CellsPerAxis = 0;
|
|
||||||
Samples.Reset();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -111,7 +111,6 @@ struct FChunkResult
|
|||||||
// count exact validation and mesher density calls locally, then publish one aggregate update.
|
// count exact validation and mesher density calls locally, then publish one aggregate update.
|
||||||
int64 ValidationDensityCalls = 0;
|
int64 ValidationDensityCalls = 0;
|
||||||
int64 MesherDensityCalls = 0;
|
int64 MesherDensityCalls = 0;
|
||||||
bool bValidationCoreReused = false;
|
|
||||||
bool bAborted = false; // Worker observed shutdown or cancellation; never mark this tile loaded
|
bool bAborted = false; // Worker observed shutdown or cancellation; never mark this tile loaded
|
||||||
bool bObsolete = false; // Worker observed per-tile desired-set cancellation
|
bool bObsolete = false; // Worker observed per-tile desired-set cancellation
|
||||||
uint32 DesiredEpoch = 0; // Desired-set epoch captured at request time
|
uint32 DesiredEpoch = 0; // Desired-set epoch captured at request time
|
||||||
@@ -929,7 +928,6 @@ public:
|
|||||||
std::atomic<uint64> TotalObsoleteWorkerTasks{0};
|
std::atomic<uint64> TotalObsoleteWorkerTasks{0};
|
||||||
std::atomic<uint64> TotalValidationDensityCalls{0};
|
std::atomic<uint64> TotalValidationDensityCalls{0};
|
||||||
std::atomic<uint64> TotalMesherDensityCalls{0};
|
std::atomic<uint64> TotalMesherDensityCalls{0};
|
||||||
std::atomic<uint64> TotalValidationCoreReuseTiles{0};
|
|
||||||
std::atomic<uint64> OuterClassifierCallsByLOD[TrackedClassifierLODCount]{};
|
std::atomic<uint64> OuterClassifierCallsByLOD[TrackedClassifierLODCount]{};
|
||||||
std::atomic<uint64> OuterClassifierVerdictsByLOD[TrackedClassifierLODCount][3]{};
|
std::atomic<uint64> OuterClassifierVerdictsByLOD[TrackedClassifierLODCount][3]{};
|
||||||
|
|
||||||
|
|||||||
+12
-2
@@ -28,6 +28,10 @@ The player falls through the world. There is **no invariant** that a tile you oc
|
|||||||
**Do**: record cook-complete; gate the pawn (spawn and movement) on the supporting tile being
|
**Do**: record cook-complete; gate the pawn (spawn and movement) on the supporting tile being
|
||||||
collidable, not merely applied.
|
collidable, not merely applied.
|
||||||
|
|
||||||
|
✅ **Owner playtest, 2026-09-13: he can no longer fall through the world.** The gate uses RMC's
|
||||||
|
`SetCollisionConfig()` future as the authority (`HandleTileCollisionCookComplete`,
|
||||||
|
`IsPlayerSupportCollisionReady`).
|
||||||
|
|
||||||
📖 **Reading `Plugins/RealtimeMeshComponent` is permitted for this item — READ ONLY.** Find whether a
|
📖 **Reading `Plugins/RealtimeMeshComponent` is permitted for this item — READ ONLY.** Find whether a
|
||||||
cook-complete delegate/flag exists. ⛔ No edits, no builds against it: the owner's copy carries custom
|
cook-complete delegate/flag exists. ⛔ No edits, no builds against it: the owner's copy carries custom
|
||||||
modifications and was destroyed once by an over-broad delete.
|
modifications and was destroyed once by an over-broad delete.
|
||||||
@@ -125,8 +129,14 @@ figure and does ~18% fewer density calls. It cannot remove geometry, since it sk
|
|||||||
*disproved* (`VoxelWorld.cpp` ~4012), and validation disproved none: 0 reuse tiles in every run. Mixed
|
*disproved* (`VoxelWorld.cpp` ~4012), and validation disproved none: 0 reuse tiles in every run. Mixed
|
||||||
tiles, the ones that actually need a grid, never run validation, so there is nothing to reuse.
|
tiles, the ones that actually need a grid, never run validation, so there is nothing to reuse.
|
||||||
|
|
||||||
**Recommendation, pending the owner:** set `GVoxelForgeOuterClassifierMode` default to 0. It stays
|
**Done 2026-09-13, owner approved:** `GVoxelForgeOuterClassifierMode` now defaults to 0, and mode 2
|
||||||
switchable by `voxel.OuterClassifierMode`.
|
is deleted along with its reuse grid, its mesher parameter and its counter. Mode 1 is still
|
||||||
|
reachable with `voxel.OuterClassifierMode=1` as an A/B instrument. Verified:
|
||||||
|
- export OBJ SHA-256 unchanged (`b3e5f4c3..9b377`)
|
||||||
|
- a run without the override prints `outer_classifier_mode=0 validation_density_calls=0`
|
||||||
|
- **841/841 tiles have identical triangle counts between mode 0 and mode 1**. The 394 tiles mode 1
|
||||||
|
skipped as uniform produce 0 triangles in mode 0, so the strate band cut was not hiding geometry
|
||||||
|
behind the classifier.
|
||||||
|
|
||||||
⚠️ **Correction to `f204413`.** Its message says the nested classifier's 3.5x "cannot be reproduced".
|
⚠️ **Correction to `f204413`.** Its message says the nested classifier's 3.5x "cannot be reproduced".
|
||||||
That was my measurement error: I passed `-voxel.UseBlockEarlyOut=1` (the STREAMING console variable)
|
That was my measurement error: I passed `-voxel.UseBlockEarlyOut=1` (the STREAMING console variable)
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ So: normals can cost as much as the entire density grid; a SurfaceWorld chunk do
|
|||||||
**T1.c — Collision only where it matters.** `bShouldCreateCollision = (LOD == 0)`. Distant chunks are unreachable by definition (if the player got there, they'd be LOD0 — and the LOD reconciliation loop §8.10 guarantees a hot-swap on approach). Kills Chaos cooking + collision memory for the large majority of loaded chunks. Also check RMC's async-collision setting is on. **Likely the best win-per-line-changed in the whole document.**
|
**T1.c — Collision only where it matters.** `bShouldCreateCollision = (LOD == 0)`. Distant chunks are unreachable by definition (if the player got there, they'd be LOD0 — and the LOD reconciliation loop §8.10 guarantees a hot-swap on approach). Kills Chaos cooking + collision memory for the large majority of loaded chunks. Also check RMC's async-collision setting is on. **Likely the best win-per-line-changed in the whole document.**
|
||||||
|
|
||||||
**T1.d — Chunk classification: skip trivially solid/air chunks before sampling.** ✅ DONE 2026-07-05
|
**T1.d — Chunk classification: skip trivially solid/air chunks before sampling.** ✅ DONE 2026-07-05
|
||||||
|
⛔ **Turned OFF in streaming 2026-09-13.** After the operator stack, it cost more than it saved on the
|
||||||
|
game path (WORK-NEXT.md A/B). Do not re-propose tile classification without a design that is
|
||||||
|
cheaper than meshing the tile.
|
||||||
(v2 — `UVoxelGenerator::ClassifyTile`, see ARCHITECTURE §8.10; a 2026-06-26 v1 with a global ceiling
|
(v2 — `UVoxelGenerator::ClassifyTile`, see ARCHITECTURE §8.10; a 2026-06-26 v1 with a global ceiling
|
||||||
bound was reverted for roof holes. Trigger: trace showed 84 % of GenerateMesh calls produced empty
|
bound was reverted for roof holes. Trigger: trace showed 84 % of GenerateMesh calls produced empty
|
||||||
tiles.) In a tall multi-strate world most chunks in the desired set are full bedrock or full sky. Conservative per-chunk test before the 33³ sample: for heightfield strates, min/max terrain over the footprint (free from T1.a's column grid ± noise amplitude bound) vs the chunk's Z range; for cave strates, "no room/tunnel/passage/spine/seal/diff-layer bounds intersect" (all bounding data already exists). Fully-solid/air ⇒ empty MeshData, no component, done. Cuts whole chunks, not percentages — compounds with everything else.
|
tiles.) In a tall multi-strate world most chunks in the desired set are full bedrock or full sky. Conservative per-chunk test before the 33³ sample: for heightfield strates, min/max terrain over the footprint (free from T1.a's column grid ± noise amplitude bound) vs the chunk's Z range; for cave strates, "no room/tunnel/passage/spine/seal/diff-layer bounds intersect" (all bounding data already exists). Fully-solid/air ⇒ empty MeshData, no component, done. Cuts whole chunks, not percentages — compounds with everything else.
|
||||||
|
|||||||
Reference in new issue
Block a user