From b90d63d0f7c08ac8a01aca7f044ca59d23d4818a Mon Sep 17 00:00:00 2001 From: Fr0zka Date: Mon, 19 Oct 2026 12:23:18 +0200 Subject: [PATCH] wip(assembler): round S10 -- LOD8 horizon sampled at MC spacing: LOD8 worker time 18.8 s -> 4.3 s Planar far horizon samples the LOD8 interior at the MC cell spacing, keeping 128-voxel boundary strips; far recipe v3. LOD8 p95 59.8 -> 13.9 ms, spawn worker-seconds ~41 -> 26.9, full set 7.8 -> 7.1 s, ring 0.49 s, LOD0 1.43 s, 40 m/s ring 75/75. S3/S4 2 cm agreement, 100% cap coverage, W1/W8/W12 and W1/W8/W16 determinism pass. Report: Standalone/Research/WorldAssemblerM3/Round14. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ --- ARCHITECTURE.md | 31 +++ CODEMAP.md | 12 + .../Private/VoxelForgeExploreCommandlet.cpp | 13 +- .../include/VoxelForgeCore/VoxelForgeCore.h | 23 +- Standalone/Core/src/VoxelForgeCore.cpp | 261 +++++++++++++----- Standalone/Core/src/WorldAssemblerInternal.h | 26 +- .../WorldAssemblerM3/Round14/REPORT.md | 213 ++++++++++++++ .../Round14/captures/s10_cap.jpg | Bin 0 -> 10783 bytes .../Round14/captures/s10_far_horizon.jpg | Bin 0 -> 17972 bytes .../Round14/captures/s9_cap.jpg | Bin 0 -> 10786 bytes .../Round14/captures/s9_far_horizon.jpg | Bin 0 -> 21897 bytes Standalone/Source/StandaloneViewer.cpp | 110 +++++++- Standalone/Source/StreamingWorld.h | 12 +- Tools/TestVoxelForgeCoreWorldHost.ps1 | 1 + 14 files changed, 616 insertions(+), 86 deletions(-) create mode 100644 Standalone/Research/WorldAssemblerM3/Round14/REPORT.md create mode 100644 Standalone/Research/WorldAssemblerM3/Round14/captures/s10_cap.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Round14/captures/s10_far_horizon.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Round14/captures/s9_cap.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Round14/captures/s9_far_horizon.jpg diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index f453b62..c6dd4fe 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1648,6 +1648,37 @@ FloatingIslands checks passed; the S3/S4 ground, cap and coverage audit passed. negative control, hashes, stage ledger and the deferred LOD8 optimization plan are in [`Standalone/Research/WorldAssemblerM3/Round13/REPORT.md`](Standalone/Research/WorldAssemblerM3/Round13/REPORT.md). +### 8.31 LOD8 planar far-horizon sampling — World Assembler S10 / M3 Round 14 + +The planar far horizon is already built from a `Heightfield2D` column profile, but LOD8 previously +refined ground and cap at a 128-voxel grid even though a 25 cm LOD8 marching-cubes cell spans 512 +voxels (128 m). `BuildNativeHorizonGridCoordinates` now keeps the globally aligned 128-voxel edge +strips and uses the marching-cubes step through the LOD8 interior. The boundary strips preserve +shared edge samples; the interior does not build detail smaller than the LOD8 cell spacing. LOD4–7 +retain their prior sample coordinates. No agreement tolerance was relaxed. + +This applies to native horizon plans that declare a planar heightfield. Ground and cap still run in +separate refinement passes, using the same deterministic coordinate lattice. `Heightfield2D` keeps +its thread-local column cache: S10 records remaining duplicate evaluations across LOD8 tiles and +workers but does not share cache pages between workers. The `--far-horizon-lod8-step=128` Standalone +override restores the old sampling density for an A/B control; it does not restore the old v2 hash +salt. The default sampling change bumps the far recipe hash version from 2 to 3. LOD0–3 hashes and +density are unchanged. + +`VoxelForgeSharedFeatureCost` now reports column evaluation, marching-cubes, ground-refine, +cap-refine and output-assembly timings separately. Column evaluation is subtracted from each +overlapping MC/refinement wall interval; output assembly includes the final payload flattening and +optional diagnostic hashing. Percentiles are computed independently and are not additive. The +optional LOD8 XY capture uses worker-local vectors and records only diagnostics; it does not affect +mesh generation or recipe identity. + +On the staged 25 cm SurfaceWorld plan, the warmed LOD8 full-tile p95 fell from 59.848 ms to +13.933 ms. Column evaluation fell from 31.319 ms to 7.478 ms; ground and cap refinement fell from +11.666/10.029 ms to 1.690/1.330 ms. The S3 ground agreement, S4 cap agreement and 100% cap-ray +coverage gates still pass at their existing tolerances. BuildHost actor startup, the W1/W8/W16 +matrix, W1/W8/W12 Standalone hashes, the 40 m/s support-ring trace and the S10/S9 captures are +recorded in [`Standalone/Research/WorldAssemblerM3/Round14/REPORT.md`](Standalone/Research/WorldAssemblerM3/Round14/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 13c5e3b..23b635d 100644 --- a/CODEMAP.md +++ b/CODEMAP.md @@ -1053,6 +1053,18 @@ missing desired keys. `VoxelForgeExploreCommandlet.cpp` and `Tools/TestVoxelForg accept a movement vector so the same trace covers horizontal and vertical travel. See ARCHITECTURE §8.30 and [`Standalone/Research/WorldAssemblerM3/Round13/REPORT.md`](Standalone/Research/WorldAssemblerM3/Round13/REPORT.md). +### 3.17 LOD8 planar far-horizon sampling — World Assembler S10 / M3 Round 14 + +Native planar SurfaceWorld horizon refinement uses the LOD8 marching-cubes step for its interior +grid (512 voxels / 128 m at 25 cm), retaining globally aligned 128-voxel boundary strips for tile +and LOD joins. LOD4–LOD7 keep their previous grids. Far recipe version 3 salts changed LOD4–LOD8 +mesh outputs; density and LOD0–LOD3 output are unchanged. The ground and cap passes use the same +coordinate lattice but still sample separately. The per-thread column cache remains local to each +worker; no cross-worker page cache was added. Diagnostics report exclusive column, MC, ground, +cap and output-assembly costs, plus opt-in duplicated LOD8 XY evaluations. The Standalone-only +`--far-horizon-lod8-step=128` restores S9 sampling for the negative control. See ARCHITECTURE §8.31 +and [`Standalone/Research/WorldAssemblerM3/Round14/REPORT.md`](Standalone/Research/WorldAssemblerM3/Round14/REPORT.md). + ## 4. The density pipeline (most-edited hot path) ### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749 diff --git a/Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp b/Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp index 63e0d5f..3a5aa9b 100644 --- a/Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp +++ b/Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp @@ -15945,6 +15945,8 @@ bool RunCoreSharedFeatureCostAudit(VoxelForgeCore::World& World, TArray FullTimes,CandidateTimes,FullShellTimes,CandidateShellTimes,ListTimes; TArray UnknownBoxes,FieldCalls,ColumnMisses,StratumShellTimes,ConnectorShellTimes; TArray ClassificationTimes,ColumnComputeTimes,MeshingTimes,HorizonTimes,RepairSkipped; + TArray ColumnEvalExclusiveTimes,MarchingExclusiveTimes,GroundRefineExclusiveTimes; + TArray CapRefineExclusiveTimes,MeshAssemblyTimes; TArray StratumFieldCalls,ConnectorFieldCalls,ShellTriangles,GroundTriangles,GroundRemoved,CapTriangles; for (const auto& Key : Keys) { @@ -15978,6 +15980,11 @@ bool RunCoreSharedFeatureCostAudit(VoxelForgeCore::World& World, ColumnMisses.Add(static_cast(Full->diagnostics.columnCacheMisses)); ClassificationTimes.Add(Full->diagnostics.classificationMilliseconds); ColumnComputeTimes.Add(Full->diagnostics.columnComputeMilliseconds); + ColumnEvalExclusiveTimes.Add(Full->diagnostics.columnEvaluationExclusiveMilliseconds); + MarchingExclusiveTimes.Add(Full->diagnostics.marchingCubesExclusiveMilliseconds); + GroundRefineExclusiveTimes.Add(Full->diagnostics.groundRefinementExclusiveMilliseconds); + CapRefineExclusiveTimes.Add(Full->diagnostics.capRefinementExclusiveMilliseconds); + MeshAssemblyTimes.Add(Full->diagnostics.meshAssemblyMilliseconds); MeshingTimes.Add(Full->diagnostics.meshingMilliseconds); HorizonTimes.Add(Full->diagnostics.farHorizonMilliseconds); RepairSkipped.Add(Full->diagnostics.farShellRepairSkipped?1.0:0.0); @@ -15994,9 +16001,11 @@ bool RunCoreSharedFeatureCostAudit(VoxelForgeCore::World& World, { Values.Sort(); return Values.IsEmpty() ? 0.0 : Values[FMath::Clamp( FMath::CeilToInt(Values.Num()*0.95f)-1,0,Values.Num()-1)]; }; UE_LOG(LogTemp,Display, - TEXT("[VoxelForgeSharedFeatureCost] label=%s lod=%d tiles=%d hashes_identical=1 full_p95_ms=%.5f candidate_p95_ms=%.5f list_p95_ms=%.5f classification_p95_ms=%.5f column_compute_p95_ms=%.5f meshing_p95_ms=%.5f horizon_p95_ms=%.5f full_shell_p95_ms=%.5f candidate_shell_p95_ms=%.5f repair_skipped_p95=%.0f unknown_boxes_p95=%.0f field_calls_p95=%.0f column_misses_p95=%.0f stratum_shell_p95_ms=%.5f stratum_field_calls_p95=%.0f connector_shell_p95_ms=%.5f connector_field_calls_p95=%.0f shell_triangles_p95=%.0f horizon_ground_triangles_p95=%.0f horizon_ground_removed_p95=%.0f horizon_cap_triangles_p95=%.0f"), + TEXT("[VoxelForgeSharedFeatureCost] label=%s lod=%d tiles=%d hashes_identical=1 full_p95_ms=%.5f candidate_p95_ms=%.5f list_p95_ms=%.5f classification_p95_ms=%.5f column_compute_p95_ms=%.5f column_eval_exclusive_p95_ms=%.5f mc_exclusive_p95_ms=%.5f ground_refine_exclusive_p95_ms=%.5f cap_refine_exclusive_p95_ms=%.5f mesh_assembly_p95_ms=%.5f meshing_p95_ms=%.5f horizon_p95_ms=%.5f full_shell_p95_ms=%.5f candidate_shell_p95_ms=%.5f repair_skipped_p95=%.0f unknown_boxes_p95=%.0f field_calls_p95=%.0f column_misses_p95=%.0f stratum_shell_p95_ms=%.5f stratum_field_calls_p95=%.0f connector_shell_p95_ms=%.5f connector_field_calls_p95=%.0f shell_triangles_p95=%.0f horizon_ground_triangles_p95=%.0f horizon_ground_removed_p95=%.0f horizon_cap_triangles_p95=%.0f"), Label,LOD,FullTimes.Num(),P95(FullTimes),P95(CandidateTimes),P95(ListTimes), - P95(ClassificationTimes),P95(ColumnComputeTimes),P95(MeshingTimes),P95(HorizonTimes), + P95(ClassificationTimes),P95(ColumnComputeTimes),P95(ColumnEvalExclusiveTimes), + P95(MarchingExclusiveTimes),P95(GroundRefineExclusiveTimes),P95(CapRefineExclusiveTimes), + P95(MeshAssemblyTimes),P95(MeshingTimes),P95(HorizonTimes), P95(FullShellTimes),P95(CandidateShellTimes),P95(RepairSkipped),P95(UnknownBoxes),P95(FieldCalls), P95(ColumnMisses),P95(StratumShellTimes),P95(StratumFieldCalls), P95(ConnectorShellTimes),P95(ConnectorFieldCalls),P95(ShellTriangles), diff --git a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h index 9e1accf..3bc6f02 100644 --- a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h +++ b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h @@ -125,6 +125,11 @@ namespace VoxelForgeCore std::uint64_t columnCacheMisses = 0; double classificationMilliseconds = 0.0; double columnComputeMilliseconds = 0.0; + double columnEvaluationExclusiveMilliseconds = 0.0; + double marchingCubesExclusiveMilliseconds = 0.0; + double groundRefinementExclusiveMilliseconds = 0.0; + double capRefinementExclusiveMilliseconds = 0.0; + double meshAssemblyMilliseconds = 0.0; double meshingMilliseconds = 0.0; double featureListMilliseconds = 0.0; double farShellMilliseconds = 0.0; @@ -146,6 +151,9 @@ namespace VoxelForgeCore std::uint64_t capRefinedCells = 0; std::uint64_t capRefinedTriangles = 0; bool capRefined = false; + std::uint64_t columnEvaluationWorkerHash = 0; + bool columnCoordinateCaptureComplete = true; + std::vector> evaluatedColumnCoordinates; }; struct TileMesh @@ -628,6 +636,9 @@ namespace VoxelForgeCore std::uint64_t columnHits = 0; std::uint64_t columnMisses = 0; double columnComputeMilliseconds = 0.0; + bool captureCoordinates = false; + bool coordinateCaptureComplete = true; + std::vector> evaluatedCoordinates; }; struct FieldInfluenceBounds @@ -676,8 +687,8 @@ namespace VoxelForgeCore std::int64_t minimumYSubvoxels, std::int64_t minimumZSubvoxels, std::int64_t maximumXSubvoxels, std::int64_t maximumYSubvoxels, std::int64_t maximumZSubvoxels, std::int32_t stepVoxels) const noexcept; - static void resetThreadColumnCacheStats() noexcept; - static HeightfieldColumnCacheStats threadColumnCacheStats() noexcept; + static void resetThreadColumnCacheStats(bool captureCoordinates = false) noexcept; + static const HeightfieldColumnCacheStats& threadColumnCacheStats() noexcept; FieldInterval interval(std::int64_t minimumXSubvoxels, std::int64_t minimumYSubvoxels, std::int64_t minimumZSubvoxels, @@ -1002,11 +1013,17 @@ namespace VoxelForgeCore // Validation control only; never participates in recipe/world identity. // Controle de validation seulement, sans effet sur l'identite du monde. bool useAssemblyFeatureQueries = true; + // Opt-in diagnostic only; records XY columns evaluated by this LOD8 tile. + // Diagnostic seulement; enregistre les colonnes XY evaluees par cette tuile LOD8. + bool captureColumnCoordinates = false; + // LOD8-only planar horizon interior grid; zero follows the MC step, 128 restores S9. + // Grille interieure LOD8 seulement; zero suit la maille MC, 128 restaure S9. + std::int32_t lod8HorizonInteriorStepVoxels = 0; // Bump when the distant cap representation changes. LOD4-8 mesh hashes are salted; // density and LOD0-3 hashes remain unchanged. // Incremente quand le plafond lointain change. Les hashes LOD4-8 incluent cette version; // densite et hashes LOD0-3 restent inchanges. - std::uint32_t farHorizonVersion = 2; + std::uint32_t farHorizonVersion = 3; }; class VFCORE_API World diff --git a/Standalone/Core/src/VoxelForgeCore.cpp b/Standalone/Core/src/VoxelForgeCore.cpp index 60f9c51..505f032 100644 --- a/Standalone/Core/src/VoxelForgeCore.cpp +++ b/Standalone/Core/src/VoxelForgeCore.cpp @@ -11,6 +11,8 @@ #include #include #include +#include +#include namespace vfcore::detail { @@ -247,6 +249,45 @@ namespace VoxelForgeCore bool Refined = false; }; + std::vector BuildNativeHorizonGridCoordinates(int32 TileExtent,int32 InteriorStep) + { + constexpr int32 BoundaryStep=128; + std::vector Coordinates; + if(TileExtent<=0)return Coordinates; + if(InteriorStep<=BoundaryStep) + { + Coordinates.reserve(static_cast(TileExtent/InteriorStep)+1u); + for(int32 Position=0;Position(TileExtent/BoundaryStep)+1u); + for(int32 Position=0;Position(GridStepVoxels)*Scale; const FIntVector TileOrigin=Key.OriginVoxels(); const int32 TileExtent=Key.ExtentVoxels(); + const int32 RequestedStep=Key.Level==8 + ?(InteriorStepOverride>0?InteriorStepOverride:MeshStepVoxels) + :std::min(BoundaryStepVoxels,1< GridCoordinates= + BuildNativeHorizonGridCoordinates(TileExtent,GridStepVoxels); + const int32 GridSize=static_cast(GridCoordinates.size()); + if(GridSize<2)return Stats; const double TileMinZ=static_cast(TileOrigin.Z)*Scale; const double TileMaxZ=static_cast(TileOrigin.Z+TileExtent)*Scale; const int32 OldSurfaceVertices=std::clamp(Mesh.NumSurfaceVertices,0,Mesh.Vertices.Num()); @@ -324,8 +376,9 @@ namespace VoxelForgeCore struct FGroundProfile { - int32 Cells=0,GridStep=0; + int32 GridStep=0; double MinimumZ=0.0,MaximumZ=0.0; + std::vector Coordinates; std::vector Ground,Cap; }; std::vector GroundProfiles; @@ -344,14 +397,14 @@ namespace VoxelForgeCore FColor Color=FColor::White; int32 VertexIndex=INDEX_NONE; }; - std::vector Grid(static_cast(Cells+1)*(Cells+1)); - const auto GridIndex=[Cells](int32 X,int32 Y) - {return static_cast(Y)*(Cells+1)+X;}; - for(int32 Y=0;Y<=Cells;++Y) - for(int32 X=0;X<=Cells;++X) + std::vector Grid(static_cast(GridSize)*GridSize); + const auto GridIndex=[GridSize](int32 X,int32 Y) + {return static_cast(Y)*GridSize+X;}; + for(int32 Y=0;Y(TileOrigin.X)+static_cast(X)*GridStepVoxels)*Scale; - const int64 YSub=(static_cast(TileOrigin.Y)+static_cast(Y)*GridStepVoxels)*Scale; + const int64 XSub=(static_cast(TileOrigin.X)+GridCoordinates[X])*Scale; + const int64 YSub=(static_cast(TileOrigin.Y)+GridCoordinates[Y])*Scale; const auto Column=Block.point(XSub,YSub,0); auto& Sample=Grid[GridIndex(X,Y)]; Sample.Ground=static_cast(Column.groundHeightSubvoxels); @@ -364,13 +417,13 @@ namespace VoxelForgeCore static_cast(std::clamp(std::lround(Blend*255.0f),0l,255l)), static_cast(std::clamp(Neighbor,0,255))); } - for(int32 Y=0;Y<=Cells;++Y) - for(int32 X=0;X<=Cells;++X) + for(int32 Y=0;Y(Right-Left)*GridStepSubvoxels; - const double DY=static_cast(Up-Down)*GridStepSubvoxels; + const int32 Left=std::max(0,X-1),Right=std::min(GridSize-1,X+1); + const int32 Down=std::max(0,Y-1),Up=std::min(GridSize-1,Y+1); + const double DX=static_cast(GridCoordinates[Right]-GridCoordinates[Left])*Scale; + const double DY=static_cast(GridCoordinates[Up]-GridCoordinates[Down])*Scale; const double SlopeX=DX>0.0?(Grid[GridIndex(Right,Y)].Ground -Grid[GridIndex(Left,Y)].Ground)/DX:0.0; const double SlopeY=DY>0.0?(Grid[GridIndex(X,Up)].Ground @@ -382,7 +435,7 @@ namespace VoxelForgeCore } FGroundProfile Profile; - Profile.Cells=Cells;Profile.GridStep=GridStepVoxels; + Profile.GridStep=GridStepVoxels;Profile.Coordinates=GridCoordinates; Profile.MinimumZ=static_cast(std::min(Plan.bottomChunkZ,Plan.topChunkZ))*CHUNK_SIZE; Profile.MaximumZ=static_cast(std::max(Plan.bottomChunkZ,Plan.topChunkZ)+1)*CHUNK_SIZE; Profile.Ground.reserve(Grid.size());Profile.Cap.reserve(Grid.size()); @@ -392,25 +445,25 @@ namespace VoxelForgeCore // Partager les sommets du profil entre cellules reduit le payload d'horizon. // Share regular profile vertices across adjacent cells to bound horizon payload. - for(int32 Y=0;Y<=Cells;++Y) - for(int32 X=0;X<=Cells;++X) + for(int32 Y=0;Y=Sample.Cap||Sample.GroundTileMaxZ)continue; - const int64 XSub=(static_cast(TileOrigin.X)+static_cast(X)*GridStepVoxels)*Scale; - const int64 YSub=(static_cast(TileOrigin.Y)+static_cast(Y)*GridStepVoxels)*Scale; + const int64 XSub=(static_cast(TileOrigin.X)+GridCoordinates[X])*Scale; + const int64 YSub=(static_cast(TileOrigin.Y)+GridCoordinates[Y])*Scale; const double VoxelSizeCm=static_cast(Owner.CoreVoxelSizeMicrons)/10000.0; Sample.VertexIndex=static_cast(AddedVertices.size()); - AddedVertices.emplace_back(static_cast(TileOrigin.X+X*GridStepVoxels)*VoxelSizeCm, - static_cast(TileOrigin.Y+Y*GridStepVoxels)*VoxelSizeCm, + AddedVertices.emplace_back(static_cast(TileOrigin.X+GridCoordinates[X])*VoxelSizeCm, + static_cast(TileOrigin.Y+GridCoordinates[Y])*VoxelSizeCm, Sample.Ground/Scale*VoxelSizeCm); AddedNormals.push_back(Sample.Normal); if(bHasUVs)AddedUVs.emplace_back(static_cast(XSub/Scale),static_cast(YSub/Scale)); if(bHasColors)AddedColors.push_back(Sample.Color); } - for(int32 Y=0;Y(TileOrigin.X+GX*GridStepVoxels)*Scale; - PolygonA[Corner].YSub=static_cast(TileOrigin.Y+GY*GridStepVoxels)*Scale; + PolygonA[Corner].XSub=static_cast(TileOrigin.X+GridCoordinates[GX])*Scale; + PolygonA[Corner].YSub=static_cast(TileOrigin.Y+GridCoordinates[GY])*Scale; PolygonA[Corner].GroundSub=Sample.Ground; PolygonA[Corner].CapSub=Sample.Cap; PolygonA[Corner].ZSub=Sample.Ground; @@ -487,16 +540,29 @@ namespace VoxelForgeCore const auto InterpolateProfile=[](const std::vector& Values,const FGroundProfile& Profile, double LocalX,double LocalY) { - const double GridX=std::clamp(LocalX/Profile.GridStep,0.0,static_cast(Profile.Cells)); - const double GridY=std::clamp(LocalY/Profile.GridStep,0.0,static_cast(Profile.Cells)); - const int32 X=std::min(Profile.Cells-1,static_cast(std::floor(GridX))); - const int32 Y=std::min(Profile.Cells-1,static_cast(std::floor(GridY))); - const double TX=GridX-X,TY=GridY-Y; - const std::size_t Width=static_cast(Profile.Cells+1); + const std::size_t Width=Profile.Coordinates.size(); + const auto FindCell=[&](double Local) + { + const double Clamped=std::clamp(Local,static_cast(Profile.Coordinates.front()), + static_cast(Profile.Coordinates.back())); + const auto Upper=std::upper_bound(Profile.Coordinates.begin(),Profile.Coordinates.end(),Clamped); + if(Upper==Profile.Coordinates.begin())return std::pair{0u,0.0}; + const std::size_t Index=std::min(Width-2u, + static_cast(Upper-Profile.Coordinates.begin()-1)); + const double Start=Profile.Coordinates[Index]; + const double Span=Profile.Coordinates[Index+1]-Profile.Coordinates[Index]; + return std::pair{Index,Span>0.0?(Clamped-Start)/Span:0.0}; + }; + const auto [X,TX]=FindCell(LocalX); + const auto [Y,TY]=FindCell(LocalY); const auto At=[&](int32 PX,int32 PY){return Values[static_cast(PY)*Width+PX];}; if(TX>=TY) - return At(X,Y)*(1.0-TX)+At(X+1,Y)*(TX-TY)+At(X+1,Y+1)*TY; - return At(X,Y)*(1.0-TY)+At(X+1,Y+1)*TX+At(X,Y+1)*(TY-TX); + return At(static_cast(X),static_cast(Y))*(1.0-TX) + +At(static_cast(X+1u),static_cast(Y))*(TX-TY) + +At(static_cast(X+1u),static_cast(Y+1u))*TY; + return At(static_cast(X),static_cast(Y))*(1.0-TY) + +At(static_cast(X+1u),static_cast(Y+1u))*TX + +At(static_cast(X),static_cast(Y+1u))*(TY-TX); }; for(int32 Triangle=0;TriangleTileExtent||LocalY>TileExtent)continue; for(const auto& Profile:GroundProfiles) { - if(LocalX>Profile.Cells*Profile.GridStep||LocalY>Profile.Cells*Profile.GridStep)continue; + if(LocalX>Profile.Coordinates.back()||LocalY>Profile.Coordinates.back())continue; const double Ground=InterpolateProfile(Profile.Ground,Profile,LocalX,LocalY); const double Cap=InterpolateProfile(Profile.Cap,Profile,LocalX,LocalY); if(Cap-Ground<=1.0||GroundTileMaxZ/Scale ||GroundProfile.MaximumZ)continue; - const double ReplacementBand=std::max(32.0,2.0*Profile.GridStep); + const double ReplacementBand=std::max(32.0, + 2.0*std::min(Profile.GridStep,BoundaryStepVoxels)); if(Z>=Ground-ReplacementBand) { RemoveOldGround[static_cast(Triangle)]=1; @@ -574,17 +641,28 @@ namespace VoxelForgeCore } FNativeCapRefinementStats RefineFarNativeCap(const FStandaloneOwnerWorld& Owner, - const WorldAssemblyManifest& Manifest,const FVoxelTileKey& Key,FVoxelMeshData& Mesh) + const WorldAssemblyManifest& Manifest,const FVoxelTileKey& Key,int32 MeshStepVoxels, + int32 InteriorStepOverride,FVoxelMeshData& Mesh) { FNativeCapRefinementStats Stats; if(Key.Level<4||!Owner.Generator)return Stats; - const int32 GridStepVoxels=Key.Level<5?std::min(128,1<(GridStepVoxels)*Scale; + constexpr int32 BoundaryStepVoxels=128; + const int32 RequestedStep=Key.Level==8 + ?(InteriorStepOverride>0?InteriorStepOverride:MeshStepVoxels) + :(Key.Level<5?std::min(BoundaryStepVoxels,1< GridCoordinates= + BuildNativeHorizonGridCoordinates(TileExtent,EffectiveGridStep); + const int32 GridSize=static_cast(GridCoordinates.size()); + if(GridSize<2)return Stats; const int64 TileMinZ=static_cast(TileOrigin.Z)*Scale; const int64 TileMaxZ=static_cast(TileOrigin.Z+TileExtent)*Scale; const int32 OldSurfaceVertices=std::clamp(Mesh.NumSurfaceVertices,0,Mesh.Vertices.Num()); @@ -612,14 +690,14 @@ namespace VoxelForgeCore FColor Color=FColor::White; int32 VertexIndex=INDEX_NONE; }; - std::vector Grid(static_cast(Cells+1)*(Cells+1)); - const auto GridIndex=[Cells](int32 X,int32 Y) - {return static_cast(Y)*(Cells+1)+X;}; - for(int32 Y=0;Y<=Cells;++Y) - for(int32 X=0;X<=Cells;++X) + std::vector Grid(static_cast(GridSize)*GridSize); + const auto GridIndex=[GridSize](int32 X,int32 Y) + {return static_cast(Y)*GridSize+X;}; + for(int32 Y=0;Y(TileOrigin.X)+static_cast(X)*GridStepVoxels)*Scale; - const int64 YSub=(static_cast(TileOrigin.Y)+static_cast(Y)*GridStepVoxels)*Scale; + const int64 XSub=(static_cast(TileOrigin.X)+GridCoordinates[X])*Scale; + const int64 YSub=(static_cast(TileOrigin.Y)+GridCoordinates[Y])*Scale; const auto Column=Block.point(XSub,YSub,0); auto& Sample=Grid[GridIndex(X,Y)]; Sample.Ground=static_cast(Column.groundHeightSubvoxels); @@ -633,13 +711,13 @@ namespace VoxelForgeCore const uint8 B=static_cast(std::clamp(std::lround(Blend*255.0f),0l,255l)); Sample.Color=FColor(R,0,B,A); } - for(int32 Y=0;Y<=Cells;++Y) - for(int32 X=0;X<=Cells;++X) + for(int32 Y=0;Y(Right-Left)*GridStepSubvoxels; - const double DY=static_cast(Up-Down)*GridStepSubvoxels; + const int32 Left=std::max(0,X-1),Right=std::min(GridSize-1,X+1); + const int32 Down=std::max(0,Y-1),Up=std::min(GridSize-1,Y+1); + const double DX=static_cast(GridCoordinates[Right]-GridCoordinates[Left])*Scale; + const double DY=static_cast(GridCoordinates[Up]-GridCoordinates[Down])*Scale; const double SlopeX=DX>0.0?(Grid[GridIndex(Right,Y)].Cap-Grid[GridIndex(Left,Y)].Cap)/DX:0.0; const double SlopeY=DY>0.0?(Grid[GridIndex(X,Up)].Cap-Grid[GridIndex(X,Down)].Cap)/DY:0.0; auto& Sample=Grid[GridIndex(X,Y)]; @@ -655,10 +733,10 @@ namespace VoxelForgeCore if(Sample.CapTileMaxZ)continue; Sample.VertexIndex=static_cast(AddedVertices.size()); const std::size_t Index=static_cast(&Sample-Grid.data()); - const int32 GX=static_cast(Index%static_cast(Cells+1)); - const int32 GY=static_cast(Index/static_cast(Cells+1)); - const double XSub=static_cast(TileOrigin.X+GX*GridStepVoxels)*Scale; - const double YSub=static_cast(TileOrigin.Y+GY*GridStepVoxels)*Scale; + const int32 GX=static_cast(Index%static_cast(GridSize)); + const int32 GY=static_cast(Index/static_cast(GridSize)); + const double XSub=static_cast(TileOrigin.X+GridCoordinates[GX])*Scale; + const double YSub=static_cast(TileOrigin.Y+GridCoordinates[GY])*Scale; const double VoxelSizeCm=static_cast(Owner.CoreVoxelSizeMicrons)/10000.0; AddedVertices.emplace_back(XSub/Scale*VoxelSizeCm,YSub/Scale*VoxelSizeCm, Sample.Cap/Scale*VoxelSizeCm); @@ -668,8 +746,8 @@ namespace VoxelForgeCore if(bHasColors)AddedColors.push_back(Sample.Color); } - for(int32 Y=0;Y(TileOrigin.X+GX*GridStepVoxels)*Scale; - PolygonA[Corner].YSub=static_cast(TileOrigin.Y+GY*GridStepVoxels)*Scale; + PolygonA[Corner].XSub=static_cast(TileOrigin.X+GridCoordinates[GX])*Scale; + PolygonA[Corner].YSub=static_cast(TileOrigin.Y+GridCoordinates[GY])*Scale; PolygonA[Corner].ZSub=Sample.Cap;PolygonA[Corner].GroundSub=Sample.Ground; PolygonA[Corner].Normal=Sample.Normal;PolygonA[Corner].Color=Sample.Color; } @@ -1400,7 +1478,8 @@ namespace VoxelForgeCore &&bHasNativeSurfaceHorizonPlan&&!bHasIslandPlanInTile; const bool bSkipFarShellRepair=key.lod>=7&&bUseNativeSurfaceHorizon; FStandaloneShapeWorld::ResetThreadClassificationStats(); - VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(); + VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats( + options.captureColumnCoordinates&&key.lod==8); bool bNativeSurfaceTile=false; bool bNativeSurfaceBandSafe=false; if(bHasWorldAssembly) @@ -1427,7 +1506,7 @@ namespace VoxelForgeCore { out.diagnostics.meshingSkipped=true; const auto Counts=FStandaloneShapeWorld::GetThreadClassificationStats(); - const auto Columns=VoxelForgeCore::Heightfield2D::threadColumnCacheStats(); + const auto& Columns=VoxelForgeCore::Heightfield2D::threadColumnCacheStats(); out.diagnostics.solidBoxes=Counts.SolidBoxes; out.diagnostics.airBoxes=Counts.AirBoxes; out.diagnostics.mixedBoxes=Counts.MixedBoxes; @@ -1571,9 +1650,18 @@ namespace VoxelForgeCore out.diagnostics.featureListMilliseconds=std::chrono::duration( std::chrono::steady_clock::now()-FeatureListStart).count(); const ScopedWorldAssemblyFeatureQueriesV1 featureScope(featureQueries); + const double ColumnMillisecondsBeforeMeshing= + VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds; + const auto MarchingCubesStart=std::chrono::steady_clock::now(); FVoxelMeshData mesh=mesher.GenerateMesh(origin,step,cells,nullptr, MeshBandMinZVoxel,MeshBandMaxZVoxel,&sampleCount,nullptr,nullptr, (options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr); + const double MarchingCubesWallMilliseconds=std::chrono::duration( + std::chrono::steady_clock::now()-MarchingCubesStart).count(); + const double ColumnMillisecondsAfterMeshing= + VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds; + out.diagnostics.marchingCubesExclusiveMilliseconds=std::max(0.0, + MarchingCubesWallMilliseconds-(ColumnMillisecondsAfterMeshing-ColumnMillisecondsBeforeMeshing)); const double MeshingMilliseconds=std::chrono::duration( std::chrono::steady_clock::now()-MeshingStart).count(); out.diagnostics.farHorizonVersion=key.lod>=4?options.farHorizonVersion:0; @@ -1612,9 +1700,29 @@ namespace VoxelForgeCore { const auto HorizonStart=std::chrono::steady_clock::now(); FNativeCapRefinementStats GroundStats; + const double ColumnMillisecondsBeforeGround= + VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds; + const auto GroundStart=std::chrono::steady_clock::now(); if(key.lod>=4) - GroundStats=RefineFarNativeGround(world,*impl_->worldAssembly,nativeKey,mesh); - const auto CapStats=RefineFarNativeCap(world,*impl_->worldAssembly,nativeKey,mesh); + GroundStats=RefineFarNativeGround(world,*impl_->worldAssembly,nativeKey,step, + options.lod8HorizonInteriorStepVoxels,mesh); + const double GroundWallMilliseconds=std::chrono::duration( + std::chrono::steady_clock::now()-GroundStart).count(); + const double ColumnMillisecondsAfterGround= + VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds; + out.diagnostics.groundRefinementExclusiveMilliseconds=std::max(0.0, + GroundWallMilliseconds-(ColumnMillisecondsAfterGround-ColumnMillisecondsBeforeGround)); + const double ColumnMillisecondsBeforeCap= + VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds; + const auto CapStart=std::chrono::steady_clock::now(); + const auto CapStats=RefineFarNativeCap(world,*impl_->worldAssembly,nativeKey,step, + options.lod8HorizonInteriorStepVoxels,mesh); + const double CapWallMilliseconds=std::chrono::duration( + std::chrono::steady_clock::now()-CapStart).count(); + const double ColumnMillisecondsAfterCap= + VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds; + out.diagnostics.capRefinementExclusiveMilliseconds=std::max(0.0, + CapWallMilliseconds-(ColumnMillisecondsAfterCap-ColumnMillisecondsBeforeCap)); out.diagnostics.farHorizonMilliseconds=std::chrono::duration( std::chrono::steady_clock::now()-HorizonStart).count(); out.diagnostics.capRefined=CapStats.Refined; @@ -1629,7 +1737,7 @@ namespace VoxelForgeCore if(bHasWorldAssembly) { const auto Counts=FStandaloneShapeWorld::GetThreadClassificationStats(); - const auto Columns=VoxelForgeCore::Heightfield2D::threadColumnCacheStats(); + const auto& Columns=VoxelForgeCore::Heightfield2D::threadColumnCacheStats(); out.diagnostics.fieldCalls=Columns.pointCalls; out.diagnostics.floatFieldCalls=Counts.NativeFloatFieldCalls; out.diagnostics.latticeFieldCalls=Counts.NativeLatticeFieldCalls; @@ -1637,7 +1745,15 @@ namespace VoxelForgeCore out.diagnostics.columnCacheHits=Columns.columnHits; out.diagnostics.columnCacheMisses=Columns.columnMisses; out.diagnostics.columnComputeMilliseconds=Columns.columnComputeMilliseconds; + out.diagnostics.columnEvaluationExclusiveMilliseconds=Columns.columnComputeMilliseconds; out.diagnostics.classificationMilliseconds=Counts.Milliseconds; + if(options.captureColumnCoordinates&&key.lod==8) + { + out.diagnostics.columnEvaluationWorkerHash=static_cast( + std::hash{}(std::this_thread::get_id()))+1u; + out.diagnostics.columnCoordinateCaptureComplete=Columns.coordinateCaptureComplete; + out.diagnostics.evaluatedColumnCoordinates=Columns.evaluatedCoordinates; + } if(bNativeSurfaceTile) { out.diagnostics.solidBoxes=Counts.SolidBoxes; @@ -1653,6 +1769,7 @@ namespace VoxelForgeCore } out.diagnostics.heightfieldBandLimited=bNativeHeightfieldBandLimited; out.diagnostics.meshingMilliseconds=MeshingMilliseconds; + const auto MeshAssemblyStart=std::chrono::steady_clock::now(); out.meshPayloadAllocatedBytes=static_cast(mesh.Vertices.GetAllocatedSize()) +static_cast(mesh.Normals.GetAllocatedSize()) +static_cast(mesh.UVs.GetAllocatedSize()) @@ -1696,6 +1813,8 @@ namespace VoxelForgeCore } out.hasDensityHash=out.hasMeshHash=out.hasPayloadHash=out.hasTopologyHash=true; } + out.diagnostics.meshAssemblyMilliseconds=std::chrono::duration( + std::chrono::steady_clock::now()-MeshAssemblyStart).count(); return out; } diff --git a/Standalone/Core/src/WorldAssemblerInternal.h b/Standalone/Core/src/WorldAssemblerInternal.h index 26be4f7..d76eeae 100644 --- a/Standalone/Core/src/WorldAssemblerInternal.h +++ b/Standalone/Core/src/WorldAssemblerInternal.h @@ -1487,6 +1487,17 @@ namespace world_assembly_detail HeightfieldColumnStats.columnComputeMilliseconds+=std::chrono::duration( std::chrono::steady_clock::now()-start).count(); ++HeightfieldColumnStats.columnMisses; + if(HeightfieldColumnStats.captureCoordinates&&HeightfieldColumnStats.coordinateCaptureComplete) + { + try + { + HeightfieldColumnStats.evaluatedCoordinates.push_back({x,y}); + } + catch(...) + { + HeightfieldColumnStats.coordinateCaptureComplete=false; + } + } entry={&recipe,&plan,recipe.contentHash,plan.contentHash,x,y,column}; return column; } @@ -1954,10 +1965,19 @@ HeightfieldPointSample Heightfield2D::point(std::int64_t x, std::int64_t y, return out; } -void Heightfield2D::resetThreadColumnCacheStats() noexcept -{ world_assembly_detail::HeightfieldColumnStats={}; } +void Heightfield2D::resetThreadColumnCacheStats(bool captureCoordinates) noexcept +{ + auto& Stats=world_assembly_detail::HeightfieldColumnStats; + Stats.pointCalls=0; + Stats.columnHits=0; + Stats.columnMisses=0; + Stats.columnComputeMilliseconds=0.0; + Stats.captureCoordinates=captureCoordinates; + Stats.coordinateCaptureComplete=true; + Stats.evaluatedCoordinates.clear(); +} -HeightfieldColumnCacheStats Heightfield2D::threadColumnCacheStats() noexcept +const HeightfieldColumnCacheStats& Heightfield2D::threadColumnCacheStats() noexcept { return world_assembly_detail::HeightfieldColumnStats; } HeightfieldBoxClass Heightfield2D::classifyLatticeBox(std::int64_t minimumX, diff --git a/Standalone/Research/WorldAssemblerM3/Round14/REPORT.md b/Standalone/Research/WorldAssemblerM3/Round14/REPORT.md new file mode 100644 index 0000000..16a6c55 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Round14/REPORT.md @@ -0,0 +1,213 @@ +# World assembler S10 / M3 Round 14 — cheaper far ring + +Date: 2026-10-19. Base: S9 / `e7586a1`. No commit, push or stash. + +## Result + +The planar far path now samples the LOD8 interior at the spacing that LOD8 marching cubes can +represent. At 25 cm, that is 512 voxels / 128 m per MC cell. Globally aligned 128-voxel (32 m) +strips remain along tile boundaries. LOD4–LOD7 coordinates and the 2 cm agreement tolerances are +unchanged. Far recipe version is 3; far mesh hashes change, while density and LOD0–LOD3 remain +unchanged. + +On the owner SurfaceWorld plan, the warmed eight-tile BuildHost LOD8 full-tile p95 fell 76.7%, +from 59.848 ms to 13.933 ms. In the fresh 6 m/s actor spawn trace, the 2,578 initial tile jobs +used 26.870 worker-seconds; the LOD8 share was 4.288 seconds. Round13’s S8 spawn reference was +about 41 worker-seconds, with 18.798 seconds in its 306 LOD8 jobs. Initial readiness was 7.117 s, +below the 7.7 s target. The active 3×3 walkable ring was ready in 0.490 s, and all 512 LOD0 tiles +were ready at 1.428 s. + +## Count first — exclusive cost and duplicate columns + +The counters are measured per tile. Column evaluation is timed only around uncached column work. +MC, ground and cap counters subtract column time from their respective wall intervals. The mesh +assembly window covers final payload flattening and optional diagnostic hashing. Each p95 is +computed independently, so these p95 values are not additive. + +| LOD8 warmed p95, BuildHost 25 cm | S9 | S10 | Change | +|---|---:|---:|---:| +| Full tile | 59.848 ms | 13.933 ms | −76.7% | +| Column evaluation | 31.319 ms | 7.478 ms | −76.1% | +| Marching cubes | 2.667 ms | 2.359 ms | −11.6% | +| Ground refinement | 11.666 ms | 1.690 ms | −85.5% | +| Cap refinement | 10.029 ms | 1.330 ms | −86.7% | +| Mesh assembly and diagnostic hash | 5.445 ms | 1.095 ms | −79.9% | +| Column-cache misses | 9,679 | 2,287 | −76.4% | + +The high-cost S9 sample generated 8,192 ground triangles, removed 531 coarse ground triangles, +and generated 6,732 cap triangles. S10 generated 968 ground triangles, removed 534, and generated +840 cap triangles. The geometric checks below pass at the same tolerance. + +The fresh W12 Standalone overlap capture counted 153 LOD8 tiles. S10 evaluated 106,219 unique XY +columns and repeated 32,738 neighboring tile evaluations, including 30,446 cross-worker repeats. +The same-binary step-128 control counted 660,283 unique columns, 44,319 neighboring repeats and +41,665 cross-worker repeats. This confirms that cross-worker duplicates remain, while the coarser +interior removes most column work without sharing cache state between workers. + +| Fresh W12 Standalone pipeline | Default step 0 | Negative control step 128 | +|---|---:|---:| +| LOD8 worker sum | 3.206 s | 13.534 s | +| LOD8 unique XY columns | 106,219 | 660,283 | +| Repeated neighboring evaluations | 32,738 | 44,319 | +| Cross-worker neighboring repeats | 30,446 | 41,665 | + +Both runs used the current v3 binary. Step 128 restores S9 sampling density, but retains the v3 +hash salt; its far mesh hash is therefore expected to differ from S9’s v2 hash. + +## BuildHost actor spawn and ring + +The fresh 6 m/s trace used the S9 movement, collision and backlog settings: Legacy order, Dynamic +near reserve, collision lookahead on, 256 result cap, and a 12-CPU affinity mask. Per-LOD worker +generation sums for the 2,578 initial generation ordinals were: + +| LOD | Initial tiles | Worker seconds | +|---:|---:|---:| +| 0 | 512 | 3.840 | +| 1 | 448 | 3.781 | +| 2 | 320 | 0.811 | +| 3 | 272 | 2.093 | +| 4 | 272 | 6.688 | +| 5 | 208 | 1.535 | +| 6 | 144 | 1.988 | +| 7 | 96 | 1.846 | +| 8 | 306 | 4.288 | +| **Total** | **2,578** | **26.870** | + +At 6 m/s, initial readiness was 7.116525 s (2,578 desired, 2,617 loaded); spawn-tick p95/max was +1.824/4.993 ms. The support ring became ready at 0.490 s (9/9). The complete LOD0 set was ready at +1.427630 s, with a 0.990836 s measured build interval. Round13’s S9 6 m/s initial-ready value was +7.813133 s. + +At 40 m/s (4,000 cm/s), all 75 moving support-center arrivals from 30.205 s through 45.011 s had +the full 9/9 walkable ring ready: 75/75, 100%. A stationary startup gate event before movement is +excluded from that moving-center denominator. Initial readiness was 7.087272 s. Spawn-tick +p95/max was 1.833/5.365 ms; the trace passed. + +## Quality and determinism + +- S3 ground agreement: 72 shared route points; far LOD7 and LOD8 maximum error 0.0427 cm, cross-LOD + error 0 cm, within 2 cm. The 100 cm datum-offset control measured 100.0427 cm and was detected. +- S4 cap agreement: 128 shared points; maximum error 0.0504 cm, within 2 cm. The 100 cm datum-offset + control measured 100.0015 cm and was detected. Cap census emitted all 385/385 requested bands. +- Cap coverage: 2,520/2,520 upward rays hit the cap (100%); no profile-roof, mesh or clip-edge misses. +- Core-off legacy passed at 12.5 cm. The M2 Hybrid V1 and V2 fixture sets each passed five analytic + fixtures and border checks. SurfaceWorld and FloatingIslands passed BuildHost actor validation at + 25 cm. Water stayed fixed; carve and fill remained unrestricted. +- Standalone W1/W8/W12 produced identical hashes for the full 2,553-tile view: density + `c10697a8f48380c7`, mesh `e75c9119a1498cd5`, payload `49a9a8d1835a6c8c`, topology + `edcb23288b5e8724`. +- BuildHost SurfaceWorld W1/W8/W16 matrix was stable: density `0cd2a1a4152ee4aa`, mesh + `a06243aee16b65d5`, payload `c700162935c262d7`, topology `0af901aa42b452bc`. FloatingIslands + W1/W8/W16 was stable: density `4dcfb37585d7d986`, mesh `f14f76fbf065c6ed`, payload + `53035c45fd1a082e`, topology `23550100654a4ec3`. W16 was a logical partition with the process + still capped to 12 executing CPUs. + +Far mesh hashes differ from S9 as expected under recipe version 3; current-build determinism passes. + +## Matched captures + +All captures are 640×400 and under 22 KB. Each was produced at 60 fps with VSync on, then the +Standalone viewer was closed after its back-buffer capture. Raw 1280×800 PNGs and run logs are under +`Saved/BuildHost/Round14/CaptureRaw/`. + +| View | S9 sampling control, 128 voxels | S10 default, MC spacing | +|---|---|---| +| Far horizon, camera 0,-35,-487.94,0,-22 | [s9_far_horizon.jpg](captures/s9_far_horizon.jpg) (21.9 KB) | [s10_far_horizon.jpg](captures/s10_far_horizon.jpg) (18.0 KB) | +| Cap, camera 0,-35,-487.94,0,22.42 | [s9_cap.jpg](captures/s9_cap.jpg) (10.8 KB) | [s10_cap.jpg](captures/s10_cap.jpg) (10.8 KB) | + +The cap is low contrast in this view, as in Round8; the 2 cm agreement, triangle census and ray +coverage provide the cap acceptance evidence. + +## Builds, commands and hashes + +Builds on the final source: + +~~~powershell +.\Tools\BuildVoxelForgeCoreHost.ps1 -MaxParallelActions 12 -TimeoutSeconds 900 +.\Standalone\Build.ps1 -Target All -CompileWorkers 12 -BuildPriority Normal +~~~ + +The fresh Standalone determinism processes used the same command with workers 1, 8 and 12: + +~~~powershell +foreach ($workers in @(1, 8, 12)) { + .\Standalone\bin\VoxelForgeCoreStandalone.exe --workers=$workers --world-assembler-prototype --stream-benchmark --stream-hash --force-generate --clip-radius=3 --max-clip-level=8 --camera=0,-35,-487.94,0,-22 --far-horizon-lod8-step=0 --process-priority=Normal --process-affinity=4095 --frame-limit=60 +} +~~~ + +The current-build duplicate count and same-binary step-128 control used: + +~~~powershell +.\Standalone\bin\VoxelForgeCoreStandalone.exe --workers=12 --world-assembler-prototype --stream-pipeline --stream-hash --profile-far-column-overlap --force-generate --clip-radius=3 --max-clip-level=8 --camera=0,-35,-487.94,0,-22 --far-horizon-lod8-step=0 --process-priority=Normal --process-affinity=4095 --frame-limit=60 +.\Standalone\bin\VoxelForgeCoreStandalone.exe --workers=12 --world-assembler-prototype --stream-pipeline --stream-hash --profile-far-column-overlap --force-generate --clip-radius=3 --max-clip-level=8 --camera=0,-35,-487.94,0,-22 --far-horizon-lod8-step=128 --process-priority=Normal --process-affinity=4095 --frame-limit=60 +~~~ + +Quality and BuildHost actor commands: + +~~~powershell +.\Standalone\bin\VoxelForgeCoreStandalone.exe --world-assembler-audit --workers=12 --clip-radius=3 --max-clip-level=8 --camera=0,-35,-487.94,0,-22 --process-priority=Normal --process-affinity=4095 --frame-limit=60 +.\Tools\TestVoxelForgeCoreWorldHost.ps1 -CoreVoxelSizeCm 25 -TimeoutSeconds 900 +.\Tools\TestVoxelForgeCoreWorldHost.ps1 -FloatingIslandsOnly -CoreVoxelSizeCm 25 -TimeoutSeconds 900 +.\Tools\TestVoxelForgeCoreWorldHost.ps1 -CoreOffTest -CoreVoxelSizeCm 12.5 -TimeoutSeconds 900 +.\Tools\TestVoxelForgeCoreWorldHost.ps1 -HybridFixturesOnly -CoreMesherRecipe WorldAssemblerHybridV1 -CoreVoxelSizeCm 25 -TimeoutSeconds 900 +.\Tools\TestVoxelForgeCoreWorldHost.ps1 -HybridFixturesOnly -CoreMesherRecipe WorldAssemblerHybridV2 -CoreVoxelSizeCm 25 -TimeoutSeconds 900 +~~~ + +The two fresh actor traces differed only in speed, 600 or 4,000 cm/s: + +~~~powershell +.\Tools\TestVoxelForgeCoreWorldHost.ps1 -StreamingTraceOnly -StartupApplyBoost 0 -StreamingOrder Legacy -NearReserveMode Dynamic -CoreVoxelSizeCm 25 -StreamingCollisionLookahead 1 -TestMoveDirection '0,1,0' -CoreRegionConcurrentAdmission 1 -StreamingResultBacklogCap 256 -TestMoveSpeedCmPerSecond 600 -TimeoutSeconds 900 +.\Tools\TestVoxelForgeCoreWorldHost.ps1 -StreamingTraceOnly -StartupApplyBoost 0 -StreamingOrder Legacy -NearReserveMode Dynamic -CoreVoxelSizeCm 25 -StreamingCollisionLookahead 1 -TestMoveDirection '0,1,0' -CoreRegionConcurrentAdmission 1 -StreamingResultBacklogCap 256 -TestMoveSpeedCmPerSecond 4000 -TimeoutSeconds 900 +~~~ + +Final binary SHA-256: + +| Binary | SHA-256 | +|---|---| +| BuildHost UnrealEditor-VoxelForgeCore.dll | `D87CC56BACCDE6559705EEAEA7DB73D1A6ABF79484D37A3887B94A9FD62BABD3` | +| BuildHost UnrealEditor-VoxelForge.dll | `9D0522ECC9A6AC52D9F3ACC2FD44399FE70F557C5F11D0203AB4C4B8EE75C198` | +| BuildHost UnrealEditor-VoxelForgeEditor.dll | `AA196DA5A6DB3C84B5EC4F002AEBC787386DBB8F2798F5D1EEC03010BF642BBA` | +| Standalone VoxelForgeCoreStandalone.exe | `E316D7E3E0B3D3639DAE61D6E2D1091F38C59A210B3B7DD8124928D21A8119A5` | + +## Run notes, files and safety review + +- BuildHost and Standalone ran at Normal priority with no more than 12 executing CPUs; free memory + was above 17 GB before the final launches. BuildHost peak private memory was 3.76 GiB for + SurfaceWorld, 3.27 GiB for FloatingIslands, 3.39 GiB for the 6 m/s trace and 3.67 GiB for the + 40 m/s trace. The Standalone overlap/control processes peaked below 0.72 GiB. No process exceeded + 8 GiB, and no launch overlapped another Unreal/Standalone process. +- Standalone’s first compile attempt failed in `StandaloneViewer.cpp` because one helper referred to + `App` outside its scope; that reference was corrected and the final build passed. The existing + `vswhere.exe` lookup warning and `VoxelStrateManager.cpp` C4838 warnings remained. +- Three BuildHost CVar attempts (direct argument and two `ExecCmds` forms) read back zero and did not + change the worker grid; that no-op CVar/harness path was removed. An early Standalone step-128 + attempt also did not reach the pipeline; CLI pass-through was corrected and the final same-binary + step-128 control above is effective. No CVar remains in the final source. +- One high-pitch cap capture was black and was discarded. A separate early capture shell lacked its + memory-probe type because the PowerShell session was fresh; that capture completed without a + pre-launch memory gate. The matched final captures were rerun with the gate active. No + `0xC0000142` occurred and no run needed retrying. +- The only UECC directory was the pre-existing + `UECC-Windows-F1A3EFD5469F5DD62E68EBBACAC84376_0000`, timestamped 2026-09-17; no new UECC folder + was created. +- No new source files. Changed source files and compile spots: + +| File | Compile spot | +|---|---| +| `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h`, `Standalone/Core/src/VoxelForgeCore.cpp`, `Standalone/Core/src/WorldAssemblerInternal.h` | VoxelForgeCore in Standalone and BuildHost | +| `Standalone/Source/StreamingWorld.h`, `Standalone/Source/StandaloneViewer.cpp` | StandaloneViewer / VoxelForgeCoreStandalone.exe | +| `Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp` | BuildHost VoxelForgeEditor module | +| `Tools/TestVoxelForgeCoreWorldHost.ps1` | PowerShell harness only; recipe-specific fixture logs | + +Async-safety review: no actor async source changed. Epoch checks, MPSC queues, EndPlay draining, +worker `bShuttingDown` checks and `ModsLock` remain unchanged. Region-page publication, shared_ptr +lifetime and concurrent eviction remain unchanged. New duplicate capture stays thread-local and is +copied into the tile result; its worker-id hash is diagnostic only. UObject and RealtimeMeshComponent +mutation remain on the game thread. No owner `.uproject`, editor, `.uasset`, or RealtimeMeshComponent +file was modified. `git diff --check` passed. No commit, push or stash. + +### Dialogue + +**User:** Keep the 12-thread cap; count exclusive LOD8 costs first, optimize the planar far path, and verify quality and determinism. + +**Codex:** The LOD8 interior now follows the MC spacing, with 128-voxel boundary strips preserved. The owner-plan LOD8 p95 fell from 59.848 ms to 13.933 ms; cap and ground gates, both BuildHost actor matrices, current-build worker hashes, and the 40 m/s support ring passed. diff --git a/Standalone/Research/WorldAssemblerM3/Round14/captures/s10_cap.jpg b/Standalone/Research/WorldAssemblerM3/Round14/captures/s10_cap.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7d3c5be0efe38c83ddd2ac2a144cbe3f85e58c88 GIT binary patch literal 10783 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 BG@Sqd literal 0 HcmV?d00001 diff --git a/Standalone/Research/WorldAssemblerM3/Round14/captures/s9_cap.jpg b/Standalone/Research/WorldAssemblerM3/Round14/captures/s9_cap.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7d3047c273f0ab6b6879eec38c9e6bb144f27bdb GIT binary patch literal 10786 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 literal 0 HcmV?d00001 diff --git a/Standalone/Research/WorldAssemblerM3/Round14/captures/s9_far_horizon.jpg b/Standalone/Research/WorldAssemblerM3/Round14/captures/s9_far_horizon.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d4e8e13917e8e4e74dbce9e449100ab65ae034b2 GIT binary patch literal 21897 zcmcG$3p`Zo+dsT!oX^y9&ar5Qhzycsq#2AE%#2jFcA+#gJ4HpMgF)0@5i+82Xc}`c zq)m;YqtsB!j+v<~m84Qc?GTaby|sV8=lML(`~Kh0|9$?Sw?1oT&8%h3y6^kCzSsBq z-uJbB^!(_D3`xWgB7{Prpv~Yv^y4WM2x+NnXlkfyX=-X}YisG~n;Pir>FFqT{!0P1wd@vUcxDN!`0| ze_Ga&?3|;=en(Cgaf(lsl%D4Ds{}%kxLQ(k;i9zh(&eV+E3LQh$lBWP-s_Mnl#d?& z)$^qH<-n`gZwBAK8yfyJHa_ura_Yz z1)GYIs+yg<`n&+DMr`tY`(+uL@an>|4Y#xwdeHthj@#9(ZL$dW(&5uw(|)$>f3IPg z|D%@uN5lTJUA>U53JMIKiV@@wjT2wiJh~d2Q6tawq-dQxeRg+L{=7r$d|j^5_JU&?+dIWh*%ij}E#nEPjqO=%Vw{-tHwq-YEZBjmH^A^#6M*dBB) zm0$u11^f&hf&&amX;l3GV26K_6P4nSsFOJi)2JIPbO>++no58nY6t;xfjJRqIrU#V z-T(gPzwE%^@c(N2`S<^vO~tFh|K4a3=>KvFm!C(#PyM%VIa1Ww1Lkz@7RSc(qj!*f zgBPw{oImQR^&GxGIhyH2iBdj)vcDnso`(P0j}fcWqKXA&G206E*%wc5>>5ppKgs{& zy_bLNRl9((O@o26?Jpyp8*q=hA3UnaL@**0g5k5Z=QelH+BgKmLxU{$;-Mj-c&m(+Ge!k2%tHPqhxNS*z?v z-k%fnz6t*nxni(nvgb}0YVyc}{EzwJbsL?^&p(WFT0Q*k^*f<$W`L@Z(pp*kcy~5T z>q1jQ36KLU1*Y_0cm93%|NUJ`+i{!pGV5KslUs6ma_IG$9XtQ5u)19&YgdFNYHRbN0Xg>VM}wW6m@Fhnp05O6q~3K&5$AN*p6bjiRUBR8#Xsaxyy5 zY*J1P6hF`q#zdsnN;(P)Lse=QSWBmq4JT;(g~_M6mpEKxXFee6*toH2vJ1u*y%4VV_YCJz&#J0|q-zSeMGDfK%c7C+^ z^Ca@l-E?>QzVCZ>{PPjo*!k_%ba(U3Yw0UU7}Q?xsraaJ9v z3e&&eeT+ZQunReNvHpElxT(q&?p~ewRO+F#=~tL5zui&$^@cQNNjPa8WR1m02CXs? ztP>HIaw619x7@u|3;k}`_5<4Uc%Q^1>eq>LZm0YmI_ej9P2zT}_Q9_zUhcdsyJ(UA zk<@B&#@U4xchfw5k(PPhW(#bh^ImOPZu^bkR3ZHVc`Lb0sk34KjD_aHYWvD9+y7>) zvwc1OS^U6cA-|GzQZt5@JQQ9U1-TtQ_)1AHT@%pLS$V7G#YvHIs;wTch+1~|RpI;I zs?)_CNYh(bdd_X|q-fVwC#PEkI1>J8^0e;%L_~*wgPwzn!~YT{|5xz&Km8uSt`zks zAy?r+{B3@~6R~>rIFW(w2qyfR6T8gpMbo)P&m}uEH3eM?7iS#hO~{`sEJ|`{W@y3Y ztY`FJOpTJK{t^=4(w7q5ft2ejhyLm`)3}$;wLg&my>Z)LJl5pu@AnEL&FO==XEu>u zktSpBju295CChw7%^QdubUU==(M_!X6>N*zJE(36p=03r)dD9%hQ_al8y(;gQ+*DV zfL4#Vt(tK5x8&Kye|vhuDC(;%!~Paqf3-Erx+^-?rJD$!^X0s&CBlBkW|^XAsd1S4 z59|0MuhE{Y)%YjV^=c+M1>Nh|(B{Z+Xi-5jA@dE(iJ0BK{TsH_r%j&;Rp-8i)|L)L zzLkjiM22; zV9dQ23$&*V!JNxOBUlV7Kw@0u6v05hp2ykA zIytoa>+6qGH`^qV+SHGQjm_%9Je@?Zk0mRSx3hs;Cl8#F+J1Z1zA|;kXGxNtbav0M zLE{E4=Ch?^Uc zkKCN#%bdWKR%1xm4nbkNdXZ{56v9&#h^KdcYnzn_gO}T8efWp10oq1_una&fuuKfZ zN5EF1k$;#Y*!)Uflxi`*(ZR*|bnIL(4?dGx`keWGig~oo?so!AH=K(s9Gd*UTLFL* z&Ri%ZxBz2mnhRh5igW+BcP!)IgpmY+$oN0nuuLpNrP}_az@AHpV=OyafdrCOK{HS- zkB(8IBu1CY<>=0d!zq_uPASgZzD`w~KPb6&$=^9jplcT8bynla`M<9%Ja~Wgh(Ytt zbr;5ylS);SzE~a7OVHUl`Fq`ct;hRPy%(F=OCGA+!v0#Tw4tsw_dL5SpLXohXH~_G zdzFv2FGzv^e*etke1bE4qkV+&)$Jqq=5;@&&eFgBt~#BKsCODnNmjL19{tq0@vdJ3 zF?n`V-GT>8BD~@+kK_2Jn?B^Xoj)1=iI(^<%_mc~*=(;(g>Ul5Ec!Hwxdxxge;Ie< z(5H~{Pfdr{Zn<+^Nk7>5eRRRCIDLM(?i1XpP=1e$S>kce=J_}5!L#2U%}V-xMnfAqO?3>X1219Mq=&Z($DW>j z2yb8bRk>U3*d?1l3ikUw$(Y8JM8|*#e!n@_YgzLQvgu;=Ykq#~IrUX}Vdj1W(1f*a zfy%a<#nxmEzCp)%1EK31>&z1gCfMWq#=qAke+(O2_yekBPDhT~|9}c94iwYaxTSW< zd6In6&L>^zy77NYZle_b#laW)cQwyq24myIg*Jlpsm{ekX741^Yg^Y|H6lKEtLy$T z$Zfs&jqU4dv$xBKHY{s8N!;wFHBk4W&-8)&<-02iXE%Lg3CXKzv@R?=LIX!1h-p-D z&>2&@y0K>3$CSGQxn&u|Q^I}6swd9`MyfD`Rj?)r%K-iW_J9QsAQ1Q{`xgWJPqUqK zj5((e@=N|@=>I@m6RwG;E|(AF&hbUW9nj@}M@w2a0wvDb{7(uu_Y?e6KQGQxm6-|u zMc_`JI?2|Kb`PC9@GqCE0&!Oz!#_1g^+?eBW1>RZE8PAqj~$Pw(o-Lu!N~XTL2XL3 zWF6vUW6%pCoG3XQ;vo#G%ZENnV;O#{=_-EnYWnDBk(2V%yz3Yh$3It}(Db}$e~54G zer=c2bs1+@LU3x;)URxLfO{_V%qUQ)xnaQqw_8>OV&qIz)%9z`I>E+AuTu*g4@Wt2 z;}~qoXVIu%k1|*MbX;U?)uSC}*%%b_ea}H@tp5${YKi~B?~zbdqObdja~iMkm+6m6 z6*vCOk{@R)aDgI<_o#D^MbXI63vLqP-g7uICvY-G-;FOg488!Z`4MmytY zZ7N&`{Wy_LKcIv>-Xy<2sAx3gnzGgHkpGI61qUo#Rn^Hf^LDaD>C-Io*X1yDtpD>} z2?zVGtxD6h5wo19b@1GH;dDf-AKRbAYPK3)dr*#ElK9nxAdw(T5j`)TC)1= z%~5T#!KMcho4ys2m)U$kpu+~$qE|N0Mdp@W^n;3b7<9}xOvg;*uZ#(}|J}2An>@(Z zIpU$w>C}(C*Z*GeD8s^=xbS`GWU8MgE=Kp^NcHYBKA$Gwm!e;$w)yA%%2=PKNOcn@ zuRn+TN}XNwjp8YNn);6P&8bg8>~%G9O5gfRaoe6z-<1Bc$N+{+(X z5;z%{e*^Q?=Id&~Vg;_OCtc4Jy)ga*R{u=pfOyzWzpex`E8;^ueA%Xj+avZn38zu zKLkG7@|$zU4`@m0H?_q*=hNS2eP7$P{Rg!D@Sdw?6FxH*KAG+Qla_x>4=Ag`uK15V zw4pigtD3Qh)tmG0c8iT|KE3`HOoiAgWA?XggP(Dych248?caP#YAga0-G4x*5~rhH zE`H1n0kpheiP-;EzC>agR`BgDHG1)?iDz8bR+*h>ro$gab1XCSVQNjv*54EMJK4qn-uH*faY7cwq$+v@gw+tU9n(S_{%6bpHyIYKzm(mT!tbRz}5+@ zewo4SM0$s`;rrdHonLVqvvXgJR6h)PI3QIB=1(Qq9IGRzU%<5gJW?bG?Oo$v;r-RI zKONrosjhr}&-XovJ3!Cwc5BG#-P-jXI-FDWn{W1eox=+rf4BMTv+(|2=V`^D&Vu39 zPwP8^lvK})w$FIQ-`95Upif=uTKUIh&`Z!QZzR8o#`^te9zP&d)x9ifa{3G0bY{Cp z=tler_ip;%@b&Ao+OneSfk)b<`6;4?r*r(}#V!)Me6YO&@EVmX?yp10vPT_Jq zV{>jc>eck_~y2w(%|%dH2C^HI~Ji=y7tf-)|nLa zH>m2&*xco2hW^rfjK-|hz{Zo2bPMKC-43Twr?%b~-}b64~KJl6h7|E-zs!SoAGwZG0_s(U|Ohi0)ouABM}N%mg8{AL4X zU!REoo5=6sTSDgGt@vTADDd0M>Br6+J&8MFro$jisYR1EJ*)=ugJ;kNCE7`2ywXsj4yFnw8z-kv*MubiC0)Yt6K`M6}z>SlE6gFT(NtoOmH_oimwJqxb` zvec!btxI19t>Qom%gUT8#%sw97SrWDS@WRekRDEu)gTEioLw(E-7p)8vNlV_y*%@Q zo8BA##}H-w;zzu`O<-7WcmVaIMw4*yHy#G{HT{WAg`LgFcWGJJ3S`IQ&o&j%O2-3- zr8lCdsME?V{&mEsSGiDq)XI2Pej7K+e~i_<1Y7phkJ&@0pRd4OOEc)G*)OWFY~bHs z@^beSGwDW7={e@J%7FrX)FG_f4UXCmh(Q~jh{dC{4`?s9=4@Z3+@Cmv*STr+ZXA2n zKnVKXpwCauabm*QNWF;3)4Ciq6m<8%_hNNL=q0wJ)6p+CNh<~Zcl@1>IbZ_c;|<)> z_eex$i>x{QVkM9pngFqb@Nv5-W5-6LZqH8iEglLVxB0%``%ToPGC$J(#m`lr`2B>*G`-7TgT^HO{gF5SUiz4A_tn2y zIOhJIHv6JaI9-i)HwQzwQ9KkrU43tor#VKjd?;d(&#)*x6Fkjp52?t;CqvxBFF|xZ z5TOzrPZ{g^0U3u}OW#M^q46afT0;w%-7w2cUVPr)qA05`Q{2lo)6M!awAbyslN&SX z5C(cN%-s7e-D!u0aH(|;+w_6;=~?W-F5knPXBo4&vZGT32wNEbH#Zjh?e?_dnhd~6 z^|e~i;MKgxG4y3g;&GX-n^SX9|o zGI9qn52}5Y?M&2}Cx;}5?@jPu#+!HNvl0})5byg18=KevIvVyeenFk1W?gzi%@1fs zBI-$v-fNsYL1VQ#Y%uA2WSa$__ci1z@5`2+(qMUgrJ-Ja>Z`QrfUs%&~alP!NeBS$^Q?nZJ4%TDpMrVex$zK_@iYLM>p%*Gr}Ml%r4yy za*T(o0le-G{}p4ltp2lT9wE%e>fI9Y7Xq}lkLk_5ux8sA=GBE$yfwaguEPcVRMChP zFr>HF-7`F~n}l|olF8aKJ-0ioZtDT=x<0nE*^s7e!&v(+=E34AaWXs>L`wL@Z}$*^r7mGuk=g# zBrgLJl(4QhueG9{mIy=7C$4t7-g-YnOSHZzbeq3_0>1hKdIE2ss$%lg^hU!}4=A@K ziA(@s)*CbnA6}2@>yy58)`mD|(#UVS6no#+Ab}^^VRq_^_)uvY*VMlc9&!7K*Tudg zcw0ZhJWRqS=|O?*LRXFJA(x-gjCZm&dyflehcwgEVz*gKQI2Y^T`ZTuJSEDGFexhi ztG6h{oTuupy`u#?D?hZKr#>Wv`$|`EWTx_2F$JxNr>QZsMni#DS0f+PGgJTX%~5S(b2~ z?qn933b(S;BG0|*Qs^7}%{^<>uRm}Cd&hvZG*82YFi@|R75gUOj7I9y3jjt%~jA|Sn(N*D^tix!QDPFO0hdM#2&YwyeD{3 z*~WESp`FBnV%|2o`boSkQeWYG@!TtX4AE|tC9je`~S@{v_a==osD{?AqSS1GrH^YiO`lRt~Hg)Ka_ z=fr-d8#WceFG!QW?JHskWss^OnBH3gN1zio1ri^9A6<&ujaS#0Vzhi8g{4rbHIOwS zb3PIif!-n?EK)DxLi<>$0mDv9&XCBBQ1IQnr^e<-ChhYcU<0|Ez3(UYbT?)PTa=_g zgr*bHeuBSKTKADjfy&|Nn5A{DAf*es4q-GN=QR(bW-&ef*O;m4jd;DW_9+6cMcf!7 zo#~c#sczZ^D|XWRyE~yMuZf&zYtJ(tUiZxize^2$qup3Ij9s+}3}T=<^X9fyjeE6- z^X130kzYgLkFIqiba}pWS9el_&{;fn^zGWye<>}_eYi8;{*t2ifwJi6u3PcF+`SfY zjVt}HLyiifBK#QxxDT@m{@7Xs5^ay*yOf)v~d5vC5|*CGEzOC}#!`K!ll(?m0P2fQT${fS6$Bmh81F+Gr^ovdXyIM6eB zR#?XjErD$ctSN@Ou}=v-;Ch4)fc{Sy_NF&gArM8mQ=B{$nhp9{MLXt?>Z-Vzh3Meu$F9qurT2yztu_O={l9%1-YEGt43QVt znUPX^z7Z_xN*b!vD$g#Ewtdq_?TxkI;89&)d1hautLfuh6kbpN_VuNu>30QpCj^dc z_^z_8V1}!QLpI~?9-`o`rYVkzjCrcdELL`S$rD9(1A$@llt=0+2+LV z@PQJ0wq-rFo_=92ngm_$%WHJ(1vubN><)WMRN(cV2CwTqBLz~^akz{XL%oz?I67Hn z{m83>8HhI;$fXtIo{QKf$PPU-93;e%#)J7au<%x z)fwQ3r3*cs@a+wCMut`}eUiJ*3enl#ms9CdCO@CJN}7m^(Y~3migT!ZW^GYnND-ms zkICIZPlnsVg|4rL@W!(UGqWydRc3D1tUVhuy$%r-h`@6IZRP%+inb0#@Tm?d=qufKY zLb8Hz&^7r%R_)gK93#9OE`$SotJp8hH10^2mjQliZzALvrKOv@tAz8hpasF$P=I?Zq> zui=PChU(>N5Xqv_;w(HON0a4){Abv`5$@B1PH(_iG{9utZX(0*Z)S_~7fE2mvd!3J z>QS+V06maPy^;$wP)Fu8tjMS0wE*HOE-UF;a7$5=U{U0L!Rs!&H`(3`wF~Zy4S1gVfDU-~F)oiRk&p%W}I^4^Uh?&M>MH zZEc?-PBdvNmfhJWcL` z8O6%-)LQ!zbDlOir>d7Vjc4Zgv-YA-X$tR2dRJ^KAqT1$g-7AC8ECeHUYL5-UHDCM z@Rm~G+3VoHjjzhl3*;Xp>2vS%XTsHz7tFMB`xqj?Q?!mMR8r z1p7{zI1{1bMZHiNAbNd*&z_%%fp=D=9zY=XkV_S3gqGJCW}VV(r;YdD6N9RGnqG3a z*34QNd#D;MA+2uwO(&$0Vrmp2bnI<@qY@ko1bk7idlVU`FcK%(@r7ggLR?!A3)*TX zTn?WGOG$F+IWMAB)q*o~r;Zk0)+a}>sn4J|X92<_Qb0k0>_k}%&Bq4%X z2J{qJZZ(wNA9*b;3x8+&5M#!W*<+o_uEp#7GFNRPmD~laQ2y=Aw;DX4!i%ko{R=;G zna8c(p_jJiDz6#5>|AQqbvP2G&-92wsqnQozW9*e%66vpKg!Me0S#B#kT&B{Vl_{U z^~9)H8KG)*1po*U@cGk$eF;9PDag|7p>SfRX_J> zTGRUb(-Qqd?+LtiVc?=QskjenCeO($*ZyuiqgFl3Km6I6I45IGF4WyORwG%Roj3_cc4#Hf;XPbrdgwxltc=ZzXQcdqP z`VUBjd|z9AJXm88p**;Lkz|!0fk7oP1@z0b6AbjiGbD{B&xOg97o%hd6-@M%=S+%I zKpF(ku|hmgBSqj$S|z2`>&d(eX+{*=F0{Ip%r`686n8b5tZJJkkN;K`z>8xqxVBf^ zZ;=l}%=NCz+qu72$wHg60g@q5P3vjoQtD+EGw%Q$ef|XtC_w{_FCtjH z!)l0=a9lUp=nN|y8y*-EB0mm^mSF`(@5A*8%a?JXBbm&m*eEZF0OQAKGCaiG_u13# zL_zx~-st4>Jjcli@yJ4<84F?*e3eh%m`HRiXvZ6j|MEEB^LmYaX;mR1^YsAvEA90e z65EEgZ)&2x#D4U2MP0%aa? zh_LvSA8Wh8M`}BeT3W1z_F_JZPu}2^mB`sH+xiL`iDKLcHTIc~1?VH;Q$qHxmE4`H z8W(Ekn~OI$o!#A6vhBJpJ+#vVl^l{qq__?*v0XsFz)+=em#AoV5{XVUX0u53pd!mC zGzfK1WOOfCI z1P0;$dJ`$vsvQY37iC?lX>E23Pm77aPF#fRpIGlo7Q-KXjrC{po@p;JbU-42qo* zTEINm5T=DdWSSxN3lGCnSF@J>Egj{989L)mxARV93&4!z@_t%ehK4%mt1wusu+F*QypWEQ6lSJ9%V5HbH(`d zCR~DOOmeWsHSmo$-jb}mnYNL0-o*atN220AZM*eES7 zMt3`#4z?AD&9wjC?vo6U4LJ<2a$5+999-`<@5zXqyW98QhC+maRrhDql}J4CRkiX0y|C7mCtAy-1@jq+kE47#>ZB3 zqs!3T7HX2+LR_1=_z9PI;y5Dns=im7sD*1|pHO~KB@tF`R+xLqcgWoJ538*$ILX@e zq`HE$hyTjXi76d_NDs!KgF83mHLN{qyyRf_=H?WA7Im!x;aEiJoXiovAPXFekM7#2 zOyJ!UI|F<am zu=_leO99i_Bbg&H->}}`az%;GwpVo#kC00R$P!a48Qehzja;gpcjL1)NPTzJHw_$p zT%?Qj=0;3xIPuO$AoKST2vpLwNhxML^}*cyfBGDzn zPEmh8v{{4YUAJzLVi)6$&Z&d9-eJZ~YIoqSR)bG_RTS_J&tFzeTs`(Nl;gVo1#$P< zsWZ{u+$E-tixfH*J6;9&@NXqK7A1ouW;uYyZ-eMi)A4i)D&$k`%))kSZe-^W&iBH0K7 zk4?ftf+ET-+^|j@AX#bzy98Wmys}FrW`OVG8Nbz%fo>xDNd`RnL^_odpL6MG2eZ+z zd&XW5Xhy-Bd%cA|q4ZB-I%HM&Z-Qg5JLy*PZH?=$-Q=NA(0~O=PF}eAM2d+{-Yp#8 zKP{E1#ed+6nfp;5l4O6}t*I1lGhS{xDeN_1K@U%^e6%h<@web&(w3Ul343yb(7e}= znitl5aE~f<$%9{#;Sq&l|NdGdi&}1>C5$>m33lR#AQg)`L(RRI9(Y_rL2K>+XLpzu z_-zXI+UN*W+`i?1p}rIGB}GH9c6WAkvt)j+04I{93m?2Tu#3TPWy#o z!ege2&0X@u&&D)lp$Va|x@e6D*amn^T3@j}RP2$&*UjN@}G z_QLC3ysm?2_{JP5M5 z^O{xM3-sQyHwu7!0fKwR+qNz~r3Y5PXo%G%x~t3H4CFy%MX-9sg**;Qi?ri*6rkS~ zch+e$TP(^F!D@wjH_E?ZUvBYRE@{n>ml&%V9xJ;_8`YtW@*JGi?oN+jTfJ62!a(#4 zbVcUtxTVoF9{oO&SB6`!?vCcW4AG1o^LP>VOG_0kLcuLuON=7hHo5><9sDe^C86R? zm#UM~THzA};sqegXpXifPm?X=fsg1Cm>0o7iHZ_eY6G0J?j^?o_Aatt##+Ygq{tvh zaha}%Q^DWI?568J+Ok~oXvqDE_S*N3hJI{*n z2)zW45@RzXkj#oJr3l=&$GMUnIz9)nt zBo({f)e);LA3&CZKA~0^5S#TXbLIP5V$4(RDOdfTh2-aKpwdiE`&MX-PYLS`D z7rBGuSJFx&6-Ebyj5m3>T<*G$sA>JnrhOMOK_ce#QM1IgeO`~j;6xp8B00|NPW zUf}LfF1RVO`WW*aM&<|B))ix_IrMP^zY^Jj_3qMVn8g(+J`sDNe7L{SHhQB}^Eg~8 zu9vN$K)yn|?R0NiiU@j0e$J&~n?NVu+8sbriOVSf>9X9NBG}gHREXC{3+V{MT%0XX zIf@&VV#F5E@26_-Hg4@)-F8IH$D}q`^oPHv3frm3D7bBRCyJFi$i^afVwwHG!Q`cy zYGjVgTvEtH*>6$4YqZ!LSp!I5u-Xc)k2nM0R!H{5iaeoZ9BP=%#85j)B&Wva+m~HB z#i60xiP(PnrS7DKGDoKvday`!pAa4W+Fi#_v(?CuQU~ILXn6P;+?z5TX5`yls zi#Lvq;!}~+z*G`FbFfb(d8Ahj^SS2-#mNzVz+zol^Ry(#Ivt;BZw|arWG1n$T8O)2 zhpTB8Ms|i3cBxQV0kY+eSOMLTr)+qwWHiRAW6Yhji;&~Et2OxD+IiW*>daJ-H>rdo z`{CZrUL1M@#r`-X5xP5VK82?W)8rNqQ=pzz zL^3JRheam~xJ%slrO`!)vCudfclF+A$UT9(duZsTlFj03h6fJK3NRD4z#EWv`CL+g zoZ7MAHeQ?v(-dz^-O89=ii<=>eGCb^8{i43;{etVa_ChjGje5SVxtgz7EBGrYlVah z016NpI0XnwyNOIG9Rwj4uUvWkhZRL|!rZ&roCa$yOi)cI@?5IUjkM;(#CyCCWix?7 zM%@h8;c{uOyRT&EIdYk=0rihO(set8o2wrBIOO&Dt^Y zLP|}uefMRY_AUgEa{#?LF;C4DbreD2OyK8F_%CChh~@{}6QDB?b>>s%{m!uCctgz| zWw#8eh@`i`nsFj0yn84S1Hr=ym_atoWwKJSBDN(rFqqbh-;Hzuaz$4E2ZOoNdiV40T))OVj zVayO7cgGPU9>|kIHLQS6oGW&qICH>Q6mN_bSwLX2poQK*)@(o`m=CZ`JPmzjkEm`{ zf!<1bh`2Eb--=L!nNUK3d_Ltlz8xru-r5mQBuyhLxXNo>uOdS0o%KQ;+OeUR7__al z-zh?BTw~aRO%6pchNS}8DrEB-g151|s1U^Um9&8pm=4M8;7?%5yggrFUj)YlV#IM= zf{F=}XbzAunTY9han>c%jKaaB7=g%IjlIe+>~Ig4Irgf1z(*oWtKb;TYKd3(rQ^tL zjLbjLjQ`3KAw+%NSEo16}DV~Bl9O7yxZLWf)Eu__ISXfa= zUTrLR3h0r-P!3LdBItxR;M9Z(=&~yX29S32R=e#HzWNEWHM^9&LBU$y5@G}Q@PkO; z=`ot5B2vSAIvd*$aL^(MIZg115@>-=h^ypR&Z{&5H-#62>83fq;VH}p*b9QIPiK4f zD7WFDbGymB^2lXiA!$K*BsoNs87*4F+Y+~oMIc9>Wnv6_ld8{;h+qWN>gJmY?H_?l0GSS3HA}FIxw^VcT2z?%ZK^p1V`*KYAB-SqGG|2VFf%$ zu{+H1WPzH4i&w^RZj8c*M?+2YQc20d@meAi;cn^zE<~Fs3lT<2)&eii4GMpX6llAL z-AZOhBG}c6Rz=rp6o}+tv2LP+@0^S)konFrQA>gg_{vT!H^R38^|FD8L2>9q?!ePQ z@fMtODwl?85*3Dn_27^fhl&8DjNoF%Kd%3h2p0L;{f>}QV0NOJ5Pb70nTXvT8X934 zf~3GLVBs#1jA?PF03^i3M(=QnW)Ggx_#n;_SNV6bR7&LXNb zN+__ekj0^~?sTJ&OA`0>hhId;WDp;-((d>wbB~?c!Qp%KiCf^(PRKn{d!JB~5-b5N zJR2fDHq;+iLCAcyI$7X@S*g*PVc2I&+!~1=DG4IE*<&A(zI4ok^!t0fe(roaUl9@6A=QlF zSDJ8KR%$~#I+)SiQX=!2<|mp9ibWu)qED58g%m*B4tFf2KyLD^U=+Kr3Wz2}ne}n{ zcLy^ouqX#J(O|A(wJBW20J-&Mf1%^>&ge8`uW`1Dr+qbSKc99k66#G-Ro0qG)~Z4H z25x!@y`N^G3Rufa8E67o?Rju?5iDmyLWvGtO8`JCl$Ih8I0Elfn74>L=w^V+Slt^U zKdqETSb$Jt;vU|s=D9nZr*6!1SY8j8{XRr!y`4F5Dx1a%urf8yL50g!I=Ie}$0LzV z0(!1~$5@lSWIu;0e z7;&};*@t6m%M-=40k(xYcAyMMEszD7-VngkyeqOe;N@hJwij|NyTT1)MisZVjxq6K z?HF3qh!?l5l0YQ5ne7*%D=wAK%Yfh!dNKAEv&S{)Qn`etCPq>0Vi?h>bK{q447jAd zP=-Sck5z-Hjhs~jwLP}l5lk$>?ganjDUr+$i)8>>5KLHsK)C>#Ii(*v zVmlu&OCWtDV1XStK;GaJ4Hyp?*HUq^7Qq!H0rNmv2sr}e5pgGfR#Vo43Rj@jfXoBv zuw-B;04nCPg}GO(d$9Vy?kLb+U|&h_Drhqn8B3Ugv zBX~VrE3>UA(arpEc`ZrJ6;prSce!8@7ux~&DuJxLTY8p2;#XuRcSvXH1Yg{GRU8L6 z8d%w(6Vb*`<;7i$Bu})bWuL*ZcVA-7^9XNa4)Ze#HHV%$?Eq%ACV;hepa5A5qns2i z0&3V$Fq%W(9UcuOiD`gYT4VQ#bQKN3af798V)ZpgQYd#wwi=<<_k_jg(6sh;AG%C(GWQTpsm3D2whV` z@rFN`*xR2JJ`IjKj@;SPEVM5pYb2C-T48`_bI8T(RB`4@0NT#_12lOzND<>sR)7z$ zfcvL_$~zE+=U`n=V1EW#jWO)N2G98hgAWqDJD{v0%IY>;D&zxt=?>0!0KA<{e+O2E zTZ14DMn0+j85ImB;M4qy5KyAw3QUC!>MZ~nr&CsBV$97&76l;N4m2ZpRTAvL9Ve6$ zouXGu*+&qvQkYD}4&ggfNc&(ax7H|$e-eRW#IR}2v*Jir88EQCuZyT;2J@g ziOn_>{)rWU{MKTJ2a$Q7aKx5GE4Jlzfgt&m2F4+7;nOfGVNMm+r_xZQSK;MShBX$_10!5mkNZq%nUULe^`yDoR4)Sbx1UO?N2)S$%Q1TDB z*Ib=g91@My1soJ4@|r+Ggpbgh)^BwIzKPbx2!V6|G#V_(+j**hSN(KVAo9#9OC(iq zm|i{CTl9ty1XMG+VnlN#|L06Cn0kQ0b1mXELA}gCL0!n2^#Tb=L6Vz2OW;PG0c)Go zW+0&qQ)>%e%|)kpP&r*EB8=$;X4O52y_zz}4yPbdym1hZilBtNpKcGr7Xe8_Kq@AM zGLd9aKNli*=PDjyUt%*1qYH_1n5DWrf&zhlMOj8B2w54OzS{j^PF`uhd9LXMo&Vs8 zK!-~(m*8bU{R7-|oazg7hk^lK!r7jp;2MgGae(7FBBxN~lq_?Pi2$-1Ne?m?*SYB` zYozQX9yf*-$2QzeZ*#&KuK>xawVE%SIj5We&;bgZSx#xI5U^Juofq@8OY6Li*W{rq zL4w*Jfd;yn8XThvI0>*b-MNt9jFqVi$Y-!ccYupAdc^D&e&OY{uuoBq+9S2vY`&?Zr4G<)+ ztk7Tvc>9@WgP6?#UWyU&3t~v56o-F`s%SJhNY22c1wi*me=;^HX9P^fXaQ{o`!8(< zfc5&sIonb9Dgh-_!m}bAaL<#-DWq8oF2=&G(wrE_Ttx?14A`I&0_F&mr$GlJCM6JE zN3pj{^@wsiQ#f>u3uy@;E+A51nXsCRoq*rs%EWit18SM7!;X%|0+fa5j~KuQfy1Z? z?JI&As8S*N6o+nz3J{7K1k14cPUmvO#Wfe{Iz~lcj&!tHoPnrngN5kukpcc*o`&y0 zZG%hL`!IAy;T%kc1^^UTE>_{@ZkZcEYM2f;0y_*ad@W#f0bwHGMKpN^KATF*piskk zDmb~)A`!y)h^s}_z&-0fKvX2f4s=>h8M0plL~~oXABz{G2y8OVf=+g1o8suZNmyM_ zG6twFHhFFUbLT3}K;Sw9%Fpy*pn_y*fngbT2aFiZD&&;r$SuI^mL9(X9M=jZfXofl zWP%~+jmCfnP#;$Wf*BEn zQEd<~0ZJFa!x%HL4CV|%!h;G=;4z?(oVwW(aS!8XQGF;)sVn2;5R69A)vB= zbD+Yia1{(4tuQL7mgdx*RF6pi#wG{wRB?&`947_>A`U2L)TXQ%CAIN)FwovU)sut5 zr@#oHV=fEln46!Q?QW_?aW?T8-7vGn)0~CsghI;YHG@Z@D+qRZkrGIV7OWN7on^iR z|DQ4pyFhx&CAiHi=d(dLHvAMCjKQ@8f){P5kQG3!3&ujy$6^rAiA?Mr&_cBpWkd$# zSjr3$$?IF_GMsjS_7YPFfJMk$Tq}WTKqI&f$DKl!puliI9L_KaR0@JJS$eQo9>)f$ z84Thng!es)^I>&At znNVl&@4>1Nx0qI53(x{}7R+wT)H5`hxy<#-gi>_|j0)EQPN^DMD!`$1v}28(>6#+D zQlao^sGPl!^?Nqy(R}Xa)w64v7NX*s=D`fT_cU7pT1en&8G@{&ur90?ueVkL(=+7* zrEGdbuo}ov$ka=7NU!202Qy>tdNS3y=>f1Zjz>Qx5TTJITt@_>{&$p6q!JFQ^nVB9 zo{}@7q6yM?u%-ZSW*uCj*10%0j^af``h0{sD!X z#~~WnMS)`FCE)Qdp}~NXNjbWpa*uj}CKxT8CNK~Vcp!_M4heofS7*+b zNEn$JA0(7Nna=~37b_X8^p%)f&ZmeA$&RJ^3+T5a!3goR0%`wO4c8yr#Ce9#Ha3Zq z@beN(U@A_V$QMWymn%@xfeE|Rc4EgqX9an#I?QD3Xhg}Gzj2oDH zIoqTy(+~z_|5PExVP!-Mb|$q6s3B}fMNqXBo3yHEdp@WAgP=Hf_WAq1_j#Z9d4D{L zs;i;d(!4L+!3l$0qcp~ihv6mcX~P(MP#Fo_%pRLnp)~3VUrJfq5)WB8Jt!q8Q_d41 z1lj7RBPRzDIBb38XVU|Hy@wsFE=GMR7}aW^mm{W!W|Vbcd_T z#9>ak9@TT*^w%kc&fP{oPkArxH*&kO{x!}9tC|AVwb2vEU@*PSGTHtxh`#VmQ?V{7 zns%c@+*^96xE#h0O_+dC5&fXvKu>l{A=fyVM2@5rhQ9_deG6TUd^C~7ZugY5FsJ0jL|M9wxp{Kqz;;Z((p9QE=OGu_~AVi zhysHyNX;9y0=Hr<ZcI$n31c6GSQ=KPg!$MPMn zbd77=;~lciR5M+(Dg>=>$D$NttPkvqsg)x`tZ$&NuOc#Io*rwdF3}tdj%Ke|2vXF~ z{pXdnuf{ajwr?EY@o01%vQf3njmkGu5JakiPX02h9poIRs;m$Nhmm!VWMx#6>AXgu z5WuVk$p>WNgi`e`q?;C*em@P6-_K13#Y4sLz`0PkwGuf8&}+ffrwDu&!{eaH9L$&@ zUPNcqnLHn&{D8*?>naAxv=EVR3Q|$fhKea;$h>sxKWx^4Fh45u&jk77elb*D?8~Ka^o&U0NnNEOj4CE>^Ng(HD`mMyZ z=})K25%muU^GrLi5w;FzODqSUxU;yt0&q#_4Ll)MIFeBEAeg3JIgZmIiPmk(Og7NL zo%&4)`-yeNaW$j+KI0qvl>M4fpYnyZRMLwCf)kkt>KNA22=@AlAa<=Fvfp$|-;+h3HEro&))HN?KU0L%1{X)Za3^Hix ztr4y5G3^R1k^(<>ce^RhJgsTi;AMl@`Srm|(v=R0;y=*gZi z$cpsfmy*~B7n?dc#G=4$zXc8MNcJ%StYCqbnF_i3Abt^V&OQ#rG|^UO8q9&Xe)#?N zgM{)C`?a^kzN2s9Bur1oNY8M{i^vWKWJO9FTDuXQeGJcw6RsYv^aXsCi+H0^gzLC zxnhXT_$Qt)Y&aZ)Eb?m0KJY(jk?x%h5^U%Q5H~ji2h2|4xNP{ z98JwzNdTQnVFFqvmrAkAF|QM|Uoi227IM=I;Fv;Rf&THjc_SGLQ;yprGQvFNvf~3K z6t-W_3ee=CTr5TG{?{^nGaGb<8S6mZWvhmvK4AWvI6}jztg3q5?T0*$v)SA%IMh{J z3bPh6p&^n~nCQn8mE?UxEUd>Oy=PLe;RzrWVOStu@)&zQrUI)8Bdl~F%Ex=;lY`uT zuybRgRld*N^487W%kFd(0iSveTn$j(Kq6PqR6jn;o<|1q zO>D*;L_)QSUuQa#>LiJgo0OxFX`m=zs6zmrBj{Q)%S@6aL>^Y9p%HsQB^9VaN%2|m z<_yxyI5n|4@INvJr#Dj)+g4RhDyY48Em61f29^;{9uPz!G$v{3b`IJF!dozM2hlMt zgaZv#kOh* zk7+(cX19>1Q&>1!!JXc@aa`%UxK8@lXQ%m#>;4$zOO(QfxV|O$v41m=;Q8lq6)RqQ z`Le#}vK4fAj>6MHR5wDqGbHekJpp-E`RLra5kc#5!mI+@afEG(LpQSxE`Xl`De!m! zfH6;9OQ(E7AyR=y(skcDRs{dO@mT-If$h=i+I6k+rz~`T0|g!~Z+2ZZuhe?xes}4< z>EQ1J`3YZeR&~`9ZADK{wLbxAc${Nh=zF3cnBl5O-<2+*3K*Lf>gREeC&+pb?cxeh zACJid1SyQ^AYg19jS;RUX$% ztpvVYB>Myb5?kRSe2(x$NywQA*4(Ti;6*}UMGD;7yNPh_$)g0TevE>{R2ahK5BucN z+_k5XQ-){aat3-5HiRp%D=T!d(PK`|=dYh}e(RTn%_VX(X5$R=D78Sj=brFl%4u4i zZ(rc(JTM(6bXKl{P)qX9YJiqfKW~Vs3+$>dam8D=(Ln=Nwc%F0KJ$msyWAH2k&5?v z{L+at`Atwk^V$%)nF>(%?=;;yiqdyUC<-5y#1$2{cc4S8`gYqH=zlBwzg*fIryX0* ztbNXOk=Oqo-?S%;p(S6$E53;57Uxz5LwDdMq!R+z`s#y`n+q f9eM!^rhq&`Zp?ja^a)C$Mn~QedKT>Cf1m#!w&9b! literal 0 HcmV?d00001 diff --git a/Standalone/Source/StandaloneViewer.cpp b/Standalone/Source/StandaloneViewer.cpp index 1dd6464..4617bf6 100644 --- a/Standalone/Source/StandaloneViewer.cpp +++ b/Standalone/Source/StandaloneViewer.cpp @@ -1720,6 +1720,7 @@ namespace bool Keys[256]{}; int32 Seed=0; int32 WorkerCount=8; + int32 FarHorizonLOD8InteriorStepVoxels=0; int32 ClipRadius=3; int32 MaxClipLevelOverride=-1; int32 CaptureTileLimit=0; @@ -2903,7 +2904,8 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; Streamer=std::make_unique(*World,StreamWorkers,false, (!bShapeWorld&&MeshCache.IsLoaded())?&MeshCache:nullptr, bFarShapeStagePending?World->FullResClipLevels-1:-1, - false,false,false,false,ShapeWorld.get()); + false,false,false,false,ShapeWorld.get(),false, + FarHorizonLOD8InteriorStepVoxels); if(bGpuFarTimelineEnabled) Streamer->EnableColdTimelineTrace(ColdProcessStart,&ColdTimelineFrameOrdinal); if(bGpuFarEnabled&&ShapeWorld&&ShapeWorld->hasFarWorld()) @@ -6974,7 +6976,8 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; const FVector& CameraPositionVoxels,int32 WorkerCount,int32 MaxLevel, VoxelForgeCore::DetailVariant DetailVariant,const VoxelForgeCore::JobScheduler& Scheduler, bool bHashDensity,bool bSkipUvAndColors,bool bSkipMaterialGroups,bool bSkipUV,bool bSkipColors, - std::chrono::steady_clock::time_point ColdStart) + std::chrono::steady_clock::time_point ColdStart,bool bProfileFarColumnOverlap, + int32 FarHorizonLOD8InteriorStepVoxels) { using Clock=std::chrono::steady_clock; FStandaloneOwnerWorld& World=*OwnerWorld; @@ -7042,7 +7045,8 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; const auto ShapeReady=Clock::now(); const double ShapeSeconds=std::chrono::duration(ShapeReady-ShapeBuildStart).count(); FStandaloneStreamingGenerator Streamer(World,WorkerCount,bHashDensity,nullptr,MaxLevel, - bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors,&Shape); + bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors,&Shape,bProfileFarColumnOverlap, + FarHorizonLOD8InteriorStepVoxels); const auto StreamStart=Clock::now(); const FStandaloneStreamingUpdate Update=Streamer.StartWithDesired(Desired); if(Update.DesiredTiles!=static_cast(Desired.Tiles.size())) @@ -7055,6 +7059,43 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; std::vector DensityRows,MeshRows,PayloadRows,TopologyRows; int32 Finished=0,NeighborhoodFinished=0,FullDetailFinished=0; int32 FullDetailExpected=0; + struct FColumnXY + { + std::int64_t X=0,Y=0; + bool operator==(const FColumnXY&)const=default; + }; + struct FColumnXYHash + { + std::size_t operator()(const FColumnXY& Value)const noexcept + { + const std::size_t X=std::hash{}(Value.X); + const std::size_t Y=std::hash{}(Value.Y); + return X^(Y+static_cast(0x9E3779B9u)+(X<<6)+(X>>2)); + } + }; + struct FFirstColumnUse + { + FVoxelTileKey Tile; + std::uint64_t WorkerHash=0; + }; + std::unordered_map LOD8ColumnFirstUse; + std::uint64_t LOD8ColumnDuplicateEvents=0,LOD8NeighbourDuplicateEvents=0; + std::uint64_t LOD8CrossWorkerNeighbourDuplicateEvents=0; + std::size_t LOD8ColumnCaptureTiles=0; + bool bLOD8ColumnCaptureComplete=true; + std::array WorkerSecondsByLOD{}; + std::array ColumnEvalMsByLOD{},MarchingExclusiveMsByLOD{}; + std::array GroundRefineExclusiveMsByLOD{},CapRefineExclusiveMsByLOD{}; + std::array MeshAssemblyMsByLOD{}; + std::array TileCountByLOD{}; + const auto AreLOD8Neighbours=[](const FVoxelTileKey& A,const FVoxelTileKey& B) + { + if(A.Level!=8||B.Level!=8)return false; + const std::int64_t DX=std::abs(static_cast(A.Coord.X)-B.Coord.X); + const std::int64_t DY=std::abs(static_cast(A.Coord.Y)-B.Coord.Y); + const std::int64_t DZ=std::abs(static_cast(A.Coord.Z)-B.Coord.Z); + return std::max({DX,DY,DZ})<=1&&(DX+DY+DZ)>0; + }; uint64 NativeSolidBoxes=0,NativeAirBoxes=0,NativeMixedBoxes=0,NativeUnknownBoxes=0; uint64 NativeFieldCalls=0,NativeColumnHits=0,NativeColumnMisses=0,NativeMeshingSkips=0; double NativeClassificationMilliseconds=0.0,NativeColumnComputeMilliseconds=0.0; @@ -7070,6 +7111,41 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; for(const FStandaloneCpuTile& Tile:Ready) { ++Finished; + if(Tile.Key.Level>=0&&Tile.Key.Level(TileCountByLOD.size())) + { + const std::size_t LOD=static_cast(Tile.Key.Level); + ++TileCountByLOD[LOD]; + WorkerSecondsByLOD[LOD]+=Tile.WorkerSeconds; + ColumnEvalMsByLOD[LOD]+=Tile.CoreDiagnostics.columnEvaluationExclusiveMilliseconds; + MarchingExclusiveMsByLOD[LOD]+=Tile.CoreDiagnostics.marchingCubesExclusiveMilliseconds; + GroundRefineExclusiveMsByLOD[LOD]+=Tile.CoreDiagnostics.groundRefinementExclusiveMilliseconds; + CapRefineExclusiveMsByLOD[LOD]+=Tile.CoreDiagnostics.capRefinementExclusiveMilliseconds; + MeshAssemblyMsByLOD[LOD]+=Tile.CoreDiagnostics.meshAssemblyMilliseconds; + } + if(bProfileFarColumnOverlap&&Tile.Key.Level==8) + { + ++LOD8ColumnCaptureTiles; + bLOD8ColumnCaptureComplete=bLOD8ColumnCaptureComplete + &&Tile.CoreDiagnostics.columnCoordinateCaptureComplete; + std::vector> Coordinates= + Tile.CoreDiagnostics.evaluatedColumnCoordinates; + std::sort(Coordinates.begin(),Coordinates.end()); + Coordinates.erase(std::unique(Coordinates.begin(),Coordinates.end()),Coordinates.end()); + for(const auto& Coordinate:Coordinates) + { + const FColumnXY XY{Coordinate[0],Coordinate[1]}; + const auto [First,Inserted]=LOD8ColumnFirstUse.try_emplace(XY, + FFirstColumnUse{Tile.Key,Tile.CoreDiagnostics.columnEvaluationWorkerHash}); + if(Inserted)continue; + ++LOD8ColumnDuplicateEvents; + if(AreLOD8Neighbours(First->second.Tile,Tile.Key)) + { + ++LOD8NeighbourDuplicateEvents; + if(First->second.WorkerHash!=Tile.CoreDiagnostics.columnEvaluationWorkerHash) + ++LOD8CrossWorkerNeighbourDuplicateEvents; + } + } + } WholeViewReady=std::max(WholeViewReady,Tile.CompletedAt); if(Neighborhood.contains(Tile.Key)) { @@ -7115,6 +7191,22 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; } } Streamer.Stop(); + if(bProfileFarColumnOverlap) + { + std::printf("[WorldAssemblerLOD8ColumnOverlap] tiles=%zu unique_xy_columns=%zu repeated_tile_evaluations=%llu neighbouring_duplicate_evaluations=%llu cross_worker_neighbouring_duplicates=%llu capture_complete=%d\n", + LOD8ColumnCaptureTiles,LOD8ColumnFirstUse.size(), + static_cast(LOD8ColumnDuplicateEvents), + static_cast(LOD8NeighbourDuplicateEvents), + static_cast(LOD8CrossWorkerNeighbourDuplicateEvents), + bLOD8ColumnCaptureComplete?1:0); + } + for(std::size_t LOD=0;LOD(TileCountByLOD[LOD]),WorkerSecondsByLOD[LOD], + ColumnEvalMsByLOD[LOD],MarchingExclusiveMsByLOD[LOD], + GroundRefineExclusiveMsByLOD[LOD],CapRefineExclusiveMsByLOD[LOD],MeshAssemblyMsByLOD[LOD]); + } const double NeighborhoodSeconds=std::chrono::duration(NeighborhoodReady-ColdStart).count(); const double NeighborhoodMeshSeconds=std::chrono::duration(NeighborhoodReady-ShapeReady).count(); const double FullDetailSeconds=std::chrono::duration(FullDetailReady-ColdStart).count(); @@ -7160,7 +7252,8 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; static_cast(HashTileRows(TopologyRows))); } return NeighborhoodFinished==static_cast(Neighborhood.size()) - &&FullDetailFinished==FullDetailExpected&&Finished==Update.DesiredTiles&&bHashesPassing?0:6; + &&FullDetailFinished==FullDetailExpected&&Finished==Update.DesiredTiles&&bHashesPassing + &&(!bProfileFarColumnOverlap||(bLOD8ColumnCaptureComplete&&LOD8ColumnCaptureTiles>0))?0:6; } int RunStreamingBenchmark(FStandaloneOwnerWorld& World,VoxelForgeCore::World* CoreWorld, const FVector& CameraPositionVoxels, @@ -9508,6 +9601,7 @@ int main(int Argc,char** Argv) std::string WorldAssemblyManifestOutputPath; bool bStreamBenchmark=false; bool bStreamPipeline=false; + bool bProfileFarColumnOverlap=false; bool bCoarseFirstStreamOrder=false; bool bProfileDetailBand=false; bool bPassageDetailAuditOnly=false; @@ -9530,6 +9624,9 @@ int main(int Argc,char** Argv) { const std::string_view Arg=Argv[I]; if(Arg.starts_with("--capture="))App.CapturePath=std::string(Arg.substr(10)); + else if(Arg.starts_with("--far-horizon-lod8-step=")) + App.FarHorizonLOD8InteriorStepVoxels=std::max(0,std::atoi( + std::string(Arg.substr(std::string_view("--far-horizon-lod8-step=").size())).c_str())); else if(Arg.starts_with("--capture-flight-sequence-prefix=")) App.FlightCaptureSequencePrefix=std::string( Arg.substr(std::string_view("--capture-flight-sequence-prefix=").size())); @@ -9642,6 +9739,8 @@ int main(int Argc,char** Argv) else if(Arg=="--coarse-first-stream-order")bCoarseFirstStreamOrder=true; else if(Arg=="--stream-pipeline") {bStreamPipeline=true;bStreamBenchmark=true;App.bShapeWorld=true;App.bForceGenerate=true;} + else if(Arg=="--profile-far-column-overlap") + {bProfileFarColumnOverlap=true;bStreamPipeline=true;bStreamBenchmark=true;App.bShapeWorld=true;App.bForceGenerate=true;} else if(Arg=="--edit-benchmark") {bEditBenchmark=true;bStreamBenchmark=true;App.bShapeWorld=true;} else if(Arg=="--stream-hash")bStreamHash=true; @@ -9858,7 +9957,8 @@ int main(int Argc,char** Argv) FVector(App.Position.X*4.0,App.Position.Y*4.0,App.Position.Z*4.0), App.WorkerCount,StreamBenchmarkMaxLevel,App.ShapeDetailVariant,App.CoreScheduler, bStreamHash||!BakeMeshCachePath.empty(),bSkipUvAndColors,bSkipMaterialGroups, - bSkipUV,bSkipColors,ColdProcessStart); + bSkipUV,bSkipColors,ColdProcessStart,bProfileFarColumnOverlap, + App.FarHorizonLOD8InteriorStepVoxels); if(bEditBenchmark) return RunEditTransactionBenchmark(*App.World,*App.ShapeWorld, FVector(App.Position.X*4.0,App.Position.Y*4.0,App.Position.Z*4.0),App.WorkerCount); diff --git a/Standalone/Source/StreamingWorld.h b/Standalone/Source/StreamingWorld.h index 7b9bcd9..764431e 100644 --- a/Standalone/Source/StreamingWorld.h +++ b/Standalone/Source/StreamingWorld.h @@ -423,11 +423,15 @@ public: bool bInSkipMaterialGroups=false, bool bInSkipUV=false, bool bInSkipColors=false, - VoxelForgeCore::World* InCoreWorld=nullptr) + VoxelForgeCore::World* InCoreWorld=nullptr, + bool bInCaptureFarColumnCoordinates=false, + int32 InLod8HorizonInteriorStepVoxels=0) :World(&InWorld),WorkerCount(FMath::Clamp(InWorkerCount,1,16)),bHashDensity(bInHashDensity), MeshCache(InMeshCache),MaxLevelFilter(InMaxLevelFilter), CoreWorld(InCoreWorld),bSkipUvAndColors(bInSkipUvAndColors),bSkipMaterialGroups(bInSkipMaterialGroups), - bSkipUV(bInSkipUV),bSkipColors(bInSkipColors) + bSkipUV(bInSkipUV),bSkipColors(bInSkipColors), + bCaptureFarColumnCoordinates(bInCaptureFarColumnCoordinates), + Lod8HorizonInteriorStepVoxels(std::max(0,InLod8HorizonInteriorStepVoxels)) { VoxelGenLOD::InitializeConsoleSwitches(); UVoxelMarchingCubesMesher::InitializeConsoleSwitches(); @@ -795,6 +799,8 @@ private: Options.bandMinZVoxel=Job.BandZMin;Options.bandMaxZVoxel=Job.BandZMax; Options.hashDensity=bHashDensity;Options.skipUvAndColors=bSkipUvAndColors; Options.skipMaterialGroups=bSkipMaterialGroups;Options.skipUv=bSkipUV;Options.skipColors=bSkipColors; + Options.captureColumnCoordinates=bCaptureFarColumnCoordinates&&Level==8; + Options.lod8HorizonInteriorStepVoxels=Lod8HorizonInteriorStepVoxels; std::string Error; auto Result=CoreWorld->generateTile( {{Job.Key.Coord.X,Job.Key.Coord.Y,Job.Key.Coord.Z},Job.Key.Level},Options,Error); @@ -965,6 +971,8 @@ private: bool bSkipMaterialGroups=false; bool bSkipUV=false; bool bSkipColors=false; + bool bCaptureFarColumnCoordinates=false; + int32 Lod8HorizonInteriorStepVoxels=0; std::atomic CacheHitCount{0}; mutable std::mutex Mutex; std::condition_variable Wake; diff --git a/Tools/TestVoxelForgeCoreWorldHost.ps1 b/Tools/TestVoxelForgeCoreWorldHost.ps1 index 8da0934..7b12030 100644 --- a/Tools/TestVoxelForgeCoreWorldHost.ps1 +++ b/Tools/TestVoxelForgeCoreWorldHost.ps1 @@ -179,6 +179,7 @@ $RunName += switch ($NearReserveMode) { 'Dynamic' { '_nearreserve_dynamic' } default { '_nearreserve_auto' } } +if ($HybridFixturesOnly) { $RunName += "_$($CoreMesherRecipe.ToLowerInvariant())" } if ($EnforceCollisionGate) { $RunName += '_collision_gate' } if ($StartupTrace -or $StreamingTraceOnly) { $RunName += '_trace' } if ($StreamingTraceOnly) {