Files
VoxelForge/Source/VoxelForge/Private/Tests/VoxelForgeOpStackChannelTest.cpp
T
Claude 8117d118eb refactor(opstack): the operator stack is the only density path
Every strate now generates through its operator stack: authored
definitions, cooked-season entries (recipe or native) and editor
composer candidates alike. The per-strate opt-in and the archetype
switch it guarded are gone.

- Remove UVoxelStrateDefinition::bUseOperatorStack and the season
  wiring that set it; UsesOperatorStackForChunk now only says whether
  the slot has a known archetype.
- GetDensityAt always builds a stack (composer, then recipe, then the
  native archetype stack); a degenerate zero-height strate stays air.
- Remove the startup opt-in warning, the operator_stack startup-trace
  field and the four "Cave Bail Not Op Stack" stats (the remaining
  bail is counted by Cave Bail No Stack).
- Explore commandlet: drop -opstack and the operator_stack fields;
  VoxelForgeTest.ps1 no longer passes -opstack=1.
- Tests: FTestWorld::Build loses its bool parameter; legacy-vs-stack
  comparisons (MazeSeam, VerticalShaftSeam, TerrainDetailLiveness,
  WorldEdgeSeal, TilePostReach) collapse to the single stack path.

This changes terrain for any strate asset that did not already have
the opt-in ticked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01URDGLsu7c4nunqNFPJEFPU
2026-09-28 15:56:19 +00:00

204 lines
7.9 KiB
C++

