wip(unreal): round 3 -- owner's in-game defects classified; opening room fixed; Core biome query
Luna, integration round 3, from the owner's four in-game screenshots: - Capsule rings at the opening and the shaft continuing through the cave room were Core world defects: the synthetic bore is removed and the opening gets a finite origin room and floor (solid below). New world version, W1/W8/W16 identical: density a8bcb72d41092503, mesh 90f29ba15e24579f, payload 82c6b489e7bb7fd7, topology 7b99448a1f844f2c. - B-side landing and surface: headless coverage found no missing tiles or LOD-transition gaps (960 / 640 fine tiles compiled after travel); best explained by Core's missing SurfaceWorld representation. Null-RHI cannot rule out a presentation-only issue. - Core mode GetBiomeAtWorldLocation now answers (e.g. Biome_Surface_Plain); biome terrain itself is not generated yet (proposal: native Core SurfaceWorld evaluator). Owner checked the Step29 viewer himself: the surface strate type is absent there too. 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
075e58187e
commit
75210e56f3
8 files changed
+957
-24
No files matched your search
@@ -12463,16 +12463,12 @@ int32 AVoxelWorld::GetStrateAtPosition(FVector WorldPosition) const
|
||||
FVoxelBiomeQuery AVoxelWorld::GetBiomeAtWorldLocation(FVector WorldLocation) const
|
||||
{
|
||||
FVoxelBiomeQuery Out;
|
||||
if (bUseVoxelForgeCore)
|
||||
{
|
||||
// Core ne publie pas encore le query biome historique.
|
||||
// Core does not expose the legacy biome query API yet.
|
||||
return Out;
|
||||
}
|
||||
if (!Generator) return Out;
|
||||
|
||||
// Bring the world point into actor-LOCAL voxel space — the SAME transform the decoration scatter
|
||||
// applies (UpdateDecorations), so the probe agrees with where props actually land.
|
||||
// Convert using the legacy generator's authored voxel size; Core still queries this biome field.
|
||||
// Convertir avec l'échelle historique du générateur; Core interroge toujours ce champ de biomes.
|
||||
const FVector Local = GetActorTransform().InverseTransformPosition(WorldLocation);
|
||||
const float VX = Local.X / VOXEL_SIZE;
|
||||
const float VY = Local.Y / VOXEL_SIZE;
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#include "Engine/World.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "GameFramework/GameModeBase.h"
|
||||
#include "GameFramework/Pawn.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "GameFramework/WorldSettings.h"
|
||||
#include "HAL/FileManager.h"
|
||||
#include "HAL/PlatformProcess.h"
|
||||
@@ -12615,6 +12617,135 @@ std::array<std::uint64_t, 4> StandaloneCoreAggregateHashes(
|
||||
return Aggregates;
|
||||
}
|
||||
|
||||
bool AuditCoreClipmapCoverage(
|
||||
const std::vector<VoxelForgeCore::TileKey>& Tiles,
|
||||
VoxelForgeCore::Vec3d Focus,
|
||||
std::int32_t FocusRadiusVoxels,
|
||||
std::int32_t& OutLocalTileCount,
|
||||
std::int32_t& OutCheckedFaces,
|
||||
std::int32_t& OutTransitionFaces,
|
||||
FString& OutError)
|
||||
{
|
||||
struct FBounds
|
||||
{
|
||||
std::int64_t Min[3]{};
|
||||
std::int64_t Max[3]{};
|
||||
std::int32_t Lod = 0;
|
||||
};
|
||||
|
||||
OutLocalTileCount = 0;
|
||||
OutCheckedFaces = 0;
|
||||
OutTransitionFaces = 0;
|
||||
OutError.Reset();
|
||||
std::vector<FBounds> Bounds;
|
||||
Bounds.reserve(Tiles.size());
|
||||
for (const VoxelForgeCore::TileKey& Key : Tiles)
|
||||
{
|
||||
if (Key.lod < 0 || Key.lod > 8)
|
||||
{
|
||||
OutError = TEXT("Core clipmap contains an invalid LOD key.");
|
||||
return false;
|
||||
}
|
||||
const std::int64_t Extent = 32ll << Key.lod;
|
||||
FBounds Box;
|
||||
Box.Lod = Key.lod;
|
||||
Box.Min[0] = static_cast<std::int64_t>(Key.coordinate.x) * Extent;
|
||||
Box.Min[1] = static_cast<std::int64_t>(Key.coordinate.y) * Extent;
|
||||
Box.Min[2] = static_cast<std::int64_t>(Key.coordinate.z) * Extent;
|
||||
for (int32 Axis = 0; Axis < 3; ++Axis) Box.Max[Axis] = Box.Min[Axis] + Extent;
|
||||
Bounds.push_back(Box);
|
||||
}
|
||||
|
||||
// Une clipmap Core ne doit jamais renvoyer deux boites qui se chevauchent en volume.
|
||||
// A Core clipmap must never return two tile boxes that overlap in volume.
|
||||
for (std::size_t A = 0; A < Bounds.size(); ++A)
|
||||
{
|
||||
for (std::size_t B = A + 1; B < Bounds.size(); ++B)
|
||||
{
|
||||
bool bOverlaps = true;
|
||||
for (int32 Axis = 0; Axis < 3; ++Axis)
|
||||
{
|
||||
bOverlaps = bOverlaps
|
||||
&& FMath::Min(Bounds[A].Max[Axis], Bounds[B].Max[Axis])
|
||||
> FMath::Max(Bounds[A].Min[Axis], Bounds[B].Min[Axis]);
|
||||
}
|
||||
if (bOverlaps)
|
||||
{
|
||||
OutError = FString::Printf(
|
||||
TEXT("Core clipmap tiles overlap: index %llu and %llu."),
|
||||
static_cast<unsigned long long>(A), static_cast<unsigned long long>(B));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const double FocusPoint[3] = {Focus.x, Focus.y, Focus.z};
|
||||
for (std::size_t TileIndex = 0; TileIndex < Bounds.size(); ++TileIndex)
|
||||
{
|
||||
const FBounds& Tile = Bounds[TileIndex];
|
||||
if (Tile.Lod > 2) continue;
|
||||
bool bNearFocus = true;
|
||||
for (int32 Axis = 0; Axis < 3; ++Axis)
|
||||
{
|
||||
bNearFocus = bNearFocus
|
||||
&& static_cast<double>(Tile.Max[Axis]) >= FocusPoint[Axis] - FocusRadiusVoxels
|
||||
&& static_cast<double>(Tile.Min[Axis]) <= FocusPoint[Axis] + FocusRadiusVoxels;
|
||||
}
|
||||
if (!bNearFocus) continue;
|
||||
++OutLocalTileCount;
|
||||
|
||||
for (int32 Axis = 0; Axis < 3; ++Axis)
|
||||
{
|
||||
const int32 UAxis = (Axis + 1) % 3;
|
||||
const int32 VAxis = (Axis + 2) % 3;
|
||||
const std::int64_t FaceArea = (Tile.Max[UAxis] - Tile.Min[UAxis])
|
||||
* (Tile.Max[VAxis] - Tile.Min[VAxis]);
|
||||
for (const bool bPositiveFace : {false, true})
|
||||
{
|
||||
const std::int64_t FaceCoordinate = bPositiveFace ? Tile.Max[Axis] : Tile.Min[Axis];
|
||||
std::int64_t CoveredArea = 0;
|
||||
bool bTouchesLodTransition = false;
|
||||
for (std::size_t NeighbourIndex = 0; NeighbourIndex < Bounds.size(); ++NeighbourIndex)
|
||||
{
|
||||
if (NeighbourIndex == TileIndex) continue;
|
||||
const FBounds& Neighbour = Bounds[NeighbourIndex];
|
||||
if (Neighbour.Min[Axis] != FaceCoordinate
|
||||
&& Neighbour.Max[Axis] != FaceCoordinate)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const std::int64_t OverlapU = FMath::Max<std::int64_t>(0,
|
||||
FMath::Min(Tile.Max[UAxis], Neighbour.Max[UAxis])
|
||||
- FMath::Max(Tile.Min[UAxis], Neighbour.Min[UAxis]));
|
||||
const std::int64_t OverlapV = FMath::Max<std::int64_t>(0,
|
||||
FMath::Min(Tile.Max[VAxis], Neighbour.Max[VAxis])
|
||||
- FMath::Max(Tile.Min[VAxis], Neighbour.Min[VAxis]));
|
||||
if (OverlapU == 0 || OverlapV == 0) continue;
|
||||
CoveredArea += OverlapU * OverlapV;
|
||||
bTouchesLodTransition = bTouchesLodTransition || Neighbour.Lod != Tile.Lod;
|
||||
}
|
||||
++OutCheckedFaces;
|
||||
if (CoveredArea != FaceArea)
|
||||
{
|
||||
OutError = FString::Printf(
|
||||
TEXT("Core clipmap has a missing or overlapping neighbour at LOD %d, axis %d, face %d (coverage %lld/%lld)."),
|
||||
Tile.Lod, Axis, bPositiveFace ? 1 : 0,
|
||||
static_cast<long long>(CoveredArea), static_cast<long long>(FaceArea));
|
||||
return false;
|
||||
}
|
||||
if (bTouchesLodTransition) ++OutTransitionFaces;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (OutLocalTileCount == 0 || OutTransitionFaces == 0)
|
||||
{
|
||||
OutError = TEXT("Core clipmap coverage audit found no local tiles or no local LOD transition faces.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VerifyStandaloneCoreAdapter(const FString& Json, FString& OutError)
|
||||
{
|
||||
FTCHARToUTF8 Utf8Json(*Json);
|
||||
@@ -13091,7 +13222,11 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
// Core complets en mémoire simultanément ; elle calcule aussi les hachages après édition.
|
||||
TMap<FVoxelTileKey, FVoxelForgeCoreTileHashRecord> ExpectedHashes;
|
||||
TMap<FVoxelTileKey, FVoxelForgeCoreTileHashRecord> ExpectedPostEditHashes;
|
||||
TMap<FVoxelTileKey, FVoxelForgeCoreTileHashRecord> ExpectedTravelHashes;
|
||||
TMap<FVoxelTileKey, FVoxelForgeCoreTileHashRecord> ExpectedSurfaceTravelHashes;
|
||||
TSet<FVoxelTileKey> ExpectedInitialDesiredSet;
|
||||
TSet<FVoxelTileKey> ExpectedTravelDesiredSet;
|
||||
TSet<FVoxelTileKey> ExpectedSurfaceTravelDesiredSet;
|
||||
TArray<FVoxelTileKey> ExpectedAffectedTiles;
|
||||
FVector CarveLocalPositionCm = FVector::ZeroVector;
|
||||
FVoxelTileKey CarveTargetTile;
|
||||
@@ -13101,7 +13236,28 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
CarveBrushRadiusLegacyVoxels * static_cast<double>(VOXEL_SIZE) / CoreVoxelSizeCm;
|
||||
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::size_t ExpectedInitialTileCount = 0;
|
||||
std::size_t ExpectedTravelTileCount = 0;
|
||||
std::size_t TravelFineTilesCompiled = 0;
|
||||
std::size_t TravelRegionsBuilt = 0;
|
||||
std::int32_t TravelCoverageLocalTiles = 0;
|
||||
std::int32_t TravelCoverageFaces = 0;
|
||||
std::int32_t TravelLodTransitionFaces = 0;
|
||||
std::int32_t TravelTilesWithSkirts = 0;
|
||||
std::size_t SurfaceTravelFineTilesCompiled = 0;
|
||||
std::size_t SurfaceTravelRegionsBuilt = 0;
|
||||
std::int32_t SurfaceTravelCoverageLocalTiles = 0;
|
||||
std::int32_t SurfaceTravelCoverageFaces = 0;
|
||||
std::int32_t SurfaceTravelLodTransitionFaces = 0;
|
||||
std::int32_t SurfaceTravelTilesWithSkirts = 0;
|
||||
VoxelForgeCore::TileRegionCompileStats TravelCompileStats;
|
||||
VoxelForgeCore::TileRegionCompileStats SurfaceTravelCompileStats;
|
||||
const VoxelForgeCore::Vec3d TravelCameraVoxels{-156.75164794921875, 25.093801498413086,
|
||||
-823.67364501953125};
|
||||
const VoxelForgeCore::Vec3d SurfaceCameraVoxels{-156.75164794921875, 25.093801498413086,
|
||||
-1024.0};
|
||||
{
|
||||
FTCHARToUTF8 Utf8Json(*Json);
|
||||
std::string CoreError;
|
||||
@@ -13206,6 +13362,120 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
}
|
||||
}
|
||||
InitialAggregates = StandaloneCoreAggregateHashes(AggregateRows);
|
||||
|
||||
// Rebuild and hash the same full clipmap at W1 and W16; the main reference is W8.
|
||||
// Recompiler et hacher le même clipmap à W1 et W16 ; la référence principale est W8.
|
||||
auto BuildWorkerHash = [&](uint32 WorkerLimit,
|
||||
std::array<std::uint64_t, 4>& OutHashes) -> bool
|
||||
{
|
||||
std::optional<VoxelForgeCore::ResolvedWorldSnapshot> WorkerSnapshot =
|
||||
VoxelForgeCore::ResolvedWorldSnapshot::FromJson(
|
||||
std::string_view(Utf8Json.Get(), static_cast<std::size_t>(Utf8Json.Length())),
|
||||
CoreError);
|
||||
if (!WorkerSnapshot)
|
||||
{
|
||||
OutError = FString::Printf(TEXT("Core worker-hash snapshot failed: %s"),
|
||||
UTF8_TO_TCHAR(CoreError.c_str()));
|
||||
return false;
|
||||
}
|
||||
VoxelForgeCore::World WorkerWorld(std::move(*WorkerSnapshot));
|
||||
VoxelForgeCore::ShapeBuildRequest WorkerRequest = Request;
|
||||
WorkerRequest.workerLimit = WorkerLimit;
|
||||
FStandaloneCoreAdapterScheduler WorkerScheduler;
|
||||
if (!WorkerWorld.compile(WorkerRequest, WorkerScheduler, CoreError))
|
||||
{
|
||||
OutError = FString::Printf(TEXT("Core W%u compile failed: %s"), WorkerLimit,
|
||||
UTF8_TO_TCHAR(CoreError.c_str()));
|
||||
return false;
|
||||
}
|
||||
WorkerWorld.retainTileRegions(Desired);
|
||||
std::vector<FStandaloneCoreAdapterTileHashes> WorkerRows;
|
||||
WorkerRows.reserve(Desired.size());
|
||||
for (const VoxelForgeCore::TileKey& Key : Desired)
|
||||
{
|
||||
const std::optional<VoxelForgeCore::TileMesh> Mesh =
|
||||
WorkerWorld.generateTile(Key, Options, CoreError);
|
||||
if (!Mesh || !Mesh->hasDensityHash || !Mesh->hasMeshHash
|
||||
|| !Mesh->hasPayloadHash || !Mesh->hasTopologyHash)
|
||||
{
|
||||
OutError = FString::Printf(TEXT("Core W%u tile hash failed: %s"), WorkerLimit,
|
||||
UTF8_TO_TCHAR(CoreError.c_str()));
|
||||
return false;
|
||||
}
|
||||
WorkerRows.push_back({Key,
|
||||
{Mesh->densityHash, Mesh->meshHash, Mesh->payloadHash, Mesh->topologyHash}});
|
||||
}
|
||||
OutHashes = StandaloneCoreAggregateHashes(WorkerRows);
|
||||
return true;
|
||||
};
|
||||
std::array<std::uint64_t, 4> Worker1Aggregates{};
|
||||
std::array<std::uint64_t, 4> Worker16Aggregates{};
|
||||
if (!BuildWorkerHash(1, Worker1Aggregates)
|
||||
|| !BuildWorkerHash(16, Worker16Aggregates))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (Worker1Aggregates != InitialAggregates || Worker16Aggregates != InitialAggregates)
|
||||
{
|
||||
OutError = TEXT("Core full-clipmap hashes differ across worker limits 1, 8, and 16.");
|
||||
return false;
|
||||
}
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCoreWorldTest] worker_hash_matrix=1/8/16 stable=1 density=0x%016llx mesh=0x%016llx payload=0x%016llx topology=0x%016llx"),
|
||||
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]));
|
||||
|
||||
if (!Manager->GetLayout().IsEmpty() && Manager->GetLayout()[0].Definition != nullptr)
|
||||
{
|
||||
const FStrateSlot& TopSlot = Manager->GetLayout()[0];
|
||||
const FIntVector MidChunk(0, 0, (TopSlot.TopChunkZ + TopSlot.BottomChunkZ) / 2);
|
||||
const FStrateGenerationParams TopParams = Manager->GetGenerationParams(MidChunk);
|
||||
const float TopZ = (static_cast<float>(TopSlot.TopChunkZ) + 1.0f) * CHUNK_SIZE;
|
||||
const float BottomZ = static_cast<float>(TopSlot.BottomChunkZ) * CHUNK_SIZE;
|
||||
const UVoxelSeasonAsset* EffectiveSeason = AuthoredSettings->Season.LoadSynchronous();
|
||||
const float OriginRadius = EffectiveSeason != nullptr
|
||||
? EffectiveSeason->GetOriginSpineRadius() : AuthoredSettings->OriginSpineRadius;
|
||||
const VoxelPassageGeometry::FOriginLandingGeometry Geometry =
|
||||
VoxelPassageGeometry::BuildOriginLandingGeometry(
|
||||
TopZ, BottomZ, TopParams.BoundarySealThickness,
|
||||
OriginRadius);
|
||||
if (Geometry.bValid)
|
||||
{
|
||||
const double RoomZ = 0.5 * (Geometry.FloorZ + Geometry.CeilingZ);
|
||||
const float RoomDensity = ReferenceWorld.densityAt({0.0, 0.0, RoomZ});
|
||||
const float FloorCenterBelow = ReferenceWorld.densityAt(
|
||||
{0.0, 0.0, Geometry.FloorZ - 8.0});
|
||||
const float FloorSideBelow = ReferenceWorld.densityAt(
|
||||
{Geometry.HalfWidth * 0.5, 0.0, Geometry.FloorZ - 8.0});
|
||||
const float FloorCenterAbove = ReferenceWorld.densityAt(
|
||||
{0.0, 0.0, Geometry.FloorZ + 8.0});
|
||||
const float FloorSideAbove = ReferenceWorld.densityAt(
|
||||
{Geometry.HalfWidth * 0.5, 0.0, Geometry.FloorZ + 8.0});
|
||||
const float ShaftContinuationDensity = ReferenceWorld.densityAt(
|
||||
{0.0, 0.0, Geometry.FloorZ - 32.0});
|
||||
if (!(RoomDensity > 0.0f) || !(FloorCenterBelow < 0.0f)
|
||||
|| !(FloorSideBelow < 0.0f) || !(FloorCenterAbove > 0.0f)
|
||||
|| !(FloorSideAbove > 0.0f) || !(ShaftContinuationDensity < 0.0f))
|
||||
{
|
||||
OutError = FString::Printf(
|
||||
TEXT("Core origin landing geometry failed: room=%.3f floor_below=(%.3f,%.3f) floor_above=(%.3f,%.3f) shaft_below=%.3f."),
|
||||
RoomDensity, FloorCenterBelow, FloorSideBelow,
|
||||
FloorCenterAbove, FloorSideAbove, ShaftContinuationDensity);
|
||||
return false;
|
||||
}
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCoreWorldTest] origin_landing=PASS floor=%.3f room=%.3f floor_below=(%.3f,%.3f) floor_above=(%.3f,%.3f) shaft_below=%.3f"),
|
||||
Geometry.FloorZ, RoomDensity, FloorCenterBelow, FloorSideBelow,
|
||||
FloorCenterAbove, FloorSideAbove, ShaftContinuationDensity);
|
||||
}
|
||||
else
|
||||
{
|
||||
OutError = TEXT("Core origin landing test geometry is invalid for the top strate.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!bFoundCarveVertex)
|
||||
{
|
||||
OutError = TEXT("Standalone Core reference has no level-0 surface vertex for the carve fixture.");
|
||||
@@ -13274,6 +13544,174 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
OutError = TEXT("Standalone Core edit fixture did not affect its carved level-0 tile.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// The authored lower landing is in the second strate (the biome/surface strate). Compile
|
||||
// its fine clipmap pages from the same Core reference before moving AVoxelWorld there.
|
||||
// Le palier inférieur appartient à la strate surface/biomes; préparer la référence avant le déplacement.
|
||||
const std::vector<VoxelForgeCore::TileKey> TravelDesired =
|
||||
ReferenceWorld.desiredTiles(TravelCameraVoxels);
|
||||
if (TravelDesired.empty())
|
||||
{
|
||||
OutError = TEXT("Standalone Core returned no desired tiles at the authored lower landing.");
|
||||
return false;
|
||||
}
|
||||
FString CoverageError;
|
||||
if (!AuditCoreClipmapCoverage(TravelDesired, TravelCameraVoxels, 128,
|
||||
TravelCoverageLocalTiles, TravelCoverageFaces, TravelLodTransitionFaces,
|
||||
CoverageError))
|
||||
{
|
||||
OutError = FString::Printf(TEXT("Standalone Core lower-landing coverage failed: %s"),
|
||||
*CoverageError);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<VoxelForgeCore::TileKey> MissingFineTiles;
|
||||
MissingFineTiles.reserve(TravelDesired.size());
|
||||
for (const VoxelForgeCore::TileKey& Key : TravelDesired)
|
||||
{
|
||||
const FVoxelTileKey RuntimeKey(
|
||||
FIntVector(Key.coordinate.x, Key.coordinate.y, Key.coordinate.z), Key.lod);
|
||||
ExpectedTravelDesiredSet.Add(RuntimeKey);
|
||||
if (Key.lod < ReferenceWorld.configuration().fullResolutionLodCount)
|
||||
{
|
||||
MissingFineTiles.push_back(Key);
|
||||
}
|
||||
}
|
||||
if (!ReferenceWorld.compileTileRegion(MissingFineTiles, 16, Scheduler, CoreError,
|
||||
&TravelFineTilesCompiled, &TravelCompileStats))
|
||||
{
|
||||
OutError = FString::Printf(TEXT("Standalone Core lower-landing region compile failed: %s"),
|
||||
UTF8_TO_TCHAR(CoreError.c_str()));
|
||||
return false;
|
||||
}
|
||||
ReferenceWorld.retainTileRegions(TravelDesired);
|
||||
TravelRegionsBuilt = TravelCompileStats.regionsBuilt;
|
||||
|
||||
VoxelForgeCore::TileGenerationOptions TravelOptions;
|
||||
TravelOptions.hashDensity = true;
|
||||
std::vector<FStandaloneCoreAdapterTileHashes> TravelRows;
|
||||
TravelRows.reserve(TravelDesired.size());
|
||||
for (const VoxelForgeCore::TileKey& Key : TravelDesired)
|
||||
{
|
||||
const std::optional<VoxelForgeCore::TileMesh> Mesh =
|
||||
ReferenceWorld.generateTile(Key, TravelOptions, CoreError);
|
||||
if (!Mesh || !Mesh->hasDensityHash || !Mesh->hasMeshHash
|
||||
|| !Mesh->hasPayloadHash || !Mesh->hasTopologyHash)
|
||||
{
|
||||
OutError = FString::Printf(TEXT("Standalone Core lower-landing tile 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;
|
||||
ExpectedTravelHashes.Add(RuntimeKey, Record);
|
||||
TravelRows.push_back({Key, {Record.Density, Record.Mesh, Record.Payload, Record.Topology}});
|
||||
if (Mesh->groups.skirtTriangleCount > 0) ++TravelTilesWithSkirts;
|
||||
}
|
||||
TravelAggregates = StandaloneCoreAggregateHashes(TravelRows);
|
||||
ExpectedTravelTileCount = TravelDesired.size();
|
||||
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCoreWorldTest] standalone_travel_reference=1 location=(%.3f,%.3f,%.3f) tiles=%llu 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"),
|
||||
TravelCameraVoxels.x, TravelCameraVoxels.y, TravelCameraVoxels.z,
|
||||
static_cast<unsigned long long>(ExpectedTravelTileCount),
|
||||
static_cast<unsigned long long>(TravelFineTilesCompiled),
|
||||
static_cast<unsigned long long>(TravelRegionsBuilt),
|
||||
TravelCoverageLocalTiles, TravelCoverageFaces, TravelLodTransitionFaces,
|
||||
TravelTilesWithSkirts,
|
||||
static_cast<unsigned long long>(TravelAggregates[0]),
|
||||
static_cast<unsigned long long>(TravelAggregates[1]),
|
||||
static_cast<unsigned long long>(TravelAggregates[2]),
|
||||
static_cast<unsigned long long>(TravelAggregates[3]));
|
||||
|
||||
// Compile a second destination deeper in the biome strate, matching the owner's surface screenshot.
|
||||
// Compiler une seconde destination dans la strate biome, à l'emplacement de la capture surface.
|
||||
const std::vector<VoxelForgeCore::TileKey> SurfaceTravelDesired =
|
||||
ReferenceWorld.desiredTiles(SurfaceCameraVoxels);
|
||||
if (SurfaceTravelDesired.empty())
|
||||
{
|
||||
OutError = TEXT("Standalone Core returned no desired tiles at the surface-strate fixture.");
|
||||
return false;
|
||||
}
|
||||
if (!AuditCoreClipmapCoverage(SurfaceTravelDesired, SurfaceCameraVoxels, 128,
|
||||
SurfaceTravelCoverageLocalTiles, SurfaceTravelCoverageFaces,
|
||||
SurfaceTravelLodTransitionFaces, CoverageError))
|
||||
{
|
||||
OutError = FString::Printf(TEXT("Standalone Core surface-strate coverage failed: %s"),
|
||||
*CoverageError);
|
||||
return false;
|
||||
}
|
||||
std::vector<VoxelForgeCore::TileKey> MissingSurfaceFineTiles;
|
||||
MissingSurfaceFineTiles.reserve(SurfaceTravelDesired.size());
|
||||
for (const VoxelForgeCore::TileKey& Key : SurfaceTravelDesired)
|
||||
{
|
||||
const FVoxelTileKey RuntimeKey(
|
||||
FIntVector(Key.coordinate.x, Key.coordinate.y, Key.coordinate.z), Key.lod);
|
||||
ExpectedSurfaceTravelDesiredSet.Add(RuntimeKey);
|
||||
if (Key.lod < ReferenceWorld.configuration().fullResolutionLodCount)
|
||||
MissingSurfaceFineTiles.push_back(Key);
|
||||
}
|
||||
// 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.
|
||||
ReferenceWorld.retainTileRegions(SurfaceTravelDesired);
|
||||
if (!ReferenceWorld.compileTileRegion(MissingSurfaceFineTiles, 16, Scheduler, CoreError,
|
||||
&SurfaceTravelFineTilesCompiled, &SurfaceTravelCompileStats))
|
||||
{
|
||||
OutError = FString::Printf(TEXT("Standalone Core surface-strate region compile failed: %s"),
|
||||
UTF8_TO_TCHAR(CoreError.c_str()));
|
||||
return false;
|
||||
}
|
||||
SurfaceTravelRegionsBuilt = SurfaceTravelCompileStats.regionsBuilt;
|
||||
std::vector<FStandaloneCoreAdapterTileHashes> SurfaceTravelRows;
|
||||
SurfaceTravelRows.reserve(SurfaceTravelDesired.size());
|
||||
for (const VoxelForgeCore::TileKey& Key : SurfaceTravelDesired)
|
||||
{
|
||||
const std::optional<VoxelForgeCore::TileMesh> Mesh =
|
||||
ReferenceWorld.generateTile(Key, TravelOptions, CoreError);
|
||||
if (!Mesh || !Mesh->hasDensityHash || !Mesh->hasMeshHash
|
||||
|| !Mesh->hasPayloadHash || !Mesh->hasTopologyHash)
|
||||
{
|
||||
OutError = FString::Printf(TEXT("Standalone Core surface-strate tile 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;
|
||||
ExpectedSurfaceTravelHashes.Add(RuntimeKey, Record);
|
||||
SurfaceTravelRows.push_back({Key,
|
||||
{Record.Density, Record.Mesh, Record.Payload, Record.Topology}});
|
||||
if (Mesh->groups.skirtTriangleCount > 0) ++SurfaceTravelTilesWithSkirts;
|
||||
}
|
||||
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"),
|
||||
SurfaceCameraVoxels.x, SurfaceCameraVoxels.y, SurfaceCameraVoxels.z,
|
||||
ExpectedSurfaceTravelDesiredSet.Num(),
|
||||
static_cast<unsigned long long>(SurfaceTravelFineTilesCompiled),
|
||||
static_cast<unsigned long long>(SurfaceTravelRegionsBuilt),
|
||||
SurfaceTravelCoverageLocalTiles, SurfaceTravelCoverageFaces,
|
||||
SurfaceTravelLodTransitionFaces, SurfaceTravelTilesWithSkirts,
|
||||
static_cast<unsigned long long>(SurfaceTravelAggregates[0]),
|
||||
static_cast<unsigned long long>(SurfaceTravelAggregates[1]),
|
||||
static_cast<unsigned long long>(SurfaceTravelAggregates[2]),
|
||||
static_cast<unsigned long long>(SurfaceTravelAggregates[3]));
|
||||
|
||||
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),
|
||||
@@ -13607,6 +14045,234 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
ExpectedAffectedTiles.Num(), CarveTargetTile.Coord.X, CarveTargetTile.Coord.Y,
|
||||
CarveTargetTile.Coord.Z, CarveTargetTile.Level,
|
||||
static_cast<unsigned long long>(ExpectedPostEditHashes.FindChecked(CarveTargetTile).Mesh));
|
||||
|
||||
Actor->UnregisterStreamingAnchor(Anchor);
|
||||
Anchor->Destroy();
|
||||
if (!WaitForStable(180.0))
|
||||
{
|
||||
OutError = TEXT("Core did not settle after removing the CollisionOnly anchor fixture.");
|
||||
return false;
|
||||
}
|
||||
|
||||
APlayerController* TravelController = TestWorld->SpawnActor<APlayerController>(
|
||||
APlayerController::StaticClass(), FVector::ZeroVector, FRotator::ZeroRotator);
|
||||
APawn* TravelPawn = TestWorld->SpawnActor<APawn>(
|
||||
APawn::StaticClass(), FVector::ZeroVector, FRotator::ZeroRotator);
|
||||
if (TravelController == nullptr || TravelPawn == nullptr)
|
||||
{
|
||||
OutError = TEXT("Could not create the BuildHost player/controller for the Core travel proof.");
|
||||
return false;
|
||||
}
|
||||
USceneComponent* TravelPawnRoot = NewObject<USceneComponent>(TravelPawn, NAME_None, RF_Transient);
|
||||
if (TravelPawnRoot == nullptr)
|
||||
{
|
||||
OutError = TEXT("Could not create a root component for the BuildHost travel pawn.");
|
||||
return false;
|
||||
}
|
||||
TravelPawn->SetRootComponent(TravelPawnRoot);
|
||||
TravelPawnRoot->RegisterComponent();
|
||||
const FVector TravelWorldPosition(
|
||||
TravelCameraVoxels.x * CoreVoxelSizeCm,
|
||||
TravelCameraVoxels.y * CoreVoxelSizeCm,
|
||||
TravelCameraVoxels.z * CoreVoxelSizeCm);
|
||||
TravelPawn->SetActorLocation(TravelWorldPosition);
|
||||
TravelController->Possess(TravelPawn);
|
||||
if (TravelController->GetPawn() != TravelPawn)
|
||||
{
|
||||
OutError = TEXT("BuildHost player controller did not possess the travel pawn.");
|
||||
return false;
|
||||
}
|
||||
if (!WaitForStable(300.0))
|
||||
{
|
||||
OutError = FString::Printf(
|
||||
TEXT("Core did not page and settle around the traveled pawn: desired=%d loaded=%d pending=%d cooks=%d active=%d."),
|
||||
Actor->DesiredSorted.Num(), Actor->LoadedTiles.Num(), Actor->PendingTiles.Num(),
|
||||
Actor->PendingCollisionCooks.Num(),
|
||||
Actor->ActiveTaskCount.load(std::memory_order_acquire));
|
||||
return false;
|
||||
}
|
||||
|
||||
TSet<FVoxelTileKey> ActualTravelDesiredSet;
|
||||
std::vector<VoxelForgeCore::TileKey> ActorTravelDesired;
|
||||
ActorTravelDesired.reserve(static_cast<std::size_t>(Actor->DesiredSorted.Num()));
|
||||
for (const FVoxelTileKey& Key : Actor->DesiredSorted)
|
||||
{
|
||||
ActualTravelDesiredSet.Add(Key);
|
||||
ActorTravelDesired.push_back({{Key.Coord.X, Key.Coord.Y, Key.Coord.Z}, Key.Level});
|
||||
}
|
||||
if (ActualTravelDesiredSet.Num() != ExpectedTravelDesiredSet.Num())
|
||||
{
|
||||
OutError = FString::Printf(
|
||||
TEXT("Core actor desired %d tiles at the lower landing; standalone Core expects %d."),
|
||||
ActualTravelDesiredSet.Num(), ExpectedTravelDesiredSet.Num());
|
||||
return false;
|
||||
}
|
||||
for (const FVoxelTileKey& Key : ExpectedTravelDesiredSet)
|
||||
{
|
||||
if (!ActualTravelDesiredSet.Contains(Key) || !Actor->LoadedTiles.Contains(Key))
|
||||
{
|
||||
OutError = FString::Printf(
|
||||
TEXT("Core actor omitted or failed to load lower-landing 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 = ExpectedTravelHashes.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 lower-landing tile differs from standalone Core at (%d,%d,%d) lod=%d."),
|
||||
Key.Coord.X, Key.Coord.Y, Key.Coord.Z, Key.Level);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
FString ActorCoverageError;
|
||||
std::int32_t ActorCoverageLocalTiles = 0;
|
||||
std::int32_t ActorCoverageFaces = 0;
|
||||
std::int32_t ActorTransitionFaces = 0;
|
||||
if (!AuditCoreClipmapCoverage(ActorTravelDesired,
|
||||
{TravelCameraVoxels.x, TravelCameraVoxels.y, TravelCameraVoxels.z}, 128,
|
||||
ActorCoverageLocalTiles, ActorCoverageFaces, ActorTransitionFaces,
|
||||
ActorCoverageError))
|
||||
{
|
||||
OutError = FString::Printf(TEXT("AVoxelWorld lower-landing coverage failed: %s"),
|
||||
*ActorCoverageError);
|
||||
return false;
|
||||
}
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCoreWorldTest] pass phase=surface_travel center=(%.3f,%.3f,%.3f) desired=%d loaded=%d local_tiles=%d checked_faces=%d lod_transition_faces=%d density=0x%016llx mesh=0x%016llx payload=0x%016llx topology=0x%016llx"),
|
||||
TravelCameraVoxels.x, TravelCameraVoxels.y, TravelCameraVoxels.z,
|
||||
ActualTravelDesiredSet.Num(), Actor->LoadedTiles.Num(), ActorCoverageLocalTiles,
|
||||
ActorCoverageFaces, ActorTransitionFaces,
|
||||
static_cast<unsigned long long>(TravelAggregates[0]),
|
||||
static_cast<unsigned long long>(TravelAggregates[1]),
|
||||
static_cast<unsigned long long>(TravelAggregates[2]),
|
||||
static_cast<unsigned long long>(TravelAggregates[3]));
|
||||
|
||||
const FVector SurfaceWorldPosition(
|
||||
SurfaceCameraVoxels.x * CoreVoxelSizeCm,
|
||||
SurfaceCameraVoxels.y * CoreVoxelSizeCm,
|
||||
SurfaceCameraVoxels.z * CoreVoxelSizeCm);
|
||||
TravelPawn->SetActorLocation(SurfaceWorldPosition);
|
||||
auto WaitForSurfaceVisualTiles = [&]()
|
||||
{
|
||||
const double Deadline = FPlatformTime::Seconds() + 120.0;
|
||||
while (FPlatformTime::Seconds() < Deadline)
|
||||
{
|
||||
TestWorld->Tick(LEVELTICK_All, 0.016f);
|
||||
++GFrameCounter;
|
||||
FPlatformProcess::Sleep(0.001f);
|
||||
bool bVisualTilesReady = true;
|
||||
for (const FVoxelTileKey& Key : ExpectedSurfaceTravelDesiredSet)
|
||||
{
|
||||
const FVoxelForgeCoreTileHashRecord* Expected = ExpectedSurfaceTravelHashes.Find(Key);
|
||||
const FVoxelForgeCoreTileHashRecord* Actual = Actor->VoxelForgeCoreTileHashes.Find(Key);
|
||||
if (Expected == nullptr || Actual == nullptr || !Actual->bHasHashes
|
||||
|| (!Expected->bEmpty && !Actor->LoadedTiles.Contains(Key)))
|
||||
{
|
||||
bVisualTilesReady = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bVisualTilesReady) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
if (!WaitForSurfaceVisualTiles())
|
||||
{
|
||||
int32 HashedSurfaceTiles = 0;
|
||||
int32 MissingVisibleSurfaceTiles = 0;
|
||||
for (const FVoxelTileKey& Key : ExpectedSurfaceTravelDesiredSet)
|
||||
{
|
||||
if (Actor->VoxelForgeCoreTileHashes.Contains(Key)) ++HashedSurfaceTiles;
|
||||
const FVoxelForgeCoreTileHashRecord* Expected = ExpectedSurfaceTravelHashes.Find(Key);
|
||||
if (Expected != nullptr && !Expected->bEmpty && !Actor->LoadedTiles.Contains(Key))
|
||||
++MissingVisibleSurfaceTiles;
|
||||
}
|
||||
OutError = FString::Printf(
|
||||
TEXT("Core did not load the surface-strate visual set: expected=%d hashed=%d missing_visible=%d desired=%d loaded=%d pending=%d active=%d."),
|
||||
ExpectedSurfaceTravelDesiredSet.Num(), HashedSurfaceTiles,
|
||||
MissingVisibleSurfaceTiles, Actor->DesiredSorted.Num(), Actor->LoadedTiles.Num(),
|
||||
Actor->PendingTiles.Num(), Actor->ActiveTaskCount.load(std::memory_order_acquire));
|
||||
return false;
|
||||
}
|
||||
|
||||
TSet<FVoxelTileKey> ActualSurfaceVisualDesiredSet;
|
||||
std::vector<VoxelForgeCore::TileKey> ActorSurfaceTravelDesired;
|
||||
ActorSurfaceTravelDesired.reserve(static_cast<std::size_t>(Actor->DesiredSorted.Num()));
|
||||
for (const FVoxelTileKey& Key : Actor->DesiredSorted)
|
||||
{
|
||||
if (!Actor->CollisionOnlyTiles.Contains(Key))
|
||||
{
|
||||
ActualSurfaceVisualDesiredSet.Add(Key);
|
||||
ActorSurfaceTravelDesired.push_back({{Key.Coord.X, Key.Coord.Y, Key.Coord.Z}, Key.Level});
|
||||
}
|
||||
}
|
||||
if (ActualSurfaceVisualDesiredSet.Num() != ExpectedSurfaceTravelDesiredSet.Num())
|
||||
{
|
||||
OutError = FString::Printf(
|
||||
TEXT("Core actor desired %d visual tiles at the surface-strate fixture; standalone Core expects %d."),
|
||||
ActualSurfaceVisualDesiredSet.Num(), ExpectedSurfaceTravelDesiredSet.Num());
|
||||
return false;
|
||||
}
|
||||
for (const FVoxelTileKey& Key : ExpectedSurfaceTravelDesiredSet)
|
||||
{
|
||||
if (!ActualSurfaceVisualDesiredSet.Contains(Key))
|
||||
{
|
||||
OutError = FString::Printf(
|
||||
TEXT("Core actor omitted surface-strate visual 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 = ExpectedSurfaceTravelHashes.Find(Key);
|
||||
if (Actual == nullptr || Expected == nullptr || !Actual->bHasHashes
|
||||
|| (!Expected->bEmpty && !Actor->LoadedTiles.Contains(Key))
|
||||
|| 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);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
FString SurfaceCoverageError;
|
||||
std::int32_t ActorSurfaceLocalTiles = 0;
|
||||
std::int32_t ActorSurfaceCheckedFaces = 0;
|
||||
std::int32_t ActorSurfaceTransitionFaces = 0;
|
||||
if (!AuditCoreClipmapCoverage(ActorSurfaceTravelDesired,
|
||||
{SurfaceCameraVoxels.x, SurfaceCameraVoxels.y, SurfaceCameraVoxels.z}, 128,
|
||||
ActorSurfaceLocalTiles, ActorSurfaceCheckedFaces, ActorSurfaceTransitionFaces,
|
||||
SurfaceCoverageError))
|
||||
{
|
||||
OutError = FString::Printf(TEXT("AVoxelWorld surface-strate coverage failed: %s"),
|
||||
*SurfaceCoverageError);
|
||||
return false;
|
||||
}
|
||||
const FVoxelBiomeQuery SurfaceBiome = Actor->GetBiomeAtWorldLocation(SurfaceWorldPosition);
|
||||
if (!SurfaceBiome.bHasBiomes || SurfaceBiome.DominantContextIndex < 0)
|
||||
{
|
||||
OutError = TEXT("Core-mode biome query returned no dominant biome at the authored surface-strate fixture.");
|
||||
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"),
|
||||
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(),
|
||||
static_cast<unsigned long long>(SurfaceTravelAggregates[0]),
|
||||
static_cast<unsigned long long>(SurfaceTravelAggregates[1]),
|
||||
static_cast<unsigned long long>(SurfaceTravelAggregates[2]),
|
||||
static_cast<unsigned long long>(SurfaceTravelAggregates[3]));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user