From 07b3fe2852dd69c17e35aaee65b545027ca2b57c Mon Sep 17 00:00:00 2001 From: Fr0zka Date: Wed, 21 Oct 2026 04:14:14 +0200 Subject: [PATCH] wip(L27, side branch): passages cross open voids inside shared natural rock columns Unsupported passage runs > 12.5 m become a solid relief-bearing column (ceiling/island to ground, flared base) with the helix inside, for every recipe; FI anchor uses the same path. All 4 styles x both recipes: 0/0/0, 0 open-air, naturalness pass; no-long-bridge audit (L26 fails it). Deterministic, Clang = GCC. Look approved; cost +35% SW / +19% FI blocks merge (L28). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX --- .../Private/VoxelForgeCoreAdapter.cpp | 2 +- .../include/VoxelForgeCore/VoxelForgeCore.h | 27 +- Standalone/Core/src/FloatingIslandsInternal.h | 16 +- Standalone/Core/src/ShapeWorldInternal.h | 358 ++++++++++++++---- Standalone/Core/src/VoxelForgeCore.cpp | 40 +- .../WorldAssemblerM3/Linux27/REPORT.md | 179 +++++++++ .../Linux27/captures/fi-anchor-pillar.jpg | Bin 0 -> 21352 bytes .../Linux27/captures/sw-column-base.jpg | Bin 0 -> 27354 bytes .../Linux27/captures/sw-column-cutaway.jpg | Bin 0 -> 61008 bytes .../Linux27/captures/sw-column-top.jpg | Bin 0 -> 30530 bytes .../Linux27/captures/sw-column-wide.jpg | Bin 0 -> 26826 bytes Standalone/Source/OwnerWorldLoader.h | 22 +- Standalone/Source/StreamBenchmarkPipeline.h | 327 +++++++++++----- Standalone/Source/WorldAssemblyPrototype.h | 2 +- 14 files changed, 772 insertions(+), 201 deletions(-) create mode 100644 Standalone/Research/WorldAssemblerM3/Linux27/REPORT.md create mode 100644 Standalone/Research/WorldAssemblerM3/Linux27/captures/fi-anchor-pillar.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux27/captures/sw-column-base.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux27/captures/sw-column-cutaway.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux27/captures/sw-column-top.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux27/captures/sw-column-wide.jpg diff --git a/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp b/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp index ea4cbfa..036d306 100644 --- a/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp +++ b/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp @@ -485,7 +485,7 @@ bool BuildFloatingIslandStratumPlan(const UVoxelStrateManager* StrateManager, IslandPlan.topChunkZ = Slot.TopChunkZ; IslandPlan.bottomChunkZ = Slot.BottomChunkZ; auto& Recipe = IslandPlan.manifest.recipe; - Recipe.geometryVersion = 7; + Recipe.geometryVersion = 8; Recipe.layoutChunkWidthSubvoxels = LayoutChunkWidthSubvoxels; Recipe.seed = static_cast(StrateManager->GetWorldSeed()); Recipe.ownerVoxelSizeMicrons = static_cast(std::llround(OwnerVoxelSizeCm * 10000.0)); diff --git a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h index c36434b..23efbbc 100644 --- a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h +++ b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h @@ -249,8 +249,8 @@ namespace VoxelForgeCore // L'assembleur v1 encode coordonnees et scalaires en 1/256 de voxel, independamment des tuiles. inline constexpr std::int64_t WorldAssemblySubvoxelsPerVoxel = 256; inline constexpr std::uint32_t WorldAssemblySchemaVersion = 2; - // Version 13 adds connected relief-bearing rock spans across native open-air passage gaps. - inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 13; + // Version 14 adds shared passage columns for long native open-air crossings. + inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 14; enum class FieldChannel : std::uint32_t { @@ -666,7 +666,7 @@ namespace VoxelForgeCore std::vector controlPoints; }; - struct PassageBridgeRangeAuditV1 + struct PassageVoidCrossingAuditV1 { std::size_t passageIndex = 0; std::size_t firstSegment = 0; @@ -675,6 +675,17 @@ namespace VoxelForgeCore double unsupportedLengthMetres = 0.0; std::array startPointSubvoxels{}; std::array endPointSubvoxels{}; + bool needsColumn = false; + bool connectedAtStart = false; + bool connectedAtEnd = false; + std::array columnAxisSubvoxels{}; + std::int64_t columnTopZSubvoxels = 0; + std::int64_t columnBottomZSubvoxels = 0; + std::int64_t columnMinimumRadiusSubvoxels = 0; + std::int64_t columnNominalRadiusSubvoxels = 0; + std::int64_t columnMaximumRadiusSubvoxels = 0; + double maximumRouteOffsetMetres = 0.0; + double maximumBoreRadiusMetres = 0.0; }; struct ResolvedLayoutPassageConfigV1 @@ -1325,10 +1336,12 @@ namespace VoxelForgeCore ShapeBuildStats buildStats() const; float densityAt(Vec3d voxelPosition) const; - // Audit-only view of connected rock spans planned for passage support. - std::vector passageBridgeRanges() const; - bool isPassageBridgeSample(std::size_t passageIndex, std::size_t segmentIndex, - Vec3d voxelPosition) const; + // Audit-only view of density-field columns and short spans supporting passage voids. + std::vector passageVoidCrossings() const; + bool isPassageColumnSample(std::size_t passageIndex, std::size_t segmentIndex, + Vec3d voxelPosition) const; + bool isPassageShortSpanSample(std::size_t passageIndex, std::size_t segmentIndex, + Vec3d voxelPosition) const; // Audit-only selector provenance for the same point evaluator; never used by meshing. std::string densityDispatchPathAt(Vec3d voxelPosition) const; // Analytic source distance without the occupancy-preserving near-zero clamp or packed proxy. diff --git a/Standalone/Core/src/FloatingIslandsInternal.h b/Standalone/Core/src/FloatingIslandsInternal.h index f3708e9..b177e64 100644 --- a/Standalone/Core/src/FloatingIslandsInternal.h +++ b/Standalone/Core/src/FloatingIslandsInternal.h @@ -281,7 +281,7 @@ namespace floating_island_detail ? recipe.ownerVoxelSizeMicrons / ScaleGcd : 0; const std::uint32_t ExpectedDenominator = ScaleGcd > 0 ? recipe.coreVoxelSizeMicrons / ScaleGcd : 0; - if (recipe.schemaVersion != 1 || recipe.geometryVersion < 1 || recipe.geometryVersion > 7 + if (recipe.schemaVersion != 1 || recipe.geometryVersion < 1 || recipe.geometryVersion > 8 || (recipe.geometryVersion == 1 && (recipe.crustDepthBonusQ16 != 0 || recipe.rootDepthRatioQ16 != 0)) || recipe.unitsPerVoxel != Q @@ -753,7 +753,8 @@ namespace floating_island_detail const double rootBlend = std::clamp(radius * 0.12, 2.0 * Q, 5.0 * Q); for (std::int32_t root = 0; root < rootCount; ++root) sdf = SmoothMin(sdf, RootLobeDensity(recipe, island, root, x, y, z), rootBlend); - if (recipe.geometryVersion >= 5 && island.ordinal == recipe.anchorIslandOrdinal + if (recipe.geometryVersion >= 5 && recipe.geometryVersion < 8 + && island.ordinal == recipe.anchorIslandOrdinal && recipe.anchorRootBottomZSubvoxels < island.bottomZSubvoxels) { // The central buttress must meet the island crust underside. IslandBottom @@ -944,14 +945,14 @@ namespace floating_island_detail FloatingIslandAnchorV1& island) { const std::int64_t pad = IslandBlendMargin(recipe) + recipe.roughnessSubvoxels; - const bool isAnchor=recipe.geometryVersion>=5 + const bool isAnchorRoot=recipe.geometryVersion>=5&&recipe.geometryVersion<8 &&island.ordinal==recipe.anchorIslandOrdinal; const std::int64_t reach = std::max(island.radiusSubvoxels, - isAnchor?recipe.anchorRootRadiusSubvoxels:std::int64_t{0}) + isAnchorRoot?recipe.anchorRootRadiusSubvoxels:std::int64_t{0}) + recipe.contourWarpSubvoxels + pad; island.influenceMinimumSubvoxels = {island.centerSubvoxels[0] - reach, island.centerSubvoxels[1] - reach, - (isAnchor?std::min(island.bottomZSubvoxels,recipe.anchorRootBottomZSubvoxels) + (isAnchorRoot?std::min(island.bottomZSubvoxels,recipe.anchorRootBottomZSubvoxels) :island.bottomZSubvoxels)-pad}; island.influenceMaximumSubvoxels = {island.centerSubvoxels[0] + reach, island.centerSubvoxels[1] + reach, island.topZSubvoxels + pad}; @@ -1026,14 +1027,15 @@ namespace floating_island_detail { error = "floating-island anchor has invalid coordinates or an inverted bound"; return false; } } const std::int64_t pad = IslandBlendMargin(recipe) + recipe.roughnessSubvoxels; + const bool hasAnchorRoot=recipe.geometryVersion>=5&&recipe.geometryVersion<8&&isAnchor; const std::int64_t reach = std::max(island.radiusSubvoxels, - isAnchor?recipe.anchorRootRadiusSubvoxels:std::int64_t{0}) + hasAnchorRoot?recipe.anchorRootRadiusSubvoxels:std::int64_t{0}) + recipe.contourWarpSubvoxels + pad; if (island.influenceMinimumSubvoxels[0] > island.centerSubvoxels[0] - reach || island.influenceMaximumSubvoxels[0] < island.centerSubvoxels[0] + reach || island.influenceMinimumSubvoxels[1] > island.centerSubvoxels[1] - reach || island.influenceMaximumSubvoxels[1] < island.centerSubvoxels[1] + reach - || island.influenceMinimumSubvoxels[2] > (isAnchor + || island.influenceMinimumSubvoxels[2] > (hasAnchorRoot ?std::min(island.bottomZSubvoxels,recipe.anchorRootBottomZSubvoxels) :island.bottomZSubvoxels) - pad || island.influenceMaximumSubvoxels[2] < island.topZSubvoxels + pad diff --git a/Standalone/Core/src/ShapeWorldInternal.h b/Standalone/Core/src/ShapeWorldInternal.h index 75c07b7..cbd207d 100644 --- a/Standalone/Core/src/ShapeWorldInternal.h +++ b/Standalone/Core/src/ShapeWorldInternal.h @@ -41,27 +41,31 @@ public: double SealThickness=0.0; }; - const std::vector& GetPassageBridgeRanges() const - {return PassageBridgeRanges;} + const std::vector& GetPassageVoidCrossings() const + {return PassageVoidCrossings;} - bool IsResolvedPassageBridgeSample(std::size_t PassageIndex,std::size_t SegmentIndex, + bool IsResolvedPassageColumnSample(std::size_t PassageIndex,std::size_t SegmentIndex, const VoxelForgeCore::Vec3d& Position) const { - if(IsPassageBridgeSegment(PassageIndex,SegmentIndex) - &&PassageBridgeSignedDistance(PassageIndex,SegmentIndex, - static_cast(Position.x),static_cast(Position.y), - static_cast(Position.z))<=0.0)return true; - for(const auto& range:PassageBridgeRanges) + (void)SegmentIndex; + for(const auto& range:PassageVoidCrossings) { - if(range.passageIndex!=PassageIndex)continue; - for(std::size_t segment=range.firstSegment;segment<=range.lastSegment;++segment) - if(segment!=SegmentIndex&&PassageBridgeSignedDistance(PassageIndex,segment, - static_cast(Position.x),static_cast(Position.y), - static_cast(Position.z))<=0.0)return true; + if(!range.needsColumn||range.passageIndex!=PassageIndex)continue; + if(PassageColumnSignedDistance(range,static_cast(Position.x), + static_cast(Position.y),static_cast(Position.z))<=0.0)return true; } return false; } + bool IsResolvedPassageShortSpanSample(std::size_t PassageIndex,std::size_t SegmentIndex, + const VoxelForgeCore::Vec3d& Position) const + { + return IsPassageShortSpanSegment(PassageIndex,SegmentIndex) + &&PassageBridgeSignedDistance(PassageIndex,SegmentIndex, + static_cast(Position.x),static_cast(Position.y), + static_cast(Position.z))<=0.0; + } + double GetCoreVoxelSizeCm() const { return Owner == nullptr || Owner->CoreVoxelSizeMicrons == 0 @@ -197,6 +201,13 @@ public: -passage.controlPoints.front().radiusSubvoxels+playerCenterAboveFloorQ; const double lastFloorQ=passage.controlPoints.back().positionSubvoxels[2] -passage.controlPoints.back().radiusSubvoxels+playerCenterAboveFloorQ; + for(const auto& crossing:PassageVoidCrossings) + { + if(!crossing.needsColumn||crossing.passageIndex!=passageIndex + ||qz>crossing.topZSubvoxels||qz(columnDistance/Q)); + } if(qz>firstFloorQ+2.0*passage.mouthRadiusSubvoxels ||qz(std::clamp(relief,-1.0f,1.0f))*0.24*qPerMetre*wallReliefWeight; - if(IsPassageBridgeSegment(passageIndex,static_cast(bestIndex))) + if(IsPassageShortSpanSegment(passageIndex,static_cast(bestIndex))) { const double bridgeDistance=PassageBridgeSignedDistance(passageIndex, static_cast(bestIndex),X,Y,Z); @@ -317,8 +328,9 @@ public: const double radiusB=segmentB.radiusSubvoxels; // Keep the tread within the probe band and below the capsule's // lowest sampled point. - const double floorOffset=IsPassageBridgeSegment(passageIndex, + const double floorOffset=IsPassageShortSpanSegment(passageIndex, static_cast(index)) + ||IsPassageColumnSegment(passageIndex,static_cast(index)) ?PassageBridgeDeckOffsetMetres(passageIndex,static_cast(index)) :0.20; const double horizontalLengthSq=dx*dx+dy*dy; @@ -387,11 +399,14 @@ public: considerSupportSegment(index); // Keep route treads inside the native rock envelope. Carving may reopen the // floor above this point, but a support cannot create a new surface in air. - const bool bridgeFloorSupport=hasSupportFloor - &&IsPassageBridgeSegment(passageIndex, + const bool passageColumnFloorSupport=hasSupportFloor + &&IsPassageColumnSegment(passageIndex, + static_cast(bestSupportSegmentIndex)); + const bool shortSpanFloorSupport=hasSupportFloor + &&IsPassageShortSpanSegment(passageIndex, static_cast(bestSupportSegmentIndex)); if(hasSupportFloor&&bestPlayerCapsuleSignedDistance>=0.05*qPerMetre - &&(density<0.0f||bridgeFloorSupport)) + &&(density<0.0f||passageColumnFloorSupport||shortSpanFloorSupport)) composed=std::min(composed,static_cast(bestSupportDensity)); } return composed; @@ -416,9 +431,8 @@ public: const double radius=static_cast(std::max(a.radiusSubvoxels,b.radiusSubvoxels))/Q; const double floorThicknessVoxels=Owner&&Owner->CoreVoxelSizeMicrons>0 ?1000000.0/Owner->CoreVoxelSizeMicrons:4.0; - // A resolved passage bridge extends to a 3.5 m half-width and almost 5 m - // below the bore floor. Keep those cells shape-dependent for classification - // and streaming even before its bridge ranges have been compiled. + // Short support spans can extend to a 3.5 m half-width and almost 5 m + // below the bore floor. Keep those cells shape-dependent for classification. const double horizontalExtent=std::max(radius,bridgeHalfWidthVoxels); const double footDepth=std::max(floorThicknessVoxels,bridgeFootDepthVoxels); const double low[3]={ @@ -435,6 +449,19 @@ public: if(overlap)return true; } } + for(const auto& crossing:PassageVoidCrossings) + { + if(!crossing.needsColumn)continue; + const double radius=static_cast(crossing.maximumRadiusSubvoxels)/Q; + const double low[3]={crossing.centerXSubvoxels/Q-radius, + crossing.centerYSubvoxels/Q-radius,crossing.bottomZSubvoxels/Q}; + const double high[3]={crossing.centerXSubvoxels/Q+radius, + crossing.centerYSubvoxels/Q+radius,crossing.topZSubvoxels/Q}; + bool overlap=true; + for(int axis=0;axis<3;++axis) + overlap&=high[axis]>=minimum[axis]&&low[axis]<=maximum[axis]; + if(overlap)return true; + } return false; } @@ -785,8 +812,10 @@ public: if(RoundTunnelsEnabled) BuildRoundTunnelPrimitives(); BuildSpatialBins(LineBinSize); - BuildPassageBridgePlan(); Stats.LocalLineBinSeconds=std::chrono::duration(Clock::now()-Substage).count(); + const auto SharedStage=Clock::now(); + RebuildSharedFieldPrimitives(); + Stats.SharedFieldSeconds=std::chrono::duration(Clock::now()-SharedStage).count(); Stats.ShapeSeconds=std::chrono::duration(Clock::now()-Stage).count(); Stats.PrimitiveCount=Primitives.size(); if(Primitives.empty()) @@ -797,6 +826,10 @@ public: RockWords.assign(static_cast(Size[0])*Size[1]*WordsPerZ,0); SurfaceDistanceQ=std::make_unique_for_overwrite(static_cast(NodeCount)); BuildPackedRockIntervals(Stats.RockIntervalLines); + // Native density queries for non-heightfield strates use the exact interval + // evaluator. Build those native intervals before scanning passage support; + // the scan itself still completes before any resolved column is composed. + BuildPassageVoidCrossingPlan(); BuildCandidateBands(Stats.CandidateAxisLines,Stats.CandidateMergeLines, Stats.CandidateMergeSeconds); if(!BuildGpuVisualDetailNodeSets(InGpuVisualDetailTiles,InCpuVisualDetailTiles,Error)) @@ -820,9 +853,6 @@ public: return false; } Stats.PassageValidationSeconds=std::chrono::duration(Clock::now()-Substage).count(); - Substage=Clock::now(); - RebuildSharedFieldPrimitives(); - Stats.SharedFieldSeconds=std::chrono::duration(Clock::now()-Substage).count(); Built=true; if(bIncludeFarWorld)BuildFarWorld(); const uint64 WordBytes=static_cast(RockWords.size()+CandidateWords.size())*sizeof(uint64); @@ -1693,23 +1723,27 @@ public: } private: - bool IsPassageBridgeSegment(std::size_t PassageIndex,std::size_t SegmentIndex) const + bool IsPassageColumnSegment(std::size_t PassageIndex,std::size_t SegmentIndex) const { - return std::any_of(PassageBridgeRanges.begin(),PassageBridgeRanges.end(), - [PassageIndex,SegmentIndex](const auto& range) - {return range.passageIndex==PassageIndex - &&SegmentIndex>=range.firstSegment&&SegmentIndex<=range.lastSegment;}); + return std::any_of(PassageVoidCrossings.begin(),PassageVoidCrossings.end(), + [PassageIndex,SegmentIndex](const auto& crossing) + {return crossing.needsColumn&&crossing.passageIndex==PassageIndex + &&SegmentIndex>=crossing.firstSegment&&SegmentIndex<=crossing.lastSegment;}); + } + + bool IsPassageShortSpanSegment(std::size_t PassageIndex,std::size_t SegmentIndex) const + { + return std::any_of(PassageVoidCrossings.begin(),PassageVoidCrossings.end(), + [PassageIndex,SegmentIndex](const auto& crossing) + {return !crossing.needsColumn&&crossing.passageIndex==PassageIndex + &&SegmentIndex>=crossing.firstSegment&&SegmentIndex<=crossing.lastSegment;}); } double PassageBridgeDeckOffsetMetres(std::size_t PassageIndex, std::size_t SegmentIndex) const { - for(const auto& range:PassageBridgeRanges) - { - if(range.passageIndex!=PassageIndex||SegmentIndex>range.lastSegment)continue; - if(SegmentIndex>=range.firstSegment)return 0.13; - } - return 0.20; + (void)PassageIndex;(void)SegmentIndex; + return 0.13; } double PassageBridgeSignedDistance(std::size_t PassageIndex,std::size_t SegmentIndex, @@ -1760,6 +1794,109 @@ private: return outside+inside; } + double PassageColumnSignedDistance(const FStandalonePassageVoidCrossing& crossing, + float X,float Y,float Z) const + { + if(crossing.maximumRadiusSubvoxels<=0||crossing.topZSubvoxels + <=crossing.bottomZSubvoxels)return std::numeric_limits::infinity(); + constexpr double Q=static_cast(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); + const double qPerMetre=Q*1000000.0/std::max(1, + ResolvedLayout?ResolvedLayout->coreVoxelSizeMicrometres:250000u); + const double qx=static_cast(X)*Q,qy=static_cast(Y)*Q, + qz=static_cast(Z)*Q; + const double dx=qx-crossing.centerXSubvoxels; + const double dy=qy-crossing.centerYSubvoxels; + const double maximumRadius=static_cast(crossing.maximumRadiusSubvoxels); + const double horizontalSquared=dx*dx+dy*dy; + if(horizontalSquared>maximumRadius*maximumRadius) + return DeterministicSqrt(horizontalSquared)-maximumRadius; + if(qz>crossing.topZSubvoxels) + return qz-crossing.topZSubvoxels; + if(qzpassages.size() + &&qzpassages[crossing.passageIndex]; + const auto lower=std::find_if(ResolvedLayout->strates.begin(), + ResolvedLayout->strates.end(),[&](const auto& value) + {return value.strateIndex==passage.lowerStrateIndex;}); + if(lower!=ResolvedLayout->strates.end()) + { + const double capsuleRadius=0.37*qPerMetre; + const double capsuleHalfHeight=0.86*qPerMetre; + const double capsuleRadiusSquared=capsuleRadius*capsuleRadius; + const VoxelForgeCore::ResolvedLayoutAnchorV2* previous=nullptr; + for(const auto& anchor:lower->anchors) + { + if(anchor.kind!="route_point"||anchor.values.size()<3)continue; + if(previous) + { + const double ax=previous->values[0],ay=previous->values[1]; + const double dx=static_cast(anchor.values[0])-ax; + const double dy=static_cast(anchor.values[1])-ay; + const double lengthSquared=dx*dx+dy*dy; + const double t=lengthSquared>0.0?std::clamp( + ((qx-ax)*dx+(qy-ay)*dy)/lengthSquared,0.0,1.0):0.0; + const double routeX=ax+t*dx,routeY=ay+t*dy; + const double routeZ=previous->values[2]+t*( + static_cast(anchor.values[2])-previous->values[2]); + const double routeDx=qx-routeX,routeDy=qy-routeY; + if(routeDx*routeDx+routeDy*routeDy<=capsuleRadiusSquared + &&std::abs(qz-routeZ)<=capsuleHalfHeight) + return maximumRadius+qPerMetre; + } + previous=&anchor; + } + } + } + + const double height=static_cast(crossing.topZSubvoxels + -crossing.bottomZSubvoxels); + const double t=std::clamp((crossing.topZSubvoxels-qz)/height,0.0,1.0); + const auto smoothStep=[](double value) + { + value=std::clamp(value,0.0,1.0); + return value*value*(3.0-2.0*value); + }; + const float z=static_cast(Z); + const float scaledZ=z*0.12f; + const float driftX=VF_FieldSurfaceRelief(0.0f,0.0f,scaledZ, + static_cast(Owner?Owner->Seed:0)^0x434f4c58u); + const float driftY=VF_FieldSurfaceRelief(0.0f,0.0f,scaledZ, + static_cast(Owner?Owner->Seed:0)^0x434f4c59u); + const double axisX=crossing.centerXSubvoxels + +std::clamp(static_cast(driftX),-1.0,1.0)*0.35*qPerMetre; + const double axisY=crossing.centerYSubvoxels + +std::clamp(static_cast(driftY),-1.0,1.0)*0.35*qPerMetre; + const double centerDx=qx-axisX,centerDy=qy-axisY; + const double radialDistance=DeterministicSqrt(centerDx*centerDx+centerDy*centerDy); + const float broadRelief=VF_FieldSurfaceRelief(X*0.12f,Y*0.12f,Z*0.12f, + static_cast(Owner?Owner->Seed:0)^0x434f4c52u); + const float surfaceRelief=VF_FieldSurfaceRelief(X,Y,Z, + static_cast(Owner?Owner->Seed:0)^0x434f4c53u); + const double baseFlare=6.0*qPerMetre*smoothStep((t-0.82)/0.18); + const double ceilingFlare=2.0*qPerMetre*(1.0-smoothStep(t/0.075)); + const double irregularity=1.25*qPerMetre + +std::clamp(static_cast(broadRelief),-1.0,1.0)*1.10*qPerMetre + +std::clamp(static_cast(surfaceRelief),-1.0,1.0)*0.35*qPerMetre; + const double radius=static_cast(crossing.minimumRadiusSubvoxels) + +std::max(0.15*qPerMetre,irregularity)+baseFlare+ceilingFlare; + const double radialDistanceField=radialDistance-radius; + const double topDistance=qz-crossing.topZSubvoxels; + const double bottomDistance=crossing.bottomZSubvoxels-qz; + const double verticalDistance=std::max(topDistance,bottomDistance); + const double outside=DeterministicSqrt( + std::max(radialDistanceField,0.0)*std::max(radialDistanceField,0.0) + +std::max(verticalDistance,0.0)*std::max(verticalDistance,0.0)); + const double inside=std::min(std::max(radialDistanceField,verticalDistance),0.0); + return outside+inside; + } + float NativePassageDensityAt(float X,float Y,float Z) const { float density=0.0f; @@ -1775,33 +1912,25 @@ private: return ApplyResolvedStrateBoundaryBand(density,X,Y,Z); } - void BuildPassageBridgePlan() + void BuildPassageVoidCrossingPlan() { - PassageBridgeRanges.clear(); + PassageVoidCrossings.clear(); if(!Owner||!ResolvedLayout||ResolvedLayout->passages.empty() - ||!Owner->PassageBridgeCache)return; - auto& cache=*Owner->PassageBridgeCache; + ||!Owner->PassageVoidCrossingCache)return; + auto& cache=*Owner->PassageVoidCrossingCache; std::lock_guard lock(cache.mutex); - const auto cached=cache.byLayout.find(ResolvedLayout->stableIdentity); + const std::string cacheKey=ResolvedLayout->stableIdentity+"/passage-void-columns-v1"; + const auto cached=cache.byLayout.find(cacheKey); if(cached!=cache.byLayout.end()) - {PassageBridgeRanges=cached->second;return;} + {PassageVoidCrossings=cached->second;return;} constexpr double Q=static_cast(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); const double qPerMetre=Q*1000000.0/std::max(1, ResolvedLayout->coreVoxelSizeMicrometres); - const auto inSurfaceWorldStratum=[&](double zSubvoxels) - { - const double zMicrometres=zSubvoxels - *ResolvedLayout->coreVoxelSizeMicrometres/Q; - return std::any_of(ResolvedLayout->strates.begin(),ResolvedLayout->strates.end(), - [zMicrometres](const auto& stratum) - {return stratum.generatorKind=="SurfaceWorld" - &&zMicrometres>=stratum.interiorBottomMicrometres - &&zMicrometres,5> supportOffsets{{ {{0.0,0.0}},{{0.45,0.0}},{{-0.45,0.0}},{{0.0,0.45}},{{0.0,-0.45}}}}; constexpr std::array supportDepthsMetres{{0.125,0.25}}; + constexpr double shortSpanMaximumMetres=12.5; // 2.5 x the 5 m legacy support thickness. struct FPathSample { std::size_t segment=0; @@ -1818,14 +1947,10 @@ private: for(const double depth:supportDepthsMetres) { const double sampleZ=floorQ-depth*qPerMetre; - // Bridge spans address the measured SurfaceWorld void. Other native - // strata retain their own route/cave composition and are excluded here. - if(!inSurfaceWorldStratum(sampleZ)){hasSolid=true;break;} - float density=1.0f; - if(!NativeSurfaceDensityAt( + const float density=NativePassageDensityAt( static_cast((point[0]+offset[0]*qPerMetre)/Q), static_cast((point[1]+offset[1]*qPerMetre)/Q), - static_cast(sampleZ/Q),density))continue; + static_cast(sampleZ/Q)); if(density<0.0f){hasSolid=true;break;} } supported+=hasSolid; @@ -1874,9 +1999,10 @@ private: const std::size_t gapBegin=cursor; while(cursor=samples.size())continue; + const std::size_t startSample=gapBegin>0?gapBegin-1:gapBegin; + const std::size_t endSample=cursor(range.unsupportedSamples),gapLength); + FStandalonePassageVoidCrossing crossing; + crossing.passageIndex=passageIndex; + crossing.firstSegment=samples[startSample].segment; + crossing.lastSegment=samples[endSample].segment; + crossing.unsupportedSamples=gapEnd-gapBegin+1; + crossing.unsupportedLengthMetres=gapLength; + crossing.startPoint=samples[startSample].point; + crossing.endPoint=samples[endSample].point; + crossing.connectedAtStart=gapBegin>0&&samples[startSample].nativeSupported; + crossing.connectedAtEnd=cursorshortSpanMaximumMetres; + if(crossing.needsColumn&&passage.controlPoints.size()>1) + crossing.lastSegment=std::min(crossing.lastSegment+1, + passage.controlPoints.size()-2); + if(crossing.needsColumn) + { + std::int64_t sumX=0,sumY=0; + std::uint64_t centerSamples=0; + for(std::size_t index=gapBegin;index<=gapEnd;++index) + { + sumX+=static_cast(std::llround(samples[index].point[0])); + sumY+=static_cast(std::llround(samples[index].point[1])); + ++centerSamples; + } + crossing.centerXSubvoxels=centerSamples>0 + ?static_cast(std::llround(static_cast(sumX)/centerSamples)):0; + crossing.centerYSubvoxels=centerSamples>0 + ?static_cast(std::llround(static_cast(sumY)/centerSamples)):0; + for(const auto& stratum:ResolvedLayout->strates) + if(stratum.strateIndex==passage.upperStrateIndex + &&stratum.generatorKind=="FloatingIslands") + for(const auto& anchor:stratum.anchors) + if(anchor.kind=="passage_anchor"&&anchor.values.size()>=2) + { + crossing.centerXSubvoxels=anchor.values[0]; + crossing.centerYSubvoxels=anchor.values[1]; + break; + } + double maximumOffsetSubvoxels=0.0; + double maximumBoreSubvoxels=0.0; + for(std::size_t index=startSample;index<=endSample;++index) + { + const auto& point=samples[index].point; + const double dx=point[0]-crossing.centerXSubvoxels; + const double dy=point[1]-crossing.centerYSubvoxels; + maximumOffsetSubvoxels=std::max(maximumOffsetSubvoxels, + DeterministicSqrt(dx*dx+dy*dy)); + maximumBoreSubvoxels=std::max(maximumBoreSubvoxels,point[3]); + } + const double maximumOffsetMetres=maximumOffsetSubvoxels/qPerMetre; + const double maximumBoreMetres=maximumBoreSubvoxels/qPerMetre; + const double minimumRadiusMetres=maximumOffsetMetres+maximumBoreMetres+2.0; + const double nominalRadiusMetres=minimumRadiusMetres+1.25; + crossing.maximumRouteOffsetMetres=maximumOffsetMetres; + crossing.maximumBoreRadiusMetres=maximumBoreMetres; + crossing.minimumRadiusSubvoxels=static_cast(std::ceil( + minimumRadiusMetres*qPerMetre)); + crossing.nominalRadiusSubvoxels=static_cast(std::llround( + nominalRadiusMetres*qPerMetre)); + crossing.maximumRadiusSubvoxels=crossing.minimumRadiusSubvoxels + +static_cast(std::ceil(11.25*qPerMetre)); + const double startZ=crossing.startPoint[2]; + const double endZ=crossing.endPoint[2]; + crossing.topZSubvoxels=static_cast(std::llround( + std::max(startZ,endZ)+5.0*qPerMetre)); + crossing.bottomZSubvoxels=static_cast(std::llround( + std::min(startZ,endZ)-6.0*qPerMetre)); + } + PassageVoidCrossings.push_back(crossing); + if(crossing.needsColumn) + { + const double coreRadius=crossing.nominalRadiusSubvoxels/qPerMetre; + const double minimumWall=coreRadius-crossing.maximumRouteOffsetMetres + -crossing.maximumBoreRadiusMetres; + std::printf("[PassageVoidColumnPlan] id=%s segments=%zu..%zu unsupported_samples=%llu span_m=%.3f threshold_m=%.3f connected=%d/%d axis_m=(%.3f,%.3f) z_m=%.3f..%.3f route_radius_m=%.3f bore_radius_m=%.3f min_radius_m=%.3f nominal_radius_m=%.3f max_radius_m=%.3f nominal_wall_m=%.3f flare_m=6.000 status=%s\n", + passage.stableId.c_str(),crossing.firstSegment,crossing.lastSegment, + static_cast(crossing.unsupportedSamples),gapLength, + shortSpanMaximumMetres,static_cast(crossing.connectedAtStart), + static_cast(crossing.connectedAtEnd), + crossing.centerXSubvoxels/qPerMetre,crossing.centerYSubvoxels/qPerMetre, + crossing.topZSubvoxels/qPerMetre,crossing.bottomZSubvoxels/qPerMetre, + crossing.maximumRouteOffsetMetres,crossing.maximumBoreRadiusMetres, + crossing.minimumRadiusSubvoxels/qPerMetre,coreRadius, + crossing.maximumRadiusSubvoxels/qPerMetre,minimumWall, + crossing.connectedAtStart&&crossing.connectedAtEnd?"PLANNED":"UNATTACHED"); + } + else + std::printf("[PassageShortGroundedSpanPlan] id=%s segments=%zu..%zu span_m=%.3f threshold_m=%.3f connected=%d/%d width_m=6.000 thickness_m=3.000..5.000 status=%s\n", + passage.stableId.c_str(),crossing.firstSegment,crossing.lastSegment, + gapLength,shortSpanMaximumMetres,static_cast(crossing.connectedAtStart), + static_cast(crossing.connectedAtEnd), + crossing.connectedAtStart&&crossing.connectedAtEnd?"PLANNED":"UNATTACHED"); } - if(std::none_of(PassageBridgeRanges.begin(),PassageBridgeRanges.end(), + if(std::none_of(PassageVoidCrossings.begin(),PassageVoidCrossings.end(), [passageIndex](const auto& range){return range.passageIndex==passageIndex;})) - std::printf("[PassageRockBridgePlan] id=%s spans=0 status=NOT_REQUIRED\n", + std::printf("[PassageVoidColumnPlan] id=%s crossings=0 status=NOT_REQUIRED\n", passage.stableId.c_str()); } - cache.byLayout.emplace(ResolvedLayout->stableIdentity,PassageBridgeRanges); + cache.byLayout.emplace(cacheKey,PassageVoidCrossings); } static uint64 PackLatticeKey(int32 X,int32 Y,int32 Z) @@ -6151,7 +6357,7 @@ private: std::vector FarConnectorSegments; std::unordered_map,FBinHash> FarConnectorSpatialBins; std::vector EditBrushes; - std::vector PassageBridgeRanges; + std::vector PassageVoidCrossings; std::array AxisBins; std::unordered_map SpatialBins; std::unordered_map CriticalAirBins; diff --git a/Standalone/Core/src/VoxelForgeCore.cpp b/Standalone/Core/src/VoxelForgeCore.cpp index 156c0d3..391c55c 100644 --- a/Standalone/Core/src/VoxelForgeCore.cpp +++ b/Standalone/Core/src/VoxelForgeCore.cpp @@ -1542,27 +1542,43 @@ namespace VoxelForgeCore float World::densityAt(Vec3d position) const { return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape?impl_->shape->DensityAt(static_cast(position.x),static_cast(position.y),static_cast(position.z)):0.0f; } - std::vector World::passageBridgeRanges() const + std::vector World::passageVoidCrossings() const { - std::vector result; + std::vector result; if(!impl_||!impl_->shapeReady.load(std::memory_order_acquire)||!impl_->shape)return result; - const auto& ranges=impl_->shape->GetPassageBridgeRanges(); - result.reserve(ranges.size()); - for(const auto& range:ranges) + const auto& crossings=impl_->shape->GetPassageVoidCrossings(); + result.reserve(crossings.size()); + for(const auto& crossing:crossings) { - PassageBridgeRangeAuditV1 value; - value.passageIndex=range.passageIndex;value.firstSegment=range.firstSegment; - value.lastSegment=range.lastSegment;value.unsupportedSamples=range.unsupportedSamples; - value.unsupportedLengthMetres=range.unsupportedLengthMetres; - value.startPointSubvoxels=range.startPoint;value.endPointSubvoxels=range.endPoint; + PassageVoidCrossingAuditV1 value; + value.passageIndex=crossing.passageIndex;value.firstSegment=crossing.firstSegment; + value.lastSegment=crossing.lastSegment;value.unsupportedSamples=crossing.unsupportedSamples; + value.unsupportedLengthMetres=crossing.unsupportedLengthMetres; + value.startPointSubvoxels=crossing.startPoint;value.endPointSubvoxels=crossing.endPoint; + value.needsColumn=crossing.needsColumn; + value.connectedAtStart=crossing.connectedAtStart; + value.connectedAtEnd=crossing.connectedAtEnd; + value.columnAxisSubvoxels={crossing.centerXSubvoxels,crossing.centerYSubvoxels}; + value.columnTopZSubvoxels=crossing.topZSubvoxels; + value.columnBottomZSubvoxels=crossing.bottomZSubvoxels; + value.columnMinimumRadiusSubvoxels=crossing.minimumRadiusSubvoxels; + value.columnNominalRadiusSubvoxels=crossing.nominalRadiusSubvoxels; + value.columnMaximumRadiusSubvoxels=crossing.maximumRadiusSubvoxels; + value.maximumRouteOffsetMetres=crossing.maximumRouteOffsetMetres; + value.maximumBoreRadiusMetres=crossing.maximumBoreRadiusMetres; result.push_back(value); } return result; } - bool World::isPassageBridgeSample(std::size_t passageIndex,std::size_t segmentIndex, + bool World::isPassageColumnSample(std::size_t passageIndex,std::size_t segmentIndex, Vec3d position) const {return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape - &&impl_->shape->IsResolvedPassageBridgeSample(passageIndex,segmentIndex, + &&impl_->shape->IsResolvedPassageColumnSample(passageIndex,segmentIndex, + {position.x,position.y,position.z});} + bool World::isPassageShortSpanSample(std::size_t passageIndex,std::size_t segmentIndex, + Vec3d position) const + {return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape + &&impl_->shape->IsResolvedPassageShortSpanSample(passageIndex,segmentIndex, {position.x,position.y,position.z});} std::string World::densityDispatchPathAt(Vec3d position) const { return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape diff --git a/Standalone/Research/WorldAssemblerM3/Linux27/REPORT.md b/Standalone/Research/WorldAssemblerM3/Linux27/REPORT.md new file mode 100644 index 0000000..5de703e --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux27/REPORT.md @@ -0,0 +1,179 @@ +# VoxelForge L27 — passage voids inside rock columns + +## Owner summary + +The SurfaceWorld passage now drops from the ceiling (about −138 to −143 m) to terrain near +−505 m inside one continuous, relief-bearing rock column. Its helix is cut inside the column; the +~2 km route no longer crosses the open sky as a bridge. FloatingIslands uses the same field and +planner path: the passage descends inside a column from its island to the bedrock floor. Styles 1 +and 3 have a second, smaller cap crossing, so they get a second column. The FI style-0 anchor stays +at the L25 axis and remains connected. Its shared column is slimmer (11.25 m nominal radius vs +L25's 13.25 m) with a larger measured wall margin (3.25 m vs 1.938 m), visible in the new +exterior capture. + +All four styles in both recipes pass on Clang and GCC: 0 blocked, 0 unsupported, 0 grade failures, +0 open-air violations, and naturalness passes. The new no-long-bridge audit detects the L26 +SurfaceWorld bridge as its negative control. + +## Column rule and size + +The shared planner samples native support every 0.5 m. A contiguous unsupported route run longer +than **12.5 m** becomes one column; this is 2.5 times the former 5 m support-thickness limit. A +shorter span is allowed only while grounded. The column is a solid signed-distance union in the +shared composition field, so terrain queries, collision and meshes all see the same rock. The +recipe still chooses passage style; the helix applies inside a column and that style continues +outside it. + +The minimum planned outer radius is maximum route offset + bore radius + 2 m; the nominal profile +adds 1.25 m. The base flares by up to 6 m and the ceiling end by up to 2 m; bounds extend 6 m +below and 5 m above the unsupported run to overlap the native rock. Measured wall margin is 1.75 m +minimum in SW and 3.0 m minimum in FI, above the 1.5 m requirement. SW columns span about +363–368 m vertically and have nominal radii 19.744–27.027 m. FI main columns span about 53–57 m +with nominal radii 9.251–12.746 m; styles 1 and 3 also have ~14 m cap columns. + +| Recipe / style | Unsupported route run(s) | Column nominal radius; audited profile radius | Minimum wall | +|---|---:|---:|---:| +| SW 0 Straight | 2,004.815 m; z −142.687 to −505.109 m | 27.027 m; 25.527–27.777 m | 1.75 m | +| SW 1 Worm | 1,946.355 m; z −138.668 to −505.109 m | 24.187 m; 22.687–24.937 m | 1.75 m | +| SW 2 Spiral | 1,957.892 m; z −137.967 to −505.138 m | 19.744 m; 18.244–20.744 m | 1.75 m | +| SW 3 Cascading | 1,981.103 m; z −137.484 to −505.112 m | 22.791 m; 21.291–23.541 m | 1.75 m | +| FI 0 Straight | 249.982 m; z −65.846 to −122.390 m | 11.250 m; 11.250–12.500 m | 3.25 m | +| FI 1 Worm | 17.042 m and 249.965 m; z −61.574 to −122.416 m | 12.719 m and 12.746 m; 12.968–16.468 m and 12.746–13.996 m | 3.25 m | +| FI 2 Spiral | 244.479 m; z −68.891 to −122.382 m | 9.251 m; 9.000–10.500 m | 3.00 m | +| FI 3 Cascading | 17.955 m and 231.814 m; z −65.805 to −122.436 m | 11.250 m each; 11.250–14.000 m and 11.250–12.500 m | 3.25 m | + +The helix path measurements are length / turns / mean path radius / vertical pitch. SW styles 0–3 +measure **2,015.1 m / 16.88 / 18.96 m / 21.00 m**, **1,956.1 / 20.69 / 14.99 / 17.32 m**, +**1,963.0 / 61.62 / 5.02 / 5.81 m**, and **1,987.2 / 21.63 / 14.50 / 16.59 m**. FI styles 0–3 +measure **254.0 m / 8.00 / 5.00 m / 5.78 m**, **254.0 / 7.99 / 4.97 / 5.72 m**, +**246.9 / 12.95 / 3.00 / 3.31 m**, and **235.5 / 7.94 / 4.50 / 5.37 m**. FI styles 1 and 3 +also have short cap descents: respectively 22.1 m / 0.59 turns / 5.70 m radius / 6.71 m pitch, +and 22.2 m / 0.75 turns / 4.54 m radius / 5.37 m pitch. Main helix grades are about 10.0–10.5°; +the walkability audit reports zero grade failures. + +## Audits + +Clang and GCC each ran all eight recipe/style combinations. Every row has 0 blocked capsule +samples, 0 unsupported samples, 0 grade failures, 0 open-air violations, connected column ends +(2/2), 128/128 route-support samples for each main crossing, and passing walkability. The naturalness wall-relief audit measured 0.509–0.806 m against +the 0.200 m minimum; flat runs were 0 m against the 8 m limit. The strict V3 layout validator +accepted with zero issues and rejected its malformed controls 9/9. + +The no-long-bridge threshold is 12.5 m. SW has 0 m uncolumned span in all styles. FI's longest +short grounded span is 4.464 m (style 0); every other span is shorter. The L26 SW bridge control +(1,946–2,005 m unsupported) was detected in all four SW styles. The L24 ungated-support and L23 +smooth-wall naturalness controls were detected in all eight recipe/style rows. The 16 final matrix logs are +`Saved/Linux/linux27-final-audit-{surface-world,floating-islands}-style{0,1,2,3}-{clang,gcc}.log`. + +## Captures + +All five JPEGs are 16:9 and below 300 KB in `captures/`. + +| Capture | View | Size | SHA-256 | +|---|---|---:|---| +| [sw-column-wide.jpg](captures/sw-column-wide.jpg) | Wide side section: ceiling, full column and local ground; 1280×720 | 27 KB | `63e621794a48747c5892b74d3313da1df93e95bc1fb5e23c511211f992023acf` | +| [sw-column-base.jpg](captures/sw-column-base.jpg) | Close base flare into terrain; 1280×720 | 27 KB | `c4dbf7bcbd4962cf0e2c4f567cceec798deac8686a85cd02716ebd3c21db43bf` | +| [sw-column-top.jpg](captures/sw-column-top.jpg) | Close ceiling blend and upper helix; 1280×720 | 30 KB | `fe83fbb0bf90ea51696f9f63129738eb55812c01bbbec65e7430b8a7e6b512ed` | +| [sw-column-cutaway.jpg](captures/sw-column-cutaway.jpg) | Full-height cutaway through the helix; 1920×1080 | 60 KB | `8233e80035aac69dec7fd6d2e77a46e5798f627aefe216671d884bf84b339c40` | +| [fi-anchor-pillar.jpg](captures/fi-anchor-pillar.jpg) | L27 FI anchor pillar from outside; 1280×720 | 21 KB | `5bb8ad6a0729818577764596d3fa9ccff9621b31217ee9d8e34401098e6b4ef6` | + +## Cost, determinism and compatibility + +The whole-view timing comparison alternated two L24 and two L27 Clang W12 runs per recipe. No +prebuilt L24 executable was present, so I rebuilt it from local commit `cb9236c` with Clang 19 and +FP contraction off under `Saved/Linux/l24-source/bin/linux/`. + +| Recipe | L24 whole-view seconds, two runs (mean) | L27 whole-view seconds, two runs (mean) | Change | Tiles, both versions | +|---|---:|---:|---:|---:| +| SurfaceWorld | 1.644 / 1.663 (1.654) | 2.207 / 2.252 (2.230) | +0.576 s (+34.8%) | 2,370 total; 960 full detail | +| Floating Islands | 15.907 / 16.848 (16.378) | 19.403 / 19.649 (19.526) | +3.149 s (+19.2%) | 2,370 total; 960 full detail | + +No tiles were added. The L24 and L27 per-run logs are `Saved/Linux/linux27-cost-interleaved-*.log`. + +The L25 whole-view command produced identical hashes across Clang/GCC and W1/W8/W12. Hash order +is density / mesh / payload / topology: + +| Recipe | L27 whole-view hash | +|---|---| +| SurfaceWorld | `117547e5e18c6847 / db5fa276315ff2d4 / b4e9d01d70413fea / 03caf881fa047f1c` | +| Floating Islands | `70ab5bc87dd174d2 / 6f357f9085a496f0 / 08ce7d4e036da65a / eb5eef04a5cb85d9` | + +Full-detail hashes also match across the matrix: SW `ace56c8f205238ad / a9c603a1248de6b8 / +ea55f1f4fcc6a894 / 5ea7bc78a1b678ae`; FI `ed550e0c8b182695 / 0e410a8e80a3e7f7 / +0751926f0c67d8a4 / f01208bc2f7841ec`. Exact-query hashes (density / unclamped / detail / collision) +are SW `c949c4df2ce6f9d9 / c949c4df2ce6f9d9 / 114c501e2d830d94 / e6011d3a248d4849` and FI +`7b7c50ac24ccadcc / 7b7c50ac24ccadcc / efcba20c0c70c415 / e604823a249029bf`. + +The 24 m crossing processed 314 moving tiles and matched across both compilers: + +| Recipe | Moving-tile hash: density / mesh / payload / topology | +|---|---| +| SurfaceWorld | `fe83368d746bf44d / d4379e0f86fa5780 / 0d1d6659a251dcaf / 73940f21b0fdaf14` | +| Floating Islands | `e3d0ea833bca4215 / f4b27f4bd1adbb07 / 8aedcdc86e5f21a2 / 323f880d952306e9` | + +L13 parity passed for both compilers and recipes: 8 page pairs, 14 higher-LOD pairs, one edit pair, +782,026 exact-query samples, zero mismatches. At a 10 km off-route view, L9 proofs checked 6,896 +blocks / 9,178,576 samples with zero violations on both compilers. SW had 1,296 fallbacks and no +unknown blocks; FI had 18,704 fallbacks, 17,408 unknown blocks and zero violations. + +At a 100 km SW view, L26 and L27 were bit-identical on Clang and GCC: full-detail hash +`3148a6af10901e4d / 7025b8fc8d4636cd / ca0a8081022340bf / 1c49e72a6636ae99`, whole-view hash +`a212129dc5f2bfcf / e9b3980b69c789bf / 785a394605885d1f / 8f7e3a55bf429175`, exact-query hash +`482500abbe835b11 / c4948c7179963784 / efcba20c0c70c415 / 137e8f6c0b03a624`; LOD0–8 also match. +At 10 km, the whole-view hash changes because the new column enters LOD8; full detail, exact queries +and LOD0–7 remain identical. + +Headless binary SHA-256: Clang `81ae5a03765eb3937f53c10e8f8b82cf1b38d3e3b6240511a57e5d66d99a9755`, +GCC `30c8927eee3c8c2ec0e7ff72228872435d19527719bedbb2399e6dcaff4819a4`. Rebuilt L24 Clang +binary: `29bb68ed425b4770b1b4ddcc68996c5d680665014e7ab7ffb3c307523015cf0b`. + +## Changes and invariants + +The deterministic crossing planner and field composition are shared in +`Standalone/Core/src/ShapeWorldInternal.h`. `VoxelForgeCore.h/.cpp` expose the void-crossing audit; +`StreamBenchmarkPipeline.h` adds column, open-air and no-long-bridge audits with the L26 negative +control. `FloatingIslandsInternal.h` retires the old special-case root for geometry 8, while +`OwnerWorldLoader.h` and `WorldAssemblyPrototype.h` use the shared crossing plan and version. +Geometry/evaluator versions are now 8/14; resolved-layout schema V3 is unchanged. The only Unreal +edit is the matching geometry-version bump in `Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp`; +Unreal was not available to compile here. +L4 lazy caches, L7 shared cache, L9/L12 proofs, L11 readiness handling, L13 route parity, L18/L19 +ceilings and L21/L22 layout behavior remain unchanged. No async path was added. + +## Failed and retried runs + +- Two temporary Clang backtrace builds failed on `signal` namespace qualification; corrected before + the instrumentation was removed. The first SW style-0 audit segfaulted because the planner read + shared-field intervals before they were built; build order was corrected. An early SW style-1 + iteration had 3,816 open-air violations and 869 blocked samples at the lower mouth; the carve + guard fixed it. +- Early FI audit revisions mis-tagged segments and reported false open-air spans; corrected to use + the actual route ranges and physical field. FI style 2 briefly had two unsupported samples; the + seam extension fixed them. A 3 m cap left 13 FI style-0 open-air samples; the 5 m cap cleared + them. A relaxed tread guard introduced one SW style-1 blocked sample and was reverted. +- A layout-only invocation without the required streaming flags exited 2. During capture setup, + `--hash` and `--data=` were rejected; the correct switches are `--stream-hash` and a positional + data path. Those were corrected. A helix-metrics helper first failed while formatting a list and + was rerun successfully. +- The first close SW export at max clip level 1 hit the 160-million-node interval safety bound. + Two finer endpoint exports then grew past 1.2 GB without a final pipeline summary and were + stopped; the already-complete full-column cutaway mesh supplied the final base/top views. The + first wide camera hid the base; a cut-plane side view fixed it. The first FI close camera cropped + the endpoints and was moved back. +- The first FI max-clip-level-3 export grew past 1.6 GB without a summary and was stopped. The + max-clip-level-8 retry completed in 21.7 s with a 104 MB mesh. The final capture is from that + L27 mesh. All rejected, stopped and successful export logs are in `Saved/Linux/`. + +No run exceeded 30 minutes or remained stuck for 15 minutes. Only the requested small JPEGs and +this report were written under `Standalone/Research/WorldAssemblerM3/Linux27/`; exports and logs +remain in `Saved/Linux/`. The pre-existing untracked `Linux18/exports/` directory was left alone. +No commit, push or stash was made. + +## Orchestrator review (Claude) — side branch + +- Look approved by me: `sw-column-wide.jpg` and `sw-column-cutaway.jpg` show one slender ceiling-to-ground + column with the helix inside; `sw-column-base.jpg` shows the base meeting the terrain with a natural crack + exit; `fi-anchor-pillar.jpg` shows a textured pillar with a flared base under the island. +- **Blocking merge: cost.** Whole view +34.8% SW (1.654 → 2.230 s) and +19.2% FI with no new tiles. A local + feature must cost locally. L28: bound column/helix evaluation spatially (feature lists / AABB culling), then + merge `wip/l25-helix` into `assembler/world`. diff --git a/Standalone/Research/WorldAssemblerM3/Linux27/captures/fi-anchor-pillar.jpg b/Standalone/Research/WorldAssemblerM3/Linux27/captures/fi-anchor-pillar.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0b315af4f1ab6820f890022869cf9b6486206b3f GIT binary patch literal 21352 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

z z0zMUF3QQb?p-7=uxHB`4W-gO$K_V-<&Vu7rBaNOAMKISNxnNu~(AqV;>P&2sT_w*9 zLYk&`60Fuj?4MKL2A->n2oXCP{2!&q{5QO#e~~W1q#8p9`ZQJop&~Fg3?la>;evIZ z_JmrDt7u`O+laeTnELFm#CSGaF`=I@FN;gS$4HJi`nNhRK&g$anwdFuT~o#n)!NUF zXG;9sjT`Rd+kr?eI>^2**UQ+p_?}jO|CN1UKSca&#?D3Z;hXItHutY+489#Ju?sdy zfdy}xH|gpYie+sU0Prp^mXxV>fT z6ET}~M{&ioxIpNRAHP^e?6_3nUB9IX5d5xYeK+G!;Q63!jqvWLtKk^Mr8>r9>sj!? zjeA^Y2bD`m?^wgP82#=0GhAoH;Im;p@55qfoASj>n+Iz{(u^g>yH<>J1zDkOl!1fq zOsq3$rih>#A!K*Lm&)h>U88>ynfSl^n2$1Y!|re(17V7<$iF?ni|77SXp4>bct35G zG_jLQlF&9R*4hzMUp4VkQ84;NOX4{CcubNz8!2FNDnxb@R4H9>SM*N)*0eJqnh*;p`F!Bq#H8ax2@Uc6x!XVOEP_kq`E6;$`Vpe(}b=5^uS$WFy5S`s7YCOgD`&_>T$kV$V)q z0n4!aAsp0uAJ<~_`F8Tq5OF;uEp#4x&$zuu$x_T?V=cw9#o37xi)@>HB^Zt%${1Xg{KAL2MjpPYK;xd7WJBXKSP*@~`o( z|1Uq~Im?{A;;&#B00bD5`t!TM$DMeaw7bT=%3YZfZ`HkBCFK$KH72F9)bX<}nf~ug zUCTOO9NeIi!ag~54$Md3_U6j6Kq4&GW<$ViXh$7vb3Pk(}WA}`Q5;3pi>6|Ti zs9*#ebO>q|Q-|;$QB~{xp`zPR^HquP(P0Haf*oC^dXqi*+C?D8gD+R#<(^pZtUQs@ zH4E#=R+JP~?ANBt1<#W6NjIrpyRTMr*$&uP2^yc5sodj;q$uO(w>+2{OW|qeO%hhs zFAEkW!MujQ+CO{-T0~EXmOd#FK)m*Wel6a~4-Ri$pr=g)-zh(X`J{WX{7?AzJHRZ>Cl^v4R-5cE~de zcjEkU?D_i@$qr8bkm#Lg;#wm1dqZ%-0?EZ;RwH}r=3`~})%Ze(M4JkoyuoO1lcl6Z zV$5W#1z6ux;ipSt?+6~Xx%lawBT!+SaOR?4B3GTrVa=J{G@tuJ$_FwI+=BljK-h?@lTgbUG>(f83P_} zePg?IEs%A@73C7-)zWjW@M#FWC)l)Do4v?rJ|qAZV|?7IOSiv5nXFMy!zKop<(_dp7(KnHMQ)4Lx&e&=;L^IuyC{(GKY1^B%0PpXihJ?5-#sCnbh zH1l6L8GFS?GAObQH9-@vVu2ZR@TvNQx+@pPd}XdrWWa@!VvJiUbrleC(u$wwn>v`G zllqzUVsh#lj#BSHt~hi<3)=9gS-YoJl}|638TNweDAe9ikLoKrlZ0aorU3GCSTrZnM#_KIk74dKxc@d21Zq09I!c0-;UoRcQ?i3+#={&t82_N{zb)#@q;v zq}vu?Vu`r8)h+y8ucVKF_O}#ft*_q+yu0G-`B*_ zlh?qEcK;1(s@?ka2V=+~FegNB{FJX|Y9Pxsxvi~XyHjOW2?7~-zY}7o73(qC&Tq?% zk*Hx8E~pGR1|5HiKcV`Q%Mh{1nnJAi6`fqPj`~^ioQ~_TYWNb7YEja;vA*$hY)No? zh(Er`nY?&&C-P43jvG0@$u&4suVyFY?)ME;q;^};>vW-tP|NM`%LU+Je12|?<*lh% z$fwr$?CQ-%Vqx+fgh+S8tT)s0`09CU>ZfrhikNjXrTu2ps5;!4VBqD)SCxTzaQL#Y z@W1r?|4$ws_NMZd705e5Pb!AnU{j#`ERMe~M{az1N>|m8wqxqoI*rN-FM8f&=t7!w z_Yzx$MVhTci=PDv?Y&Owg_`Cic|U4@jFBzTf-Up?9pznLIKxj1Vo55p`MLz63}ECn z z^o9B`T}tMaTVXN^&c5TZGAu`pCEQM4soKKw{MM#$a+`U;=4HvhCAM~;vfvQe81uzj zPaTYIw$WuQM1MQ{q792B%!4Fuz=2c4|1%#EzwO_tAw9<@cV^=#Rxl zN@BqCcZ$8p+*L>{V#ONWudByQs_Ydm_A zIIom1wNjPCj!bu?(6V&X&$b365i8mSn&bSi$?dLAawaibT@@=a^Y>DVGy@!t(9+Bc z43SVhswhc$BIEchrA2{{DOH4MDuzL!RD6-tmTCg5^{TnymRnR8f{n`83xNjTqD#o~ ztB_sH_Ec26VXpKHdk^V{y-`;!d}0C@o!V_F32&Nj!Jm+vdeY2TewCwsN|nukGp1lm zX5Css@i}paq1cOEIJ>|MKoM26!iQY@!PJ(Ca{0)2QAeA0Ri^7epCI|g#D<$C+oz#v zR!^n&$+q-{h(_i2e%`+^O|HskA#zUYqN98v5Q-*IKlA*YJy_iTQVieL)m4~=;a zwQHnoEWOv|zqim@zv85AoS(C&{r$s-=g-rg>B`hLl3ERnCap4_XSoq({6r^V$SIC^ zD$G@~azC+W+-5MeIf?s@qrb9(05*0vxBQh-?os1(a2IXEr|gW^tqAYKX~M_c@FIur zGa`~lDX4_VS>H3be!r^l;q1x@ame3QBS6XK%a{N9B>Xeq$xY8K1nJ<)-ws&v^A{Pw z%9$e`V3kOd%!7UQHC+mgX=FZDm1YF_gg@D{T`Qy0w!5rRv9z243%}Ib_M{`CeTJq& z$uZKR#*~d^oc99@gJ%%#D{R<+UCfMBf+UX;osbKM(IqNC){zRF5qRPe42&O#Tir{1 zKDx`hCKKPXxykxyPIk~lZcF<|O^QbiviGF0gk!9*4@B-wP~A#W%ee4 zSmV?DFoW5(8WsDiw?9vePo*iUN+*nXXUW)4N4{tG`}c2*5;1+cyW#-{FuMs;%ytOP z2a@}Aze~w&xz4cUZ$REy*v;7z3!hl?HS4Tzfa*8E^z&Xk{eIRsKa3$Nchfiq&IhAc zgJrkEkqzKjV>?fI0^P-8meas-bp}uI>;)d)N{x6%Aoh%L12Ybzy7IjdI9UhhxIjtK z+_lT*Y}H-Pvu8+247bWd7tRk*d|B^{vrx|75MGmr`6)ojSo_)M>A|Q6R7Vj;E!4kw zRxdGUZOCoIxywmf`kZ9ZlT=Yh%v{+vv1C%vCT2i1e?#4N_sW*}h!VfBOP)>D`s5Vp z$0L%`yzS+W>U5zU=lz>)i;1j&WX9ZpW${cf6zU}{EG*8=$Sv*f1~~z7?Fc)A2Ks(E z|L^2_`k;~dUuj%xPcrED#aRK6__t}=3fFuCs(j32l$Ndy(+bn9s^nC(_IYVW2D##! zjPya;+BWq_2WgAQbpPTouuA2p10)LhjZwl!zWp7+-dYOz8FMazlVjDt0aI{i%-4lF z$kt~_Nuz2Oe6_Jr{t;L<(~`;g{-M>^%{||!dPXZbzi(I3anXOfeMm;;g|wvIa&FOj zXu>yOTmMa>d7LRd^n@GH-Qq|Z$l~lYOcq!kXuXvAg$|zMtQlreAhS^gba1-0uBx)l zFs;e`a4m{`Lz`J~ay;Sb1_+j~#EJN#|D*6CjYoXluvaVoT$UrP+D*d$Rz z7apfP9dhSanw_3jBu*n4I$*}r=SRnm1)r?=a?Q_77alA7LQObeq}HfN00Xs(q->HC zvY)JCqRq1?&#O4b(R5|HtEyUf3I(4tJJ}z0utnP9MPTYx;{);|W2{u`ZaYg*5nb7* zeF1miJ0)M8AQCCs^Xf9)9mq@<)lLz0tJ9?VTqX~m6J zmv##qwL9P~7dD_b?2fPLZKHPdZ!50`X8rWAIYVkxX_Vrbm=WX$vqGp8fIM%;A1z!h zR@(Gk4ez2j&qEb+1uT|3q*v}4y&VnrG`wpHKXI3<@(nEW1XS!czz8#V zM7VKaq}|?(-tuz{IK4c*qcYuGx$CS(3j-A4BRurf^o)dR&OKuZXUl8b%g!bFLoqw6 zB4FoAg_uVE{@WHKd!^HUtnqwx0VhJc3ba%h)9O!fWCx9vtiNsWs=*GWgzyfIG54jNA2dw8}O-qp+Ya45I4a;*d@!{8v8M^ZXRNv~9a z=Qs(|8KSPL7?&2nX^cC=YFu-pE~W3R5#v!ZVig|iOyu{MOah114QKAR2Va$TkH}Aj z^{hRqaDyc?Wqy?j9`x{68hVo_5bqM9dH?>PJmX_}`Z-zrmHA3Wyi@0Jx#~(%de?e} zV=pJP>FMcd#lT?A^!H8rNX8{29LxCxaI<#d3*-?jBmdFz#tO@o zlS7Ru5)99HKH+S)v2~&QRO}(w!PFouK6(>MY0=}K?q|BK71b}M8(pr2HVoL;`L2aJ z=4M{ZN1ou zL%iA zz4SyPRzVmWsGpcRw7I4IGd0LyG?H=1JvbPSYz-lOuJB_y>Kc=ES=A=8m&*7Xm-i}K z1CtluHEGQ__q$@`Q<7p}gyLE`)wJEhX8r;1MZ-^5&lzU~=AAG=`mgJWi`y^koK12D z0jXXGrB2SA4LDQB;dnPLSxImJI+59+Zm;g7?I%&eHEg|<@IfWA_^ZUI z1nj$E@3EZuN{mg$%s!u&F34Qu3UkSGqJ-2E^|!Kxl$E}Y_bV+C&){b7QpqL};hfyb zF1S_%f%2jxTkg3vuj6T&-JH=qG9vV5co7@lY#|T=#&8$!8hWOwy02nZu%L)1i44UGQw-4zbZnIj*dw0Z~f1L8U zvVyN_=N_NESVzbbECjJ58qa69++UaSqEQ9hWQ()Og{%w5^X~woOL~dZgC#DTw$7K@ zCtG^6I-!<&TG(~D`zhG<3DovLZH?TU`&r(1-W0pv05vm^K!*=(1>xarKECNw=)-J@ z1m-Z1Ta-7Te?Vy0gf>g7PPl1lfsr@z^ot3OukDyF0Dna=%Fszyu4Fri`L_CA^^22= zk>#kJz?3@`gYAWg;F|`421pCRSeiSBgY)+|E&R)KZT|qWiJyZhDlch+^R_J*-e#KB zxyP0Ranv>%N6SU-w&dNHA+mN)P%w(fmi9OrrLQZgVwSWSj993wC=R`Xz1!NL?4p-A z)yuX6t6SkpO5<^=Mej3W5U=5nHyc3kvwO<8@#sFeZQ0I!(x-jpv@jt~-lN&EPY(kU zNU2M1-Gjb$iLt#y63Jr~!_SLJ7xD3pKNHQj5MF>=A>23H+S+#R#k)lI{hSfEKRZ^x z&yGN`lPX9r1cJs$?CXpnNoq6&&NI&&)#iod>VOEubW{z>*4z~AU8fbh(Hp25>DGu*bwXuT z$%3KWTBKD(%#0g(bboA~g}XFbSkmG*pb`A{0ld`cnUXO*9QT<3+tKk>65dH=o`L&9 z+yn}3)A!0n+*1i1HqEagWkVt$m6n(BO0hHyT3T}{%%xg=*?eUXGks6FFhMuU=Cex-So*MPvU7QdD=!~5E$}V+)q(0It zY;i;=?lVzFCM`>{G7C(0OW1=1``>AVYVLmShLs{v60WY=sBB`^@s~aIoamJl|HzI} z$>0>@zHkPsaG_72b!cu6ZE{4@x|H*EIWH$cD3C8zZRDRJ#2WS6oB{|2NN#zSl2 zePItJzX5}g3G(I)I8jZ1+))4TH(a!wijB9kqQxZ!L_{RpNkc>o%=b~;Lb=L} z?=K&*vRK}{Er=M+<0lTZ4Zc*4>MU+pE3tDZ)g?Kp;l^!Sg_P&T#D${9=dL}hAIB@Y z#J*XguaeKk%=#AZ^nn^x0KoQMu&X3tku>f`WsLdg51p5@`9}3QpPt_6Ra;c zFs6p?BviOPYaMcRLjClJ;3Z>U3~NV-%SI$+JOiuq4`baE&bPj}dZI(;{X*m_eLAQe zArjLx`#2(|VPW3&SPY>R`i`YE^DqzQ^5UGG`pxOS-F(6br`R*y!GG;~RNDZ3*qbka zun=mY%?tr=F1;m7%YM>gRT0Z8bT2%7sXmrg5aVA_q(<18aCHq;0N0sK3wcOaeG|jG zk~p=iyb2Dpp(hI*rZaBF860L~5kxj30`m+?XcZ_kUbD*%yZ(nIoYQKO zpzh*0Uy`y@|LdN@i@ifGwoZ9>_hzQaZ2@7Uyn0~H zLmHREKD1OPfb(}{Z+gbDEihwrll;{s0QdWhDK2TCysR;k=TVxq{w%gPO(u4g6lgFT z6f--vzmwE(-1=nEo2@+`jrt7%%Hd*WhY-+KofeX;VP(69;TvnIcC%H+-Ukklx;fNa4NKl47>*)LFs}WraaZLO+{u)Q3OjhWC5g9)3p0Byy6W zc;3JW|MpQh4r)|Jufbxw`+W>2{1&kT)I5P}xcE<7_*|a}In81UNZe!nN{p|dWQUYv zT54dP6X+oAs){#Kg@?frN*Xmb|VIObg1`?BEW_&CK?fla~q-TfaZC5{0tb2UD zOfB!)LLrC&2!`}}Cic?eKV21DoeT=2?`ppqKVR!V(&pt9GPjb%E?CNy;J6R2Epoa; ziaTEY^jz!(xw+;f^nSE=+{v3Rp!=06S`^fgZ&_sJDr3nn9(4Ko2F%es@^XwSMKSM6 zWI@ka<~@(&n2bKXsJmQ?W~WJ|wJvc<_3Pxfe6a^@M>n2XN@l4Li4E ziH|OL<`_HhP{6|ca4%S9gv}0pbx&NXv@Xg*z+$n@y+-Pk#akK6GG~1rj-Ev&?rHbz z1mK{%Kq{B=7ys#i`EM%k4m1PDmf{{98+r+NF|PwMRlwoOv?;E~IjDO}fam(dqC3FQ zIFHuL!@KOafM8O;(*v#&k3OY$hn(I8q4WhK8)V0T0b%#$mxtK~gw4+fTndkSbpaUu zCVjp_^XQ!q2WRyUT6HBgXC8hBzIrZI<{J<|Deh!+%6zVG&{^|Ne`S;9=P zLBX@A45_7YQrS}iHGp1G($5$vR`)w$Sfx&{2>@&Ulx&8#sR1kOrSET;o3~<4x>uTn zuSntR}R;XF_~ zEc`)e*;4<+W>MiLAdTQ(G|B!6HJPSRRI=)-a|B$4~6e5m}Jd<>p;75OYgF=><=>S9|S)BLfm9;5eNd~?*LK&#c2q`mk1 z+`xqqF;GPGUi{Xx?UcCdR5+Di9ZuJ7(n+gGmn#JQPBEj^SbSIz{8VJU_26(Y|y6Y zfmVp6XV_i=w!j_Z*`recLGCm_j$8#$RhPH&QUISx#+|J)PGw}l#>Dl$vu+_@ZZAi~ z&J3q31F;SITz$IelavPs8=o4YkGmS0%6e?Sly5Fntbq zJA~$|QUfw%?{bA*jt9=iR;)~l6j^P`3N*HM2aGSUkZ0NVD`IINd-JFc%VL%<-$~|X z)Eg%~V{vtU)y0V%R&%VM#{gYsYhNNL?y;P{*VSah(bb?(t2)ghlMW+V~v4nZC!in>xg03WJY2DBV=>YB)Vt_OywJcwHFCZ zuNC`?Svo*ro%SGg$4_0-PP!5ZkVpEHM1Bj7LaQe7za5FL^{gO2POcFT6QizqD4|N7_mh645T~%WPR@jed1-C)M^@JcR0B20=Bw4KB{GC$ zhM1@CNXmIAG!X6`)HKm~YY&syq#%-pI4tfn^C3R3xG!vmo%Dx96Z$q`}hWd z_&f>V48WfWbamRJ7s z-?t)daE;68AKm{k|0(n#LdgfAn9INbiw7LUse|nnMWGiCW6l9T_WyF?y`f`<#c<^57O5Vxef+s(MK-UALY`ezS{poAr>qT(0TD%CxmV-?r zVBM7$d9$&f#(q-n5Hr(k3^A+#efAk9#YHN1#`WGR!_vAB4aGI~pZTA>oqS<9lQ?~T z5EZ$evyClc38p-($U+DVGKSgw4xC}hkjhE4invI8RAMCP0#$Zy7AY>8{`XK$Os!&< zuK)7=(vMVlhdcxKAj78dHEqoguXsLAXr-~a0Jg>OU#30k2hO0m5DixVY#NdA9TdrU zz?-(R4IF7F_RKi};_6Q~n}9DVRu6!PaPSCF1q7F{`dmYgzi?N34g#vv@3`#%K_lDo3{SL_8UN2?~`iw%qg0a-WPrced zM8`T>6-%lj_!nSxEB+f#qmVRm9{F%xw)x3>tHKeSq}@`H-J#5P?`V`((DoP*+aamW zj*~l+HWomXpVcQB;vF(U%yJ@VFVE{jM3J-+a3vy@iFFDa26hx*kwJP0%GpBCQSs+N zTsq&-4@VALw<@U|9E-2ZJcXz`$r4TJj_RgM-1>1JPny27_Fu(Ewof1*jak zeQ@nMxT%O-2QF}31_UG9&{v3P?jmD%z|^DigW%A^l3=ug=KfC8-t&c<^Eh4&v?1V8 zkGaZR&n)$FquwL|b{;{%rCz{uKb-z&y7Mjp9J_bi=?du=jvFX|eq-vRDXyhu zjr*aYvh~fBbMEge^Sui(;961gpw@dU^}Fgyts3f!9Vpzr*vblgG-^I+!}1-OPA*+A z@7vY{y`$ALbi!Kpg>4Nm_J7v>bFw&Ymg~;C>?+g4Q5#UK}D2)EMG5>RZf+Qr*mZM8;iRBcr zwfohBPL%gikCFzFcHP#&q&zvtpa)$2Tb9`%4&c&mrKiLF`ywfU)=!!fSHPnVq^UD1 z77mXx!gaRKdY=z#3ql~9Mm9Y59Q2s4;P8&kV*A-3PLG?V18sh$RO-&Xm)BPH6eSZg zqM|O@T|J5$mTqp^Z;5D$nmb`&&_%SCP)c;mbI)NikF71nsA|`EpLT7g&pFdrlm6v-ASb z%l&=e?jdAkRj}i5PS;AMoS}4L)GXV8?&`;Rsh%02b_oBDd%*g^ zu{VG)>;jkJ9FXPr?x4ab@Yaohz5u|I*soD=k&NBYrk(TQjrNbZsx=Zm+SHoI6Bi6a zB*+>o=}a-E_{U0>rkmT9tKDHN@efycYhpm=f;S_C$ESR%OKv*q7zVe)S?(K1-vLX$ z9qpvF?6=O5KCaFyvqJQW!j%lNuLyJW=a0N8C*`YVu1h|C8!MUn`D2uPd-f1v zt1ZZ!DZb$@V9^exM7N>xZ(K$Ly}vKDW1VAwyPp2h?)=ZdqInW`b(iY^@WcL(PNNUC ztOIJ~Xh1fPdg?GqZHgJTbU6|io$MITx#K?^OzlU^RCK4ugG-XK=r z_x1EJIl;=Srb-)k26oc26vhRb?V+>22v~K}(%`1Y#s0{aaiPnN=l9ZDrmNh9B+oNy z8yea+Q)DV#y*3^8I`D4CLo30UpPzw_nG_@86{XZ<+fB`?^@k7!h) zy9+fVHfSAwBHgCNdX=0U*q$n(!Q&EF_ZB?k^N8PZ>yvVm(OVSM^)#@MV$#0NvQ<2A zXtx)iFi|rw(%yj|VGJ37aXU1`}^vX+7e zfoYk)o9~TT&fZE&{Z7tZ4wBO+iF1k$X(@A$YCA;Q>l!B@?b|fGNLz5J!(La2Olzf; zt!?n<_=g8Y9@^YJJY^+iAR@sBYNFG}KX5BtP69j##U68K9%G@bH(ZX_%`^eCwfumW zhliftkq2;jAC9As^#dY>2vwsG;>K}z0X3w@=3}l9)lIbGwH|fAb1D2W7?EY;xSn`& zDSHBu3a0_gp-Ld&`!T@OVgd*d(?3ke8F+B8?{^~^aP&3-lg=z+)4MuvA92oyj^)4x zhF8fML*D?rHLVF}RSJe!mwr8M(LBC%ZK>SB70Jg3V^x5Lk(;ka8(5n%Ybi>4XqFw- z=YyOr+UAf#6Y=>QcE1Qb#ABL>GK%FmN(W*wvSDk+Pi5nWofD0kHhKQT!@6}(BXFT} z=v&1tNnmDnPE&4C!9ZV^+ho7qro~a;$vC~Z;fG6^+0$Ui1u}^GaKd5OYM4*}0Kzx8 zSTZlUo$_CD4_}#o#HEt`=@Hl6c4>f@zvTg!u==}$j{&o|+Xn+G0a=0jTuL@NXg<|0 z>90o;;!^-MBO}lYH`e*?@GfDFi%6(wL<=TKd^ifmlmNct?PwwRJWwqG92y1AcyNX6fwv9MZ0P=?k{M5DYTYA>_gA>~d!XaWnoRU{;Qw z5}D*E3j|yxuT**+D)Z5ZPffKqW!ukhNXXPep_>k`NR8RP0ujqAgWC(T`=DkCcL`ji?BjNF$NR8;zR zCx;ibmcj$XjzC_~0tLpr*{mQ??^`9g(Gz0_aNmIM%mb2=B24bayG)@-kOJ?mM6uLZ z-C8hA#NXdd2Ii3?FnwR?lOh1$kG=Q zjRbSIa+|do3rGBp)tn0!|g@D(KH;uP*TX`x!sp} z==#e;(n4Pj+*!VTNbv6mIJiuK$GzPAGr%uXT)=_R8Bxh!yJt&`&Q?^s9mm%skn&-* z{@tc|PL)II!w=W>4>T^LHj2jIY^bn*i;m2g*Qh^gqhW>kIEIM(Dekd#ZJQePI=CMmKFE+ znf(9Z7g4Xj(HA-c`H|x8&lICwbKtG_hVdpDT_uwQYD|`^3`niW zE{N=_aJu__xrYH-`ZEA0NZ+=|`a;U&`VFqn4a)AZUheB@pyGWRh`9>jBewzT#t^jZ zXx#?^j@^T!;c8&1FL3h#m#LyE0Ec<}Szg86jgd15y>K$av_7DMEjAah(Zupr-=Mn3 zY*3NRkXo;vn5Py}VnM?Wmg8gvYGT4VId+{Hyc&{bdB%AROq1MdT;(e^5BNh9e`dln zB<3#EN+z;|SGU2kV_rwWSS-liYg9LcaI9G&Zlg`E;w6HwFC#}x;+~lqC*E0|0JI0l+{( z;N<}dya7-(b*a(kQ7FHCm{dFp;9n14IJ#^A_T0mHd8oFh8?7)_z`YIn-rpDiY9O}> zF;ZIp7*1|7^PuU4W%R)SnHE4GsZ$v!`2rAbW}cX~d3kX98i4z>cY#-tuJ`(5+pvT9 z3la4iF{*=x0$;ydQw27+qGXk=wXKa!%;)T%f>Ch|V?QmKT{V&$twkva4|+Mbluvui z%UG3+hMsQG{V~rwC_%RIGewZCWH+Omr#m}L34zl6`N+S_TJrDbMciilf#h+zR~XGU zskQc$Scd`po3#=fa3$;3R*Yz4Owz7{=6)nM`p-%EC;Z?gKcffk>hycgZ=8-t)(n2%d?~gth%py9P>EuFz?w7K%#sW{tnEgIp%CNWrFV zj`bqpSTNYA_WAP^0yy#MqQO11p#fU*e%@`1k}p`m9SW{A;NUcf19OlKfXXi@hccj& z2YQ-%0JF~k^!s;*Kf{**ga;V0p36_-3hg{vQTvDB3N@~c+(-ko2ZQG?=0DrK5n&PU zKEvUEoxq$j2xaH5_iw#vFEiNDZVa8;$a?u>*sW13ly7@YLz`78{tewV6yigYps6SC zNcw{6aR0&ATUx*Bwwkv{k>kLW^#$Muof!s31d7T%{Hs1F83#Z85ivW~2^`~?ec%(& zLGxcL`EcqFp&^EI7d__sL+k$I%D?~5RaQm^UHdSd94=CRhrY(hjsHr85t= z7XY=bHU}QvvN-^E_#ko}j5{IEG-d+fuuELW0C*V!;JFn5`q;v`3ppSqWXs;Pr2D>+>$p_E^R zC6r9984l@-h~1`d?)~cc6f^}sxHrzZd1cC74Ae75mT5FE}a?11GZNVQhB}{-jmKS)ee$4~+ z9gKE2A^@`(^p$tqg3G03zmxOmNlo2^AHbygPmM?K0jG5R6GiV1qQP^lUk!-(Wsb`< z?*W(C%_&e@281pE2IwpMeMzj8-0V-pfpF5rMUNw*44U$!We5!v+)aHk2rfi?HZec! znRp6}S%!dCDXg2vOimUB{n+~n^w2a2{cmchY_*G?=*d{;kHf)DQI(QT_DEY7iD_`C z`5*0Fd010tx<4>uwbl($up;O{0WBf|g36LOQv}pV3MgAxDhLQM1Y{>+sjX!*L_{Tl zLM*Z>AtEFJWU*z55CS7$A_0T!g$OY~0twrEvF*K`J9qxL&wZZz+?mJoN0u)q=R4>9 zw)cDA-|u}_HSl`)0KF&$epOUey3puzmc2)IVg=6}jv>)aP6wi>52OwyF2F%#%ipV@ zcK=%*+yA^kbXsAR+G+p-VZw>YckHHi6g@ytb&NY=z1scY{V+HN+eYj|zdYd-c;qQfcwb`OB(}HwsV>mIsMJHM7Vmf{_v|xMoBXn0o!_NflI}w~K ztChvm^gnBv)yDftqw*3|VJSNv%j+)lJkpkX;b^JUr!W{_GODo-@n%bJp9{Re8cz#2 zKHG_4b-^XCn=-+20QN%n>SOqR;|Gs@p7$F-!r+P$a9e^As(l_Zq(d6J05Hh)t4}aq zNr#J{seh@2d8pXVePZ(vdX)mXCH>(HWP2MT?0m04Rvp-+*yeo<0bNt)90VqB@a?xF z?>;bj=G|AIHNI55zt`+`D!eX`>Q10`DJnU&L3&@gr{Wo~eKJawds3jk1VD1>-gmTCCexsG2Py}v! zxlKqw0M5oHt}{t@B#!rz$&qPM&?6-c>fOdFI5}?)H`r6RSn&D@)+f-5{dK6MzUzmU zsfGEmiQ@3uuwjbBth( zbe4DP*{4Ow9rlnGpG*|#f99}y$j4wbr7t=+78_q+#?+sx(5-FoNwyz!A)|AoX`BLs z?*Lc`?HH6&JlTW!_y%Gp2k+5}R(Y4wLkjt3^XS~vTiK`RQd*O#%3j{hE+I8Rb?a+~ zy0P=jCJXLR^v}XY^Xr91J(zMSK^QaXIG@)dUCqUlOuDvIw`;Bh_D=5Kvm|GIrR$a9 zfZTJh0wd=5_F7L^#>f%KLQ!I0!Hy*>gWC2WKvks8EL95xPS^3(3x=69`^%9r<$S*mKgU`q{*?0a`_T?`R#foMN(U56HfXLE=ua5v zb4Jo`Hz>tk*;nQ9hL^9D11>@yy1-mylXU3)d5Ts9s_#EPNQz0|4!~;t?x_6ok7s+J z`(XY^JqA^u*#R*)kcLB&>SLu*i*=}H|72`D4E_^yCw+OPVQY(#eQ-1L+?_lM@ z<}KeyHuk)Vm9#$}ajB8va`{hRns%~cNMt}8LZ#alU`!{4;|lYH-}u}x`}#cbh~-E_ zWE~(G+Kz_R@wNTJESK*%p8DP0{F{7Ky46US>^3Or`edME1+`?yCre!jqpwSVMWVr1 zr-z=>%ZhL)v)0LVuq}Qyp+`phXbxyLdOHe%o4~S7LTx#Fw(!yCGZontXSoa29*h(^ z#$BO`j3{tItiom;osd4z{vH#jfUED;OfALkmhyBgbcA+GnG=5CWAX(s@xTAw zZ0_Wqni^WJ*{GI@sXS|X;m}g#}JDfxh3=}B(12p>h0)j zLW>6@_u1paFmJjC*`JRH~) z>*_N0N%MS}WAlkiG~=gHRO&3^w8U+2NjNh8wT{Ibwr6$GRIq>nbgW+xjR-^(SWrl@ zLqF7Jk4MD0tT|iZnLm!B&lkTWYVde(ThTFvu8jqiwA)(_PcPsrj!*8IBQovtqLv-! zWUHLUgrl2aR#yAZ`L9FzlZz`&RHk0Um*|O+bh|t=En`6?-)#Sq-7E~>U;Lt!;^Vjh zFG??s3{gi^$UgODaD{1&QdCr4a3(Olk|Cn70Vj&FuBG&RxOP9(E^x;wan||PWy$a0W*Bx zlIrG5sMvis>&uy(ocS!>$td8LT!$_w0vpVz@GZ?CYj`ikaqj5Y=(}(hy&2bbIBFd# zWF03_b;2ze7Tp+ROzMn9$|H`Gloc$vr&x6Xzz0N|8AKqW!6ZXEbK}en|4GiX$v4v{ z3@97^iGBH$vac$@9Rs)LS06R)1_mfMr3#>2Z@2_Y`uN5vHPA`kE6lDzBEF2DQs>&LAM7}iec#v3JD z@MlI0aF@m;X+Q3VHcx%^QQ3FxP_WObP2WCK{wrj-xh2W4@U7A&GUVcqvRBsY0BC2w zAH;DS^hvAA@j`^Nnb|ni;ZY2QXEs#>OC(NwDTM{X}D;Lg99NVDn2 zyHD`T5uMAm1}KyxUz<_hUC!ZyIV*lSL3v_5Fk`W`|X+%9H7-zxN+%2Y^nCJ z`gZpnz;7e~#zL-j>(z-yWYh~7wWgXyVo_9v3 zPj}HW#OT0cH^OpubDDv5Bdn{s3G2=M7F;ybd;2o_?=MQ!L{?yS(>MT!+RhHK&_j)elK|fMZlPk(ik*zYMN2rS)HBkfFbSdN%W36?(k=dNgex ziDqhCy7-{q)JS=A;&B|MN^|Mw)=LsY{_xadtWY%XK zd_kpghNWM5D=y@aimGr*Xt3ka4%)3>he6yr^g~^@M#``)@wsW;yy2Vtg{W`M&p62zJT4o_OccPj49h`TQn zO5ZGigfy|s_!l)&w_<%4;z91Y)tulLkL+-5iz98N<-;Xdip14v;(P%aZq9<;Y3B3O zL-ekJ(5`a}sW}|qNGXn;Lb~u16*r(u#qlv{1fN^o$teyDH#G%o8Q1^U4dKQoasx`* zq1+omX=C5#4`@v`+UMAK&G#mM>Zy@85kuGb@e`LJ)>hbSm4_Q(l`~tv6AcHb` z`vDdy`%?SVI^+}~2szkLH8OfRg=pn2@;Dz5&1W(S+>5udP4snCNp68v`vqC@ur4zayL{+!gPDDVjCdY+&MqBly6XIeWud;i! zhJr{wX@W=TSEYTqaj4`I_{&wD-M&HDM`8u_VZQYqI?kd57h_kSZM}_a*WY+rUYnDx z)eN`#y$Xx{)UZ)}FKncOZoTs8WC@Ok-Oo?|*3%kc6)hF5A%Vm|FHj7;%i^JdZW(+H z*fnn{PRIf&POmUXWe zwH9{Wtm$wK(>;&%RA%7MShF7WYH$oqPcBHdOy^kyntHRczYD+)?`~+wn+f6;RJRY~ zuwg2techLLL@>Zcn4b$>4PsN&Haj3{52fAZioljcBM;~^fEXwgL6~ffOo^S{C`V{j z+8%7X66PY)?gjC5Ok5f}0^(tVHpn27x>B->x7}R{_uybyOg3YRFQe@eHzj&T%+Hla zQJC)e;(?dy%ELlk;>rDPkyiyUwPqG~@olAz4C~#b-2KY{NLa+fM zKZ1aEEFH89@bE8oj0u5r2dB^Lm}>#XY_Px>A8Rx#WV1W^Qpq9<7VTRblN$ffMI*y+ zgc)Y_4Na=%aO8gfIRBxF#qqb16@2dElA@no+1vZzF5v>YLXmi~ejJ~#IE;rpJA?!*jF>Mo!14D{Ja#xm<*Nl_bj&9_Y zuvTY!6DgtVP#YjBw?XoS@o{-};idYX!8%0S)1KOEuJu^DxTlZOZ{4ur*`>bF@Kv9l zu$U#3dAulry^EDW7tWNZ)0$BWv*}-lie@I77!Ai)k)|s*;k}CIL=5}Ns|qg{di0CZ z0u#pHjpy1W604|FLp*vBc!X#l^KH?pSwY2G4qLb>b2+2zbDg#$#~<|Bq*l8e zhwWZ=Av=;JMGtHP`faoC%D^=a+0LGyfC9qM+9|@ma84`a#ByK#g5vAVuI&iD@ z-3f2s<3qZ}mzl9cxUnJ{NY&-o-+pwDCTMdd;sL3DbaxFTYq+Y$-=f zv5)TAZ<`Qpde^Nu0jcvoAj7q>246l&Kn;2ykwNzCA0KGHgZG23?lY|H>~=-K#Kx=l z-eZ`kAg<8B|4?(r{P?`Pt$JviCkbObYRD~%A<1t)z~|CISg!tf7LxLI{{0Xc5an9?K2&d zI2N!5LYIPyiq-}sMiMLdoM|Fr{>%-8K@UiaUY9M;-!J>LsW`R}+}qD0Nr^KUAH?)C zBvQJo)_HE=!UMM9qZ$dJh1I1McC_q96r64yC>xS%!517(COuoGaV_$IbI9`UQ$**| zM7!Hy*Rl&Etp#78tqt4$tr8Qi1J`?j@4psMZFdu;I}@8S*u@MYb0=_SidMANN64G9 z+r%t3v8%G}L3gj2-)kaWdM#Kx(B&mHNEDr`AW|cfxfY2q-xDj}M+3sOQ$Wz|0G$qe zm;j`ajvlmaNI{Bn@Ie9} UB=A83A0+TW0{@>R0AKI_8&}pP6#xJL literal 0 HcmV?d00001 diff --git a/Standalone/Research/WorldAssemblerM3/Linux27/captures/sw-column-top.jpg b/Standalone/Research/WorldAssemblerM3/Linux27/captures/sw-column-top.jpg new file mode 100644 index 0000000000000000000000000000000000000000..727443be892640b146ade60eb6dddb3cec02b44e GIT binary patch literal 30530 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�$y5mnCaq(f#;^P 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 startPoint{}; std::array endPoint{}; + bool needsColumn=false; + bool connectedAtStart=false; + bool connectedAtEnd=false; + std::int64_t centerXSubvoxels=0; + std::int64_t centerYSubvoxels=0; + std::int64_t topZSubvoxels=0; + std::int64_t bottomZSubvoxels=0; + std::int64_t minimumRadiusSubvoxels=0; + std::int64_t nominalRadiusSubvoxels=0; + std::int64_t maximumRadiusSubvoxels=0; + double maximumRouteOffsetMetres=0.0; + double maximumBoreRadiusMetres=0.0; }; -struct FStandalonePassageBridgeCache +struct FStandalonePassageVoidCrossingCache { std::mutex mutex; - std::unordered_map> byLayout; + std::unordered_map> byLayout; }; struct FStandaloneOwnerWorld @@ -66,8 +78,8 @@ struct FStandaloneOwnerWorld std::uint32_t CoreVoxelSizeMicrons = 250000; std::uint32_t StrateLayoutChunkSizeMicrometres = 8000000; std::int64_t StrateLayoutChunkWidthSubvoxels = 8192; - std::shared_ptr PassageBridgeCache = - std::make_shared(); + std::shared_ptr PassageVoidCrossingCache = + std::make_shared(); }; namespace StandaloneOwnerLoader diff --git a/Standalone/Source/StreamBenchmarkPipeline.h b/Standalone/Source/StreamBenchmarkPipeline.h index f659e82..d3ce0dc 100644 --- a/Standalone/Source/StreamBenchmarkPipeline.h +++ b/Standalone/Source/StreamBenchmarkPipeline.h @@ -726,7 +726,8 @@ namespace StandaloneStreamBenchmark } struct FPassageOpenAirAudit { - std::uint64_t supportSamples=0,violations=0,legacyViolations=0,bridgeSamples=0; + std::uint64_t supportSamples=0,violations=0,legacyViolations=0; + std::uint64_t columnSamples=0,shortSpanSamples=0; std::uint64_t routeSupportSamples=0,routeSupportViolations=0; std::array,8> firstViolations{}; std::size_t firstViolationCount=0; @@ -773,10 +774,14 @@ namespace StandaloneStreamBenchmark ++audit.legacyViolations; if(nativeDensity>=0.0f&&passageDensity<0.0f) { - const bool connectedBridge=!routeSupport + const bool connectedColumn=!routeSupport &&passageSegment!=std::numeric_limits::max() - &&Field.isPassageBridgeSample(passageIndex,passageSegment,sample); - if(connectedBridge){++audit.bridgeSamples;return;} + &&Field.isPassageColumnSample(passageIndex,passageSegment,sample); + if(connectedColumn){++audit.columnSamples;return;} + const bool groundedShortSpan=!routeSupport + &&passageSegment!=std::numeric_limits::max() + &&Field.isPassageShortSpanSample(passageIndex,passageSegment,sample); + if(groundedShortSpan){++audit.shortSpanSamples;return;} audit.routeSupportViolations+=routeSupport; if(audit.firstViolationCount::infinity(); - double maximumThicknessMetres=0.0; - double thicknessReliefPeakToPeakMetres=0.0; + std::uint64_t columns=0,shortSpans=0,connectedEnds=0,routeSections=0; + std::uint64_t routeSectionFailures=0,wallSections=0,wallFailures=0; + double minimumWallMetres=std::numeric_limits::infinity(); + double minimumSurfaceRadiusMetres=std::numeric_limits::infinity(); + double maximumSurfaceRadiusMetres=0.0; + double surfaceRadiusReliefMetres=0.0; + double longestShortSpanMetres=0.0; bool passed=true; }; - const auto auditPassageBridges=[&](const VoxelForgeCore::World& Field, + const auto auditPassageColumns=[&](const VoxelForgeCore::World& Field, const VoxelForgeCore::World& Native, const VoxelForgeCore::ResolvedLayoutPassageV1& passage, std::size_t passageIndex) { - FPassageBridgeAudit audit; + FPassageColumnAudit audit; const double qMetres=coreMetres /VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; constexpr std::array,5> supportOffsets{{ @@ -928,37 +936,67 @@ namespace StandaloneStreamBenchmark } return supported>=3; }; - const auto bridgeSolidAt=[&](std::size_t segment,double x,double y,double z) + const auto columnSolidAt=[&](std::size_t segment,double x,double y,double z) { - return Field.isPassageBridgeSample(passageIndex,segment, + return Field.isPassageColumnSample(passageIndex,segment, {x/coreMetres,y/coreMetres,z/coreMetres}); }; - const auto thicknessAt=[&](std::size_t segment,double x,double y,double floor) + const auto shortSpanSolidAt=[&](std::size_t segment,double x,double y,double z) { - double thickness=0.0; - for(double depth=0.125;depth<=6.0+1.0e-6;depth+=0.125) - { - if(!bridgeSolidAt(segment,x,y,floor-depth))break; - thickness=depth; - } - return thickness; + return Field.isPassageShortSpanSample(passageIndex,segment, + {x/coreMetres,y/coreMetres,z/coreMetres}); }; - for(const auto& range:Field.passageBridgeRanges()) + const auto crossings=Field.passageVoidCrossings(); + for(const auto& crossing:crossings) { - if(range.passageIndex!=passageIndex - ||range.lastSegment+1>=passage.controlPoints.size())continue; - ++audit.spans; - const bool connectedStart=endpointConnected(range.startPointSubvoxels); - const bool connectedEnd=endpointConnected(range.endPointSubvoxels); + if(crossing.passageIndex!=passageIndex)continue; + const bool connectedStart=endpointConnected(crossing.startPointSubvoxels); + const bool connectedEnd=endpointConnected(crossing.endPointSubvoxels); + if(!crossing.needsColumn) + { + ++audit.shortSpans; + audit.longestShortSpanMetres=std::max(audit.longestShortSpanMetres, + crossing.unsupportedLengthMetres); + const bool shortSpanPass=connectedStart&&connectedEnd + &&crossing.unsupportedLengthMetres<=12.5 + &&crossing.lastSegment+1(connectedStart)+static_cast(connectedEnd), + shortSpanPass?"PASS":"FAIL"); + const std::size_t segment=std::min(crossing.firstSegment, + passage.controlPoints.size()-2); + const auto& a=passage.controlPoints[segment]; + const auto& b=passage.controlPoints[segment+1]; + const double x=(qToMetres(a.positionSubvoxels[0]) + +qToMetres(b.positionSubvoxels[0]))*0.5; + const double y=(qToMetres(a.positionSubvoxels[1]) + +qToMetres(b.positionSubvoxels[1]))*0.5; + const double z=(qToMetres(a.positionSubvoxels[2]) + +qToMetres(b.positionSubvoxels[2]))*0.5 + -qToMetres(a.radiusSubvoxels+b.radiusSubvoxels)*0.5+0.13; + audit.passed&=shortSpanSolidAt(segment,x,y,z-0.25); + continue; + } + if(crossing.lastSegment+1>=passage.controlPoints.size()) + {audit.passed=false;continue;} + ++audit.columns; audit.connectedEnds+=connectedStart+connectedEnd; - const std::size_t segmentCount=range.lastSegment-range.firstSegment+1; - const std::size_t probeCount=std::min(256,segmentCount); - double spanMinimumThickness=std::numeric_limits::infinity(); - double spanMaximumThickness=0.0; - std::uint64_t spanCrossSections=0,spanCrossSectionFailures=0; + const double axisX=qToMetres(crossing.columnAxisSubvoxels[0]); + const double axisY=qToMetres(crossing.columnAxisSubvoxels[1]); + const double minimumRadius=qToMetres(crossing.columnMinimumRadiusSubvoxels); + const double maximumRadius=qToMetres(crossing.columnMaximumRadiusSubvoxels); + const double minimumWall=minimumRadius-crossing.maximumRouteOffsetMetres + -crossing.maximumBoreRadiusMetres-0.50; + audit.minimumWallMetres=std::min(audit.minimumWallMetres,minimumWall); + const std::size_t segmentCount=crossing.lastSegment-crossing.firstSegment+1; + const std::size_t probeCount=std::min(128,segmentCount); + std::uint64_t crossingRouteFailures=0,crossingWallFailures=0; for(std::size_t probe=0;probe1.0e-6?(x-axisX)/radial:1.0; + const double outwardY=radial>1.0e-6?(y-axisY)/radial:0.0; + const bool wallSolid=columnSolidAt(segment, + axisX+outwardX*wallRadius,axisY+outwardY*wallRadius,z); + ++audit.wallSections; + audit.wallFailures+=!wallSolid; + crossingWallFailures+=!wallSolid; } - const double relief=std::isfinite(spanMinimumThickness) - ?spanMaximumThickness-spanMinimumThickness:0.0; - audit.thicknessReliefPeakToPeakMetres=std::max( - audit.thicknessReliefPeakToPeakMetres,relief); - const bool spanPass=connectedStart&&connectedEnd - &&spanCrossSections>=3&&spanCrossSectionFailures==0 - &&spanMinimumThickness>=2.75 - &&spanMaximumThickness<=6.5 - &&audit.thicknessReliefPeakToPeakMetres>=0.20; - audit.passed&=spanPass; - std::printf("[PassageRockBridgeAudit] id=%s segments=%zu..%zu gap_m=%.3f connected_ends=%d/2 width_at_least_m=5.000 cross_sections=%llu/%llu thickness_m=%.3f..%.3f relief_p2p_m=%.3f status=%s\n", - passage.stableId.c_str(),range.firstSegment,range.lastSegment, - range.unsupportedLengthMetres,static_cast(connectedStart) - +static_cast(connectedEnd), - static_cast(spanCrossSections-spanCrossSectionFailures), - static_cast(spanCrossSections), - std::isfinite(spanMinimumThickness)?spanMinimumThickness:0.0, - spanMaximumThickness,relief, - spanPass?"PASS":"FAIL"); + double crossingMinimumSurfaceRadius=std::numeric_limits::infinity(); + double crossingMaximumSurfaceRadius=0.0; + std::array meanRadiusByHeight{}; + std::size_t heightSamples=0; + constexpr double RayStepMetres=0.25; + constexpr std::array HeightFractions{{0.25,0.35,0.45,0.55,0.65,0.75}}; + constexpr std::size_t DirectionCount=16; + const double topMetres=qToMetres(crossing.columnTopZSubvoxels); + const double bottomMetres=qToMetres(crossing.columnBottomZSubvoxels); + const double rayStart=crossing.maximumRouteOffsetMetres + +crossing.maximumBoreRadiusMetres+0.75; + for(const double heightFraction:HeightFractions) + { + const double z=topMetres+(bottomMetres-topMetres)*heightFraction; + double heightMeanRadius=0.0; + std::uint64_t heightRayCount=0; + for(std::size_t direction=0;direction(std::ceil( + (maximumRadius+2.0-rayStart)/RayStepMetres)); + for(std::size_t step=0;step<=raySteps;++step) + { + const double rayRadius=rayStart+step*RayStepMetres; + const VoxelForgeCore::Vec3d point{ + (axisX+rayRadius*std::cos(angle))/coreMetres, + (axisY+rayRadius*std::sin(angle))/coreMetres, + z/coreMetres}; + if(Field.densityAt(point)<0.0f) + {enteredColumn=true;lastSolidRadius=rayRadius;} + else if(enteredColumn)break; + } + if(!enteredColumn)continue; + heightMeanRadius+=lastSolidRadius; + ++heightRayCount; + crossingMinimumSurfaceRadius=std::min(crossingMinimumSurfaceRadius, + lastSolidRadius); + crossingMaximumSurfaceRadius=std::max(crossingMaximumSurfaceRadius, + lastSolidRadius); + } + if(heightRayCount==0)continue; + meanRadiusByHeight[heightSamples++]=heightMeanRadius/heightRayCount; + } + const double crossingSurfaceRelief=heightSamples>=2 + ?*std::max_element(meanRadiusByHeight.begin(), + meanRadiusByHeight.begin()+heightSamples) + -*std::min_element(meanRadiusByHeight.begin(), + meanRadiusByHeight.begin()+heightSamples):0.0; + audit.minimumSurfaceRadiusMetres=std::min(audit.minimumSurfaceRadiusMetres, + crossingMinimumSurfaceRadius); + audit.maximumSurfaceRadiusMetres=std::max(audit.maximumSurfaceRadiusMetres, + crossingMaximumSurfaceRadius); + audit.surfaceRadiusReliefMetres=std::max(audit.surfaceRadiusReliefMetres, + crossingSurfaceRelief); + const double measuredWall=crossingMinimumSurfaceRadius + -crossing.maximumRouteOffsetMetres-crossing.maximumBoreRadiusMetres; + const bool columnPass=connectedStart&&connectedEnd + &&minimumWall>=1.50&&crossingRouteFailures==0 + &&crossingWallFailures==0&&std::isfinite(measuredWall) + &&measuredWall>=1.50&&crossingSurfaceRelief>=0.20; + audit.passed&=columnPass; + std::printf("[PassageRockColumnAudit] id=%s segments=%zu..%zu span_m=%.3f axis_m=(%.3f,%.3f) connected=%d/2 route_support=%llu/%llu wall_margin_m=%.3f threshold_m=1.500 radii_m=%.3f..%.3f relief_p2p_m=%.3f nominal_radius_m=%.3f status=%s\n", + passage.stableId.c_str(),crossing.firstSegment,crossing.lastSegment, + crossing.unsupportedLengthMetres,axisX,axisY, + static_cast(connectedStart)+static_cast(connectedEnd), + static_cast(probeCount-crossingRouteFailures), + static_cast(probeCount),measuredWall, + crossingMinimumSurfaceRadius,crossingMaximumSurfaceRadius, + crossingSurfaceRelief,qToMetres(crossing.columnNominalRadiusSubvoxels), + columnPass?"PASS":"FAIL"); } return audit; }; @@ -1022,6 +1114,7 @@ namespace StandaloneStreamBenchmark Assembly->floatingIslands.end(),[&](const auto& value) {return value.strateIndex==stratum.strateIndex;}); if(anchor==stratum.anchors.end()||islandPlan==Assembly->floatingIslands.end())continue; + if(islandPlan->manifest.recipe.geometryVersion>=8)continue; const auto ordinal=islandPlan->manifest.recipe.anchorIslandOrdinal; if(ordinal>=islandPlan->manifest.plan.islands.size())continue; const auto& anchorIsland=islandPlan->manifest.plan.islands[ordinal]; @@ -1110,9 +1203,34 @@ namespace StandaloneStreamBenchmark }; std::uint64_t passageCount=0,passagePasses=0,blockedControls=0,naturalPasses=0; std::uint64_t openAirViolations=0,openAirLegacyViolations=0,openAirSamples=0; - std::uint64_t openAirAudits=0,openAirBridgeSamples=0,bridgeSpans=0, - bridgeConnectedEnds=0; - bool bridgeAuditsPassed=true; + std::uint64_t openAirAudits=0,openAirColumnSamples=0,openAirShortSpanSamples=0; + std::uint64_t columnCrossings=0,shortGroundedSpans=0,columnConnectedEnds=0; + std::uint64_t columnRouteFailures=0,columnWallFailures=0; + double longestUncolumnedSpanMetres=0.0,longestShortSpanMetres=0.0; + double l26LegacyBridgeSpanMetres=0.0; + bool columnAuditsPassed=true,noLongBridgeAuditPassed=true; + bool l26NegativeControlApplicable=false; + const auto plannedVoidCrossings=Shape.passageVoidCrossings(); + for(const auto& crossing:plannedVoidCrossings) + { + if(crossing.needsColumn)continue; + longestUncolumnedSpanMetres=std::max(longestUncolumnedSpanMetres, + crossing.unsupportedLengthMetres); + } + for(std::size_t passageIndex=0;passageIndexpassages.size(); + ++passageIndex) + { + const auto& passage=ResolvedLayout->passages[passageIndex]; + const auto* upper=findStratum(passage.upperStrateIndex); + const auto* lower=findStratum(passage.lowerStrateIndex); + if(!upper||!lower||lower->generatorKind!="SurfaceWorld" + ||upper->generatorKind=="FloatingIslands")continue; + l26NegativeControlApplicable=true; + for(const auto& crossing:plannedVoidCrossings) + if(crossing.passageIndex==passageIndex) + l26LegacyBridgeSpanMetres=std::max(l26LegacyBridgeSpanMetres, + crossing.unsupportedLengthMetres); + } for(std::size_t passageIndex=0;passageIndexpassages.size();++passageIndex) { const auto& passage=ResolvedLayout->passages[passageIndex]; @@ -1131,15 +1249,21 @@ namespace StandaloneStreamBenchmark const FPassageNaturalnessAudit naturalAudit=auditPassageNaturalness(Shape,passage); const FPassageOpenAirAudit openAirAudit=auditPassageOpenAir(Shape,blockedWorld, passage,passageIndex); - const FPassageBridgeAudit bridgeAudit=auditPassageBridges(Shape,blockedWorld, + const FPassageColumnAudit columnAudit=auditPassageColumns(Shape,blockedWorld, passage,passageIndex); openAirSamples+=openAirAudit.supportSamples; openAirViolations+=openAirAudit.violations; openAirLegacyViolations+=openAirAudit.legacyViolations; - openAirBridgeSamples+=openAirAudit.bridgeSamples; - bridgeSpans+=bridgeAudit.spans; - bridgeConnectedEnds+=bridgeAudit.connectedEnds; - bridgeAuditsPassed&=bridgeAudit.passed; + openAirColumnSamples+=openAirAudit.columnSamples; + openAirShortSpanSamples+=openAirAudit.shortSpanSamples; + columnCrossings+=columnAudit.columns; + shortGroundedSpans+=columnAudit.shortSpans; + columnConnectedEnds+=columnAudit.connectedEnds; + columnRouteFailures+=columnAudit.routeSectionFailures; + columnWallFailures+=columnAudit.wallFailures; + longestShortSpanMetres=std::max(longestShortSpanMetres, + columnAudit.longestShortSpanMetres); + columnAuditsPassed&=columnAudit.passed; ++openAirAudits; const auto radialSquared=[](const std::array& p) {return p[0]*p[0]+p[1]*p[1];}; @@ -1167,10 +1291,11 @@ namespace StandaloneStreamBenchmark static_cast(naturalAudit.wallSamples), static_cast(naturalAudit.missingWallSamples), naturalAudit.passed?"PASS":"FAIL"); - std::printf("[PassageOpenAirAudit] id=%s support_samples=%llu violations=%llu bridge_samples=%llu l24_ungated_violations=%llu route_support_samples=%llu route_support_violations=%llu spacing_m=%.3f status=%s\n", + std::printf("[PassageOpenAirAudit] id=%s support_samples=%llu violations=%llu column_samples=%llu short_span_samples=%llu l24_ungated_violations=%llu route_support_samples=%llu route_support_violations=%llu spacing_m=%.3f status=%s\n", passage.stableId.c_str(),static_cast(openAirAudit.supportSamples), static_cast(openAirAudit.violations), - static_cast(openAirAudit.bridgeSamples), + static_cast(openAirAudit.columnSamples), + static_cast(openAirAudit.shortSpanSamples), static_cast(openAirAudit.legacyViolations), static_cast(openAirAudit.routeSupportSamples), static_cast(openAirAudit.routeSupportViolations), @@ -1229,12 +1354,12 @@ namespace StandaloneStreamBenchmark const VoxelForgeCore::Vec3d nativePosition{sample[0]/coreMetres, sample[1]/coreMetres,sample[2]/coreMetres}; const float nativeDensity=blockedWorld.densityAt(nativePosition); - const bool bridgeVolume=Shape.isPassageBridgeSample(passageIndex, + const bool passageColumn=Shape.isPassageColumnSample(passageIndex, nearestSegment,nativePosition); - std::printf("[ResolvedPassageAuditSample] id=%s kind=blocked xyz_m=(%.3f,%.3f,%.3f) density=%.6f native_density=%.6f segment=%zu t=%.3f passage_segment=%zu passage_sdf_m=%.3f bridge_volume=%d native_path=%s\n", + std::printf("[ResolvedPassageAuditSample] id=%s kind=blocked xyz_m=(%.3f,%.3f,%.3f) density=%.6f native_density=%.6f segment=%zu t=%.3f passage_segment=%zu passage_sdf_m=%.3f passage_column=%d native_path=%s\n", passage.stableId.c_str(),sample[0],sample[1],sample[2],sample[3], nativeDensity,nearestSegment,nearestT,nearestPassageSegment, - nearestPassageSdf,bridgeVolume?1:0, + nearestPassageSdf,passageColumn?1:0, blockedWorld.densityDispatchPathAt(nativePosition).c_str()); } for(std::size_t i=0;i(item.centers), static_cast(item.unsupported), static_cast(item.nativeUnsupported), - sample[0],sample[1],sample[2],bridgeAtSupport?1:0, + sample[0],sample[1],sample[2],columnAtSupport?1:0, item.firstNativeSupportedProbes,item.firstNativeDensities[0],item.firstNativeDensities[1], item.firstNativeDensities[2],item.firstNativeDensities[3], blockedWorld.densityDispatchPathAt({firstX,firstY,firstZ}).c_str()); @@ -1429,19 +1554,37 @@ namespace StandaloneStreamBenchmark std::printf("[PassageOpenAirNegativeControl] source=L24_ungated_support unsupported_samples=%llu detected=%s\n", static_cast(openAirLegacyViolations), l24OpenAirControlDetected?"true":"false"); - std::printf("[PassageOpenAirAuditSummary] interfaces=%llu samples=%llu violations=%llu bridge_samples=%llu l24_negative_control=%s status=%s\n", + std::printf("[PassageOpenAirAuditSummary] interfaces=%llu samples=%llu violations=%llu column_samples=%llu short_span_samples=%llu l24_negative_control=%s status=%s\n", static_cast(openAirAudits), static_cast(openAirSamples), static_cast(openAirViolations), - static_cast(openAirBridgeSamples), + static_cast(openAirColumnSamples), + static_cast(openAirShortSpanSamples), l24OpenAirControlDetected?"DETECTED":"MISSED", openAirAudits==passageCount&&openAirViolations==0&&l24OpenAirControlDetected ?"PASS":"FAIL"); - std::printf("[PassageRockBridgeAuditSummary] spans=%llu connected_ends=%llu/%llu status=%s\n", - static_cast(bridgeSpans), - static_cast(bridgeConnectedEnds), - static_cast(bridgeSpans*2), - bridgeAuditsPassed?"PASS":"FAIL"); + const bool l26NoLongBridgeControlDetected=l26NegativeControlApplicable + &&l26LegacyBridgeSpanMetres>12.5; + const bool l26NoLongBridgeControlPass=!l26NegativeControlApplicable + ||l26NoLongBridgeControlDetected; + noLongBridgeAuditPassed=longestUncolumnedSpanMetres<=12.5 + &&l26NoLongBridgeControlPass; + std::printf("[PassageNoLongBridgeNegativeControl] source=L26_SurfaceWorld_bridge applicable=%s longest_uncolumned_m=%.3f threshold_m=12.500 detected=%s\n", + l26NegativeControlApplicable?"true":"false",l26LegacyBridgeSpanMetres, + l26NoLongBridgeControlDetected?"true":"false"); + std::printf("[PassageNoLongBridgeAudit] columns=%llu short_grounded_spans=%llu longest_uncolumned_m=%.3f threshold_m=12.500 longest_short_span_m=%.3f l26_negative_control=%s status=%s\n", + static_cast(columnCrossings), + static_cast(shortGroundedSpans),longestUncolumnedSpanMetres, + longestShortSpanMetres,l26NegativeControlApplicable + ?(l26NoLongBridgeControlDetected?"DETECTED":"MISSED"):"NOT_APPLICABLE", + noLongBridgeAuditPassed?"PASS":"FAIL"); + std::printf("[PassageRockColumnAuditSummary] columns=%llu connected_ends=%llu/%llu short_spans=%llu wall_or_support_failures=%llu status=%s\n", + static_cast(columnCrossings), + static_cast(columnConnectedEnds), + static_cast(columnCrossings*2), + static_cast(shortGroundedSpans), + static_cast(columnRouteFailures+columnWallFailures), + columnAuditsPassed?"PASS":"FAIL"); const bool l23SmoothWallDetected=!looksNatural(0.0,0.0); std::printf("[PassageNaturalnessNegativeControl] source=L23_smooth_unrelieved_wall relief_p2p_m=0.000 threshold_m=%.3f detected=%s\n", MinimumWallReliefMetres,l23SmoothWallDetected?"true":"false"); @@ -1452,7 +1595,7 @@ namespace StandaloneStreamBenchmark const bool auditPassed=passageCount>0&&passagePasses==passageCount &&naturalPasses==passageCount&&l23SmoothWallDetected &&openAirAudits==passageCount&&openAirViolations==0&&l24OpenAirControlDetected - &&bridgeAuditsPassed + &&columnAuditsPassed&&noLongBridgeAuditPassed &&routePasses>0&&routePasses==routeWalkPasses&&routePasses==routeBindings &&routeBindingFailures==0 &&blockedCompiled&&blockedControls==passageCount; diff --git a/Standalone/Source/WorldAssemblyPrototype.h b/Standalone/Source/WorldAssemblyPrototype.h index 4c1a010..e7d3eb3 100644 --- a/Standalone/Source/WorldAssemblyPrototype.h +++ b/Standalone/Source/WorldAssemblyPrototype.h @@ -540,7 +540,7 @@ namespace StandaloneWorldAssemblyPrototype FIntVector(0,0,islandSlot->TopChunkZ)); const FStratePassageConfig& passage=islandSlot->Definition->PassageConfig; recipe.seed=static_cast(owner.Seed); - recipe.geometryVersion=7; + recipe.geometryVersion=8; recipe.ownerVoxelSizeMicrons=owner.OwnerVoxelSizeMicrons; recipe.coreVoxelSizeMicrons=owner.CoreVoxelSizeMicrons; recipe.ownerToCoreScaleNumerator=owner.OwnerVoxelSizeMicrons/static_cast(divisor);