test: unit-test the op-stack fold, and make the build actually see VoxelDensityOp.h
VoxelDensityOp.h was included by no .cpp, so the compiler would never have looked at it -- a header committed as "ready to build" that the build ignores. The ClassifyTile test now includes it, which is also the right home for the fold's own test: the fold claims to reproduce the hand-written ClassifyTile, and that claim is pure logic with no world, noise or threads behind it. VoxelForge.OpStack.BoxVerdictFold walks the correspondence case by case, including the one that justifies ClassifyBox existing at all: a box entirely inside the top seal band, where the source says AllAir and the seal forces AllSolid. A pure FillOnly would resolve that to Mixed and silently lose a tile T1.d skips today. Also casts UE_ARRAY_COUNT to int32 in the fixture -- signed/unsigned comparison in a loop condition is a warning, and UE builds warnings as errors. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -37,6 +37,13 @@
|
||||
#include "Misc/AutomationTest.h"
|
||||
|
||||
#include "VoxelForgeTestFixture.h"
|
||||
// Inclus ici DÉLIBÉRÉMENT : VoxelDensityOp.h n'est encore inclus par aucun .cpp, donc le
|
||||
// compilateur ne le verrait jamais. Le fold qu'il définit prétend reproduire ClassifyTile — ce
|
||||
// fichier est l'endroit naturel pour que cette prétention soit à la fois COMPILÉE et TESTÉE.
|
||||
// Deliberately included here: VoxelDensityOp.h is not yet included by any .cpp, so the compiler
|
||||
// would never see it. Its fold claims to reproduce ClassifyTile, so this is the natural place for
|
||||
// that claim to be both compiled and tested.
|
||||
#include "VoxelDensityOp.h"
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FVoxelForgeClassifyTileTest,
|
||||
@@ -226,4 +233,97 @@ bool FVoxelForgeClassifyTileTest::RunTest(const FString& Parameters)
|
||||
return true;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// LE FOLD DE LA PILE D'OPÉRATEURS / the op-stack fold
|
||||
//=============================================================================
|
||||
// `VoxelDensityOp.h` affirme que son fold reproduit le ClassifyTile écrit à la main. C'est de la
|
||||
// logique pure — pas de monde, pas de bruit, pas de thread — donc elle peut être vérifiée
|
||||
// exhaustivement, et elle doit l'être : c'est elle qui décidera un jour si une tuile est maillée.
|
||||
//
|
||||
// `VoxelDensityOp.h` claims its fold reproduces the hand-written ClassifyTile. That is pure logic —
|
||||
// no world, no noise, no threads — so it can be checked exhaustively, and it should be: this is what
|
||||
// will one day decide whether a tile gets meshed at all.
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FVoxelForgeOpFoldTest,
|
||||
"VoxelForge.OpStack.BoxVerdictFold",
|
||||
EAutomationTestFlags_ApplicationContextMask | EAutomationTestFlags::EngineFilter)
|
||||
|
||||
bool FVoxelForgeOpFoldTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
// Un état neuf ne prouve rien ⇒ Mixed (les deux hypothèses vivantes = égalité = prudence).
|
||||
{
|
||||
FVoxelBoxHypotheses H;
|
||||
TestEqual(TEXT("a fresh state proves nothing"), (int32)H.Resolve(), (int32)EVoxelTileClass::Mixed);
|
||||
}
|
||||
|
||||
// Une source qui affirme un côté tue l'autre hypothèse.
|
||||
{
|
||||
FVoxelBoxHypotheses H;
|
||||
VF_ForceHypotheses(H, EVoxelTileClass::AllSolid);
|
||||
TestEqual(TEXT("a solid source yields AllSolid"), (int32)H.Resolve(), (int32)EVoxelTileClass::AllSolid);
|
||||
VF_FoldEffect(H, EVoxelOpEffect::Identity);
|
||||
TestEqual(TEXT("Identity changes nothing"), (int32)H.Resolve(), (int32)EVoxelTileClass::AllSolid);
|
||||
}
|
||||
|
||||
// ≡ « AnyPassageNearBox ⇒ bCanSolid = false » : un carve tue AllSolid.
|
||||
{
|
||||
FVoxelBoxHypotheses H;
|
||||
VF_ForceHypotheses(H, EVoxelTileClass::AllSolid);
|
||||
VF_FoldEffect(H, EVoxelOpEffect::CarveOnly);
|
||||
TestEqual(TEXT("a passage over solid rock forces Mixed"), (int32)H.Resolve(), (int32)EVoxelTileClass::Mixed);
|
||||
}
|
||||
|
||||
// ≡ « bande de seal ⇒ bCanAir = false » : un fill tue AllAir.
|
||||
{
|
||||
FVoxelBoxHypotheses H;
|
||||
VF_ForceHypotheses(H, EVoxelTileClass::AllAir);
|
||||
VF_FoldEffect(H, EVoxelOpEffect::FillOnly);
|
||||
TestEqual(TEXT("a fill over open air forces Mixed"), (int32)H.Resolve(), (int32)EVoxelTileClass::Mixed);
|
||||
}
|
||||
|
||||
// LE CAS QUI JUSTIFIE ClassifyBox : au-dessus du terrain mais DANS la bande de seal supérieure,
|
||||
// la source dit « tout air » et le seal FORCE « tout solide ». Aujourd'hui ClassifyTile rend
|
||||
// AllSolid ici. Un simple FillOnly rendrait Mixed et perdrait la tuile.
|
||||
// THE CASE THAT JUSTIFIES ClassifyBox — a pure FillOnly would lose this tile.
|
||||
{
|
||||
FVoxelBoxHypotheses H;
|
||||
VF_ForceHypotheses(H, EVoxelTileClass::AllAir); // source: above the terrain
|
||||
VF_ForceHypotheses(H, EVoxelTileClass::AllSolid); // seal: forcing, inside its band
|
||||
TestEqual(TEXT("a forcing seal recovers AllSolid over an air source"),
|
||||
(int32)H.Resolve(), (int32)EVoxelTileClass::AllSolid);
|
||||
|
||||
// …et un passage qui traverse cette même boîte la reprend, exactement comme aujourd'hui.
|
||||
VF_FoldEffect(H, EVoxelOpEffect::CarveOnly);
|
||||
TestEqual(TEXT("a passage still takes the sealed verdict back"),
|
||||
(int32)H.Resolve(), (int32)EVoxelTileClass::Mixed);
|
||||
}
|
||||
|
||||
// Le diff layer : Both tue tout, ce qui est le comportement voulu (une édition joueur peut
|
||||
// creuser OU remplir n'importe où).
|
||||
{
|
||||
FVoxelBoxHypotheses H;
|
||||
VF_ForceHypotheses(H, EVoxelTileClass::AllSolid);
|
||||
VF_FoldEffect(H, EVoxelOpEffect::Both);
|
||||
TestTrue(TEXT("Both kills every hypothesis"), H.IsDead());
|
||||
TestEqual(TEXT("a player edit in range forces Mixed"), (int32)H.Resolve(), (int32)EVoxelTileClass::Mixed);
|
||||
}
|
||||
|
||||
// Une source qui ne sait rien (Mixed) ne peut jamais être ressuscitée par un opérateur
|
||||
// directionnel — seul un opérateur FORÇANT le peut. C'est la propriété de sûreté.
|
||||
{
|
||||
for (const EVoxelOpEffect E : { EVoxelOpEffect::Identity, EVoxelOpEffect::CarveOnly,
|
||||
EVoxelOpEffect::FillOnly, EVoxelOpEffect::Both })
|
||||
{
|
||||
FVoxelBoxHypotheses H;
|
||||
VF_ForceHypotheses(H, EVoxelTileClass::Mixed);
|
||||
VF_FoldEffect(H, E);
|
||||
TestEqual(TEXT("a directional op can never resurrect an unprovable box"),
|
||||
(int32)H.Resolve(), (int32)EVoxelTileClass::Mixed);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // WITH_DEV_AUTOMATION_TESTS
|
||||
|
||||
@@ -90,7 +90,8 @@ namespace VoxelForgeTest
|
||||
ECaveGeneratorType::Underwater,
|
||||
};
|
||||
|
||||
for (int32 i = 0; i < UE_ARRAY_COUNT(Archetypes); ++i)
|
||||
const int32 NumArchetypes = (int32)UE_ARRAY_COUNT(Archetypes);
|
||||
for (int32 i = 0; i < NumArchetypes; ++i)
|
||||
{
|
||||
UVoxelStrateDefinition* Def = NewObject<UVoxelStrateDefinition>(
|
||||
GetTransientPackage(), NAME_None, RF_Transient);
|
||||
@@ -105,7 +106,7 @@ namespace VoxelForgeTest
|
||||
Settings->FixedStrates.Add(i, SoftDef);
|
||||
Settings->StratePool.Add(SoftDef); // fallback if a fixed entry fails to resolve
|
||||
}
|
||||
Settings->TotalStrates = UE_ARRAY_COUNT(Archetypes);
|
||||
Settings->TotalStrates = NumArchetypes;
|
||||
|
||||
StrateManager = TStrongObjectPtr<UVoxelStrateManager>(
|
||||
NewObject<UVoxelStrateManager>(GetTransientPackage(), NAME_None, RF_Transient));
|
||||
|
||||
Reference in New Issue
Block a user