wip(assembler): round L24 -- per-strate passage styles; natural Worm default; FI anchor island
Core plans inter-strate passages from each upper strate's FStratePassageConfig for every generator: Straight / Worm (the new natural default: meander, variable radius, rough walls) / Spiral / Cascading. A naturalness audit (no >8 m flat runs, >=0.20 m wall relief) fails on L23's smooth tube. FloatingIslands selects Spiral, with a deterministic unique anchor island (ordinal 2) joined to the bedrock floor by a rock root. The SW arrival no longer stamps flat shelves. Capsule audits 0 blocked / 0 unsupported / 0 grade failures, all styles. Full-view hashes deterministic W1/W8/W12, Clang = GCC (reviewer re-run). Evaluator 10, FI geometry 5. KNOWN: the FI helix renders as a spiral ribbon in open air around the pillar instead of a tunnel enclosed in rock; fix next round. Unreal edits uncompiled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
This commit is contained in:
1 parent
71d09a8c52
commit
cb9236c265
17 files changed
+922
-122
No files matched your search
@@ -107,6 +107,66 @@ public:
|
||||
const double floorThicknessVoxels=qPerMetre/Q;
|
||||
const double playerCenterAboveFloorQ=1.13*qPerMetre;
|
||||
float composed=density;
|
||||
const auto composeRouteMouthSupport=[&](std::int32_t strateIndex,
|
||||
const std::array<std::int64_t,3>& mouth,bool departure)
|
||||
{
|
||||
const double mouthDx=qx-static_cast<double>(mouth[0]);
|
||||
const double mouthDy=qy-static_cast<double>(mouth[1]);
|
||||
const double routeSupportReach=13.0*qPerMetre;
|
||||
if(mouthDx*mouthDx+mouthDy*mouthDy>routeSupportReach*routeSupportReach)return;
|
||||
const auto stratum=std::find_if(ResolvedLayout->strates.begin(),
|
||||
ResolvedLayout->strates.end(),[strateIndex](const auto& value)
|
||||
{return value.strateIndex==strateIndex;});
|
||||
if(stratum==ResolvedLayout->strates.end())return;
|
||||
std::array<const VoxelForgeCore::ResolvedLayoutAnchorV2*,5> route{};
|
||||
std::size_t routeCount=0;
|
||||
for(const auto& anchor:stratum->anchors)
|
||||
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
|
||||
{
|
||||
if(departure)
|
||||
{
|
||||
if(routeCount<route.size())route[routeCount++]=&anchor;
|
||||
else
|
||||
{
|
||||
std::move(route.begin()+1,route.end(),route.begin());
|
||||
route.back()=&anchor;
|
||||
}
|
||||
}
|
||||
else if(routeCount<route.size())route[routeCount++]=&anchor;
|
||||
}
|
||||
if(routeCount<2)return;
|
||||
const std::size_t lastSegment=routeCount-1;
|
||||
const double floorThickness=1000000.0/std::max<std::uint32_t>(1,
|
||||
ResolvedLayout->coreVoxelSizeMicrometres);
|
||||
for(std::size_t index=0;index<lastSegment;++index)
|
||||
{
|
||||
const auto& a=*route[index];const auto& b=*route[index+1];
|
||||
const double dx=static_cast<double>(b.values[0]-a.values[0]);
|
||||
const double dy=static_cast<double>(b.values[1]-a.values[1]);
|
||||
const double lengthSq=dx*dx+dy*dy;
|
||||
const double t=lengthSq>0.0?std::clamp(((qx-a.values[0])*dx
|
||||
+(qy-a.values[1])*dy)/lengthSq,0.0,1.0):0.0;
|
||||
const double centerX=a.values[0]+t*dx,centerY=a.values[1]+t*dy;
|
||||
const double centerZ=a.values[2]+t*(static_cast<double>(b.values[2])-a.values[2]);
|
||||
const double routeRadius=a.values.size()>=4
|
||||
?a.values[3]+t*(static_cast<double>(b.values[3])-a.values[3]):0.0;
|
||||
const double halfWidth=std::max(0.75*qPerMetre,routeRadius);
|
||||
const double horizontalX=qx-centerX,horizontalY=qy-centerY;
|
||||
if(horizontalX*horizontalX+horizontalY*horizontalY>halfWidth*halfWidth)continue;
|
||||
const double floorVoxels=(centerZ-1.20*qPerMetre)/Q;
|
||||
if(Z<floorVoxels-floorThickness||Z>floorVoxels+1.0/Q)continue;
|
||||
composed=std::min(composed,static_cast<float>(
|
||||
std::clamp(Z-floorVoxels,-floorThickness,0.0)));
|
||||
}
|
||||
};
|
||||
// Seed the route floor first. The bore carve below reopens any overlap at the
|
||||
// mouth, then applies its own lower tread, so neither shape blocks the capsule.
|
||||
for(const auto& passage:ResolvedLayout->passages)
|
||||
{
|
||||
composeRouteMouthSupport(passage.upperStrateIndex,passage.upperMouthSubvoxels,true);
|
||||
// The lower mouth emerges into its native terrain. Keep the passage tread
|
||||
// support, but do not stamp short, flat route-anchor shelves over that cave mouth.
|
||||
}
|
||||
for(const auto& passage:ResolvedLayout->passages)
|
||||
{
|
||||
if(passage.controlPoints.size()<2)continue;
|
||||
@@ -125,16 +185,25 @@ public:
|
||||
const std::ptrdiff_t lowerMouthIndex=std::clamp<std::ptrdiff_t>(
|
||||
static_cast<std::ptrdiff_t>(passage.lowerMouthControlIndex),1,lastIndex);
|
||||
const auto mainEnd=passage.controlPoints.begin()+lowerMouthIndex+1;
|
||||
const auto carveFound=std::lower_bound(passage.controlPoints.begin(),mainEnd,qz,
|
||||
// The tube radius is measured from its centerline, while floorProfile is
|
||||
// one player-center offset away. Search the full vertical radius of the SDF;
|
||||
// a narrow point window misses earlier turns of a low-pitch helix.
|
||||
const double carveSearchMargin=std::max(passage.mouthRadiusSubvoxels,
|
||||
passage.midRadiusSubvoxels)+0.25*qPerMetre;
|
||||
const auto carveHigh=std::lower_bound(passage.controlPoints.begin(),mainEnd,
|
||||
qz+carveSearchMargin,
|
||||
[&](const auto& point,double value){return floorProfile(point)>value;});
|
||||
const std::ptrdiff_t carveCenter=carveFound-passage.controlPoints.begin();
|
||||
const std::ptrdiff_t carveBegin=std::max<std::ptrdiff_t>(0,carveCenter-3);
|
||||
const std::ptrdiff_t carveEnd=std::min<std::ptrdiff_t>(lowerMouthIndex-1,carveCenter+2);
|
||||
const auto carveLow=std::lower_bound(passage.controlPoints.begin(),mainEnd,
|
||||
qz-carveSearchMargin,
|
||||
[&](const auto& point,double value){return floorProfile(point)>value;});
|
||||
const std::ptrdiff_t carveBegin=std::max<std::ptrdiff_t>(0,
|
||||
(carveHigh-passage.controlPoints.begin())-1);
|
||||
const std::ptrdiff_t carveEnd=std::min<std::ptrdiff_t>(lowerMouthIndex-1,
|
||||
(carveLow-passage.controlPoints.begin())+1);
|
||||
double bestSignedDistance=std::numeric_limits<double>::infinity();
|
||||
double bestT=0.0;
|
||||
std::ptrdiff_t bestIndex=0;
|
||||
bool hasSupportFloor=false;
|
||||
double bestSupportFloorVoxels=-std::numeric_limits<double>::infinity();
|
||||
double bestSupportDensity=0.0;
|
||||
const auto considerCarveSegment=[&](std::ptrdiff_t index)
|
||||
{
|
||||
@@ -164,65 +233,94 @@ public:
|
||||
for(std::ptrdiff_t index=lowerMouthIndex;index<lastIndex;++index)
|
||||
considerCarveSegment(index);
|
||||
if(!std::isfinite(bestSignedDistance))continue;
|
||||
// Positive density is air; the passage SDF is applied after both owner bands.
|
||||
composed=std::max(composed,static_cast<float>(-bestSignedDistance/Q));
|
||||
const auto& nearestA=passage.controlPoints[static_cast<std::size_t>(bestIndex)];
|
||||
const auto& nearestB=passage.controlPoints[static_cast<std::size_t>(bestIndex+1)];
|
||||
const double nearestRadius=nearestA.radiusSubvoxels+bestT*(
|
||||
static_cast<double>(nearestB.radiusSubvoxels)-nearestA.radiusSubvoxels);
|
||||
const double nearestPlayerCenterZ=nearestA.positionSubvoxels[2]+bestT*(
|
||||
const double nearestDx=static_cast<double>(nearestB.positionSubvoxels[0])
|
||||
-nearestA.positionSubvoxels[0];
|
||||
const double nearestDy=static_cast<double>(nearestB.positionSubvoxels[1])
|
||||
-nearestA.positionSubvoxels[1];
|
||||
const double nearestHorizontalSq=nearestDx*nearestDx+nearestDy*nearestDy;
|
||||
const double supportPathT=nearestHorizontalSq>0.0
|
||||
?std::clamp(((qx-nearestA.positionSubvoxels[0])*nearestDx
|
||||
+(qy-nearestA.positionSubvoxels[1])*nearestDy)/nearestHorizontalSq,0.0,1.0)
|
||||
:bestT;
|
||||
const double supportPathRadius=nearestA.radiusSubvoxels+supportPathT*(
|
||||
static_cast<double>(nearestB.radiusSubvoxels)-nearestA.radiusSubvoxels);
|
||||
const double supportPathPlayerCenterZ=nearestA.positionSubvoxels[2]+supportPathT*(
|
||||
static_cast<double>(nearestB.positionSubvoxels[2])-nearestA.positionSubvoxels[2])
|
||||
-nearestRadius+playerCenterAboveFloorQ;
|
||||
// Only compose a support shelf below the capsule feet. This keeps support
|
||||
// probes on the matching floor while capsule samples remain air at bends.
|
||||
if(qz<nearestPlayerCenterZ-0.90*qPerMetre)
|
||||
-supportPathRadius+playerCenterAboveFloorQ;
|
||||
const double nearestPlayerCenterZ=supportPathPlayerCenterZ;
|
||||
const double distanceToCenter=std::max(1.0e-9,bestSignedDistance+nearestRadius);
|
||||
const double verticalWallAlignment=distanceToCenter>1.0e-9
|
||||
?std::clamp(-(qz-(nearestA.positionSubvoxels[2]+bestT*(
|
||||
static_cast<double>(nearestB.positionSubvoxels[2])-nearestA.positionSubvoxels[2])))
|
||||
/distanceToCenter,0.0,1.0):0.0;
|
||||
const float relief=VF_FieldSurfaceRelief(static_cast<float>(X),static_cast<float>(Y),
|
||||
static_cast<float>(Z),static_cast<std::uint32_t>(Owner?Owner->Seed:0));
|
||||
const double wallReliefWeight=1.0-0.80*verticalWallAlignment;
|
||||
const double roughSignedDistance=bestSignedDistance
|
||||
+static_cast<double>(std::clamp(relief,-1.0f,1.0f))*0.24*qPerMetre*wallReliefWeight;
|
||||
// Positive density is air; rough rock is cut with full side/ceiling detail and
|
||||
// gentler relief on the gravity-facing wall so the supported tread stays open.
|
||||
composed=std::max(composed,static_cast<float>(-roughSignedDistance/Q));
|
||||
// At tight bends, keep the tread tied to the player-center profile selected
|
||||
// by horizontal path position so vertical probe offsets cannot shift the ramp.
|
||||
const bool fallbackSupport=qz>=nearestPlayerCenterZ-1.50*qPerMetre;
|
||||
const double supportThicknessVoxels=fallbackSupport
|
||||
?qPerMetre/Q:0.5*qPerMetre/Q;
|
||||
double bestSupportDistanceSquared=std::numeric_limits<double>::infinity();
|
||||
const auto considerSupportSegment=[&](std::ptrdiff_t index)
|
||||
{
|
||||
const double supportCenterQueryQ=qz+playerCenterAboveFloorQ;
|
||||
const auto supportFound=std::lower_bound(passage.controlPoints.begin(),
|
||||
mainEnd,supportCenterQueryQ,
|
||||
[&](const auto& point,double value){return floorProfile(point)>value;});
|
||||
const std::ptrdiff_t supportCenter=supportFound-passage.controlPoints.begin();
|
||||
const std::ptrdiff_t supportBegin=std::max<std::ptrdiff_t>(0,supportCenter-3);
|
||||
const std::ptrdiff_t supportEnd=std::min<std::ptrdiff_t>(lowerMouthIndex-1,supportCenter+2);
|
||||
const auto considerSupportSegment=[&](std::ptrdiff_t index)
|
||||
const auto& segmentA=passage.controlPoints[static_cast<std::size_t>(index)];
|
||||
const auto& segmentB=passage.controlPoints[static_cast<std::size_t>(index+1)];
|
||||
const double dx=static_cast<double>(segmentB.positionSubvoxels[0])-segmentA.positionSubvoxels[0];
|
||||
const double dy=static_cast<double>(segmentB.positionSubvoxels[1])-segmentA.positionSubvoxels[1];
|
||||
const double radiusA=segmentA.radiusSubvoxels;
|
||||
const double radiusB=segmentB.radiusSubvoxels;
|
||||
// Keep the tread within the probe band and below the capsule's
|
||||
// lowest sampled point.
|
||||
const double floorOffset=0.20;
|
||||
const double horizontalLengthSq=dx*dx+dy*dy;
|
||||
const double supportT=horizontalLengthSq>0.0
|
||||
?std::clamp(((qx-segmentA.positionSubvoxels[0])*dx
|
||||
+(qy-segmentA.positionSubvoxels[1])*dy)/horizontalLengthSq,0.0,1.0)
|
||||
:bestT;
|
||||
const double centerX=segmentA.positionSubvoxels[0]+supportT*dx;
|
||||
const double centerY=segmentA.positionSubvoxels[1]+supportT*dy;
|
||||
const double centerZ=segmentA.positionSubvoxels[2]+supportT*(
|
||||
static_cast<double>(segmentB.positionSubvoxels[2])-segmentA.positionSubvoxels[2]);
|
||||
const double radius=segmentA.radiusSubvoxels+supportT*(
|
||||
static_cast<double>(segmentB.radiusSubvoxels)-segmentA.radiusSubvoxels);
|
||||
const double supportPlayerCenterZ=centerZ-radius+playerCenterAboveFloorQ;
|
||||
if(fallbackSupport)
|
||||
{
|
||||
const auto& segmentA=passage.controlPoints[static_cast<std::size_t>(index)];
|
||||
const auto& segmentB=passage.controlPoints[static_cast<std::size_t>(index+1)];
|
||||
const double dx=static_cast<double>(segmentB.positionSubvoxels[0])-segmentA.positionSubvoxels[0];
|
||||
const double dy=static_cast<double>(segmentB.positionSubvoxels[1])-segmentA.positionSubvoxels[1];
|
||||
const double horizontalLengthSq=dx*dx+dy*dy;
|
||||
const double supportT=horizontalLengthSq>0.0
|
||||
?std::clamp(((qx-segmentA.positionSubvoxels[0])*dx
|
||||
+(qy-segmentA.positionSubvoxels[1])*dy)/horizontalLengthSq,0.0,1.0)
|
||||
:bestT;
|
||||
const double supportCenterX=segmentA.positionSubvoxels[0]+supportT*dx;
|
||||
const double supportCenterY=segmentA.positionSubvoxels[1]+supportT*dy;
|
||||
const double supportCenterZ=segmentA.positionSubvoxels[2]+supportT*(
|
||||
static_cast<double>(segmentB.positionSubvoxels[2])-segmentA.positionSubvoxels[2]);
|
||||
const double supportRadius=segmentA.radiusSubvoxels+supportT*(
|
||||
static_cast<double>(segmentB.radiusSubvoxels)-segmentA.radiusSubvoxels);
|
||||
// Leave a 12 cm settle gap below the capsule feet while staying inside
|
||||
// the route support probes' search depth.
|
||||
const double floorQ=supportCenterZ-supportRadius+0.12*qPerMetre;
|
||||
const double floorVoxels=floorQ/Q;
|
||||
const double horizontalX=qx-supportCenterX,horizontalY=qy-supportCenterY;
|
||||
const double horizontalDistance=DeterministicSqrt(horizontalX*horizontalX+horizontalY*horizontalY);
|
||||
const double supportHalfWidth=std::max(0.0,supportRadius-0.15*qPerMetre);
|
||||
if(horizontalDistance<=supportHalfWidth&&Z>=floorVoxels-floorThicknessVoxels
|
||||
&&Z<=floorVoxels+1.0/Q)
|
||||
{
|
||||
if(!hasSupportFloor||floorVoxels>bestSupportFloorVoxels)
|
||||
{
|
||||
bestSupportFloorVoxels=floorVoxels;hasSupportFloor=true;
|
||||
bestSupportDensity=std::clamp(Z-floorVoxels,-floorThicknessVoxels,0.0);
|
||||
}
|
||||
}
|
||||
};
|
||||
for(std::ptrdiff_t index=supportBegin;index<=supportEnd;++index)
|
||||
considerSupportSegment(index);
|
||||
for(std::ptrdiff_t index=lowerMouthIndex;index<lastIndex;++index)
|
||||
considerSupportSegment(index);
|
||||
}
|
||||
const double supportProfileError=std::abs(
|
||||
supportPlayerCenterZ-nearestPlayerCenterZ);
|
||||
if(supportProfileError>0.06*qPerMetre)return;
|
||||
}
|
||||
const double horizontalX=qx-centerX,horizontalY=qy-centerY;
|
||||
const double horizontalDistance=DeterministicSqrt(horizontalX*horizontalX
|
||||
+horizontalY*horizontalY);
|
||||
const double floorQ=centerZ-radius+floorOffset*qPerMetre;
|
||||
const double floorVoxels=floorQ/Q;
|
||||
if(Z<floorVoxels-supportThicknessVoxels||Z>floorVoxels+1.0/Q)return;
|
||||
const double supportHalfWidth=fallbackSupport?radius
|
||||
:std::max(0.0,radius+2.0*qPerMetre);
|
||||
if(horizontalDistance>supportHalfWidth)return;
|
||||
const double supportDistanceSq=horizontalDistance*horizontalDistance
|
||||
+(Z-floorVoxels)*(Z-floorVoxels);
|
||||
if(supportDistanceSq>=bestSupportDistanceSquared)return;
|
||||
bestSupportDistanceSquared=supportDistanceSq;
|
||||
hasSupportFloor=true;
|
||||
bestSupportDensity=std::clamp(Z-floorVoxels,
|
||||
-supportThicknessVoxels,0.0);
|
||||
};
|
||||
const std::ptrdiff_t supportBegin=std::max<std::ptrdiff_t>(0,bestIndex-1);
|
||||
const std::ptrdiff_t supportEnd=std::min<std::ptrdiff_t>(lastIndex-1,bestIndex+1);
|
||||
for(std::ptrdiff_t index=supportBegin;index<=supportEnd;++index)
|
||||
considerSupportSegment(index);
|
||||
if(hasSupportFloor)
|
||||
composed=std::min(composed,static_cast<float>(bestSupportDensity));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user