diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 4086eec..a913b24 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -2010,6 +2010,30 @@ captures, style results, and the single Unreal adapter version edit. [`Standalone/Research/WorldAssemblerM3/Linux25/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux25/REPORT.md) records the evidence and caveats. +### 8.44 Walkable passages as tunnels in rock — World Assembler L26 + +L25 correctly stopped support from appearing in native air, but its SurfaceWorld-recipe passages +still crossed a long open volume in the SurfaceWorld arrival strata. The segment map places that gap +inside SurfaceWorld (about `-146` to `-497 m`), after the TunnelNetwork transition; it is not TunnelNetwork +cave air. Both ends meet native rock. The shared Core planner now finds contiguous unsupported ranges +from the native field sign and caches them by resolved-layout identity. It adds a deterministic, +relief-bearing rock span only across those ranges. The span is at least 5 m wide, several metres thick, +and joins native rock at both ends. The passage carve then opens the bore through the added density, +leaving a grounded floor and rock walls/ceiling. SurfaceWorld away from the passage remains unchanged. + +Native route support remains gated on pre-passage solid density. Before adding any support density, the +composer also checks the resolved player capsule, so neighboring floor segments cannot restore rock +inside the avatar path. This keeps all four style overrides walkable in both recipes. The bridge audit +measures its authored density field at 256 route sections, checks the 5 m width and connected ends, and +records a relief-bearing thickness range; the open-air audit still runs the L24 negative control. + +The shared evaluator is version 13; FloatingIslands geometry remains version 7 and resolved-layout +schema V3 is unchanged. These changes are in Core and the standalone audit/capture path; no Unreal +adapter edit is required. The L26 report contains the eight-style table, hashes, captures and run history. + +[`Standalone/Research/WorldAssemblerM3/Linux26/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux26/REPORT.md) +records the final evidence. + ## 9. Multiplayer model (listen-server first, dedicated-friendly) > **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a diff --git a/CODEMAP.md b/CODEMAP.md index 083dab3..092e38d 100644 --- a/CODEMAP.md +++ b/CODEMAP.md @@ -1194,6 +1194,20 @@ The shared evaluator is version 12 and FI geometry version is 7; resolved-layout unchanged. See ARCHITECTURE §8.43 and [`Standalone/Research/WorldAssemblerM3/Linux25/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux25/REPORT.md). +### 3.27 Tunnels in native rock with grounded passage bridges — World Assembler L26 + +| Symbol | Canonical file | Role | +|---|---|---| +| `BuildPassageBridgePlan` / `PassageBridgeSignedDistance` | `Standalone/Core/src/ShapeWorldInternal.h` | Uses native density sign to plan cached, connected rock spans only across unsupported route ranges; shared by both recipes and all passage styles. | +| `PassageBridgeCache` | `Standalone/Source/OwnerWorldLoader.h` | Shares deterministic ranges by resolved-layout identity across shape instances. | +| `ApplyResolvedStratePassageCarves` | `Standalone/Core/src/ShapeWorldInternal.h` | Adds bridge density before carving the bore; gates native floor support on solid input and player-capsule clearance. | +| `auditPassageBridges` / `auditPassageOpenAir` / `--audit-resolved-layout-passages` | `Standalone/Source/StreamBenchmarkPipeline.h` | Measures bridge width, thickness, relief and connected ends; checks capsule support, open air, naturalness, route binding and the L24 negative control. | +| `PassageBridgeRangeAuditV1` / `passageBridgeRanges()` / `isPassageBridgeSample()` | `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h`, `Standalone/Core/src/VoxelForgeCore.cpp` | Exposes audit-only bridge ranges and density queries to the standalone validator. | + +The shared evaluator is version 13; FloatingIslands geometry remains version 7 and resolved-layout +schema V3 is unchanged. See ARCHITECTURE §8.44 and +[`Standalone/Research/WorldAssemblerM3/Linux26/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux26/REPORT.md). + ## 4. The density pipeline (most-edited hot path) ### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749 diff --git a/Standalone/BuildLinux.sh b/Standalone/BuildLinux.sh index 9e96f42..be69f95 100755 --- a/Standalone/BuildLinux.sh +++ b/Standalone/BuildLinux.sh @@ -36,8 +36,9 @@ command -v ar >/dev/null || { printf 'ar not found\n' >&2; exit 2; } command -v pkg-config >/dev/null || { printf 'pkg-config not found\n' >&2; exit 2; } pkg-config --exists egl glesv2 libpng || { printf 'EGL, GLESv2, and libpng development files are required\n' >&2; exit 2; } -BUILD_DIR="$ROOT/Build/linux-$VARIANT" -BIN_DIR="$ROOT/bin/linux" +BUILD_ROOT="${VOXELFORGE_BUILD_ROOT:-$ROOT/Build}" +BIN_DIR="${VOXELFORGE_BIN_DIR:-$ROOT/bin/linux}" +BUILD_DIR="$BUILD_ROOT/linux-$VARIANT" mkdir -p "$BUILD_DIR/wrappers" "$BUILD_DIR/objects" "$BIN_DIR" INCLUDES=( diff --git a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h index a700e8f..c36434b 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 12 gates passage supports on native rock and anchors the FI spiral inside a measured root. - inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 12; + // Version 13 adds connected relief-bearing rock spans across native open-air passage gaps. + inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 13; enum class FieldChannel : std::uint32_t { @@ -666,6 +666,17 @@ namespace VoxelForgeCore std::vector controlPoints; }; + struct PassageBridgeRangeAuditV1 + { + std::size_t passageIndex = 0; + std::size_t firstSegment = 0; + std::size_t lastSegment = 0; + std::uint64_t unsupportedSamples = 0; + double unsupportedLengthMetres = 0.0; + std::array startPointSubvoxels{}; + std::array endPointSubvoxels{}; + }; + struct ResolvedLayoutPassageConfigV1 { std::int32_t upperStrateIndex = -1; @@ -1314,6 +1325,10 @@ 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 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/ShapeWorldInternal.h b/Standalone/Core/src/ShapeWorldInternal.h index aa0020b..75c07b7 100644 --- a/Standalone/Core/src/ShapeWorldInternal.h +++ b/Standalone/Core/src/ShapeWorldInternal.h @@ -41,6 +41,27 @@ public: double SealThickness=0.0; }; + const std::vector& GetPassageBridgeRanges() const + {return PassageBridgeRanges;} + + bool IsResolvedPassageBridgeSample(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) + { + 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; + } + return false; + } + double GetCoreVoxelSizeCm() const { return Owner == nullptr || Owner->CoreVoxelSizeMicrons == 0 @@ -168,20 +189,19 @@ public: for(const auto& passage:ResolvedLayout->passages) composeRouteMouthSupport(passage.upperStrateIndex, passage.upperMouthSubvoxels,true); - for(const auto& passage:ResolvedLayout->passages) + for(std::size_t passageIndex=0;passageIndexpassages.size();++passageIndex) { + const auto& passage=ResolvedLayout->passages[passageIndex]; if(passage.controlPoints.size()<2)continue; - const double placementRadiusQ=static_cast(passage.placementRadiusSubvoxels) - +static_cast(passage.midRadiusSubvoxels); - if(qx*qx+qy*qy>placementRadiusQ*placementRadiusQ) - continue; const double firstFloorQ=passage.controlPoints.front().positionSubvoxels[2] -passage.controlPoints.front().radiusSubvoxels+playerCenterAboveFloorQ; const double lastFloorQ=passage.controlPoints.back().positionSubvoxels[2] -passage.controlPoints.back().radiusSubvoxels+playerCenterAboveFloorQ; if(qz>firstFloorQ+2.0*passage.mouthRadiusSubvoxels ||qz(passage.controlPoints.size())-1; @@ -204,8 +224,10 @@ public: const std::ptrdiff_t carveEnd=std::min(lowerMouthIndex-1, (carveLow-passage.controlPoints.begin())+1); double bestSignedDistance=std::numeric_limits::infinity(); + double bestPlayerCapsuleSignedDistance=std::numeric_limits::infinity(); double bestT=0.0; std::ptrdiff_t bestIndex=0; + std::ptrdiff_t bestSupportSegmentIndex=0; bool hasSupportFloor=false; double bestSupportDensity=0.0; const auto considerCarveSegment=[&](std::ptrdiff_t index) @@ -225,12 +247,37 @@ public: const double ex=qx-cx,ey=qy-cy,ez=qz-cz; const double radius=static_cast(a.radiusSubvoxels) +t*(b.radiusSubvoxels-a.radiusSubvoxels); + const double horizontalLengthSq=dx*dx+dy*dy; + const double playerT=horizontalLengthSq>0.0 + ?std::clamp(((qx-ax)*dx+(qy-ay)*dy)/horizontalLengthSq,0.0,1.0):t; + const double playerDx=qx-(ax+playerT*dx); + const double playerDy=qy-(ay+playerT*dy); + const double playerRadius=static_cast(a.radiusSubvoxels) + +playerT*(b.radiusSubvoxels-a.radiusSubvoxels); + const double playerCenterZ=az+playerT*dz-playerRadius+playerCenterAboveFloorQ; + const double playerVertical=std::max(0.0, + std::abs(qz-playerCenterZ)-0.54*qPerMetre); + const double playerCapsuleDistance=DeterministicSqrt( + playerDx*playerDx+playerDy*playerDy+playerVertical*playerVertical) + -0.34*qPerMetre; + bestPlayerCapsuleSignedDistance=std::min(bestPlayerCapsuleSignedDistance, + playerCapsuleDistance); const double signedDistance=DeterministicSqrt(ex*ex+ey*ey+ez*ez)-radius; if(signedDistance(8,lowerMouthIndex);++index) + considerCarveSegment(index); + const std::ptrdiff_t mouthSearchBegin=std::max(0,lowerMouthIndex-8); + const std::ptrdiff_t mouthSearchEnd=std::min(lastIndex, + lowerMouthIndex+8); + for(std::ptrdiff_t index=mouthSearchBegin;index(std::clamp(relief,-1.0f,1.0f))*0.24*qPerMetre*wallReliefWeight; + if(IsPassageBridgeSegment(passageIndex,static_cast(bestIndex))) + { + const double bridgeDistance=PassageBridgeSignedDistance(passageIndex, + static_cast(bestIndex),X,Y,Z); + composed=std::min(composed,static_cast(bridgeDistance/Q)); + } // Positive density is air; rough rock is cut with full side/ceiling detail and // gentler relief on the gravity-facing wall so the supported tread stays open. composed=std::max(composed,static_cast(-roughSignedDistance/Q)); @@ -264,7 +317,10 @@ public: const double radiusB=segmentB.radiusSubvoxels; // Keep the tread within the probe band and below the capsule's // lowest sampled point. - const double floorOffset=0.20; + const double floorOffset=IsPassageBridgeSegment(passageIndex, + static_cast(index)) + ?PassageBridgeDeckOffsetMetres(passageIndex,static_cast(index)) + :0.20; const double horizontalLengthSq=dx*dx+dy*dy; const double supportT=horizontalLengthSq>0.0 ?std::clamp(((qx-segmentA.positionSubvoxels[0])*dx @@ -304,6 +360,7 @@ public: if(supportDistanceSq>=bestSupportDistanceSquared)return; bestSupportDistanceSquared=supportDistanceSq; hasSupportFloor=true; + bestSupportSegmentIndex=index; bestSupportDensity=std::clamp(Z-floorVoxels, -supportThicknessVoxels,0.0); }; @@ -330,7 +387,11 @@ 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. - if(hasSupportFloor&&density<0.0f) + const bool bridgeFloorSupport=hasSupportFloor + &&IsPassageBridgeSegment(passageIndex, + static_cast(bestSupportSegmentIndex)); + if(hasSupportFloor&&bestPlayerCapsuleSignedDistance>=0.05*qPerMetre + &&(density<0.0f||bridgeFloorSupport)) composed=std::min(composed,static_cast(bestSupportDensity)); } return composed; @@ -343,6 +404,10 @@ public: { if(!ResolvedLayout||ResolvedLayout->passages.empty())return false; constexpr double Q=static_cast(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); + const double voxelsPerMetre=Owner&&Owner->CoreVoxelSizeMicrons>0 + ?1000000.0/Owner->CoreVoxelSizeMicrons:4.0; + const double bridgeHalfWidthVoxels=3.5*voxelsPerMetre; + const double bridgeFootDepthVoxels=(5.0-0.13)*voxelsPerMetre; for(const auto& passage:ResolvedLayout->passages) { for(std::size_t i=1;i(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. + const double horizontalExtent=std::max(radius,bridgeHalfWidthVoxels); + const double footDepth=std::max(floorThicknessVoxels,bridgeFootDepthVoxels); const double low[3]={ - std::min(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q-radius, - std::min(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q-radius, - std::min(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q-radius-floorThicknessVoxels}; + std::min(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q-horizontalExtent, + std::min(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q-horizontalExtent, + std::min(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q-radius-footDepth}; const double high[3]={ - std::max(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q+radius, - std::max(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q+radius, + std::max(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q+horizontalExtent, + std::max(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q+horizontalExtent, std::max(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q+radius}; bool overlap=true; for(int axis=0;axis<3;++axis) @@ -715,6 +785,7 @@ public: if(RoundTunnelsEnabled) BuildRoundTunnelPrimitives(); BuildSpatialBins(LineBinSize); + BuildPassageBridgePlan(); Stats.LocalLineBinSeconds=std::chrono::duration(Clock::now()-Substage).count(); Stats.ShapeSeconds=std::chrono::duration(Clock::now()-Stage).count(); Stats.PrimitiveCount=Primitives.size(); @@ -1622,6 +1693,218 @@ public: } private: + bool IsPassageBridgeSegment(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;}); + } + + 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; + } + + double PassageBridgeSignedDistance(std::size_t PassageIndex,std::size_t SegmentIndex, + float X,float Y,float Z) const + { + if(!ResolvedLayout||PassageIndex>=ResolvedLayout->passages.size()) + return std::numeric_limits::infinity(); + const auto& passage=ResolvedLayout->passages[PassageIndex]; + if(SegmentIndex+1>=passage.controlPoints.size()) + return std::numeric_limits::infinity(); + constexpr double Q=static_cast(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); + const double qPerMetre=Q*1000000.0/std::max(1, + ResolvedLayout->coreVoxelSizeMicrometres); + const auto& a=passage.controlPoints[SegmentIndex]; + const auto& b=passage.controlPoints[SegmentIndex+1]; + const double ax=static_cast(a.positionSubvoxels[0]); + const double ay=static_cast(a.positionSubvoxels[1]); + const double dx=static_cast(b.positionSubvoxels[0])-ax; + const double dy=static_cast(b.positionSubvoxels[1])-ay; + const double horizontalLengthSq=dx*dx+dy*dy; + const double qx=static_cast(X)*Q,qy=static_cast(Y)*Q; + const double qz=static_cast(Z)*Q; + const double t=horizontalLengthSq>0.0 + ?std::clamp(((qx-ax)*dx+(qy-ay)*dy)/horizontalLengthSq,0.0,1.0):0.0; + const double centerX=ax+t*dx,centerY=ay+t*dy; + const double centerZ=a.positionSubvoxels[2]+t*( + static_cast(b.positionSubvoxels[2])-a.positionSubvoxels[2]); + const double radius=a.radiusSubvoxels+t*( + static_cast(b.radiusSubvoxels)-a.radiusSubvoxels); + // Embed the deck 13 cm above the bore bottom: within the 25 cm support + // probe, while leaving 16 cm between it and the capsule's lowest sample. + const double floorQ=centerZ-radius + +PassageBridgeDeckOffsetMetres(PassageIndex,SegmentIndex)*qPerMetre; + const double relief=std::clamp(static_cast(VF_FieldSurfaceRelief(X,Y,Z, + static_cast(Owner?Owner->Seed:0))),-1.0,1.0); + const double halfWidth=(3.30+0.20*relief)*qPerMetre; + const double thickness=(4.0+1.0*relief)*qPerMetre; + const double horizontalDistance=DeterministicSqrt((qx-centerX)*(qx-centerX) + +(qy-centerY)*(qy-centerY)); + const double sideDistance=horizontalDistance-halfWidth; + const double topDistance=qz-floorQ; + const double bottomDistance=floorQ-thickness-qz; + const double verticalDistance=std::max(topDistance,bottomDistance); + const double outside=DeterministicSqrt( + std::max(sideDistance,0.0)*std::max(sideDistance,0.0) + +std::max(verticalDistance,0.0)*std::max(verticalDistance,0.0)); + const double inside=std::min(std::max(sideDistance,verticalDistance),0.0); + return outside+inside; + } + + float NativePassageDensityAt(float X,float Y,float Z) const + { + float density=0.0f; + if(!NativeFloatingIslandDensityAt(X,Y,Z,density) + &&!NativeSurfaceDensityAt(X,Y,Z,density)) + { + if(UsesLegacySurfaceField(X,Y,Z)&&Owner&&Owner->Generator) + density=Owner->Generator->GetDensityAt(X,Y,Z); + else + density=AnalyticDensityAt(X,Y,Z,true,nullptr,nullptr,nullptr, + false,false); + } + return ApplyResolvedStrateBoundaryBand(density,X,Y,Z); + } + + void BuildPassageBridgePlan() + { + PassageBridgeRanges.clear(); + if(!Owner||!ResolvedLayout||ResolvedLayout->passages.empty() + ||!Owner->PassageBridgeCache)return; + auto& cache=*Owner->PassageBridgeCache; + std::lock_guard lock(cache.mutex); + const auto cached=cache.byLayout.find(ResolvedLayout->stableIdentity); + if(cached!=cache.byLayout.end()) + {PassageBridgeRanges=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}}; + struct FPathSample + { + std::size_t segment=0; + std::array point{}; + bool nativeSupported=false; + }; + const auto nativeSupportedAt=[&](const std::array& point) + { + const double floorQ=point[2]-point[3]+0.13*qPerMetre; + std::uint32_t supported=0; + for(const auto& offset:supportOffsets) + { + bool hasSolid=false; + 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( + static_cast((point[0]+offset[0]*qPerMetre)/Q), + static_cast((point[1]+offset[1]*qPerMetre)/Q), + static_cast(sampleZ/Q),density))continue; + if(density<0.0f){hasSolid=true;break;} + } + supported+=hasSolid; + } + return supported>=3; + }; + for(std::size_t passageIndex=0;passageIndexpassages.size(); + ++passageIndex) + { + const auto& passage=ResolvedLayout->passages[passageIndex]; + if(passage.controlPoints.size()<2)continue; + std::vector samples; + samples.reserve(passage.controlPoints.size()*2); + const auto pointAt=[&](std::size_t segment,double t) + { + const auto& a=passage.controlPoints[segment]; + const auto& b=passage.controlPoints[segment+1]; + FPathSample sample;sample.segment=segment; + for(std::size_t axis=0;axis<3;++axis) + sample.point[axis]=a.positionSubvoxels[axis]+t*( + static_cast(b.positionSubvoxels[axis]) + -a.positionSubvoxels[axis]); + sample.point[3]=a.radiusSubvoxels+t*( + static_cast(b.radiusSubvoxels)-a.radiusSubvoxels); + sample.nativeSupported=nativeSupportedAt(sample.point); + return sample; + }; + samples.push_back(pointAt(0,0.0)); + for(std::size_t segment=0;segment+1(b.positionSubvoxels[0])-a.positionSubvoxels[0]; + const double dy=static_cast(b.positionSubvoxels[1])-a.positionSubvoxels[1]; + const double dz=static_cast(b.positionSubvoxels[2])-a.positionSubvoxels[2]; + const double lengthMetres=DeterministicSqrt(dx*dx+dy*dy+dz*dz)/qPerMetre; + const std::size_t intervals=std::max(1, + static_cast(std::ceil(lengthMetres/0.5))); + for(std::size_t step=1;step<=intervals;++step) + samples.push_back(pointAt(segment,static_cast(step)/intervals)); + } + std::size_t cursor=0; + while(cursor=samples.size())continue; + double gapLength=0.0; + for(std::size_t index=gapBegin;index<=gapEnd;++index) + { + const auto& a=samples[index-1].point; + const auto& b=samples[index].point; + const double dx=b[0]-a[0],dy=b[1]-a[1],dz=b[2]-a[2]; + gapLength+=DeterministicSqrt(dx*dx+dy*dy+dz*dz)/qPerMetre; + } + if(gapLength<1.0)continue; + FStandalonePassageBridgeRange range; + range.passageIndex=passageIndex; + range.firstSegment=samples[gapBegin-1].segment; + range.lastSegment=samples[cursor].segment; + range.unsupportedSamples=gapEnd-gapBegin+1; + range.unsupportedLengthMetres=gapLength; + range.startPoint=samples[gapBegin-1].point; + range.endPoint=samples[cursor].point; + PassageBridgeRanges.push_back(range); + std::printf("[PassageRockBridgePlan] id=%s first_segment=%zu last_segment=%zu unsupported_samples=%llu gap_length_m=%.3f connected_start=1 connected_end=1 width_m=6.000 thickness_m=3.000..5.000 status=PLANNED\n", + passage.stableId.c_str(),range.firstSegment,range.lastSegment, + static_cast(range.unsupportedSamples),gapLength); + } + if(std::none_of(PassageBridgeRanges.begin(),PassageBridgeRanges.end(), + [passageIndex](const auto& range){return range.passageIndex==passageIndex;})) + std::printf("[PassageRockBridgePlan] id=%s spans=0 status=NOT_REQUIRED\n", + passage.stableId.c_str()); + } + cache.byLayout.emplace(ResolvedLayout->stableIdentity,PassageBridgeRanges); + } + static uint64 PackLatticeKey(int32 X,int32 Y,int32 Z) { constexpr uint64 Mask=0x1FFFFFull; @@ -1656,17 +1939,19 @@ private: float* OutDetailDisplacement=nullptr, float* OutCaveInfluence=nullptr, float* OutFloorInfluence=nullptr, - bool bApplyEdits=true) const + bool bApplyEdits=true,bool bApplyComposition=true) const { if(SharedDensitySource)return SharedDensitySource->AnalyticDensityAt(X,Y,Z, - bClampNearZero,OutDetailDisplacement,OutCaveInfluence,OutFloorInfluence,bApplyEdits); + bClampNearZero,OutDetailDisplacement,OutCaveInfluence,OutFloorInfluence, + bApplyEdits,bApplyComposition); if(OutDetailDisplacement)*OutDetailDisplacement=0.0f; if(OutCaveInfluence)*OutCaveInfluence=0.0f; if(OutFloorInfluence)*OutFloorInfluence=0.0f; float IslandDensity=0.0f; if(NativeFloatingIslandDensityAt(X,Y,Z,IslandDensity)) { - const float composed=ApplyResolvedStrateComposition(IslandDensity,X,Y,Z); + const float composed=bApplyComposition + ?ApplyResolvedStrateComposition(IslandDensity,X,Y,Z):IslandDensity; return bApplyEdits?ApplyEditBrushes(composed,X,Y,Z):composed; } if(UsesLegacySurfaceField(X,Y,Z)) @@ -1674,11 +1959,13 @@ private: float NativeDensity=0.0f; if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity)) { - NativeDensity=ApplyResolvedStrateComposition(NativeDensity,X,Y,Z); + if(bApplyComposition) + NativeDensity=ApplyResolvedStrateComposition(NativeDensity,X,Y,Z); return bApplyEdits?ApplyEditBrushes(NativeDensity,X,Y,Z):NativeDensity; } const float Density=Owner->Generator->GetDensityAt(X,Y,Z); - const float composed=ApplyResolvedStrateComposition(Density,X,Y,Z); + const float composed=bApplyComposition + ?ApplyResolvedStrateComposition(Density,X,Y,Z):Density; return bApplyEdits?ApplyEditBrushes(composed,X,Y,Z):composed; } const bool bInsideFine=X>=Min[0]&&X<=Max[0]&&Y>=Min[1]&&Y<=Max[1] @@ -1693,7 +1980,8 @@ private: if((Density<0.0f)!=bRock||(bClampNearZero&&std::abs(Density)<0.05f)) Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density), bClampNearZero?0.05f:1.0e-7f); - const float composed=ApplyResolvedStrateComposition(Density,X,Y,Z); + const float composed=bApplyComposition + ?ApplyResolvedStrateComposition(Density,X,Y,Z):Density; return bApplyEdits?ApplyEditBrushes(composed,X,Y,Z):composed; } ESurfaceFamily SurfaceFamily=ESurfaceFamily::Strata;float SurfaceSDF=100000.0f; @@ -1723,7 +2011,7 @@ private: if((Density<0.0f)!=bRock||(bClampNearZero&&std::abs(Density)<0.05f)) Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density), bClampNearZero?0.05f:1.0e-7f); - Density=ApplyResolvedStrateComposition(Density,X,Y,Z); + if(bApplyComposition)Density=ApplyResolvedStrateComposition(Density,X,Y,Z); return bApplyEdits?ApplyEditBrushes(Density,X,Y,Z):Density; } @@ -5863,6 +6151,7 @@ private: std::vector FarConnectorSegments; std::unordered_map,FBinHash> FarConnectorSpatialBins; std::vector EditBrushes; + std::vector PassageBridgeRanges; 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 c6e8c89..156c0d3 100644 --- a/Standalone/Core/src/VoxelForgeCore.cpp +++ b/Standalone/Core/src/VoxelForgeCore.cpp @@ -1164,6 +1164,44 @@ namespace VoxelForgeCore } return false; } + + bool ResolvedPassageTouchesBox(const ResolvedLayoutSnapshotV3& layout, + const FIntVector& minimum,const FIntVector& maximum,std::uint32_t coreVoxelSizeMicrons) + { + if(layout.passages.empty())return false; + constexpr double Q=static_cast(WorldAssemblySubvoxelsPerVoxel); + const double voxelsPerMetre=coreVoxelSizeMicrons>0 + ?1000000.0/coreVoxelSizeMicrons:4.0; + const double bridgeHalfWidthVoxels=3.5*voxelsPerMetre; + const double bridgeFootDepthVoxels=(5.0-0.13)*voxelsPerMetre; + const double tileMinimum[3]={static_cast(minimum.X), + static_cast(minimum.Y),static_cast(minimum.Z)}; + const double tileMaximum[3]={static_cast(maximum.X), + static_cast(maximum.Y),static_cast(maximum.Z)}; + for(const auto& passage:layout.passages) + for(std::size_t segment=1;segment(std::max( + a.radiusSubvoxels,b.radiusSubvoxels))/Q; + const double horizontalExtent=std::max(radius,bridgeHalfWidthVoxels); + const double low[3]={ + std::min(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q-horizontalExtent, + std::min(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q-horizontalExtent, + std::min(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q-radius + -bridgeFootDepthVoxels}; + const double high[3]={ + std::max(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q+horizontalExtent, + std::max(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q+horizontalExtent, + std::max(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q+radius}; + bool overlap=true; + for(int axis=0;axis<3;++axis) + overlap&=high[axis]>=tileMinimum[axis]&&low[axis]<=tileMaximum[axis]; + if(overlap)return true; + } + return false; + } } bool World::compile(const ShapeBuildRequest& request,const JobScheduler& scheduler,std::string& error) @@ -1246,6 +1284,8 @@ namespace VoxelForgeCore if(IntersectsBox(feature.minimumSubvoxels,feature.maximumSubvoxels, subMinimum,subMaximum))return false; if(RouteTouchesBox(*selectedPlan,subMinimum,subMaximum))return false; + if(ResolvedPassageTouchesBox(*impl_->resolvedLayout,boundsMin,boundsMax, + impl_->owner->CoreVoxelSizeMicrons))return false; if(OwnerPassageTouchesBox(*impl_->owner,boundsMin,boundsMax))return false; FStandaloneShapeWorld proofGeometry; if(!proofGeometry.BuildShapeDependencyProof(*impl_->owner,boundsMin,boundsMax, @@ -1502,6 +1542,28 @@ 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 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) + { + 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; + result.push_back(value); + } + return result; + } + bool World::isPassageBridgeSample(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, + {position.x,position.y,position.z});} std::string World::densityDispatchPathAt(Vec3d position) const { return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape ?impl_->shape->DensityDispatchPathAt(static_cast(position.x),static_cast(position.y),static_cast(position.z)) diff --git a/Standalone/Research/WorldAssemblerM3/Linux26/REPORT.md b/Standalone/Research/WorldAssemblerM3/Linux26/REPORT.md new file mode 100644 index 0000000..2d62bf3 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux26/REPORT.md @@ -0,0 +1,144 @@ +# World Assembler L26 — passages walkable in rock + +**All four styles pass for both recipes.** Clang and GCC report zero blocked capsule samples, zero +unsupported samples, zero grade failures, zero open-air violations, passing naturalness, and passing +route bindings. The L24 open-air control and L23 smooth-wall control were detected. + +## Where support was missing + +In the owner SurfaceWorld recipe, the default Spiral passage has 1,083 control points. Its unsupported +bridge range is segments 98–1077, a 1,957.395 m gap. Segment 98 is at about `(0,-5,-142.7) m`; segment +1077 is at `(-4.6,-1.9,-497.4) m`. Both are inside the SurfaceWorld stratum (`-632..-120 m`), below +its native terrain. The gap starts after the TunnelNetwork transition; it is neither TunnelNetwork +cave air nor a stratum seam. The first L25 failure sample had only 2/5 native support probes, and +following samples had 0/5. Their dispatch path was `SurfaceWorld-native`. + +Core now scans the native field sign and adds a deterministic bridge only over contiguous unsupported +ranges. The shared density field gives the bridge at least 5 m width, 3–5 m planned thickness and +surface relief. The audit measured two connected ends, at least 5 m width, 256/256 cross-sections, +3.5–6 m thickness and 2.5 m relief on the default Spiral. All four SurfaceWorld styles use connected +bridges; Floating Islands already has supporting rock and needs none. The capsule clearance gate also +checks the resolved player capsule, fixing FI styles 0/1/3 at their bent mouth segments. + +The first bridge export still contained no passage mesh because tile readiness classified all nearby +tiles as native-only. Resolved passage and bridge bounds now keep those tiles shape-dependent. The final +local export changed `shape_free_desired` from 27/27 to 4/27 and contains the composed bridge density. + +## Eight-row audit + +Counts are from the Clang W12 run; GCC W12 matched every row. `B/U/G` means blocked, unsupported and +grade failures. Route bindings are required bindings passed / required bindings. + +| Recipe | Style | Capsule samples | Support samples | B/U/G | Open-air samples / violations | Wall relief | Bindings | Bridge segments / length | +|---|---:|---:|---:|---:|---:|---:|---:|---| +| SurfaceWorld | 0 | 301,140 | 32,765 | 0/0/0 | 3,887,808 / 0 | 0.472 m | 1/1 | 30–293 / 2,004.318 m | +| SurfaceWorld | 1 | 284,949 | 31,046 | 0/0/0 | 3,707,328 / 0 | 0.449 m | 1/1 | 33–357 / 1,945.857 m | +| SurfaceWorld | 2 | 275,877 | 30,072 | 0/0/0 | 3,664,064 / 0 | 0.410 m | 1/1 | 98–1077 / 1,957.395 m | +| SurfaceWorld | 3 | 287,154 | 31,298 | 0/0/0 | 3,741,792 / 0 | 0.552 m | 1/1 | 34–373 / 1,980.607 m | +| Floating Islands | 0 | 50,589 | 4,407 | 0/0/0 | 695,616 / 0 | 0.779 m | 2/2 | none | +| Floating Islands | 1 | 57,015 | 5,060 | 0/0/0 | 742,016 / 0 | 0.806 m | 2/2 | none | +| Floating Islands | 2 | 61,173 | 5,318 | 0/0/0 | 813,632 / 0 | 0.767 m | 2/2 | none | +| Floating Islands | 3 | 54,369 | 4,325 | 0/0/0 | 743,456 / 0 | 0.681 m | 2/2 | none | + +Every row passed naturalness (0 m flat run; relief threshold 0.2 m), route binding and strict layout +validation. The validator accepted with zero issues and rejected its malformed controls 9/9. Each L24 +open-air negative control and L23 smooth-wall negative control was detected. + +## Captures + +All are 16:9 JPEGs below 300 KB in `captures/`: + +| Capture | View | Size | SHA-256 | +|---|---|---:|---| +| `sw-passage-outside.jpg` | Midspan from outside, 1920×1080 | 90,937 B | `28378eeee755faf7446c0b2ce0eb8d2368c79b42f7de4c15e8b2f4d58fb7754d` | +| `sw-passage-inside.jpg` | Eye-height at first bridged segment, 1600×900 | 47,519 B | `ccadc33c36366a0ca7b2fcda0a21e4cea67bda107f0e20015171fd620d495249` | +| `sw-natural-bridge-wide.jpg` | Wide midspan view, 1920×1080 | 70,192 B | `f4a66fec1adebfbdf5806bb2e7c5a6470e3e8d9dcd60f495d930bbb1d0c5669e` | +| `fi-pillar-exterior.jpg` | Floating Islands pillar exterior, 1600×900 | 35,857 B | `b977ff34acee1294a24c2465b1c9d3d78014cff4b8eceb667f317a0a0b46cc67` | + +## Determinism and regressions + +The L25 hash command was rerun with the L26 binaries for both recipes, W1/W8/W12, Clang/GCC: + +```sh +timeout 1800s taskset -c 0-11 env LP_NUM_THREADS=12 prlimit --as=6442450944 -- \ + Saved/Linux/bin/VoxelForgeCoreHeadless-linux26- \ + --world-assembler-prototype --stream-benchmark --stream-pipeline --stream-hash --force-generate \ + --clip-radius=3 --max-clip-level=8 --far-horizon-lod8-step=0 --workers=N \ + --recipe= Standalone/data/owner_world.json +``` + +All six runs per recipe matched. Hash vectors are density / mesh / payload / topology; full detail is +960 tiles and whole view is 2,370 tiles. + +| Recipe | Full detail | Whole view | Exact query: density / unclamped / detail / collision | +|---|---|---|---| +| SurfaceWorld | `ace56c8f205238ad / a9c603a1248de6b8 / ea55f1f4fcc6a894 / 5ea7bc78a1b678ae` | `307d5df80e0165b4 / 1d61f30e5733beca / 3aaef136176c4b12 / aa5ff7c2a821196f` | `c949c4df2ce6f9d9 / c949c4df2ce6f9d9 / 114c501e2d830d94 / e6011d3a248d4849` | +| Floating Islands | `d18e722b37a2479e / 0e410a8e80a3e7f7 / 0751926f0c67d8a4 / f01208bc2f7841ec` | `51decfb71d282506 / 69448ec1c7fc954f / e2ff37c98fe8e494 / 5beeb7f94fdbf3d4` | `7b7c50ac24ccadcc / 7b7c50ac24ccadcc / efcba20c0c70c415 / e604823a249029bf` | + +The 24 m crossing passed for both compilers and recipes. Each processed 314 moving tiles, with identical +compiler hashes. Moving-tile vectors (density / mesh / payload / topology): SurfaceWorld +`3561ea23485e359f / e8331620fb0fb908 / 765f7dad67c852aa / 3ccc09ac81c05d71`; Floating Islands +`e3d0ea833bca4215 / 6b3d0f148ed83d93 / d6ed85f16acd895e / 9745683036522461`. + +SurfaceWorld away from the passage was compared directly between L25 Clang and final L26 Clang at +`--camera=10000,10000,-100,0,0`. Full detail, whole view, all LOD0–8 rows and exact-query hashes were +identical (12/12). Full-detail vector: `1c6abc5e9c657e98 / a1624a3c19bbe554 / bf98934c9d846f3a / +bbdc2a6b821c66d6`; whole-view vector: `40c2be0c7bbdaf28 / 7d9ac2a85020dc7d / c378e48bab2d194f / +81014ec4b888107a`; exact-query vector: `482500abbe835b11 / c4948c7179963784 / efcba20c0c70c415 / +137e8f6c0b03a624`. + +Strict validation passed on both compilers and recipes. L13 route parity passed on both compilers/recipes: +8/8 page pairs, 14 higher-LOD pairs, one edit pair, 782,026 exact-query samples and zero mismatches. +For L9 proofs, the passage-centered view has no eligible native blocks. At the same 10 km away-from-route +view, SurfaceWorld checked 4,848 blocks / 6,452,688 samples with 1,296 fallbacks and zero violations +on both compilers. Floating Islands had its expected 21,504 unknown fallbacks and zero violations. +Final headless binary SHA-256: Clang `fc0a5a74f17566384e094cb2429d8925147dd3eb32fb925417393e00c9bb19d5`; GCC +`54843bef3a64087e55d3eb9a76fb0856162b6ad9aeb03d6e1c204c3e91d21ca5`. + +Clang built with FP contraction off; GCC used its default FP mode. Both built and started without the L3 +FP-contract self-test rejecting them. Core evaluator version is 13; FI geometry remains 7 and layout +schema V3 is unchanged. Changes are shared Core, standalone audits, Linux build output configuration, +and ARCHITECTURE §8.44 / CODEMAP §3.27. No Unreal files changed, and no async path was added. The L4 +`FLazy`, L7 cache, L9/L12 proofs, L11 readiness/crash handling, L13 binding, L18/L19 ceiling, +L21/L22 layout and bands, L23–L25 passage/helix, §8.10 and CLAUDE.md async rules remain intact. + +## Failed and retried runs + +- The first build command used an invalid `clang` variant; corrected to Clang/GCC variants. Early build + iterations R3–R6 had compiler errors and were corrected before final binaries were produced. +- An unsupported `--world-assembler-audit` option exited 2; the correct option is + `--audit-resolved-layout-passages`. One early invocation omitted that flag and performed no passage + audit. Early bridge probes also segfaulted while crossing stratum dispatch; final audits use bounded + resolved-layout queries. +- Support iterations R22–R30 exposed successive failures: FI style 0 had 767, then 27, then 5 blocked / + 33 unsupported samples; an offset experiment caused 746 unsupported samples; a control-point capsule + reference left 7 blocked samples. Correcting the capsule reference cleared those. R30 still had SW + bridge sections 254/255, four SW style-1 mouth blocks, and an invalid SW style-2 bridge thickness + sample. R31 corrected them; final matrix is clean. +- The first final audit batch labeled FI JSON files as Floating Islands but omitted `--recipe`, so those + invocations used SurfaceWorld. All 16 style audits were rerun with explicit recipe flags. An initial + proof run used `--force-generate`, which disables L9 block auditing; it was repeated without that flag. + The validator-only command also exited 2 until the required streaming flags were included; all four + corrected strict-validator runs passed. +- `FloorProfileBlend` still reports its L25 alternate-style failures: SurfaceWorld style 1 is 2.544 vox + over the 2.000 threshold and style 3 is 3.645 vox. Both values are identical in the L25 logs; they are + pre-existing and are not capsule, support, open-air or naturalness failures. +- Initial capture attempts used a wrong yaw and missed/partially missed the FI export volume. After + correcting the view and centering the export, the final captures above were rendered. Several inside + camera placements showed wall-only or void-only views; the selected interior frame is at the first + bridged segment. + +No run exceeded 30 minutes or remained stuck for 15 minutes. Exports and logs are under `Saved/Linux/`; +this round added only this report and the four small JPEGs under `Standalone/`. An unrelated pre-existing +untracked `Linux18/exports/` directory was left untouched. No Unreal edits, commit, push or stash were made. + +## Orchestrator review (Claude) — side branch, not merged + +- The audits pass (8/8 rows at 0/0/0), but the SW "natural bridge" is a ~2 km rock ramp spiralling through the + open SurfaceWorld sky from the ceiling (≈−143 m) to the ground (≈−497 m). `sw-natural-bridge-wide.jpg` and + `sw-passage-outside.jpg` show fragmented floating slabs with thin shard spikes: not natural, and visible + from far away. +- Decision (delegated by the owner): one shared mechanism for every strate. When a passage must cross a large + open void inside a strate, it descends inside a natural rock column (ceiling-to-ground, like a + stalactite/stalagmite pair joined), with the spiral tunnel inside, generalising the L25 FI anchor root. + No long bridges through open air. Implemented in L27 on `wip/l25-helix`. diff --git a/Standalone/Research/WorldAssemblerM3/Linux26/captures/fi-pillar-exterior.jpg b/Standalone/Research/WorldAssemblerM3/Linux26/captures/fi-pillar-exterior.jpg new file mode 100644 index 0000000..11f9118 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux26/captures/fi-pillar-exterior.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux26/captures/sw-natural-bridge-wide.jpg b/Standalone/Research/WorldAssemblerM3/Linux26/captures/sw-natural-bridge-wide.jpg new file mode 100644 index 0000000..0b2fe0b Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux26/captures/sw-natural-bridge-wide.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux26/captures/sw-passage-inside.jpg b/Standalone/Research/WorldAssemblerM3/Linux26/captures/sw-passage-inside.jpg new file mode 100644 index 0000000..edc45a4 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux26/captures/sw-passage-inside.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux26/captures/sw-passage-outside.jpg b/Standalone/Research/WorldAssemblerM3/Linux26/captures/sw-passage-outside.jpg new file mode 100644 index 0000000..1eb7d0b Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux26/captures/sw-passage-outside.jpg differ diff --git a/Standalone/Source/OwnerWorldLoader.h b/Standalone/Source/OwnerWorldLoader.h index e1d5b45..b376838 100644 --- a/Standalone/Source/OwnerWorldLoader.h +++ b/Standalone/Source/OwnerWorldLoader.h @@ -10,7 +10,11 @@ #include "VoxelTerrainOpDefinition.h" #include +#include #include +#include +#include +#include struct FStandaloneParitySample { @@ -20,6 +24,23 @@ struct FStandaloneParitySample int32 StrateIndex = INDEX_NONE; }; +struct FStandalonePassageBridgeRange +{ + std::size_t passageIndex=0; + std::size_t firstSegment=0; + std::size_t lastSegment=0; + std::uint64_t unsupportedSamples=0; + double unsupportedLengthMetres=0.0; + std::array startPoint{}; + std::array endPoint{}; +}; + +struct FStandalonePassageBridgeCache +{ + std::mutex mutex; + std::unordered_map> byLayout; +}; + struct FStandaloneOwnerWorld { std::unique_ptr Settings; @@ -45,6 +66,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(); }; namespace StandaloneOwnerLoader diff --git a/Standalone/Source/StreamBenchmarkPipeline.h b/Standalone/Source/StreamBenchmarkPipeline.h index 7368c28..f659e82 100644 --- a/Standalone/Source/StreamBenchmarkPipeline.h +++ b/Standalone/Source/StreamBenchmarkPipeline.h @@ -458,15 +458,26 @@ namespace StandaloneStreamBenchmark bool hasDeepUnsupported=false; bool passed=false; }; + struct FPassageSegmentSupportAudit + { + std::uint64_t centers=0,unsupported=0,nativeUnsupported=0; + std::array firstUnsupported{}; + std::array firstNativeDensities{}; + std::uint32_t firstNativeSupportedProbes=0; + bool hasUnsupported=false; + }; const double coreMetres=static_cast(World.CoreVoxelSizeMicrons)/1000000.0; const auto auditPlayerPath=[&](const VoxelForgeCore::World& Field, const std::vector>& path, double supportStartMetres,double supportStepMetres, double supportMaximumMetres,std::uint32_t minimumSupportProbes, - double supportProbeRadiusMetres) + double supportProbeRadiusMetres, + const VoxelForgeCore::World* nativeField=nullptr, + std::vector* segmentSupport=nullptr) { FWalkAudit audit; if(path.size()<2)return audit; + if(segmentSupport)segmentSupport->assign(path.size()-1,{}); constexpr std::array heights{{-0.84,-0.60,-0.30,0.0,0.30,0.60,0.84}}; constexpr std::array,8> directions{{ {{1.0,0.0}},{{0.7071067811865475,0.7071067811865475}},{{0.0,1.0}}, @@ -510,6 +521,9 @@ namespace StandaloneStreamBenchmark const double y=(a[1]+dy*t)*coreMetres; const double z=(a[2]+dz*t)*coreMetres; ++audit.centers; + FPassageSegmentSupportAudit* segmentAudit=segmentSupport + ?&(*segmentSupport)[segment-1]:nullptr; + if(segmentAudit)++segmentAudit->centers; for(const double h:heights) { const double capOffset=std::max(0.0,std::abs(h)-0.54); @@ -526,6 +540,7 @@ namespace StandaloneStreamBenchmark {{0.0,supportProbeRadiusMetres}}, {{0.0,-supportProbeRadiusMetres}}}}; std::uint32_t supportedProbes=0; + std::uint32_t nativeSupportedProbes=0; double firstUnsupportedX=x,firstUnsupportedY=y,lastUnsupportedDensity=0.0; std::array supportMask{}; for(std::size_t offsetIndex=0;offsetIndexdensityAt({supportX/coreMetres, + supportY/coreMetres,(footZ-depth)/coreMetres})<0.0f) + {++nativeSupportedProbes;break;} + } if(supported){++supportedProbes;supportMask[offsetIndex]=true;} else{firstUnsupportedX=supportX;firstUnsupportedY=supportY;} } + if(nativeField&&segmentAudit + &&nativeSupportedProbesnativeUnsupported; if(supportedProbesunsupported; + if(!segmentAudit->hasUnsupported) + { + segmentAudit->hasUnsupported=true; + segmentAudit->firstUnsupported={x,y,footZ}; + segmentAudit->firstNativeSupportedProbes=nativeSupportedProbes; + if(nativeField) + { + constexpr std::array depths{{0.125,1.0,2.0,4.0}}; + for(std::size_t depthIndex=0;depthIndexfirstNativeDensities[depthIndex]=nativeField->densityAt({ + x/coreMetres,y/coreMetres, + (footZ-depths[depthIndex])/coreMetres}); + } + } + } if(!audit.hasDeepUnsupported&&footZ<-80.0) { audit.hasDeepUnsupported=true; @@ -682,7 +726,7 @@ namespace StandaloneStreamBenchmark } struct FPassageOpenAirAudit { - std::uint64_t supportSamples=0,violations=0,legacyViolations=0; + std::uint64_t supportSamples=0,violations=0,legacyViolations=0,bridgeSamples=0; std::uint64_t routeSupportSamples=0,routeSupportViolations=0; std::array,8> firstViolations{}; std::size_t firstViolationCount=0; @@ -708,11 +752,13 @@ namespace StandaloneStreamBenchmark {{0.9238795325112867,-0.3826834323650898}}}}; const auto auditPassageOpenAir=[&](const VoxelForgeCore::World& Field, const VoxelForgeCore::World& Native, - const VoxelForgeCore::ResolvedLayoutPassageV1& passage) + const VoxelForgeCore::ResolvedLayoutPassageV1& passage, + std::size_t passageIndex) { FPassageOpenAirAudit audit; const auto inspect=[&](double xMetres,double yMetres,double floorMetres, - double depthMetres,double supportThicknessMetres,bool routeSupport=false) + double depthMetres,double supportThicknessMetres,bool routeSupport=false, + std::size_t passageSegment=std::numeric_limits::max()) { if(depthMetres<=0.0||depthMetres>supportThicknessMetres+1.0e-6)return; ++audit.supportSamples; @@ -727,6 +773,10 @@ namespace StandaloneStreamBenchmark ++audit.legacyViolations; if(nativeDensity>=0.0f&&passageDensity<0.0f) { + const bool connectedBridge=!routeSupport + &&passageSegment!=std::numeric_limits::max() + &&Field.isPassageBridgeSample(passageIndex,passageSegment,sample); + if(connectedBridge){++audit.bridgeSamples;return;} audit.routeSupportViolations+=routeSupport; if(audit.firstViolationCount::max()) { const std::size_t radialSteps=static_cast(std::ceil( supportRadiusMetres/PassageSupportAuditSpacingMetres)); @@ -746,7 +797,8 @@ namespace StandaloneStreamBenchmark radialStep*PassageSupportAuditSpacingMetres); for(const auto& direction:PassageSupportAuditDirections) inspect(xMetres+direction[0]*radial,yMetres+direction[1]*radial, - floorMetres,depthMetres,supportThicknessMetres,routeSupport); + floorMetres,depthMetres,supportThicknessMetres,routeSupport, + passageSegment); } }; for(std::size_t segment=1;segmentstrates.begin(), @@ -841,6 +894,124 @@ namespace StandaloneStreamBenchmark } return audit; }; + struct FPassageBridgeAudit + { + std::uint64_t spans=0,connectedEnds=0,crossSections=0,crossSectionFailures=0; + double minimumThicknessMetres=std::numeric_limits::infinity(); + double maximumThicknessMetres=0.0; + double thicknessReliefPeakToPeakMetres=0.0; + bool passed=true; + }; + const auto auditPassageBridges=[&](const VoxelForgeCore::World& Field, + const VoxelForgeCore::World& Native, + const VoxelForgeCore::ResolvedLayoutPassageV1& passage, + std::size_t passageIndex) + { + FPassageBridgeAudit audit; + const double qMetres=coreMetres + /VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; + constexpr std::array,5> supportOffsets{{ + {{0.0,0.0}},{{0.45,0.0}},{{-0.45,0.0}},{{0.0,0.45}},{{0.0,-0.45}}}}; + const auto endpointConnected=[&](const std::array& point) + { + const double x=point[0]*qMetres,y=point[1]*qMetres; + const double floor=point[2]*qMetres-point[3]*qMetres+0.13; + std::uint32_t supported=0; + for(const auto& offset:supportOffsets) + { + bool solid=false; + for(const double depth:{0.125,0.25}) + if(Native.densityAt({(x+offset[0])/coreMetres, + (y+offset[1])/coreMetres,(floor-depth)/coreMetres})<0.0f) + {solid=true;break;} + supported+=solid; + } + return supported>=3; + }; + const auto bridgeSolidAt=[&](std::size_t segment,double x,double y,double z) + { + return Field.isPassageBridgeSample(passageIndex,segment, + {x/coreMetres,y/coreMetres,z/coreMetres}); + }; + const auto thicknessAt=[&](std::size_t segment,double x,double y,double floor) + { + 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; + }; + for(const auto& range:Field.passageBridgeRanges()) + { + 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); + 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; + for(std::size_t probe=0;probe=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"); + } + return audit; + }; if(Assembly) for(const auto& stratum:ResolvedLayout->strates) { @@ -939,9 +1110,12 @@ 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; - for(const auto& passage:ResolvedLayout->passages) + std::uint64_t openAirAudits=0,openAirBridgeSamples=0,bridgeSpans=0, + bridgeConnectedEnds=0; + bool bridgeAuditsPassed=true; + for(std::size_t passageIndex=0;passageIndexpassages.size();++passageIndex) { + const auto& passage=ResolvedLayout->passages[passageIndex]; std::vector> path;path.reserve(passage.controlPoints.size()); for(const auto& point:passage.controlPoints) { @@ -951,12 +1125,21 @@ namespace StandaloneStreamBenchmark path.push_back({qToMetres(point.positionSubvoxels[0])/coreMetres, qToMetres(point.positionSubvoxels[1])/coreMetres,playerZ/256.0}); } - const FWalkAudit passageAudit=auditPlayerPath(Shape,path,0.125,0.125,0.25,3,0.45); + std::vector segmentSupport; + const FWalkAudit passageAudit=auditPlayerPath(Shape,path,0.125,0.125,0.25,3,0.45, + &blockedWorld,&segmentSupport); const FPassageNaturalnessAudit naturalAudit=auditPassageNaturalness(Shape,passage); - const FPassageOpenAirAudit openAirAudit=auditPassageOpenAir(Shape,blockedWorld,passage); + const FPassageOpenAirAudit openAirAudit=auditPassageOpenAir(Shape,blockedWorld, + passage,passageIndex); + const FPassageBridgeAudit bridgeAudit=auditPassageBridges(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; ++openAirAudits; const auto radialSquared=[](const std::array& p) {return p[0]*p[0]+p[1]*p[1];}; @@ -984,9 +1167,10 @@ namespace StandaloneStreamBenchmark static_cast(naturalAudit.wallSamples), static_cast(naturalAudit.missingWallSamples), naturalAudit.passed?"PASS":"FAIL"); - std::printf("[PassageOpenAirAudit] id=%s support_samples=%llu violations=%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 bridge_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.legacyViolations), static_cast(openAirAudit.routeSupportSamples), static_cast(openAirAudit.routeSupportViolations), @@ -1000,8 +1184,58 @@ namespace StandaloneStreamBenchmark for(std::size_t i=0;i::infinity(); + std::size_t nearestSegment=0; + double nearestT=0.0; + double nearestPassageSdf=std::numeric_limits::infinity(); + std::size_t nearestPassageSegment=0; + for(std::size_t segment=0;segment+10.0?std::clamp( + ((sample[0]/coreMetres-a[0])*dx+(sample[1]/coreMetres-a[1])*dy + +(sample[2]/coreMetres-a[2])*dz)/lengthSquared,0.0,1.0):0.0; + const double ex=sample[0]/coreMetres-(a[0]+t*dx); + const double ey=sample[1]/coreMetres-(a[1]+t*dy); + const double ez=sample[2]/coreMetres-(a[2]+t*dz); + const double distanceSquared=ex*ex+ey*ey+ez*ez; + if(distanceSquared0.0?std::clamp( + ((sample[0]-ax)*dx+(sample[1]-ay)*dy+(sample[2]-az)*dz) + /lengthSquared,0.0,1.0):0.0; + const double ex=sample[0]-(ax+t*dx),ey=sample[1]-(ay+t*dy), + ez=sample[2]-(az+t*dz); + const double radius=qToMetres(a.radiusSubvoxels) + +(qToMetres(b.radiusSubvoxels)-qToMetres(a.radiusSubvoxels))*t; + const double signedDistance=std::sqrt(ex*ex+ey*ey+ez*ez)-radius; + if(signedDistance(item.centers), + static_cast(item.unsupported), + static_cast(item.nativeUnsupported), + sample[0],sample[1],sample[2],bridgeAtSupport?1:0, + item.firstNativeSupportedProbes,item.firstNativeDensities[0],item.firstNativeDensities[1], + item.firstNativeDensities[2],item.firstNativeDensities[3], + blockedWorld.densityDispatchPathAt({firstX,firstY,firstZ}).c_str()); + } } std::uint64_t routePoints=0,routePasses=0,routeWalkPasses=0,routeBindings=0,routeBindingFailures=0; struct FRouteAuditInput @@ -1082,7 +1340,7 @@ namespace StandaloneStreamBenchmark } const FWalkAudit routeAudit=auditPlayerPath(Shape,route,supportStartMetres, supportStepMetres,supportMaximumMetres,minimumSupportProbes, - supportProbeRadiusMetres); + supportProbeRadiusMetres,nullptr,nullptr); routeAuditInputs.push_back({stratum.strateIndex,route,supportStartMetres, supportStepMetres,supportMaximumMetres,supportProbeRadiusMetres, minimumSupportProbes}); @@ -1151,7 +1409,7 @@ namespace StandaloneStreamBenchmark qToMetres(point.positionSubvoxels[1])/coreMetres,playerZ/256.0}); } const FWalkAudit blockedAudit=auditPlayerPath(blockedWorld,path, - 0.125,0.125,0.25,3,0.45); + 0.125,0.125,0.25,3,0.45,nullptr,nullptr); blockedControls+=blockedAudit.blocked>0&&!blockedAudit.passed; std::printf("[ResolvedPassageBlockedControl] id=%s blocked_capsule_samples=%llu detected=%s\n", passage.stableId.c_str(),static_cast(blockedAudit.blocked), @@ -1161,7 +1419,7 @@ namespace StandaloneStreamBenchmark { const FWalkAudit controlRoute=auditPlayerPath(blockedWorld,input.path, input.supportStart,input.supportStep,input.supportMaximum, - input.minimumSupportProbes,input.supportProbeRadius); + input.minimumSupportProbes,input.supportProbeRadius,nullptr,nullptr); std::printf("[ResolvedStrateRouteBlockedControl] strate=%d blocked=%llu unsupported=%llu status=%s\n", input.strateIndex,static_cast(controlRoute.blocked), static_cast(controlRoute.unsupported), @@ -1171,13 +1429,19 @@ 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 l24_negative_control=%s status=%s\n", + std::printf("[PassageOpenAirAuditSummary] interfaces=%llu samples=%llu violations=%llu bridge_samples=%llu l24_negative_control=%s status=%s\n", static_cast(openAirAudits), static_cast(openAirSamples), static_cast(openAirViolations), + static_cast(openAirBridgeSamples), 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 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"); @@ -1188,6 +1452,7 @@ namespace StandaloneStreamBenchmark const bool auditPassed=passageCount>0&&passagePasses==passageCount &&naturalPasses==passageCount&&l23SmoothWallDetected &&openAirAudits==passageCount&&openAirViolations==0&&l24OpenAirControlDetected + &&bridgeAuditsPassed &&routePasses>0&&routePasses==routeWalkPasses&&routePasses==routeBindings &&routeBindingFailures==0 &&blockedCompiled&&blockedControls==passageCount;