wip(assembler): round L13 -- one density field for every route (multiplayer determinism fix)
Region pages fell back to the legacy GetDensityAt evaluator while the base shape used the Core manifest field, so a tile's density (and sometimes mesh) depended on streaming history. Pages are now bound to their world's base FStandaloneShapeWorld evaluator before publication; density, lattice, detail, classification and solid queries all delegate to it. WorldAssemblyEvaluatorVersion 5 -> 6. Route-parity audit: fails on L12 (5/5); after the fix 0 mismatches over 87 tile pairs (base/page, base/native-only, edited, two histories), 56 exact-query pairs, 2.3M page samples, 4 seeds/recipes. Page-tile proof audit: 1,274 blocks, 0 violations. The spawn and island worlds are unchanged; crossing output changed as intended. Reviewer interleaved spawn: +1.1% (noise). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
This commit is contained in:
1 parent
32b8670833
commit
976d012a86
10 files changed
+767
-11
No files matched your search
@@ -1823,6 +1823,23 @@ column metrics. All W1/W12 L1 hashes, exact queries, and the seed-0 FloatingIsla
|
|||||||
the pre-L7 binary with Clang FP contraction off and GCC defaults. See
|
the pre-L7 binary with Clang FP contraction off and GCC defaults. See
|
||||||
[`Standalone/Research/WorldAssemblerM3/Linux7/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux7/REPORT.md).
|
[`Standalone/Research/WorldAssemblerM3/Linux7/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux7/REPORT.md).
|
||||||
|
|
||||||
|
### 8.36 Route-invariant Core density — World Assembler M3 L13
|
||||||
|
|
||||||
|
For an unedited Core world, tile density, mesh, payload, and topology are a function of the world
|
||||||
|
manifest/recipe, seed, and tile key. The reviewed manifest field is authoritative: SurfaceWorld uses
|
||||||
|
its `Heightfield2D`, while FloatingIslands uses its `FloatingIslandFieldV1`. `World::densityAt` and
|
||||||
|
tile generation read the same `FStandaloneShapeWorld` instance. A compiled analytic region page
|
||||||
|
retains bounded spatial bins for page ownership and routing, but `SetSharedDensitySource` binds it to
|
||||||
|
that base shape before publication; density, lattice, detail, classification, and solid queries
|
||||||
|
delegate to the shared evaluator. This keeps exact queries, collision occupancy signs, page tiles,
|
||||||
|
and base tiles on one field. Player edits remain explicit versioned overlays on that world state.
|
||||||
|
|
||||||
|
The route-binding change increments `WorldAssemblyEvaluatorVersion` from 5 to 6. Do not restore a
|
||||||
|
page-local legacy evaluator: it disagrees with the reviewed field. Uniform-block proof audits must
|
||||||
|
fill and check page tiles against the evaluator they actually sample. This is the shared Core path;
|
||||||
|
the Unreal adapter and its asynchronous MPSC/Epoch flow are unchanged. See
|
||||||
|
[`Standalone/Research/WorldAssemblerM3/Linux13/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux13/REPORT.md).
|
||||||
|
|
||||||
## 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
|
||||||
|
|||||||
@@ -1128,6 +1128,15 @@ Mixed/Unknown remains on the fill path. The headless `--audit-uniform-block-proo
|
|||||||
verification, while `--profile-lod-work` reports per-LOD filled samples; both are off by default.
|
verification, while `--profile-lod-work` reports per-LOD filled samples; both are off by default.
|
||||||
Implementation and evidence: [`Standalone/Research/WorldAssemblerM3/Linux9/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux9/REPORT.md).
|
Implementation and evidence: [`Standalone/Research/WorldAssemblerM3/Linux9/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux9/REPORT.md).
|
||||||
|
|
||||||
|
### 3.22 Route-invariant Core density — World Assembler M3 Linux13
|
||||||
|
|
||||||
|
| Symbol | Canonical file | Role |
|
||||||
|
|---|---|---|
|
||||||
|
| `FStandaloneShapeWorld::SetSharedDensitySource`, `SharedDensitySource` | `Standalone/Core/src/ShapeWorldInternal.h`, bound before publication in `Standalone/Core/src/VoxelForgeCore.cpp` | Makes compiled analytic pages delegate field, lattice, detail, classification, and solid queries to the Core world's base shape; `WorldAssemblyEvaluatorVersion` is 6. |
|
||||||
|
|
||||||
|
The base manifest field is authoritative for tile generation and exact queries. See ARCHITECTURE
|
||||||
|
§8.36 and [`Standalone/Research/WorldAssemblerM3/Linux13/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux13/REPORT.md).
|
||||||
|
|
||||||
## 4. The density pipeline (most-edited hot path)
|
## 4. The density pipeline (most-edited hot path)
|
||||||
|
|
||||||
### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749
|
### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749
|
||||||
|
|||||||
@@ -235,7 +235,8 @@ namespace VoxelForgeCore
|
|||||||
// L'assembleur v1 encode coordonnees et scalaires en 1/256 de voxel, independamment des tuiles.
|
// L'assembleur v1 encode coordonnees et scalaires en 1/256 de voxel, independamment des tuiles.
|
||||||
inline constexpr std::int64_t WorldAssemblySubvoxelsPerVoxel = 256;
|
inline constexpr std::int64_t WorldAssemblySubvoxelsPerVoxel = 256;
|
||||||
inline constexpr std::uint32_t WorldAssemblySchemaVersion = 1;
|
inline constexpr std::uint32_t WorldAssemblySchemaVersion = 1;
|
||||||
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 5;
|
// Version 6 binds every compiled tile page to the world's base density evaluator.
|
||||||
|
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 6;
|
||||||
|
|
||||||
enum class FieldChannel : std::uint32_t
|
enum class FieldChannel : std::uint32_t
|
||||||
{
|
{
|
||||||
@@ -1050,6 +1051,9 @@ namespace VoxelForgeCore
|
|||||||
std::int32_t bandMaxZVoxel = 2147483647;
|
std::int32_t bandMaxZVoxel = 2147483647;
|
||||||
bool hashDensity = false;
|
bool hashDensity = false;
|
||||||
bool captureDensitySamples = false;
|
bool captureDensitySamples = false;
|
||||||
|
// Diagnostic only; materialize the full density lattice even when block proofs
|
||||||
|
// establish that the tile has no surface.
|
||||||
|
bool forceFullDensityCapture = false;
|
||||||
bool skipUvAndColors = false;
|
bool skipUvAndColors = false;
|
||||||
bool skipUv = false;
|
bool skipUv = false;
|
||||||
bool skipColors = false;
|
bool skipColors = false;
|
||||||
|
|||||||
@@ -478,9 +478,9 @@ public:
|
|||||||
using Clock=std::chrono::steady_clock;
|
using Clock=std::chrono::steady_clock;
|
||||||
Owner=&World;WorldAssembly=InWorldAssembly;
|
Owner=&World;WorldAssembly=InWorldAssembly;
|
||||||
WorkerCount=1;DetailVariant=Variant;Scheduler=nullptr;
|
WorkerCount=1;DetailVariant=Variant;Scheduler=nullptr;
|
||||||
// Region pages use the immutable manifest for conservative block proofs, while keeping
|
// Analytic pages retain their bounded owner bins for route bookkeeping. Once published,
|
||||||
// the legacy SurfaceWorld density evaluator so existing tile hashes remain exact.
|
// the Core world binds each page to its base shape as the single density evaluator.
|
||||||
bUseNativeManifestFields=false;
|
bUseNativeManifestFields=true;
|
||||||
RoundTunnelsEnabled=bInRoundTunnels;bAnalyticOnly=true;bProfileDetailBand=false;
|
RoundTunnelsEnabled=bInRoundTunnels;bAnalyticOnly=true;bProfileDetailBand=false;
|
||||||
for(int32 A=0;A<3;++A)
|
for(int32 A=0;A<3;++A)
|
||||||
{
|
{
|
||||||
@@ -991,6 +991,9 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SetSharedDensitySource(const FStandaloneShapeWorld* Source)
|
||||||
|
{ SharedDensitySource=Source==this?nullptr:Source; }
|
||||||
|
|
||||||
bool HasEditTransaction() const { return !EditBrushes.empty(); }
|
bool HasEditTransaction() const { return !EditBrushes.empty(); }
|
||||||
FIntVector GetEditBoundsMin() const { return EditBoundsMin; }
|
FIntVector GetEditBoundsMin() const { return EditBoundsMin; }
|
||||||
FIntVector GetEditBoundsMax() const { return EditBoundsMax; }
|
FIntVector GetEditBoundsMax() const { return EditBoundsMax; }
|
||||||
@@ -1017,6 +1020,7 @@ public:
|
|||||||
// edge interpolation and normals to the existing mesher.
|
// edge interpolation and normals to the existing mesher.
|
||||||
float DensityAt(float X,float Y,float Z) const
|
float DensityAt(float X,float Y,float Z) const
|
||||||
{
|
{
|
||||||
|
if(SharedDensitySource)return SharedDensitySource->DensityAt(X,Y,Z);
|
||||||
if(bNativeOnly)
|
if(bNativeOnly)
|
||||||
{
|
{
|
||||||
if(!NativeOnlyContains(X,Y,Z))
|
if(!NativeOnlyContains(X,Y,Z))
|
||||||
@@ -1050,6 +1054,7 @@ public:
|
|||||||
|
|
||||||
float DensityAtLattice(int32 X,int32 Y,int32 Z) const
|
float DensityAtLattice(int32 X,int32 Y,int32 Z) const
|
||||||
{
|
{
|
||||||
|
if(SharedDensitySource)return SharedDensitySource->DensityAtLattice(X,Y,Z);
|
||||||
if(bNativeOnly)
|
if(bNativeOnly)
|
||||||
{
|
{
|
||||||
if(!NativeOnlyContains(static_cast<float>(X),static_cast<float>(Y),
|
if(!NativeOnlyContains(static_cast<float>(X),static_cast<float>(Y),
|
||||||
@@ -1089,6 +1094,7 @@ public:
|
|||||||
|
|
||||||
float DensityAtUnclamped(float X,float Y,float Z) const
|
float DensityAtUnclamped(float X,float Y,float Z) const
|
||||||
{
|
{
|
||||||
|
if(SharedDensitySource)return SharedDensitySource->DensityAtUnclamped(X,Y,Z);
|
||||||
if(bNativeOnly)
|
if(bNativeOnly)
|
||||||
{
|
{
|
||||||
if(!NativeOnlyContains(X,Y,Z))
|
if(!NativeOnlyContains(X,Y,Z))
|
||||||
@@ -1117,6 +1123,7 @@ public:
|
|||||||
|
|
||||||
VoxelForgeCore::DetailFieldSample DetailSampleAt(float X,float Y,float Z) const
|
VoxelForgeCore::DetailFieldSample DetailSampleAt(float X,float Y,float Z) const
|
||||||
{
|
{
|
||||||
|
if(SharedDensitySource)return SharedDensitySource->DetailSampleAt(X,Y,Z);
|
||||||
VoxelForgeCore::DetailFieldSample Sample;
|
VoxelForgeCore::DetailFieldSample Sample;
|
||||||
if(bNativeOnly)
|
if(bNativeOnly)
|
||||||
{RecordNativeOnlyViolation();return Sample;}
|
{RecordNativeOnlyViolation();return Sample;}
|
||||||
@@ -1135,6 +1142,11 @@ public:
|
|||||||
void DensityAtLatticeX8(int32 XStart,int32 XStep,int32 Y,int32 Z,
|
void DensityAtLatticeX8(int32 XStart,int32 XStep,int32 Y,int32 Z,
|
||||||
int32 Count,float OutDensity[8]) const
|
int32 Count,float OutDensity[8]) const
|
||||||
{
|
{
|
||||||
|
if(SharedDensitySource)
|
||||||
|
{
|
||||||
|
SharedDensitySource->DensityAtLatticeX8(XStart,XStep,Y,Z,Count,OutDensity);
|
||||||
|
return;
|
||||||
|
}
|
||||||
Count=std::clamp(Count,0,8);
|
Count=std::clamp(Count,0,8);
|
||||||
if(Count==0)return;
|
if(Count==0)return;
|
||||||
if(bNativeOnly)
|
if(bNativeOnly)
|
||||||
@@ -1241,6 +1253,8 @@ private:
|
|||||||
float* OutFloorInfluence=nullptr,
|
float* OutFloorInfluence=nullptr,
|
||||||
bool bApplyEdits=true) const
|
bool bApplyEdits=true) const
|
||||||
{
|
{
|
||||||
|
if(SharedDensitySource)return SharedDensitySource->AnalyticDensityAt(X,Y,Z,
|
||||||
|
bClampNearZero,OutDetailDisplacement,OutCaveInfluence,OutFloorInfluence,bApplyEdits);
|
||||||
if(OutDetailDisplacement)*OutDetailDisplacement=0.0f;
|
if(OutDetailDisplacement)*OutDetailDisplacement=0.0f;
|
||||||
if(OutCaveInfluence)*OutCaveInfluence=0.0f;
|
if(OutCaveInfluence)*OutCaveInfluence=0.0f;
|
||||||
if(OutFloorInfluence)*OutFloorInfluence=0.0f;
|
if(OutFloorInfluence)*OutFloorInfluence=0.0f;
|
||||||
@@ -1303,6 +1317,8 @@ public:
|
|||||||
bool GetNativeSurfaceHeightsAt(float X,float Y,int32 ChunkZ,
|
bool GetNativeSurfaceHeightsAt(float X,float Y,int32 ChunkZ,
|
||||||
float& OutGroundZ,float& OutCeilingZ) const
|
float& OutGroundZ,float& OutCeilingZ) const
|
||||||
{
|
{
|
||||||
|
if(SharedDensitySource)
|
||||||
|
return SharedDensitySource->GetNativeSurfaceHeightsAt(X,Y,ChunkZ,OutGroundZ,OutCeilingZ);
|
||||||
if(!WorldAssembly||!Owner||!Owner->Manager||!Owner->Generator
|
if(!WorldAssembly||!Owner||!Owner->Manager||!Owner->Generator
|
||||||
||!std::isfinite(X)||!std::isfinite(Y))return false;
|
||!std::isfinite(X)||!std::isfinite(Y))return false;
|
||||||
const std::int64_t chunkMinimum=static_cast<std::int64_t>(ChunkZ)*CHUNK_SIZE;
|
const std::int64_t chunkMinimum=static_cast<std::int64_t>(ChunkZ)*CHUNK_SIZE;
|
||||||
@@ -1339,6 +1355,7 @@ public:
|
|||||||
|
|
||||||
bool IsNativeSurfaceBox(const FIntVector& Origin,int32 Step,int32 Cells) const
|
bool IsNativeSurfaceBox(const FIntVector& Origin,int32 Step,int32 Cells) const
|
||||||
{
|
{
|
||||||
|
if(SharedDensitySource)return SharedDensitySource->IsNativeSurfaceBox(Origin,Step,Cells);
|
||||||
if(!WorldAssembly||!Owner||!Owner->Manager||Step<=0||Cells<=0)return false;
|
if(!WorldAssembly||!Owner||!Owner->Manager||Step<=0||Cells<=0)return false;
|
||||||
const int32 Pad=std::max(Step,1);
|
const int32 Pad=std::max(Step,1);
|
||||||
const int32 BMin=Origin.Z-Pad;
|
const int32 BMin=Origin.Z-Pad;
|
||||||
@@ -1377,6 +1394,8 @@ public:
|
|||||||
FBoxClassification ClassifyBoxDetailed(const FIntVector& Origin,int32 Step,int32 Cells,
|
FBoxClassification ClassifyBoxDetailed(const FIntVector& Origin,int32 Step,int32 Cells,
|
||||||
bool bUseNativeLatticeProof=false) const
|
bool bUseNativeLatticeProof=false) const
|
||||||
{
|
{
|
||||||
|
if(SharedDensitySource)
|
||||||
|
return SharedDensitySource->ClassifyBoxDetailed(Origin,Step,Cells,bUseNativeLatticeProof);
|
||||||
using Clock=std::chrono::steady_clock;
|
using Clock=std::chrono::steady_clock;
|
||||||
const auto Start=Clock::now();
|
const auto Start=Clock::now();
|
||||||
const int32 Pad=std::max(Step,1);
|
const int32 Pad=std::max(Step,1);
|
||||||
@@ -1557,6 +1576,7 @@ public:
|
|||||||
|
|
||||||
bool IsRock(int32 X,int32 Y,int32 Z) const
|
bool IsRock(int32 X,int32 Y,int32 Z) const
|
||||||
{
|
{
|
||||||
|
if(SharedDensitySource)return SharedDensitySource->IsRock(X,Y,Z);
|
||||||
if(!Contains(X,Y,Z))return EvaluateFast(X,Y,Z);
|
if(!Contains(X,Y,Z))return EvaluateFast(X,Y,Z);
|
||||||
const size_t Line=static_cast<size_t>(Y-Min[1])*Size[0]
|
const size_t Line=static_cast<size_t>(Y-Min[1])*Size[0]
|
||||||
+static_cast<size_t>(X-Min[0]);
|
+static_cast<size_t>(X-Min[0]);
|
||||||
@@ -2083,7 +2103,8 @@ private:
|
|||||||
bool NativeFloatingIslandDensityAt(float X,float Y,float Z,float& OutDensity) const
|
bool NativeFloatingIslandDensityAt(float X,float Y,float Z,float& OutDensity) const
|
||||||
{
|
{
|
||||||
if(!bUseNativeManifestFields||!WorldAssembly||!Owner||!Owner->Manager||!Owner->Generator
|
if(!bUseNativeManifestFields||!WorldAssembly||!Owner||!Owner->Manager||!Owner->Generator
|
||||||
||!std::isfinite(X)||!std::isfinite(Y)||!std::isfinite(Z))return false;
|
||!std::isfinite(X)||!std::isfinite(Y)||!std::isfinite(Z))
|
||||||
|
return false;
|
||||||
// Convert Core query Z back to the owner's legacy chunk coordinate.
|
// Convert Core query Z back to the owner's legacy chunk coordinate.
|
||||||
// Convertir Z du Core vers la coordonnee de chunk legacy du proprietaire.
|
// Convertir Z du Core vers la coordonnee de chunk legacy du proprietaire.
|
||||||
const double CoreVoxelsPerLegacyChunk=GetCoreVoxelsPerOwnerChunk();
|
const double CoreVoxelsPerLegacyChunk=GetCoreVoxelsPerOwnerChunk();
|
||||||
@@ -4823,6 +4844,11 @@ public:
|
|||||||
void DensityAtLatticeZRun(int32 X,int32 Y,int32 ZStart,int32 ZStep,
|
void DensityAtLatticeZRun(int32 X,int32 Y,int32 ZStart,int32 ZStep,
|
||||||
int32 Count,float* OutDensity) const
|
int32 Count,float* OutDensity) const
|
||||||
{
|
{
|
||||||
|
if(SharedDensitySource)
|
||||||
|
{
|
||||||
|
SharedDensitySource->DensityAtLatticeZRun(X,Y,ZStart,ZStep,Count,OutDensity);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if(Count<=0||OutDensity==nullptr)return;
|
if(Count<=0||OutDensity==nullptr)return;
|
||||||
if(bNativeOnly)
|
if(bNativeOnly)
|
||||||
{
|
{
|
||||||
@@ -5353,6 +5379,7 @@ private:
|
|||||||
|
|
||||||
FStandaloneOwnerWorld* Owner=nullptr;
|
FStandaloneOwnerWorld* Owner=nullptr;
|
||||||
const VoxelForgeCore::WorldAssemblyManifest* WorldAssembly=nullptr;
|
const VoxelForgeCore::WorldAssemblyManifest* WorldAssembly=nullptr;
|
||||||
|
const FStandaloneShapeWorld* SharedDensitySource=nullptr;
|
||||||
int32 WorkerCount=6;
|
int32 WorkerCount=6;
|
||||||
const VoxelForgeCore::JobScheduler* Scheduler=nullptr;
|
const VoxelForgeCore::JobScheduler* Scheduler=nullptr;
|
||||||
bool RoundTunnelsEnabled=false;
|
bool RoundTunnelsEnabled=false;
|
||||||
|
|||||||
@@ -1386,6 +1386,8 @@ namespace VoxelForgeCore
|
|||||||
compiledRegions[i]=std::move(region);
|
compiledRegions[i]=std::move(region);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for(const auto& region:compiledRegions)
|
||||||
|
if(region)region->SetSharedDensitySource(impl_->shape.get());
|
||||||
{
|
{
|
||||||
std::unordered_map<TileKey,std::shared_ptr<FStandaloneShapeWorld>,TileKeyHash> publish;
|
std::unordered_map<TileKey,std::shared_ptr<FStandaloneShapeWorld>,TileKeyHash> publish;
|
||||||
publish.reserve(stats.tileKeysCovered);
|
publish.reserve(stats.tileKeysCovered);
|
||||||
@@ -2021,7 +2023,8 @@ namespace VoxelForgeCore
|
|||||||
MeshBandMinZVoxel,MeshBandMaxZVoxel,&sampleCount,nullptr,nullptr,
|
MeshBandMinZVoxel,MeshBandMaxZVoxel,&sampleCount,nullptr,nullptr,
|
||||||
(options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr,
|
(options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr,
|
||||||
options.profileMesherStages?&McStageTimings:nullptr,
|
options.profileMesherStages?&McStageTimings:nullptr,
|
||||||
options.auditUniformBlockProofs?&BlockProofAudit:nullptr);
|
options.auditUniformBlockProofs?&BlockProofAudit:nullptr,
|
||||||
|
options.forceFullDensityCapture);
|
||||||
if(options.auditUniformBlockProofs)
|
if(options.auditUniformBlockProofs)
|
||||||
{
|
{
|
||||||
out.diagnostics.uniformBlockProofsChecked=BlockProofAudit.CheckedBlocks;
|
out.diagnostics.uniformBlockProofsChecked=BlockProofAudit.CheckedBlocks;
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# VoxelForge M3 Linux13 — route-invariant density
|
||||||
|
|
||||||
|
## Field decision and fix
|
||||||
|
|
||||||
|
The approved reference is the Core base shape built from the reviewed owner manifest: `Heightfield2D`
|
||||||
|
for SurfaceWorld and `FloatingIslandFieldV1` for FloatingIslands. L12's base route sampled that
|
||||||
|
field. Its analytic pages instead fell through to the copied legacy owner `GetDensityAt` evaluator,
|
||||||
|
so the same key could sample different values after the player crossed into a page. In the L12
|
||||||
|
SurfaceWorld audit, all 48 base-covered LOD1 candidates had different density; 10 also differed in
|
||||||
|
mesh, payload, and topology. A 214,375-sample L12 negative-control run measured max/mean density
|
||||||
|
delta `0.076171875 / 0.0133467594`, 29 sign changes, 170 edge-crossing changes, and up to 1.7778
|
||||||
|
voxels of surface displacement. The manifest field is the approved Core world and is the reference
|
||||||
|
kept here.
|
||||||
|
|
||||||
|
Changing the page manifest flag alone did not fix FloatingIslands: 8/8 page pairs still failed,
|
||||||
|
with max density delta `433.925781`, 262,676 sign changes, and 17,188 edge-crossing changes across
|
||||||
|
343,000 samples. The source fix binds each compiled page to its world's base `FStandaloneShapeWorld`
|
||||||
|
before publication. Page density, lattice, detail, classification, and solid queries now delegate
|
||||||
|
to that one evaluator; the legacy page-local evaluator is no longer an independent output route.
|
||||||
|
`WorldAssemblyEvaluatorVersion` is bumped from 5 to 6.
|
||||||
|
|
||||||
|
## Route parity
|
||||||
|
|
||||||
|
The L12 evaluator-v5 negative control fails as required: 5/5 selected base/page pairs mismatch. After the fix,
|
||||||
|
four audits passed: SurfaceWorld seeds 0, 1, and 17, plus FloatingIslands seed 0.
|
||||||
|
|
||||||
|
| Compared routes | Pairs | Mismatches |
|
||||||
|
|---|---:|---:|
|
||||||
|
| Base vs page (28 clipmap-requested pages; 26 base-covered page controls) | 54 | 0 |
|
||||||
|
| Base vs native-only shape-free route (SurfaceWorld LOD0) | 27 | 0 |
|
||||||
|
| Edited base vs edited page | 4 | 0 |
|
||||||
|
| Same key built in initial and moved-world history | 2 | 0 |
|
||||||
|
| Tile samples vs exact Core queries at LOD2–8 | 56 | 0 |
|
||||||
|
|
||||||
|
All 87 tile-generation pairs match density, mesh, payload, and topology hashes. The 56 higher-LOD
|
||||||
|
pairs compare the sampled tile lattice to direct `densityAt` / `densityAtLattice` queries; these
|
||||||
|
also match all sampled densities and solid/air signs. The owner fixture has
|
||||||
|
`full_res_clip_levels=2`, so analytic page routes exist only at LOD0–1. In total, page comparisons
|
||||||
|
covered 2,315,250 density samples with zero delta, sign, edge-crossing, or surface-root differences;
|
||||||
|
6,172,229 exact-query samples had zero mismatches. The audit exits nonzero on any output-hash,
|
||||||
|
sample, route-verification, exact-query, or proof-violation mismatch.
|
||||||
|
|
||||||
|
The history probe builds the same LOD0 key in a second Core world after a 96 m camera move. Both
|
||||||
|
SurfaceWorld and FloatingIslands match all four hashes across those two build histories. The edit
|
||||||
|
probe applies a sequence-1 density offset centered on a sampled sign crossing; it changes the field,
|
||||||
|
and edited base/page results still match.
|
||||||
|
|
||||||
|
### Page proof audit
|
||||||
|
|
||||||
|
The fill-and-check audit generated page tiles with full density capture and uniform-block proof
|
||||||
|
verification, so it checked the field those pages actually sample. Across the four audits it checked
|
||||||
|
1,274 blocks / 1,695,694 samples: 518 blocks used the exact fallback, 6 were unknown, and there were
|
||||||
|
zero false proofs or violations. SurfaceWorld walkability passed 5,642/5,642, 6,080/6,080, and
|
||||||
|
4,785/4,785 samples for seeds 0, 1, and 17; FloatingIslands passed 4,000/4,000. Shape readiness
|
||||||
|
passed 27/27 near-ring tiles where eligible. FloatingIslands has no eligible native-only tiles.
|
||||||
|
|
||||||
|
## Cost, hashing off
|
||||||
|
|
||||||
|
W12, seed 0, `--force-generate`, camera `0,-35,-487.94`, with the same crossing setup as L12:
|
||||||
|
|
||||||
|
| Move | Entering tiles | L12 crossing | L13 crossing | L13 peak RSS |
|
||||||
|
|---|---:|---:|---:|---:|
|
||||||
|
| 8 m | 144 | 0.0347 s | 0.0399 s | 468 MiB |
|
||||||
|
| 16 m | 288 | 0.1249 s | 0.0856 s | 513 MiB |
|
||||||
|
| 24 m | 432 | 0.1635 s | 0.1242 s | 512 MiB |
|
||||||
|
| 32 m | 576 | 0.1717 s | 0.1692 s | 511 MiB |
|
||||||
|
| 1 km | 2,260 | 1.2474 s | 0.7696 s | 673 MiB |
|
||||||
|
|
||||||
|
Three W12 spawn runs had medians of 157 ms first tile, 180 ms near ring, and 1.499 s whole view;
|
||||||
|
L12 medians were 146 ms, 161 ms, and 1.361 s. Each spawn run generated the same 2,553-tile
|
||||||
|
benchmark view. The final timings are in `Saved/Linux/l13-crossing-final-*.log` and
|
||||||
|
`Saved/Linux/l13-spawn-final-*.log`.
|
||||||
|
|
||||||
|
## Determinism and invariants
|
||||||
|
|
||||||
|
All 28 required configurations passed: SurfaceWorld and FloatingIslands seed 0; Clang
|
||||||
|
`-ffp-contract=off` and GCC default; W1/W8/W12, with five W12 repeats per compiler and recipe. Each
|
||||||
|
run matched all 12 rows (full detail, whole view, LOD0–8, exact queries) against L12/L11.
|
||||||
|
|
||||||
|
| Recipe | Full detail D/M/P/T | Whole view D/M/P/T | Exact density / unclamped / detail / collision |
|
||||||
|
|---|---|---|---|
|
||||||
|
| SurfaceWorld | `0x0ee0579bd15cbbe4 / 0x32e5d4964d610fee / 0x1932b96ee4b1d910 / 0x69743c6bad3447dc` | `0xc10697a8f48380c7 / 0xe75c9119a1498cd5 / 0x49a9a8d1835a6c8c / 0xedcb23288b5e8724` | `0xe4d4a8eb95b35933 / 0xe4d4a8eb95b35933 / 0xefcba20c0c70c415 / 0xe604823a249029bf` |
|
||||||
|
| FloatingIslands | `0x6d842d2878b08ef6 / 0x4b868a3184f08035 / 0x2cedf2236788752d / 0xeac17d5e0cb5044a` | `0x5be4d8cde79b2a76 / 0x96f75041f6861b3a / 0x3700a516cc89d50c / 0x6b99948d6eab4f21` | `0x61daded9c303becb / 0x61daded9c303becb / 0xefcba20c0c70c415 / 0x84d37463be9b0573` |
|
||||||
|
|
||||||
|
The crossing output changes as intended. The 24 m W1 and W12 moved-tile hashes match each other:
|
||||||
|
`0x337ca62131576728 / 0x7612f48e2ddbbaab / 0x69a67ed4add83b87 / 0xf11ac1ebd0f7ddb8` (D/M/P/T),
|
||||||
|
replacing L12's `0x174588abdc8c0d2c / 0x4c401e27d9d05dea / 0xeb10d9801c5ef125 / 0xf59d12304d0dcb4a`.
|
||||||
|
|
||||||
|
The L3 FP-contract preamble and runtime self-test, L4 `FLazy`, L7 shared column cache, L9/L12
|
||||||
|
proofs, and L11 native-only readiness/crash fixes were preserved. The L6 screen-space controls
|
||||||
|
still pass their six same-LOD checks and reject the missing/floating-surface, global-octave, and
|
||||||
|
stride-2 negative controls as required. Its whole gate still rejects the known far-field error over
|
||||||
|
the 1 px bound (exit 6); the route binding does not affect those LOD3–8 base-field comparisons.
|
||||||
|
The asynchronous MPSC/Epoch path and `ARCHITECTURE.md` §8.10 invariants are unchanged. No Unreal
|
||||||
|
adapter files were touched; the Core exact-query and collision-sign routes were checked, but Unreal
|
||||||
|
collision cooking was not built here. `ARCHITECTURE.md` §8.36 and `CODEMAP.md` §3.22 document the new
|
||||||
|
route law.
|
||||||
|
|
||||||
|
Builds and runs used CPUs 0–11, at most 12 workers, a 6 GiB address-space cap, and a 6,000,000,000
|
||||||
|
byte RSS monitor. The governor remained `powersave`. Peak sampled RSS was 904 MiB across runs
|
||||||
|
(673 MiB in the no-hash cost table); the GCC build process group peaked at 4.27 GiB. Clang and GCC
|
||||||
|
builds passed. No system changes or Unreal builds were made.
|
||||||
|
|
||||||
|
## Failed and retried runs
|
||||||
|
|
||||||
|
- `l13-route-baseline-s0-w12.log` exited 2 because the inferred binary-relative owner JSON path did
|
||||||
|
not exist; reruns used the explicit repository JSON. The first baseline capture used the wrong
|
||||||
|
camera, then an audit pass had empty proof-skipped density captures; the final L12 negative
|
||||||
|
control is `l13-route-baseline-s0-w12-r4.log`.
|
||||||
|
- `l13-route-fi-diagnostic.log` and `l13-route-fi-diagnostic-r2.log` intentionally exposed the
|
||||||
|
remaining FloatingIslands split before the shared-source binding; both exit 9 on 8/8 mismatches.
|
||||||
|
- `l13-route-edit-s0.log` exited 9 because its brush did not change the selected tile. Centering the
|
||||||
|
brush on a sampled sign crossing fixed the audit in `l13-route-edit-s0-r2.log`.
|
||||||
|
- `l13-route-history-sw-s0.log` exited 9 because the second-world probe exceeded the 4,096-node
|
||||||
|
region compiler envelope. The retry compiles one matching LOD0 anchor tile and passes.
|
||||||
|
- The first `l13-cost-*` measurements omitted `--force-generate` and used the owner JSON start;
|
||||||
|
those are not used above. The final crossing and spawn runs use L12's camera and force-generation
|
||||||
|
setting. The screen-space run's exit 6 is its expected far-field rejection, not a retried run.
|
||||||
|
- All final builds, the 28 hash runs, eight no-hash cost runs, and two crossing-hash runs completed
|
||||||
|
within their limits. No commit, push, or stash was made.
|
||||||
|
|
||||||
|
## Multiplayer note for the owner
|
||||||
|
|
||||||
|
Peers with the same manifest, seed, evaluator version 6, and tile key now reconstruct the same Core
|
||||||
|
density and tile output through each available base, page, or native-only route, regardless of
|
||||||
|
arrival history; terrain geometry still does not need to cross the network. Player edits remain an
|
||||||
|
explicit versioned diff that must be ordered and synchronized between peers.
|
||||||
|
|
||||||
|
## Orchestrator review (Claude)
|
||||||
|
|
||||||
|
- **Spawn cost re-run by me, 5 interleaved W12 pairs, no hashing:** whole view L12
|
||||||
|
1.385/1.405/1.419/1.443/1.460 s vs L13 1.401/1.448/1.430/1.435/1.451 s (median 1.419 → 1.435 s,
|
||||||
|
+1.1%, within noise); near ring 0.168 → 0.171 s. The +10% in the cost table above compares L13 runs
|
||||||
|
with L12 numbers from another session; interleaved, there is no measurable spawn regression.
|
||||||
|
- The fix is structural: pages are bound to their world's single base `FStandaloneShapeWorld`
|
||||||
|
evaluator before publication, so routes can't diverge by construction. The negative control fails
|
||||||
|
on L12 (5/5), and the 87 tile pairs, 56 exact-query pairs and 2.3M page samples show 0 mismatches
|
||||||
|
after. The page lifetime is safe because the base shape is owned by the `World` and outlives its pages.
|
||||||
|
- Crossing output changed, as intended (evaluator version 5 → 6); the spawn and island worlds are
|
||||||
|
unchanged.
|
||||||
@@ -43,6 +43,7 @@ namespace
|
|||||||
bool AuditUniformBlockProofs=false;
|
bool AuditUniformBlockProofs=false;
|
||||||
bool ProfileLodWork=false;
|
bool ProfileLodWork=false;
|
||||||
bool AuditBasePageEquivalence=false;
|
bool AuditBasePageEquivalence=false;
|
||||||
|
bool AuditRouteParity=false;
|
||||||
bool RegionCrossing=false;
|
bool RegionCrossing=false;
|
||||||
bool AuditTileShapeReadiness=false;
|
bool AuditTileShapeReadiness=false;
|
||||||
int DiagnosticSlowShapeCompileMs=0;
|
int DiagnosticSlowShapeCompileMs=0;
|
||||||
@@ -122,6 +123,7 @@ namespace
|
|||||||
" --audit-uniform-block-proofs (fill and check every interval-certified block)\n"
|
" --audit-uniform-block-proofs (fill and check every interval-certified block)\n"
|
||||||
" --profile-lod-work (report per-LOD density sample counts)\n"
|
" --profile-lod-work (report per-LOD density sample counts)\n"
|
||||||
" --audit-base-page-equivalence (hash one tile through both shape routes)\n"
|
" --audit-base-page-equivalence (hash one tile through both shape routes)\n"
|
||||||
|
" --audit-route-parity (compare page, native-only, exact-query and mesh routes)\n"
|
||||||
" --audit-tile-shape-readiness (run a deliberately mis-marked tile against the native-only guard)\n"
|
" --audit-tile-shape-readiness (run a deliberately mis-marked tile against the native-only guard)\n"
|
||||||
" --diagnostic-slow-shape-compile-ms=N (diagnostic overlap control; default 0)\n"
|
" --diagnostic-slow-shape-compile-ms=N (diagnostic overlap control; default 0)\n"
|
||||||
" --render-distance-chunks=N (0 disables the extra far ring)\n"
|
" --render-distance-chunks=N (0 disables the extra far ring)\n"
|
||||||
@@ -142,6 +144,7 @@ namespace
|
|||||||
else if(arg=="--audit-uniform-block-proofs")options.AuditUniformBlockProofs=true;
|
else if(arg=="--audit-uniform-block-proofs")options.AuditUniformBlockProofs=true;
|
||||||
else if(arg=="--profile-lod-work")options.ProfileLodWork=true;
|
else if(arg=="--profile-lod-work")options.ProfileLodWork=true;
|
||||||
else if(arg=="--audit-base-page-equivalence")options.AuditBasePageEquivalence=true;
|
else if(arg=="--audit-base-page-equivalence")options.AuditBasePageEquivalence=true;
|
||||||
|
else if(arg=="--audit-route-parity")options.AuditRouteParity=true;
|
||||||
else if(arg=="--region-crossing")options.RegionCrossing=true;
|
else if(arg=="--region-crossing")options.RegionCrossing=true;
|
||||||
else if(arg=="--audit-tile-shape-readiness")options.AuditTileShapeReadiness=true;
|
else if(arg=="--audit-tile-shape-readiness")options.AuditTileShapeReadiness=true;
|
||||||
else if(arg=="--auto-surface-camera")options.AutoSurfaceCamera=true;
|
else if(arg=="--auto-surface-camera")options.AutoSurfaceCamera=true;
|
||||||
@@ -373,5 +376,6 @@ int main(int argc,char** argv)
|
|||||||
options.CameraPitchDegrees,options.ScreenSpaceGate,options.UseSharedColumnCache,
|
options.CameraPitchDegrees,options.ScreenSpaceGate,options.UseSharedColumnCache,
|
||||||
cameraStepVoxels,options.AuditUniformBlockProofs,options.ProfileLodWork,
|
cameraStepVoxels,options.AuditUniformBlockProofs,options.ProfileLodWork,
|
||||||
options.RegionCrossing,options.AuditTileShapeReadiness,
|
options.RegionCrossing,options.AuditTileShapeReadiness,
|
||||||
options.DiagnosticSlowShapeCompileMs,options.AuditBasePageEquivalence);
|
options.DiagnosticSlowShapeCompileMs,options.AuditBasePageEquivalence,
|
||||||
|
options.AuditRouteParity);
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <bit>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@@ -23,6 +24,113 @@
|
|||||||
|
|
||||||
namespace StandaloneStreamBenchmark
|
namespace StandaloneStreamBenchmark
|
||||||
{
|
{
|
||||||
|
struct FRouteDensityComparison
|
||||||
|
{
|
||||||
|
std::uint64_t samples=0;
|
||||||
|
std::uint64_t signMismatches=0;
|
||||||
|
std::uint64_t crossingEdgeMismatches=0;
|
||||||
|
std::uint64_t sharedCrossingEdges=0;
|
||||||
|
std::uint64_t exactQuerySamples=0;
|
||||||
|
std::uint64_t exactQueryMismatches=0;
|
||||||
|
double maxAbsoluteDelta=0.0;
|
||||||
|
double sumAbsoluteDelta=0.0;
|
||||||
|
double maxSurfaceDisplacementVoxels=0.0;
|
||||||
|
double sumSurfaceDisplacementVoxels=0.0;
|
||||||
|
bool valid=false;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline FRouteDensityComparison CompareRouteDensities(
|
||||||
|
const VoxelForgeCore::TileMesh& A,const VoxelForgeCore::TileMesh& B,
|
||||||
|
const VoxelForgeCore::World& World)
|
||||||
|
{
|
||||||
|
FRouteDensityComparison Result;
|
||||||
|
if(A.debugDensitySamples.empty()
|
||||||
|
||A.debugDensitySamples.size()!=B.debugDensitySamples.size())return Result;
|
||||||
|
const double Root=std::cbrt(static_cast<double>(A.debugDensitySamples.size()));
|
||||||
|
const std::int32_t Dim=static_cast<std::int32_t>(std::llround(Root));
|
||||||
|
if(Dim<4||static_cast<std::size_t>(Dim)*Dim*Dim!=A.debugDensitySamples.size())return Result;
|
||||||
|
const std::int32_t GridDim=Dim-3;
|
||||||
|
const std::int32_t Extent=CHUNK_SIZE<<A.key.lod;
|
||||||
|
if(GridDim<=0||Extent%GridDim!=0)return Result;
|
||||||
|
const std::int32_t Step=Extent/GridDim;
|
||||||
|
const FVoxelTileKey NativeKey(
|
||||||
|
FIntVector(A.key.coordinate.x,A.key.coordinate.y,A.key.coordinate.z),A.key.lod);
|
||||||
|
const FIntVector Origin=NativeKey.OriginVoxels();
|
||||||
|
const auto Index=[Dim](std::int32_t X,std::int32_t Y,std::int32_t Z)
|
||||||
|
{return (static_cast<std::size_t>(Z)*Dim+Y)*Dim+X;};
|
||||||
|
const auto IsSolid=[](float Density){return Density<0.0f;};
|
||||||
|
const auto EdgeCrosses=[&](const std::vector<float>& Values,
|
||||||
|
std::int32_t X0,std::int32_t Y0,std::int32_t Z0,
|
||||||
|
std::int32_t X1,std::int32_t Y1,std::int32_t Z1)
|
||||||
|
{
|
||||||
|
return IsSolid(Values[Index(X0,Y0,Z0)])!=IsSolid(Values[Index(X1,Y1,Z1)]);
|
||||||
|
};
|
||||||
|
for(std::int32_t Z=0;Z<Dim;++Z)
|
||||||
|
for(std::int32_t Y=0;Y<Dim;++Y)
|
||||||
|
for(std::int32_t X=0;X<Dim;++X)
|
||||||
|
{
|
||||||
|
const std::size_t I=Index(X,Y,Z);
|
||||||
|
const float DA=A.debugDensitySamples[I],DB=B.debugDensitySamples[I];
|
||||||
|
if(!std::isfinite(DA)||!std::isfinite(DB))return Result;
|
||||||
|
const double Delta=std::abs(static_cast<double>(DA)-DB);
|
||||||
|
Result.maxAbsoluteDelta=std::max(Result.maxAbsoluteDelta,Delta);
|
||||||
|
Result.sumAbsoluteDelta+=Delta;
|
||||||
|
Result.signMismatches+=IsSolid(DA)!=IsSolid(DB)?1u:0u;
|
||||||
|
++Result.samples;
|
||||||
|
|
||||||
|
const std::int32_t WorldX=Origin.X+(X-1)*Step;
|
||||||
|
const std::int32_t WorldY=Origin.Y+(Y-1)*Step;
|
||||||
|
const std::int32_t WorldZ=Origin.Z+(Z-1)*Step;
|
||||||
|
const VoxelForgeCore::Vec3i Lattice{WorldX,WorldY,WorldZ};
|
||||||
|
const float ExactLattice=World.densityAtLattice(Lattice);
|
||||||
|
const float ExactPoint=World.densityAt({static_cast<double>(WorldX),
|
||||||
|
static_cast<double>(WorldY),static_cast<double>(WorldZ)});
|
||||||
|
++Result.exactQuerySamples;
|
||||||
|
if(std::bit_cast<std::uint32_t>(DA)!=std::bit_cast<std::uint32_t>(ExactLattice)
|
||||||
|
||std::bit_cast<std::uint32_t>(DA)!=std::bit_cast<std::uint32_t>(ExactPoint))
|
||||||
|
++Result.exactQueryMismatches;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto CompareEdge=[&](std::int32_t X0,std::int32_t Y0,std::int32_t Z0,
|
||||||
|
std::int32_t X1,std::int32_t Y1,std::int32_t Z1)
|
||||||
|
{
|
||||||
|
const bool CrossA=EdgeCrosses(A.debugDensitySamples,X0,Y0,Z0,X1,Y1,Z1);
|
||||||
|
const bool CrossB=EdgeCrosses(B.debugDensitySamples,X0,Y0,Z0,X1,Y1,Z1);
|
||||||
|
if(CrossA!=CrossB)++Result.crossingEdgeMismatches;
|
||||||
|
if(!CrossA||!CrossB)return;
|
||||||
|
const float A0=A.debugDensitySamples[Index(X0,Y0,Z0)];
|
||||||
|
const float A1=A.debugDensitySamples[Index(X1,Y1,Z1)];
|
||||||
|
const float B0=B.debugDensitySamples[Index(X0,Y0,Z0)];
|
||||||
|
const float B1=B.debugDensitySamples[Index(X1,Y1,Z1)];
|
||||||
|
const double TA=static_cast<double>(A0)/(static_cast<double>(A0)-A1);
|
||||||
|
const double TB=static_cast<double>(B0)/(static_cast<double>(B0)-B1);
|
||||||
|
const double Displacement=std::abs(TA-TB)*Step;
|
||||||
|
Result.maxSurfaceDisplacementVoxels=std::max(
|
||||||
|
Result.maxSurfaceDisplacementVoxels,Displacement);
|
||||||
|
Result.sumSurfaceDisplacementVoxels+=Displacement;
|
||||||
|
++Result.sharedCrossingEdges;
|
||||||
|
};
|
||||||
|
for(std::int32_t Z=0;Z<Dim;++Z)
|
||||||
|
for(std::int32_t Y=0;Y<Dim;++Y)
|
||||||
|
for(std::int32_t X=0;X<Dim;++X)
|
||||||
|
{
|
||||||
|
if(X+1<Dim)CompareEdge(X,Y,Z,X+1,Y,Z);
|
||||||
|
if(Y+1<Dim)CompareEdge(X,Y,Z,X,Y+1,Z);
|
||||||
|
if(Z+1<Dim)CompareEdge(X,Y,Z,X,Y,Z+1);
|
||||||
|
}
|
||||||
|
Result.valid=true;
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool SameTileOutput(const VoxelForgeCore::TileMesh& A,
|
||||||
|
const VoxelForgeCore::TileMesh& B)
|
||||||
|
{
|
||||||
|
return A.hasDensityHash&&B.hasDensityHash&&A.hasMeshHash&&B.hasMeshHash
|
||||||
|
&&A.hasPayloadHash&&B.hasPayloadHash&&A.hasTopologyHash&&B.hasTopologyHash
|
||||||
|
&&A.densityHash==B.densityHash&&A.meshHash==B.meshHash
|
||||||
|
&&A.payloadHash==B.payloadHash&&A.topologyHash==B.topologyHash;
|
||||||
|
}
|
||||||
|
|
||||||
struct FSceneExportWriter
|
struct FSceneExportWriter
|
||||||
{
|
{
|
||||||
std::ofstream File;
|
std::ofstream File;
|
||||||
@@ -145,7 +253,7 @@ namespace StandaloneStreamBenchmark
|
|||||||
bool bAuditUniformBlockProofs=false,bool bReportLodWorkProfile=false,
|
bool bAuditUniformBlockProofs=false,bool bReportLodWorkProfile=false,
|
||||||
bool bRegionCrossing=false,
|
bool bRegionCrossing=false,
|
||||||
bool bAuditTileShapeReadiness=false,int DiagnosticSlowShapeCompileMs=0,
|
bool bAuditTileShapeReadiness=false,int DiagnosticSlowShapeCompileMs=0,
|
||||||
bool bAuditBasePageEquivalence=false)
|
bool bAuditBasePageEquivalence=false,bool bAuditRouteParity=false)
|
||||||
{
|
{
|
||||||
using Clock=std::chrono::steady_clock;
|
using Clock=std::chrono::steady_clock;
|
||||||
FStandaloneOwnerWorld& World=*OwnerWorld;
|
FStandaloneOwnerWorld& World=*OwnerWorld;
|
||||||
@@ -472,6 +580,447 @@ namespace StandaloneStreamBenchmark
|
|||||||
return 9;
|
return 9;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(bAuditRouteParity)
|
||||||
|
{
|
||||||
|
struct FRouteAuditCandidate
|
||||||
|
{
|
||||||
|
VoxelForgeCore::TileKey Key;
|
||||||
|
bool bWouldRequestPage=false;
|
||||||
|
};
|
||||||
|
struct FRouteAuditRow
|
||||||
|
{
|
||||||
|
FRouteAuditCandidate Candidate;
|
||||||
|
std::optional<VoxelForgeCore::TileMesh> Base;
|
||||||
|
};
|
||||||
|
VoxelForgeCore::TileGenerationOptions CaptureOptions;
|
||||||
|
CaptureOptions.hashDensity=true;
|
||||||
|
CaptureOptions.captureDensitySamples=true;
|
||||||
|
CaptureOptions.forceFullDensityCapture=true;
|
||||||
|
CaptureOptions.profileMesherStages=true;
|
||||||
|
std::vector<FRouteAuditRow> Rows;
|
||||||
|
Rows.reserve(static_cast<std::size_t>(World.FullResClipLevels)*8);
|
||||||
|
std::vector<VoxelForgeCore::TileKey> PageKeys;
|
||||||
|
PageKeys.reserve(Rows.capacity());
|
||||||
|
const int32 AuditPageLODs=std::min(World.FullResClipLevels,3);
|
||||||
|
std::vector<const FStandaloneDesiredTileSet*> CandidateSets{&Desired};
|
||||||
|
if(bHasCameraStep)CandidateSets.push_back(&MovedDesired);
|
||||||
|
for(int32 LOD=0;LOD<AuditPageLODs;++LOD)
|
||||||
|
{
|
||||||
|
for(const bool bPageRequested:{true,false})
|
||||||
|
{
|
||||||
|
int32 SurfaceCandidates=0;
|
||||||
|
bool bHaveFlatCandidate=false;
|
||||||
|
int32 Inspected=0;
|
||||||
|
for(const FStandaloneDesiredTileSet* CandidateSet:CandidateSets)
|
||||||
|
{
|
||||||
|
for(const FVoxelTileKey& Tile:CandidateSet->Tiles)
|
||||||
|
{
|
||||||
|
if(Tile.Level!=LOD)continue;
|
||||||
|
const VoxelForgeCore::TileKey Key{{Tile.Coord.X,Tile.Coord.Y,Tile.Coord.Z},Tile.Level};
|
||||||
|
if(Shape.isTileShapeFree(Key)
|
||||||
|
||Shape.needsTileRegion(Key)!=bPageRequested)continue;
|
||||||
|
if(Inspected++>=256)break;
|
||||||
|
std::string BaseError;
|
||||||
|
auto Base=Shape.generateTile(Key,CaptureOptions,BaseError);
|
||||||
|
if(!Base)
|
||||||
|
{
|
||||||
|
std::printf("[RouteParityCandidate] route=%s key=(%d,%d,%d,lod%d) base=FAIL error=%s\n",
|
||||||
|
bPageRequested?"actual-page":"base-covered-page",Key.coordinate.x,
|
||||||
|
Key.coordinate.y,Key.coordinate.z,Key.lod,BaseError.c_str());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const bool bHasSurface=!Base->vertices.empty()&&!Base->indices.empty();
|
||||||
|
if(bHasSurface&&SurfaceCandidates<3)
|
||||||
|
{
|
||||||
|
Rows.push_back({{Key,bPageRequested},std::move(Base)});
|
||||||
|
PageKeys.push_back(Key);
|
||||||
|
++SurfaceCandidates;
|
||||||
|
}
|
||||||
|
else if(!bHasSurface&&!bHaveFlatCandidate)
|
||||||
|
{
|
||||||
|
Rows.push_back({{Key,bPageRequested},std::move(Base)});
|
||||||
|
PageKeys.push_back(Key);
|
||||||
|
bHaveFlatCandidate=true;
|
||||||
|
}
|
||||||
|
if(SurfaceCandidates>=3&&bHaveFlatCandidate)break;
|
||||||
|
}
|
||||||
|
if(SurfaceCandidates>=3&&bHaveFlatCandidate)break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(Rows.empty())
|
||||||
|
{
|
||||||
|
std::printf("[RouteParityAudit] status=UNAVAILABLE reason=no_page_route_candidates\n");
|
||||||
|
Streamer.Stop();
|
||||||
|
return 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
VoxelForgeCore::TileGenerationOptions PageOptions=CaptureOptions;
|
||||||
|
PageOptions.auditUniformBlockProofs=true;
|
||||||
|
int32 BasePageMismatches=0,PageRoutesVerified=0;
|
||||||
|
std::uint64_t PageProofBlocks=0,PageProofSamples=0,PageProofFallbacks=0,
|
||||||
|
PageProofUnknown=0,PageProofViolations=0;
|
||||||
|
std::uint64_t PageExactSamples=0,PageExactMismatches=0;
|
||||||
|
double MaxDensityDelta=0.0,SumDensityDelta=0.0;
|
||||||
|
std::uint64_t ComparedDensitySamples=0,SignMismatches=0,
|
||||||
|
CrossingEdgeMismatches=0,SharedCrossingEdges=0;
|
||||||
|
double MaxSurfaceDisplacement=0.0,SumSurfaceDisplacement=0.0;
|
||||||
|
for(const FRouteAuditRow& Row:Rows)
|
||||||
|
if(Row.Base)
|
||||||
|
{
|
||||||
|
const FRouteDensityComparison Query=CompareRouteDensities(
|
||||||
|
*Row.Base,*Row.Base,Shape);
|
||||||
|
PageExactSamples+=Query.exactQuerySamples;
|
||||||
|
PageExactMismatches+=Query.exactQueryMismatches;
|
||||||
|
}
|
||||||
|
else ++BasePageMismatches;
|
||||||
|
VoxelForgeCore::TileRegionCompileStats PageStats;
|
||||||
|
std::size_t CoveredPageKeys=0;
|
||||||
|
std::string PageCompileError;
|
||||||
|
const bool bPagesCompiled=Shape.compileTileRegion(PageKeys,
|
||||||
|
static_cast<std::uint32_t>(std::clamp(WorkerCount,1,16)),Scheduler,
|
||||||
|
PageCompileError,&CoveredPageKeys,&PageStats,true,true);
|
||||||
|
if(!bPagesCompiled)
|
||||||
|
{
|
||||||
|
std::printf("[RouteParityAudit] page_compile=FAIL error=%s\n",PageCompileError.c_str());
|
||||||
|
Streamer.Stop();
|
||||||
|
return 9;
|
||||||
|
}
|
||||||
|
for(FRouteAuditRow& Row:Rows)
|
||||||
|
{
|
||||||
|
std::string Error;
|
||||||
|
const auto Page=Shape.generateTile(Row.Candidate.Key,PageOptions,Error);
|
||||||
|
const bool bVerifiedPage=Page&&Page->diagnostics.analyticRegionPageRoute;
|
||||||
|
PageRoutesVerified+=bVerifiedPage?1:0;
|
||||||
|
const bool bSame=Row.Base&&bVerifiedPage&&SameTileOutput(*Row.Base,*Page);
|
||||||
|
const FRouteDensityComparison Comparison=Page&&Row.Base
|
||||||
|
?CompareRouteDensities(*Page,*Row.Base,Shape):FRouteDensityComparison{};
|
||||||
|
if(!bSame||!Comparison.valid||Comparison.exactQueryMismatches!=0)
|
||||||
|
++BasePageMismatches;
|
||||||
|
PageExactSamples+=Comparison.exactQuerySamples;
|
||||||
|
PageExactMismatches+=Comparison.exactQueryMismatches;
|
||||||
|
MaxDensityDelta=std::max(MaxDensityDelta,Comparison.maxAbsoluteDelta);
|
||||||
|
SumDensityDelta+=Comparison.sumAbsoluteDelta;
|
||||||
|
ComparedDensitySamples+=Comparison.samples;
|
||||||
|
SignMismatches+=Comparison.signMismatches;
|
||||||
|
CrossingEdgeMismatches+=Comparison.crossingEdgeMismatches;
|
||||||
|
SharedCrossingEdges+=Comparison.sharedCrossingEdges;
|
||||||
|
MaxSurfaceDisplacement=std::max(MaxSurfaceDisplacement,
|
||||||
|
Comparison.maxSurfaceDisplacementVoxels);
|
||||||
|
SumSurfaceDisplacement+=Comparison.sumSurfaceDisplacementVoxels;
|
||||||
|
if(Page)
|
||||||
|
{
|
||||||
|
PageProofBlocks+=Page->diagnostics.uniformBlockProofsChecked;
|
||||||
|
PageProofSamples+=Page->diagnostics.uniformBlockProofSamplesChecked;
|
||||||
|
PageProofFallbacks+=Page->diagnostics.uniformBlockProofFallbackBlocks;
|
||||||
|
PageProofUnknown+=Page->diagnostics.uniformBlockProofUnknownBlocks;
|
||||||
|
PageProofViolations+=Page->diagnostics.uniformBlockProofViolations;
|
||||||
|
}
|
||||||
|
std::printf("[RouteParityPair] route=base/page key=(%d,%d,%d,lod%d) page_requested=%d page_verified=%d same_density=%d same_mesh=%d same_payload=%d same_topology=%d samples=%llu max_abs_density_delta=%.9g sign_mismatches=%llu edge_crossing_mismatches=%llu shared_surface_edges=%llu max_surface_delta_vox=%.9g exact_query_mismatches=%llu base_native_float=%llu page_native_float=%llu first_density=(%.9g,%.9g) status=%s error=%s\n",
|
||||||
|
Row.Candidate.Key.coordinate.x,Row.Candidate.Key.coordinate.y,
|
||||||
|
Row.Candidate.Key.coordinate.z,Row.Candidate.Key.lod,
|
||||||
|
Row.Candidate.bWouldRequestPage?1:0,bVerifiedPage?1:0,
|
||||||
|
Row.Base&&Page&&Row.Base->densityHash==Page->densityHash?1:0,
|
||||||
|
Row.Base&&Page&&Row.Base->meshHash==Page->meshHash?1:0,
|
||||||
|
Row.Base&&Page&&Row.Base->payloadHash==Page->payloadHash?1:0,
|
||||||
|
Row.Base&&Page&&Row.Base->topologyHash==Page->topologyHash?1:0,
|
||||||
|
static_cast<unsigned long long>(Comparison.samples),
|
||||||
|
Comparison.maxAbsoluteDelta,
|
||||||
|
static_cast<unsigned long long>(Comparison.signMismatches),
|
||||||
|
static_cast<unsigned long long>(Comparison.crossingEdgeMismatches),
|
||||||
|
static_cast<unsigned long long>(Comparison.sharedCrossingEdges),
|
||||||
|
Comparison.maxSurfaceDisplacementVoxels,
|
||||||
|
static_cast<unsigned long long>(Comparison.exactQueryMismatches),
|
||||||
|
static_cast<unsigned long long>(Row.Base?Row.Base->diagnostics.floatFieldCalls:0),
|
||||||
|
static_cast<unsigned long long>(Page?Page->diagnostics.floatFieldCalls:0),
|
||||||
|
Row.Base&&!Row.Base->debugDensitySamples.empty()
|
||||||
|
?Row.Base->debugDensitySamples.front():0.0f,
|
||||||
|
Page&&!Page->debugDensitySamples.empty()?Page->debugDensitySamples.front():0.0f,
|
||||||
|
bSame&&Comparison.valid&&Comparison.exactQueryMismatches==0?"PASS":"FAIL",
|
||||||
|
Page?Error.c_str():Error.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 NativeMismatches=0,NativeRoutesVerified=0;
|
||||||
|
std::uint64_t NativeExactSamples=0,NativeExactMismatches=0;
|
||||||
|
int32 NativePairs=0;
|
||||||
|
VoxelForgeCore::TileGenerationOptions NativeOptions=CaptureOptions;
|
||||||
|
NativeOptions.diagnosticForceNativeOnly=true;
|
||||||
|
for(const FVoxelTileKey& Tile:ShapeFreeTiles)
|
||||||
|
{
|
||||||
|
if(NativePairs>=9)break;
|
||||||
|
if(!Neighborhood.contains(Tile))continue;
|
||||||
|
const VoxelForgeCore::TileKey Key{{Tile.Coord.X,Tile.Coord.Y,Tile.Coord.Z},Tile.Level};
|
||||||
|
std::string BaseError,NativeError;
|
||||||
|
const auto Base=Shape.generateTile(Key,CaptureOptions,BaseError);
|
||||||
|
const auto Native=Shape.generateTile(Key,NativeOptions,NativeError);
|
||||||
|
++NativePairs;
|
||||||
|
const bool bVerifiedNative=Native&&Native->diagnostics.nativeOnlyRoute;
|
||||||
|
NativeRoutesVerified+=bVerifiedNative?1:0;
|
||||||
|
const bool bSame=Base&&bVerifiedNative&&SameTileOutput(*Base,*Native);
|
||||||
|
const FRouteDensityComparison Comparison=Base&&Native
|
||||||
|
?CompareRouteDensities(*Native,*Base,Shape):FRouteDensityComparison{};
|
||||||
|
NativeExactSamples+=Comparison.exactQuerySamples;
|
||||||
|
NativeExactMismatches+=Comparison.exactQueryMismatches;
|
||||||
|
if(!bSame||!Comparison.valid||Comparison.exactQueryMismatches!=0)
|
||||||
|
++NativeMismatches;
|
||||||
|
std::printf("[RouteParityPair] route=compiled/native-only key=(%d,%d,%d,lod%d) native_verified=%d same_density=%d same_mesh=%d same_payload=%d same_topology=%d samples=%llu exact_query_mismatches=%llu status=%s error=%s\n",
|
||||||
|
Key.coordinate.x,Key.coordinate.y,Key.coordinate.z,Key.lod,
|
||||||
|
bVerifiedNative?1:0,Base&&Native&&Base->densityHash==Native->densityHash?1:0,
|
||||||
|
Base&&Native&&Base->meshHash==Native->meshHash?1:0,
|
||||||
|
Base&&Native&&Base->payloadHash==Native->payloadHash?1:0,
|
||||||
|
Base&&Native&&Base->topologyHash==Native->topologyHash?1:0,
|
||||||
|
static_cast<unsigned long long>(Comparison.samples),
|
||||||
|
static_cast<unsigned long long>(Comparison.exactQueryMismatches),
|
||||||
|
bSame&&Comparison.valid&&Comparison.exactQueryMismatches==0?"PASS":"FAIL",
|
||||||
|
Native?NativeError.c_str():NativeError.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 ExactQueryPairs=0,ExactQueryMismatches=0;
|
||||||
|
std::uint64_t ExactQuerySamples=0;
|
||||||
|
for(int32 LOD=World.FullResClipLevels;LOD<=World.MaxClipLevel&&LOD<9;++LOD)
|
||||||
|
{
|
||||||
|
int32 Added=0;
|
||||||
|
for(const FVoxelTileKey& Tile:Desired.Tiles)
|
||||||
|
{
|
||||||
|
if(Tile.Level!=LOD||Added>=2)continue;
|
||||||
|
const VoxelForgeCore::TileKey Key{{Tile.Coord.X,Tile.Coord.Y,Tile.Coord.Z},Tile.Level};
|
||||||
|
std::string Error;
|
||||||
|
const auto Mesh=Shape.generateTile(Key,CaptureOptions,Error);
|
||||||
|
const FRouteDensityComparison Comparison=Mesh
|
||||||
|
?CompareRouteDensities(*Mesh,*Mesh,Shape):FRouteDensityComparison{};
|
||||||
|
++ExactQueryPairs;++Added;
|
||||||
|
ExactQuerySamples+=Comparison.exactQuerySamples;
|
||||||
|
if(!Mesh||!Comparison.valid||Comparison.exactQueryMismatches!=0)
|
||||||
|
++ExactQueryMismatches;
|
||||||
|
std::printf("[RouteParityPair] route=tile/exact-query key=(%d,%d,%d,lod%d) samples=%llu exact_query_mismatches=%llu status=%s error=%s\n",
|
||||||
|
Key.coordinate.x,Key.coordinate.y,Key.coordinate.z,Key.lod,
|
||||||
|
static_cast<unsigned long long>(Comparison.exactQuerySamples),
|
||||||
|
static_cast<unsigned long long>(Comparison.exactQueryMismatches),
|
||||||
|
Mesh&&Comparison.valid&&Comparison.exactQueryMismatches==0?"PASS":"FAIL",
|
||||||
|
Error.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int32 EditPairs=0,EditMismatches=0;
|
||||||
|
const auto EditRow=std::find_if(Rows.begin(),Rows.end(),[](const FRouteAuditRow& Row)
|
||||||
|
{
|
||||||
|
return Row.Base&&!Row.Candidate.bWouldRequestPage&&Row.Candidate.Key.lod<=1
|
||||||
|
&&!Row.Base->vertices.empty()&&!Row.Base->indices.empty();
|
||||||
|
});
|
||||||
|
if(EditRow!=Rows.end())
|
||||||
|
{
|
||||||
|
VoxelForgeCore::World EditWorld(CoreSnapshot);
|
||||||
|
std::string EditCompileError;
|
||||||
|
if(!EditWorld.compile(ShapeRequest,Scheduler,EditCompileError))
|
||||||
|
{
|
||||||
|
++EditMismatches;
|
||||||
|
std::printf("[RouteParityEdit] status=FAIL stage=compile error=%s\n",
|
||||||
|
EditCompileError.c_str());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const VoxelForgeCore::TileKey Key=EditRow->Candidate.Key;
|
||||||
|
const std::int32_t Extent=CHUNK_SIZE<<Key.lod;
|
||||||
|
const std::array<VoxelForgeCore::TileKey,1> EditKeys={Key};
|
||||||
|
VoxelForgeCore::TileGenerationOptions EditOptions=CaptureOptions;
|
||||||
|
EditOptions.auditUniformBlockProofs=true;
|
||||||
|
std::string BeforeError,ApplyError,BaseError,PageCompileError,PageError;
|
||||||
|
const auto Before=EditWorld.generateTile(Key,EditOptions,BeforeError);
|
||||||
|
VoxelForgeCore::EditTransaction Transaction;
|
||||||
|
Transaction.sequence=1;
|
||||||
|
VoxelForgeCore::EditBrush Brush;
|
||||||
|
Brush.shape=VoxelForgeCore::EditBrush::Shape::sphere;
|
||||||
|
Brush.operation=VoxelForgeCore::EditBrush::Operation::densityOffset;
|
||||||
|
bool bSurfaceCenterFound=false;
|
||||||
|
if(Before&&!Before->debugDensitySamples.empty())
|
||||||
|
{
|
||||||
|
const std::int32_t Dim=static_cast<std::int32_t>(std::llround(
|
||||||
|
std::cbrt(static_cast<double>(Before->debugDensitySamples.size()))));
|
||||||
|
const std::int32_t GridDim=Dim-3;
|
||||||
|
if(Dim>=4&&static_cast<std::size_t>(Dim)*Dim*Dim
|
||||||
|
==Before->debugDensitySamples.size()&&GridDim>0&&Extent%GridDim==0)
|
||||||
|
{
|
||||||
|
const std::int32_t Step=Extent/GridDim;
|
||||||
|
const std::int32_t OriginX=Key.coordinate.x*Extent;
|
||||||
|
const std::int32_t OriginY=Key.coordinate.y*Extent;
|
||||||
|
const std::int32_t OriginZ=Key.coordinate.z*Extent;
|
||||||
|
const auto Index=[Dim](std::int32_t X,std::int32_t Y,std::int32_t Z)
|
||||||
|
{return (static_cast<std::size_t>(Z)*Dim+Y)*Dim+X;};
|
||||||
|
for(std::int32_t Z=0;Z<Dim&&!bSurfaceCenterFound;++Z)
|
||||||
|
for(std::int32_t Y=0;Y<Dim&&!bSurfaceCenterFound;++Y)
|
||||||
|
for(std::int32_t X=0;X<Dim&&!bSurfaceCenterFound;++X)
|
||||||
|
{
|
||||||
|
for(const std::array<std::int32_t,3>& Delta:
|
||||||
|
{std::array<std::int32_t,3>{1,0,0},
|
||||||
|
std::array<std::int32_t,3>{0,1,0},
|
||||||
|
std::array<std::int32_t,3>{0,0,1}})
|
||||||
|
{
|
||||||
|
const std::int32_t NX=X+Delta[0],NY=Y+Delta[1],NZ=Z+Delta[2];
|
||||||
|
if(NX>=Dim||NY>=Dim||NZ>=Dim)continue;
|
||||||
|
const float D0=Before->debugDensitySamples[Index(X,Y,Z)];
|
||||||
|
const float D1=Before->debugDensitySamples[Index(NX,NY,NZ)];
|
||||||
|
if((D0<0.0f)==(D1<0.0f))continue;
|
||||||
|
const double T=static_cast<double>(D0)
|
||||||
|
/(static_cast<double>(D0)-D1);
|
||||||
|
Brush.a={OriginX+(X-1+T*Delta[0])*Step,
|
||||||
|
OriginY+(Y-1+T*Delta[1])*Step,
|
||||||
|
OriginZ+(Z-1+T*Delta[2])*Step};
|
||||||
|
bSurfaceCenterFound=true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!bSurfaceCenterFound)
|
||||||
|
Brush.a={static_cast<double>(Key.coordinate.x*Extent)+Extent*0.5,
|
||||||
|
static_cast<double>(Key.coordinate.y*Extent)+Extent*0.5,
|
||||||
|
static_cast<double>(Key.coordinate.z*Extent)+Extent*0.5};
|
||||||
|
Brush.radius=10.0;
|
||||||
|
Brush.strength=-48.0f;
|
||||||
|
Transaction.brushes.push_back(Brush);
|
||||||
|
const auto Applied=Before?EditWorld.applyEditTransaction(Transaction,
|
||||||
|
EditKeys,ApplyError):std::optional<VoxelForgeCore::EditResult>{};
|
||||||
|
const auto EditedBase=Applied
|
||||||
|
?EditWorld.generateTile(Key,EditOptions,BaseError)
|
||||||
|
:std::optional<VoxelForgeCore::TileMesh>{};
|
||||||
|
VoxelForgeCore::TileRegionCompileStats EditPageStats;
|
||||||
|
std::size_t EditPageKeys=0;
|
||||||
|
const bool bEditPageBuilt=EditedBase&&EditWorld.compileTileRegion(EditKeys,
|
||||||
|
static_cast<std::uint32_t>(std::clamp(WorkerCount,1,16)),Scheduler,
|
||||||
|
PageCompileError,&EditPageKeys,&EditPageStats,true,true);
|
||||||
|
const auto EditedPage=bEditPageBuilt
|
||||||
|
?EditWorld.generateTile(Key,EditOptions,PageError)
|
||||||
|
:std::optional<VoxelForgeCore::TileMesh>{};
|
||||||
|
const FRouteDensityComparison EditComparison=EditedBase&&EditedPage
|
||||||
|
?CompareRouteDensities(*EditedPage,*EditedBase,EditWorld)
|
||||||
|
:FRouteDensityComparison{};
|
||||||
|
const bool bEditChanged=Before&&EditedBase
|
||||||
|
&&Before->densityHash!=EditedBase->densityHash;
|
||||||
|
const bool bEditPageVerified=EditedPage
|
||||||
|
&&EditedPage->diagnostics.analyticRegionPageRoute;
|
||||||
|
const bool bEditSame=EditedBase&&EditedPage&&bEditPageVerified
|
||||||
|
&&SameTileOutput(*EditedBase,*EditedPage);
|
||||||
|
const bool bEditPass=Before&&Applied&&EditedBase&&bEditPageBuilt
|
||||||
|
&&bEditPageVerified&&bEditChanged&&bEditSame&&EditComparison.valid
|
||||||
|
&&EditComparison.exactQueryMismatches==0;
|
||||||
|
++EditPairs;
|
||||||
|
if(!bEditPass)++EditMismatches;
|
||||||
|
if(EditedPage)
|
||||||
|
{
|
||||||
|
PageProofBlocks+=EditedPage->diagnostics.uniformBlockProofsChecked;
|
||||||
|
PageProofSamples+=EditedPage->diagnostics.uniformBlockProofSamplesChecked;
|
||||||
|
PageProofFallbacks+=EditedPage->diagnostics.uniformBlockProofFallbackBlocks;
|
||||||
|
PageProofUnknown+=EditedPage->diagnostics.uniformBlockProofUnknownBlocks;
|
||||||
|
PageProofViolations+=EditedPage->diagnostics.uniformBlockProofViolations;
|
||||||
|
}
|
||||||
|
std::printf("[RouteParityEdit] key=(%d,%d,%d,lod%d) sequence=%llu before_hash=0x%016llx edited_base_hash=0x%016llx edited_page_hash=0x%016llx edit_changed=%d page_verified=%d same_mesh=%d same_payload=%d same_topology=%d samples=%llu exact_query_mismatches=%llu status=%s error=%s\n",
|
||||||
|
Key.coordinate.x,Key.coordinate.y,Key.coordinate.z,Key.lod,
|
||||||
|
static_cast<unsigned long long>(Transaction.sequence),
|
||||||
|
static_cast<unsigned long long>(Before?Before->densityHash:0),
|
||||||
|
static_cast<unsigned long long>(EditedBase?EditedBase->densityHash:0),
|
||||||
|
static_cast<unsigned long long>(EditedPage?EditedPage->densityHash:0),
|
||||||
|
bEditChanged?1:0,bEditPageVerified?1:0,
|
||||||
|
EditedBase&&EditedPage&&EditedBase->meshHash==EditedPage->meshHash?1:0,
|
||||||
|
EditedBase&&EditedPage&&EditedBase->payloadHash==EditedPage->payloadHash?1:0,
|
||||||
|
EditedBase&&EditedPage&&EditedBase->topologyHash==EditedPage->topologyHash?1:0,
|
||||||
|
static_cast<unsigned long long>(EditComparison.samples),
|
||||||
|
static_cast<unsigned long long>(EditComparison.exactQueryMismatches),
|
||||||
|
bEditPass?"PASS":"FAIL",
|
||||||
|
!Before?BeforeError.c_str():!Applied?ApplyError.c_str():
|
||||||
|
!EditedBase?BaseError.c_str():!bEditPageBuilt?PageCompileError.c_str():
|
||||||
|
PageError.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int32 HistoryPairs=0,HistoryMismatches=0;
|
||||||
|
if(bHasCameraStep&&bRegionCrossing)
|
||||||
|
{
|
||||||
|
const auto MovedAnchor=std::find_if(MovedDesired.Tiles.begin(),MovedDesired.Tiles.end(),
|
||||||
|
[](const FVoxelTileKey& Tile){return Tile.Level==0;});
|
||||||
|
const auto HistoryRow=std::find_if(Rows.begin(),Rows.end(),
|
||||||
|
[&](const FRouteAuditRow& Row)
|
||||||
|
{
|
||||||
|
if(!Row.Base||Row.Candidate.bWouldRequestPage)return false;
|
||||||
|
return std::none_of(MovedDesired.Tiles.begin(),MovedDesired.Tiles.end(),
|
||||||
|
[&](const FVoxelTileKey& Tile)
|
||||||
|
{
|
||||||
|
return Tile.Level==Row.Candidate.Key.lod
|
||||||
|
&&Tile.Coord.X==Row.Candidate.Key.coordinate.x
|
||||||
|
&&Tile.Coord.Y==Row.Candidate.Key.coordinate.y
|
||||||
|
&&Tile.Coord.Z==Row.Candidate.Key.coordinate.z;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if(HistoryRow!=Rows.end()&&MovedAnchor!=MovedDesired.Tiles.end())
|
||||||
|
{
|
||||||
|
VoxelForgeCore::ShapeBuildRequest MovedShapeRequest=ShapeRequest;
|
||||||
|
const FIntVector MovedOrigin=MovedAnchor->OriginVoxels();
|
||||||
|
const std::int32_t MovedExtent=MovedAnchor->ExtentVoxels();
|
||||||
|
MovedShapeRequest.minVoxel={MovedOrigin.X,MovedOrigin.Y,MovedOrigin.Z};
|
||||||
|
MovedShapeRequest.maxVoxel={MovedOrigin.X+MovedExtent,
|
||||||
|
MovedOrigin.Y+MovedExtent,MovedOrigin.Z+MovedExtent};
|
||||||
|
MovedShapeRequest.clipMinVoxel=MovedShapeRequest.minVoxel;
|
||||||
|
MovedShapeRequest.clipMaxVoxel=MovedShapeRequest.maxVoxel;
|
||||||
|
VoxelForgeCore::World MovedWorld(CoreSnapshot);
|
||||||
|
std::string MovedShapeError,MovedTileError;
|
||||||
|
const bool bMovedCompiled=MovedWorld.compile(MovedShapeRequest,Scheduler,
|
||||||
|
MovedShapeError);
|
||||||
|
const auto MovedTile=bMovedCompiled
|
||||||
|
?MovedWorld.generateTile(HistoryRow->Candidate.Key,CaptureOptions,MovedTileError)
|
||||||
|
:std::optional<VoxelForgeCore::TileMesh>{};
|
||||||
|
const FRouteDensityComparison HistoryComparison=MovedTile
|
||||||
|
?CompareRouteDensities(*MovedTile,*HistoryRow->Base,MovedWorld)
|
||||||
|
:FRouteDensityComparison{};
|
||||||
|
const bool bHistorySame=MovedTile
|
||||||
|
&&SameTileOutput(*MovedTile,*HistoryRow->Base)
|
||||||
|
&&HistoryComparison.valid&&HistoryComparison.exactQueryMismatches==0;
|
||||||
|
++HistoryPairs;
|
||||||
|
if(!bHistorySame)++HistoryMismatches;
|
||||||
|
std::printf("[RouteParityHistory] from=initial_base to=moved_world key=(%d,%d,%d,lod%d) same_density=%d same_mesh=%d same_payload=%d same_topology=%d samples=%llu max_abs_density_delta=%.9g exact_query_mismatches=%llu status=%s error=%s\n",
|
||||||
|
HistoryRow->Candidate.Key.coordinate.x,HistoryRow->Candidate.Key.coordinate.y,
|
||||||
|
HistoryRow->Candidate.Key.coordinate.z,HistoryRow->Candidate.Key.lod,
|
||||||
|
MovedTile&&MovedTile->densityHash==HistoryRow->Base->densityHash?1:0,
|
||||||
|
MovedTile&&MovedTile->meshHash==HistoryRow->Base->meshHash?1:0,
|
||||||
|
MovedTile&&MovedTile->payloadHash==HistoryRow->Base->payloadHash?1:0,
|
||||||
|
MovedTile&&MovedTile->topologyHash==HistoryRow->Base->topologyHash?1:0,
|
||||||
|
static_cast<unsigned long long>(HistoryComparison.samples),
|
||||||
|
HistoryComparison.maxAbsoluteDelta,
|
||||||
|
static_cast<unsigned long long>(HistoryComparison.exactQueryMismatches),
|
||||||
|
bHistorySame?"PASS":"FAIL",
|
||||||
|
!bMovedCompiled?MovedShapeError.c_str():MovedTileError.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const double MeanDensityDelta=ComparedDensitySamples
|
||||||
|
?SumDensityDelta/static_cast<double>(ComparedDensitySamples):0.0;
|
||||||
|
const double MeanSurfaceDisplacement=SharedCrossingEdges
|
||||||
|
?SumSurfaceDisplacement/static_cast<double>(SharedCrossingEdges):0.0;
|
||||||
|
const bool bRouteParityPassing=BasePageMismatches==0&&NativeMismatches==0
|
||||||
|
&&ExactQueryMismatches==0&&EditPairs==1&&EditMismatches==0
|
||||||
|
&&HistoryPairs==(bHasCameraStep&&bRegionCrossing?1:0)&&HistoryMismatches==0
|
||||||
|
&&PageRoutesVerified==static_cast<int32>(Rows.size())
|
||||||
|
&&NativeRoutesVerified==NativePairs&&PageProofViolations==0
|
||||||
|
&&PageExactMismatches==0&&NativeExactMismatches==0;
|
||||||
|
std::printf("[RouteParityAudit] seed=%d page_pairs=%zu page_verified=%d page_mismatches=%d native_only_pairs=%d native_verified=%d native_mismatches=%d higher_lod_exact_pairs=%d higher_lod_exact_mismatches=%d edit_pairs=%d edit_mismatches=%d history_pairs=%d history_mismatches=%d exact_query_samples=%llu exact_query_mismatches=%llu page_density_samples=%llu max_abs_density_delta=%.9g mean_abs_density_delta=%.9g sign_mismatches=%llu edge_crossing_mismatches=%llu shared_surface_edges=%llu max_surface_delta_vox=%.9g mean_surface_delta_vox=%.9g page_proof_blocks=%llu page_proof_samples=%llu page_proof_fallbacks=%llu page_proof_unknown=%llu page_proof_violations=%llu status=%s\n",
|
||||||
|
World.Seed,Rows.size(),PageRoutesVerified,BasePageMismatches,
|
||||||
|
NativePairs,NativeRoutesVerified,NativeMismatches,ExactQueryPairs,
|
||||||
|
ExactQueryMismatches,EditPairs,EditMismatches,HistoryPairs,HistoryMismatches,
|
||||||
|
static_cast<unsigned long long>(PageExactSamples
|
||||||
|
+NativeExactSamples+ExactQuerySamples),
|
||||||
|
static_cast<unsigned long long>(PageExactMismatches
|
||||||
|
+NativeExactMismatches+ExactQueryMismatches),
|
||||||
|
static_cast<unsigned long long>(ComparedDensitySamples),MaxDensityDelta,
|
||||||
|
MeanDensityDelta,static_cast<unsigned long long>(SignMismatches),
|
||||||
|
static_cast<unsigned long long>(CrossingEdgeMismatches),
|
||||||
|
static_cast<unsigned long long>(SharedCrossingEdges),MaxSurfaceDisplacement,
|
||||||
|
MeanSurfaceDisplacement,static_cast<unsigned long long>(PageProofBlocks),
|
||||||
|
static_cast<unsigned long long>(PageProofSamples),
|
||||||
|
static_cast<unsigned long long>(PageProofFallbacks),
|
||||||
|
static_cast<unsigned long long>(PageProofUnknown),
|
||||||
|
static_cast<unsigned long long>(PageProofViolations),
|
||||||
|
bRouteParityPassing?"PASS":"FAIL");
|
||||||
|
if(!bRouteParityPassing)
|
||||||
|
{
|
||||||
|
Streamer.Stop();
|
||||||
|
return 9;
|
||||||
|
}
|
||||||
|
}
|
||||||
const FStandaloneStreamingUpdate Update=Streamer.UpdateDesiredWithSet(Desired,{},0);
|
const FStandaloneStreamingUpdate Update=Streamer.UpdateDesiredWithSet(Desired,{},0);
|
||||||
if(Update.DesiredTiles!=static_cast<int32>(Desired.Tiles.size()))
|
if(Update.DesiredTiles!=static_cast<int32>(Desired.Tiles.size()))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -122,7 +122,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
const FVoxelSharedDensityGrid* InSharedDensityGrid,
|
const FVoxelSharedDensityGrid* InSharedDensityGrid,
|
||||||
std::vector<float>* OutDebugDensitySamples,
|
std::vector<float>* OutDebugDensitySamples,
|
||||||
FVoxelMarchingCubesStageTimings* OutStageTimings,
|
FVoxelMarchingCubesStageTimings* OutStageTimings,
|
||||||
FVoxelUniformBlockProofAuditStats* OutBlockProofAudit)
|
FVoxelUniformBlockProofAuditStats* OutBlockProofAudit,
|
||||||
|
bool bForceFullDensityCapture)
|
||||||
{
|
{
|
||||||
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_MesherGenerateMesh);
|
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_MesherGenerateMesh);
|
||||||
const uint64 StageMesherStartCycles=OutStageTimings?FPlatformTime::Cycles64():0;
|
const uint64 StageMesherStartCycles=OutStageTimings?FPlatformTime::Cycles64():0;
|
||||||
@@ -865,7 +866,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
// allocating the density grid; no samples are skipped for a tile that can contain
|
// allocating the density grid; no samples are skipped for a tile that can contain
|
||||||
// geometry, and the final geometry remains byte-identical because this path emits none.
|
// geometry, and the final geometry remains byte-identical because this path emits none.
|
||||||
if (bHaveUniformBlock && bAllRelevantBlocksSame && bAllRelevantBlocksUniform
|
if (bHaveUniformBlock && bAllRelevantBlocksSame && bAllRelevantBlocksUniform
|
||||||
&& !(Step == 2 && OutDebugDensitySamples != nullptr))
|
&& !(Step == 2 && OutDebugDensitySamples != nullptr)
|
||||||
|
&& !(bForceFullDensityCapture && OutDebugDensitySamples != nullptr))
|
||||||
{
|
{
|
||||||
return MeshData;
|
return MeshData;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,7 +120,8 @@ public:
|
|||||||
const FVoxelSharedDensityGrid* InSharedDensityGrid = nullptr,
|
const FVoxelSharedDensityGrid* InSharedDensityGrid = nullptr,
|
||||||
std::vector<float>* OutDebugDensitySamples = nullptr,
|
std::vector<float>* OutDebugDensitySamples = nullptr,
|
||||||
FVoxelMarchingCubesStageTimings* OutStageTimings = nullptr,
|
FVoxelMarchingCubesStageTimings* OutStageTimings = nullptr,
|
||||||
FVoxelUniformBlockProofAuditStats* OutBlockProofAudit = nullptr);
|
FVoxelUniformBlockProofAuditStats* OutBlockProofAudit = nullptr,
|
||||||
|
bool bForceFullDensityCapture = false);
|
||||||
|
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
// SERVICES (injectés par AVoxelWorld)
|
// SERVICES (injectés par AVoxelWorld)
|
||||||
|
|||||||
Reference in new issue
Block a user