core: chamber floors planned into the passage h(s) profile (L152, v239; surface landing support open)
Seed 2 planned grades 9.58/6.58/8.28 deg (were 19 deg); 0 solid in 320,355 passage interior samples; 0 shell, chamber, wall violations; envelope audit clean. Open: the upper Surface World landing plane, fitted to the surface route, leaves 5 route + 2 chamber support samples unsupported. 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
5bd158d4eb
commit
11b59a9a9e
12 files changed
+1315
-81
No files matched your search
@@ -511,7 +511,8 @@ namespace VoxelForgeCore
|
||||
// the planned graph route, preserves its walk corridor through shell overlaps, and keeps
|
||||
// boundary-blended host rock from widening the authored passage wall.
|
||||
// v238 plans one resolved passage spline with explicit landing chambers.
|
||||
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 238;
|
||||
// v239 plans their floor planes as the passage's continuous floor profile.
|
||||
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 239;
|
||||
|
||||
enum class FieldChannel : std::uint32_t
|
||||
{
|
||||
@@ -1235,6 +1236,10 @@ namespace VoxelForgeCore
|
||||
std::size_t lowerMouthControlIndex = 0;
|
||||
ResolvedLayoutPassageLandingChamberV1 upperLandingChamber;
|
||||
ResolvedLayoutPassageLandingChamberV1 lowerLandingChamber;
|
||||
// When true, each control point's position.z - radius is the planned
|
||||
// floor h(s), and both chamber planes continue that same endpoint profile.
|
||||
// False preserves older resolved snapshots' runtime floor composition.
|
||||
bool floorProfileIncludesLandingChambers = false;
|
||||
std::vector<ResolvedLayoutPassagePointV1> controlPoints;
|
||||
};
|
||||
|
||||
|
||||
@@ -565,7 +565,9 @@ namespace
|
||||
<<"],\"lower_mouth_q\":["<<p.lowerMouthSubvoxels[0]<<','
|
||||
<<p.lowerMouthSubvoxels[1]<<','<<p.lowerMouthSubvoxels[2]
|
||||
<<"],\"mouth_radius_q\":"<<p.mouthRadiusSubvoxels
|
||||
<<",\"mid_radius_q\":"<<p.midRadiusSubvoxels;
|
||||
<<",\"mid_radius_q\":"<<p.midRadiusSubvoxels
|
||||
<<",\"floor_profile_includes_landing_chambers\":"
|
||||
<<(p.floorProfileIncludesLandingChambers?"true":"false");
|
||||
const auto writeLandingChamber=[&](const char* name,
|
||||
const ResolvedLayoutPassageLandingChamberV1& chamber)
|
||||
{
|
||||
@@ -1796,6 +1798,8 @@ std::optional<ResolvedLayoutSnapshotV3> ResolvedLayoutSnapshotV3::FromJson(
|
||||
p.style=static_cast<std::uint32_t>(LayoutInt(v,"style"));
|
||||
p.placementRadiusSubvoxels=LayoutInt(v,"placement_radius_q");
|
||||
p.mouthRadiusSubvoxels=LayoutInt(v,"mouth_radius_q");p.midRadiusSubvoxels=LayoutInt(v,"mid_radius_q");
|
||||
p.floorProfileIncludesLandingChambers=
|
||||
LayoutBool(v,"floor_profile_includes_landing_chambers");
|
||||
const auto& upper=v.Get("upper_mouth_q").AsArray();
|
||||
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();
|
||||
@@ -3845,6 +3849,38 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
out={to->values[0]-from->values[0],to->values[1]-from->values[1]};
|
||||
return out[0]!=0||out[1]!=0;
|
||||
};
|
||||
const auto plannedMouthFloor=[&](const ResolvedLayoutStratumV2& host,
|
||||
const std::array<std::int64_t,3>& mouth,bool upperSide,
|
||||
std::int64_t playerCenterAboveFloorQ,std::int64_t mouthRadiusQ)
|
||||
{
|
||||
std::int64_t result=mouth[2]-playerCenterAboveFloorQ;
|
||||
const bool receivesRoute=upperSide
|
||||
?host.generatorKind=="SurfaceWorld"||host.generatorKind=="TunnelNetwork"
|
||||
:host.generatorKind=="TunnelNetwork"||host.generatorKind=="VerticalShafts";
|
||||
if(!receivesRoute)return result;
|
||||
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])});
|
||||
if(host.generatorKind=="TunnelNetwork"&&upperSide&&route.size()>2)
|
||||
route.erase(route.begin(),route.end()-2);
|
||||
else if(!upperSide&&(host.generatorKind=="TunnelNetwork"
|
||||
||host.generatorKind=="VerticalShafts")&&route.size()>2)
|
||||
{
|
||||
route.resize(2);
|
||||
}
|
||||
double routeHeight=0.0,distanceSquared=0.0;
|
||||
const double reach=static_cast<double>(mouthRadiusQ)/qPerMetre+0.5;
|
||||
if(VoxelForgeCore::PassageSpline::ClosestRouteHeightAtXY(route,
|
||||
static_cast<double>(mouth[0]),static_cast<double>(mouth[1]),
|
||||
routeHeight,distanceSquared)
|
||||
&&distanceSquared<=std::pow(reach*qPerMetre,2.0))
|
||||
result=static_cast<std::int64_t>(std::llround(routeHeight
|
||||
-playerCenterAboveFloorQ));
|
||||
return result;
|
||||
};
|
||||
const auto radialWithin=[](const std::array<std::int64_t,3>& p,std::int64_t radius)
|
||||
{return radius>0&&p[0]*p[0]+p[1]*p[1]<=radius*radius;};
|
||||
const auto reachValue=[](std::uint64_t hash,std::int64_t lo,std::int64_t hi)
|
||||
@@ -3977,6 +4013,10 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
&&std::any_of(lower.anchors.begin(),lower.anchors.end(),
|
||||
[](const auto& anchor){return anchor.kind=="heightfield_profile"
|
||||
&&anchor.values.size()>=2&&anchor.values[0]>0;});
|
||||
const std::int64_t upperPlannedFloor=plannedMouthFloor(upper,upperMouth,
|
||||
true,upperPlayerCenterAboveFloorQ,mouthRadius);
|
||||
const std::int64_t lowerPlannedFloor=plannedMouthFloor(lower,lowerMouth,
|
||||
false,playerCenterAboveFloorQ,mouthRadius);
|
||||
|
||||
const std::int64_t outerRingRadius=std::max<std::int64_t>(1,
|
||||
placementRadius-mouthRadius-qPerMetre/2);
|
||||
@@ -4170,7 +4210,7 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
const std::uint32_t hashedPhase=phase;
|
||||
const int hashedDirection=direction;
|
||||
const std::int64_t entryVerticalDistance=std::abs(
|
||||
lowerMouth[2]-upperMouth[2]);
|
||||
lowerPlannedFloor-upperPlannedFloor);
|
||||
const std::int64_t entryRouteGradeQ16=profiledSurfaceJoin?8000
|
||||
:style==0&&anchoredIslandPassage&&!anchoredSpiral?15500
|
||||
:TargetGradeQ16;
|
||||
@@ -4597,7 +4637,8 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
else pushXY(firstRing);
|
||||
std::uint64_t totalHorizontal=0;std::uint32_t loops=0;
|
||||
const std::int64_t verticalDistance=hasAnchorHelix
|
||||
?std::abs(lowerMouth[2]-helixBedrockZ):std::abs(lowerMouth[2]-upperMouth[2]);
|
||||
?std::abs(lowerPlannedFloor-(helixBedrockZ-playerCenterAboveFloorQ))
|
||||
:std::abs(lowerPlannedFloor-upperPlannedFloor);
|
||||
auto measurePath=[&]()
|
||||
{
|
||||
std::uint64_t total=0;
|
||||
@@ -4923,6 +4964,13 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
chamberFloorControls,chamberFloorProfile,
|
||||
lowerMouthIndex,route,qPerMetre);
|
||||
}
|
||||
// Commit the endpoint route fit into h(s) before chamber records are
|
||||
// resolved. The passage field and both landing planes then consume the
|
||||
// same deterministic profile instead of blending the chamber in later.
|
||||
for(std::size_t i=0;i<passage.controlPoints.size();++i)
|
||||
passage.controlPoints[i].positionSubvoxels[2]=static_cast<std::int64_t>(
|
||||
std::llround(chamberFloorProfile[i]))
|
||||
+passage.controlPoints[i].radiusSubvoxels;
|
||||
const auto resolveLandingChamber=[&](
|
||||
ResolvedLayoutPassageLandingChamberV1& chamber,
|
||||
const std::array<std::int64_t,3>& mouth,std::size_t endpointIndex,
|
||||
@@ -4953,71 +5001,51 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
horizontalSquared),
|
||||
PassageRoundRatio(dy<0?-dz:dz,std::abs(dy)*65536,
|
||||
horizontalSquared)};
|
||||
if(host.generatorKind=="SurfaceWorld")
|
||||
if(host.generatorKind=="SurfaceWorld"&&side[0]=='u')
|
||||
{
|
||||
// The surface route enters the outer half of this chamber. Fit
|
||||
// its landing plane to that receiving route, while keeping its
|
||||
// center height equal to the passage's planned h(s) endpoint.
|
||||
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 radiusSquared=static_cast<double>(
|
||||
chamber.radiusSubvoxels)*chamber.radiusSubvoxels;
|
||||
double farthestDistanceSquared=0.0;
|
||||
std::array<double,3> farthestPoint{};
|
||||
for(const auto& routePoint: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);
|
||||
const double routeDx=routePoint[0]-mouth[0];
|
||||
const double routeDy=routePoint[1]-mouth[1];
|
||||
const double distanceSquared=routeDx*routeDx+routeDy*routeDy;
|
||||
if(distanceSquared<=radiusSquared
|
||||
&&distanceSquared>farthestDistanceSquared)
|
||||
{
|
||||
farthestDistanceSquared=distanceSquared;
|
||||
farthestPoint=routePoint;
|
||||
}
|
||||
}
|
||||
if(nearbyRoute.size()>=2)
|
||||
double mouthRouteHeight=0.0,mouthRouteDistanceSquared=0.0;
|
||||
if(farthestDistanceSquared>0.0
|
||||
&&VoxelForgeCore::PassageSpline::ClosestRouteHeightAtXY(
|
||||
surfaceRoute,static_cast<double>(mouth[0]),
|
||||
static_cast<double>(mouth[1]),mouthRouteHeight,
|
||||
mouthRouteDistanceSquared))
|
||||
{
|
||||
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::int64_t surfaceDx=static_cast<std::int64_t>(
|
||||
std::llround(farthestPoint[0]-mouth[0]));
|
||||
const std::int64_t surfaceDy=static_cast<std::int64_t>(
|
||||
std::llround(farthestPoint[1]-mouth[1]));
|
||||
const std::int64_t surfaceDz=static_cast<std::int64_t>(
|
||||
std::llround(farthestPoint[2]-mouthRouteHeight));
|
||||
const std::int64_t surfaceHorizontalSquared=surfaceDx*surfaceDx
|
||||
+surfaceDy*surfaceDy;
|
||||
if(surfaceHorizontalSquared>0)
|
||||
chamber.floorGradientQ16={
|
||||
PassageRoundRatio(surfaceDx<0?-surfaceDz:surfaceDz,
|
||||
std::abs(surfaceDx)*65536,
|
||||
surfaceHorizontalSquared),
|
||||
PassageRoundRatio(surfaceDy<0?-surfaceDz:surfaceDz,
|
||||
std::abs(surfaceDy)*65536,
|
||||
surfaceHorizontalSquared)};
|
||||
}
|
||||
}
|
||||
const std::uint64_t gradientSquared=static_cast<std::uint64_t>(
|
||||
@@ -5053,6 +5081,7 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
||!resolveLandingChamber(passage.lowerLandingChamber,lowerMouth,
|
||||
lowerMouthIndex,lowerMouthIndex-1,lower,"lower"))
|
||||
return false;
|
||||
passage.floorProfileIncludesLandingChambers=true;
|
||||
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),
|
||||
|
||||
@@ -3836,6 +3836,7 @@ private:
|
||||
std::array<double,2> lowerLandingFloorGradient{};
|
||||
double lowerLandingRadiusSubvoxels=0.0;
|
||||
std::size_t routeEndControlIndex=0;
|
||||
bool floorProfileIncludesLandingChambers=false;
|
||||
};
|
||||
struct FPassageSegmentData
|
||||
{
|
||||
@@ -3937,6 +3938,8 @@ private:
|
||||
+profileT*profileSegment.authoredFloorZDelta;
|
||||
}
|
||||
}
|
||||
if(constants.floorProfileIncludesLandingChambers)
|
||||
return plannedFloor+floorDepthSubvoxels;
|
||||
const double upperBlendFloor=VoxelForgeCore::PassageSpline::BlendUpperMouthFloorAtXY(
|
||||
constants.upperMouthReceivingRoute,queryXSubvoxels,queryYSubvoxels,
|
||||
plannedFloor,constants.upperMouthX,constants.upperMouthY,
|
||||
@@ -5112,6 +5115,8 @@ private:
|
||||
passage.lowerMouthControlIndex,1,passage.controlPoints.size()-1);
|
||||
auto& constants=PassageComposerConstants[passageIndex];
|
||||
constants.routeEndControlIndex=routeEnd;
|
||||
constants.floorProfileIncludesLandingChambers=
|
||||
passage.floorProfileIncludesLandingChambers;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
constants.upperLandingFloorCenterSubvoxels[axis]=static_cast<double>(
|
||||
@@ -5177,8 +5182,10 @@ private:
|
||||
upperMouthFloorDepth=std::max(upperMouthFloorDepth,
|
||||
std::max(0.0,(controls.front()[2]-routeHeight)/qPerMetre
|
||||
+constants.upperMouthPlayerCenterHeightMetres));
|
||||
if(upperStratum->generatorKind=="TunnelNetwork"
|
||||
if(!passage.floorProfileIncludesLandingChambers
|
||||
&&(upperStratum->generatorKind=="TunnelNetwork"
|
||||
||upperStratum->generatorKind=="SurfaceWorld")
|
||||
)
|
||||
VoxelForgeCore::PassageSpline::BlendEndpointFloorToRoute(
|
||||
controls,plannedFloorProfile,0,route,qPerMetre,
|
||||
constants.upperMouthPlayerCenterHeightMetres);
|
||||
@@ -5226,9 +5233,10 @@ private:
|
||||
&&distanceSquared<=std::pow(controls[routeEnd][3]
|
||||
+2.0*qPerMetre,2.0))
|
||||
{
|
||||
VoxelForgeCore::PassageSpline::BlendEndpointFloorToRoute(
|
||||
controls,plannedFloorProfile,routeEnd,
|
||||
constants.lowerMouthReceivingRoute,qPerMetre);
|
||||
if(!passage.floorProfileIncludesLandingChambers)
|
||||
VoxelForgeCore::PassageSpline::BlendEndpointFloorToRoute(
|
||||
controls,plannedFloorProfile,routeEnd,
|
||||
constants.lowerMouthReceivingRoute,qPerMetre);
|
||||
constants.lowerMouthX=controls[routeEnd][0];
|
||||
constants.lowerMouthY=controls[routeEnd][1];
|
||||
constants.lowerMouthPlannedFloor=plannedFloorProfile[routeEnd];
|
||||
|
||||
Reference in new issue
Block a user