diff --git a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h index 4799b9e..82bc9e1 100644 --- a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h +++ b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h @@ -512,7 +512,8 @@ namespace VoxelForgeCore // boundary-blended host rock from widening the authored passage wall. // v238 plans one resolved passage spline with explicit landing chambers. // v239 plans their floor planes as the passage's continuous floor profile. - inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 239; + // v240 treats SurfaceWorld landings as headroom above max(planned floor,native ground). + inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 240; enum class FieldChannel : std::uint32_t { @@ -1212,8 +1213,9 @@ namespace VoxelForgeCore 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. + // The planned chamber floor passes through this point. SurfaceWorld adds + // native ground where it lies within the clear-height envelope. Gradient is + // dz/dx,dy in Q16 and continues the passage's authored h(s) grade. std::array floorCenterSubvoxels{}; std::array floorGradientQ16{}; std::int64_t radiusSubvoxels = 0; @@ -2009,6 +2011,9 @@ namespace VoxelForgeCore ShapeBuildStats buildStats() const; float densityAt(Vec3d voxelPosition) const; + // Deterministic native SurfaceWorld or receiving-route ground in Core voxels. + bool nativeSurfaceGroundHeightAt(Vec3d voxelPosition, + double& groundHeightVoxels) const; // Companion material query for the bound strate evaluator; coordinates use Core voxels. MaterialSampleV1 materialAt(Vec3d voxelPosition) const; // Measure-only carve estimate. This does not apply an edit or grant inventory. diff --git a/Standalone/Core/src/ResolvedLayoutInternal.h b/Standalone/Core/src/ResolvedLayoutInternal.h index 7ea6c8c..c80b95d 100644 --- a/Standalone/Core/src/ResolvedLayoutInternal.h +++ b/Standalone/Core/src/ResolvedLayoutInternal.h @@ -3993,6 +3993,72 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t reach/static_cast(qPerMetre)); } } + // Reserve each landing envelope inside its host band before building h(s). + // SurfaceWorld can raise its floor by at most the 4.5 m headroom allowance, + // so include that bounded rise as well as the grade reach and floor shell. + constexpr std::int64_t MaximumLandingGradeQ16=17560; + const std::int64_t landingRadius=mouthRadius+3*qPerMetre; + const std::int64_t landingClearHeight=static_cast( + std::llround(4.5*qPerMetre)); + const std::int64_t landingShell=static_cast( + std::llround(0.75*qPerMetre)); + const std::int64_t landingPlaneReach=(landingRadius + *MaximumLandingGradeQ16+65535)/65536; + const std::array upperMouthBeforeBandFit=upperMouth; + const std::array lowerMouthBeforeBandFit=lowerMouth; + const auto fitMouth=[&](std::array& mouth, + const ResolvedLayoutStratumV2& host,std::int64_t playerHeightQ, + const char* side) + { + if(!host.content.presentInCurrentField)return true; + const std::int64_t minimumFloor=contentZQ(host.content.bottomMicrometres) + +landingPlaneReach+landingShell; + const std::int64_t nativeFloorRise=host.generatorKind=="SurfaceWorld" + ?landingClearHeight:0; + const std::int64_t maximumFloor=contentZQ(host.content.topMicrometres) + -landingPlaneReach-landingShell-landingClearHeight-nativeFloorRise; + if(maximumFloor(qPerMetre)); + } + return true; + }; + if(!fitMouth(upperMouth,upper,upperPlayerCenterAboveFloorQ,"upper") + ||!fitMouth(lowerMouth,lower,playerCenterAboveFloorQ,"lower"))return false; + const auto bindFittedMouthAnchor=[&]( + const ResolvedLayoutStratumV2& host, + const std::array& before, + const std::array& after,const char* side) + { + if(before==after)return; + auto mutableHost=std::find_if(layout.strates.begin(),layout.strates.end(), + [&](const auto& stratum) + {return stratum.strateIndex==host.strateIndex;}); + if(mutableHost!=layout.strates.end()) + for(auto& anchor:mutableHost->anchors) + if((anchor.kind=="passage_arrival" + ||anchor.kind=="passage_departure") + &&anchor.values.size()>=3 + &&anchor.values[0]==after[0] + &&anchor.values[1]==after[1]) + { + anchor.values[2]=after[2]; + std::printf("[PassageLandingChamberArrivalBinding] id=%s side=%s anchor=%s status=PASS\n", + passage.stableId.c_str(),side,anchor.stableId.c_str()); + break; + } + }; + bindFittedMouthAnchor(upper,upperMouthBeforeBandFit,upperMouth,"upper"); + bindFittedMouthAnchor(lower,lowerMouthBeforeBandFit,lowerMouth,"lower"); if(!radialWithin(upperMouth,placementRadius)||!radialWithin(lowerMouth,placementRadius)) {error=passage.stableId+" route mouth is outside the resolved PassageConfig.DistanceMax radius";return false;} passage.upperMouthSubvoxels=upperMouth;passage.lowerMouthSubvoxels=lowerMouth; @@ -5048,6 +5114,91 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t surfaceHorizontalSquared)}; } } + if(host.generatorKind=="FloatingIslands"&&side[0]=='u') + { + // The island route is authored as player-center heights. Keep + // the chamber center bound to passage h(s), while selecting the + // closest grade plane that leaves the audited lower capsule arc + // above every receiving route point under the chamber footprint. + const auto route=routePoints(host); + const std::int64_t routeRadius=chamber.radiusSubvoxels + +static_cast(std::llround(0.34*qPerMetre)); + const std::int64_t routeRadiusSquared=routeRadius*routeRadius; + const std::int64_t footClearanceQ=static_cast( + std::llround((0.84+0.025)*qPerMetre)); + struct FRouteFloorConstraint + { + std::int64_t dx=0,dy=0,maximumRiseQ16=0; + }; + std::vector constraints; + constraints.reserve(route.size()); + for(const auto& point:route) + { + const auto dx=static_cast(std::llround( + point[0]-chamber.floorCenterSubvoxels[0])); + const auto dy=static_cast(std::llround( + point[1]-chamber.floorCenterSubvoxels[1])); + if(dx*dx+dy*dy>routeRadiusSquared)continue; + const auto routeFloorMaximum=static_cast( + std::llround(point[2]))-footClearanceQ; + constraints.push_back({dx,dy, + (routeFloorMaximum-chamber.floorCenterSubvoxels[2])*65536}); + } + const auto originalGradient=chamber.floorGradientQ16; + for(std::size_t pass=0;pass<256&&!constraints.empty();++pass) + { + bool adjusted=false; + for(const auto& constraint:constraints) + { + const std::int64_t distanceSquared=constraint.dx*constraint.dx + +constraint.dy*constraint.dy; + if(distanceSquared<=0)continue; + const std::int64_t rise= + static_cast(chamber.floorGradientQ16[0]) + *constraint.dx + +static_cast(chamber.floorGradientQ16[1]) + *constraint.dy; + const std::int64_t excess=rise-constraint.maximumRiseQ16; + if(excess<=0)continue; + const auto correction=[&](std::int64_t delta) + { + if(delta==0)return std::int64_t{0}; + const std::uint64_t product=static_cast(excess) + *static_cast(std::abs(delta)); + return static_cast((product + +static_cast(distanceSquared)-1) + /static_cast(distanceSquared)); + }; + const auto correctionX=correction(constraint.dx); + const auto correctionY=correction(constraint.dy); + chamber.floorGradientQ16[0]-=static_cast( + constraint.dx<0?-correctionX:correctionX); + chamber.floorGradientQ16[1]-=static_cast( + constraint.dy<0?-correctionY:correctionY); + adjusted|=correctionX!=0||correctionY!=0; + } + if(!adjusted)break; + } + for(const auto& constraint:constraints) + { + const std::int64_t rise= + static_cast(chamber.floorGradientQ16[0]) + *constraint.dx + +static_cast(chamber.floorGradientQ16[1]) + *constraint.dy; + if(rise>constraint.maximumRiseQ16) + {error=passage.stableId + +" upper landing floor cannot clear its receiving FloatingIslands route"; + return false;} + } + if(chamber.floorGradientQ16!=originalGradient) + std::printf("[PassageLandingChamberRouteFit] id=%s side=upper host=FloatingIslands route_points=%zu fitted_constraints=%zu source_gradient_q16=(%lld,%lld) fitted_gradient_q16=(%lld,%lld) foot_clearance_m=0.865 max_grade_deg=15.000 status=PASS\n", + passage.stableId.c_str(),route.size(),constraints.size(), + static_cast(originalGradient[0]), + static_cast(originalGradient[1]), + static_cast(chamber.floorGradientQ16[0]), + static_cast(chamber.floorGradientQ16[1])); + } const std::uint64_t gradientSquared=static_cast( chamber.floorGradientQ16[0]*chamber.floorGradientQ16[0] +chamber.floorGradientQ16[1]*chamber.floorGradientQ16[1]); diff --git a/Standalone/Core/src/ShapeWorldInternal.h b/Standalone/Core/src/ShapeWorldInternal.h index 6b1a6af..c20b0ce 100644 --- a/Standalone/Core/src/ShapeWorldInternal.h +++ b/Standalone/Core/src/ShapeWorldInternal.h @@ -1280,11 +1280,32 @@ public: double unionDistance=std::numeric_limits::infinity(); double unionShellDistance=std::numeric_limits::infinity(); double unionFloorShellDistance=std::numeric_limits::infinity(); + double unionLandingChamberDistance=std::numeric_limits::infinity(); double unionLandingChamberFloorShellDistance= std::numeric_limits::infinity(); double plannedTunnelPassageShellDistance=std::numeric_limits::infinity(); const std::uint32_t reliefSeed=static_cast(Owner?Owner->Seed:0); const auto* queryStratum=FindBoundStratumForManagerZ(Z); + const auto effectiveLandingFloor=[&]( + const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& chamber, + double plannedFloorZ,double sampleXSubvoxels,double sampleYSubvoxels) + { + if(!std::isfinite(sampleXSubvoxels)||!std::isfinite(sampleYSubvoxels)) + return plannedFloorZ; + double nativeGroundCoreVoxels=0.0; + if(!GetNativeSurfaceGroundHeightAt( + static_cast(sampleXSubvoxels/Q), + static_cast(sampleYSubvoxels/Q), + static_cast(plannedFloorZ/Q),nativeGroundCoreVoxels)) + return plannedFloorZ; + const double clearHeight=static_cast( + chamber.clearHeightSubvoxels); + const double nativeFloor=nativeGroundCoreVoxels*Q; + // A native surface above the chamber's clear-height budget denotes a + // different layer surface, not a landing floor to clear from this room. + return nativeFloor>plannedFloorZ&&nativeFloor<=plannedFloorZ+clearHeight + ?nativeFloor:plannedFloorZ; + }; const bool plannedTunnelStratum=queryStratum&&queryStratum->Resolved &&(queryStratum->Resolved->generatorKind=="TunnelNetwork" ||queryStratum->Resolved->generatorKind=="VerticalShafts") @@ -1339,10 +1360,12 @@ public: landingChamber->floorCenterSubvoxels[0]); const double centerY=static_cast( landingChamber->floorCenterSubvoxels[1]); - const double floorCenterZ=static_cast( + const double plannedFloorCenterZ=static_cast( landingChamber->floorCenterSubvoxels[2]) +(landingChamber->floorGradientQ16[0]*(qx-centerX) +landingChamber->floorGradientQ16[1]*(qy-centerY))/65536.0; + const double floorCenterZ=effectiveLandingFloor(*landingChamber, + plannedFloorCenterZ,qx,qy); const double radialDistance=DeterministicSqrt( (qx-centerX)*(qx-centerX)+(qy-centerY)*(qy-centerY)) -landingChamber->radiusSubvoxels; @@ -1357,6 +1380,8 @@ public: std::max(-floorDistance,floorDistance-shellThickness), radialDistance); unionDistance=std::min(unionDistance,chamberDistance); + unionLandingChamberDistance=std::min(unionLandingChamberDistance, + chamberDistance); unionShellDistance=std::min(unionShellDistance,chamberShellDistance); unionFloorShellDistance=std::min(unionFloorShellDistance, chamberFloorShellDistance); @@ -1746,15 +1771,18 @@ public: departureTailDistance) ||nearRouteSegment(*lastTwo,*last,PortalWalkLeadMetres,true,0.0); }; - const auto connectedSurfaceLandingShellContains=[&]() + const auto connectedNativeLandingShellContains=[&]() { if(!queryStratum||!queryStratum->Resolved - ||queryStratum->Resolved->generatorKind!="SurfaceWorld")return false; + ||(queryStratum->Resolved->generatorKind!="SurfaceWorld" + &&queryStratum->Resolved->generatorKind!="FloatingIslands")) + 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; + const double portalWalkLeadMetres= + queryStratum->Resolved->generatorKind=="FloatingIslands"?12.0:9.0; for(const auto& passage:ResolvedLayout->passages) { if(passage.upperStrateIndex!=queryStratum->StrateIndex)continue; @@ -1762,9 +1790,13 @@ public: 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 + const double plannedFloorZ=static_cast( + chamber.floorCenterSubvoxels[2])/qPerMetre +chamber.floorGradientQ16[0]/65536.0*(queryX-cx) +chamber.floorGradientQ16[1]/65536.0*(queryY-cy); + const double floorZ=effectiveLandingFloor(chamber, + plannedFloorZ*qPerMetre,queryX*qPerMetre, + queryY*qPerMetre)/qPerMetre; const double radialDistance=std::hypot(queryX-cx,queryY-cy) -static_cast(chamber.radiusSubvoxels)/qPerMetre; const double floorDistance=floorZ-queryZ; @@ -1784,7 +1816,11 @@ public: const double mouthZ=static_cast(passage.upperMouthSubvoxels[2]) /qPerMetre; std::size_t endpoint=route.size(); - double endpointDistanceSquared=1.0; + const double endpointRadius= + queryStratum->Resolved->generatorKind=="FloatingIslands"?3.0:1.0; + const double endpointHeight= + queryStratum->Resolved->generatorKind=="FloatingIslands"?2.0:1.5; + double endpointDistanceSquared=endpointRadius*endpointRadius; for(std::size_t index=0;indexvalues[0]/qPerMetre; @@ -1793,7 +1829,7 @@ public: const double dx=x-cx,dy=y-cy; const double distanceSquared=dx*dx+dy*dy; if(distanceSquaredPortalWalkLeadMetres)continue; + if(alongFromEndpoint>portalWalkLeadMetres)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; + const double lateral=std::hypot(queryX-routeX,queryY-routeY); + const double vertical=std::abs(queryZ-routeZ); + if(lateral<=RouteClearanceMetres + &&vertical<=RouteHeightClearanceMetres)return true; } } return false; @@ -1887,13 +1925,19 @@ public: } if(nativeFloorSupport!=std::numeric_limits::lowest() &&nativeFloorSupport<0.0f) + { + // Host floor support keeps solid ground under a landing chamber, but it + // cannot refill the chamber air above the selected planned/native floor. + if(unionLandingChamberDistance<=0.0) + return passageAirDistance; return std::min(nativeFloorSupport,unionDistance<=0.0 ?passageAirDistance:density); + } if(nativeTunnelFieldDensity!=std::numeric_limits::lowest()) return std::max(density,passageAirDistance); if(unionDistance>0.0&&unionShellDistance<0.0&&density>=0.0f) { - if(connectedSurfaceLandingShellContains()) + if(connectedNativeLandingShellContains()) return std::max(density,passageAirDistance); return static_cast(unionShellDistance/Q); } @@ -2044,11 +2088,12 @@ public: static_cast(chamber.floorGradientQ16[0])/65536.0, static_cast(chamber.floorGradientQ16[1])/65536.0); const double extent=reachCoreVoxels+0.01; + const double clearHeight=static_cast( + chamber.clearHeightSubvoxels)/Q; 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}; + cz+floorExtent+2.0*clearHeight+extent}; return overlaps(low,high); }; if(chamberOverlaps(passage.upperLandingChamber) @@ -5488,7 +5533,7 @@ private: cx-radius-extent,cy-radius-extent,cz-floorExtent-extent}; const std::array maximum{ cx+radius+extent,cy+radius+extent, - cz+floorExtent+height+extent}; + cz+floorExtent+2.0*height+extent}; FPassageProofBound proof; for(std::size_t axis=0;axis<3;++axis) { @@ -5859,6 +5904,71 @@ public: return std::isfinite(OutGroundZ)&&std::isfinite(OutCeilingZ); } + bool GetNativeSurfaceGroundHeightAt(float X,float Y,float Z, + double& OutGroundZ) const + { + if(!ResolvedLayout||!std::isfinite(X)||!std::isfinite(Y)||!std::isfinite(Z)) + return false; + const auto* bound=FindBoundStratumForManagerZ(Z); + if(!bound)return false; + constexpr double Scale=static_cast( + VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); + const double maxVoxel=static_cast(std::numeric_limits::max())/Scale; + if(std::abs(static_cast(X))>maxVoxel + ||std::abs(static_cast(Y))>maxVoxel)return false; + const auto toFixed=[](float value) + {return static_cast(std::llround(static_cast(value) + *VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel));}; + const auto* field=std::get_if(&bound->Evaluator); + if(field&&field->Evaluator.isValid()) + { + const auto column=field->Evaluator.point(toFixed(X),toFixed(Y),0); + OutGroundZ=static_cast(column.groundHeightSubvoxels)/Scale; + return std::isfinite(OutGroundZ); + } + const auto* islands=std::get_if(&bound->Evaluator); + if(!islands||!bound->IslandPlan)return false; + const auto& route=bound->IslandPlan->manifest.plan.route; + if(route.size()<2)return false; + const auto& recipe=bound->IslandPlan->manifest.recipe; + const std::int64_t routeLift=recipe.routeConstraints.capsuleHeightSubvoxels/2 + +recipe.routeConstraints.minimumClearanceSubvoxels + +(recipe.geometryVersion>=2 + ?VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel/8:0); + const std::int64_t supportRadius=recipe.routeConstraints + .supportProbeRadiusSubvoxels; + if(routeLift<0||supportRadius<0)return false; + const std::int64_t queryX=toFixed(X),queryY=toFixed(Y); + std::uint64_t nearestDistanceSquared=std::numeric_limits::max(); + std::int64_t nearestRouteZ=0; + bool found=false; + for(std::size_t index=1;index( + offsetX*offsetX+offsetY*offsetY); + if(distanceSquared>static_cast(supportRadius)*supportRadius + ||distanceSquared>=nearestDistanceSquared)continue; + const std::int64_t dz=b[2]-a[2]; + nearestRouteZ=a[2]+dz*projection/lengthSquared; + nearestDistanceSquared=distanceSquared; + found=true; + } + if(!found)return false; + OutGroundZ=static_cast(nearestRouteZ-routeLift)/Scale; + return std::isfinite(OutGroundZ); + } + EVoxelTileClass ClassifyBox(const FIntVector& Origin,int32 Step,int32 Cells) const { return ClassifyBoxDetailed(Origin,Step,Cells).Verdict; } diff --git a/Standalone/Core/src/VoxelForgeCore.cpp b/Standalone/Core/src/VoxelForgeCore.cpp index b139ec4..e40250c 100644 --- a/Standalone/Core/src/VoxelForgeCore.cpp +++ b/Standalone/Core/src/VoxelForgeCore.cpp @@ -1811,6 +1811,13 @@ namespace VoxelForgeCore float World::densityAt(Vec3d position) const { return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape?impl_->shape->DensityAt(static_cast(position.x),static_cast(position.y),static_cast(position.z)):0.0f; } + bool World::nativeSurfaceGroundHeightAt(Vec3d position,double& groundHeightVoxels) const + { + return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape + &&impl_->shape->GetNativeSurfaceGroundHeightAt(static_cast(position.x), + static_cast(position.y),static_cast(position.z), + groundHeightVoxels); + } MaterialSampleV1 World::materialAt(Vec3d position) const { if(!impl_||!impl_->shapeReady.load(std::memory_order_acquire)||!impl_->shape) diff --git a/Standalone/Source/StreamBenchmarkPipeline.h b/Standalone/Source/StreamBenchmarkPipeline.h index 2fcf4b5..3ed4dd1 100644 --- a/Standalone/Source/StreamBenchmarkPipeline.h +++ b/Standalone/Source/StreamBenchmarkPipeline.h @@ -3051,7 +3051,8 @@ namespace StandaloneStreamBenchmark double supportMaximumMetres,std::uint32_t minimumSupportProbes, double supportProbeRadiusMetres, const std::function& supportExemption={}, - const std::function& passageInteriorPredicate={}) + const std::function& passageInteriorPredicate={}, + const std::function& densityOverride={}) { FWalkAudit audit; if(path.size()<2)return audit; @@ -3068,15 +3069,18 @@ namespace StandaloneStreamBenchmark &&passageInteriorPredicate(xMetres,yMetres,zMetres); if(inPassageInterior)++audit.passageInteriorCapsuleSamples; const VoxelForgeCore::Vec3d position{xMetres/coreMetres,yMetres/coreMetres,zMetres/coreMetres}; - const float density=Field.densityAt(position); + const float density=densityOverride + ?densityOverride(position):Field.densityAt(position); if(density<0.0f) { if(inPassageInterior)++audit.passageInteriorSolidSamples; if(audit.firstBlockedCount::max(), std::size_t ignoredSegment=std::numeric_limits::max(), - std::size_t selectedPassage=std::numeric_limits::max()) + std::size_t selectedPassage=std::numeric_limits::max(), + bool includeLandingChambers=true) { constexpr double CornerBlendMetres=0.50; constexpr double WallReliefMetres=0.21; @@ -3748,8 +3761,9 @@ namespace StandaloneStreamBenchmark +qToMetres(chamber.clearHeightSubvoxels)); return std::max({radialDistance,floorDistance,ceilingDistance})<=0.0; }; - if(inLandingChamber(passage.upperLandingChamber) - ||inLandingChamber(passage.lowerLandingChamber))return true; + if(includeLandingChambers + &&(inLandingChamber(passage.upperLandingChamber) + ||inLandingChamber(passage.lowerLandingChamber)))return true; const auto& route=allPassageSmoothRoutes[passageIndex]; if(route.size()<2)continue; double bestRoundDistance=std::numeric_limits::infinity(); @@ -4722,7 +4736,13 @@ namespace StandaloneStreamBenchmark const double mouthZ=qToMetres(upper?passage.upperMouthSubvoxels[2] :passage.lowerMouthSubvoxels[2]); std::size_t endpoint=route.size(); - double endpointDistanceSquared=1.0; + const double endpointRadius=stratum->generatorKind=="FloatingIslands" + ?3.0:1.0; + const double endpointHeight=stratum->generatorKind=="FloatingIslands" + ?2.0:1.5; + const double portalWalkLead=stratum->generatorKind=="FloatingIslands" + ?12.0:9.0; + double endpointDistanceSquared=endpointRadius*endpointRadius; for(std::size_t index=0;indexvalues[0])-cx; @@ -4730,7 +4750,7 @@ namespace StandaloneStreamBenchmark const double z=qToMetres(route[index]->values[2]); const double distanceSquared=dx*dx+dy*dy; if(distanceSquared9.0)continue; + if(fromEndpoint>portalWalkLead)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, @@ -5167,6 +5187,21 @@ namespace StandaloneStreamBenchmark const double radius=qToMetres(chamber.radiusSubvoxels); const double gx=chamber.floorGradientQ16[0]/65536.0; const double gy=chamber.floorGradientQ16[1]/65536.0; + const auto floorAt=[&](double x,double y) + { + const double planned=baseFloorZ+gx*(x-centerX)+gy*(y-centerY); + double nativeGroundVoxels=0.0; + if(Field.nativeSurfaceGroundHeightAt({x/coreMetres,y/coreMetres, + planned/coreMetres},nativeGroundVoxels)) + { + const double nativeGround=nativeGroundVoxels*coreMetres; + const double clearHeight=qToMetres( + chamber.clearHeightSubvoxels); + if(nativeGround>planned&&nativeGround<=planned+clearHeight) + return nativeGround; + } + return planned; + }; for(const double radial:chamberFloorRadii) { if(radial>=radius-1.0)continue; @@ -5177,16 +5212,18 @@ namespace StandaloneStreamBenchmark *static_cast(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); + const double floorZ=floorAt(sampleX,sampleY); 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)) + if(passageInteriorWinsAt(sampleX,sampleY,sampleZ, + std::numeric_limits::max(), + std::numeric_limits::max(),passageIndex) + ||landingChamberConnectionContains(passage,upper, + sampleX,sampleY,sampleZ)) {++audit.chamberConnectionSkips;continue;} ++audit.chamberFloorSamples; const float density=Field.densityAt(position); @@ -5211,8 +5248,7 @@ namespace StandaloneStreamBenchmark { 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 floorZ=floorAt(sampleX,sampleY); const double sampleZ=floorZ+height; const VoxelForgeCore::Vec3d position{sampleX/coreMetres, sampleY/coreMetres,sampleZ/coreMetres}; @@ -5275,6 +5311,25 @@ namespace StandaloneStreamBenchmark {return passageInteriorWinsAt(x,y,z, std::numeric_limits::max(), std::numeric_limits::max(),passageIndex);}; + const auto floorAt=[&](double x,double y) + { + const double planned=centerZ+gx*(x-centerX)+gy*(y-centerY); + double nativeGroundVoxels=0.0; + if(field.nativeSurfaceGroundHeightAt({x/coreMetres,y/coreMetres, + planned/coreMetres},nativeGroundVoxels)) + { + const double nativeGround=nativeGroundVoxels*coreMetres; + const double clearHeight=qToMetres(chamber.clearHeightSubvoxels); + if(nativeGround>planned&&nativeGround<=planned+clearHeight) + return nativeGround; + } + return planned; + }; + const std::function supportExemption= + [&](double x,double y,double z) + {return passageInteriorWinsAt(x,y,z, + std::numeric_limits::max(), + std::numeric_limits::max(),passageIndex,false);}; for(const auto& direction:directions) { std::vector> chamberPath; @@ -5286,12 +5341,12 @@ namespace StandaloneStreamBenchmark 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); + const double floorZ=floorAt(x,y); 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); + 0.125,0.125,1.125,3,0.45,supportExemption,interiorPredicate); total.segments+=part.segments;total.centers+=part.centers; total.capsuleSamples+=part.capsuleSamples; total.blocked+=part.blocked; @@ -5305,10 +5360,116 @@ namespace StandaloneStreamBenchmark } return total; }; + const auto auditOpenChamberFloorNegativeControl=[&]( + const VoxelForgeCore::World& field, + const VoxelForgeCore::ResolvedLayoutPassageV1& passage, + std::size_t passageIndex,bool upper) + { + const auto& chamber=upper?passage.upperLandingChamber + :passage.lowerLandingChamber; + const double centerX=qToMetres(chamber.floorCenterSubvoxels[0]); + const double centerY=qToMetres(chamber.floorCenterSubvoxels[1]); + const double centerZ=qToMetres(chamber.floorCenterSubvoxels[2]); + const double gx=chamber.floorGradientQ16[0]/65536.0; + const double gy=chamber.floorGradientQ16[1]/65536.0; + const double chamberAuditRadius=std::min(5.75, + qToMetres(chamber.radiusSubvoxels)-1.5); + const double mouthRadius=qToMetres(passage.mouthRadiusSubvoxels); + const double radius=std::min(chamberAuditRadius-0.1,mouthRadius+1.25); + if(radius<=0.0) + { + std::printf("[PassageLandingChamberOpenFloorNegativeControl] id=%s side=%s radius_m=%.3f mouth_radius_m=%.3f unsupported=0 support_overlap_skips=0 expected=FAIL status=FAIL reason=no_outer_floor_sample\n", + passage.stableId.c_str(),upper?"upper":"lower",radius,mouthRadius); + return false; + } + const auto floorAt=[&](double x,double y) + { + const double planned=centerZ+gx*(x-centerX)+gy*(y-centerY); + double nativeGroundVoxels=0.0; + if(field.nativeSurfaceGroundHeightAt({x/coreMetres,y/coreMetres, + planned/coreMetres},nativeGroundVoxels)) + { + const double nativeGround=nativeGroundVoxels*coreMetres; + const double clearHeight=qToMetres(chamber.clearHeightSubvoxels); + if(nativeGround>planned&&nativeGround<=planned+clearHeight) + return nativeGround; + } + return planned; + }; + const std::function interiorPredicate= + [&](double x,double y,double z) + {return passageInteriorWinsAt(x,y,z, + std::numeric_limits::max(), + std::numeric_limits::max(),passageIndex);}; + const std::function supportExemption= + [&](double x,double y,double z) + {return passageInteriorWinsAt(x,y,z, + std::numeric_limits::max(), + std::numeric_limits::max(),passageIndex,false);}; + constexpr std::array,5> supportOffsets{{ + {{0.0,0.0}},{{0.45,0.0}},{{-0.45,0.0}}, + {{0.0,0.45}},{{0.0,-0.45}}}}; + std::vector> path; + double selectedAngle=0.0; + bool foundOutsideBore=false; + for(std::size_t directionIndex=0;directionIndex<32&&!foundOutsideBore; + ++directionIndex) + { + selectedAngle=2.0*3.14159265358979323846 + *static_cast(directionIndex)/32.0; + const double nx=std::cos(selectedAngle),ny=std::sin(selectedAngle); + const double tangentX=-ny,tangentY=nx; + const double x0=centerX+radius*nx-0.25*tangentX; + const double y0=centerY+radius*ny-0.25*tangentY; + const double x1=centerX+radius*nx+0.25*tangentX; + const double y1=centerY+radius*ny+0.25*tangentY; + const double floor0=floorAt(x0,y0),floor1=floorAt(x1,y1); + const std::array,2> candidate{{ + {{x0,y0,floor0+1.13}},{{x1,y1,floor1+1.13}}}}; + bool samplesOutsideBore=true; + for(const auto& point:candidate) + { + const double supportZ=point[2]-0.88-0.125; + for(const auto& offset:supportOffsets) + if(supportExemption(point[0]+offset[0], + point[1]+offset[1],supportZ)) + {samplesOutsideBore=false;break;} + if(!samplesOutsideBore)break; + } + if(samplesOutsideBore) + { + path={{candidate[0][0]/coreMetres,candidate[0][1]/coreMetres, + candidate[0][2]/coreMetres}, + {candidate[1][0]/coreMetres,candidate[1][1]/coreMetres, + candidate[1][2]/coreMetres}}; + foundOutsideBore=true; + } + } + if(!foundOutsideBore) + { + std::printf("[PassageLandingChamberOpenFloorNegativeControl] id=%s side=%s radius_m=%.3f mouth_radius_m=%.3f unsupported=0 support_overlap_skips=0 expected=FAIL status=FAIL reason=no_bore_clear_spoke\n", + passage.stableId.c_str(),upper?"upper":"lower",radius,mouthRadius); + return false; + } + const std::function openDensity= + [](VoxelForgeCore::Vec3d){return 1.0f;}; + const FWalkAudit control=auditPlayerPath(field,path,0.125,0.125, + 1.125,3,0.45,supportExemption,interiorPredicate,openDensity); + const bool passed=!control.passed&&control.unsupported>0 + &&control.supportOverlapSkips==0; + std::printf("[PassageLandingChamberOpenFloorNegativeControl] id=%s side=%s radius_m=%.3f mouth_radius_m=%.3f angle_deg=%.1f unsupported=%llu support_overlap_skips=%llu result=%s expected=FAIL status=%s\n", + passage.stableId.c_str(),upper?"upper":"lower",radius,mouthRadius, + selectedAngle*180.0/3.14159265358979323846, + static_cast(control.unsupported), + static_cast(control.supportOverlapSkips), + control.passed?"PASS":"FAIL",passed?"PASS":"FAIL"); + return passed; + }; 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 openChamberFloorNegativeControls=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; @@ -5346,6 +5507,9 @@ namespace StandaloneStreamBenchmark passage,passageIndex,true); const FWalkAudit lowerChamberAudit=auditLandingChamber(Shape, passage,passageIndex,false); + const bool openChamberFloorControl=auditOpenChamberFloorNegativeControl( + Shape,passage,passageIndex,true); + openChamberFloorNegativeControls+=openChamberFloorControl; const bool landingChambersWalkable=upperChamberAudit.passed &&lowerChamberAudit.passed; const bool passageInteriorAirPassed= @@ -5889,6 +6053,10 @@ namespace StandaloneStreamBenchmark static_cast(landingChamberCount), static_cast(landingChamberCount -landingChamberNegativeControls)); + std::printf("[PassageLandingChamberOpenFloorNegativeControlSummary] expected_fail_detected=%llu/%llu status=%s\n", + static_cast(openChamberFloorNegativeControls), + static_cast(passageCount), + openChamberFloorNegativeControls==passageCount?"PASS":"FAIL"); const bool shellAuditPassed=shellAudits==passageCount &&shellFloorViolations==0&&shellWallViolations==0 &&shellChamberFloorViolations==0&&shellChamberWallViolations==0 @@ -5939,6 +6107,7 @@ namespace StandaloneStreamBenchmark &&mouthProfilePasses==passageCount&&l23TerraceControlDetected &&ripplePasses==passageCount &&shellAuditPassed + &&openChamberFloorNegativeControls==passageCount &&routePasses>0&&routePasses==routeWalkPasses&&routePasses==routeBindings &&routeBindingFailures==0 &&tunnelNetworkRoutes==tunnelNetworkBlockedControls