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:
@@ -2850,3 +2850,88 @@ tunnels.
|
||||
|
||||
`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.
|
||||
|
||||
## 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,6 +1102,12 @@ 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;
|
||||
@@ -1149,7 +1155,7 @@ bool FVoxelForgeOpStackTunnelTest::RunTest(const FString& Parameters)
|
||||
// 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))
|
||||
> FMath::Square(3.0f * TP.OriginRoomRadius))
|
||||
{
|
||||
++NumTilesAwayFromSpine;
|
||||
}
|
||||
@@ -1163,11 +1169,11 @@ bool FVoxelForgeOpStackTunnelTest::RunTest(const FString& Parameters)
|
||||
// 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);
|
||||
const EVoxelTileClass Verdict = S.ClassifyBoxAttributed(Box, TCtx, SolidKiller, AirKiller);
|
||||
|
||||
if (SolidKiller != INDEX_NONE)
|
||||
{
|
||||
const FString KillerName = Stack.GetOpDebugName(SolidKiller);
|
||||
const FString KillerName = S.GetOpDebugName(SolidKiller);
|
||||
SolidKillerCounts.FindOrAdd(KillerName)++;
|
||||
|
||||
// VENTILATION PAR CLASSE DE PRIMITIVE. Quand c'est la source de salles qui tue,
|
||||
@@ -1203,7 +1209,7 @@ bool FVoxelForgeOpStackTunnelTest::RunTest(const FString& Parameters)
|
||||
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);
|
||||
const float D = S.EvalMC(X, Y, Z);
|
||||
++NumBruteSamples;
|
||||
const bool bViolates = bClaimSolid ? (D > 0.0f) : (D < 0.0f);
|
||||
if (bViolates)
|
||||
@@ -1215,17 +1221,17 @@ bool FVoxelForgeOpStackTunnelTest::RunTest(const FString& Parameters)
|
||||
}
|
||||
|
||||
AddInfo(FString::Printf(
|
||||
TEXT("Tile sampler: 40 tiles of 10 voxels, XY drawn from +/-%d voxels (= %.1f x ")
|
||||
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."),
|
||||
SpanVoxelsReported, (float)SpanVoxelsReported / FMath::Max(P.RoomSpacing, 1.0f),
|
||||
P.RoomSpacing, NumTilesAwayFromSpine));
|
||||
Label, SpanVoxelsReported, (float)SpanVoxelsReported / FMath::Max(TP.RoomSpacing, 1.0f),
|
||||
TP.RoomSpacing, NumTilesAwayFromSpine));
|
||||
|
||||
AddInfo(FString::Printf(
|
||||
TEXT("Box verdicts over 40 TunnelNetwork tiles: %d proved (%d AllSolid, %d AllAir), ")
|
||||
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 ")
|
||||
@@ -1233,7 +1239,7 @@ bool FVoxelForgeOpStackTunnelTest::RunTest(const FString& Parameters)
|
||||
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));
|
||||
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.
|
||||
@@ -1248,19 +1254,19 @@ bool FVoxelForgeOpStackTunnelTest::RunTest(const FString& Parameters)
|
||||
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("[%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."),
|
||||
*Breakdown));
|
||||
Label, *Breakdown));
|
||||
|
||||
if (NumRoomKilled > 0)
|
||||
{
|
||||
AddInfo(FString::Printf(
|
||||
TEXT("...and when RoomGraphSource is the killer (%d tiles), WHICH primitive class ")
|
||||
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 ")
|
||||
@@ -1269,7 +1275,7 @@ bool FVoxelForgeOpStackTunnelTest::RunTest(const FString& Parameters)
|
||||
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,
|
||||
Label, NumRoomKilled, TilesHitByRooms, TilesHitByTunnels, TilesHitByPits, TilesHitByChimneys,
|
||||
(float)SumHitRooms / (float)NumRoomKilled, (float)SumNumRooms / (float)NumRoomKilled,
|
||||
(float)SumHitTunnels / (float)NumRoomKilled, (float)SumNumTunnels / (float)NumRoomKilled));
|
||||
}
|
||||
@@ -1277,18 +1283,69 @@ bool FVoxelForgeOpStackTunnelTest::RunTest(const FString& Parameters)
|
||||
|
||||
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."));
|
||||
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),
|
||||
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"));
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
|
||||
Reference in New Issue
Block a user