wip(L25, side branch): FI helix enclosed in a 13 m rock root; support never created in open air

Fixes the L24 ribbon (owner request). NOT merged: the open-air support gate leaves ~4,000 unsupported
samples on every SurfaceWorld-recipe passage style (TN -> SW), a walkability regression. L26 fixes that
on this branch. See Linux25/REPORT.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-21 00:38:57 +02:00
1 parent cb9236c265
commit ef789aaa00
14 files changed
+662 -110

No files matched your search

@@ -249,8 +249,8 @@ namespace VoxelForgeCore
// L'assembleur v1 encode coordonnees et scalaires en 1/256 de voxel, independamment des tuiles.
inline constexpr std::int64_t WorldAssemblySubvoxelsPerVoxel = 256;
inline constexpr std::uint32_t WorldAssemblySchemaVersion = 2;
// Version 10 adds per-strate passage styles and rough wall relief to resolved passages.
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 10;
// Version 12 gates passage supports on native rock and anchors the FI spiral inside a measured root.
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 12;
enum class FieldChannel : std::uint32_t
{
@@ -281,7 +281,7 @@ namespace floating_island_detail
? recipe.ownerVoxelSizeMicrons / ScaleGcd : 0;
const std::uint32_t ExpectedDenominator = ScaleGcd > 0
? recipe.coreVoxelSizeMicrons / ScaleGcd : 0;
if (recipe.schemaVersion != 1 || recipe.geometryVersion < 1 || recipe.geometryVersion > 5
if (recipe.schemaVersion != 1 || recipe.geometryVersion < 1 || recipe.geometryVersion > 7
|| (recipe.geometryVersion == 1
&& (recipe.crustDepthBonusQ16 != 0 || recipe.rootDepthRatioQ16 != 0))
|| recipe.unitsPerVoxel != Q
@@ -756,7 +756,9 @@ namespace floating_island_detail
if (recipe.geometryVersion >= 5 && island.ordinal == recipe.anchorIslandOrdinal
&& recipe.anchorRootBottomZSubvoxels < island.bottomZSubvoxels)
{
const double rootTop = static_cast<double>(island.bottomZSubvoxels);
// The central buttress must meet the island crust underside. IslandBottom
// also includes the four shorter root lobes and sits below that surface.
const double rootTop = static_cast<double>(IslandCrustBottom(recipe, island));
const double rootBottom = static_cast<double>(recipe.anchorRootBottomZSubvoxels);
const double depth = std::max(1.0, rootTop - rootBottom);
const double blend = std::clamp(static_cast<double>(recipe.anchorRootRadiusSubvoxels)
@@ -1330,7 +1332,7 @@ std::optional<FloatingIslandManifestV1> FloatingIslandManifestV1::FromJson(
manifest.recipe.anchorRootBottomZSubvoxels)
||!floating_island_detail::ReadInteger(recipeValue.Get("anchor_mouth_offset_q"),
manifest.recipe.anchorMouthOffsetSubvoxels))
{error="floating-island geometry version 5 is missing a passage-rooted anchor field";return std::nullopt;}
{error="floating-island geometry version 7 is missing a passage-rooted anchor field";return std::nullopt;}
}
if (!floating_island_detail::ReadU32(root.Get("schema_version"), schema)
|| !floating_island_detail::ReadHash(root.Get("content_hash"), manifestHash)
+42 -9
View File
@@ -550,6 +550,14 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
const auto routeAnchor=[](const ResolvedLayoutStratumV2& s,bool departure,
std::array<std::int64_t,3>& out)
{
if(departure)
for(const auto& a:s.anchors)
if(a.kind=="passage_anchor"&&a.values.size()>=10)
{out={a.values[7],a.values[8],a.values[9]};return true;}
if(!departure)
for(const auto& a:s.anchors)
if(a.kind=="passage_arrival"&&a.values.size()>=3)
{out={a.values[0],a.values[1],a.values[2]};return true;}
const ResolvedLayoutAnchorV2* selected=nullptr;
for(const auto& a:s.anchors)
if(a.kind=="route_point"&&a.values.size()>=3)
@@ -597,8 +605,9 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
// Leave room for the capsule and bounded wall relief at the floor/wall seam.
const std::int64_t mouthRadius=std::max<std::int64_t>(static_cast<std::int64_t>(std::llround(1.5*qPerMetre)),
cfg?cfg->mouthRadiusSubvoxels:0);
const std::int64_t midRadius=std::max<std::int64_t>(mouthRadius,
cfg?cfg->midRadiusSubvoxels:0);
const std::int64_t midRadius=std::max<std::int64_t>(
static_cast<std::int64_t>(std::llround(1.5*qPerMetre)),
cfg?cfg->midRadiusSubvoxels:mouthRadius);
const std::int64_t reachMinimum=cfg?std::max<std::int64_t>(0,cfg->reachMinimumSubvoxels):8*qPerMetre;
const std::int64_t reachMaximum=cfg?std::max<std::int64_t>(reachMinimum,cfg->reachMaximumSubvoxels):24*qPerMetre;
const std::uint32_t style=cfg?std::min<std::uint32_t>(cfg->style,3u):1u;
@@ -624,7 +633,9 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
for(const auto& anchor:upper.anchors)
if(anchor.kind=="passage_anchor"&&anchor.values.size()>=7)
{passageAnchor=&anchor;break;}
const bool anchoredSpiral=style==2&&passageAnchor!=nullptr;
const bool anchoredIslandPassage=passageAnchor!=nullptr
&&upper.generatorKind=="FloatingIslands";
const bool anchoredSpiral=style==2&&anchoredIslandPassage;
if(!hasUpperRoute||!hasLowerRoute)
{
const auto xy=PassageRandomXY(PassageSeed(seed,upper.strateIndex,lower.strateIndex,
@@ -652,7 +663,10 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
std::array<std::int64_t,2> upperTangent{},lowerTangent{};
const bool hasUpperTangent=hasUpperRoute
&&routeTangent(upper,true,upperTangent);
const bool hasLowerTangent=hasLowerRoute
const bool hasPassageArrival=std::any_of(lower.anchors.begin(),lower.anchors.end(),
[](const auto& anchor){return anchor.kind=="passage_arrival"
&&anchor.values.size()>=3;});
const bool hasLowerTangent=hasLowerRoute&&!hasPassageArrival
&&routeTangent(lower,false,lowerTangent);
const std::int64_t outerRingRadius=std::max<std::int64_t>(1,
@@ -671,6 +685,8 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
std::max<std::int64_t>(0,cfg?cfg->cascadeLedgeSubvoxels:0)
*static_cast<std::int64_t>(cfg?cfg->cascadeSteps:4)),
mouthRadius+2*qPerMetre,outerRingRadius);
if(anchoredIslandPassage)
ringRadius=std::min(ringRadius,mouthRadius+qPerMetre/2);
std::uint32_t phase=static_cast<std::uint32_t>(PassageSeed(seed,
upper.strateIndex,lower.strateIndex,connection,0x50484153u)%16);
if(hasUpperRoute)
@@ -700,7 +716,7 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
+clockwiseTangentY*upperTangent[1]>=0.0;
}
const int direction=clockwise?1:-1;
constexpr std::int64_t TargetGradeQ16=12500; // leave margin below the 15-degree contract.
constexpr std::int64_t TargetGradeQ16=12500; // leave margin below the 15-degree helix contract.
const std::int64_t wormRadialWobble=std::min<std::int64_t>(
std::max<std::int64_t>(0,wander/3),2*qPerMetre);
const std::int64_t wormCenterWobble=std::min<std::int64_t>(
@@ -716,10 +732,14 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
const double loop=static_cast<double>(loopIndex);
const double phaseA=loop*0.6180339887498948+0.37;
const double phaseB=loop*0.4142135623730950+1.11;
const double jitterX=static_cast<double>(wormCenterWobble)*std::sin(phaseB);
const double jitterY=static_cast<double>(wormCenterWobble)*std::cos(phaseB);
radius+=static_cast<double>(wormRadialWobble)
*std::sin(angle+phaseA);
centerX=static_cast<double>(wormCenterWobble)*std::sin(phaseB);
centerY=static_cast<double>(wormCenterWobble)*std::cos(phaseB);
centerX=jitterX+(anchoredIslandPassage&&passageAnchor
?static_cast<double>(passageAnchor->values[0]):0.0);
centerY=jitterY+(anchoredIslandPassage&&passageAnchor
?static_cast<double>(passageAnchor->values[1]):0.0);
}
else if(pathStyle==3u)
{
@@ -730,6 +750,11 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
cfg->cascadeLedgeSubvoxels):0));
radius-=ledge*(0.5-0.5*std::cos(angle*steps));
}
if(pathStyle!=1u&&anchoredIslandPassage&&passageAnchor)
{
centerX=static_cast<double>(passageAnchor->values[0]);
centerY=static_cast<double>(passageAnchor->values[1]);
}
return std::array<std::int64_t,2>{
static_cast<std::int64_t>(std::llround(centerX+radius*directions[directionIndex][0]/65536.0)),
static_cast<std::int64_t>(std::llround(centerY+radius*directions[directionIndex][1]/65536.0))};
@@ -748,7 +773,11 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
const std::int64_t anchorY=passageAnchor->values[1];
const std::int64_t rootRadius=passageAnchor->values[2];
const std::int64_t rootBottom=passageAnchor->values[5];
helixBedrockZ=rootBottom!=0?rootBottom:contentZQ(upper.bedrock.topMicrometres);
const std::int64_t bedrockTop=rootBottom!=0?rootBottom
:contentZQ(upper.bedrock.topMicrometres);
// Passage route heights are player-center positions. End one capsule
// half-height above the bedrock top so the final ramp step is walkable.
helixBedrockZ=bedrockTop+qPerMetre;
const std::int64_t helixRadius=spiralRadius;
if(rootRadius<helixRadius+midRadius+qPerMetre/4
||std::hypot(static_cast<double>(upperMouth[0]-anchorX),
@@ -796,6 +825,8 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
helixEndIndex=xy.size()-1;
hasAnchorHelix=helixEndIndex>0;
}
const std::int64_t routeGradeQ16=hasAnchorHelix
&&lower.generatorKind=="SurfaceWorld"?8000:TargetGradeQ16;
const std::array<std::int64_t,2> firstRing=ringPoint(phase);
if(hasAnchorHelix&&pathStyle==1u)
{
@@ -843,12 +874,14 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
const std::size_t start=hasAnchorHelix?helixEndIndex:0;
for(std::size_t i=start+1;i<xy.size();++i)
total+=distanceQ(xy[i][0]-xy[i-1][0],xy[i][1]-xy[i-1][1]);
if(!hasLowerTangent&&!xy.empty())
total+=distanceQ(lowerMouth[0]-xy.back()[0],lowerMouth[1]-xy.back()[1]);
return total;
};
while(loops<256)
{
totalHorizontal=measurePath();
if(totalHorizontal*static_cast<std::uint64_t>(TargetGradeQ16)
if(totalHorizontal*static_cast<std::uint64_t>(routeGradeQ16)
>=static_cast<std::uint64_t>(verticalDistance)*65536ull)break;
for(int step=1;step<=16;++step)
{
+50 -41
View File
@@ -110,6 +110,9 @@ public:
const auto composeRouteMouthSupport=[&](std::int32_t strateIndex,
const std::array<std::int64_t,3>& mouth,bool departure)
{
// Support is allowed to reinforce native solid terrain only. Without this
// guard the route-point tread stamps a floating shelf into an open stratum.
if(!(density<0.0f))return;
const double mouthDx=qx-static_cast<double>(mouth[0]);
const double mouthDy=qy-static_cast<double>(mouth[1]);
const double routeSupportReach=13.0*qPerMetre;
@@ -159,26 +162,26 @@ public:
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.
// Keep the native surface route connected to the entrance. Passage carving
// then removes only the parts that overlap the tunnel, whose own tread owns
// the interior ramp.
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.
}
composeRouteMouthSupport(passage.upperStrateIndex,
passage.upperMouthSubvoxels,true);
for(const auto& passage:ResolvedLayout->passages)
{
if(passage.controlPoints.size()<2)continue;
const double placementRadiusQ=static_cast<double>(passage.placementRadiusSubvoxels)
+static_cast<double>(passage.midRadiusSubvoxels);
if(qx*qx+qy*qy>placementRadiusQ*placementRadiusQ)continue;
if(qx*qx+qy*qy>placementRadiusQ*placementRadiusQ)
continue;
const double firstFloorQ=passage.controlPoints.front().positionSubvoxels[2]
-passage.controlPoints.front().radiusSubvoxels+playerCenterAboveFloorQ;
const double lastFloorQ=passage.controlPoints.back().positionSubvoxels[2]
-passage.controlPoints.back().radiusSubvoxels+playerCenterAboveFloorQ;
if(qz>firstFloorQ+2.0*passage.mouthRadiusSubvoxels
||qz<lastFloorQ-2.0*passage.mouthRadiusSubvoxels)continue;
||qz<lastFloorQ-2.0*passage.mouthRadiusSubvoxels)
continue;
const auto floorProfile=[&](const auto& point)
{return point.positionSubvoxels[2]-point.radiusSubvoxels+playerCenterAboveFloorQ;};
const std::ptrdiff_t lastIndex=static_cast<std::ptrdiff_t>(passage.controlPoints.size())-1;
@@ -237,21 +240,6 @@ public:
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 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])
-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*(
@@ -265,11 +253,6 @@ public:
// 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)
{
@@ -293,21 +276,28 @@ public:
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 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 supportPlayerCenterZ=centerZ-radius+playerCenterAboveFloorQ;
const double supportQueryDepth=supportPlayerCenterZ-qz;
// Support belongs just below the avatar's foot plane. Queries within
// the capsule body, or a neighbouring coil's overlapping bore, must
// not turn that tread into a ceiling or mid-air shelf.
if(supportQueryDepth<0.93*qPerMetre||supportQueryDepth>1.30*qPerMetre)return;
const bool fallbackSupport=qz>=supportPlayerCenterZ-1.50*qPerMetre;
const double supportThicknessVoxels=fallbackSupport
?qPerMetre/Q:0.5*qPerMetre/Q;
const double floorQ=centerZ-radius+floorOffset*qPerMetre;
const double floorVoxels=floorQ/Q;
// Keep the supported ramp to a comfortable walking width. Extending a
// full bore-radius shelf across overlapping helix coils can block the
// capsule on the neighbouring turn.
const bool lowerMouthBodyOverlap=index>=lowerMouthIndex-2
&&supportQueryDepth<0.98*qPerMetre;
const double supportHalfWidth=std::min(radius,
(lowerMouthBodyOverlap?0.90:1.25)*qPerMetre);
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);
@@ -317,11 +307,30 @@ public:
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);
std::ptrdiff_t supportCenterIndex=bestIndex;
if(bestIndex<lowerMouthIndex)
{
// A helix can have multiple centerlines at one XY. Select the tread
// whose floor is just below this density query, not the closest wall.
const double supportProfileTarget=qz+0.93*qPerMetre;
const auto supportProfile=std::lower_bound(passage.controlPoints.begin(),mainEnd,
supportProfileTarget,[&](const auto& point,double value)
{return floorProfile(point)>value;});
const std::ptrdiff_t profileIndex=std::clamp<std::ptrdiff_t>(
supportProfile-passage.controlPoints.begin(),0,lowerMouthIndex);
// At floor height the nearest 3D centerline can belong to the next
// coil below. The descending floor profile identifies the coil that
// owns this tread, even when it is more than eight control points away.
supportCenterIndex=profileIndex;
}
const std::ptrdiff_t supportBegin=std::max<std::ptrdiff_t>(0,supportCenterIndex-1);
const std::ptrdiff_t supportEnd=std::min<std::ptrdiff_t>(lastIndex-1,
supportCenterIndex+1);
for(std::ptrdiff_t index=supportBegin;index<=supportEnd;++index)
considerSupportSegment(index);
if(hasSupportFloor)
// Keep route treads inside the native rock envelope. Carving may reopen the
// floor above this point, but a support cannot create a new surface in air.
if(hasSupportFloor&&density<0.0f)
composed=std::min(composed,static_cast<float>(bestSupportDensity));
}
return composed;