diff --git a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h index 67fdf10..8985f87 100644 --- a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h +++ b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h @@ -517,7 +517,8 @@ namespace VoxelForgeCore // v239 plans their floor planes as the passage's continuous floor profile. // v240 adds level end runs and rejects passage tube overlaps and hairpins. // v241 keeps passage composition sample-local and out of base-only X8 lanes. - inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 241; + // v242 composes layer content, air-only passage bores, and bounded landing pads. + inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 242; enum class FieldChannel : std::uint32_t { @@ -1331,16 +1332,6 @@ namespace VoxelForgeCore std::int64_t radiusSubvoxels = 0; }; - struct ResolvedLayoutPassageLandingChamberV1 - { - // The chamber floor plane passes through this point. Gradient is dz/dx, - // dz/dy in Q16 so the chamber continues the passage's authored h(s) grade. - std::array floorCenterSubvoxels{}; - std::array floorGradientQ16{}; - std::int64_t radiusSubvoxels = 0; - std::int64_t clearHeightSubvoxels = 0; - }; - inline std::int64_t ResolvedLayoutPassageLandingDiscRadiusSubvoxels( std::int64_t mouthRadiusSubvoxels, std::uint32_t coreVoxelSizeMicrometres) noexcept @@ -1376,34 +1367,6 @@ namespace VoxelForgeCore <=static_cast(halfWidthSubvoxels); } - // Shared landing-volume predicate for planners, the passage composer, and audits. - // Query coordinates use the same world subvoxel units as the resolved passage. - inline double ResolvedLayoutPassageLandingFloorAtXYSubvoxels( - const ResolvedLayoutPassageLandingChamberV1& chamber, - double xSubvoxels,double ySubvoxels) noexcept - { - return static_cast(chamber.floorCenterSubvoxels[2]) - +static_cast(chamber.floorGradientQ16[0])/65536.0 - *(xSubvoxels-static_cast(chamber.floorCenterSubvoxels[0])) - +static_cast(chamber.floorGradientQ16[1])/65536.0 - *(ySubvoxels-static_cast(chamber.floorCenterSubvoxels[1])); - } - - inline bool IsInsideResolvedLayoutPassageLandingChamberV1( - const ResolvedLayoutPassageLandingChamberV1& chamber, - double xSubvoxels,double ySubvoxels,double zSubvoxels) noexcept - { - if(chamber.radiusSubvoxels<=0||chamber.clearHeightSubvoxels<=0)return false; - const double dx=xSubvoxels-static_cast(chamber.floorCenterSubvoxels[0]); - const double dy=ySubvoxels-static_cast(chamber.floorCenterSubvoxels[1]); - const double radius=static_cast(chamber.radiusSubvoxels); - const double floor=ResolvedLayoutPassageLandingFloorAtXYSubvoxels( - chamber,xSubvoxels,ySubvoxels); - return dx*dx+dy*dy<=radius*radius - &&zSubvoxels>=floor - &&zSubvoxels<=floor+static_cast(chamber.clearHeightSubvoxels); - } - struct ResolvedLayoutPassageV1 { std::string stableId; @@ -1420,15 +1383,22 @@ namespace VoxelForgeCore std::int64_t endRunSubvoxels = 0; // Derived from the mouth binding; the main descent is monotone through this point. std::size_t lowerMouthControlIndex = 0; - ResolvedLayoutPassageLandingChamberV1 upperLandingChamber; - ResolvedLayoutPassageLandingChamberV1 lowerLandingChamber; - // When true, each control point's position.z - radius is the planned - // floor h(s), and both chamber planes continue that same endpoint profile. - // False preserves older resolved snapshots' runtime floor composition. - bool floorProfileIncludesLandingChambers = false; std::vector controlPoints; }; + inline std::array ResolvedLayoutPassageLandingPadCenterSubvoxels( + const ResolvedLayoutPassageV1& passage,bool upper) noexcept + { + const auto& mouth=upper?passage.upperMouthSubvoxels:passage.lowerMouthSubvoxels; + std::array center=mouth; + if(passage.controlPoints.empty())return center; + const std::size_t endIndex=upper?0: + std::min(passage.lowerMouthControlIndex,passage.controlPoints.size()-1); + const auto& endpoint=passage.controlPoints[endIndex]; + center[2]=endpoint.positionSubvoxels[2]-endpoint.radiusSubvoxels; + return center; + } + struct ResolvedLayoutPassageConfigV1 { std::int32_t upperStrateIndex = -1; @@ -2257,6 +2227,12 @@ namespace VoxelForgeCore float unclampedDensityAt(Vec3d voxelPosition) const; // Research-only source field entering passage composition, with passage carving omitted. float passageSourceDensityAt(Vec3d voxelPosition) const; + // Research-only signed distance to the passage floor shell, without other composition. + float passageFloorShellDistanceAt(Vec3d voxelPosition) const; + // Audit-only influence bounds in Core voxel coordinates, shared with tile routing. + bool resolvedPassageLandingPadInfluenceBounds(std::size_t passageIndex,bool upper, + std::array& minimumCoreVoxels, + std::array& maximumCoreVoxels) const; DetailFieldSample detailSampleAt(Vec3d voxelPosition) const; float densityAtLattice(Vec3i voxelPosition) const; bool isSolidAt(Vec3d voxelPosition) const; diff --git a/Standalone/Core/src/ResolvedLayoutInternal.h b/Standalone/Core/src/ResolvedLayoutInternal.h index 56e8e06..8d5b3db 100644 --- a/Standalone/Core/src/ResolvedLayoutInternal.h +++ b/Standalone/Core/src/ResolvedLayoutInternal.h @@ -653,22 +653,7 @@ namespace <<"],\"mouth_radius_q\":"<& mouth,const char* side) - { - const double playerCenterHeight=(side==std::string_view("upper") - &&host.generatorKind=="SurfaceWorld")?0.98:1.13; - const std::int64_t playerCenterHeightQ=static_cast( - std::llround(playerCenterHeight*WorldAssemblySubvoxelsPerVoxel - *1000000.0/std::max(1,coreVoxelSizeMicrometres))); - if(chamber.floorCenterSubvoxels[0]!=mouth[0] - ||chamber.floorCenterSubvoxels[1]!=mouth[1] - ||chamber.floorCenterSubvoxels[2]+playerCenterHeightQ!=mouth[2] - ||chamber.radiusSubvoxels<=0 - ||chamber.clearHeightSubvoxels<=0 - ||chamber.radiusSubvoxels( - std::llround(2.2*WorldAssemblySubvoxelsPerVoxel*1000000.0 - /std::max(1,coreVoxelSizeMicrometres)))) - { - add("passage_landing_chamber",passage.stableId+" "+side - +" landing chamber has invalid floor, radius, or capsule clearance"); - return; - } - const std::uint64_t gradientSquared=static_cast( - chamber.floorGradientQ16[0]*chamber.floorGradientQ16[0] - +chamber.floorGradientQ16[1]*chamber.floorGradientQ16[1]); - const std::int64_t gradientMagnitude=static_cast( - PassageIsqrt(gradientSquared)); - if(gradientMagnitude>17560) - add("passage_landing_grade",passage.stableId+" "+side - +" landing chamber floor exceeds 15 degrees"); - const std::int64_t planeReachQ=static_cast( - (chamber.radiusSubvoxels*gradientMagnitude+65535)/65536); - const std::int64_t shellQ=static_cast(std::llround( - 0.75*WorldAssemblySubvoxelsPerVoxel*1000000.0 - /std::max(1,coreVoxelSizeMicrometres))); - const auto qToUm=[&](std::int64_t q) - {return q*static_cast(coreVoxelSizeMicrometres) - /WorldAssemblySubvoxelsPerVoxel;}; - if(!host.content.presentInCurrentField - ||qToUm(chamber.floorCenterSubvoxels[2]-planeReachQ-shellQ) - host.content.topMicrometres) - add("passage_landing_content",passage.stableId+" "+side - +" landing chamber or plain shell leaves its host content band"); - }; - if(upper&&lower) - { - validateLandingChamber(passage.upperLandingChamber,*upper, - passage.upperMouthSubvoxels,"upper"); - validateLandingChamber(passage.lowerLandingChamber,*lower, - passage.lowerMouthSubvoxels,"lower"); - } if(passage.controlPoints.size()<2)continue; const std::int64_t PlayerCenterAboveFloorQ=coreVoxelSizeMicrometres>0 ?static_cast(std::llround(1.13*WorldAssemblySubvoxelsPerVoxel @@ -2113,27 +2042,10 @@ std::optional ResolvedLayoutSnapshotV3::FromJson( p.placementRadiusSubvoxels=LayoutInt(v,"placement_radius_q"); p.mouthRadiusSubvoxels=LayoutInt(v,"mouth_radius_q");p.midRadiusSubvoxels=LayoutInt(v,"mid_radius_q"); p.endRunSubvoxels=LayoutInt(v,"end_run_q"); - p.floorProfileIncludesLandingChambers= - LayoutBool(v,"floor_profile_includes_landing_chambers"); const auto& upper=v.Get("upper_mouth_q").AsArray(); const auto& lower=v.Get("lower_mouth_q").AsArray(); for(std::size_t axis=0;axis<3&&axis& authoredRouteTangentQ16, std::int64_t authoredFloorSlopeQ16,const FPoint& mouthFloorPoint, - std::int64_t chamberRadiusMicrometres) + std::int64_t landingRadiusMicrometres) { constexpr std::array,16> DirectionsQ16={{ {{65536,0}},{{60547,25080}},{{46341,46341}},{{25080,60547}}, @@ -2959,7 +2871,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout, /(65536ll*65536ll)); constexpr std::int64_t MouthClearanceProbeExtent=48000000; const std::int64_t firstClearanceProbe=std::max( - 2000000,chamberRadiusMicrometres+1000000); + 2000000,landingRadiusMicrometres+1000000); for(std::int64_t distance=firstClearanceProbe; distance<=MouthClearanceProbeExtent;distance+=2000000) { @@ -3057,13 +2969,13 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout, bool lowerSide,FPoint& point,std::array& routeTangentQ16, std::int64_t& floorSlopeQ16) { - const auto& chamber=lowerSide?passage.lowerLandingChamber - :passage.upperLandingChamber; - for(std::size_t axis=0;axis<3;++axis) - point[axis]=qToMicrometres(chamber.floorCenterSubvoxels[axis]); if(passage.controlPoints.size()<2)return false; const std::size_t mouthIndex=lowerSide ?std::min(passage.lowerMouthControlIndex,passage.controlPoints.size()-1):0; + const auto& mouth=lowerSide?passage.lowerMouthSubvoxels + :passage.upperMouthSubvoxels; + point[0]=qToMicrometres(mouth[0]); + point[1]=qToMicrometres(mouth[1]); const std::size_t tangentIndex=lowerSide ?(mouthIndex>0?mouthIndex-1:1):1; // The lower arrival continues from the previous passage point through its @@ -3080,26 +2992,23 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout, if(tangentLength<=0)return false; routeTangentQ16={tx*65536/tangentLength,ty*65536/tangentLength}; const auto& mouthControl=passage.controlPoints[mouthIndex]; - const std::int64_t mouthFloor=chamber.floorCenterSubvoxels[2]; + const std::int64_t mouthFloor=mouthControl.positionSubvoxels[2] + -mouthControl.radiusSubvoxels; point[2]=qToMicrometres(mouthFloor); - floorSlopeQ16=(chamber.floorGradientQ16[0]*routeTangentQ16[0] - +chamber.floorGradientQ16[1]*routeTangentQ16[1])/65536; + floorSlopeQ16=0; const auto authoredRouteTangentQ16=routeTangentQ16; routeTangentQ16=chooseMouthRouteTangent(passage,mouthIndex,lowerSide, authoredRouteTangentQ16,floorSlopeQ16,point, - qToMicrometres(chamber.radiusSubvoxels)); - floorSlopeQ16=(chamber.floorGradientQ16[0]*routeTangentQ16[0] - +chamber.floorGradientQ16[1]*routeTangentQ16[1])/65536; + qToMicrometres(ResolvedLayoutPassageLandingDiscRadiusSubvoxels( + passage.mouthRadiusSubvoxels,layout.coreVoxelSizeMicrometres))); const std::int64_t offsetDirection=lowerSide?1:-1; const std::int64_t portOffset=std::max(0, - qToMicrometres(chamber.radiusSubvoxels)-LandingConnectorBoreRadius); + qToMicrometres(ResolvedLayoutPassageLandingDiscRadiusSubvoxels( + passage.mouthRadiusSubvoxels,layout.coreVoxelSizeMicrometres)) + -LandingConnectorBoreRadius); point[0]+=offsetDirection*routeTangentQ16[0]*portOffset/65536; point[1]+=offsetDirection*routeTangentQ16[1]*portOffset/65536; - point[2]=qToMicrometres(mouthFloor) - +(chamber.floorGradientQ16[0]*(point[0] - -qToMicrometres(chamber.floorCenterSubvoxels[0])) - +chamber.floorGradientQ16[1]*(point[1] - -qToMicrometres(chamber.floorCenterSubvoxels[1])))/65536; + point[2]=qToMicrometres(mouthFloor); return true; }; FPoint routeStart{},routeEnd{}; @@ -3932,7 +3841,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout, const auto arrivalPoint=routeStart; FPoint previousPoint=arrivalPoint; std::int64_t previousProfileDistance=0; - std::string previousAnchor=arrivalPassage->stableId+"/lower-landing-chamber"; + std::string previousAnchor=arrivalPassage->stableId+"/lower-landing-pad"; for(std::size_t nodeIndex=0;nodeIndexstableId+"/upper-landing-chamber", + departurePassage->stableId+"/upper-landing-pad", routeEnd,nodes.back(), std::min(selectedBoreRadius(departureEdgeId),LandingConnectorBoreRadius), totalRouteLength,nodes.back().floorProfileDistanceMicrometres); @@ -4375,17 +4284,21 @@ static FPassageTubeSelfOverlapCensus CensusPassageTubeSelfOverlaps( passage.mouthRadiusSubvoxels+qPerMetre)+qPerMetre); const std::int64_t landingDiscRadius=ResolvedLayoutPassageLandingDiscRadiusSubvoxels( passage.mouthRadiusSubvoxels,coreVoxelSizeMicrometres); - const auto overlapsLandingColumn=[&]( - const ResolvedLayoutPassageLandingChamberV1& chamber, - const std::array& landingMouth, - const std::array& center) + const std::int64_t landingHeadroom=static_cast( + std::llround(4.5*qPerMetre)); + const auto overlapsLandingColumn=[&](bool upper,const std::array& center) { - if(chamber.radiusSubvoxels<=0||chamber.clearHeightSubvoxels<=0)return false; - const double floor=static_cast(chamber.floorCenterSubvoxels[2]); - const double ceiling=floor+static_cast(chamber.clearHeightSubvoxels); + if(landingDiscRadius<=0||passage.controlPoints.size()<2)return false; + const std::size_t endpointIndex=upper?0: + std::min(passage.lowerMouthControlIndex,passage.controlPoints.size()-1); + const auto& endpoint=passage.controlPoints[endpointIndex]; + const double floor=static_cast(endpoint.positionSubvoxels[2] + -endpoint.radiusSubvoxels); + const double ceiling=floor+static_cast(landingHeadroom); if(center[2]ceiling)return false; + const auto& mouth=upper?passage.upperMouthSubvoxels:passage.lowerMouthSubvoxels; return IsInsideResolvedLayoutPassageLandingDiscV1( - {landingMouth[0],landingMouth[1]},landingDiscRadius,center[0],center[1]); + {mouth[0],mouth[1]},landingDiscRadius,center[0],center[1]); }; for(std::size_t segment=0;segment+1 center={a[0]+t*(b[0]-a[0]), a[1]+t*(b[1]-a[1]),a[2]+t*(b[2]-a[2])}; - upperColumnViolation|=overlapsLandingColumn(passage.upperLandingChamber, - passage.upperMouthSubvoxels,center); - lowerColumnViolation|=overlapsLandingColumn(passage.lowerLandingChamber, - passage.lowerMouthSubvoxels,center); + upperColumnViolation|=overlapsLandingColumn(true,center); + lowerColumnViolation|=overlapsLandingColumn(false,center); if(upperColumnViolation&&lowerColumnViolation)break; } if((upperColumnViolation||lowerColumnViolation) @@ -4867,72 +4778,6 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t } } } - // Reserve each landing envelope inside its host band before building h(s). - // SurfaceWorld can raise its floor by at most the 4.5 m headroom allowance, - // so include that bounded rise as well as the grade reach and floor shell. - constexpr std::int64_t MaximumLandingGradeQ16=17560; - const std::int64_t landingRadius=mouthRadius+3*qPerMetre; - const std::int64_t landingClearHeight=static_cast( - std::llround(4.5*qPerMetre)); - const std::int64_t landingShell=static_cast( - std::llround(0.75*qPerMetre)); - const std::int64_t landingPlaneReach=(landingRadius - *MaximumLandingGradeQ16+65535)/65536; - const std::array upperMouthBeforeBandFit=upperMouth; - const std::array lowerMouthBeforeBandFit=lowerMouth; - const auto fitMouth=[&](std::array& mouth, - const ResolvedLayoutStratumV2& host,std::int64_t playerHeightQ, - const char* side) - { - if(!host.content.presentInCurrentField)return true; - const std::int64_t minimumFloor=contentZQ(host.content.bottomMicrometres) - +landingPlaneReach+landingShell; - const std::int64_t nativeFloorRise=host.generatorKind=="SurfaceWorld" - ?landingClearHeight:0; - const std::int64_t maximumFloor=contentZQ(host.content.topMicrometres) - -landingPlaneReach-landingShell-landingClearHeight-nativeFloorRise; - if(maximumFloor(qPerMetre)); - } - return true; - }; - if(!fitMouth(upperMouth,upper,upperPlayerCenterAboveFloorQ,"upper") - ||!fitMouth(lowerMouth,lower,playerCenterAboveFloorQ,"lower"))return false; - const auto bindFittedMouthAnchor=[&]( - const ResolvedLayoutStratumV2& host, - const std::array& before, - const std::array& after,const char* side) - { - if(before==after)return; - auto mutableHost=std::find_if(layout.strates.begin(),layout.strates.end(), - [&](const auto& stratum) - {return stratum.strateIndex==host.strateIndex;}); - if(mutableHost!=layout.strates.end()) - for(auto& anchor:mutableHost->anchors) - if((anchor.kind=="passage_arrival" - ||anchor.kind=="passage_departure") - &&anchor.values.size()>=3 - &&anchor.values[0]==after[0] - &&anchor.values[1]==after[1]) - { - anchor.values[2]=after[2]; - std::printf("[PassageLandingChamberArrivalBinding] id=%s side=%s anchor=%s status=PASS\n", - passage.stableId.c_str(),side,anchor.stableId.c_str()); - break; - } - }; - bindFittedMouthAnchor(upper,upperMouthBeforeBandFit,upperMouth,"upper"); - bindFittedMouthAnchor(lower,lowerMouthBeforeBandFit,lowerMouth,"lower"); if(!radialWithin(upperMouth,placementRadius)||!radialWithin(lowerMouth,placementRadius)) {error=passage.stableId+" route mouth is outside the resolved PassageConfig.DistanceMax radius";return false;} passage.upperMouthSubvoxels=upperMouth;passage.lowerMouthSubvoxels=lowerMouth; @@ -6007,18 +5852,18 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t passage.controlPoints[lowerMouthIndex].positionSubvoxels[2]=lowerMouth[2] +passage.controlPoints[lowerMouthIndex].radiusSubvoxels -playerCenterAboveFloorQ; - std::vector chamberFloorControls; - std::vector chamberFloorProfile; - chamberFloorControls.reserve(passage.controlPoints.size()); - chamberFloorProfile.reserve(passage.controlPoints.size()); + std::vector endpointFloorControls; + std::vector endpointFloorProfile; + endpointFloorControls.reserve(passage.controlPoints.size()); + endpointFloorProfile.reserve(passage.controlPoints.size()); for(const auto& point:passage.controlPoints) { - chamberFloorControls.push_back({ + endpointFloorControls.push_back({ static_cast(point.positionSubvoxels[0]), static_cast(point.positionSubvoxels[1]), static_cast(point.positionSubvoxels[2]), static_cast(point.radiusSubvoxels)}); - chamberFloorProfile.push_back(static_cast( + endpointFloorProfile.push_back(static_cast( point.positionSubvoxels[2]-point.radiusSubvoxels)); } const auto routePoints=[&](const ResolvedLayoutStratumV2& host) @@ -6039,12 +5884,12 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t route.erase(route.begin(),route.end()-2); double routeHeight=0.0,distanceSquared=0.0; if(VoxelForgeCore::PassageSpline::ClosestRouteHeightAtXY(route, - chamberFloorControls.front()[0],chamberFloorControls.front()[1], + endpointFloorControls.front()[0],endpointFloorControls.front()[1], routeHeight,distanceSquared) - &&distanceSquared<=std::pow(chamberFloorControls.front()[3] + &&distanceSquared<=std::pow(endpointFloorControls.front()[3] +2.0*qPerMetre,2.0)) VoxelForgeCore::PassageSpline::BlendEndpointFloorToRoute( - chamberFloorControls,chamberFloorProfile,0,route,qPerMetre, + endpointFloorControls,endpointFloorProfile,0,route,qPerMetre, upper.generatorKind=="SurfaceWorld"?0.98:1.13); } if(lower.generatorKind=="TunnelNetwork" @@ -6054,31 +5899,30 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t if(route.size()>2)route.resize(2); double routeHeight=0.0,distanceSquared=0.0; if(VoxelForgeCore::PassageSpline::ClosestRouteHeightAtXY(route, - chamberFloorControls[lowerMouthIndex][0], - chamberFloorControls[lowerMouthIndex][1], + endpointFloorControls[lowerMouthIndex][0], + endpointFloorControls[lowerMouthIndex][1], routeHeight,distanceSquared) &&distanceSquared<=std::pow( - chamberFloorControls[lowerMouthIndex][3] + endpointFloorControls[lowerMouthIndex][3] +2.0*qPerMetre,2.0)) VoxelForgeCore::PassageSpline::BlendEndpointFloorToRoute( - chamberFloorControls,chamberFloorProfile, + endpointFloorControls,endpointFloorProfile, lowerMouthIndex,route,qPerMetre); } - const double upperEndRunFloor=chamberFloorProfile.front(); - const double lowerEndRunFloor=chamberFloorProfile[lowerMouthIndex]; - for(std::size_t i=0;i=lowerFlatStartDistance) - chamberFloorProfile[i]=lowerEndRunFloor; + endpointFloorProfile[i]=lowerEndRunFloor; } - // Commit the endpoint route fit into h(s) before chamber records are - // resolved. The passage field and both landing planes then consume the - // same deterministic profile instead of blending the chamber in later. + // Store route binding and level end runs in h(s), then use that profile + // directly for the bore and its landing-pad stamp. for(std::size_t i=0;i( - std::llround(chamberFloorProfile[i])) + std::llround(endpointFloorProfile[i])) +passage.controlPoints[i].radiusSubvoxels; const auto endRunAudit=AuditResolvedLayoutPassageEndRunsV1( passage,layout.coreVoxelSizeMicrometres,endRunLength); @@ -6090,117 +5934,6 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t endRunAudit.startMaximumGradeDegrees, endRunAudit.endMaximumGradeDegrees, endRunAudit.passed?"PASS":"FAIL"); - const auto resolveLandingChamber=[&]( - ResolvedLayoutPassageLandingChamberV1& chamber, - const std::array& mouth,std::size_t endpointIndex, - std::size_t neighborIndex,const ResolvedLayoutStratumV2& host, - const char* side) - { - chamber.floorCenterSubvoxels=mouth; - chamber.radiusSubvoxels=mouthRadius+3*qPerMetre; - chamber.clearHeightSubvoxels=static_cast( - std::llround(4.5*qPerMetre)); - const auto& endpoint=passage.controlPoints[endpointIndex]; - const auto& neighbor=passage.controlPoints[neighborIndex]; - const std::int64_t endpointFloor=static_cast( - std::llround(chamberFloorProfile[endpointIndex])); - const std::int64_t neighborFloor=static_cast( - std::llround(chamberFloorProfile[neighborIndex])); - chamber.floorCenterSubvoxels[2]=endpointFloor; - const std::int64_t dx=endpoint.positionSubvoxels[0] - -neighbor.positionSubvoxels[0]; - const std::int64_t dy=endpoint.positionSubvoxels[1] - -neighbor.positionSubvoxels[1]; - const std::int64_t dz=endpointFloor-neighborFloor; - const std::int64_t horizontalSquared=dx*dx+dy*dy; - if(horizontalSquared<=0) - {error=passage.stableId+" "+side+" landing chamber has no endpoint tangent";return false;} - chamber.floorGradientQ16={ - PassageRoundRatio(dx<0?-dz:dz,std::abs(dx)*65536, - horizontalSquared), - PassageRoundRatio(dy<0?-dz:dz,std::abs(dy)*65536, - horizontalSquared)}; - if(host.generatorKind=="SurfaceWorld"&&side[0]=='u') - { - // The surface route enters the outer half of this chamber. Fit - // its landing plane to that receiving route, while keeping its - // center height equal to the passage's planned h(s) endpoint. - const auto surfaceRoute=routePoints(host); - const double radiusSquared=static_cast( - chamber.radiusSubvoxels)*chamber.radiusSubvoxels; - double farthestDistanceSquared=0.0; - std::array farthestPoint{}; - for(const auto& routePoint:surfaceRoute) - { - const double routeDx=routePoint[0]-mouth[0]; - const double routeDy=routePoint[1]-mouth[1]; - const double distanceSquared=routeDx*routeDx+routeDy*routeDy; - if(distanceSquared<=radiusSquared - &&distanceSquared>farthestDistanceSquared) - { - farthestDistanceSquared=distanceSquared; - farthestPoint=routePoint; - } - } - double mouthRouteHeight=0.0,mouthRouteDistanceSquared=0.0; - if(farthestDistanceSquared>0.0 - &&VoxelForgeCore::PassageSpline::ClosestRouteHeightAtXY( - surfaceRoute,static_cast(mouth[0]), - static_cast(mouth[1]),mouthRouteHeight, - mouthRouteDistanceSquared)) - { - const std::int64_t surfaceDx=static_cast( - std::llround(farthestPoint[0]-mouth[0])); - const std::int64_t surfaceDy=static_cast( - std::llround(farthestPoint[1]-mouth[1])); - const std::int64_t surfaceDz=static_cast( - std::llround(farthestPoint[2]-mouthRouteHeight)); - const std::int64_t surfaceHorizontalSquared=surfaceDx*surfaceDx - +surfaceDy*surfaceDy; - if(surfaceHorizontalSquared>0) - chamber.floorGradientQ16={ - PassageRoundRatio(surfaceDx<0?-surfaceDz:surfaceDz, - std::abs(surfaceDx)*65536, - surfaceHorizontalSquared), - PassageRoundRatio(surfaceDy<0?-surfaceDz:surfaceDz, - std::abs(surfaceDy)*65536, - surfaceHorizontalSquared)}; - } - } - const std::uint64_t gradientSquared=static_cast( - chamber.floorGradientQ16[0]*chamber.floorGradientQ16[0] - +chamber.floorGradientQ16[1]*chamber.floorGradientQ16[1]); - const std::int64_t gradientMagnitude=static_cast( - PassageIsqrt(gradientSquared)); - constexpr std::int64_t MaximumLandingGradeQ16=17560; - if(gradientMagnitude>MaximumLandingGradeQ16) - {error=passage.stableId+" "+side+" landing chamber floor exceeds 15 degrees";return false;} - const std::int64_t planeReachQ=PassageRoundRatio( - chamber.radiusSubvoxels,gradientMagnitude,65536); - const std::int64_t shellQ=static_cast( - std::llround(0.75*qPerMetre)); - const auto qToUm=[&](std::int64_t q) - {return q*static_cast(layout.coreVoxelSizeMicrometres) - /WorldAssemblySubvoxelsPerVoxel;}; - if(!host.content.presentInCurrentField - ||qToUm(chamber.floorCenterSubvoxels[2]-planeReachQ-shellQ) - host.content.topMicrometres) - { - error=passage.stableId+" "+side - +" landing chamber does not fit inside its host content band"; - return false; - } - return true; - }; - if(!resolveLandingChamber(passage.upperLandingChamber,upperMouth,0,1, - upper,"upper") - ||!resolveLandingChamber(passage.lowerLandingChamber,lowerMouth, - lowerMouthIndex,lowerMouthIndex-1,lower,"lower")) - return false; - passage.floorProfileIncludesLandingChambers=true; const auto overlapCensus=AuditResolvedLayoutPassageSelfOverlapV1( passage,layout.coreVoxelSizeMicrometres); std::printf("[PassageTubeSelfOverlapCensus] id=%s segment_pairs=%llu measured_pairs=%llu local_exempt_pairs=%llu overlap_pairs=%llu first_overlap_segments=(%llu,%llu) end_turn_max_deg=%.3f end_turn_violations=%u landing_column_violations=%llu minimum_clearance_m=%.6f status=%s\n", @@ -6256,14 +5989,20 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t endRunLength/static_cast(qPerMetre)); } } - std::printf("[PassageLandingChamberPlan] id=%s upper_radius_m=%.3f lower_radius_m=%.3f clear_height_m=4.500 upper_floor_grade_q16=(%lld,%lld) lower_floor_grade_q16=(%lld,%lld) status=PASS\n", + std::printf("[PassageLandingPadPlan] id=%s upper_radius_m=%.3f lower_radius_m=%.3f upper_floor_z_m=%.3f lower_floor_z_m=%.3f source=h(s) status=PASS\n", passage.stableId.c_str(), - passage.upperLandingChamber.radiusSubvoxels/static_cast(qPerMetre), - passage.lowerLandingChamber.radiusSubvoxels/static_cast(qPerMetre), - static_cast(passage.upperLandingChamber.floorGradientQ16[0]), - static_cast(passage.upperLandingChamber.floorGradientQ16[1]), - static_cast(passage.lowerLandingChamber.floorGradientQ16[0]), - static_cast(passage.lowerLandingChamber.floorGradientQ16[1])); + ResolvedLayoutPassageLandingDiscRadiusSubvoxels( + passage.mouthRadiusSubvoxels,layout.coreVoxelSizeMicrometres) + /static_cast(qPerMetre), + ResolvedLayoutPassageLandingDiscRadiusSubvoxels( + passage.mouthRadiusSubvoxels,layout.coreVoxelSizeMicrometres) + /static_cast(qPerMetre), + (passage.controlPoints.front().positionSubvoxels[2] + -passage.controlPoints.front().radiusSubvoxels) + /static_cast(qPerMetre), + (passage.controlPoints[passage.lowerMouthControlIndex].positionSubvoxels[2] + -passage.controlPoints[passage.lowerMouthControlIndex].radiusSubvoxels) + /static_cast(qPerMetre)); layout.passages.push_back(std::move(passage)); } } diff --git a/Standalone/Core/src/ShapeWorldInternal.h b/Standalone/Core/src/ShapeWorldInternal.h index 2dfd37e..9fc4c2a 100644 --- a/Standalone/Core/src/ShapeWorldInternal.h +++ b/Standalone/Core/src/ShapeWorldInternal.h @@ -56,6 +56,11 @@ static bool VF_IsL130PassageReachProofAuditEnabled() static constexpr double VF_PassageShellThicknessMetres=0.75; static constexpr double VF_PassageWallReliefMetres=0.21; static constexpr double VF_PassageCornerBlendMetres=0.50; +static constexpr double VF_LandingPadConeSlope=0.2125565616700221; +static constexpr double VF_LandingPadFillDepthMetres=1.25; +static constexpr double VF_LandingPadHeadroomMetres=3.0; +static constexpr double VF_LandingPadBlendRadiusMetres=8.0; +static constexpr double VF_LandingPadSmoothMinMaximumMetres=0.50; static constexpr double VF_PassageReachMarginMetres=0.01; // Radial support in metres: shell + maximum relief + the smooth-max support // (corner-blend width / 6) + a 1 cm guard for float and bound rounding. @@ -265,7 +270,7 @@ public: struct FPassageSweepSample { double signedDistance=std::numeric_limits::infinity(); - double shellSignedDistance=std::numeric_limits::infinity(); + double floorShellSignedDistance=std::numeric_limits::infinity(); double roundSignedDistance=std::numeric_limits::infinity(); double centerZSubvoxels=0.0,floorCenterZSubvoxels=0.0; double radiusSubvoxels=0.0,floorDepthSubvoxels=0.0; @@ -1245,8 +1250,10 @@ public: float ApplyResolvedStratePassageCarves(float density,float X,float Y,float Z, float nativeTunnelFieldDensity=std::numeric_limits::lowest(), - float nativeFloorSupport=std::numeric_limits::lowest()) const + float* outTubeOnlyDistance=nullptr,float* outFloorShellDistance=nullptr) const { + if(outTubeOnlyDistance)*outTubeOnlyDistance=std::numeric_limits::infinity(); + if(outFloorShellDistance)*outFloorShellDistance=std::numeric_limits::infinity(); VoxelForgeCore::Linux77Profiler::FScopedPhase l77PassagePhase( VoxelForgeCore::Linux77Profiler::EPhase::Passage); if(!ResolvedLayout||ResolvedLayout->passages.empty()||!std::isfinite(X) @@ -1268,8 +1275,6 @@ public: constexpr double Q=static_cast(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); constexpr double FloorShellThicknessMetres=VF_PassageShellThicknessMetres; - constexpr double WallReliefMetres=VF_PassageWallReliefMetres; - constexpr double OpenAirSideWallHeightMetres=1.0; constexpr double MouthShellBlendMetres=5.0; const double qPerMetre=Q*1000000.0/ std::max(1,ResolvedLayout->coreVoxelSizeMicrometres); @@ -1279,7 +1284,6 @@ public: struct FNearestTubeSample { double signedDistance=std::numeric_limits::infinity(); - double shellSignedDistance=std::numeric_limits::infinity(); double floorShellSignedDistance=std::numeric_limits::infinity(); double roundSignedDistance=std::numeric_limits::infinity(); double centerZ=0.0,floorCenterZ=0.0,radius=0.0,floorDepth=0.0; @@ -1289,11 +1293,7 @@ public: const FScopedPassageTileQueries::FPassageSweepSample* swept=nullptr; }; double unionDistance=std::numeric_limits::infinity(); - double unionShellDistance=std::numeric_limits::infinity(); double unionFloorShellDistance=std::numeric_limits::infinity(); - double unionLandingChamberFloorShellDistance= - std::numeric_limits::infinity(); - double plannedTunnelPassageShellDistance=std::numeric_limits::infinity(); const std::uint32_t reliefSeed=static_cast(Owner?Owner->Seed:0); const auto* queryStratum=FindBoundStratumForManagerZ(Z); const bool plannedTunnelStratum=queryStratum&&queryStratum->Resolved @@ -1310,7 +1310,7 @@ public: const std::size_t passageIndex=passageTileScopeMatches ?(*routePassageIndices)[routeOffset]:routeOffset; // A passage can be in this tile's list because a different route segment - // overlaps the tile. Check the sample before its landing chamber is composed. + // overlaps the tile. Check the sample before its landing pad is composed. if(passageTileScopeMatches &&!passageTileQueries->routeFeatureMayAffect(passageIndex,qx,qy,qz)) { @@ -1339,52 +1339,10 @@ public: const double mouthShellBlendMetres=passageTargetsPlannedTunnelNetwork ?2.0:MouthShellBlendMetres; // Legacy native cave fields own the tunnel bore on their lower-strate - // boundary. The shared passage composer still owns its landing chamber. + // boundary. The shared passage composer still owns landing-pad stamps. if(passageProfile)++passageProfile->passageIterations; const auto& constants=PassageComposerConstants[passageIndex]; - const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1* landingChamber=nullptr; - if(queryStratum&&queryStratum->Resolved) - { - if(queryStratum->StrateIndex==passage.upperStrateIndex) - landingChamber=&passage.upperLandingChamber; - else if(queryStratum->StrateIndex==passage.lowerStrateIndex) - landingChamber=&passage.lowerLandingChamber; - } - if(landingChamber&&landingChamber->radiusSubvoxels>0 - &&landingChamber->clearHeightSubvoxels>0) - { - const double centerX=static_cast( - landingChamber->floorCenterSubvoxels[0]); - const double centerY=static_cast( - landingChamber->floorCenterSubvoxels[1]); - const double floorCenterZ=static_cast( - landingChamber->floorCenterSubvoxels[2]) - +(landingChamber->floorGradientQ16[0]*(qx-centerX) - +landingChamber->floorGradientQ16[1]*(qy-centerY))/65536.0; - const double radialDistance=DeterministicSqrt( - (qx-centerX)*(qx-centerX)+(qy-centerY)*(qy-centerY)) - -landingChamber->radiusSubvoxels; - const double floorDistance=floorCenterZ-qz; - const double ceilingDistance=qz-(floorCenterZ - +landingChamber->clearHeightSubvoxels); - const double chamberDistance=std::max({radialDistance, - floorDistance,ceilingDistance}); - const double chamberShellDistance=std::max(-chamberDistance, - chamberDistance-shellThickness); - const double chamberFloorShellDistance=std::max( - std::max(-floorDistance,floorDistance-shellThickness), - radialDistance); - unionDistance=std::min(unionDistance,chamberDistance); - unionShellDistance=std::min(unionShellDistance,chamberShellDistance); - unionFloorShellDistance=std::min(unionFloorShellDistance, - chamberFloorShellDistance); - unionLandingChamberFloorShellDistance=std::min( - unionLandingChamberFloorShellDistance,chamberFloorShellDistance); - if(passageTargetsPlannedTunnelNetwork) - plannedTunnelPassageShellDistance=std::min( - plannedTunnelPassageShellDistance,chamberShellDistance); - } if(passageTargetsNativeCaveField&&!passageTargetsPlannedTunnelNetwork) continue; @@ -1396,7 +1354,6 @@ public: if(swept) { nearest.signedDistance=swept->signedDistance; - nearest.shellSignedDistance=swept->shellSignedDistance; nearest.roundSignedDistance=swept->roundSignedDistance; nearest.segmentIndex=swept->segmentIndex; nearest.t=swept->t; @@ -1411,10 +1368,8 @@ public: if(!swept->hasExactContribution) { // The prepared sentinel is farther from the route than the - // maximum body, relief, shell, and blend support. Preserve its - // exact flat distance in the passage union; its wall and floor - // shell bounds are positive, so the rest of the composer has - // no effect for this passage sample. + // maximum body, relief, and blend support. Preserve its exact + // flat distance in the passage union. const double outsideDistance=PassageFlatBottomDistance( static_cast(swept->roundSignedDistance), swept->floorCenterZSubvoxels,qz, @@ -1468,7 +1423,7 @@ public: constants,segmentIndex,floorTClamped,floorRadius,qPerMetre); const double floorCenterZ=PassageFlatFloorCenterZSubvoxels( constants,segments,segmentIndex,floorT,segment, - qx,qy,floorDepth,qPerMetre); + floorDepth,qPerMetre); const double flatDistance=PassageFlatBottomDistance( static_cast(roundDistance),floorCenterZ,qz,floorDepth,qPerMetre); // The profile uses the closest route projection's arc length. Tube @@ -1508,10 +1463,8 @@ public: static_cast(nearest.roundSignedDistance), nearest.floorCenterZ,qz,nearest.floorDepth,qPerMetre); - // Shells form a union over nearby route segments, even when another - // segment supplies the nearest interior distance. The hierarchy bound - // is deliberately wider than the shell so its radius and corner blend - // cannot prune a segment that contributes a boundary shell. + // Floor shells form a union over nearby route segments, even when + // another segment supplies the nearest interior distance. double shellSearchBound=2.0*qPerMetre; const auto visitShellSegment=[&](std::size_t segmentIndex,int) { @@ -1539,7 +1492,7 @@ public: constants,segmentIndex,floorTClamped,floorRadius,qPerMetre); const double floorCenterZ=PassageFlatFloorCenterZSubvoxels( constants,segments,segmentIndex,floorT,segment, - qx,qy,floorDepth,qPerMetre); + floorDepth,qPerMetre); const double flatDistance=PassageFlatBottomDistance( static_cast(roundDistance),floorCenterZ,qz,floorDepth,qPerMetre); const double alongMetres=segment.authoredAlongMetres @@ -1555,10 +1508,6 @@ public: const double floorBlend=std::clamp(floorDistanceMetres/0.25,0.0,1.0); const double smoothFloorBlend=floorBlend*floorBlend *(3.0-2.0*floorBlend); - const double wallElevationMetres=(qz-(floorCenterZ-floorDepth)) - /qPerMetre; - const double mouthWallThickness=segment.routeSegment - ?smoothShellFade*shellThickness:0.0; double floorExtensionFade=1.0; if(!segment.routeSegment) { @@ -1571,23 +1520,11 @@ public: } const double floorShellThickness=floorThicknessWithLanding( smoothFloorBlend*(shellThickness - +(passageTargetsPlannedTunnelNetwork?0.25*qPerMetre:0.0)) - *floorExtensionFade - *smoothShellFade, + +(passageTargetsPlannedTunnelNetwork?0.25*qPerMetre:0.0)) + *floorExtensionFade*smoothShellFade, smoothFloorBlend,alongMetres); - const double wallShellDistance=PassageRoundedWallShellDistance( - flatDistance,wallElevationMetres*qPerMetre, - OpenAirSideWallHeightMetres*qPerMetre,mouthWallThickness); const double floorShellDistance=std::max(-static_cast(flatDistance), static_cast(flatDistance)-floorShellThickness); - const double localShellDistance=std::min(wallShellDistance, - floorShellDistance); - if(passageTargetsPlannedTunnelNetwork - &&mouthDistanceMetres>=mouthShellBlendMetres) - plannedTunnelPassageShellDistance=std::min( - plannedTunnelPassageShellDistance,localShellDistance); - nearest.shellSignedDistance=std::min(nearest.shellSignedDistance, - localShellDistance); nearest.floorShellSignedDistance=std::min( nearest.floorShellSignedDistance,floorShellDistance); }; @@ -1597,15 +1534,6 @@ public: visitShellSegment); } if(!nearest.valid)continue; - const double wallElevationMetres=(qz-(nearest.floorCenterZ - -nearest.floorDepth))/qPerMetre; - double distanceToCenter=std::max(1.0e-9, - nearest.roundSignedDistance+nearest.radius); - const double verticalWallAlignment=distanceToCenter>1.0e-9 - ?std::clamp(-(qz-nearest.centerZ)/distanceToCenter,0.0,1.0):0.0; - const double crestFade=std::clamp((OpenAirSideWallHeightMetres - -wallElevationMetres)/0.25,0.0,1.0); - const double smoothCrestFade=crestFade*crestFade*(3.0-2.0*crestFade); const auto& segment=PassageSegmentData[passageIndex][nearest.segmentIndex]; const double alongMetres=segment.authoredAlongMetres +nearest.t*segment.authoredLengthMetres; @@ -1614,6 +1542,14 @@ public: const double shellFade=std::clamp(mouthDistanceMetres/ mouthShellBlendMetres,0.0,1.0); const double smoothShellFade=shellFade*shellFade*(3.0-2.0*shellFade); + const double wallElevationMetres=(qz-(nearest.floorCenterZ + -nearest.floorDepth))/qPerMetre; + const double distanceToCenter=std::max(1.0e-9, + nearest.roundSignedDistance+nearest.radius); + const double verticalWallAlignment=std::clamp( + -(qz-nearest.centerZ)/distanceToCenter,0.0,1.0); + const double crestFade=std::clamp((1.0-wallElevationMetres)/0.25,0.0,1.0); + const double smoothCrestFade=crestFade*crestFade*(3.0-2.0*crestFade); const double wallReliefWeight=(0.05+0.95*(1.0-verticalWallAlignment)) *smoothCrestFade; float relief=0.0f; @@ -1628,20 +1564,18 @@ public: } const double roughRoundDistance=nearest.roundSignedDistance +static_cast(std::clamp(relief,-1.0f,1.0f)) - *WallReliefMetres*qPerMetre*wallReliefWeight; + *VF_PassageWallReliefMetres*qPerMetre*wallReliefWeight; const double shapedDistance=PassageFlatBottomDistance( static_cast(roughRoundDistance),nearest.floorCenterZ,qz, nearest.floorDepth,qPerMetre); const auto& floorSegment=PassageSegmentData[passageIndex][nearest.floorSegmentIndex]; - // Ease both wall and floor shell into the host ground over five metres. - // The floor shell remains below the floor plane throughout that blend. + // Fade the floor shell into the host ground over the mouth blend. + // The shell remains below the floor plane throughout that blend. const double floorDistanceMetres=(nearest.floorCenterZ -nearest.floorDepth-qz)/qPerMetre; const double floorBlend=std::clamp(floorDistanceMetres/0.25,0.0,1.0); const double smoothFloorBlend=floorBlend*floorBlend *(3.0-2.0*floorBlend); - const double mouthWallThickness=segment.routeSegment - ?smoothShellFade*shellThickness:0.0; double floorExtensionFade=1.0; if(!floorSegment.routeSegment) { @@ -1660,9 +1594,6 @@ public: *floorExtensionFade *smoothShellFade, smoothFloorBlend,alongMetres); - const double wallShellDistance=PassageRoundedWallShellDistance( - shapedDistance,wallElevationMetres*qPerMetre, - OpenAirSideWallHeightMetres*qPerMetre,mouthWallThickness); const double floorShellDistance=std::max(-static_cast(shapedDistance), static_cast(shapedDistance)-floorShellThickness); nearest.floorShellSignedDistance=std::min( @@ -1670,356 +1601,187 @@ public: unionDistance=std::min(unionDistance,static_cast(shapedDistance)); unionFloorShellDistance=std::min(unionFloorShellDistance, nearest.floorShellSignedDistance); - const double localShellDistance=std::min(wallShellDistance, - floorShellDistance); - unionShellDistance=std::min(unionShellDistance, - std::min(localShellDistance,nearest.shellSignedDistance)); } if(!std::isfinite(unionDistance))return density; + if(outTubeOnlyDistance) + *outTubeOnlyDistance=static_cast(unionDistance/Q); + if(outFloorShellDistance) + *outFloorShellDistance=static_cast(unionFloorShellDistance/Q); const float passageAirDistance=static_cast(-unionDistance/Q); - // Keep the landing floor shell where it is exposed to the chamber. At - // the tunnel joint, the passage void must be able to open through that - // floor shell so its own continuous floor and shell define the throat. - if(unionLandingChamberFloorShellDistance<0.0&&unionDistance>0.0) - return static_cast(unionLandingChamberFloorShellDistance/Q); - const auto plannedPortalWalkCorridorContains=[&]() - { - if(!queryStratum||!queryStratum->Resolved - ||(queryStratum->Resolved->generatorKind!="TunnelNetwork" - &&queryStratum->Resolved->generatorKind!="VerticalShafts") - ||queryStratum->Resolved->recipeId!="Core.Caves.PrimitiveField" - ||queryStratum->Resolved->recipeVersion!=1)return false; - const bool terminalShaftRoute= - queryStratum->Resolved->generatorKind=="VerticalShafts"; - const VoxelForgeCore::ResolvedLayoutAnchorV2* first=nullptr; - const VoxelForgeCore::ResolvedLayoutAnchorV2* second=nullptr; - const VoxelForgeCore::ResolvedLayoutAnchorV2* third=nullptr; - const VoxelForgeCore::ResolvedLayoutAnchorV2* lastThree=nullptr; - const VoxelForgeCore::ResolvedLayoutAnchorV2* lastTwo=nullptr; - const VoxelForgeCore::ResolvedLayoutAnchorV2* last=nullptr; - std::size_t routeCount=0; - for(const auto& anchor:queryStratum->Resolved->anchors) - { - if(anchor.kind!="route_point"||anchor.values.size()<3)continue; - if(routeCount==0)first=&anchor; - else if(routeCount==1)second=&anchor; - else if(routeCount==2)third=&anchor; - lastThree=lastTwo;lastTwo=last;last=&anchor; - ++routeCount; - } - if(routeCount<6||!first||!second||!third||!lastThree||!lastTwo||!last - ||!first->stableId.ends_with("000-arrival") - ||!second->stableId.ends_with("000-edge-start"))return false; - if(terminalShaftRoute) - { - if(!last->stableId.ends_with("999-basal-terminal"))return false; - } - else if(!lastTwo->stableId.ends_with("998-departure-inner") - ||!last->stableId.ends_with("999-departure"))return false; - const double metresPerSubvoxel=1.0/qPerMetre; - const auto position=[&](const VoxelForgeCore::ResolvedLayoutAnchorV2& anchor) - { - return std::array{anchor.values[0]*metresPerSubvoxel, - anchor.values[1]*metresPerSubvoxel, - anchor.values[2]*metresPerSubvoxel}; - }; - const double queryX=X*Q/qPerMetre; - const double queryY=Y*Q/qPerMetre; - const double queryZ=Z*Q/qPerMetre; - const auto nearRouteSegment=[&](const VoxelForgeCore::ResolvedLayoutAnchorV2& start, - const VoxelForgeCore::ResolvedLayoutAnchorV2& end,double limit,bool fromDeparture, - double distanceToDepartureAfterEnd) - { - const auto a=position(start),b=position(end); - const double dx=b[0]-a[0],dy=b[1]-a[1]; - const double horizontalLength=std::hypot(dx,dy); - if(horizontalLength<=1.0e-9)return false; - const double t=std::clamp(((queryX-a[0])*dx - +(queryY-a[1])*dy)/(horizontalLength*horizontalLength),0.0,1.0); - const double along=fromDeparture - ?distanceToDepartureAfterEnd+(1.0-t)*horizontalLength - :distanceToDepartureAfterEnd+t*horizontalLength; - if(along>limit)return false; - const double routeX=a[0]+t*dx,routeY=a[1]+t*dy; - if(std::hypot(queryX-routeX,queryY-routeY)>0.80)return false; - const double routeFloor=std::lerp(a[2],b[2],t)-1.13; - return queryZ>=routeFloor-0.30&&queryZ<=routeFloor+2.25; - }; - // The connected layer route begins inside the landing chamber. Let - // its air open the chamber's plain side shell through the chamber - // radius plus the shell thickness; the condition below remains - // limited to samples where the tunnel/chamber shell is present. - constexpr double PortalWalkLeadMetres=9.0; - const auto arrival=position(*first),arrivalInner=position(*second); - const double arrivalInnerDistance=std::hypot( - arrivalInner[0]-arrival[0],arrivalInner[1]-arrival[1]); - if(nearRouteSegment(*first,*second,PortalWalkLeadMetres,false,0.0) - ||nearRouteSegment(*second,*third,PortalWalkLeadMetres,false, - arrivalInnerDistance))return true; - if(terminalShaftRoute)return false; - const auto departureInner=position(*lastTwo),departure=position(*last); - const double departureTailDistance=std::hypot( - departure[0]-departureInner[0],departure[1]-departureInner[1]); - return nearRouteSegment(*lastThree,*lastTwo,PortalWalkLeadMetres,true, - departureTailDistance) - ||nearRouteSegment(*lastTwo,*last,PortalWalkLeadMetres,true,0.0); - }; - const auto connectedSurfaceLandingShellContains=[&]() - { - if(!queryStratum||!queryStratum->Resolved - ||queryStratum->Resolved->generatorKind!="SurfaceWorld")return false; - const double queryX=qx/qPerMetre,queryY=qy/qPerMetre,queryZ=qz/qPerMetre; - constexpr double ChamberShellThicknessMetres=0.75; - constexpr double RouteClearanceMetres=1.25; - constexpr double RouteHeightClearanceMetres=1.5; - constexpr double PortalWalkLeadMetres=9.0; - for(const auto& passage:ResolvedLayout->passages) - { - if(passage.upperStrateIndex!=queryStratum->StrateIndex)continue; - const auto& chamber=passage.upperLandingChamber; - if(chamber.radiusSubvoxels<=0||chamber.clearHeightSubvoxels<=0)continue; - const double cx=static_cast(chamber.floorCenterSubvoxels[0])/qPerMetre; - const double cy=static_cast(chamber.floorCenterSubvoxels[1])/qPerMetre; - const double floorZ=static_cast(chamber.floorCenterSubvoxels[2])/qPerMetre - +chamber.floorGradientQ16[0]/65536.0*(queryX-cx) - +chamber.floorGradientQ16[1]/65536.0*(queryY-cy); - const double radialDistance=std::hypot(queryX-cx,queryY-cy) - -static_cast(chamber.radiusSubvoxels)/qPerMetre; - const double floorDistance=floorZ-queryZ; - const double ceilingDistance=queryZ-(floorZ - +static_cast(chamber.clearHeightSubvoxels)/qPerMetre); - const double chamberDistance=std::max({radialDistance, - floorDistance,ceilingDistance}); - const double chamberShellDistance=std::max(-chamberDistance, - chamberDistance-ChamberShellThicknessMetres); - if(chamberDistance<=0.0||chamberShellDistance>=0.0)continue; + float composed=std::max(density,passageAirDistance); + if(density>=0.0f&&unionFloorShellDistance<0.0) + composed=std::min(composed, + static_cast(unionFloorShellDistance/Q)); + return composed; + } - std::vector route; - for(const auto& anchor:queryStratum->Resolved->anchors) - if(anchor.kind=="route_point"&&anchor.values.size()>=3) - route.push_back(&anchor); - if(route.size()<2)continue; - const double mouthZ=static_cast(passage.upperMouthSubvoxels[2]) - /qPerMetre; - std::size_t endpoint=route.size(); - double endpointDistanceSquared=1.0; - for(std::size_t index=0;indexvalues[0]/qPerMetre; - const double y=route[index]->values[1]/qPerMetre; - const double z=route[index]->values[2]/qPerMetre; - const double dx=x-cx,dy=y-cy; - const double distanceSquared=dx*dx+dy*dy; - if(distanceSquared routeDistance(route.size(),0.0); - for(std::size_t index=1;indexvalues[0]-route[index-1]->values[0]) - /qPerMetre; - const double dy=(route[index]->values[1]-route[index-1]->values[1]) - /qPerMetre; - routeDistance[index]=routeDistance[index-1]+std::hypot(dx,dy); - } - for(std::size_t index=1;indexPortalWalkLeadMetres)continue; - const double routeX=ax+t*dx,routeY=ay+t*dy; - const double routeZ=az+t*(b.values[2]-a.values[2])/qPerMetre; - if(std::hypot(queryX-routeX,queryY-routeY)<=RouteClearanceMetres - &&std::abs(queryZ-routeZ)<=RouteHeightClearanceMetres)return true; - } - } - return false; - }; - if(plannedTunnelStratum - &&nativeTunnelFieldDensity!=std::numeric_limits::lowest() - &&nativeTunnelFieldDensity<0.0f) + float ApplyResolvedLandingPadStamps(float density,float X,float Y,float Z, + float tubeOnlyDistance) const + { + if(!ResolvedLayout||ResolvedLayout->passages.empty()||!std::isfinite(X) + ||!std::isfinite(Y)||!std::isfinite(Z))return density; + const auto* bound=FindBoundStratumForManagerZ(Z); + if(!bound||!bound->Resolved)return density; + constexpr double Q=static_cast( + VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); + const double coreMetres=std::max(1, + ResolvedLayout->coreVoxelSizeMicrometres)/1000000.0; + const double qPerMetre=Q/coreMetres; + const double xMetres=static_cast(X)*coreMetres; + const double yMetres=static_cast(Y)*coreMetres; + const double zMetres=static_cast(Z)*coreMetres; + const double tubeDistanceMetres=static_cast(tubeOnlyDistance)*coreMetres; + const auto* tileQueries=FScopedPassageTileQueries::CurrentFor(*this); + const auto* candidates=tileQueries?&tileQueries->routePassageIndices():nullptr; + const std::size_t count=candidates?candidates->size():ResolvedLayout->passages.size(); + constexpr double CosTwelveDegrees=0.9781476007338057; + constexpr double OuterReachMetres=VF_PassageInfluenceReachMetres; + float result=density; + for(std::size_t offset=0;offset( - queryStratum->Resolved->content.bottomMicrometres)/1000000.0; - const double contentTopMetres=static_cast( - queryStratum->Resolved->content.topMicrometres)/1000000.0; - const double boundaryDistance=std::min( - std::abs(queryZMetres-contentBottomMetres), - std::abs(queryZMetres-contentTopMetres)); - const double boundaryJoinBand=std::max(2.0, - static_cast(queryStratum->Resolved->transitionWidthMicrometres) - /2000000.0); - if(boundaryDistance<=boundaryJoinBand - &&std::abs(static_cast(passageAirDistance)*metresPerVoxel) - <=FloorShellThicknessMetres) - // A neighboring stratum can be open at a boundary sample even - // though the native graph owns solid host rock there. Keep the - // authored passage signed distance through that transition band; - // otherwise the neighboring field can widen its wall by a whole - // shell thickness. Native graph air still wins above via the - // positive nativeTunnelFieldDensity path. - return passageAirDistance; + const std::size_t passageIndex=candidates?(*candidates)[offset]:offset; + if(passageIndex>=ResolvedLayout->passages.size())continue; + if(tileQueries&&!tileQueries->routeFeatureMayAffect(passageIndex, + static_cast(X)*Q,static_cast(Y)*Q, + static_cast(Z)*Q))continue; + const auto& passage=ResolvedLayout->passages[passageIndex]; + const auto applyPad=[&](bool upper) + { + if(passage.controlPoints.empty())return; + const auto centerSubvoxels= + VoxelForgeCore::ResolvedLayoutPassageLandingPadCenterSubvoxels( + passage,upper); + const double centerX=static_cast(centerSubvoxels[0])/qPerMetre; + const double centerY=static_cast(centerSubvoxels[1])/qPerMetre; + const double floorP=static_cast(centerSubvoxels[2])/qPerMetre; + const double radius=static_cast( + VoxelForgeCore::ResolvedLayoutPassageLandingDiscRadiusSubvoxels( + passage.mouthRadiusSubvoxels, + ResolvedLayout->coreVoxelSizeMicrometres))/qPerMetre; + const double dx=xMetres-centerX,dy=yMetres-centerY; + const double radial=DeterministicSqrt(dx*dx+dy*dy); + const double outerRadius=radius+VF_LandingPadBlendRadiusMetres; + const double coneHeight=VF_LandingPadConeSlope + *VF_LandingPadBlendRadiusMetres; + if(radial>outerRadius+OuterReachMetres + ||zMetresfloorP+VF_LandingPadHeadroomMetres+coneHeight + +OuterReachMetres)return; + const double coneRun=std::max(0.0,radial-radius); + const double lower=floorP-VF_LandingPadConeSlope*coneRun; + const double upperHeight=floorP+VF_LandingPadConeSlope*coneRun; + const double fillDistance=std::max({ + CosTwelveDegrees*(zMetres-lower), + CosTwelveDegrees*(lower-VF_LandingPadFillDepthMetres-zMetres), + radial-outerRadius}); + const double airDistance=std::min({ + CosTwelveDegrees*(zMetres-upperHeight), + CosTwelveDegrees*(upperHeight+VF_LandingPadHeadroomMetres-zMetres), + outerRadius-radial}); + const double maskedFill=std::max(fillDistance/coreMetres, + -tubeDistanceMetres/coreMetres); + const double smoothWidth=VF_LandingPadSmoothMinMaximumMetres + *std::clamp(radial-radius,0.0,1.0)/coreMetres; + const float fill=static_cast(maskedFill); + const float width=static_cast(smoothWidth); + const float stamped=width>0.0f + ?SmoothMinDistance(result,fill,width):std::min(result,fill); + // The pad clamps existing layer content. Do not union its fill + // volume into host air; the bore's floor shell is the only + // passage solid allowed to occupy air. + result=std::max(result,std::max(stamped, + static_cast(airDistance/coreMetres))); + }; + if(passage.upperStrateIndex==bound->StrateIndex)applyPad(true); + if(passage.lowerStrateIndex==bound->StrateIndex)applyPad(false); } - // Air is the union of the native graph and passage voids. Keeping the - // signed passage distance outside the void as well gives marching cubes - // a real negative distance through the floor instead of the terrain's - // saturated deep-rock value. - if(nativeTunnelFieldDensity!=std::numeric_limits::lowest() - &&plannedTunnelPassageShellDistance<0.0&&unionDistance>0.0) - { - if(nativeTunnelFieldDensity>=0.0f&&plannedPortalWalkCorridorContains()) - return std::max(nativeTunnelFieldDensity,passageAirDistance); - return std::min(density, - static_cast(plannedTunnelPassageShellDistance/Q)); - } - if(nativeTunnelFieldDensity>=0.0f) - { - // The planner's passage shell remains authoritative through native - // cave voids after the explicit mouth fade has ended. Keep this scoped - // to the planner-crossing passage shell so nearby portal joints stay open. - if(plannedTunnelPassageShellDistance<0.0&&unionDistance>0.0) - return std::min(nativeTunnelFieldDensity, - static_cast(plannedTunnelPassageShellDistance/Q)); - constexpr float NativeCaveAirMarginVoxels=0.75f; - if(unionDistance>0.0 - &&nativeTunnelFieldDensity(unionFloorShellDistance/Q)); - return std::max(nativeTunnelFieldDensity,passageAirDistance); - } - if(nativeFloorSupport!=std::numeric_limits::lowest() - &&nativeFloorSupport<0.0f) - return std::min(nativeFloorSupport,unionDistance<=0.0 - ?passageAirDistance:density); - if(nativeTunnelFieldDensity!=std::numeric_limits::lowest()) - return std::max(density,passageAirDistance); - if(unionDistance>0.0&&unionShellDistance<0.0&&density>=0.0f) - { - if(connectedSurfaceLandingShellContains()) - return std::max(density,passageAirDistance); - return static_cast(unionShellDistance/Q); - } - return std::max(density,passageAirDistance); + return result; } float ApplyResolvedStrateComposition(float density,float X,float Y,float Z) const { float graphDensity=0.0f; - float nativeGraphFloorSupport=std::numeric_limits::lowest(); { VoxelForgeCore::Linux77Profiler::FScopedPhase l77GraphPhase( VoxelForgeCore::Linux77Profiler::EPhase::CompositionGraph); FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::ComposerCavern); - graphDensity=ApplyResolvedCavernGraph(density,X,Y,Z, - &nativeGraphFloorSupport); + graphDensity=ApplyResolvedCavernGraph(density,X,Y,Z); } const auto* bound=FindBoundStratumForManagerZ(Z); - const bool liminalRouteComposition=bound&&bound->Resolved - &&bound->Resolved->recipeId==VoxelForgeCore::CoreLiminalFieldRecipeIdV1; const bool nativeLattice=bound&&bound->Resolved &&VoxelForgeCore::IsNativeLatticeFieldStratum(*bound->Resolved) &&bound->Resolved->passageCapabilities.hasNativeLatticeField; - // The native lattice field is a complete host-rock SDF: it owns both the - // voids and the surrounding solid. Replacing its air-side distances with - // the generic stratum transition distance creates a discontinuity at a - // mouth that crosses a wide transition band. Keep that opt-in field whole; - // its host-rock value already supplies the ceiling and bedrock. - float boundary=graphDensity; + float layerContent=graphDensity; if(!nativeLattice) { VoxelForgeCore::Linux77Profiler::FScopedPhase l77BandsPhase( VoxelForgeCore::Linux77Profiler::EPhase::Bands); FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::ComposerBoundary); - boundary=ApplyResolvedStrateBoundaryBand(graphDensity,X,Y,Z); + layerContent=ApplyResolvedStrateBoundaryBand(layerContent,X,Y,Z); } - float composed=boundary; - float stamped=composed; if(!StampRuntime.empty()) { VoxelForgeCore::Linux77Profiler::FScopedPhase l77StampPhase( VoxelForgeCore::Linux77Profiler::EPhase::Stamps); FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::ComposerStamps); - stamped=ApplyResolvedSdfStamps(composed,X,Y,Z, - liminalRouteComposition?std::string_view("core/liminal-field/route/") - :std::string_view{}); + layerContent=ApplyResolvedSdfStamps(layerContent,X,Y,Z); } - static const bool bL75TraceTnComposition=[] - { - const char* Value=std::getenv("VOXELFORGE_L75_TN_BLOCK_REGRESSION"); - return Value&&Value[0]=='1'&&Value[1]=='\0'; - }(); - const bool bTraceTnComposition=bL75TraceTnComposition - &&X==-80.0f&&Y==-20.0f&&Z==-3218.0f; - if(bound&&bound->Kind==FBoundStratumField::EKind::Caves + const bool nativeGraphField=bound&&bound->Kind==FBoundStratumField::EKind::Caves &&bound->Resolved&&VoxelForgeCore::HasNativeGraphField(*bound->Resolved) &&(VoxelForgeCore::IsNativeCaveFieldStratum(*bound->Resolved) ?bound->Resolved->passageCapabilities.hasNativeCaveField - :bound->Resolved->passageCapabilities.hasNativeLatticeField)) - { - { - VoxelForgeCore::Linux77Profiler::FScopedPhase l77PassagePhase( - VoxelForgeCore::Linux77Profiler::EPhase::Passage); - FScopedLegacyFieldTermTimer TermTimer( - ELegacyFieldTerm::ComposerPassage); - const bool nativeTunnelField= - (VoxelForgeCore::IsNativeCaveFieldStratum(*bound->Resolved) - &&bound->Resolved->passageCapabilities.hasNativeCaveField) - ||(VoxelForgeCore::IsNativeLatticeFieldStratum(*bound->Resolved) - &&bound->Resolved->passageCapabilities.hasNativeLatticeField); - const float nativeTunnelFieldDensity=nativeTunnelField - ?graphDensity:std::numeric_limits::lowest(); - stamped=ApplyResolvedStratePassageCarves(stamped,X,Y,Z, - nativeTunnelFieldDensity,nativeGraphFloorSupport); - } - if(bTraceTnComposition) - std::printf("[L75TNCompositionTrace] input=%.9g graph=%.9g boundary=%.9g passage=%.9g stamp=%.9g native=1\n", - density,graphDensity,boundary,composed,stamped); - return stamped; - } - // The legacy landmark demo retains its compatibility floor cap. Native caves - // already combine each floor with its wall before the single void carve above. - float final=0.0f; + :bound->Resolved->passageCapabilities.hasNativeLatticeField); + if(!nativeGraphField) { VoxelForgeCore::Linux77Profiler::FScopedPhase l77CapsPhase( VoxelForgeCore::Linux77Profiler::EPhase::Caps); - FScopedLegacyFieldTermTimer TermTimer( - ELegacyFieldTerm::ComposerPassage); - final=ApplyResolvedCavernFloorCaps(stamped,X,Y,Z); + FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::ComposerPassage); + layerContent=ApplyResolvedCavernFloorCaps(layerContent,X,Y,Z); } + const float nativeTunnelField=nativeGraphField?graphDensity + :std::numeric_limits::lowest(); + float tubeOnlyDistance=std::numeric_limits::infinity(); + float floorShellDistance=std::numeric_limits::infinity(); + float composed=0.0f; { VoxelForgeCore::Linux77Profiler::FScopedPhase l77PassagePhase( VoxelForgeCore::Linux77Profiler::EPhase::Passage); - FScopedLegacyFieldTermTimer TermTimer( - ELegacyFieldTerm::ComposerPassage); - final=ApplyResolvedStratePassageCarves(final,X,Y,Z); + FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::ComposerPassage); + composed=ApplyResolvedStratePassageCarves(layerContent,X,Y,Z, + nativeTunnelField,&tubeOnlyDistance,&floorShellDistance); } - // A liminal route is authored to terminate at fixed passage mouths. The - // passage carve owns the surrounding bore, while the deck/support stamps - // restore its walkable floor so the composed route crosses the interface. - if(liminalRouteComposition) - final=ApplyResolvedSdfStamps(final,X,Y,Z,{}, - "core/liminal-field/route/"); - if(bTraceTnComposition) - std::printf("[L75TNCompositionTrace] input=%.9g graph=%.9g boundary=%.9g passage=%.9g stamp=%.9g final=%.9g native=0\n", - density,graphDensity,boundary,composed,stamped,final); - return final; + return ApplyResolvedLandingPadStamps(composed,X,Y,Z,tubeOnlyDistance); + } + + static bool ResolvedLandingPadInfluenceBounds( + const VoxelForgeCore::ResolvedLayoutPassageV1& passage,bool upper, + std::uint32_t coreVoxelSizeMicrometres, + std::array& minimum,std::array& maximum) + { + if(passage.controlPoints.empty()||coreVoxelSizeMicrometres==0)return false; + constexpr double Q=static_cast( + VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); + const double coreMetres=coreVoxelSizeMicrometres/1000000.0; + const auto center=VoxelForgeCore::ResolvedLayoutPassageLandingPadCenterSubvoxels( + passage,upper); + const double radiusVoxels=static_cast( + VoxelForgeCore::ResolvedLayoutPassageLandingDiscRadiusSubvoxels( + passage.mouthRadiusSubvoxels,coreVoxelSizeMicrometres))/Q; + const double x=static_cast(center[0])/Q; + const double y=static_cast(center[1])/Q; + const double p=static_cast(center[2])/Q; + const double reachMetres=VF_PassageInfluenceReachMetres+0.01*coreMetres; + const double radialExtent=radiusVoxels + +(VF_LandingPadBlendRadiusMetres+reachMetres)/coreMetres; + const double coneHeight=VF_LandingPadConeSlope + *VF_LandingPadBlendRadiusMetres; + minimum={x-radialExtent,y-radialExtent, + p-(VF_LandingPadFillDepthMetres+coneHeight+reachMetres)/coreMetres}; + maximum={x+radialExtent,y+radialExtent, + p+(VF_LandingPadHeadroomMetres+coneHeight+reachMetres)/coreMetres}; + return true; } bool ResolvedPassageIntersectsBox(const int32 minimum[3],const int32 maximum[3]) const @@ -2062,26 +1824,14 @@ public: } if(overlaps(low,high))return true; } - const auto chamberOverlaps=[&]( - const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& chamber) + const auto padOverlaps=[&](bool upper) { - const double cx=static_cast(chamber.floorCenterSubvoxels[0])/Q; - const double cy=static_cast(chamber.floorCenterSubvoxels[1])/Q; - const double cz=static_cast(chamber.floorCenterSubvoxels[2])/Q; - const double radius=static_cast(chamber.radiusSubvoxels)/Q; - const double floorExtent=radius*std::hypot( - static_cast(chamber.floorGradientQ16[0])/65536.0, - static_cast(chamber.floorGradientQ16[1])/65536.0); - const double extent=reachCoreVoxels+0.01; - const double low[3]={cx-radius-extent,cy-radius-extent, - cz-floorExtent-extent}; - const double high[3]={cx+radius+extent,cy+radius+extent, - cz+floorExtent+static_cast( - chamber.clearHeightSubvoxels)/Q+extent}; - return overlaps(low,high); + std::array low{},high{}; + return ResolvedLandingPadInfluenceBounds(passage,upper, + ResolvedLayout->coreVoxelSizeMicrometres,low,high) + &&overlaps(low.data(),high.data()); }; - if(chamberOverlaps(passage.upperLandingChamber) - ||chamberOverlaps(passage.lowerLandingChamber))return true; + if(padOverlaps(true)||padOverlaps(false))return true; } return false; } @@ -3485,6 +3235,14 @@ public: float PassageSourceDensityAt(float X,float Y,float Z) const {return NativePassageDensityAt(X,Y,Z);} + float PassageFloorShellDistanceAt(float X,float Y,float Z) const + { + float tubeDistance=std::numeric_limits::infinity(); + float floorShellDistance=std::numeric_limits::infinity(); + (void)ApplyResolvedStratePassageCarves(0.0f,X,Y,Z, + std::numeric_limits::lowest(),&tubeDistance,&floorShellDistance); + return floorShellDistance; + } std::vector OreVeinTileCandidates( const std::array& minimum, @@ -3912,25 +3670,7 @@ private: struct FPassageComposerConstants { double floorDepthMetres=0.0,totalPathLengthMetres=0.0; - double authoredTotalPathLengthMetres=0.0; - double upperMouthPlayerCenterHeightMetres=1.13; std::size_t routeProfileSegmentCount=0; - double upperMouthX=0.0,upperMouthY=0.0; - double upperMouthPlannedFloor=0.0; - double upperMouthTangentX=0.0,upperMouthTangentY=0.0; - std::vector> upperMouthReceivingRoute; - double lowerMouthX=0.0,lowerMouthY=0.0; - double lowerMouthPlannedFloor=0.0; - double lowerMouthTangentX=0.0,lowerMouthTangentY=0.0; - std::vector> lowerMouthReceivingRoute; - std::array upperLandingFloorCenterSubvoxels{}; - std::array upperLandingFloorGradient{}; - double upperLandingRadiusSubvoxels=0.0; - std::array lowerLandingFloorCenterSubvoxels{}; - std::array lowerLandingFloorGradient{}; - double lowerLandingRadiusSubvoxels=0.0; - std::size_t routeEndControlIndex=0; - bool floorProfileIncludesLandingChambers=false; }; struct FPassageSegmentData { @@ -3954,55 +3694,11 @@ private: std::max(0.0,radiusSubvoxels-0.23*qPerMetre)); } - static double BlendPassageFloorToLandingChambers( - const FPassageComposerConstants& constants, - const FPassageSegmentData& segment,double floorT, - double queryXSubvoxels,double queryYSubvoxels,double plannedFloor, - double qPerMetre) - { - if(qPerMetre<=0.0||!(constants.totalPathLengthMetres>0.0)) - return plannedFloor; - const double alongMetres=segment.authoredAlongMetres - +floorT*segment.authoredLengthMetres; - constexpr double LandingFloorBlendMetres=12.0; - const auto floorWeight=[&](double distanceMetres,double radiusSubvoxels) - { - if(!(radiusSubvoxels>0.0))return 0.0; - const double radiusMetres=radiusSubvoxels/qPerMetre; - if(distanceMetres<=radiusMetres)return 1.0; - if(distanceMetres>=radiusMetres+LandingFloorBlendMetres)return 0.0; - const double t=(distanceMetres-radiusMetres)/LandingFloorBlendMetres; - return 1.0-t*t*(3.0-2.0*t); - }; - const double upperWeight=floorWeight(std::max(0.0,alongMetres), - constants.upperLandingRadiusSubvoxels); - const double lowerWeight=floorWeight(std::max(0.0, - constants.totalPathLengthMetres-alongMetres), - constants.lowerLandingRadiusSubvoxels); - if(upperWeight<=0.0&&lowerWeight<=0.0)return plannedFloor; - const auto floorAt=[&](const std::array& center, - const std::array& gradient) - { - return center[2]+gradient[0]*(queryXSubvoxels-center[0]) - +gradient[1]*(queryYSubvoxels-center[1]); - }; - const double upperFloor=floorAt(constants.upperLandingFloorCenterSubvoxels, - constants.upperLandingFloorGradient); - const double lowerFloor=floorAt(constants.lowerLandingFloorCenterSubvoxels, - constants.lowerLandingFloorGradient); - const double totalWeight=upperWeight+lowerWeight; - if(totalWeight<=1.0) - return plannedFloor*(1.0-totalWeight) - +upperFloor*upperWeight+lowerFloor*lowerWeight; - return (upperFloor*upperWeight+lowerFloor*lowerWeight)/totalWeight; - } - static double PassageFlatFloorCenterZSubvoxels( const FPassageComposerConstants& constants, const std::vector& segments, std::size_t segmentIndex,double floorT, - const FPassageSegmentData& segment,double queryXSubvoxels,double queryYSubvoxels, - double floorDepthSubvoxels,double qPerMetre) + const FPassageSegmentData& segment,double floorDepthSubvoxels,double qPerMetre) { if(!segment.routeSegment||qPerMetre<=0.0) return segment.a[2]+std::clamp(floorT,0.0,1.0)*segment.delta[2]; @@ -4032,26 +3728,7 @@ private: +profileT*profileSegment.authoredFloorZDelta; } } - if(constants.floorProfileIncludesLandingChambers) - return plannedFloor+floorDepthSubvoxels; - const double upperBlendFloor=VoxelForgeCore::PassageSpline::BlendUpperMouthFloorAtXY( - constants.upperMouthReceivingRoute,queryXSubvoxels,queryYSubvoxels, - plannedFloor,constants.upperMouthX,constants.upperMouthY, - constants.upperMouthPlannedFloor, - constants.upperMouthTangentX,constants.upperMouthTangentY, - VoxelForgeCore::PassageSpline::UpperMouthFloorBlendRadiusMetres, - constants.upperMouthPlayerCenterHeightMetres, - qPerMetre); - const double lowerBlendFloor=VoxelForgeCore::PassageSpline::BlendUpperMouthFloorAtXY( - constants.lowerMouthReceivingRoute,queryXSubvoxels,queryYSubvoxels, - upperBlendFloor,constants.lowerMouthX,constants.lowerMouthY, - constants.lowerMouthPlannedFloor, - -constants.lowerMouthTangentX,-constants.lowerMouthTangentY, - VoxelForgeCore::PassageSpline::UpperMouthFloorBlendRadiusMetres,1.13, - qPerMetre); - const double landingFloor=BlendPassageFloorToLandingChambers(constants, - segment,floorT,queryXSubvoxels,queryYSubvoxels,lowerBlendFloor,qPerMetre); - return landingFloor+floorDepthSubvoxels; + return plannedFloor+floorDepthSubvoxels; } static float PassageFlatBottomDistance(float roundDistanceSubvoxels, @@ -4064,25 +3741,6 @@ private: return SmoothMaxDistance(roundDistanceSubvoxels,floorDistance, static_cast(CornerBlendMetres*qPerMetre)); } - static double PassageRoundedWallShellDistance(double flatDistanceSubvoxels, - double wallElevationSubvoxels,double wallHeightSubvoxels, - double wallThicknessSubvoxels) - { - if(!(wallThicknessSubvoxels>1.0e-9)||!(wallHeightSubvoxels>0.0)) - return std::numeric_limits::infinity(); - const double radius=wallThicknessSubvoxels*0.5; - const double capCenter=wallHeightSubvoxels-radius; - if(!(capCenter>0.0))return std::numeric_limits::infinity(); - const double x=flatDistanceSubvoxels-radius; - const double boxHalfHeight=capCenter*0.5; - const double boxX=std::abs(x)-radius; - const double boxY=std::abs(wallElevationSubvoxels-boxHalfHeight) - -boxHalfHeight; - const double boxDistance=std::min(std::max(boxX,boxY),0.0) - +std::hypot(std::max(boxX,0.0),std::max(boxY,0.0)); - const double capDistance=std::hypot(x,wallElevationSubvoxels-capCenter)-radius; - return std::min(boxDistance,capDistance); - } struct FPassageNearestNode { std::array minimum{},maximum{}; @@ -5225,33 +4883,6 @@ private: const std::size_t routeEnd=std::clamp( passage.lowerMouthControlIndex,1,passage.controlPoints.size()-1); auto& constants=PassageComposerConstants[passageIndex]; - constants.routeEndControlIndex=routeEnd; - constants.floorProfileIncludesLandingChambers= - passage.floorProfileIncludesLandingChambers; - for(std::size_t axis=0;axis<3;++axis) - { - constants.upperLandingFloorCenterSubvoxels[axis]=static_cast( - passage.upperLandingChamber.floorCenterSubvoxels[axis]); - constants.lowerLandingFloorCenterSubvoxels[axis]=static_cast( - passage.lowerLandingChamber.floorCenterSubvoxels[axis]); - } - constants.upperLandingFloorGradient={ - passage.upperLandingChamber.floorGradientQ16[0]/65536.0, - passage.upperLandingChamber.floorGradientQ16[1]/65536.0}; - constants.lowerLandingFloorGradient={ - passage.lowerLandingChamber.floorGradientQ16[0]/65536.0, - passage.lowerLandingChamber.floorGradientQ16[1]/65536.0}; - constants.upperLandingRadiusSubvoxels=static_cast( - passage.upperLandingChamber.radiusSubvoxels); - constants.lowerLandingRadiusSubvoxels=static_cast( - passage.lowerLandingChamber.radiusSubvoxels); - const auto upperStratum=std::find_if(ResolvedLayout->strates.begin(), - ResolvedLayout->strates.end(),[&](const auto& stratum) - {return stratum.strateIndex==passage.upperStrateIndex;}); - if(upperStratum!=ResolvedLayout->strates.end() - &&upperStratum->generatorKind=="SurfaceWorld") - constants.upperMouthPlayerCenterHeightMetres=0.98; - using FRawPassageSegment=VoxelForgeCore::PassageSpline::FSegment; std::vector controls; controls.reserve(passage.controlPoints.size()); @@ -5266,101 +4897,6 @@ private: plannedFloorProfile.push_back(static_cast( point.positionSubvoxels[2]-point.radiusSubvoxels)); } - const double authoredUpperMouthFloorDepth=std::max(0.0, - static_cast(passage.controlPoints.front().positionSubvoxels[2] - -passage.upperMouthSubvoxels[2])/qPerMetre - +constants.upperMouthPlayerCenterHeightMetres); - double upperMouthFloorDepth=authoredUpperMouthFloorDepth; - if(upperStratum!=ResolvedLayout->strates.end() - &&(upperStratum->generatorKind=="FloatingIslands" - ||upperStratum->generatorKind=="TunnelNetwork" - ||upperStratum->generatorKind=="SurfaceWorld")) - { - std::vector> route; - for(const auto& anchor:upperStratum->anchors) - if(anchor.kind=="route_point"&&anchor.values.size()>=3) - route.push_back({static_cast(anchor.values[0]), - static_cast(anchor.values[1]), - static_cast(anchor.values[2])}); - if(upperStratum->generatorKind=="TunnelNetwork"&&route.size()>2) - route.erase(route.begin(),route.end()-2); - constants.upperMouthReceivingRoute=route; - double routeHeight=0.0,distanceSquared=0.0; - if(VoxelForgeCore::PassageSpline::ClosestRouteHeightAtXY(route, - controls.front()[0],controls.front()[1],routeHeight,distanceSquared) - &&distanceSquared<=std::pow(controls.front()[3]+2.0*qPerMetre,2.0)) - { - upperMouthFloorDepth=std::max(upperMouthFloorDepth, - std::max(0.0,(controls.front()[2]-routeHeight)/qPerMetre - +constants.upperMouthPlayerCenterHeightMetres)); - if(!passage.floorProfileIncludesLandingChambers - &&(upperStratum->generatorKind=="TunnelNetwork" - ||upperStratum->generatorKind=="SurfaceWorld") - ) - VoxelForgeCore::PassageSpline::BlendEndpointFloorToRoute( - controls,plannedFloorProfile,0,route,qPerMetre, - constants.upperMouthPlayerCenterHeightMetres); - } - } - controls.front()[3]=std::max(controls.front()[3], - upperMouthFloorDepth*qPerMetre); - if(upperStratum!=ResolvedLayout->strates.end() - &&(upperStratum->generatorKind=="FloatingIslands" - ||upperStratum->generatorKind=="TunnelNetwork" - ||upperStratum->generatorKind=="SurfaceWorld")) - for(const auto& anchor:upperStratum->anchors) - if(anchor.kind=="route_point"&&anchor.values.size()>=3) - constants.upperMouthReceivingRoute.push_back({ - static_cast(anchor.values[0]), - static_cast(anchor.values[1]), - static_cast(anchor.values[2])}); - if(upperStratum!=ResolvedLayout->strates.end() - &&upperStratum->generatorKind=="TunnelNetwork" - &&constants.upperMouthReceivingRoute.size()>2) - constants.upperMouthReceivingRoute.erase( - constants.upperMouthReceivingRoute.begin(), - constants.upperMouthReceivingRoute.end()-2); - - const auto lowerStratum=std::find_if(ResolvedLayout->strates.begin(), - ResolvedLayout->strates.end(),[&](const auto& stratum) - {return stratum.strateIndex==passage.lowerStrateIndex;}); - if(lowerStratum!=ResolvedLayout->strates.end() - &&(lowerStratum->generatorKind=="TunnelNetwork" - ||lowerStratum->generatorKind=="VerticalShafts") - &&routeEndanchors) - if(anchor.kind=="route_point"&&anchor.values.size()>=3) - constants.lowerMouthReceivingRoute.push_back({ - static_cast(anchor.values[0]), - static_cast(anchor.values[1]), - static_cast(anchor.values[2])}); - if(constants.lowerMouthReceivingRoute.size()>2) - constants.lowerMouthReceivingRoute.resize(2); - if(VoxelForgeCore::PassageSpline::ClosestRouteHeightAtXY( - constants.lowerMouthReceivingRoute,controls[routeEnd][0], - controls[routeEnd][1],routeHeight,distanceSquared) - &&distanceSquared<=std::pow(controls[routeEnd][3] - +2.0*qPerMetre,2.0)) - { - if(!passage.floorProfileIncludesLandingChambers) - VoxelForgeCore::PassageSpline::BlendEndpointFloorToRoute( - controls,plannedFloorProfile,routeEnd, - constants.lowerMouthReceivingRoute,qPerMetre); - constants.lowerMouthX=controls[routeEnd][0]; - constants.lowerMouthY=controls[routeEnd][1]; - constants.lowerMouthPlannedFloor=plannedFloorProfile[routeEnd]; - if(routeEnd>0) - { - constants.lowerMouthTangentX=controls[routeEnd][0] - -controls[routeEnd-1][0]; - constants.lowerMouthTangentY=controls[routeEnd][1] - -controls[routeEnd-1][1]; - } - } - else constants.lowerMouthReceivingRoute.clear(); - } const auto sampledSegments=VoxelForgeCore::PassageSpline::BuildSegments( controls,controls.size()-1,PassageSegmentSpacingMetres,qPerMetre, plannedFloorProfile,PassageSegmentChordErrorMetres); @@ -5386,36 +4922,6 @@ private: passage.stableId.c_str(),floorGradeSamples,maximumFloorGradeDegrees, MaximumFloorGradeDegrees,floorGradeViolations, floorGradeViolations==0?"PASS":"FAIL"); - constants.upperMouthX=controls.front()[0]; - constants.upperMouthY=controls.front()[1]; - constants.upperMouthPlannedFloor=plannedFloorProfile.front(); - if(!sampledSegments.empty()) - { - constants.upperMouthTangentX=sampledSegments.front().b[0] - -sampledSegments.front().a[0]; - constants.upperMouthTangentY=sampledSegments.front().b[1] - -sampledSegments.front().a[1]; - } - const std::string lowerMouthAnchor=passage.stableId+"/lower-landing-chamber"; - const std::string upperMouthAnchor=passage.stableId+"/upper-landing-chamber"; - for(const auto& edge:ResolvedLayout->cavernEdges) - { - if(edge.controlPointsMicrometres.size()<2)continue; - if(edge.startAnchorStableId==lowerMouthAnchor) - { - constants.lowerMouthTangentX=edge.controlPointsMicrometres[1][0] - -edge.controlPointsMicrometres[0][0]; - constants.lowerMouthTangentY=edge.controlPointsMicrometres[1][1] - -edge.controlPointsMicrometres[0][1]; - } - if(edge.startAnchorStableId==upperMouthAnchor) - { - constants.upperMouthTangentX=edge.controlPointsMicrometres[0][0] - -edge.controlPointsMicrometres[1][0]; - constants.upperMouthTangentY=edge.controlPointsMicrometres[0][1] - -edge.controlPointsMicrometres[1][1]; - } - } std::vector rawSegments=sampledSegments; std::vector> routeRadiusSamples; routeRadiusSamples.push_back({0.0,controls.front()[3]/qPerMetre}); @@ -5434,7 +4940,6 @@ private: if(raw.controlSpan+1==routeEnd) authoredTotalPathLengthMetres=raw.alongMetres+raw.lengthMetres; } - constants.authoredTotalPathLengthMetres=authoredTotalPathLengthMetres; double minimumBodyRadius=std::numeric_limits::infinity(); double minimumRadius=std::numeric_limits::infinity(); for(const auto& sample:routeRadiusSamples) @@ -5582,24 +5087,12 @@ private: std::ceil((localMaximum[axis]+localExtent)*Q)); } } - const auto includeLandingChamber=[&]( - const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& chamber) + const auto includeLandingPad=[&](bool upper) { - const double cx=static_cast(chamber.floorCenterSubvoxels[0])/Q; - const double cy=static_cast(chamber.floorCenterSubvoxels[1])/Q; - const double cz=static_cast(chamber.floorCenterSubvoxels[2])/Q; - const double radius=static_cast(chamber.radiusSubvoxels)/Q; - const double gx=static_cast(chamber.floorGradientQ16[0])/65536.0; - const double gy=static_cast(chamber.floorGradientQ16[1])/65536.0; - const double floorExtent=radius*std::hypot(gx,gy); - const double height=static_cast( - chamber.clearHeightSubvoxels)/Q; - const double extent=requiredReachCoreVoxels+0.01; - const std::array minimum{ - cx-radius-extent,cy-radius-extent,cz-floorExtent-extent}; - const std::array maximum{ - cx+radius+extent,cy+radius+extent, - cz+floorExtent+height+extent}; + std::array minimum{},maximum{}; + if(!ResolvedLandingPadInfluenceBounds( + ResolvedLayout->passages[passageIndex],upper, + ResolvedLayout->coreVoxelSizeMicrometres,minimum,maximum))return; FPassageProofBound proof; for(std::size_t axis=0;axis<3;++axis) { @@ -5611,11 +5104,9 @@ private: PassageProofBounds.push_back(proof); }; if(segmentIndex==0) - includeLandingChamber( - ResolvedLayout->passages[passageIndex].upperLandingChamber); + includeLandingPad(true); if(segmentEndIndex==segmentCount) - includeLandingChamber( - ResolvedLayout->passages[passageIndex].lowerLandingChamber); + includeLandingPad(false); const double routeExtent=maximumRadiusSubvoxels/Q +requiredReachCoreVoxels+0.01; const double tileExtent=routeExtent; @@ -5682,7 +5173,7 @@ private: const double floorDepth=PassageFlatFloorDepthSubvoxels( constants,segmentIndex,T,radius,qPerMetre); const double floorCenterZ=PassageFlatFloorCenterZSubvoxels( - constants,segments,segmentIndex,T,segment,centerX,centerY, + constants,segments,segmentIndex,T,segment, floorDepth,qPerMetre); for(const double crestHeightMetres:CrestHeightsMetres) { @@ -5740,14 +5231,87 @@ private: } } } + std::uint64_t landingPadBoundProbes=0,landingPadProofMisses=0; + std::uint64_t landingPadFeatureMisses=0,landingPadStampSamples=0; + std::uint64_t landingPadStampMisses=0; + const auto checkLandingPadBounds=[&](std::size_t passageIndex, + const std::array& sampleVoxel) + { + ++landingPadBoundProbes; + if(PassageProofNodes.empty() + ||!PassageProofBoundsOverlapNode(0,sampleVoxel.data(),sampleVoxel.data())) + ++landingPadProofMisses; + std::array sampleMinimum{},sampleMaximum{}; + for(std::size_t axis=0;axis<3;++axis) + { + const double sampleQ=sampleVoxel[axis]*Q; + sampleMinimum[axis]=static_cast(std::floor(sampleQ)); + sampleMaximum[axis]=static_cast(std::ceil(sampleQ)); + } + const auto candidates=BuildPassageFeatureTileList(sampleMinimum,sampleMaximum); + const bool containsPassage=std::any_of(candidates.begin(),candidates.end(), + [&](std::uint32_t featureIndex) + { + return featureIndex(1, + ResolvedLayout->coreVoxelSizeMicrometres)/1000000.0; + for(std::size_t passageIndex=0; + passageIndexpassages.size();++passageIndex) + { + const auto& passage=ResolvedLayout->passages[passageIndex]; + for(const bool upper:{true,false}) + { + std::array minimum{},maximum{}; + if(!ResolvedLandingPadInfluenceBounds(passage,upper, + ResolvedLayout->coreVoxelSizeMicrometres,minimum,maximum)) + continue; + const auto center= + VoxelForgeCore::ResolvedLayoutPassageLandingPadCenterSubvoxels( + passage,upper); + const std::array centerVoxel={ + static_cast(center[0])/Q, + static_cast(center[1])/Q, + static_cast(center[2])/Q}; + const std::array stampProbe={centerVoxel[0],centerVoxel[1], + centerVoxel[2]+0.5/coreMetres}; + const float stampContribution=ApplyResolvedLandingPadStamps(-1.0f, + static_cast(stampProbe[0]),static_cast(stampProbe[1]), + static_cast(stampProbe[2]), + std::numeric_limits::infinity()); + ++landingPadStampSamples; + if(stampContribution<=-1.0f+1.0e-6f)++landingPadStampMisses; + checkLandingPadBounds(passageIndex,stampProbe); + for(std::size_t axis=0;axis<3;++axis) + for(const bool high:{false,true}) + { + std::array point{}; + for(std::size_t other=0;other<3;++other) + point[other]=(minimum[other]+maximum[other])*0.5; + point[axis]=high?maximum[axis]-0.002:minimum[axis]+0.002; + checkLandingPadBounds(passageIndex,point); + } + } + } const bool auditPassed=sampled>0&&signRelevant>0 - &&proofMisses==0&&featureMisses==0; - std::printf("[L130PassageReachProofAudit] reach_m=%.3f sampled=%llu sign_relevant=%llu proof_misses=%llu feature_misses=%llu status=%s\n", + &&proofMisses==0&&featureMisses==0 + &&landingPadBoundProbes>0&&landingPadProofMisses==0 + &&landingPadFeatureMisses==0&&landingPadStampSamples>0 + &&landingPadStampMisses==0; + std::printf("[L130PassageReachProofAudit] reach_m=%.3f sampled=%llu sign_relevant=%llu proof_misses=%llu feature_misses=%llu pad_bound_probes=%llu pad_proof_misses=%llu pad_feature_misses=%llu pad_stamp_samples=%llu pad_stamp_misses=%llu status=%s\n", VF_PassageInfluenceReachMetres, static_cast(sampled), static_cast(signRelevant), static_cast(proofMisses), static_cast(featureMisses), + static_cast(landingPadBoundProbes), + static_cast(landingPadProofMisses), + static_cast(landingPadFeatureMisses), + static_cast(landingPadStampSamples), + static_cast(landingPadStampMisses), auditPassed?"PASS":"FAIL"); if(!auditPassed)std::abort(); } @@ -8415,11 +7979,8 @@ public: } private: - float ApplyResolvedCavernGraph(float density,float X,float Y,float Z, - float* nativeFloorSupport=nullptr) const + float ApplyResolvedCavernGraph(float density,float X,float Y,float Z) const { - if(nativeFloorSupport) - *nativeFloorSupport=std::numeric_limits::lowest(); if(CavernRuntime.empty()||!Owner||Owner->CoreVoxelSizeMicrons==0)return density; const auto* tileQueries=ActivePassageTileQueries; const bool scoped=tileQueries&&tileQueries->matches(*this); @@ -8431,8 +7992,7 @@ private: &&(VoxelForgeCore::IsNativeCaveFieldStratum(*bound->Resolved) ?bound->Resolved->passageCapabilities.hasNativeCaveField :bound->Resolved->passageCapabilities.hasNativeLatticeField)) - return ApplyResolvedNativeCavesField(density,X,Y,Z,*bound,tileQueries,scoped, - nativeFloorSupport); + return ApplyResolvedNativeCavesField(density,X,Y,Z,*bound,tileQueries,scoped); const double micronsPerVoxel=Owner->CoreVoxelSizeMicrons; const std::array worldMicrometres={X*micronsPerVoxel, Y*micronsPerVoxel,Z*micronsPerVoxel}; @@ -8578,10 +8138,8 @@ private: float ApplyResolvedNativeCavesField(float density,float X,float Y,float Z, const FBoundStratumField& bound,const FScopedPassageTileQueries* tileQueries, - bool scoped,float* nativeFloorSupport=nullptr) const + bool scoped) const { - if(nativeFloorSupport) - *nativeFloorSupport=std::numeric_limits::lowest(); (void)density; const double metresPerVoxel=Owner->CoreVoxelSizeMicrons/1000000.0; std::vector localCandidates; @@ -8922,8 +8480,6 @@ private: &&point[2]>lowestOpenEdgeFloor&&!sameContinuousFloorProfile; if(std::isfinite(nativeFloorSupportMinimum)&&!sealsOpenLowerEdge) composed=std::min(composed,nativeFloorSupportMinimum); - if(nativeFloorSupport&&std::isfinite(nativeFloorSupportMinimum)) - *nativeFloorSupport=static_cast(nativeFloorSupportMinimum/metresPerVoxel); return static_cast(composed/metresPerVoxel); } @@ -12853,7 +12409,6 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa const FIntVector& origin,std::int32_t step,std::int32_t cells,std::uint32_t seed) { constexpr std::int64_t Q=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; - constexpr double OpenAirSideWallHeightMetres=1.0; if(!shape_||step<=0||cells<=0||!std::isfinite(passageCarveReachMetres_) ||std::abs(passageCarveReachMetres_-VF_PassageInfluenceReachMetres)>1.0e-9)return; const bool profile=profileStats_!=nullptr; @@ -12962,7 +12517,7 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa if(!sample.valid) { sample.signedDistance=stampedOutsideDistance; - sample.shellSignedDistance=stampedOutsideDistance; + sample.floorShellSignedDistance=stampedOutsideDistance; sample.roundSignedDistance=stampedOutsideDistance; sample.centerZSubvoxels=qz; sample.floorCenterZSubvoxels=qz; @@ -13033,7 +12588,7 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa const double floorCenterZ= FStandaloneShapeWorld::PassageFlatFloorCenterZSubvoxels( constants,segments,segmentIndex,floorT,segment, - qx,qy,floorDepth,qPerMetre); + floorDepth,qPerMetre); if(profile)++profileStats_->passageSweepSegmentSamplePairs; if(signedDistance>sweep->influenceSubvoxels) { @@ -13050,8 +12605,8 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa sample.floorSegmentIndex=static_cast( segmentIndex); } - // A negative shell distance is possible only when the flattened tube - // distance lies in [0, shellThickness]. SmoothMaxDistance is at least + // A negative floor-shell distance is possible only when the flattened + // tube distance lies in [0, shellThickness]. SmoothMaxDistance is at least // max(roundDistance, floorDistance) and can raise that maximum by no // more than cornerWidth/6. Skip its work for pairs outside this exact // support, with a small subvoxel guard for float rounding. The final @@ -13083,10 +12638,6 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa const double floorBlend=std::clamp(floorDistanceMetres/0.25,0.0,1.0); const double smoothFloorBlend=floorBlend*floorBlend *(3.0-2.0*floorBlend); - const double wallElevationMetres=(qz-(floorCenterZ-floorDepth)) - /qPerMetre; - const double mouthWallThickness=segment.routeSegment - ?smoothShellFade*shellThickness:0.0; double floorExtensionFade=1.0; if(!segment.routeSegment) { @@ -13097,19 +12648,14 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa floorExtensionFade=extensionFade*extensionFade *(3.0-2.0*extensionFade); } - const double floorShellThickness=smoothFloorBlend*shellThickness + const double floorShellThickness=smoothFloorBlend*(shellThickness + +(mouthShellBlendMetres==2.0?0.25*qPerMetre:0.0)) *floorExtensionFade*smoothShellFade; - const double wallShellDistance= - FStandaloneShapeWorld::PassageRoundedWallShellDistance( - flatDistance,wallElevationMetres*qPerMetre, - OpenAirSideWallHeightMetres*qPerMetre,mouthWallThickness); const double floorShellDistance=std::max( -static_cast(flatDistance), static_cast(flatDistance)-floorShellThickness); - const double localShellDistance=std::min(wallShellDistance, - floorShellDistance); - sample.shellSignedDistance=std::min(sample.shellSignedDistance, - localShellDistance); + sample.floorShellSignedDistance=std::min( + sample.floorShellSignedDistance,floorShellDistance); } else if(profile) ++profileStats_->passageSweepShellSupportSkips; @@ -13155,7 +12701,7 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa { if(profile)++stampedOutsideSampleCount; sample.signedDistance=stampedOutsideDistance; - sample.shellSignedDistance=stampedOutsideDistance; + sample.floorShellSignedDistance=stampedOutsideDistance; continue; } if(profile) @@ -13185,8 +12731,8 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa ++bodyBandSampleCount; anyExactBandSample[sampleIndex]=1; } - if(profile&&std::isfinite(sample.shellSignedDistance) - &&std::abs(sample.shellSignedDistance)<=sweep->stepSubvoxels) + if(profile&&std::isfinite(sample.floorShellSignedDistance) + &&std::abs(sample.floorShellSignedDistance)<=sweep->stepSubvoxels) { ++shellBandSampleCount; anyExactBandSample[sampleIndex]=1; diff --git a/Standalone/Core/src/VoxelForgeCore.cpp b/Standalone/Core/src/VoxelForgeCore.cpp index 3089b3f..70b47e5 100644 --- a/Standalone/Core/src/VoxelForgeCore.cpp +++ b/Standalone/Core/src/VoxelForgeCore.cpp @@ -1875,6 +1875,20 @@ namespace VoxelForgeCore { return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape?impl_->shape->DensityAtUnclamped(static_cast(position.x),static_cast(position.y),static_cast(position.z)):0.0f; } float World::passageSourceDensityAt(Vec3d position) const { return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape?impl_->shape->PassageSourceDensityAt(static_cast(position.x),static_cast(position.y),static_cast(position.z)):0.0f; } + float World::passageFloorShellDistanceAt(Vec3d position) const + { return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape?impl_->shape->PassageFloorShellDistanceAt(static_cast(position.x),static_cast(position.y),static_cast(position.z)):std::numeric_limits::infinity(); } + bool World::resolvedPassageLandingPadInfluenceBounds(std::size_t passageIndex, + bool upper,std::array& minimumCoreVoxels, + std::array& maximumCoreVoxels) const + { + if(!impl_||!impl_->shapeReady.load(std::memory_order_acquire)||!impl_->shape + ||!impl_->shape->ResolvedLayout + ||passageIndex>=impl_->shape->ResolvedLayout->passages.size())return false; + return FStandaloneShapeWorld::ResolvedLandingPadInfluenceBounds( + impl_->shape->ResolvedLayout->passages[passageIndex],upper, + impl_->shape->ResolvedLayout->coreVoxelSizeMicrometres, + minimumCoreVoxels,maximumCoreVoxels); + } DetailFieldSample World::detailSampleAt(Vec3d position) const { return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape?impl_->shape->DetailSampleAt(static_cast(position.x),static_cast(position.y),static_cast(position.z)):DetailFieldSample{}; } float World::densityAtLattice(Vec3i position) const diff --git a/Standalone/Research/WorldAssemblerM3/Linux176/RunL176CanonicalHashes.py b/Standalone/Research/WorldAssemblerM3/Linux176/RunL176CanonicalHashes.py new file mode 100644 index 0000000..ada241a --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux176/RunL176CanonicalHashes.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 +"""Verify v242 canonical hashes across Clang/GCC and W1/W8/W12.""" +from __future__ import annotations + +import json +import re +import subprocess +import sys +import time +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[4] +OUT = ROOT / "Saved/Linux/l176/hashes" +DATASETS = { + "owner4": ROOT / "Standalone/data/owner_world.json", + "owner2": ROOT / "Standalone/data/regression/owner_world_2strate_l61.json", +} +RECIPES = { + "owner4": ("surface-world",), + "owner2": ("surface-world", "floating-islands"), +} +BINARIES = { + "clang": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-l176-clang", + "gcc": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-l176-gcc", +} +BASELINE_ROOT = ROOT / "Standalone/Tools/verify/baselines/owner-hash/canonical" +BASELINES = { + ("owner2", "surface-world"): ( + "owner-hash-surface-world-style1-v241.baseline", + "owner-hash-surface-world-style1-v242.baseline"), + ("owner2", "floating-islands"): ( + "owner-hash-floating-islands-style2-v241.baseline", + "owner-hash-floating-islands-style2-v242.baseline"), +} +CANONICAL_RE = re.compile( + r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M) +ALL_HASH_RE = re.compile( + r"^\[Pipeline(?:FullDetailHash|WholeViewHash|MaterialHash|LODHash|ExactQueryHash)\].*$", re.M) +STAMP_RE = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=([0-9a-f]+)$", re.M) + + +def run(dataset: str, recipe: str, compiler: str, workers: int, + expected_stamp: str) -> dict[str, object]: + label = f"{dataset}-{recipe}-{compiler}-w{workers}" + log = OUT / f"{label}.log" + layout = OUT / f"{label}.layout.json" + command = [ + "taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", + "timeout", "1740s", str(BINARIES[compiler]), str(DATASETS[dataset]), + "--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline", + "--force-generate", f"--workers={workers}", "--clip-radius=3", + "--max-clip-level=8", "--max-level=8", f"--recipe={recipe}", + "--stream-hash", f"--resolved-layout-output={layout}", + ] + if dataset == "owner4": + command.append("--seed=0") + started = time.monotonic() + try: + with log.open("w", encoding="utf-8") as output: + result = subprocess.run(command, cwd=ROOT, stdout=output, + stderr=subprocess.STDOUT, check=False, timeout=1740) + code: int | str = result.returncode + except subprocess.TimeoutExpired: + code = "OUTER_TIMEOUT" + content = log.read_text(encoding="utf-8", errors="replace") if log.is_file() else "" + hashes = ALL_HASH_RE.findall(content) + canonical = CANONICAL_RE.findall(content) + stamp = STAMP_RE.search(content) + row: dict[str, object] = { + "dataset": dataset, "recipe": recipe, "compiler": compiler, + "workers": workers, "exit_code": code, + "run_passed": code == 0 and len(canonical) == 12 and len(hashes) == 13 + and stamp is not None and stamp.group(1) == expected_stamp, + "canonical_hashes": canonical, "all_pipeline_hashes": hashes, + "source_stamp": stamp.group(1) if stamp else None, + "elapsed_s": round(time.monotonic() - started, 3), + "log": str(log.relative_to(ROOT)), + } + print("[L176CanonicalHashRun] " + json.dumps(row, sort_keys=True), flush=True) + return row + + +def main() -> int: + if any(not path.is_file() for path in (*DATASETS.values(), *BINARIES.values())): + raise SystemExit("missing v242 owner input or final compiler binary") + OUT.mkdir(parents=True, exist_ok=True) + expected_stamp = subprocess.check_output([ + sys.executable, str(ROOT / "Standalone/Tools/verify/source_stamp.py"), + "stamp", "--root", str(ROOT)], text=True).strip() + rows: list[dict[str, object]] = [] + groups: dict[str, dict[str, object]] = {} + partial = OUT / "runs-partial.json" + for dataset, recipes in RECIPES.items(): + for recipe in recipes: + current: list[dict[str, object]] = [] + for workers in (1, 8, 12): + for compiler in ("clang", "gcc"): + row = run(dataset, recipe, compiler, workers, expected_stamp) + rows.append(row) + current.append(row) + partial.write_text(json.dumps(rows, indent=2) + "\n", + encoding="utf-8") + deterministic = (len(current) == 6 and all(row["run_passed"] for row in current) + and all(row["all_pipeline_hashes"] == current[0]["all_pipeline_hashes"] + for row in current)) + group = f"{dataset}/{recipe}" + groups[group] = { + "all_compilers_workers_match": deterministic, + "canonical_rows": len(current[0]["canonical_hashes"]) if current else 0, + "pipeline_rows": len(current[0]["all_pipeline_hashes"]) if current else 0, + } + print("[L176CanonicalHashGroup] " + json.dumps( + {"group": group, **groups[group]}, sort_keys=True), flush=True) + + passed = len(rows) == 18 and all(row["run_passed"] for row in rows) \ + and all(group["all_compilers_workers_match"] for group in groups.values()) + if passed: + for key, (old_name, new_name) in BASELINES.items(): + canonical = next(row["canonical_hashes"] for row in rows + if (row["dataset"], row["recipe"], row["compiler"], row["workers"]) + == (*key, "clang", 12)) + (BASELINE_ROOT / new_name).write_text("\n".join(canonical) + "\n", + encoding="utf-8") + old = CANONICAL_RE.findall((BASELINE_ROOT / old_name).read_text( + encoding="utf-8", errors="replace")) + groups[f"{key[0]}/{key[1]}"]["changed_v241_rows"] = sum( + a != b for a, b in zip(old, canonical)) + summary = {"status": "PASS" if passed else "FAIL", + "source_stamp": expected_stamp, "runs": rows, "groups": groups} + (OUT / "summary.json").write_text(json.dumps(summary, indent=2) + "\n", + encoding="utf-8") + print(f"[L176CanonicalHashSummary] status={summary['status']} runs={len(rows)} " + f"groups={len(groups)}", flush=True) + return 0 if passed else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/Standalone/Research/WorldAssemblerM3/Linux176/RunL176SeedSweep.py b/Standalone/Research/WorldAssemblerM3/Linux176/RunL176SeedSweep.py new file mode 100755 index 0000000..0df2681 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux176/RunL176SeedSweep.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +"""Run L176 step-3 audits and route gates for owner seeds 0..15.""" +from __future__ import annotations + +import json +import os +import re +import subprocess +import sys +import time +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[4] +OUT = ROOT / "Saved/Linux/l176/seeds" +BIN = ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-l176-clang" +DATA = ROOT / "Standalone/data/owner_world.json" +STAMP_TOOL = ROOT / "Standalone/Tools/verify/source_stamp.py" +STAMP_RE = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=([0-9a-f]+)$", re.M) + + +def one(pattern: str, content: str) -> re.Match[str] | None: + return re.search(pattern, content, re.M) + + +def evaluate(content: str, expected_stamp: str, require_l130: bool) -> dict[str, object]: + stamp = STAMP_RE.search(content) + seam = one(r"^\[PassageTileSeamAudit\].*$", content) + seam_fields = dict(re.findall(r"\b([a-z0-9_]+)=([^ ]+)", seam.group(0))) if seam else {} + pad = one(r"^\[LandingPadAuditSummary\].*$", content) + air = one(r"^\[NoAddedSolidOverHostAirAudit\].*$", content) + floor = one(r"^\[PassageFloorShellAuditSummary\].*$", content) + interior = one(r"^\[PassageInteriorCapsuleAuditSummary\].*$", content) + end_run = one(r"^\[PassageEndRunAuditSummary\].*$", content) + l130 = one(r"^\[L130PassageReachProofAudit\].*$", content) + route_rows = {} + for name, strate in (("tunnel_network", 2), ("vertical_shafts", 3)): + route_rows[name] = one(rf"^\[ResolvedStrateRouteAudit\] strate={strate} .*status=(PASS|FAIL)$", content) + controls = { + name: bool(one(rf"^\[{label}BlockedRouteControl\].*expected=FAIL status=PASS$", content)) + for name, label in (("tunnel_network", "TunnelNetwork"), + ("vertical_shafts", "VerticalShafts")) + } + checks = { + "source_stamp": bool(stamp and stamp.group(1) == expected_stamp), + "seam_zero_mismatches": seam_fields.get("mismatches") == "0", + "seam_no_exempt_pairs": seam_fields.get("exempt_pairs") == "0", + "landing_pads": bool(pad and re.search( + r"pads=6 passed=6 negative_controls=24/24 status=PASS$", pad.group(0))), + "no_added_solid_over_host_air": bool(air and re.search( + r"added_solid_samples=0 .*negative_control=DETECTED status=PASS$", air.group(0))), + "floor_shell": bool(floor and "status=PASS" in floor.group(0)), + "passage_interior_air": bool(interior and "status=PASS" in interior.group(0)), + "end_runs": bool(end_run and re.search( + r"passages=3 measured=3 passed=3 status=PASS$", end_run.group(0))), + "tunnel_network_route": bool(route_rows["tunnel_network"] and + "status=PASS" in route_rows["tunnel_network"].group(0)), + "vertical_shafts_route": bool(route_rows["vertical_shafts"] and + "status=PASS" in route_rows["vertical_shafts"].group(0)), + "route_blocked_controls": all(controls.values()), + } + if l130: + fields = dict(re.findall(r"\b([a-z0-9_]+)=([^ ]+)", l130.group(0))) + checks["l130_pad_bounds"] = (fields.get("status") == "PASS" + and fields.get("pad_proof_misses") == "0" + and fields.get("pad_feature_misses") == "0" + and fields.get("pad_stamp_misses") == "0") + elif require_l130: + checks["l130_pad_bounds"] = False + else: + checks["l130_pad_bounds"] = True + return { + "checks": checks, + "step3_passed": all(checks.values()), + "seam_summary": seam_fields, + "route_rows": {key: value.group(0) if value else None + for key, value in route_rows.items()}, + "resolved_passage_summary": (one(r"^\[ResolvedPassageAuditSummary\].*$", content).group(0) + if one(r"^\[ResolvedPassageAuditSummary\].*$", content) else None), + "l130_line": l130.group(0) if l130 else None, + } + + +def main() -> int: + import argparse + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--seed-start", type=int, default=0) + parser.add_argument("--seed-stop", type=int, default=16, help="exclusive") + parser.add_argument("--no-resume", action="store_true") + args = parser.parse_args() + if not 0 <= args.seed_start <= args.seed_stop <= 16: + raise SystemExit("seed range must be within 0..16") + if not BIN.is_file() or not DATA.is_file(): + raise SystemExit(f"missing binary or owner fixture: {BIN}") + OUT.mkdir(parents=True, exist_ok=True) + stamp = subprocess.check_output( + [sys.executable, str(STAMP_TOOL), "stamp", "--root", str(ROOT)], text=True).strip() + rows: list[dict[str, object]] = [] + for seed in range(args.seed_start, args.seed_stop): + log, layout, exit_file = (OUT / f"seed{seed}.log", OUT / f"seed{seed}.layout.json", + OUT / f"seed{seed}.exit") + command = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", + "timeout", "1740s", str(BIN), str(DATA), "--world-assembler-prototype", + "--stream-benchmark", "--stream-pipeline", "--workers=8", "--clip-radius=3", + "--max-clip-level=8", "--max-level=0", "--force-generate", f"--seed={seed}", + "--recipe=surface-world", "--density-dispatch-report", + "--audit-resolved-layout-passages", "--audit-passage-tile-seams", + f"--resolved-layout-output={layout}"] + env = os.environ.copy() + if seed == 0: + env["VOXELFORGE_L130_PASSAGE_REACH_PROOF_AUDIT"] = "1" + resumed = False + started = time.monotonic() + if not args.no_resume and log.is_file() and layout.is_file() and exit_file.is_file(): + code: int | str = int(exit_file.read_text().strip()) + resumed = True + else: + try: + with log.open("w", encoding="utf-8") as output: + proc = subprocess.run(command, cwd=ROOT, env=env, stdout=output, + stderr=subprocess.STDOUT, check=False, timeout=1740) + code = proc.returncode + except subprocess.TimeoutExpired: + code = "OUTER_TIMEOUT" + if isinstance(code, int): + exit_file.write_text(f"{code}\n", encoding="utf-8") + content = log.read_text(encoding="utf-8", errors="replace") if log.is_file() else "" + result = evaluate(content, stamp, require_l130=(seed == 0)) + row = {"seed": seed, "process_exit": code, "source_stamp": stamp, + "elapsed_s": round(time.monotonic() - started, 3), "resumed": resumed, + "step3_passed": result["step3_passed"], **result, + "log": str(log.relative_to(ROOT)), "layout": str(layout.relative_to(ROOT))} + rows.append(row) + (OUT / "runs-partial.json").write_text(json.dumps(rows, indent=2) + "\n") + print("[L176SeedRun] " + json.dumps(row, sort_keys=True), flush=True) + passed = sum(bool(row["step3_passed"]) for row in rows) + summary = {"source_stamp": stamp, "seeds": list(range(args.seed_start, args.seed_stop)), + "step3_passed": passed, "step3_failed": len(rows) - passed, "rows": rows} + (OUT / "summary.json").write_text(json.dumps(summary, indent=2) + "\n") + print(f"[L176SeedSummary] step3={passed}/{len(rows)}", flush=True) + return 0 if passed == len(rows) else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/Standalone/Source/StreamBenchmarkPipeline.h b/Standalone/Source/StreamBenchmarkPipeline.h index bfb17d3..d2d712e 100644 --- a/Standalone/Source/StreamBenchmarkPipeline.h +++ b/Standalone/Source/StreamBenchmarkPipeline.h @@ -3079,10 +3079,13 @@ namespace StandaloneStreamBenchmark if(density<0.0f) { if(inPassageInterior)++audit.passageInteriorSolidSamples; - audit.firstBlocked.push_back({xMetres,yMetres,zMetres,density}); - std::printf("[ResolvedWalkDispatch] kind=blocked xyz_m=(%.3f,%.3f,%.3f) path=%s\n", - xMetres,yMetres,zMetres,Field.densityDispatchPathAt(position).c_str()); - ++audit.blocked;return true; + ++audit.blocked; + if(audit.firstBlocked.size()<16) + audit.firstBlocked.push_back({xMetres,yMetres,zMetres,density}); + if(audit.blocked<=16) + std::printf("[ResolvedWalkDispatch] kind=blocked xyz_m=(%.3f,%.3f,%.3f) path=%s\n", + xMetres,yMetres,zMetres,Field.densityDispatchPathAt(position).c_str()); + return true; } return false; }; @@ -3179,14 +3182,16 @@ namespace StandaloneStreamBenchmark if(supportedProbes(q)*World.CoreVoxelSizeMicrons /(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel*1000000.0);}; - const auto passageUpperMouthFloorDepth=[&]( - const VoxelForgeCore::ResolvedLayoutPassageV1& passage) - { - const auto upperStratum=std::find_if(ResolvedLayout->strates.begin(), - ResolvedLayout->strates.end(),[&](const auto& stratum) - {return stratum.strateIndex==passage.upperStrateIndex;}); - const double mouthCenterHeight=upperStratum!=ResolvedLayout->strates.end() - &&upperStratum->generatorKind=="SurfaceWorld"?0.98:1.13; - const double controlZ=qToMetres( - passage.controlPoints.front().positionSubvoxels[2]); - const double authoredMouthDepth=std::max(0.0,controlZ - -qToMetres(passage.upperMouthSubvoxels[2])+mouthCenterHeight); - if(upperStratum==ResolvedLayout->strates.end() - ||(upperStratum->generatorKind!="FloatingIslands" - &&upperStratum->generatorKind!="TunnelNetwork" - &&upperStratum->generatorKind!="SurfaceWorld")) - return authoredMouthDepth; - std::vector> route; - for(const auto& anchor:upperStratum->anchors) - if(anchor.kind=="route_point"&&anchor.values.size()>=3) - route.push_back({qToMetres(anchor.values[0]), - qToMetres(anchor.values[1]),qToMetres(anchor.values[2])}); - if(upperStratum->generatorKind=="TunnelNetwork"&&route.size()>2) - route.erase(route.begin(),route.end()-2); - double routeHeight=0.0,distanceSquared=0.0; - const double mouthX=qToMetres(passage.controlPoints.front().positionSubvoxels[0]); - const double mouthY=qToMetres(passage.controlPoints.front().positionSubvoxels[1]); - if(!VoxelForgeCore::PassageSpline::ClosestRouteHeightAtXY( - route,mouthX,mouthY,routeHeight,distanceSquared) - ||distanceSquared>std::pow( - qToMetres(passage.controlPoints.front().radiusSubvoxels)+2.0,2.0)) - return authoredMouthDepth; - const double routeAlignedDepth=std::max(0.0, - controlZ-routeHeight+mouthCenterHeight); - return std::max(authoredMouthDepth,routeAlignedDepth); - }; const auto passageSplineControls=[&]( const VoxelForgeCore::ResolvedLayoutPassageV1& passage) { @@ -3602,9 +3571,6 @@ namespace StandaloneStreamBenchmark qToMetres(point.positionSubvoxels[1]), qToMetres(point.positionSubvoxels[2]), qToMetres(point.radiusSubvoxels)}); - if(!controls.empty()) - controls.front()[3]=std::max(controls.front()[3], - passageUpperMouthFloorDepth(passage)); return controls; }; const auto passageSplineFloorProfile=[&]( @@ -3615,61 +3581,6 @@ namespace StandaloneStreamBenchmark for(const auto& point:passage.controlPoints) profile.push_back(qToMetres(point.positionSubvoxels[2] -point.radiusSubvoxels)); - const auto controls=passageSplineControls(passage); - const auto routeForStrate=[&](std::int32_t strateIndex) - { - std::vector> route; - const auto stratum=std::find_if(ResolvedLayout->strates.begin(), - ResolvedLayout->strates.end(),[&](const auto& candidate) - {return candidate.strateIndex==strateIndex;}); - if(stratum!=ResolvedLayout->strates.end() - &&(stratum->generatorKind=="TunnelNetwork" - ||stratum->generatorKind=="FloatingIslands" - ||stratum->generatorKind=="SurfaceWorld")) - for(const auto& anchor:stratum->anchors) - if(anchor.kind=="route_point"&&anchor.values.size()>=3) - route.push_back({qToMetres(anchor.values[0]), - qToMetres(anchor.values[1]),qToMetres(anchor.values[2])}); - return route; - }; - const auto upper=std::find_if(ResolvedLayout->strates.begin(), - ResolvedLayout->strates.end(),[&](const auto& stratum) - {return stratum.strateIndex==passage.upperStrateIndex;}); - if(!passage.floorProfileIncludesLandingChambers - &&upper!=ResolvedLayout->strates.end()&&!controls.empty() - &&(upper->generatorKind=="TunnelNetwork" - ||upper->generatorKind=="SurfaceWorld")) - { - auto route=routeForStrate(passage.upperStrateIndex); - if(upper->generatorKind=="TunnelNetwork"&&route.size()>2) - route.erase(route.begin(),route.end()-2); - double routeHeight=0.0,distanceSquared=0.0; - if(VoxelForgeCore::PassageSpline::ClosestRouteHeightAtXY(route, - controls.front()[0],controls.front()[1],routeHeight,distanceSquared) - &&distanceSquared<=std::pow(controls.front()[3]+2.0,2.0)) - VoxelForgeCore::PassageSpline::BlendEndpointFloorToRoute( - controls,profile,0,route,1.0, - upper->generatorKind=="SurfaceWorld"?0.98:1.13); - } - const auto lower=std::find_if(ResolvedLayout->strates.begin(), - ResolvedLayout->strates.end(),[&](const auto& stratum) - {return stratum.strateIndex==passage.lowerStrateIndex;}); - const std::size_t routeEnd=std::clamp( - passage.lowerMouthControlIndex,1,passage.controlPoints.size()-1); - if(!passage.floorProfileIncludesLandingChambers - &&lower!=ResolvedLayout->strates.end() - &&lower->generatorKind=="TunnelNetwork"&&routeEnd2)route.resize(2); - double routeHeight=0.0,distanceSquared=0.0; - if(VoxelForgeCore::PassageSpline::ClosestRouteHeightAtXY(route, - controls[routeEnd][0],controls[routeEnd][1], - routeHeight,distanceSquared) - &&distanceSquared<=std::pow(controls[routeEnd][3]+2.0,2.0)) - VoxelForgeCore::PassageSpline::BlendEndpointFloorToRoute( - controls,profile,routeEnd,route,1.0); - } return profile; }; const auto passageBodyFloorDepth=[&]( @@ -3691,8 +3602,8 @@ namespace StandaloneStreamBenchmark if(distance>=10.0&&totalLength-distance>=10.0) minimumBodyRadius=std::min(minimumBodyRadius,radius); }; - for(const auto& segment:segments)totalLength=segment.alongMetres - +segment.lengthMetres; + for(const auto& segment:segments) + totalLength=segment.alongMetres+segment.lengthMetres; consider(0.0,controls.front()[3]); for(const auto& segment:segments) consider(segment.alongMetres+segment.lengthMetres,segment.b[3]); @@ -3709,46 +3620,6 @@ namespace StandaloneStreamBenchmark double x=0.0,y=0.0,z=0.0,floorZ=0.0,radius=0.0,distance=0.0; std::size_t segmentIndex=0; }; - const double landingSubvoxelsPerMetre=static_cast( - VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel)*1000000.0 - /World.CoreVoxelSizeMicrons; - const auto blendLandingChamberFloor=[&]( - const VoxelForgeCore::ResolvedLayoutPassageV1& passage, - double alongMetres,double totalLengthMetres,double x,double y, - double plannedFloor) - { - if(passage.floorProfileIncludesLandingChambers)return plannedFloor; - constexpr double LandingFloorBlendMetres=12.0; - const auto weight=[&](double distanceMetres, - const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& chamber) - { - const double radius=qToMetres(chamber.radiusSubvoxels); - if(!(radius>0.0))return 0.0; - if(distanceMetres<=radius)return 1.0; - if(distanceMetres>=radius+LandingFloorBlendMetres)return 0.0; - const double t=(distanceMetres-radius)/LandingFloorBlendMetres; - return 1.0-t*t*(3.0-2.0*t); - }; - const double upperWeight=weight(std::max(0.0,alongMetres), - passage.upperLandingChamber); - const double lowerWeight=weight(std::max(0.0, - totalLengthMetres-alongMetres),passage.lowerLandingChamber); - if(upperWeight<=0.0&&lowerWeight<=0.0)return plannedFloor; - const auto floorAt=[&]( - const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& chamber) - { - return VoxelForgeCore::ResolvedLayoutPassageLandingFloorAtXYSubvoxels( - chamber,x*landingSubvoxelsPerMetre,y*landingSubvoxelsPerMetre) - /landingSubvoxelsPerMetre; - }; - const double upperFloor=floorAt(passage.upperLandingChamber); - const double lowerFloor=floorAt(passage.lowerLandingChamber); - const double totalWeight=upperWeight+lowerWeight; - if(totalWeight<=1.0) - return plannedFloor*(1.0-totalWeight) - +upperFloor*upperWeight+lowerFloor*lowerWeight; - return (upperFloor*upperWeight+lowerFloor*lowerWeight)/totalWeight; - }; const auto passageSmoothRoute=[&]( const VoxelForgeCore::ResolvedLayoutPassageV1& passage,double bodyDepth) { @@ -3761,16 +3632,13 @@ namespace StandaloneStreamBenchmark const auto segments=VoxelForgeCore::PassageSpline::BuildSegments( controls,routeEnd,8.0,1.0,floorProfile,0.100); const auto effectiveCenterZ=[&](const auto& point,double plannedFloor) - { - return plannedFloor+passageTubeDepthAt(point[3],bodyDepth); - }; + {return plannedFloor+passageTubeDepthAt(point[3],bodyDepth);}; if(!segments.empty()) { const auto& first=segments.front(); route.push_back({first.a[0],first.a[1], effectiveCenterZ(first.a,first.plannedFloorA), - first.plannedFloorA,first.a[3], - first.alongMetres,0}); + first.plannedFloorA,first.a[3],first.alongMetres,0}); for(const auto& segment:segments) route.push_back({segment.b[0],segment.b[1], effectiveCenterZ(segment.b,segment.plannedFloorB), @@ -3778,156 +3646,26 @@ namespace StandaloneStreamBenchmark segment.alongMetres+segment.lengthMetres, segment.controlSpan+1}); } - if(route.size()>=2) - { - const double totalLength=route.back().distance; - for(auto& point:route) - { - point.floorZ=blendLandingChamberFloor(passage,point.distance, - totalLength,point.x,point.y,point.floorZ); - point.z=point.floorZ+passageTubeDepthAt(point.radius,bodyDepth); - } - } return route; }; std::vector allPassageBodyFloorDepths; std::vector> allPassageSmoothRoutes; - struct FMouthFloorBlend - { - std::vector> receivingRoute; - double mouthX=0.0,mouthY=0.0,mouthFloor=0.0; - double tangentX=0.0,tangentY=0.0; - }; - struct FPassageFloorBlends - { - FMouthFloorBlend upper,lower; - }; - std::vector allPassageFloorBlends; allPassageBodyFloorDepths.reserve(ResolvedLayout->passages.size()); allPassageSmoothRoutes.reserve(ResolvedLayout->passages.size()); - allPassageFloorBlends.reserve(ResolvedLayout->passages.size()); for(const auto& passage:ResolvedLayout->passages) { const double bodyDepth=passageBodyFloorDepth(passage); allPassageBodyFloorDepths.push_back(bodyDepth); allPassageSmoothRoutes.push_back(passageSmoothRoute(passage,bodyDepth)); - FPassageFloorBlends blends; - const auto controls=passageSplineControls(passage); - const auto floorProfile=passageSplineFloorProfile(passage); - const std::size_t routeEnd=std::clamp( - passage.lowerMouthControlIndex,1,passage.controlPoints.size()-1); - const auto segments=VoxelForgeCore::PassageSpline::BuildSegments( - controls,routeEnd,8.0,1.0,floorProfile,0.100); - const auto upperStratum=std::find_if(ResolvedLayout->strates.begin(), - ResolvedLayout->strates.end(),[&](const auto& stratum) - {return stratum.strateIndex==passage.upperStrateIndex;}); - if(upperStratum!=ResolvedLayout->strates.end() - &&(upperStratum->generatorKind=="FloatingIslands" - ||upperStratum->generatorKind=="TunnelNetwork" - ||upperStratum->generatorKind=="SurfaceWorld")) - { - for(const auto& anchor:upperStratum->anchors) - if(anchor.kind=="route_point"&&anchor.values.size()>=3) - blends.upper.receivingRoute.push_back({ - qToMetres(anchor.values[0]),qToMetres(anchor.values[1]), - qToMetres(anchor.values[2])}); - if(upperStratum->generatorKind=="TunnelNetwork" - &&blends.upper.receivingRoute.size()>2) - blends.upper.receivingRoute.erase( - blends.upper.receivingRoute.begin(), - blends.upper.receivingRoute.end()-2); - if(!segments.empty()) - { - blends.upper.mouthX=controls.front()[0]; - blends.upper.mouthY=controls.front()[1]; - blends.upper.mouthFloor=floorProfile.front(); - blends.upper.tangentX=segments.front().b[0]-segments.front().a[0]; - blends.upper.tangentY=segments.front().b[1]-segments.front().a[1]; - } - if(upperStratum->generatorKind=="TunnelNetwork") - for(const auto& edge:ResolvedLayout->cavernEdges) - if(edge.startAnchorStableId==passage.stableId+"/upper-mouth" - &&edge.controlPointsMicrometres.size()>=2) - { - blends.upper.tangentX= - (edge.controlPointsMicrometres[0][0] - -edge.controlPointsMicrometres[1][0])/1000000.0; - blends.upper.tangentY= - (edge.controlPointsMicrometres[0][1] - -edge.controlPointsMicrometres[1][1])/1000000.0; - } - } - const auto lowerStratum=std::find_if(ResolvedLayout->strates.begin(), - ResolvedLayout->strates.end(),[&](const auto& stratum) - {return stratum.strateIndex==passage.lowerStrateIndex;}); - if(lowerStratum!=ResolvedLayout->strates.end() - &&lowerStratum->generatorKind=="TunnelNetwork" - &&!controls.empty()&&routeEndanchors) - if(anchor.kind=="route_point"&&anchor.values.size()>=3) - blends.lower.receivingRoute.push_back({ - qToMetres(anchor.values[0]),qToMetres(anchor.values[1]), - qToMetres(anchor.values[2])}); - if(blends.lower.receivingRoute.size()>2) - blends.lower.receivingRoute.resize(2); - blends.lower.mouthX=controls[routeEnd][0]; - blends.lower.mouthY=controls[routeEnd][1]; - blends.lower.mouthFloor=floorProfile[routeEnd]; - if(!segments.empty()) - { - blends.lower.tangentX=controls[routeEnd][0] - -controls[routeEnd-1][0]; - blends.lower.tangentY=controls[routeEnd][1] - -controls[routeEnd-1][1]; - } - for(const auto& edge:ResolvedLayout->cavernEdges) - if(edge.startAnchorStableId==passage.stableId+"/lower-mouth" - &&edge.controlPointsMicrometres.size()>=2) - { - blends.lower.tangentX= - (edge.controlPointsMicrometres[1][0] - -edge.controlPointsMicrometres[0][0])/1000000.0; - blends.lower.tangentY= - (edge.controlPointsMicrometres[1][1] - -edge.controlPointsMicrometres[0][1])/1000000.0; - } - } - allPassageFloorBlends.push_back(std::move(blends)); } const auto passageFloorAtXY=[&](std::size_t passageIndex,double x,double y, double plannedFloor) { - if(passageIndex>=allPassageFloorBlends.size())return plannedFloor; - const auto& blends=allPassageFloorBlends[passageIndex]; - double floor=plannedFloor; - const auto upperStratum=std::find_if(ResolvedLayout->strates.begin(), - ResolvedLayout->strates.end(),[&](const auto& stratum) - {return stratum.strateIndex - ==ResolvedLayout->passages[passageIndex].upperStrateIndex;}); - const double upperPlayerCenterHeight=upperStratum - !=ResolvedLayout->strates.end() - &&upperStratum->generatorKind=="SurfaceWorld"?0.98:1.13; - if(!ResolvedLayout->passages[passageIndex] - .floorProfileIncludesLandingChambers) - { - floor=VoxelForgeCore::PassageSpline::BlendUpperMouthFloorAtXY( - blends.upper.receivingRoute,x,y,floor,blends.upper.mouthX, - blends.upper.mouthY,blends.upper.mouthFloor, - blends.upper.tangentX,blends.upper.tangentY, - VoxelForgeCore::PassageSpline::UpperMouthFloorBlendRadiusMetres, - upperPlayerCenterHeight); - floor=VoxelForgeCore::PassageSpline::BlendUpperMouthFloorAtXY( - blends.lower.receivingRoute,x,y,floor,blends.lower.mouthX, - blends.lower.mouthY,blends.lower.mouthFloor, - -blends.lower.tangentX,-blends.lower.tangentY, - VoxelForgeCore::PassageSpline::UpperMouthFloorBlendRadiusMetres,1.13); - } - if(passageIndex>=allPassageSmoothRoutes.size())return floor; + if(passageIndex>=allPassageSmoothRoutes.size())return plannedFloor; const auto& route=allPassageSmoothRoutes[passageIndex]; - if(route.size()<2)return floor; + if(route.size()<2)return plannedFloor; double closestDistanceSquared=std::numeric_limits::infinity(); - double closestAlong=0.0; + double floor=plannedFloor; for(std::size_t index=1;indexpassages[passageIndex],closestAlong, - route.back().distance,x,y,floor); + return floor; }; const auto interpolatePassageRoute=[]( const std::vector& route,double distance) @@ -4102,15 +3838,6 @@ namespace StandaloneStreamBenchmark &&passageIndex!=selectedPassage)continue; const auto& passage=ResolvedLayout->passages[passageIndex]; if(passage.controlPoints.size()<2)continue; - const auto inLandingChamber=[&]( - const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& chamber) - { - return VoxelForgeCore::IsInsideResolvedLayoutPassageLandingChamberV1( - chamber,xMetres*subvoxelsPerMetre, - yMetres*subvoxelsPerMetre,zMetres*subvoxelsPerMetre); - }; - if(inLandingChamber(passage.upperLandingChamber) - ||inLandingChamber(passage.lowerLandingChamber))return true; const auto& route=allPassageSmoothRoutes[passageIndex]; if(route.size()<2)continue; double bestRoundDistance=std::numeric_limits::infinity(); @@ -5079,78 +4806,6 @@ namespace StandaloneStreamBenchmark [index](const auto& s){return s.strateIndex==index;}); return it==ResolvedLayout->strates.end()?nullptr:&*it; }; - const auto landingChamberConnectionContains=[&]( - const VoxelForgeCore::ResolvedLayoutPassageV1& passage,bool upper, - double xMetres,double yMetres,double zMetres) - { - const auto& chamber=upper?passage.upperLandingChamber - :passage.lowerLandingChamber; - const auto* stratum=findStratum(upper?passage.upperStrateIndex - :passage.lowerStrateIndex); - if(!stratum||chamber.radiusSubvoxels<=0)return false; - std::vector route; - for(const auto& anchor:stratum->anchors) - if(anchor.kind=="route_point"&&anchor.values.size()>=3) - route.push_back(&anchor); - if(route.size()<2)return false; - const double cx=qToMetres(chamber.floorCenterSubvoxels[0]); - const double cy=qToMetres(chamber.floorCenterSubvoxels[1]); - const double mouthZ=qToMetres(upper?passage.upperMouthSubvoxels[2] - :passage.lowerMouthSubvoxels[2]); - std::size_t endpoint=route.size(); - double endpointDistanceSquared=1.0; - for(std::size_t index=0;indexvalues[0])-cx; - const double dy=qToMetres(route[index]->values[1])-cy; - const double z=qToMetres(route[index]->values[2]); - const double distanceSquared=dx*dx+dy*dy; - if(distanceSquared routeDistance(route.size(),0.0); - for(std::size_t index=1;indexvalues[0] - -route[index-1]->values[0]); - const double dy=qToMetres(route[index]->values[1] - -route[index-1]->values[1]); - routeDistance[index]=routeDistance[index-1]+std::hypot(dx,dy); - } - for(std::size_t index=1;index9.0)continue; - const double routeX=ax+t*dx,routeY=ay+t*dy; - const double routeZ=az+t*qToMetres(b.values[2]-a.values[2]); - const double lateralDistance=std::hypot(xMetres-routeX, - yMetres-routeY); - if(stratum->generatorKind=="TunnelNetwork" - ||stratum->generatorKind=="VerticalShafts") - { - const double routeFloor=routeZ-1.13; - if(lateralDistance<=0.80&&zMetres>=routeFloor-0.30 - &&zMetres<=routeFloor+2.25)return true; - } - else if(lateralDistance<=1.25 - &&std::abs(zMetres-routeZ)<=1.5)return true; - } - return false; - }; const auto controlSnapshot=CoreSnapshot.withoutResolvedLayoutPassages(); VoxelForgeCore::World blockedWorld(controlSnapshot); std::string blockedCompileError; @@ -5162,444 +4817,53 @@ namespace StandaloneStreamBenchmark blockedCompileError.c_str()); bAuditFailureDetected=true; } - struct FPassageShellAudit + struct FPassageFloorShellAudit { - std::uint64_t floorSamples=0,floorViolations=0,openAirSamples=0; - std::uint64_t wallSamples=0,wallViolations=0; - std::uint64_t chamberFloorSamples=0,chamberFloorViolations=0; - std::uint64_t chamberWallSamples=0,chamberWallViolations=0; - std::uint64_t chamberConnectionSkips=0; - std::uint64_t floorInteriorWins=0,wallInteriorWins=0; - std::uint64_t floorMouthBlendSkips=0,wallMouthBlendSkips=0; - std::uint64_t interiorSamples=0,interiorViolations=0; - std::vector openAirDistancesMetres; - std::vector> firstViolations; - std::vector> firstWallViolations; + std::uint64_t hostAirSamples=0,shellSamples=0,missingSamples=0; }; - constexpr double PassageShellAuditSpacingMetres=0.25; - constexpr std::array PassageShellAuditDepthsMetres{{0.125,0.375}}; - constexpr float PassageShellDensityTolerance=1.0e-6f; - std::vector surfaceLandingPassages(ResolvedLayout->passages.size(),false); - for(std::size_t passageIndex=0;passageIndexpassages.size(); - ++passageIndex) + constexpr double PassageFloorShellAuditSpacingMetres=0.5; + constexpr std::array PassageFloorShellAuditDepthsMetres{{0.125,0.375}}; + constexpr std::array,5> PassageFloorShellAuditOffsets{{ + {{0.0,0.0}},{{0.30,0.0}},{{-0.30,0.0}},{{0.0,0.30}},{{0.0,-0.30}}}}; + const auto auditPassageFloorShell=[&](const VoxelForgeCore::World& field, + const VoxelForgeCore::World& layerContent,std::size_t passageIndex) { - const auto& passage=ResolvedLayout->passages[passageIndex]; - const auto upper=std::find_if(ResolvedLayout->strates.begin(), - ResolvedLayout->strates.end(),[&](const auto& stratum) - {return stratum.strateIndex==passage.upperStrateIndex;}); - surfaceLandingPassages[passageIndex]=upper!=ResolvedLayout->strates.end() - &&upper->generatorKind=="SurfaceWorld" - &&upper->lowerInterfaceCapabilities.surfaceToTunnelLandingSupport; - } - const auto expectedPassageShellAt=[&](double xMetres,double yMetres, - double zMetres) - { - constexpr double CornerBlendMetres=0.50; - constexpr double ReliefBoundMetres=0.21; - double nearestWallShellDistance=std::numeric_limits::infinity(); - bool mouthBlend=false; - if(passageInteriorWinsAt(xMetres,yMetres,zMetres)) - return std::pair{0,nearestWallShellDistance}; - for(std::size_t passageIndex=0; - passageIndexpassages.size();++passageIndex) - { - const auto& passage=ResolvedLayout->passages[passageIndex]; - if(passage.controlPoints.size()<2)continue; - const auto& path=allPassageSmoothRoutes[passageIndex]; - if(path.size()<2)continue; - const double totalLength=path.back().distance; - for(std::size_t segment=1;segment1.0e-12 - ?((xMetres-ax)*dx+(yMetres-ay)*dy) - /horizontalLengthSquared - :std::clamp((zMetres-az)*dz/lengthSquared,0.0,1.0); - const double floorAlong=std::lerp(a.distance,b.distance,horizontalT); - const double t=std::clamp(((xMetres-ax)*dx+(yMetres-ay)*dy - +(zMetres-az)*dz)/lengthSquared,0.0,1.0); - const double centerX=ax+t*dx,centerY=ay+t*dy,centerZ=az+t*dz; - const double radius=std::lerp(a.radius,b.radius,t); - const double along=std::lerp(a.distance,b.distance,t); - const auto floorPose=interpolatePassageRoute(path,floorAlong); - const double floorCenterZ=passageFloorAtXY(passageIndex, - xMetres,yMetres,floorPose.floorZ); - const double roundDistance=std::sqrt((xMetres-centerX)*(xMetres-centerX) - +(yMetres-centerY)*(yMetres-centerY) - +(zMetres-centerZ)*(zMetres-centerZ))-radius; - const double floorDistance=floorCenterZ-zMetres; - const double blend=std::max(CornerBlendMetres- - std::abs(roundDistance-floorDistance),0.0)/CornerBlendMetres; - const double flatDistance=std::max(roundDistance,floorDistance) - +blend*blend*blend*(CornerBlendMetres/6.0); - const double mouthDistance=std::max(0.0,std::min(along, - totalLength-along)); - if(flatDistance<=0.0)continue; - const double fade=std::clamp(mouthDistance/5.0,0.0,1.0); - const double smoothFade=fade*fade*(3.0-2.0*fade); - const double wallElevation=-floorDistance; - const double wallThickness=wallElevation>=0.0&&wallElevation<=1.0 - ?smoothFade*0.75:0.0; - if(wallThickness>1.0e-9) - { - const double shellRadius=wallThickness*0.5; - const double capCenter=1.0-shellRadius; - const double x=flatDistance-shellRadius; - const double boxHalfHeight=capCenter*0.5; - const double boxX=std::abs(x)-shellRadius; - const double boxY=std::abs(wallElevation-boxHalfHeight) - -boxHalfHeight; - const double boxDistance=std::min(std::max(boxX,boxY),0.0) - +std::hypot(std::max(boxX,0.0),std::max(boxY,0.0)); - const double capDistance=std::hypot(x,wallElevation-capCenter) - -shellRadius; - nearestWallShellDistance=std::min(nearestWallShellDistance, - std::min(boxDistance,capDistance)); - } - else if(mouthDistance<5.0)mouthBlend=true; - } - } - if(nearestWallShellDistance<-ReliefBoundMetres) - return std::pair{1,nearestWallShellDistance}; - if(nearestWallShellDistance{-1,nearestWallShellDistance}; - return std::pair{0,nearestWallShellDistance}; - }; - const auto auditPassageShell=[&](const VoxelForgeCore::World& Field, - const VoxelForgeCore::World& Native, - const VoxelForgeCore::ResolvedLayoutPassageV1& passage, - std::size_t passageIndex) - { - FPassageShellAudit audit; + FPassageFloorShellAudit audit; + if(passageIndex>=allPassageSmoothRoutes.size())return audit; const auto& route=allPassageSmoothRoutes[passageIndex]; if(route.size()<2)return audit; - const double totalRouteLength=route.back().distance; - const double bodyFloorDepth=allPassageBodyFloorDepths[passageIndex]; - constexpr std::array,5> floorOffsets{{ - {{0.0,0.0}},{{0.30,0.0}},{{-0.30,0.0}}, - {{0.0,0.30}},{{0.0,-0.30}}}}; - const auto expectedFloorShellMarginAt=[&](double xMetres, - double yMetres,double zMetres) - { - constexpr double CornerBlendMetres=0.50; - double minimumShellDistance=std::numeric_limits::infinity(); - for(std::size_t candidatePassage=0; - candidatePassage1.0e-12 - ?((xMetres-a.x)*dx+(yMetres-a.y)*dy) - /horizontalLengthSquared:t; - const double centerX=a.x+t*dx,centerY=a.y+t*dy, - centerZ=a.z+t*dz; - const double radius=std::lerp(a.radius,b.radius,t); - const double along=std::lerp(a.distance,b.distance,t); - const double floorAlong=std::lerp(a.distance,b.distance,floorT); - const double plannedFloor=passageFloorAtXY(candidatePassage, - xMetres,yMetres,interpolatePassageRoute(candidateRoute, - floorAlong).floorZ); - const double roundDistance=std::sqrt( - (xMetres-centerX)*(xMetres-centerX) - +(yMetres-centerY)*(yMetres-centerY) - +(zMetres-centerZ)*(zMetres-centerZ))-radius; - const double floorDistance=plannedFloor-zMetres; - const double blend=std::max(CornerBlendMetres - -std::abs(roundDistance-floorDistance),0.0) - /CornerBlendMetres; - const double flatDistance=std::max(roundDistance,floorDistance) - +blend*blend*blend*(CornerBlendMetres/6.0); - const double mouthDistance=std::max(0.0,std::min(along, - candidateLength-along)); - const double fade=std::clamp(mouthDistance/5.0,0.0,1.0); - const double smoothFade=fade*fade*(3.0-2.0*fade); - const double floorBlend=std::clamp(floorDistance/0.25,0.0,1.0); - const double smoothFloorBlend=floorBlend*floorBlend - *(3.0-2.0*floorBlend); - double thickness=0.75*smoothFade*smoothFloorBlend; - if(candidatePassagepassages) - for(const auto* chamber:{&passage.upperLandingChamber, - &passage.lowerLandingChamber}) - { - if(chamber->radiusSubvoxels<=0)continue; - const double centerX=qToMetres(chamber->floorCenterSubvoxels[0]); - const double centerY=qToMetres(chamber->floorCenterSubvoxels[1]); - const double floorCenterZ=qToMetres( - chamber->floorCenterSubvoxels[2]) - +chamber->floorGradientQ16[0]/65536.0*(xMetres-centerX) - +chamber->floorGradientQ16[1]/65536.0*(yMetres-centerY); - const double radialDistance=std::hypot(xMetres-centerX, - yMetres-centerY)-qToMetres(chamber->radiusSubvoxels); - const double floorDistance=floorCenterZ-zMetres; - const double shellDistance=std::max({-floorDistance, - floorDistance-LandingChamberShellThicknessMetres, - radialDistance}); - minimumShellDistance=std::min(minimumShellDistance, - shellDistance); - } - return minimumShellDistance; - }; for(std::size_t segment=1;segment(1, static_cast(std::ceil( - length/PassageShellAuditSpacingMetres))); - for(std::size_t sampleIndex=segment==1?0:1; - sampleIndex<=intervals;++sampleIndex) + length/PassageFloorShellAuditSpacingMetres))); + for(std::size_t step=segment==1?0:1;step<=intervals;++step) { - const double t=static_cast(sampleIndex)/intervals; - const double centerX=a.x+dx*t,centerY=a.y+dy*t,centerZ=a.z+dz*t; - const double along=a.distance+alongLength*t; - const double radius=std::lerp(a.radius,b.radius,t); - if(std::min(along,totalRouteLength-along)>0.0) + const double t=static_cast(step)/intervals; + const double along=std::lerp(a.distance,b.distance,t); + if(std::min(along,route.back().distance-along)<5.0)continue; + const double x=a.x+t*dx,y=a.y+t*dy; + const double fallbackFloor=std::lerp(a.floorZ,b.floorZ,t); + for(const auto& offset:PassageFloorShellAuditOffsets) + for(const double depth:PassageFloorShellAuditDepthsMetres) { - for(const auto& offset:floorOffsets) - for(const double depth:PassageShellAuditDepthsMetres) - { - const double sampleX=centerX+offset[0]; - const double sampleY=centerY+offset[1]; - double floorZ=passageFloorAtXY(passageIndex, - sampleX,sampleY,interpolatePassageRoute(route,along).floorZ); - FPassageRouteProjection floorProjection; - for(int projectionStep=0;projectionStep<2;++projectionStep) - { - floorProjection=projectPassageRoute(route,sampleX,sampleY, - floorZ-depth); - const auto floorPose=interpolatePassageRoute(route, - floorProjection.centerAlong); - floorZ=passageFloorAtXY(passageIndex,sampleX,sampleY, - floorPose.floorZ); - } - const VoxelForgeCore::Vec3d position{ - sampleX/coreMetres,sampleY/coreMetres, - (floorZ-depth)/coreMetres}; - const double sampleZ=floorZ-depth; - if(passageInteriorWinsAt(sampleX,sampleY,sampleZ)) - { - ++audit.floorInteriorWins; - continue; - } - ++audit.floorSamples; - if(Native.densityAt(position)>=0.0f) - { - ++audit.openAirSamples; - audit.openAirDistancesMetres.push_back(along); - const double shellMargin=expectedFloorShellMarginAt( - sampleX,sampleY,sampleZ); - if(std::abs(shellMargin)<=0.011) - { - ++audit.floorMouthBlendSkips; - continue; - } - const bool shellExpected=shellMargin<-0.011; - const float density=Field.densityAt(position); - if((shellExpected&&density>=PassageShellDensityTolerance) - ||(!shellExpected&&density<-PassageShellDensityTolerance)) - { - ++audit.floorViolations; - audit.firstViolations.push_back({sampleX,sampleY, - sampleZ,density,along,depth,floorZ, - floorProjection.centerAlong, - floorProjection.tubeAlong,shellMargin}); - } - } - } - } - const double tangentLength=std::hypot(dx,dy); - if(tangentLength<=1.0e-6)continue; - if(std::min(along,totalRouteLength-along)<5.0) - { - audit.wallMouthBlendSkips+=5; - continue; - } - const double nx=-dy/tangentLength,ny=dx/tangentLength; - const double floorDepth=passageTubeDepthAt(radius,bodyFloorDepth); - double floorZ=passageFloorAtXY(passageIndex,centerX,centerY, - interpolatePassageRoute(route,along).floorZ); - struct FWallProbe {double side=0.0,height=0.0;bool roof=false;}; - const std::array wallProbes{{ - {0.0,0.0,true},{-1.0,0.25,false},{1.0,0.25,false}, - {-1.0,0.75,false},{1.0,0.75,false}}}; - for(const auto& probe:wallProbes) - { - double sampleX=centerX,sampleY=centerY,sampleZ=0.0; - if(probe.roof) - sampleZ=centerZ+radius+0.375; - else - { - const double vertical=probe.height-floorDepth; - const double wallRadiusSquared=radius*radius-vertical*vertical; - if(wallRadiusSquared<=0.0)continue; - const double wallRadius=std::sqrt(wallRadiusSquared); - sampleX+=nx*probe.side*(wallRadius+0.375); - sampleY+=ny*probe.side*(wallRadius+0.375); - for(int projectionStep=0;projectionStep<2;++projectionStep) - { - sampleZ=floorZ+probe.height; - const auto floorProjection=projectPassageRoute(route, - sampleX,sampleY,sampleZ); - const auto floorPose=interpolatePassageRoute(route, - floorProjection.centerAlong); - floorZ=passageFloorAtXY(passageIndex,sampleX,sampleY, - floorPose.floorZ); - } - sampleZ=floorZ+probe.height; - } - const VoxelForgeCore::Vec3d wallPosition{sampleX/coreMetres, - sampleY/coreMetres,sampleZ/coreMetres}; - if(passageInteriorWinsAt(sampleX,sampleY,sampleZ)) - { - ++audit.wallInteriorWins; - continue; - } - if(Native.densityAt(wallPosition)<0.0f)continue; - ++audit.openAirSamples; - ++audit.wallSamples; - audit.openAirDistancesMetres.push_back(along); - const float density=Field.densityAt(wallPosition); - const auto [shellExpected,wallShellMargin]= - expectedPassageShellAt(sampleX,sampleY,sampleZ); - if(shellExpected>=0 - &&((shellExpected==1 - &&density>=PassageShellDensityTolerance) - ||(shellExpected==0 - &&density<-PassageShellDensityTolerance))) - { - ++audit.wallViolations; - audit.firstWallViolations.push_back({sampleX,sampleY, - sampleZ,density,along,probe.height,probe.side, - centerX,centerY,centerZ,floorZ,radius,wallShellMargin}); - } - else if(shellExpected<0) - ++audit.wallMouthBlendSkips; - } - ++audit.interiorSamples; - const float interior=Field.densityAt({centerX/coreMetres, - centerY/coreMetres,centerZ/coreMetres}); - if(interior<=0.0f)++audit.interiorViolations; - } - } - constexpr std::array chamberFloorRadii{{0.0,2.5,5.0}}; - constexpr std::array chamberWallHeights{{1.13,3.20}}; - constexpr std::size_t ChamberAngularSamples=16; - for(const bool upper:{true,false}) - { - const auto& chamber=upper?passage.upperLandingChamber - :passage.lowerLandingChamber; - if(chamber.radiusSubvoxels<=0||chamber.clearHeightSubvoxels<=0)continue; - const double centerX=qToMetres(chamber.floorCenterSubvoxels[0]); - const double centerY=qToMetres(chamber.floorCenterSubvoxels[1]); - const double baseFloorZ=qToMetres(chamber.floorCenterSubvoxels[2]); - const double radius=qToMetres(chamber.radiusSubvoxels); - const double gx=chamber.floorGradientQ16[0]/65536.0; - const double gy=chamber.floorGradientQ16[1]/65536.0; - for(const double radial:chamberFloorRadii) - { - if(radial>=radius-1.0)continue; - const std::size_t angles=radial==0.0?1:ChamberAngularSamples; - for(std::size_t angleIndex=0;angleIndex(angleIndex)/ChamberAngularSamples; - const double sampleX=centerX+radial*std::cos(angle); - const double sampleY=centerY+radial*std::sin(angle); - const double floorZ=baseFloorZ+gx*(sampleX-centerX) - +gy*(sampleY-centerY); - for(const double depth:PassageShellAuditDepthsMetres) - { - const double sampleZ=floorZ-depth; - const VoxelForgeCore::Vec3d position{sampleX/coreMetres, - sampleY/coreMetres,sampleZ/coreMetres}; - if(Native.densityAt(position)<0.0f)continue; - if(landingChamberConnectionContains(passage,upper, - sampleX,sampleY,sampleZ)) - {++audit.chamberConnectionSkips;continue;} - ++audit.chamberFloorSamples; - const float density=Field.densityAt(position); - if(density>=PassageShellDensityTolerance) - { - ++audit.chamberFloorViolations; - std::printf("[PassageLandingChamberShellSample] id=%s side=%s kind=floor_missing xyz_m=(%.3f,%.3f,%.3f) native=%.6f density=%.6f\n", - passage.stableId.c_str(),upper?"upper":"lower", - sampleX,sampleY,sampleZ,Native.densityAt(position),density); - } - } - } - } - for(std::size_t angleIndex=0;angleIndex(angleIndex)/ChamberAngularSamples; - const double nx=std::cos(angle),ny=std::sin(angle); - for(const double depth:PassageShellAuditDepthsMetres) - for(const double height:chamberWallHeights) - { - const double sampleX=centerX+(radius+depth)*nx; - const double sampleY=centerY+(radius+depth)*ny; - const double floorZ=baseFloorZ+gx*(sampleX-centerX) - +gy*(sampleY-centerY); - const double sampleZ=floorZ+height; + const double sampleX=x+offset[0],sampleY=y+offset[1]; + const double floorZ=passageFloorAtXY(passageIndex,sampleX, + sampleY,fallbackFloor); const VoxelForgeCore::Vec3d position{sampleX/coreMetres, - sampleY/coreMetres,sampleZ/coreMetres}; - if(Native.densityAt(position)<0.0f)continue; - if(passageInteriorWinsAt(sampleX,sampleY,sampleZ, - std::numeric_limits::max(), - std::numeric_limits::max(),passageIndex) - ||landingChamberConnectionContains(passage,upper, - sampleX,sampleY,sampleZ)) - {++audit.chamberConnectionSkips;continue;} - ++audit.chamberWallSamples; - const float density=Field.densityAt(position); - if(density>=PassageShellDensityTolerance) + sampleY/coreMetres,(floorZ-depth)/coreMetres}; + if(layerContent.densityAt(position)<0.0f)continue; + ++audit.hostAirSamples; + const double shellDistance=field.passageFloorShellDistanceAt(position); + if(shellDistance< -0.01) { - ++audit.chamberWallViolations; - std::printf("[PassageLandingChamberShellSample] id=%s side=%s kind=wall_missing xyz_m=(%.3f,%.3f,%.3f) native=%.6f density=%.6f radial_m=%.3f height_m=%.3f\n", - passage.stableId.c_str(),upper?"upper":"lower", - sampleX,sampleY,sampleZ,Native.densityAt(position),density, - radius+depth,height); + ++audit.shellSamples; + if(field.densityAt(position)>=0.0f) + ++audit.missingSamples; } } } @@ -5617,88 +4881,426 @@ namespace StandaloneStreamBenchmark return zUm>=stratum->content.bottomMicrometres &&zUmcontent.topMicrometres; }; - const auto auditLandingDisc=[&](const VoxelForgeCore::World& field, - const VoxelForgeCore::ResolvedLayoutPassageV1& passage, - std::size_t passageIndex,bool upper) + struct FLandingPadAudit { - FWalkAudit total;total.passed=true; - const auto& chamber=upper?passage.upperLandingChamber - :passage.lowerLandingChamber; - if(chamber.radiusSubvoxels<=0||chamber.clearHeightSubvoxels<=0) - {total.passed=false;return total;} - const auto& mouth=upper?passage.upperMouthSubvoxels - :passage.lowerMouthSubvoxels; - constexpr std::array,8> directions{{ - {{1.0,0.0}},{{0.7071067811865475,0.7071067811865475}}, - {{0.0,1.0}},{{-0.7071067811865475,0.7071067811865475}}, - {{-1.0,0.0}},{{-0.7071067811865475,-0.7071067811865475}}, - {{0.0,-1.0}},{{0.7071067811865475,-0.7071067811865475}}}}; - const double centerX=qToMetres(mouth[0]); - const double centerY=qToMetres(mouth[1]); - const double centerZ=qToMetres(chamber.floorCenterSubvoxels[2]); - const std::int64_t discRadiusSubvoxels= + std::uint64_t flatSamples=0,flatFailures=0; + std::uint64_t supportSamples=0,supportFailures=0; + std::uint64_t headroomSamples=0,headroomFailures=0; + std::uint64_t blendRootSamples=0,blendMissing=0,blendGradeFailures=0; + std::uint64_t negativeControlsDetected=0; + double maximumFlatnessMetres=0.0,maximumGradeDegrees=0.0; + bool hasFirstBlendMissing=false,hasFirstBlendGradeFailure=false; + std::array firstBlendMissing{}; + std::array firstBlendGradeFailure{}; + bool passed=false; + }; + const auto auditLandingPad=[&](const VoxelForgeCore::World& field, + const VoxelForgeCore::ResolvedLayoutPassageV1& passage,bool upper) + { + constexpr double ConeSlope=0.2125565616700221; + constexpr double CosTwelveDegrees=0.9781476007338057; + constexpr double FillDepthMetres=1.25,HeadroomMetres=3.0; + constexpr double BlendRadiusMetres=8.0; + constexpr double FlatnessLimitMetres=0.020; + constexpr double GradeLimitDegrees=15.0; + constexpr std::size_t RadialDirections=36; + constexpr double FloorSearchStepMetres=0.125; + FLandingPadAudit audit; + if(passage.controlPoints.empty())return audit; + const auto padCenter= + VoxelForgeCore::ResolvedLayoutPassageLandingPadCenterSubvoxels( + passage,upper); + const double centerX=qToMetres(padCenter[0]); + const double centerY=qToMetres(padCenter[1]); + const double floorP=qToMetres(padCenter[2]); + const std::int64_t radiusSubvoxels= VoxelForgeCore::ResolvedLayoutPassageLandingDiscRadiusSubvoxels( passage.mouthRadiusSubvoxels,World.CoreVoxelSizeMicrons); - constexpr double SupportProbeRadiusMetres=0.45; - const double radius=qToMetres(discRadiusSubvoxels) - -SupportProbeRadiusMetres; - if(discRadiusSubvoxels<=0||radius<=0.0) - {total.passed=false;return total;} - const std::array discCenter={mouth[0],mouth[1]}; - const std::function interiorPredicate= - [&](double x,double y,double z) - {return passageInteriorWinsAt(x,y,z, - std::numeric_limits::max(), - std::numeric_limits::max(),passageIndex);}; - for(const auto& direction:directions) + const double radius=qToMetres(radiusSubvoxels); + const auto densityMetres=[&](const VoxelForgeCore::World& source, + double x,double y,double z) { - std::vector> chamberPath; - const std::size_t intervals=std::max(1, - static_cast(std::ceil(radius/0.5))); - chamberPath.reserve(intervals+1); - for(std::size_t step=0;step<=intervals;++step) + const float value=source.densityAt({x/coreMetres,y/coreMetres, + z/coreMetres}); + return std::isfinite(value)?static_cast(value)*coreMetres + :std::numeric_limits::quiet_NaN(); + }; + struct FFloorRoot + {double z=0.0;std::uint32_t crossings=0;bool found=false;}; + const auto findFloorRoot=[&](auto&& sampleDensity,double x,double y, + double low,double high) + { + FFloorRoot result; + double previousZ=low; + double previous=sampleDensity(x,y,previousZ); + if(!std::isfinite(previous))return result; + for(double z=low+FloorSearchStepMetres; + z=0.0) + { + const double denominator=current-previous; + const double t=std::abs(denominator)>1.0e-12 + ?std::clamp(-previous/denominator,0.0,1.0):0.0; + const double root=std::lerp(previousZ,currentZ,t); + ++result.crossings; + if(!result.found)result.z=root; + result.found=true; + } + previousZ=currentZ; + previous=current; + if(currentZ>=high)break; } - const FWalkAudit part=auditPlayerPath(field,chamberPath, - 0.125,0.125,1.125,3,0.45,{},interiorPredicate, - upper?"upper_landing":"lower_landing"); - total.segments+=part.segments;total.centers+=part.centers; - total.capsuleSamples+=part.capsuleSamples; - total.blocked+=part.blocked; - total.passageInteriorCapsuleSamples+=part.passageInteriorCapsuleSamples; - total.passageInteriorSolidSamples+=part.passageInteriorSolidSamples; - total.supportSamples+=part.supportSamples; - total.unsupported+=part.unsupported; - total.gradeFailures+=part.gradeFailures; - total.supportOverlapSkips+=part.supportOverlapSkips; - total.passed=total.passed&&part.passed; + return result; + }; + const auto fieldDensity=[&](double x,double y,double z) + {return densityMetres(field,x,y,z);}; + const auto sourceFieldDensity=[&](double x,double y,double z) + { + const float value=Shape.passageSourceDensityAt({x/coreMetres, + y/coreMetres,z/coreMetres}); + return std::isfinite(value)?static_cast(value)*coreMetres + :std::numeric_limits::quiet_NaN(); + }; + struct FFlatProbe {double x=0.0,y=0.0,z=0.0;}; + std::vector flatProbes; + const auto probeFlatness=[&](double radial,double angle) + { + const double x=centerX+radial*std::cos(angle); + const double y=centerY+radial*std::sin(angle); + const auto root=findFloorRoot(fieldDensity,x,y, + floorP-0.75,floorP+0.75); + ++audit.flatSamples; + if(!root.found||root.crossings!=1) + {++audit.flatFailures;return;} + const double error=std::abs(root.z-floorP); + audit.maximumFlatnessMetres=std::max( + audit.maximumFlatnessMetres,error); + if(error>FlatnessLimitMetres)++audit.flatFailures; + flatProbes.push_back({x,y,root.z}); + }; + probeFlatness(0.0,0.0); + for(std::size_t direction=0;direction(direction)/RadialDirections; + probeFlatness(0.90*radius,angle); } - return total; + constexpr std::array SupportRadii{{0.0,0.5,0.9}}; + for(const double radialFraction:SupportRadii) + for(std::size_t direction=0;direction(direction)/RadialDirections; + const double x=centerX+radialFraction*radius*std::cos(angle); + const double y=centerY+radialFraction*radius*std::sin(angle); + ++audit.supportSamples; + if(!(fieldDensity(x,y,floorP-0.10)<0.0)) + ++audit.supportFailures; + ++audit.headroomSamples; + if(!(fieldDensity(x,y,floorP+2.0)>0.0)) + ++audit.headroomFailures; + } + constexpr std::size_t BlendStations=16; + const double blendEnd=BlendRadiusMetres-0.25; + for(std::size_t direction=0;direction(direction)/RadialDirections; + bool previousFound=false; + double previousRadius=0.0,previousFloor=0.0; + for(std::size_t station=0;station<=BlendStations;++station) + { + const double radial=radius+blendEnd + *static_cast(station)/BlendStations; + const double x=centerX+radial*std::cos(angle); + const double y=centerY+radial*std::sin(angle); + const double coneRun=std::max(0.0,radial-radius); + const double coneHeight=ConeSlope*coneRun; + const auto root=findFloorRoot(fieldDensity,x,y, + floorP-coneHeight-0.75,floorP+coneHeight+0.75); + ++audit.blendRootSamples; + if(!root.found||root.crossings!=1) + { + ++audit.blendMissing; + if(!audit.hasFirstBlendMissing) + { + audit.hasFirstBlendMissing=true; + audit.firstBlendMissing={static_cast(direction), + static_cast(station),radial,x,y,floorP}; + } + previousFound=false; + continue; + } + if(previousFound) + { + const double grade=std::atan2( + std::abs(root.z-previousFloor),radial-previousRadius) + *180.0/3.14159265358979323846; + audit.maximumGradeDegrees=std::max( + audit.maximumGradeDegrees,grade); + if(grade>GradeLimitDegrees) + { + ++audit.blendGradeFailures; + if(!audit.hasFirstBlendGradeFailure) + { + audit.hasFirstBlendGradeFailure=true; + audit.firstBlendGradeFailure={ + static_cast(direction),previousRadius, + previousFloor,radial,root.z,grade}; + } + } + } + previousFound=true; + previousRadius=radial; + previousFloor=root.z; + } + } + const auto sourceField=[&](double x,double y,double z) + {return sourceFieldDensity(x,y,z);}; + bool padOffRejected=false; + for(std::size_t direction=0;direction(direction)/RadialDirections; + const double x=centerX+0.90*radius*std::cos(angle); + const double y=centerY+0.90*radius*std::sin(angle); + const auto root=findFloorRoot(sourceField,x,y, + floorP-0.75,floorP+0.75); + if(!root.found||root.crossings!=1 + ||std::abs(root.z-floorP)>FlatnessLimitMetres) + {padOffRejected=true;break;} + } + const bool tiltRejected=std::any_of(flatProbes.begin(),flatProbes.end(), + [&](const FFlatProbe& probe) + { + return std::abs((probe.z-floorP) + +0.02*(probe.x-centerX))>FlatnessLimitMetres; + }); + const auto controlStamp=[&](double blendRadius,double radial,double z, + double native,double tubeDistance) + { + const double coneRun=std::max(0.0,radial-radius); + const double lower=floorP-ConeSlope*coneRun; + const double upper=floorP+ConeSlope*coneRun; + const double outer=radius+blendRadius; + const double fill=std::max({CosTwelveDegrees*(z-lower), + CosTwelveDegrees*(lower-FillDepthMetres-z),radial-outer}); + const double air=std::min({CosTwelveDegrees*(z-upper), + CosTwelveDegrees*(upper+HeadroomMetres-z),outer-radial}); + const double masked=std::max(fill,-tubeDistance); + const double smoothWidth=0.5*std::clamp(radial-radius,0.0,1.0); + double smoothMinimum=std::min(native,masked); + if(smoothWidth>0.0) + { + const double h=std::max(smoothWidth + -std::abs(native-masked),0.0)/smoothWidth; + smoothMinimum-=h*h*h*(smoothWidth/6.0); + } + return std::max(smoothMinimum,air); + }; + const double outerProbe=radius+BlendRadiusMetres-0.25; + const double outerProbeZ=floorP-ConeSlope*(outerProbe-radius)-0.05; + const double fullBlendProbe=controlStamp(BlendRadiusMetres, + outerProbe,outerProbeZ,0.0,std::numeric_limits::infinity()); + const double halfBlendProbe=controlStamp(BlendRadiusMetres*0.5, + outerProbe,outerProbeZ,0.0,std::numeric_limits::infinity()); + const bool halfBlendRejected=fullBlendProbe< -0.01 + &&std::abs(halfBlendProbe)<1.0e-6; + const double boreMasked=controlStamp(BlendRadiusMetres, + radius*0.5,floorP-0.25,0.25,-0.50); + const double boreUnmasked=controlStamp(BlendRadiusMetres, + radius*0.5,floorP-0.25,0.25, + std::numeric_limits::infinity()); + const bool boreMaskRejected=boreMasked>0.0&&boreUnmasked<0.0; + audit.negativeControlsDetected=static_cast(padOffRejected) + +static_cast(tiltRejected) + +static_cast(halfBlendRejected) + +static_cast(boreMaskRejected); + audit.passed=audit.flatSamples>0&&audit.flatFailures==0 + &&audit.supportSamples>0&&audit.supportFailures==0 + &&audit.headroomSamples>0&&audit.headroomFailures==0 + &&audit.blendRootSamples>0&&audit.blendMissing==0 + &&audit.blendGradeFailures==0 + &&audit.negativeControlsDetected==4; + std::printf("[LandingPadAudit] id=%s side=%s radius_m=%.3f blend_m=%.3f flatness_samples=%llu flatness_max_m=%.5f flatness_failures=%llu support=%llu/%llu headroom=%llu/%llu blend_transects=%zu blend_roots=%llu blend_missing=%llu blend_grade_max_deg=%.3f blend_grade_failures=%llu negative_controls=%llu/4 pad_off=%s tilt_2pct=%s blend_B_half=%s bore_mask_removed=%s status=%s\n", + passage.stableId.c_str(),upper?"upper":"lower",radius, + BlendRadiusMetres,static_cast(audit.flatSamples), + audit.maximumFlatnessMetres, + static_cast(audit.flatFailures), + static_cast(audit.supportSamples + -audit.supportFailures), + static_cast(audit.supportSamples), + static_cast(audit.headroomSamples + -audit.headroomFailures), + static_cast(audit.headroomSamples), + RadialDirections, + static_cast(audit.blendRootSamples), + static_cast(audit.blendMissing), + audit.maximumGradeDegrees, + static_cast(audit.blendGradeFailures), + static_cast(audit.negativeControlsDetected), + padOffRejected?"detected":"MISSED",tiltRejected?"detected":"MISSED", + halfBlendRejected?"detected":"MISSED", + boreMaskRejected?"detected":"MISSED",audit.passed?"PASS":"FAIL"); + if(audit.hasFirstBlendMissing) + std::printf("[LandingPadBlendMissing] id=%s side=%s direction=%zu station=%zu radius_m=%.3f sample_xyz_m=(%.3f,%.3f,%.3f)\n", + passage.stableId.c_str(),upper?"upper":"lower", + static_cast(audit.firstBlendMissing[0]), + static_cast(audit.firstBlendMissing[1]), + audit.firstBlendMissing[2],audit.firstBlendMissing[3], + audit.firstBlendMissing[4],audit.firstBlendMissing[5]); + if(audit.hasFirstBlendGradeFailure) + std::printf("[LandingPadBlendGradeFailure] id=%s side=%s direction=%zu a_radius_m=%.3f a_floor_m=%.3f b_radius_m=%.3f b_floor_m=%.3f grade_deg=%.3f limit_deg=%.3f\n", + passage.stableId.c_str(),upper?"upper":"lower", + static_cast(audit.firstBlendGradeFailure[0]), + audit.firstBlendGradeFailure[1],audit.firstBlendGradeFailure[2], + audit.firstBlendGradeFailure[3],audit.firstBlendGradeFailure[4], + audit.firstBlendGradeFailure[5],GradeLimitDegrees); + return audit; + }; + struct FNoAddedSolidOverHostAirAudit + { + std::uint64_t hostAirSamples=0,addedSolidSamples=0; + bool negativeControlDetected=false; + std::array firstViolation{}; + bool hasViolation=false; + }; + constexpr double NoAddedSolidTolerance=1.0e-6; + constexpr double NoAddedSolidRouteSpacingMetres=1.0; + const auto auditNoAddedSolidOverHostAir=[&]( + const VoxelForgeCore::World& field, + const VoxelForgeCore::World& layerContent) + { + FNoAddedSolidOverHostAirAudit audit; + bool hasControlSample=false; + const auto moreSolidOverHostAir=[&](float hostDensity, + float composedDensity,double floorShellDistance) + { + return hostDensity>=0.0f + &&composedDensity=0.0; + }; + const auto sample=[&](double xMetres,double yMetres,double zMetres) + { + const VoxelForgeCore::Vec3d position{xMetres/coreMetres, + yMetres/coreMetres,zMetres/coreMetres}; + const float hostDensity=layerContent.densityAt(position); + if(hostDensity<0.0f)return; + ++audit.hostAirSamples; + const float composedDensity=field.densityAt(position); + if(!hasControlSample) + { + const double floorShellDistance= + field.passageFloorShellDistanceAt(position); + if(floorShellDistance>=0.0) + { + hasControlSample=true; + audit.negativeControlDetected=moreSolidOverHostAir( + hostDensity,-0.25f,floorShellDistance); + } + } + if(composedDensity>=hostDensity-NoAddedSolidTolerance)return; + const double floorShellDistance=field.passageFloorShellDistanceAt(position); + if(moreSolidOverHostAir(hostDensity,composedDensity, + floorShellDistance)) + { + ++audit.addedSolidSamples; + if(!audit.hasViolation) + { + audit.hasViolation=true; + audit.firstViolation={xMetres,yMetres,zMetres, + composedDensity,hostDensity}; + } + } + }; + for(std::size_t passageIndex=0; + passageIndex(1, + static_cast(std::ceil( + totalLength/NoAddedSolidRouteSpacingMetres))); + constexpr std::array SideHeights{{0.125,0.75,1.5}}; + constexpr std::size_t SideDirections=8; + for(std::size_t station=0;station<=intervals;++station) + { + const double along=totalLength*station/intervals; + const auto point=interpolatePassageRoute(route,along); + const auto before=interpolatePassageRoute(route, + std::max(0.0,along-0.25)); + const auto after=interpolatePassageRoute(route, + std::min(totalLength,along+0.25)); + double tangentX=after.x-before.x,tangentY=after.y-before.y; + const double tangentLength=std::hypot(tangentX,tangentY); + if(tangentLength>1.0e-9) + {tangentX/=tangentLength;tangentY/=tangentLength;} + else {tangentX=1.0;tangentY=0.0;} + const double normalX=-tangentY,normalY=tangentX; + const double sideRadius=point.radius+0.25; + for(std::size_t direction=0;direction(direction)/SideDirections; + const double radialX=normalX*std::cos(angle) + +tangentX*std::sin(angle); + const double radialY=normalY*std::cos(angle) + +tangentY*std::sin(angle); + for(const double height:SideHeights) + sample(point.x+sideRadius*radialX, + point.y+sideRadius*radialY,point.floorZ+height); + } + sample(point.x,point.y,point.floorZ-1.0); + sample(point.x,point.y,point.z+point.radius+0.25); + } + const auto& passage=ResolvedLayout->passages[passageIndex]; + for(const bool upper:{true,false}) + { + const auto center= + VoxelForgeCore::ResolvedLayoutPassageLandingPadCenterSubvoxels( + passage,upper); + const double centerX=qToMetres(center[0]); + const double centerY=qToMetres(center[1]); + const double floorP=qToMetres(center[2]); + const double radius=qToMetres( + VoxelForgeCore::ResolvedLayoutPassageLandingDiscRadiusSubvoxels( + passage.mouthRadiusSubvoxels,World.CoreVoxelSizeMicrons)); + constexpr std::array radialOffsets{{ + 0.0,0.5,1.0,3.0,5.5,7.5}}; + constexpr std::array verticalOffsets{{ + -1.5,-0.5,0.5,2.0}}; + for(std::size_t direction=0;direction(direction)/SideDirections; + for(const double radialOffset:radialOffsets) + for(const double verticalOffset:verticalOffsets) + { + const double radial=std::max(0.0,radius+radialOffset); + sample(centerX+radial*std::cos(angle), + centerY+radial*std::sin(angle), + floorP+verticalOffset); + } + } + } + } + return audit; }; std::uint64_t passageCount=0,passagePasses=0,blockedControls=0,naturalPasses=0; std::uint64_t passageSupportSamples=0,passageSupportExempt=0; std::uint64_t passageInteriorCapsuleSamples=0,passageInteriorSolidSamples=0; std::uint64_t passageInteriorControls=0; - std::uint64_t landingChamberCount=0,landingChamberNegativeControls=0; + std::uint64_t landingPadCount=0,landingPadPasses=0,landingPadControls=0; std::uint64_t mouthProfilePasses=0,ripplePasses=0,rippleControlsDetected=0; std::uint64_t floorRippleControlsDetected=0,floorStepRippleControlsDetected=0; - std::uint64_t shellAudits=0,shellFloorSamples=0,shellFloorViolations=0; - std::uint64_t shellFloorMouthBlendSkips=0; - std::uint64_t shellOpenAirSamples=0,shellWallSamples=0,shellWallViolations=0; - std::uint64_t shellChamberFloorSamples=0,shellChamberFloorViolations=0; - std::uint64_t shellChamberWallSamples=0,shellChamberWallViolations=0; - std::uint64_t shellChamberConnectionSkips=0; - std::uint64_t shellInteriorSamples=0,shellInteriorViolations=0; + std::uint64_t floorShellAudits=0,floorShellHostAirSamples=0; + std::uint64_t floorShellSamples=0,floorShellMissingSamples=0; for(std::size_t passageIndex=0;passageIndexpassages.size();++passageIndex) { const auto& passage=ResolvedLayout->passages[passageIndex]; @@ -5723,31 +5325,20 @@ namespace StandaloneStreamBenchmark std::numeric_limits::max(),passageIndex);}; const FWalkAudit passageAudit=auditPlayerPath(Shape,path,0.375,0.125, 1.125,3,0.45,passageSupportExemption,passageInteriorPredicate); - const FWalkAudit upperChamberAudit=auditLandingDisc(Shape, - passage,passageIndex,true); - const FWalkAudit lowerChamberAudit=auditLandingDisc(Shape, - passage,passageIndex,false); - const bool landingChambersWalkable=upperChamberAudit.passed - &&lowerChamberAudit.passed; - passageSupportSamples+=passageAudit.supportSamples - +upperChamberAudit.supportSamples+lowerChamberAudit.supportSamples; - passageSupportExempt+=passageAudit.supportOverlapSkips - +upperChamberAudit.supportOverlapSkips - +lowerChamberAudit.supportOverlapSkips; + const FLandingPadAudit upperPadAudit=auditLandingPad(Shape,passage,true); + const FLandingPadAudit lowerPadAudit=auditLandingPad(Shape,passage,false); + landingPadCount+=2; + landingPadPasses+=static_cast(upperPadAudit.passed) + +static_cast(lowerPadAudit.passed); + landingPadControls+=upperPadAudit.negativeControlsDetected + +lowerPadAudit.negativeControlsDetected; + passageSupportSamples+=passageAudit.supportSamples; + passageSupportExempt+=passageAudit.supportOverlapSkips; const bool passageInteriorAirPassed= passageAudit.passageInteriorCapsuleSamples>0 - &&passageAudit.passageInteriorSolidSamples==0 - &&upperChamberAudit.passageInteriorCapsuleSamples>0 - &&lowerChamberAudit.passageInteriorCapsuleSamples>0 - &&upperChamberAudit.passageInteriorSolidSamples==0 - &&lowerChamberAudit.passageInteriorSolidSamples==0; - passageInteriorCapsuleSamples+=passageAudit.passageInteriorCapsuleSamples - +upperChamberAudit.passageInteriorCapsuleSamples - +lowerChamberAudit.passageInteriorCapsuleSamples; - passageInteriorSolidSamples+=passageAudit.passageInteriorSolidSamples - +upperChamberAudit.passageInteriorSolidSamples - +lowerChamberAudit.passageInteriorSolidSamples; - landingChamberCount+=2; + &&passageAudit.passageInteriorSolidSamples==0; + passageInteriorCapsuleSamples+=passageAudit.passageInteriorCapsuleSamples; + passageInteriorSolidSamples+=passageAudit.passageInteriorSolidSamples; const FPassageNaturalnessAudit naturalAudit= auditPassageNaturalness(Shape,passageIndex); const auto lowerProfileStratum=std::find_if(ResolvedLayout->strates.begin(), @@ -5762,41 +5353,12 @@ namespace StandaloneStreamBenchmark auditPassageMouthProfiles(Shape,passage,profiledLowerSurface); const FPassageRippleAudit rippleAudit= auditPassageRipples(Shape,passageIndex); - const FPassageShellAudit shellAudit=auditPassageShell(Shape,blockedWorld, - passage,passageIndex); - std::vector openAirDistances=shellAudit.openAirDistancesMetres; - std::sort(openAirDistances.begin(),openAirDistances.end()); - openAirDistances.erase(std::unique(openAirDistances.begin(), - openAirDistances.end(),[](double a,double b){return std::abs(a-b)<0.01;}), - openAirDistances.end()); - for(std::size_t first=0,section=0;first& p) {return p[0]*p[0]+p[1]*p[1];}; const bool mouthsInRadius=radialSquared(passage.upperMouthSubvoxels) @@ -5811,8 +5373,8 @@ namespace StandaloneStreamBenchmark rippleControlsDetected+=rippleAudit.controlDetected; floorRippleControlsDetected+=rippleAudit.floorControlDetected; floorStepRippleControlsDetected+=rippleAudit.floorStepControlDetected; - passagePasses+=passageAudit.passed&&landingChambersWalkable - &&passageInteriorAirPassed + passagePasses+=passageAudit.passed&&upperPadAudit.passed + &&lowerPadAudit.passed&&passageInteriorAirPassed &&mouthsInRadius&&mouthsInContent &&naturalAudit.passed&&mouthProfileAudit.passed&&rippleAudit.passed; std::printf("[ResolvedPassageAudit] id=%s interface=%d-%d control_points=%zu center_samples=%llu capsule_samples=%llu blocked=%llu passage_interior_capsule_samples=%llu passage_interior_solid_samples=%llu interior_air=%s support_samples=%llu support_overlap_skips=%llu unsupported=%llu grade_failures=%llu mouth_content=%d/2 mouth_radius=%d/2 walkability=%s\n", @@ -5828,33 +5390,15 @@ namespace StandaloneStreamBenchmark static_cast(passageAudit.unsupported), static_cast(passageAudit.gradeFailures),mouthsInContent?2:0, mouthsInRadius?2:0, - passageAudit.passed&&landingChambersWalkable?"PASS":"FAIL"); - std::printf("[PassageLandingDiscSupportAudit] id=%s side=upper capsule_samples=%llu solid_samples=%llu support_samples=%llu unsupported=%llu exempt_probes=%llu grade_failures=%llu status=%s\n", + passageAudit.passed&&upperPadAudit.passed&&lowerPadAudit.passed?"PASS":"FAIL"); + std::printf("[LandingPadPairAudit] id=%s passed=%d/2 negative_controls=%llu/8 status=%s\n", passage.stableId.c_str(), - static_cast(upperChamberAudit.passageInteriorCapsuleSamples), - static_cast(upperChamberAudit.passageInteriorSolidSamples), - static_cast(upperChamberAudit.supportSamples), - static_cast(upperChamberAudit.unsupported), - static_cast(upperChamberAudit.supportOverlapSkips), - static_cast(upperChamberAudit.gradeFailures), - upperChamberAudit.passed&&upperChamberAudit.passageInteriorSolidSamples==0 - &&upperChamberAudit.passageInteriorCapsuleSamples>0?"PASS":"FAIL"); - for(const auto& sample:upperChamberAudit.firstUnsupported) - std::printf("[PassageLandingSupportFailure] id=%s side=upper xyz_m=(%.3f,%.3f,%.3f) density=%.9g\n", - passage.stableId.c_str(),sample[0],sample[1],sample[2],sample[3]); - std::printf("[PassageLandingDiscSupportAudit] id=%s side=lower capsule_samples=%llu solid_samples=%llu support_samples=%llu unsupported=%llu exempt_probes=%llu grade_failures=%llu status=%s\n", - passage.stableId.c_str(), - static_cast(lowerChamberAudit.passageInteriorCapsuleSamples), - static_cast(lowerChamberAudit.passageInteriorSolidSamples), - static_cast(lowerChamberAudit.supportSamples), - static_cast(lowerChamberAudit.unsupported), - static_cast(lowerChamberAudit.supportOverlapSkips), - static_cast(lowerChamberAudit.gradeFailures), - lowerChamberAudit.passed&&lowerChamberAudit.passageInteriorSolidSamples==0 - &&lowerChamberAudit.passageInteriorCapsuleSamples>0?"PASS":"FAIL"); - for(const auto& sample:lowerChamberAudit.firstUnsupported) - std::printf("[PassageLandingSupportFailure] id=%s side=lower xyz_m=(%.3f,%.3f,%.3f) density=%.9g\n", - passage.stableId.c_str(),sample[0],sample[1],sample[2],sample[3]); + static_cast(upperPadAudit.passed)+static_cast(lowerPadAudit.passed), + static_cast(upperPadAudit.negativeControlsDetected + +lowerPadAudit.negativeControlsDetected), + upperPadAudit.passed&&lowerPadAudit.passed + &&upperPadAudit.negativeControlsDetected==4 + &&lowerPadAudit.negativeControlsDetected==4?"PASS":"FAIL"); std::printf("[PassageNaturalnessAudit] id=%s flat_run_max_m=%.3f limit_m=%.3f wall_relief_p2p_m=%.3f minimum_m=%.3f samples=%llu missing=%llu status=%s\n", passage.stableId.c_str(),naturalAudit.maximumFlatRunMetres,MaximumFlatRunMetres, naturalAudit.wallReliefPeakToPeakMetres,MinimumWallReliefMetres, @@ -5910,67 +5454,13 @@ namespace StandaloneStreamBenchmark passage.stableId.c_str(),value[0],value[1],value[2],value[2]-value[1], value[3],value[4],value[5]); } - std::printf("[PassageShellAudit] id=%s floor_samples=%llu floor_violations=%llu floor_overlap_skips=%llu floor_mouth_blend_skips=%llu chamber_floor_samples=%llu chamber_floor_violations=%llu chamber_wall_samples=%llu chamber_wall_violations=%llu chamber_connection_skips=%llu open_air_shell_samples=%llu wall_samples=%llu wall_violations=%llu wall_overlap_skips=%llu wall_mouth_blend_skips=%llu interior_samples=%llu interior_violations=%llu spacing_m=%.3f status=%s\n", + std::printf("[PassageFloorShellAudit] id=%s host_air_samples=%llu shell_samples=%llu missing_shell_samples=%llu status=%s\n", passage.stableId.c_str(), - static_cast(shellAudit.floorSamples), - static_cast(shellAudit.floorViolations), - static_cast(shellAudit.floorInteriorWins), - static_cast(shellAudit.floorMouthBlendSkips), - static_cast(shellAudit.chamberFloorSamples), - static_cast(shellAudit.chamberFloorViolations), - static_cast(shellAudit.chamberWallSamples), - static_cast(shellAudit.chamberWallViolations), - static_cast(shellAudit.chamberConnectionSkips), - static_cast(shellAudit.openAirSamples), - static_cast(shellAudit.wallSamples), - static_cast(shellAudit.wallViolations), - static_cast(shellAudit.wallInteriorWins), - static_cast(shellAudit.wallMouthBlendSkips), - static_cast(shellAudit.interiorSamples), - static_cast(shellAudit.interiorViolations), - PassageShellAuditSpacingMetres, - shellAudit.floorViolations==0&&shellAudit.wallViolations==0 - &&shellAudit.chamberFloorViolations==0 - &&shellAudit.chamberWallViolations==0 - &&shellAudit.interiorViolations==0 + static_cast(floorShellAudit.hostAirSamples), + static_cast(floorShellAudit.shellSamples), + static_cast(floorShellAudit.missingSamples), + floorShellAudit.shellSamples>0&&floorShellAudit.missingSamples==0 ?"PASS":"FAIL"); - for(std::size_t i=0;i(Shape.passageSourceDensityAt({ - sample[0]/coreMetres,sample[1]/coreMetres, - sample[2]/coreMetres})), - static_cast(Shape.unclampedDensityAt({ - sample[0]/coreMetres,sample[1]/coreMetres, - sample[2]/coreMetres}))); - } for(std::size_t i=0;i0 + &&noAddedSolidAudit.addedSolidSamples==0 + &&noAddedSolidAudit.negativeControlDetected; + std::printf("[NoAddedSolidOverHostAirAudit] host_air_samples=%llu added_solid_samples=%llu planted_wall_density=-0.250 negative_control=%s status=%s\n", + static_cast(noAddedSolidAudit.hostAirSamples), + static_cast(noAddedSolidAudit.addedSolidSamples), + noAddedSolidAudit.negativeControlDetected?"DETECTED":"MISSED", + noAddedSolidOverHostAirPassed?"PASS":"FAIL"); + if(noAddedSolidAudit.hasViolation) + std::printf("[NoAddedSolidOverHostAirSample] xyz_m=(%.3f,%.3f,%.3f) composed_density=%.6f host_density=%.6f\n", + noAddedSolidAudit.firstViolation[0],noAddedSolidAudit.firstViolation[1], + noAddedSolidAudit.firstViolation[2],noAddedSolidAudit.firstViolation[3], + noAddedSolidAudit.firstViolation[4]); std::uint64_t routePoints=0,routePasses=0,routeWalkPasses=0,routeBindings=0,routeBindingFailures=0; std::uint64_t tunnelNetworkRoutes=0,tunnelNetworkBlockedControls=0; std::uint64_t verticalShaftRoutes=0,verticalShaftBlockedControls=0; @@ -6229,19 +5735,6 @@ namespace StandaloneStreamBenchmark std::numeric_limits::max(),passageIndex);}; const FWalkAudit blockedAudit=auditPlayerPath(blockedWorld,path, 0.125,0.125,0.25,3,0.45,{},passageInteriorPredicate); - const FWalkAudit upperChamberBlocked=auditLandingDisc(blockedWorld, - passage,passageIndex,true); - const FWalkAudit lowerChamberBlocked=auditLandingDisc(blockedWorld, - passage,passageIndex,false); - blockedControls+=blockedAudit.blocked>0&&!blockedAudit.passed; - const bool upperChamberControlDetected= - upperChamberBlocked.passageInteriorCapsuleSamples>0 - &&upperChamberBlocked.passageInteriorSolidSamples>0; - const bool lowerChamberControlDetected= - lowerChamberBlocked.passageInteriorCapsuleSamples>0 - &&lowerChamberBlocked.passageInteriorSolidSamples>0; - landingChamberNegativeControls+=upperChamberControlDetected - +lowerChamberControlDetected; blockedControls+=blockedAudit.blocked>0&&!blockedAudit.passed; const bool passageInteriorControlDetected= blockedAudit.passageInteriorCapsuleSamples>0 @@ -6250,16 +5743,6 @@ namespace StandaloneStreamBenchmark std::printf("[ResolvedPassageBlockedControl] id=%s blocked_capsule_samples=%llu detected=%s\n", passage.stableId.c_str(),static_cast(blockedAudit.blocked), blockedAudit.blocked>0&&!blockedAudit.passed?"true":"false"); - std::printf("[PassageLandingChamberNegativeControl] id=%s side=upper capsule_samples=%llu interior_solid_samples=%llu detected=%s\n", - passage.stableId.c_str(), - static_cast(upperChamberBlocked.passageInteriorCapsuleSamples), - static_cast(upperChamberBlocked.passageInteriorSolidSamples), - upperChamberControlDetected?"true":"false"); - std::printf("[PassageLandingChamberNegativeControl] id=%s side=lower capsule_samples=%llu interior_solid_samples=%llu detected=%s\n", - passage.stableId.c_str(), - static_cast(lowerChamberBlocked.passageInteriorCapsuleSamples), - static_cast(lowerChamberBlocked.passageInteriorSolidSamples), - lowerChamberControlDetected?"true":"false"); std::printf("[ResolvedPassageInteriorNegativeControl] id=%s passage_interior_capsule_samples=%llu interior_solid_samples=%llu detected=%s status=%s\n", passage.stableId.c_str(), static_cast(blockedAudit.passageInteriorCapsuleSamples), @@ -6270,42 +5753,29 @@ namespace StandaloneStreamBenchmark const bool passageInteriorAuditPassed=passageCount>0 &&passageInteriorCapsuleSamples>0&&passageInteriorSolidSamples==0 &&passageInteriorControls==passageCount; - std::printf("[PassageInteriorCapsuleAuditSummary] interfaces=%llu capsule_samples=%llu solid_samples=%llu negative_controls=%llu/%llu landing_chambers=%llu chamber_negative_controls=%llu/%llu status=%s\n", + std::printf("[PassageInteriorCapsuleAuditSummary] interfaces=%llu capsule_samples=%llu solid_samples=%llu negative_controls=%llu/%llu status=%s\n", static_cast(passageCount), static_cast(passageInteriorCapsuleSamples), static_cast(passageInteriorSolidSamples), static_cast(passageInteriorControls), static_cast(passageCount), - static_cast(landingChamberCount), - static_cast(landingChamberNegativeControls), - static_cast(landingChamberCount), passageInteriorAuditPassed?"PASS":"FAIL"); - std::printf("[PassageLandingChamberNegativeControlSummary] detected=%llu/%llu naturally_open_baselines=%llu status=OBSERVED\n", - static_cast(landingChamberNegativeControls), - static_cast(landingChamberCount), - static_cast(landingChamberCount - -landingChamberNegativeControls)); - const bool shellAuditPassed=shellAudits==passageCount - &&shellFloorSamples>0&&shellWallSamples>0&&shellInteriorSamples>0 - &&shellFloorViolations==0&&shellWallViolations==0 - &&shellChamberFloorViolations==0&&shellChamberWallViolations==0 - &&shellInteriorViolations==0&&shellOpenAirSamples>0; - std::printf("[PassageShellAuditSummary] interfaces=%llu floor_samples=%llu floor_violations=%llu floor_mouth_blend_skips=%llu chamber_floor_samples=%llu chamber_floor_violations=%llu chamber_wall_samples=%llu chamber_wall_violations=%llu chamber_connection_skips=%llu open_air_shell_samples=%llu wall_samples=%llu wall_violations=%llu interior_samples=%llu interior_violations=%llu status=%s\n", - static_cast(shellAudits), - static_cast(shellFloorSamples), - static_cast(shellFloorViolations), - static_cast(shellFloorMouthBlendSkips), - static_cast(shellChamberFloorSamples), - static_cast(shellChamberFloorViolations), - static_cast(shellChamberWallSamples), - static_cast(shellChamberWallViolations), - static_cast(shellChamberConnectionSkips), - static_cast(shellOpenAirSamples), - static_cast(shellWallSamples), - static_cast(shellWallViolations), - static_cast(shellInteriorSamples), - static_cast(shellInteriorViolations), - shellAuditPassed?"PASS":"FAIL"); + std::printf("[LandingPadAuditSummary] pads=%llu passed=%llu negative_controls=%llu/%llu status=%s\n", + static_cast(landingPadCount), + static_cast(landingPadPasses), + static_cast(landingPadControls), + static_cast(landingPadCount*4), + landingPadCount>0&&landingPadPasses==landingPadCount + &&landingPadControls==landingPadCount*4?"PASS":"FAIL"); + const bool floorShellAuditPassed=floorShellAudits==passageCount + &&floorShellHostAirSamples>0&&floorShellSamples>0 + &&floorShellMissingSamples==0; + std::printf("[PassageFloorShellAuditSummary] passages=%llu host_air_samples=%llu shell_samples=%llu missing_shell_samples=%llu status=%s\n", + static_cast(floorShellAudits), + static_cast(floorShellHostAirSamples), + static_cast(floorShellSamples), + static_cast(floorShellMissingSamples), + floorShellAuditPassed?"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"); @@ -6394,10 +5864,13 @@ namespace StandaloneStreamBenchmark const bool auditPassed=passageCount>0&&passagePasses==passageCount &&passageSupportSamples>0 &&passageInteriorAuditPassed + &&noAddedSolidOverHostAirPassed + &&landingPadCount>0&&landingPadPasses==landingPadCount + &&landingPadControls==landingPadCount*4 &&naturalPasses==passageCount&&l23SmoothWallDetected &&mouthProfilePasses==passageCount&&l23TerraceControlDetected &&ripplePasses==passageCount - &&shellAuditPassed + &&floorShellAuditPassed &&selfOverlapAuditPassed &&endRunAuditPassed &&routePasses>0&&routePasses==routeWalkPasses&&routePasses==routeBindings @@ -7259,36 +6732,13 @@ namespace StandaloneStreamBenchmark } for(int side=0;side<2;++side) { - const auto& chamber=side==0?passage.upperLandingChamber - :passage.lowerLandingChamber; - if(chamber.radiusSubvoxels<=0||chamber.clearHeightSubvoxels<=0)continue; - const auto& mouth=side==0?passage.upperMouthSubvoxels - :passage.lowerMouthSubvoxels; - const double cx=qToMetres(mouth[0]); - const double cy=qToMetres(mouth[1]); - const double floor=qToMetres(chamber.floorCenterSubvoxels[2]); - const std::int64_t radiusSubvoxels= - VoxelForgeCore::ResolvedLayoutPassageLandingDiscRadiusSubvoxels( - passage.mouthRadiusSubvoxels,World.CoreVoxelSizeMicrons); - const double radius=qToMetres(radiusSubvoxels); - const double floorExtent=0.0; - const double top=floor+qToMetres(chamber.clearHeightSubvoxels); - const std::array minimum={ - (cx-radius)/coreMetres,(cy-radius)/coreMetres, - (floor-floorExtent-0.75)/coreMetres}; - const std::array maximum={ - (cx+radius)/coreMetres,(cy+radius)/coreMetres, - (top+floorExtent)/coreMetres}; - const std::array rimMinimum={ - (cx-radius-0.5)/coreMetres,(cy-radius-0.5)/coreMetres, - (floor-floorExtent-1.0)/coreMetres}; - const std::array rimMaximum={ - (cx+radius+0.5)/coreMetres,(cy+radius+0.5)/coreMetres, - (top+floorExtent+0.5)/coreMetres}; + std::array minimum{},maximum{}; + if(!Shape.resolvedPassageLandingPadInfluenceBounds( + passageIndex,side==0,minimum,maximum))continue; for(const int lod:{0,1}) { addBounds(minimum,maximum,lod,passageIndex,0,side); - addBounds(rimMinimum,rimMaximum,lod,passageIndex,4,side); + addBounds(minimum,maximum,lod,passageIndex,4,side); } } } @@ -7482,51 +6932,22 @@ namespace StandaloneStreamBenchmark const auto& passage=ResolvedLayout->passages[region.passageIndex]; if(region.kind<=1) { - const auto& chamber=region.side==0 - ?passage.upperLandingChamber - :passage.lowerLandingChamber; - const auto& mouth=region.side==0 - ?passage.upperMouthSubvoxels - :passage.lowerMouthSubvoxels; - if(chamber.radiusSubvoxels<=0 - ||chamber.clearHeightSubvoxels<=0)continue; - const std::int64_t radiusSubvoxels= - VoxelForgeCore::ResolvedLayoutPassageLandingDiscRadiusSubvoxels( - passage.mouthRadiusSubvoxels, - World.CoreVoxelSizeMicrons); - const std::array center={mouth[0],mouth[1]}; - const double floorSubvoxels= - VoxelForgeCore::ResolvedLayoutPassageLandingFloorAtXYSubvoxels( - chamber,pointXSubvoxels,pointYSubvoxels); - const bool inVerticalRange=pointZSubvoxels>=floorSubvoxels - -0.75*subvoxelsPerMetre - &&pointZSubvoxels<=floorSubvoxels - +chamber.clearHeightSubvoxels; - bool measured=false; - if(region.kind==0) + std::array minimum{},maximum{}; + const bool hasBounds=Shape.resolvedPassageLandingPadInfluenceBounds( + region.passageIndex,region.side==0,minimum,maximum); + const bool insideBounds=hasBounds + &&pointVoxels[0]>=minimum[0] + &&pointVoxels[0]<=maximum[0] + &&pointVoxels[1]>=minimum[1] + &&pointVoxels[1]<=maximum[1] + &&pointVoxels[2]>=minimum[2] + &&pointVoxels[2]<=maximum[2]; + if(insideBounds) { - measured=inVerticalRange - &&VoxelForgeCore::IsInsideResolvedLayoutPassageLandingDiscV1( - center,radiusSubvoxels,pointXSubvoxels, - pointYSubvoxels); - if(measured) + if(region.kind==0) ++coverage[region.passageIndex].discSamples[ region.side][a.lod]; - } - else - { - const std::int64_t rimHalfWidth=static_cast( - std::llround(0.5*subvoxelsPerMetre)); - const bool inRimVerticalRange=pointZSubvoxels>=floorSubvoxels - -0.75*subvoxelsPerMetre - &&pointZSubvoxels<=floorSubvoxels - +chamber.clearHeightSubvoxels - +0.5*subvoxelsPerMetre; - measured=inRimVerticalRange - &&VoxelForgeCore::IsInsideResolvedLayoutPassageLandingRimV1( - center,radiusSubvoxels,rimHalfWidth, - pointXSubvoxels,pointYSubvoxels); - if(measured) + else ++coverage[region.passageIndex].rimSamples[ region.side][a.lod]; } @@ -7609,37 +7030,20 @@ namespace StandaloneStreamBenchmark bool inLanding=false; for(const int side:{0,1}) { - const auto& chamber=side==0?passage.upperLandingChamber - :passage.lowerLandingChamber; - const auto& mouth=side==0?passage.upperMouthSubvoxels - :passage.lowerMouthSubvoxels; - const std::int64_t radiusQ= - VoxelForgeCore::ResolvedLayoutPassageLandingDiscRadiusSubvoxels( - passage.mouthRadiusSubvoxels, - World.CoreVoxelSizeMicrons); - const std::array center={mouth[0],mouth[1]}; - const bool inside=VoxelForgeCore:: - IsInsideResolvedLayoutPassageLandingDiscV1(center, - radiusQ,xMetres*subvoxelsPerMetre, - yMetres*subvoxelsPerMetre); - const double floor=qToMetres( - chamber.floorCenterSubvoxels[2]); - const double top=floor+qToMetres( - chamber.clearHeightSubvoxels); - const bool insideDisc=inside - &&zMetres>=floor-0.75&&zMetres<=top; - const std::int64_t halfRimWidth=static_cast( - std::llround(0.5*subvoxelsPerMetre)); - const bool onRim=VoxelForgeCore:: - IsInsideResolvedLayoutPassageLandingRimV1(center, - radiusQ,halfRimWidth, - xMetres*subvoxelsPerMetre, - yMetres*subvoxelsPerMetre) - &&zMetres>=floor-0.75&&zMetres<=top+0.5; - if(insideDisc||onRim) + std::array minimum{},maximum{}; + if(!Shape.resolvedPassageLandingPadInfluenceBounds( + static_cast(&passage + -ResolvedLayout->passages.data()),side==0, + minimum,maximum))continue; + const bool insideBounds=pointVoxels[0]>=minimum[0] + &&pointVoxels[0]<=maximum[0] + &&pointVoxels[1]>=minimum[1] + &&pointVoxels[1]<=maximum[1] + &&pointVoxels[2]>=minimum[2] + &&pointVoxels[2]<=maximum[2]; + if(insideBounds) { - sampleTag=onRim - ?"rim":"disc"; + sampleTag="landing_pad"; inLanding=true; break; } diff --git a/Standalone/Tools/verify/verify.py b/Standalone/Tools/verify/verify.py index 6fe79fe..9fad1aa 100644 --- a/Standalone/Tools/verify/verify.py +++ b/Standalone/Tools/verify/verify.py @@ -125,11 +125,11 @@ RECIPE_RE = re.compile( def owner_hash_baseline(recipe: str, style: int) -> tuple[Path, str]: if recipe == "surface-world" and style == 1: - return (BASELINES / "owner-hash/canonical/owner-hash-surface-world-style1-v241.baseline", - "L175-v241-passage-tile-seam-fix") + return (BASELINES / "owner-hash/canonical/owner-hash-surface-world-style1-v242.baseline", + "L176-v242-air-only-passage-clamp") if recipe == "floating-islands" and style == 2: - return (BASELINES / "owner-hash/canonical/owner-hash-floating-islands-style2-v241.baseline", - "L175-v241-passage-tile-seam-fix") + return (BASELINES / "owner-hash/canonical/owner-hash-floating-islands-style2-v242.baseline", + "L176-v242-air-only-passage-clamp") raise VerificationFailure(f"no pinned owner hash baseline for {recipe} style {style}")