diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 52481c2..9a4bcbd 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -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 [`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) > **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a diff --git a/CODEMAP.md b/CODEMAP.md index 02a0220..2d0f7e7 100644 --- a/CODEMAP.md +++ b/CODEMAP.md @@ -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. 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.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749 diff --git a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h index e4deebc..3df33f5 100644 --- a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h +++ b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h @@ -235,7 +235,8 @@ namespace VoxelForgeCore // 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::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 { @@ -1050,6 +1051,9 @@ namespace VoxelForgeCore std::int32_t bandMaxZVoxel = 2147483647; bool hashDensity = 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 skipUv = false; bool skipColors = false; diff --git a/Standalone/Core/src/ShapeWorldInternal.h b/Standalone/Core/src/ShapeWorldInternal.h index 24c5363..f05f7ee 100644 --- a/Standalone/Core/src/ShapeWorldInternal.h +++ b/Standalone/Core/src/ShapeWorldInternal.h @@ -478,9 +478,9 @@ public: using Clock=std::chrono::steady_clock; Owner=&World;WorldAssembly=InWorldAssembly; WorkerCount=1;DetailVariant=Variant;Scheduler=nullptr; - // Region pages use the immutable manifest for conservative block proofs, while keeping - // the legacy SurfaceWorld density evaluator so existing tile hashes remain exact. - bUseNativeManifestFields=false; + // Analytic pages retain their bounded owner bins for route bookkeeping. Once published, + // the Core world binds each page to its base shape as the single density evaluator. + bUseNativeManifestFields=true; RoundTunnelsEnabled=bInRoundTunnels;bAnalyticOnly=true;bProfileDetailBand=false; 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(); } FIntVector GetEditBoundsMin() const { return EditBoundsMin; } FIntVector GetEditBoundsMax() const { return EditBoundsMax; } @@ -1017,6 +1020,7 @@ public: // edge interpolation and normals to the existing mesher. float DensityAt(float X,float Y,float Z) const { + if(SharedDensitySource)return SharedDensitySource->DensityAt(X,Y,Z); if(bNativeOnly) { if(!NativeOnlyContains(X,Y,Z)) @@ -1050,6 +1054,7 @@ public: float DensityAtLattice(int32 X,int32 Y,int32 Z) const { + if(SharedDensitySource)return SharedDensitySource->DensityAtLattice(X,Y,Z); if(bNativeOnly) { if(!NativeOnlyContains(static_cast(X),static_cast(Y), @@ -1089,6 +1094,7 @@ public: float DensityAtUnclamped(float X,float Y,float Z) const { + if(SharedDensitySource)return SharedDensitySource->DensityAtUnclamped(X,Y,Z); if(bNativeOnly) { if(!NativeOnlyContains(X,Y,Z)) @@ -1117,6 +1123,7 @@ public: VoxelForgeCore::DetailFieldSample DetailSampleAt(float X,float Y,float Z) const { + if(SharedDensitySource)return SharedDensitySource->DetailSampleAt(X,Y,Z); VoxelForgeCore::DetailFieldSample Sample; if(bNativeOnly) {RecordNativeOnlyViolation();return Sample;} @@ -1135,6 +1142,11 @@ public: void DensityAtLatticeX8(int32 XStart,int32 XStep,int32 Y,int32 Z, int32 Count,float OutDensity[8]) const { + if(SharedDensitySource) + { + SharedDensitySource->DensityAtLatticeX8(XStart,XStep,Y,Z,Count,OutDensity); + return; + } Count=std::clamp(Count,0,8); if(Count==0)return; if(bNativeOnly) @@ -1241,6 +1253,8 @@ private: float* OutFloorInfluence=nullptr, bool bApplyEdits=true) const { + if(SharedDensitySource)return SharedDensitySource->AnalyticDensityAt(X,Y,Z, + bClampNearZero,OutDetailDisplacement,OutCaveInfluence,OutFloorInfluence,bApplyEdits); if(OutDetailDisplacement)*OutDetailDisplacement=0.0f; if(OutCaveInfluence)*OutCaveInfluence=0.0f; if(OutFloorInfluence)*OutFloorInfluence=0.0f; @@ -1303,6 +1317,8 @@ public: bool GetNativeSurfaceHeightsAt(float X,float Y,int32 ChunkZ, float& OutGroundZ,float& OutCeilingZ) const { + if(SharedDensitySource) + return SharedDensitySource->GetNativeSurfaceHeightsAt(X,Y,ChunkZ,OutGroundZ,OutCeilingZ); if(!WorldAssembly||!Owner||!Owner->Manager||!Owner->Generator ||!std::isfinite(X)||!std::isfinite(Y))return false; const std::int64_t chunkMinimum=static_cast(ChunkZ)*CHUNK_SIZE; @@ -1339,6 +1355,7 @@ public: 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; const int32 Pad=std::max(Step,1); const int32 BMin=Origin.Z-Pad; @@ -1377,6 +1394,8 @@ public: FBoxClassification ClassifyBoxDetailed(const FIntVector& Origin,int32 Step,int32 Cells, bool bUseNativeLatticeProof=false) const { + if(SharedDensitySource) + return SharedDensitySource->ClassifyBoxDetailed(Origin,Step,Cells,bUseNativeLatticeProof); using Clock=std::chrono::steady_clock; const auto Start=Clock::now(); const int32 Pad=std::max(Step,1); @@ -1557,6 +1576,7 @@ public: 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); const size_t Line=static_cast(Y-Min[1])*Size[0] +static_cast(X-Min[0]); @@ -2083,7 +2103,8 @@ private: bool NativeFloatingIslandDensityAt(float X,float Y,float Z,float& OutDensity) const { 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. // Convertir Z du Core vers la coordonnee de chunk legacy du proprietaire. const double CoreVoxelsPerLegacyChunk=GetCoreVoxelsPerOwnerChunk(); @@ -4823,6 +4844,11 @@ public: void DensityAtLatticeZRun(int32 X,int32 Y,int32 ZStart,int32 ZStep, int32 Count,float* OutDensity) const { + if(SharedDensitySource) + { + SharedDensitySource->DensityAtLatticeZRun(X,Y,ZStart,ZStep,Count,OutDensity); + return; + } if(Count<=0||OutDensity==nullptr)return; if(bNativeOnly) { @@ -5353,6 +5379,7 @@ private: FStandaloneOwnerWorld* Owner=nullptr; const VoxelForgeCore::WorldAssemblyManifest* WorldAssembly=nullptr; + const FStandaloneShapeWorld* SharedDensitySource=nullptr; int32 WorkerCount=6; const VoxelForgeCore::JobScheduler* Scheduler=nullptr; bool RoundTunnelsEnabled=false; diff --git a/Standalone/Core/src/VoxelForgeCore.cpp b/Standalone/Core/src/VoxelForgeCore.cpp index a7ea6b3..495d4a7 100644 --- a/Standalone/Core/src/VoxelForgeCore.cpp +++ b/Standalone/Core/src/VoxelForgeCore.cpp @@ -1386,6 +1386,8 @@ namespace VoxelForgeCore compiledRegions[i]=std::move(region); } } + for(const auto& region:compiledRegions) + if(region)region->SetSharedDensitySource(impl_->shape.get()); { std::unordered_map,TileKeyHash> publish; publish.reserve(stats.tileKeysCovered); @@ -2021,7 +2023,8 @@ namespace VoxelForgeCore MeshBandMinZVoxel,MeshBandMaxZVoxel,&sampleCount,nullptr,nullptr, (options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr, options.profileMesherStages?&McStageTimings:nullptr, - options.auditUniformBlockProofs?&BlockProofAudit:nullptr); + options.auditUniformBlockProofs?&BlockProofAudit:nullptr, + options.forceFullDensityCapture); if(options.auditUniformBlockProofs) { out.diagnostics.uniformBlockProofsChecked=BlockProofAudit.CheckedBlocks; diff --git a/Standalone/Research/WorldAssemblerM3/Linux13/REPORT.md b/Standalone/Research/WorldAssemblerM3/Linux13/REPORT.md new file mode 100644 index 0000000..7ba85be --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux13/REPORT.md @@ -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. diff --git a/Standalone/Source/StreamBenchmarkHeadless.cpp b/Standalone/Source/StreamBenchmarkHeadless.cpp index b57d01f..973591b 100644 --- a/Standalone/Source/StreamBenchmarkHeadless.cpp +++ b/Standalone/Source/StreamBenchmarkHeadless.cpp @@ -43,6 +43,7 @@ namespace bool AuditUniformBlockProofs=false; bool ProfileLodWork=false; bool AuditBasePageEquivalence=false; + bool AuditRouteParity=false; bool RegionCrossing=false; bool AuditTileShapeReadiness=false; int DiagnosticSlowShapeCompileMs=0; @@ -122,6 +123,7 @@ namespace " --audit-uniform-block-proofs (fill and check every interval-certified block)\n" " --profile-lod-work (report per-LOD density sample counts)\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" " --diagnostic-slow-shape-compile-ms=N (diagnostic overlap control; default 0)\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=="--profile-lod-work")options.ProfileLodWork=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=="--audit-tile-shape-readiness")options.AuditTileShapeReadiness=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, cameraStepVoxels,options.AuditUniformBlockProofs,options.ProfileLodWork, options.RegionCrossing,options.AuditTileShapeReadiness, - options.DiagnosticSlowShapeCompileMs,options.AuditBasePageEquivalence); + options.DiagnosticSlowShapeCompileMs,options.AuditBasePageEquivalence, + options.AuditRouteParity); } diff --git a/Standalone/Source/StreamBenchmarkPipeline.h b/Standalone/Source/StreamBenchmarkPipeline.h index edeaca1..af0b212 100644 --- a/Standalone/Source/StreamBenchmarkPipeline.h +++ b/Standalone/Source/StreamBenchmarkPipeline.h @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -23,6 +24,113 @@ 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(A.debugDensitySamples.size())); + const std::int32_t Dim=static_cast(std::llround(Root)); + if(Dim<4||static_cast(Dim)*Dim*Dim!=A.debugDensitySamples.size())return Result; + const std::int32_t GridDim=Dim-3; + const std::int32_t Extent=CHUNK_SIZE<(Z)*Dim+Y)*Dim+X;}; + const auto IsSolid=[](float Density){return Density<0.0f;}; + const auto EdgeCrosses=[&](const std::vector& 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(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(WorldX), + static_cast(WorldY),static_cast(WorldZ)}); + ++Result.exactQuerySamples; + if(std::bit_cast(DA)!=std::bit_cast(ExactLattice) + ||std::bit_cast(DA)!=std::bit_cast(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(A0)/(static_cast(A0)-A1); + const double TB=static_cast(B0)/(static_cast(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 Base; + }; + VoxelForgeCore::TileGenerationOptions CaptureOptions; + CaptureOptions.hashDensity=true; + CaptureOptions.captureDensitySamples=true; + CaptureOptions.forceFullDensityCapture=true; + CaptureOptions.profileMesherStages=true; + std::vector Rows; + Rows.reserve(static_cast(World.FullResClipLevels)*8); + std::vector PageKeys; + PageKeys.reserve(Rows.capacity()); + const int32 AuditPageLODs=std::min(World.FullResClipLevels,3); + std::vector CandidateSets{&Desired}; + if(bHasCameraStep)CandidateSets.push_back(&MovedDesired); + for(int32 LOD=0;LODTiles) + { + 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::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(Comparison.samples), + Comparison.maxAbsoluteDelta, + static_cast(Comparison.signMismatches), + static_cast(Comparison.crossingEdgeMismatches), + static_cast(Comparison.sharedCrossingEdges), + Comparison.maxSurfaceDisplacementVoxels, + static_cast(Comparison.exactQueryMismatches), + static_cast(Row.Base?Row.Base->diagnostics.floatFieldCalls:0), + static_cast(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(Comparison.samples), + static_cast(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(Comparison.exactQuerySamples), + static_cast(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< 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::llround( + std::cbrt(static_cast(Before->debugDensitySamples.size())))); + const std::int32_t GridDim=Dim-3; + if(Dim>=4&&static_cast(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(Z)*Dim+Y)*Dim+X;}; + for(std::int32_t Z=0;Z& Delta: + {std::array{1,0,0}, + std::array{0,1,0}, + std::array{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(D0) + /(static_cast(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(Key.coordinate.x*Extent)+Extent*0.5, + static_cast(Key.coordinate.y*Extent)+Extent*0.5, + static_cast(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{}; + const auto EditedBase=Applied + ?EditWorld.generateTile(Key,EditOptions,BaseError) + :std::optional{}; + VoxelForgeCore::TileRegionCompileStats EditPageStats; + std::size_t EditPageKeys=0; + const bool bEditPageBuilt=EditedBase&&EditWorld.compileTileRegion(EditKeys, + static_cast(std::clamp(WorkerCount,1,16)),Scheduler, + PageCompileError,&EditPageKeys,&EditPageStats,true,true); + const auto EditedPage=bEditPageBuilt + ?EditWorld.generateTile(Key,EditOptions,PageError) + :std::optional{}; + 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(Transaction.sequence), + static_cast(Before?Before->densityHash:0), + static_cast(EditedBase?EditedBase->densityHash:0), + static_cast(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(EditComparison.samples), + static_cast(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{}; + 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(HistoryComparison.samples), + HistoryComparison.maxAbsoluteDelta, + static_cast(HistoryComparison.exactQueryMismatches), + bHistorySame?"PASS":"FAIL", + !bMovedCompiled?MovedShapeError.c_str():MovedTileError.c_str()); + } + } + const double MeanDensityDelta=ComparedDensitySamples + ?SumDensityDelta/static_cast(ComparedDensitySamples):0.0; + const double MeanSurfaceDisplacement=SharedCrossingEdges + ?SumSurfaceDisplacement/static_cast(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(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(PageExactSamples + +NativeExactSamples+ExactQuerySamples), + static_cast(PageExactMismatches + +NativeExactMismatches+ExactQueryMismatches), + static_cast(ComparedDensitySamples),MaxDensityDelta, + MeanDensityDelta,static_cast(SignMismatches), + static_cast(CrossingEdgeMismatches), + static_cast(SharedCrossingEdges),MaxSurfaceDisplacement, + MeanSurfaceDisplacement,static_cast(PageProofBlocks), + static_cast(PageProofSamples), + static_cast(PageProofFallbacks), + static_cast(PageProofUnknown), + static_cast(PageProofViolations), + bRouteParityPassing?"PASS":"FAIL"); + if(!bRouteParityPassing) + { + Streamer.Stop(); + return 9; + } + } const FStandaloneStreamingUpdate Update=Streamer.UpdateDesiredWithSet(Desired,{},0); if(Update.DesiredTiles!=static_cast(Desired.Tiles.size())) { diff --git a/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp b/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp index 297bdbe..e4df7c0 100644 --- a/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp +++ b/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp @@ -122,7 +122,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels, const FVoxelSharedDensityGrid* InSharedDensityGrid, std::vector* OutDebugDensitySamples, FVoxelMarchingCubesStageTimings* OutStageTimings, - FVoxelUniformBlockProofAuditStats* OutBlockProofAudit) + FVoxelUniformBlockProofAuditStats* OutBlockProofAudit, + bool bForceFullDensityCapture) { TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_MesherGenerateMesh); 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 // geometry, and the final geometry remains byte-identical because this path emits none. if (bHaveUniformBlock && bAllRelevantBlocksSame && bAllRelevantBlocksUniform - && !(Step == 2 && OutDebugDensitySamples != nullptr)) + && !(Step == 2 && OutDebugDensitySamples != nullptr) + && !(bForceFullDensityCapture && OutDebugDensitySamples != nullptr)) { return MeshData; } diff --git a/Standalone/Source/VoxelForge/Public/VoxelMarchingCubesMesher.h b/Standalone/Source/VoxelForge/Public/VoxelMarchingCubesMesher.h index 8fb76ba..1450776 100644 --- a/Standalone/Source/VoxelForge/Public/VoxelMarchingCubesMesher.h +++ b/Standalone/Source/VoxelForge/Public/VoxelMarchingCubesMesher.h @@ -120,7 +120,8 @@ public: const FVoxelSharedDensityGrid* InSharedDensityGrid = nullptr, std::vector* OutDebugDensitySamples = nullptr, FVoxelMarchingCubesStageTimings* OutStageTimings = nullptr, - FVoxelUniformBlockProofAuditStats* OutBlockProofAudit = nullptr); + FVoxelUniformBlockProofAuditStats* OutBlockProofAudit = nullptr, + bool bForceFullDensityCapture = false); //========================================================================= // SERVICES (injectés par AVoxelWorld)