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
@@ -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
// 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.
// ⚠️ 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 NumBruteSamples = 0, NumViolations = 0;
float WorstViolation = 0.0f;
TMap<FString, int32> SolidKillerCounts;
int32 NumRoomKilled = 0, NumTilesAwayFromSpine = 0;
// ±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.
const int32 SpanCells = 40;
const int32 SpanVoxelsReported = SpanCells * 8; // Extent = Step * Cells = 1 * 8
int32 TilesHitByRooms = 0, TilesHitByTunnels = 0, TilesHitByPits = 0, TilesHitByChimneys = 0;
int32 SumHitRooms = 0, SumNumRooms = 0, SumHitTunnels = 0, SumNumTunnels = 0;
int32 NumProved = 0, NumMixed = 0, NumSolid = 0, NumAir = 0;
int32 NumBruteSamples = 0, NumViolations = 0;
float WorstViolation = 0.0f;
TMap<FString, int32> SolidKillerCounts;
int32 NumRoomKilled = 0, NumTilesAwayFromSpine = 0;
// ±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.
const int32 SpanCells = 40;
const int32 SpanVoxelsReported = SpanCells * 8; // Extent = Step * Cells = 1 * 8
int32 TilesHitByRooms = 0, TilesHitByTunnels = 0, TilesHitByPits = 0, TilesHitByChimneys = 0;
int32 SumHitRooms = 0, SumNumRooms = 0, SumHitTunnels = 0, SumNumTunnels = 0;
FRandomStream Rng(97531);
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))
FRandomStream Rng(97531);
for (int32 t = 0; t < 40; ++t)
{
++NumTilesAwayFromSpine;
}
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));
const int32 Step = 1, Cells = 8;
const int32 Extent = Step * Cells;
// ATTRIBUTION — le même pliage, mais il dit QUI tue chaque hypothèse. Le premier build
// 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 = Stack.ClassifyBoxAttributed(Box, Ctx, SolidKiller, AirKiller);
// ⚠️ 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);
if (SolidKiller != INDEX_NONE)
{
const FString KillerName = Stack.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"))
// 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 * TP.OriginRoomRadius))
{
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;
++NumTilesAwayFromSpine;
}
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
// 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(
TEXT("AllSolid killed by: %s. This is the line that replaced a guess. The first ")
TEXT("build of the spatial EffectOverBox reported 0 proved of 40, and the warning ")
TEXT("offered two candidate causes -- BOTH WRONG. The real one was a third operator ")
TEXT("nobody was looking at: FWormFieldSource answered CarveOnly everywhere, and ")
TEXT("since BaseDensity=8 < WormStrength=10 BY DEFAULT, its provable amplitude bound ")
TEXT("alone drove SolidMargin negative on every tile in the world. Attribution is ")
TEXT("cheap; a second wrong guess is not."),
*Breakdown));
TEXT("[%s] 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."),
Label, SpanVoxelsReported, (float)SpanVoxelsReported / FMath::Max(TP.RoomSpacing, 1.0f),
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(
TEXT("...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."),
NumRoomKilled, TilesHitByRooms, TilesHitByTunnels, TilesHitByPits, TilesHitByChimneys,
(float)SumHitRooms / (float)NumRoomKilled, (float)SumNumRooms / (float)NumRoomKilled,
(float)SumHitTunnels / (float)NumRoomKilled, (float)SumNumTunnels / (float)NumRoomKilled));
TEXT("[%s] AllSolid killed by: %s. This is the line that replaced a guess. The first ")
TEXT("build of the spatial EffectOverBox reported 0 proved of 40, and the warning ")
TEXT("offered two candidate causes -- BOTH WRONG. The real one was a third operator ")
TEXT("nobody was looking at: FWormFieldSource answered CarveOnly everywhere, and ")
TEXT("since BaseDensity=8 < WormStrength=10 BY DEFAULT, its provable amplitude bound ")
TEXT("alone drove SolidMargin negative on every tile in the world. Attribution is ")
TEXT("cheap; a second wrong guess is not."),
Label, *Breakdown));
if (NumRoomKilled > 0)
{
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)
{
AddWarning(TEXT("No TunnelNetwork tile was proved, so the brute force below verified ")
TEXT("nothing -- it has no verdict to contradict. Do NOT re-derive the cause: ")
TEXT("read the 'AllSolid killed by' line above, which names the operator and ")
TEXT("counts how often. If it names RoomGraphSource, the tiles genuinely ")
TEXT("straddle cave (check the bake-coverage line of 5b); anything else is an ")
TEXT("operator whose box answer is more pessimistic than its Eval."));
}
if (NumProved == 0)
{
AddWarning(FString::Printf(
TEXT("[%s] No tile was proved, so the brute force verified nothing -- it has no ")
TEXT("verdict to contradict. Do NOT re-derive the cause: read the two lines above, ")
TEXT("which name the operator and then the primitive class. ⚠️ On the DENSE ")
TEXT("FIXTURE this is the EXPECTED and correct result, not a defect: room cull ")
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(
TEXT("every proved TunnelNetwork tile survives brute force (worst |density| ")
TEXT("on the wrong side: %.9g)"), WorstViolation),
NumViolations, 0);
TestEqual(FString::Printf(
TEXT("[%s] every proved TunnelNetwork tile survives brute force (worst ")
TEXT("|density| on the wrong side: %.9g)"), Label, WorstViolation),
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"));
}
//=========================================================================