From 5bd158d4eb995a91edf063378124a94aec93170e Mon Sep 17 00:00:00 2001 From: Fr0zka Date: Sun, 1 Nov 2026 18:41:40 +0100 Subject: [PATCH] core: one tunnel for two strates with landing chambers (L151, owner design; v238, grade open) Each passage records upper and lower landing chambers (floor, gradient, radius, 4.5 m clear height) composed with the tunnel shell; cave route endpoints bind to the chambers; envelope audit permits only the named chamber joints. Seed 2: 0 solid in 319,579 passage+chamber capsule samples, 0 shell violations, 0 envelope conflicts, controls fire. Open: tunnel-to-chamber floor blend leaves 19.1/19.0 deg grades (limit 15); sweeps/timing not run. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1 --- .../include/VoxelForgeCore/VoxelForgeCore.h | 16 +- Standalone/Core/src/ResolvedLayoutInternal.h | 397 ++++++++++++--- Standalone/Core/src/ShapeWorldInternal.h | 293 ++++++++++- .../Linux151/PassageEnvelopeAudit.py | 318 ++++++++++++ .../WorldAssemblerM3/Linux151/REPORT.md | 39 ++ .../Linux151/RunL151Captures.py | 206 ++++++++ .../Linux151/captures/INDEX.md | 35 ++ .../after-l151-0-1-0-lower-landing.jpg | Bin 0 -> 17604 bytes .../after-l151-0-1-0-upper-landing.jpg | Bin 0 -> 25822 bytes .../after-l151-1-2-0-lower-landing.jpg | Bin 0 -> 19808 bytes .../after-l151-1-2-0-upper-landing.jpg | Bin 0 -> 18580 bytes .../after-l151-2-3-0-lower-landing.jpg | Bin 0 -> 20302 bytes .../after-l151-2-3-0-upper-landing.jpg | Bin 0 -> 22040 bytes .../before-l150-0-1-0-lower-landing.jpg | Bin 0 -> 15232 bytes .../before-l150-0-1-0-upper-landing.jpg | Bin 0 -> 26058 bytes .../before-l150-1-2-0-lower-landing.jpg | Bin 0 -> 16148 bytes .../before-l150-1-2-0-upper-landing.jpg | Bin 0 -> 16632 bytes .../before-l150-2-3-0-lower-landing.jpg | Bin 0 -> 14685 bytes .../before-l150-2-3-0-upper-landing.jpg | Bin 0 -> 16652 bytes .../Linux151/captures/manifest.json | 278 +++++++++++ Standalone/Source/StreamBenchmarkPipeline.h | 472 +++++++++++++++++- 21 files changed, 1949 insertions(+), 105 deletions(-) create mode 100644 Standalone/Research/WorldAssemblerM3/Linux151/PassageEnvelopeAudit.py create mode 100644 Standalone/Research/WorldAssemblerM3/Linux151/REPORT.md create mode 100644 Standalone/Research/WorldAssemblerM3/Linux151/RunL151Captures.py create mode 100644 Standalone/Research/WorldAssemblerM3/Linux151/captures/INDEX.md create mode 100644 Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-0-1-0-lower-landing.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-0-1-0-upper-landing.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-1-2-0-lower-landing.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-1-2-0-upper-landing.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-2-3-0-lower-landing.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-2-3-0-upper-landing.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux151/captures/before-l150-0-1-0-lower-landing.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux151/captures/before-l150-0-1-0-upper-landing.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux151/captures/before-l150-1-2-0-lower-landing.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux151/captures/before-l150-1-2-0-upper-landing.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux151/captures/before-l150-2-3-0-lower-landing.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux151/captures/before-l150-2-3-0-upper-landing.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux151/captures/manifest.json diff --git a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h index 8c44461..7ddad4e 100644 --- a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h +++ b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h @@ -510,8 +510,8 @@ namespace VoxelForgeCore // preserves open cave lanes through passage shell overlaps; 236 binds passage mouths to // the planned graph route, preserves its walk corridor through shell overlaps, and keeps // boundary-blended host rock from widening the authored passage wall. - // v237 adds the shared Core cave-graph shaft primitive and basal terminal route. - inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 237; + // v238 plans one resolved passage spline with explicit landing chambers. + inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 238; enum class FieldChannel : std::uint32_t { @@ -1209,6 +1209,16 @@ 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; + }; + struct ResolvedLayoutPassageV1 { std::string stableId; @@ -1223,6 +1233,8 @@ namespace VoxelForgeCore std::int64_t midRadiusSubvoxels = 0; // Derived from the mouth binding; the main descent is monotone through this point. std::size_t lowerMouthControlIndex = 0; + ResolvedLayoutPassageLandingChamberV1 upperLandingChamber; + ResolvedLayoutPassageLandingChamberV1 lowerLandingChamber; std::vector controlPoints; }; diff --git a/Standalone/Core/src/ResolvedLayoutInternal.h b/Standalone/Core/src/ResolvedLayoutInternal.h index 7b7520a..a244131 100644 --- a/Standalone/Core/src/ResolvedLayoutInternal.h +++ b/Standalone/Core/src/ResolvedLayoutInternal.h @@ -565,7 +565,21 @@ namespace <<"],\"lower_mouth_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 @@ -1730,6 +1800,21 @@ std::optional ResolvedLayoutSnapshotV3::FromJson( const auto& lower=v.Get("lower_mouth_q").AsArray(); for(std::size_t axis=0;axis<3&&axis(layout.coreVoxelSizeMicrometres) /WorldAssemblySubvoxelsPerVoxel;}; - const std::int64_t mouthBoreRadius=std::clamp( - std::max(MinimumBoreRadius,settings.minimumTunnelRadiusMicrometres), - MinimumBoreRadius,MaximumBoreRadius); + constexpr std::int64_t LandingConnectorBoreRadius=2000000; + const std::int64_t mouthBoreRadius=LandingConnectorBoreRadius; const auto chooseMouthRouteTangent=[&](const ResolvedLayoutPassageV1& passage, std::size_t mouthIndex,bool lowerSide, const std::array& authoredRouteTangentQ16, - std::int64_t authoredFloorSlopeQ16,const FPoint& mouthFloorPoint) + std::int64_t authoredFloorSlopeQ16,const FPoint& mouthFloorPoint, + std::int64_t chamberRadiusMicrometres) { constexpr std::array,16> DirectionsQ16={{ {{65536,0}},{{60547,25080}},{{46341,46341}},{{25080,60547}}, @@ -2418,7 +2502,9 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout, +outwardQ16[1]*authoredRouteTangentQ16[1]) /(65536ll*65536ll)); constexpr std::int64_t MouthClearanceProbeExtent=48000000; - for(std::int64_t distance=2000000; + const std::int64_t firstClearanceProbe=std::max( + 2000000,chamberRadiusMicrometres+1000000); + for(std::int64_t distance=firstClearanceProbe; distance<=MouthClearanceProbeExtent;distance+=2000000) { const std::int64_t x=mouthFloorPoint[0] @@ -2515,9 +2601,10 @@ 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(lowerSide?passage.lowerMouthSubvoxels[axis] - :passage.upperMouthSubvoxels[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; @@ -2537,28 +2624,26 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout, if(tangentLength<=0)return false; routeTangentQ16={tx*65536/tangentLength,ty*65536/tangentLength}; const auto& mouthControl=passage.controlPoints[mouthIndex]; - const auto& tangentControl=passage.controlPoints[tangentIndex]; - const std::int64_t mouthFloor=mouthControl.positionSubvoxels[2] - -mouthControl.radiusSubvoxels; - const std::int64_t tangentFloor=tangentControl.positionSubvoxels[2] - -tangentControl.radiusSubvoxels; - // Resolved passage mouths already store the player center at floor + 1.13 m. - // Cave edge control points store the floor itself, so start from the passage - // control's floor rather than reusing the player-center mouth height. + const std::int64_t mouthFloor=chamber.floorCenterSubvoxels[2]; point[2]=qToMicrometres(mouthFloor); - const std::int64_t passageFloorDelta=direction*(mouthFloor-tangentFloor); - floorSlopeQ16=passageFloorDelta*65536/tangentLength; + floorSlopeQ16=(chamber.floorGradientQ16[0]*routeTangentQ16[0] + +chamber.floorGradientQ16[1]*routeTangentQ16[1])/65536; const auto authoredRouteTangentQ16=routeTangentQ16; routeTangentQ16=chooseMouthRouteTangent(passage,mouthIndex,lowerSide, - authoredRouteTangentQ16,floorSlopeQ16,point); - const std::int64_t tangentAlignment=routeTangentQ16[0] - *authoredRouteTangentQ16[0]+routeTangentQ16[1] - *authoredRouteTangentQ16[1]; - floorSlopeQ16=floorSlopeQ16*tangentAlignment/(65536ll*65536ll); + authoredRouteTangentQ16,floorSlopeQ16,point, + qToMicrometres(chamber.radiusSubvoxels)); + floorSlopeQ16=(chamber.floorGradientQ16[0]*routeTangentQ16[0] + +chamber.floorGradientQ16[1]*routeTangentQ16[1])/65536; const std::int64_t offsetDirection=lowerSide?1:-1; - point[0]+=offsetDirection*routeTangentQ16[0]*MouthOverlap/65536; - point[1]+=offsetDirection*routeTangentQ16[1]*MouthOverlap/65536; - point[2]+=offsetDirection*floorSlopeQ16*MouthOverlap/65536; + const std::int64_t portOffset=std::max(0, + qToMicrometres(chamber.radiusSubvoxels)-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; return true; }; FPoint routeStart{},routeEnd{}; @@ -3391,13 +3476,14 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout, const auto arrivalPoint=routeStart; FPoint previousPoint=arrivalPoint; std::int64_t previousProfileDistance=0; - std::string previousAnchor=arrivalPassage->stableId+"/lower-mouth"; + std::string previousAnchor=arrivalPassage->stableId+"/lower-landing-chamber"; for(std::size_t nodeIndex=0;nodeIndexstableId+"/upper-mouth", - routeEnd,nodes.back(),selectedBoreRadius(departureEdgeId), + auto edge=makeEdge(departureEdgeId, + departurePassage->stableId+"/upper-landing-chamber", + routeEnd,nodes.back(), + std::min(selectedBoreRadius(departureEdgeId),LandingConnectorBoreRadius), totalRouteLength,nodes.back().floorProfileDistanceMicrometres); edges.push_back(std::move(edge)); } @@ -3873,7 +3961,6 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t {error=passage.stableId+" route mouth is outside the resolved PassageConfig.DistanceMax radius";return false;} passage.upperMouthSubvoxels=upperMouth;passage.lowerMouthSubvoxels=lowerMouth; - constexpr std::int64_t LandingExtensionQ=8; std::array upperTangent{},lowerTangent{}; const bool hasUpperTangent=hasUpperRoute &&routeTangent(upper,true,upperTangent); @@ -4643,39 +4730,6 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t } pushXY({lowerMouth[0],lowerMouth[1]});totalHorizontal=measurePath(); std::size_t lowerMouthIndex=xy.size()-1; - std::vector> lowerLandingPoints; - if(hasLowerTangent) - { - std::array previousRoutePoint=lowerMouth; - std::int64_t remainingQ=LandingExtensionQ*qPerMetre; - bool skippedMouth=false; - for(const auto& anchor:lower.anchors) - if(anchor.kind=="route_point"&&anchor.values.size()>=3) - { - const std::array routePoint{{anchor.values[0], - anchor.values[1],anchor.values[2]}}; - if(!skippedMouth){skippedMouth=true;continue;} - if(remainingQ<=0)break; - const std::int64_t dx=routePoint[0]-previousRoutePoint[0]; - const std::int64_t dy=routePoint[1]-previousRoutePoint[1]; - const std::uint64_t segmentLength=distanceQ(dx,dy); - if(segmentLength==0)continue; - const std::int64_t advance=std::min(remainingQ, - static_cast(segmentLength)); - std::array landingPoint{}; - for(std::size_t axis=0;axis<3;++axis) - landingPoint[axis]=previousRoutePoint[axis]+PassageRoundRatio( - routePoint[axis]-previousRoutePoint[axis],advance, - static_cast(segmentLength)); - if(!radialWithin(landingPoint,placementRadius))break; - lowerLandingPoints.push_back(landingPoint); - pushXY({landingPoint[0],landingPoint[1]}); - previousRoutePoint=landingPoint;remainingQ-=advance; - if(advance(segmentLength))break; - } - if(lowerLandingPoints.empty()) - {error=passage.stableId+" lower route has no landing length inside PassageConfig.DistanceMax";return false;} - } if(VoxelForgeCore::WorldAssemblyEvaluatorVersion>=188 &&style==0&&anchoredIslandPassage&&!anchoredSpiral&&xy.size()>4) { @@ -4718,8 +4772,7 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t std::max(8*qPerMetre,2*mouthRadius)); for(std::size_t i=0;ilowerMouthIndex&&i+1>=xy.size()-1))?mouthRadius:midRadius; + std::int64_t radius=(i==0||i==lowerMouthIndex)?mouthRadius:midRadius; if(surfaceGroundDeparture&&i(cumulative[i]) @@ -4761,9 +4814,7 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t } } std::int64_t playerZ=0; - if(i>lowerMouthIndex) - playerZ=lowerLandingPoints[i-lowerMouthIndex-1][2]; - else if(hasAnchorHelix&&i<=helixEndIndex) + if(hasAnchorHelix&&i<=helixEndIndex) playerZ=upperMouth[2]+PassageRoundRatio(helixBedrockZ-upperMouth[2], static_cast(cumulative[i]), static_cast(helixHorizontal)); @@ -4815,6 +4866,201 @@ 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()); + for(const auto& point:passage.controlPoints) + { + chamberFloorControls.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( + point.positionSubvoxels[2]-point.radiusSubvoxels)); + } + const auto routePoints=[&](const ResolvedLayoutStratumV2& host) + { + std::vector> route; + for(const auto& anchor:host.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])}); + return route; + }; + if(upper.generatorKind=="TunnelNetwork" + ||upper.generatorKind=="SurfaceWorld") + { + auto route=routePoints(upper); + 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, + chamberFloorControls.front()[0],chamberFloorControls.front()[1], + routeHeight,distanceSquared) + &&distanceSquared<=std::pow(chamberFloorControls.front()[3] + +2.0*qPerMetre,2.0)) + VoxelForgeCore::PassageSpline::BlendEndpointFloorToRoute( + chamberFloorControls,chamberFloorProfile,0,route,qPerMetre, + upper.generatorKind=="SurfaceWorld"?0.98:1.13); + } + if(lower.generatorKind=="TunnelNetwork" + ||lower.generatorKind=="VerticalShafts") + { + auto route=routePoints(lower); + 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], + routeHeight,distanceSquared) + &&distanceSquared<=std::pow( + chamberFloorControls[lowerMouthIndex][3] + +2.0*qPerMetre,2.0)) + VoxelForgeCore::PassageSpline::BlendEndpointFloorToRoute( + chamberFloorControls,chamberFloorProfile, + lowerMouthIndex,route,qPerMetre); + } + 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") + { + const auto surfaceRoute=routePoints(host); + std::vector> nearbyRoute; + // Fit the landing floor only to route anchors inside its + // chamber footprint; samples beyond the chamber can pull + // the floor away from the route at the joint. + const double supportRadius=static_cast( + chamber.radiusSubvoxels); + for(const auto& point:surfaceRoute) + { + const double routeDx=point[0]-endpoint.positionSubvoxels[0]; + const double routeDy=point[1]-endpoint.positionSubvoxels[1]; + if(routeDx*routeDx+routeDy*routeDy<=supportRadius*supportRadius) + nearbyRoute.push_back(point); + } + if(nearbyRoute.size()>=2) + { + std::array mean{}; + for(const auto& point:nearbyRoute) + for(std::size_t axis=0;axis<3;++axis) + mean[axis]+=point[axis]/nearbyRoute.size(); + double xx=0.0,xy=0.0,yy=0.0,xz=0.0,yz=0.0; + for(const auto& point:nearbyRoute) + { + const double routeDx=point[0]-mean[0]; + const double routeDy=point[1]-mean[1]; + const double routeDz=point[2]-mean[2]; + xx+=routeDx*routeDx;xy+=routeDx*routeDy; + yy+=routeDy*routeDy;xz+=routeDx*routeDz; + yz+=routeDy*routeDz; + } + const double determinant=xx*yy-xy*xy; + double gradientX=0.0,gradientY=0.0; + if(determinant>1.0e-9) + { + gradientX=(xz*yy-yz*xy)/determinant; + gradientY=(yz*xx-xz*xy)/determinant; + } + else + { + // Route anchors can be collinear inside the chamber. + // Fit their dominant horizontal direction by least + // squares instead of extrapolating from one far point. + const double trace=xx+yy; + const double discriminant=std::sqrt(std::max(0.0, + (xx-yy)*(xx-yy)+4.0*xy*xy)); + const double variance=0.5*(trace+discriminant); + if(variance>1.0e-9) + { + double axisX=1.0,axisY=0.0; + if(std::abs(xy)>1.0e-12) + {axisX=xy;axisY=variance-xx;} + else if(yy>xx) + {axisX=0.0;axisY=1.0;} + const double axisLength=std::hypot(axisX,axisY); + axisX/=axisLength;axisY/=axisLength; + const double routeSlope=(axisX*xz+axisY*yz)/variance; + gradientX=routeSlope*axisX; + gradientY=routeSlope*axisY; + } + } + chamber.floorGradientQ16={ + static_cast(std::llround(gradientX*65536.0)), + static_cast(std::llround(gradientY*65536.0))}; + } + } + 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; + 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", + 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])); layout.passages.push_back(std::move(passage)); } } @@ -4842,17 +5088,22 @@ ResolvedLayoutValidatorAuditV3 AuditResolvedLayoutValidatorV3() else result.failures.push_back({"negative_control_accepted",std::move(name)+" was accepted by strict validator"}); }; auto base=ResolvedLayoutSnapshotV3{};base.worldIdentity="synthetic";base.ownerVoxelSizeMicrometres=125000; - base.coreVoxelSizeMicrometres=250000;base.strates={make("a",0,10000000,0),make("b",1,-2000000,-12000000)}; + base.coreVoxelSizeMicrometres=250000;base.strates={make("a",0,30000000,0),make("b",1,-2000000,-40000000)}; ResolvedLayoutPassageConfigV1 passageConfig;passageConfig.upperStrateIndex=0;passageConfig.connections=1; passageConfig.mouthRadiusSubvoxels=1280;passageConfig.midRadiusSubvoxels=1280; - passageConfig.reachMinimumSubvoxels=2048;passageConfig.reachMaximumSubvoxels=2048; + passageConfig.reachMinimumSubvoxels=9216;passageConfig.reachMaximumSubvoxels=9216; passageConfig.distanceMinimumSubvoxels=1024;passageConfig.distanceMaximumSubvoxels=10240; std::string planError; if(!PlanResolvedLayoutPassagesV1(base,19,{passageConfig},planError)) result.failures.push_back({"synthetic_passage_plan_failed",planError}); {std::vector issues;std::string error; if(!base.validate(ResolvedLayoutValidationModeV3::strict,issues,error)) - result.failures.push_back({"positive_control_rejected","valid ceiling/content/bedrock layout was rejected"});} + { + for(const auto& issue:issues) + std::printf("[ResolvedLayoutValidatorPositiveControlIssue] code=%s detail=%s\n", + issue.code.c_str(),issue.detail.c_str()); + result.failures.push_back({"positive_control_rejected","valid ceiling/content/bedrock layout was rejected"}); + }} auto missingPassage=base;missingPassage.passages.clear();expectReject("missing interface passage",missingPassage); auto outsidePassage=base; if(!outsidePassage.passages.empty()) diff --git a/Standalone/Core/src/ShapeWorldInternal.h b/Standalone/Core/src/ShapeWorldInternal.h index 1a57f85..df853c5 100644 --- a/Standalone/Core/src/ShapeWorldInternal.h +++ b/Standalone/Core/src/ShapeWorldInternal.h @@ -1280,6 +1280,8 @@ public: 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); @@ -1317,14 +1319,56 @@ public: const bool passageTargetsPlannedTunnelNetwork=plannedTunnelStratum; const double mouthShellBlendMetres=passageTargetsPlannedTunnelNetwork ?2.0:MouthShellBlendMetres; - // Legacy native cave fields own the floor and mouth carve on their - // lower-strate boundary. The planner's TunnelNetwork field instead - // needs the shared passage composer to carry its incoming mouth open. - if(passageTargetsNativeCaveField&&!passageTargetsPlannedTunnelNetwork) - continue; + // Legacy native cave fields own the tunnel bore on their lower-strate + // boundary. The shared passage composer still owns its landing chamber. 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; + FNearestTubeSample nearest; const bool usePreparedSweep=passageTileScopeMatches &&passageTileQueries->hasSweptTubeBase(); @@ -1615,6 +1659,11 @@ public: if(!std::isfinite(unionDistance))return density; 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 @@ -1678,7 +1727,11 @@ public: const double routeFloor=std::lerp(a[2],b[2],t)-1.13; return queryZ>=routeFloor-0.30&&queryZ<=routeFloor+2.25; }; - constexpr double PortalWalkLeadMetres=8.0; + // 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]); @@ -1693,6 +1746,90 @@ public: 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; + + 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) @@ -1755,7 +1892,11 @@ public: 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); } @@ -1892,6 +2033,26 @@ public: } if(overlaps(low,high))return true; } + const auto chamberOverlaps=[&]( + const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& chamber) + { + 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); + }; + if(chamberOverlaps(passage.upperLandingChamber) + ||chamberOverlaps(passage.lowerLandingChamber))return true; } return false; } @@ -3668,6 +3829,12 @@ private: 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; }; struct FPassageSegmentData @@ -3692,6 +3859,49 @@ 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, @@ -3742,7 +3952,9 @@ private: -constants.lowerMouthTangentX,-constants.lowerMouthTangentY, VoxelForgeCore::PassageSpline::UpperMouthFloorBlendRadiusMetres,1.13, qPerMetre); - return lowerBlendFloor+floorDepthSubvoxels; + const double landingFloor=BlendPassageFloorToLandingChambers(constants, + segment,floorT,queryXSubvoxels,queryYSubvoxels,lowerBlendFloor,qPerMetre); + return landingFloor+floorDepthSubvoxels; } static float PassageFlatBottomDistance(float roundDistanceSubvoxels, @@ -4900,6 +5112,23 @@ private: passage.lowerMouthControlIndex,1,passage.controlPoints.size()-1); auto& constants=PassageComposerConstants[passageIndex]; constants.routeEndControlIndex=routeEnd; + 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;}); @@ -4928,7 +5157,8 @@ private: double upperMouthFloorDepth=authoredUpperMouthFloorDepth; if(upperStratum!=ResolvedLayout->strates.end() &&(upperStratum->generatorKind=="FloatingIslands" - ||upperStratum->generatorKind=="TunnelNetwork")) + ||upperStratum->generatorKind=="TunnelNetwork" + ||upperStratum->generatorKind=="SurfaceWorld")) { std::vector> route; for(const auto& anchor:upperStratum->anchors) @@ -4947,16 +5177,19 @@ private: upperMouthFloorDepth=std::max(upperMouthFloorDepth, std::max(0.0,(controls.front()[2]-routeHeight)/qPerMetre +constants.upperMouthPlayerCenterHeightMetres)); - if(upperStratum->generatorKind=="TunnelNetwork") + if(upperStratum->generatorKind=="TunnelNetwork" + ||upperStratum->generatorKind=="SurfaceWorld") VoxelForgeCore::PassageSpline::BlendEndpointFloorToRoute( - controls,plannedFloorProfile,0,route,qPerMetre); + 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=="TunnelNetwork" + ||upperStratum->generatorKind=="SurfaceWorld")) for(const auto& anchor:upperStratum->anchors) if(anchor.kind=="route_point"&&anchor.values.size()>=3) constants.upperMouthReceivingRoute.push_back({ @@ -5044,8 +5277,8 @@ private: constants.upperMouthTangentY=sampledSegments.front().b[1] -sampledSegments.front().a[1]; } - const std::string lowerMouthAnchor=passage.stableId+"/lower-mouth"; - const std::string upperMouthAnchor=passage.stableId+"/upper-mouth"; + 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; @@ -5230,6 +5463,40 @@ private: std::ceil((localMaximum[axis]+localExtent)*Q)); } } + const auto includeLandingChamber=[&]( + const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& chamber) + { + 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}; + FPassageProofBound proof; + for(std::size_t axis=0;axis<3;++axis) + { + routeMinimum[axis]=std::min(routeMinimum[axis],minimum[axis]); + routeMaximum[axis]=std::max(routeMaximum[axis],maximum[axis]); + proof.minimum[axis]=minimum[axis]; + proof.maximum[axis]=maximum[axis]; + } + PassageProofBounds.push_back(proof); + }; + if(segmentIndex==0) + includeLandingChamber( + ResolvedLayout->passages[passageIndex].upperLandingChamber); + if(segmentEndIndex==segmentCount) + includeLandingChamber( + ResolvedLayout->passages[passageIndex].lowerLandingChamber); const double routeExtent=maximumRadiusSubvoxels/Q +requiredReachCoreVoxels+0.01; const double tileExtent=routeExtent; diff --git a/Standalone/Research/WorldAssemblerM3/Linux151/PassageEnvelopeAudit.py b/Standalone/Research/WorldAssemblerM3/Linux151/PassageEnvelopeAudit.py new file mode 100644 index 0000000..14f3b62 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux151/PassageEnvelopeAudit.py @@ -0,0 +1,318 @@ +#!/usr/bin/env python3 +"""Audit cave primitive bounds against passage envelopes, allowing chamber joints only.""" +from __future__ import annotations + +import math +from typing import Any + + +INFLUENCE_REACH_M = 1.053334 +SHELL_THICKNESS_M = 0.75 + + +def _point(control: dict[str, Any], scale: float) -> tuple[float, float, float, float]: + x, y, z = control["position_q"] + return (x * scale, y * scale, z * scale, control["radius_q"] * scale) + + +def _knot_span(a, b) -> float: + return max(1.0e-6, math.sqrt(math.sqrt((b[0] - a[0]) ** 2 + (b[1] - a[1]) ** 2))) + + +def _evaluate(controls, span: int, t: float): + p1, p2 = controls[span], controls[span + 1] + p0 = controls[span - 1] if span else controls[0] + p3 = controls[min(span + 2, len(controls) - 1)] + dt0, dt1, dt2 = _knot_span(p0, p1), _knot_span(p1, p2), _knot_span(p2, p3) + t2, t3 = t * t, t * t * t + result = [] + for axis in (0, 1, 3): + m1 = dt1 * ((p1[axis] - p0[axis]) / dt0 + - (p2[axis] - p0[axis]) / (dt0 + dt1) + + (p2[axis] - p1[axis]) / dt1) + m2 = dt1 * ((p2[axis] - p1[axis]) / dt1 + - (p3[axis] - p1[axis]) / (dt1 + dt2) + + (p3[axis] - p2[axis]) / dt2) + value = ((2 * t3 - 3 * t2 + 1) * p1[axis] + + (-2 * t3 + 3 * t2) * p2[axis] + + (t3 - 2 * t2 + t) * m1 + (t3 - t2) * m2) + result.append(value) + radius = min(max(result[2], min(p1[3], p2[3])), max(p1[3], p2[3])) + return result[0], result[1], p1[2] + (p2[2] - p1[2]) * t, radius + + +def _segments(passage: dict[str, Any], scale: float): + controls = [_point(row, scale) for row in passage["control_points"]] + route_end = min(max(1, int(passage.get("lower_mouth_control_index", len(controls) - 1))), + len(controls) - 1) + along = 0.0 + for span in range(route_end): + a, b = controls[span], controls[span + 1] + chord = math.hypot(b[0] - a[0], b[1] - a[1]) + steps = max(1, math.ceil(chord / 2.0)) + prior = _evaluate(controls, span, 0.0) + for step in range(1, steps + 1): + current = _evaluate(controls, span, step / steps) + length = math.dist(prior[:3], current[:3]) + yield prior, current, along, max(prior[3], current[3]) + INFLUENCE_REACH_M + along += length + prior = current + + +def _distance_squared(a, b, bounds, t: float) -> float: + total = 0.0 + for axis in range(3): + pos = a[axis] + (b[axis] - a[axis]) * t + lo, hi = bounds[axis] + gap = lo - pos if pos < lo else pos - hi if pos > hi else 0.0 + total += gap * gap + return total + + +def _overlap_interval(a, b, bounds, radius: float): + cuts = {0.0, 1.0} + for axis in range(3): + delta = b[axis] - a[axis] + if abs(delta) < 1.0e-15: + continue + for edge in bounds[axis]: + t = (edge - a[axis]) / delta + if 0.0 < t < 1.0: + cuts.add(t) + cuts = sorted(cuts) + best_t, best_d2 = 0.0, math.inf + for lo, hi in zip(cuts, cuts[1:]): + mid = (lo + hi) * 0.5 + quadratic = linear = 0.0 + for axis in range(3): + delta = b[axis] - a[axis] + pos = a[axis] + delta * mid + low, high = bounds[axis] + edge = low if pos < low else high if pos > high else None + if edge is not None: + quadratic += delta * delta + linear += delta * (a[axis] - edge) + candidate = min(hi, max(lo, -linear / quadratic)) if quadratic else lo + d2 = _distance_squared(a, b, bounds, candidate) + if d2 < best_d2: + best_t, best_d2 = candidate, d2 + for t in (0.0, 1.0): + d2 = _distance_squared(a, b, bounds, t) + if d2 < best_d2: + best_t, best_d2 = t, d2 + if best_d2 > radius * radius: + return None + + radius2 = radius * radius + if _distance_squared(a, b, bounds, 0.0) <= radius2: + left = 0.0 + else: + low, high = 0.0, best_t + for _ in range(42): + mid = (low + high) * 0.5 + if _distance_squared(a, b, bounds, mid) <= radius2: + high = mid + else: + low = mid + left = high + if _distance_squared(a, b, bounds, 1.0) <= radius2: + right = 1.0 + else: + low, high = best_t, 1.0 + for _ in range(42): + mid = (low + high) * 0.5 + if _distance_squared(a, b, bounds, mid) <= radius2: + low = mid + else: + high = mid + right = low + return left, right, best_t + + +def _inside_chamber(point, chamber: dict[str, Any], scale: float) -> bool: + cx, cy, cz = (value * scale for value in chamber["floor_center_q"]) + radius = chamber["radius_q"] * scale + clear = chamber["clear_height_q"] * scale + gx, gy = (value / 65536.0 for value in chamber["floor_gradient_q16"]) + floor = cz + gx * (point[0] - cx) + gy * (point[1] - cy) + chamber_distance = max(math.hypot(point[0] - cx, point[1] - cy) - radius, + floor - point[2], point[2] - (floor + clear)) + return chamber_distance <= SHELL_THICKNESS_M + + +def _primitive_bounds(row: dict[str, Any]): + return tuple((row["bounds_min_um"][i] / 1.0e6, + row["bounds_max_um"][i] / 1.0e6) for i in range(3)) + + +def _bezier(points, t: float): + u = 1.0 - t + return tuple(u**3 * points[0][axis] + 3*u*u*t * points[1][axis] + + 3*u*t*t * points[2][axis] + t**3 * points[3][axis] + for axis in range(3)) + + +def _distance_to_ellipse(radial: float, vertical: float, + radius: float, height: float) -> float: + """Euclidean distance to the edge bore's horizontal/vertical ellipse.""" + radial = abs(radial) + vertical = abs(vertical) + if (radial / radius) ** 2 + (vertical / height) ** 2 <= 1.0: + return 0.0 + + lo, hi = 0.0, math.pi / 2.0 + ratio = (math.sqrt(5.0) - 1.0) / 2.0 + + def squared_distance(angle: float) -> float: + return ((radial - radius * math.cos(angle)) ** 2 + + (vertical - height * math.sin(angle)) ** 2) + + left = hi - ratio * (hi - lo) + right = lo + ratio * (hi - lo) + left_value, right_value = squared_distance(left), squared_distance(right) + for _ in range(48): + if left_value < right_value: + hi, right, right_value = right, left, left_value + left = hi - ratio * (hi - lo) + left_value = squared_distance(left) + else: + lo, left, left_value = left, right, right_value + right = lo + ratio * (hi - lo) + right_value = squared_distance(right) + return math.sqrt(min(left_value, right_value)) + + +def _actual_edge_clearance(passage, edge, scale: float, + chamber: dict[str, Any]) -> float: + """Return minimum true tube clearance outside the landing chamber. + + The route edge is a swept ellipse, with its recorded polyline as the axis. + Comparing the tunnel tube to that ellipse avoids the false clashes caused by + intersecting two enclosing boxes. + """ + controls = [tuple(value / 1.0e6 for value in point) + for point in edge["control_points_um"]] + axis = [tuple(value / 1.0e6 for value in point) + for point in edge.get("polyline_um", [])] + if len(axis) < 2: + axis = [_bezier(controls, index / 64.0) for index in range(65)] + edge_radius = (edge["bore_radius_um"] + + edge["irregularity_amplitude_um"]) / 1.0e6 + edge_height = (edge["bore_height_um"] + + edge["irregularity_amplitude_um"]) / 1.0e6 + passage_segments = passage.get("_audit_segments") or tuple(_segments(passage, scale)) + minimum_clearance = math.inf + for a, b, _, passage_radius in passage_segments: + length = math.dist(a[:3], b[:3]) + steps = max(1, math.ceil(length / 0.05)) + for step in range(steps + 1): + t = step / steps + point = tuple(a[i] + (b[i] - a[i]) * t for i in range(3)) + if _inside_chamber(point, chamber, scale): + continue + tunnel_radius = (a[3] + (b[3] - a[3]) * t + + INFLUENCE_REACH_M) + for start, end in zip(axis, axis[1:]): + delta = tuple(end[i] - start[i] for i in range(3)) + length_squared = sum(value * value for value in delta) + projection = (sum((point[i] - start[i]) * delta[i] + for i in range(3)) / length_squared + if length_squared else 0.0) + projection = min(1.0, max(0.0, projection)) + center = tuple(start[i] + projection * delta[i] for i in range(3)) + radial = math.hypot(point[0] - center[0], point[1] - center[1]) + vertical = abs(point[2] - center[2]) + clearance = (_distance_to_ellipse(radial, vertical, + edge_radius, edge_height) + - tunnel_radius) + minimum_clearance = min(minimum_clearance, clearance) + return minimum_clearance + + +def _test_bounds(bounds, strate_index: int, passages, scale: float, + *, allow_chamber: bool = True): + for passage in passages: + if strate_index not in (passage["upper_strate_index"], passage["lower_strate_index"]): + continue + chamber_key = ("upper_landing_chamber" if strate_index == passage["upper_strate_index"] + else "lower_landing_chamber") + chamber = passage[chamber_key] + segments = passage.get("_audit_segments") + if segments is None: + segments = tuple(_segments(passage, scale)) + for a, b, along, radius in segments: + expanded = [(a[i] - radius, b[i] + radius) if a[i] <= b[i] + else (b[i] - radius, a[i] + radius) for i in range(3)] + if any(expanded[i][1] < bounds[i][0] or expanded[i][0] > bounds[i][1] + for i in range(3)): + continue + overlap = _overlap_interval(a, b, bounds, radius) + if overlap is None: + continue + first, last, closest = overlap + if allow_chamber: + points = [tuple(a[i] + (b[i] - a[i]) * t for i in range(3)) + for t in (first, closest, last)] + if all(_inside_chamber(point, chamber, scale) for point in points): + continue + point = tuple(a[i] + (b[i] - a[i]) * closest for i in range(3)) + return passage["stable_id"], along + closest * math.dist(a[:3], b[:3]), point + return None + + +def audit_layout(layout: dict[str, Any]) -> dict[str, Any]: + scale = layout["core_voxel_size_um"] / (256.0 * 1.0e6) + passages = [dict(row, _audit_segments=tuple(_segments(row, scale))) + for row in layout.get("passages", [])] + primitives = [*layout.get("cavern_rooms", []), *layout.get("cavern_edges", [])] + checked = conflicts = chamber_joints = bounds_candidates = refined_false_positives = 0 + first_conflict = None + for primitive in primitives: + bounds = _primitive_bounds(primitive) + checked += 1 + primitive_conflicted = False + for passage in passages: + found = _test_bounds(bounds, primitive["strate_index"], [passage], scale) + if not found: + continue + bounds_candidates += 1 + passage_id, station, point = found + if "control_points_um" in primitive: + side = ("upper_landing_chamber" + if primitive["strate_index"] == passage["upper_strate_index"] + else "lower_landing_chamber") + clearance = _actual_edge_clearance(passage, primitive, scale, + passage[side]) + if clearance >= 0.0: + refined_false_positives += 1 + continue + primitive_conflicted = True + conflicts += 1 + if first_conflict is None: + first_conflict = {"primitive": primitive["stable_id"], + "passage": passage_id, "station_m": round(station, 3), + "center_m": [round(value, 3) for value in point]} + break + if not primitive_conflicted: + # Count the edge's named landing association as a confirmed joint. + anchor = primitive.get("start_anchor_stable_id", "") + if anchor.endswith(("/upper-landing-chamber", "/lower-landing-chamber")): + chamber_joints += 1 + + negative_controls = 0 + long_passage = max(passages, key=lambda row: len(row["control_points"]), default=None) + if long_passage: + controls = [_point(row, scale) for row in long_passage["control_points"]] + index = min(max(1, len(controls) // 2), len(controls) - 2) + center = controls[index] + bounds = tuple((center[axis] - 0.25, center[axis] + 0.25) for axis in range(3)) + # Select the side whose chamber is farther from this forced tunnel overlap. + strate = long_passage["lower_strate_index"] + if _test_bounds(bounds, strate, [long_passage], scale, allow_chamber=False): + negative_controls = 1 + return {"primitives_checked": checked, "bounds_candidates": bounds_candidates, + "refined_edge_false_positives": refined_false_positives, + "outside_chamber_conflicts": conflicts, + "chamber_joints": chamber_joints, "negative_controls": negative_controls, + "negative_controls_expected": 1, "first_conflict": first_conflict, + "passed": conflicts == 0 and negative_controls == 1} diff --git a/Standalone/Research/WorldAssemblerM3/Linux151/REPORT.md b/Standalone/Research/WorldAssemblerM3/Linux151/REPORT.md new file mode 100644 index 0000000..74bb059 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux151/REPORT.md @@ -0,0 +1,39 @@ +# L151 — One tunnel for two strata + +| Landing chamber | Before (L150) | After (L151) | +|---|---|---| +| passage/0-1/0 · upper | ![L150 upper landing, passage 0-1](captures/before-l150-0-1-0-upper-landing.jpg) | ![L151 upper landing, passage 0-1](captures/after-l151-0-1-0-upper-landing.jpg) | +| passage/0-1/0 · lower | ![L150 lower landing, passage 0-1](captures/before-l150-0-1-0-lower-landing.jpg) | ![L151 lower landing, passage 0-1](captures/after-l151-0-1-0-lower-landing.jpg) | +| passage/1-2/0 · upper | ![L150 upper landing, passage 1-2](captures/before-l150-1-2-0-upper-landing.jpg) | ![L151 upper landing, passage 1-2](captures/after-l151-1-2-0-upper-landing.jpg) | +| passage/1-2/0 · lower | ![L150 lower landing, passage 1-2](captures/before-l150-1-2-0-lower-landing.jpg) | ![L151 lower landing, passage 1-2](captures/after-l151-1-2-0-lower-landing.jpg) | +| passage/2-3/0 · upper | ![L150 upper landing, passage 2-3](captures/before-l150-2-3-0-upper-landing.jpg) | ![L151 upper landing, passage 2-3](captures/after-l151-2-3-0-upper-landing.jpg) | +| passage/2-3/0 · lower | ![L150 lower landing, passage 2-3](captures/before-l150-2-3-0-lower-landing.jpg) | ![L151 lower landing, passage 2-3](captures/after-l151-2-3-0-lower-landing.jpg) | + +Seed 0 camera poses are matched at eye height. All 12 images passed the capture quality check. The L150 build stamp is `c6b8a100355e271ddc40eb5973569d2ff4e2ba8d9c628c72f8e0af2493f0609f`; L151 is `1ca3b84f6d35f7f355ec0f72dabd3b87a455ea02e5789fea611916962e5db8e5`. The [capture index](captures/INDEX.md) and [manifest](captures/manifest.json) record the poses and files. + +## Result + +**L151 is not promotion-ready.** The resolved layout now records one passage spline with an upper and lower landing chamber, and the evaluator version is 238. The best tested floor blend still leaves two walk segments above the 15° limit: 19.077° on passage 0-1 and 19.033° on passage 2-3. The three-variant stop rule was reached; I stopped without a fourth geometry attempt. No owner baselines were promoted. + +## What changed + +- Added serialized and validated landing chamber records with floor center, floor gradient, radius, and 4.5 m clear height. The shared passage field composes each chamber with the tunnel shell and floor. +- Cave route endpoints now bind to the passage's landing chamber anchors. The passage floor blends to chamber floors over the tested 12 m transition. +- Extended passage audits for chamber capsule clearance, floor and wall shell, and blocked-world negative controls. Added a geometric cave-primitive envelope audit that permits the named chamber joints and refines conservative edge-box candidates against the swept bore. +- Retained the L135 mouth profile, L147 interior-air gate, and L148 envelope allowance. + +## Measurements and checks + +- **Focused owner-world run:** one seed-2 W8 run, source stamp above. Three interfaces were audited. Passage and chamber interiors together had 0 solid samples across 319,579 capsule samples; all six chambers had 0 solid samples. Passage floor, wall, and interior shell violations were all zero. Naturalness, mouth profile, ripple, and all three layer route audits passed. Tunnel-network, vertical-shaft, and blocked-passage negative controls were detected (1/1, 1/1, and 3/3). The combined passage summary failed because only one of three interfaces met walkability. +- **Chamber envelope audit:** 549 primitives checked; 2 conservative bounds candidates were refined as false positives; 0 conflicts remained outside chambers; 3 named chamber joints and the negative control passed (1/1). Result: [JSON](../../../../Saved/Linux/l151/passage-envelope-audit.json). +- **Blend attempts:** 12 m retained two grades at 19.077° and 19.033°. Extending the blend to 24 m produced 436 solid interior capsule samples and one wall violation, as well as grades of 15.282° and 17.406° on passage 0-1. Restricting the external blend to 0 m kept interior and shell checks clear but left a 22.226° grade on passage 0-1. The 12 m variant is the final source state. +- **Build and snapshots:** Clang 19 build passed for stamp `1ca3b84f…`; the explicit seed-0 resolved-layout snapshot round trip passed. Python capture and envelope audit scripts ran successfully. `git diff --check` passed for the tracked diff; the report's whitespace and image references were checked separately. +- **Seams:** the saved seed-2 log has no `[PassageTileSeamAudit]` result, so seam status is unverified. + +## Not run + +The three-failure stop was reached before the fast tier, the 16-seed Tunnel Network and Vertical Shafts sweeps, Clang/GCC W1/W8/W12, and L80 timing for all four layers. No timing claim is made. No owner-world baseline files were updated because the composed walkability gate failed. The only modified `Source/` file is `Standalone/Source/StreamBenchmarkPipeline.h`; no Unreal `Source/` files changed. + +## Remaining risk + +The tunnel-to-chamber floor transition still creates local grades above 15° in two interfaces. The envelope audit and geometry evidence cover seed 2 only; multi-seed composed behavior, seams, compiler/worker determinism, the fast tier, and L80 performance remain unverified. diff --git a/Standalone/Research/WorldAssemblerM3/Linux151/RunL151Captures.py b/Standalone/Research/WorldAssemblerM3/Linux151/RunL151Captures.py new file mode 100644 index 0000000..1f41c85 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux151/RunL151Captures.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python3 +"""Capture matched eye-height views from both ends of every owner tunnel.""" +from __future__ import annotations + +import json +import math +import os +from pathlib import Path +import re +import subprocess +import sys + + +ROOT = Path(__file__).resolve().parents[4] +DATA = ROOT / "Standalone/data/owner_world.json" +OUT = ROOT / "Saved/Linux/l151/captures" +LAYOUT = OUT / "after-resolved-layout.json" +ARTIFACTS = ROOT / "Standalone/Research/WorldAssemblerM3/Linux151/captures" +LIMIT = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--"] +VERSIONS = { + "before-l150": { + "headless": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-l150-head-clang", + "renderer": ROOT / "Standalone/bin/linux/VoxelForgeOffscreenCapture-l150-head-clang", + "stamp": "c6b8a100355e271ddc40eb5973569d2ff4e2ba8d9c628c72f8e0af2493f0609f", + }, + "after-l151": { + "headless": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-l151-clang", + "renderer": ROOT / "Standalone/bin/linux/VoxelForgeOffscreenCapture-l151-clang", + "stamp": "1ca3b84f6d35f7f355ec0f72dabd3b87a455ea02e5789fea611916962e5db8e5", + }, +} +STAMP_RE = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=([0-9a-f]{64})$", re.M) + + +def fmt(values) -> str: + return ",".join(f"{value:.6f}" for value in values) + + +def angles(camera, target) -> tuple[float, float]: + dx, dy, dz = (target[index] - camera[index] for index in range(3)) + return math.degrees(math.atan2(dx, dy)), math.degrees( + math.atan2(dz, math.hypot(dx, dy))) + + +def point_q(layout: dict, values) -> tuple[float, float, float]: + scale = layout["core_voxel_size_um"] / (256.0 * 1_000_000.0) + return tuple(value * scale for value in values) + + +def chamber_views(layout: dict) -> list[dict]: + result = [] + for passage in layout["passages"]: + controls = passage["control_points"] + for side, endpoint, neighbor in ( + ("upper", controls[0], controls[1]), + ("lower", controls[-1], controls[-2])): + chamber = passage[f"{side}_landing_chamber"] + center = point_q(layout, chamber["floor_center_q"]) + a = point_q(layout, neighbor["position_q"]) + b = point_q(layout, endpoint["position_q"]) + dx, dy = a[0] - b[0], a[1] - b[1] + length = math.hypot(dx, dy) + if length <= 1.0e-9: + raise ValueError(f"degenerate {side} tunnel tangent: {passage['stable_id']}") + dx, dy = dx / length, dy / length + eye_z = center[2] + 1.45 + distance = 9.0 + target = (center[0] + dx * distance, center[1] + dy * distance, + eye_z + (chamber["floor_gradient_q16"][0] / 65536.0 * dx + + chamber["floor_gradient_q16"][1] / 65536.0 * dy) + * distance) + camera = (center[0], center[1], eye_z) + slug = passage["stable_id"].replace("passage/", "").replace("/", "-") + result.append({"name": f"{slug}-{side}-landing", "passage": passage["stable_id"], + "side": side, "camera": camera, "target": target, + "focus": (center[0], center[1], eye_z, 18.0)}) + return result + + +def stamped_run(name: str, command: list[object], log: Path, expected_stamp: str, + env: dict[str, str]) -> str: + binary = Path(str(command[0])) + probe = subprocess.run([*LIMIT, "timeout", "15s", str(binary), "--print-source-stamp"], + cwd=ROOT, env=env, text=True, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, check=False, timeout=20) + match = STAMP_RE.search(probe.stdout or "") + if probe.returncode or not match or match.group(1) != expected_stamp: + raise RuntimeError(f"{name}: source stamp mismatch for {binary}") + log.parent.mkdir(parents=True, exist_ok=True) + with log.open("w", encoding="utf-8") as stream: + result = subprocess.run([*LIMIT, "timeout", "1740s", *map(str, command)], + cwd=ROOT, env=env, stdout=stream, stderr=subprocess.STDOUT, + timeout=1745, check=False) + output = log.read_text(encoding="utf-8", errors="replace") + emitted = STAMP_RE.search(output) + if result.returncode or not emitted or emitted.group(1) != expected_stamp: + raise RuntimeError(f"{name}: export or render failed; see {log}") + return output + + +def resolve_layout(env: dict[str, str]) -> dict: + config = VERSIONS["after-l151"] + command = [config["headless"], DATA, "--world-assembler-prototype", + "--stream-benchmark", "--stream-pipeline", "--workers=12", + "--clip-radius=6", "--max-clip-level=0", "--max-level=0", + "--force-generate", "--seed=0", "--recipe=surface-world", "--resolved-layout-only", + f"--resolved-layout-output={LAYOUT}"] + stamped_run("after-l151-resolve-layout", list(map(str, command)), + OUT / "after-l151-resolved-layout.log", config["stamp"], env) + layout = json.loads(LAYOUT.read_text(encoding="utf-8")) + if len(layout.get("passages", [])) != 3: + raise RuntimeError("owner layout did not resolve the expected three tunnels") + return layout + + +def main() -> int: + if not DATA.is_file(): + raise SystemExit("missing owner fixture") + OUT.mkdir(parents=True, exist_ok=True) + ARTIFACTS.mkdir(parents=True, exist_ok=True) + env = os.environ.copy() + env["LP_NUM_THREADS"] = "12" + env["XDG_CACHE_HOME"] = str(OUT / "xdg-cache") + layout = resolve_layout(env) + views = chamber_views(layout) + quality_tool = ROOT / "Standalone/Tools/verify/capture_quality.py" + manifest = [] + failures = [] + for version, config in VERSIONS.items(): + for view in views: + camera, target = view["camera"], view["target"] + yaw, pitch = angles(camera, target) + mesh_dir = OUT / "meshes" / version + mesh_dir.mkdir(parents=True, exist_ok=True) + mesh = mesh_dir / f"{view['name']}.vfmesh" + capture_layout = mesh_dir / f"{view['name']}-layout.json" + export = [config["headless"], DATA, "--world-assembler-prototype", + "--stream-benchmark", "--stream-pipeline", "--workers=12", + "--clip-radius=6", "--max-clip-level=0", "--max-level=0", + "--force-generate", "--seed=0", "--recipe=surface-world", + "--render-distance-chunks=0", + f"--camera={fmt((*camera, yaw, pitch))}", + f"--resolved-layout-output={capture_layout}", f"--mesh-export={mesh}"] + stamped_run(f"{version}-{view['name']}-export", list(map(str, export)), + OUT / f"{version}-{view['name']}-export.log", config["stamp"], env) + if not mesh.is_file() or mesh.stat().st_size == 0: + raise RuntimeError(f"{version}-{view['name']}: empty mesh export") + png = OUT / f"{version}-{view['name']}.png" + jpg = ARTIFACTS / f"{version}-{view['name']}.jpg" + render = [config["renderer"], mesh, png, "--size=1280x720", + "--fov-deg=78.0", f"--camera-m={fmt(camera)}", + f"--yaw-deg={yaw:.6f}", f"--pitch-deg={pitch:.6f}", + "--tile-skirts=off", "--shade=readable", "--headlamp=on", + "--height-tint=on", "--height-center-m=-950", "--height-range-m=60", + "--tile-edges=off", f"--focus-region={fmt(view['focus'])}"] + stamped_run(f"{version}-{view['name']}-render", list(map(str, render)), + OUT / f"{version}-{view['name']}-render.log", config["stamp"], env) + subprocess.run([*LIMIT, "timeout", "120s", "ffmpeg", "-nostdin", + "-hide_banner", "-loglevel", "error", "-y", "-i", png, + "-frames:v", "1", "-q:v", "4", jpg], cwd=ROOT, env=env, + check=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, + timeout=125) + check = subprocess.run([*LIMIT, "timeout", "120s", sys.executable, + quality_tool, jpg], cwd=ROOT, env=env, text=True, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + check=False, timeout=125) + quality_ok = check.returncode == 0 and "status=PASS" in check.stdout + if not quality_ok: + failures.append(f"{version}-{view['name']}") + quality_line = (check.stdout.strip().splitlines()[-1] + if check.stdout.strip() else f"quality exit={check.returncode}") + manifest.append({"version": version, "view": view["name"], + "passage": view["passage"], "side": view["side"], + "file": str(jpg.relative_to(ROOT)), "camera_m": camera, + "target_m": target, "yaw_deg": yaw, "pitch_deg": pitch, + "source_stamp": config["stamp"], + "pose_layout_identity": layout.get("stable_identity"), + "quality_status": "PASS" if quality_ok else "FAIL", + "quality": quality_line}) + print(f"[L151Capture] version={version} view={view['name']} " + f"quality={'PASS' if quality_ok else 'FAIL'} " + f"file={jpg.relative_to(ROOT)}", flush=True) + (ARTIFACTS / "manifest.json").write_text( + json.dumps(manifest, indent=2) + "\n", encoding="utf-8") + with (ARTIFACTS / "INDEX.md").open("w", encoding="utf-8") as index: + index.write("# L151 matched owner tunnel landing captures\n\n" + "Seed 0 camera poses resolved from the L151 owner layout and reused for both " + "versions. Each view is at eye height at one of the three tunnel landing " + "chambers. Before uses the L150 head build; after uses L151 source stamp " + f"`{VERSIONS['after-l151']['stamp']}`.\n\n") + for view in views: + index.write(f"## {view['passage']} / {view['side']} landing\n\n" + f"- Before: [image](before-l150-{view['name']}.jpg)\n" + f"- After: [image](after-l151-{view['name']}.jpg)\n\n") + index.write(f"Capture quality failures: {len(failures)}/{len(manifest)}.\n") + print(f"[L151CaptureSummary] chamber_views={len(views)} " + f"images={len(manifest)} quality_failures={len(failures)}", flush=True) + return 0 if not failures else 1 + + +if __name__ == "__main__": + try: + raise SystemExit(main()) + except Exception as error: + print(f"[L151CaptureFailure] {error}", file=sys.stderr, flush=True) + raise SystemExit(1) diff --git a/Standalone/Research/WorldAssemblerM3/Linux151/captures/INDEX.md b/Standalone/Research/WorldAssemblerM3/Linux151/captures/INDEX.md new file mode 100644 index 0000000..40f36c3 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux151/captures/INDEX.md @@ -0,0 +1,35 @@ +# L151 matched owner tunnel landing captures + +Seed 0 camera poses resolved from the L151 owner layout and reused for both versions. Each view is at eye height at one of the three tunnel landing chambers. Before uses the L150 head build; after uses L151 source stamp `1ca3b84f6d35f7f355ec0f72dabd3b87a455ea02e5789fea611916962e5db8e5`. + +## passage/0-1/0 / upper landing + +- Before: [image](before-l150-0-1-0-upper-landing.jpg) +- After: [image](after-l151-0-1-0-upper-landing.jpg) + +## passage/0-1/0 / lower landing + +- Before: [image](before-l150-0-1-0-lower-landing.jpg) +- After: [image](after-l151-0-1-0-lower-landing.jpg) + +## passage/1-2/0 / upper landing + +- Before: [image](before-l150-1-2-0-upper-landing.jpg) +- After: [image](after-l151-1-2-0-upper-landing.jpg) + +## passage/1-2/0 / lower landing + +- Before: [image](before-l150-1-2-0-lower-landing.jpg) +- After: [image](after-l151-1-2-0-lower-landing.jpg) + +## passage/2-3/0 / upper landing + +- Before: [image](before-l150-2-3-0-upper-landing.jpg) +- After: [image](after-l151-2-3-0-upper-landing.jpg) + +## passage/2-3/0 / lower landing + +- Before: [image](before-l150-2-3-0-lower-landing.jpg) +- After: [image](after-l151-2-3-0-lower-landing.jpg) + +Capture quality failures: 0/12. diff --git a/Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-0-1-0-lower-landing.jpg b/Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-0-1-0-lower-landing.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c4013c0879bc8f76943dff3e61c51c0eb0754ea0 GIT binary patch literal 17604 zcmeHu30zah*7!|WOGSu)1z8KNrr?GM${tz*7Eyw-gYX0;0wS_20_Lfe7+K0wHf2eJ zK-i0bC@xsp6cA)DAn*X$L3WV+n|qTWDD7))f4~3xe*bT7?ks1{oH=*q%$YMwmVaGd zg8221d)X<;@03^GDZg7DT5f^9gSff5KjZ%FbHwp(;C=5lZrq5$V1UodyKy5wKL)c& zU=sjf1Ox;H1%-vb_(DufTw3~@=y$NF51T8m18vqeN00U`4(|lwN zIu%8MD)&OA;ZMaLlO|Wy-7bW&b}FpyPP<$7C?X(nycU}&-qHW) zUil6+?|_u?mhBx$l+h{cE#=C`lY&Pl+eA->#~Sb%gh!pUwj67`>(*ed6DwNm$JZ#Q zfAZ?+Y^;sWf`QJS!gIbAexHxtD=~B8i=DRl;htN$*}2%;qd8G0&rYb?1gD%7Dy}U& zaQU{sEOmF|t>hn1y3x5I9x3Tgsx*i|Xc~5J8CIzbs)6>Bsh~Y6kQ5pYA>dI-AmqW4 zq(HE)BYg}-W6=yC4-y~=1P%K!?jMF!^K+6RyKi|8ye6>+J(t?5g4Z$|9hM?z91Wo1 z%VZ884Y(*Za;9Q;U$M67?ec;! zZIiD&LJfts?IclpJ6>sWQZlvEP^hy&v?(<0Zsiu=`7!l2(ee!)NiPdT z`)q<9Wrgpq?P(7hDG;sQelk2xX~D(sU0c-o?yK!eqciQ-`rvN7-91tuP#ySlo1~^h z%a{@j+Nf^Y=^TBxLBh(%cWxx-B_VvkSu|K;As~LTARFRA(=Ktp;wi^bDBQ^us4Bb&?>^o~r*w5R zB^p{^7<<@du48e2SILx}IT&YhV~=Ak%QvJJ&b3wXc86P>bK!HjdbfOs2I!p;-tMcr zYkO^i-WG_SCxTI0Ss>8vuQXm_UT6?A*ETqo7w%SQz!h*eCq_oU0z3WtT>@eVAz6kHueFsz0| z5v&}JEjSMN0b&5*7?Oa42NDDk5GGJs3|`en6vNF;gC~L?=SV+N zFPV4bK5iwXE$m3zokDs}gCNl55QL>cBsg5xNqq<<;mZ41{rV@o=JPbuk=b^wT6!7H;k}UN8`C1RXH5<>-u$SG% zDo02(x3bz;xIdUq+wbLR^u3r#j#TkMoJbl)3Q&VzK$AjF;bByaq}O<0B(6zAOhwOV zG>|~j2)}Fb{&b2C)nch_cS$Oh(cO3>AL8LXcWdGsRor@hu01fN*DJ?UqrT@sSy@hq z&I2Hw`6aZ=8CjqeOQ`hbQ)~x#C?xtymCS}?H6RtqU@(rQGD2n(GgS&eAfcg4JUnEo z{3(>xu;8~@r%)MQ6d9BIZ9iaC`b#r@;Lk0`bEezjHc^@NFzEb#^O2C6-+Rn5NWV&f zIzjV+_iu8%unhK!80)6SMxQ)sbm42)-b7==!LTxqvYg<#B06@Uf}W!C6%HQK{G2}u zfia50VZbn6Zx90@tYLtTA)t_u0~z%I20|nFL17?HX97

