core: Vertical Shafts on the shared Core cave planner (L139, v237; 15/16 seeds)

Shaft mode reuses PlanResolvedCavernNetworkV1 with a capped full-height cylinder primitive (shapeKind=1), ledges,
access bores and cross links; 11 VerticalShaftParams fields mapped. Basal law: arrival 2-3 to a walkable terminal
landing; drops/climbs stay reporting tiers. Seed 2 fails shell/seams at the 2-3 arrival mouth (3 variants tried,
stopped per AGENTS.md). Whole view faster than assembler/world in 8/8 cells; near-ready slower in 7/8.
Source/: VoxelForgeCoreAdapter.cpp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-11-01 05:34:00 +01:00
1 parent fa4417aad0
commit 318c5cf4f2
31 files changed
+1719 -106

No files matched your search

+263 -31
View File
@@ -626,8 +626,12 @@ namespace
<<",\"floor_profile_distance_um\":"<<room.floorProfileDistanceMicrometres
<<",\"floor_gradient_q16\":["<<room.floorGradientQ16[0]<<','
<<room.floorGradientQ16[1]<<']'
<<",\"smooth_width_um\":"<<room.smoothWidthMicrometres
;
<<",\"smooth_width_um\":"<<room.smoothWidthMicrometres;
if(room.shapeKind!=0)
out<<",\"shape_kind\":"<<static_cast<unsigned>(room.shapeKind)
<<",\"ledge_spacing_um\":"<<room.ledgeSpacingMicrometres
<<",\"ledge_depth_um\":"<<room.ledgeDepthMicrometres
<<",\"ledge_side\":"<<static_cast<unsigned>(room.ledgeSide);
if(room.unionWidthMicrometres>0)
out<<",\"union_width_um\":"<<room.unionWidthMicrometres;
out<<",\"irregularity_amplitude_um\":"<<room.irregularityAmplitudeMicrometres
@@ -1294,7 +1298,8 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
"cavern graph vocabulary version is set without graph records");
}
else if(cavernGraphVocabularyVersion!=ResolvedLayoutCavernGraphVocabularyVersionV1
&&cavernGraphVocabularyVersion!=ResolvedLayoutCavernGraphVocabularyVersionV2)
&&cavernGraphVocabularyVersion!=ResolvedLayoutCavernGraphVocabularyVersionV2
&&cavernGraphVocabularyVersion!=ResolvedLayoutCavernGraphVocabularyVersionV3)
add("cavern_graph_vocabulary_version",
"cavern graph records require a supported vocabulary version");
if(cavernGraphVocabularyVersion!=0)
@@ -1327,6 +1332,33 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
||(room.irregularityAmplitudeMicrometres>0
&&room.irregularityWavelengthMicrometres<=0))
add("cavern_room_parameters",room.stableId+" has invalid or unbounded parameters");
const auto* roomStratum=findStratum(room.strateIndex);
if(room.shapeKind>1)
add("cavern_room_shape",room.stableId+" has an unknown shape kind");
if(room.shapeKind==1)
{
const bool verticalShaftStratum=roomStratum
&&roomStratum->generatorKind=="VerticalShafts"
&&IsNativeCaveFieldStratum(*roomStratum);
if(cavernGraphVocabularyVersion<ResolvedLayoutCavernGraphVocabularyVersionV3
||!verticalShaftStratum
||room.halfExtentsMicrometres[0]!=room.halfExtentsMicrometres[1]
||room.ledgeSpacingMicrometres<0
||room.ledgeSpacingMicrometres>100000000
||room.ledgeDepthMicrometres<0
||room.ledgeDepthMicrometres>=room.halfExtentsMicrometres[0]
||room.ledgeSide>3
||room.floorMicrometres
!=room.centerMicrometres[2]+room.halfExtentsMicrometres[2]
||!room.floorProfileStableId.empty()
||room.floorGradientQ16[0]!=0||room.floorGradientQ16[1]!=0)
add("cavern_shaft_parameters",room.stableId+
" has invalid native shaft bounds, ledges, or recipe binding");
}
else if(room.ledgeSpacingMicrometres!=0||room.ledgeDepthMicrometres!=0
||room.ledgeSide!=0)
add("cavern_room_shape",room.stableId+
" carries shaft parameters on an ordinary room");
const std::int64_t gradientSquared=static_cast<std::int64_t>(room.floorGradientQ16[0])
*room.floorGradientQ16[0]+static_cast<std::int64_t>(room.floorGradientQ16[1])
*room.floorGradientQ16[1];
@@ -1415,11 +1447,37 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
if(room!=cavernRooms.end())
{
const auto& endpoint=edge.controlPointsMicrometres.back();
bool validRoomEndpoint=false;
if(room->shapeKind==1)
{
const std::int64_t shaftBottom=room->centerMicrometres[2]
-room->halfExtentsMicrometres[2];
const std::int64_t shaftTop=room->centerMicrometres[2]
+room->halfExtentsMicrometres[2];
const bool insideShaft=endpoint[2]>=shaftBottom
&&endpoint[2]<=shaftTop;
bool onLanding=room->ledgeSpacingMicrometres==0;
if(room->ledgeSpacingMicrometres>0)
{
const std::int64_t ledgeIndex=(shaftTop-endpoint[2]
+room->ledgeSpacingMicrometres/2)
/room->ledgeSpacingMicrometres;
const std::int64_t ledgeZ=shaftTop
-ledgeIndex*room->ledgeSpacingMicrometres;
const std::int64_t maximumLedge=(shaftTop-shaftBottom-1000000)
/room->ledgeSpacingMicrometres;
onLanding=ledgeIndex>=1&&ledgeIndex<=maximumLedge
&&std::abs(endpoint[2]-ledgeZ)<=500000;
}
validRoomEndpoint=insideShaft&&onLanding;
}
else validRoomEndpoint=
std::abs(endpoint[2]-room->floorMicrometres)<=500000;
if(endpoint[0]<room->centerMicrometres[0]-room->halfExtentsMicrometres[0]
||endpoint[0]>room->centerMicrometres[0]+room->halfExtentsMicrometres[0]
||endpoint[1]<room->centerMicrometres[1]-room->halfExtentsMicrometres[1]
||endpoint[1]>room->centerMicrometres[1]+room->halfExtentsMicrometres[1]
||std::abs(endpoint[2]-room->floorMicrometres)>500000)
||!validRoomEndpoint)
{
add("cavern_edge_portal",edge.stableId+" endpoint_um=("
+std::to_string(endpoint[0])+","+std::to_string(endpoint[1])+","+
@@ -1733,6 +1791,10 @@ std::optional<ResolvedLayoutSnapshotV3> ResolvedLayoutSnapshotV3::FromJson(
room.irregularityAmplitudeMicrometres=LayoutInt(v,"irregularity_amplitude_um");
room.irregularityWavelengthMicrometres=LayoutInt(v,"irregularity_wavelength_um");
room.irregularitySeed=static_cast<std::uint64_t>(LayoutInt(v,"irregularity_seed"));
room.shapeKind=static_cast<std::uint8_t>(LayoutInt(v,"shape_kind"));
room.ledgeSpacingMicrometres=LayoutInt(v,"ledge_spacing_um");
room.ledgeDepthMicrometres=LayoutInt(v,"ledge_depth_um");
room.ledgeSide=static_cast<std::uint8_t>(LayoutInt(v,"ledge_side"));
s.cavernRooms.push_back(std::move(room));
}
for(const auto& v:root.Get("cavern_edges").AsArray())
@@ -2088,7 +2150,11 @@ bool ResolveResolvedCavernGraphV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
if(!candidate.finalize(error))return false;
layout=std::move(candidate);return true;
}
candidate.cavernGraphVocabularyVersion=ResolvedLayoutCavernGraphVocabularyVersionV2;
const bool hasVerticalShaft=std::any_of(rooms.begin(),rooms.end(),
[](const auto& room){return room.shapeKind==1;});
candidate.cavernGraphVocabularyVersion=hasVerticalShaft
?ResolvedLayoutCavernGraphVocabularyVersionV3
:ResolvedLayoutCavernGraphVocabularyVersionV2;
candidate.cavernRooms.assign(rooms.begin(),rooms.end());
candidate.cavernEdges.assign(edges.begin(),edges.end());
const auto isNativeCavesStrate=[&candidate](std::int32_t strateIndex)
@@ -2268,7 +2334,11 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
||settings.irregularityAmplitudeMicrometres<0
||settings.irregularityAmplitudeMicrometres>10000000
||(settings.irregularityAmplitudeMicrometres>0
&&settings.irregularityWavelengthMicrometres<=0))
&&settings.irregularityWavelengthMicrometres<=0)
||(settings.ledgeSpacingMicrometres<0
||settings.ledgeSpacingMicrometres>100000000)
||settings.ledgeDepthMicrometres<0
||settings.ledgeDepthMicrometres>5000000)
{error="cavern planner settings are outside the bounded room, tunnel or relief ranges";return false;}
const ResolvedLayoutPassageV1* arrivalPassage=nullptr;
@@ -2282,12 +2352,17 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
&&(!departurePassage||passage.stableId<departurePassage->stableId))
departurePassage=&passage;
}
if(!arrivalPassage||!departurePassage)
{error="cavern planner needs an incoming lower mouth and an outgoing upper mouth in the target strate";return false;}
const bool terminalShaftRoute=settings.verticalShaftMode
&&stratum->generatorKind=="VerticalShafts"&&!departurePassage;
if(!arrivalPassage||(!departurePassage&&!terminalShaftRoute)
||(settings.verticalShaftMode&&!terminalShaftRoute))
{error="cavern planner needs an incoming mouth and either an outgoing mouth or a Vertical Shafts basal terminal";return false;}
if(layout.coreVoxelSizeMicrometres==0)
{error="cavern planner requires a nonzero Core voxel size";return false;}
using FPoint=std::array<std::int64_t,3>;
const std::string routePrefix=(terminalShaftRoute?"vertical-shafts/strate-"
:"tunnel-network/strate-")+std::to_string(strateIndex)+"/";
constexpr std::int64_t MicrometresPerMetre=1000000;
constexpr std::int64_t MinimumBoreRadius=1750000;
constexpr std::int64_t MinimumBoreHeight=2200000;
@@ -2490,19 +2565,47 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
std::array<std::int64_t,2> arrivalTangentQ16{},departureTangentQ16{};
std::int64_t arrivalFloorSlopeQ16=0,departureFloorSlopeQ16=0;
if(!mouthInnerPoint(*arrivalPassage,true,routeStart,arrivalTangentQ16,
arrivalFloorSlopeQ16)
||!mouthInnerPoint(*departurePassage,false,routeEnd,departureTangentQ16,
arrivalFloorSlopeQ16))
{error="cavern passage mouth is missing a usable horizontal route tangent";return false;}
if(!terminalShaftRoute
&&!mouthInnerPoint(*departurePassage,false,routeEnd,departureTangentQ16,
departureFloorSlopeQ16))
{error="cavern passage mouth is missing a usable horizontal route tangent";return false;}
if(terminalShaftRoute)
{
departureTangentQ16=arrivalTangentQ16;
routeEnd[2]=stratum->content.bottomMicrometres+3000000;
departureFloorSlopeQ16=0;
}
const std::int64_t dx=routeEnd[0]-routeStart[0];
const std::int64_t dy=routeEnd[1]-routeStart[1];
std::int64_t dx=routeEnd[0]-routeStart[0];
std::int64_t dy=routeEnd[1]-routeStart[1];
const std::int64_t dz=routeEnd[2]-routeStart[2];
const std::int64_t horizontalBias=std::clamp<std::int64_t>(
settings.tunnelHorizontalBiasQ16,0,65536);
const std::int64_t planGradeDenominator=MaximumWalkGradeDenominator
+horizontalBias/32768;
const std::int64_t requiredLength=(std::abs(dz)*planGradeDenominator*130)/100+1000;
if(terminalShaftRoute)
{
// The basal stratum has no next mouth by design. The walk-only spine starts
// at its incoming passage and ends on a supported landing above bedrock.
// Optional open shafts are side features with separate climb/fall reporting.
// Normalize by the truncated Q16 tangent magnitude: dividing by 65536
// shortens non-cardinal vectors enough to miss the strict grade-run bound.
const std::uint64_t tangentSquared=static_cast<std::uint64_t>(
departureTangentQ16[0]*departureTangentQ16[0]
+departureTangentQ16[1]*departureTangentQ16[1]);
const std::int64_t tangentMagnitudeQ16=static_cast<std::int64_t>(
integerSqrt(tangentSquared));
if(tangentMagnitudeQ16<=0)
{error="cavern planner terminal tangent has zero horizontal length";return false;}
const std::int64_t terminalProjectionLength=requiredLength+1000;
for(std::size_t axis=0;axis<2;++axis)
routeEnd[axis]=routeStart[axis]
+departureTangentQ16[axis]*terminalProjectionLength/tangentMagnitudeQ16;
dx=routeEnd[0]-routeStart[0];dy=routeEnd[1]-routeStart[1];
}
std::int64_t pathAxisX=dx,pathAxisY=dy;
FPoint routeLeadStart=routeStart,routeLeadEnd=routeEnd;
for(std::size_t axis=0;axis<2;++axis)
@@ -2648,7 +2751,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
}
};
appendPassageClearanceSpans(*arrivalPassage,true);
appendPassageClearanceSpans(*departurePassage,false);
if(departurePassage)appendPassageClearanceSpans(*departurePassage,false);
const auto segmentDistanceMicrometres=[](const std::array<double,3>& p0,
const std::array<double,3>& p1,const std::array<double,3>& q0,
const std::array<double,3>& q1)
@@ -2804,8 +2907,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
cumulative[i]=cumulative[i-1]+distance2d(routePoints[i-1],routePoints[i]);
if(cumulative.back()!=totalRouteLength)
{error="cavern planner route length accounting failed";return false;}
const std::string backboneFloorProfileId="tunnel-network/strate-"
+std::to_string(strateIndex)+"/backbone-floor";
const std::string backboneFloorProfileId=routePrefix+"backbone-floor";
constexpr std::int64_t ProfileOne=std::int64_t{1}<<30;
const auto roundedDivide=[](std::int64_t numerator,std::int64_t denominator)
{
@@ -3008,7 +3110,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
std::int64_t radiusMicrometres=0;
bool destination=false;
};
std::vector<FRoomNode> nodes;nodes.reserve(nodeDistances.size());
std::vector<FRoomNode> nodes;nodes.reserve(nodeDistances.size()+1);
const std::int64_t floorCutMinimum=clampQ16(settings.roomFloorCutMinimumQ16);
const std::int64_t floorCutMaximum=std::max(floorCutMinimum,
clampQ16(settings.roomFloorCutMaximumQ16));
@@ -3025,15 +3127,23 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
node.floorProfileStableId=backboneFloorProfileId;
node.floorProfileDistanceMicrometres=distance;
node.floorGradientQ16=routeFloorGradientAt(distance);
node.destination=distance==mainRouteMidpoint;
node.id="tunnel-network/strate-"+std::to_string(strateIndex)+"/room-"
+std::to_string(nodeIndex);
node.destination=!terminalShaftRoute&&distance==mainRouteMidpoint;
node.id=routePrefix+"room-"+std::to_string(nodeIndex);
const std::uint64_t hash=LayoutStampSeed(seed,strateIndex,node.id);
node.radiusMicrometres=node.destination?settings.destinationRadiusMicrometres
:settings.minimumRoomRadiusMicrometres
+static_cast<std::int64_t>(hash%static_cast<std::uint64_t>(baseRoomSpan+1));
nodes.push_back(std::move(node));
}
if(terminalShaftRoute)
{
FRoomNode terminal;terminal.id=routePrefix+"basal-landing";
terminal.point=routeEnd;terminal.floorProfileStableId=backboneFloorProfileId;
terminal.floorProfileDistanceMicrometres=totalRouteLength;
terminal.floorGradientQ16=routeFloorGradientAt(totalRouteLength);
terminal.radiusMicrometres=settings.destinationRadiusMicrometres;
terminal.destination=true;nodes.push_back(std::move(terminal));
}
const auto makeCompound=[&](const FRoomNode& node,bool destination,
std::vector<ResolvedCavernRoomV1>& outRooms)
{
@@ -3154,6 +3264,130 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
}
return edge;
};
struct FPlannedShaftLanding
{
FRoomNode node;
FPoint point{};
};
std::vector<FPlannedShaftLanding> shaftLandings;
std::size_t plannedShaftCount=0;
if(terminalShaftRoute&&settings.roomDensityQ16>0)
{
const std::int64_t ledgeSpacing=std::max<std::int64_t>(0,
settings.ledgeSpacingMicrometres);
const std::int64_t ledgeDepth=std::clamp<std::int64_t>(
settings.ledgeDepthMicrometres,0,5000000);
bool emittedShaft=false;
const std::int64_t routeLengthXY=std::max<std::int64_t>(1,
static_cast<std::int64_t>(integerSqrt(static_cast<std::uint64_t>(
pathAxisX*pathAxisX+pathAxisY*pathAxisY))));
const std::int64_t sideX=-pathAxisY*65536/routeLengthXY;
const std::int64_t sideY=pathAxisX*65536/routeLengthXY;
const auto selectedRadius=[&](const std::string& stableId)
{
const std::int64_t span=settings.maximumRoomRadiusMicrometres
-settings.minimumRoomRadiusMicrometres;
return settings.minimumRoomRadiusMicrometres+(span==0?0:
static_cast<std::int64_t>(LayoutStampSeed(seed,strateIndex,stableId)
%static_cast<std::uint64_t>(span+1)));
};
for(std::size_t nodeIndex=1;nodeIndex+1<nodes.size()&&rooms.size()<2048;
++nodeIndex)
{
const auto& parent=nodes[nodeIndex];
const std::uint64_t hash=LayoutStampSeed(seed,strateIndex,
parent.id+"/shaft");
if(emittedShaft&&static_cast<std::int64_t>(hash&0xffffu)
>=settings.roomDensityQ16)continue;
const std::int64_t radius=selectedRadius(parent.id+"/shaft-radius");
const std::int64_t top=parent.point[2]
+(ledgeSpacing>0?ledgeSpacing:1000000);
const std::int64_t minimumTop=stratum->content.bottomMicrometres+4000000;
if(top>stratum->content.topMicrometres-1000000
||top<minimumTop)continue;
// Shaft length has no independent owner parameter: use as much content
// as available, with the graph's 200 m half-extent bound as a hard cap.
const std::int64_t bottom=std::max(
stratum->content.bottomMicrometres+1000000,top-200000000);
if(ledgeSpacing>0&&top-bottom<ledgeSpacing+2000000)continue;
const bool positiveSide=(hash&0x10000u)!=0;
const std::int64_t sideXSign=(sideX>=0?1:-1)*(positiveSide?1:-1);
const std::int64_t sideYSign=(sideY>=0?1:-1)*(positiveSide?1:-1);
const bool useX=std::abs(sideX)>=std::abs(sideY);
const std::uint8_t outwardSide=useX
?static_cast<std::uint8_t>(sideXSign>0?0:2)
:static_cast<std::uint8_t>(sideYSign>0?1:3);
const std::array<std::array<std::int64_t,2>,4> cardinal={{{1,0},{0,1},
{-1,0},{0,-1}}};
const auto outward=cardinal[outwardSide];
const std::int64_t offset=std::max<std::int64_t>(
parent.radiusMicrometres+radius+3000000,
settings.roomSpacingMicrometres/2);
FPlannedShaftLanding landing;landing.node.id=routePrefix+"shaft-"
+std::to_string(shaftLandings.size());
landing.node.point={parent.point[0]+outward[0]*offset,
parent.point[1]+outward[1]*offset,parent.point[2]};
landing.node.radiusMicrometres=radius;
const std::int64_t landingZ=top-(ledgeSpacing>0?ledgeSpacing:1000000);
const std::array<std::int64_t,2> toParent={-outward[0],-outward[1]};
const std::uint8_t ledgeSide=static_cast<std::uint8_t>(
(outwardSide+2)%4);
ResolvedCavernRoomV1 shaft;shaft.stableId=landing.node.id+"/primitive-0";
shaft.strateIndex=strateIndex;shaft.shapeKind=1;
shaft.centerMicrometres={landing.node.point[0],landing.node.point[1],
(top+bottom)/2};
shaft.halfExtentsMicrometres={radius,radius,(top-bottom)/2};
shaft.floorMicrometres=top;shaft.smoothWidthMicrometres=smooth;
shaft.ledgeSpacingMicrometres=ledgeSpacing;
shaft.ledgeDepthMicrometres=ledgeDepth;shaft.ledgeSide=ledgeSide;
shaft.irregularityAmplitudeMicrometres=irregularity;
shaft.irregularityWavelengthMicrometres=
settings.irregularityWavelengthMicrometres;
shaft.irregularitySeed=LayoutStampSeed(seed,strateIndex,shaft.stableId);
rooms.push_back(std::move(shaft));
landing.point={landing.node.point[0]
+toParent[0]*(radius-ledgeDepth/2),
landing.node.point[1]+toParent[1]*(radius-ledgeDepth/2),landingZ};
landing.node.point=landing.point;
const std::string accessId=routePrefix+"shaft-access-"
+std::to_string(shaftLandings.size());
auto access=makeEdge(accessId,componentRoomId(parent),parent.point,
landing.node,routeBoreRadius,-1,-1);
// The connector's own floor SDF retains its tread. It must not enter
// the descending backbone's finite floor-cap arbitration, where its
// nearby but independent floor could suppress backbone support.
access.floorSupportDepthMicrometres=0;
const std::int64_t run=distance2d(parent.point,landing.point);
const std::int64_t rise=std::abs(parent.point[2]-landing.point[2]);
if(run<=0||rise*MaximumWalkGradeDenominator>run)
{
rooms.pop_back();
continue;
}
edges.push_back(std::move(access));
shaftLandings.push_back(std::move(landing));
emittedShaft=true;
}
plannedShaftCount=shaftLandings.size();
for(std::size_t index=1;index<shaftLandings.size();++index)
{
const std::uint64_t hash=LayoutStampSeed(seed,strateIndex,
shaftLandings[index-1].node.id+"/cross-"+std::to_string(index));
if(static_cast<std::int64_t>(hash&0xffffu)>=settings.tunnelDensityQ16)
continue;
const auto& from=shaftLandings[index-1];
const auto& to=shaftLandings[index];
const std::int64_t run=distance2d(from.point,to.point);
const std::int64_t rise=std::abs(from.point[2]-to.point[2]);
if(run<=0||run>settings.maximumTunnelLengthMicrometres
||rise*MaximumWalkGradeDenominator>run)continue;
auto link=makeEdge(routePrefix+"shaft-cross-"+std::to_string(index-1),
componentRoomId(from.node),from.point,to.node,routeBoreRadius,-1,-1);
link.floorSupportDepthMicrometres=0;
edges.push_back(std::move(link));
}
}
const auto arrivalPoint=routeStart;
FPoint previousPoint=arrivalPoint;
std::int64_t previousProfileDistance=0;
@@ -3161,8 +3395,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
for(std::size_t nodeIndex=0;nodeIndex<nodes.size();++nodeIndex)
{
const auto& node=nodes[nodeIndex];
const std::string edgeId="tunnel-network/strate-"+std::to_string(strateIndex)
+"/route-edge-"+std::to_string(nodeIndex);
const std::string edgeId=routePrefix+"route-edge-"+std::to_string(nodeIndex);
auto edge=makeEdge(edgeId,previousAnchor,previousPoint,node,
selectedBoreRadius(edgeId),
previousProfileDistance,node.floorProfileDistanceMicrometres);
@@ -3171,10 +3404,9 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
previousProfileDistance=node.floorProfileDistanceMicrometres;
previousAnchor=componentRoomId(node);
}
if(!nodes.empty())
if(!nodes.empty()&&departurePassage)
{
const std::string departureEdgeId="tunnel-network/strate-"
+std::to_string(strateIndex)+"/departure-edge";
const std::string departureEdgeId=routePrefix+"departure-edge";
auto edge=makeEdge(departureEdgeId,departurePassage->stableId+"/upper-mouth",
routeEnd,nodes.back(),selectedBoreRadius(departureEdgeId),
totalRouteLength,nodes.back().floorProfileDistanceMicrometres);
@@ -3187,7 +3419,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
const std::size_t branchLimit=settings.originRoomMaximumConnections<=0
?32u:static_cast<std::size_t>(std::clamp(settings.originRoomMaximumConnections,0,32));
std::size_t branchCount=0;
for(std::size_t nodeIndex=1;nodeIndex+1<nodes.size()
for(std::size_t nodeIndex=1;!terminalShaftRoute&&nodeIndex+1<nodes.size()
&&branchCount<branchLimit&&rooms.size()<2048;++nodeIndex)
{
const auto& parent=nodes[nodeIndex];
@@ -3210,8 +3442,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
// A side branch leaves the backbone laterally, so it has its own local flat
// floor and must not reference the parent's one-dimensional h(s) profile.
makeCompound(branch,false,rooms);
const std::string branchEdgeId="tunnel-network/strate-"
+std::to_string(strateIndex)+"/side-edge-"+std::to_string(branchCount);
const std::string branchEdgeId=routePrefix+"side-edge-"+std::to_string(branchCount);
ResolvedCavernEdgeV1 branchEdge;
if(settings.tunnelsFlowTowardOrigin)
branchEdge=makeEdge(branchEdgeId,componentRoomId(branch),
@@ -3226,7 +3457,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
++branchCount;
}
if(nodes.empty()||rooms.empty()||edges.empty())
{error="cavern planner emitted an empty arrival-to-departure graph";return false;}
{error="cavern planner emitted an empty mouth-to-terminal graph";return false;}
double maximumGradeDegrees=0.0;
for(const auto& edge:edges)
@@ -3255,9 +3486,10 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
}
const double plannerMilliseconds=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-plannerStarted).count();
std::printf("[CavernPlannerV1] strate=%d seed=%d mouths=%s->%s route_nodes=%zu room_primitives=%zu edges=%zu branches=%zu route_horizontal_m=%.3f floor_delta_m=%.3f max_grade_deg=%.3f mouth_tangent_min_dot=%.3f grade_limit_deg=11.310 clearance_radius_m=%.3f clearance_height_m=%.3f destination_radius_m=%.3f plan_ms=%.3f status=PASS\n",
strateIndex,seed,arrivalPassage->stableId.c_str(),departurePassage->stableId.c_str(),
nodes.size(),rooms.size(),edges.size(),branchCount,totalRouteLength/1.0e6,
std::printf("[CavernPlannerV1] strate=%d seed=%d route=%s->%s route_nodes=%zu room_primitives=%zu shafts=%zu edges=%zu branches=%zu route_horizontal_m=%.3f floor_delta_m=%.3f max_grade_deg=%.3f mouth_tangent_min_dot=%.3f grade_limit_deg=11.310 clearance_radius_m=%.3f clearance_height_m=%.3f destination_radius_m=%.3f plan_ms=%.3f status=PASS\n",
strateIndex,seed,arrivalPassage->stableId.c_str(),
departurePassage?departurePassage->stableId.c_str():"basal-terminal",
nodes.size(),rooms.size(),plannedShaftCount,edges.size(),branchCount,totalRouteLength/1.0e6,
std::abs(dz)/1.0e6,maximumGradeDegrees,plannedMouthTangentAlignment,
routeBoreRadius/1.0e6,PlannedBoreHeight/1.0e6,
settings.destinationRadiusMicrometres/1.0e6,plannerMilliseconds);