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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
1 parent
9ac60d5d18
commit
5bd158d4eb
21 files changed
+1949
-105
No files matched your search
@@ -565,7 +565,21 @@ namespace
|
||||
<<"],\"lower_mouth_q\":["<<p.lowerMouthSubvoxels[0]<<','
|
||||
<<p.lowerMouthSubvoxels[1]<<','<<p.lowerMouthSubvoxels[2]
|
||||
<<"],\"mouth_radius_q\":"<<p.mouthRadiusSubvoxels
|
||||
<<",\"mid_radius_q\":"<<p.midRadiusSubvoxels<<",\"control_points\":[";
|
||||
<<",\"mid_radius_q\":"<<p.midRadiusSubvoxels;
|
||||
const auto writeLandingChamber=[&](const char* name,
|
||||
const ResolvedLayoutPassageLandingChamberV1& chamber)
|
||||
{
|
||||
out<<",\""<<name<<"\":{\"floor_center_q\":["
|
||||
<<chamber.floorCenterSubvoxels[0]<<','
|
||||
<<chamber.floorCenterSubvoxels[1]<<','
|
||||
<<chamber.floorCenterSubvoxels[2]<<"],\"floor_gradient_q16\":["
|
||||
<<chamber.floorGradientQ16[0]<<','<<chamber.floorGradientQ16[1]
|
||||
<<"],\"radius_q\":"<<chamber.radiusSubvoxels
|
||||
<<",\"clear_height_q\":"<<chamber.clearHeightSubvoxels<<'}';
|
||||
};
|
||||
writeLandingChamber("upper_landing_chamber",p.upperLandingChamber);
|
||||
writeLandingChamber("lower_landing_chamber",p.lowerLandingChamber);
|
||||
out<<",\"control_points\":[";
|
||||
for(std::size_t j=0;j<p.controlPoints.size();++j)
|
||||
{
|
||||
if(j)out<<',';const auto& point=p.controlPoints[j];
|
||||
@@ -1095,6 +1109,62 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
||||
if(!insideContent(*upper,passage.upperMouthSubvoxels)
|
||||
||!insideContent(*lower,passage.lowerMouthSubvoxels))
|
||||
add("passage_mouth_content",passage.stableId+" mouth is outside its owned content band");
|
||||
const auto validateLandingChamber=[&](
|
||||
const ResolvedLayoutPassageLandingChamberV1& chamber,
|
||||
const ResolvedLayoutStratumV2& host,
|
||||
const std::array<std::int64_t,3>& 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::int64_t>(
|
||||
std::llround(playerCenterHeight*WorldAssemblySubvoxelsPerVoxel
|
||||
*1000000.0/std::max<std::uint32_t>(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<passage.mouthRadiusSubvoxels
|
||||
||chamber.clearHeightSubvoxels<static_cast<std::int64_t>(
|
||||
std::llround(2.2*WorldAssemblySubvoxelsPerVoxel*1000000.0
|
||||
/std::max<std::uint32_t>(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<std::uint64_t>(
|
||||
chamber.floorGradientQ16[0]*chamber.floorGradientQ16[0]
|
||||
+chamber.floorGradientQ16[1]*chamber.floorGradientQ16[1]);
|
||||
const std::int64_t gradientMagnitude=static_cast<std::int64_t>(
|
||||
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<std::int64_t>(
|
||||
(chamber.radiusSubvoxels*gradientMagnitude+65535)/65536);
|
||||
const std::int64_t shellQ=static_cast<std::int64_t>(std::llround(
|
||||
0.75*WorldAssemblySubvoxelsPerVoxel*1000000.0
|
||||
/std::max<std::uint32_t>(1,coreVoxelSizeMicrometres)));
|
||||
const auto qToUm=[&](std::int64_t q)
|
||||
{return q*static_cast<std::int64_t>(coreVoxelSizeMicrometres)
|
||||
/WorldAssemblySubvoxelsPerVoxel;};
|
||||
if(!host.content.presentInCurrentField
|
||||
||qToUm(chamber.floorCenterSubvoxels[2]-planeReachQ-shellQ)
|
||||
<host.content.bottomMicrometres
|
||||
||qToUm(chamber.floorCenterSubvoxels[2]+planeReachQ
|
||||
+chamber.clearHeightSubvoxels+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::int64_t>(std::llround(1.13*WorldAssemblySubvoxelsPerVoxel
|
||||
@@ -1730,6 +1800,21 @@ std::optional<ResolvedLayoutSnapshotV3> ResolvedLayoutSnapshotV3::FromJson(
|
||||
const auto& lower=v.Get("lower_mouth_q").AsArray();
|
||||
for(std::size_t axis=0;axis<3&&axis<upper.size();++axis)p.upperMouthSubvoxels[axis]=upper[axis].Integer();
|
||||
for(std::size_t axis=0;axis<3&&axis<lower.size();++axis)p.lowerMouthSubvoxels[axis]=lower[axis].Integer();
|
||||
const auto readLandingChamber=[&](const char* key,
|
||||
ResolvedLayoutPassageLandingChamberV1& chamber)
|
||||
{
|
||||
const auto& value=v.Get(key);
|
||||
const auto& center=value.Get("floor_center_q").AsArray();
|
||||
const auto& gradient=value.Get("floor_gradient_q16").AsArray();
|
||||
for(std::size_t axis=0;axis<3&&axis<center.size();++axis)
|
||||
chamber.floorCenterSubvoxels[axis]=center[axis].Integer();
|
||||
for(std::size_t axis=0;axis<2&&axis<gradient.size();++axis)
|
||||
chamber.floorGradientQ16[axis]=gradient[axis].Integer();
|
||||
chamber.radiusSubvoxels=LayoutInt(value,"radius_q");
|
||||
chamber.clearHeightSubvoxels=LayoutInt(value,"clear_height_q");
|
||||
};
|
||||
readLandingChamber("upper_landing_chamber",p.upperLandingChamber);
|
||||
readLandingChamber("lower_landing_chamber",p.lowerLandingChamber);
|
||||
for(const auto& q:v.Get("control_points").AsArray())
|
||||
{
|
||||
ResolvedLayoutPassagePointV1 point;const auto& position=q.Get("position_q").AsArray();
|
||||
@@ -2367,7 +2452,6 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
||||
constexpr std::int64_t MinimumBoreRadius=1750000;
|
||||
constexpr std::int64_t MinimumBoreHeight=2200000;
|
||||
constexpr std::int64_t PlannedBoreHeight=4500000;
|
||||
constexpr std::int64_t MouthOverlap=2000000;
|
||||
constexpr std::int64_t PortalLeadLength=6000000;
|
||||
constexpr std::int64_t MaximumBoreRadius=10000000;
|
||||
constexpr std::int64_t MaximumWalkGradeDenominator=5; // 1:5 = 11.31 degrees.
|
||||
@@ -2394,13 +2478,13 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
||||
const auto qToMicrometres=[&](std::int64_t q)
|
||||
{return q*static_cast<std::int64_t>(layout.coreVoxelSizeMicrometres)
|
||||
/WorldAssemblySubvoxelsPerVoxel;};
|
||||
const std::int64_t mouthBoreRadius=std::clamp<std::int64_t>(
|
||||
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<std::int64_t,2>& authoredRouteTangentQ16,
|
||||
std::int64_t authoredFloorSlopeQ16,const FPoint& mouthFloorPoint)
|
||||
std::int64_t authoredFloorSlopeQ16,const FPoint& mouthFloorPoint,
|
||||
std::int64_t chamberRadiusMicrometres)
|
||||
{
|
||||
constexpr std::array<std::array<std::int64_t,2>,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<std::int64_t>(
|
||||
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<std::int64_t,2>& 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<std::int64_t>(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;nodeIndex<nodes.size();++nodeIndex)
|
||||
{
|
||||
const auto& node=nodes[nodeIndex];
|
||||
const std::string edgeId=routePrefix+"route-edge-"+std::to_string(nodeIndex);
|
||||
auto edge=makeEdge(edgeId,previousAnchor,previousPoint,node,
|
||||
selectedBoreRadius(edgeId),
|
||||
nodeIndex==0?std::min(selectedBoreRadius(edgeId),LandingConnectorBoreRadius)
|
||||
:selectedBoreRadius(edgeId),
|
||||
previousProfileDistance,node.floorProfileDistanceMicrometres);
|
||||
edges.push_back(std::move(edge));
|
||||
previousPoint={node.point[0],node.point[1],node.point[2]};
|
||||
@@ -3407,8 +3493,10 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
||||
if(!nodes.empty()&&departurePassage)
|
||||
{
|
||||
const std::string departureEdgeId=routePrefix+"departure-edge";
|
||||
auto edge=makeEdge(departureEdgeId,departurePassage->stableId+"/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<std::int64_t,2> 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<std::array<std::int64_t,3>> lowerLandingPoints;
|
||||
if(hasLowerTangent)
|
||||
{
|
||||
std::array<std::int64_t,3> 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<std::int64_t,3> 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<std::int64_t>(remainingQ,
|
||||
static_cast<std::int64_t>(segmentLength));
|
||||
std::array<std::int64_t,3> landingPoint{};
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
landingPoint[axis]=previousRoutePoint[axis]+PassageRoundRatio(
|
||||
routePoint[axis]-previousRoutePoint[axis],advance,
|
||||
static_cast<std::int64_t>(segmentLength));
|
||||
if(!radialWithin(landingPoint,placementRadius))break;
|
||||
lowerLandingPoints.push_back(landingPoint);
|
||||
pushXY({landingPoint[0],landingPoint[1]});
|
||||
previousRoutePoint=landingPoint;remainingQ-=advance;
|
||||
if(advance<static_cast<std::int64_t>(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<std::int64_t>(8*qPerMetre,2*mouthRadius));
|
||||
for(std::size_t i=0;i<xy.size();++i)
|
||||
{
|
||||
std::int64_t radius=(i==0||i==lowerMouthIndex
|
||||
||(i>lowerMouthIndex&&i+1>=xy.size()-1))?mouthRadius:midRadius;
|
||||
std::int64_t radius=(i==0||i==lowerMouthIndex)?mouthRadius:midRadius;
|
||||
if(surfaceGroundDeparture&&i<lowerMouthIndex)
|
||||
{
|
||||
const double t=std::clamp(static_cast<double>(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<std::int64_t>(cumulative[i]),
|
||||
static_cast<std::int64_t>(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<VoxelForgeCore::PassageSpline::FPoint> chamberFloorControls;
|
||||
std::vector<double> chamberFloorProfile;
|
||||
chamberFloorControls.reserve(passage.controlPoints.size());
|
||||
chamberFloorProfile.reserve(passage.controlPoints.size());
|
||||
for(const auto& point:passage.controlPoints)
|
||||
{
|
||||
chamberFloorControls.push_back({
|
||||
static_cast<double>(point.positionSubvoxels[0]),
|
||||
static_cast<double>(point.positionSubvoxels[1]),
|
||||
static_cast<double>(point.positionSubvoxels[2]),
|
||||
static_cast<double>(point.radiusSubvoxels)});
|
||||
chamberFloorProfile.push_back(static_cast<double>(
|
||||
point.positionSubvoxels[2]-point.radiusSubvoxels));
|
||||
}
|
||||
const auto routePoints=[&](const ResolvedLayoutStratumV2& host)
|
||||
{
|
||||
std::vector<std::array<double,3>> route;
|
||||
for(const auto& anchor:host.anchors)
|
||||
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
|
||||
route.push_back({static_cast<double>(anchor.values[0]),
|
||||
static_cast<double>(anchor.values[1]),
|
||||
static_cast<double>(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<std::int64_t,3>& 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::int64_t>(
|
||||
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::int64_t>(
|
||||
std::llround(chamberFloorProfile[endpointIndex]));
|
||||
const std::int64_t neighborFloor=static_cast<std::int64_t>(
|
||||
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<std::array<double,3>> 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<double>(
|
||||
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<double,3> 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::int32_t>(std::llround(gradientX*65536.0)),
|
||||
static_cast<std::int32_t>(std::llround(gradientY*65536.0))};
|
||||
}
|
||||
}
|
||||
const std::uint64_t gradientSquared=static_cast<std::uint64_t>(
|
||||
chamber.floorGradientQ16[0]*chamber.floorGradientQ16[0]
|
||||
+chamber.floorGradientQ16[1]*chamber.floorGradientQ16[1]);
|
||||
const std::int64_t gradientMagnitude=static_cast<std::int64_t>(
|
||||
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::int64_t>(
|
||||
std::llround(0.75*qPerMetre));
|
||||
const auto qToUm=[&](std::int64_t q)
|
||||
{return q*static_cast<std::int64_t>(layout.coreVoxelSizeMicrometres)
|
||||
/WorldAssemblySubvoxelsPerVoxel;};
|
||||
if(!host.content.presentInCurrentField
|
||||
||qToUm(chamber.floorCenterSubvoxels[2]-planeReachQ-shellQ)
|
||||
<host.content.bottomMicrometres
|
||||
||qToUm(chamber.floorCenterSubvoxels[2]+planeReachQ
|
||||
+chamber.clearHeightSubvoxels+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<double>(qPerMetre),
|
||||
passage.lowerLandingChamber.radiusSubvoxels/static_cast<double>(qPerMetre),
|
||||
static_cast<long long>(passage.upperLandingChamber.floorGradientQ16[0]),
|
||||
static_cast<long long>(passage.upperLandingChamber.floorGradientQ16[1]),
|
||||
static_cast<long long>(passage.lowerLandingChamber.floorGradientQ16[0]),
|
||||
static_cast<long long>(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<ResolvedLayoutIssueV2> 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())
|
||||
|
||||
Reference in new issue
Block a user