// VoxelForgeOpStackChannelTest.cpp
// The channel declarations are executable stack-assembly metadata, not comments.
#if WITH_DEV_AUTOMATION_TESTS
#include "Misc/AutomationTest.h"
#include "VoxelForgeTestFixture.h"
#include "VoxelDensityOpStack.h"
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FVoxelForgeOpStackChannelTest,
"VoxelForge.OpStack.ChannelDAG",
EAutomationTestFlags_ApplicationContextMask | EAutomationTestFlags::EngineFilter)
namespace
{
/** A deliberately small declaration-only op for the validator's negative controls. */
class FDeclarationProbeOp final : public IVoxelDensityOp
{
public:
FDeclarationProbeOp(EVoxelOpChannelMask InReads,
EVoxelOpChannelMask InWrites,
bool bInAdditive)
: Reads(InReads), Writes(InWrites), bAdditive(bInAdditive)
{}
EVoxelOpRole GetRole() const override { return EVoxelOpRole::DetailModifier; }
EVoxelOpChannelMask ChannelReads() const override { return Reads; }
EVoxelOpChannelMask ChannelWrites() const override { return Writes; }
bool IsAdditive() const override { return bAdditive; }
void PrepareChunk(const FVoxelOpContext&) override {}
void Eval(float, float, float, FVoxelOpSample&) const override {}
EVoxelOpEffect EffectOverBox(const FBox&, const FVoxelOpContext&) const override
{
return EVoxelOpEffect::Identity;
}
const TCHAR* DebugName() const override { return TEXT("DeclarationProbeOp"); }
private:
EVoxelOpChannelMask Reads;
EVoxelOpChannelMask Writes;
bool bAdditive;
};
}
bool FVoxelForgeOpStackChannelTest::RunTest(const FString& Parameters)
{
using namespace VoxelForgeTest;
FTestWorld World;
World.Build();
if (!World.IsValid())
{
AddError(World.WhyInvalid());
return false;
}
const UVoxelGenerator* Generator = World.Generator.Get();
const int32 Seed = World.Settings->Seed;
auto MidChunkForSlot = [&](int32 Slot, const TCHAR* Name, int32& OutMidChunkZ) -> bool
{
int32 TopVoxelZ = 0;
int32 BottomVoxelZ = 0;
if (!World.GetSlotVoxelZRange(Slot, TopVoxelZ, BottomVoxelZ))
{
AddError(FString::Printf(
TEXT("The fixture has no %s slot; check FTestWorld::Build's Archetypes[] and "
"the corresponding slot constant."), Name));
return false;
}
OutMidChunkZ = ((TopVoxelZ + BottomVoxelZ) / 2) / CHUNK_SIZE;
return true;
};
auto Validate = [&](FVoxelOpStack& Stack, const TCHAR* Name, int32 ExpectedNum)
{
TestEqual(*FString::Printf(TEXT("%s shipping stack op count"), Name),
Stack.Num(), ExpectedNum);
FString Error;
const bool bValid = Stack.ValidateChannelOrder(&Error);
if (!bValid)
{
AddError(FString::Printf(TEXT("%s shipping stack failed channel validation: %s"),
Name, *Error));
}
TestTrue(*FString::Printf(TEXT("%s shipping stack has a legal channel DAG"), Name), bValid);
};
// These are the eight shipped archetype builders. Keep the archetype list explicit so adding
// a builder without adding its validator assertion is visible in this test.
int32 MidChunkZ = 0;
if (MidChunkForSlot(FTestWorld::SlotTunnelNetwork, TEXT("TunnelNetwork"), MidChunkZ))
{
const FStrateGenerationParams Params =
World.StrateManager->GetGenerationParams(FIntVector(0, 0, MidChunkZ));
FVoxelOpStack Stack;
VoxelDensityOps::BuildTunnelNetworkStack(
Stack, Params, Seed, Generator->OriginSpineRadius, World.StrateManager.Get());
Validate(Stack, TEXT("TunnelNetwork"), 20);
}
if (MidChunkForSlot(FTestWorld::SlotFlatPlain, TEXT("FlatPlain"), MidChunkZ))
{
const FSlabGenerationParams Params =
World.StrateManager->GetSlabParamsForChunk(FIntVector(0, 0, MidChunkZ));
FVoxelOpStack Stack;
VoxelDensityOps::BuildSlabStack(
Stack, Params, Seed, Generator->OriginSpineRadius, World.StrateManager.Get());
Validate(Stack, TEXT("FlatPlain"), 6);
}
if (MidChunkForSlot(FTestWorld::SlotCrystalChamber, TEXT("CrystalChamber"), MidChunkZ))
{
const FSlabGenerationParams Params =
World.StrateManager->GetSlabParamsForChunk(FIntVector(0, 0, MidChunkZ));
FVoxelOpStack Stack;
VoxelDensityOps::BuildSlabStack(
Stack, Params, Seed, Generator->OriginSpineRadius, World.StrateManager.Get());
Validate(Stack, TEXT("CrystalChamber"), 6);
}
if (MidChunkForSlot(FTestWorld::SlotMaze, TEXT("Maze"), MidChunkZ))
{
const FMazeGenerationParams Params =
World.StrateManager->GetMazeParamsForChunk(FIntVector(0, 0, MidChunkZ));
FVoxelOpStack Stack;
VoxelDensityOps::BuildMazeStack(
Stack, Params, Seed, Generator->OriginSpineRadius, World.StrateManager.Get());
Validate(Stack, TEXT("Maze"), 8);
}
if (MidChunkForSlot(FTestWorld::SlotSurfaceWorld, TEXT("SurfaceWorld"), MidChunkZ))
{
const FSurfaceGenerationParams Params =
World.StrateManager->GetSurfaceParamsForChunk(FIntVector(0, 0, MidChunkZ));
FVoxelOpStack Stack;
VoxelDensityOps::BuildSurfaceStack(
Stack, Params, Seed, Generator->OriginSpineRadius, World.StrateManager.Get());
Validate(Stack, TEXT("SurfaceWorld"), 6);
}
if (MidChunkForSlot(FTestWorld::SlotVerticalShafts, TEXT("VerticalShafts"), MidChunkZ))
{
const FVerticalShaftParams Params =
World.StrateManager->GetVerticalShaftParamsForChunk(FIntVector(0, 0, MidChunkZ));
FVoxelOpStack Stack;
VoxelDensityOps::BuildVerticalShaftStack(
Stack, Params, Seed, Generator->OriginSpineRadius, World.StrateManager.Get());
Validate(Stack, TEXT("VerticalShafts"), 11);
}
if (MidChunkForSlot(FTestWorld::SlotFloatingIsland, TEXT("FloatingIslands"), MidChunkZ))
{
const FFloatingIslandParams Params =
World.StrateManager->GetFloatingIslandParamsForChunk(FIntVector(0, 0, MidChunkZ));
FVoxelOpStack Stack;
VoxelDensityOps::BuildFloatingIslandStack(
Stack, Params, Seed, Generator->OriginSpineRadius, World.StrateManager.Get());
Validate(Stack, TEXT("FloatingIslands"), 8);
}
if (MidChunkForSlot(FTestWorld::SlotUnderwater, TEXT("Underwater"), MidChunkZ))
{
const FStrateGenerationParams Params =
World.StrateManager->GetGenerationParams(FIntVector(0, 0, MidChunkZ));
FVoxelOpStack Stack;
VoxelDensityOps::BuildTunnelNetworkStack(
Stack, Params, Seed, Generator->OriginSpineRadius, World.StrateManager.Get());
Validate(Stack, TEXT("Underwater"), 20);
}
// A declaration that reads an unavailable channel must fail, and a second write-only
// replacement must not silently erase the first producer. These controls pin the validator's
// two most important safety rules independently of the shipping builders.
{
FVoxelOpStack MissingProducer;
MissingProducer.Add(MakeUnique<FDeclarationProbeOp>(
VoxelOpChannels::Density, VoxelOpChannels::Density, true));
FString Error;
TestFalse(TEXT("a read before its first producer is rejected"),
MissingProducer.ValidateChannelOrder(&Error));
}
{
FVoxelOpStack Clobber;
Clobber.Add(MakeUnique<FDeclarationProbeOp>(
VoxelOpChannels::None, VoxelOpChannels::Density, false));
Clobber.Add(MakeUnique<FDeclarationProbeOp>(
VoxelOpChannels::None, VoxelOpChannels::Density, false));
FString Error;
TestFalse(TEXT("a second write-only replacement is rejected"),
Clobber.ValidateChannelOrder(&Error));
}
return true;
}
#endif // WITH_DEV_AUTOMATION_TESTS