test(opstack): measure the box verdict on BOTH densities -- the fixture cannot prove a tile

The widened sampler worked: 39 of 40 tiles now land clear of the (0,0) spine.
The verdict did not move -- rooms still hit 40 of 40. By my own pre-registered
rule that means the room-lattice model was wrong, and it was, for a reason the
report had been printing at me for three runs: "= 7.6 x RoomSpacing 42".

RoomSpacing is 42 here, not the 80 I did the arithmetic with. I read the
UPROPERTY default out of the header instead of the fixture that overrides it.
Fifth premise this refactor that reversed on being checked, and the plainest:
the number was on screen.

EnableTunnelFeatures densifies on purpose -- RoomSpacing 80 -> 42, RoomDensity
0.35 -> 0.85 -- because at the defaults only 1.1% of samples were in open cave
and the equivalence compared solid rock to solid rock. That densification is what
makes check 1 mean anything, and it is exactly antagonistic to provability:

  room cull radius = max(R*1.5, R*HeightRatio) + 3*SDFBlendRadius
                   = 1.5R + 12  for R in [10,30]  =>  27..57, mean ~42
  lattice spacing  = 42 at 85% occupancy

The mean cull radius equals the lattice spacing, so cull spheres cover that world
~3.6x over and NO box can be outside all of them. "6.3 of 8.3 rooms reach" is not
a loose test, it is a saturated world. No sampler change and no tunnel tightening
can move it -- which is why widening the sampler correctly changed nothing.

So 0 proved on this fixture is the RIGHT answer, and informative: the prize
shrinks to nothing as caves saturate. It is simply not an answer about
production.

Check 4 is now a lambda run twice -- parameterised rather than copy-pasted,
because two copies of the criterion would drift and the second would lie:
[dense fixture] as before, expected to stay ~0 and now documented as correct;
[production defaults] the same 40 tiles against RoomSpacing 80 / RoomDensity
0.35, the real UVoxelStrateDefinition defaults. Both brute-forced voxel by voxel.
Not "widen it until it passes": the dense run stays in the report, must stay ~0,
and a false verdict in either still fails. The two stacks deliberately share
FRoomGraphSource's thread_local caches, so this run and check 3 now watch each
other through the params fingerprint in the key.

