diagnostics(opstack): separate disabled-slot bail causes
The old Not Op Stack counter fired before slot attribution, so interior disabled strates and boundary encounters produced the same number. Split the instrumentation into sole-slot, boundary-tile, unresolved-layout, and late-recheck counters while preserving every ClassifyTile condition, return point, and verdict.
This commit is contained in:
+2
-2
@@ -79,7 +79,7 @@ Paths relative to `Source/VoxelForge/`. `Public/` = headers, `Private/` = impl.
|
|||||||
| `../../VoxelForge.uplugin` | Plugin manifest. One Runtime module `VoxelForge`. Beta. |
|
| `../../VoxelForge.uplugin` | Plugin manifest. One Runtime module `VoxelForge`. Beta. |
|
||||||
| `VoxelForge.Build.cs` | Deps: Core, CoreUObject, Engine, **GameplayTags**, **RealtimeMeshComponent**. |
|
| `VoxelForge.Build.cs` | Deps: Core, CoreUObject, Engine, **GameplayTags**, **RealtimeMeshComponent**. |
|
||||||
| `Public/VoxelForgeModule.h` / `Private/VoxelForgeModule.cpp` | `FVoxelForgeModule` boilerplate (Startup/Shutdown just log). |
|
| `Public/VoxelForgeModule.h` / `Private/VoxelForgeModule.cpp` | `FVoxelForgeModule` boilerplate (Startup/Shutdown just log). |
|
||||||
| `Public/VoxelStats.h` / `Private/VoxelStats.cpp` | `stat VoxelForge` DWORD counters for tile classification, skipping, meshing, operator-stack verdicts, and cave-bail diagnosis. |
|
| `Public/VoxelStats.h` / `Private/VoxelStats.cpp` | `stat VoxelForge` DWORD counters for tile classification, skipping, meshing, operator-stack verdicts, and cave-bail diagnosis. The former ambiguous `Cave Bail Not Op Stack` is split into `Sole Slot`, `Boundary Tile`, `No Layout`, and late `Recheck` counters, so each increment names one guard/context. |
|
||||||
|
|
||||||
### 3.2 Foundational types — `Public/VoxelTypes.h` (no UClass, everyone includes it)
|
### 3.2 Foundational types — `Public/VoxelTypes.h` (no UClass, everyone includes it)
|
||||||
| Symbol | Line | Notes |
|
| Symbol | Line | Notes |
|
||||||
@@ -268,7 +268,7 @@ redesign; tile identity lives in `FVoxelTileKey` (VoxelWorld.h).
|
|||||||
| `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** (worker, called by `LoadTile` before `GenerateMesh`): proves a tile AllSolid/AllAir on the mesher's exact lattice (gap chunks + SurfaceWorld columns via the SHARED `GSurfColCache`; seal bands; **cave archetypes via `FVoxelOpStack::ClassifyBox` when the strate opted in** — see §3.2d for the six guards, all failing to `Mixed`; guards: diff mods, passages, spine, disturbances, **F20 overhang** — a column point in `(TerrainZ, TerrainZ+OverhangMargin]` (margin = max `OverhangHeight`) is unprovable ⇒ Mixed, UPWARD only since the shelf union only ADDS rock above ground, so an overhang shelf never holes a trivially-skipped tile) → skip gen. Mixed = generate normally. §8.10. |
|
| `ClassifyTile` | — | **T1.d trivial-tile reject** (worker, called by `LoadTile` before `GenerateMesh`): proves a tile AllSolid/AllAir on the mesher's exact lattice (gap chunks + SurfaceWorld columns via the SHARED `GSurfColCache`; seal bands; **cave archetypes via `FVoxelOpStack::ClassifyBox` when the strate opted in** — see §3.2d for the six guards, all failing to `Mixed`; guards: diff mods, passages, spine, disturbances, **F20 overhang** — a column point in `(TerrainZ, TerrainZ+OverhangMargin]` (margin = max `OverhangHeight`) is unprovable ⇒ Mixed, UPWARD only since the shelf union only ADDS rock above ground, so an overhang shelf never holes a trivially-skipped tile) → skip gen. Mixed = generate normally. Its diagnostic-only not-op-stack bail attribution distinguishes a tile wholly inside the disabled slot, a boundary tile, and an unresolved layout; the classifier's conditions/returns are unchanged. §8.10. |
|
||||||
| `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. |
|
||||||
|
|||||||
@@ -2809,7 +2809,33 @@ EVoxelTileClass UVoxelGenerator::ClassifyTile(const FIntVector& OriginVoxels, in
|
|||||||
// même endroit, que `GetDensityAt`.
|
// même endroit, que `GetDensityAt`.
|
||||||
if (!StrateManager->UsesOperatorStackForChunk(CC))
|
if (!StrateManager->UsesOperatorStackForChunk(CC))
|
||||||
{
|
{
|
||||||
INC_DWORD_STAT(STAT_VoxelForgeCaveBailNotOpStack);
|
// Attribution DIAGNOSTIQUE uniquement : l'ancien compteur mélangeait une
|
||||||
|
// strate cave entièrement désactivée avec une tuile de frontière qui avait
|
||||||
|
// rencontré un slot désactivé avant la garde « slot différent » ci-dessous.
|
||||||
|
// On résout les bornes APRÈS l'échec du même prédicat ; elles ne changent ni
|
||||||
|
// la condition, ni le point de retour, ni le verdict.
|
||||||
|
// Diagnostic attribution only: the old counter mixed a wholly disabled cave
|
||||||
|
// slot with a boundary tile that met a disabled slot before the different-slot
|
||||||
|
// guard below. Resolve bounds only after the same predicate fails; classification
|
||||||
|
// control flow and return value stay unchanged.
|
||||||
|
int32 FailedTopCZ = 0, FailedBotCZ = 0;
|
||||||
|
if (!StrateManager->GetStrateChunkZBounds(ChunkZ, FailedTopCZ, FailedBotCZ))
|
||||||
|
{
|
||||||
|
INC_DWORD_STAT(STAT_VoxelForgeCaveBailNotOpStackNoLayout);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const int32 TileMinCZ = FloorDivC(MinZ, CHUNK_SIZE);
|
||||||
|
const int32 TileMaxCZ = FloorDivC(MaxZ, CHUNK_SIZE);
|
||||||
|
if (TileMinCZ >= FailedBotCZ && TileMaxCZ <= FailedTopCZ)
|
||||||
|
{
|
||||||
|
INC_DWORD_STAT(STAT_VoxelForgeCaveBailNotOpStackSoleSlot);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
INC_DWORD_STAT(STAT_VoxelForgeCaveBailNotOpStackBoundaryTile);
|
||||||
|
}
|
||||||
|
}
|
||||||
return EVoxelTileClass::Mixed;
|
return EVoxelTileClass::Mixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2929,7 +2955,12 @@ EVoxelTileClass UVoxelGenerator::ClassifyTile(const FIntVector& OriginVoxels, in
|
|||||||
// déclenché la tentative : un seul chunk hors pile invaliderait le verdict.
|
// déclenché la tentative : un seul chunk hors pile invaliderait le verdict.
|
||||||
if (!StrateManager->UsesOperatorStackForChunk(CC))
|
if (!StrateManager->UsesOperatorStackForChunk(CC))
|
||||||
{
|
{
|
||||||
INC_DWORD_STAT(STAT_VoxelForgeCaveBailNotOpStack);
|
// Le passage Z précédent a déjà accepté l'unique slot cave. Avec le layout actuel
|
||||||
|
// (prédicat indépendant de X/Y), ce recheck est redondant ; un hit nomme donc
|
||||||
|
// précisément cette garde tardive au lieu d'être agrégé aux opt-ins désactivés.
|
||||||
|
// The prior Z pass already accepted the sole cave slot. With the current X/Y-
|
||||||
|
// independent predicate this recheck is redundant, so attribute it separately.
|
||||||
|
INC_DWORD_STAT(STAT_VoxelForgeCaveBailNotOpStackRecheck);
|
||||||
return EVoxelTileClass::Mixed;
|
return EVoxelTileClass::Mixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ DEFINE_STAT(STAT_VoxelForgeTilesSkippedAllAir);
|
|||||||
DEFINE_STAT(STAT_VoxelForgeTilesMeshed);
|
DEFINE_STAT(STAT_VoxelForgeTilesMeshed);
|
||||||
DEFINE_STAT(STAT_VoxelForgeTilesOpStackSolid);
|
DEFINE_STAT(STAT_VoxelForgeTilesOpStackSolid);
|
||||||
DEFINE_STAT(STAT_VoxelForgeTilesOpStackAir);
|
DEFINE_STAT(STAT_VoxelForgeTilesOpStackAir);
|
||||||
DEFINE_STAT(STAT_VoxelForgeCaveBailNotOpStack);
|
DEFINE_STAT(STAT_VoxelForgeCaveBailNotOpStackSoleSlot);
|
||||||
|
DEFINE_STAT(STAT_VoxelForgeCaveBailNotOpStackBoundaryTile);
|
||||||
|
DEFINE_STAT(STAT_VoxelForgeCaveBailNotOpStackNoLayout);
|
||||||
|
DEFINE_STAT(STAT_VoxelForgeCaveBailNotOpStackRecheck);
|
||||||
DEFINE_STAT(STAT_VoxelForgeCaveBailMixedContent);
|
DEFINE_STAT(STAT_VoxelForgeCaveBailMixedContent);
|
||||||
DEFINE_STAT(STAT_VoxelForgeCaveBailParams);
|
DEFINE_STAT(STAT_VoxelForgeCaveBailParams);
|
||||||
DEFINE_STAT(STAT_VoxelForgeCaveBailStackVerdict);
|
DEFINE_STAT(STAT_VoxelForgeCaveBailStackVerdict);
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Tiles Skipped All Air"), STAT_VoxelForge
|
|||||||
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Tiles Meshed"), STAT_VoxelForgeTilesMeshed, STATGROUP_VoxelForge, VOXELFORGE_API);
|
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Tiles Meshed"), STAT_VoxelForgeTilesMeshed, STATGROUP_VoxelForge, VOXELFORGE_API);
|
||||||
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Tiles Operator Stack Solid"), STAT_VoxelForgeTilesOpStackSolid, STATGROUP_VoxelForge, VOXELFORGE_API);
|
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Tiles Operator Stack Solid"), STAT_VoxelForgeTilesOpStackSolid, STATGROUP_VoxelForge, VOXELFORGE_API);
|
||||||
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Tiles Operator Stack Air"), STAT_VoxelForgeTilesOpStackAir, STATGROUP_VoxelForge, VOXELFORGE_API);
|
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Tiles Operator Stack Air"), STAT_VoxelForgeTilesOpStackAir, STATGROUP_VoxelForge, VOXELFORGE_API);
|
||||||
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Cave Bail Not Op Stack"), STAT_VoxelForgeCaveBailNotOpStack, STATGROUP_VoxelForge, VOXELFORGE_API);
|
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Cave Bail Not Op Stack Sole Slot"), STAT_VoxelForgeCaveBailNotOpStackSoleSlot, STATGROUP_VoxelForge, VOXELFORGE_API);
|
||||||
|
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Cave Bail Not Op Stack Boundary Tile"), STAT_VoxelForgeCaveBailNotOpStackBoundaryTile, STATGROUP_VoxelForge, VOXELFORGE_API);
|
||||||
|
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Cave Bail Not Op Stack No Layout"), STAT_VoxelForgeCaveBailNotOpStackNoLayout, STATGROUP_VoxelForge, VOXELFORGE_API);
|
||||||
|
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Cave Bail Not Op Stack Recheck"), STAT_VoxelForgeCaveBailNotOpStackRecheck, STATGROUP_VoxelForge, VOXELFORGE_API);
|
||||||
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Cave Bail Mixed Content"), STAT_VoxelForgeCaveBailMixedContent, STATGROUP_VoxelForge, VOXELFORGE_API);
|
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Cave Bail Mixed Content"), STAT_VoxelForgeCaveBailMixedContent, STATGROUP_VoxelForge, VOXELFORGE_API);
|
||||||
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Cave Bail Params"), STAT_VoxelForgeCaveBailParams, STATGROUP_VoxelForge, VOXELFORGE_API);
|
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Cave Bail Params"), STAT_VoxelForgeCaveBailParams, STATGROUP_VoxelForge, VOXELFORGE_API);
|
||||||
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Cave Bail Stack Verdict"), STAT_VoxelForgeCaveBailStackVerdict, STATGROUP_VoxelForge, VOXELFORGE_API);
|
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Cave Bail Stack Verdict"), STAT_VoxelForgeCaveBailStackVerdict, STATGROUP_VoxelForge, VOXELFORGE_API);
|
||||||
|
|||||||
Reference in New Issue
Block a user