wip(assembler): round L12 -- region pages keep LOD1 block proofs: 24 m crossing 5x faster

Analytic region pages now retain the immutable shared manifest, so LOD1 page tiles get the L9
uniform-block proofs (71.7 -> 5.6 ms per tile). Reviewer W12 no-hash: 24 m crossing 0.898 ->
0.166 s; Luna: 1 km 5.89 -> 1.25 s. Spawn unchanged; all L1/FloatingIslands hashes and the 24 m
crossing hash unchanged (W1/W12).
KNOWN BUG (pre-existing, measured here): the same tile key yields different density from the base
shape (manifest field) than from a region page (legacy field), sometimes different mesh too, so
output depends on streaming history. To be fixed in L13.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-20 01:07:47 +02:00
1 parent b1cfaf53b8
commit 32b8670833
6 files changed
+292 -23

No files matched your search

@@ -120,6 +120,9 @@ namespace VoxelForgeCore
struct TileGenerationStats struct TileGenerationStats
{ {
TileClass tileClass = TileClass::unknown; TileClass tileClass = TileClass::unknown;
bool nativeOnlyRoute = false;
bool analyticRegionPageRoute = false;
bool nativeSurfaceTile = false;
std::uint64_t solidBoxes = 0; std::uint64_t solidBoxes = 0;
std::uint64_t airBoxes = 0; std::uint64_t airBoxes = 0;
std::uint64_t mixedBoxes = 0; std::uint64_t mixedBoxes = 0;
@@ -1122,7 +1125,8 @@ namespace VoxelForgeCore
const JobScheduler& scheduler, std::string& error, const JobScheduler& scheduler, std::string& error,
std::size_t* outCompiledTiles = nullptr, std::size_t* outCompiledTiles = nullptr,
TileRegionCompileStats* outStats = nullptr, TileRegionCompileStats* outStats = nullptr,
bool analyticOnly = true); bool analyticOnly = true,
bool diagnosticIncludeBaseCoveredTiles = false);
void retainTileRegions(std::span<const TileKey> desiredTiles); void retainTileRegions(std::span<const TileKey> desiredTiles);
void buildFarWorld(); void buildFarWorld();
bool isCompiled() const noexcept; bool isCompiled() const noexcept;
+13 -5
View File
@@ -319,6 +319,7 @@ public:
Owner=&World;WorldAssembly=InWorldAssembly; Owner=&World;WorldAssembly=InWorldAssembly;
WorkerCount=std::clamp(Workers,1,16);DetailVariant=Variant; WorkerCount=std::clamp(Workers,1,16);DetailVariant=Variant;
Scheduler=InScheduler;RoundTunnelsEnabled=bInRoundTunnels; Scheduler=InScheduler;RoundTunnelsEnabled=bInRoundTunnels;
bUseNativeManifestFields=true;
bAnalyticOnly=false; bAnalyticOnly=false;
bProfileDetailBand=bInProfileDetailBand; bProfileDetailBand=bInProfileDetailBand;
for(int32 A=0;A<3;++A) for(int32 A=0;A<3;++A)
@@ -470,10 +471,16 @@ public:
bool BuildAnalyticRegion(FStandaloneOwnerWorld& World,const FIntVector& MinVoxels, bool BuildAnalyticRegion(FStandaloneOwnerWorld& World,const FIntVector& MinVoxels,
const FIntVector& MaxVoxels,const FIntVector& ClipMinVoxels, const FIntVector& MaxVoxels,const FIntVector& ClipMinVoxels,
const FIntVector& ClipMaxVoxels,EDetailVariant Variant, const FIntVector& ClipMaxVoxels,EDetailVariant Variant,
bool bInRoundTunnels,std::string& Error) bool bInRoundTunnels,
const VoxelForgeCore::WorldAssemblyManifest* InWorldAssembly,
std::string& Error)
{ {
using Clock=std::chrono::steady_clock; using Clock=std::chrono::steady_clock;
Owner=&World;WorkerCount=1;DetailVariant=Variant;Scheduler=nullptr; 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;
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)
{ {
@@ -509,7 +516,7 @@ public:
{ {
Owner=&World;WorldAssembly=InWorldAssembly; Owner=&World;WorldAssembly=InWorldAssembly;
NativeOnlyMinimum=InMinimum;NativeOnlyMaximum=InMaximum; NativeOnlyMinimum=InMinimum;NativeOnlyMaximum=InMaximum;
bNativeOnly=true;bAnalyticOnly=true;Built=true; bUseNativeManifestFields=true;bNativeOnly=true;bAnalyticOnly=true;Built=true;
NativeOnlyViolations.store(0,std::memory_order_relaxed); NativeOnlyViolations.store(0,std::memory_order_relaxed);
} }
@@ -2050,7 +2057,7 @@ private:
bool NativeSurfaceDensityAt(float X,float Y,float Z,float& OutDensity) const bool NativeSurfaceDensityAt(float X,float Y,float Z,float& OutDensity) const
{ {
if(!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;
const int32 ChunkZ=FMath::FloorToInt(Z/static_cast<float>(CHUNK_SIZE)); const int32 ChunkZ=FMath::FloorToInt(Z/static_cast<float>(CHUNK_SIZE));
if(Owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,ChunkZ)) if(Owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,ChunkZ))
@@ -2075,7 +2082,7 @@ private:
bool NativeFloatingIslandDensityAt(float X,float Y,float Z,float& OutDensity) const bool NativeFloatingIslandDensityAt(float X,float Y,float Z,float& OutDensity) const
{ {
if(!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.
@@ -5360,6 +5367,7 @@ private:
EDetailVariant DetailVariant=EDetailVariant::Coherent; EDetailVariant DetailVariant=EDetailVariant::Coherent;
bool Built=false; bool Built=false;
bool bNativeOnly=false; bool bNativeOnly=false;
bool bUseNativeManifestFields=false;
FIntVector NativeOnlyMinimum=FIntVector::ZeroValue; FIntVector NativeOnlyMinimum=FIntVector::ZeroValue;
FIntVector NativeOnlyMaximum=FIntVector::ZeroValue; FIntVector NativeOnlyMaximum=FIntVector::ZeroValue;
mutable std::atomic<uint32> NativeOnlyViolations{0}; mutable std::atomic<uint32> NativeOnlyViolations{0};
+24 -14
View File
@@ -1226,7 +1226,8 @@ namespace VoxelForgeCore
bool World::compileTileRegion(std::span<const TileKey> tiles,std::uint32_t workerLimit, bool World::compileTileRegion(std::span<const TileKey> tiles,std::uint32_t workerLimit,
const JobScheduler& scheduler,std::string& error, const JobScheduler& scheduler,std::string& error,
std::size_t* outCompiledTiles, std::size_t* outCompiledTiles,
TileRegionCompileStats* outStats,bool analyticOnly) TileRegionCompileStats* outStats,bool analyticOnly,
bool diagnosticIncludeBaseCoveredTiles)
{ {
if(outCompiledTiles)*outCompiledTiles=0; if(outCompiledTiles)*outCompiledTiles=0;
if(outStats)*outStats={}; if(outStats)*outStats={};
@@ -1269,18 +1270,19 @@ namespace VoxelForgeCore
||origin+extent>std::numeric_limits<std::int32_t>::max()-RegionPadding) ||origin+extent>std::numeric_limits<std::int32_t>::max()-RegionPadding)
{error="tile region coordinate is out of range";return false;} {error="tile region coordinate is out of range";return false;}
} }
if(CoveredByBaseShape(impl_->buildRequest,impl_->configuration,key))continue; if(!diagnosticIncludeBaseCoveredTiles
&&CoveredByBaseShape(impl_->buildRequest,impl_->configuration,key))continue;
if(impl_->tileRegions.contains(key)||!seenTiles.insert(key).second)continue; if(impl_->tileRegions.contains(key)||!seenTiles.insert(key).second)continue;
// Keep every region's bounds spatially finite. A large camera step can bring in // Keep every region's bounds spatially finite. A large camera step can bring in
// disjoint tiles across the whole view; coalescing an entire LOD slab makes the // disjoint tiles across the whole view; coalescing an entire LOD slab makes the
// dense axis-bin grid grow with the bounding rectangle of all of them at once. // dense axis-bin grid grow with the bounding rectangle of all of them at once.
// Garder chaque region spatialement bornee: un grand deplacement peut amener des // Garder chaque region spatialement bornee: un grand deplacement peut amener des
// tuiles disjointes; fusionner tout un niveau ferait croitre les bins avec son // tuiles disjointes; fusionner tout un niveau ferait croitre les bins avec son
// grand rectangle englobant. // grand rectangle englobant.
const TileKey pageKey=ShapePageKey(key); const TileKey pageKey=ShapePageKey(key);
auto [found,inserted]=regionByPage.try_emplace(pageKey,pendingRegions.size()); auto [found,inserted]=regionByPage.try_emplace(pageKey,pendingRegions.size());
if(inserted)pendingRegions.push_back({pageKey,{}}); if(inserted)pendingRegions.push_back({pageKey,{}});
pendingRegions[found->second].tiles.push_back(key); pendingRegions[found->second].tiles.push_back(key);
} }
} }
if(pendingRegions.empty())return true; if(pendingRegions.empty())return true;
@@ -1317,7 +1319,8 @@ namespace VoxelForgeCore
const FIntVector fineMax(regionMax.X+PagePad,regionMax.Y+PagePad,regionMax.Z+PagePad); const FIntVector fineMax(regionMax.X+PagePad,regionMax.Y+PagePad,regionMax.Z+PagePad);
auto region=std::make_shared<FStandaloneShapeWorld>(); auto region=std::make_shared<FStandaloneShapeWorld>();
if(!region->BuildAnalyticRegion(*impl_->owner,fineMin,fineMax,regionMin,regionMax, if(!region->BuildAnalyticRegion(*impl_->owner,fineMin,fineMax,regionMin,regionMax,
ToNative(base.detail),base.roundTunnels,regionErrors[index]))return; ToNative(base.detail),base.roundTunnels,impl_->worldAssembly.get(),
regionErrors[index]))return;
compiledRegions[index]=std::move(region); compiledRegions[index]=std::move(region);
}; };
struct FAnalyticBuildContext struct FAnalyticBuildContext
@@ -1749,6 +1752,11 @@ namespace VoxelForgeCore
FVoxelMarchingCubesStageTimings McStageTimings; FVoxelMarchingCubesStageTimings McStageTimings;
TileMesh out;out.key=key; TileMesh out;out.key=key;
out.diagnostics.featureRefinedResolution=bFeatureRefinedResolution; out.diagnostics.featureRefinedResolution=bFeatureRefinedResolution;
if(options.profileMesherStages)
{
out.diagnostics.nativeOnlyRoute=bNativeOnly;
out.diagnostics.analyticRegionPageRoute=page&&page->IsAnalyticOnly();
}
const bool bHasWorldAssembly=impl_->worldAssembly!=nullptr; const bool bHasWorldAssembly=impl_->worldAssembly!=nullptr;
const bool bNeedsFarShell=bHasWorldAssembly&&key.lod>=7 const bool bNeedsFarShell=bHasWorldAssembly&&key.lod>=7
&&(tileShape.HasFarWorld()||(bShapeReady&&impl_->shape->HasFarWorld())); &&(tileShape.HasFarWorld()||(bShapeReady&&impl_->shape->HasFarWorld()));
@@ -1812,6 +1820,8 @@ namespace VoxelForgeCore
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds
-columnMillisecondsBeforeClassification); -columnMillisecondsBeforeClassification);
bNativeSurfaceTile=Classification.bNativeSurface; bNativeSurfaceTile=Classification.bNativeSurface;
if(options.profileMesherStages)
out.diagnostics.nativeSurfaceTile=bNativeSurfaceTile;
// NativeSurfaceDensityAt consumes only the bounded manifest heightfield and its // NativeSurfaceDensityAt consumes only the bounded manifest heightfield and its
// bounded type-2/type-4 features. Unknown tile occupancy does not make that Z support // bounded type-2/type-4 features. Unknown tile occupancy does not make that Z support
// unknown; edits and cross-strate tiles are still excluded below. // unknown; edits and cross-strate tiles are still excluded below.
@@ -1870,7 +1880,7 @@ namespace VoxelForgeCore
int32 MeshBandMinZVoxel=options.bandMinZVoxel; int32 MeshBandMinZVoxel=options.bandMinZVoxel;
int32 MeshBandMaxZVoxel=options.bandMaxZVoxel; int32 MeshBandMaxZVoxel=options.bandMaxZVoxel;
bool bNativeHeightfieldBandLimited=false; bool bNativeHeightfieldBandLimited=false;
if(bHasWorldAssembly&&bNativeSurfaceBandSafe if(bHasWorldAssembly&&bNativeSurfaceBandSafe&&!(page&&page->IsAnalyticOnly())
&&!options.hashDensity&&!options.captureDensitySamples&&world.Manager!=nullptr) &&!options.hashDensity&&!options.captureDensitySamples&&world.Manager!=nullptr)
{ {
// La surface 2D et les bornes de volume du manifeste definissent tout le support Z. // La surface 2D et les bornes de volume du manifeste definissent tout le support Z.
@@ -0,0 +1,62 @@
# VoxelForge L12 — Linux report
Governor stayed `powersave`. Builds and runs used CPUs 0–11, at most 12 jobs, a 6 GiB address-space cap, and RSS monitors. Clang and GCC builds passed; peak sampled RSS was 881 MiB for one GCC compiler process and 679 MiB for the 1 km crossing. The GCC build process group peaked at 4.35 GiB. No Unreal adapter files changed.
## LOD cost and change
The 24 m profile is like-for-like at LOD1 in the same SurfaceWorld crossing. L11 generated 160 LOD1 tiles: 32 base-shape tiles averaged 2.73 ms each, while 128 analytic-page tiles averaged 71.72 ms. Of the LOD1 worker time, 9.06 of 9.27 seconds went to the marching-cubes density grid; the page compiler added 15 ms and built no packed lattice nodes. The page route lacked the manifest input needed for the exact LOD1 block proofs. Across all 160 LOD1 tiles, the sample counter reported 5.64 million density samples, or 35,271 per tile.
Analytic pages now retain the immutable shared manifest for block classification while continuing to sample the legacy density field. The proof audit checked 27,479 blocks and 36,574,549 samples, with zero violations. On the plain 24 m profile, page-backed LOD1 fell to 5.60 ms per tile; all 160 LOD1 tiles reported 432,000 samples (2,700 per tile) and 435 ms in density-grid work. Base LOD1 tiles averaged 2.51 ms, so page tiles are now about 2.2× their base cost. No base-shape bounds changed; pages remain privately built, immutably published, and bounded, with 16 pages at 24/32 m and 72 at 1 km.
## Plain W12 crossings
Worker time is the sum across tile workers; per-tile values use that sum. L11 values are from the L11 executable and logs. L12 values include region compilation in wall time.
| Step | Tiles | L11 wall / worker-s / ms per tile | L12 wall / worker-s / ms per tile |
|---:|---:|---:|---:|
| 8 m | 144 | 0.038 s / 0.381 / 2.65 | 0.035 s / 0.360 / 2.50 |
| 16 m | 288 | 0.831 s / 9.476 / 32.90 | 0.125 s / 1.096 / 3.80 |
| 24 m | 432 | 0.869 s / 9.912 / 22.95 | 0.164 s / 1.596 / 3.69 |
| 32 m | 576 | 0.898 s / 10.261 / 17.81 | 0.172 s / 1.691 / 2.94 |
| 1 km | 2,260 | 5.892 s / 69.534 / 30.77 | 1.247 s / 14.003 / 6.20 |
All five L12 steps passed; peak crossing RSS was 679 MiB. The 8 m L11 comparison is `l12-baseline-8m-page.log`; 16 m is `l12-baseline-16m-page.log`; 24 m, 32 m, and 1 km are the L11 final crossing logs.
## Spawn and hashes
Three plain W12 spawn runs had medians of 146 ms first tile, 161 ms near ring, and 1.361 s whole view. L11 medians were 153 ms, 170 ms, and 1.447 s.
All 28 requested L1 hash configurations matched L11: SurfaceWorld and FloatingIslands seed 0, Clang `-ffp-contract=off` and GCC default, W1/W8/W12, with five W12 repeats. Every run matched all 12 rows: full detail, whole view, nine LODs, and exact queries. Clang/GCC full-detail and whole-view D/M/P/T plus exact-query values are unchanged from the L11 report. The L3 FP-contract preamble/self-test remains in place.
| Recipe | Full-detail density / mesh / payload / topology | Whole-view density / mesh / payload / topology | Exact density+unclamped / detail / collision |
|---|---|---|---|
| SurfaceWorld | `0x0ee0579bd15cbbe4` / `0x32e5d4964d610fee` / `0x1932b96ee4b1d910` / `0x69743c6bad3447dc` | `0xc10697a8f48380c7` / `0xe75c9119a1498cd5` / `0x49a9a8d1835a6c8c` / `0xedcb23288b5e8724` | `0xe4d4a8eb95b35933` / `0xefcba20c0c70c415` / `0xe604823a249029bf` |
| FloatingIslands | `0x6d842d2878b08ef6` / `0x4b868a3184f08035` / `0x2cedf2236788752d` / `0xeac17d5e0cb5044a` | `0x5be4d8cde79b2a76` / `0x96f75041f6861b3a` / `0x3700a516cc89d50c` / `0x6b99948d6eab4f21` | `0x61daded9c303becb` / `0xefcba20c0c70c415` / `0x84d37463be9b0573` |
The 24 m crossing hash also matches L11 at W1 and W12 (432 rows each): density `0x174588abdc8c0d2c`, mesh `0x4c401e27d9d05dea`, payload `0xeb10d9801c5ef125`, topology `0xf59d12304d0dcb4a`.
The route-verified base/page audit does **not** pass the requested exact-tile invariant. For `(0,-5,-31,lod1)`, the base density hash is `0xe0672dae7528f8d7` and page density hash is `0x882280f67bfddd77`; mesh, payload, and topology match. All 48 base-covered LOD1 candidates had density mismatches; 10 also differed in mesh/payload/topology. Across eight route-verified LOD0/LOD1 pairs, all eight density hashes differed. The base SurfaceWorld route uses manifest density while L11 pages use the legacy density evaluator; switching page samples to the base field would change the L11 crossing hashes. I kept the legacy page field to preserve the required crossing output, so this pre-existing parity conflict remains unresolved.
## Failed and retried runs
- An attempted 24 m base-shape expansion exited 2 at the 160-million-node bound (`l12-baseline-24m-base.log`). A separate per-tile native-only proof promotion failed with `region OOM` and was reverted (`l12-probe-cross24-profile-w12.log`).
- The first plain step command passed `32` as the camera distance while labeling it 8 m; output identified it as 32 m. It was retained as the 32 m result, and 8 m was rerun with the correct distance.
- The first FloatingIslands matrix omitted `--auto-surface-camera`, so it hashed the wrong 2,553-tile view. It was rerun with the L11 seed-0 island placement; all 28 corrected matrix configurations passed. The comparison helper also needed two small script fixes before it reported the final 28/28 result.
- One base/page audit initially treated a LOD2 base-route fallback (`page_keys=0`) as a match. The audit now verifies the generated route; that apparent pass was rejected and the route-verified parity failure above is the final result.
Walking into new ground should feel much better: the plain 24 m crossing fell from 0.869 s to 0.164 s, and the 1 km crossing from 5.892 s to 1.247 s. Page-backed LOD1 tiles are down from 71.7 to 5.6 ms each while the L11 crossing hashes stay exact; base/page density-hash parity remains a separate unresolved constraint.
## Orchestrator review (Claude)
- Re-run by me (W12, no hashing, 3 interleaved pairs): 24 m crossing L11 0.874/0.914/0.898 s vs L12
0.154/0.166/0.166 s (median 0.898 → 0.166 s, 5.4×). Spawn is unchanged (near ring 0.160–0.184 s).
The 24 m crossing hash (density `0x174588abdc8c0d2c`, mesh `0x4c401e27d9d05dea`) matches L11 at W1
and W12. Hash runs refill skipped samples, so time crossings with hashing off.
- **Soundness of page-tile skipping:** the proof audit figures (27,479 blocks / 36,574,549 samples)
are the base-shape audit from L9, not page tiles. The evidence for pages is the crossing hash
equalling L11 (which filled every sample). That is necessary but narrow: one crossing, one seed.
- ⚠️ **Route-dependent output (a pre-existing multiplayer bug, now measured):** the same tile key
gives different density from the base shape (manifest field) than from an analytic page (legacy
field). All 48 base-covered LOD1 candidates differed in density, 10 also in mesh/payload/topology.
A tile's geometry then depends on streaming history (spawned near it vs walked to it), which
breaks peer determinism. Top priority for L13.
@@ -42,6 +42,7 @@ namespace
bool ScreenSpaceGate=false; bool ScreenSpaceGate=false;
bool AuditUniformBlockProofs=false; bool AuditUniformBlockProofs=false;
bool ProfileLodWork=false; bool ProfileLodWork=false;
bool AuditBasePageEquivalence=false;
bool RegionCrossing=false; bool RegionCrossing=false;
bool AuditTileShapeReadiness=false; bool AuditTileShapeReadiness=false;
int DiagnosticSlowShapeCompileMs=0; int DiagnosticSlowShapeCompileMs=0;
@@ -120,6 +121,7 @@ namespace
" --screen-space-gate (measure display error and historical negative controls)\n" " --screen-space-gate (measure display error and historical negative controls)\n"
" --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-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"
@@ -139,6 +141,7 @@ namespace
else if(arg=="--screen-space-gate")options.ScreenSpaceGate=true; else if(arg=="--screen-space-gate")options.ScreenSpaceGate=true;
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=="--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;
@@ -370,5 +373,5 @@ 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.DiagnosticSlowShapeCompileMs,options.AuditBasePageEquivalence);
} }
+184 -2
View File
@@ -144,7 +144,8 @@ namespace StandaloneStreamBenchmark
bool bUseSharedColumnCache=true,FVector CameraStepVoxels=FVector::ZeroVector, bool bUseSharedColumnCache=true,FVector CameraStepVoxels=FVector::ZeroVector,
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)
{ {
using Clock=std::chrono::steady_clock; using Clock=std::chrono::steady_clock;
FStandaloneOwnerWorld& World=*OwnerWorld; FStandaloneOwnerWorld& World=*OwnerWorld;
@@ -304,7 +305,7 @@ namespace StandaloneStreamBenchmark
bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors,&Shape,bProfileColumnOverlap, bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors,&Shape,bProfileColumnOverlap,
FarHorizonLOD8InteriorStepVoxels,bUseSharedColumnCache); FarHorizonLOD8InteriorStepVoxels,bUseSharedColumnCache);
Streamer.SetUniformBlockProofAuditEnabled(bAuditUniformBlockProofs); Streamer.SetUniformBlockProofAuditEnabled(bAuditUniformBlockProofs);
if(bScreenSpaceGate)Streamer.SetMesherStageProfilingEnabled(true); if(bScreenSpaceGate||bReportLodWorkProfile)Streamer.SetMesherStageProfilingEnabled(true);
const auto StreamStart=Clock::now(); const auto StreamStart=Clock::now();
const FStandaloneStreamingUpdate EarlyUpdate=Streamer.StartWithDesired(ShapeFreeDesired); const FStandaloneStreamingUpdate EarlyUpdate=Streamer.StartWithDesired(ShapeFreeDesired);
if(EarlyUpdate.DesiredTiles!=static_cast<int32>(ShapeFreeDesired.Tiles.size())) if(EarlyUpdate.DesiredTiles!=static_cast<int32>(ShapeFreeDesired.Tiles.size()))
@@ -371,6 +372,106 @@ namespace StandaloneStreamBenchmark
return 9; return 9;
} }
} }
if(bAuditBasePageEquivalence)
{
std::vector<VoxelForgeCore::TileKey> AuditKeys;
AuditKeys.reserve(48);
const auto AddAuditCandidates=[&](int32 PreferredLOD)
{
const std::size_t ExistingCount=AuditKeys.size();
for(const FVoxelTileKey& Tile:Desired.Tiles)
{
if(Tile.Level!=PreferredLOD)continue;
const VoxelForgeCore::TileKey Key{{Tile.Coord.X,Tile.Coord.Y,Tile.Coord.Z},
Tile.Level};
if(Shape.isTileShapeFree(Key)||Shape.needsTileRegion(Key))continue;
if(std::find(AuditKeys.begin(),AuditKeys.end(),Key)==AuditKeys.end())
AuditKeys.push_back(Key);
if(AuditKeys.size()>=48||AuditKeys.size()-ExistingCount>=4)return;
}
};
for(int32 PreferredLOD:{1,0,2,3,4,5,6,7,8})
{
AddAuditCandidates(PreferredLOD);
if(AuditKeys.size()>=48)break;
}
if(AuditKeys.empty())
{
std::printf("[BasePageTileEquivalence] status=UNAVAILABLE reason=no_tile_reachable_through_both_routes\n");
Streamer.Stop();
return 9;
}
VoxelForgeCore::TileGenerationOptions HashOptions;
HashOptions.hashDensity=true;
HashOptions.profileMesherStages=true;
bool bFoundEquivalentTile=false;
std::size_t CandidatesTried=0;
VoxelForgeCore::TileKey EquivalentKey{};
for(const VoxelForgeCore::TileKey& AuditKey:AuditKeys)
{
++CandidatesTried;
std::string BaseError,PageError,CompileError;
const auto BaseGenerationStart=Clock::now();
const auto BaseTile=Shape.generateTile(AuditKey,HashOptions,BaseError);
const double BaseGenerationSeconds=std::chrono::duration<double>(
Clock::now()-BaseGenerationStart).count();
if(!BaseTile)
{
std::printf("[BasePageTileEquivalenceCandidate] key=(%d,%d,%d,lod%d) base=FAIL reason=%s\n",
AuditKey.coordinate.x,AuditKey.coordinate.y,AuditKey.coordinate.z,
AuditKey.lod,BaseError.c_str());
continue;
}
const std::array<VoxelForgeCore::TileKey,1> AuditTiles={AuditKey};
VoxelForgeCore::TileRegionCompileStats PageBuildStats;
std::size_t PageTileCount=0;
const auto PageCompileStart=Clock::now();
const bool bPageBuilt=Shape.compileTileRegion(AuditTiles,
static_cast<std::uint32_t>(std::clamp(WorkerCount,1,16)),Scheduler,
CompileError,&PageTileCount,&PageBuildStats,true,true);
const double PageCompileSeconds=std::chrono::duration<double>(
Clock::now()-PageCompileStart).count();
const auto PageGenerationStart=Clock::now();
const auto PageTile=bPageBuilt?Shape.generateTile(AuditKey,HashOptions,PageError)
:std::optional<VoxelForgeCore::TileMesh>{};
const double PageGenerationSeconds=std::chrono::duration<double>(
Clock::now()-PageGenerationStart).count();
const bool bDensitySame=PageTile&&BaseTile->densityHash==PageTile->densityHash;
const bool bMeshSame=PageTile&&BaseTile->meshHash==PageTile->meshHash;
const bool bPayloadSame=PageTile&&BaseTile->payloadHash==PageTile->payloadHash;
const bool bTopologySame=PageTile&&BaseTile->topologyHash==PageTile->topologyHash;
const bool bActualPage=PageTile&&PageTile->diagnostics.analyticRegionPageRoute;
const int32 Matches=static_cast<int32>(bDensitySame)+static_cast<int32>(bMeshSame)
+static_cast<int32>(bPayloadSame)+static_cast<int32>(bTopologySame);
const bool bSame=bActualPage&&Matches==4;
std::printf("[BasePageTileEquivalenceCandidate] key=(%d,%d,%d,lod%d) page_built=%d page_route_verified=%d page_keys=%zu page_nodes=%llu base_route=base-shape page_route=analytic-page base_generation_ms=%.3f page_compile_ms=%.3f page_generation_ms=%.3f density=%s mesh=%s payload=%s topology=%s base_density=0x%016llx page_density=0x%016llx status=%s error=%s\n",
AuditKey.coordinate.x,AuditKey.coordinate.y,AuditKey.coordinate.z,AuditKey.lod,
bPageBuilt?1:0,bActualPage?1:0,PageTileCount,
static_cast<unsigned long long>(PageBuildStats.latticeNodes),
BaseGenerationSeconds*1000.0,PageCompileSeconds*1000.0,
PageGenerationSeconds*1000.0,
bDensitySame?"same":"DIFF",bMeshSame?"same":"DIFF",
bPayloadSame?"same":"DIFF",bTopologySame?"same":"DIFF",
static_cast<unsigned long long>(BaseTile->densityHash),
static_cast<unsigned long long>(PageTile?PageTile->densityHash:0),
bSame?"PASS":"DIFF",bPageBuilt?PageError.c_str():CompileError.c_str());
if(bSame)
{
bFoundEquivalentTile=true;
EquivalentKey=AuditKey;
break;
}
}
std::printf("[BasePageTileEquivalence] candidates=%zu tried=%zu matching_key=(%d,%d,%d,lod%d) status=%s\n",
AuditKeys.size(),CandidatesTried,EquivalentKey.coordinate.x,
EquivalentKey.coordinate.y,EquivalentKey.coordinate.z,EquivalentKey.lod,
bFoundEquivalentTile?"PASS":"FAIL");
if(!bFoundEquivalentTile)
{
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()))
{ {
@@ -431,6 +532,7 @@ namespace StandaloneStreamBenchmark
std::array<bool,9> ColumnCaptureCompleteByLOD{}; std::array<bool,9> ColumnCaptureCompleteByLOD{};
ColumnCaptureCompleteByLOD.fill(true); ColumnCaptureCompleteByLOD.fill(true);
std::array<double,9> WorkerSecondsByLOD{}; std::array<double,9> WorkerSecondsByLOD{};
std::array<double,9> BaseRouteWorkerSecondsByLOD{},PageRouteWorkerSecondsByLOD{};
std::array<double,9> ColumnEvalMsByLOD{},MarchingExclusiveMsByLOD{}; std::array<double,9> ColumnEvalMsByLOD{},MarchingExclusiveMsByLOD{};
std::array<double,9> GroundRefineExclusiveMsByLOD{},CapRefineExclusiveMsByLOD{}; std::array<double,9> GroundRefineExclusiveMsByLOD{},CapRefineExclusiveMsByLOD{};
std::array<double,9> MeshAssemblyMsByLOD{}; std::array<double,9> MeshAssemblyMsByLOD{};
@@ -438,6 +540,7 @@ namespace StandaloneStreamBenchmark
std::array<std::uint64_t,9> DensitySamplesByLOD{}; std::array<std::uint64_t,9> DensitySamplesByLOD{};
std::array<std::uint64_t,9> SkirtTrianglesByLOD{}; std::array<std::uint64_t,9> SkirtTrianglesByLOD{};
std::array<std::uint64_t,9> TileCountByLOD{}; std::array<std::uint64_t,9> TileCountByLOD{};
std::array<std::uint64_t,9> BaseRouteTilesByLOD{},PageRouteTilesByLOD{};
for(const FVoxelTileKey& Tile:Desired.Tiles) for(const FVoxelTileKey& Tile:Desired.Tiles)
if(Tile.Level>=0&&Tile.Level<static_cast<int32>(ExpectedTilesByLOD.size())) if(Tile.Level>=0&&Tile.Level<static_cast<int32>(ExpectedTilesByLOD.size()))
++ExpectedTilesByLOD[static_cast<std::size_t>(Tile.Level)]; ++ExpectedTilesByLOD[static_cast<std::size_t>(Tile.Level)];
@@ -487,6 +590,19 @@ namespace StandaloneStreamBenchmark
const std::size_t LOD=static_cast<std::size_t>(Tile.Key.Level); const std::size_t LOD=static_cast<std::size_t>(Tile.Key.Level);
++TileCountByLOD[LOD]; ++TileCountByLOD[LOD];
WorkerSecondsByLOD[LOD]+=Tile.WorkerSeconds; WorkerSecondsByLOD[LOD]+=Tile.WorkerSeconds;
if(bReportLodWorkProfile)
{
if(Tile.CoreDiagnostics.analyticRegionPageRoute)
{
++PageRouteTilesByLOD[LOD];
PageRouteWorkerSecondsByLOD[LOD]+=Tile.WorkerSeconds;
}
else
{
++BaseRouteTilesByLOD[LOD];
BaseRouteWorkerSecondsByLOD[LOD]+=Tile.WorkerSeconds;
}
}
ColumnEvalMsByLOD[LOD]+=Tile.CoreDiagnostics.columnEvaluationExclusiveMilliseconds; ColumnEvalMsByLOD[LOD]+=Tile.CoreDiagnostics.columnEvaluationExclusiveMilliseconds;
MarchingExclusiveMsByLOD[LOD]+=Tile.CoreDiagnostics.marchingCubesExclusiveMilliseconds; MarchingExclusiveMsByLOD[LOD]+=Tile.CoreDiagnostics.marchingCubesExclusiveMilliseconds;
GroundRefineExclusiveMsByLOD[LOD]+=Tile.CoreDiagnostics.groundRefinementExclusiveMilliseconds; GroundRefineExclusiveMsByLOD[LOD]+=Tile.CoreDiagnostics.groundRefinementExclusiveMilliseconds;
@@ -636,6 +752,16 @@ namespace StandaloneStreamBenchmark
const auto MovingUpdate=Streamer.UpdateDesiredWithSet(MovedDesired,Desired.Tiles,1); const auto MovingUpdate=Streamer.UpdateDesiredWithSet(MovedDesired,Desired.Tiles,1);
std::array<std::uint64_t,9> MovingTilesByLOD{}; std::array<std::uint64_t,9> MovingTilesByLOD{};
std::array<double,9> MovingWorkerSecondsByLOD{}; std::array<double,9> MovingWorkerSecondsByLOD{};
std::array<std::uint64_t,9> MovingSamplesByLOD{},MovingNativeFloatCallsByLOD{},
MovingNativeLatticeCallsByLOD{},MovingNativeOnlyTilesByLOD{},
MovingAnalyticPageTilesByLOD{},MovingNativeSurfaceTilesByLOD{};
std::array<std::uint64_t,9> MovingBaseRouteTilesByLOD{};
std::array<double,9> MovingBaseRouteWorkerSecondsByLOD{},
MovingPageRouteWorkerSecondsByLOD{};
std::array<double,9> MovingColumnComputeMsByLOD{},MovingClassificationMsByLOD{},
MovingMcExclusiveMsByLOD{},MovingMcDensityGridMsByLOD{},
MovingMcCellClassMsByLOD{},MovingMcGradientMsByLOD{},
MovingMcInterpolationMsByLOD{},MovingMcDedupMsByLOD{},MovingMcOtherMsByLOD{};
std::vector<FHashedTile> MovingDensityRows,MovingMeshRows, std::vector<FHashedTile> MovingDensityRows,MovingMeshRows,
MovingPayloadRows,MovingTopologyRows; MovingPayloadRows,MovingTopologyRows;
int32 MovingFinished=0; int32 MovingFinished=0;
@@ -652,6 +778,32 @@ namespace StandaloneStreamBenchmark
const std::size_t LOD=static_cast<std::size_t>(Tile.Key.Level); const std::size_t LOD=static_cast<std::size_t>(Tile.Key.Level);
++MovingTilesByLOD[LOD]; ++MovingTilesByLOD[LOD];
MovingWorkerSecondsByLOD[LOD]+=Tile.WorkerSeconds; MovingWorkerSecondsByLOD[LOD]+=Tile.WorkerSeconds;
if(bReportLodWorkProfile)
{
const auto& D=Tile.CoreDiagnostics;
MovingSamplesByLOD[LOD]+=Tile.DensitySamples;
MovingNativeFloatCallsByLOD[LOD]+=D.floatFieldCalls;
MovingNativeLatticeCallsByLOD[LOD]+=D.latticeFieldCalls;
MovingNativeOnlyTilesByLOD[LOD]+=D.nativeOnlyRoute?1u:0u;
MovingAnalyticPageTilesByLOD[LOD]+=D.analyticRegionPageRoute?1u:0u;
MovingNativeSurfaceTilesByLOD[LOD]+=D.nativeSurfaceTile?1u:0u;
if(D.analyticRegionPageRoute)
MovingPageRouteWorkerSecondsByLOD[LOD]+=Tile.WorkerSeconds;
else
{
++MovingBaseRouteTilesByLOD[LOD];
MovingBaseRouteWorkerSecondsByLOD[LOD]+=Tile.WorkerSeconds;
}
MovingColumnComputeMsByLOD[LOD]+=D.columnEvaluationExclusiveMilliseconds;
MovingClassificationMsByLOD[LOD]+=D.classificationExclusiveMilliseconds;
MovingMcExclusiveMsByLOD[LOD]+=D.marchingCubesExclusiveMilliseconds;
MovingMcDensityGridMsByLOD[LOD]+=D.marchingCubesDensityGridMilliseconds;
MovingMcCellClassMsByLOD[LOD]+=D.marchingCubesCellClassificationMilliseconds;
MovingMcGradientMsByLOD[LOD]+=D.marchingCubesGradientNormalsMilliseconds;
MovingMcInterpolationMsByLOD[LOD]+=D.marchingCubesEdgeInterpolationMilliseconds;
MovingMcDedupMsByLOD[LOD]+=D.marchingCubesVertexDedupStreamMilliseconds;
MovingMcOtherMsByLOD[LOD]+=D.marchingCubesOtherMilliseconds;
}
} }
if(bHashDensity) if(bHashDensity)
{ {
@@ -693,9 +845,32 @@ namespace StandaloneStreamBenchmark
?"PASS":"FAIL"); ?"PASS":"FAIL");
for(std::size_t LOD=0;LOD<MovingTilesByLOD.size();++LOD) for(std::size_t LOD=0;LOD<MovingTilesByLOD.size();++LOD)
if(MovingTilesByLOD[LOD]!=0) if(MovingTilesByLOD[LOD]!=0)
{
std::printf("[MovingCameraStepLOD] lod=%zu tiles=%llu worker_sum_s=%.6f\n", std::printf("[MovingCameraStepLOD] lod=%zu tiles=%llu worker_sum_s=%.6f\n",
LOD,static_cast<unsigned long long>(MovingTilesByLOD[LOD]), LOD,static_cast<unsigned long long>(MovingTilesByLOD[LOD]),
MovingWorkerSecondsByLOD[LOD]); MovingWorkerSecondsByLOD[LOD]);
if(bReportLodWorkProfile)
std::printf("[MovingCameraStepLODProfile] lod=%zu density_samples=%llu samples_per_tile=%.1f native_float_calls=%llu native_lattice_calls=%llu native_surface_tiles=%llu native_only_tiles=%llu analytic_page_tiles=%llu base_route_tiles=%llu base_route_avg_ms=%.3f page_route_avg_ms=%.3f column_compute_ms=%.3f classification_ms=%.3f mc_exclusive_ms=%.3f mc_density_grid_ms=%.3f mc_cell_class_ms=%.3f mc_gradient_ms=%.3f mc_interpolation_ms=%.3f mc_dedup_ms=%.3f mc_other_ms=%.3f\n",
LOD,static_cast<unsigned long long>(MovingSamplesByLOD[LOD]),
static_cast<double>(MovingSamplesByLOD[LOD])/MovingTilesByLOD[LOD],
static_cast<unsigned long long>(MovingNativeFloatCallsByLOD[LOD]),
static_cast<unsigned long long>(MovingNativeLatticeCallsByLOD[LOD]),
static_cast<unsigned long long>(MovingNativeSurfaceTilesByLOD[LOD]),
static_cast<unsigned long long>(MovingNativeOnlyTilesByLOD[LOD]),
static_cast<unsigned long long>(MovingAnalyticPageTilesByLOD[LOD]),
static_cast<unsigned long long>(MovingBaseRouteTilesByLOD[LOD]),
MovingBaseRouteTilesByLOD[LOD]
?MovingBaseRouteWorkerSecondsByLOD[LOD]*1000.0
/MovingBaseRouteTilesByLOD[LOD]:0.0,
MovingAnalyticPageTilesByLOD[LOD]
?MovingPageRouteWorkerSecondsByLOD[LOD]*1000.0
/MovingAnalyticPageTilesByLOD[LOD]:0.0,
MovingColumnComputeMsByLOD[LOD],MovingClassificationMsByLOD[LOD],
MovingMcExclusiveMsByLOD[LOD],MovingMcDensityGridMsByLOD[LOD],
MovingMcCellClassMsByLOD[LOD],MovingMcGradientMsByLOD[LOD],
MovingMcInterpolationMsByLOD[LOD],MovingMcDedupMsByLOD[LOD],
MovingMcOtherMsByLOD[LOD]);
}
} }
Streamer.Stop(); Streamer.Stop();
bool bScreenSpaceGatePassing=true; bool bScreenSpaceGatePassing=true;
@@ -809,6 +984,13 @@ namespace StandaloneStreamBenchmark
?static_cast<double>(DensitySamplesByLOD[LOD])/TileCountByLOD[LOD]:0.0; ?static_cast<double>(DensitySamplesByLOD[LOD])/TileCountByLOD[LOD]:0.0;
std::printf("[WorldAssemblerLODSamples] lod=%zu samples=%llu samples_per_tile=%.1f\n", std::printf("[WorldAssemblerLODSamples] lod=%zu samples=%llu samples_per_tile=%.1f\n",
LOD,static_cast<unsigned long long>(DensitySamplesByLOD[LOD]),SamplesPerTile); LOD,static_cast<unsigned long long>(DensitySamplesByLOD[LOD]),SamplesPerTile);
std::printf("[WorldAssemblerLODRouteProfile] lod=%zu base_shape_tiles=%llu base_shape_avg_ms=%.3f analytic_page_tiles=%llu analytic_page_avg_ms=%.3f\n",
LOD,static_cast<unsigned long long>(BaseRouteTilesByLOD[LOD]),
BaseRouteTilesByLOD[LOD]
?BaseRouteWorkerSecondsByLOD[LOD]*1000.0/BaseRouteTilesByLOD[LOD]:0.0,
static_cast<unsigned long long>(PageRouteTilesByLOD[LOD]),
PageRouteTilesByLOD[LOD]
?PageRouteWorkerSecondsByLOD[LOD]*1000.0/PageRouteTilesByLOD[LOD]:0.0);
} }
} }
const double NeighborhoodSeconds=std::chrono::duration<double>(NeighborhoodReady-ColdStart).count(); const double NeighborhoodSeconds=std::chrono::duration<double>(NeighborhoodReady-ColdStart).count();