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 0000000..0b315af Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux27/captures/fi-anchor-pillar.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux27/captures/sw-column-base.jpg b/Standalone/Research/WorldAssemblerM3/Linux27/captures/sw-column-base.jpg new file mode 100644 index 0000000..1530ee5 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux27/captures/sw-column-base.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux27/captures/sw-column-cutaway.jpg b/Standalone/Research/WorldAssemblerM3/Linux27/captures/sw-column-cutaway.jpg new file mode 100644 index 0000000..406f0d3 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux27/captures/sw-column-cutaway.jpg differ 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 0000000..727443b Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux27/captures/sw-column-top.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux27/captures/sw-column-wide.jpg b/Standalone/Research/WorldAssemblerM3/Linux27/captures/sw-column-wide.jpg new file mode 100644 index 0000000..020e6cf Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux27/captures/sw-column-wide.jpg differ diff --git a/Standalone/Source/OwnerWorldLoader.h b/Standalone/Source/OwnerWorldLoader.h index b376838..1aabf11 100644 --- a/Standalone/Source/OwnerWorldLoader.h +++ b/Standalone/Source/OwnerWorldLoader.h @@ -24,7 +24,7 @@ struct FStandaloneParitySample int32 StrateIndex = INDEX_NONE; }; -struct FStandalonePassageBridgeRange +struct FStandalonePassageVoidCrossing { std::size_t passageIndex=0; std::size_t firstSegment=0; @@ -33,12 +33,24 @@ struct FStandalonePassageBridgeRange double unsupportedLengthMetres=0.0; std::array 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);