wip(unreal): AVoxelWorld Core mode -- streaming, collision and carving through VoxelForgeCore

Luna, integration round 2 (BuildHost only): AVoxelWorld::bUseVoxelForgeCore (default
false) routes desired tiles, generation, upload/collision and carving through Core.
Core-only CoreClipRadius=3 / CoreMaxClipLevel=8 (owner's asset untouched); 25 cm Core
voxels in every cm<->voxel conversion; legacy brush sizes scaled to keep physical size;
UE-task JobScheduler, epoch-checked Mpsc results, existing RMC upload + collision cook;
carving via sequenced applyEditTransaction. Switch off: BuildDesiredTiles takes the old
path unchanged.
BuildHost proof (actor commandlet, staged copies of the owner's assets): all 2,226
initial tiles hash-match standalone Core; collision bodies as expected; anchor move
requests 1 / releases 1 tile; carved tile hash matches standalone; peak ~2.68 GiB.
Legacy CrossPlatformDigest automation: 115,000 samples pass (wrapper flagged an
analytics write outside isolation).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-08 20:33:46 +02:00
1 parent 29ca99af7f
commit 075e58187e
7 files changed
+2069 -118

No files matched your search

@@ -6,7 +6,15 @@
#include "Async/ParallelFor.h"
#include "Commandlets/Commandlet.h"
#include "Containers/StringConv.h"
#include "Components/SceneComponent.h"
#include "Engine/Engine.h"
#include "Engine/GameInstance.h"
#include "Engine/World.h"
#include "GameFramework/Actor.h"
#include "GameFramework/GameModeBase.h"
#include "GameFramework/WorldSettings.h"
#include "HAL/FileManager.h"
#include "HAL/PlatformProcess.h"
#include "HAL/PlatformTime.h"
#include "HAL/PlatformTLS.h"
#include "IImageWrapper.h"
@@ -48,6 +56,8 @@
#include "VoxelStrateComposer.h"
#include "VoxelTerrainOpDefinition.h"
#include "VoxelTypes.h"
#include "VoxelWorld.h"
#include "VoxelForgeCoreAssetAdapter.h"
#include "VoxelForgeCore/VoxelForgeCore.h"
#include <algorithm>
@@ -395,6 +405,7 @@ struct FExploreArguments
bool bPassageFloorWritesOverride = false;
int32 PassageFloorWrites = 1;
bool bStandaloneWorldExport = false;
bool bCoreWorldTest = false;
bool bRenderCameraOverride = false;
FVector RenderCamera = FVector::ZeroVector;
bool bRenderTargetOverride = false;
@@ -437,6 +448,9 @@ bool ParseArguments(const FString& Params, FExploreArguments& OutArguments, FStr
int32 StandaloneWorldExport = 0;
FParse::Value(*Params, TEXT("standaloneworldexport="), StandaloneWorldExport);
OutArguments.bStandaloneWorldExport = StandaloneWorldExport != 0;
int32 CoreWorldTest = 0;
FParse::Value(*Params, TEXT("coreworldtest="), CoreWorldTest);
OutArguments.bCoreWorldTest = CoreWorldTest != 0;
FParse::Value(*Params, TEXT("archetype="), ArchetypeText);
FParse::Value(*Params, TEXT("slot="), OutArguments.Slot);
FParse::Value(*Params, TEXT("strateref="), OutArguments.StrateReference);
@@ -965,6 +979,12 @@ bool ParseArguments(const FString& Params, FExploreArguments& OutArguments, FStr
OutError = TEXT("standaloneworldexport requires modes=export and settingsref, and must omit strateref.");
return false;
}
if (OutArguments.bCoreWorldTest
&& (OutArguments.SettingsReference.IsEmpty() || !OutArguments.StrateReference.IsEmpty()))
{
OutError = TEXT("coreworldtest requires settingsref and must omit strateref.");
return false;
}
if (OutText.IsEmpty())
{
@@ -13035,6 +13055,561 @@ bool RunStandaloneWorldExport(const FExploreArguments& Arguments, FString& OutEr
return true;
}
bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FString& OutError)
{
UVoxelSettings* AuthoredSettings = LoadObject<UVoxelSettings>(
nullptr, *Arguments.SettingsReference);
if (!AuthoredSettings)
{
OutError = FString::Printf(TEXT("Could not load Core actor test settings '%s'."),
*Arguments.SettingsReference);
return false;
}
TStrongObjectPtr<UVoxelStrateManager> Manager(NewObject<UVoxelStrateManager>(
GetTransientPackage(), NAME_None, RF_Transient));
int32 ResolvedSeed = AuthoredSettings->Seed;
if (const UVoxelSeasonAsset* Season = AuthoredSettings->Season.LoadSynchronous())
{
ResolvedSeed = Season->GetSeasonSeed();
}
if (!Manager.IsValid() || !Manager->Initialize(AuthoredSettings, ResolvedSeed))
{
OutError = TEXT("Core actor test could not resolve the owner's strate manager.");
return false;
}
FString Json;
if (!VoxelForgeCoreAdapter::SerializeResolvedWorld(
AuthoredSettings, Manager.Get(), 3, 8, Json, OutError))
{
return false;
}
// Build one Standalone reference before creating the actor world so the two full Core worlds
// never overlap in memory. The reference also precomputes the exact edit result and hashes.
// Construire la référence Standalone avant le monde acteur pour ne jamais garder deux mondes
// Core complets en mémoire simultanément ; elle calcule aussi les hachages après édition.
TMap<FVoxelTileKey, FVoxelForgeCoreTileHashRecord> ExpectedHashes;
TMap<FVoxelTileKey, FVoxelForgeCoreTileHashRecord> ExpectedPostEditHashes;
TSet<FVoxelTileKey> ExpectedInitialDesiredSet;
TArray<FVoxelTileKey> ExpectedAffectedTiles;
FVector CarveLocalPositionCm = FVector::ZeroVector;
FVoxelTileKey CarveTargetTile;
constexpr float CarveBrushRadiusLegacyVoxels = 5.0f;
constexpr double CoreVoxelSizeCm = 25.0;
const double CarveBrushRadiusCoreVoxels =
CarveBrushRadiusLegacyVoxels * static_cast<double>(VOXEL_SIZE) / CoreVoxelSizeCm;
uint64 ExpectedEditDigest = 0;
std::array<std::uint64_t, 4> InitialAggregates{};
std::size_t ExpectedInitialTileCount = 0;
{
FTCHARToUTF8 Utf8Json(*Json);
std::string CoreError;
std::optional<VoxelForgeCore::ResolvedWorldSnapshot> Snapshot =
VoxelForgeCore::ResolvedWorldSnapshot::FromJson(
std::string_view(Utf8Json.Get(), static_cast<std::size_t>(Utf8Json.Length())),
CoreError);
if (!Snapshot)
{
OutError = FString::Printf(TEXT("Core rejected runtime adapter JSON: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
VoxelForgeCore::World ReferenceWorld(std::move(*Snapshot));
const std::vector<VoxelForgeCore::TileKey> Desired = ReferenceWorld.desiredTiles({});
if (Desired.empty())
{
OutError = TEXT("Standalone Core returned an empty reference clipmap at the origin.");
return false;
}
ExpectedInitialTileCount = Desired.size();
std::int32_t FineMin[3] = {MAX_int32, MAX_int32, MAX_int32};
std::int32_t FineMax[3] = {MIN_int32, MIN_int32, MIN_int32};
std::int32_t ClipMin[3] = {MAX_int32, MAX_int32, MAX_int32};
std::int32_t ClipMax[3] = {MIN_int32, MIN_int32, MIN_int32};
for (const VoxelForgeCore::TileKey& Key : Desired)
{
const std::int32_t Extent = 32 << Key.lod;
const std::int32_t Origin[3] = {
Key.coordinate.x * Extent, Key.coordinate.y * Extent, Key.coordinate.z * Extent};
for (int32 Axis = 0; Axis < 3; ++Axis)
{
ClipMin[Axis] = FMath::Min(ClipMin[Axis], Origin[Axis]);
ClipMax[Axis] = FMath::Max(ClipMax[Axis], Origin[Axis] + Extent);
if (Key.lod < ReferenceWorld.configuration().fullResolutionLodCount)
{
FineMin[Axis] = FMath::Min(FineMin[Axis], Origin[Axis]);
FineMax[Axis] = FMath::Max(FineMax[Axis], Origin[Axis] + Extent);
}
}
}
if (FineMin[0] == MAX_int32 || ClipMin[0] == MAX_int32)
{
OutError = TEXT("Standalone Core reference clipmap bounds are invalid.");
return false;
}
VoxelForgeCore::ShapeBuildRequest Request;
Request.minVoxel = {FineMin[0], FineMin[1], FineMin[2]};
Request.maxVoxel = {FineMax[0], FineMax[1], FineMax[2]};
Request.clipMinVoxel = {ClipMin[0], ClipMin[1], ClipMin[2]};
Request.clipMaxVoxel = {ClipMax[0], ClipMax[1], ClipMax[2]};
Request.workerLimit = 8;
Request.detail = VoxelForgeCore::DetailVariant::coherent;
Request.includeFarWorld = true;
FStandaloneCoreAdapterScheduler Scheduler;
if (!ReferenceWorld.compile(Request, Scheduler, CoreError))
{
OutError = FString::Printf(TEXT("Standalone Core reference compile failed: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
ReferenceWorld.retainTileRegions(Desired);
VoxelForgeCore::TileGenerationOptions Options;
Options.hashDensity = true;
std::vector<FStandaloneCoreAdapterTileHashes> AggregateRows;
AggregateRows.reserve(Desired.size());
bool bFoundCarveVertex = false;
for (const VoxelForgeCore::TileKey& Key : Desired)
{
const std::optional<VoxelForgeCore::TileMesh> Mesh =
ReferenceWorld.generateTile(Key, Options, CoreError);
if (!Mesh || !Mesh->hasDensityHash || !Mesh->hasMeshHash
|| !Mesh->hasPayloadHash || !Mesh->hasTopologyHash)
{
OutError = FString::Printf(TEXT("Standalone Core hash generation failed: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
const FVoxelTileKey RuntimeKey(
FIntVector(Key.coordinate.x, Key.coordinate.y, Key.coordinate.z), Key.lod);
FVoxelForgeCoreTileHashRecord Record;
Record.Density = Mesh->densityHash;
Record.Mesh = Mesh->meshHash;
Record.Payload = Mesh->payloadHash;
Record.Topology = Mesh->topologyHash;
Record.Triangles = static_cast<int32>(Mesh->indices.size() / 3);
Record.bEmpty = Mesh->indices.empty();
Record.bHasHashes = true;
ExpectedHashes.Add(RuntimeKey, Record);
ExpectedInitialDesiredSet.Add(RuntimeKey);
AggregateRows.push_back({Key, {Record.Density, Record.Mesh, Record.Payload, Record.Topology}});
if (!bFoundCarveVertex && Key.lod == 0 && !Mesh->vertices.empty()
&& !Mesh->indices.empty())
{
const VoxelForgeCore::Vec3d Vertex = Mesh->vertices.front().position;
CarveLocalPositionCm = FVector(Vertex.x, Vertex.y, Vertex.z);
CarveTargetTile = RuntimeKey;
bFoundCarveVertex = true;
}
}
InitialAggregates = StandaloneCoreAggregateHashes(AggregateRows);
if (!bFoundCarveVertex)
{
OutError = TEXT("Standalone Core reference has no level-0 surface vertex for the carve fixture.");
return false;
}
VoxelForgeCore::EditTransaction Transaction;
Transaction.sequence = 1;
VoxelForgeCore::EditBrush Brush;
Brush.shape = VoxelForgeCore::EditBrush::Shape::sphere;
Brush.operation = VoxelForgeCore::EditBrush::Operation::densityOffset;
Brush.a = {CarveLocalPositionCm.X / CoreVoxelSizeCm,
CarveLocalPositionCm.Y / CoreVoxelSizeCm,
CarveLocalPositionCm.Z / CoreVoxelSizeCm};
Brush.halfExtent = {5.0, 5.0, 5.0};
Brush.radius = CarveBrushRadiusCoreVoxels;
Brush.falloff = 2.0f;
Brush.strength = -40.0f;
Transaction.brushes.push_back(Brush);
const std::optional<VoxelForgeCore::EditResult> Edit =
ReferenceWorld.applyEditTransaction(Transaction, Desired, CoreError);
if (!Edit)
{
OutError = FString::Printf(TEXT("Standalone Core reference edit failed: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
ExpectedEditDigest = Edit->digest;
for (const std::vector<VoxelForgeCore::TileKey>& ByLod : Edit->affectedByLod)
{
for (const VoxelForgeCore::TileKey& Key : ByLod)
{
const FVoxelTileKey RuntimeKey(
FIntVector(Key.coordinate.x, Key.coordinate.y, Key.coordinate.z), Key.lod);
ExpectedAffectedTiles.Add(RuntimeKey);
const std::optional<VoxelForgeCore::TileMesh> Mesh =
ReferenceWorld.generateTile(Key, Options, CoreError);
if (!Mesh || !Mesh->hasDensityHash || !Mesh->hasMeshHash
|| !Mesh->hasPayloadHash || !Mesh->hasTopologyHash)
{
OutError = FString::Printf(TEXT("Standalone Core edited tile hash failed: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
FVoxelForgeCoreTileHashRecord Record;
Record.Density = Mesh->densityHash;
Record.Mesh = Mesh->meshHash;
Record.Payload = Mesh->payloadHash;
Record.Topology = Mesh->topologyHash;
Record.Triangles = static_cast<int32>(Mesh->indices.size() / 3);
Record.bEmpty = Mesh->indices.empty();
Record.bHasHashes = true;
ExpectedPostEditHashes.Add(RuntimeKey, Record);
}
}
ExpectedAffectedTiles.Sort([](const FVoxelTileKey& A, const FVoxelTileKey& B)
{
if (A.Level != B.Level) return A.Level < B.Level;
if (A.Coord.X != B.Coord.X) return A.Coord.X < B.Coord.X;
if (A.Coord.Y != B.Coord.Y) return A.Coord.Y < B.Coord.Y;
return A.Coord.Z < B.Coord.Z;
});
if (!ExpectedPostEditHashes.Contains(CarveTargetTile)
|| ExpectedAffectedTiles.IsEmpty())
{
OutError = TEXT("Standalone Core edit fixture did not affect its carved level-0 tile.");
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] standalone_reference=1 tiles=%llu affected=%d target=(%d,%d,%d) lod=%d"),
static_cast<unsigned long long>(ExpectedInitialTileCount),
ExpectedAffectedTiles.Num(), CarveTargetTile.Coord.X, CarveTargetTile.Coord.Y,
CarveTargetTile.Coord.Z, CarveTargetTile.Level);
}
const UWorld::InitializationValues WorldInitialization = UWorld::InitializationValues()
.AllowAudioPlayback(false)
.RequiresHitProxies(false)
.CreatePhysicsScene(true)
.CreateNavigation(false)
.CreateAISystem(false)
.ShouldSimulatePhysics(true)
.SetDefaultGameMode(AGameModeBase::StaticClass())
.SetTransactional(false);
UWorld* TestWorld = UWorld::CreateWorld(
EWorldType::Game, false, FName(TEXT("VoxelForgeCoreActorValidationWorld")),
GetTransientPackage(), false, ERHIFeatureLevel::Num, &WorldInitialization);
if (TestWorld == nullptr)
{
OutError = TEXT("Could not create the transient BuildHost actor test world.");
return false;
}
UGameInstance* TestGameInstance = NewObject<UGameInstance>(GEngine);
FWorldContext& TestWorldContext = GEngine->CreateNewWorldContext(EWorldType::Game);
TestWorldContext.OwningGameInstance = TestGameInstance;
TestWorldContext.SetCurrentWorld(TestWorld);
TestWorld->SetGameInstance(TestGameInstance);
TestWorld->SetShouldTick(false);
TestGameInstance->Init();
TestWorld->AddToRoot();
struct FTransientWorldCleanup
{
UWorld* World = nullptr;
bool bHasEngineWorldContext = false;
uint64 PreviousFrameCounter = GFrameCounter;
~FTransientWorldCleanup()
{
if (World != nullptr)
{
if (World->HasBegunPlay())
{
World->BeginTearingDown();
World->EndPlay(EEndPlayReason::Quit);
}
if (UGameInstance* GameInstance = World->GetGameInstance())
{
GameInstance->Shutdown();
}
if (bHasEngineWorldContext && GEngine != nullptr)
{
GEngine->DestroyWorldContext(World);
}
World->DestroyWorld(false);
if (World->IsRooted()) World->RemoveFromRoot();
}
GFrameCounter = PreviousFrameCounter;
}
} Cleanup{TestWorld, true, GFrameCounter};
const FTransform ActorTransform = FTransform::Identity;
AVoxelWorld* Actor = TestWorld->SpawnActorDeferred<AVoxelWorld>(
AVoxelWorld::StaticClass(), ActorTransform, nullptr, nullptr,
ESpawnActorCollisionHandlingMethod::AlwaysSpawn);
if (Actor == nullptr)
{
OutError = TEXT("Could not spawn the transient AVoxelWorld for Core validation.");
return false;
}
Actor->Settings = AuthoredSettings;
Actor->bManageAtmosphere = false;
Actor->bUseVoxelForgeCore = true;
Actor->CoreClipRadius = 3;
Actor->CoreMaxClipLevel = 8;
Actor->bCaptureVoxelForgeCoreHashesForValidation = true;
Actor->FinishSpawning(ActorTransform);
if (!TestWorld->SetGameMode(FURL()))
{
OutError = TEXT("Could not create the transient BuildHost GameMode for actor validation.");
return false;
}
TestWorld->InitializeActorsForPlay(FURL());
TestWorld->BeginPlay();
Actor->SetActorTickEnabled(true);
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] world_initialized=1 world_begun=%d actor_initialized=%d actor_beginplay=%d"),
TestWorld->HasBegunPlay() ? 1 : 0, Actor->IsActorInitialized() ? 1 : 0,
Actor->HasActorBegunPlay() ? 1 : 0);
auto DesiredTilesLoaded = [](const AVoxelWorld* World)
{
if (World == nullptr || World->DesiredSorted.IsEmpty()) return false;
for (const FVoxelTileKey& Key : World->DesiredSorted)
{
if (!World->LoadedTiles.Contains(Key)) return false;
}
return true;
};
auto CollisionSetSettled = [](const AVoxelWorld* World)
{
if (World == nullptr) return false;
for (const FVoxelTileKey& Key : World->CollisionDesiredTiles)
{
if (!World->CollisionReadyTiles.Contains(Key)
&& !World->CollisionNotRequiredTiles.Contains(Key))
{
return false;
}
}
return true;
};
auto WaitForStable = [&](double TimeoutSeconds)
{
const double Deadline = FPlatformTime::Seconds() + TimeoutSeconds;
const double ProgressIntervalSeconds = 10.0;
double LastProgressSeconds = FPlatformTime::Seconds();
int32 TickCount = 0;
while (FPlatformTime::Seconds() < Deadline)
{
TestWorld->Tick(LEVELTICK_All, 0.016f);
// Match FTestWorldWrapper::TickTestWorld: each manually driven world tick must advance
// the global frame counter or registered actor ticks run only once in this harness.
++GFrameCounter;
++TickCount;
FPlatformProcess::Sleep(0.001f);
if (Actor->IsVoxelForgeCoreReady() && Actor->bAllChunksLoaded
&& Actor->PendingTiles.IsEmpty() && Actor->PendingCollisionCooks.IsEmpty()
&& Actor->PendingUnload.IsEmpty()
&& Actor->ActiveTaskCount.load(std::memory_order_acquire) == 0
&& DesiredTilesLoaded(Actor) && CollisionSetSettled(Actor))
{
return true;
}
const double NowSeconds = FPlatformTime::Seconds();
if (NowSeconds - LastProgressSeconds >= ProgressIntervalSeconds)
{
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] wait ticks=%d ready=%d desired=%d loaded=%d pending=%d cooks=%d active=%d"),
TickCount, Actor->IsVoxelForgeCoreReady() ? 1 : 0,
Actor->DesiredSorted.Num(), Actor->LoadedTiles.Num(), Actor->PendingTiles.Num(),
Actor->PendingCollisionCooks.Num(),
Actor->ActiveTaskCount.load(std::memory_order_acquire));
LastProgressSeconds = NowSeconds;
}
}
return false;
};
if (!WaitForStable(600.0))
{
OutError = FString::Printf(
TEXT("AVoxelWorld Core initial clipmap did not settle: ready=%d desired=%d loaded=%d pending=%d cooks=%d active=%d"),
Actor->IsVoxelForgeCoreReady() ? 1 : 0, Actor->DesiredSorted.Num(),
Actor->LoadedTiles.Num(), Actor->PendingTiles.Num(),
Actor->PendingCollisionCooks.Num(),
Actor->ActiveTaskCount.load(std::memory_order_acquire));
return false;
}
TSet<FVoxelTileKey> ActualInitialDesiredSet;
for (const FVoxelTileKey& Key : Actor->DesiredSorted) ActualInitialDesiredSet.Add(Key);
if (ActualInitialDesiredSet.Num() != ExpectedInitialDesiredSet.Num())
{
OutError = FString::Printf(TEXT("Core actor desired %d tiles; standalone reference desired %d."),
ActualInitialDesiredSet.Num(), ExpectedInitialDesiredSet.Num());
return false;
}
for (const FVoxelTileKey& Key : ExpectedInitialDesiredSet)
{
if (!ActualInitialDesiredSet.Contains(Key))
{
OutError = FString::Printf(TEXT("Core actor initial clipmap omitted tile (%d,%d,%d) lod=%d."),
Key.Coord.X, Key.Coord.Y, Key.Coord.Z, Key.Level);
return false;
}
const FVoxelForgeCoreTileHashRecord* Actual = Actor->VoxelForgeCoreTileHashes.Find(Key);
const FVoxelForgeCoreTileHashRecord* Expected = ExpectedHashes.Find(Key);
if (Actual == nullptr || Expected == nullptr || !Actual->bHasHashes
|| Actual->Density != Expected->Density || Actual->Mesh != Expected->Mesh
|| Actual->Payload != Expected->Payload || Actual->Topology != Expected->Topology
|| Actual->Triangles != Expected->Triangles || Actual->bEmpty != Expected->bEmpty)
{
OutError = FString::Printf(
TEXT("Core actor tile hash mismatch at (%d,%d,%d) lod=%d."),
Key.Coord.X, Key.Coord.Y, Key.Coord.Z, Key.Level);
return false;
}
}
const int32 CollisionRadius = FMath::Clamp(AuthoredSettings->PawnCollisionRadiusChunks, 1, 4);
TSet<FVoxelTileKey> ExpectedCollisionSet;
int32 ExpectedBodyCount = 0;
int32 ExpectedNoBodyCount = 0;
int32 ActualNoBodyCount = 0;
for (int32 Z = -CollisionRadius; Z <= CollisionRadius; ++Z)
for (int32 Y = -CollisionRadius; Y <= CollisionRadius; ++Y)
for (int32 X = -CollisionRadius; X <= CollisionRadius; ++X)
{
const FVoxelTileKey Key(FIntVector(X, Y, Z), 0);
ExpectedCollisionSet.Add(Key);
const FVoxelForgeCoreTileHashRecord* Record = ExpectedHashes.Find(Key);
if (Record == nullptr)
{
OutError = FString::Printf(TEXT("Standalone reference omitted collision tile (%d,%d,%d)."),
X, Y, Z);
return false;
}
if (Record->bEmpty) ++ExpectedNoBodyCount;
else ++ExpectedBodyCount;
if (Actor->CollisionNotRequiredTiles.Contains(Key)) ++ActualNoBodyCount;
}
if (Actor->CollisionDesiredTiles.Num() != ExpectedCollisionSet.Num()
|| Actor->CollisionReadyTiles.Num() != ExpectedBodyCount
|| ActualNoBodyCount != ExpectedNoBodyCount)
{
OutError = FString::Printf(
TEXT("Core collision count mismatch: desired=%d/%d ready=%d/%d no_body=%d/%d."),
Actor->CollisionDesiredTiles.Num(), ExpectedCollisionSet.Num(),
Actor->CollisionReadyTiles.Num(), ExpectedBodyCount,
ActualNoBodyCount, ExpectedNoBodyCount);
return false;
}
for (const FVoxelTileKey& Key : ExpectedCollisionSet)
{
const FVoxelForgeCoreTileHashRecord& Record = ExpectedHashes.FindChecked(Key);
const bool bExpectedCollisionReady = !Record.bEmpty;
if (!Actor->CollisionDesiredTiles.Contains(Key)
|| (bExpectedCollisionReady && !Actor->CollisionReadyTiles.Contains(Key))
|| (!bExpectedCollisionReady && !Actor->CollisionNotRequiredTiles.Contains(Key)))
{
OutError = FString::Printf(TEXT("Core collision result differs for tile (%d,%d,%d)."),
Key.Coord.X, Key.Coord.Y, Key.Coord.Z);
return false;
}
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] pass phase=initial tiles=%llu collision_desired=%d collision_ready=%d collision_no_body=%d density=0x%016llx mesh=0x%016llx payload=0x%016llx topology=0x%016llx"),
static_cast<unsigned long long>(ExpectedInitialTileCount), ExpectedCollisionSet.Num(),
Actor->CollisionReadyTiles.Num(), Actor->CollisionNotRequiredTiles.Num(),
static_cast<unsigned long long>(InitialAggregates[0]),
static_cast<unsigned long long>(InitialAggregates[1]),
static_cast<unsigned long long>(InitialAggregates[2]),
static_cast<unsigned long long>(InitialAggregates[3]));
AActor* Anchor = TestWorld->SpawnActor<AActor>(AActor::StaticClass());
if (Anchor == nullptr)
{
OutError = TEXT("Could not spawn the Core streaming anchor fixture.");
return false;
}
USceneComponent* AnchorRoot = NewObject<USceneComponent>(Anchor, NAME_None, RF_Transient);
if (AnchorRoot == nullptr)
{
OutError = TEXT("Could not create the Core streaming anchor root component.");
return false;
}
Anchor->SetRootComponent(AnchorRoot);
AnchorRoot->RegisterComponent();
constexpr double CoreTileExtentCm = 32.0 * 25.0;
const FVector OldAnchorPosition(CoreTileExtentCm * 20.0, 0.0, 0.0);
const FVector NewAnchorPosition(CoreTileExtentCm * 21.0, 0.0, 0.0);
Anchor->SetActorLocation(OldAnchorPosition);
Actor->RegisterStreamingAnchor(
Anchor, EVoxelAnchorPolicy::CollisionOnly, 0, 0, 0);
if (!WaitForStable(180.0))
{
OutError = TEXT("Core CollisionOnly anchor did not finish its initial tile request.");
return false;
}
const FVoxelTileKey OldAnchorKey(FIntVector(20, 0, 0), 0);
const FVoxelTileKey NewAnchorKey(FIntVector(21, 0, 0), 0);
if (!Actor->DesiredSorted.Contains(OldAnchorKey)
|| !Actor->LoadedTiles.Contains(OldAnchorKey)
|| !Actor->CollisionDesiredTiles.Contains(OldAnchorKey))
{
OutError = TEXT("Core did not request and load the original anchor tile.");
return false;
}
Anchor->SetActorLocation(NewAnchorPosition);
if (!WaitForStable(180.0))
{
OutError = TEXT("Core did not settle after moving the streaming anchor by one tile.");
return false;
}
if (!Actor->DesiredSorted.Contains(NewAnchorKey)
|| !Actor->LoadedTiles.Contains(NewAnchorKey)
|| !Actor->CollisionDesiredTiles.Contains(NewAnchorKey)
|| Actor->DesiredSorted.Contains(OldAnchorKey)
|| Actor->LoadedTiles.Contains(OldAnchorKey)
|| Actor->CollisionDesiredTiles.Contains(OldAnchorKey))
{
OutError = TEXT("Core anchor move did not request the entering tile and release the leaving tile.");
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] pass phase=anchor old=(20,0,0) new=(21,0,0) requested=1 released=1"));
Actor->CarveAtPosition(Actor->GetActorTransform().TransformPosition(CarveLocalPositionCm),
CarveBrushRadiusLegacyVoxels, 40.0f);
if (Actor->LastVoxelForgeCoreEditSequence != 1
|| Actor->LastVoxelForgeCoreEditDigest != ExpectedEditDigest
|| Actor->LastVoxelForgeCoreAffectedTiles != ExpectedAffectedTiles)
{
OutError = TEXT("Core actor carve transaction sequence, digest, or affected tile list differs from Standalone.");
return false;
}
if (!WaitForStable(240.0))
{
OutError = TEXT("Core actor carve remesh/collision work did not settle.");
return false;
}
for (const FVoxelTileKey& Key : ExpectedAffectedTiles)
{
const FVoxelForgeCoreTileHashRecord* Actual = Actor->VoxelForgeCoreTileHashes.Find(Key);
const FVoxelForgeCoreTileHashRecord* Expected = ExpectedPostEditHashes.Find(Key);
if (Actual == nullptr || Expected == nullptr || !Actual->bHasHashes
|| Actual->Density != Expected->Density || Actual->Mesh != Expected->Mesh
|| Actual->Payload != Expected->Payload || Actual->Topology != Expected->Topology
|| Actual->Triangles != Expected->Triangles || Actual->bEmpty != Expected->bEmpty)
{
OutError = FString::Printf(
TEXT("Core actor edited tile hash differs from Standalone at (%d,%d,%d) lod=%d."),
Key.Coord.X, Key.Coord.Y, Key.Coord.Z, Key.Level);
return false;
}
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] pass phase=edit sequence=%llu digest=0x%016llx affected=%d target=(%d,%d,%d) lod=%d hash=0x%016llx"),
static_cast<unsigned long long>(Actor->LastVoxelForgeCoreEditSequence),
static_cast<unsigned long long>(Actor->LastVoxelForgeCoreEditDigest),
ExpectedAffectedTiles.Num(), CarveTargetTile.Coord.X, CarveTargetTile.Coord.Y,
CarveTargetTile.Coord.Z, CarveTargetTile.Level,
static_cast<unsigned long long>(ExpectedPostEditHashes.FindChecked(CarveTargetTile).Mesh));
return true;
}
} // namespace
UVoxelForgeExploreCommandlet::UVoxelForgeExploreCommandlet()
@@ -13078,6 +13653,17 @@ int32 RunExploreCase(const FString& Params, FString* OutJson)
return 1;
}
if (Arguments.bCoreWorldTest)
{
if (!RunCoreWorldActorIntegrationTest(Arguments, Error))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeCoreWorldTest] failed: %s"), *Error);
return 1;
}
UE_LOG(LogTemp, Display, TEXT("[VoxelForgeCoreWorldTest] pass complete=1"));
return 0;
}
if (Arguments.bStandaloneWorldExport)
{
if (!RunStandaloneWorldExport(Arguments, Error, OutJson))