3W(4_UjBk0=iMuSlh8t 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 F{{gfp0PO$( literal 0 HcmV?d00001 diff --git a/Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-1-2-0-upper-landing.jpg b/Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-1-2-0-upper-landing.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5763300081cfd1688dae1dba46466417e012028d GIT binary patch literal 18580 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 literal 0 HcmV?d00001 diff --git a/Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-2-3-0-lower-landing.jpg b/Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-2-3-0-lower-landing.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7765a7fed9134de156c5468f9fd48e6743463c26 GIT binary patch literal 20302 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 literal 0 HcmV?d00001 diff --git a/Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-2-3-0-upper-landing.jpg b/Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-2-3-0-upper-landing.jpg new file mode 100644 index 0000000000000000000000000000000000000000..86ecb2b9458922cf081faecff3546979095bcb74 GIT binary patch literal 22040 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 literal 0 HcmV?d00001 diff --git a/Standalone/Research/WorldAssemblerM3/Linux151/captures/before-l150-0-1-0-lower-landing.jpg b/Standalone/Research/WorldAssemblerM3/Linux151/captures/before-l150-0-1-0-lower-landing.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d7b41a1a8b13e4e4a7b01f43e7dfcf140d093be7 GIT binary patch literal 15232 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 literal 0 HcmV?d00001 diff --git a/Standalone/Research/WorldAssemblerM3/Linux151/captures/before-l150-2-3-0-upper-landing.jpg b/Standalone/Research/WorldAssemblerM3/Linux151/captures/before-l150-2-3-0-upper-landing.jpg new file mode 100644 index 0000000000000000000000000000000000000000..95ef1b7d694775af24bf0c6c835df6fec9544df1 GIT binary patch literal 16652 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.0,coverage>=0.001) status=PASS" + }, + { + "version": "before-l150", + "view": "0-1-0-lower-landing", + "passage": "passage/0-1/0", + "side": "lower", + "file": "Standalone/Research/WorldAssemblerM3/Linux151/captures/before-l150-0-1-0-lower-landing.jpg", + "camera_m": [ + 25.8369140625, + 3.19921875, + -736.6798828125 + ], + "target_m": [ + 17.97380590951016, + -1.1793118453464206, + -734.970169262755 + ], + "yaw_deg": -119.11101808688004, + "pitch_deg": 10.756207017349817, + "source_stamp": "c6b8a100355e271ddc40eb5973569d2ff4e2ba8d9c628c72f8e0af2493f0609f", + "pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux151/captures/before-l150-0-1-0-lower-landing.jpg pixels=921600 mean_luma=106.418 stddev_luma=2.881 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + }, + { + "version": "before-l150", + "view": "1-2-0-upper-landing", + "passage": "passage/1-2/0", + "side": "upper", + "file": "Standalone/Research/WorldAssemblerM3/Linux151/captures/before-l150-1-2-0-upper-landing.jpg", + "camera_m": [ + 19.166015625, + -14.2001953125, + -731.8810546875 + ], + "target_m": [ + 10.35825176774404, + -16.050410390488395, + -733.216132282697 + ], + "yaw_deg": -101.86342177874246, + "pitch_deg": -8.437834550031965, + "source_stamp": "c6b8a100355e271ddc40eb5973569d2ff4e2ba8d9c628c72f8e0af2493f0609f", + "pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux151/captures/before-l150-1-2-0-upper-landing.jpg pixels=921600 mean_luma=108.420 stddev_luma=2.873 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + }, + { + "version": "before-l150", + "view": "1-2-0-lower-landing", + "passage": "passage/1-2/0", + "side": "lower", + "file": "Standalone/Research/WorldAssemblerM3/Linux151/captures/before-l150-1-2-0-lower-landing.jpg", + "camera_m": [ + 19.166015625, + -14.2001953125, + -753.05390625 + ], + "target_m": [ + 10.48848361510052, + -16.587754362485967, + -751.7193175939389 + ], + "yaw_deg": -105.38384894032598, + "pitch_deg": 8.434788863627224, + "source_stamp": "c6b8a100355e271ddc40eb5973569d2ff4e2ba8d9c628c72f8e0af2493f0609f", + "pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux151/captures/before-l150-1-2-0-lower-landing.jpg pixels=921600 mean_luma=113.918 stddev_luma=10.489 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + }, + { + "version": "before-l150", + "view": "2-3-0-upper-landing", + "passage": "passage/2-3/0", + "side": "upper", + "file": "Standalone/Research/WorldAssemblerM3/Linux151/captures/before-l150-2-3-0-upper-landing.jpg", + "camera_m": [ + -20.0625, + -5.068359375, + -812.075390625 + ], + "target_m": [ + -13.604954164712868, + -11.337338700629157, + -813.6662645746064 + ], + "yaw_deg": 134.15112141809087, + "pitch_deg": -10.024269857937275, + "source_stamp": "c6b8a100355e271ddc40eb5973569d2ff4e2ba8d9c628c72f8e0af2493f0609f", + "pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux151/captures/before-l150-2-3-0-upper-landing.jpg pixels=921600 mean_luma=108.739 stddev_luma=2.947 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + }, + { + "version": "before-l150", + "view": "2-3-0-lower-landing", + "passage": "passage/2-3/0", + "side": "lower", + "file": "Standalone/Research/WorldAssemblerM3/Linux151/captures/before-l150-2-3-0-lower-landing.jpg", + "camera_m": [ + -20.0625, + -5.068359375, + -900.0333984375 + ], + "target_m": [ + -14.865342978484083, + -12.416118141706198, + -898.4427583280197 + ], + "yaw_deg": 144.72777711381306, + "pitch_deg": 10.022826283920647, + "source_stamp": "c6b8a100355e271ddc40eb5973569d2ff4e2ba8d9c628c72f8e0af2493f0609f", + "pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux151/captures/before-l150-2-3-0-lower-landing.jpg pixels=921600 mean_luma=112.176 stddev_luma=9.871 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + }, + { + "version": "after-l151", + "view": "0-1-0-upper-landing", + "passage": "passage/0-1/0", + "side": "upper", + "file": "Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-0-1-0-upper-landing.jpg", + "camera_m": [ + 37.0, + -5.25, + -372.3810546875 + ], + "target_m": [ + 28.40474992442715, + -7.918646874047773, + -372.325197471535 + ], + "yaw_deg": -107.24848481126237, + "pitch_deg": 0.3555935154874107, + "source_stamp": "1ca3b84f6d35f7f355ec0f72dabd3b87a455ea02e5789fea611916962e5db8e5", + "pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-0-1-0-upper-landing.jpg pixels=921600 mean_luma=118.392 stddev_luma=15.921 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + }, + { + "version": "after-l151", + "view": "0-1-0-lower-landing", + "passage": "passage/0-1/0", + "side": "lower", + "file": "Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-0-1-0-lower-landing.jpg", + "camera_m": [ + 25.8369140625, + 3.19921875, + -736.6798828125 + ], + "target_m": [ + 17.97380590951016, + -1.1793118453464206, + -734.970169262755 + ], + "yaw_deg": -119.11101808688004, + "pitch_deg": 10.756207017349817, + "source_stamp": "1ca3b84f6d35f7f355ec0f72dabd3b87a455ea02e5789fea611916962e5db8e5", + "pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-0-1-0-lower-landing.jpg pixels=921600 mean_luma=111.426 stddev_luma=12.411 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + }, + { + "version": "after-l151", + "view": "1-2-0-upper-landing", + "passage": "passage/1-2/0", + "side": "upper", + "file": "Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-1-2-0-upper-landing.jpg", + "camera_m": [ + 19.166015625, + -14.2001953125, + -731.8810546875 + ], + "target_m": [ + 10.35825176774404, + -16.050410390488395, + -733.216132282697 + ], + "yaw_deg": -101.86342177874246, + "pitch_deg": -8.437834550031965, + "source_stamp": "1ca3b84f6d35f7f355ec0f72dabd3b87a455ea02e5789fea611916962e5db8e5", + "pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-1-2-0-upper-landing.jpg pixels=921600 mean_luma=117.134 stddev_luma=15.664 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + }, + { + "version": "after-l151", + "view": "1-2-0-lower-landing", + "passage": "passage/1-2/0", + "side": "lower", + "file": "Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-1-2-0-lower-landing.jpg", + "camera_m": [ + 19.166015625, + -14.2001953125, + -753.05390625 + ], + "target_m": [ + 10.48848361510052, + -16.587754362485967, + -751.7193175939389 + ], + "yaw_deg": -105.38384894032598, + "pitch_deg": 8.434788863627224, + "source_stamp": "1ca3b84f6d35f7f355ec0f72dabd3b87a455ea02e5789fea611916962e5db8e5", + "pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-1-2-0-lower-landing.jpg pixels=921600 mean_luma=119.710 stddev_luma=14.975 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + }, + { + "version": "after-l151", + "view": "2-3-0-upper-landing", + "passage": "passage/2-3/0", + "side": "upper", + "file": "Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-2-3-0-upper-landing.jpg", + "camera_m": [ + -20.0625, + -5.068359375, + -812.075390625 + ], + "target_m": [ + -13.604954164712868, + -11.337338700629157, + -813.6662645746064 + ], + "yaw_deg": 134.15112141809087, + "pitch_deg": -10.024269857937275, + "source_stamp": "1ca3b84f6d35f7f355ec0f72dabd3b87a455ea02e5789fea611916962e5db8e5", + "pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-2-3-0-upper-landing.jpg pixels=921600 mean_luma=118.198 stddev_luma=15.882 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + }, + { + "version": "after-l151", + "view": "2-3-0-lower-landing", + "passage": "passage/2-3/0", + "side": "lower", + "file": "Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-2-3-0-lower-landing.jpg", + "camera_m": [ + -20.0625, + -5.068359375, + -900.0333984375 + ], + "target_m": [ + -14.865342978484083, + -12.416118141706198, + -898.4427583280197 + ], + "yaw_deg": 144.72777711381306, + "pitch_deg": 10.022826283920647, + "source_stamp": "1ca3b84f6d35f7f355ec0f72dabd3b87a455ea02e5789fea611916962e5db8e5", + "pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003", + "quality_status": "PASS", + "quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-2-3-0-lower-landing.jpg pixels=921600 mean_luma=121.734 stddev_luma=16.193 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS" + } +] diff --git a/Standalone/Source/StreamBenchmarkPipeline.h b/Standalone/Source/StreamBenchmarkPipeline.h index 48b5a30..ae028b7 100644 --- a/Standalone/Source/StreamBenchmarkPipeline.h +++ b/Standalone/Source/StreamBenchmarkPipeline.h @@ -3090,7 +3090,16 @@ namespace StandaloneStreamBenchmark const double horizontal=std::hypot(dx,dy)*coreMetres; const double vertical=std::abs(dz)*coreMetres; if(horizontal<=1.0e-6||vertical>horizontal*0.2679491924311227) + { + if(audit.gradeFailures<8) + std::printf("[ResolvedWalkGradeFailure] segment=%zu a_xyz_m=(%.3f,%.3f,%.3f) b_xyz_m=(%.3f,%.3f,%.3f) horizontal_m=%.6f vertical_m=%.6f grade_deg=%.3f\n", + segment-1,a[0]*coreMetres,a[1]*coreMetres, + a[2]*coreMetres,b[0]*coreMetres,b[1]*coreMetres, + b[2]*coreMetres,horizontal,vertical, + std::atan2(vertical,horizontal) + *180.0/3.14159265358979323846); ++audit.gradeFailures; + } const double length=std::sqrt(dx*dx+dy*dy+dz*dz)*coreMetres; const std::uint64_t intervals=std::max(1, static_cast(std::ceil(length/0.5))); @@ -3193,7 +3202,8 @@ namespace StandaloneStreamBenchmark -qToMetres(passage.upperMouthSubvoxels[2])+mouthCenterHeight); if(upperStratum==ResolvedLayout->strates.end() ||(upperStratum->generatorKind!="FloatingIslands" - &&upperStratum->generatorKind!="TunnelNetwork")) + &&upperStratum->generatorKind!="TunnelNetwork" + &&upperStratum->generatorKind!="SurfaceWorld")) return authoredMouthDepth; std::vector> route; for(const auto& anchor:upperStratum->anchors) @@ -3246,7 +3256,8 @@ namespace StandaloneStreamBenchmark {return candidate.strateIndex==strateIndex;}); if(stratum!=ResolvedLayout->strates.end() &&(stratum->generatorKind=="TunnelNetwork" - ||stratum->generatorKind=="FloatingIslands")) + ||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]), @@ -3256,17 +3267,20 @@ namespace StandaloneStreamBenchmark const auto upper=std::find_if(ResolvedLayout->strates.begin(), ResolvedLayout->strates.end(),[&](const auto& stratum) {return stratum.strateIndex==passage.upperStrateIndex;}); - if(upper!=ResolvedLayout->strates.end() - &&upper->generatorKind=="TunnelNetwork"&&!controls.empty()) + if(upper!=ResolvedLayout->strates.end()&&!controls.empty() + &&(upper->generatorKind=="TunnelNetwork" + ||upper->generatorKind=="SurfaceWorld")) { auto route=routeForStrate(passage.upperStrateIndex); - if(route.size()>2)route.erase(route.begin(),route.end()-2); + 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); + 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) @@ -3325,6 +3339,44 @@ 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 auto blendLandingChamberFloor=[&]( + const VoxelForgeCore::ResolvedLayoutPassageV1& passage, + double alongMetres,double totalLengthMetres,double x,double y, + double 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) + { + const double cx=qToMetres(chamber.floorCenterSubvoxels[0]); + const double cy=qToMetres(chamber.floorCenterSubvoxels[1]); + const double cz=qToMetres(chamber.floorCenterSubvoxels[2]); + return cz+chamber.floorGradientQ16[0]/65536.0*(x-cx) + +chamber.floorGradientQ16[1]/65536.0*(y-cy); + }; + 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) { @@ -3354,6 +3406,16 @@ 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; @@ -3389,7 +3451,8 @@ namespace StandaloneStreamBenchmark {return stratum.strateIndex==passage.upperStrateIndex;}); if(upperStratum!=ResolvedLayout->strates.end() &&(upperStratum->generatorKind=="FloatingIslands" - ||upperStratum->generatorKind=="TunnelNetwork")) + ||upperStratum->generatorKind=="TunnelNetwork" + ||upperStratum->generatorKind=="SurfaceWorld")) { for(const auto& anchor:upperStratum->anchors) if(anchor.kind=="route_point"&&anchor.values.size()>=3) @@ -3466,16 +3529,49 @@ namespace StandaloneStreamBenchmark 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; 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,1.13); - return VoxelForgeCore::PassageSpline::BlendUpperMouthFloorAtXY( + 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; + const auto& route=allPassageSmoothRoutes[passageIndex]; + if(route.size()<2)return floor; + double closestDistanceSquared=std::numeric_limits::infinity(); + double closestAlong=0.0; + for(std::size_t index=1;index1.0e-12 + ?std::clamp(((x-a.x)*dx+(y-a.y)*dy)/lengthSquared,0.0,1.0):0.0; + const double nearestX=a.x+t*dx,nearestY=a.y+t*dy; + const double ex=x-nearestX,ey=y-nearestY; + const double distanceSquared=ex*ex+ey*ey; + if(distanceSquaredpassages[passageIndex],closestAlong, + route.back().distance,x,y,floor); }; const auto interpolatePassageRoute=[]( const std::vector& route,double distance) @@ -3627,6 +3723,26 @@ namespace StandaloneStreamBenchmark &&passageIndex!=selectedPassage)continue; const auto& passage=ResolvedLayout->passages[passageIndex]; if(passage.controlPoints.size()<2)continue; + const auto inLandingChamber=[&]( + const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& chamber) + { + if(chamber.radiusSubvoxels<=0||chamber.clearHeightSubvoxels<=0) + return false; + 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 ceilingDistance=zMetres-(floorCenterZ + +qToMetres(chamber.clearHeightSubvoxels)); + return std::max({radialDistance,floorDistance,ceilingDistance})<=0.0; + }; + 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(); @@ -4580,6 +4696,78 @@ 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; @@ -4596,6 +4784,9 @@ namespace StandaloneStreamBenchmark { 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; @@ -4782,6 +4973,27 @@ namespace StandaloneStreamBenchmark shellDistance); } } + constexpr double LandingChamberShellThicknessMetres=0.75; + for(const auto& passage:ResolvedLayout->passages) + 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 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 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) + { + ++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); + } + } + } + } return audit; }; const auto mouthInContent=[&](std::int32_t index, @@ -4947,14 +5242,74 @@ namespace StandaloneStreamBenchmark return zUm>=stratum->content.bottomMicrometres &&zUmcontent.topMicrometres; }; + const auto auditLandingChamber=[&](const VoxelForgeCore::World& field, + const VoxelForgeCore::ResolvedLayoutPassageV1& passage, + std::size_t passageIndex,bool upper) + { + 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;} + 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(chamber.floorCenterSubvoxels[0]); + const double centerY=qToMetres(chamber.floorCenterSubvoxels[1]); + const double centerZ=qToMetres(chamber.floorCenterSubvoxels[2]); + const double radius=std::min(5.75, + qToMetres(chamber.radiusSubvoxels)-1.5); + const double gx=chamber.floorGradientQ16[0]/65536.0; + const double gy=chamber.floorGradientQ16[1]/65536.0; + 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) + { + 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 double distance=radius*step/intervals; + const double x=centerX+direction[0]*distance; + const double y=centerY+direction[1]*distance; + const double floorZ=centerZ+gx*(x-centerX)+gy*(y-centerY); + chamberPath.push_back({x/coreMetres,y/coreMetres, + (floorZ+1.13)/coreMetres}); + } + const FWalkAudit part=auditPlayerPath(field,chamberPath, + 0.125,0.125,1.125,3,0.45,{},interiorPredicate); + 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 total; + }; std::uint64_t passageCount=0,passagePasses=0,blockedControls=0,naturalPasses=0; std::uint64_t passageInteriorCapsuleSamples=0,passageInteriorSolidSamples=0; std::uint64_t passageInteriorControls=0; + std::uint64_t landingChamberCount=0,landingChamberNegativeControls=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; for(std::size_t passageIndex=0;passageIndexpassages.size();++passageIndex) { @@ -4980,11 +5335,26 @@ 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=auditLandingChamber(Shape, + passage,passageIndex,true); + const FWalkAudit lowerChamberAudit=auditLandingChamber(Shape, + passage,passageIndex,false); + const bool landingChambersWalkable=upperChamberAudit.passed + &&lowerChamberAudit.passed; const bool passageInteriorAirPassed= passageAudit.passageInteriorCapsuleSamples>0 - &&passageAudit.passageInteriorSolidSamples==0; - passageInteriorCapsuleSamples+=passageAudit.passageInteriorCapsuleSamples; - passageInteriorSolidSamples+=passageAudit.passageInteriorSolidSamples; + &&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; const FPassageNaturalnessAudit naturalAudit= auditPassageNaturalness(Shape,passageIndex); const auto lowerProfileStratum=std::find_if(ResolvedLayout->strates.begin(), @@ -5027,6 +5397,11 @@ namespace StandaloneStreamBenchmark shellOpenAirSamples+=shellAudit.openAirSamples; shellWallSamples+=shellAudit.wallSamples; shellWallViolations+=shellAudit.wallViolations; + shellChamberFloorSamples+=shellAudit.chamberFloorSamples; + shellChamberFloorViolations+=shellAudit.chamberFloorViolations; + shellChamberWallSamples+=shellAudit.chamberWallSamples; + shellChamberWallViolations+=shellAudit.chamberWallViolations; + shellChamberConnectionSkips+=shellAudit.chamberConnectionSkips; shellInteriorSamples+=shellAudit.interiorSamples; shellInteriorViolations+=shellAudit.interiorViolations; const auto radialSquared=[](const std::array& p) @@ -5043,7 +5418,8 @@ namespace StandaloneStreamBenchmark rippleControlsDetected+=rippleAudit.controlDetected; floorRippleControlsDetected+=rippleAudit.floorControlDetected; floorStepRippleControlsDetected+=rippleAudit.floorStepControlDetected; - passagePasses+=passageAudit.passed&&passageInteriorAirPassed + passagePasses+=passageAudit.passed&&landingChambersWalkable + &&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", @@ -5058,7 +5434,26 @@ namespace StandaloneStreamBenchmark static_cast(passageAudit.supportOverlapSkips), static_cast(passageAudit.unsupported), static_cast(passageAudit.gradeFailures),mouthsInContent?2:0, - mouthsInRadius?2:0,passageAudit.passed?"PASS":"FAIL"); + mouthsInRadius?2:0, + passageAudit.passed&&landingChambersWalkable?"PASS":"FAIL"); + std::printf("[PassageLandingChamberAudit] id=%s side=upper capsule_samples=%llu solid_samples=%llu support_samples=%llu unsupported=%llu grade_failures=%llu 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.gradeFailures), + upperChamberAudit.passed&&upperChamberAudit.passageInteriorSolidSamples==0 + &&upperChamberAudit.passageInteriorCapsuleSamples>0?"PASS":"FAIL"); + std::printf("[PassageLandingChamberAudit] id=%s side=lower capsule_samples=%llu solid_samples=%llu support_samples=%llu unsupported=%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.gradeFailures), + lowerChamberAudit.passed&&lowerChamberAudit.passageInteriorSolidSamples==0 + &&lowerChamberAudit.passageInteriorCapsuleSamples>0?"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, @@ -5114,12 +5509,17 @@ 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 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("[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", 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), @@ -5129,6 +5529,8 @@ namespace StandaloneStreamBenchmark static_cast(shellAudit.interiorViolations), PassageShellAuditSpacingMetres, shellAudit.floorViolations==0&&shellAudit.wallViolations==0 + &&shellAudit.chamberFloorViolations==0 + &&shellAudit.chamberWallViolations==0 &&shellAudit.interiorViolations==0 ?"PASS":"FAIL"); for(std::size_t i=0;i::max(),passageIndex);}; const FWalkAudit blockedAudit=auditPlayerPath(blockedWorld,path, 0.125,0.125,0.25,3,0.45,{},passageInteriorPredicate); + const FWalkAudit upperChamberBlocked=auditLandingChamber(blockedWorld, + passage,passageIndex,true); + const FWalkAudit lowerChamberBlocked=auditLandingChamber(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; const bool passageInteriorControlDetected= blockedAudit.passageInteriorCapsuleSamples>0 &&blockedAudit.passageInteriorSolidSamples>0; @@ -5433,6 +5847,16 @@ 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), @@ -5443,21 +5867,35 @@ 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 status=%s\n", + 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", 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 &&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 open_air_shell_samples=%llu wall_samples=%llu wall_violations=%llu interior_samples=%llu interior_violations=%llu status=%s\n", + 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),