wip: continuous backbone floor profile for the cave planner (L134 design 4, untested)

Written by Luna before the Codex usage limit cut the round: one shared distance/floor profile across backbone rooms
and edges, edge cubic floors derived from it, sloped route-room floors, lower-floor backing cap at overlaps, and
validator checks for joint height/slope continuity. Not yet built or audited.

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-10-31 12:03:48 +01:00
1 parent 379093401e
commit 4a97023b24
3 files changed
+296 -19

No files matched your search

+233 -9
View File
@@ -621,6 +621,10 @@ namespace
<<"],\"half_extents_um\":["<<room.halfExtentsMicrometres[0]<<','
<<room.halfExtentsMicrometres[1]<<','<<room.halfExtentsMicrometres[2]
<<"],\"floor_um\":"<<room.floorMicrometres
<<",\"floor_profile_id\":"<<LayoutEscape(room.floorProfileStableId)
<<",\"floor_profile_distance_um\":"<<room.floorProfileDistanceMicrometres
<<",\"floor_gradient_q16\":["<<room.floorGradientQ16[0]<<','
<<room.floorGradientQ16[1]<<']'
<<",\"smooth_width_um\":"<<room.smoothWidthMicrometres
;
if(room.unionWidthMicrometres>0)
@@ -647,6 +651,11 @@ namespace
out<<'['<<p[0]<<','<<p[1]<<','<<p[2]<<']';
}
out<<']';
out<<",\"floor_profile_id\":"<<LayoutEscape(edge.floorProfileStableId)
<<",\"floor_profile_start_distance_um\":"
<<edge.floorProfileStartDistanceMicrometres
<<",\"floor_profile_end_distance_um\":"
<<edge.floorProfileEndDistanceMicrometres;
if(!edge.polylineMicrometres.empty())
{
out<<",\"polyline_um\":[";
@@ -1235,7 +1244,8 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
add("cavern_graph_vocabulary_version",
"cavern graph vocabulary version is set without graph records");
}
else if(cavernGraphVocabularyVersion!=ResolvedLayoutCavernGraphVocabularyVersionV1)
else if(cavernGraphVocabularyVersion!=ResolvedLayoutCavernGraphVocabularyVersionV1
&&cavernGraphVocabularyVersion!=ResolvedLayoutCavernGraphVocabularyVersionV2)
add("cavern_graph_vocabulary_version",
"cavern graph records require vocabulary version 1");
if(cavernGraphVocabularyVersion!=0)
@@ -1268,6 +1278,16 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
||(room.irregularityAmplitudeMicrometres>0
&&room.irregularityWavelengthMicrometres<=0))
add("cavern_room_parameters",room.stableId+" has invalid or unbounded parameters");
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];
if((room.floorProfileStableId.empty()
&&room.floorProfileDistanceMicrometres!=-1)
||(!room.floorProfileStableId.empty()
&&room.floorProfileDistanceMicrometres<0)
||gradientSquared>static_cast<std::int64_t>(13108)*13108)
add("cavern_room_floor_profile",room.stableId+
" has an invalid shared profile reference or floor grade");
const std::int64_t pad=room.smoothWidthMicrometres
+room.unionWidthMicrometres+room.irregularityAmplitudeMicrometres;
for(std::size_t axis=0;axis<3;++axis)
@@ -1308,6 +1328,15 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
||(edge.irregularityAmplitudeMicrometres>0
&&edge.irregularityWavelengthMicrometres<=0))
add("cavern_edge_parameters",edge.stableId+" has invalid or unbounded parameters");
const bool hasFloorProfile=!edge.floorProfileStableId.empty();
if((hasFloorProfile&&(edge.floorProfileStartDistanceMicrometres<0
||edge.floorProfileEndDistanceMicrometres<0
||edge.floorProfileStartDistanceMicrometres
==edge.floorProfileEndDistanceMicrometres))
||(!hasFloorProfile&&(edge.floorProfileStartDistanceMicrometres!=-1
||edge.floorProfileEndDistanceMicrometres!=-1)))
add("cavern_edge_floor_profile",edge.stableId+
" has an invalid shared profile distance interval");
std::array<std::int64_t,3> low=edge.controlPointsMicrometres[0];
std::array<std::int64_t,3> high=low;
if(nativeLattice&&!edge.polylineMicrometres.empty())
@@ -1363,6 +1392,114 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
add("cavern_edge_polyline",edge.stableId+
" has no bounded resolve-time polyline with matching endpoints");
}
std::vector<std::string> floorProfileIds;
for(const auto& edge:cavernEdges)
if(!edge.floorProfileStableId.empty()
&&std::find(floorProfileIds.begin(),floorProfileIds.end(),
edge.floorProfileStableId)==floorProfileIds.end())
floorProfileIds.push_back(edge.floorProfileStableId);
for(const std::string& profileId:floorProfileIds)
{
std::vector<const ResolvedCavernEdgeV1*> profileEdges;
for(const auto& edge:cavernEdges)
if(edge.floorProfileStableId==profileId)profileEdges.push_back(&edge);
std::sort(profileEdges.begin(),profileEdges.end(),[](const auto* a,const auto* b)
{
return std::min(a->floorProfileStartDistanceMicrometres,
a->floorProfileEndDistanceMicrometres)
<std::min(b->floorProfileStartDistanceMicrometres,
b->floorProfileEndDistanceMicrometres);
});
const auto endpointZ=[](const ResolvedCavernEdgeV1& edge,bool atMinimum)
{
const bool forward=edge.floorProfileStartDistanceMicrometres
<edge.floorProfileEndDistanceMicrometres;
const std::size_t point=(atMinimum==forward)?0:3;
return edge.controlPointsMicrometres[point][2];
};
const auto endpointSlope=[](const ResolvedCavernEdgeV1& edge,bool atMinimum)
{
const double span=static_cast<double>(edge.floorProfileEndDistanceMicrometres
-edge.floorProfileStartDistanceMicrometres);
if(span==0.0)return std::numeric_limits<double>::infinity();
const bool forward=edge.floorProfileStartDistanceMicrometres
<edge.floorProfileEndDistanceMicrometres;
const std::size_t control=atMinimum==forward?0:2;
return 3.0*(edge.controlPointsMicrometres[control+1][2]
-edge.controlPointsMicrometres[control][2])/span;
};
const ResolvedCavernEdgeV1* previous=nullptr;
for(const auto* edge:profileEdges)
{
const std::int64_t low=std::min(edge->floorProfileStartDistanceMicrometres,
edge->floorProfileEndDistanceMicrometres);
const std::int64_t high=std::max(edge->floorProfileStartDistanceMicrometres,
edge->floorProfileEndDistanceMicrometres);
const double slope0=endpointSlope(*edge,true);
const double slope1=endpointSlope(*edge,false);
if(std::max(std::abs(slope0),std::abs(slope1))>0.200001)
add("cavern_floor_profile_grade",edge->stableId+
" exceeds the shared backbone 1:5 grade limit");
if(previous)
{
const std::int64_t previousHigh=std::max(
previous->floorProfileStartDistanceMicrometres,
previous->floorProfileEndDistanceMicrometres);
if(low!=previousHigh
||std::abs(endpointZ(*previous,false)-endpointZ(*edge,true))>1000
||std::abs(endpointSlope(*previous,false)-slope0)>0.000001)
add("cavern_floor_profile_joint",profileId+
" has a gap, height step, or slope step between adjacent edges");
}
previous=edge;
(void)high;
}
for(const auto& room:cavernRooms)
{
if(room.floorProfileStableId!=profileId)continue;
const ResolvedCavernEdgeV1* containing=nullptr;
for(const auto* edge:profileEdges)
{
const auto low=std::min(edge->floorProfileStartDistanceMicrometres,
edge->floorProfileEndDistanceMicrometres);
const auto high=std::max(edge->floorProfileStartDistanceMicrometres,
edge->floorProfileEndDistanceMicrometres);
if(room.floorProfileDistanceMicrometres>=low
&&room.floorProfileDistanceMicrometres<=high)
{containing=edge;break;}
}
if(!containing)
{
add("cavern_room_floor_profile",room.stableId+
" does not lie on its referenced continuous floor profile");
continue;
}
const auto& edge=*containing;
const double t=static_cast<double>(room.floorProfileDistanceMicrometres
-edge.floorProfileStartDistanceMicrometres)
/(edge.floorProfileEndDistanceMicrometres
-edge.floorProfileStartDistanceMicrometres);
const double one=1.0-t;
const double expected=one*one*one*edge.controlPointsMicrometres[0][2]
+3.0*one*one*t*edge.controlPointsMicrometres[1][2]
+3.0*one*t*t*edge.controlPointsMicrometres[2][2]
+t*t*t*edge.controlPointsMicrometres[3][2];
std::array<double,2> routeXY{};
for(std::size_t axis=0;axis<2;++axis)
routeXY[axis]=one*one*one*edge.controlPointsMicrometres[0][axis]
+3.0*one*one*t*edge.controlPointsMicrometres[1][axis]
+3.0*one*t*t*edge.controlPointsMicrometres[2][axis]
+t*t*t*edge.controlPointsMicrometres[3][axis];
const double roomFloorAtProfile=room.floorMicrometres
+room.floorGradientQ16[0]/65536.0
*(routeXY[0]-room.centerMicrometres[0])
+room.floorGradientQ16[1]/65536.0
*(routeXY[1]-room.centerMicrometres[1]);
if(std::abs(expected-roomFloorAtProfile)>1000.0)
add("cavern_room_floor_profile",room.stableId+
" floor does not match its referenced h(s)");
}
}
}
if(oreVeins.empty())
{
@@ -1526,6 +1663,12 @@ std::optional<ResolvedLayoutSnapshotV3> ResolvedLayoutSnapshotV3::FromJson(
readTriplet("bounds_min_um",room.boundsMinimumMicrometres);
readTriplet("bounds_max_um",room.boundsMaximumMicrometres);
room.floorMicrometres=LayoutInt(v,"floor_um");
room.floorProfileStableId=LayoutString(v,"floor_profile_id");
if(v.Get("floor_profile_distance_um").IsNumber())
room.floorProfileDistanceMicrometres=LayoutInt(v,"floor_profile_distance_um");
const auto& floorGradient=v.Get("floor_gradient_q16").AsArray();
for(std::size_t axis=0;axis<2&&axis<floorGradient.size();++axis)
room.floorGradientQ16[axis]=static_cast<std::int32_t>(floorGradient[axis].Integer());
room.smoothWidthMicrometres=LayoutInt(v,"smooth_width_um");
room.unionWidthMicrometres=LayoutInt(v,"union_width_um");
room.irregularityAmplitudeMicrometres=LayoutInt(v,"irregularity_amplitude_um");
@@ -1539,6 +1682,13 @@ std::optional<ResolvedLayoutSnapshotV3> ResolvedLayoutSnapshotV3::FromJson(
edge.startAnchorStableId=LayoutString(v,"start_anchor_stable_id");
edge.endRoomStableId=LayoutString(v,"end_room_stable_id");
edge.strateIndex=static_cast<std::int32_t>(LayoutInt(v,"strate_index"));
edge.floorProfileStableId=LayoutString(v,"floor_profile_id");
if(v.Get("floor_profile_start_distance_um").IsNumber())
edge.floorProfileStartDistanceMicrometres=
LayoutInt(v,"floor_profile_start_distance_um");
if(v.Get("floor_profile_end_distance_um").IsNumber())
edge.floorProfileEndDistanceMicrometres=
LayoutInt(v,"floor_profile_end_distance_um");
const auto& points=v.Get("control_points_um").AsArray();
for(std::size_t i=0;i<edge.controlPointsMicrometres.size()&&i<points.size();++i)
{
@@ -1879,7 +2029,7 @@ bool ResolveResolvedCavernGraphV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
if(!candidate.finalize(error))return false;
layout=std::move(candidate);return true;
}
candidate.cavernGraphVocabularyVersion=ResolvedLayoutCavernGraphVocabularyVersionV1;
candidate.cavernGraphVocabularyVersion=ResolvedLayoutCavernGraphVocabularyVersionV2;
candidate.cavernRooms.assign(rooms.begin(),rooms.end());
candidate.cavernEdges.assign(edges.begin(),edges.end());
const auto isNativeCavesStrate=[&candidate](std::int32_t strateIndex)
@@ -2195,6 +2345,42 @@ 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 auto routeFloorAt=[&](std::int64_t distance)
{
distance=std::clamp<std::int64_t>(distance,0,totalRouteLength);
return routeStart[2]+dz*distance/totalRouteLength;
};
const auto routeFloorGradientAt=[&](std::int64_t distance)
{
distance=std::clamp<std::int64_t>(distance,0,totalRouteLength);
std::size_t segment=1;
while(segment<cumulative.size()-1&&distance>cumulative[segment])++segment;
const auto unitTangent=[&](std::size_t index)
{
const double tx=static_cast<double>(routePoints[index][0]
-routePoints[index-1][0]);
const double ty=static_cast<double>(routePoints[index][1]
-routePoints[index-1][1]);
const double length=std::hypot(tx,ty);
return length>0.0?std::array<double,2>{tx/length,ty/length}
:std::array<double,2>{0.0,0.0};
};
auto tangent=unitTangent(segment);
if(segment+1<cumulative.size()&&distance==cumulative[segment])
{
const auto next=unitTangent(segment+1);
tangent[0]+=next[0];tangent[1]+=next[1];
const double length=std::hypot(tangent[0],tangent[1]);
if(length>1.0e-12){tangent[0]/=length;tangent[1]/=length;}
}
constexpr double Q16=65536.0;
const double profileSlope=static_cast<double>(dz)/totalRouteLength;
return std::array<std::int32_t,2>{
static_cast<std::int32_t>(std::llround(tangent[0]*profileSlope*Q16)),
static_cast<std::int32_t>(std::llround(tangent[1]*profileSlope*Q16))};
};
const auto pointAt=[&](std::int64_t distance)
{
distance=std::clamp<std::int64_t>(distance,0,totalRouteLength);
@@ -2211,7 +2397,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
-routePoints[segment-1][axis];
point[axis]=routePoints[segment-1][axis]+delta*local/segmentLength;
}
point[2]=routeStart[2]+dz*distance/totalRouteLength;
point[2]=routeFloorAt(distance);
return point;
};
std::int64_t spacing=std::min(std::max<std::int64_t>(settings.roomSpacingMicrometres,
@@ -2281,6 +2467,9 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
{
std::string id;
FPoint point{};
std::string floorProfileStableId;
std::int64_t floorProfileDistanceMicrometres=-1;
std::array<std::int32_t,2> floorGradientQ16{};
std::int64_t radiusMicrometres=0;
bool destination=false;
};
@@ -2298,6 +2487,9 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
{
const std::int64_t distance=nodeDistances[nodeIndex];
FRoomNode node;node.point=pointAt(distance);
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);
@@ -2347,7 +2539,10 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
pathAxisX*pathAxisX+pathAxisY*pathAxisY))));
const std::int64_t centerOffset=(component%2==0?1:-1)*offsetMagnitude;
ResolvedCavernRoomV1 room;room.stableId=stableId;
room.strateIndex=strateIndex;room.floorMicrometres=node.point[2];
room.strateIndex=strateIndex;
room.floorProfileStableId=node.floorProfileStableId;
room.floorProfileDistanceMicrometres=node.floorProfileDistanceMicrometres;
room.floorGradientQ16=node.floorGradientQ16;
room.halfExtentsMicrometres={halfX,halfY,halfZ};
room.centerMicrometres={node.point[0]+(-pathAxisY)*centerOffset/routeLengthXY,
node.point[1]+pathAxisX*centerOffset/routeLengthXY,
@@ -2362,6 +2557,11 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
%static_cast<std::uint64_t>(jitterRange+1)):0;
room.centerMicrometres[2]+=ceilingJitter;
}
room.floorMicrometres=node.point[2]
-(static_cast<std::int64_t>(room.floorGradientQ16[0])
*(room.centerMicrometres[0]-node.point[0])
+static_cast<std::int64_t>(room.floorGradientQ16[1])
*(room.centerMicrometres[1]-node.point[1]))/65536;
room.smoothWidthMicrometres=smooth;
room.irregularityAmplitudeMicrometres=irregularity;
room.irregularityWavelengthMicrometres=settings.irregularityWavelengthMicrometres;
@@ -2374,7 +2574,8 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
const auto componentRoomId=[](const FRoomNode& node)
{return node.id+"/primitive-0";};
const auto makeEdge=[&](std::string id,std::string startAnchor,
const FPoint& start,const FRoomNode& endNode,std::int64_t boreRadius)
const FPoint& start,const FRoomNode& endNode,std::int64_t boreRadius,
std::int64_t profileStartDistance,std::int64_t profileEndDistance)
{
ResolvedCavernEdgeV1 edge;edge.stableId=std::move(id);
edge.startAnchorStableId=std::move(startAnchor);
@@ -2382,6 +2583,12 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
edge.boreRadiusMicrometres=boreRadius;
edge.boreHeightMicrometres=std::max(PlannedBoreHeight,MinimumBoreHeight);
edge.floorSupportDepthMicrometres=settings.floorSupportDepthMicrometres;
if(profileStartDistance>=0&&profileEndDistance>=0)
{
edge.floorProfileStableId=backboneFloorProfileId;
edge.floorProfileStartDistanceMicrometres=profileStartDistance;
edge.floorProfileEndDistanceMicrometres=profileEndDistance;
}
edge.smoothWidthMicrometres=smooth;
edge.irregularityAmplitudeMicrometres=irregularity;
edge.irregularityWavelengthMicrometres=settings.irregularityWavelengthMicrometres;
@@ -2394,10 +2601,21 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
edge.controlPointsMicrometres[2][axis]=start[axis]+(delta*2)/3;
edge.controlPointsMicrometres[3][axis]=end[axis];
}
if(profileStartDistance>=0&&profileEndDistance>=0)
{
const std::int64_t floorStart=routeFloorAt(profileStartDistance);
const std::int64_t floorEnd=routeFloorAt(profileEndDistance);
const std::int64_t floorDelta=floorEnd-floorStart;
edge.controlPointsMicrometres[0][2]=floorStart;
edge.controlPointsMicrometres[1][2]=floorStart+floorDelta/3;
edge.controlPointsMicrometres[2][2]=floorStart+(floorDelta*2)/3;
edge.controlPointsMicrometres[3][2]=floorEnd;
}
return edge;
};
const auto arrivalPoint=routeStart;
FPoint previousPoint=arrivalPoint;
std::int64_t previousProfileDistance=0;
std::string previousAnchor=arrivalPassage->stableId+"/lower-mouth";
for(std::size_t nodeIndex=0;nodeIndex<nodes.size();++nodeIndex)
{
@@ -2406,8 +2624,10 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
const std::string edgeId="tunnel-network/strate-"+std::to_string(strateIndex)
+"/route-edge-"+std::to_string(nodeIndex);
edges.push_back(makeEdge(edgeId,previousAnchor,previousPoint,node,
portal?passageBoreRadius(*arrivalPassage,true):selectedBoreRadius(edgeId)));
portal?passageBoreRadius(*arrivalPassage,true):selectedBoreRadius(edgeId),
previousProfileDistance,node.floorProfileDistanceMicrometres));
previousPoint={node.point[0],node.point[1],node.point[2]};
previousProfileDistance=node.floorProfileDistanceMicrometres;
previousAnchor=componentRoomId(node);
}
if(!nodes.empty())
@@ -2415,7 +2635,8 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
const std::string departureEdgeId="tunnel-network/strate-"
+std::to_string(strateIndex)+"/departure-edge";
edges.push_back(makeEdge(departureEdgeId,departurePassage->stableId+"/upper-mouth",
routeEnd,nodes.back(),passageBoreRadius(*departurePassage,false)));
routeEnd,nodes.back(),passageBoreRadius(*departurePassage,false),
totalRouteLength,nodes.back().floorProfileDistanceMicrometres));
}
const std::int64_t roomDensity=clampQ16(settings.roomDensityQ16);
@@ -2444,16 +2665,19 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
branch.point={parent.point[0]+(-pathAxisY)*branchLength/pathAxisLength*direction,
parent.point[1]+pathAxisX*branchLength/pathAxisLength*direction,
parent.point[2]};
branch.floorProfileStableId=parent.floorProfileStableId;
branch.floorProfileDistanceMicrometres=parent.floorProfileDistanceMicrometres;
branch.floorGradientQ16=parent.floorGradientQ16;
makeCompound(branch,false,rooms);
const std::string branchEdgeId="tunnel-network/strate-"
+std::to_string(strateIndex)+"/side-edge-"+std::to_string(branchCount);
ResolvedCavernEdgeV1 branchEdge;
if(settings.tunnelsFlowTowardOrigin)
branchEdge=makeEdge(branchEdgeId,componentRoomId(branch),
branch.point,parent,selectedBoreRadius(branchEdgeId));
branch.point,parent,selectedBoreRadius(branchEdgeId),-1,-1);
else
branchEdge=makeEdge(branchEdgeId,componentRoomId(parent),
parent.point,branch,selectedBoreRadius(branchEdgeId));
parent.point,branch,selectedBoreRadius(branchEdgeId),-1,-1);
// Side branches retain their cave floor SDF, but they do not cap the
// descending backbone when their tubes overlap it near a junction.
branchEdge.floorSupportDepthMicrometres=0;