wip(assembler): round S2 (Sol) -- shared per-tile feature queries for every Core family; island placement 30x faster
Island-only worker context replaced by a shared worker-local candidate domain (WorldAssemblyFeatureTileListV1 / ScopedWorldAssemblyFeatureQueriesV1) serving SurfaceWorld, FloatingIslands and far-shell queries; a per-domain cost hint keeps the full scan on small plans. Content placement prepares the same queries and skips proven air: owner island cell p95 7.07 -> 0.24 ms; populated fixture 572 exact placements, 3.27 s -> 29 ms. Full/candidate hashes equal with dropped-feature controls on both families; S1 default W1/W8/W16 hashes reproduced; Core-off and M2 fixtures pass. Default contract for future families in ARCHITECTURE 8.23. Run on gpt-6.1-sol medium. 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
d6e69420e8
commit
6ddb834b1c
19 files changed
+1847
-130
No files matched your search
@@ -1326,6 +1326,44 @@ figures measure anchor-search work and steady update overhead, not spawn/asset-i
|
|||||||
See Standalone/Research/WorldAssemblerM3/Round5/REPORT.md for the test matrix, owner-plan hash proof,
|
See Standalone/Research/WorldAssemblerM3/Round5/REPORT.md for the test matrix, owner-plan hash proof,
|
||||||
and content-placement measurements.
|
and content-placement measurements.
|
||||||
|
|
||||||
|
### 8.23 Shared assembler query domains — World Assembler S2 / M3 Round 6
|
||||||
|
|
||||||
|
Meshing, far-shell field queries and placement share `ScopedWorldAssemblyFeatureQueriesV1`.
|
||||||
|
Each family prepares conservative candidates for an inclusive domain, including normal halos.
|
||||||
|
Scopes borrow immutable recipe/plan pointers from the owning world/snapshot, live on the worker,
|
||||||
|
restore the previous scope when nested, and fall back to full evaluation outside their domain.
|
||||||
|
Queues, generation epochs, edit locks, streaming policy and canonical field arithmetic are unchanged.
|
||||||
|
`candidateEvaluationUseful` is a cost hint resolved once per domain: the measured one-feature
|
||||||
|
SurfaceWorld and four-island plans prefer the full evaluator. Larger plans use candidates. Validation
|
||||||
|
can force candidates to prove the bounds even for a small plan; this never changes recipe identity.
|
||||||
|
|
||||||
|
The default family contract for caves, shafts, pits, passages and rivers is:
|
||||||
|
|
||||||
|
1. Declare conservative inclusive influence bounds, including blend, displacement and warp.
|
||||||
|
Unknown influence retains the feature. Keep canonical operation order.
|
||||||
|
2. If remote features can determine exact positive density, provide sound value-side certificates
|
||||||
|
or evaluate them. Geometric AABBs alone cannot preserve arbitrary density values; island root
|
||||||
|
ellipsoids retain S1's slope and smooth-min certificate. Optional union-group data belongs to the
|
||||||
|
family proof, not to the engine's notion of Euclidean distance.
|
||||||
|
3. Families with a proven air base may declare conservative solid-support bounds. The shared column
|
||||||
|
union skips only proven air; solid-base caves and unknown support retain their ordinary march.
|
||||||
|
Preserve the original sampling phase, crossing count, bisection and final interpolation.
|
||||||
|
4. Audit full-scan versus candidates, halos and outside-domain fallback with equal hashes and a
|
||||||
|
deliberately removed needed feature before enabling a new proof.
|
||||||
|
|
||||||
|
SurfaceWorld's biome-site search remains its bounded local lattice. Cached XY column evaluation
|
||||||
|
uses finite feature candidates; types 1/3 affect every Z, while types 2/4 are neutral outside their
|
||||||
|
validated boxes. Shelf source-column queries outside the scope use the full column evaluator.
|
||||||
|
Core meshing prepares both native families, including cross-strate tiles. FarShells remains derived
|
||||||
|
repair geometry with its existing connector/stratum filtering; its canonical field calls benefit
|
||||||
|
from the same scope wherever their samples lie inside the domain.
|
||||||
|
|
||||||
|
Content snapshots declare support bounds once. Decoration cells and landmark columns prepare the
|
||||||
|
shared domain with a gradient halo, and retain exact crossings. Core-off retains its legacy march.
|
||||||
|
Validation compares complete placement transforms and canonical anchor residuals, and applies then
|
||||||
|
clears an isolated populated HISM fixture under BuildHost null RHI. These CPU costs do not measure
|
||||||
|
rendering or GPU upload. See the Round 6 report for results and limits.
|
||||||
|
|
||||||
## 9. Multiplayer model (listen-server first, dedicated-friendly)
|
## 9. Multiplayer model (listen-server first, dedicated-friendly)
|
||||||
|
|
||||||
> **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a
|
> **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a
|
||||||
|
|||||||
+19
-5
@@ -882,11 +882,25 @@ uses the owner/Core ratio as well. Edits use sequenced `applyEditTransaction` ca
|
|||||||
Core work drains, then regenerate Core's affected keys under a new epoch. Existing streaming anchors
|
Core work drains, then regenerate Core's affected keys under a new epoch. Existing streaming anchors
|
||||||
participate; the headless move proof covers `CollisionOnly` policy.
|
participate; the headless move proof covers `CollisionOnly` policy.
|
||||||
|
|
||||||
The reusable `BuildWorldAssemblyFeatureTileListV1` accepts conservative inclusive feature bounds for
|
The reusable `BuildWorldAssemblyFeatureTileListV1` accepts conservative inclusive feature bounds.
|
||||||
future caves, shafts, pits, and passages. Floating Islands builds one `FloatingIslandTileFeatureListV1`
|
`ScopedWorldAssemblyFeatureQueriesV1` replaces the island-only ShapeWorld worker context: meshing,
|
||||||
per tile and the scoped worker context makes `FloatingIslandFieldV1::densityAt` evaluate only that
|
derived shells, decoration cells and landmark columns share its nestable worker scope. Recipe/plan
|
||||||
candidate list. Candidate order stays canonical, the field hash remains bit-identical, and a dropped-
|
identity and an inclusive domain guard each query; outside queries fall back to the full evaluator.
|
||||||
feature negative control is part of the standalone Round 5 audit.
|
See ARCHITECTURE §8.23 and WorldAssemblerM3/Round6/REPORT.md.
|
||||||
|
|
||||||
|
| Symbol | Canonical file | Role |
|
||||||
|
|---|---|---|
|
||||||
|
| `WorldAssemblyFeatureTileListV1`, `WorldAssemblyFeatureGroupBoundsV1`, `WorldAssemblyFeatureQueryV1` | `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` | Stable-order candidates, optional exact value-side group certificates and borrowed recipe/plan identity. Old island names remain aliases. |
|
||||||
|
| `ScopedWorldAssemblyFeatureQueriesV1::{find,ScopedWorldAssemblyFeatureQueriesV1}`, `WorldAssemblyFeatureTileListV1::candidateEvaluationUseful` | `Standalone/Core/src/WorldAssemblerInternal.h` / public header | Worker-local domain, cost hint resolved once per query, forced validation, nested restoration and full-scan fallback; replaces ShapeWorld's island context. |
|
||||||
|
| `Heightfield2D::buildFeatureTileList`, `world_assembly_detail::FeatureRange` | `Standalone/Core/src/WorldAssemblerInternal.h` | XY bounds for column writers; exact no-op bounds for finite boxes/shelves; canonical operation order. |
|
||||||
|
| `FloatingIslandFieldV1::{solidSupportBounds,buildFeatureTileList,densitySubvoxels}` | `Standalone/Core/src/FloatingIslandsInternal.h` | Finite support declarations, root-aware distance certificates, automatic shared-scope consumption. |
|
||||||
|
| `WorldAssemblyFeatureColumnRangeV1`, `BuildWorldAssemblyFeatureColumnRangeV1` | Public Core header / `Standalone/Core/src/WorldAssemblerInternal.h` | Conservative column support union for air-base families; invalid bounds are unknown. |
|
||||||
|
| `VF_ContentFeatureQueries`, `VF_ConstrainContentMarch` | `Private/VoxelContentManager.cpp` | Immutable queries prepared per cell/landmark; skips proven air while preserving march phase and exact crossing refinement. |
|
||||||
|
| `UVoxelContentManager::MeasurePlacementCostForValidation` | `Private/VoxelContentManager.cpp` | Authored/populated full-versus-candidate transforms, canonical anchor checks, batch and isolated HISM application cost. |
|
||||||
|
| `UVoxelContentManager::ApplyRegion` | `Private/VoxelContentManager.cpp` / `Public/VoxelContentManager.h` | Normal region application retains landmark footprints; its default-true footprint flag isolates the temporary populated validation fixture. |
|
||||||
|
| `RunCoreSharedFeatureCostAudit`, `RunCoreContentPlacementCostAudit` | `Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp` | Paired owner-plan tile/shell timings and hashes, placement metrics and transient BuildHost plan exports. |
|
||||||
|
| `TileGenerationOptions::useAssemblyFeatureQueries`, `TileGenerationStats::{featureListMilliseconds,farShellMilliseconds}` | Public Core header / `Standalone/Core/src/VoxelForgeCore.cpp` | Default-on candidate route, full-scan validation control and stage timing; no identity change. |
|
||||||
|
| `SharedFeatureListAudit.cpp`, `BuildSharedFeatureListAudit.ps1` | `Standalone/Research/WorldAssemblerM3/Round6/` | Owner-plan/stress hashes, shared-scope probes and deliberately dropped needed features. |
|
||||||
|
|
||||||
`-coreworldtest=1` in the BuildHost editor commandlet validates initial tiles, expected collision
|
`-coreworldtest=1` in the BuildHost editor commandlet validates initial tiles, expected collision
|
||||||
bodies/empty tiles, anchor movement, a carve transaction, travel to the B landing and native surface
|
bodies/empty tiles, anchor movement, a carve transaction, travel to the B landing and native surface
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ struct FCoreFloatingIslandFieldSnapshot
|
|||||||
VoxelForgeCore::FloatingIslandRecipeV1 Recipe;
|
VoxelForgeCore::FloatingIslandRecipeV1 Recipe;
|
||||||
VoxelForgeCore::FloatingIslandPlanV1 Plan;
|
VoxelForgeCore::FloatingIslandPlanV1 Plan;
|
||||||
TUniquePtr<VoxelForgeCore::FloatingIslandFieldV1> Field;
|
TUniquePtr<VoxelForgeCore::FloatingIslandFieldV1> Field;
|
||||||
|
std::vector<VoxelForgeCore::WorldAssemblyFeatureBoundsV1> SolidSupportBounds;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FCoreFloatingIslandSnapshot
|
struct FCoreFloatingIslandSnapshot
|
||||||
@@ -231,6 +232,49 @@ struct FCoreFloatingIslandSnapshot
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Shared consumer preparation; snapshots own all pointers for the entire worker lifetime.
|
||||||
|
// Preparation partagee : les snapshots gardent les pointeurs vivants durant toute la tache.
|
||||||
|
static std::vector<VoxelForgeCore::WorldAssemblyFeatureQueryV1> VF_ContentFeatureQueries(
|
||||||
|
const FCoreSurfaceSnapshot* Surface, const FCoreFloatingIslandSnapshot* Islands,
|
||||||
|
int32 StrateIndex, float MinX, float MinY, float MinZ, float MaxX, float MaxY, float MaxZ)
|
||||||
|
{
|
||||||
|
using namespace VoxelForgeCore;
|
||||||
|
constexpr double Q = WorldAssemblySubvoxelsPerVoxel;
|
||||||
|
const std::array<int64,3> Min{static_cast<int64>(std::floor(MinX*Q)),
|
||||||
|
static_cast<int64>(std::floor(MinY*Q)),static_cast<int64>(std::floor(MinZ*Q))};
|
||||||
|
const std::array<int64,3> Max{static_cast<int64>(std::ceil(MaxX*Q)),
|
||||||
|
static_cast<int64>(std::ceil(MaxY*Q)),static_cast<int64>(std::ceil(MaxZ*Q))};
|
||||||
|
std::vector<WorldAssemblyFeatureQueryV1> Queries;
|
||||||
|
if (Surface)
|
||||||
|
if (const auto* Plan = Surface->FindPlan(StrateIndex))
|
||||||
|
{
|
||||||
|
const Heightfield2D Field(Surface->Recipe,*Plan);
|
||||||
|
Queries.push_back({Field.recipe(),Field.plan(),Field.buildFeatureTileList(Min,Max)});
|
||||||
|
}
|
||||||
|
if (Islands)
|
||||||
|
if (const auto* Entry = Islands->FindField(StrateIndex))
|
||||||
|
Queries.push_back({&Entry->Recipe,&Entry->Plan,Entry->Field->buildFeatureTileList(Min,Max)});
|
||||||
|
return Queries;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preserve the original march lattice and crossing refinement, skipping only certified air.
|
||||||
|
// Garder la phase du pas et le raffinement d'origine; sauter uniquement l'air prouve.
|
||||||
|
static bool VF_ConstrainContentMarch(std::span<const VoxelForgeCore::WorldAssemblyFeatureBoundsV1> Bounds,
|
||||||
|
float X, float Y, float Step, float& Top, float& Bottom)
|
||||||
|
{
|
||||||
|
constexpr double Q = VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||||
|
const auto Range = VoxelForgeCore::BuildWorldAssemblyFeatureColumnRangeV1(Bounds,
|
||||||
|
static_cast<int64>(std::llround(X*Q)),static_cast<int64>(std::llround(Y*Q)));
|
||||||
|
if (!Range.known) return true;
|
||||||
|
if (Range.empty) return false;
|
||||||
|
const float SupportTop = static_cast<float>(Range.maximumZSubvoxels/Q);
|
||||||
|
const float SupportBottom = static_cast<float>(Range.minimumZSubvoxels/Q);
|
||||||
|
const float SkipSteps = FMath::Max(0.0f,FMath::FloorToFloat((Top-SupportTop)/Step)-1.0f);
|
||||||
|
Top -= SkipSteps*Step;
|
||||||
|
Bottom = FMath::Max(Bottom,SupportBottom-Step);
|
||||||
|
return Top >= Bottom;
|
||||||
|
}
|
||||||
|
|
||||||
// Global safety cap on spawned decoration ACTORS per cell (across all entries).
|
// Global safety cap on spawned decoration ACTORS per cell (across all entries).
|
||||||
// HISM instances are exempt — they're batched render data, capped per entry by MaxPerChunk.
|
// HISM instances are exempt — they're batched render data, capped per entry by MaxPerChunk.
|
||||||
static constexpr int32 GMaxDecorationActorsPerCell = 400;
|
static constexpr int32 GMaxDecorationActorsPerCell = 400;
|
||||||
@@ -330,6 +374,7 @@ void UVoxelContentManager::Initialize(AActor* InOwner, UVoxelStrateManager* InSt
|
|||||||
IslandPlan.strateIndex);
|
IslandPlan.strateIndex);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
Entry->SolidSupportBounds = Entry->Field->solidSupportBounds();
|
||||||
IslandSnapshot->Fields.Add(IslandPlan.strateIndex, Entry);
|
IslandSnapshot->Fields.Add(IslandPlan.strateIndex, Entry);
|
||||||
}
|
}
|
||||||
if (!IslandSnapshot->Fields.IsEmpty())
|
if (!IslandSnapshot->Fields.IsEmpty())
|
||||||
@@ -799,7 +844,7 @@ bool UVoxelContentManager::MeasurePlacementCostForValidation(
|
|||||||
const int32 Step = FMath::Max(1, Settings->DecorationMarchStepVoxels);
|
const int32 Step = FMath::Max(1, Settings->DecorationMarchStepVoxels);
|
||||||
const int32 MaxCrossings = FMath::Max(1, Settings->DecorationMaxCrossingsPerColumn);
|
const int32 MaxCrossings = FMath::Max(1, Settings->DecorationMaxCrossingsPerColumn);
|
||||||
const float ColumnDepth = static_cast<float>(FMath::Max(8, Settings->DecorationColumnDepthVoxels));
|
const float ColumnDepth = static_cast<float>(FMath::Max(8, Settings->DecorationColumnDepthVoxels));
|
||||||
TArray<double> CellMilliseconds;
|
TArray<double> CellMilliseconds, FullScanCellMilliseconds;
|
||||||
CellMilliseconds.Reserve(SampleCellCount);
|
CellMilliseconds.Reserve(SampleCellCount);
|
||||||
int64 TotalColumns = 0;
|
int64 TotalColumns = 0;
|
||||||
int64 TotalDensityQueries = 0;
|
int64 TotalDensityQueries = 0;
|
||||||
@@ -810,7 +855,7 @@ bool UVoxelContentManager::MeasurePlacementCostForValidation(
|
|||||||
const int32 X = Sample % GridSide - GridSide / 2;
|
const int32 X = Sample % GridSide - GridSide / 2;
|
||||||
const int32 Y = Sample / GridSide - GridSide / 2;
|
const int32 Y = Sample / GridSide - GridSide / 2;
|
||||||
const FIntPoint Cell(PlayerCell.X + X, PlayerCell.Y + Y);
|
const FIntPoint Cell(PlayerCell.X + X, PlayerCell.Y + Y);
|
||||||
const double CellStart = FPlatformTime::Seconds();
|
double CellElapsed = 0.0, FullScanCellElapsed = 0.0;
|
||||||
for (const auto& GridAudit : {
|
for (const auto& GridAudit : {
|
||||||
std::make_pair(&NearGrid, &NearEntriesAudit),
|
std::make_pair(&NearGrid, &NearEntriesAudit),
|
||||||
std::make_pair(&FarGrid, &FarEntriesAudit)})
|
std::make_pair(&FarGrid, &FarEntriesAudit)})
|
||||||
@@ -822,9 +867,26 @@ bool UVoxelContentManager::MeasurePlacementCostForValidation(
|
|||||||
if (Entries.IsEmpty()) continue;
|
if (Entries.IsEmpty()) continue;
|
||||||
TArray<FDecoSpawn> Spawns;
|
TArray<FDecoSpawn> Spawns;
|
||||||
FPlacementBuildCounters Counters;
|
FPlacementBuildCounters Counters;
|
||||||
|
TArray<FDecoSpawn> FullScanSpawns;
|
||||||
|
const double FullStart = FPlatformTime::Seconds();
|
||||||
|
BuildCellSpawns(Generator, OwnerXf, Cell, CurrentCtx, Entries, EntryBiome,
|
||||||
|
static_cast<uint32>(Seed), Grid.Spacing, static_cast<float>(Step),
|
||||||
|
MaxCrossings, ColumnDepth, FullScanSpawns, nullptr, false);
|
||||||
|
FullScanCellElapsed += (FPlatformTime::Seconds()-FullStart)*1000.0;
|
||||||
|
const double CandidateStart = FPlatformTime::Seconds();
|
||||||
BuildCellSpawns(Generator, OwnerXf, Cell, CurrentCtx, Entries, EntryBiome,
|
BuildCellSpawns(Generator, OwnerXf, Cell, CurrentCtx, Entries, EntryBiome,
|
||||||
static_cast<uint32>(Seed), Grid.Spacing, static_cast<float>(Step),
|
static_cast<uint32>(Seed), Grid.Spacing, static_cast<float>(Step),
|
||||||
MaxCrossings, ColumnDepth, Spawns, &Counters);
|
MaxCrossings, ColumnDepth, Spawns, &Counters);
|
||||||
|
CellElapsed += (FPlatformTime::Seconds()-CandidateStart)*1000.0;
|
||||||
|
if (Spawns.Num()!=FullScanSpawns.Num())
|
||||||
|
{ OutError=TEXT("Feature-query placement count changed from full scan."); return false; }
|
||||||
|
for (int32 Index=0;Index<Spawns.Num();++Index)
|
||||||
|
{
|
||||||
|
if (Spawns[Index].EntryIdx!=FullScanSpawns[Index].EntryIdx
|
||||||
|
|| !Spawns[Index].Xf.Equals(FullScanSpawns[Index].Xf,0.0f))
|
||||||
|
{ OutError=TEXT("Feature-query placement transform changed from full scan."); return false; }
|
||||||
|
++OutAudit.ExactPlacementComparisons;
|
||||||
|
}
|
||||||
TotalColumns += Counters.Columns;
|
TotalColumns += Counters.Columns;
|
||||||
TotalDensityQueries += Counters.DensityQueries;
|
TotalDensityQueries += Counters.DensityQueries;
|
||||||
TotalSurfaceQueries += Counters.SurfaceQueries;
|
TotalSurfaceQueries += Counters.SurfaceQueries;
|
||||||
@@ -875,7 +937,10 @@ bool UVoxelContentManager::MeasurePlacementCostForValidation(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CellMilliseconds.Add((FPlatformTime::Seconds() - CellStart) * 1000.0);
|
CellMilliseconds.Add(CellElapsed);
|
||||||
|
FullScanCellMilliseconds.Add(FullScanCellElapsed);
|
||||||
|
OutAudit.ActiveBatchMilliseconds += CellElapsed;
|
||||||
|
OutAudit.FullScanActiveBatchMilliseconds += FullScanCellElapsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto MedianAndP95 = [](TArray<double> Samples, double& OutMedian, double& OutP95)
|
auto MedianAndP95 = [](TArray<double> Samples, double& OutMedian, double& OutP95)
|
||||||
@@ -899,6 +964,117 @@ bool UVoxelContentManager::MeasurePlacementCostForValidation(
|
|||||||
MedianAndP95(MoveTemp(CellMilliseconds), OutAudit.CellMedianMilliseconds,
|
MedianAndP95(MoveTemp(CellMilliseconds), OutAudit.CellMedianMilliseconds,
|
||||||
OutAudit.CellP95Milliseconds);
|
OutAudit.CellP95Milliseconds);
|
||||||
|
|
||||||
|
double UnusedMedian = 0.0;
|
||||||
|
MedianAndP95(MoveTemp(FullScanCellMilliseconds),UnusedMedian,OutAudit.FullScanCellP95Milliseconds);
|
||||||
|
|
||||||
|
// Deliberately populated fixture alongside the unmodified authored palette. No actors are spawned.
|
||||||
|
// Fixture volontairement peuplee, a cote de la palette auteur intacte. Aucun acteur cree.
|
||||||
|
UStaticMesh* FixtureMesh=nullptr;
|
||||||
|
for (const auto* Entries : {&NearEntriesAudit,&FarEntriesAudit})
|
||||||
|
for (const auto& Entry : *Entries)
|
||||||
|
if (!FixtureMesh && Entry.Profile.InstancedMesh) FixtureMesh=Entry.Profile.InstancedMesh;
|
||||||
|
if (!FixtureMesh)
|
||||||
|
FixtureMesh=LoadObject<UStaticMesh>(nullptr,TEXT("/Engine/BasicShapes/Cube.Cube"));
|
||||||
|
TMap<FIntPoint,FDecoRegionBuild> FixtureBuilds;
|
||||||
|
TArray<FStrateDecoration> FixtureEntries;
|
||||||
|
TArray<int32> FixtureBiomes;
|
||||||
|
const int32 BiomeCount = CurrentCtx.BiomeCtx.IsValid() ? CurrentCtx.BiomeCtx.Biomes.Num() : 0;
|
||||||
|
for (int32 Biome=0;Biome<FMath::Max(1,BiomeCount);++Biome)
|
||||||
|
for (int32 Wet=0;Wet<2;++Wet)
|
||||||
|
{
|
||||||
|
FStrateDecoration Entry;
|
||||||
|
Entry.Profile.ActorClass=AActor::StaticClass();
|
||||||
|
Entry.Profile.InstancedMesh=FixtureMesh;
|
||||||
|
Entry.Profile.SurfacePlacement=ESurfaceType::Floor;
|
||||||
|
Entry.Profile.bPlaceBelowWater=Wet!=0;
|
||||||
|
Entry.SpawnDensity=1.0f;
|
||||||
|
Entry.MaxPerChunk=64;
|
||||||
|
FixtureEntries.Add(Entry);
|
||||||
|
FixtureBiomes.Add(BiomeCount ? Biome : -1);
|
||||||
|
}
|
||||||
|
TArray<double> PopulatedTimes, FullPopulatedTimes;
|
||||||
|
for (int32 Sample=0;Sample<SampleCellCount;++Sample)
|
||||||
|
{
|
||||||
|
const FIntPoint Cell(PlayerCell.X+Sample%GridSide-GridSide/2,
|
||||||
|
PlayerCell.Y+Sample/GridSide-GridSide/2);
|
||||||
|
TArray<FDecoSpawn> FullSpawns, Spawns;
|
||||||
|
const double FullStart=FPlatformTime::Seconds();
|
||||||
|
BuildCellSpawns(Generator,OwnerXf,Cell,CurrentCtx,FixtureEntries,FixtureBiomes,
|
||||||
|
static_cast<uint32>(Seed),4,static_cast<float>(Step),MaxCrossings,ColumnDepth,
|
||||||
|
FullSpawns,nullptr,false);
|
||||||
|
const double FullElapsed=(FPlatformTime::Seconds()-FullStart)*1000.0;
|
||||||
|
const double CandidateStart=FPlatformTime::Seconds();
|
||||||
|
BuildCellSpawns(Generator,OwnerXf,Cell,CurrentCtx,FixtureEntries,FixtureBiomes,
|
||||||
|
static_cast<uint32>(Seed),4,static_cast<float>(Step),MaxCrossings,ColumnDepth,Spawns);
|
||||||
|
const double Elapsed=(FPlatformTime::Seconds()-CandidateStart)*1000.0;
|
||||||
|
OutAudit.PopulatedBatchMilliseconds+=Elapsed;
|
||||||
|
OutAudit.FullScanPopulatedBatchMilliseconds+=FullElapsed;
|
||||||
|
if (Spawns.Num()!=FullSpawns.Num())
|
||||||
|
{ OutError=TEXT("Populated fixture count differs from full scan."); return false; }
|
||||||
|
const double MergeStart=FPlatformTime::Seconds();
|
||||||
|
if (FixtureMesh && !Spawns.IsEmpty())
|
||||||
|
{
|
||||||
|
auto& Build=FixtureBuilds.FindOrAdd(CellToRegion(Cell,RegionSize()));
|
||||||
|
auto& Bucket=Build.MeshBuckets.FindOrAdd(FixtureMesh);
|
||||||
|
for (const auto& Spawn : Spawns)
|
||||||
|
{
|
||||||
|
Bucket.Profile=FixtureEntries[Spawn.EntryIdx].Profile;
|
||||||
|
Bucket.Xforms.Add(Spawn.Xf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OutAudit.PopulatedMergeMilliseconds+=(FPlatformTime::Seconds()-MergeStart)*1000.0;
|
||||||
|
for (int32 Index=0;Index<Spawns.Num();++Index)
|
||||||
|
{
|
||||||
|
if (!Spawns[Index].Xf.Equals(FullSpawns[Index].Xf,0.0f))
|
||||||
|
{ OutError=TEXT("Populated fixture anchor differs from full scan."); return false; }
|
||||||
|
++OutAudit.ExactPlacementComparisons;
|
||||||
|
const FVector Local=OwnerXf.InverseTransformPosition(Spawns[Index].Xf.GetLocation());
|
||||||
|
float Density=0.0f;
|
||||||
|
if (CurrentCtx.bNativeFloatingIslands && CurrentCtx.NativeIslands.IsValid()
|
||||||
|
&& CurrentCtx.NativeIslands->GetDensity(CurrentCtx.StrateIndex,
|
||||||
|
Local.X/CurrentCtx.VoxelSizeCm,Local.Y/CurrentCtx.VoxelSizeCm,
|
||||||
|
Local.Z/CurrentCtx.VoxelSizeCm,Density))
|
||||||
|
{
|
||||||
|
OutAudit.MaximumDensityResidual=FMath::Max(OutAudit.MaximumDensityResidual,
|
||||||
|
FMath::Abs(static_cast<double>(Density)));
|
||||||
|
++OutAudit.AnchorChecks;
|
||||||
|
if (FMath::Abs(Density)>0.25f)
|
||||||
|
{ OutError=TEXT("Populated fixture is not anchored on the canonical field."); return false; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!Spawns.IsEmpty())
|
||||||
|
{
|
||||||
|
++OutAudit.PopulatedCells;
|
||||||
|
OutAudit.PopulatedPlacements+=Spawns.Num();
|
||||||
|
PopulatedTimes.Add(Elapsed);
|
||||||
|
FullPopulatedTimes.Add(FullElapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((CurrentCtx.bNativeSurface||CurrentCtx.bNativeFloatingIslands) && OutAudit.PopulatedPlacements==0)
|
||||||
|
{ OutError=TEXT("Populated content fixture produced no placements."); return false; }
|
||||||
|
if (FixtureMesh && OutAudit.PopulatedPlacements>0)
|
||||||
|
{
|
||||||
|
// Exercise actual region application, then close this isolated fixture immediately.
|
||||||
|
// Appliquer une vraie region puis detruire immediatement cette fixture isolee.
|
||||||
|
FDecoGrid FixtureGrid;
|
||||||
|
const double ApplyStart=FPlatformTime::Seconds();
|
||||||
|
// Fixture footprints are isolated from live landmark suppression. / Isoler les empreintes de fixture.
|
||||||
|
for (auto& Pair : FixtureBuilds) ApplyRegion(FixtureGrid,Pair.Key,Pair.Value,false);
|
||||||
|
OutAudit.PopulatedApplyMilliseconds=(FPlatformTime::Seconds()-ApplyStart)*1000.0;
|
||||||
|
for (const auto& Pair : FixtureGrid.Regions)
|
||||||
|
for (const auto& WeakHISM : Pair.Value.Instances)
|
||||||
|
if (const auto* HISM=WeakHISM.Get()) OutAudit.AppliedInstances+=HISM->GetInstanceCount();
|
||||||
|
for (const auto& Pair : FixtureBuilds) ClearDecorationRegion(FixtureGrid,Pair.Key);
|
||||||
|
if (OutAudit.AppliedInstances!=OutAudit.PopulatedPlacements)
|
||||||
|
{
|
||||||
|
OutError=FString::Printf(TEXT("Populated HISM fixture applied %d of %d placements (%d regions, mesh %s)."),
|
||||||
|
OutAudit.AppliedInstances,OutAudit.PopulatedPlacements,FixtureBuilds.Num(),*FixtureMesh->GetPathName());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MedianAndP95(MoveTemp(PopulatedTimes),UnusedMedian,OutAudit.PopulatedCellP95Milliseconds);
|
||||||
|
MedianAndP95(MoveTemp(FullPopulatedTimes),UnusedMedian,OutAudit.FullScanPopulatedCellP95Milliseconds);
|
||||||
|
|
||||||
// The game-thread update cost is sampled with task dispatch disabled; worker-cell cost is reported
|
// The game-thread update cost is sampled with task dispatch disabled; worker-cell cost is reported
|
||||||
// separately above, so the frame figure is not polluted by waiting for asynchronous work.
|
// separately above, so the frame figure is not polluted by waiting for asynchronous work.
|
||||||
UpdateDecorations(PlayerWorldPos);
|
UpdateDecorations(PlayerWorldPos);
|
||||||
@@ -1092,7 +1268,7 @@ void UVoxelContentManager::BuildCellSpawns(const UVoxelGenerator* Gen, const FTr
|
|||||||
const TArray<int32>& EntryBiome, uint32 InSeed,
|
const TArray<int32>& EntryBiome, uint32 InSeed,
|
||||||
int32 Spacing, float Step, int32 MaxCrossings, float ColumnDepth,
|
int32 Spacing, float Step, int32 MaxCrossings, float ColumnDepth,
|
||||||
TArray<FDecoSpawn>& OutSpawns,
|
TArray<FDecoSpawn>& OutSpawns,
|
||||||
FPlacementBuildCounters* OutCounters)
|
FPlacementBuildCounters* OutCounters, bool bUseFeatureQueries)
|
||||||
{
|
{
|
||||||
if (OutCounters != nullptr) *OutCounters = FPlacementBuildCounters();
|
if (OutCounters != nullptr) *OutCounters = FPlacementBuildCounters();
|
||||||
if (!Gen || Entries.Num() == 0) return;
|
if (!Gen || Entries.Num() == 0) return;
|
||||||
@@ -1101,6 +1277,14 @@ void UVoxelContentManager::BuildCellSpawns(const UVoxelGenerator* Gen, const FTr
|
|||||||
const int32 CellOriginVX = Cell.X * CHUNK_SIZE;
|
const int32 CellOriginVX = Cell.X * CHUNK_SIZE;
|
||||||
const int32 CellOriginVY = Cell.Y * CHUNK_SIZE;
|
const int32 CellOriginVY = Cell.Y * CHUNK_SIZE;
|
||||||
|
|
||||||
|
const auto FeatureQueries = bUseFeatureQueries ? VF_ContentFeatureQueries(
|
||||||
|
Ctx.bNativeSurface ? Ctx.NativeSurface.Get() : nullptr,
|
||||||
|
Ctx.bNativeFloatingIslands ? Ctx.NativeIslands.Get() : nullptr,Ctx.StrateIndex,
|
||||||
|
CellOriginVX-1.0f,CellOriginVY-1.0f,Ctx.BottomVoxelZ-1.0f,
|
||||||
|
CellOriginVX+CHUNK_SIZE+1.0f,CellOriginVY+CHUNK_SIZE+1.0f,Ctx.TopVoxelZ+1.0f)
|
||||||
|
: std::vector<VoxelForgeCore::WorldAssemblyFeatureQueryV1>{};
|
||||||
|
const VoxelForgeCore::ScopedWorldAssemblyFeatureQueriesV1 FeatureScope(FeatureQueries);
|
||||||
|
|
||||||
TArray<int32> EntryCount; EntryCount.Init(0, Entries.Num());
|
TArray<int32> EntryCount; EntryCount.Init(0, Entries.Num());
|
||||||
int32 TotalActors = 0;
|
int32 TotalActors = 0;
|
||||||
|
|
||||||
@@ -1437,12 +1621,17 @@ void UVoxelContentManager::BuildCellSpawns(const UVoxelGenerator* Gen, const FTr
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NON-SURFACE archetypes (caves/shafts/islands): ray-march the density column for crossings.
|
// NON-SURFACE archetypes (caves/shafts/islands): ray-march the density column for crossings.
|
||||||
float PrevD = D(VX, VY, Ctx.TopVoxelZ);
|
float MarchTop = Ctx.TopVoxelZ, MarchBottom = Ctx.BottomVoxelZ;
|
||||||
|
if (bUseFeatureQueries && Ctx.bNativeFloatingIslands && Ctx.NativeIslands.IsValid())
|
||||||
|
if (const auto* Entry = Ctx.NativeIslands->FindField(Ctx.StrateIndex))
|
||||||
|
if (!VF_ConstrainContentMarch(Entry->SolidSupportBounds,VX,VY,Step,MarchTop,MarchBottom))
|
||||||
|
continue;
|
||||||
|
float PrevD = D(VX, VY, MarchTop);
|
||||||
int32 Crossings = 0;
|
int32 Crossings = 0;
|
||||||
bool bSeenAir = (PrevD >= 0.0f); // GetDensityAt >= 0 == air
|
bool bSeenAir = (PrevD >= 0.0f); // GetDensityAt >= 0 == air
|
||||||
float SolidRun = 0.0f; // contiguous solid voxels since the last open air
|
float SolidRun = 0.0f; // contiguous solid voxels since the last open air
|
||||||
|
|
||||||
for (float Z = Ctx.TopVoxelZ - Step; Z >= Ctx.BottomVoxelZ && Crossings < MaxCrossings; Z -= Step)
|
for (float Z = MarchTop - Step; Z >= MarchBottom && Crossings < MaxCrossings; Z -= Step)
|
||||||
{
|
{
|
||||||
const float Dz = D(VX, VY, Z);
|
const float Dz = D(VX, VY, Z);
|
||||||
if ((PrevD >= 0.0f) != (Dz >= 0.0f)) // straddles IsoLevel 0 (air ↔ solid)
|
if ((PrevD >= 0.0f) != (Dz >= 0.0f)) // straddles IsoLevel 0 (air ↔ solid)
|
||||||
@@ -1615,7 +1804,8 @@ void UVoxelContentManager::MarkCellDone(FDecoGrid& G, const FIntPoint& Region, c
|
|||||||
|
|
||||||
// Build the region's components: one HISM per mesh (all cells merged → one batched AddInstances), actors
|
// Build the region's components: one HISM per mesh (all cells merged → one batched AddInstances), actors
|
||||||
// spawned inline. Moves the region into G.Regions; the build is removed by the caller.
|
// spawned inline. Moves the region into G.Regions; the build is removed by the caller.
|
||||||
void UVoxelContentManager::ApplyRegion(FDecoGrid& G, const FIntPoint& Region, FDecoRegionBuild& Build)
|
void UVoxelContentManager::ApplyRegion(FDecoGrid& G, const FIntPoint& Region, FDecoRegionBuild& Build,
|
||||||
|
bool bApplyLandmarkFootprints)
|
||||||
{
|
{
|
||||||
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_DecoApply); // total game-thread cost to apply one region
|
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_DecoApply); // total game-thread cost to apply one region
|
||||||
|
|
||||||
@@ -1714,7 +1904,7 @@ void UVoxelContentManager::ApplyRegion(FDecoGrid& G, const FIntPoint& Region, FD
|
|||||||
// but this region just streamed in FRESH → re-clear under any loaded suppressing landmark overlapping it
|
// but this region just streamed in FRESH → re-clear under any loaded suppressing landmark overlapping it
|
||||||
// (the per-HISM spatial query early-outs for the non-overlapping majority). Keeps temple floors clear as
|
// (the per-HISM spatial query early-outs for the non-overlapping majority). Keeps temple floors clear as
|
||||||
// you leave and return.
|
// you leave and return.
|
||||||
if (Content.Instances.Num() > 0)
|
if (bApplyLandmarkFootprints && Content.Instances.Num() > 0)
|
||||||
{
|
{
|
||||||
for (const TPair<FIntVector, FLandmarkInstance>& LP : LandmarkInstances)
|
for (const TPair<FIntVector, FLandmarkInstance>& LP : LandmarkInstances)
|
||||||
{
|
{
|
||||||
@@ -1781,6 +1971,11 @@ bool UVoxelContentManager::FindLandmarkColumn(const UVoxelGenerator* Gen, const
|
|||||||
{
|
{
|
||||||
if (!Gen) return false;
|
if (!Gen) return false;
|
||||||
|
|
||||||
|
const auto FeatureQueries = VF_ContentFeatureQueries(
|
||||||
|
Ctx.bNativeSurface ? Ctx.NativeSurface.Get() : nullptr,
|
||||||
|
Ctx.bNativeFloatingIslands ? Ctx.NativeIslands.Get() : nullptr,Ctx.StrateIndex,
|
||||||
|
VX-1.0f,VY-1.0f,Ctx.BottomVoxelZ-1.0f,VX+1.0f,VY+1.0f,Ctx.TopVoxelZ+1.0f);
|
||||||
|
const VoxelForgeCore::ScopedWorldAssemblyFeatureQueriesV1 FeatureScope(FeatureQueries);
|
||||||
const auto Density = [&](float SampleX, float SampleY, float SampleZ)
|
const auto Density = [&](float SampleX, float SampleY, float SampleZ)
|
||||||
{
|
{
|
||||||
if (Ctx.bNativeFloatingIslands && Ctx.NativeIslands.IsValid())
|
if (Ctx.bNativeFloatingIslands && Ctx.NativeIslands.IsValid())
|
||||||
@@ -1852,10 +2047,15 @@ bool UVoxelContentManager::FindLandmarkColumn(const UVoxelGenerator* Gen, const
|
|||||||
// Bounded by ColDepth like the decoration march (this runs SYNCHRONOUSLY on the game thread):
|
// Bounded by ColDepth like the decoration march (this runs SYNCHRONOUSLY on the game thread):
|
||||||
// once past open air, a solid run longer than ColDepth means bedrock down to the strate floor —
|
// once past open air, a solid run longer than ColDepth means bedrock down to the strate floor —
|
||||||
// stop instead of paying GetDensityAt across the whole remaining band.
|
// stop instead of paying GetDensityAt across the whole remaining band.
|
||||||
float PrevD = Density(VX, VY, Ctx.TopVoxelZ);
|
float MarchTop = Ctx.TopVoxelZ, MarchBottom = Ctx.BottomVoxelZ;
|
||||||
|
if (Ctx.bNativeFloatingIslands && Ctx.NativeIslands.IsValid())
|
||||||
|
if (const auto* Entry = Ctx.NativeIslands->FindField(Ctx.StrateIndex))
|
||||||
|
if (!VF_ConstrainContentMarch(Entry->SolidSupportBounds,VX,VY,Step,MarchTop,MarchBottom))
|
||||||
|
return false;
|
||||||
|
float PrevD = Density(VX, VY, MarchTop);
|
||||||
bool bSeenAir = (PrevD >= 0.0f);
|
bool bSeenAir = (PrevD >= 0.0f);
|
||||||
float SolidRun = 0.0f;
|
float SolidRun = 0.0f;
|
||||||
for (float Z = Ctx.TopVoxelZ - Step; Z >= Ctx.BottomVoxelZ; Z -= Step)
|
for (float Z = MarchTop - Step; Z >= MarchBottom; Z -= Step)
|
||||||
{
|
{
|
||||||
const float Dz = Density(VX, VY, Z);
|
const float Dz = Density(VX, VY, Z);
|
||||||
if ((PrevD >= 0.0f) != (Dz >= 0.0f)) // air ↔ solid crossing
|
if ((PrevD >= 0.0f) != (Dz >= 0.0f)) // air ↔ solid crossing
|
||||||
|
|||||||
@@ -137,6 +137,19 @@ public:
|
|||||||
double MaximumDensityResidual = 0.0;
|
double MaximumDensityResidual = 0.0;
|
||||||
double DensityQueriesPerCell = 0.0;
|
double DensityQueriesPerCell = 0.0;
|
||||||
double SurfaceQueriesPerCell = 0.0;
|
double SurfaceQueriesPerCell = 0.0;
|
||||||
|
double FullScanCellP95Milliseconds = 0.0;
|
||||||
|
double ActiveBatchMilliseconds = 0.0;
|
||||||
|
double FullScanActiveBatchMilliseconds = 0.0;
|
||||||
|
double PopulatedBatchMilliseconds = 0.0;
|
||||||
|
double FullScanPopulatedBatchMilliseconds = 0.0;
|
||||||
|
double PopulatedMergeMilliseconds = 0.0;
|
||||||
|
double PopulatedApplyMilliseconds = 0.0;
|
||||||
|
int32 AppliedInstances = 0;
|
||||||
|
double PopulatedCellP95Milliseconds = 0.0;
|
||||||
|
double FullScanPopulatedCellP95Milliseconds = 0.0;
|
||||||
|
int32 PopulatedPlacements = 0;
|
||||||
|
int32 PopulatedCells = 0;
|
||||||
|
int32 ExactPlacementComparisons = 0;
|
||||||
double CellMedianMilliseconds = 0.0;
|
double CellMedianMilliseconds = 0.0;
|
||||||
double CellP95Milliseconds = 0.0;
|
double CellP95Milliseconds = 0.0;
|
||||||
double UpdateMedianMilliseconds = 0.0;
|
double UpdateMedianMilliseconds = 0.0;
|
||||||
@@ -338,7 +351,7 @@ private:
|
|||||||
const TArray<int32>& EntryBiome, uint32 InSeed,
|
const TArray<int32>& EntryBiome, uint32 InSeed,
|
||||||
int32 Spacing, float Step, int32 MaxCrossings, float ColumnDepth,
|
int32 Spacing, float Step, int32 MaxCrossings, float ColumnDepth,
|
||||||
TArray<FDecoSpawn>& OutSpawns,
|
TArray<FDecoSpawn>& OutSpawns,
|
||||||
FPlacementBuildCounters* OutCounters = nullptr);
|
FPlacementBuildCounters* OutCounters = nullptr, bool bUseFeatureQueries = true);
|
||||||
|
|
||||||
// Each step operates on ONE grid (G = NearGrid or FarGrid). LaunchDecoTasks throttles against the
|
// Each step operates on ONE grid (G = NearGrid or FarGrid). LaunchDecoTasks throttles against the
|
||||||
// COMBINED in-flight count (OtherInFlight = the other grid's in-flight cells) so the two grids share
|
// COMBINED in-flight count (OtherInFlight = the other grid's in-flight cells) so the two grids share
|
||||||
@@ -348,7 +361,8 @@ private:
|
|||||||
void ProcessDecoResults(const FIntPoint& PlayerCell);
|
void ProcessDecoResults(const FIntPoint& PlayerCell);
|
||||||
void MergeCellResult(FDecoGrid& G, const FDecoCellResult& Result); // fold one cell's spawns into its region build
|
void MergeCellResult(FDecoGrid& G, const FDecoCellResult& Result); // fold one cell's spawns into its region build
|
||||||
void MarkCellDone(FDecoGrid& G, const FIntPoint& Region, const FIntPoint& Cell, uint32 BuildId); // idempotent per-cell accounting
|
void MarkCellDone(FDecoGrid& G, const FIntPoint& Region, const FIntPoint& Cell, uint32 BuildId); // idempotent per-cell accounting
|
||||||
void ApplyRegion(FDecoGrid& G, const FIntPoint& Region, FDecoRegionBuild& Build);
|
void ApplyRegion(FDecoGrid& G, const FIntPoint& Region, FDecoRegionBuild& Build,
|
||||||
|
bool bApplyLandmarkFootprints = true);
|
||||||
void RebuildDesiredCells(FDecoGrid& G, const FIntPoint& PlayerCell);
|
void RebuildDesiredCells(FDecoGrid& G, const FIntPoint& PlayerCell);
|
||||||
void ClearDecorationRegion(FDecoGrid& G, const FIntPoint& Region);
|
void ClearDecorationRegion(FDecoGrid& G, const FIntPoint& Region);
|
||||||
void ClearAllDecorations();
|
void ClearAllDecorations();
|
||||||
|
|||||||
@@ -15147,6 +15147,53 @@ bool RunCoreOffActorTest(const FExploreArguments& Arguments, FString& OutError)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Paired engine timings include preparation and derived shells, with canonical output hashes.
|
||||||
|
// Mesures moteur appariees : preparation et coques derivees incluses, avec hashes canoniques.
|
||||||
|
bool RunCoreSharedFeatureCostAudit(VoxelForgeCore::World& World,
|
||||||
|
const std::vector<VoxelForgeCore::TileKey>& Keys,const TCHAR* Label,FString& OutError)
|
||||||
|
{
|
||||||
|
for (const int32 LOD : {0,1,7,8})
|
||||||
|
{
|
||||||
|
TArray<double> FullTimes,CandidateTimes,FullShellTimes,CandidateShellTimes,ListTimes;
|
||||||
|
for (const auto& Key : Keys)
|
||||||
|
{
|
||||||
|
if (Key.lod!=LOD || FullTimes.Num()>=8) continue;
|
||||||
|
VoxelForgeCore::TileGenerationOptions Options;
|
||||||
|
Options.hashDensity=true;
|
||||||
|
Options.useAssemblyFeatureQueries=false;
|
||||||
|
std::string Error;
|
||||||
|
// Warm both routes before measuring: column/page caches must not favor the second route.
|
||||||
|
// Chauffer les deux parcours : les caches ne doivent pas favoriser le second.
|
||||||
|
if (!World.generateTile(Key,Options,Error)) return false;
|
||||||
|
Options.useAssemblyFeatureQueries=true;
|
||||||
|
if (!World.generateTile(Key,Options,Error)) return false;
|
||||||
|
Options.useAssemblyFeatureQueries=false;
|
||||||
|
const double FullStart=FPlatformTime::Seconds();
|
||||||
|
auto Full=World.generateTile(Key,Options,Error);
|
||||||
|
FullTimes.Add((FPlatformTime::Seconds()-FullStart)*1000.0);
|
||||||
|
Options.useAssemblyFeatureQueries=true;
|
||||||
|
const double CandidateStart=FPlatformTime::Seconds();
|
||||||
|
auto Candidate=World.generateTile(Key,Options,Error);
|
||||||
|
CandidateTimes.Add((FPlatformTime::Seconds()-CandidateStart)*1000.0);
|
||||||
|
if (!Full||!Candidate||Full->densityHash!=Candidate->densityHash
|
||||||
|
||Full->meshHash!=Candidate->meshHash||Full->payloadHash!=Candidate->payloadHash
|
||||||
|
||Full->topologyHash!=Candidate->topologyHash)
|
||||||
|
{ OutError=TEXT("Shared feature full/candidate tile hashes differ."); return false; }
|
||||||
|
FullShellTimes.Add(Full->diagnostics.farShellMilliseconds);
|
||||||
|
CandidateShellTimes.Add(Candidate->diagnostics.farShellMilliseconds);
|
||||||
|
ListTimes.Add(Candidate->diagnostics.featureListMilliseconds);
|
||||||
|
}
|
||||||
|
const auto P95=[](TArray<double> Values)
|
||||||
|
{ Values.Sort(); return Values.IsEmpty() ? 0.0 : Values[FMath::Clamp(
|
||||||
|
FMath::CeilToInt(Values.Num()*0.95f)-1,0,Values.Num()-1)]; };
|
||||||
|
UE_LOG(LogTemp,Display,
|
||||||
|
TEXT("[VoxelForgeSharedFeatureCost] label=%s lod=%d tiles=%d hashes_identical=1 full_p95_ms=%.5f candidate_p95_ms=%.5f list_p95_ms=%.5f full_shell_p95_ms=%.5f candidate_shell_p95_ms=%.5f"),
|
||||||
|
Label,LOD,FullTimes.Num(),P95(FullTimes),P95(CandidateTimes),P95(ListTimes),
|
||||||
|
P95(FullShellTimes),P95(CandidateShellTimes));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool RunCoreContentPlacementCostAudit(AVoxelWorld* Actor, const FVector& CoreLocalProbeCm,
|
bool RunCoreContentPlacementCostAudit(AVoxelWorld* Actor, const FVector& CoreLocalProbeCm,
|
||||||
const TCHAR* Label, FString& OutError);
|
const TCHAR* Label, FString& OutError);
|
||||||
|
|
||||||
@@ -15382,6 +15429,7 @@ bool RunCoreFloatingIslandActorIntegrationTest(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ReferenceWorld.retainTileRegions(Desired);
|
ReferenceWorld.retainTileRegions(Desired);
|
||||||
|
if (!RunCoreSharedFeatureCostAudit(ReferenceWorld,Desired,TEXT("islands"),OutError)) return false;
|
||||||
VoxelForgeCore::TileGenerationOptions Options;
|
VoxelForgeCore::TileGenerationOptions Options;
|
||||||
Options.hashDensity = true;
|
Options.hashDensity = true;
|
||||||
std::vector<FStandaloneCoreAdapterTileHashes> AggregateRows;
|
std::vector<FStandaloneCoreAdapterTileHashes> AggregateRows;
|
||||||
@@ -16272,6 +16320,15 @@ bool RunCoreContentPlacementCostAudit(AVoxelWorld* Actor, const FVector& CoreLoc
|
|||||||
const float CoreVoxelSizeCm = Actor->CoreVoxelSizeCm;
|
const float CoreVoxelSizeCm = Actor->CoreVoxelSizeCm;
|
||||||
const FTransform OwnerXf = Actor->GetActorTransform();
|
const FTransform OwnerXf = Actor->GetActorTransform();
|
||||||
|
|
||||||
|
FString AssemblyJson, AssemblyHash;
|
||||||
|
if (Actor->GetVoxelForgeCoreWorldAssembly(AssemblyJson,AssemblyHash))
|
||||||
|
{
|
||||||
|
const FString ExportDir=FPaths::Combine(FPaths::ProjectSavedDir(),TEXT("CoreWorldValidation/Output"));
|
||||||
|
IFileManager::Get().MakeDirectory(*ExportDir,true);
|
||||||
|
FFileHelper::SaveStringToFile(AssemblyJson,
|
||||||
|
*FPaths::Combine(ExportDir,FString::Printf(TEXT("s2_assembly_%s.json"),Label)));
|
||||||
|
}
|
||||||
|
|
||||||
UVoxelContentManager::FDecorationPlacementCostAudit CoreAudit;
|
UVoxelContentManager::FDecorationPlacementCostAudit CoreAudit;
|
||||||
Actor->bUseVoxelForgeCore = true;
|
Actor->bUseVoxelForgeCore = true;
|
||||||
const FVector CorePlayerWorld = OwnerXf.TransformPosition(CoreLocalProbeCm);
|
const FVector CorePlayerWorld = OwnerXf.TransformPosition(CoreLocalProbeCm);
|
||||||
@@ -16319,6 +16376,20 @@ bool RunCoreContentPlacementCostAudit(AVoxelWorld* Actor, const FVector& CoreLoc
|
|||||||
CoreAudit.CellP95Milliseconds, CoreAudit.UpdateMedianMilliseconds,
|
CoreAudit.CellP95Milliseconds, CoreAudit.UpdateMedianMilliseconds,
|
||||||
CoreAudit.UpdateP95Milliseconds, CoreAudit.Placements, CoreAudit.AnchorChecks,
|
CoreAudit.UpdateP95Milliseconds, CoreAudit.Placements, CoreAudit.AnchorChecks,
|
||||||
CoreAudit.MaximumAnchorErrorCm, CoreAudit.MaximumDensityResidual);
|
CoreAudit.MaximumAnchorErrorCm, CoreAudit.MaximumDensityResidual);
|
||||||
|
UE_LOG(LogTemp, Display,
|
||||||
|
TEXT("[VoxelForgeContentCostS2] label=%s authored_full_p95_ms=%.5f authored_candidate_p95_ms=%.5f authored_active_batch_full_ms=%.5f authored_active_batch_candidate_ms=%.5f populated_cells=%d populated_placements=%d populated_full_p95_ms=%.5f populated_candidate_p95_ms=%.5f exact_transform_comparisons=%d anchor_checks=%d max_density_residual=%.6f"),
|
||||||
|
Label,CoreAudit.FullScanCellP95Milliseconds,CoreAudit.CellP95Milliseconds,
|
||||||
|
CoreAudit.FullScanActiveBatchMilliseconds,CoreAudit.ActiveBatchMilliseconds,
|
||||||
|
CoreAudit.PopulatedCells,CoreAudit.PopulatedPlacements,
|
||||||
|
CoreAudit.FullScanPopulatedCellP95Milliseconds,CoreAudit.PopulatedCellP95Milliseconds,
|
||||||
|
CoreAudit.ExactPlacementComparisons,CoreAudit.AnchorChecks,CoreAudit.MaximumDensityResidual);
|
||||||
|
UE_LOG(LogTemp,Display,
|
||||||
|
TEXT("[VoxelForgeContentActiveS2] label=%s cells=%d placements=%d full_batch_ms=%.5f candidate_batch_ms=%.5f merge_ms=%.5f apply_ms=%.5f applied_instances=%d full_total_cpu_ms=%.5f candidate_total_cpu_ms=%.5f rhi=null"),
|
||||||
|
Label,CoreAudit.SampledCells,CoreAudit.PopulatedPlacements,
|
||||||
|
CoreAudit.FullScanPopulatedBatchMilliseconds,CoreAudit.PopulatedBatchMilliseconds,
|
||||||
|
CoreAudit.PopulatedMergeMilliseconds,CoreAudit.PopulatedApplyMilliseconds,CoreAudit.AppliedInstances,
|
||||||
|
CoreAudit.FullScanPopulatedBatchMilliseconds+CoreAudit.PopulatedMergeMilliseconds+CoreAudit.PopulatedApplyMilliseconds,
|
||||||
|
CoreAudit.PopulatedBatchMilliseconds+CoreAudit.PopulatedMergeMilliseconds+CoreAudit.PopulatedApplyMilliseconds);
|
||||||
UE_LOG(LogTemp, Display,
|
UE_LOG(LogTemp, Display,
|
||||||
TEXT("[VoxelForgeContentCost] label=%s route=legacy voxel_cm=%.3f tile_cell_cm=%.3f authored_entries=%d synthetic=%d cells=%d columns_per_cell=%d density_queries_per_cell=%.2f surface_queries_per_cell=%.2f cell_median_ms=%.5f cell_p95_ms=%.5f update_median_ms=%.5f update_p95_ms=%.5f placements=%d anchor_checks=%d max_anchor_error_cm=%.6f max_density_residual=%.6f"),
|
TEXT("[VoxelForgeContentCost] label=%s route=legacy voxel_cm=%.3f tile_cell_cm=%.3f authored_entries=%d synthetic=%d cells=%d columns_per_cell=%d density_queries_per_cell=%.2f surface_queries_per_cell=%.2f cell_median_ms=%.5f cell_p95_ms=%.5f update_median_ms=%.5f update_p95_ms=%.5f placements=%d anchor_checks=%d max_anchor_error_cm=%.6f max_density_residual=%.6f"),
|
||||||
Label, VOXEL_SIZE, CHUNK_SIZE * VOXEL_SIZE,
|
Label, VOXEL_SIZE, CHUNK_SIZE * VOXEL_SIZE,
|
||||||
@@ -16767,6 +16838,12 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!RunCoreSharedFeatureCostAudit(ReferenceWorld,Desired,TEXT("surface"),OutError)) return false;
|
||||||
|
if (FParse::Param(FCommandLine::Get(),TEXT("corefeatureauditonly")))
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp,Display,TEXT("[VoxelForgeSharedFeatureCost] pass complete=1 warmed_routes=1"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
VoxelForgeCore::TileGenerationOptions Options;
|
VoxelForgeCore::TileGenerationOptions Options;
|
||||||
Options.hashDensity = true;
|
Options.hashDensity = true;
|
||||||
if (Arguments.CoreHybridMesherVersion == 2)
|
if (Arguments.CoreHybridMesherVersion == 2)
|
||||||
|
|||||||
@@ -56,6 +56,18 @@ namespace VoxelForgeCore
|
|||||||
const std::array<std::int64_t, 3>& tileMinimumSubvoxels,
|
const std::array<std::int64_t, 3>& tileMinimumSubvoxels,
|
||||||
const std::array<std::int64_t, 3>& tileMaximumSubvoxels);
|
const std::array<std::int64_t, 3>& tileMaximumSubvoxels);
|
||||||
|
|
||||||
|
struct WorldAssemblyFeatureColumnRangeV1
|
||||||
|
{
|
||||||
|
bool known = false;
|
||||||
|
bool empty = false;
|
||||||
|
std::int64_t minimumZSubvoxels = 0, maximumZSubvoxels = 0;
|
||||||
|
};
|
||||||
|
// Only families whose base is air may use this solid-support union to shorten a march.
|
||||||
|
// Reserve aux familles a fond d'air : union conservative du support solide de la colonne.
|
||||||
|
VFCORE_API WorldAssemblyFeatureColumnRangeV1 BuildWorldAssemblyFeatureColumnRangeV1(
|
||||||
|
std::span<const WorldAssemblyFeatureBoundsV1> solidSupportBounds,
|
||||||
|
std::int64_t xSubvoxels, std::int64_t ySubvoxels);
|
||||||
|
|
||||||
struct VFCORE_API TileKeyHash
|
struct VFCORE_API TileKeyHash
|
||||||
{
|
{
|
||||||
std::size_t operator()(const TileKey& key) const noexcept;
|
std::size_t operator()(const TileKey& key) const noexcept;
|
||||||
@@ -114,6 +126,8 @@ namespace VoxelForgeCore
|
|||||||
double classificationMilliseconds = 0.0;
|
double classificationMilliseconds = 0.0;
|
||||||
double columnComputeMilliseconds = 0.0;
|
double columnComputeMilliseconds = 0.0;
|
||||||
double meshingMilliseconds = 0.0;
|
double meshingMilliseconds = 0.0;
|
||||||
|
double featureListMilliseconds = 0.0;
|
||||||
|
double farShellMilliseconds = 0.0;
|
||||||
bool tileWasClassified = false;
|
bool tileWasClassified = false;
|
||||||
bool meshingSkipped = false;
|
bool meshingSkipped = false;
|
||||||
bool featureRefinedResolution = false;
|
bool featureRefinedResolution = false;
|
||||||
@@ -386,25 +400,60 @@ namespace VoxelForgeCore
|
|||||||
std::uint64_t contentHash = 0;
|
std::uint64_t contentHash = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FloatingIslandFeatureGroupBoundsV1
|
struct WorldAssemblyFeatureGroupBoundsV1
|
||||||
{
|
{
|
||||||
std::array<std::int64_t, 3> minimumSubvoxels{};
|
std::array<std::int64_t, 3> minimumSubvoxels{};
|
||||||
std::array<std::int64_t, 3> maximumSubvoxels{};
|
std::array<std::int64_t, 3> maximumSubvoxels{};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FloatingIslandTileFeatureListV1
|
struct WorldAssemblyFeatureTileListV1
|
||||||
{
|
{
|
||||||
// Flat stable-order IDs for island and connector features that touch this tile.
|
// Stable recipe-order indices for features that touch this query domain.
|
||||||
// Identifiants ordonnes des features qui touchent ce carreau.
|
// Indices dans l'ordre de recette des features touchant ce domaine.
|
||||||
std::vector<std::uint32_t> featureIndices;
|
std::vector<std::uint32_t> featureIndices;
|
||||||
// Cheap conservative group bounds preserve exact positive SDF distances while distant
|
// Optional ordered-union group certificates preserve exact positive density values.
|
||||||
// feature evaluators are skipped. Bornes legeres pour garder les distances SDF exactes.
|
// Certificats optionnels de groupes d'union pour conserver les valeurs positives exactes.
|
||||||
std::vector<FloatingIslandFeatureGroupBoundsV1> groupBounds;
|
std::vector<WorldAssemblyFeatureGroupBoundsV1> groupBounds;
|
||||||
// Groups are ordered by a tile-wide conservative distance lower bound. Samples stop at
|
// Groups are ordered by a tile-wide conservative distance lower bound. Samples stop at
|
||||||
// the first group that cannot beat their current density. Ordre par distance minimale.
|
// the first group that cannot beat their current density. Ordre par distance minimale.
|
||||||
std::vector<std::uint32_t> groupSearchOrder;
|
std::vector<std::uint32_t> groupSearchOrder;
|
||||||
std::vector<std::int64_t> groupDistanceLowerBoundsSubvoxels;
|
std::vector<std::int64_t> groupDistanceLowerBoundsSubvoxels;
|
||||||
bool groupSearchComplete = true;
|
bool groupSearchComplete = true;
|
||||||
|
// Performance hint only: the shared engine may prefer the exact full scan on small plans.
|
||||||
|
// Indication de cout seulement : le moteur peut preferer le parcours complet exact.
|
||||||
|
bool candidateEvaluationUseful = true;
|
||||||
|
// Exact-domain guard: outside queries always fall back to the canonical full scan.
|
||||||
|
// Hors du domaine prepare, revenir au parcours complet canonique.
|
||||||
|
std::array<std::int64_t, 3> minimumSubvoxels{};
|
||||||
|
std::array<std::int64_t, 3> maximumSubvoxels{};
|
||||||
|
};
|
||||||
|
|
||||||
|
using FloatingIslandTileFeatureListV1 = WorldAssemblyFeatureTileListV1;
|
||||||
|
using FloatingIslandFeatureGroupBoundsV1 = WorldAssemblyFeatureGroupBoundsV1;
|
||||||
|
|
||||||
|
struct WorldAssemblyFeatureQueryV1
|
||||||
|
{
|
||||||
|
const void* recipe = nullptr;
|
||||||
|
const void* plan = nullptr;
|
||||||
|
WorldAssemblyFeatureTileListV1 features;
|
||||||
|
};
|
||||||
|
|
||||||
|
// One worker-local, nestable scope for meshing, shells and placement in every family.
|
||||||
|
// Une portee locale au worker, emboitable, pour toutes les familles et tous les consommateurs.
|
||||||
|
class VFCORE_API ScopedWorldAssemblyFeatureQueriesV1
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit ScopedWorldAssemblyFeatureQueriesV1(std::span<const WorldAssemblyFeatureQueryV1> queries,
|
||||||
|
bool forceCandidatesForValidation = false) noexcept;
|
||||||
|
~ScopedWorldAssemblyFeatureQueriesV1();
|
||||||
|
ScopedWorldAssemblyFeatureQueriesV1(const ScopedWorldAssemblyFeatureQueriesV1&) = delete;
|
||||||
|
ScopedWorldAssemblyFeatureQueriesV1& operator=(const ScopedWorldAssemblyFeatureQueriesV1&) = delete;
|
||||||
|
static const WorldAssemblyFeatureTileListV1* find(const void* recipe, const void* plan,
|
||||||
|
std::int64_t x, std::int64_t y, std::int64_t z, bool columnOnly = false) noexcept;
|
||||||
|
private:
|
||||||
|
std::span<const WorldAssemblyFeatureQueryV1> queries_;
|
||||||
|
bool forceCandidates_ = false;
|
||||||
|
const ScopedWorldAssemblyFeatureQueriesV1* previous_ = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VFCORE_API FloatingIslandManifestV1
|
struct VFCORE_API FloatingIslandManifestV1
|
||||||
@@ -510,6 +559,7 @@ namespace VoxelForgeCore
|
|||||||
FloatingIslandTileFeatureListV1 buildFeatureTileList(
|
FloatingIslandTileFeatureListV1 buildFeatureTileList(
|
||||||
const std::array<std::int64_t, 3>& tileMinimumSubvoxels,
|
const std::array<std::int64_t, 3>& tileMinimumSubvoxels,
|
||||||
const std::array<std::int64_t, 3>& tileMaximumSubvoxels) const;
|
const std::array<std::int64_t, 3>& tileMaximumSubvoxels) const;
|
||||||
|
std::vector<WorldAssemblyFeatureBoundsV1> solidSupportBounds() const;
|
||||||
std::int64_t densitySubvoxelsForTile(std::int64_t xSubvoxels,
|
std::int64_t densitySubvoxelsForTile(std::int64_t xSubvoxels,
|
||||||
std::int64_t ySubvoxels, std::int64_t zSubvoxels,
|
std::int64_t ySubvoxels, std::int64_t zSubvoxels,
|
||||||
const FloatingIslandTileFeatureListV1& featureList) const noexcept;
|
const FloatingIslandTileFeatureListV1& featureList) const noexcept;
|
||||||
@@ -601,6 +651,9 @@ namespace VoxelForgeCore
|
|||||||
bool isValid() const noexcept;
|
bool isValid() const noexcept;
|
||||||
HeightfieldPointSample point(std::int64_t xSubvoxels, std::int64_t ySubvoxels,
|
HeightfieldPointSample point(std::int64_t xSubvoxels, std::int64_t ySubvoxels,
|
||||||
std::int64_t zSubvoxels) const noexcept;
|
std::int64_t zSubvoxels) const noexcept;
|
||||||
|
WorldAssemblyFeatureTileListV1 buildFeatureTileList(
|
||||||
|
const std::array<std::int64_t, 3>& tileMinimumSubvoxels,
|
||||||
|
const std::array<std::int64_t, 3>& tileMaximumSubvoxels) const;
|
||||||
HeightfieldBoxClass classifyLatticeBox(std::int64_t minimumXSubvoxels,
|
HeightfieldBoxClass classifyLatticeBox(std::int64_t minimumXSubvoxels,
|
||||||
std::int64_t minimumYSubvoxels, std::int64_t minimumZSubvoxels,
|
std::int64_t minimumYSubvoxels, std::int64_t minimumZSubvoxels,
|
||||||
std::int64_t maximumXSubvoxels, std::int64_t maximumYSubvoxels,
|
std::int64_t maximumXSubvoxels, std::int64_t maximumYSubvoxels,
|
||||||
@@ -928,6 +981,9 @@ namespace VoxelForgeCore
|
|||||||
bool skipUv = false;
|
bool skipUv = false;
|
||||||
bool skipColors = false;
|
bool skipColors = false;
|
||||||
bool skipMaterialGroups = false;
|
bool skipMaterialGroups = false;
|
||||||
|
// Validation control only; never participates in recipe/world identity.
|
||||||
|
// Controle de validation seulement, sans effet sur l'identite du monde.
|
||||||
|
bool useAssemblyFeatureQueries = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
class VFCORE_API World
|
class VFCORE_API World
|
||||||
|
|||||||
@@ -1645,18 +1645,18 @@ bool FloatingIslandFieldV1::isValid() const noexcept
|
|||||||
std::int64_t FloatingIslandFieldV1::densitySubvoxels(std::int64_t x,
|
std::int64_t FloatingIslandFieldV1::densitySubvoxels(std::int64_t x,
|
||||||
std::int64_t y, std::int64_t z) const noexcept
|
std::int64_t y, std::int64_t z) const noexcept
|
||||||
{
|
{
|
||||||
|
// The selected evaluator validates once; do not scan the plan twice per scoped sample.
|
||||||
|
// L'evaluateur choisi valide une seule fois; ne pas reparcourir le plan a chaque echantillon.
|
||||||
|
if (const auto* list = ScopedWorldAssemblyFeatureQueriesV1::find(recipe_, plan_, x, y, z))
|
||||||
|
return densitySubvoxelsForTile(x, y, z, *list);
|
||||||
if (!isValid() || !floating_island_detail::ValidScalar(x)
|
if (!isValid() || !floating_island_detail::ValidScalar(x)
|
||||||
|| !floating_island_detail::ValidScalar(y) || !floating_island_detail::ValidScalar(z))
|
|| !floating_island_detail::ValidScalar(y) || !floating_island_detail::ValidScalar(z))
|
||||||
return floating_island_detail::MaxDensity;
|
return floating_island_detail::MaxDensity;
|
||||||
return floating_island_detail::FieldDensity(*recipe_, *plan_, x, y, z);
|
return floating_island_detail::FieldDensity(*recipe_, *plan_, x, y, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
FloatingIslandTileFeatureListV1 FloatingIslandFieldV1::buildFeatureTileList(
|
std::vector<WorldAssemblyFeatureBoundsV1> FloatingIslandFieldV1::solidSupportBounds() const
|
||||||
const std::array<std::int64_t, 3>& tileMinimumSubvoxels,
|
|
||||||
const std::array<std::int64_t, 3>& tileMaximumSubvoxels) const
|
|
||||||
{
|
{
|
||||||
FloatingIslandTileFeatureListV1 result;
|
|
||||||
if (!isValid()) return result;
|
|
||||||
std::vector<WorldAssemblyFeatureBoundsV1> features;
|
std::vector<WorldAssemblyFeatureBoundsV1> features;
|
||||||
features.reserve(plan_->islands.size() + plan_->clusterEdges.size());
|
features.reserve(plan_->islands.size() + plan_->clusterEdges.size());
|
||||||
for (const auto& island : plan_->islands)
|
for (const auto& island : plan_->islands)
|
||||||
@@ -1666,6 +1666,22 @@ FloatingIslandTileFeatureListV1 FloatingIslandFieldV1::buildFeatureTileList(
|
|||||||
features.push_back({edge.stableId, edge.influenceMinimumSubvoxels,
|
features.push_back({edge.stableId, edge.influenceMinimumSubvoxels,
|
||||||
edge.influenceMaximumSubvoxels});
|
edge.influenceMaximumSubvoxels});
|
||||||
|
|
||||||
|
return features;
|
||||||
|
}
|
||||||
|
|
||||||
|
FloatingIslandTileFeatureListV1 FloatingIslandFieldV1::buildFeatureTileList(
|
||||||
|
const std::array<std::int64_t, 3>& tileMinimumSubvoxels,
|
||||||
|
const std::array<std::int64_t, 3>& tileMaximumSubvoxels) const
|
||||||
|
{
|
||||||
|
FloatingIslandTileFeatureListV1 result;
|
||||||
|
// The measured four-island plan is cheaper to scan; larger plans amortize group certificates.
|
||||||
|
// Le plan mesure de quatre iles coute moins en parcours complet; les grands plans amortissent les bornes.
|
||||||
|
result.candidateEvaluationUseful=plan_->islands.size()>4;
|
||||||
|
result.minimumSubvoxels = tileMinimumSubvoxels;
|
||||||
|
result.maximumSubvoxels = tileMaximumSubvoxels;
|
||||||
|
if (!isValid()) return result;
|
||||||
|
const auto features = solidSupportBounds();
|
||||||
|
|
||||||
result.featureIndices = BuildWorldAssemblyFeatureTileListV1(features,
|
result.featureIndices = BuildWorldAssemblyFeatureTileListV1(features,
|
||||||
tileMinimumSubvoxels, tileMaximumSubvoxels);
|
tileMinimumSubvoxels, tileMaximumSubvoxels);
|
||||||
const std::size_t islandCount = plan_->islands.size();
|
const std::size_t islandCount = plan_->islands.size();
|
||||||
|
|||||||
@@ -32,43 +32,6 @@
|
|||||||
class FStandaloneShapeWorld
|
class FStandaloneShapeWorld
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
struct FNativeIslandTileFeatureContext
|
|
||||||
{
|
|
||||||
const VoxelForgeCore::FloatingIslandPlanV1* Plan = nullptr;
|
|
||||||
const VoxelForgeCore::FloatingIslandTileFeatureListV1* FeatureList = nullptr;
|
|
||||||
};
|
|
||||||
class FScopedNativeIslandTileFeatureContext
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
FScopedNativeIslandTileFeatureContext(const VoxelForgeCore::FloatingIslandPlanV1* plan,
|
|
||||||
const VoxelForgeCore::FloatingIslandTileFeatureListV1* featureList)
|
|
||||||
{
|
|
||||||
Context.Plan = plan;
|
|
||||||
Context.FeatureList = featureList;
|
|
||||||
Previous = SetNativeIslandTileFeatureContext(plan && featureList ? &Context : nullptr);
|
|
||||||
}
|
|
||||||
~FScopedNativeIslandTileFeatureContext()
|
|
||||||
{ RestoreNativeIslandTileFeatureContext(Previous); }
|
|
||||||
FScopedNativeIslandTileFeatureContext(const FScopedNativeIslandTileFeatureContext&) = delete;
|
|
||||||
FScopedNativeIslandTileFeatureContext& operator=(const FScopedNativeIslandTileFeatureContext&) = delete;
|
|
||||||
|
|
||||||
private:
|
|
||||||
FNativeIslandTileFeatureContext Context{};
|
|
||||||
const FNativeIslandTileFeatureContext* Previous = nullptr;
|
|
||||||
};
|
|
||||||
static const FNativeIslandTileFeatureContext* SetNativeIslandTileFeatureContext(
|
|
||||||
const FNativeIslandTileFeatureContext* context)
|
|
||||||
{
|
|
||||||
auto& active = ActiveNativeIslandTileFeatureContext();
|
|
||||||
const auto* previous = active;
|
|
||||||
active = context;
|
|
||||||
return previous;
|
|
||||||
}
|
|
||||||
static void RestoreNativeIslandTileFeatureContext(const FNativeIslandTileFeatureContext* context)
|
|
||||||
{ ActiveNativeIslandTileFeatureContext() = context; }
|
|
||||||
static const FNativeIslandTileFeatureContext* GetNativeIslandTileFeatureContext()
|
|
||||||
{ return ActiveNativeIslandTileFeatureContext(); }
|
|
||||||
|
|
||||||
double GetCoreVoxelSizeCm() const
|
double GetCoreVoxelSizeCm() const
|
||||||
{
|
{
|
||||||
return Owner == nullptr || Owner->CoreVoxelSizeMicrons == 0
|
return Owner == nullptr || Owner->CoreVoxelSizeMicrons == 0
|
||||||
@@ -1140,12 +1103,6 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const FNativeIslandTileFeatureContext*& ActiveNativeIslandTileFeatureContext()
|
|
||||||
{
|
|
||||||
static thread_local const FNativeIslandTileFeatureContext* Context = nullptr;
|
|
||||||
return Context;
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint64 PackLatticeKey(int32 X,int32 Y,int32 Z)
|
static uint64 PackLatticeKey(int32 X,int32 Y,int32 Z)
|
||||||
{
|
{
|
||||||
constexpr uint64 Mask=0x1FFFFFull;
|
constexpr uint64 Mask=0x1FFFFFull;
|
||||||
@@ -1961,13 +1918,7 @@ private:
|
|||||||
const VoxelForgeCore::FloatingIslandFieldV1 Field(
|
const VoxelForgeCore::FloatingIslandFieldV1 Field(
|
||||||
Plan->manifest.recipe,Plan->manifest.plan);
|
Plan->manifest.recipe,Plan->manifest.plan);
|
||||||
if(!Field.isValid())return false;
|
if(!Field.isValid())return false;
|
||||||
std::int64_t Density=0;
|
std::int64_t Density=Field.densitySubvoxels(ToFixed(X),ToFixed(Y),ToFixed(Z));
|
||||||
const auto* TileContext=GetNativeIslandTileFeatureContext();
|
|
||||||
if(TileContext&&TileContext->Plan==&Plan->manifest.plan&&TileContext->FeatureList)
|
|
||||||
Density=Field.densitySubvoxelsForTile(ToFixed(X),ToFixed(Y),ToFixed(Z),
|
|
||||||
*TileContext->FeatureList);
|
|
||||||
else
|
|
||||||
Density=Field.densitySubvoxels(ToFixed(X),ToFixed(Y),ToFixed(Z));
|
|
||||||
const std::int64_t Bottom=Plan->manifest.recipe.layoutMinimumZSubvoxels;
|
const std::int64_t Bottom=Plan->manifest.recipe.layoutMinimumZSubvoxels;
|
||||||
const std::int64_t Top=Plan->manifest.recipe.layoutMaximumZSubvoxels;
|
const std::int64_t Top=Plan->manifest.recipe.layoutMaximumZSubvoxels;
|
||||||
const std::int64_t Seal=Plan->manifest.recipe.boundarySealSubvoxels;
|
const std::int64_t Seal=Plan->manifest.recipe.boundarySealSubvoxels;
|
||||||
|
|||||||
@@ -1154,68 +1154,51 @@ namespace VoxelForgeCore
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const WorldAssemblyFloatingIslandPlanV1* IslandTilePlan=nullptr;
|
// Shared query domain includes the normal halo; all overlapping strate families participate.
|
||||||
FloatingIslandTileFeatureListV1 IslandTileFeatureList;
|
// Domaine partage avec marge de normales; inclure toutes les familles des strates traversees.
|
||||||
if(bHasWorldAssembly)
|
const std::int64_t queryScale=WorldAssemblySubvoxelsPerVoxel;
|
||||||
|
const std::array<std::int64_t,3> queryMinimum={
|
||||||
|
(static_cast<std::int64_t>(origin.X)-step)*queryScale,
|
||||||
|
(static_cast<std::int64_t>(origin.Y)-step)*queryScale,
|
||||||
|
(static_cast<std::int64_t>(origin.Z)-step)*queryScale};
|
||||||
|
const std::array<std::int64_t,3> queryMaximum={
|
||||||
|
(static_cast<std::int64_t>(origin.X)+extent+step)*queryScale,
|
||||||
|
(static_cast<std::int64_t>(origin.Y)+extent+step)*queryScale,
|
||||||
|
(static_cast<std::int64_t>(origin.Z)+extent+step)*queryScale};
|
||||||
|
const auto FeatureListStart=std::chrono::steady_clock::now();
|
||||||
|
std::vector<WorldAssemblyFeatureQueryV1> featureQueries;
|
||||||
|
if(bHasWorldAssembly&&options.useAssemblyFeatureQueries)
|
||||||
{
|
{
|
||||||
|
for(const auto& plan:impl_->worldAssembly->plan.strates)
|
||||||
|
{
|
||||||
|
const Heightfield2D field(impl_->worldAssembly->recipe,plan);
|
||||||
|
featureQueries.push_back({field.recipe(),field.plan(),
|
||||||
|
field.buildFeatureTileList(queryMinimum,queryMaximum)});
|
||||||
|
}
|
||||||
for(const auto& entry:impl_->worldAssembly->floatingIslands)
|
for(const auto& entry:impl_->worldAssembly->floatingIslands)
|
||||||
{
|
{
|
||||||
const auto& scale=entry.manifest.recipe;
|
const FloatingIslandFieldV1 field(entry.manifest.recipe,entry.manifest.plan);
|
||||||
const std::int64_t ownerToCoreNumerator=scale.ownerToCoreScaleNumerator;
|
featureQueries.push_back({field.recipe(),field.plan(),
|
||||||
const std::int64_t ownerToCoreDenominator=scale.ownerToCoreScaleDenominator;
|
field.buildFeatureTileList(queryMinimum,queryMaximum)});
|
||||||
if(ownerToCoreNumerator<=0||ownerToCoreDenominator<=0)continue;
|
|
||||||
const std::int64_t coreVoxelsPerOwnerChunk=static_cast<std::int64_t>(CHUNK_SIZE)
|
|
||||||
*ownerToCoreNumerator/ownerToCoreDenominator;
|
|
||||||
if(coreVoxelsPerOwnerChunk<=0)continue;
|
|
||||||
const std::int64_t stratumBottom=static_cast<std::int64_t>(entry.bottomChunkZ)
|
|
||||||
*coreVoxelsPerOwnerChunk;
|
|
||||||
const std::int64_t stratumTop=(static_cast<std::int64_t>(entry.topChunkZ)+1)
|
|
||||||
*coreVoxelsPerOwnerChunk;
|
|
||||||
if(static_cast<std::int64_t>(origin.Z)>stratumTop
|
|
||||||
||static_cast<std::int64_t>(origin.Z)+extent<stratumBottom)continue;
|
|
||||||
IslandTilePlan=&entry;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if(IslandTilePlan)
|
|
||||||
{
|
|
||||||
const FloatingIslandFieldV1 field(IslandTilePlan->manifest.recipe,
|
|
||||||
IslandTilePlan->manifest.plan);
|
|
||||||
const std::int64_t scale=WorldAssemblySubvoxelsPerVoxel;
|
|
||||||
// Include the mesher's one-sample gradient halo on each face.
|
|
||||||
// Inclure la marge d'un echantillon du maillage sur chaque face.
|
|
||||||
const std::int64_t sampleHalo=static_cast<std::int64_t>(step)*scale;
|
|
||||||
const std::array<std::int64_t,3> tileMinimum={
|
|
||||||
static_cast<std::int64_t>(origin.X)*scale-sampleHalo,
|
|
||||||
static_cast<std::int64_t>(origin.Y)*scale-sampleHalo,
|
|
||||||
static_cast<std::int64_t>(origin.Z)*scale-sampleHalo};
|
|
||||||
const std::array<std::int64_t,3> tileMaximum={
|
|
||||||
(static_cast<std::int64_t>(origin.X)+extent)*scale+sampleHalo,
|
|
||||||
(static_cast<std::int64_t>(origin.Y)+extent)*scale+sampleHalo,
|
|
||||||
(static_cast<std::int64_t>(origin.Z)+extent)*scale+sampleHalo};
|
|
||||||
IslandTileFeatureList=field.buildFeatureTileList(tileMinimum,tileMaximum);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FVoxelMeshData mesh;
|
out.diagnostics.featureListMilliseconds=std::chrono::duration<double,std::milli>(
|
||||||
if(IslandTilePlan)
|
std::chrono::steady_clock::now()-FeatureListStart).count();
|
||||||
{
|
const ScopedWorldAssemblyFeatureQueriesV1 featureScope(featureQueries);
|
||||||
FStandaloneShapeWorld::FScopedNativeIslandTileFeatureContext tileFeatureContext(
|
FVoxelMeshData mesh=mesher.GenerateMesh(origin,step,cells,nullptr,
|
||||||
&IslandTilePlan->manifest.plan,&IslandTileFeatureList);
|
|
||||||
mesh=mesher.GenerateMesh(origin,step,cells,nullptr,
|
|
||||||
MeshBandMinZVoxel,MeshBandMaxZVoxel,&sampleCount,nullptr,nullptr,
|
|
||||||
(options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
mesh=mesher.GenerateMesh(origin,step,cells,nullptr,
|
|
||||||
MeshBandMinZVoxel,MeshBandMaxZVoxel,&sampleCount,nullptr,nullptr,
|
MeshBandMinZVoxel,MeshBandMaxZVoxel,&sampleCount,nullptr,nullptr,
|
||||||
(options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr);
|
(options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr);
|
||||||
const double MeshingMilliseconds=std::chrono::duration<double,std::milli>(
|
const double MeshingMilliseconds=std::chrono::duration<double,std::milli>(
|
||||||
std::chrono::steady_clock::now()-MeshingStart).count();
|
std::chrono::steady_clock::now()-MeshingStart).count();
|
||||||
|
const auto FarShellStart=std::chrono::steady_clock::now();
|
||||||
const FStandaloneShapeWorld& farShape=tileShape.HasFarWorld()?tileShape:*impl_->shape;
|
const FStandaloneShapeWorld& farShape=tileShape.HasFarWorld()?tileShape:*impl_->shape;
|
||||||
if(farShape.HasFarWorld())
|
if(farShape.HasFarWorld())
|
||||||
{
|
{
|
||||||
AppendFarStratumShells(world,farShape,nativeKey,step,mesh);
|
AppendFarStratumShells(world,farShape,nativeKey,step,mesh);
|
||||||
AppendFarConnectorShells(farShape,nativeKey,step,world.CoarseTileCells,mesh);
|
AppendFarConnectorShells(farShape,nativeKey,step,world.CoarseTileCells,mesh);
|
||||||
}
|
}
|
||||||
|
out.diagnostics.farShellMilliseconds=std::chrono::duration<double,std::milli>(
|
||||||
|
std::chrono::steady_clock::now()-FarShellStart).count();
|
||||||
if(impl_->worldAssembly&&key.lod>=8)
|
if(impl_->worldAssembly&&key.lod>=8)
|
||||||
{
|
{
|
||||||
const auto CapStats=RefineLod8NativeCap(world,*impl_->worldAssembly,nativeKey,mesh);
|
const auto CapStats=RefineLod8NativeCap(world,*impl_->worldAssembly,nativeKey,mesh);
|
||||||
|
|||||||
@@ -15,6 +15,45 @@
|
|||||||
|
|
||||||
namespace VoxelForgeCore
|
namespace VoxelForgeCore
|
||||||
{
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
thread_local const ScopedWorldAssemblyFeatureQueriesV1* ActiveAssemblyQueries = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopedWorldAssemblyFeatureQueriesV1::ScopedWorldAssemblyFeatureQueriesV1(
|
||||||
|
std::span<const WorldAssemblyFeatureQueryV1> queries, bool forceCandidatesForValidation) noexcept
|
||||||
|
: queries_(queries), forceCandidates_(forceCandidatesForValidation), previous_(ActiveAssemblyQueries)
|
||||||
|
{
|
||||||
|
// Resolve cost hints once per domain, never rediscover them in the sample loop.
|
||||||
|
// Resoudre les indications de cout une fois par domaine, hors de la boucle d'echantillons.
|
||||||
|
ActiveAssemblyQueries = nullptr;
|
||||||
|
for (const auto& query : queries_)
|
||||||
|
if (forceCandidates_ || query.features.candidateEvaluationUseful)
|
||||||
|
{ ActiveAssemblyQueries = this; break; }
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopedWorldAssemblyFeatureQueriesV1::~ScopedWorldAssemblyFeatureQueriesV1()
|
||||||
|
{ ActiveAssemblyQueries = previous_; }
|
||||||
|
|
||||||
|
const WorldAssemblyFeatureTileListV1* ScopedWorldAssemblyFeatureQueriesV1::find(
|
||||||
|
const void* recipe, const void* plan, std::int64_t x, std::int64_t y,
|
||||||
|
std::int64_t z, bool columnOnly) noexcept
|
||||||
|
{
|
||||||
|
const std::array<std::int64_t, 3> point{x,y,z};
|
||||||
|
if (ActiveAssemblyQueries)
|
||||||
|
for (const auto& query : ActiveAssemblyQueries->queries_)
|
||||||
|
{
|
||||||
|
if (query.recipe != recipe || query.plan != plan
|
||||||
|
|| (!ActiveAssemblyQueries->forceCandidates_ && !query.features.candidateEvaluationUseful)) continue;
|
||||||
|
bool inside = true;
|
||||||
|
for (std::size_t axis=0; axis<(columnOnly ? 2u : 3u); ++axis)
|
||||||
|
inside = inside && point[axis]>=query.features.minimumSubvoxels[axis]
|
||||||
|
&& point[axis]<=query.features.maximumSubvoxels[axis];
|
||||||
|
if (inside) return &query.features;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<std::uint32_t> BuildWorldAssemblyFeatureTileListV1(
|
std::vector<std::uint32_t> BuildWorldAssemblyFeatureTileListV1(
|
||||||
std::span<const WorldAssemblyFeatureBoundsV1> features,
|
std::span<const WorldAssemblyFeatureBoundsV1> features,
|
||||||
const std::array<std::int64_t, 3>& tileMinimumSubvoxels,
|
const std::array<std::int64_t, 3>& tileMinimumSubvoxels,
|
||||||
@@ -52,8 +91,54 @@ std::vector<std::uint32_t> BuildWorldAssemblyFeatureTileListV1(
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WorldAssemblyFeatureColumnRangeV1 BuildWorldAssemblyFeatureColumnRangeV1(
|
||||||
|
std::span<const WorldAssemblyFeatureBoundsV1> bounds, std::int64_t x, std::int64_t y)
|
||||||
|
{
|
||||||
|
WorldAssemblyFeatureColumnRangeV1 result;
|
||||||
|
result.known=true;
|
||||||
|
result.empty=true;
|
||||||
|
for(const auto& bound:bounds)
|
||||||
|
{
|
||||||
|
for(std::size_t axis=0;axis<3;++axis)
|
||||||
|
if(bound.minimumSubvoxels[axis]>bound.maximumSubvoxels[axis]) return {};
|
||||||
|
if(x<bound.minimumSubvoxels[0]||x>bound.maximumSubvoxels[0]
|
||||||
|
||y<bound.minimumSubvoxels[1]||y>bound.maximumSubvoxels[1])continue;
|
||||||
|
if(result.empty)
|
||||||
|
{
|
||||||
|
result.minimumZSubvoxels=bound.minimumSubvoxels[2];
|
||||||
|
result.maximumZSubvoxels=bound.maximumSubvoxels[2];
|
||||||
|
result.empty=false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.minimumZSubvoxels=std::min(result.minimumZSubvoxels,bound.minimumSubvoxels[2]);
|
||||||
|
result.maximumZSubvoxels=std::max(result.maximumZSubvoxels,bound.maximumSubvoxels[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
namespace world_assembly_detail
|
namespace world_assembly_detail
|
||||||
{
|
{
|
||||||
|
// Preserve recipe order without allocating or looking up metadata in the point loop.
|
||||||
|
// Garder l'ordre de recette sans allocation ni recherche de metadonnees par point.
|
||||||
|
struct FeatureRange
|
||||||
|
{
|
||||||
|
const std::vector<WorldAssemblyFeatureV1>& features;
|
||||||
|
const WorldAssemblyFeatureTileListV1* list;
|
||||||
|
struct Iterator
|
||||||
|
{
|
||||||
|
const FeatureRange* range;
|
||||||
|
std::size_t index;
|
||||||
|
const WorldAssemblyFeatureV1& operator*() const
|
||||||
|
{ return range->features[range->list ? range->list->featureIndices[index] : index]; }
|
||||||
|
Iterator& operator++() { ++index; return *this; }
|
||||||
|
bool operator!=(const Iterator& other) const { return index!=other.index; }
|
||||||
|
};
|
||||||
|
Iterator begin() const { return {this,0}; }
|
||||||
|
Iterator end() const { return {this,list ? list->featureIndices.size() : features.size()}; }
|
||||||
|
};
|
||||||
|
|
||||||
constexpr std::int64_t Q16 = 1ll << 16;
|
constexpr std::int64_t Q16 = 1ll << 16;
|
||||||
constexpr std::int64_t MinimumCliffShelfThicknessSubvoxels = 2 * WorldAssemblySubvoxelsPerVoxel;
|
constexpr std::int64_t MinimumCliffShelfThicknessSubvoxels = 2 * WorldAssemblySubvoxelsPerVoxel;
|
||||||
constexpr std::int64_t MaxSerializedMagnitude = 1000000000000ll;
|
constexpr std::int64_t MaxSerializedMagnitude = 1000000000000ll;
|
||||||
@@ -1294,7 +1379,8 @@ namespace world_assembly_detail
|
|||||||
:CapHeightV2(recipe,plan,neighbor,x,y);
|
:CapHeightV2(recipe,plan,neighbor,x,y);
|
||||||
std::int64_t cap=primaryCap+((neighborCap-primaryCap)
|
std::int64_t cap=primaryCap+((neighborCap-primaryCap)
|
||||||
*(static_cast<std::int64_t>(Q16)-selection.primaryWeightQ16))/Q16;
|
*(static_cast<std::int64_t>(Q16)-selection.primaryWeightQ16))/Q16;
|
||||||
for(const auto& feature:plan.features)
|
const auto* list=ScopedWorldAssemblyFeatureQueriesV1::find(&recipe,&plan,x,y,0,true);
|
||||||
|
for(const auto& feature:FeatureRange{plan.features,list})
|
||||||
{
|
{
|
||||||
if(feature.type==1)
|
if(feature.type==1)
|
||||||
{
|
{
|
||||||
@@ -1730,6 +1816,42 @@ bool Heightfield2D::isValid() const noexcept
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WorldAssemblyFeatureTileListV1 Heightfield2D::buildFeatureTileList(
|
||||||
|
const std::array<std::int64_t,3>& minimum,
|
||||||
|
const std::array<std::int64_t,3>& maximum) const
|
||||||
|
{
|
||||||
|
WorldAssemblyFeatureTileListV1 result;
|
||||||
|
// One shelf in the staged owner plan has no useful scan to shorten. Keep its full evaluator.
|
||||||
|
// Une seule corniche dans le plan auteur ne justifie pas la selection par echantillon.
|
||||||
|
result.candidateEvaluationUseful=plan_->features.size()>1;
|
||||||
|
result.minimumSubvoxels=minimum;
|
||||||
|
result.maximumSubvoxels=maximum;
|
||||||
|
std::vector<WorldAssemblyFeatureBoundsV1> bounds;
|
||||||
|
bounds.reserve(plan_->features.size());
|
||||||
|
for(const auto& feature:plan_->features)
|
||||||
|
{
|
||||||
|
WorldAssemblyFeatureBoundsV1 bound{feature.stableId,
|
||||||
|
feature.minimumSubvoxels,feature.maximumSubvoxels};
|
||||||
|
// Column writers affect every Z; finite volumes have an exact no-op outside their box.
|
||||||
|
// Les modificateurs de colonne agissent sur tout Z; les volumes sont neutres hors boite.
|
||||||
|
if(feature.type==1||feature.type==3)
|
||||||
|
{
|
||||||
|
bound.minimumSubvoxels[2]=std::numeric_limits<std::int64_t>::min();
|
||||||
|
bound.maximumSubvoxels[2]=std::numeric_limits<std::int64_t>::max();
|
||||||
|
}
|
||||||
|
else if(feature.type!=2&&feature.type!=4)
|
||||||
|
{
|
||||||
|
// Unknown future operations must opt into a proof before they can be culled.
|
||||||
|
// Une operation inconnue reste active tant que sa preuve manque.
|
||||||
|
bound.minimumSubvoxels.fill(std::numeric_limits<std::int64_t>::min());
|
||||||
|
bound.maximumSubvoxels.fill(std::numeric_limits<std::int64_t>::max());
|
||||||
|
}
|
||||||
|
bounds.push_back(bound);
|
||||||
|
}
|
||||||
|
result.featureIndices=BuildWorldAssemblyFeatureTileListV1(bounds,minimum,maximum);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
HeightfieldPointSample Heightfield2D::point(std::int64_t x, std::int64_t y,
|
HeightfieldPointSample Heightfield2D::point(std::int64_t x, std::int64_t y,
|
||||||
std::int64_t z) const noexcept
|
std::int64_t z) const noexcept
|
||||||
{
|
{
|
||||||
@@ -1744,7 +1866,8 @@ HeightfieldPointSample Heightfield2D::point(std::int64_t x, std::int64_t y,
|
|||||||
out.capHeightSubvoxels-z);
|
out.capHeightSubvoxels-z);
|
||||||
if(recipe_->evaluatorVersion>=2)
|
if(recipe_->evaluatorVersion>=2)
|
||||||
{
|
{
|
||||||
for(const auto& feature:plan_->features)
|
const auto* list=ScopedWorldAssemblyFeatureQueriesV1::find(recipe_,plan_,x,y,z);
|
||||||
|
for(const auto& feature:FeatureRange{plan_->features,list})
|
||||||
{
|
{
|
||||||
if(feature.type==2)
|
if(feature.type==2)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1138,3 +1138,59 @@ link-safety passed. No 0xC0000142 retry or new UECC folder occurred. I did not o
|
|||||||
editor or build his project, edit any .uasset, create a link, commit, push or stash.
|
editor or build his project, edit any .uasset, create a link, commit, push or stash.
|
||||||
|
|
||||||
— Luna
|
— Luna
|
||||||
|
|
||||||
|
|
||||||
|
## World assembler S2 / M3 Round 6 — 2026-10-18
|
||||||
|
|
||||||
|
The shared engine now owns a family-neutral, worker-local feature-query scope. I removed the
|
||||||
|
island-only context from ShapeWorld; SurfaceWorld finite features, island groups, meshing, far-shell
|
||||||
|
field calls, decoration cells and landmark columns use the same domain/identity/halo contract.
|
||||||
|
S1's root-aware positive-density bounds and ordered connector composition are preserved.
|
||||||
|
|
||||||
|
The measurements matter here: the staged SurfaceWorld has one finite shelf, and the staged island
|
||||||
|
plan has four islands. Forced candidate bookkeeping gave no useful small-plan gain and regressed
|
||||||
|
the island tile timings by 7–13%. I added a shared cost hint, resolved once per domain, so these
|
||||||
|
small plans keep their full evaluator. Richer plans still use candidates. With the actual staged
|
||||||
|
owner template expanded to 256 islands, forced-list p95 was 110.570 -> 7.3619 ms (about 15x), with
|
||||||
|
identical hashes and a detected dropped-feature control. The composed surface stress (64 shelves,
|
||||||
|
one XY height writer and one cap writer) was 0.7084 -> 0.4078 ms, again exact with its control caught.
|
||||||
|
The S1 57x stress number used a different Round 3 template, so I do not compare those as a regression.
|
||||||
|
|
||||||
|
The placement cost came from marching almost the entire strate and using a full plan at every
|
||||||
|
step. Family-declared solid-support bounds now feed a shared column range query, alongside the
|
||||||
|
same feature scopes. We skip only certified air and keep the old march phase, bisection,
|
||||||
|
interpolation and transforms. Owner-palette p95 fell 7.0706 -> 0.2385 ms, with 58.84 density calls
|
||||||
|
per cell versus S1's 7,763.38. Its sampled authored palette still places zero objects; I keep that
|
||||||
|
limitation explicit rather than claiming those timings include spawning trees.
|
||||||
|
|
||||||
|
A separate populated fixture produced 572 placements across 14 cells. All 572 full/candidate
|
||||||
|
transforms match exactly, all canonical anchor checks pass, and the largest density residual is
|
||||||
|
0.035156 Core voxel units. The 64-cell active batch, including merge and real HISM application,
|
||||||
|
was 3271.147 -> 29.105 ms. All 572 instances were applied and then cleared. This is synchronous
|
||||||
|
CPU work under null RHI; it excludes rendering, GPU upload, async tree completion and owner tree
|
||||||
|
Blueprint logic. One run failed the new count assertion because normal region application clears
|
||||||
|
live landmark footprints. I isolated the validation fixture from those footprints; normal content
|
||||||
|
keeps them enabled. The corrected run passes.
|
||||||
|
|
||||||
|
BuildHost SurfaceWorld passed its full actor/streaming/collision/edit checks and W1/W8/W16 with
|
||||||
|
S1's default 25 cm hashes. The final island actor also keeps S1's entire W1/W8/W16 matrix. The final
|
||||||
|
focused surface replay checks LOD0/1/7/8 full-versus-default tile hashes after the cost hint. M2 V1/V2
|
||||||
|
fixture repeats, borders, MC identity and all 125 doorway phases pass. A separate Core-off run
|
||||||
|
proves unchanged legacy density and 12.5 cm scale. All builds/link audits passed. No timeout,
|
||||||
|
0xC0000142 or new UECC folder occurred. The failed HISM fixture run exited normally with test status 1.
|
||||||
|
|
||||||
|
My next structural suggestion is an immutable per-strate family-provider table, carrying declared
|
||||||
|
bounds, cost hints, value certificates and optional support/surface queries. It would avoid repeating
|
||||||
|
concrete family enumeration and rebuilding metadata in each consumer. I have not rebuilt that
|
||||||
|
boundary this round; the report lists the allocation, many-strate, hash and stale-provider evidence
|
||||||
|
needed first. Preserve the existing bounded biome lattice and cave interval machinery meanwhile.
|
||||||
|
|
||||||
|
New source files are `Round6/SharedFeatureListAudit.cpp` and `Round6/BuildSharedFeatureListAudit.ps1`
|
||||||
|
under WorldAssemblerM3. The report lists every modified source, likely compile spots, all failed and
|
||||||
|
replayed runs, timing limitations and the plain-words owner note. CODEMAP section 3 and ARCHITECTURE
|
||||||
|
section 8.23 are updated. No owner project/editor was opened, closed or built. No .uasset was edited,
|
||||||
|
and no link, commit, push or stash was created.
|
||||||
|
|
||||||
|
[Full S2 report](../../WorldAssemblerM3/Round6/REPORT.md).
|
||||||
|
|
||||||
|
— Sol
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
[CmdletBinding()]
|
||||||
|
param([string]$IslandAssemblyPath, [string]$SurfaceAssemblyPath)
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$PluginRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..\..\..'))
|
||||||
|
$HostOutput = Join-Path $PluginRoot 'Saved\BuildHost\HostProject\Plugins\VoxelForge\Saved\CoreWorldValidation\User\Saved\CoreWorldValidation\Output'
|
||||||
|
if (!$IslandAssemblyPath) { $IslandAssemblyPath = Join-Path $HostOutput 's2_assembly_FloatingIslands.json' }
|
||||||
|
if (!$SurfaceAssemblyPath) { $SurfaceAssemblyPath = Join-Path $HostOutput 's2_assembly_SurfaceWorld.json' }
|
||||||
|
$Results = Join-Path $PSScriptRoot 'results'
|
||||||
|
$BuildRoot = Join-Path $PluginRoot 'Standalone\Build\VoxelForgeCore'
|
||||||
|
$Exe = Join-Path $BuildRoot 'SharedFeatureListAudit.exe'
|
||||||
|
$Rsp = Join-Path $BuildRoot 'shared-feature-list-audit.rsp'
|
||||||
|
$VcVars = 'E:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat'
|
||||||
|
$Source = Join-Path $PSScriptRoot 'SharedFeatureListAudit.cpp'
|
||||||
|
$Library = Join-Path $BuildRoot 'VoxelForgeCore.lib'
|
||||||
|
foreach ($Required in @($IslandAssemblyPath,$SurfaceAssemblyPath,$VcVars,$Library,$Source)) {
|
||||||
|
if (!(Test-Path -LiteralPath $Required -PathType Leaf)) { throw "Missing audit input: $Required" }
|
||||||
|
}
|
||||||
|
[IO.Directory]::CreateDirectory($Results) | Out-Null
|
||||||
|
$Lines = @('/nologo','/std:c++20','/O2','/arch:AVX2','/fp:precise','/EHsc',
|
||||||
|
('/I"{0}"' -f (Join-Path $PluginRoot 'Standalone\Core\include')),
|
||||||
|
('/Fe"{0}"' -f $Exe),('"{0}"' -f $Source),('"{0}"' -f $Library))
|
||||||
|
[IO.File]::WriteAllLines($Rsp,$Lines,[Text.UTF8Encoding]::new($false))
|
||||||
|
Push-Location $BuildRoot
|
||||||
|
try {
|
||||||
|
$Command = 'call "{0}" >nul && cl.exe @"{1}"' -f $VcVars,$Rsp
|
||||||
|
& $env:ComSpec /d /s /c $Command
|
||||||
|
if ($LASTEXITCODE -eq -1073741502) { & $env:ComSpec /d /s /c $Command }
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "Audit compile failed: $LASTEXITCODE" }
|
||||||
|
} finally { Pop-Location }
|
||||||
|
for ($Attempt=1;$Attempt -le 2;++$Attempt) {
|
||||||
|
$Stdout = Join-Path $Results "shared_audit_attempt_$Attempt.stdout.log"
|
||||||
|
$Stderr = Join-Path $Results "shared_audit_attempt_$Attempt.stderr.log"
|
||||||
|
$Process = Start-Process -FilePath $Exe -WindowStyle Hidden -PassThru -WorkingDirectory $BuildRoot `
|
||||||
|
-ArgumentList @(('"{0}"' -f $IslandAssemblyPath),('"{0}"' -f $SurfaceAssemblyPath),('"{0}"' -f $Results)) `
|
||||||
|
-RedirectStandardOutput $Stdout -RedirectStandardError $Stderr
|
||||||
|
$Process.PriorityClass = [Diagnostics.ProcessPriorityClass]::Normal
|
||||||
|
$Process.ProcessorAffinity = [IntPtr]0xFFF
|
||||||
|
$Timer = [Diagnostics.Stopwatch]::StartNew()
|
||||||
|
$Peak = 0L
|
||||||
|
while (!$Process.WaitForExit(250)) {
|
||||||
|
$Process.Refresh()
|
||||||
|
$Peak = [Math]::Max($Peak,$Process.PrivateMemorySize64)
|
||||||
|
if ($Timer.Elapsed.TotalSeconds -gt 900 -or $Peak -gt 8GB) {
|
||||||
|
Stop-Process -Id $Process.Id -Force
|
||||||
|
throw 'Shared audit exceeded 15 minutes or 8 GiB.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$Process.WaitForExit()
|
||||||
|
$Process.Refresh()
|
||||||
|
$ExitCode = $Process.ExitCode
|
||||||
|
if ($null -eq $ExitCode -and (Select-String -LiteralPath $Stdout -SimpleMatch 'SHARED_FEATURE_AUDIT_PASS' -Quiet)) { $ExitCode=0 }
|
||||||
|
"attempt=$Attempt exit=$ExitCode elapsed_s=$($Timer.Elapsed.TotalSeconds) peak_private_bytes=$Peak" |
|
||||||
|
Add-Content (Join-Path $Results 'shared_audit_ledger.txt')
|
||||||
|
Get-Content $Stdout
|
||||||
|
Get-Content $Stderr
|
||||||
|
if ($ExitCode -eq -1073741502 -and $Attempt -eq 1) { continue }
|
||||||
|
if ($ExitCode -ne 0 -or !(Select-String -LiteralPath $Stdout -SimpleMatch 'SHARED_FEATURE_AUDIT_PASS' -Quiet)) {
|
||||||
|
throw "Shared feature audit failed: $ExitCode"
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
@@ -0,0 +1,269 @@
|
|||||||
|
# World assembler S2 / M3 Round 6
|
||||||
|
|
||||||
|
Date: 2026-10-18. Author: Sol. Base: `d6e6942`, branch `assembler/world`. No commit, push or stash.
|
||||||
|
|
||||||
|
## Shared engine change
|
||||||
|
|
||||||
|
The island-only worker context has been removed from `ShapeWorldInternal.h`.
|
||||||
|
`WorldAssemblyFeatureTileListV1` and `ScopedWorldAssemblyFeatureQueriesV1` now carry a shared,
|
||||||
|
worker-local candidate domain, keyed by immutable recipe/plan identity. Nested scopes restore their
|
||||||
|
predecessor; samples outside the prepared domain use the full evaluator. Tile preparation includes
|
||||||
|
the mesher's normal halo and prepares both existing native families. Derived far-shell field calls
|
||||||
|
stay in that scope too. The old island payload names remain aliases so S1 research tools still build.
|
||||||
|
|
||||||
|
Families retain their mathematics: SurfaceWorld declares XY influence for column writers and finite
|
||||||
|
no-op boxes for volumetric exceptions; islands retain S1's root-aware positive-density certificate
|
||||||
|
and ordered island/connector groups. The engine never assumes that density magnitude is Euclidean
|
||||||
|
distance. Unknown operations stay active. A shared `candidateEvaluationUseful` cost hint is resolved
|
||||||
|
once per domain. The measured one-feature SurfaceWorld and four-island plans prefer the full scan;
|
||||||
|
larger plans retain culling. Audits force candidates on small plans to prove their bounds too. No density arithmetic, recipe serialization/version,
|
||||||
|
voxel-size resolution, queue, Epoch, edit lock or streaming policy was changed.
|
||||||
|
|
||||||
|
The default contract for future caves, shafts, pits, passages and rivers is documented in
|
||||||
|
ARCHITECTURE §8.23. Declare conservative bounds and any value-side proof needed to preserve exact
|
||||||
|
positive values. Preserve operation order; unknown proofs evaluate normally. An air-base family can
|
||||||
|
also declare conservative solid support, allowing the shared column union to shorten a search.
|
||||||
|
Solid-base caves cannot use that air proof. Every new proof needs equal full/candidate hashes and a
|
||||||
|
dropped-needed-feature control.
|
||||||
|
|
||||||
|
Surface biome-site selection already searches a fixed local lattice and is cached by XY. It is not
|
||||||
|
a global feature scan. We preserve that machinery and target its finite feature loops. Existing
|
||||||
|
cave extraction keeps its existing line/spatial bins; this round makes no new cave performance claim.
|
||||||
|
FarShells remains derived repair geometry with existing connector/stratum candidate filtering, rather
|
||||||
|
than becoming another canonical generator. Its field queries inherit the shared scope.
|
||||||
|
|
||||||
|
## Placement cause and correction
|
||||||
|
|
||||||
|
Native island placement previously scanned the entire owner strate, calling a full-plan evaluator
|
||||||
|
at each step. The owner workload used about 7,763 density queries per cell in S1. Its sampling cell
|
||||||
|
was mostly air, so optimizing only the top-anchor helper did not remove the dominant work.
|
||||||
|
|
||||||
|
Snapshots now declare conservative support bounds once. Decoration cells and landmark columns
|
||||||
|
prepare the same shared feature queries as meshing. A shared support-union query skips proven air,
|
||||||
|
including entirely empty columns. The march retains its original lattice phase, crossing budget,
|
||||||
|
four bisections, interpolation, normals and placement hashes. No alternate approximate top surface
|
||||||
|
was introduced. Companion queries get their own nested domain when needed.
|
||||||
|
|
||||||
|
Validation preserves the owner's palette and reports it separately from a deliberately populated
|
||||||
|
fixture. That fixture uses dense deterministic floor placements, both water classifications, the
|
||||||
|
resolved biome tags, and a staged decoration mesh (engine cube fallback if none is available).
|
||||||
|
It checks exact full/candidate transforms and canonical island density residuals, then merges and
|
||||||
|
applies real HISM regions and clears them immediately. CPU totals include cell preparation, merge
|
||||||
|
and region application; the identical application/merge cost is shared by both comparison totals.
|
||||||
|
The isolated HISM fixture bypasses live landmark footprints, which otherwise suppress its instances.
|
||||||
|
Normal region application keeps those footprints enabled. Null RHI does not measure GPU upload,
|
||||||
|
rendering, a rendered frame, or asynchronous cluster-tree completion. Existing native content
|
||||||
|
snapshots still describe the planned base field; this round does not add edit-aware reanchoring.
|
||||||
|
|
||||||
|
## Measurements and proof
|
||||||
|
|
||||||
|
The field audit replays the plans exported from BuildHost's staged owner route. These are the
|
||||||
|
same transient owner-height fixtures described in Round 4: the saved settings point to `DA_Strate4`,
|
||||||
|
while the logged SurfaceWorld layout is reconstructed from `DA_Strate1` without saving an asset.
|
||||||
|
No Season is assigned in these copies. The island plan has 4 islands and 3 connectors; the surface
|
||||||
|
plan has one finite type-4 shelf. The stress cases extend those plans to 256 islands/240 connectors
|
||||||
|
and 64 surface shelves plus one XY height writer and one XY cap writer. They do not change any production recipe.
|
||||||
|
|
||||||
|
### Forced candidate proof and cost
|
||||||
|
|
||||||
|
Each island row samples 64 tiles / 64,000 points, including the normal halo. Each surface row
|
||||||
|
samples 64 tiles / 46,656 points and hashes density, ground, cap and biome ID (186,624 values).
|
||||||
|
Surface timings start both paths with cold column caches by changing only the local validation
|
||||||
|
cache epoch. Final timing replay ran without an overlapping build or Unreal process.
|
||||||
|
|
||||||
|
| Family / plan | Full-scan p95 ms | Forced-candidate p95 ms | List build p95 ms | Full and candidate hash | Negative control |
|
||||||
|
|---|---:|---:|---:|---|---|
|
||||||
|
| Islands, staged owner | 0.9803 | 1.0194 | 0.0012 | `cb94db3ca317d001` | detected |
|
||||||
|
| Islands, 256 stress | 110.570 | 7.3619 | 0.0803 | `d83bac7778f3d62a` | detected |
|
||||||
|
| Surface, staged owner | 0.4748 | 0.4559 | 0.0003 | `4207efd4ac869108` | detected |
|
||||||
|
| Surface, 64 shelves + height/cap writers | 0.7084 | 0.4078 | 0.0011 | `f1c6ea123284cc38` | detected |
|
||||||
|
|
||||||
|
Every row has zero mismatches. Each control removes a needed feature; the island control removes
|
||||||
|
its group certificate too. The shared-scope audit also checks identity/domain fallback, nested empty
|
||||||
|
scope restoration, scope teardown and the full-scan cost hint.
|
||||||
|
|
||||||
|
The owner island point audit offers no useful culling gain. Stress p95 improves about **15×**;
|
||||||
|
3,362 stress lists cost 216.615 ms in total (108 owner lists cost 0.0766 ms). The stress template comes
|
||||||
|
from the actual staged owner plan, whereas S1's standalone audit used Round 3's manifest; its 57×
|
||||||
|
figure is not an apples-to-apples regression comparison.
|
||||||
|
|
||||||
|
Surface's single feature has no material point-query gain. Its column computation alone is about
|
||||||
|
0.368 ms of the 0.475 ms p95, and the existing bounded biome/cap path stays intact. The stress case
|
||||||
|
reduces p95 by about 42% while column cost stays similar (0.3699/0.3552 ms). There is no reason to
|
||||||
|
force a global biome/cap feature list into this owner plan.
|
||||||
|
|
||||||
|
Warmed BuildHost tile timings initially exposed a 7–13% island regression from forcing group
|
||||||
|
bookkeeping on four islands. That led to the shared cost hint. The final default path keeps its small-plan timing:
|
||||||
|
|
||||||
|
| Staged plan | LOD | Full tile p95 ms | Default shared-engine p95 ms | Full / shared shell p95 ms |
|
||||||
|
|---|---:|---:|---:|---:|
|
||||||
|
| Islands | 0 | 94.984 | 95.094 | 0.0002 / 0.0003 |
|
||||||
|
| Islands | 1 | 87.280 | 87.577 | 0.0005 / 0.0003 |
|
||||||
|
| Islands | 7 | 20.442 | 20.409 | 16.918 / 16.882 |
|
||||||
|
| Islands | 8 | 73.038 | 72.923 | 69.930 / 69.844 |
|
||||||
|
| Surface | 0 | 11.238 | 11.178 | 0.0001 / 0.0001 |
|
||||||
|
| Surface | 1 | 7.346 | 7.354 | 0.0001 / 0.0001 |
|
||||||
|
| Surface | 7 | 32.739 | 32.381 | 26.378 / 26.021 |
|
||||||
|
| Surface | 8 | 168.545 | 168.063 | 135.960 / 136.310 |
|
||||||
|
|
||||||
|
Each row measures eight owner clipmap keys after warming both routes, including list preparation,
|
||||||
|
meshing and density hashes. All four output hashes match on every paired key. This is a small,
|
||||||
|
hash-capture workload, not a rendered-frame percentile. The far-shell stage excludes LOD8 cap
|
||||||
|
refinement. There is no measured reason to add another shell-specific feature index for these
|
||||||
|
small owner plans; larger canonical fields inherit their family culling through the shared scope.
|
||||||
|
|
||||||
|
### Island placement and active populated cost
|
||||||
|
|
||||||
|
| Workload | Full scan | Shared engine | Result |
|
||||||
|
|---|---:|---:|---|
|
||||||
|
| Owner palette, cell p95 | 7.0706 ms | 0.2385 ms | about 29.6× faster |
|
||||||
|
| Owner palette, 64-cell preparation | 412.960 ms | 3.840 ms | same zero authored placements |
|
||||||
|
| Populated fixture, populated-cell p95 | 53.9875 ms | 2.1501 ms | 14 populated cells / 572 placements |
|
||||||
|
| Populated fixture, all 64 cells | 3270.478 ms | 28.436 ms | exact transforms match |
|
||||||
|
| Merge / real HISM apply | 0.0623 / 0.6068 ms | same products/cost | 572 applied instances |
|
||||||
|
| Populated active batch CPU total | 3271.147 ms | 29.105 ms | about 112× faster |
|
||||||
|
|
||||||
|
Candidate density calls are 58.84 per authored cell, versus S1's 7,763.38 for the same owner plan.
|
||||||
|
Legacy remains 193.25 calls/cell and 0.2855 ms p95. Stationary manager update p95 is 0.0005 ms;
|
||||||
|
that idle number is separate from the active batch totals above.
|
||||||
|
|
||||||
|
All 572 populated transforms match their full-scan counterparts exactly. All 572 canonical island
|
||||||
|
anchor checks pass; maximum density residual is 0.035156 Core voxel density units. The existing
|
||||||
|
terrain/content tests pass on both island and connector: island height difference is 0.373 cm,
|
||||||
|
connector difference 0.096 cm. Capsule contacts, Blueprint carve/fill, water and atmosphere pass.
|
||||||
|
No approximate anchor shortcut is used.
|
||||||
|
|
||||||
|
The six-entry authored palette still produces zero placements in these 64 cells. The populated
|
||||||
|
fixture is explicit synthetic coverage; it does not claim the cost of the owner's tree Blueprint
|
||||||
|
logic or rendered foliage. Active totals sum worker preparation and synchronous merge/application
|
||||||
|
calls over the whole batch, not one frame. Footprint suppression is disabled only for the isolated
|
||||||
|
fixture, which is immediately cleared. Async HISM tree completion and rendering are not included.
|
||||||
|
The earlier SurfaceWorld populated diagnostic produced 8,192 synthetic transforms with exact
|
||||||
|
full/candidate agreement, but had no mesh application in that build and warm-cache ordering;
|
||||||
|
its speed figures are not used as a SurfaceWorld improvement claim.
|
||||||
|
|
||||||
|
### Default identity
|
||||||
|
|
||||||
|
No recipe version was bumped: all changes select exact evaluators or skip certified air.
|
||||||
|
The full SurfaceWorld actor run reproduced S1's W1/W8/W16 matrix:
|
||||||
|
`0cd2a1a4152ee4aa / 17775c835711e5a3 / a27224958fc27d7b / 3821fea90b134ce4`
|
||||||
|
(density / mesh / payload / topology). Island assembly/recipe/plan remain
|
||||||
|
`9c372e7bfb5b8310 / 5e0370d9f95f1c15 / dbbda4932378047a`.
|
||||||
|
The final-source island W1/W8/W16 matrix is also exactly S1:
|
||||||
|
`4dcfb37585d7d986 / 725a18374c5623de / fb345d8cb319d18b / 76f2846be8efb5f4`.
|
||||||
|
|
||||||
|
The full SurfaceWorld actor pass precedes the final cost hint; the final-source focused replay
|
||||||
|
checks its LOD0/1/7/8 full-versus-default hashes after that selection-only change. Core-off's separate
|
||||||
|
final run proves unchanged legacy density at three probes and 12.5 cm. M2 V1/V2 five-fixture repeats,
|
||||||
|
same-LOD borders, V2 MC identity and 125 doorway phases all pass. Existing doorway contour
|
||||||
|
mismatch remains 22.844 cm in that fixture audit; this round does not claim a new seam solution.
|
||||||
|
|
||||||
|
### Run ledger and limits
|
||||||
|
|
||||||
|
| Run | Result / limit |
|
||||||
|
|---|---|
|
||||||
|
| Standalone Core library builds (four iterations) | all first-attempt passes; final link-safety audit checks 220,200 symbol lines |
|
||||||
|
| BuildHost UBT/UHT builds (six iterations) | all first-attempt passes; latest 62.03 s; 18 Core shims, link-safety 95,738 symbol lines; five staged asset hashes verified |
|
||||||
|
| Preliminary island actor | pass, about 3m57s, 3.19 GiB peak; canonical hashes and 572 anchor checks |
|
||||||
|
| Full SurfaceWorld actor | pass, about 12m15s, 3.58 GiB peak; streaming, collisions, motion, edits, geometry/anchors, W1/W8/W16 |
|
||||||
|
| Island actor with new HISM fixture | exit 1, about 4m14s; landmark footprint suppression hit the fixture count assertion; field/worker/anchor gates passed |
|
||||||
|
| Final island actor, cost hint and isolated HISM fixture | pass, about 3m52s, 3.03 GiB peak; W1/W8/W16, 572 exact anchors and applied instances |
|
||||||
|
| Final focused SurfaceWorld tile replay | pass, about 21s, 2.70 GiB peak; 32 paired LOD0/1/7/8 keys, all hashes equal |
|
||||||
|
| Final M2 V1/V2 fixtures | pass, about 17s, 2.34 GiB peak; repeats, borders, MC identity, 125 doorway phases |
|
||||||
|
| Final Core-off | pass, about 14s, 2.14 GiB peak; legacy density and scale unchanged |
|
||||||
|
| Final composed standalone audit | pass, 7.87s, about 64 MiB peak; all four full/candidate rows exact and controls detected |
|
||||||
|
|
||||||
|
Build iterations cover initial integration, added diagnostics, active fixture application, warmed
|
||||||
|
measurements, duplicate-validation removal, and the cost-hint/fixture fix. Their logs are the six
|
||||||
|
`results/host_build*.log` files and four `results/core_build*.log` files. The standalone audit was
|
||||||
|
replayed as those diagnostics were refined; the final composed stress also exercises XY height/cap
|
||||||
|
writers at unrelated Z values. Every replay passed its field/control assertions.
|
||||||
|
|
||||||
|
One actor run failed its new populated-HISM count
|
||||||
|
assertion after passing field/worker/anchor gates: normal `ApplyRegion` also clears live landmark
|
||||||
|
footprints. The fixture now explicitly isolates those footprints; normal application defaults to
|
||||||
|
keeping them enabled. The failed log is `results/islands_hism_fixture_failed.log` (exit 1, no crash).
|
||||||
|
|
||||||
|
Preliminary surface tile timings used a cold full path followed by a warm candidate path. Those
|
||||||
|
numbers are discarded; the final focused replay warms both paths. The first standalone audit
|
||||||
|
also overlapped compilation; it passed functional controls, but its timings are discarded. Idle
|
||||||
|
replays and the final-source replay are all recorded in `results/shared_audit_ledger.txt`.
|
||||||
|
An audit iteration removed duplicate plan validation from scoped island samples. All standalone
|
||||||
|
field audits passed; `vswhere.exe` was not found by the installed VC setup script, but CL compiled
|
||||||
|
and the audit ran successfully. A documentation-edit attempt failed on Python's default Windows
|
||||||
|
encoding; it changed no files and was corrected with UTF-8/apply_patch.
|
||||||
|
|
||||||
|
The first preliminary island launch used the existing launcher; I applied 12-CPU affinity during
|
||||||
|
that run, then added `-corelimit=12` and 12-CPU affinity to the launcher for subsequent runs. Builds
|
||||||
|
used at most eight UBT actions, or eight UBT plus four standalone compiler actions. W16 remains a
|
||||||
|
logical determinism partition under the 12-CPU execution cap. Each Unreal launch checks 4 GiB free
|
||||||
|
RAM and refuses another Unreal process. Every process is guarded at 8 GiB and 900 seconds.
|
||||||
|
No viewer or GPU capture was launched. No run exceeded its memory/time guard, no timeout occurred,
|
||||||
|
and no run returned `0xC0000142` (so its one-retry rule was never needed).
|
||||||
|
|
||||||
|
Crash audit: `Saved/BuildHost/HostProject/Saved/Crashes/` still contains only
|
||||||
|
`UECC-Windows-F1A3EFD5469F5DD62E68EBBACAC84376_0000` (2026-09-17). The isolated User crash root
|
||||||
|
still contains only `UECC-Windows-7B23387E4235BCFEDE42BC9D637E41CB_0000` (2026-10-08).
|
||||||
|
No new UECC folder exists in the host, staged plugin or isolated User crash roots. The final
|
||||||
|
commandlets finished with zero errors; known missing staged `/Game/VoxelForge/Blocks/NewDataAsset`
|
||||||
|
and certificate-store warnings remain. The failed fixture run logged its explicit test error and
|
||||||
|
exited normally.
|
||||||
|
|
||||||
|
Evidence: `results/SUMMARY.json`, `results/shared_audit_attempt_1.stdout.log`,
|
||||||
|
`results/island_tile_lists.json`, `results/shared_audit_ledger.txt`, `results/surface_full_actor.log`,
|
||||||
|
`results/islands_full_actor_final.log`, `results/surface_warmed_final.log`,
|
||||||
|
`results/m2_fixtures_final.log` and `results/core_off_final.log`. Owner plans exported under
|
||||||
|
BuildHost's isolated User Saved directory are the standalone replay inputs.
|
||||||
|
|
||||||
|
No owner project/editor was opened, closed or built, no `.uasset` was edited, and no junction,
|
||||||
|
symlink, commit, push or stash was created. All authored writes and build outputs stayed inside
|
||||||
|
this plugin's workspace/BuildHost.
|
||||||
|
|
||||||
|
|
||||||
|
## My view of the boundary
|
||||||
|
|
||||||
|
The next structural improvement should be a prepared immutable family-provider table per strate.
|
||||||
|
Today the shared scope is generic, but the world and content snapshot adapters still enumerate the
|
||||||
|
two concrete field families, and preparation rebuilds metadata vectors for each query. A provider
|
||||||
|
could publish stable feature IDs, bounds, an optional value certificate and optional surface/support
|
||||||
|
queries once, while the engine handles consumer domains, ordering, cache lifetime and instrumentation.
|
||||||
|
That would also give future cave/rivers placement the same entry point without adding another
|
||||||
|
content snapshot class. I have not rebuilt the architecture for that proposal in this round.
|
||||||
|
|
||||||
|
Evidence required before doing it: a many-strate preparation profile showing the cost of rebuilding
|
||||||
|
bounds/group summaries; separate point-evaluation and list-build allocation counts; equal full versus
|
||||||
|
prepared hashes across owner plans, halos and edited epochs; a deliberately stale-provider control;
|
||||||
|
and no regression in time to collision-ready terrain or peak memory. Preserve the current bounded
|
||||||
|
lattice path for procedural biomes and the current cave interval engine until a measured replacement
|
||||||
|
wins. A universal list of shapes would not capture those processes or their proofs.
|
||||||
|
|
||||||
|
## Files and compile spots
|
||||||
|
|
||||||
|
New source files:
|
||||||
|
|
||||||
|
- `Standalone/Research/WorldAssemblerM3/Round6/SharedFeatureListAudit.cpp` — standalone owner/stress audit.
|
||||||
|
- `Standalone/Research/WorldAssemblerM3/Round6/BuildSharedFeatureListAudit.ps1` — guarded audit compile/run script.
|
||||||
|
|
||||||
|
Modified runtime/Core sources:
|
||||||
|
|
||||||
|
- `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h`
|
||||||
|
- `Standalone/Core/src/WorldAssemblerInternal.h`
|
||||||
|
- `Standalone/Core/src/FloatingIslandsInternal.h`
|
||||||
|
- `Standalone/Core/src/ShapeWorldInternal.h`
|
||||||
|
- `Standalone/Core/src/VoxelForgeCore.cpp`
|
||||||
|
- `Source/VoxelForge/Private/VoxelContentManager.cpp`
|
||||||
|
- `Source/VoxelForge/Public/VoxelContentManager.h`
|
||||||
|
- `Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp`
|
||||||
|
|
||||||
|
Also updated `Tools/TestVoxelForgeCoreWorldHost.ps1`, CODEMAP §3, ARCHITECTURE §8.23 and the dialogue.
|
||||||
|
Likely compile spots: the public Core API/export boundary and canonical Core shim; shared scope/TLS
|
||||||
|
linkage; `VoxelContentManager.h` parsing and its runtime translation unit; editor commandlet timing
|
||||||
|
and fixture code. No new runtime translation unit or manifest shim is required.
|
||||||
|
|
||||||
|
## Owner note
|
||||||
|
|
||||||
|
Keep Core enabled at the existing 25 cm setting. There is no new generator setting to configure.
|
||||||
|
The shared engine now narrows terrain searches for both native families and skips empty space when
|
||||||
|
placing island content. Terrain shapes and default hashes are preserved. The populated validation
|
||||||
|
fixture measures placement without changing your decoration assets. Ready to build your project
|
||||||
|
when you choose; I only build and launch the isolated plugin BuildHost.
|
||||||
@@ -0,0 +1,601 @@
|
|||||||
|
#include "VoxelForgeCore/VoxelForgeCore.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <iterator>
|
||||||
|
#include <numeric>
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
using namespace VoxelForgeCore;
|
||||||
|
using Clock = std::chrono::steady_clock;
|
||||||
|
constexpr std::int64_t Q = WorldAssemblySubvoxelsPerVoxel;
|
||||||
|
constexpr std::int64_t TileQ = 32 * Q;
|
||||||
|
constexpr std::int64_t TileStepQ = 4 * Q;
|
||||||
|
constexpr std::int64_t MesherHaloQ = Q;
|
||||||
|
|
||||||
|
struct Tile
|
||||||
|
{
|
||||||
|
std::array<std::int64_t, 3> minimum{};
|
||||||
|
std::array<std::int64_t, 3> maximum{};
|
||||||
|
FloatingIslandTileFeatureListV1 featureList;
|
||||||
|
double buildMilliseconds = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Measurement
|
||||||
|
{
|
||||||
|
std::uint64_t tilesBuilt = 0;
|
||||||
|
std::uint64_t sampledTiles = 0;
|
||||||
|
std::uint64_t samples = 0;
|
||||||
|
std::uint64_t candidateEvaluations = 0;
|
||||||
|
std::uint64_t fullEvaluations = 0;
|
||||||
|
std::uint64_t exactMismatches = 0;
|
||||||
|
std::uint64_t droppedFeatureDetected = 0;
|
||||||
|
double listBuildTotalMilliseconds = 0.0;
|
||||||
|
double listBuildP95Milliseconds = 0.0;
|
||||||
|
double beforeP95Milliseconds = 0.0;
|
||||||
|
double afterP95Milliseconds = 0.0;
|
||||||
|
double beforeMedianMilliseconds = 0.0;
|
||||||
|
double afterMedianMilliseconds = 0.0;
|
||||||
|
double meanCandidateFeatures = 0.0;
|
||||||
|
double p95CandidateFeatures = 0.0;
|
||||||
|
std::string beforeHash;
|
||||||
|
std::string afterHash;
|
||||||
|
bool negativeControlPassed = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string Hex64(std::uint64_t value)
|
||||||
|
{
|
||||||
|
std::ostringstream output;
|
||||||
|
output << std::hex << std::setfill('0') << std::setw(16) << value;
|
||||||
|
return output.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint64_t HashValues(const std::vector<std::int64_t>& values)
|
||||||
|
{
|
||||||
|
std::uint64_t hash = 14695981039346656037ull;
|
||||||
|
for (const auto value : values)
|
||||||
|
for (int byte = 0; byte < 8; ++byte)
|
||||||
|
{
|
||||||
|
hash ^= static_cast<std::uint8_t>(static_cast<std::uint64_t>(value) >> (byte * 8));
|
||||||
|
hash *= 1099511628211ull;
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
double Percentile95(std::vector<double> values)
|
||||||
|
{
|
||||||
|
if (values.empty()) return 0.0;
|
||||||
|
std::sort(values.begin(), values.end());
|
||||||
|
const std::size_t index = (values.size() * 95 + 99) / 100 - 1;
|
||||||
|
return values[std::min(index, values.size() - 1)];
|
||||||
|
}
|
||||||
|
|
||||||
|
double Median(std::vector<double> values)
|
||||||
|
{
|
||||||
|
if (values.empty()) return 0.0;
|
||||||
|
std::sort(values.begin(), values.end());
|
||||||
|
const std::size_t middle = values.size() / 2;
|
||||||
|
if (values.size() & 1u) return values[middle];
|
||||||
|
return 0.5 * (values[middle - 1] + values[middle]);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::int64_t FloorDiv(std::int64_t value, std::int64_t divisor)
|
||||||
|
{
|
||||||
|
std::int64_t quotient = value / divisor;
|
||||||
|
const std::int64_t remainder = value % divisor;
|
||||||
|
if (remainder != 0 && ((remainder < 0) != (divisor < 0))) --quotient;
|
||||||
|
return quotient;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShiftBounds(std::array<std::int64_t, 3>& minimum,
|
||||||
|
std::array<std::int64_t, 3>& maximum, const std::array<std::int64_t, 3>& delta)
|
||||||
|
{
|
||||||
|
for (std::size_t axis = 0; axis < 3; ++axis)
|
||||||
|
{ minimum[axis] += delta[axis]; maximum[axis] += delta[axis]; }
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint64_t SyntheticId(std::uint32_t index, std::uint64_t salt)
|
||||||
|
{
|
||||||
|
std::uint64_t value = (static_cast<std::uint64_t>(index) << 32) ^ salt;
|
||||||
|
value += 0x9e3779b97f4a7c15ull;
|
||||||
|
value = (value ^ (value >> 30)) * 0xbf58476d1ce4e5b9ull;
|
||||||
|
value = (value ^ (value >> 27)) * 0x94d049bb133111ebull;
|
||||||
|
value ^= value >> 31;
|
||||||
|
return value == 0 ? 1 : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
FloatingIslandPlanV1 MakeStressPlan(const FloatingIslandRecipeV1& recipe,
|
||||||
|
const FloatingIslandPlanV1& source, std::size_t islandCount)
|
||||||
|
{
|
||||||
|
if (source.islands.empty()) throw std::runtime_error("source plan has no island template");
|
||||||
|
FloatingIslandPlanV1 result;
|
||||||
|
result.islands.reserve(islandCount);
|
||||||
|
const auto& prototype = source.islands.front();
|
||||||
|
constexpr std::size_t Columns = 16;
|
||||||
|
const std::int64_t spacing = 80 * Q;
|
||||||
|
for (std::size_t index = 0; index < islandCount; ++index)
|
||||||
|
{
|
||||||
|
FloatingIslandAnchorV1 island = prototype;
|
||||||
|
const std::int64_t dx = static_cast<std::int64_t>(index % Columns) * spacing;
|
||||||
|
const std::int64_t dy = static_cast<std::int64_t>(index / Columns) * spacing;
|
||||||
|
const std::int64_t dz = static_cast<std::int64_t>(index % 3) * 4 * Q;
|
||||||
|
const std::array<std::int64_t, 3> delta{dx, dy, dz};
|
||||||
|
island.centerSubvoxels = {prototype.centerSubvoxels[0] + dx,
|
||||||
|
prototype.centerSubvoxels[1] + dy, prototype.centerSubvoxels[2] + dz};
|
||||||
|
island.topZSubvoxels += dz;
|
||||||
|
island.bottomZSubvoxels += dz;
|
||||||
|
ShiftBounds(island.influenceMinimumSubvoxels, island.influenceMaximumSubvoxels, delta);
|
||||||
|
island.ordinal = static_cast<std::uint32_t>(index);
|
||||||
|
island.stableId = SyntheticId(static_cast<std::uint32_t>(index), 0x49534c414e44ull);
|
||||||
|
result.islands.push_back(island);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.clusterEdges.reserve(islandCount - islandCount / Columns);
|
||||||
|
for (std::size_t index = 0; index < islandCount; ++index)
|
||||||
|
{
|
||||||
|
if (index % Columns + 1 >= Columns || index + 1 >= islandCount) continue;
|
||||||
|
const auto& from = result.islands[index];
|
||||||
|
const auto& to = result.islands[index + 1];
|
||||||
|
FloatingIslandConnectorV1 edge;
|
||||||
|
edge.stableId = SyntheticId(static_cast<std::uint32_t>(index), 0x425249444745ull);
|
||||||
|
edge.fromIslandId = from.stableId;
|
||||||
|
edge.toIslandId = to.stableId;
|
||||||
|
edge.startSubvoxels = {from.centerSubvoxels[0] + from.radiusSubvoxels,
|
||||||
|
from.centerSubvoxels[1], from.topZSubvoxels};
|
||||||
|
edge.endSubvoxels = {to.centerSubvoxels[0] - to.radiusSubvoxels,
|
||||||
|
to.centerSubvoxels[1], to.topZSubvoxels};
|
||||||
|
edge.halfWidthSubvoxels = recipe.bridgeHalfWidthSubvoxels;
|
||||||
|
edge.thicknessSubvoxels = recipe.bridgeThicknessSubvoxels;
|
||||||
|
const std::int64_t pad = recipe.blendRadiusSubvoxels + recipe.roughnessSubvoxels;
|
||||||
|
const std::int64_t archDepth = recipe.geometryVersion >= 2
|
||||||
|
? 2 * edge.thicknessSubvoxels : 0;
|
||||||
|
const std::int64_t xyPad = edge.halfWidthSubvoxels + pad;
|
||||||
|
edge.influenceMinimumSubvoxels = {
|
||||||
|
std::min(edge.startSubvoxels[0], edge.endSubvoxels[0]) - xyPad,
|
||||||
|
std::min(edge.startSubvoxels[1], edge.endSubvoxels[1]) - xyPad,
|
||||||
|
std::min(edge.startSubvoxels[2], edge.endSubvoxels[2])
|
||||||
|
- edge.thicknessSubvoxels - pad - archDepth};
|
||||||
|
edge.influenceMaximumSubvoxels = {
|
||||||
|
std::max(edge.startSubvoxels[0], edge.endSubvoxels[0]) + xyPad,
|
||||||
|
std::max(edge.startSubvoxels[1], edge.endSubvoxels[1]) + xyPad,
|
||||||
|
std::max(edge.startSubvoxels[2], edge.endSubvoxels[2]) + pad};
|
||||||
|
result.clusterEdges.push_back(edge);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Tile> BuildMixedTiles(const FloatingIslandFieldV1& field,
|
||||||
|
const std::array<std::int64_t, 3>& tileLimitMinimum,
|
||||||
|
const std::array<std::int64_t, 3>& tileLimitMaximum,
|
||||||
|
Measurement& measurement)
|
||||||
|
{
|
||||||
|
std::vector<Tile> tiles;
|
||||||
|
for (std::int64_t z = tileLimitMinimum[2]; z <= tileLimitMaximum[2]; z += TileQ)
|
||||||
|
for (std::int64_t y = tileLimitMinimum[1]; y <= tileLimitMaximum[1]; y += TileQ)
|
||||||
|
for (std::int64_t x = tileLimitMinimum[0]; x <= tileLimitMaximum[0]; x += TileQ)
|
||||||
|
{
|
||||||
|
Tile tile;
|
||||||
|
tile.minimum = {x, y, z};
|
||||||
|
tile.maximum = {x + TileQ, y + TileQ, z + TileQ};
|
||||||
|
const std::array<std::int64_t, 3> sampleMinimum{
|
||||||
|
x - MesherHaloQ, y - MesherHaloQ, z - MesherHaloQ};
|
||||||
|
const std::array<std::int64_t, 3> sampleMaximum{
|
||||||
|
x + TileQ + MesherHaloQ, y + TileQ + MesherHaloQ, z + TileQ + MesherHaloQ};
|
||||||
|
const auto begin = Clock::now();
|
||||||
|
tile.featureList = field.buildFeatureTileList(sampleMinimum, sampleMaximum);
|
||||||
|
tile.buildMilliseconds = std::chrono::duration<double, std::milli>(Clock::now() - begin).count();
|
||||||
|
measurement.listBuildTotalMilliseconds += tile.buildMilliseconds;
|
||||||
|
++measurement.tilesBuilt;
|
||||||
|
if (!tile.featureList.featureIndices.empty()) tiles.push_back(std::move(tile));
|
||||||
|
}
|
||||||
|
return tiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
Measurement MeasureField(const FloatingIslandFieldV1& field,
|
||||||
|
std::array<std::int64_t, 3> limitMinimum,
|
||||||
|
std::array<std::int64_t, 3> limitMaximum)
|
||||||
|
{
|
||||||
|
Measurement measurement;
|
||||||
|
for (std::size_t axis = 0; axis < 3; ++axis)
|
||||||
|
{
|
||||||
|
limitMinimum[axis] = FloorDiv(limitMinimum[axis], TileQ) * TileQ;
|
||||||
|
limitMaximum[axis] = FloorDiv(limitMaximum[axis], TileQ) * TileQ;
|
||||||
|
}
|
||||||
|
auto tiles = BuildMixedTiles(field, limitMinimum, limitMaximum, measurement);
|
||||||
|
if (tiles.empty()) throw std::runtime_error("tile bounds produced no candidate tiles");
|
||||||
|
std::vector<double> buildTimes, candidateCounts;
|
||||||
|
buildTimes.reserve(tiles.size()); candidateCounts.reserve(tiles.size());
|
||||||
|
for (const Tile& tile : tiles)
|
||||||
|
{ buildTimes.push_back(tile.buildMilliseconds); candidateCounts.push_back(static_cast<double>(tile.featureList.featureIndices.size())); }
|
||||||
|
measurement.listBuildP95Milliseconds = Percentile95(std::move(buildTimes));
|
||||||
|
measurement.meanCandidateFeatures = std::accumulate(candidateCounts.begin(), candidateCounts.end(), 0.0)
|
||||||
|
/ static_cast<double>(candidateCounts.size());
|
||||||
|
measurement.p95CandidateFeatures = Percentile95(std::move(candidateCounts));
|
||||||
|
|
||||||
|
constexpr std::size_t MaximumSampleTiles = 64;
|
||||||
|
if (tiles.size() > MaximumSampleTiles)
|
||||||
|
{
|
||||||
|
std::vector<Tile> selected;
|
||||||
|
selected.reserve(MaximumSampleTiles);
|
||||||
|
for (std::size_t slot = 0; slot < MaximumSampleTiles; ++slot)
|
||||||
|
{
|
||||||
|
const std::size_t index = slot * (tiles.size() - 1) / (MaximumSampleTiles - 1);
|
||||||
|
selected.push_back(std::move(tiles[index]));
|
||||||
|
}
|
||||||
|
tiles = std::move(selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<double> beforeTimes, afterTimes;
|
||||||
|
std::vector<std::int64_t> beforeValues, afterValues;
|
||||||
|
beforeTimes.reserve(tiles.size()); afterTimes.reserve(tiles.size());
|
||||||
|
for (const Tile& tile : tiles)
|
||||||
|
{
|
||||||
|
// Cover the mesher's one-sample LOD0 halo as well as the tile at four-voxel spacing.
|
||||||
|
// Couvrir la marge d'un echantillon du maillage LOD0 et la tuile a pas de quatre voxels.
|
||||||
|
const auto buildAxis = [](std::int64_t minimum, std::int64_t maximum)
|
||||||
|
{
|
||||||
|
std::vector<std::int64_t> values;
|
||||||
|
const std::int64_t start = minimum - MesherHaloQ;
|
||||||
|
const std::int64_t end = maximum + MesherHaloQ;
|
||||||
|
for (std::int64_t value = start; value <= end; value += TileStepQ)
|
||||||
|
values.push_back(value);
|
||||||
|
if (values.empty() || values.back() != end) values.push_back(end);
|
||||||
|
return values;
|
||||||
|
};
|
||||||
|
const auto xs = buildAxis(tile.minimum[0], tile.maximum[0]);
|
||||||
|
const auto ys = buildAxis(tile.minimum[1], tile.maximum[1]);
|
||||||
|
const auto zs = buildAxis(tile.minimum[2], tile.maximum[2]);
|
||||||
|
std::vector<std::array<std::int64_t, 3>> points;
|
||||||
|
points.reserve(xs.size() * ys.size() * zs.size());
|
||||||
|
for (const std::int64_t z : zs)
|
||||||
|
for (const std::int64_t y : ys)
|
||||||
|
for (const std::int64_t x : xs)
|
||||||
|
points.push_back({x, y, z});
|
||||||
|
measurement.samples += points.size();
|
||||||
|
measurement.fullEvaluations += points.size();
|
||||||
|
measurement.candidateEvaluations += points.size() * tile.featureList.featureIndices.size();
|
||||||
|
|
||||||
|
const auto beforeStart = Clock::now();
|
||||||
|
for (const auto& point : points)
|
||||||
|
beforeValues.push_back(field.densitySubvoxels(point[0], point[1], point[2]));
|
||||||
|
beforeTimes.push_back(std::chrono::duration<double, std::milli>(Clock::now() - beforeStart).count());
|
||||||
|
|
||||||
|
const auto afterStart = Clock::now();
|
||||||
|
const WorldAssemblyFeatureQueryV1 query{field.recipe(),field.plan(),tile.featureList};
|
||||||
|
{
|
||||||
|
const ScopedWorldAssemblyFeatureQueriesV1 scope(std::span(&query,1),true);
|
||||||
|
for (const auto& point : points)
|
||||||
|
afterValues.push_back(field.densitySubvoxels(point[0],point[1],point[2]));
|
||||||
|
}
|
||||||
|
afterTimes.push_back(std::chrono::duration<double, std::milli>(Clock::now() - afterStart).count());
|
||||||
|
++measurement.sampledTiles;
|
||||||
|
}
|
||||||
|
measurement.beforeP95Milliseconds = Percentile95(beforeTimes);
|
||||||
|
measurement.afterP95Milliseconds = Percentile95(afterTimes);
|
||||||
|
measurement.beforeMedianMilliseconds = Median(beforeTimes);
|
||||||
|
measurement.afterMedianMilliseconds = Median(afterTimes);
|
||||||
|
measurement.beforeHash = Hex64(HashValues(beforeValues));
|
||||||
|
measurement.afterHash = Hex64(HashValues(afterValues));
|
||||||
|
const std::size_t common = std::min(beforeValues.size(), afterValues.size());
|
||||||
|
for (std::size_t index = 0; index < common; ++index)
|
||||||
|
if (beforeValues[index] != afterValues[index]) ++measurement.exactMismatches;
|
||||||
|
|
||||||
|
if (field.plan() && !field.plan()->islands.empty())
|
||||||
|
{
|
||||||
|
constexpr std::uint32_t droppedFeature = 0;
|
||||||
|
const auto& island = field.plan()->islands.front();
|
||||||
|
const std::array<std::int64_t, 3> point{island.centerSubvoxels[0], island.centerSubvoxels[1],
|
||||||
|
island.bottomZSubvoxels + (island.topZSubvoxels - island.bottomZSubvoxels) / 2};
|
||||||
|
std::array<std::int64_t, 3> minimum{};
|
||||||
|
std::array<std::int64_t, 3> maximum{};
|
||||||
|
for (std::size_t axis = 0; axis < 3; ++axis)
|
||||||
|
{
|
||||||
|
minimum[axis] = FloorDiv(point[axis], TileQ) * TileQ;
|
||||||
|
maximum[axis] = minimum[axis] + TileQ;
|
||||||
|
}
|
||||||
|
auto sampleMinimum = minimum;
|
||||||
|
auto sampleMaximum = maximum;
|
||||||
|
for (std::size_t axis = 0; axis < 3; ++axis)
|
||||||
|
{
|
||||||
|
sampleMinimum[axis] -= MesherHaloQ;
|
||||||
|
sampleMaximum[axis] += MesherHaloQ;
|
||||||
|
}
|
||||||
|
FloatingIslandTileFeatureListV1 dropped = field.buildFeatureTileList(
|
||||||
|
sampleMinimum, sampleMaximum);
|
||||||
|
auto candidate = std::lower_bound(dropped.featureIndices.begin(), dropped.featureIndices.end(),
|
||||||
|
droppedFeature);
|
||||||
|
if (candidate != dropped.featureIndices.end() && *candidate == droppedFeature)
|
||||||
|
{
|
||||||
|
dropped.featureIndices.erase(candidate);
|
||||||
|
for (std::size_t axis = 0; axis < 3; ++axis)
|
||||||
|
{
|
||||||
|
dropped.groupBounds[droppedFeature].minimumSubvoxels[axis] += 100000000000ll;
|
||||||
|
dropped.groupBounds[droppedFeature].maximumSubvoxels[axis] += 100000000000ll;
|
||||||
|
}
|
||||||
|
const auto order = std::find(dropped.groupSearchOrder.begin(),
|
||||||
|
dropped.groupSearchOrder.end(), droppedFeature);
|
||||||
|
if (order != dropped.groupSearchOrder.end())
|
||||||
|
{
|
||||||
|
const std::size_t orderIndex = static_cast<std::size_t>(order - dropped.groupSearchOrder.begin());
|
||||||
|
dropped.groupSearchOrder.erase(order);
|
||||||
|
dropped.groupDistanceLowerBoundsSubvoxels.erase(
|
||||||
|
dropped.groupDistanceLowerBoundsSubvoxels.begin() + orderIndex);
|
||||||
|
dropped.groupSearchComplete = false;
|
||||||
|
const auto full = field.densitySubvoxels(point[0], point[1], point[2]);
|
||||||
|
const WorldAssemblyFeatureQueryV1 badQuery{field.recipe(),field.plan(),dropped};
|
||||||
|
const ScopedWorldAssemblyFeatureQueriesV1 badScope(std::span(&badQuery,1),true);
|
||||||
|
const auto bad = field.densitySubvoxels(point[0],point[1],point[2]);
|
||||||
|
if (full != bad) ++measurement.droppedFeatureDetected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
measurement.negativeControlPassed = measurement.droppedFeatureDetected > 0;
|
||||||
|
return measurement;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Print(const char* label, const Measurement& measurement,
|
||||||
|
std::size_t islandCount, std::size_t connectorCount)
|
||||||
|
{
|
||||||
|
std::cout << label << " islands=" << islandCount << " connectors=" << connectorCount
|
||||||
|
<< " forced_candidates=1 list_tiles_built=" << measurement.tilesBuilt
|
||||||
|
<< " sampled_tiles=" << measurement.sampledTiles
|
||||||
|
<< " sample_count=" << measurement.samples
|
||||||
|
<< " mean_candidates=" << measurement.meanCandidateFeatures
|
||||||
|
<< " p95_candidates=" << measurement.p95CandidateFeatures
|
||||||
|
<< " list_build_total_ms=" << measurement.listBuildTotalMilliseconds
|
||||||
|
<< " list_build_p95_ms=" << measurement.listBuildP95Milliseconds
|
||||||
|
<< " full_tile_p95_ms=" << measurement.beforeP95Milliseconds
|
||||||
|
<< " candidate_tile_p95_ms=" << measurement.afterP95Milliseconds
|
||||||
|
<< " full_tile_median_ms=" << measurement.beforeMedianMilliseconds
|
||||||
|
<< " candidate_tile_median_ms=" << measurement.afterMedianMilliseconds
|
||||||
|
<< " exact_mismatches=" << measurement.exactMismatches
|
||||||
|
<< " full_hash=" << measurement.beforeHash
|
||||||
|
<< " candidate_hash=" << measurement.afterHash
|
||||||
|
<< " negative_control_detected=" << measurement.negativeControlPassed << '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int IslandAuditMain(int argc, char** argv)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (argc != 3)
|
||||||
|
{ std::cerr << "usage: FloatingIslandTileListAudit <manifest.json> <output.json>\n"; return 2; }
|
||||||
|
std::ifstream input(argv[1], std::ios::binary);
|
||||||
|
if (!input) throw std::runtime_error("could not open the recorded round-3 island manifest");
|
||||||
|
const std::string json{std::istreambuf_iterator<char>(input), std::istreambuf_iterator<char>()};
|
||||||
|
std::string error;
|
||||||
|
const auto manifest = FloatingIslandManifestV1::FromJson(json, error);
|
||||||
|
if (!manifest || !manifest->validate(error)) throw std::runtime_error("manifest invalid: " + error);
|
||||||
|
|
||||||
|
FloatingIslandFieldV1 field(manifest->recipe, manifest->plan);
|
||||||
|
if (!field.isValid()) throw std::runtime_error("round-3 production field did not validate");
|
||||||
|
const auto bounds = field.influenceBounds();
|
||||||
|
const auto& center=manifest->plan.islands.front().centerSubvoxels;
|
||||||
|
WorldAssemblyFeatureQueryV1 guardQuery{field.recipe(),field.plan(),field.buildFeatureTileList(center,center)};
|
||||||
|
if(!guardQuery.features.candidateEvaluationUseful)
|
||||||
|
{
|
||||||
|
const ScopedWorldAssemblyFeatureQueriesV1 preferredFull(std::span(&guardQuery,1));
|
||||||
|
if(ScopedWorldAssemblyFeatureQueriesV1::find(field.recipe(),field.plan(),center[0],center[1],center[2]))
|
||||||
|
throw std::runtime_error("shared scope ignored the full-scan cost hint");
|
||||||
|
}
|
||||||
|
const auto outsideX=center[0]+1000*Q;
|
||||||
|
const auto outsideFull=field.densitySubvoxels(outsideX,center[1],center[2]);
|
||||||
|
{
|
||||||
|
const ScopedWorldAssemblyFeatureQueriesV1 scope(std::span(&guardQuery,1),true);
|
||||||
|
if(field.densitySubvoxels(outsideX,center[1],center[2])!=outsideFull)
|
||||||
|
throw std::runtime_error("outside-domain fallback changed density");
|
||||||
|
{
|
||||||
|
const ScopedWorldAssemblyFeatureQueriesV1 emptyScope(std::span<const WorldAssemblyFeatureQueryV1>{});
|
||||||
|
if(ScopedWorldAssemblyFeatureQueriesV1::find(field.recipe(),field.plan(),center[0],center[1],center[2]))
|
||||||
|
throw std::runtime_error("nested empty scope inherited candidates");
|
||||||
|
}
|
||||||
|
if(!ScopedWorldAssemblyFeatureQueriesV1::find(field.recipe(),field.plan(),center[0],center[1],center[2]))
|
||||||
|
throw std::runtime_error("nested scope did not restore candidates");
|
||||||
|
}
|
||||||
|
if(ScopedWorldAssemblyFeatureQueriesV1::find(field.recipe(),field.plan(),center[0],center[1],center[2]))
|
||||||
|
throw std::runtime_error("scope leaked candidates");
|
||||||
|
const Measurement today = MeasureField(field, bounds.minimumSubvoxels, bounds.maximumSubvoxels);
|
||||||
|
|
||||||
|
const FloatingIslandPlanV1 stressPlan = MakeStressPlan(manifest->recipe, manifest->plan, 256);
|
||||||
|
FloatingIslandFieldV1 stressField(manifest->recipe, stressPlan);
|
||||||
|
if (!stressField.isValid()) throw std::runtime_error("synthetic stress field did not validate");
|
||||||
|
const auto stressBounds = stressField.influenceBounds();
|
||||||
|
const Measurement stress = MeasureField(stressField,
|
||||||
|
stressBounds.minimumSubvoxels, stressBounds.maximumSubvoxels);
|
||||||
|
|
||||||
|
Print("today", today, manifest->plan.islands.size(), manifest->plan.clusterEdges.size());
|
||||||
|
Print("stress", stress, stressPlan.islands.size(), stressPlan.clusterEdges.size());
|
||||||
|
std::ofstream output(argv[2], std::ios::trunc);
|
||||||
|
if (!output) throw std::runtime_error("could not create audit result JSON");
|
||||||
|
output << std::setprecision(12) << "{\n"
|
||||||
|
<< " \"baseline\":{\"islands\":" << manifest->plan.islands.size()
|
||||||
|
<< ",\"connectors\":" << manifest->plan.clusterEdges.size()
|
||||||
|
<< ",\"tiles_built\":" << today.tilesBuilt << ",\"sampled_tiles\":" << today.sampledTiles
|
||||||
|
<< ",\"samples\":" << today.samples << ",\"mean_candidates\":" << today.meanCandidateFeatures
|
||||||
|
<< ",\"p95_candidates\":" << today.p95CandidateFeatures
|
||||||
|
<< ",\"list_build_total_ms\":" << today.listBuildTotalMilliseconds
|
||||||
|
<< ",\"list_build_p95_ms\":" << today.listBuildP95Milliseconds
|
||||||
|
<< ",\"before_p95_ms\":" << today.beforeP95Milliseconds
|
||||||
|
<< ",\"after_p95_ms\":" << today.afterP95Milliseconds
|
||||||
|
<< ",\"before_median_ms\":" << today.beforeMedianMilliseconds
|
||||||
|
<< ",\"after_median_ms\":" << today.afterMedianMilliseconds
|
||||||
|
<< ",\"exact_mismatches\":" << today.exactMismatches
|
||||||
|
<< ",\"before_hash\":\"" << today.beforeHash << "\",\"after_hash\":\"" << today.afterHash
|
||||||
|
<< "\",\"negative_control\":" << (today.negativeControlPassed ? "true" : "false") << "},\n"
|
||||||
|
<< " \"stress\":{\"islands\":" << stressPlan.islands.size()
|
||||||
|
<< ",\"connectors\":" << stressPlan.clusterEdges.size()
|
||||||
|
<< ",\"tiles_built\":" << stress.tilesBuilt << ",\"sampled_tiles\":" << stress.sampledTiles
|
||||||
|
<< ",\"samples\":" << stress.samples << ",\"mean_candidates\":" << stress.meanCandidateFeatures
|
||||||
|
<< ",\"p95_candidates\":" << stress.p95CandidateFeatures
|
||||||
|
<< ",\"list_build_total_ms\":" << stress.listBuildTotalMilliseconds
|
||||||
|
<< ",\"list_build_p95_ms\":" << stress.listBuildP95Milliseconds
|
||||||
|
<< ",\"before_p95_ms\":" << stress.beforeP95Milliseconds
|
||||||
|
<< ",\"after_p95_ms\":" << stress.afterP95Milliseconds
|
||||||
|
<< ",\"before_median_ms\":" << stress.beforeMedianMilliseconds
|
||||||
|
<< ",\"after_median_ms\":" << stress.afterMedianMilliseconds
|
||||||
|
<< ",\"exact_mismatches\":" << stress.exactMismatches
|
||||||
|
<< ",\"before_hash\":\"" << stress.beforeHash << "\",\"after_hash\":\"" << stress.afterHash
|
||||||
|
<< "\",\"negative_control\":" << (stress.negativeControlPassed ? "true" : "false") << "}\n}\n";
|
||||||
|
if (!today.negativeControlPassed || !stress.negativeControlPassed)
|
||||||
|
throw std::runtime_error("dropped-feature negative control was not detected");
|
||||||
|
if (today.exactMismatches || stress.exactMismatches)
|
||||||
|
throw std::runtime_error("tile feature evaluation changed canonical field values");
|
||||||
|
std::cout << "TILE_LIST_AUDIT_PASS\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
catch (const std::exception& exception)
|
||||||
|
{
|
||||||
|
std::cerr << "FLOATING_ISLAND_TILE_LIST_AUDIT_FAIL: " << exception.what() << '\n';
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Surface finite features use the same scope as islands. Biome sites stay in their fixed local window.
|
||||||
|
// Les features finies de surface partagent la portee; les sites biomes restent dans leur fenetre locale.
|
||||||
|
void MeasureSurface(WorldAssemblyRecipeV1 recipe, WorldAssemblyStratumPlanV1 plan,
|
||||||
|
const char* label, bool stress)
|
||||||
|
{
|
||||||
|
if(stress)
|
||||||
|
{
|
||||||
|
const auto originals=plan.features;
|
||||||
|
for(int copy=1;copy<64;++copy)
|
||||||
|
for(auto feature:originals)
|
||||||
|
{
|
||||||
|
feature.stableId=SyntheticId(static_cast<std::uint32_t>(plan.features.size()),0x53555246);
|
||||||
|
for(int axis=0;axis<2;++axis)
|
||||||
|
{
|
||||||
|
const auto delta=(axis==0 ? copy%8 : copy/8)*512*Q;
|
||||||
|
feature.minimumSubvoxels[axis]+=delta;
|
||||||
|
feature.maximumSubvoxels[axis]+=delta;
|
||||||
|
}
|
||||||
|
plan.features.push_back(feature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(stress)
|
||||||
|
{
|
||||||
|
// One composed stress case also exercises XY column writers at unrelated Z values.
|
||||||
|
// Une fixture composee couvre aussi les modificateurs XY a des altitudes independantes.
|
||||||
|
const auto& shelf=plan.features.front();
|
||||||
|
WorldAssemblyFeatureV1 cliff;
|
||||||
|
cliff.stableId=SyntheticId(10000,0x434c494646);
|
||||||
|
cliff.type=1;
|
||||||
|
cliff.minimumSubvoxels={(shelf.minimumSubvoxels[0]+shelf.maximumSubvoxels[0])/2-8*Q,
|
||||||
|
(shelf.minimumSubvoxels[1]+shelf.maximumSubvoxels[1])/2-8*Q,0};
|
||||||
|
cliff.maximumSubvoxels={cliff.minimumSubvoxels[0]+16*Q,cliff.minimumSubvoxels[1]+16*Q,0};
|
||||||
|
cliff.parametersSubvoxels={8*Q,8*Q,0,0};
|
||||||
|
auto cap=cliff;
|
||||||
|
cap.stableId=SyntheticId(10001,0x434150);
|
||||||
|
cap.type=3;
|
||||||
|
cap.parametersSubvoxels={4*Q,32*Q,8*Q,0};
|
||||||
|
plan.features.push_back(cliff);
|
||||||
|
plan.features.push_back(cap);
|
||||||
|
}
|
||||||
|
if(stress) plan.contentHash ^= 0x5354524553534341ull; // Separate validation cache epochs. / Epoques distinctes.
|
||||||
|
Heightfield2D field(recipe,plan);
|
||||||
|
std::vector<double> fullTimes,candidateTimes,buildTimes,counts,fullColumnTimes,candidateColumnTimes;
|
||||||
|
std::vector<std::int64_t> fullValues,candidateValues;
|
||||||
|
std::uint64_t mismatches=0;
|
||||||
|
bool negative=false;
|
||||||
|
if(plan.features.empty())throw std::runtime_error("surface has no finite features to audit");
|
||||||
|
// 64 bounded tile probes, spanning each feature and its faces, including normal halos.
|
||||||
|
for(int tile=0;tile<64;++tile)
|
||||||
|
{
|
||||||
|
const auto& feature=plan.features[static_cast<std::size_t>(tile)%plan.features.size()];
|
||||||
|
const std::int64_t x=(feature.minimumSubvoxels[0]+feature.maximumSubvoxels[0])/2;
|
||||||
|
const std::int64_t y=(feature.minimumSubvoxels[1]+feature.maximumSubvoxels[1])/2;
|
||||||
|
const auto column=field.point(x,y,0);
|
||||||
|
const std::int64_t z=(tile%3==0) ? column.groundHeightSubvoxels :
|
||||||
|
(tile%3==1 ? column.capHeightSubvoxels :
|
||||||
|
(feature.minimumSubvoxels[2]+feature.maximumSubvoxels[2])/2);
|
||||||
|
const std::array<std::int64_t,3> lo{x-17*Q,y-17*Q,z-17*Q};
|
||||||
|
const std::array<std::int64_t,3> hi{x+17*Q,y+17*Q,z+17*Q};
|
||||||
|
const auto buildStart=Clock::now();
|
||||||
|
WorldAssemblyFeatureQueryV1 query{field.recipe(),field.plan(),field.buildFeatureTileList(lo,hi)};
|
||||||
|
buildTimes.push_back(std::chrono::duration<double,std::milli>(Clock::now()-buildStart).count());
|
||||||
|
counts.push_back(static_cast<double>(query.features.featureIndices.size()));
|
||||||
|
auto sample=[&](std::vector<std::int64_t>& values)
|
||||||
|
{
|
||||||
|
for(int iz=0;iz<9;++iz)for(int iy=0;iy<9;++iy)for(int ix=0;ix<9;++ix)
|
||||||
|
{
|
||||||
|
const auto p=field.point(lo[0]+ix*4*Q,lo[1]+iy*4*Q,lo[2]+iz*4*Q);
|
||||||
|
values.push_back(p.densitySubvoxels);
|
||||||
|
values.push_back(p.groundHeightSubvoxels);
|
||||||
|
values.push_back(p.capHeightSubvoxels);
|
||||||
|
values.push_back(p.biomeId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Change only the local cache epoch: both timed paths start with cold columns.
|
||||||
|
// Epoch de cache local seulement : les deux parcours partent de colonnes froides.
|
||||||
|
++plan.contentHash;
|
||||||
|
Heightfield2D::resetThreadColumnCacheStats();
|
||||||
|
const auto start=Clock::now();sample(fullValues);
|
||||||
|
fullTimes.push_back(std::chrono::duration<double,std::milli>(Clock::now()-start).count());
|
||||||
|
fullColumnTimes.push_back(Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds);
|
||||||
|
++plan.contentHash;
|
||||||
|
Heightfield2D::resetThreadColumnCacheStats();
|
||||||
|
const auto candidateStart=Clock::now();
|
||||||
|
{const ScopedWorldAssemblyFeatureQueriesV1 scope(std::span(&query,1),true);sample(candidateValues);}
|
||||||
|
candidateTimes.push_back(std::chrono::duration<double,std::milli>(Clock::now()-candidateStart).count());
|
||||||
|
candidateColumnTimes.push_back(Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds);
|
||||||
|
if(!negative)
|
||||||
|
{
|
||||||
|
if (!query.features.featureIndices.empty())
|
||||||
|
query.features.featureIndices.erase(query.features.featureIndices.begin());
|
||||||
|
std::vector<std::int64_t> bad;
|
||||||
|
++plan.contentHash;
|
||||||
|
{const ScopedWorldAssemblyFeatureQueriesV1 scope(std::span(&query,1),true);sample(bad);}
|
||||||
|
const auto offset=fullValues.size()-bad.size();
|
||||||
|
for(std::size_t i=0;i<bad.size();++i)if(bad[i]!=fullValues[offset+i])negative=true;
|
||||||
|
++plan.contentHash; // Discard deliberately poisoned negative-control columns. / Purger le controle negatif.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(std::size_t i=0;i<fullValues.size();++i)if(fullValues[i]!=candidateValues[i])++mismatches;
|
||||||
|
std::cout<<"surface label="<<label<<" forced_candidates=1 features="<<plan.features.size()
|
||||||
|
<<" values="<<fullValues.size()<<" full_hash="<<Hex64(HashValues(fullValues))
|
||||||
|
<<" candidate_hash="<<Hex64(HashValues(candidateValues))
|
||||||
|
<<" mismatches="<<mismatches<<" negative_control="<<negative
|
||||||
|
<<" full_p95_ms="<<Percentile95(fullTimes)<<" candidate_p95_ms="<<Percentile95(candidateTimes)
|
||||||
|
<<" build_p95_ms="<<Percentile95(buildTimes)<<" candidates_p95="<<Percentile95(counts)
|
||||||
|
<<" full_column_p95_ms="<<Percentile95(fullColumnTimes)
|
||||||
|
<<" candidate_column_p95_ms="<<Percentile95(candidateColumnTimes)<<'\n';
|
||||||
|
if(mismatches||!negative)throw std::runtime_error("surface candidate proof failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc,char** argv)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if(argc!=4)throw std::runtime_error("usage: audit owner_island_assembly owner_surface_assembly results_dir");
|
||||||
|
const auto read=[](const char* path)
|
||||||
|
{std::ifstream input(path);return std::string(std::istreambuf_iterator<char>(input),{});};
|
||||||
|
std::string error;
|
||||||
|
auto islands=WorldAssemblyManifest::FromJson(read(argv[1]),error);
|
||||||
|
if(!islands||islands->floatingIslands.empty())throw std::runtime_error("island assembly: "+error);
|
||||||
|
const auto islandPath=std::filesystem::path(argv[3])/"owner_island_manifest.json";
|
||||||
|
{std::ofstream output(islandPath);output<<islands->floatingIslands.front().manifest.ToJson();}
|
||||||
|
std::string exe=argv[0],islandFile=islandPath.string();
|
||||||
|
std::string resultFile=(std::filesystem::path(argv[3])/"island_tile_lists.json").string();
|
||||||
|
char* islandArgs[]{exe.data(),islandFile.data(),resultFile.data()};
|
||||||
|
if(IslandAuditMain(3,islandArgs)!=0)return 1;
|
||||||
|
auto surface=WorldAssemblyManifest::FromJson(read(argv[2]),error);
|
||||||
|
if(!surface||surface->plan.strates.empty())throw std::runtime_error("surface assembly: "+error);
|
||||||
|
MeasureSurface(surface->recipe,surface->plan.strates.front(),"owner",false);
|
||||||
|
MeasureSurface(surface->recipe,surface->plan.strates.front(),"stress64",true);
|
||||||
|
std::cout<<"SHARED_FEATURE_AUDIT_PASS\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
catch(const std::exception& e){std::cerr<<"SHARED_FEATURE_AUDIT_FAIL: "<<e.what()<<'\n';return 1;}
|
||||||
|
}
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
{
|
||||||
|
"field_audit": [
|
||||||
|
{
|
||||||
|
"islands": 4,
|
||||||
|
"connectors": 3,
|
||||||
|
"forced_candidates": 1,
|
||||||
|
"list_tiles_built": 108,
|
||||||
|
"sampled_tiles": 64,
|
||||||
|
"sample_count": 64000,
|
||||||
|
"mean_candidates": 2.63158,
|
||||||
|
"p95_candidates": 6,
|
||||||
|
"list_build_total_ms": 0.0766,
|
||||||
|
"list_build_p95_ms": 0.0012,
|
||||||
|
"full_tile_p95_ms": 0.9803,
|
||||||
|
"candidate_tile_p95_ms": 1.0194,
|
||||||
|
"full_tile_median_ms": 0.85265,
|
||||||
|
"candidate_tile_median_ms": 0.88875,
|
||||||
|
"exact_mismatches": 0,
|
||||||
|
"full_hash": "cb94db3ca317d001",
|
||||||
|
"candidate_hash": "cb94db3ca317d001",
|
||||||
|
"negative_control_detected": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"islands": 256,
|
||||||
|
"connectors": 240,
|
||||||
|
"forced_candidates": 1,
|
||||||
|
"list_tiles_built": 3362,
|
||||||
|
"sampled_tiles": 64,
|
||||||
|
"sample_count": 64000,
|
||||||
|
"mean_candidates": 3.31231,
|
||||||
|
"p95_candidates": 4,
|
||||||
|
"list_build_total_ms": 216.615,
|
||||||
|
"list_build_p95_ms": 0.0803,
|
||||||
|
"full_tile_p95_ms": 110.57,
|
||||||
|
"candidate_tile_p95_ms": 7.3619,
|
||||||
|
"full_tile_median_ms": 109.41,
|
||||||
|
"candidate_tile_median_ms": 4.12255,
|
||||||
|
"exact_mismatches": 0,
|
||||||
|
"full_hash": "d83bac7778f3d62a",
|
||||||
|
"candidate_hash": "d83bac7778f3d62a",
|
||||||
|
"negative_control_detected": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "owner",
|
||||||
|
"forced_candidates": 1,
|
||||||
|
"features": 1,
|
||||||
|
"values": 186624,
|
||||||
|
"full_hash": "4207efd4ac869108",
|
||||||
|
"candidate_hash": "4207efd4ac869108",
|
||||||
|
"mismatches": 0,
|
||||||
|
"negative_control": 1,
|
||||||
|
"full_p95_ms": 0.4748,
|
||||||
|
"candidate_p95_ms": 0.4559,
|
||||||
|
"build_p95_ms": 0.0003,
|
||||||
|
"candidates_p95": 1,
|
||||||
|
"full_column_p95_ms": 0.3679,
|
||||||
|
"candidate_column_p95_ms": 0.3693
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "stress64",
|
||||||
|
"forced_candidates": 1,
|
||||||
|
"features": 66,
|
||||||
|
"values": 186624,
|
||||||
|
"full_hash": "f1c6ea123284cc38",
|
||||||
|
"candidate_hash": "f1c6ea123284cc38",
|
||||||
|
"mismatches": 0,
|
||||||
|
"negative_control": 1,
|
||||||
|
"full_p95_ms": 0.7084,
|
||||||
|
"candidate_p95_ms": 0.4078,
|
||||||
|
"build_p95_ms": 0.0011,
|
||||||
|
"candidates_p95": 1,
|
||||||
|
"full_column_p95_ms": 0.3699,
|
||||||
|
"candidate_column_p95_ms": 0.3552
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default_tile_audit": [
|
||||||
|
{
|
||||||
|
"label": "islands",
|
||||||
|
"lod": 0,
|
||||||
|
"tiles": 8,
|
||||||
|
"hashes_identical": 1,
|
||||||
|
"full_p95_ms": 94.9835,
|
||||||
|
"candidate_p95_ms": 95.0935,
|
||||||
|
"list_p95_ms": 0.0021,
|
||||||
|
"full_shell_p95_ms": 0.0002,
|
||||||
|
"candidate_shell_p95_ms": 0.0003
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "islands",
|
||||||
|
"lod": 1,
|
||||||
|
"tiles": 8,
|
||||||
|
"hashes_identical": 1,
|
||||||
|
"full_p95_ms": 87.2796,
|
||||||
|
"candidate_p95_ms": 87.5774,
|
||||||
|
"list_p95_ms": 0.0035,
|
||||||
|
"full_shell_p95_ms": 0.0005,
|
||||||
|
"candidate_shell_p95_ms": 0.0003
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "islands",
|
||||||
|
"lod": 7,
|
||||||
|
"tiles": 8,
|
||||||
|
"hashes_identical": 1,
|
||||||
|
"full_p95_ms": 20.4421,
|
||||||
|
"candidate_p95_ms": 20.4088,
|
||||||
|
"list_p95_ms": 0.0017,
|
||||||
|
"full_shell_p95_ms": 16.9176,
|
||||||
|
"candidate_shell_p95_ms": 16.8824
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "islands",
|
||||||
|
"lod": 8,
|
||||||
|
"tiles": 8,
|
||||||
|
"hashes_identical": 1,
|
||||||
|
"full_p95_ms": 73.0376,
|
||||||
|
"candidate_p95_ms": 72.9233,
|
||||||
|
"list_p95_ms": 0.0016,
|
||||||
|
"full_shell_p95_ms": 69.9303,
|
||||||
|
"candidate_shell_p95_ms": 69.8442
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "surface",
|
||||||
|
"lod": 0,
|
||||||
|
"tiles": 8,
|
||||||
|
"hashes_identical": 1,
|
||||||
|
"full_p95_ms": 11.2377,
|
||||||
|
"candidate_p95_ms": 11.1781,
|
||||||
|
"list_p95_ms": 0.0006,
|
||||||
|
"full_shell_p95_ms": 0.0001,
|
||||||
|
"candidate_shell_p95_ms": 0.0001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "surface",
|
||||||
|
"lod": 1,
|
||||||
|
"tiles": 8,
|
||||||
|
"hashes_identical": 1,
|
||||||
|
"full_p95_ms": 7.3458,
|
||||||
|
"candidate_p95_ms": 7.3544,
|
||||||
|
"list_p95_ms": 0.0007,
|
||||||
|
"full_shell_p95_ms": 0.0001,
|
||||||
|
"candidate_shell_p95_ms": 0.0001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "surface",
|
||||||
|
"lod": 7,
|
||||||
|
"tiles": 8,
|
||||||
|
"hashes_identical": 1,
|
||||||
|
"full_p95_ms": 32.7389,
|
||||||
|
"candidate_p95_ms": 32.3814,
|
||||||
|
"list_p95_ms": 0.0006,
|
||||||
|
"full_shell_p95_ms": 26.3776,
|
||||||
|
"candidate_shell_p95_ms": 26.021
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "surface",
|
||||||
|
"lod": 8,
|
||||||
|
"tiles": 8,
|
||||||
|
"hashes_identical": 1,
|
||||||
|
"full_p95_ms": 168.5449,
|
||||||
|
"candidate_p95_ms": 168.0627,
|
||||||
|
"list_p95_ms": 0.0008,
|
||||||
|
"full_shell_p95_ms": 135.9596,
|
||||||
|
"candidate_shell_p95_ms": 136.31
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"island_placement": [
|
||||||
|
{
|
||||||
|
"label": "FloatingIslands",
|
||||||
|
"authored_full_p95_ms": 7.0706,
|
||||||
|
"authored_candidate_p95_ms": 0.2385,
|
||||||
|
"authored_active_batch_full_ms": 412.95957,
|
||||||
|
"authored_active_batch_candidate_ms": 3.84022,
|
||||||
|
"populated_cells": 14,
|
||||||
|
"populated_placements": 572,
|
||||||
|
"populated_full_p95_ms": 53.9875,
|
||||||
|
"populated_candidate_p95_ms": 2.1501,
|
||||||
|
"exact_transform_comparisons": 572,
|
||||||
|
"anchor_checks": 572,
|
||||||
|
"max_density_residual": 0.035156
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"island_active": [
|
||||||
|
{
|
||||||
|
"label": "FloatingIslands",
|
||||||
|
"cells": 64,
|
||||||
|
"placements": 572,
|
||||||
|
"full_batch_ms": 3270.4782,
|
||||||
|
"candidate_batch_ms": 28.43621,
|
||||||
|
"merge_ms": 0.06231,
|
||||||
|
"apply_ms": 0.6068,
|
||||||
|
"applied_instances": 572,
|
||||||
|
"full_total_cpu_ms": 3271.14731,
|
||||||
|
"candidate_total_cpu_ms": 29.10532,
|
||||||
|
"rhi": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"hash_matrices": [
|
||||||
|
{
|
||||||
|
"worker_hash_matrix": "1/8/16",
|
||||||
|
"stable": 1,
|
||||||
|
"density": "0x4dcfb37585d7d986",
|
||||||
|
"mesh": "0x725a18374c5623de",
|
||||||
|
"payload": "0xfb345d8cb319d18b",
|
||||||
|
"topology": "0x76f2846be8efb5f4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"worker_hash_matrix": "1/8/16",
|
||||||
|
"stable": 1,
|
||||||
|
"density": "0x0cd2a1a4152ee4aa",
|
||||||
|
"mesh": "0x17775c835711e5a3",
|
||||||
|
"payload": "0xa27224958fc27d7b",
|
||||||
|
"topology": "0x3821fea90b134ce4"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"limits": {
|
||||||
|
"unreal_core_limit": 12,
|
||||||
|
"max_ubt_actions": 8,
|
||||||
|
"max_standalone_compile_actions": 4,
|
||||||
|
"process_limit_gib": 8,
|
||||||
|
"launch_memory_gate_gib": 4,
|
||||||
|
"step_limit_seconds": 900
|
||||||
|
},
|
||||||
|
"known_failed_run": "islands_hism_fixture_failed.log: exit 1; fixture collided with live landmark suppression; isolated fixture passes."
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"baseline":{"islands":4,"connectors":3,"tiles_built":108,"sampled_tiles":64,"samples":64000,"mean_candidates":2.63157894737,"p95_candidates":6,"list_build_total_ms":0.0766,"list_build_p95_ms":0.0012,"before_p95_ms":0.9803,"after_p95_ms":1.0194,"before_median_ms":0.85265,"after_median_ms":0.88875,"exact_mismatches":0,"before_hash":"cb94db3ca317d001","after_hash":"cb94db3ca317d001","negative_control":true},
|
||||||
|
"stress":{"islands":256,"connectors":240,"tiles_built":3362,"sampled_tiles":64,"samples":64000,"mean_candidates":3.31231409875,"p95_candidates":4,"list_build_total_ms":216.6153,"list_build_p95_ms":0.0803,"before_p95_ms":110.5695,"after_p95_ms":7.3619,"before_median_ms":109.40955,"after_median_ms":4.12255,"exact_mismatches":0,"before_hash":"d83bac7778f3d62a","after_hash":"d83bac7778f3d62a","negative_control":true}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"schema_version":1,"content_hash":"87222553193002d8","recipe":{"schema_version":1,"units_per_voxel":256,"owner_voxel_size_microns":125000,"core_voxel_size_microns":250000,"owner_to_core_scale_numerator":1,"owner_to_core_scale_denominator":2,"seed":0,"origin_x_q":0,"origin_y_q":0,"center_top_z_q":-126976,"layout_min_z_q":-258048,"layout_max_z_q":4096,"primary_island_count":3,"island_density_q16":32768,"spacing_q":14336,"minimum_radius_q":3072,"maximum_radius_q":6144,"thickness_ratio_q16":39322,"vertical_jitter_q16":39322,"top_flatten_q16":39322,"taper_end_min_q16":29491,"taper_end_max_q16":45875,"geometry_version":2,"crust_depth_bonus_q16":9830,"root_depth_ratio_q16":49152,"blend_radius_q":768,"contour_warp_q":1613,"roughness_q":384,"boundary_seal_q":512,"bridge_half_width_q":768,"bridge_thickness_q":1024,"route_constraints":{"version":1,"capsule_radius_q":348,"capsule_height_q":1802,"maximum_grade_q16":17560,"sweep_spacing_q":128,"support_probe_radius_q":230,"support_search_depth_q":512,"minimum_clearance_q":128,"minimum_support_probes":3},"content_hash":"5e0370d9f95f1c15"},"plan":{"content_hash":"dbbda4932378047a","islands":[{"stable_id":"e6e461e18b3a9c87","ordinal":0,"center_q":[-10035,4422,-129150],"radius_q":3605,"top_half_q":721,"top_z_q":-128429,"bottom_z_q":-133837,"taper_end_q16":41663,"bounds_min_q":[-17941,-3484,-136525],"bounds_max_q":[-2129,12328,-125741]},{"stable_id":"2bc1820baba047f6","ordinal":1,"center_q":[0,-5178,-127714],"radius_q":3693,"top_half_q":738,"top_z_q":-126976,"bottom_z_q":-132516,"taper_end_q16":33506,"bounds_min_q":[-7994,-13172,-135204],"bounds_max_q":[7994,2816,-124288]},{"stable_id":"adc259cbe2a395e9","ordinal":2,"center_q":[10035,4681,-127887],"radius_q":5181,"top_half_q":1036,"top_z_q":-126851,"bottom_z_q":-134623,"taper_end_q16":39164,"bounds_min_q":[553,-4801,-137311],"bounds_max_q":[19517,14163,-124163]},{"stable_id":"7855fb022cc561c9","ordinal":3,"center_q":[-6451,-19514,-124414],"radius_q":5840,"top_half_q":1168,"top_z_q":-123246,"bottom_z_q":-132006,"taper_end_q16":33440,"bounds_min_q":[-16592,-29655,-134694],"bounds_max_q":[3690,-9373,-120558]}],"cluster_edges":[{"stable_id":"eaa5dd310e846520","from_island_id":"e6e461e18b3a9c87","to_island_id":"2bc1820baba047f6","start_q":[-8680,3126,-128554],"end_q":[-1388,-3851,-126819],"half_width_q":768,"thickness_q":1024,"bounds_min_q":[-10600,-5771,-132778],"bounds_max_q":[532,5046,-125667],"sharp_tag_ready":true},{"stable_id":"034023599b466c51","from_island_id":"2bc1820baba047f6","to_island_id":"adc259cbe2a395e9","start_q":[1370,-3832,-126951],"end_q":[8113,2793,-126831],"half_width_q":768,"thickness_q":1024,"bounds_min_q":[-550,-5752,-131175],"bounds_max_q":[10033,4713,-125679],"sharp_tag_ready":true},{"stable_id":"4962fa885545fb26","from_island_id":"2bc1820baba047f6","to_island_id":"7855fb022cc561c9","start_q":[-788,-6929,-126846],"end_q":[-5205,-16745,-123222],"half_width_q":768,"thickness_q":1024,"bounds_min_q":[-7125,-18665,-131070],"bounds_max_q":[1132,-5009,-122070],"sharp_tag_ready":true}],"route":[{"feature_id":"2e805a1f1f778cbb","position_q":[-10035,4422,-127367],"radius_q":1024},{"feature_id":"5be3b2b1e01722ea","position_q":[-9318,3736,-127414],"radius_q":1024},{"feature_id":"ab4eda612c1f03b5","position_q":[-8601,3051,-127336],"radius_q":1024},{"feature_id":"ba2fd2b050d0dd8c","position_q":[-7885,2365,-127288],"radius_q":1024},{"feature_id":"fc5829ee129fdc4f","position_q":[-7168,1679,-127113],"radius_q":1024},{"feature_id":"1393bb00329a9d5e","position_q":[-6451,993,-126951],"radius_q":1024},{"feature_id":"1dabc9715c93e2c9","position_q":[-5734,308,-126760],"radius_q":1024},{"feature_id":"0ea2008a85e73050","position_q":[-5018,-378,-126563],"radius_q":1024},{"feature_id":"754f55a5f1465cf3","position_q":[-4301,-1064,-126408],"radius_q":1024},{"feature_id":"1b6752776b4a7d22","position_q":[-3584,-1749,-126236],"radius_q":1024},{"feature_id":"0ab3b704db76084d","position_q":[-2867,-2435,-126017],"radius_q":1024},{"feature_id":"bd6980911e0879c4","position_q":[-2150,-3121,-125838],"radius_q":1024},{"feature_id":"ad164d7d0c049d87","position_q":[-1434,-3807,-125738],"radius_q":1024},{"feature_id":"9661463acbada2d6","position_q":[-717,-4492,-125928],"radius_q":1024},{"feature_id":"280a96547fadb0a1","position_q":[0,-5178,-125908],"radius_q":1024},{"feature_id":"531455b42fc13108","position_q":[717,-4474,-125939],"radius_q":1024},{"feature_id":"55d04c20849d7fcb","position_q":[1434,-3770,-125798],"radius_q":1024},{"feature_id":"ac4ebe80860dbdfa","position_q":[2150,-3065,-125893],"radius_q":1024},{"feature_id":"ff942855bf8e3fc5","position_q":[2867,-2361,-125886],"radius_q":1024},{"feature_id":"e6b8bba6c6acaf9c","position_q":[3584,-1657,-125861],"radius_q":1024},{"feature_id":"ae47379bbd4f7fdf","position_q":[4301,-953,-125839],"radius_q":1024},{"feature_id":"98779442384a30ee","position_q":[5018,-249,-125844],"radius_q":1024},{"feature_id":"f4c7757b2ee9fe19","position_q":[5734,456,-125814],"radius_q":1024},{"feature_id":"3ea33fae0fLine truncated
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
attempt=1 exit=0 elapsed_s=8.7570529 peak_private_bytes=68067328
|
||||||
|
Final timing replay: no overlapping BuildHost build or Unreal process.
|
||||||
|
attempt=1 exit=0 elapsed_s=8.0385819 peak_private_bytes=66658304
|
||||||
|
Final source timing replay: redundant scoped validation removed; idle build/Unreal.
|
||||||
|
attempt=1 exit=0 elapsed_s=7.9966612 peak_private_bytes=66658304
|
||||||
|
Final cost-hint source: force candidates for proof; no overlapping build/Unreal.
|
||||||
|
attempt=1 exit=0 elapsed_s=8.0365247 peak_private_bytes=65884160
|
||||||
|
Final composed stress: 64 shelves plus XY cliff/cap writers; no overlapping build/Unreal.
|
||||||
|
attempt=1 exit=0 elapsed_s=7.8703235 peak_private_bytes=66654208
|
||||||
@@ -12,6 +12,7 @@ param(
|
|||||||
[switch]$MismatchedManifestFallback,
|
[switch]$MismatchedManifestFallback,
|
||||||
[switch]$CoreOffTest,
|
[switch]$CoreOffTest,
|
||||||
[switch]$CorePlanOnly,
|
[switch]$CorePlanOnly,
|
||||||
|
[switch]$SharedFeatureAuditOnly,
|
||||||
[int]$CoreTestSeed = [int]::MinValue
|
[int]$CoreTestSeed = [int]::MinValue
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -34,7 +35,8 @@ $DerivedDataCache = Join-Path $PluginRoot 'Saved\BuildHost\VFCoreWorldDDC'
|
|||||||
$ZenData = Join-Path $OutputRoot 'ZenData'
|
$ZenData = Join-Path $OutputRoot 'ZenData'
|
||||||
$EditorCmd = 'E:\Program Files\Epic Games\UE_5.7\Engine\Binaries\Win64\UnrealEditor-Cmd.exe'
|
$EditorCmd = 'E:\Program Files\Epic Games\UE_5.7\Engine\Binaries\Win64\UnrealEditor-Cmd.exe'
|
||||||
$MemoryLimitBytes = 8GB
|
$MemoryLimitBytes = 8GB
|
||||||
$CrashRoots = @((Join-Path $HostRoot 'Saved\Crashes'), (Join-Path $StagePlugin 'Saved\Crashes'))
|
$CrashRoots = @((Join-Path $HostRoot 'Saved\Crashes'), (Join-Path $StagePlugin 'Saved\Crashes'),
|
||||||
|
(Join-Path $UserDirectory 'Saved\Crashes'))
|
||||||
$CoreSizeSuffix = ''
|
$CoreSizeSuffix = ''
|
||||||
if ([Math]::Abs($CoreVoxelSizeCm - 25.0) -gt 0.000001) {
|
if ([Math]::Abs($CoreVoxelSizeCm - 25.0) -gt 0.000001) {
|
||||||
$CoreSizeText = $CoreVoxelSizeCm.ToString('0.####', [Globalization.CultureInfo]::InvariantCulture)
|
$CoreSizeText = $CoreVoxelSizeCm.ToString('0.####', [Globalization.CultureInfo]::InvariantCulture)
|
||||||
@@ -127,6 +129,10 @@ if ($HasManifestOverride -and -not (Test-Path -LiteralPath $WorldAssemblyManifes
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($MismatchedManifestFallback) { $RunName = 'mismatched_fallback' }
|
if ($MismatchedManifestFallback) { $RunName = 'mismatched_fallback' }
|
||||||
|
if ($SharedFeatureAuditOnly) {
|
||||||
|
if ($SelectedModeCount -ne 0) { throw '-SharedFeatureAuditOnly uses the SurfaceWorld owner route.' }
|
||||||
|
$RunName = 'shared_feature_audit'
|
||||||
|
}
|
||||||
|
|
||||||
$foreignEditors = @(Get-Process -ErrorAction SilentlyContinue |
|
$foreignEditors = @(Get-Process -ErrorAction SilentlyContinue |
|
||||||
Where-Object { $_.ProcessName -like 'UnrealEditor*' })
|
Where-Object { $_.ProcessName -like 'UnrealEditor*' })
|
||||||
@@ -221,7 +227,7 @@ $CoreOffTestFlag = if ($CoreOffTest) { 1 } else { 0 }
|
|||||||
$CorePlanOnlyFlag = if ($CorePlanOnly) { 1 } else { 0 }
|
$CorePlanOnlyFlag = if ($CorePlanOnly) { 1 } else { 0 }
|
||||||
$BaseArguments = @(
|
$BaseArguments = @(
|
||||||
('-project="{0}"' -f $HostProject),
|
('-project="{0}"' -f $HostProject),
|
||||||
'-nullrhi', '-unattended', '-nop4', '-nosplash', '-NoSound',
|
'-nullrhi', '-unattended', '-nop4', '-nosplash', '-NoSound', '-corelimit=12',
|
||||||
'-stdout', '-FullStdOutLogOutput', '-forcelogflush', '-NoZenAutoLaunch',
|
'-stdout', '-FullStdOutLogOutput', '-forcelogflush', '-NoZenAutoLaunch',
|
||||||
'-notraceserver', '-nocrashreports', '-ddc=NoZenLocalFallback',
|
'-notraceserver', '-nocrashreports', '-ddc=NoZenLocalFallback',
|
||||||
('-userdir="{0}"' -f $UserDirectory),
|
('-userdir="{0}"' -f $UserDirectory),
|
||||||
@@ -241,6 +247,7 @@ $BaseArguments = @(
|
|||||||
("-corehybridfixturesonly=$HybridFixturesOnlyFlag"),
|
("-corehybridfixturesonly=$HybridFixturesOnlyFlag"),
|
||||||
('-out="{0}"' -f $OutputDirectory)
|
('-out="{0}"' -f $OutputDirectory)
|
||||||
)
|
)
|
||||||
|
if ($SharedFeatureAuditOnly) { $BaseArguments += '-corefeatureauditonly' }
|
||||||
if ($HasManifestOverride) {
|
if ($HasManifestOverride) {
|
||||||
$BaseArguments += ('-coreworldassembly="{0}"' -f $StagedWorldAssembly)
|
$BaseArguments += ('-coreworldassembly="{0}"' -f $StagedWorldAssembly)
|
||||||
}
|
}
|
||||||
@@ -285,6 +292,9 @@ try {
|
|||||||
-RedirectStandardOutput $StdoutPath -RedirectStandardError $StderrPath `
|
-RedirectStandardOutput $StdoutPath -RedirectStandardError $StderrPath `
|
||||||
-WindowStyle Hidden -PassThru
|
-WindowStyle Hidden -PassThru
|
||||||
$Process.PriorityClass = [Diagnostics.ProcessPriorityClass]::Normal
|
$Process.PriorityClass = [Diagnostics.ProcessPriorityClass]::Normal
|
||||||
|
# W16 is a logical determinism partition; at most 12 logical CPUs execute host work.
|
||||||
|
# W16 reste un partitionnement logique; limiter l'execution hote a 12 processeurs logiques.
|
||||||
|
$Process.ProcessorAffinity = [IntPtr]0xFFF
|
||||||
$StartedUtc = [datetime]::UtcNow
|
$StartedUtc = [datetime]::UtcNow
|
||||||
$PeakPrivateBytes = [int64]0
|
$PeakPrivateBytes = [int64]0
|
||||||
while (-not $Process.HasExited) {
|
while (-not $Process.HasExited) {
|
||||||
@@ -351,6 +361,16 @@ if (-not (Test-Path -LiteralPath $FinalLog -PathType Leaf)) {
|
|||||||
$FinalLog = Join-Path $LogDirectory "CoreWorldValidation-$RunName.retry.log"
|
$FinalLog = Join-Path $LogDirectory "CoreWorldValidation-$RunName.retry.log"
|
||||||
}
|
}
|
||||||
$Text = Get-Content -LiteralPath $FinalLog -Raw
|
$Text = Get-Content -LiteralPath $FinalLog -Raw
|
||||||
|
if ($SharedFeatureAuditOnly) {
|
||||||
|
if ($Text -notmatch '\[VoxelForgeSharedFeatureCost\] pass complete=1 warmed_routes=1') {
|
||||||
|
throw "Shared feature audit did not pass: $FinalLog"
|
||||||
|
}
|
||||||
|
if (@(Get-NewCrashFolders).Count -gt 0) { throw 'Shared feature audit produced a new crash folder.' }
|
||||||
|
Write-Host 'VOXELFORGE_SHARED_FEATURE_AUDIT_PASS warmed_routes=1'
|
||||||
|
Write-Host "Core actor log: $FinalLog"
|
||||||
|
Write-Host "Peak commandlet private bytes sampled: $PeakPrivateBytes"
|
||||||
|
return
|
||||||
|
}
|
||||||
if ($CoreFallbackTest) {
|
if ($CoreFallbackTest) {
|
||||||
foreach ($Marker in @(
|
foreach ($Marker in @(
|
||||||
'[VoxelForgeCoreFallbackTest] fallback=PASS core_disabled=1 legacy_generator=1',
|
'[VoxelForgeCoreFallbackTest] fallback=PASS core_disabled=1 legacy_generator=1',
|
||||||
|
|||||||
Reference in new issue
Block a user