From 08ceed200d6dacd880f9491830d91ca24f474946 Mon Sep 17 00:00:00 2001 From: Fr0zka Date: Mon, 5 Oct 2026 20:12:51 +0200 Subject: [PATCH] standalone: cheap chunk crossings -- exact-tile slab compile, nearest tiles first (~1.2 s -> 121 ms peak) - Core compiles exact-tile analytic slabs for entering tiles; a Z-column sampler reuses the coherent noise context along each tile column; the viewer keeps residents and prioritises nearby tiles. - 30-crossing straight flight: nearest tile ready 15.8 ms median / 27.6 ms max; full crossing wall 121 ms peak; worker CPU per crossing 545 ms median, 834 ms max. - W1/W8 hashes equal; routes 5642/5642; look, passage, stair, thin-sheet, coarse-panel and far audits pass; bad-shell control fails; path-hash audit kept. Cold whole view, measured by me interleaved (busy machine): 1.242 s vs 1.255 s for 3b37d57, parity. Still open from the owner (LOD0): fins along a strata seam, choppy stair facets. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ --- Standalone/Core/API.md | 23 +- .../include/VoxelForgeCore/VoxelForgeCore.h | 24 +- Standalone/Core/src/ShapeWorldInternal.h | 119 ++++++++- Standalone/Core/src/VoxelForgeCore.cpp | 169 +++++++++--- .../verification/chunk_fast_column_results.md | 73 +++++ Standalone/Source/StandaloneViewer.cpp | 250 +++++++++++++++--- .../Private/VoxelMarchingCubesMesher.cpp | 42 ++- 7 files changed, 614 insertions(+), 86 deletions(-) create mode 100644 Standalone/Core/verification/chunk_fast_column_results.md diff --git a/Standalone/Core/API.md b/Standalone/Core/API.md index 64f6864..c4560ad 100644 --- a/Standalone/Core/API.md +++ b/Standalone/Core/API.md @@ -64,15 +64,20 @@ for (const auto& key : entering) { world.retainTileRegions(entering); ``` -The Core groups fine tiles into pages using absolute tile coordinates, so a page's contents do not -depend on the camera or the order in which the player reaches it. Pages cover a 128-voxel cube (or -one larger coarse tile), with an 8-voxel sampling apron. `compileTileRegion` skips LODs at or above -`fullResolutionLodCount`, keys covered by the initial `ShapeBuildRequest`, and pages already -resident. Its optional output counts requested fine tile keys that needed a newly compiled page; -it does not count the page's internal lattice samples. After all pages needed by the next desired -set compile, call `retainTileRegions` with that set to release pages no longer needed. The Core -owns page lifetime and mesh data; the host keeps still-visible meshes resident until replacement -tiles are generated and uploaded. +By default, the Core groups missing keys by LOD and builds one analytic primitive/bin region for +each entering LOD slab. The region bounds come from the requested absolute tile coordinates, with +an 8-voxel sampling apron; no dense lattice is allocated for unused parts of the slab. A tile's +samples therefore depend on its world coordinates and the world snapshot, not the camera path or +the order in which tiles arrive. `compileTileRegion` skips LODs at or above +`fullResolutionLodCount`, keys covered by the initial `ShapeBuildRequest`, and keys already +resident. Its optional output counts requested keys that received a new region. After the regions +needed by the next desired set compile, call `retainTileRegions` with that set to release regions +that are no longer needed. The Core owns region lifetime and mesh data; the host keeps still-visible +meshes resident until replacement tiles are generated and uploaded. + +Pass `analyticOnly = false` to retain the reference packed-page compiler. Packed pages use absolute +parent-cell coordinates and an 8-voxel apron; each covers a 128-voxel cube or one larger coarse +tile. This path remains available for parity checks and workloads that need the packed lattice. ## Queries and tiles diff --git a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h index 3aee70d..d976f1e 100644 --- a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h +++ b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h @@ -148,6 +148,18 @@ namespace VoxelForgeCore std::uint64_t walkFailures = 0; }; + struct TileRegionCompileStats + { + std::size_t regionsBuilt = 0; + std::size_t tileKeysCovered = 0; + std::uint64_t latticeNodes = 0; + double shapeSeconds = 0.0; + double intervalSeconds = 0.0; + double surfaceSeconds = 0.0; + double workerSeconds = 0.0; + double wallSeconds = 0.0; + }; + struct FarConnectorSegment { Vec3d start, end; float radius = 0.0f; }; struct EditBrush @@ -200,12 +212,16 @@ namespace VoxelForgeCore bool compile(const ShapeBuildRequest& request, const JobScheduler& scheduler, std::string& error); - // Compile any missing absolute-coordinate detail pages needed by entering fine tiles. - // The optional count is requested tile keys newly covered by compiled pages, not lattice - // samples. Existing pages stay resident until retainTileRegions. + // Compile missing detail regions for entering fine tiles. Generated tile keys map to + // shared world-coordinate regions. The default analyticOnly path coalesces each entering + // LOD slab, builds its primitives/bins, and evaluates density on demand without + // rasterizing unused lattice volumes. Set false to use fixed absolute packed pages. The + // optional count is newly covered tile keys; regions stay resident until retainTileRegions. bool compileTileRegion(std::span tiles, std::uint32_t workerLimit, const JobScheduler& scheduler, std::string& error, - std::size_t* outCompiledTiles = nullptr); + std::size_t* outCompiledTiles = nullptr, + TileRegionCompileStats* outStats = nullptr, + bool analyticOnly = true); void retainTileRegions(std::span desiredTiles); void buildFarWorld(); bool isCompiled() const noexcept; diff --git a/Standalone/Core/src/ShapeWorldInternal.h b/Standalone/Core/src/ShapeWorldInternal.h index dc59610..7202558 100644 --- a/Standalone/Core/src/ShapeWorldInternal.h +++ b/Standalone/Core/src/ShapeWorldInternal.h @@ -271,12 +271,14 @@ public: bool bIncludeFarWorld, std::string& Error, const VoxelForgeCore::JobScheduler* InScheduler=nullptr, - bool bInRoundTunnels=false,bool bInProfileDetailBand=false) + bool bInRoundTunnels=false,bool bInProfileDetailBand=false, + bool bValidatePassageRoutes=true) { using Clock=std::chrono::steady_clock; const auto TotalStart=Clock::now(); Owner=&World;WorkerCount=std::clamp(Workers,1,8);DetailVariant=Variant; Scheduler=InScheduler;RoundTunnelsEnabled=bInRoundTunnels; + bAnalyticOnly=false; bProfileDetailBand=bInProfileDetailBand; for(int32 A=0;A<3;++A) { @@ -321,7 +323,9 @@ public: ApplyDetailBand(Stats.DetailBandLines); Stats.SurfaceProxyNodes+=CompleteSignEdgeDistances(); Stats.SurfaceSeconds=std::chrono::duration(Clock::now()-Stage).count(); - ValidatePassageRoutes(); + // Route validation is world-wide. Detail regions reuse the same authored route data and + // do not need to repeat the 5,642-point world audit for every entering tile group. + if(bValidatePassageRoutes)ValidatePassageRoutes(); Built=true; if(bIncludeFarWorld)BuildFarWorld(); const uint64 WordBytes=static_cast(RockWords.size()+CandidateWords.size())*sizeof(uint64); @@ -403,6 +407,35 @@ public: return true; } + bool BuildAnalyticRegion(FStandaloneOwnerWorld& World,const FIntVector& MinVoxels, + const FIntVector& MaxVoxels,const FIntVector& ClipMinVoxels, + const FIntVector& ClipMaxVoxels,EDetailVariant Variant, + bool bInRoundTunnels,std::string& Error) + { + using Clock=std::chrono::steady_clock; + Owner=&World;WorkerCount=1;DetailVariant=Variant;Scheduler=nullptr; + RoundTunnelsEnabled=bInRoundTunnels;bAnalyticOnly=true;bProfileDetailBand=false; + for(int32 A=0;A<3;++A) + { + Min[A]=(&MinVoxels.X)[A]-2;Max[A]=(&MaxVoxels.X)[A]+2; + ClipMin[A]=(&ClipMinVoxels.X)[A];ClipMax[A]=(&ClipMaxVoxels.X)[A]; + Size[A]=Max[A]-Min[A]+1; + if(Size[A]<=0||Size[A]>4096||ClipMax[A](Clock::now()-Stage).count(); + Stats.PrimitiveCount=Primitives.size(); + if(Primitives.empty()) + {Error="owner shape extraction produced no rooms, tunnels, or passages";return false;} + Built=true; + return true; + } + const FBuildStats& GetStats() const { return Stats; } bool IsBuilt() const { return Built; } bool HasFarWorld() const { return FarBuilt; } @@ -490,7 +523,7 @@ public: { int32 IX=0,IY=0,IZ=0; const bool bInteger=NearInteger(X,IX)&&NearInteger(Y,IY)&&NearInteger(Z,IZ); - const bool bPacked=bInteger&&Contains(IX,IY,IZ); + const bool bPacked=!bAnalyticOnly&&bInteger&&Contains(IX,IY,IZ); if(bPacked) return PackedDensityAt(IX,IY,IZ,IsRock(IX,IY,IZ),X,Y,Z); return AnalyticDensityAt(X,Y,Z); @@ -498,7 +531,7 @@ public: float DensityAtLattice(int32 X,int32 Y,int32 Z) const { - const bool bPacked=Contains(X,Y,Z); + const bool bPacked=!bAnalyticOnly&&Contains(X,Y,Z); if(bPacked) return PackedDensityAt(X,Y,Z,IsRock(X,Y,Z),static_cast(X), static_cast(Y),static_cast(Z)); @@ -506,11 +539,19 @@ public: static_cast(Z)); } + bool IsAnalyticOnly() const { return bAnalyticOnly; } + void DensityAtLatticeX8(int32 XStart,int32 XStep,int32 Y,int32 Z, int32 Count,float OutDensity[8]) const { Count=std::clamp(Count,0,8); if(Count==0)return; + if(bAnalyticOnly) + { + for(int32 Lane=0;Lane=Min[1]&&Y<=Max[1] &&Z>=Min[2]&&Z<=Max[2] &&XStart>=Min[0] @@ -3430,6 +3471,75 @@ private: } } +public: + void DensityAtLatticeZRun(int32 X,int32 Y,int32 ZStart,int32 ZStep, + int32 Count,float* OutDensity) const + { + if(Count<=0||OutDensity==nullptr)return; + if(!bAnalyticOnly) + { + for(int32 Lane=0;Lane(X),static_cast(Y),static_cast(Owner->Seed)); + for(int32 Offset=0;Offsetsecond; + int32 BatchCount=1; + while(BatchCount=Min[0]&&X<=Max[0] + &&Y>=Min[1]&&Y<=Max[1]&&SampleZ>=Min[2]&&SampleZ<=Max[2]; + float RockDistance=Samples[Lane].Distance; + if(bInsideFine&&DetailVariant!=EDetailVariant::Smooth + &&std::abs(RockDistance)<=DetailSupremum+0.125f) + { + const float Fade=DetailBandFadeAt(static_cast(X), + static_cast(Y),static_cast(SampleZ),RockDistance, + Samples[Lane].SurfaceSDF,Samples[Lane].CaveInfluence, + Samples[Lane].FloorInfluence); + if(Fade>0.0f)RockDistance+=DetailNoiseOnLine(static_cast(X), + static_cast(Y),static_cast(SampleZ), + Samples[Lane].SurfaceSDF,Samples[Lane].CaveInfluence, + Samples[Lane].FloorInfluence,Samples[Lane].PassageInfluence, + DetailNoiseLine)*Fade; + } + if(Samples[Lane].bCriticalAir) + RockDistance=std::min(RockDistance,-1.0f/16.0f); + const bool bRock=RockDistance>=0.0f; + float Density=-RockDistance; + if((Density<0.0f)!=bRock||std::abs(Density)<0.05f) + Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),0.05f); + OutDensity[Offset+Lane]=ApplyEditBrushes(Density,X,Y,SampleZ); + } + Offset+=BatchCount; + } + } + + void DensityAtLatticeZ8(int32 X,int32 Y,int32 ZStart,int32 ZStep, + int32 Count,float OutDensity[8]) const + { + DensityAtLatticeZRun(X,Y,ZStart,ZStep,std::clamp(Count,0,8),OutDensity); + } + +private: + float CoarseRockDistance(double X,double Y,double Z,bool* OutCriticalAir) const { const float Base=StrataDistance(Z); @@ -3906,4 +4016,5 @@ private: std::array,3> CandidateAxisWords; std::vector CandidateWords; FBuildStats Stats; + bool bAnalyticOnly=false; }; diff --git a/Standalone/Core/src/VoxelForgeCore.cpp b/Standalone/Core/src/VoxelForgeCore.cpp index 83317cb..8fd7bd1 100644 --- a/Standalone/Core/src/VoxelForgeCore.cpp +++ b/Standalone/Core/src/VoxelForgeCore.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -316,59 +317,156 @@ namespace VoxelForgeCore bool World::compileTileRegion(std::span tiles,std::uint32_t workerLimit, const JobScheduler& scheduler,std::string& error, - std::size_t* outCompiledTiles) + std::size_t* outCompiledTiles, + TileRegionCompileStats* outStats,bool analyticOnly) { if(outCompiledTiles)*outCompiledTiles=0; + if(outStats)*outStats={}; if(!impl_||!impl_->owner||!impl_->shape||!impl_->shape->IsBuilt()) {error="the base world must be compiled before adding tile regions";return false;} if(tiles.empty())return true; - std::unordered_map,TileKeyHash> pendingPages; - pendingPages.reserve(tiles.size()); + struct FPendingRegion + { + TileKey pageKey; + std::vector tiles; + }; + std::vector pendingRegions; + pendingRegions.reserve(tiles.size()); + std::unordered_map regionByPage; + regionByPage.reserve(tiles.size()); + std::unordered_set seenTiles; + seenTiles.reserve(tiles.size()*2+1); { std::shared_lock lock(impl_->tileRegionMutex); - for(const TileKey& key:tiles) + for(std::size_t inputIndex=0;inputIndex8) {error="tile region contains an invalid LOD";return false;} if(key.lod>=impl_->configuration.fullResolutionLodCount ||CoveredByBaseShape(impl_->buildRequest,impl_->configuration,key))continue; - const TileKey pageKey=ShapePageKey(key); - if(!impl_->tileRegions.contains(pageKey))pendingPages[pageKey].push_back(key); + if(impl_->tileRegions.contains(key)||!seenTiles.insert(key).second)continue; + // Analytic regions evaluate only requested fine tiles and can safely coalesce a + // whole entering LOD slab. Packed pages retain their fixed absolute parent cells. + const TileKey pageKey=analyticOnly?TileKey{{0,0,0},key.lod}:ShapePageKey(key); + auto [found,inserted]=regionByPage.try_emplace(pageKey,pendingRegions.size()); + if(inserted)pendingRegions.push_back({pageKey,{}}); + pendingRegions[found->second].tiles.push_back(key); } } - if(pendingPages.empty())return true; + if(pendingRegions.empty())return true; + constexpr std::int32_t PagePad=8; + using Clock=std::chrono::steady_clock; + const auto compileStart=Clock::now(); + TileRegionCompileStats stats; std::size_t compiledTileCount=0; - for(const auto& [pageKey,requestedTiles]:pendingPages) + std::vector> compiledRegions(pendingRegions.size()); + std::vector regionErrors(pendingRegions.size()); + const ShapeBuildRequest& base=impl_->buildRequest; + const std::uint32_t clampedWorkers=std::clamp(workerLimit,1,8); + + if(analyticOnly) { - const std::int32_t tileCount=TilesPerShapePage(pageKey.lod); - const std::int32_t tileExtent=CHUNK_SIZE<(); - const ShapeBuildRequest& base=impl_->buildRequest; - if(!page->Build(*impl_->owner,fineMin,fineMax,pageMin,pageMax, - static_cast(std::clamp(workerLimit,1,8)), - ToNative(base.detail),false,error,&scheduler, - base.roundTunnels,base.profileDetailBand))return false; - if(!impl_->editLog.empty())page->SetEditTransaction(impl_->editLog); - + const auto BuildAnalyticRegionAt=[&](std::size_t index) { - std::unique_lock lock(impl_->tileRegionMutex); - impl_->tileRegions.try_emplace(pageKey,std::move(page)); - impl_->tileRegionCount.store(impl_->tileRegions.size(),std::memory_order_release); + const FPendingRegion& pending=pendingRegions[index]; + FIntVector regionMin(MAX_int32,MAX_int32,MAX_int32); + FIntVector regionMax(MIN_int32,MIN_int32,MIN_int32); + for(const TileKey& key:pending.tiles) + { + const FVoxelTileKey native=ToNative(key); + const FIntVector origin=native.OriginVoxels(); + const std::int32_t extent=native.ExtentVoxels(); + for(std::int32_t axis=0;axis<3;++axis) + { + (®ionMin.X)[axis]=std::min((®ionMin.X)[axis],(&origin.X)[axis]); + (®ionMax.X)[axis]=std::max((®ionMax.X)[axis],(&origin.X)[axis]+extent); + } + } + const FIntVector fineMin(regionMin.X-PagePad,regionMin.Y-PagePad,regionMin.Z-PagePad); + const FIntVector fineMax(regionMax.X+PagePad,regionMax.Y+PagePad,regionMax.Z+PagePad); + auto region=std::make_shared(); + if(!region->BuildAnalyticRegion(*impl_->owner,fineMin,fineMax,regionMin,regionMax, + ToNative(base.detail),base.roundTunnels,regionErrors[index]))return; + compiledRegions[index]=std::move(region); + }; + struct FAnalyticBuildContext + { + decltype(BuildAnalyticRegionAt)* BuildAt; + } context{&BuildAnalyticRegionAt}; + std::array workerActive{}; + if(pendingRegions.size()==1) + { + // A chunk crossing normally contributes one entering slab. Building that small + // immutable primitive/bin view inline avoids paying for a scheduler wake-up while + // the existing tile workers are already busy finishing the previous crossing. + BuildAnalyticRegionAt(0); + if(compiledRegions[0])stats.workerSeconds=compiledRegions[0]->GetStats().ShapeSeconds; } - compiledTileCount+=requestedTiles.size(); + else + stats.workerSeconds=scheduler.parallel_for(pendingRegions.size(),1,clampedWorkers, + &context,[](void* raw,std::size_t index,std::size_t) + { + auto& ctx=*static_cast(raw); + (*ctx.BuildAt)(index); + },workerActive.data(),workerActive.size()); + for(std::size_t i=0;ieditLog.empty())compiledRegions[i]->SetEditTransaction(impl_->editLog); + stats.shapeSeconds+=compiledRegions[i]->GetStats().ShapeSeconds; + stats.tileKeysCovered+=pendingRegions[i].tiles.size(); + } + stats.regionsBuilt=pendingRegions.size(); } + else + { + for(std::size_t i=0;i(); + if(!region->Build(*impl_->owner,fineMin,fineMax,regionMin,regionMax, + static_cast(clampedWorkers),ToNative(base.detail),false,error,&scheduler, + base.roundTunnels,base.profileDetailBand,false))return false; + if(!impl_->editLog.empty())region->SetEditTransaction(impl_->editLog); + const auto& build=region->GetStats(); + ++stats.regionsBuilt; + stats.tileKeysCovered+=pending.tiles.size(); + stats.latticeNodes+=build.SignNodes; + stats.shapeSeconds+=build.ShapeSeconds; + stats.intervalSeconds+=build.IntervalSeconds; + stats.surfaceSeconds+=build.SurfaceSeconds; + stats.workerSeconds+=build.RockIntervalLines.WorkerSeconds + +build.CandidateAxisLines.WorkerSeconds + +build.CandidateMergeLines.WorkerSeconds + +build.DetailBandLines.WorkerSeconds; + compiledRegions[i]=std::move(region); + } + } + { + std::unique_lock lock(impl_->tileRegionMutex); + for(std::size_t i=0;itileRegions.try_emplace(key,compiledRegions[i]).second)++compiledTileCount; + } + impl_->tileRegionCount.store(impl_->tileRegions.size(),std::memory_order_release); + } + stats.wallSeconds=std::chrono::duration(Clock::now()-compileStart).count(); if(outCompiledTiles)*outCompiledTiles=compiledTileCount; + if(outStats)*outStats=stats; return true; } @@ -379,7 +477,7 @@ namespace VoxelForgeCore desired.reserve(desiredTiles.size()*2+1); for(const TileKey& key:desiredTiles) if(key.lod>=0&&key.lodconfiguration.fullResolutionLodCount) - desired.insert(ShapePageKey(key)); + desired.insert(key); std::unique_lock lock(impl_->tileRegionMutex); std::erase_if(impl_->tileRegions,[&](const auto& entry){return !desired.contains(entry.first);}); impl_->tileRegionCount.store(impl_->tileRegions.size(),std::memory_order_release); @@ -483,7 +581,7 @@ namespace VoxelForgeCore if(impl_->tileRegionCount.load(std::memory_order_acquire)!=0) { std::shared_lock lock(impl_->tileRegionMutex); - if(const auto found=impl_->tileRegions.find(ShapePageKey(key));found!=impl_->tileRegions.end()) + if(const auto found=impl_->tileRegions.find(key);found!=impl_->tileRegions.end()) page=found->second; } const FStandaloneShapeWorld& tileShape=page?*page:*impl_->shape; @@ -584,8 +682,11 @@ namespace VoxelForgeCore impl_->shape->SetEditTransaction(impl_->editLog); { std::shared_lock lock(impl_->tileRegionMutex); + std::unordered_set updatedRegions; + updatedRegions.reserve(impl_->tileRegions.size()); for(const auto& [key,page]:impl_->tileRegions) - if(page)page->SetEditTransaction(impl_->editLog); + if(page&&updatedRegions.insert(page.get()).second) + page->SetEditTransaction(impl_->editLog); } const FIntVector min=impl_->shape->GetEditBoundsMin(),max=impl_->shape->GetEditBoundsMax(); EditResult result;result.digest=digest; diff --git a/Standalone/Core/verification/chunk_fast_column_results.md b/Standalone/Core/verification/chunk_fast_column_results.md new file mode 100644 index 0000000..3519599 --- /dev/null +++ b/Standalone/Core/verification/chunk_fast_column_results.md @@ -0,0 +1,73 @@ +# Chunk streaming optimization results + +Build: `VoxelForgeChunkStreamFastColumn.exe`; built with `Standalone/Build.ps1` (`/MP8`) and run via `Core/verification/RunMachine.ps1` (`BelowNormal`, affinity `0xFF00`). No `0xC0000142` occurred. Cache-use audits report zero hits. + +## Flight benchmark + +Command: `--shape-world --test-flight-crossings=30 --flight-speed-mps=16 --workers=8`. Straight +Y flight, 30 unit chunk crossings. `crossing wall` is from crossing start through all requested tiles; `worker CPU sum` sums each tile job's execution time across workers. + +| Crossing | Tiles | Page-covered | Regions | Core region wall ms | Nearest ready ms | Nearest tile | Worker CPU sum ms | Crossing wall ms | +|---:|---:|---:|---:|---:|---:|:---|---:|---:| +| 1 | 144 | 128 | 1 | 9.1 | 16.62 | `-1,-4,-2,lod1` | 478 | 105 | +| 2 | 144 | 0 | 0 | 0.0 | 2.48 | `-3,-10,-3,lod0` | 128 | 19 | +| 3 | 108 | 0 | 0 | 0.0 | 2.73 | `-1,0,-1,lod2` | 98 | 15 | +| 4 | 144 | 128 | 1 | 6.2 | 9.75 | `-3,-8,-3,lod0` | 450 | 67 | +| 5 | 144 | 128 | 1 | 7.7 | 11.81 | `-2,-2,-2,lod1` | 405 | 62 | +| 6 | 144 | 128 | 1 | 6.6 | 11.25 | `-3,-6,-4,lod0` | 545 | 78 | +| 7 | 76 | 0 | 0 | 0.0 | 1.90 | `-1,2,-1,lod3` | 44 | 8 | +| 8 | 144 | 128 | 1 | 5.7 | 9.35 | `-3,-4,-4,lod0` | 452 | 69 | +| 9 | 144 | 128 | 1 | 7.2 | 14.34 | `-1,0,-2,lod1` | 618 | 89 | +| 10 | 144 | 128 | 1 | 5.8 | 9.20 | `-3,-2,-3,lod0` | 511 | 75 | +| 11 | 108 | 0 | 0 | 0.0 | 2.25 | `-1,2,-1,lod2` | 80 | 12 | +| 12 | 144 | 144 | 2 | 8.9 | 16.00 | `-3,0,-3,lod0` | 729 | 108 | +| 13 | 144 | 128 | 1 | 7.5 | 21.19 | `-2,2,-2,lod1` | 700 | 100 | +| 14 | 144 | 144 | 2 | 11.7 | 27.57 | `-3,2,-4,lod0` | 786 | 118 | +| 15 | 26 | 0 | 0 | 0.0 | 10.29 | `-1,6,-1,lod8` | 123 | 19 | +| 16 | 144 | 144 | 2 | 9.2 | 23.58 | `-3,4,-4,lod0` | 798 | 115 | +| 17 | 144 | 128 | 1 | 7.5 | 24.21 | `-1,4,-2,lod1` | 675 | 105 | +| 18 | 144 | 144 | 2 | 9.2 | 20.95 | `-3,6,-3,lod0` | 834 | 120 | +| 19 | 108 | 0 | 0 | 0.0 | 2.22 | `-1,4,-1,lod2` | 62 | 10 | +| 20 | 144 | 144 | 2 | 9.8 | 20.24 | `-3,8,-3,lod0` | 704 | 106 | +| 21 | 144 | 128 | 1 | 6.7 | 22.71 | `-2,6,-2,lod1` | 771 | 121 | +| 22 | 144 | 144 | 2 | 10.2 | 21.40 | `-3,10,-4,lod0` | 605 | 93 | +| 23 | 76 | 0 | 0 | 0.0 | 1.88 | `-1,4,-1,lod3` | 38 | 6 | +| 24 | 144 | 144 | 2 | 8.5 | 19.45 | `-3,12,-4,lod0` | 620 | 93 | +| 25 | 144 | 128 | 1 | 8.2 | 23.03 | `-1,8,-2,lod1` | 758 | 116 | +| 26 | 144 | 144 | 2 | 8.2 | 15.76 | `-3,14,-3,lod0` | 659 | 97 | +| 27 | 108 | 0 | 0 | 0.0 | 2.74 | `-1,6,-1,lod2` | 51 | 9 | +| 28 | 144 | 144 | 2 | 8.2 | 16.06 | `-3,16,-3,lod0` | 663 | 97 | +| 29 | 144 | 128 | 1 | 7.6 | 20.73 | `-2,10,-2,lod1` | 483 | 80 | +| 30 | 144 | 144 | 2 | 9.2 | 21.09 | `-3,18,-4,lod0` | 700 | 104 | + +Flight summary: [FlightBenchmarkComplete] crossings=30/30 speed=16.00m/s elapsed=15.006s cache_hits=0 crossing_deltas=unit-+Y nearest_ready_p50=15.762ms nearest_ready_p95=23.577ms nearest_ready_max=27.570ms max_crossing_wall=0.121s max_worker_cpu_sum=0.834s resident=2226/2226 missing_tiles=0 deferred_leavers=0 uploads_drained=yes status=PASS + +Nearest-ready median/p95/max: 15.762 / 23.577 / 27.570 ms. Maximum full crossing wall: 121 ms. Aggregate worker CPU sum: median 545 ms, p95 798 ms, max 834 ms. The 200 ms wall-latency target is met; a 200 ms aggregate-CPU interpretation is not. All 30 crossings completed; resident 2226/2226, missing tiles 0, deferred leavers 0, uploads drained, cache hits 0. + +## Profile + +The prior packed-page crossing in `owner_final6_chunk_crossing.stdout.log` rebuilt 16 pages covering 128 tiles: Core page wall 1.1828 s, then tile worker sum 156 ms and crossing wall 1.206 s. The analytic path now builds one region for a typical new slab (sometimes two); the flight log shows 0 lattice nodes and 0 interval/sign or detail-band compile time, with primitive/bin setup at about 6–12 ms per region. Crossings already covered by the initial region or retained tiles build 0 new regions. The 128 LOD1 tile meshes dominate new-slab work at roughly 0.4–0.8 s aggregate worker CPU, while all tile jobs finish in 0.06–0.12 s wall. The 30-crossing residency audit found no holes, deferred leavers, or undrained uploads. + +Crossing 1 profile: Core slab build 9.6 ms wall / 9.1 ms worker, zero lattice nodes and zero interval/detail work; tile meshing 478 ms aggregate worker CPU (128 LOD1 tiles: 472 ms; 16 LOD2: 6 ms), 105 ms crossing wall, nearest tile ready in 16.62 ms. This replaces the baseline 1.183 s page build and 1.206 s crossing wall. + +## Determinism and cold view + +W1 and W8 full-clipmap command: `--stream-benchmark --shape-world --force-generate --stream-hash --workers=N --clip-radius=3`. Both generated 2226/2226 tiles with cache hits 0 and identical hashes: density `0x0ed4113e468cda86`, mesh `0x20c2f9d3e3d28c6e`, payload `0xd0349793eed57609`, topology `0x0ed6c6bc668bfb9b`. + +Interleaved cold comparison used `--stream-benchmark --shape-world --force-generate --stream-hash --workers=8 --clip-radius=3`, order baseline A / candidate A / candidate B / baseline B. `StreamFullClipmap` walls: baseline 0.826 / 0.759 s (mean 0.793 s); candidate 0.744 / 0.729 s (mean 0.737 s), with identical hashes and zero cache hits. + +## Audits + +- Route audit: 5642/5642 PASS. +- Shape/look checks: flat surface, grid stair, thin sheet, flat patch, surface band, detail PASS. Six fixed captures match the prior candidate bit-for-bit. Against Round 11, changed pixels were 1432 (chamber), 310 (tunnel), 3879 (passage), 514 (far), 1811 (passage-floor close-up), and 0 (tunnel bend); each view remains 800×1280. Against `OwnerFinal6`, five views are bit-identical and 339/1,024,000 passage-floor pixels differ (mean channel delta 0.003896, max 58). Passage-floor recapture is bit-identical. +- Passage wall detail: 13,271 passage samples, ratio 1.152, PASS. +- Grid-stair distance: 952,997 sign edges, zero candidate fallback edges and zero both-fallback edges, PASS. +- Fine shard audit: zero thin sheets, PASS. Coarse panel audit: zero off-surface panels and zero panel triangles, PASS. +- Far survival: 7,200 boundary columns, zero missing/extra/hole samples, PASS. Bad-shell control: expected FAIL, 16 missing boundary surfaces, 512 removed triangles, exit 14. + +## Log files + +- Flight: `chunk_fast_column_flight30.stdout.log` +- Path hash: `chunk_fast_column_path_hash.stdout.log` +- W1/W8: `chunk_fast_column_hash_w1.stdout.log`, `chunk_fast_column_hash_w8.stdout.log` +- Cold runs: `chunk_fast_column_cold_baseline_a.stdout.log`, `chunk_fast_column_cold_candidate_a.stdout.log`, `chunk_fast_column_cold_candidate_b.stdout.log`, `chunk_fast_column_cold_baseline_b.stdout.log` +- Audits: `chunk_fast_column_shape_audit.stdout.log`, `chunk_fast_column_passage_detail.stdout.log`, `chunk_fast_column_grid_stair.stdout.log`, `chunk_fast_column_mesh_far_audits.stdout.log`, `chunk_fast_column_bad_shell_control.stdout.log` diff --git a/Standalone/Source/StandaloneViewer.cpp b/Standalone/Source/StandaloneViewer.cpp index 453a880..411a977 100644 --- a/Standalone/Source/StandaloneViewer.cpp +++ b/Standalone/Source/StandaloneViewer.cpp @@ -442,7 +442,17 @@ namespace uint64 Samples=0; double WorkerSeconds=0.0; double CorePageSeconds=0.0; + double CorePageWorkerSeconds=0.0; uint64 PageCoveredTiles=0; + uint64 PageRegions=0; + uint64 PageLatticeNodes=0; + FVoxelTileKey NearestNewTile{}; + double NearestReadySeconds=-1.0; + bool bHasNearestNewTile=false; + uint64 TilesByLevel[MaxLevels+1]{}; + uint64 SamplesByLevel[MaxLevels+1]{}; + double WorkerSecondsByLevel[MaxLevels+1]{}; + double WallSeconds=0.0; std::chrono::steady_clock::time_point StartTime{}; bool bReported=false; }; @@ -559,11 +569,16 @@ namespace bool bCompareEvaluators=false; bool bParityOnly=false; bool bDensityHash=false; - bool bTestOneChunkCrossing=false; - bool bCrossingMoveStarted=false; - bool bCrossingMoveFinished=false; - bool bCrossingClosePosted=false; - std::chrono::steady_clock::time_point CrossingMoveStopTime{}; + int32 FlightTargetCrossings=0; + int32 FlightObservedCrossings=0; + uint64 FlightStartCrossingIndex=0; + float FlightSpeedMps=16.0f; + bool bFlightStarted=false; + bool bFlightFinished=false; + bool bFlightClosePosted=false; + bool bFlightCrossingsValid=true; + V3 FlightStartPosition{}; + std::chrono::steady_clock::time_point FlightStartTime{}; bool bCaptureAfterClipmap=false; bool bQuitAfterClipmap=false; bool bPrintedClipOverride=false; @@ -1198,6 +1213,14 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; } if(NewDesired.CenterChunk==BuildTargetChunk)return; const FIntVector Previous=BuildTargetChunk; + const FIntVector CenterDelta(NewDesired.CenterChunk.X-Previous.X, + NewDesired.CenterChunk.Y-Previous.Y,NewDesired.CenterChunk.Z-Previous.Z); + if(FlightTargetCrossings>0&&bFlightStarted&&!bFlightFinished) + { + ++FlightObservedCrossings; + if(CenterDelta.X!=0||CenterDelta.Y!=1||CenterDelta.Z!=0) + bFlightCrossingsValid=false; + } std::unordered_set NewSet; NewSet.reserve(NewDesired.Tiles.size()*2+1); for(const FVoxelTileKey& Key:NewDesired.Tiles)NewSet.insert(Key); @@ -1206,6 +1229,17 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; const auto CrossingStart=std::chrono::steady_clock::now(); double PageCompileSeconds=0.0; uint64 PageCoveredTiles=0; + VoxelForgeCore::TileRegionCompileStats PageStats; + const uint64 NextCrossing=CrossingIndex+1; + FCrossingWork& NextWork=CrossingWork[NextCrossing]; + NextWork.StartTime=CrossingStart; + for(const FVoxelTileKey& Key:NewDesired.Tiles) + if(NewSet.contains(Key)&&!ResidentTiles.contains(Key)) + { + NextWork.NearestNewTile=Key; + NextWork.bHasNearestNewTile=true; + break; + } if(bShapeWorld&&ShapeWorld) { std::vector NewCoreTiles; @@ -1216,8 +1250,8 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; std::string PageError; size_t CompiledPageCount=0; if(!ShapeWorld->compileTileRegion(NewCoreTiles, - static_cast(std::min(4,WorkerCount)),CoreScheduler,PageError, - &CompiledPageCount)) + static_cast(std::clamp(WorkerCount,1,8)),CoreScheduler,PageError, + &CompiledPageCount,&PageStats,true)) { std::fprintf(stderr,"[ShapeRegionPage] compile failed at center=(%d,%d,%d): %s\n", NewDesired.CenterChunk.X,NewDesired.CenterChunk.Y,NewDesired.CenterChunk.Z, @@ -1233,11 +1267,13 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; for(const FVoxelTileKey& Key:Leavers)DeferredLeaverTiles.insert(Key); const double PageSeconds=std::chrono::duration( std::chrono::steady_clock::now()-CrossingStart).count(); - PageCompileSeconds=PageSeconds; - std::printf("[ShapeRegionPage] center=(%d,%d,%d) requested_tiles=%zu page_covered_tiles=%llu worker_limit=%d seconds=%.4f retained_resident=%zu deferred_leavers=%zu\n", + PageCompileSeconds=PageStats.wallSeconds; + std::printf("[ShapeRegionPage] region_mode=analytic-lod-slab center=(%d,%d,%d) requested_tiles=%zu page_covered_tiles=%llu regions=%zu lattice_nodes=%llu worker_limit=%d shape=%.4fs intervals_signs=%.4fs detail=%.4fs core_worker_sum=%.4fs seconds=%.4f retained_resident=%zu deferred_leavers=%zu\n", NewDesired.CenterChunk.X,NewDesired.CenterChunk.Y,NewDesired.CenterChunk.Z, NewCoreTiles.size(),static_cast(PageCoveredTiles), - std::min(4,WorkerCount),PageSeconds, + PageStats.regionsBuilt,static_cast(PageStats.latticeNodes), + std::clamp(WorkerCount,1,8),PageStats.shapeSeconds,PageStats.intervalSeconds, + PageStats.surfaceSeconds,PageStats.workerSeconds,PageSeconds, ResidentTiles.size()-std::min(ResidentTiles.size(),Leavers.size()),Leavers.size()); } else @@ -1260,8 +1296,9 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; } FCrossingWork& Work=CrossingWork[CrossingIndex]; Work.SubmittedJobs=Update.NewJobs;Work.PendingJobs=Update.NewJobs; - Work.StartTime=CrossingStart; Work.PageCoveredTiles=PageCoveredTiles;Work.CorePageSeconds=PageCompileSeconds; + Work.CorePageWorkerSeconds=PageStats.workerSeconds; + Work.PageRegions=PageStats.regionsBuilt;Work.PageLatticeNodes=PageStats.latticeNodes; BuildTargetChunk=Update.CenterChunk;bGenerationRunning=true; std::printf("[ChunkCrossing] id=%llu center=(%d,%d,%d) delta=(%d,%d,%d) desired=%d retained=%d new_jobs=%d deferred_resident=%zu cancelled_queued=%d full_detail=%d\n", static_cast(CrossingIndex),Update.CenterChunk.X,Update.CenterChunk.Y,Update.CenterChunk.Z, @@ -1416,9 +1453,11 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; Work.bReported=true; const double AverageMs=Work.FinishedJobs>0 ?Work.WorkerSeconds*1000.0/static_cast(Work.FinishedJobs):0.0; - const double WallSeconds=Work.StartTime.time_since_epoch().count()!=0 + Work.WallSeconds=Work.StartTime.time_since_epoch().count()!=0 ?std::chrono::duration(std::chrono::steady_clock::now()-Work.StartTime).count():0.0; - std::printf("[ChunkCrossingWork] id=%llu submitted=%llu finished=%llu generated=%llu cancelled_queued=%llu stale=%llu samples=%.2fM page_covered_tiles=%llu core_page=%.4fs worker_sum=%.3fs wall=%.3fs avg=%.3fms\n", + const double NearestReadyMs=Work.NearestReadySeconds>=0.0 + ?Work.NearestReadySeconds*1000.0:-1.0; + std::printf("[ChunkCrossingWork] id=%llu submitted=%llu finished=%llu generated=%llu cancelled_queued=%llu stale=%llu samples=%.2fM page_covered_tiles=%llu page_regions=%llu page_nodes=%llu core_page=%.4fs core_page_worker=%.4fs nearest_ready=%.3fms nearest_tile=(%d,%d,%d,lod%d) worker_sum=%.3fs wall=%.3fs avg=%.3fms\n", static_cast(Pair.first), static_cast(Work.SubmittedJobs), static_cast(Work.FinishedJobs), @@ -1426,8 +1465,22 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; static_cast(Work.CancelledQueuedJobs), static_cast(Work.StaleResults), static_cast(Work.Samples)/1.0e6, - static_cast(Work.PageCoveredTiles),Work.CorePageSeconds, - Work.WorkerSeconds,WallSeconds,AverageMs); + static_cast(Work.PageCoveredTiles), + static_cast(Work.PageRegions), + static_cast(Work.PageLatticeNodes), + Work.CorePageSeconds,Work.CorePageWorkerSeconds,NearestReadyMs, + Work.NearestNewTile.Coord.X,Work.NearestNewTile.Coord.Y, + Work.NearestNewTile.Coord.Z,Work.NearestNewTile.Level, + Work.WorkerSeconds,Work.WallSeconds,AverageMs); + for(int32 Level=0;Level<=MaxLevels;++Level) + if(Work.TilesByLevel[Level]>0) + std::printf("[ChunkCrossingLevel] id=%llu lod=%d tiles=%llu samples=%.3fM worker_sum=%.4fs avg=%.3fms\n", + static_cast(Pair.first),Level, + static_cast(Work.TilesByLevel[Level]), + static_cast(Work.SamplesByLevel[Level])/1.0e6, + Work.WorkerSecondsByLevel[Level], + Work.WorkerSecondsByLevel[Level]*1000.0/ + static_cast(Work.TilesByLevel[Level])); } } @@ -1442,6 +1495,16 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; FCrossingWork& Crossing=CrossingWork[Tile.RequestedCrossing]; if(Crossing.PendingJobs>0)--Crossing.PendingJobs; ++Crossing.FinishedJobs;Crossing.Samples+=Tile.DensitySamples;Crossing.WorkerSeconds+=Tile.WorkerSeconds; + if(Level>=0&&Level<=MaxLevels) + { + ++Crossing.TilesByLevel[Level]; + Crossing.SamplesByLevel[Level]+=Tile.DensitySamples; + Crossing.WorkerSecondsByLevel[Level]+=Tile.WorkerSeconds; + } + if(Crossing.bHasNearestNewTile&&Tile.Key==Crossing.NearestNewTile + &&Crossing.NearestReadySeconds<0.0) + Crossing.NearestReadySeconds=std::chrono::duration( + Tile.CompletedAt-Crossing.StartTime).count(); if(!Streamer->IsDesired(Tile.Key)||ResidentTiles.contains(Tile.Key)) {++Crossing.StaleResults;continue;} ++Crossing.Tiles; @@ -1475,14 +1538,56 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; } } if(bShapePipelineActive)TryCompleteShapePipeline(); - if(bTestOneChunkCrossing&&bCrossingMoveFinished&&!bCrossingClosePosted - &&DirtyBatchSet.empty()) + if(FlightTargetCrossings>0&&bFlightFinished&&!bFlightClosePosted + &&DirtyBatchSet.empty()&&DeferredLeaverTiles.empty()) { - const auto Crossing=CrossingWork.find(1); - if(Crossing!=CrossingWork.end()&&Crossing->second.bReported) + bool bAllFlightWorkReported=true; + bool bCrossingLatencyUnderTarget=true; + double MaxCrossingWallSeconds=0.0,MaxWorkerCpuSeconds=0.0; + std::vector NearestReadyMilliseconds; + for(uint64 Crossing=FlightStartCrossingIndex+1; + Crossing<=CrossingIndex;++Crossing) { - bCrossingClosePosted=true; - std::printf("[CrossingTestComplete] crossing work and its bounded uploads drained; closing this test viewer.\n"); + const auto It=CrossingWork.find(Crossing); + if(It==CrossingWork.end()||!It->second.bReported) + {bAllFlightWorkReported=false;continue;} + const FCrossingWork& Work=It->second; + MaxCrossingWallSeconds=std::max(MaxCrossingWallSeconds,Work.WallSeconds); + MaxWorkerCpuSeconds=std::max(MaxWorkerCpuSeconds,Work.WorkerSeconds); + if(Work.WallSeconds>=0.200)bCrossingLatencyUnderTarget=false; + if(Work.NearestReadySeconds>=0.0) + NearestReadyMilliseconds.push_back(Work.NearestReadySeconds*1000.0); + } + if(bAllFlightWorkReported) + { + bFlightClosePosted=true; + const double Elapsed=std::chrono::duration( + std::chrono::steady_clock::now()-FlightStartTime).count(); + const uint64 CacheHits=Streamer?Streamer->GetCacheHits():0; + std::size_t DesiredResidentCount=0; + for(const FVoxelTileKey& Key:DesiredTiles) + if(ResidentTiles.contains(Key))++DesiredResidentCount; + std::sort(NearestReadyMilliseconds.begin(),NearestReadyMilliseconds.end()); + const auto Percentile=[&](std::size_t Percent) + { + if(NearestReadyMilliseconds.empty())return -1.0; + const std::size_t Index=(NearestReadyMilliseconds.size()-1)*Percent/100; + return NearestReadyMilliseconds[Index]; + }; + const double NearestMaximum=NearestReadyMilliseconds.empty()?-1.0: + NearestReadyMilliseconds.back(); + const std::size_t MissingDesired=DesiredTiles.size()-DesiredResidentCount; + const bool bPass=FlightObservedCrossings==FlightTargetCrossings + &&bFlightCrossingsValid&&CacheHits==0&&bCrossingLatencyUnderTarget + &&MissingDesired==0&&DeferredLeaverTiles.empty()&&DirtyBatchSet.empty(); + std::printf("[FlightBenchmarkComplete] crossings=%d/%d speed=%.2fm/s elapsed=%.3fs cache_hits=%llu crossing_deltas=%s nearest_ready_p50=%.3fms nearest_ready_p95=%.3fms nearest_ready_max=%.3fms max_crossing_wall=%.3fs max_worker_cpu_sum=%.3fs resident=%zu/%zu missing_tiles=%zu deferred_leavers=%zu uploads_drained=%s status=%s\n", + FlightObservedCrossings,FlightTargetCrossings,FlightSpeedMps,Elapsed, + static_cast(CacheHits), + bFlightCrossingsValid?"unit-+Y":"invalid", + Percentile(50),Percentile(95),NearestMaximum,MaxCrossingWallSeconds, + MaxWorkerCpuSeconds,DesiredResidentCount,DesiredTiles.size(),MissingDesired, + DeferredLeaverTiles.size(),DirtyBatchSet.empty()?"yes":"no", + bPass?"PASS":"FAIL"); std::fflush(stdout); PostMessageA(Window,WM_CLOSE,0,0); } @@ -1547,20 +1652,27 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; { PumpGeneration(); const auto Now=std::chrono::steady_clock::now(); - if(bTestOneChunkCrossing&&bFullDetailReported&&!bCrossingMoveStarted&&!bCrossingMoveFinished) + if(FlightTargetCrossings>0&&bFullDetailReported&&!bFlightStarted) { - bCrossingMoveStarted=true; - CrossingMoveStopTime=Now+std::chrono::milliseconds(350); - Keys[VK_SHIFT]=true;Keys['W']=true; - std::printf("[CrossingTestMove] moving forward for 350 ms after full detail.\n"); + bFlightStarted=true;FlightStartTime=Now;FlightStartPosition=Position; + FlightStartCrossingIndex=CrossingIndex;FlightObservedCrossings=0; + bFlightCrossingsValid=true; + std::printf("[FlightBenchmarkStart] crossings=%d speed=%.2fm/s direction=+Y start=(%.2f,%.2f,%.2f)m cache_mode=none\n", + FlightTargetCrossings,FlightSpeedMps,Position.X,Position.Y,Position.Z); std::fflush(stdout); } - if(bCrossingMoveStarted&&Now>=CrossingMoveStopTime) + if(FlightTargetCrossings>0&&bFlightStarted&&!bFlightFinished) { - Keys[VK_SHIFT]=false;Keys['W']=false; - bCrossingMoveStarted=false;bCrossingMoveFinished=true; - std::printf("[CrossingTestMove] movement stopped; waiting for crossing work and uploads.\n"); - std::fflush(stdout); + const double Elapsed=std::chrono::duration(Now-FlightStartTime).count(); + Position=V3(FlightStartPosition.X,FlightStartPosition.Y+ + static_cast(FlightSpeedMps*Elapsed),FlightStartPosition.Z); + if(FlightObservedCrossings>=FlightTargetCrossings) + { + bFlightFinished=true; + std::printf("[FlightBenchmarkMove] movement stopped after %d center-chunk crossings; waiting for new nearest tiles, meshes and uploads.\n", + FlightObservedCrossings); + std::fflush(stdout); + } } const float Dt=std::clamp(std::chrono::duration(Now-PreviousFrame).count(),0.0f,0.1f); PreviousFrame=Now;FrameMs=Dt*1000.0; @@ -2807,7 +2919,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; const std::array pageTiles={target}; std::size_t pageTilesA=0; if(!base.compileTileRegion(pageTiles,static_cast(std::clamp(workers,1,8)), - scheduler,error,&pageTilesA)) + scheduler,error,&pageTilesA,nullptr,false)) {std::fprintf(stderr,"[TilePathHashAudit] path-A page compile failed: %s\n",error.c_str());return 11;} auto pathA=base.generateTile(target,options,error); if(!pathA){std::fprintf(stderr,"[TilePathHashAudit] path-A generation failed: %s\n",error.c_str());return 11;} @@ -2839,7 +2951,63 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; static_cast(pathB->payloadHash), static_cast(pathA->topologyHash), static_cast(pathB->topologyHash),pass?"PASS":"FAIL"); - return pass?0:12; + + VoxelForgeCore::World analyticForward(snapshot),analyticReverse(snapshot); + VoxelForgeCore::ShapeBuildRequest forwardRequest,reverseRequest; + const FVector reverseCamera((target.coordinate.x+TargetChunkOffset+0.5)*CHUNK_SIZE, + camera.Y,camera.Z); + const auto reverseTiles=base.desiredTiles( + {reverseCamera.X,reverseCamera.Y,reverseCamera.Z}); + if(!MakeRegionBuildRequest(baseTiles,config,workers,detail,roundTunnels, + enhancedWalls,forwardRequest,error) + ||!analyticForward.compile(forwardRequest,scheduler,error) + ||reverseTiles.empty() + ||!MakeRegionBuildRequest(reverseTiles,config,workers,detail,roundTunnels, + enhancedWalls,reverseRequest,error) + ||!analyticReverse.compile(reverseRequest,scheduler,error)) + {std::fprintf(stderr,"[TilePathHashDirections] setup failed: %s\n",error.c_str());return 11;} + std::size_t analyticForwardCovered=0,analyticReverseCovered=0; + VoxelForgeCore::TileRegionCompileStats forwardStats,reverseStats; + if(!analyticForward.compileTileRegion(pageTiles,static_cast(std::clamp(workers,1,8)), + scheduler,error,&analyticForwardCovered,&forwardStats,true) + ||!analyticReverse.compileTileRegion(pageTiles,static_cast(std::clamp(workers,1,8)), + scheduler,error,&analyticReverseCovered,&reverseStats,true)) + {std::fprintf(stderr,"[TilePathHashDirections] region compile failed: %s\n",error.c_str());return 11;} + auto analyticPathForward=analyticForward.generateTile(target,options,error); + auto analyticPathReverse=analyticReverse.generateTile(target,options,error); + if(!analyticPathForward||!analyticPathReverse) + {std::fprintf(stderr,"[TilePathHashDirections] tile generation failed: %s\n",error.c_str());return 11;} + const bool analyticDirectionsSame=Same(*analyticPathForward,*analyticPathReverse); + const bool packedAnalyticRenderSame=pathA->meshHash==analyticPathForward->meshHash + &&pathA->payloadHash==analyticPathForward->payloadHash + &&pathA->topologyHash==analyticPathForward->topologyHash; + const bool packedAnalyticDensitySame=pathA->densityHash==analyticPathForward->densityHash; + std::printf("[TilePathHashDirections] tile=(%d,%d,%d) lod=%d approach_deltas=(-%d,+%d) regions=(%zu,%zu) density=(0x%016llx,0x%016llx) mesh=(0x%016llx,0x%016llx) payload=(0x%016llx,0x%016llx) topology=(0x%016llx,0x%016llx) status=%s\n", + target.coordinate.x,target.coordinate.y,target.coordinate.z,target.lod, + TargetChunkOffset,TargetChunkOffset,analyticForwardCovered,analyticReverseCovered, + static_cast(analyticPathForward->densityHash), + static_cast(analyticPathReverse->densityHash), + static_cast(analyticPathForward->meshHash), + static_cast(analyticPathReverse->meshHash), + static_cast(analyticPathForward->payloadHash), + static_cast(analyticPathReverse->payloadHash), + static_cast(analyticPathForward->topologyHash), + static_cast(analyticPathReverse->topologyHash), + analyticDirectionsSame&&analyticForwardCovered==1&&analyticReverseCovered==1?"PASS":"FAIL"); + std::printf("[TilePathPackedAnalyticParity] tile=(%d,%d,%d) density=(0x%016llx,0x%016llx) density_representation=%s mesh=(0x%016llx,0x%016llx) payload=(0x%016llx,0x%016llx) topology=(0x%016llx,0x%016llx) status=%s\n", + target.coordinate.x,target.coordinate.y,target.coordinate.z, + static_cast(pathA->densityHash), + static_cast(analyticPathForward->densityHash), + packedAnalyticDensitySame?"same":"different", + static_cast(pathA->meshHash), + static_cast(analyticPathForward->meshHash), + static_cast(pathA->payloadHash), + static_cast(analyticPathForward->payloadHash), + static_cast(pathA->topologyHash), + static_cast(analyticPathForward->topologyHash), + packedAnalyticRenderSame?"PASS":"FAIL"); + return pass&&analyticDirectionsSame&&analyticForwardCovered==1 + &&analyticReverseCovered==1&&packedAnalyticRenderSame?0:12; } } @@ -2928,7 +3096,21 @@ int main(int Argc,char** Argv) else if(Arg.starts_with("--max-clip-level=")) MaxClipLevelOverride=std::clamp( std::atoi(std::string(Arg.substr(17)).c_str()),0,8); - else if(Arg=="--test-one-chunk-crossing")App.bTestOneChunkCrossing=true; + else if(Arg=="--test-one-chunk-crossing") + { + App.FlightTargetCrossings=1; + App.bShapeWorld=true; + } + else if(Arg.starts_with("--test-flight-crossings=")) + { + App.FlightTargetCrossings=std::max(1,std::atoi( + std::string(Arg.substr(std::string_view("--test-flight-crossings=").size())).c_str())); + App.bShapeWorld=true; + } + else if(Arg.starts_with("--flight-speed-mps=")) + App.FlightSpeedMps=std::clamp(std::strtof( + std::string(Arg.substr(std::string_view("--flight-speed-mps=").size())).c_str(),nullptr), + 1.0f,256.0f); else if(Arg=="--capture-after-clipmap")App.bCaptureAfterClipmap=true; else if(Arg=="--quit-after-clipmap")App.bQuitAfterClipmap=true; else if(Arg.starts_with("--workers=")) diff --git a/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp b/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp index 7a9e9d1..dbad2c0 100644 --- a/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp +++ b/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp @@ -1040,9 +1040,49 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels, const int64 TileExtentVoxels = static_cast(Step) * CellsPerAxis; const bool bUseVerticalCacheTraversal = TileExtentVoxels > CHUNK_SIZE * 16; const FStandaloneShapeWorld* ActiveShapeWorld=ShapeWorldContext; + const bool bUseShapeWorldZBatch=ActiveShapeWorld!=nullptr + &&ActiveShapeWorld->IsAnalyticOnly() + &&!bUseSharedDensityGrid&&!bCapturePreEditGrid; const bool bUseShapeWorldXBatch=ActiveShapeWorld!=nullptr &&!bUseSharedDensityGrid&&!bCapturePreEditGrid; - if (bUseVerticalCacheTraversal) + if(bUseShapeWorldZBatch&&!bUseVerticalCacheTraversal) + { + for(const std::vector& YGroup:YGroups) + for(const std::vector& XGroup:XGroups) + for(const int32 gy:YGroup) + for(const int32 gx:XGroup) + { + if(ShouldAbortWork()) + { + if(bUseOperatorBlock){Generator->EndDensityBlock();} + return FVoxelMeshData(); + } + const int32 BatchCount=GzHi-GzLo+1; + const int32 SampleX=OriginVoxels.X+gx*Step; + const int32 SampleY=OriginVoxels.Y+gy*Step; + float Densities[CHUNK_SIZE+3]{}; + ActiveShapeWorld->DensityAtLatticeZRun(SampleX,SampleY, + OriginVoxels.Z+GzLo*Step,Step,BatchCount,Densities); + for(int32 Lane=0;Lane& YGroup : YGroups) for (const std::vector& XGroup : XGroups)