feat(stats): stat VoxelForge -- make tile skipping and the column memo observable
The plugin had ZERO stat counters, so every claim this refactor makes was harness-only: "skipped correctly" and "skipped nothing" render identically. CODEX-TASK-001 and -002, executed by Codex (gpt-5.6-luna xhigh), reviewed against the source. Eight DWORD counters in a new stat group: GenerateTileResult -- TilesClassified / SkippedAllSolid / SkippedAllAir / Meshed ClassifyTile bAnyCave exit -- TilesOpStackSolid / TilesOpStackAir FSurfaceColumnSource::GetColumn -- ColumnMemoHit / ColumnMemoMiss The op-stack counters are separate from the lumped skip counters on purpose: ClassifyTile also proves AllSolid on its hand-written bedrock-gap path with no strate opted in, so the lumped number cannot show a before/after. The op-stack pair is zero BY CONSTRUCTION until a strate ticks the flag. GetColumn is instrumented and deliberately NOT fixed -- the instrument and the fix in one build would make each other unreadable. Verified against UE_5.7 Stats.h rather than assumed (first stats use in this plugin, no in-repo precedent): all four macro arities, and INC_DWORD_STAT -> FThreadStats::AddMessage, so the worker-thread safety both sites need holds by mechanism. Not built -- Jahni builds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -79,6 +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, and operator-stack verdicts. |
|
||||||
|
|
||||||
### 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 |
|
||||||
|
|||||||
@@ -3430,3 +3430,70 @@ nothing** — deliberately, so the instrument and the fix cannot land in the sam
|
|||||||
other unreadable. A negative result is specified as a real result.
|
other unreadable. A negative result is specified as a real result.
|
||||||
|
|
||||||
`CODEX-TASK-002-column-memo-thrash.md`. **Nothing was built or run.**
|
`CODEX-TASK-002-column-memo-thrash.md`. **Nothing was built or run.**
|
||||||
|
|
||||||
|
## 2026-08-16 (c) — Codex delivered 001 + 002. Reviewed against the code. READY TO BUILD.
|
||||||
|
|
||||||
|
First tandem round actually executed by me rather than handed over: Codex (`gpt-5.6-luna`, xhigh)
|
||||||
|
ran both specs, I reviewed the diffs against the source rather than against its reports.
|
||||||
|
|
||||||
|
### What landed
|
||||||
|
|
||||||
|
`stat VoxelForge` — the plugin's **first stat group ever** (`grep INC_DWORD_STAT` used to return
|
||||||
|
nothing). New `Public/VoxelStats.h` + `Private/VoxelStats.cpp`, eight `DWORD_COUNTER`s:
|
||||||
|
|
||||||
|
| counter | site |
|
||||||
|
|---|---|
|
||||||
|
| `TilesClassified` / `TilesSkippedAllSolid` / `TilesSkippedAllAir` / `TilesMeshed` | `AVoxelWorld::GenerateTileResult` |
|
||||||
|
| `TilesOpStackSolid` / `TilesOpStackAir` | `UVoxelGenerator::ClassifyTile`, the `bAnyCave` exit |
|
||||||
|
| `ColumnMemoHit` / `ColumnMemoMiss` | `FSurfaceColumnSource::GetColumn` |
|
||||||
|
|
||||||
|
### The review — what was checked, not what was reported
|
||||||
|
|
||||||
|
- **Site A's verdict refactor is contract-identical.** `bTrivialEmpty = (Verdict != Mixed)` where
|
||||||
|
`Verdict` is the stored return. The gate's five clauses are untouched. That bool decides whether a
|
||||||
|
tile has **collision**, so this was checked character by character rather than read.
|
||||||
|
- **Site B increments AFTER the `bCanSolid == bCanAir` bail-out**, and the pair follows `bCanSolid`
|
||||||
|
the same way the returned enum does — the swap that would compile, look plausible, and prove the
|
||||||
|
wrong thing did not happen.
|
||||||
|
- **`GetColumn` is instrumented and NOT fixed**, which was the whole point of specifying 002 that
|
||||||
|
way. Table size, hash and the full key comparison are byte-identical; miss inside the `if`, hit in
|
||||||
|
the `else`, neither derived by subtracting from the other.
|
||||||
|
- **Every `INC_DWORD_STAT` body is braced.** The macro expands to a braced block, so an unbraced
|
||||||
|
`if` would have left a stray `;` and broken the following `else`.
|
||||||
|
|
||||||
|
### The macro arities were VERIFIED, not assumed
|
||||||
|
|
||||||
|
This is the plugin's first use of the stats system, so there was no in-repo precedent to match and
|
||||||
|
the usual "likely compile spot" would have been a guess. Read out of
|
||||||
|
`UE_5.7/Engine/Source/Runtime/Core/Public/Stats/Stats.h` instead:
|
||||||
|
|
||||||
|
- `DECLARE_STATS_GROUP(GroupDesc, GroupId, GroupCat)` — 3 args ✔
|
||||||
|
- `DECLARE_DWORD_COUNTER_STAT_EXTERN(CounterName, StatId, GroupId, API)` — 4 args ✔
|
||||||
|
- `DEFINE_STAT(Stat)` ✔ · `INC_DWORD_STAT(Stat)` ✔
|
||||||
|
- `INC_DWORD_STAT` → **`FThreadStats::AddMessage`**, i.e. per-thread stat packets ⇒ **the
|
||||||
|
worker-thread invariant holds by mechanism**, not by hope. Both sites are on workers.
|
||||||
|
- `Stats/Stats.h` includes `CoreGlobals.h` + `CoreTypes.h` itself ⇒ `VoxelStats.h` is self-
|
||||||
|
sufficient and IWYU-clean as a first include (it is, in `VoxelStats.cpp`).
|
||||||
|
- `VOXELFORGE_API` needs no include — UBT defines it on the command line.
|
||||||
|
- `VoxelForge.Build.cs` does not enumerate sources, so no build-script change is needed.
|
||||||
|
|
||||||
|
### READY TO BUILD — and what to read afterwards
|
||||||
|
|
||||||
|
Remaining compile risk is low and concentrated in `VoxelStats.h`/`.cpp` (identifier mismatch between
|
||||||
|
the eight declarations and the eight definitions — checked by eye, they match).
|
||||||
|
|
||||||
|
Three readings, and **the order matters**:
|
||||||
|
|
||||||
|
1. **Baseline, nothing ticked, underground:** `TilesOpStackSolid` / `TilesOpStackAir` must be **0**.
|
||||||
|
They are zero by construction (the cave branch returns `Mixed` at `UsesOperatorStackForChunk`),
|
||||||
|
but observe it anyway or the next step proves nothing. `TilesSkippedAllSolid` **will** be non-zero
|
||||||
|
here — that is the pre-existing bedrock/surface skipping, not a bug.
|
||||||
|
2. **Tick `bUseOperatorStack` on ONE `TunnelNetwork` strate, same route:** `TilesOpStackSolid` goes
|
||||||
|
**non-zero**. ← the production proof of T1.d, which has never existed.
|
||||||
|
3. **A `SurfaceWorld` strate ticked:** report `ColumnMemoHit` **and** `ColumnMemoMiss`, both numbers.
|
||||||
|
Miss ≈ once per distinct column ⇒ my ~9× thrash derivation is **wrong**, the table is fine, and
|
||||||
|
the perf cost is the 19-virtual-calls suspect instead. Miss stuck at 20–30 % of lookups ⇒
|
||||||
|
confirmed, and the fix gets its own task with this run as its before.
|
||||||
|
|
||||||
|
Also still unbuilt and riding along: `e002bd4` (VerticalShafts connector capsules) —
|
||||||
|
`Box verdicts over 60 VerticalShafts tiles`, **0 is the number to beat**, `violations` must stay 0.
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
#include "VoxelTerrainOpDefinition.h" // ApplyTo — l'override d'op PAR SALLE (étape C1)
|
#include "VoxelTerrainOpDefinition.h" // ApplyTo — l'override d'op PAR SALLE (étape C1)
|
||||||
#include "VoxelStrateManager.h" // EvaluateModifierSDF / AnyPassageNearBox
|
#include "VoxelStrateManager.h" // EvaluateModifierSDF / AnyPassageNearBox
|
||||||
#include "VoxelTypes.h" // SmoothStep01, VOXEL_NOISE_SCALE
|
#include "VoxelTypes.h" // SmoothStep01, VOXEL_NOISE_SCALE
|
||||||
|
#include "VoxelStats.h"
|
||||||
|
|
||||||
#include <atomic> // l'id d'instance non recyclé du mémo de colonne
|
#include <atomic> // l'id d'instance non recyclé du mémo de colonne
|
||||||
|
|
||||||
@@ -641,6 +642,7 @@ namespace
|
|||||||
FSlot& S = Slots[Idx];
|
FSlot& S = Slots[Idx];
|
||||||
if (S.Key != ColumnKey || S.X != WorldX || S.Y != WorldY)
|
if (S.Key != ColumnKey || S.X != WorldX || S.Y != WorldY)
|
||||||
{
|
{
|
||||||
|
INC_DWORD_STAT(STAT_VoxelForgeColumnMemoMiss);
|
||||||
S.Key = ColumnKey; S.X = WorldX; S.Y = WorldY;
|
S.Key = ColumnKey; S.X = WorldX; S.Y = WorldY;
|
||||||
FColumn& C = S.C;
|
FColumn& C = S.C;
|
||||||
|
|
||||||
@@ -700,6 +702,10 @@ namespace
|
|||||||
if (Slope > KINDA_SMALL_NUMBER) { C.DirX = GX / Slope; C.DirY = GY / Slope; }
|
if (Slope > KINDA_SMALL_NUMBER) { C.DirX = GX / Slope; C.DirY = GY / Slope; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
INC_DWORD_STAT(STAT_VoxelForgeColumnMemoHit);
|
||||||
|
}
|
||||||
return S.C;
|
return S.C;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#include "VoxelDensityPrimitives.h" // spine / seal / passage — shared with the operator stack
|
#include "VoxelDensityPrimitives.h" // spine / seal / passage — shared with the operator stack
|
||||||
#include "VoxelDensityOpStack.h" // OPSTACK Phase 1: the opt-in per-strate operator stack
|
#include "VoxelDensityOpStack.h" // OPSTACK Phase 1: the opt-in per-strate operator stack
|
||||||
#include "VoxelHeightOp.h" // IVoxelBiomeField — the adapter below implements it
|
#include "VoxelHeightOp.h" // IVoxelBiomeField — the adapter below implements it
|
||||||
|
#include "VoxelStats.h"
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
// L'ADAPTATEUR DE CHAMP DE BIOMES / THE BIOME FIELD ADAPTER
|
// L'ADAPTATEUR DE CHAMP DE BIOMES / THE BIOME FIELD ADAPTER
|
||||||
@@ -3007,6 +3008,14 @@ EVoxelTileClass UVoxelGenerator::ClassifyTile(const FIntVector& OriginVoxels, in
|
|||||||
if (D.BridgeDensity > 0.0f || D.RidgeDensity > 0.0f) { bCanAir = false; }
|
if (D.BridgeDensity > 0.0f || D.RidgeDensity > 0.0f) { bCanAir = false; }
|
||||||
|
|
||||||
if (bCanSolid == bCanAir) { return EVoxelTileClass::Mixed; }
|
if (bCanSolid == bCanAir) { return EVoxelTileClass::Mixed; }
|
||||||
|
if (bCanSolid)
|
||||||
|
{
|
||||||
|
INC_DWORD_STAT(STAT_VoxelForgeTilesOpStackSolid);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
INC_DWORD_STAT(STAT_VoxelForgeTilesOpStackAir);
|
||||||
|
}
|
||||||
return bCanSolid ? EVoxelTileClass::AllSolid : EVoxelTileClass::AllAir;
|
return bCanSolid ? EVoxelTileClass::AllSolid : EVoxelTileClass::AllAir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// VoxelStats.cpp
|
||||||
|
// Definitions for the VoxelForge runtime statistics.
|
||||||
|
// Définitions des statistiques runtime de VoxelForge.
|
||||||
|
|
||||||
|
#include "VoxelStats.h"
|
||||||
|
|
||||||
|
DEFINE_STAT(STAT_VoxelForgeTilesClassified);
|
||||||
|
DEFINE_STAT(STAT_VoxelForgeTilesSkippedAllSolid);
|
||||||
|
DEFINE_STAT(STAT_VoxelForgeTilesSkippedAllAir);
|
||||||
|
DEFINE_STAT(STAT_VoxelForgeTilesMeshed);
|
||||||
|
DEFINE_STAT(STAT_VoxelForgeTilesOpStackSolid);
|
||||||
|
DEFINE_STAT(STAT_VoxelForgeTilesOpStackAir);
|
||||||
|
DEFINE_STAT(STAT_VoxelForgeColumnMemoHit);
|
||||||
|
DEFINE_STAT(STAT_VoxelForgeColumnMemoMiss);
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "VoxelTerrainOpDefinition.h"
|
#include "VoxelTerrainOpDefinition.h"
|
||||||
#include "VoxelContentManager.h"
|
#include "VoxelContentManager.h"
|
||||||
#include "VoxelDensityVolume.h"
|
#include "VoxelDensityVolume.h"
|
||||||
|
#include "VoxelStats.h"
|
||||||
// IWYU (FPSemantics = Precise ⇒ plus de PCH partagé) : GetPlayerPosition déréférence le pawn, donc
|
// IWYU (FPSemantics = Precise ⇒ plus de PCH partagé) : GetPlayerPosition déréférence le pawn, donc
|
||||||
// APawn doit être COMPLET — `Casts.h` n'en donne qu'une déclaration avant. APlayerController était
|
// APawn doit être COMPLET — `Casts.h` n'en donne qu'une déclaration avant. APlayerController était
|
||||||
// complet par transitivité seulement : on l'inclut explicitement, c'est exactement la fragilité
|
// complet par transitivité seulement : on l'inclut explicitement, c'est exactement la fragilité
|
||||||
@@ -1502,11 +1503,24 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
|
|||||||
if (!bSheetTile && !bWantCapture && Generator && Mesher && Mesher->IsoLevel == 0.0f)
|
if (!bSheetTile && !bWantCapture && Generator && Mesher && Mesher->IsoLevel == 0.0f)
|
||||||
{
|
{
|
||||||
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_ClassifyTile);
|
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_ClassifyTile);
|
||||||
bTrivialEmpty = (Generator->ClassifyTile(OriginVoxels, Step, Cells) != EVoxelTileClass::Mixed);
|
INC_DWORD_STAT(STAT_VoxelForgeTilesClassified);
|
||||||
|
const EVoxelTileClass Verdict = Generator->ClassifyTile(OriginVoxels, Step, Cells);
|
||||||
|
if (Verdict == EVoxelTileClass::AllSolid)
|
||||||
|
{
|
||||||
|
INC_DWORD_STAT(STAT_VoxelForgeTilesSkippedAllSolid);
|
||||||
|
}
|
||||||
|
else if (Verdict == EVoxelTileClass::AllAir)
|
||||||
|
{
|
||||||
|
INC_DWORD_STAT(STAT_VoxelForgeTilesSkippedAllAir);
|
||||||
|
}
|
||||||
|
bTrivialEmpty = (Verdict != EVoxelTileClass::Mixed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// F18 — feuille : deux heightfields sol/cap échantillonnés par colonne (pas de marching
|
// F18 — feuille : deux heightfields sol/cap échantillonnés par colonne (pas de marching
|
||||||
// cubes, pas de classifieur — la classe de surface est vraie par construction).
|
// cubes, pas de classifieur — la classe de surface est vraie par construction).
|
||||||
|
// `TilesMeshed` peut dépasser `TilesClassified` : les tuiles qui ratent cette porte sont
|
||||||
|
// maillées sans classification. / `TilesMeshed` may exceed `TilesClassified`: tiles that
|
||||||
|
// fail this gate are meshed without classification.
|
||||||
FVoxelMeshData MeshData;
|
FVoxelMeshData MeshData;
|
||||||
if (!bTrivialEmpty)
|
if (!bTrivialEmpty)
|
||||||
{
|
{
|
||||||
@@ -1517,6 +1531,7 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
|
|||||||
: Mesher->GenerateMesh(OriginVoxels, Step, Cells,
|
: Mesher->GenerateMesh(OriginVoxels, Step, Cells,
|
||||||
bWantCapture ? &Result.CaptureGrid : nullptr,
|
bWantCapture ? &Result.CaptureGrid : nullptr,
|
||||||
BandVoxLo, BandVoxHi);
|
BandVoxLo, BandVoxHi);
|
||||||
|
INC_DWORD_STAT(STAT_VoxelForgeTilesMeshed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// T1.f — build the RMC geometry buffers HERE (worker), not on the game thread. Empty/all-air
|
// T1.f — build the RMC geometry buffers HERE (worker), not on the game thread. Empty/all-air
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// VoxelStats.h
|
||||||
|
// Per-frame runtime counters for tile classification and meshing.
|
||||||
|
// Compteurs runtime par frame pour la classification et le meshing des tuiles.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Stats/Stats.h"
|
||||||
|
|
||||||
|
DECLARE_STATS_GROUP(TEXT("VoxelForge"), STATGROUP_VoxelForge, STATCAT_Advanced);
|
||||||
|
|
||||||
|
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Tiles Classified"), STAT_VoxelForgeTilesClassified, STATGROUP_VoxelForge, VOXELFORGE_API);
|
||||||
|
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Tiles Skipped All Solid"), STAT_VoxelForgeTilesSkippedAllSolid, STATGROUP_VoxelForge, VOXELFORGE_API);
|
||||||
|
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Tiles Skipped All Air"), STAT_VoxelForgeTilesSkippedAllAir, 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 Air"), STAT_VoxelForgeTilesOpStackAir, STATGROUP_VoxelForge, VOXELFORGE_API);
|
||||||
|
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Column Memo Hits"), STAT_VoxelForgeColumnMemoHit, STATGROUP_VoxelForge, VOXELFORGE_API);
|
||||||
|
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Column Memo Misses"), STAT_VoxelForgeColumnMemoMiss, STATGROUP_VoxelForge, VOXELFORGE_API);
|
||||||
Reference in New Issue
Block a user