Clamp-between-cones pad at each tunnel end, last composition step, chamber cases removed, bounds extended (proof and feature samplers 16/16 clean). Open-ground result looks as intended; but pads pass 8/96 (flatness up to 0.75 m, blend up to 81 deg), interior air fails 16/16 (839-1,588 solid capsule samples), route audits fail, FI band check fails at seed 0. Owner asked for one attempt, no tuning. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
1069 lines
63 KiB
Diff
1069 lines
63 KiB
Diff
diff --git a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h
|
|
index 4799b9e..13440df 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 replaces chamber boxes and shells with bounded clamp-and-cone landing pads.
|
|
+ inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 240;
|
|
|
|
enum class FieldChannel : std::uint32_t
|
|
{
|
|
diff --git a/Standalone/Core/src/ShapeWorldInternal.h b/Standalone/Core/src/ShapeWorldInternal.h
|
|
index 6b1a6af..3ce93e4 100644
|
|
--- a/Standalone/Core/src/ShapeWorldInternal.h
|
|
+++ b/Standalone/Core/src/ShapeWorldInternal.h
|
|
@@ -56,6 +56,11 @@ static bool VF_IsL130PassageReachProofAuditEnabled()
|
|
static constexpr double VF_PassageShellThicknessMetres=0.75;
|
|
static constexpr double VF_PassageWallReliefMetres=0.21;
|
|
static constexpr double VF_PassageCornerBlendMetres=0.50;
|
|
+static constexpr double VF_LandingPadConeSlope=0.2125565616700221; // tan(12 degrees)
|
|
+static constexpr double VF_LandingPadFillDepthMetres=1.25;
|
|
+static constexpr double VF_LandingPadHeadroomMetres=3.0;
|
|
+static constexpr double VF_LandingPadBlendRadiusMetres=8.0;
|
|
+static constexpr double VF_LandingPadSmoothMinMaximumMetres=0.50;
|
|
static constexpr double VF_PassageReachMarginMetres=0.01;
|
|
// Radial support in metres: shell + maximum relief + the smooth-max support
|
|
// (corner-blend width / 6) + a 1 cm guard for float and bound rounding.
|
|
@@ -1234,8 +1239,11 @@ public:
|
|
|
|
float ApplyResolvedStratePassageCarves(float density,float X,float Y,float Z,
|
|
float nativeTunnelFieldDensity=std::numeric_limits<float>::lowest(),
|
|
- float nativeFloorSupport=std::numeric_limits<float>::lowest()) const
|
|
+ float nativeFloorSupport=std::numeric_limits<float>::lowest(),
|
|
+ float* outTubeOnlyDistance=nullptr) const
|
|
{
|
|
+ if(outTubeOnlyDistance)
|
|
+ *outTubeOnlyDistance=std::numeric_limits<float>::infinity();
|
|
VoxelForgeCore::Linux77Profiler::FScopedPhase l77PassagePhase(
|
|
VoxelForgeCore::Linux77Profiler::EPhase::Passage);
|
|
if(!ResolvedLayout||ResolvedLayout->passages.empty()||!std::isfinite(X)
|
|
@@ -1280,8 +1288,6 @@ public:
|
|
double unionDistance=std::numeric_limits<double>::infinity();
|
|
double unionShellDistance=std::numeric_limits<double>::infinity();
|
|
double unionFloorShellDistance=std::numeric_limits<double>::infinity();
|
|
- double unionLandingChamberFloorShellDistance=
|
|
- std::numeric_limits<double>::infinity();
|
|
double plannedTunnelPassageShellDistance=std::numeric_limits<double>::infinity();
|
|
const std::uint32_t reliefSeed=static_cast<std::uint32_t>(Owner?Owner->Seed:0);
|
|
const auto* queryStratum=FindBoundStratumForManagerZ(Z);
|
|
@@ -1324,48 +1330,6 @@ public:
|
|
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<double>(
|
|
- landingChamber->floorCenterSubvoxels[0]);
|
|
- const double centerY=static_cast<double>(
|
|
- landingChamber->floorCenterSubvoxels[1]);
|
|
- const double floorCenterZ=static_cast<double>(
|
|
- 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;
|
|
|
|
@@ -1658,12 +1622,9 @@ public:
|
|
}
|
|
|
|
if(!std::isfinite(unionDistance))return density;
|
|
+ if(outTubeOnlyDistance)
|
|
+ *outTubeOnlyDistance=static_cast<float>(unionDistance/Q);
|
|
const float passageAirDistance=static_cast<float>(-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<float>(unionLandingChamberFloorShellDistance/Q);
|
|
const auto plannedPortalWalkCorridorContains=[&]()
|
|
{
|
|
if(!queryStratum||!queryStratum->Resolved
|
|
@@ -1746,90 +1707,6 @@ 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<double>(chamber.floorCenterSubvoxels[0])/qPerMetre;
|
|
- const double cy=static_cast<double>(chamber.floorCenterSubvoxels[1])/qPerMetre;
|
|
- const double floorZ=static_cast<double>(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<double>(chamber.radiusSubvoxels)/qPerMetre;
|
|
- const double floorDistance=floorZ-queryZ;
|
|
- const double ceilingDistance=queryZ-(floorZ
|
|
- +static_cast<double>(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<const VoxelForgeCore::ResolvedLayoutAnchorV2*> 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<double>(passage.upperMouthSubvoxels[2])
|
|
- /qPerMetre;
|
|
- std::size_t endpoint=route.size();
|
|
- double endpointDistanceSquared=1.0;
|
|
- for(std::size_t index=0;index<route.size();++index)
|
|
- {
|
|
- const double x=route[index]->values[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<endpointDistanceSquared
|
|
- &&std::abs(z-mouthZ)<=1.5)
|
|
- {endpoint=index;endpointDistanceSquared=distanceSquared;}
|
|
- }
|
|
- if(endpoint==route.size())continue;
|
|
- std::vector<double> routeDistance(route.size(),0.0);
|
|
- for(std::size_t index=1;index<route.size();++index)
|
|
- {
|
|
- const double dx=(route[index]->values[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;index<route.size();++index)
|
|
- {
|
|
- const auto& a=*route[index-1];
|
|
- const auto& b=*route[index];
|
|
- const double ax=a.values[0]/qPerMetre,ay=a.values[1]/qPerMetre;
|
|
- const double az=a.values[2]/qPerMetre;
|
|
- const double dx=(b.values[0]-a.values[0])/qPerMetre;
|
|
- const double dy=(b.values[1]-a.values[1])/qPerMetre;
|
|
- const double horizontalLengthSquared=dx*dx+dy*dy;
|
|
- if(horizontalLengthSquared<=1.0e-12)continue;
|
|
- const double t=std::clamp(((queryX-ax)*dx+(queryY-ay)*dy)
|
|
- /horizontalLengthSquared,0.0,1.0);
|
|
- const double alongFromEndpoint=std::abs(
|
|
- std::lerp(routeDistance[index-1],routeDistance[index],t)
|
|
- -routeDistance[endpoint]);
|
|
- 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;
|
|
- }
|
|
- }
|
|
- return false;
|
|
- };
|
|
if(plannedTunnelStratum
|
|
&&nativeTunnelFieldDensity!=std::numeric_limits<float>::lowest()
|
|
&&nativeTunnelFieldDensity<0.0f)
|
|
@@ -1893,13 +1770,95 @@ public:
|
|
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<float>(unionShellDistance/Q);
|
|
}
|
|
return std::max(density,passageAirDistance);
|
|
}
|
|
|
|
+ float ApplyResolvedLandingPadStamps(float density,float X,float Y,float Z,
|
|
+ float tubeOnlyDistance) const
|
|
+ {
|
|
+ if(!ResolvedLayout||ResolvedLayout->passages.empty()||!std::isfinite(X)
|
|
+ ||!std::isfinite(Y)||!std::isfinite(Z))return density;
|
|
+ const auto* bound=FindBoundStratumForManagerZ(Z);
|
|
+ if(!bound||!bound->Resolved)return density;
|
|
+ const double coreMetres=std::max<std::uint32_t>(1,
|
|
+ ResolvedLayout->coreVoxelSizeMicrometres)/1000000.0;
|
|
+ constexpr double Q=static_cast<double>(
|
|
+ VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
|
+ const double qPerMetre=Q/coreMetres;
|
|
+ const double xMetres=static_cast<double>(X)*coreMetres;
|
|
+ const double yMetres=static_cast<double>(Y)*coreMetres;
|
|
+ const double zMetres=static_cast<double>(Z)*coreMetres;
|
|
+ const double tubeDistanceMetres=static_cast<double>(tubeOnlyDistance)*coreMetres;
|
|
+ const auto* tileQueries=FScopedPassageTileQueries::CurrentFor(*this);
|
|
+ const auto* candidatePassages=tileQueries
|
|
+ ?&tileQueries->routePassageIndices():nullptr;
|
|
+ const std::size_t candidateCount=candidatePassages
|
|
+ ?candidatePassages->size():ResolvedLayout->passages.size();
|
|
+ float result=density;
|
|
+ constexpr double CosTwelveDegrees=0.9781476007338057;
|
|
+ constexpr double OuterReachMetres=VF_PassageInfluenceReachMetres;
|
|
+ for(std::size_t offset=0;offset<candidateCount;++offset)
|
|
+ {
|
|
+ const std::size_t passageIndex=candidatePassages
|
|
+ ?(*candidatePassages)[offset]:offset;
|
|
+ if(passageIndex>=ResolvedLayout->passages.size())continue;
|
|
+ const auto& passage=ResolvedLayout->passages[passageIndex];
|
|
+ const auto applyPad=[&](
|
|
+ const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& pad)
|
|
+ {
|
|
+ if(pad.radiusSubvoxels<=0)return;
|
|
+ const double centerX=static_cast<double>(pad.floorCenterSubvoxels[0])
|
|
+ /qPerMetre;
|
|
+ const double centerY=static_cast<double>(pad.floorCenterSubvoxels[1])
|
|
+ /qPerMetre;
|
|
+ const double floorP=static_cast<double>(pad.floorCenterSubvoxels[2])
|
|
+ /qPerMetre;
|
|
+ const double radius=std::max(3.0,
|
|
+ static_cast<double>(std::max<std::int64_t>(0,
|
|
+ passage.mouthRadiusSubvoxels))/qPerMetre+1.0);
|
|
+ const double dx=xMetres-centerX,dy=yMetres-centerY;
|
|
+ const double radial=DeterministicSqrt(dx*dx+dy*dy);
|
|
+ const double outerRadius=radius+VF_LandingPadBlendRadiusMetres;
|
|
+ if(radial>outerRadius+OuterReachMetres
|
|
+ ||zMetres<floorP-VF_LandingPadFillDepthMetres
|
|
+ -VF_LandingPadConeSlope*VF_LandingPadBlendRadiusMetres
|
|
+ -OuterReachMetres
|
|
+ ||zMetres>floorP+VF_LandingPadHeadroomMetres
|
|
+ +VF_LandingPadConeSlope*VF_LandingPadBlendRadiusMetres
|
|
+ +OuterReachMetres)return;
|
|
+ const double coneRun=std::max(0.0,radial-radius);
|
|
+ const double lower=floorP-VF_LandingPadConeSlope*coneRun;
|
|
+ const double upper=floorP+VF_LandingPadConeSlope*coneRun;
|
|
+ const double fillDistanceMetres=std::max({
|
|
+ CosTwelveDegrees*(zMetres-lower),
|
|
+ CosTwelveDegrees*(lower-VF_LandingPadFillDepthMetres-zMetres),
|
|
+ radial-outerRadius});
|
|
+ const double airDistanceMetres=std::min({
|
|
+ CosTwelveDegrees*(zMetres-upper),
|
|
+ CosTwelveDegrees*(upper+VF_LandingPadHeadroomMetres-zMetres),
|
|
+ outerRadius-radial});
|
|
+ const double fillVoxels=fillDistanceMetres/coreMetres;
|
|
+ const double airVoxels=airDistanceMetres/coreMetres;
|
|
+ const double maskedFill=std::max(fillVoxels,-tubeDistanceMetres/coreMetres);
|
|
+ const double smoothWidthMetres=VF_LandingPadSmoothMinMaximumMetres
|
|
+ *std::clamp(radial-radius,0.0,1.0);
|
|
+ const float next=static_cast<float>(result);
|
|
+ const float fill=static_cast<float>(maskedFill);
|
|
+ const float width=static_cast<float>(smoothWidthMetres/coreMetres);
|
|
+ const float clamped=width>0.0f
|
|
+ ?SmoothMinDistance(next,fill,width):std::min(next,fill);
|
|
+ result=std::max(clamped,static_cast<float>(airVoxels));
|
|
+ };
|
|
+ if(passage.upperStrateIndex==bound->StrateIndex)
|
|
+ applyPad(passage.upperLandingChamber);
|
|
+ if(passage.lowerStrateIndex==bound->StrateIndex)
|
|
+ applyPad(passage.lowerLandingChamber);
|
|
+ }
|
|
+ return result;
|
|
+ }
|
|
+
|
|
float ApplyResolvedStrateComposition(float density,float X,float Y,float Z) const
|
|
{
|
|
float graphDensity=0.0f;
|
|
@@ -1930,6 +1889,7 @@ public:
|
|
}
|
|
float composed=boundary;
|
|
float stamped=composed;
|
|
+ float tubeOnlyDistance=std::numeric_limits<float>::infinity();
|
|
if(!StampRuntime.empty())
|
|
{
|
|
VoxelForgeCore::Linux77Profiler::FScopedPhase l77StampPhase(
|
|
@@ -1963,12 +1923,13 @@ public:
|
|
const float nativeTunnelFieldDensity=nativeTunnelField
|
|
?graphDensity:std::numeric_limits<float>::lowest();
|
|
stamped=ApplyResolvedStratePassageCarves(stamped,X,Y,Z,
|
|
- nativeTunnelFieldDensity,nativeGraphFloorSupport);
|
|
+ nativeTunnelFieldDensity,nativeGraphFloorSupport,
|
|
+ &tubeOnlyDistance);
|
|
}
|
|
if(bTraceTnComposition)
|
|
std::printf("[L75TNCompositionTrace] input=%.9g graph=%.9g boundary=%.9g passage=%.9g stamp=%.9g native=1\n",
|
|
density,graphDensity,boundary,composed,stamped);
|
|
- return stamped;
|
|
+ return ApplyResolvedLandingPadStamps(stamped,X,Y,Z,tubeOnlyDistance);
|
|
}
|
|
// The legacy landmark demo retains its compatibility floor cap. Native caves
|
|
// already combine each floor with its wall before the single void carve above.
|
|
@@ -1985,12 +1946,14 @@ public:
|
|
VoxelForgeCore::Linux77Profiler::EPhase::Passage);
|
|
FScopedLegacyFieldTermTimer TermTimer(
|
|
ELegacyFieldTerm::ComposerPassage);
|
|
- final=ApplyResolvedStratePassageCarves(final,X,Y,Z);
|
|
+ final=ApplyResolvedStratePassageCarves(final,X,Y,Z,
|
|
+ std::numeric_limits<float>::lowest(),
|
|
+ std::numeric_limits<float>::lowest(),&tubeOnlyDistance);
|
|
}
|
|
if(bTraceTnComposition)
|
|
std::printf("[L75TNCompositionTrace] input=%.9g graph=%.9g boundary=%.9g passage=%.9g stamp=%.9g final=%.9g native=0\n",
|
|
density,graphDensity,boundary,composed,stamped,final);
|
|
- return final;
|
|
+ return ApplyResolvedLandingPadStamps(final,X,Y,Z,tubeOnlyDistance);
|
|
}
|
|
|
|
bool ResolvedPassageIntersectsBox(const int32 minimum[3],const int32 maximum[3]) const
|
|
@@ -2033,26 +1996,35 @@ public:
|
|
}
|
|
if(overlaps(low,high))return true;
|
|
}
|
|
- const auto chamberOverlaps=[&](
|
|
- const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& chamber)
|
|
- {
|
|
- const double cx=static_cast<double>(chamber.floorCenterSubvoxels[0])/Q;
|
|
- const double cy=static_cast<double>(chamber.floorCenterSubvoxels[1])/Q;
|
|
- const double cz=static_cast<double>(chamber.floorCenterSubvoxels[2])/Q;
|
|
- const double radius=static_cast<double>(chamber.radiusSubvoxels)/Q;
|
|
- const double floorExtent=radius*std::hypot(
|
|
- static_cast<double>(chamber.floorGradientQ16[0])/65536.0,
|
|
- static_cast<double>(chamber.floorGradientQ16[1])/65536.0);
|
|
+ const auto padOverlaps=[&](
|
|
+ const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& pad,
|
|
+ std::int64_t mouthRadiusSubvoxels)
|
|
+ {
|
|
+ if(pad.radiusSubvoxels<=0)return false;
|
|
+ const double cx=static_cast<double>(pad.floorCenterSubvoxels[0])/Q;
|
|
+ const double cy=static_cast<double>(pad.floorCenterSubvoxels[1])/Q;
|
|
+ const double pz=static_cast<double>(pad.floorCenterSubvoxels[2])/Q;
|
|
+ const double qPerMetre=Q*1000000.0/
|
|
+ std::max<std::uint32_t>(1,ResolvedLayout->coreVoxelSizeMicrometres);
|
|
+ const double coreMetres=Q/qPerMetre;
|
|
+ const double radius=std::max(3.0,
|
|
+ static_cast<double>(std::max<std::int64_t>(0,mouthRadiusSubvoxels))
|
|
+ /qPerMetre+1.0);
|
|
+ const double outerRadius=radius+VF_LandingPadBlendRadiusMetres;
|
|
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<double>(
|
|
- chamber.clearHeightSubvoxels)/Q+extent};
|
|
+ const double coneHeight=VF_LandingPadConeSlope
|
|
+ *VF_LandingPadBlendRadiusMetres;
|
|
+ const double low[3]={cx-outerRadius/coreMetres-extent,
|
|
+ cy-outerRadius/coreMetres-extent,
|
|
+ pz-(VF_LandingPadFillDepthMetres+coneHeight)/coreMetres-extent};
|
|
+ const double high[3]={cx+outerRadius/coreMetres+extent,
|
|
+ cy+outerRadius/coreMetres+extent,
|
|
+ pz+(VF_LandingPadHeadroomMetres+coneHeight)/coreMetres+extent};
|
|
return overlaps(low,high);
|
|
};
|
|
- if(chamberOverlaps(passage.upperLandingChamber)
|
|
- ||chamberOverlaps(passage.lowerLandingChamber))return true;
|
|
+ if(padOverlaps(passage.upperLandingChamber,passage.mouthRadiusSubvoxels)
|
|
+ ||padOverlaps(passage.lowerLandingChamber,passage.mouthRadiusSubvoxels))
|
|
+ return true;
|
|
}
|
|
return false;
|
|
}
|
|
@@ -5415,6 +5387,9 @@ private:
|
|
if(!ResolvedLayout||ResolvedLayout->passages.empty())return;
|
|
constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
|
constexpr std::size_t CandidateFineSamplesPerSpan=5;
|
|
+ const double coreMetres=std::max<std::uint32_t>(1,
|
|
+ ResolvedLayout->coreVoxelSizeMicrometres)/1000000.0;
|
|
+ const double qPerMetre=Q/coreMetres;
|
|
const double requiredReachCoreVoxels=VF_PassageInfluenceReachCoreVoxels(
|
|
ResolvedLayout->coreVoxelSizeMicrometres);
|
|
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();
|
|
@@ -5471,40 +5446,47 @@ private:
|
|
std::ceil((localMaximum[axis]+localExtent)*Q));
|
|
}
|
|
}
|
|
- const auto includeLandingChamber=[&](
|
|
- const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& chamber)
|
|
- {
|
|
- const double cx=static_cast<double>(chamber.floorCenterSubvoxels[0])/Q;
|
|
- const double cy=static_cast<double>(chamber.floorCenterSubvoxels[1])/Q;
|
|
- const double cz=static_cast<double>(chamber.floorCenterSubvoxels[2])/Q;
|
|
- const double radius=static_cast<double>(chamber.radiusSubvoxels)/Q;
|
|
- const double gx=static_cast<double>(chamber.floorGradientQ16[0])/65536.0;
|
|
- const double gy=static_cast<double>(chamber.floorGradientQ16[1])/65536.0;
|
|
- const double floorExtent=radius*std::hypot(gx,gy);
|
|
- const double height=static_cast<double>(
|
|
- chamber.clearHeightSubvoxels)/Q;
|
|
- const double extent=requiredReachCoreVoxels+0.01;
|
|
- const std::array<double,3> minimum{
|
|
- cx-radius-extent,cy-radius-extent,cz-floorExtent-extent};
|
|
- const std::array<double,3> maximum{
|
|
- cx+radius+extent,cy+radius+extent,
|
|
- cz+floorExtent+height+extent};
|
|
+ const auto includeLandingPad=[&](
|
|
+ const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& pad,
|
|
+ std::int64_t mouthRadiusSubvoxels)
|
|
+ {
|
|
+ if(pad.radiusSubvoxels<=0)return;
|
|
+ const double cx=static_cast<double>(pad.floorCenterSubvoxels[0])/Q;
|
|
+ const double cy=static_cast<double>(pad.floorCenterSubvoxels[1])/Q;
|
|
+ const double pz=static_cast<double>(pad.floorCenterSubvoxels[2])/Q;
|
|
+ const double radius=std::max(3.0,
|
|
+ static_cast<double>(std::max<std::int64_t>(0,mouthRadiusSubvoxels))
|
|
+ /qPerMetre+1.0);
|
|
+ const double outerRadius=radius+VF_LandingPadBlendRadiusMetres;
|
|
+ const double coneHeight=VF_LandingPadConeSlope
|
|
+ *VF_LandingPadBlendRadiusMetres;
|
|
+ const std::array<double,3> baseMinimum{
|
|
+ cx-outerRadius/coreMetres,
|
|
+ cy-outerRadius/coreMetres,
|
|
+ pz-(VF_LandingPadFillDepthMetres+coneHeight)/coreMetres};
|
|
+ const std::array<double,3> baseMaximum{
|
|
+ cx+outerRadius/coreMetres,
|
|
+ cy+outerRadius/coreMetres,
|
|
+ pz+(VF_LandingPadHeadroomMetres+coneHeight)/coreMetres};
|
|
FPassageProofBound proof;
|
|
+ const double extent=requiredReachCoreVoxels+0.01;
|
|
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];
|
|
+ routeMinimum[axis]=std::min(routeMinimum[axis],baseMinimum[axis]);
|
|
+ routeMaximum[axis]=std::max(routeMaximum[axis],baseMaximum[axis]);
|
|
+ proof.minimum[axis]=baseMinimum[axis]-extent;
|
|
+ proof.maximum[axis]=baseMaximum[axis]+extent;
|
|
}
|
|
PassageProofBounds.push_back(proof);
|
|
};
|
|
if(segmentIndex==0)
|
|
- includeLandingChamber(
|
|
- ResolvedLayout->passages[passageIndex].upperLandingChamber);
|
|
+ includeLandingPad(ResolvedLayout->passages[passageIndex]
|
|
+ .upperLandingChamber,ResolvedLayout->passages[passageIndex]
|
|
+ .mouthRadiusSubvoxels);
|
|
if(segmentEndIndex==segmentCount)
|
|
- includeLandingChamber(
|
|
- ResolvedLayout->passages[passageIndex].lowerLandingChamber);
|
|
+ includeLandingPad(ResolvedLayout->passages[passageIndex]
|
|
+ .lowerLandingChamber,ResolvedLayout->passages[passageIndex]
|
|
+ .mouthRadiusSubvoxels);
|
|
const double routeExtent=maximumRadiusSubvoxels/Q
|
|
+requiredReachCoreVoxels+0.01;
|
|
const double tileExtent=routeExtent;
|
|
@@ -5629,6 +5611,108 @@ private:
|
|
}
|
|
}
|
|
}
|
|
+ for(std::size_t passageIndex=0;
|
|
+ passageIndex<ResolvedLayout->passages.size();++passageIndex)
|
|
+ {
|
|
+ const auto& passage=ResolvedLayout->passages[passageIndex];
|
|
+ const auto sampleLandingPad=[&](
|
|
+ const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& pad)
|
|
+ {
|
|
+ if(pad.radiusSubvoxels<=0)return;
|
|
+ const double centerX=static_cast<double>(
|
|
+ pad.floorCenterSubvoxels[0])/qPerMetre;
|
|
+ const double centerY=static_cast<double>(
|
|
+ pad.floorCenterSubvoxels[1])/qPerMetre;
|
|
+ const double floorP=static_cast<double>(
|
|
+ pad.floorCenterSubvoxels[2])/qPerMetre;
|
|
+ const double radius=std::max(3.0,
|
|
+ static_cast<double>(std::max<std::int64_t>(0,
|
|
+ passage.mouthRadiusSubvoxels))/qPerMetre+1.0);
|
|
+ const double probeRadius=radius+VF_LandingPadBlendRadiusMetres-0.25;
|
|
+ const std::array<double,2> probeHeights{
|
|
+ floorP-0.5*VF_LandingPadFillDepthMetres,
|
|
+ floorP+0.5*VF_LandingPadHeadroomMetres};
|
|
+ for(const double probeZ:probeHeights)
|
|
+ {
|
|
+ const double sampleQ[3]={centerX*qPerMetre,
|
|
+ centerY*qPerMetre,probeZ*qPerMetre};
|
|
+ const double sampleVoxel[3]={sampleQ[0]/Q,
|
|
+ sampleQ[1]/Q,sampleQ[2]/Q};
|
|
+ const float contribution=ApplyResolvedLandingPadStamps(
|
|
+ 0.0f,static_cast<float>(sampleVoxel[0]),
|
|
+ static_cast<float>(sampleVoxel[1]),
|
|
+ static_cast<float>(sampleVoxel[2]),
|
|
+ std::numeric_limits<float>::infinity());
|
|
+ ++sampled;
|
|
+ if(std::abs(contribution)<=1.0e-6f)continue;
|
|
+ ++signRelevant;
|
|
+ if(PassageProofNodes.empty()
|
|
+ ||!PassageProofBoundsOverlapNode(0,sampleVoxel,sampleVoxel))
|
|
+ ++proofMisses;
|
|
+ std::array<std::int64_t,3> sampleMinimum{},sampleMaximum{};
|
|
+ for(std::size_t axis=0;axis<3;++axis)
|
|
+ {
|
|
+ sampleMinimum[axis]=static_cast<std::int64_t>(
|
|
+ std::floor(sampleQ[axis]));
|
|
+ sampleMaximum[axis]=static_cast<std::int64_t>(
|
|
+ std::ceil(sampleQ[axis]));
|
|
+ }
|
|
+ const auto candidates=BuildPassageFeatureTileList(
|
|
+ sampleMinimum,sampleMaximum);
|
|
+ const bool featureContainsPassage=std::any_of(
|
|
+ candidates.begin(),candidates.end(),
|
|
+ [&](std::uint32_t featureIndex)
|
|
+ {
|
|
+ return featureIndex<PassageTileFeatures.size()
|
|
+ &&PassageTileFeatures[featureIndex].passageIndex
|
|
+ ==passageIndex;
|
|
+ });
|
|
+ if(!featureContainsPassage)++featureMisses;
|
|
+
|
|
+ const double edgeSampleQ[3]={
|
|
+ (centerX+probeRadius)*qPerMetre,
|
|
+ centerY*qPerMetre,
|
|
+ (probeZ<=floorP
|
|
+ ?floorP-VF_LandingPadConeSlope
|
|
+ *(probeRadius-radius)-0.125
|
|
+ :floorP+VF_LandingPadConeSlope
|
|
+ *(probeRadius-radius)+0.25)*qPerMetre};
|
|
+ const double edgeSampleVoxel[3]={edgeSampleQ[0]/Q,
|
|
+ edgeSampleQ[1]/Q,edgeSampleQ[2]/Q};
|
|
+ const float edgeContribution=ApplyResolvedLandingPadStamps(
|
|
+ 0.0f,static_cast<float>(edgeSampleVoxel[0]),
|
|
+ static_cast<float>(edgeSampleVoxel[1]),
|
|
+ static_cast<float>(edgeSampleVoxel[2]),
|
|
+ std::numeric_limits<float>::infinity());
|
|
+ ++sampled;
|
|
+ if(std::abs(edgeContribution)<=1.0e-6f)continue;
|
|
+ ++signRelevant;
|
|
+ if(PassageProofNodes.empty()
|
|
+ ||!PassageProofBoundsOverlapNode(0,edgeSampleVoxel,edgeSampleVoxel))
|
|
+ ++proofMisses;
|
|
+ for(std::size_t axis=0;axis<3;++axis)
|
|
+ {
|
|
+ sampleMinimum[axis]=static_cast<std::int64_t>(
|
|
+ std::floor(edgeSampleQ[axis]));
|
|
+ sampleMaximum[axis]=static_cast<std::int64_t>(
|
|
+ std::ceil(edgeSampleQ[axis]));
|
|
+ }
|
|
+ const auto edgeCandidates=BuildPassageFeatureTileList(
|
|
+ sampleMinimum,sampleMaximum);
|
|
+ const bool edgeFeatureContainsPassage=std::any_of(
|
|
+ edgeCandidates.begin(),edgeCandidates.end(),
|
|
+ [&](std::uint32_t featureIndex)
|
|
+ {
|
|
+ return featureIndex<PassageTileFeatures.size()
|
|
+ &&PassageTileFeatures[featureIndex].passageIndex
|
|
+ ==passageIndex;
|
|
+ });
|
|
+ if(!edgeFeatureContainsPassage)++featureMisses;
|
|
+ }
|
|
+ };
|
|
+ sampleLandingPad(passage.upperLandingChamber);
|
|
+ sampleLandingPad(passage.lowerLandingChamber);
|
|
+ }
|
|
const bool auditPassed=sampled>0&&signRelevant>0
|
|
&&proofMisses==0&&featureMisses==0;
|
|
std::printf("[L130PassageReachProofAudit] reach_m=%.3f sampled=%llu sign_relevant=%llu proof_misses=%llu feature_misses=%llu status=%s\n",
|
|
diff --git a/Standalone/Source/StreamBenchmarkPipeline.h b/Standalone/Source/StreamBenchmarkPipeline.h
|
|
index 2fcf4b5..1b502ed 100644
|
|
--- a/Standalone/Source/StreamBenchmarkPipeline.h
|
|
+++ b/Standalone/Source/StreamBenchmarkPipeline.h
|
|
@@ -4703,78 +4703,6 @@ namespace StandaloneStreamBenchmark
|
|
[index](const auto& s){return s.strateIndex==index;});
|
|
return it==ResolvedLayout->strates.end()?nullptr:&*it;
|
|
};
|
|
- const auto landingChamberConnectionContains=[&](
|
|
- const VoxelForgeCore::ResolvedLayoutPassageV1& passage,bool upper,
|
|
- double xMetres,double yMetres,double zMetres)
|
|
- {
|
|
- const auto& chamber=upper?passage.upperLandingChamber
|
|
- :passage.lowerLandingChamber;
|
|
- const auto* stratum=findStratum(upper?passage.upperStrateIndex
|
|
- :passage.lowerStrateIndex);
|
|
- if(!stratum||chamber.radiusSubvoxels<=0)return false;
|
|
- std::vector<const VoxelForgeCore::ResolvedLayoutAnchorV2*> 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;index<route.size();++index)
|
|
- {
|
|
- const double dx=qToMetres(route[index]->values[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<endpointDistanceSquared
|
|
- &&std::abs(z-mouthZ)<=1.5)
|
|
- {endpoint=index;endpointDistanceSquared=distanceSquared;}
|
|
- }
|
|
- if(endpoint==route.size())return false;
|
|
- std::vector<double> routeDistance(route.size(),0.0);
|
|
- for(std::size_t index=1;index<route.size();++index)
|
|
- {
|
|
- const double dx=qToMetres(route[index]->values[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;index<route.size();++index)
|
|
- {
|
|
- const auto& a=*route[index-1];
|
|
- const auto& b=*route[index];
|
|
- const double ax=qToMetres(a.values[0]),ay=qToMetres(a.values[1]);
|
|
- const double az=qToMetres(a.values[2]);
|
|
- const double dx=qToMetres(b.values[0]-a.values[0]);
|
|
- const double dy=qToMetres(b.values[1]-a.values[1]);
|
|
- const double lengthSquared=dx*dx+dy*dy;
|
|
- if(lengthSquared<=1.0e-12)continue;
|
|
- const double t=std::clamp(((xMetres-ax)*dx+(yMetres-ay)*dy)
|
|
- /lengthSquared,0.0,1.0);
|
|
- const double fromEndpoint=std::abs(std::lerp(
|
|
- routeDistance[index-1],routeDistance[index],t)
|
|
- -routeDistance[endpoint]);
|
|
- if(fromEndpoint>9.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;
|
|
@@ -5153,89 +5081,6 @@ namespace StandaloneStreamBenchmark
|
|
if(interior<=0.0f)++audit.interiorViolations;
|
|
}
|
|
}
|
|
- constexpr std::array<double,3> chamberFloorRadii{{0.0,2.5,5.0}};
|
|
- constexpr std::array<double,2> 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<angles;++angleIndex)
|
|
- {
|
|
- const double angle=2.0*3.14159265358979323846
|
|
- *static_cast<double>(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<ChamberAngularSamples;
|
|
- ++angleIndex)
|
|
- {
|
|
- const double angle=2.0*3.14159265358979323846
|
|
- *static_cast<double>(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<std::size_t>::max(),
|
|
- std::numeric_limits<std::size_t>::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,
|
|
@@ -5305,10 +5150,257 @@ namespace StandaloneStreamBenchmark
|
|
}
|
|
return total;
|
|
};
|
|
+ struct FLandingPadAudit
|
|
+ {
|
|
+ std::uint64_t flatSamples=0,flatFailures=0;
|
|
+ std::uint64_t supportSamples=0,supportFailures=0;
|
|
+ std::uint64_t headroomSamples=0,headroomFailures=0;
|
|
+ std::uint64_t blendRootSamples=0,blendMissing=0,blendGradeFailures=0;
|
|
+ std::uint64_t negativeControlsDetected=0;
|
|
+ double maximumFlatnessMetres=0.0,maximumGradeDegrees=0.0;
|
|
+ bool passed=false;
|
|
+ };
|
|
+ const auto auditLandingPad=[&](const VoxelForgeCore::World& field,
|
|
+ const VoxelForgeCore::ResolvedLayoutPassageV1& passage,bool upper)
|
|
+ {
|
|
+ constexpr double ConeSlope=0.2125565616700221;
|
|
+ constexpr double CosTwelveDegrees=0.9781476007338057;
|
|
+ constexpr double FillDepthMetres=1.25,HeadroomMetres=3.0;
|
|
+ constexpr double BlendRadiusMetres=8.0;
|
|
+ constexpr double FlatnessLimitMetres=0.020;
|
|
+ constexpr double GradeLimitDegrees=15.0;
|
|
+ constexpr std::size_t RadialDirections=36;
|
|
+ constexpr double FloorSearchStepMetres=0.125;
|
|
+ FLandingPadAudit audit;
|
|
+ const auto& pad=upper?passage.upperLandingChamber
|
|
+ :passage.lowerLandingChamber;
|
|
+ if(pad.radiusSubvoxels<=0)return audit;
|
|
+ const double centerX=qToMetres(pad.floorCenterSubvoxels[0]);
|
|
+ const double centerY=qToMetres(pad.floorCenterSubvoxels[1]);
|
|
+ const double floorP=qToMetres(pad.floorCenterSubvoxels[2]);
|
|
+ const double radius=std::max(3.0,
|
|
+ qToMetres(std::max<std::int64_t>(0,passage.mouthRadiusSubvoxels))+1.0);
|
|
+ const auto densityMetres=[&](const VoxelForgeCore::World& source,
|
|
+ double x,double y,double z)
|
|
+ {
|
|
+ const float value=source.densityAt({x/coreMetres,y/coreMetres,
|
|
+ z/coreMetres});
|
|
+ return std::isfinite(value)?static_cast<double>(value)*coreMetres
|
|
+ :std::numeric_limits<double>::quiet_NaN();
|
|
+ };
|
|
+ struct FFloorRoot
|
|
+ {double z=0.0;std::uint32_t crossings=0;bool found=false;};
|
|
+ const auto findFloorRoot=[&](auto&& sampleDensity,double x,double y,
|
|
+ double low,double high)
|
|
+ {
|
|
+ FFloorRoot result;
|
|
+ double previousZ=low;
|
|
+ double previous=sampleDensity(x,y,previousZ);
|
|
+ if(!std::isfinite(previous))return result;
|
|
+ for(double z=low+FloorSearchStepMetres;
|
|
+ z<high+FloorSearchStepMetres*0.25;
|
|
+ z=std::min(high,z+FloorSearchStepMetres))
|
|
+ {
|
|
+ const double currentZ=std::min(z,high);
|
|
+ const double current=sampleDensity(x,y,currentZ);
|
|
+ if(!std::isfinite(current))return FFloorRoot{};
|
|
+ if(previous<0.0&¤t>=0.0)
|
|
+ {
|
|
+ const double denominator=current-previous;
|
|
+ const double t=std::abs(denominator)>1.0e-12
|
|
+ ?std::clamp(-previous/denominator,0.0,1.0):0.0;
|
|
+ const double root=std::lerp(previousZ,currentZ,t);
|
|
+ ++result.crossings;
|
|
+ if(!result.found)result.z=root;
|
|
+ result.found=true;
|
|
+ }
|
|
+ previousZ=currentZ;
|
|
+ previous=current;
|
|
+ if(currentZ>=high)break;
|
|
+ }
|
|
+ return result;
|
|
+ };
|
|
+ const auto fieldDensity=[&](double x,double y,double z)
|
|
+ {return densityMetres(field,x,y,z);};
|
|
+ const auto sourceFieldDensity=[&](double x,double y,double z)
|
|
+ {
|
|
+ const float value=Shape.passageSourceDensityAt({x/coreMetres,
|
|
+ y/coreMetres,z/coreMetres});
|
|
+ return std::isfinite(value)?static_cast<double>(value)*coreMetres
|
|
+ :std::numeric_limits<double>::quiet_NaN();
|
|
+ };
|
|
+ struct FFlatProbe {double x=0.0,y=0.0,z=0.0;};
|
|
+ std::vector<FFlatProbe> flatProbes;
|
|
+ const auto probeFlatness=[&](double radial,double angle)
|
|
+ {
|
|
+ const double x=centerX+radial*std::cos(angle);
|
|
+ const double y=centerY+radial*std::sin(angle);
|
|
+ const auto root=findFloorRoot(fieldDensity,x,y,
|
|
+ floorP-0.75,floorP+0.75);
|
|
+ ++audit.flatSamples;
|
|
+ if(!root.found||root.crossings!=1)
|
|
+ {++audit.flatFailures;return;}
|
|
+ const double error=std::abs(root.z-floorP);
|
|
+ audit.maximumFlatnessMetres=std::max(
|
|
+ audit.maximumFlatnessMetres,error);
|
|
+ if(error>FlatnessLimitMetres)++audit.flatFailures;
|
|
+ flatProbes.push_back({x,y,root.z});
|
|
+ };
|
|
+ probeFlatness(0.0,0.0);
|
|
+ for(std::size_t direction=0;direction<RadialDirections;++direction)
|
|
+ {
|
|
+ const double angle=2.0*3.14159265358979323846
|
|
+ *static_cast<double>(direction)/RadialDirections;
|
|
+ probeFlatness(0.90*radius,angle);
|
|
+ }
|
|
+ constexpr std::array<double,3> SupportRadii{{0.0,0.5,0.9}};
|
|
+ for(const double radialFraction:SupportRadii)
|
|
+ for(std::size_t direction=0;direction<RadialDirections;++direction)
|
|
+ {
|
|
+ const double angle=2.0*3.14159265358979323846
|
|
+ *static_cast<double>(direction)/RadialDirections;
|
|
+ const double x=centerX+radialFraction*radius*std::cos(angle);
|
|
+ const double y=centerY+radialFraction*radius*std::sin(angle);
|
|
+ ++audit.supportSamples;
|
|
+ if(!(fieldDensity(x,y,floorP-0.10)<0.0))
|
|
+ ++audit.supportFailures;
|
|
+ ++audit.headroomSamples;
|
|
+ if(!(fieldDensity(x,y,floorP+2.0)>0.0))
|
|
+ ++audit.headroomFailures;
|
|
+ }
|
|
+ constexpr std::size_t BlendStations=16;
|
|
+ const double blendEnd=BlendRadiusMetres-0.25;
|
|
+ for(std::size_t direction=0;direction<RadialDirections;++direction)
|
|
+ {
|
|
+ const double angle=2.0*3.14159265358979323846
|
|
+ *static_cast<double>(direction)/RadialDirections;
|
|
+ bool previousFound=false;
|
|
+ double previousRadius=0.0,previousFloor=0.0;
|
|
+ for(std::size_t station=0;station<=BlendStations;++station)
|
|
+ {
|
|
+ const double radial=radius+blendEnd
|
|
+ *static_cast<double>(station)/BlendStations;
|
|
+ const double x=centerX+radial*std::cos(angle);
|
|
+ const double y=centerY+radial*std::sin(angle);
|
|
+ const double coneRun=std::max(0.0,radial-radius);
|
|
+ const double coneHeight=ConeSlope*coneRun;
|
|
+ const auto root=findFloorRoot(fieldDensity,x,y,
|
|
+ floorP-coneHeight-0.75,floorP+coneHeight+0.75);
|
|
+ ++audit.blendRootSamples;
|
|
+ if(!root.found||root.crossings!=1)
|
|
+ {
|
|
+ ++audit.blendMissing;
|
|
+ previousFound=false;
|
|
+ continue;
|
|
+ }
|
|
+ if(previousFound)
|
|
+ {
|
|
+ const double grade=std::atan2(
|
|
+ std::abs(root.z-previousFloor),radial-previousRadius)
|
|
+ *180.0/3.14159265358979323846;
|
|
+ audit.maximumGradeDegrees=std::max(
|
|
+ audit.maximumGradeDegrees,grade);
|
|
+ if(grade>GradeLimitDegrees)++audit.blendGradeFailures;
|
|
+ }
|
|
+ previousFound=true;
|
|
+ previousRadius=radial;
|
|
+ previousFloor=root.z;
|
|
+ }
|
|
+ }
|
|
+ const auto sourceField=[&](double x,double y,double z)
|
|
+ {return sourceFieldDensity(x,y,z);};
|
|
+ bool padOffRejected=false;
|
|
+ for(std::size_t direction=0;direction<RadialDirections;++direction)
|
|
+ {
|
|
+ const double angle=2.0*3.14159265358979323846
|
|
+ *static_cast<double>(direction)/RadialDirections;
|
|
+ const double x=centerX+0.90*radius*std::cos(angle);
|
|
+ const double y=centerY+0.90*radius*std::sin(angle);
|
|
+ const auto root=findFloorRoot(sourceField,x,y,
|
|
+ floorP-0.75,floorP+0.75);
|
|
+ if(!root.found||root.crossings!=1
|
|
+ ||std::abs(root.z-floorP)>FlatnessLimitMetres)
|
|
+ {padOffRejected=true;break;}
|
|
+ }
|
|
+ const bool tiltRejected=std::any_of(flatProbes.begin(),flatProbes.end(),
|
|
+ [&](const FFlatProbe& probe)
|
|
+ {
|
|
+ return std::abs((probe.z-floorP)
|
|
+ +0.02*(probe.x-centerX))>FlatnessLimitMetres;
|
|
+ });
|
|
+ const auto controlStamp=[&](double blendRadius,double radial,double z,
|
|
+ double native,double tubeDistance)
|
|
+ {
|
|
+ const double coneRun=std::max(0.0,radial-radius);
|
|
+ const double lower=floorP-ConeSlope*coneRun;
|
|
+ const double upper=floorP+ConeSlope*coneRun;
|
|
+ const double outer=radius+blendRadius;
|
|
+ const double fill=std::max({CosTwelveDegrees*(z-lower),
|
|
+ CosTwelveDegrees*(lower-FillDepthMetres-z),radial-outer});
|
|
+ const double air=std::min({CosTwelveDegrees*(z-upper),
|
|
+ CosTwelveDegrees*(upper+HeadroomMetres-z),outer-radial});
|
|
+ const double masked=std::max(fill,-tubeDistance);
|
|
+ const double smoothWidth=0.5*std::clamp(radial-radius,0.0,1.0);
|
|
+ double smoothMinimum=std::min(native,masked);
|
|
+ if(smoothWidth>0.0)
|
|
+ {
|
|
+ const double h=std::max(smoothWidth
|
|
+ -std::abs(native-masked),0.0)/smoothWidth;
|
|
+ smoothMinimum-=h*h*h*(smoothWidth/6.0);
|
|
+ }
|
|
+ return std::max(smoothMinimum,air);
|
|
+ };
|
|
+ const double outerProbe=radius+BlendRadiusMetres-0.25;
|
|
+ const double outerProbeZ=floorP-ConeSlope*(outerProbe-radius)-0.05;
|
|
+ const double fullBlendProbe=controlStamp(BlendRadiusMetres,
|
|
+ outerProbe,outerProbeZ,0.0,std::numeric_limits<double>::infinity());
|
|
+ const double halfBlendProbe=controlStamp(BlendRadiusMetres*0.5,
|
|
+ outerProbe,outerProbeZ,0.0,std::numeric_limits<double>::infinity());
|
|
+ const bool halfBlendRejected=fullBlendProbe< -0.01
|
|
+ &&std::abs(halfBlendProbe)<1.0e-6;
|
|
+ const double boreMasked=controlStamp(BlendRadiusMetres,
|
|
+ radius*0.5,floorP-0.25,0.25,-0.50);
|
|
+ const double boreUnmasked=controlStamp(BlendRadiusMetres,
|
|
+ radius*0.5,floorP-0.25,0.25,
|
|
+ std::numeric_limits<double>::infinity());
|
|
+ const bool boreMaskRejected=boreMasked>0.0&&boreUnmasked<0.0;
|
|
+ audit.negativeControlsDetected=static_cast<std::uint64_t>(padOffRejected)
|
|
+ +static_cast<std::uint64_t>(tiltRejected)
|
|
+ +static_cast<std::uint64_t>(halfBlendRejected)
|
|
+ +static_cast<std::uint64_t>(boreMaskRejected);
|
|
+ audit.passed=audit.flatSamples>0&&audit.flatFailures==0
|
|
+ &&audit.supportSamples>0&&audit.supportFailures==0
|
|
+ &&audit.headroomSamples>0&&audit.headroomFailures==0
|
|
+ &&audit.blendRootSamples>0&&audit.blendMissing==0
|
|
+ &&audit.blendGradeFailures==0
|
|
+ &&audit.negativeControlsDetected==4;
|
|
+ std::printf("[LandingPadAudit] id=%s side=%s radius_m=%.3f blend_m=%.3f flatness_samples=%llu flatness_max_m=%.5f flatness_failures=%llu support=%llu/%llu headroom=%llu/%llu blend_transects=%zu blend_roots=%llu blend_missing=%llu blend_grade_max_deg=%.3f blend_grade_failures=%llu negative_controls=%llu/4 pad_off=%s tilt_2pct=%s blend_B_half=%s bore_mask_removed=%s status=%s\n",
|
|
+ passage.stableId.c_str(),upper?"upper":"lower",radius,
|
|
+ BlendRadiusMetres,static_cast<unsigned long long>(audit.flatSamples),
|
|
+ audit.maximumFlatnessMetres,
|
|
+ static_cast<unsigned long long>(audit.flatFailures),
|
|
+ static_cast<unsigned long long>(audit.supportSamples
|
|
+ -audit.supportFailures),
|
|
+ static_cast<unsigned long long>(audit.supportSamples),
|
|
+ static_cast<unsigned long long>(audit.headroomSamples
|
|
+ -audit.headroomFailures),
|
|
+ static_cast<unsigned long long>(audit.headroomSamples),
|
|
+ RadialDirections,
|
|
+ static_cast<unsigned long long>(audit.blendRootSamples),
|
|
+ static_cast<unsigned long long>(audit.blendMissing),
|
|
+ audit.maximumGradeDegrees,
|
|
+ static_cast<unsigned long long>(audit.blendGradeFailures),
|
|
+ static_cast<unsigned long long>(audit.negativeControlsDetected),
|
|
+ padOffRejected?"detected":"MISSED",tiltRejected?"detected":"MISSED",
|
|
+ halfBlendRejected?"detected":"MISSED",
|
|
+ boreMaskRejected?"detected":"MISSED",audit.passed?"PASS":"FAIL");
|
|
+ return audit;
|
|
+ };
|
|
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 landingPadCount=0,landingPadPasses=0,landingPadControls=0;
|
|
std::uint64_t mouthProfilePasses=0,ripplePasses=0,rippleControlsDetected=0;
|
|
std::uint64_t floorRippleControlsDetected=0,floorStepRippleControlsDetected=0;
|
|
std::uint64_t shellAudits=0,shellFloorSamples=0,shellFloorViolations=0;
|
|
@@ -5346,6 +5438,13 @@ namespace StandaloneStreamBenchmark
|
|
passage,passageIndex,true);
|
|
const FWalkAudit lowerChamberAudit=auditLandingChamber(Shape,
|
|
passage,passageIndex,false);
|
|
+ const FLandingPadAudit upperPadAudit=auditLandingPad(Shape,passage,true);
|
|
+ const FLandingPadAudit lowerPadAudit=auditLandingPad(Shape,passage,false);
|
|
+ landingPadCount+=2;
|
|
+ landingPadPasses+=static_cast<std::uint64_t>(upperPadAudit.passed)
|
|
+ +static_cast<std::uint64_t>(lowerPadAudit.passed);
|
|
+ landingPadControls+=upperPadAudit.negativeControlsDetected
|
|
+ +lowerPadAudit.negativeControlsDetected;
|
|
const bool landingChambersWalkable=upperChamberAudit.passed
|
|
&&lowerChamberAudit.passed;
|
|
const bool passageInteriorAirPassed=
|
|
@@ -5426,6 +5525,7 @@ namespace StandaloneStreamBenchmark
|
|
floorRippleControlsDetected+=rippleAudit.floorControlDetected;
|
|
floorStepRippleControlsDetected+=rippleAudit.floorStepControlDetected;
|
|
passagePasses+=passageAudit.passed&&landingChambersWalkable
|
|
+ &&upperPadAudit.passed&&lowerPadAudit.passed
|
|
&&passageInteriorAirPassed
|
|
&&mouthsInRadius&&mouthsInContent
|
|
&&naturalAudit.passed&&mouthProfileAudit.passed&&rippleAudit.passed;
|
|
@@ -5889,6 +5989,13 @@ namespace StandaloneStreamBenchmark
|
|
static_cast<unsigned long long>(landingChamberCount),
|
|
static_cast<unsigned long long>(landingChamberCount
|
|
-landingChamberNegativeControls));
|
|
+ std::printf("[LandingPadAuditSummary] pads=%llu passed=%llu negative_controls=%llu/%llu status=%s\n",
|
|
+ static_cast<unsigned long long>(landingPadCount),
|
|
+ static_cast<unsigned long long>(landingPadPasses),
|
|
+ static_cast<unsigned long long>(landingPadControls),
|
|
+ static_cast<unsigned long long>(landingPadCount*4),
|
|
+ landingPadCount>0&&landingPadPasses==landingPadCount
|
|
+ &&landingPadControls==landingPadCount*4?"PASS":"FAIL");
|
|
const bool shellAuditPassed=shellAudits==passageCount
|
|
&&shellFloorViolations==0&&shellWallViolations==0
|
|
&&shellChamberFloorViolations==0&&shellChamberWallViolations==0
|
|
@@ -5935,6 +6042,8 @@ namespace StandaloneStreamBenchmark
|
|
ripplePasses==passageCount?"PASS":"FAIL");
|
|
const bool auditPassed=passageCount>0&&passagePasses==passageCount
|
|
&&passageInteriorAuditPassed
|
|
+ &&landingPadCount>0&&landingPadPasses==landingPadCount
|
|
+ &&landingPadControls==landingPadCount*4
|
|
&&naturalPasses==passageCount&&l23SmoothWallDetected
|
|
&&mouthProfilePasses==passageCount&&l23TerraceControlDetected
|
|
&&ripplePasses==passageCount
|