feat(classify): fold cave boundary categories safely
This commit is contained in:
@@ -4455,3 +4455,90 @@ four-way split of `Cave Bail Not Op Stack` showed `SoleSlot` and `BoundaryTile`
|
||||
proved the assets were correctly ticked (Jahni said so twice; I doubted it twice) and pointed at
|
||||
`NoLayout`, a cause nobody had considered. *A counter that can fire for two reasons is not a
|
||||
measurement.*
|
||||
|
||||
## 2026-08-17 (v) — mixed-content split + guarded category fold LANDED, awaiting Jahni's build
|
||||
|
||||
**Nothing in this entry was built, compiled, run in the editor, automation-tested, or measured.**
|
||||
The only validation was static source/diff inspection and `git diff --check`. All rates below are
|
||||
instructions for the next flight, not claimed results.
|
||||
|
||||
### The old 0.77 bucket is split before changing its guard
|
||||
|
||||
`Cave Bail Mixed Content` had three call sites and four meanings. It is replaced by:
|
||||
|
||||
- `Cave Mix Out Of Layout` — a tile contains one cave slot plus constant-air outside the layout;
|
||||
- `Cave Mix Gap` — one cave slot plus solid inter-strate bedrock;
|
||||
- `Cave Mix Surface World` — one cave slot plus a SurfaceWorld slot;
|
||||
- `Cave Bail Two Cave Slots` — a second cave slot was found; this remains an immediate `Mixed` bail.
|
||||
|
||||
The first three are deliberately **presence counters**, incremented once per present category per
|
||||
tile before the later stack guards. They are not exclusive for an unusually tall tile, so they are
|
||||
not expected to sum to the old 0.77; each answers one unambiguous yes/no question. This placement is
|
||||
why the diagnostic survives the fold: a tile can increment `Cave Mix Out Of Layout` and later be
|
||||
proved `AllAir`. `Two Cave Slots` is still a bail counter because that condition was not relaxed.
|
||||
|
||||
The split was applied first with the original `return Mixed` intact. Only after that
|
||||
behavior-preserving split was inspected was the return deliberately replaced by the fold below.
|
||||
|
||||
### Decision: relax one-category, NOT one-cave-slot
|
||||
|
||||
Folding is safe, but only in the narrow form suggested by the code:
|
||||
|
||||
- Gap already contributes a proved `solid` hypothesis, out-of-layout a proved `air` hypothesis,
|
||||
and SurfaceWorld classifies its exact mesher lattice through `ComputeSurfaceColumn`.
|
||||
- The one cave slot records the minimum/maximum **sampled cave Z**. `ClassifyBox` receives that cave
|
||||
Z sub-box with the tile's full XY extent; asking it about the previous full tile box would model
|
||||
gap/surface/out-of-layout with cave params and is not valid.
|
||||
- Opt-in, generator type, and bit-identical params are still checked for **every chunk intersecting
|
||||
that cave sub-box in XYZ**. They were narrowed in Z only to exclude categories represented by
|
||||
their own hypotheses; none was removed or weakened.
|
||||
- The stack verdict folds into the existing `bCanSolid` / `bCanAir`, then SurfaceWorld's exact
|
||||
column scan still runs. `Tiles Operator Stack Solid/Air` now increments only for the final
|
||||
non-`Mixed` tile verdict, after all categories have agreed.
|
||||
|
||||
The independent **one-cave-slot requirement cannot be relaxed by this fold**. A second cave slot can
|
||||
carry a different archetype, opt-in, params, and stack; proving it would require building and
|
||||
guarding a second stack, not stretching the first. `Cave Bail Two Cave Slots` therefore still
|
||||
returns `Mixed`. Category folding is compatible with the generator-type and bit-identical-param
|
||||
guards precisely because those guards remain exhaustive over the sole cave sub-box.
|
||||
|
||||
`Cave Bail Fold Conflict` was added for a proved cave verdict that contradicts an already proved
|
||||
category (for example cave=solid plus out-of-layout=air). That is distinct from `Cave Bail Stack
|
||||
Verdict` (the stack itself could not prove its box) and `Cave Bail Disturbance` (a post-stack feature
|
||||
killed the remaining hypothesis).
|
||||
|
||||
### Exact next-build / next-flight readout
|
||||
|
||||
Read these counters together on the same underground route:
|
||||
|
||||
1. `Cave Mix Out Of Layout`, `Cave Mix Gap`, `Cave Mix Surface World`, and
|
||||
`Cave Bail Two Cave Slots` — whichever is largest names the dominant old mixed-content cause.
|
||||
The first three are presence counts and may overlap; `Cave Bail Mixed Content` should be absent.
|
||||
2. `Tiles Operator Stack Air` and `Tiles Operator Stack Solid` — either becoming non-zero proves a
|
||||
cave stack participated in a **final** skip verdict. For the measured cave+open-air population,
|
||||
the expected success signal is primarily `Tiles Operator Stack Air`.
|
||||
3. `Tiles Skipped All Air`, `Tiles Skipped All Solid`, `Tiles Meshed`, and `Tiles Classified` — the
|
||||
accounting must still close: `Meshed + SkippedAllAir + SkippedAllSolid = Classified`. A fold win
|
||||
raises a skipped counter and lowers `Tiles Meshed`; the pre-fold baseline was 1.15 meshed,
|
||||
0.73 skipped-air, 1.88 classified.
|
||||
4. `Cave Bail Fold Conflict`, `Cave Bail Stack Verdict`, `Cave Bail Params`,
|
||||
`Cave Bail Disturbance`, and `Cave Bail Two Cave Slots` — these explain mixed tiles that remain.
|
||||
In particular, a high `Fold Conflict` means the dominant mix is genuinely heterogeneous rather
|
||||
than blocked merely by the former one-category policy.
|
||||
|
||||
After Jahni runs the suite, every brute-force `violations` count must remain zero and all eight
|
||||
operator-stack equivalences must remain bit-identical. Those are correctness requirements, not
|
||||
results from this unbuilt change.
|
||||
|
||||
### Likely compile-error / review watch spots
|
||||
|
||||
- declaration/definition/use spelling for the five new UE stats, especially
|
||||
`STAT_VoxelForgeCaveBailFoldConflict` and `STAT_VoxelForgeCaveBailTwoCaveSlots`;
|
||||
- the `MAX_int32` / `MIN_int32` cave-extrema initializers and `FMath::Min/Max(int32)` assignments to
|
||||
`CaveMinZ` / `CaveMaxZ`;
|
||||
- construction of `FBox CaveBox` from the cave-only integer Z extrema;
|
||||
- control flow after removing the cave branch's final return: SurfaceWorld columns must run before
|
||||
the shared final verdict, and `bAnyCave` must imply initialized cave extrema (established in the
|
||||
same `MemoCat == 2` iteration);
|
||||
- no protected layout accessor was introduced: the code still uses public
|
||||
`GetStrateChunkZBounds`, never `FindSlotIndexForChunkZ`.
|
||||
|
||||
@@ -2725,8 +2725,8 @@ EVoxelTileClass UVoxelGenerator::ClassifyTile(const FIntVector& OriginVoxels, in
|
||||
if (CX * CX + CY * CY <= Reach * Reach) bCanSolid = false;
|
||||
}
|
||||
|
||||
// ── Catégorisation par Z de treillis. v1 : gap bedrock = solide ; SurfaceWorld = test
|
||||
// colonne ; tout le reste (intérieurs de caves, hors layout) = Mixed immédiat. ──
|
||||
// ── Catégorisation par Z du treillis : gap bedrock = solide ; hors layout = air constant ;
|
||||
// SurfaceWorld = test colonne ; un slot cave opt-in = verdict de pile sur sa sous-boîte. ──
|
||||
struct FSurfSlot
|
||||
{
|
||||
int32 BotChunkZ = INT32_MAX; // identité du slot (borne basse de la strate, en chunks)
|
||||
@@ -2753,13 +2753,19 @@ EVoxelTileClass UVoxelGenerator::ClassifyTile(const FIntVector& OriginVoxels, in
|
||||
int32 NumSlots = 0;
|
||||
|
||||
// ── T1.d GÉNÉRIQUE : la pile d'opérateurs classe les archétypes de CAVE ──
|
||||
// Ces trois-là suivent le slot de cave que la tuile touche. Le verdict de la pile porte sur la
|
||||
// BOÎTE ENTIÈRE, pas sur un z, donc il ne peut être calculé qu'après la boucle — et il n'est
|
||||
// valable que si la tuile ne touche QUE ce slot-là (voir la garde `bAnyNonCave`).
|
||||
// Ces valeurs suivent l'UNIQUE slot de cave que la tuile touche. La pile classera seulement la
|
||||
// sous-boîte Z de ses échantillons ; les catégories gap/surface/hors-layout plient séparément
|
||||
// leurs hypothèses dans `bCanSolid` / `bCanAir`.
|
||||
// These values track the ONE cave slot touched by the tile. The stack classifies only its
|
||||
// sampled Z sub-box; gap/surface/out-of-layout fold their hypotheses separately.
|
||||
int32 CaveBotChunkZ = INT32_MAX; // identité du slot de cave (borne basse, en chunks)
|
||||
int32 CaveRepChunkZ = 0;
|
||||
int32 CaveMinZ = MAX_int32;
|
||||
int32 CaveMaxZ = MIN_int32;
|
||||
bool bAnyCave = false;
|
||||
bool bAnyNonCave = false; // gap ou SurfaceWorld dans la même tuile ⇒ on abandonne
|
||||
bool bAnyGap = false;
|
||||
bool bAnySurface = false;
|
||||
bool bAnyOutOfLayout = false;
|
||||
|
||||
int32 MemoChunkZ = INT32_MAX;
|
||||
int32 MemoCat = -1; // 0 = gap, 1 = surface, 2 = cave (pile), 3 = hors layout
|
||||
@@ -2783,7 +2789,7 @@ EVoxelTileClass UVoxelGenerator::ClassifyTile(const FIntVector& OriginVoxels, in
|
||||
if (StrateManager->IsGapChunk(CC))
|
||||
{
|
||||
MemoCat = 0;
|
||||
bAnyNonCave = true;
|
||||
bAnyGap = true;
|
||||
}
|
||||
//=================================================================
|
||||
// ⛔ HORS LAYOUT = AIR CONSTANT. C'ÉTAIT LE BLOCAGE DE T1.d.
|
||||
@@ -2809,7 +2815,7 @@ EVoxelTileClass UVoxelGenerator::ClassifyTile(const FIntVector& OriginVoxels, in
|
||||
else if (!VF_ChunkZHasSlot(ChunkZ))
|
||||
{
|
||||
MemoCat = 3;
|
||||
bAnyNonCave = true; // n'entre JAMAIS dans la branche de cave
|
||||
bAnyOutOfLayout = true;
|
||||
|
||||
// Les disturbances sont appliquées APRÈS la densité d'archétype et peuvent AJOUTER
|
||||
// de la roche (ponts, arêtes). Même prudence que les branches gap et cave : si
|
||||
@@ -2861,7 +2867,7 @@ EVoxelTileClass UVoxelGenerator::ClassifyTile(const FIntVector& OriginVoxels, in
|
||||
if (D.BridgeDensity > 0.0f || D.RidgeDensity > 0.0f) bCanAir = false;
|
||||
}
|
||||
MemoCat = 1;
|
||||
bAnyNonCave = true;
|
||||
bAnySurface = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2909,12 +2915,12 @@ EVoxelTileClass UVoxelGenerator::ClassifyTile(const FIntVector& OriginVoxels, in
|
||||
int32 CaveTopCZ = 0, CaveBotCZ = 0;
|
||||
if (!StrateManager->GetStrateChunkZBounds(ChunkZ, CaveTopCZ, CaveBotCZ))
|
||||
{
|
||||
INC_DWORD_STAT(STAT_VoxelForgeCaveBailMixedContent);
|
||||
INC_DWORD_STAT(STAT_VoxelForgeCaveMixOutOfLayout);
|
||||
return EVoxelTileClass::Mixed; // hors layout
|
||||
}
|
||||
if (CaveBotChunkZ != INT32_MAX && CaveBotChunkZ != CaveBotCZ)
|
||||
{
|
||||
INC_DWORD_STAT(STAT_VoxelForgeCaveBailMixedContent);
|
||||
INC_DWORD_STAT(STAT_VoxelForgeCaveBailTwoCaveSlots);
|
||||
return EVoxelTileClass::Mixed;
|
||||
}
|
||||
CaveBotChunkZ = CaveBotCZ;
|
||||
@@ -2926,7 +2932,10 @@ EVoxelTileClass UVoxelGenerator::ClassifyTile(const FIntVector& OriginVoxels, in
|
||||
|
||||
if (MemoCat == 2)
|
||||
{
|
||||
// Rien par z : la pile répond pour la boîte entière, après la boucle.
|
||||
// La pile répond après la boucle, sur la sous-boîte Z contenant exactement les
|
||||
// échantillons cave (XY reste la boîte complète du treillis).
|
||||
CaveMinZ = FMath::Min(CaveMinZ, Zi);
|
||||
CaveMaxZ = FMath::Max(CaveMaxZ, Zi);
|
||||
}
|
||||
else if (MemoCat == 0)
|
||||
{
|
||||
@@ -2970,23 +2979,27 @@ EVoxelTileClass UVoxelGenerator::ClassifyTile(const FIntVector& OriginVoxels, in
|
||||
// DENSITÉ de cette tuile — même fabrique, mêmes params, même drapeau.
|
||||
if (bAnyCave)
|
||||
{
|
||||
// Une tuile mi-cave mi-surface (ou mi-gap) n'est pas classable ainsi : la pile de cave ne
|
||||
// répond que pour SA strate, et sa boîte couvrirait des z appartenant à une autre.
|
||||
if (bAnyNonCave)
|
||||
// Diagnostic de PRÉSENCE avant les gardes : le signal reste visible même si le pliage rend
|
||||
// finalement AllSolid/AllAir. Ces compteurs ne sont pas exclusifs entre eux sur une tuile
|
||||
// très haute ; chacun répond exactement à « cette catégorie était-elle aussi présente ? ».
|
||||
// Presence diagnostics run before the guards, so a successful fold cannot hide the mix.
|
||||
// They are not mutually exclusive for a very tall tile; each answers one exact question.
|
||||
if (bAnyOutOfLayout || bAnyGap || bAnySurface)
|
||||
{
|
||||
INC_DWORD_STAT(STAT_VoxelForgeCaveBailMixedContent);
|
||||
return EVoxelTileClass::Mixed;
|
||||
if (bAnyOutOfLayout) { INC_DWORD_STAT(STAT_VoxelForgeCaveMixOutOfLayout); }
|
||||
if (bAnyGap) { INC_DWORD_STAT(STAT_VoxelForgeCaveMixGap); }
|
||||
if (bAnySurface) { INC_DWORD_STAT(STAT_VoxelForgeCaveMixSurfaceWorld); }
|
||||
}
|
||||
|
||||
const FIntVector RepCC(0, 0, CaveRepChunkZ);
|
||||
const ECaveGeneratorType CaveType = StrateManager->GetGeneratorTypeForChunk(RepCC);
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// ⚠️ LA GARDE QUI COMPTE : LES PARAMS DOIVENT ÊTRE LES MÊMES SUR TOUTE LA TUILE
|
||||
// ⚠️ LA GARDE QUI COMPTE : LES PARAMS DOIVENT ÊTRE LES MÊMES SUR TOUTE LA SOUS-BOÎTE CAVE
|
||||
//---------------------------------------------------------------------
|
||||
// `GetGenerationParams` et ses homologues BLENDENT les params dans les bandes de transition :
|
||||
// `Alpha` dépend du chunk Z pour `Gradient`, et du chunk XY EN PLUS pour `Interleaved`. Deux
|
||||
// chunks d'une même tuile peuvent donc porter des params différents — c'est le constat de
|
||||
// chunks d'une même sous-boîte peuvent donc porter des params différents — c'est le constat de
|
||||
// `AUDIT §C2`, confirmé par lecture le 2026-07-28 — et UNE pile ne peut pas représenter DEUX
|
||||
// champs. On construit donc les params pour CHAQUE coordonnée de chunk que la boîte touche et
|
||||
// on exige qu'ils soient identiques bit à bit.
|
||||
@@ -2995,7 +3008,12 @@ EVoxelTileClass UVoxelGenerator::ClassifyTile(const FIntVector& OriginVoxels, in
|
||||
// `Mixed` de trop. On se trompe du côté du CPU, jamais du côté du trou.
|
||||
const int32 CX0 = FloorDivC(MinX, CHUNK_SIZE), CX1 = FloorDivC(MaxX, CHUNK_SIZE);
|
||||
const int32 CY0 = FloorDivC(MinY, CHUNK_SIZE), CY1 = FloorDivC(MaxY, CHUNK_SIZE);
|
||||
const int32 CZ0 = FloorDivC(MinZ, CHUNK_SIZE), CZ1 = FloorDivC(MaxZ, CHUNK_SIZE);
|
||||
// IMPORTANT : les gardes restent complètes, mais seulement sur les chunks où le mesher
|
||||
// échantillonne réellement CETTE strate cave. Inclure gap/surface/hors-layout ici ferait
|
||||
// échouer la garde d'archétype avant de pouvoir plier leurs hypothèses indépendantes.
|
||||
// The guards stay exhaustive over the cave samples. Non-cave chunks are intentionally not
|
||||
// represented by this stack; their hypotheses were folded separately in the Z pass.
|
||||
const int32 CZ0 = FloorDivC(CaveMinZ, CHUNK_SIZE), CZ1 = FloorDivC(CaveMaxZ, CHUNK_SIZE);
|
||||
|
||||
// Une tuile très étalée (Step élevé) toucherait trop de chunks pour que cette vérification
|
||||
// reste bon marché. Au-delà, `Mixed` — on renonce au gain, jamais à la sûreté.
|
||||
@@ -3098,7 +3116,7 @@ EVoxelTileClass UVoxelGenerator::ClassifyTile(const FIntVector& OriginVoxels, in
|
||||
}
|
||||
default:
|
||||
INC_DWORD_STAT(STAT_VoxelForgeCaveBailParams);
|
||||
return EVoxelTileClass::Mixed; // SurfaceWorld ne peut pas arriver ici (bAnyNonCave)
|
||||
return EVoxelTileClass::Mixed; // SurfaceWorld ne peut pas être le type du slot cave
|
||||
}
|
||||
|
||||
bFirst = false;
|
||||
@@ -3134,9 +3152,9 @@ EVoxelTileClass UVoxelGenerator::ClassifyTile(const FIntVector& OriginVoxels, in
|
||||
}
|
||||
TileStack.PrepareChunk(OpCtx);
|
||||
|
||||
const FBox TileBox(FVector((float)MinX, (float)MinY, (float)MinZ),
|
||||
FVector((float)MaxX, (float)MaxY, (float)MaxZ));
|
||||
const EVoxelTileClass StackVerdict = TileStack.ClassifyBox(TileBox, OpCtx);
|
||||
const FBox CaveBox(FVector((float)MinX, (float)MinY, (float)CaveMinZ),
|
||||
FVector((float)MaxX, (float)MaxY, (float)CaveMaxZ));
|
||||
const EVoxelTileClass StackVerdict = TileStack.ClassifyBox(CaveBox, OpCtx);
|
||||
if (StackVerdict == EVoxelTileClass::Mixed)
|
||||
{
|
||||
INC_DWORD_STAT(STAT_VoxelForgeCaveBailStackVerdict);
|
||||
@@ -3146,6 +3164,17 @@ EVoxelTileClass UVoxelGenerator::ClassifyTile(const FIntVector& OriginVoxels, in
|
||||
if (StackVerdict == EVoxelTileClass::AllSolid) { bCanAir = false; }
|
||||
else { bCanSolid = false; }
|
||||
|
||||
// Le verdict cave se plie avec gap=solide, hors-layout=air, seals surface=solide. Si les
|
||||
// deux hypothèses sont mortes ici, les catégories se contredisent : ce n'est PAS un échec
|
||||
// de borne de la pile ni une disturbance.
|
||||
// Fold the cave verdict with gap=solid, out-of-layout=air, and solid surface seals. If both
|
||||
// hypotheses die here, the categories conflict; this is not a stack-bound/disturbance bail.
|
||||
if (!bCanSolid && !bCanAir)
|
||||
{
|
||||
INC_DWORD_STAT(STAT_VoxelForgeCaveBailFoldConflict);
|
||||
return EVoxelTileClass::Mixed;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// ⚠️ LES DISTURBANCES NE SONT PAS DANS LA PILE (`OPSTACK-DECOMPOSITION §10.2`) :
|
||||
// `GetDensityAt` les applique APRÈS, sur la densité déjà négatée. Un verdict qui les
|
||||
@@ -3156,20 +3185,11 @@ EVoxelTileClass UVoxelGenerator::ClassifyTile(const FIntVector& OriginVoxels, in
|
||||
if (D.ChasmDensity > 0.0f) { bCanSolid = false; }
|
||||
if (D.BridgeDensity > 0.0f || D.RidgeDensity > 0.0f) { bCanAir = false; }
|
||||
|
||||
if (bCanSolid == bCanAir)
|
||||
if (!bCanSolid && !bCanAir)
|
||||
{
|
||||
INC_DWORD_STAT(STAT_VoxelForgeCaveBailDisturbance);
|
||||
return EVoxelTileClass::Mixed;
|
||||
}
|
||||
if (bCanSolid)
|
||||
{
|
||||
INC_DWORD_STAT(STAT_VoxelForgeTilesOpStackSolid);
|
||||
}
|
||||
else
|
||||
{
|
||||
INC_DWORD_STAT(STAT_VoxelForgeTilesOpStackAir);
|
||||
}
|
||||
return bCanSolid ? EVoxelTileClass::AllSolid : EVoxelTileClass::AllAir;
|
||||
}
|
||||
|
||||
// ── Balayage des colonnes XY sur le treillis exact du mesher (marge incluse). Une colonne
|
||||
@@ -3228,6 +3248,15 @@ EVoxelTileClass UVoxelGenerator::ClassifyTile(const FIntVector& OriginVoxels, in
|
||||
// Ici exactement UNE hypothèse doit survivre (chaque point testé en tue une ; les tuiles
|
||||
// sans point intérieur ont tué AllAir via gap/seal). Égalité = prudence → Mixed.
|
||||
if (bCanSolid == bCanAir) return EVoxelTileClass::Mixed;
|
||||
if (bAnyCave)
|
||||
{
|
||||
// Compte seulement les verdicts FINAUX qui sautent réellement une tuile. Une pile peut avoir
|
||||
// prouvé sa sous-boîte cave puis perdre l'hypothèse sur une colonne SurfaceWorld adjacente.
|
||||
// Count only final verdicts that actually skip a tile; a later surface column may still
|
||||
// invalidate the hypothesis proved for the cave sub-box.
|
||||
if (bCanSolid) { INC_DWORD_STAT(STAT_VoxelForgeTilesOpStackSolid); }
|
||||
else { INC_DWORD_STAT(STAT_VoxelForgeTilesOpStackAir); }
|
||||
}
|
||||
return bCanSolid ? EVoxelTileClass::AllSolid : EVoxelTileClass::AllAir;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,9 +14,13 @@ DEFINE_STAT(STAT_VoxelForgeCaveBailNotOpStackSoleSlot);
|
||||
DEFINE_STAT(STAT_VoxelForgeCaveBailNotOpStackBoundaryTile);
|
||||
DEFINE_STAT(STAT_VoxelForgeCaveBailNotOpStackNoLayout);
|
||||
DEFINE_STAT(STAT_VoxelForgeCaveBailNotOpStackRecheck);
|
||||
DEFINE_STAT(STAT_VoxelForgeCaveBailMixedContent);
|
||||
DEFINE_STAT(STAT_VoxelForgeCaveMixOutOfLayout);
|
||||
DEFINE_STAT(STAT_VoxelForgeCaveMixGap);
|
||||
DEFINE_STAT(STAT_VoxelForgeCaveMixSurfaceWorld);
|
||||
DEFINE_STAT(STAT_VoxelForgeCaveBailTwoCaveSlots);
|
||||
DEFINE_STAT(STAT_VoxelForgeCaveBailParams);
|
||||
DEFINE_STAT(STAT_VoxelForgeCaveBailStackVerdict);
|
||||
DEFINE_STAT(STAT_VoxelForgeCaveBailFoldConflict);
|
||||
DEFINE_STAT(STAT_VoxelForgeCaveBailDisturbance);
|
||||
DEFINE_STAT(STAT_VoxelForgeCaveBailNoStack);
|
||||
DEFINE_STAT(STAT_VoxelForgeColumnMemoHit);
|
||||
|
||||
@@ -313,12 +313,12 @@ public:
|
||||
* (OriginVoxels, Step, CellsPerAxis) = les MÊMES arguments que GenerateMesh ; le verdict
|
||||
* porte sur le treillis exact que le mesher échantillonnerait (marge ±1 incluse).
|
||||
*
|
||||
* v1 : ne prouve que les chunks GAP (bedrock) et les strates SurfaceWorld — colonnes
|
||||
* terrain/plafond évaluées par le MÊME ComputeSurfaceColumn que le chemin densité (donc
|
||||
* bit-identiques), bandes de seal solides, gardes spine/passages/disturbances/diff.
|
||||
* Tout autre archétype (intérieur de caves) ⇒ Mixed. Worker-safe (lecture seule +
|
||||
* caches thread_local partagés avec GetDensityAt — un verdict Mixed laisse les colonnes
|
||||
* chaudes pour la génération qui suit).
|
||||
* GAP (bedrock), hors-layout (air constant) et SurfaceWorld plient leurs hypothèses par Z ;
|
||||
* SurfaceWorld évalue ses colonnes terrain/plafond avec le MÊME ComputeSurfaceColumn que le
|
||||
* chemin densité. Un unique slot cave opt-in peut ajouter le verdict conservatif de sa pile,
|
||||
* sous gardes d'archétype et de params bit-identiques. Spine/passages/disturbances/diff restent
|
||||
* des gardes conservatrices. Worker-safe (lecture seule + caches thread_local partagés avec
|
||||
* GetDensityAt — un verdict Mixed laisse les colonnes chaudes pour la génération qui suit).
|
||||
*/
|
||||
EVoxelTileClass ClassifyTile(const FIntVector& OriginVoxels, int32 Step, int32 CellsPerAxis) const;
|
||||
|
||||
|
||||
@@ -18,9 +18,13 @@ DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Cave Bail Not Op Stack Sole Slot"), STAT
|
||||
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 Mix Out Of Layout"), STAT_VoxelForgeCaveMixOutOfLayout, STATGROUP_VoxelForge, VOXELFORGE_API);
|
||||
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Cave Mix Gap"), STAT_VoxelForgeCaveMixGap, STATGROUP_VoxelForge, VOXELFORGE_API);
|
||||
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Cave Mix Surface World"), STAT_VoxelForgeCaveMixSurfaceWorld, STATGROUP_VoxelForge, VOXELFORGE_API);
|
||||
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Cave Bail Two Cave Slots"), STAT_VoxelForgeCaveBailTwoCaveSlots, 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 Fold Conflict"), STAT_VoxelForgeCaveBailFoldConflict, STATGROUP_VoxelForge, VOXELFORGE_API);
|
||||
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Cave Bail Disturbance"), STAT_VoxelForgeCaveBailDisturbance, STATGROUP_VoxelForge, VOXELFORGE_API);
|
||||
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Cave Bail No Stack"), STAT_VoxelForgeCaveBailNoStack, STATGROUP_VoxelForge, VOXELFORGE_API);
|
||||
DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Column Memo Hits"), STAT_VoxelForgeColumnMemoHit, STATGROUP_VoxelForge, VOXELFORGE_API);
|
||||
|
||||
Reference in New Issue
Block a user