wip(assembler): M1 round 5 -- native SurfaceWorld manifest delivered into the Unreal Core route
Manifest imported into UVoxelSettings (hash 8933693d9560ba9f), folded into the season content hash only when Core is on. BuildHost: W1/W8/W16 stable, 2553 surface tiles match standalone, 432 LOD transitions, collision on all 36 expected tiles. Matched timing 96.8 s native vs 197.3 s bridge (2.04x). Collision support tiles wait for Core region admission. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
1 parent
2d0cec90e4
commit
f67b519e75
11 files changed
+1002
-45
No files matched your search
@@ -2,9 +2,12 @@
|
||||
|
||||
#include "Dom/JsonObject.h"
|
||||
#include "Policies/CondensedJsonPrintPolicy.h"
|
||||
#include "Serialization/JsonReader.h"
|
||||
#include "Serialization/JsonSerializer.h"
|
||||
#include "Serialization/JsonWriter.h"
|
||||
#include "UObject/UnrealType.h"
|
||||
#include "UObject/TextProperty.h"
|
||||
#include <VoxelForgeCore/VoxelForgeCore.h>
|
||||
#include "VoxelSeasonAsset.h"
|
||||
#include "VoxelSettings.h"
|
||||
#include "VoxelStrateDefinition.h"
|
||||
@@ -271,6 +274,95 @@ void WriteCoreJsonValue(FCoreJsonWriter& Writer, const TCHAR* Identifier,
|
||||
Writer.WriteNull();
|
||||
}
|
||||
}
|
||||
|
||||
bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
|
||||
const UVoxelStrateManager* StrateManager,
|
||||
const TSharedRef<FJsonObject>& Root,
|
||||
FString& OutError)
|
||||
{
|
||||
FString ManifestJson;
|
||||
FString ManifestHash;
|
||||
if (!Settings->GetWorldAssemblyManifestJson(ManifestJson, ManifestHash, OutError))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (ManifestJson.IsEmpty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
FTCHARToUTF8 Utf8Json(*ManifestJson);
|
||||
std::string CoreError;
|
||||
const std::optional<VoxelForgeCore::WorldAssemblyManifest> Manifest =
|
||||
VoxelForgeCore::WorldAssemblyManifest::FromJson(
|
||||
std::string_view(Utf8Json.Get(), static_cast<std::size_t>(Utf8Json.Length())), CoreError);
|
||||
if (!Manifest)
|
||||
{
|
||||
OutError = FString::Printf(TEXT("Core world assembly validation failed: %s"),
|
||||
UTF8_TO_TCHAR(CoreError.c_str()));
|
||||
return false;
|
||||
}
|
||||
if (Manifest->recipe.seed != static_cast<uint32>(StrateManager->GetWorldSeed()))
|
||||
{
|
||||
OutError = TEXT("Core world assembly recipe seed does not match the resolved strate layout.");
|
||||
return false;
|
||||
}
|
||||
|
||||
int32 SurfaceSlotCount = 0;
|
||||
for (const FStrateSlot& Slot : StrateManager->GetLayout())
|
||||
{
|
||||
if (Slot.Definition != nullptr
|
||||
&& Slot.Definition->GeneratorType == ECaveGeneratorType::SurfaceWorld)
|
||||
{
|
||||
++SurfaceSlotCount;
|
||||
}
|
||||
}
|
||||
if (SurfaceSlotCount == 0
|
||||
|| Manifest->plan.strates.size() != static_cast<std::size_t>(SurfaceSlotCount))
|
||||
{
|
||||
OutError = FString::Printf(
|
||||
TEXT("Core world assembly has %d plans for %d resolved SurfaceWorld strates."),
|
||||
static_cast<int32>(Manifest->plan.strates.size()), SurfaceSlotCount);
|
||||
return false;
|
||||
}
|
||||
|
||||
TSet<int32> MatchedStrateIndices;
|
||||
for (const VoxelForgeCore::WorldAssemblyStratumPlanV1& Plan : Manifest->plan.strates)
|
||||
{
|
||||
const FStrateSlot* MatchingSlot = StrateManager->GetLayout().FindByPredicate(
|
||||
[&Plan](const FStrateSlot& Slot) { return Slot.StrateIndex == Plan.strateIndex; });
|
||||
if (MatchingSlot == nullptr || MatchingSlot->Definition == nullptr
|
||||
|| MatchingSlot->Definition->GeneratorType != ECaveGeneratorType::SurfaceWorld
|
||||
|| MatchingSlot->TopChunkZ != Plan.topChunkZ
|
||||
|| MatchingSlot->BottomChunkZ != Plan.bottomChunkZ
|
||||
|| MatchedStrateIndices.Contains(Plan.strateIndex))
|
||||
{
|
||||
OutError = FString::Printf(
|
||||
TEXT("Core world assembly plan for strate %d does not exactly match a unique resolved SurfaceWorld slot."),
|
||||
Plan.strateIndex);
|
||||
return false;
|
||||
}
|
||||
MatchedStrateIndices.Add(Plan.strateIndex);
|
||||
}
|
||||
|
||||
const std::string CanonicalJson = Manifest->ToJson();
|
||||
if (CanonicalJson.empty())
|
||||
{
|
||||
OutError = TEXT("Core world assembly manifest could not be canonicalized for the snapshot.");
|
||||
return false;
|
||||
}
|
||||
const FString CanonicalText = UTF8_TO_TCHAR(CanonicalJson.c_str());
|
||||
TSharedPtr<FJsonObject> ManifestObject;
|
||||
const TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(CanonicalText);
|
||||
if (!FJsonSerializer::Deserialize(Reader, ManifestObject) || !ManifestObject.IsValid())
|
||||
{
|
||||
OutError = TEXT("Core world assembly canonical JSON could not be embedded in the snapshot.");
|
||||
return false;
|
||||
}
|
||||
Root->SetObjectField(TEXT("world_assembly"), ManifestObject.ToSharedRef());
|
||||
Root->SetStringField(TEXT("world_assembly_content_hash"), ManifestHash);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool VoxelForgeCoreAdapter::SerializeResolvedWorld(
|
||||
@@ -380,6 +472,10 @@ bool VoxelForgeCoreAdapter::SerializeResolvedWorld(
|
||||
Passages.Add(MakeShared<FJsonValueObject>(CorePassageJson(Passage)));
|
||||
}
|
||||
Root->SetArrayField(TEXT("passages"), MoveTemp(Passages));
|
||||
if (!AttachValidatedWorldAssembly(Settings, StrateManager, Root, OutError))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Root->SetArrayField(TEXT("density_samples"), {});
|
||||
TSharedRef<FJsonObject> ViewerStart = MakeShared<FJsonObject>();
|
||||
ViewerStart->SetBoolField(TEXT("found_open_space"), false);
|
||||
|
||||
@@ -2,6 +2,13 @@
|
||||
|
||||
#include "VoxelSeasonAsset.h"
|
||||
|
||||
#if WITH_EDITOR
|
||||
#include "Misc/FileHelper.h"
|
||||
#endif
|
||||
|
||||
#include <VoxelForgeCore/VoxelForgeCore.h>
|
||||
#include <string>
|
||||
|
||||
int32 UVoxelSettings::GetEffectiveWorldSeed() const
|
||||
{
|
||||
if (!Season.IsNull())
|
||||
@@ -49,3 +56,123 @@ int32 UVoxelSettings::GetEffectiveSeasonNumber() const
|
||||
}
|
||||
return CurrentSeason;
|
||||
}
|
||||
|
||||
bool UVoxelSettings::SetWorldAssemblyManifestJson(const FString& InJson, FString& OutReport)
|
||||
{
|
||||
OutReport.Reset();
|
||||
if (InJson.IsEmpty())
|
||||
{
|
||||
OutReport = TEXT("world assembly manifest JSON is empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
FTCHARToUTF8 Utf8Json(*InJson);
|
||||
std::string CoreError;
|
||||
const std::optional<VoxelForgeCore::WorldAssemblyManifest> Manifest =
|
||||
VoxelForgeCore::WorldAssemblyManifest::FromJson(
|
||||
std::string_view(Utf8Json.Get(), static_cast<std::size_t>(Utf8Json.Length())), CoreError);
|
||||
if (!Manifest)
|
||||
{
|
||||
OutReport = FString::Printf(TEXT("world assembly manifest is invalid: %s"),
|
||||
UTF8_TO_TCHAR(CoreError.c_str()));
|
||||
return false;
|
||||
}
|
||||
if (Manifest->recipe.seed != static_cast<uint32>(GetEffectiveWorldSeed()))
|
||||
{
|
||||
OutReport = FString::Printf(
|
||||
TEXT("world assembly seed %u does not match the effective settings seed %d"),
|
||||
Manifest->recipe.seed, GetEffectiveWorldSeed());
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string CanonicalJson = Manifest->ToJson();
|
||||
if (CanonicalJson.empty())
|
||||
{
|
||||
OutReport = TEXT("world assembly manifest could not be canonicalized");
|
||||
return false;
|
||||
}
|
||||
Modify();
|
||||
WorldAssemblyManifestJson = UTF8_TO_TCHAR(CanonicalJson.c_str());
|
||||
WorldAssemblyManifestHash = FString::Printf(TEXT("%016llx"),
|
||||
static_cast<unsigned long long>(Manifest->contentHash));
|
||||
MarkPackageDirty();
|
||||
OutReport = FString::Printf(TEXT("imported native world assembly %s (%d stratum plans)"),
|
||||
*WorldAssemblyManifestHash, static_cast<int32>(Manifest->plan.strates.size()));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UVoxelSettings::GetWorldAssemblyManifestJson(
|
||||
FString& OutCanonicalJson, FString& OutContentHash, FString& OutError) const
|
||||
{
|
||||
OutCanonicalJson.Reset();
|
||||
OutContentHash.Reset();
|
||||
OutError.Reset();
|
||||
if (WorldAssemblyManifestJson.IsEmpty())
|
||||
{
|
||||
if (!WorldAssemblyManifestHash.IsEmpty())
|
||||
{
|
||||
OutError = TEXT("world assembly hash is set but its manifest JSON is empty");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
FTCHARToUTF8 Utf8Json(*WorldAssemblyManifestJson);
|
||||
std::string CoreError;
|
||||
const std::optional<VoxelForgeCore::WorldAssemblyManifest> Manifest =
|
||||
VoxelForgeCore::WorldAssemblyManifest::FromJson(
|
||||
std::string_view(Utf8Json.Get(), static_cast<std::size_t>(Utf8Json.Length())), CoreError);
|
||||
if (!Manifest)
|
||||
{
|
||||
OutError = FString::Printf(TEXT("cooked world assembly manifest is invalid: %s"),
|
||||
UTF8_TO_TCHAR(CoreError.c_str()));
|
||||
return false;
|
||||
}
|
||||
if (Manifest->recipe.seed != static_cast<uint32>(GetEffectiveWorldSeed()))
|
||||
{
|
||||
OutError = TEXT("cooked world assembly seed no longer matches the effective settings seed");
|
||||
return false;
|
||||
}
|
||||
const FString ComputedHash = FString::Printf(TEXT("%016llx"),
|
||||
static_cast<unsigned long long>(Manifest->contentHash));
|
||||
if (!ComputedHash.Equals(WorldAssemblyManifestHash, ESearchCase::IgnoreCase))
|
||||
{
|
||||
OutError = FString::Printf(
|
||||
TEXT("cooked world assembly hash mismatch (stored %s, computed %s)"),
|
||||
*WorldAssemblyManifestHash, *ComputedHash);
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string CanonicalJson = Manifest->ToJson();
|
||||
if (CanonicalJson.empty())
|
||||
{
|
||||
OutError = TEXT("cooked world assembly manifest could not be canonicalized");
|
||||
return false;
|
||||
}
|
||||
OutCanonicalJson = UTF8_TO_TCHAR(CanonicalJson.c_str());
|
||||
OutContentHash = ComputedHash;
|
||||
return true;
|
||||
}
|
||||
|
||||
void UVoxelSettings::ImportWorldAssemblyManifestJson()
|
||||
{
|
||||
#if WITH_EDITOR
|
||||
FString JsonText;
|
||||
FString Report;
|
||||
if (SourceWorldAssemblyManifestJson.FilePath.IsEmpty()
|
||||
|| !FFileHelper::LoadFileToString(JsonText, *SourceWorldAssemblyManifestJson.FilePath))
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("[VoxelSettings] Could not read world assembly manifest '%s'."),
|
||||
*SourceWorldAssemblyManifestJson.FilePath);
|
||||
return;
|
||||
}
|
||||
if (!SetWorldAssemblyManifestJson(JsonText, Report))
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("[VoxelSettings] World assembly import failed: %s"), *Report);
|
||||
return;
|
||||
}
|
||||
UE_LOG(LogTemp, Log, TEXT("[VoxelSettings] %s"), *Report);
|
||||
#else
|
||||
UE_LOG(LogTemp, Error, TEXT("[VoxelSettings] World assembly JSON import is editor-only."));
|
||||
#endif
|
||||
}
|
||||
@@ -40,6 +40,7 @@
|
||||
#include "Engine/World.h"
|
||||
#include "Misc/IQueuedWork.h"
|
||||
#include "Misc/QueuedThreadPool.h"
|
||||
#include "Misc/SecureHash.h"
|
||||
#include "RealtimeMeshCollisionLibrary.h"
|
||||
#include "Camera/PlayerCameraManager.h"
|
||||
#include "Async/Async.h"
|
||||
@@ -7259,6 +7260,15 @@ bool AVoxelWorld::InitializeVoxelForgeCoreWorld()
|
||||
UTF8_TO_TCHAR(CoreError.c_str()));
|
||||
return false;
|
||||
}
|
||||
if (const VoxelForgeCore::WorldAssemblyManifest* Assembly = Snapshot->worldAssembly())
|
||||
{
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCoreRuntime] world_assembly_hash=%016llx recipe_hash=%016llx plan_hash=%016llx strates=%d"),
|
||||
static_cast<unsigned long long>(Assembly->contentHash),
|
||||
static_cast<unsigned long long>(Assembly->recipe.contentHash),
|
||||
static_cast<unsigned long long>(Assembly->plan.contentHash),
|
||||
static_cast<int32>(Assembly->plan.strates.size()));
|
||||
}
|
||||
|
||||
TSharedPtr<FVoxelForgeCoreRuntimeState, ESPMode::ThreadSafe> State =
|
||||
MakeShared<FVoxelForgeCoreRuntimeState, ESPMode::ThreadSafe>();
|
||||
@@ -7786,6 +7796,18 @@ void AVoxelWorld::RequestCollisionGateSupportTile(const FVoxelTileKey& SupportTi
|
||||
}
|
||||
bForceDesiredRebuild |= bNeedsDesiredRebuild;
|
||||
|
||||
// Core surface tiles must enter through the desired-set region compile before collision work.
|
||||
// Les tuiles de surface Core doivent passer par la compilation de région avant la collision.
|
||||
if (bUseVoxelForgeCore && !bDesiredAtRequest)
|
||||
{
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCollisionGate] support_request_deferred tile=(%d,%d,%d) "
|
||||
"reason=core_region_admission desired=0"),
|
||||
SupportTile.Coord.X, SupportTile.Coord.Y, SupportTile.Coord.Z);
|
||||
bPawnGateSupportRequestReported = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Preserve the established critical floor ordering when it already belongs to this desired
|
||||
// epoch. Before the first build there is no array to promote into; LoadTile below still uses
|
||||
// the same CollisionCritical work class and priority queue.
|
||||
@@ -13222,7 +13244,47 @@ int32 AVoxelWorld::GetCurrentSeason() const
|
||||
|
||||
FString AVoxelWorld::GetCurrentSeasonContentHash() const
|
||||
{
|
||||
return StrateManager ? StrateManager->GetSeasonContentHash() : FString();
|
||||
const FString SeasonHash = StrateManager ? StrateManager->GetSeasonContentHash() : FString();
|
||||
// Only active Core generation includes the optional native surface manifest in world identity.
|
||||
// Seul le Core actif inclut le manifeste de surface natif dans l'identite du monde.
|
||||
if (!Settings || !bUseVoxelForgeCore)
|
||||
{
|
||||
return SeasonHash;
|
||||
}
|
||||
|
||||
FString AssemblyJson;
|
||||
FString AssemblyHash;
|
||||
FString AssemblyError;
|
||||
if (!Settings->GetWorldAssemblyManifestJson(AssemblyJson, AssemblyHash, AssemblyError))
|
||||
{
|
||||
return FString();
|
||||
}
|
||||
if (AssemblyHash.IsEmpty())
|
||||
{
|
||||
return SeasonHash;
|
||||
}
|
||||
|
||||
FString Payload = SeasonHash;
|
||||
if (Payload.IsEmpty())
|
||||
{
|
||||
Payload = FString::Printf(TEXT("seed=%d"), StrateManager ? StrateManager->GetWorldSeed()
|
||||
: Settings->GetEffectiveWorldSeed());
|
||||
if (StrateManager)
|
||||
{
|
||||
for (const FStrateSlot& Slot : StrateManager->GetLayout())
|
||||
{
|
||||
Payload += FString::Printf(TEXT("|strate=%d,%d,%d,%s"), Slot.StrateIndex,
|
||||
Slot.TopChunkZ, Slot.BottomChunkZ,
|
||||
Slot.Definition ? *Slot.Definition->GetPathName() : TEXT("missing"));
|
||||
}
|
||||
}
|
||||
}
|
||||
Payload += TEXT("|world_assembly=");
|
||||
Payload += AssemblyHash;
|
||||
FTCHARToUTF8 Utf8Payload(*Payload);
|
||||
uint8 Digest[FSHA1::DigestSize];
|
||||
FSHA1::HashBuffer(Utf8Payload.Get(), Utf8Payload.Length(), Digest);
|
||||
return BytesToHex(Digest, FSHA1::DigestSize);
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
|
||||
@@ -28,6 +28,14 @@ public:
|
||||
float GetEffectiveWorldRadiusVoxels() const;
|
||||
int32 GetEffectiveSeasonNumber() const;
|
||||
|
||||
/** Import a reviewed native SurfaceWorld plan; empty preserves the existing Core/legacy routes. */
|
||||
VOXELFORGE_API bool SetWorldAssemblyManifestJson(const FString& InJson, FString& OutReport);
|
||||
VOXELFORGE_API bool GetWorldAssemblyManifestJson(FString& OutCanonicalJson, FString& OutContentHash,
|
||||
FString& OutError) const;
|
||||
|
||||
UFUNCTION(CallInEditor, Category = "Voxel|Strates")
|
||||
void ImportWorldAssemblyManifestJson();
|
||||
|
||||
//=========================================================================
|
||||
// STREAMING (distance de vue)
|
||||
//=========================================================================
|
||||
@@ -365,6 +373,21 @@ public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Voxel|Strates")
|
||||
TSoftObjectPtr<UVoxelSeasonAsset> Season;
|
||||
|
||||
// Optional reviewed Core surface plan. The canonical JSON stays in this cooked settings asset;
|
||||
// its Core FNV digest is included in GetCurrentSeasonContentHash when present.
|
||||
// Plan de surface Core facultatif; le JSON canonique est cuit avec les reglages et son hash
|
||||
// Core est inclus dans GetCurrentSeasonContentHash lorsqu'il est present.
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Voxel|Strates", meta = (MultiLine = true))
|
||||
FString WorldAssemblyManifestJson;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Voxel|Strates")
|
||||
FString WorldAssemblyManifestHash;
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
UPROPERTY(EditAnywhere, Category = "Voxel|Strates|World Assembly", meta = (FilePathFilter = "json"))
|
||||
FFilePath SourceWorldAssemblyManifestJson;
|
||||
#endif
|
||||
|
||||
// Numéro de saison (incrementé par ChangeSeed). Purement informatif côté gameplay.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Strates")
|
||||
int32 CurrentSeason = 1;
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
#include <atomic>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
@@ -341,6 +342,7 @@ struct FExploreArguments
|
||||
int32 Slot = 4;
|
||||
FString StrateReference;
|
||||
FString SettingsReference;
|
||||
FString CoreWorldAssemblyManifestPath;
|
||||
bool bRender = true;
|
||||
bool bWalk = true;
|
||||
bool bExport = true;
|
||||
@@ -457,6 +459,7 @@ bool ParseArguments(const FString& Params, FExploreArguments& OutArguments, FStr
|
||||
FParse::Value(*Params, TEXT("slot="), OutArguments.Slot);
|
||||
FParse::Value(*Params, TEXT("strateref="), OutArguments.StrateReference);
|
||||
FParse::Value(*Params, TEXT("settingsref="), OutArguments.SettingsReference);
|
||||
FParse::Value(*Params, TEXT("coreworldassembly="), OutArguments.CoreWorldAssemblyManifestPath);
|
||||
FParse::Value(*Params, TEXT("modes="), ModesText);
|
||||
// FParse::Value treats a comma as a command-line separator in some UE commandlet
|
||||
// launch paths. Recover the complete token so both -modes=walk,export and the
|
||||
@@ -987,6 +990,11 @@ bool ParseArguments(const FString& Params, FExploreArguments& OutArguments, FStr
|
||||
OutError = TEXT("coreworldtest requires settingsref and must omit strateref.");
|
||||
return false;
|
||||
}
|
||||
if (!OutArguments.CoreWorldAssemblyManifestPath.IsEmpty() && !OutArguments.bCoreWorldTest)
|
||||
{
|
||||
OutError = TEXT("coreworldassembly is accepted only by coreworldtest.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (OutText.IsEmpty())
|
||||
{
|
||||
@@ -13197,6 +13205,30 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Arguments.CoreWorldAssemblyManifestPath.IsEmpty())
|
||||
{
|
||||
const FString ManifestPath = FPaths::ConvertRelativePathToFull(
|
||||
Arguments.CoreWorldAssemblyManifestPath);
|
||||
if (!IsUnderVoxelForgeSaved(ManifestPath))
|
||||
{
|
||||
OutError = TEXT("Core world assembly test input must be staged under the plugin Saved directory.");
|
||||
return false;
|
||||
}
|
||||
FString ManifestJson;
|
||||
if (!FFileHelper::LoadFileToString(ManifestJson, *ManifestPath))
|
||||
{
|
||||
OutError = FString::Printf(TEXT("Could not read staged Core world assembly '%s'."), *ManifestPath);
|
||||
return false;
|
||||
}
|
||||
FString ImportReport;
|
||||
if (!AuthoredSettings->SetWorldAssemblyManifestJson(ManifestJson, ImportReport))
|
||||
{
|
||||
OutError = FString::Printf(TEXT("Could not inject native SurfaceWorld plan: %s"), *ImportReport);
|
||||
return false;
|
||||
}
|
||||
UE_LOG(LogTemp, Display, TEXT("[VoxelForgeCoreWorldTest] %s"), *ImportReport);
|
||||
}
|
||||
|
||||
TStrongObjectPtr<UVoxelStrateManager> Manager(NewObject<UVoxelStrateManager>(
|
||||
GetTransientPackage(), NAME_None, RF_Transient));
|
||||
int32 ResolvedSeed = AuthoredSettings->Seed;
|
||||
@@ -13227,6 +13259,9 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
TSet<FVoxelTileKey> ExpectedInitialDesiredSet;
|
||||
TSet<FVoxelTileKey> ExpectedTravelDesiredSet;
|
||||
TSet<FVoxelTileKey> ExpectedSurfaceTravelDesiredSet;
|
||||
TSet<FVoxelTileKey> ExpectedSurfaceCollisionSet;
|
||||
int32 ExpectedSurfaceCollisionBodyCount = 0;
|
||||
int32 ExpectedSurfaceCollisionNoBodyCount = 0;
|
||||
TArray<FVoxelTileKey> ExpectedAffectedTiles;
|
||||
FVector CarveLocalPositionCm = FVector::ZeroVector;
|
||||
FVoxelTileKey CarveTargetTile;
|
||||
@@ -13234,10 +13269,18 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
constexpr double CoreVoxelSizeCm = 25.0;
|
||||
const double CarveBrushRadiusCoreVoxels =
|
||||
CarveBrushRadiusLegacyVoxels * static_cast<double>(VOXEL_SIZE) / CoreVoxelSizeCm;
|
||||
const int32 CollisionRadius = FMath::Clamp(AuthoredSettings->PawnCollisionRadiusChunks, 1, 4);
|
||||
uint64 ExpectedEditDigest = 0;
|
||||
std::array<std::uint64_t, 4> InitialAggregates{};
|
||||
std::array<std::uint64_t, 4> TravelAggregates{};
|
||||
std::array<std::uint64_t, 4> SurfaceTravelAggregates{};
|
||||
std::vector<VoxelForgeCore::TileKey> MatchedTimingInitialDesired;
|
||||
std::vector<VoxelForgeCore::TileKey> MatchedTimingSurfaceDesired;
|
||||
std::vector<VoxelForgeCore::TileKey> MatchedTimingSurfaceMissingFine;
|
||||
VoxelForgeCore::ShapeBuildRequest MatchedTimingInitialRequest;
|
||||
double NativeSurfaceRegionCompileSeconds = 0.0;
|
||||
double NativeSurfaceTileGenerationSeconds = 0.0;
|
||||
std::uint64_t NativeSurfaceTriangleCount = 0;
|
||||
std::size_t ExpectedInitialTileCount = 0;
|
||||
std::size_t ExpectedTravelTileCount = 0;
|
||||
std::size_t TravelFineTilesCompiled = 0;
|
||||
@@ -13256,8 +13299,11 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
VoxelForgeCore::TileRegionCompileStats SurfaceTravelCompileStats;
|
||||
const VoxelForgeCore::Vec3d TravelCameraVoxels{-156.75164794921875, 25.093801498413086,
|
||||
-823.67364501953125};
|
||||
const VoxelForgeCore::Vec3d SurfaceCameraVoxels{-156.75164794921875, 25.093801498413086,
|
||||
-1024.0};
|
||||
const VoxelForgeCore::Vec3d SurfaceCameraVoxels{37.0, 165.0, -2011.0};
|
||||
uint32 NativeSurfaceBiomeId = 0;
|
||||
int32 NativeBiomeIdsSampled = 0;
|
||||
int32 NativeBiomeSampleCount = 0;
|
||||
bool bNativeBiomeFieldValidated = false;
|
||||
{
|
||||
FTCHARToUTF8 Utf8Json(*Json);
|
||||
std::string CoreError;
|
||||
@@ -13271,6 +13317,102 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
UTF8_TO_TCHAR(CoreError.c_str()));
|
||||
return false;
|
||||
}
|
||||
FString CheckedAssemblyJson;
|
||||
FString ExpectedAssemblyHash;
|
||||
FString AssemblyValidationError;
|
||||
if (!AuthoredSettings->GetWorldAssemblyManifestJson(
|
||||
CheckedAssemblyJson, ExpectedAssemblyHash, AssemblyValidationError))
|
||||
{
|
||||
OutError = FString::Printf(TEXT("Core actor test settings manifest failed validation: %s"),
|
||||
*AssemblyValidationError);
|
||||
return false;
|
||||
}
|
||||
if (!ExpectedAssemblyHash.IsEmpty())
|
||||
{
|
||||
const VoxelForgeCore::WorldAssemblyManifest* Assembly = Snapshot->worldAssembly();
|
||||
const FString SnapshotHash = Assembly == nullptr ? FString() : FString::Printf(
|
||||
TEXT("%016llx"), static_cast<unsigned long long>(Assembly->contentHash));
|
||||
if (Assembly == nullptr || !SnapshotHash.Equals(ExpectedAssemblyHash, ESearchCase::IgnoreCase))
|
||||
{
|
||||
OutError = TEXT("Core runtime adapter did not preserve the settings world assembly manifest.");
|
||||
return false;
|
||||
}
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCoreWorldTest] world_assembly_hash=%s plans=%d native=1"),
|
||||
*SnapshotHash, static_cast<int32>(Assembly->plan.strates.size()));
|
||||
}
|
||||
else if (Snapshot->worldAssembly() != nullptr)
|
||||
{
|
||||
OutError = TEXT("Core runtime adapter unexpectedly attached a world assembly manifest.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate the native Core field's biome assignment, not the legacy decoration query.
|
||||
// Valider les biomes du champ Core natif, pas la requete historique des decorations.
|
||||
const VoxelForgeCore::WorldAssemblyManifest* NativeAssembly = Snapshot->worldAssembly();
|
||||
const VoxelForgeCore::WorldAssemblyStratumPlanV1* NativeSurfacePlan = nullptr;
|
||||
if (NativeAssembly != nullptr)
|
||||
{
|
||||
for (const VoxelForgeCore::WorldAssemblyStratumPlanV1& Plan : NativeAssembly->plan.strates)
|
||||
{
|
||||
if (Plan.strateIndex == 1)
|
||||
{
|
||||
NativeSurfacePlan = &Plan;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (NativeAssembly == nullptr || NativeSurfacePlan == nullptr)
|
||||
{
|
||||
OutError = TEXT("Core runtime snapshot has no native biome plan for SurfaceWorld strate 1.");
|
||||
return false;
|
||||
}
|
||||
const VoxelForgeCore::Heightfield2D NativeSurfaceField(
|
||||
NativeAssembly->recipe, *NativeSurfacePlan);
|
||||
if (!NativeSurfaceField.isValid())
|
||||
{
|
||||
OutError = TEXT("Core runtime snapshot's native SurfaceWorld heightfield is invalid.");
|
||||
return false;
|
||||
}
|
||||
const int64 SubvoxelsPerVoxel = VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
const VoxelForgeCore::HeightfieldPointSample SurfaceBiomeSample = NativeSurfaceField.point(
|
||||
static_cast<int64>(std::llround(SurfaceCameraVoxels.x * SubvoxelsPerVoxel)),
|
||||
static_cast<int64>(std::llround(SurfaceCameraVoxels.y * SubvoxelsPerVoxel)), 0);
|
||||
if (SurfaceBiomeSample.biomeId == 0)
|
||||
{
|
||||
OutError = TEXT("Native SurfaceWorld heightfield returned no biome at the route camera.");
|
||||
return false;
|
||||
}
|
||||
TSet<uint32> NativeBiomeIds;
|
||||
constexpr int32 BiomeProbeSide = 64;
|
||||
constexpr double BiomeProbeSpanVoxels = 3200.0;
|
||||
for (int32 Y = 0; Y < BiomeProbeSide; ++Y)
|
||||
for (int32 X = 0; X < BiomeProbeSide; ++X)
|
||||
{
|
||||
const double XVoxel = (static_cast<double>(X) / (BiomeProbeSide - 1) - 0.5)
|
||||
* BiomeProbeSpanVoxels;
|
||||
const double YVoxel = (static_cast<double>(Y) / (BiomeProbeSide - 1) - 0.5)
|
||||
* BiomeProbeSpanVoxels;
|
||||
const VoxelForgeCore::HeightfieldPointSample Sample = NativeSurfaceField.point(
|
||||
static_cast<int64>(std::llround(XVoxel * SubvoxelsPerVoxel)),
|
||||
static_cast<int64>(std::llround(YVoxel * SubvoxelsPerVoxel)), 0);
|
||||
if (Sample.biomeId != 0) NativeBiomeIds.Add(Sample.biomeId);
|
||||
if (Sample.neighborBiomeId != 0 && Sample.biomeWeightQ16 > 0)
|
||||
NativeBiomeIds.Add(Sample.neighborBiomeId);
|
||||
++NativeBiomeSampleCount;
|
||||
}
|
||||
if (NativeBiomeIds.Num() < 2)
|
||||
{
|
||||
OutError = TEXT("Native SurfaceWorld heightfield did not assign at least two biomes across its review domain.");
|
||||
return false;
|
||||
}
|
||||
NativeSurfaceBiomeId = SurfaceBiomeSample.biomeId;
|
||||
NativeBiomeIdsSampled = NativeBiomeIds.Num();
|
||||
bNativeBiomeFieldValidated = true;
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCoreWorldTest] native_biome_field=1 surface_biome_id=%u sampled_biomes=%d samples=%d span_voxels=%.0f"),
|
||||
NativeSurfaceBiomeId, NativeBiomeIdsSampled, NativeBiomeSampleCount,
|
||||
BiomeProbeSpanVoxels);
|
||||
|
||||
VoxelForgeCore::World ReferenceWorld(std::move(*Snapshot));
|
||||
const std::vector<VoxelForgeCore::TileKey> Desired = ReferenceWorld.desiredTiles({});
|
||||
@@ -13314,6 +13456,8 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
Request.workerLimit = 8;
|
||||
Request.detail = VoxelForgeCore::DetailVariant::coherent;
|
||||
Request.includeFarWorld = true;
|
||||
MatchedTimingInitialDesired = Desired;
|
||||
MatchedTimingInitialRequest = Request;
|
||||
FStandaloneCoreAdapterScheduler Scheduler;
|
||||
if (!ReferenceWorld.compile(Request, Scheduler, CoreError))
|
||||
{
|
||||
@@ -13651,6 +13795,7 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
}
|
||||
std::vector<VoxelForgeCore::TileKey> MissingSurfaceFineTiles;
|
||||
MissingSurfaceFineTiles.reserve(SurfaceTravelDesired.size());
|
||||
MatchedTimingSurfaceDesired = SurfaceTravelDesired;
|
||||
for (const VoxelForgeCore::TileKey& Key : SurfaceTravelDesired)
|
||||
{
|
||||
const FVoxelTileKey RuntimeKey(
|
||||
@@ -13659,8 +13804,10 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
if (Key.lod < ReferenceWorld.configuration().fullResolutionLodCount)
|
||||
MissingSurfaceFineTiles.push_back(Key);
|
||||
}
|
||||
MatchedTimingSurfaceMissingFine = MissingSurfaceFineTiles;
|
||||
// Keep the retained reference clipmap bounded while compiling the second remote region.
|
||||
// Garder la référence bornée avant de compiler la seconde région distante.
|
||||
const double NativeSurfaceRegionCompileStart = FPlatformTime::Seconds();
|
||||
ReferenceWorld.retainTileRegions(SurfaceTravelDesired);
|
||||
if (!ReferenceWorld.compileTileRegion(MissingSurfaceFineTiles, 16, Scheduler, CoreError,
|
||||
&SurfaceTravelFineTilesCompiled, &SurfaceTravelCompileStats))
|
||||
@@ -13669,11 +13816,14 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
UTF8_TO_TCHAR(CoreError.c_str()));
|
||||
return false;
|
||||
}
|
||||
NativeSurfaceRegionCompileSeconds = FPlatformTime::Seconds()
|
||||
- NativeSurfaceRegionCompileStart;
|
||||
SurfaceTravelRegionsBuilt = SurfaceTravelCompileStats.regionsBuilt;
|
||||
std::vector<FStandaloneCoreAdapterTileHashes> SurfaceTravelRows;
|
||||
SurfaceTravelRows.reserve(SurfaceTravelDesired.size());
|
||||
for (const VoxelForgeCore::TileKey& Key : SurfaceTravelDesired)
|
||||
{
|
||||
const double TileGenerationStart = FPlatformTime::Seconds();
|
||||
const std::optional<VoxelForgeCore::TileMesh> Mesh =
|
||||
ReferenceWorld.generateTile(Key, TravelOptions, CoreError);
|
||||
if (!Mesh || !Mesh->hasDensityHash || !Mesh->hasMeshHash
|
||||
@@ -13683,6 +13833,9 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
UTF8_TO_TCHAR(CoreError.c_str()));
|
||||
return false;
|
||||
}
|
||||
NativeSurfaceTileGenerationSeconds += FPlatformTime::Seconds()
|
||||
- TileGenerationStart;
|
||||
NativeSurfaceTriangleCount += Mesh->indices.size() / 3;
|
||||
const FVoxelTileKey RuntimeKey(
|
||||
FIntVector(Key.coordinate.x, Key.coordinate.y, Key.coordinate.z), Key.lod);
|
||||
FVoxelForgeCoreTileHashRecord Record;
|
||||
@@ -13698,6 +13851,27 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
{Record.Density, Record.Mesh, Record.Payload, Record.Topology}});
|
||||
if (Mesh->groups.skirtTriangleCount > 0) ++SurfaceTravelTilesWithSkirts;
|
||||
}
|
||||
const FIntVector SurfaceCollisionCenter(
|
||||
FMath::FloorToInt(SurfaceCameraVoxels.x / CHUNK_SIZE),
|
||||
FMath::FloorToInt(SurfaceCameraVoxels.y / CHUNK_SIZE),
|
||||
FMath::FloorToInt(SurfaceCameraVoxels.z / CHUNK_SIZE));
|
||||
for (int32 Z = -CollisionRadius; Z <= CollisionRadius; ++Z)
|
||||
for (int32 Y = -CollisionRadius; Y <= CollisionRadius; ++Y)
|
||||
for (int32 X = -CollisionRadius; X <= CollisionRadius; ++X)
|
||||
{
|
||||
const FVoxelTileKey Key(SurfaceCollisionCenter + FIntVector(X, Y, Z), 0);
|
||||
const FVoxelForgeCoreTileHashRecord* Record = ExpectedSurfaceTravelHashes.Find(Key);
|
||||
if (Record == nullptr)
|
||||
{
|
||||
OutError = FString::Printf(
|
||||
TEXT("Standalone native-surface reference omitted expected collision tile (%d,%d,%d)."),
|
||||
Key.Coord.X, Key.Coord.Y, Key.Coord.Z);
|
||||
return false;
|
||||
}
|
||||
ExpectedSurfaceCollisionSet.Add(Key);
|
||||
if (Record->bEmpty) ++ExpectedSurfaceCollisionNoBodyCount;
|
||||
else ++ExpectedSurfaceCollisionBodyCount;
|
||||
}
|
||||
SurfaceTravelAggregates = StandaloneCoreAggregateHashes(SurfaceTravelRows);
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCoreWorldTest] standalone_surface_reference=1 location=(%.3f,%.3f,%.3f) tiles=%d fine_compiled=%llu regions=%llu local_tiles=%d checked_faces=%d lod_transition_faces=%d skirt_tiles=%d density=0x%016llx mesh=0x%016llx payload=0x%016llx topology=0x%016llx"),
|
||||
@@ -13719,6 +13893,107 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
CarveTargetTile.Coord.Z, CarveTargetTile.Level);
|
||||
}
|
||||
|
||||
// Compare native and compatibility bridge on the same remote surface keys in this process.
|
||||
// Comparer le champ natif et le pont de compatibilite sur les memes cles de surface.
|
||||
if (MatchedTimingInitialDesired.empty() || MatchedTimingSurfaceDesired.empty()
|
||||
|| MatchedTimingSurfaceMissingFine.empty())
|
||||
{
|
||||
OutError = TEXT("Matched native/bridge timing did not retain its reference tile keys.");
|
||||
return false;
|
||||
}
|
||||
FString BridgeJson = Json;
|
||||
TSharedPtr<FJsonObject> BridgeRoot;
|
||||
const TSharedRef<TJsonReader<>> BridgeReader = TJsonReaderFactory<>::Create(BridgeJson);
|
||||
if (!FJsonSerializer::Deserialize(BridgeReader, BridgeRoot) || !BridgeRoot.IsValid())
|
||||
{
|
||||
OutError = TEXT("Matched legacy-bridge timing could not parse the Core snapshot JSON.");
|
||||
return false;
|
||||
}
|
||||
BridgeRoot->RemoveField(TEXT("world_assembly"));
|
||||
BridgeRoot->RemoveField(TEXT("world_assembly_content_hash"));
|
||||
BridgeJson.Reset();
|
||||
const TSharedRef<FExploreJsonWriter> BridgeWriter = TJsonWriterFactory<TCHAR,
|
||||
TCondensedJsonPrintPolicy<TCHAR>>::Create(&BridgeJson);
|
||||
if (!FJsonSerializer::Serialize(BridgeRoot.ToSharedRef(), BridgeWriter))
|
||||
{
|
||||
OutError = TEXT("Matched legacy-bridge timing could not serialize the bridge snapshot.");
|
||||
return false;
|
||||
}
|
||||
BridgeWriter->Close();
|
||||
|
||||
FTCHARToUTF8 BridgeUtf8(*BridgeJson);
|
||||
std::string BridgeError;
|
||||
std::optional<VoxelForgeCore::ResolvedWorldSnapshot> BridgeSnapshot =
|
||||
VoxelForgeCore::ResolvedWorldSnapshot::FromJson(
|
||||
std::string_view(BridgeUtf8.Get(), static_cast<std::size_t>(BridgeUtf8.Length())),
|
||||
BridgeError);
|
||||
if (!BridgeSnapshot || BridgeSnapshot->worldAssembly() != nullptr)
|
||||
{
|
||||
OutError = FString::Printf(
|
||||
TEXT("Matched legacy-bridge snapshot failed validation or retained a native plan: %s"),
|
||||
UTF8_TO_TCHAR(BridgeError.c_str()));
|
||||
return false;
|
||||
}
|
||||
VoxelForgeCore::World BridgeWorld(std::move(*BridgeSnapshot));
|
||||
FStandaloneCoreAdapterScheduler BridgeScheduler;
|
||||
if (!BridgeWorld.compile(MatchedTimingInitialRequest, BridgeScheduler, BridgeError))
|
||||
{
|
||||
OutError = FString::Printf(TEXT("Matched legacy-bridge initial compile failed: %s"),
|
||||
UTF8_TO_TCHAR(BridgeError.c_str()));
|
||||
return false;
|
||||
}
|
||||
BridgeWorld.retainTileRegions(MatchedTimingInitialDesired);
|
||||
const double BridgeRegionCompileStart = FPlatformTime::Seconds();
|
||||
BridgeWorld.retainTileRegions(MatchedTimingSurfaceDesired);
|
||||
VoxelForgeCore::TileRegionCompileStats BridgeRegionStats;
|
||||
if (!BridgeWorld.compileTileRegion(MatchedTimingSurfaceMissingFine, 16, BridgeScheduler,
|
||||
BridgeError, nullptr, &BridgeRegionStats))
|
||||
{
|
||||
OutError = FString::Printf(TEXT("Matched legacy-bridge surface compile failed: %s"),
|
||||
UTF8_TO_TCHAR(BridgeError.c_str()));
|
||||
return false;
|
||||
}
|
||||
const double BridgeSurfaceRegionCompileSeconds = FPlatformTime::Seconds()
|
||||
- BridgeRegionCompileStart;
|
||||
VoxelForgeCore::TileGenerationOptions BridgeTileOptions;
|
||||
BridgeTileOptions.hashDensity = true;
|
||||
double BridgeSurfaceTileGenerationSeconds = 0.0;
|
||||
std::uint64_t BridgeSurfaceTriangleCount = 0;
|
||||
std::size_t BridgeSurfaceTilesGenerated = 0;
|
||||
for (const VoxelForgeCore::TileKey& Key : MatchedTimingSurfaceDesired)
|
||||
{
|
||||
const double TileGenerationStart = FPlatformTime::Seconds();
|
||||
const std::optional<VoxelForgeCore::TileMesh> Mesh =
|
||||
BridgeWorld.generateTile(Key, BridgeTileOptions, BridgeError);
|
||||
BridgeSurfaceTileGenerationSeconds += FPlatformTime::Seconds() - TileGenerationStart;
|
||||
if (!Mesh || !Mesh->hasDensityHash || !Mesh->hasMeshHash
|
||||
|| !Mesh->hasPayloadHash || !Mesh->hasTopologyHash)
|
||||
{
|
||||
OutError = FString::Printf(TEXT("Matched legacy-bridge tile generation failed: %s"),
|
||||
UTF8_TO_TCHAR(BridgeError.c_str()));
|
||||
return false;
|
||||
}
|
||||
BridgeSurfaceTriangleCount += Mesh->indices.size() / 3;
|
||||
++BridgeSurfaceTilesGenerated;
|
||||
}
|
||||
if (BridgeSurfaceTilesGenerated != MatchedTimingSurfaceDesired.size())
|
||||
{
|
||||
OutError = TEXT("Matched legacy-bridge timing did not generate the complete surface key set.");
|
||||
return false;
|
||||
}
|
||||
const double NativeTotal = NativeSurfaceRegionCompileSeconds
|
||||
+ NativeSurfaceTileGenerationSeconds;
|
||||
const double BridgeTotal = BridgeSurfaceRegionCompileSeconds
|
||||
+ BridgeSurfaceTileGenerationSeconds;
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCoreWorldTest] matched_surface_tile_timing=1 native_backend=heightfield2d bridge_backend=legacy_surface_bridge same_keys=1 tiles=%llu hashes=on native_region_compile_s=%.6f bridge_region_compile_s=%.6f native_tile_generation_s=%.6f bridge_tile_generation_s=%.6f native_compile_plus_tiles_s=%.6f bridge_compile_plus_tiles_s=%.6f speedup=%.3fx native_triangles=%llu bridge_triangles=%llu"),
|
||||
static_cast<unsigned long long>(MatchedTimingSurfaceDesired.size()),
|
||||
NativeSurfaceRegionCompileSeconds, BridgeSurfaceRegionCompileSeconds,
|
||||
NativeSurfaceTileGenerationSeconds, BridgeSurfaceTileGenerationSeconds,
|
||||
NativeTotal, BridgeTotal, BridgeTotal / FMath::Max(NativeTotal, 1.0e-9),
|
||||
static_cast<unsigned long long>(NativeSurfaceTriangleCount),
|
||||
static_cast<unsigned long long>(BridgeSurfaceTriangleCount));
|
||||
|
||||
const UWorld::InitializationValues WorldInitialization = UWorld::InitializationValues()
|
||||
.AllowAudioPlayback(false)
|
||||
.RequiresHitProxies(false)
|
||||
@@ -13901,7 +14176,6 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
}
|
||||
}
|
||||
|
||||
const int32 CollisionRadius = FMath::Clamp(AuthoredSettings->PawnCollisionRadiusChunks, 1, 4);
|
||||
TSet<FVoxelTileKey> ExpectedCollisionSet;
|
||||
int32 ExpectedBodyCount = 0;
|
||||
int32 ExpectedNoBodyCount = 0;
|
||||
@@ -14178,6 +14452,21 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bVisualTilesReady)
|
||||
{
|
||||
for (const FVoxelTileKey& Key : ExpectedSurfaceCollisionSet)
|
||||
{
|
||||
const FVoxelForgeCoreTileHashRecord* Expected = ExpectedSurfaceTravelHashes.Find(Key);
|
||||
const bool bExpectedBody = Expected != nullptr && !Expected->bEmpty;
|
||||
if (!Actor->CollisionDesiredTiles.Contains(Key)
|
||||
|| (bExpectedBody && !Actor->CollisionReadyTiles.Contains(Key))
|
||||
|| (!bExpectedBody && !Actor->CollisionNotRequiredTiles.Contains(Key)))
|
||||
{
|
||||
bVisualTilesReady = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bVisualTilesReady) return true;
|
||||
}
|
||||
return false;
|
||||
@@ -14201,6 +14490,39 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
return false;
|
||||
}
|
||||
|
||||
int32 ActualSurfaceCollisionReady = 0;
|
||||
int32 ActualSurfaceCollisionNoBody = 0;
|
||||
for (const FVoxelTileKey& Key : ExpectedSurfaceCollisionSet)
|
||||
{
|
||||
const FVoxelForgeCoreTileHashRecord& Expected = ExpectedSurfaceTravelHashes.FindChecked(Key);
|
||||
if (!Actor->CollisionDesiredTiles.Contains(Key))
|
||||
{
|
||||
OutError = FString::Printf(TEXT("Native-surface collision tile (%d,%d,%d) was not requested."),
|
||||
Key.Coord.X, Key.Coord.Y, Key.Coord.Z);
|
||||
return false;
|
||||
}
|
||||
if (Expected.bEmpty)
|
||||
{
|
||||
if (!Actor->CollisionNotRequiredTiles.Contains(Key))
|
||||
{
|
||||
OutError = FString::Printf(TEXT("Native-surface empty tile (%d,%d,%d) has unexpected collision state."),
|
||||
Key.Coord.X, Key.Coord.Y, Key.Coord.Z);
|
||||
return false;
|
||||
}
|
||||
++ActualSurfaceCollisionNoBody;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!Actor->CollisionReadyTiles.Contains(Key))
|
||||
{
|
||||
OutError = FString::Printf(TEXT("Native-surface solid tile (%d,%d,%d) has no cooked collision body."),
|
||||
Key.Coord.X, Key.Coord.Y, Key.Coord.Z);
|
||||
return false;
|
||||
}
|
||||
++ActualSurfaceCollisionReady;
|
||||
}
|
||||
}
|
||||
|
||||
TSet<FVoxelTileKey> ActualSurfaceVisualDesiredSet;
|
||||
std::vector<VoxelForgeCore::TileKey> ActorSurfaceTravelDesired;
|
||||
ActorSurfaceTravelDesired.reserve(static_cast<std::size_t>(Actor->DesiredSorted.Num()));
|
||||
@@ -14231,14 +14553,31 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
const FVoxelForgeCoreTileHashRecord* Actual = Actor->VoxelForgeCoreTileHashes.Find(Key);
|
||||
const FVoxelForgeCoreTileHashRecord* Expected = ExpectedSurfaceTravelHashes.Find(Key);
|
||||
if (Actual == nullptr || Expected == nullptr || !Actual->bHasHashes
|
||||
|| (!Expected->bEmpty && !Actor->LoadedTiles.Contains(Key))
|
||||
|| Actual->Density != Expected->Density || Actual->Mesh != Expected->Mesh
|
||||
|| (!Expected->bEmpty && !Actor->LoadedTiles.Contains(Key)))
|
||||
{
|
||||
OutError = FString::Printf(
|
||||
TEXT("Core actor surface-strate tile state is incomplete at (%d,%d,%d) lod=%d (actual=%d expected=%d hashed=%d loaded=%d)."),
|
||||
Key.Coord.X, Key.Coord.Y, Key.Coord.Z, Key.Level,
|
||||
Actual ? 1 : 0, Expected ? 1 : 0, Actual && Actual->bHasHashes ? 1 : 0,
|
||||
Actor->LoadedTiles.Contains(Key) ? 1 : 0);
|
||||
return false;
|
||||
}
|
||||
if (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 surface-strate tile differs from standalone Core at (%d,%d,%d) lod=%d."),
|
||||
Key.Coord.X, Key.Coord.Y, Key.Coord.Z, Key.Level);
|
||||
TEXT("Core actor surface-strate hash mismatch at (%d,%d,%d) lod=%d; density=%016llx/%016llx mesh=%016llx/%016llx payload=%016llx/%016llx topology=%016llx/%016llx triangles=%d/%d empty=%d/%d."),
|
||||
Key.Coord.X, Key.Coord.Y, Key.Coord.Z, Key.Level,
|
||||
static_cast<unsigned long long>(Actual->Density),
|
||||
static_cast<unsigned long long>(Expected->Density),
|
||||
static_cast<unsigned long long>(Actual->Mesh),
|
||||
static_cast<unsigned long long>(Expected->Mesh),
|
||||
static_cast<unsigned long long>(Actual->Payload),
|
||||
static_cast<unsigned long long>(Expected->Payload),
|
||||
static_cast<unsigned long long>(Actual->Topology),
|
||||
static_cast<unsigned long long>(Expected->Topology),
|
||||
Actual->Triangles, Expected->Triangles, Actual->bEmpty ? 1 : 0, Expected->bEmpty ? 1 : 0);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -14255,20 +14594,20 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
*SurfaceCoverageError);
|
||||
return false;
|
||||
}
|
||||
const FVoxelBiomeQuery SurfaceBiome = Actor->GetBiomeAtWorldLocation(SurfaceWorldPosition);
|
||||
if (!SurfaceBiome.bHasBiomes || SurfaceBiome.DominantContextIndex < 0)
|
||||
if (!bNativeBiomeFieldValidated || NativeBiomeIdsSampled < 2)
|
||||
{
|
||||
OutError = TEXT("Core-mode biome query returned no dominant biome at the authored surface-strate fixture.");
|
||||
OutError = TEXT("Core native SurfaceWorld biome validation did not complete.");
|
||||
return false;
|
||||
}
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCoreWorldTest] pass phase=surface_strate_travel center=(%.3f,%.3f,%.3f) desired=%d loaded=%d fine_compiled=%llu regions=%llu local_tiles=%d checked_faces=%d lod_transition_faces=%d biome_index=%d biome=%s density=0x%016llx mesh=0x%016llx payload=0x%016llx topology=0x%016llx"),
|
||||
TEXT("[VoxelForgeCoreWorldTest] pass phase=surface_strate_travel center=(%.3f,%.3f,%.3f) desired=%d loaded=%d fine_compiled=%llu regions=%llu local_tiles=%d checked_faces=%d lod_transition_faces=%d collision_desired=%d collision_ready=%d collision_no_body=%d native_surface_biome_id=%u native_biomes_sampled=%d density=0x%016llx mesh=0x%016llx payload=0x%016llx topology=0x%016llx"),
|
||||
SurfaceCameraVoxels.x, SurfaceCameraVoxels.y, SurfaceCameraVoxels.z,
|
||||
ActualSurfaceVisualDesiredSet.Num(), Actor->LoadedTiles.Num(),
|
||||
static_cast<unsigned long long>(SurfaceTravelFineTilesCompiled),
|
||||
static_cast<unsigned long long>(SurfaceTravelRegionsBuilt), ActorSurfaceLocalTiles,
|
||||
ActorSurfaceCheckedFaces, ActorSurfaceTransitionFaces,
|
||||
SurfaceBiome.DominantContextIndex, *SurfaceBiome.DominantName.ToString(),
|
||||
ActorSurfaceCheckedFaces, ActorSurfaceTransitionFaces, ExpectedSurfaceCollisionSet.Num(),
|
||||
ActualSurfaceCollisionReady, ActualSurfaceCollisionNoBody,
|
||||
NativeSurfaceBiomeId, NativeBiomeIdsSampled,
|
||||
static_cast<unsigned long long>(SurfaceTravelAggregates[0]),
|
||||
static_cast<unsigned long long>(SurfaceTravelAggregates[1]),
|
||||
static_cast<unsigned long long>(SurfaceTravelAggregates[2]),
|
||||
|
||||
Reference in new issue
Block a user