Unbuilt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-28 18:37:45 +02:00
parent f2fefade4c
commit 87a0b996ec
2 changed files with 313 additions and 171 deletions
+85
View File
@@ -2850,3 +2850,88 @@ tunnels.
`SpanCells` / `SpanVoxelsReported` / `NumTilesAwayFromSpine` are new locals hoisted **outside** the `SpanCells` / `SpanVoxelsReported` / `NumTilesAwayFromSpine` are new locals hoisted **outside** the
tile loop (the report line needs them); `P.OriginRoomRadius` and `P.RoomSpacing` are read in check 4. tile loop (the report line needs them); `P.OriginRoomRadius` and `P.RoomSpacing` are read in check 4.
## 2026-07-28 — the sampler fix worked, the prediction failed, and the fixture is the reason
The widened sampler did exactly what it was meant to: **39 of 40 tiles now land clear of the (0,0)
spine.** And the verdict did not move.
```
[before] rooms 40, tunnels 40 | 4.9 of 7.2 rooms, 69.2 of 80.4 tunnels (XY ±32)
[after ] rooms 40, tunnels 40 | 6.3 of 8.3 rooms, 78.0 of 87.7 tunnels (XY ±320, 39/40 off-spine)
```
My stated prediction was *"rooms should stop hitting every tile."* **They did not.** By my own
pre-registered rule that means the room-lattice model was wrong — and it was, for a reason printed in
the report I had been reading past for three runs:
> `= 7.6 x RoomSpacing 42`
**`RoomSpacing` is 42 here, not the 80 I computed with.** I read the `UPROPERTY` default out of the
header instead of the fixture, which overrides it. Fifth premise this refactor that reversed on being
checked, and the plainest one: *the number was on screen.*
### Why the fixture cannot ever prove a tile — arithmetic, not opinion
`EnableTunnelFeatures` densifies **on purpose**, and says so: `RoomSpacing` 80 → 42,
`RoomDensity` 0.35 → 0.85, because at the defaults the first run had **1.1 %** of samples in open
cave and the equivalence was comparing solid rock to solid rock. That densification is what makes
check 1 mean anything.
It is also exactly antagonistic to provability:
```
room cull radius = max(R·1.5, R·HeightRatio) + 3·SDFBlendRadius
= 1.5R + 12 for R ∈ [10,30] ⇒ 27 … 57, mean ≈ 42
lattice spacing = 42, occupancy 0.85
```
**The mean cull radius equals the lattice spacing.** Sphere volume over cell volume gives ~3.6×
redundant coverage — *no box in that world can be outside all room cull spheres.* `6.3 of 8.3 rooms
reach` is not a symptom of a loose test; it is the world being saturated. No sampler change and no
tightening of tunnels can move it, which is why widening the sampler correctly changed nothing.
**So `0 proved` on this fixture is the right answer**, and it is informative: the tile-skipping prize
shrinks to nothing as caves saturate. What it is *not* is an answer about production.
### Both worlds, one criterion
Check 4 is now a lambda run twice — **parameterised, not copy-pasted**, because two copies of the
criterion would drift and the second one would lie:
- **`[dense fixture]`** — as before. Expected to keep reporting ~0, and that is now written down as
the correct result rather than read as a failure.
- **`[production defaults]`** — the same 40 tiles against `RoomSpacing 80 / RoomDensity 0.35`, the
actual `UVoxelStrateDefinition` defaults, which is the world the question "how many tiles can we
skip" is really about. At that spacing, cull spheres of mean radius 42 on an 80-lattice at 35 %
occupancy cover roughly a fifth of space.
Both are brute-forced voxel by voxel. This is **not** "widen it until it passes": the dense run stays
in the report and must stay ~0, and a false verdict in either run still fails the assertion. The two
stacks deliberately share `FRoomGraphSource`'s `thread_local` caches — the params fingerprint in the
key is what keeps them apart, so this run and check 3 now watch each other.
### The lesson, and it is not the one I expected to write
Three runs in a row I reasoned from `RoomSpacing = 80` while the test printed 42 at me. The
instrument was right, the arithmetic done on it was right, and the *input to the arithmetic* was
taken from the wrong file. **"Verify the premise" has to include the premises you are confident
enough about not to look up** — especially a default, when a fixture exists whose entire job is to
override defaults.
### Ready to build. Compile-error spots
1. Check 4 is now `auto RunTileScan = [&](const FVoxelOpStack& S, const FStrateGenerationParams& TP,
const FVoxelOpContext& TCtx, const TCHAR* Label)`, called twice. Verified mechanically: zero bare
`Stack` / `P.` / `Ctx` references survive inside the lambda body, braces and parens balance.
2. Five `FString::Printf` sites gained a leading `%s` **and** the matching `Label` argument — the
mismatch was caught and fixed before commit; worth re-checking if the log looks scrambled.
3. `SparseStack` is a second `FVoxelOpStack` built by `BuildTunnelNetworkStack` and `PrepareChunk`ed.
### What to read
**`[production defaults] Box verdicts`** — the only new number that matters. Non-zero is the first
real T1.d saving in the plugin. If it is *also* zero, then the per-class line under it says whether
rooms still saturate at 80 spacing (my model is wrong again, look there) or whether tunnels are
finally alone (the segment-vs-box disjunction from the previous entry is then the whole remaining
job, and its three unverified premises are listed there).
@@ -1102,193 +1102,250 @@ bool FVoxelForgeOpStackTunnelTest::RunTest(const FString& Parameters)
// would now forbid the very gain this change makes. What replaces it is the assertion that // would now forbid the very gain this change makes. What replaces it is the assertion that
// actually matters — every proved tile is brute-forced voxel by voxel, because a false verdict // actually matters — every proved tile is brute-forced voxel by voxel, because a false verdict
// means no geometry and NO COLLISION until a player falls through it. // means no geometry and NO COLLISION until a player falls through it.
// ⚠️ UNE SEULE DÉFINITION DU BALAYAGE, DEUX MONDES. Voir le commentaire d'appel plus bas :
// la densité de la fixture rend ce verdict STRUCTURELLEMENT impossible, donc mesurer sur elle
// seule ne dit rien de la production. Copier-coller le balayage aurait donné deux critères qui
// divergent ; c'est un paramètre, pas un doublon.
auto RunTileScan = [&](const FVoxelOpStack& S, const FStrateGenerationParams& TP,
const FVoxelOpContext& TCtx, const TCHAR* Label)
{ {
int32 NumProved = 0, NumMixed = 0, NumSolid = 0, NumAir = 0; int32 NumProved = 0, NumMixed = 0, NumSolid = 0, NumAir = 0;
int32 NumBruteSamples = 0, NumViolations = 0; int32 NumBruteSamples = 0, NumViolations = 0;
float WorstViolation = 0.0f; float WorstViolation = 0.0f;
TMap<FString, int32> SolidKillerCounts; TMap<FString, int32> SolidKillerCounts;
int32 NumRoomKilled = 0, NumTilesAwayFromSpine = 0; int32 NumRoomKilled = 0, NumTilesAwayFromSpine = 0;
// ±320 voxels = 4 x RoomSpacing. Hors de la boucle : la ligne de rapport en a besoin, et // ±320 voxels = 4 x RoomSpacing. Hors de la boucle : la ligne de rapport en a besoin, et
// une étendue d'échantillonnage qu'on ne peut pas citer est une étendue qu'on ne surveille pas. // une étendue d'échantillonnage qu'on ne peut pas citer est une étendue qu'on ne surveille pas.
const int32 SpanCells = 40; const int32 SpanCells = 40;
const int32 SpanVoxelsReported = SpanCells * 8; // Extent = Step * Cells = 1 * 8 const int32 SpanVoxelsReported = SpanCells * 8; // Extent = Step * Cells = 1 * 8
int32 TilesHitByRooms = 0, TilesHitByTunnels = 0, TilesHitByPits = 0, TilesHitByChimneys = 0; int32 TilesHitByRooms = 0, TilesHitByTunnels = 0, TilesHitByPits = 0, TilesHitByChimneys = 0;
int32 SumHitRooms = 0, SumNumRooms = 0, SumHitTunnels = 0, SumNumTunnels = 0; int32 SumHitRooms = 0, SumNumRooms = 0, SumHitTunnels = 0, SumNumTunnels = 0;
FRandomStream Rng(97531); FRandomStream Rng(97531);
for (int32 t = 0; t < 40; ++t) for (int32 t = 0; t < 40; ++t)
{
const int32 Step = 1, Cells = 8;
const int32 Extent = Step * Cells;
// ⚠️ L'ÉTENDUE XY ÉTAIT ±32 VOXELS, ET C'EST CE QUI RENDAIT CE BLOC INEXPLOITABLE.
// `RandRange(-4, 4) * 8` échantillonnait 40 tuiles dans un cube de ±32 voxels autour de
// (0,0) — c'est-à-dire l'endroit le PLUS creusé du monde entier, et de loin :
// • `RoomSpacing = 80`, donc ±32 ne couvre même pas la moitié d'UNE cellule de salle ;
// • `OriginRoomRadius = 20` garantit une grosse salle exactement à (0,0), de rayon de
// cull `max(20·1.5, 8) + 3·4 = 42` — qui avale la quasi-totalité de la fenêtre ;
// • la spine (0,0) descend précisément là.
// La mesure « 4.9 salles sur 7.2 atteignent la boîte » ne décrivait donc pas la densité
// de grottes du monde, elle décrivait le hub de la spine. Aucune conclusion sur la
// prouvabilité du roc profond ne pouvait sortir de cet échantillon.
//
// ⚠️ CE N'EST PAS « ÉLARGIR JUSQU'À CE QUE ÇA PASSE ». Le verdict de chaque tuile reste
// brute-forcé voxel par voxel juste en dessous : un échantillonneur plus large qui
// produirait un verdict FAUX échoue exactement comme avant. On corrige ce que la mesure
// REGARDE, pas ce qu'elle exige.
//
// The XY extent was ±32 voxels around (0,0) -- with RoomSpacing = 80 and a guaranteed
// OriginRoomRadius = 20 room at the origin, that samples the single most cave-dense spot
// in the world and says nothing about deep rock. Widening changes what the measurement
// LOOKS AT, not what it demands: every verdict is still brute-forced below.
const FIntVector Origin(
Rng.RandRange(-SpanCells, SpanCells) * Extent,
Rng.RandRange(-SpanCells, SpanCells) * Extent,
FMath::Clamp(Rng.RandRange(BottomVoxelZ / Extent, TopVoxelZ / Extent), -4096, 4096) * Extent);
// Combien de tuiles échappent vraiment au hub de la spine : sans ce compte, un futur
// resserrement de l'étendue redeviendrait invisible.
if (FMath::Square((float)Origin.X) + FMath::Square((float)Origin.Y)
> FMath::Square(3.0f * P.OriginRoomRadius))
{ {
++NumTilesAwayFromSpine; const int32 Step = 1, Cells = 8;
} const int32 Extent = Step * Cells;
const int32 GridDim = Cells + 1;
const FBox Box(
FVector(Origin.X - Step, Origin.Y - Step, Origin.Z - Step),
FVector(Origin.X + GridDim * Step, Origin.Y + GridDim * Step, Origin.Z + GridDim * Step));
// ATTRIBUTION — le même pliage, mais il dit QUI tue chaque hypothèse. Le premier build // ⚠️ L'ÉTENDUE XY ÉTAIT ±32 VOXELS, ET C'EST CE QUI RENDAIT CE BLOC INEXPLOITABLE.
// de l'`EffectOverBox` spatial est revenu vert avec 0 tuile prouvée, et le rapport ne // `RandRange(-4, 4) * 8` échantillonnait 40 tuiles dans un cube de ±32 voxels autour de
// savait nommer aucun coupable : les deux causes que la mise en garde proposait étaient // (0,0) — c'est-à-dire l'endroit le PLUS creusé du monde entier, et de loin :
// toutes les deux fausses, la vraie étant un troisième opérateur. On ne redevine pas. // • `RoomSpacing = 80`, donc ±32 ne couvre même pas la moitié d'UNE cellule de salle ;
int32 SolidKiller = INDEX_NONE, AirKiller = INDEX_NONE; // • `OriginRoomRadius = 20` garantit une grosse salle exactement à (0,0), de rayon de
const EVoxelTileClass Verdict = Stack.ClassifyBoxAttributed(Box, Ctx, SolidKiller, AirKiller); // cull `max(20·1.5, 8) + 3·4 = 42` — qui avale la quasi-totalité de la fenêtre ;
// • la spine (0,0) descend précisément là.
// La mesure « 4.9 salles sur 7.2 atteignent la boîte » ne décrivait donc pas la densité
// de grottes du monde, elle décrivait le hub de la spine. Aucune conclusion sur la
// prouvabilité du roc profond ne pouvait sortir de cet échantillon.
//
// ⚠️ CE N'EST PAS « ÉLARGIR JUSQU'À CE QUE ÇA PASSE ». Le verdict de chaque tuile reste
// brute-forcé voxel par voxel juste en dessous : un échantillonneur plus large qui
// produirait un verdict FAUX échoue exactement comme avant. On corrige ce que la mesure
// REGARDE, pas ce qu'elle exige.
//
// The XY extent was ±32 voxels around (0,0) -- with RoomSpacing = 80 and a guaranteed
// OriginRoomRadius = 20 room at the origin, that samples the single most cave-dense spot
// in the world and says nothing about deep rock. Widening changes what the measurement
// LOOKS AT, not what it demands: every verdict is still brute-forced below.
const FIntVector Origin(
Rng.RandRange(-SpanCells, SpanCells) * Extent,
Rng.RandRange(-SpanCells, SpanCells) * Extent,
FMath::Clamp(Rng.RandRange(BottomVoxelZ / Extent, TopVoxelZ / Extent), -4096, 4096) * Extent);
if (SolidKiller != INDEX_NONE) // Combien de tuiles échappent vraiment au hub de la spine : sans ce compte, un futur
{ // resserrement de l'étendue redeviendrait invisible.
const FString KillerName = Stack.GetOpDebugName(SolidKiller); if (FMath::Square((float)Origin.X) + FMath::Square((float)Origin.Y)
SolidKillerCounts.FindOrAdd(KillerName)++; > FMath::Square(3.0f * TP.OriginRoomRadius))
// VENTILATION PAR CLASSE DE PRIMITIVE. Quand c'est la source de salles qui tue,
// « les tuiles traversent une grotte » n'est pas une réponse : les salles, les
// tunnels, les pits et les cheminées ont chacun leur borne, de finesse très
// différente (une sphère englobante de capsule est un très mauvais tunnel). On lit
// ce que l'opérateur a RÉELLEMENT calculé plutôt que de rejouer le critère ici.
if (KillerName == TEXT("RoomGraphSource"))
{ {
const VoxelDensityOps::FRoomBoxDiagnostic D = ++NumTilesAwayFromSpine;
VoxelDensityOps::GetLastRoomBoxDiagnostic(); }
++NumRoomKilled; const int32 GridDim = Cells + 1;
if (D.HitRooms > 0) { ++TilesHitByRooms; } const FBox Box(
if (D.HitTunnels > 0) { ++TilesHitByTunnels; } FVector(Origin.X - Step, Origin.Y - Step, Origin.Z - Step),
if (D.HitPits > 0) { ++TilesHitByPits; } FVector(Origin.X + GridDim * Step, Origin.Y + GridDim * Step, Origin.Z + GridDim * Step));
if (D.HitChimneys > 0) { ++TilesHitByChimneys; }
SumHitRooms += D.HitRooms; SumNumRooms += D.NumRooms; // ATTRIBUTION — le même pliage, mais il dit QUI tue chaque hypothèse. Le premier build
SumHitTunnels += D.HitTunnels; SumNumTunnels += D.NumTunnels; // de l'`EffectOverBox` spatial est revenu vert avec 0 tuile prouvée, et le rapport ne
// savait nommer aucun coupable : les deux causes que la mise en garde proposait étaient
// toutes les deux fausses, la vraie étant un troisième opérateur. On ne redevine pas.
int32 SolidKiller = INDEX_NONE, AirKiller = INDEX_NONE;
const EVoxelTileClass Verdict = S.ClassifyBoxAttributed(Box, TCtx, SolidKiller, AirKiller);
if (SolidKiller != INDEX_NONE)
{
const FString KillerName = S.GetOpDebugName(SolidKiller);
SolidKillerCounts.FindOrAdd(KillerName)++;
// VENTILATION PAR CLASSE DE PRIMITIVE. Quand c'est la source de salles qui tue,
// « les tuiles traversent une grotte » n'est pas une réponse : les salles, les
// tunnels, les pits et les cheminées ont chacun leur borne, de finesse très
// différente (une sphère englobante de capsule est un très mauvais tunnel). On lit
// ce que l'opérateur a RÉELLEMENT calculé plutôt que de rejouer le critère ici.
if (KillerName == TEXT("RoomGraphSource"))
{
const VoxelDensityOps::FRoomBoxDiagnostic D =
VoxelDensityOps::GetLastRoomBoxDiagnostic();
++NumRoomKilled;
if (D.HitRooms > 0) { ++TilesHitByRooms; }
if (D.HitTunnels > 0) { ++TilesHitByTunnels; }
if (D.HitPits > 0) { ++TilesHitByPits; }
if (D.HitChimneys > 0) { ++TilesHitByChimneys; }
SumHitRooms += D.HitRooms; SumNumRooms += D.NumRooms;
SumHitTunnels += D.HitTunnels; SumNumTunnels += D.NumTunnels;
}
}
if (Verdict == EVoxelTileClass::Mixed) { ++NumMixed; continue; }
++NumProved;
const bool bClaimSolid = (Verdict == EVoxelTileClass::AllSolid);
if (bClaimSolid) { ++NumSolid; } else { ++NumAir; }
// BRUTE FORCE — la boîte entière, pas un échantillonnage. `EvalMC` rend la convention
// du mesher (négatif = solide), donc « tout solide » veut dire qu'aucun échantillon
// n'est du côté air. On teste le SIGNE, c'est-à-dire l'existence d'une traversée
// d'isosurface : c'est exactement la propriété sur laquelle le mesher est sauté.
for (float Z = (float)Box.Min.Z; Z <= (float)Box.Max.Z; Z += 1.0f)
for (float Y = (float)Box.Min.Y; Y <= (float)Box.Max.Y; Y += 1.0f)
for (float X = (float)Box.Min.X; X <= (float)Box.Max.X; X += 1.0f)
{
const float D = S.EvalMC(X, Y, Z);
++NumBruteSamples;
const bool bViolates = bClaimSolid ? (D > 0.0f) : (D < 0.0f);
if (bViolates)
{
++NumViolations;
WorstViolation = FMath::Max(WorstViolation, FMath::Abs(D));
}
} }
} }
if (Verdict == EVoxelTileClass::Mixed) { ++NumMixed; continue; }
++NumProved;
const bool bClaimSolid = (Verdict == EVoxelTileClass::AllSolid);
if (bClaimSolid) { ++NumSolid; } else { ++NumAir; }
// BRUTE FORCE — la boîte entière, pas un échantillonnage. `EvalMC` rend la convention
// du mesher (négatif = solide), donc « tout solide » veut dire qu'aucun échantillon
// n'est du côté air. On teste le SIGNE, c'est-à-dire l'existence d'une traversée
// d'isosurface : c'est exactement la propriété sur laquelle le mesher est sauté.
for (float Z = (float)Box.Min.Z; Z <= (float)Box.Max.Z; Z += 1.0f)
for (float Y = (float)Box.Min.Y; Y <= (float)Box.Max.Y; Y += 1.0f)
for (float X = (float)Box.Min.X; X <= (float)Box.Max.X; X += 1.0f)
{
const float D = Stack.EvalMC(X, Y, Z);
++NumBruteSamples;
const bool bViolates = bClaimSolid ? (D > 0.0f) : (D < 0.0f);
if (bViolates)
{
++NumViolations;
WorstViolation = FMath::Max(WorstViolation, FMath::Abs(D));
}
}
}
AddInfo(FString::Printf(
TEXT("Tile sampler: 40 tiles of 10 voxels, XY drawn from +/-%d voxels (= %.1f x ")
TEXT("RoomSpacing %.0f), Z across the strate; %d of 40 landed further than 3 x ")
TEXT("OriginRoomRadius from the (0,0) spine. THIS LINE EXISTS BECAUSE THE SAMPLER WAS ")
TEXT("THE BUG ONCE: it drew XY from +/-32 voxels, i.e. entirely inside the origin room's ")
TEXT("cull sphere, so every number below described the spine hub rather than the world. ")
TEXT("If the last count is low, nothing below says anything about deep rock."),
SpanVoxelsReported, (float)SpanVoxelsReported / FMath::Max(P.RoomSpacing, 1.0f),
P.RoomSpacing, NumTilesAwayFromSpine));
AddInfo(FString::Printf(
TEXT("Box verdicts over 40 TunnelNetwork tiles: %d proved (%d AllSolid, %d AllAir), ")
TEXT("%d Mixed -- brute-forced over %d voxels, %d violations. This number was 0 proved / ")
TEXT("40 Mixed until FRoomGraphSource::EffectOverBox learned to answer spatially, and it ")
TEXT("is the single largest perf item of the whole plan (OPSTACK-DECOMPOSITION 0.2): a ")
TEXT("proved tile skips GenerateMesh entirely, so it trades one BuildChunkCache against ")
TEXT("30000+ density evaluations. Read the PROVED count as a measurement, never as a ")
TEXT("contract -- what is asserted below is that none of them is WRONG, because a false ")
TEXT("verdict leaves no geometry and no collision behind it."),
NumProved, NumSolid, NumAir, NumMixed, NumBruteSamples, NumViolations));
// QUI TUE `AllSolid`, ET COMBIEN DE FOIS. Toujours imprimé, pas seulement en cas d'échec :
// c'est aussi la ligne qui dit, quand des tuiles SONT prouvées, ce qui bloque les autres.
{
SolidKillerCounts.ValueSort([](int32 A, int32 B) { return A > B; });
FString Breakdown;
for (const TPair<FString, int32>& Kv : SolidKillerCounts)
{
if (!Breakdown.IsEmpty()) { Breakdown += TEXT(", "); }
Breakdown += FString::Printf(TEXT("%s x%d"), *Kv.Key, Kv.Value);
}
if (Breakdown.IsEmpty()) { Breakdown = TEXT("nothing -- AllSolid survived every tile"); }
AddInfo(FString::Printf( AddInfo(FString::Printf(
TEXT("AllSolid killed by: %s. This is the line that replaced a guess. The first ") TEXT("[%s] Tile sampler: 40 tiles of 10 voxels, XY drawn from +/-%d voxels (= %.1f x ")
TEXT("build of the spatial EffectOverBox reported 0 proved of 40, and the warning ") TEXT("RoomSpacing %.0f), Z across the strate; %d of 40 landed further than 3 x ")
TEXT("offered two candidate causes -- BOTH WRONG. The real one was a third operator ") TEXT("OriginRoomRadius from the (0,0) spine. THIS LINE EXISTS BECAUSE THE SAMPLER WAS ")
TEXT("nobody was looking at: FWormFieldSource answered CarveOnly everywhere, and ") TEXT("THE BUG ONCE: it drew XY from +/-32 voxels, i.e. entirely inside the origin room's ")
TEXT("since BaseDensity=8 < WormStrength=10 BY DEFAULT, its provable amplitude bound ") TEXT("cull sphere, so every number below described the spine hub rather than the world. ")
TEXT("alone drove SolidMargin negative on every tile in the world. Attribution is ") TEXT("If the last count is low, nothing below says anything about deep rock."),
TEXT("cheap; a second wrong guess is not."), Label, SpanVoxelsReported, (float)SpanVoxelsReported / FMath::Max(TP.RoomSpacing, 1.0f),
*Breakdown)); TP.RoomSpacing, NumTilesAwayFromSpine));
if (NumRoomKilled > 0) AddInfo(FString::Printf(
TEXT("[%s] Box verdicts over 40 TunnelNetwork tiles: %d proved (%d AllSolid, %d AllAir), ")
TEXT("%d Mixed -- brute-forced over %d voxels, %d violations. This number was 0 proved / ")
TEXT("40 Mixed until FRoomGraphSource::EffectOverBox learned to answer spatially, and it ")
TEXT("is the single largest perf item of the whole plan (OPSTACK-DECOMPOSITION 0.2): a ")
TEXT("proved tile skips GenerateMesh entirely, so it trades one BuildChunkCache against ")
TEXT("30000+ density evaluations. Read the PROVED count as a measurement, never as a ")
TEXT("contract -- what is asserted below is that none of them is WRONG, because a false ")
TEXT("verdict leaves no geometry and no collision behind it."),
Label, NumProved, NumSolid, NumAir, NumMixed, NumBruteSamples, NumViolations));
// QUI TUE `AllSolid`, ET COMBIEN DE FOIS. Toujours imprimé, pas seulement en cas d'échec :
// c'est aussi la ligne qui dit, quand des tuiles SONT prouvées, ce qui bloque les autres.
{ {
SolidKillerCounts.ValueSort([](int32 A, int32 B) { return A > B; });
FString Breakdown;
for (const TPair<FString, int32>& Kv : SolidKillerCounts)
{
if (!Breakdown.IsEmpty()) { Breakdown += TEXT(", "); }
Breakdown += FString::Printf(TEXT("%s x%d"), *Kv.Key, Kv.Value);
}
if (Breakdown.IsEmpty()) { Breakdown = TEXT("nothing -- AllSolid survived every tile"); }
AddInfo(FString::Printf( AddInfo(FString::Printf(
TEXT("...and when RoomGraphSource is the killer (%d tiles), WHICH primitive class ") TEXT("[%s] AllSolid killed by: %s. This is the line that replaced a guess. The first ")
TEXT("reaches the box: rooms %d, tunnels %d, pits %d, chimneys %d (tiles, not ") TEXT("build of the spatial EffectOverBox reported 0 proved of 40, and the warning ")
TEXT("primitives -- a tile can be hit by several). Averages per killed tile: ") TEXT("offered two candidate causes -- BOTH WRONG. The real one was a third operator ")
TEXT("%.1f of %.1f rooms reach, %.1f of %.1f tunnels reach. THIS is the line that ") TEXT("nobody was looking at: FWormFieldSource answered CarveOnly everywhere, and ")
TEXT("says what to tighten. A tunnel is culled per voxel by its BOUNDING SPHERE, ") TEXT("since BaseDensity=8 < WormStrength=10 BY DEFAULT, its provable amplitude bound ")
TEXT("which for a long thin capsule is an enormous over-estimate; a room's cull ") TEXT("alone drove SolidMargin negative on every tile in the world. Attribution is ")
TEXT("sphere is a fair fit. So tunnels >> rooms here would mean the box test is ") TEXT("cheap; a second wrong guess is not."),
TEXT("losing to capsule bounding spheres, not to real cave -- and the fix would ") Label, *Breakdown));
TEXT("be a segment-vs-box distance, not anything about the sampler."),
NumRoomKilled, TilesHitByRooms, TilesHitByTunnels, TilesHitByPits, TilesHitByChimneys, if (NumRoomKilled > 0)
(float)SumHitRooms / (float)NumRoomKilled, (float)SumNumRooms / (float)NumRoomKilled, {
(float)SumHitTunnels / (float)NumRoomKilled, (float)SumNumTunnels / (float)NumRoomKilled)); AddInfo(FString::Printf(
TEXT("[%s] ...and when RoomGraphSource is the killer (%d tiles), WHICH primitive class ")
TEXT("reaches the box: rooms %d, tunnels %d, pits %d, chimneys %d (tiles, not ")
TEXT("primitives -- a tile can be hit by several). Averages per killed tile: ")
TEXT("%.1f of %.1f rooms reach, %.1f of %.1f tunnels reach. THIS is the line that ")
TEXT("says what to tighten. A tunnel is culled per voxel by its BOUNDING SPHERE, ")
TEXT("which for a long thin capsule is an enormous over-estimate; a room's cull ")
TEXT("sphere is a fair fit. So tunnels >> rooms here would mean the box test is ")
TEXT("losing to capsule bounding spheres, not to real cave -- and the fix would ")
TEXT("be a segment-vs-box distance, not anything about the sampler."),
Label, NumRoomKilled, TilesHitByRooms, TilesHitByTunnels, TilesHitByPits, TilesHitByChimneys,
(float)SumHitRooms / (float)NumRoomKilled, (float)SumNumRooms / (float)NumRoomKilled,
(float)SumHitTunnels / (float)NumRoomKilled, (float)SumNumTunnels / (float)NumRoomKilled));
}
} }
}
if (NumProved == 0) if (NumProved == 0)
{ {
AddWarning(TEXT("No TunnelNetwork tile was proved, so the brute force below verified ") AddWarning(FString::Printf(
TEXT("nothing -- it has no verdict to contradict. Do NOT re-derive the cause: ") TEXT("[%s] No tile was proved, so the brute force verified nothing -- it has no ")
TEXT("read the 'AllSolid killed by' line above, which names the operator and ") TEXT("verdict to contradict. Do NOT re-derive the cause: read the two lines above, ")
TEXT("counts how often. If it names RoomGraphSource, the tiles genuinely ") TEXT("which name the operator and then the primitive class. ⚠️ On the DENSE ")
TEXT("straddle cave (check the bake-coverage line of 5b); anything else is an ") TEXT("FIXTURE this is the EXPECTED and correct result, not a defect: room cull ")
TEXT("operator whose box answer is more pessimistic than its Eval.")); TEXT("radius (1.5R + 3*SDFBlendRadius, mean ~42) equals RoomSpacing 42 at 85%% ")
} TEXT("occupancy, so cull spheres cover that world ~3.6x over and no box can be ")
TEXT("outside all of them. It is the 'production defaults' run that answers ")
TEXT("whether real worlds have skippable rock."),
Label));
}
TestEqual(FString::Printf( TestEqual(FString::Printf(
TEXT("every proved TunnelNetwork tile survives brute force (worst |density| ") TEXT("[%s] every proved TunnelNetwork tile survives brute force (worst ")
TEXT("on the wrong side: %.9g)"), WorstViolation), TEXT("|density| on the wrong side: %.9g)"), Label, WorstViolation),
NumViolations, 0); NumViolations, 0);
};
//-------------------------------------------------------------------------
// LES DEUX MONDES, ET POURQUOI IL EN FAUT DEUX
//-------------------------------------------------------------------------
// ⚠️ LA FIXTURE REND CE VERDICT STRUCTURELLEMENT IMPOSSIBLE, ET CE N'EST PAS UN DÉFAUT DE LA
// FIXTURE. `EnableTunnelFeatures` densifie délibérément (`RoomSpacing` 80 → 42,
// `RoomDensity` 0.35 → 0.85) parce qu'aux défauts le premier run n'avait que 1,1 % des
// échantillons en grotte — l'équivalence comparait du roc plein à du roc plein. Cette
// densification est ce qui rend le contrôle 1 SIGNIFIANT.
//
// Mais elle est exactement ANTAGONISTE de la prouvabilité, et l'arithmétique le dit sans
// ambiguïté : le rayon de cull d'une salle vaut `max(R·1.5, R·HeightRatio) + 3·SDFBlendRadius`,
// soit `1.5R + 12` ⇒ entre 27 et 57 pour `R ∈ [10, 30]`, moyenne ≈ 42 — c'est-à-dire
// **exactement le pas du réseau**, à 85 % d'occupation. Des sphères de cull de rayon égal au pas
// du réseau recouvrent l'espace ~3,6 fois. **Aucune boîte de ce monde ne peut être hors de
// toutes les sphères de cull.** Le « 6.3 salles sur 8.3 atteignent la boîte » mesuré est
// exactement ça, et élargir l'échantillonneur n'y a rien changé (39 tuiles sur 40 étaient déjà
// loin de la spine, et le compte est resté 40 sur 40).
//
// Donc on mesure les DEUX : la fixture dense, où `0 prouvé` est le résultat CORRECT et
// informatif (le gain disparaît quand les grottes saturent), et un jeu de params aux DÉFAUTS
// DE PRODUCTION, qui est le monde dont la question « combien de tuiles peut-on sauter » parle
// réellement. Ce n'est pas « élargir jusqu'à ce que ça passe » : les deux sont rapportés, les
// deux sont brute-forcés, et le dense DOIT continuer à rendre ~0.
//
// Two worlds on purpose: the fixture is deliberately densified so the equivalence check means
// something, and that same densification makes tile-proving structurally impossible (room cull
// radius ~= the lattice spacing, at 85% occupancy). Both are measured and both are brute-forced;
// the dense one reporting ~0 is the correct answer, not a failure.
RunTileScan(Stack, P, Ctx, TEXT("dense fixture"));
{
FStrateGenerationParams SparseP = P;
SparseP.RoomSpacing = 80.0f; // le défaut d'`UVoxelStrateDefinition`
SparseP.RoomDensity = 0.35f; // idem — voir `EnableTunnelFeatures`
FVoxelOpStack SparseStack;
VoxelDensityOps::BuildTunnelNetworkStack(SparseStack, SparseP, World.Settings->Seed,
Gen->OriginSpineRadius, World.StrateManager.Get());
SparseStack.PrepareChunk(Ctx);
// ⚠️ Les deux piles partagent les caches `thread_local` de `FRoomGraphSource`. C'est VOULU,
// et c'est exactement ce que le contrôle 3 vérifie : l'empreinte de params est dans la clé,
// donc l'une ne peut pas se servir les salles de l'autre. Le jour où ce contrôle tombe,
// cette ligne-ci devient fausse en même temps — elles se surveillent mutuellement.
RunTileScan(SparseStack, SparseP, Ctx, TEXT("production defaults"));
} }
//========================================================================= //=========================================================================