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:
Fr0zkaandClaude Opus 5.5 committed 2026-10-20 21:42:21 +02:00
1 parent 71d09a8c52
commit cb9236c265
17 files changed
+922 -122

No files matched your search

+195 -19
View File
@@ -594,12 +594,19 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
const std::int64_t inner=cfg?std::min(cfg->distanceMinimumSubvoxels,
cfg->distanceMaximumSubvoxels):8*qPerMetre;
const std::int64_t placementRadius=std::max<std::int64_t>(1,outer);
const std::int64_t mouthRadius=std::max<std::int64_t>(static_cast<std::int64_t>(std::llround(1.25*qPerMetre)),
// 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 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;
const std::uint32_t segments=cfg?std::clamp<std::uint32_t>(cfg->segments,16u,48u):20u;
const std::int64_t wander=cfg?std::max<std::int64_t>(0,cfg->wanderSubvoxels):3*qPerMetre;
const std::int64_t spiralRadius=cfg?std::max<std::int64_t>(0,cfg->spiralRadiusSubvoxels):6*qPerMetre;
const std::int64_t spiralTurnsQ16=cfg?std::max<std::int64_t>(16384,cfg->spiralTurnsQ16):2ll<<16;
const std::int64_t verticalWobble=cfg?std::max<std::int64_t>(0,cfg->verticalWobbleSubvoxels):0;
const std::uint32_t connectionCount=cfg?static_cast<std::uint32_t>(std::clamp(cfg->connections,1,12)):1;
for(std::uint32_t connection=0;connection<connectionCount;++connection)
{
@@ -612,6 +619,12 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
std::array<std::int64_t,3> upperMouth{},lowerMouth{};
const bool hasUpperRoute=connection==0&&routeAnchor(upper,true,upperMouth);
const bool hasLowerRoute=connection==0&&routeAnchor(lower,false,lowerMouth);
const ResolvedLayoutAnchorV2* passageAnchor=nullptr;
if(connection==0)
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;
if(!hasUpperRoute||!hasLowerRoute)
{
const auto xy=PassageRandomXY(PassageSeed(seed,upper.strateIndex,lower.strateIndex,
@@ -642,8 +655,22 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
const bool hasLowerTangent=hasLowerRoute
&&routeTangent(lower,false,lowerTangent);
const std::int64_t ringRadius=std::max<std::int64_t>(1,
const std::int64_t outerRingRadius=std::max<std::int64_t>(1,
placementRadius-mouthRadius-qPerMetre/2);
const std::uint32_t pathStyle=anchoredSpiral?1u:style;
std::int64_t ringRadius=outerRingRadius;
if(pathStyle==1u)
ringRadius=std::min<std::int64_t>(outerRingRadius,
std::max<std::int64_t>(mouthRadius+2*qPerMetre,placementRadius*5/8));
else if(pathStyle==2u)
ringRadius=std::clamp(spiralRadius,
std::min(outerRingRadius,mouthRadius+qPerMetre/2),outerRingRadius);
else if(pathStyle==3u)
ringRadius=std::clamp(outerRingRadius
-std::min<std::int64_t>(outerRingRadius/3,
std::max<std::int64_t>(0,cfg?cfg->cascadeLedgeSubvoxels:0)
*static_cast<std::int64_t>(cfg?cfg->cascadeSteps:4)),
mouthRadius+2*qPerMetre,outerRingRadius);
std::uint32_t phase=static_cast<std::uint32_t>(PassageSeed(seed,
upper.strateIndex,lower.strateIndex,connection,0x50484153u)%16);
if(hasUpperRoute)
@@ -662,28 +689,159 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
{bestAlignment=alignment;phase=candidate;}
}
}
const bool clockwise=(PassageSeed(seed,upper.strateIndex,lower.strateIndex,
bool clockwise=(PassageSeed(seed,upper.strateIndex,lower.strateIndex,
connection,0x44495245u)&1u)!=0;
const int direction=clockwise?1:-1;
const auto ringPoint=[&](std::uint32_t directionIndex)
if(anchoredSpiral&&passageAnchor&&hasUpperTangent)
{
const double radialX=static_cast<double>(upperMouth[0]-passageAnchor->values[0]);
const double radialY=static_cast<double>(upperMouth[1]-passageAnchor->values[1]);
const double clockwiseTangentX=-radialY,clockwiseTangentY=radialX;
clockwise=clockwiseTangentX*upperTangent[0]
+clockwiseTangentY*upperTangent[1]>=0.0;
}
const int direction=clockwise?1:-1;
constexpr std::int64_t TargetGradeQ16=12500; // leave margin below the 15-degree 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>(
std::max<std::int64_t>(0,wander/6),qPerMetre);
const auto ringPoint=[&](std::uint32_t directionIndex,std::uint32_t loopIndex=0)
{
const double angle=static_cast<double>(directionIndex)*
(3.14159265358979323846/8.0);
double radius=static_cast<double>(ringRadius);
double centerX=0.0,centerY=0.0;
if(pathStyle==1u)
{
const double loop=static_cast<double>(loopIndex);
const double phaseA=loop*0.6180339887498948+0.37;
const double phaseB=loop*0.4142135623730950+1.11;
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);
}
else if(pathStyle==3u)
{
const std::uint32_t steps=std::max<std::uint32_t>(1,
cfg?cfg->cascadeSteps:4u);
const double ledge=std::min<double>(ringRadius/4,
static_cast<double>(cfg?std::max<std::int64_t>(0,
cfg->cascadeLedgeSubvoxels):0));
radius-=ledge*(0.5-0.5*std::cos(angle*steps));
}
return std::array<std::int64_t,2>{
static_cast<std::int64_t>(ringRadius)*directions[directionIndex][0]/65536,
static_cast<std::int64_t>(ringRadius)*directions[directionIndex][1]/65536};
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))};
};
std::vector<std::array<std::int64_t,2>> xy;
const auto pushXY=[&](std::array<std::int64_t,2> p)
{if(xy.empty()||xy.back()!=p)xy.push_back(p);};
const auto firstRing=ringPoint(phase);
std::size_t helixEndIndex=0;
std::int64_t helixBedrockZ=0;
bool hasAnchorHelix=false;
pushXY({upperMouth[0],upperMouth[1]});
if(anchoredSpiral&&passageAnchor&&upper.bedrock.presentInCurrentField
&&spiralRadius>0)
{
const std::int64_t anchorX=passageAnchor->values[0];
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 helixRadius=spiralRadius;
if(rootRadius<helixRadius+midRadius+qPerMetre/4
||std::hypot(static_cast<double>(upperMouth[0]-anchorX),
static_cast<double>(upperMouth[1]-anchorY))
>static_cast<double>(rootRadius+qPerMetre/2)
||upperMouth[2]<=helixBedrockZ)
{error=passage.stableId+" anchor root cannot contain its authored spiral mouth or bedrock descent";return false;}
const double drop=static_cast<double>(upperMouth[2]-helixBedrockZ);
const double pointsPerTurn=static_cast<double>(segments);
const double polygonCircumference=2.0*3.14159265358979323846
*static_cast<double>(helixRadius)
*(std::sin(3.14159265358979323846/pointsPerTurn)
/(3.14159265358979323846/pointsPerTurn));
const double horizontalPerTurn=polygonCircumference
*static_cast<double>(TargetGradeQ16)/65536.0;
const std::uint32_t authoredTurns=static_cast<std::uint32_t>(std::max<std::int64_t>(1,
(spiralTurnsQ16+65535)/65536));
const std::uint32_t gradeTurns=static_cast<std::uint32_t>(std::max(1.0,
std::ceil(drop/std::max(1.0,horizontalPerTurn))));
// The first coil grows from the island mouth offset to the full authored
// radius, so reserve one extra turn for that shorter opening revolution.
const std::uint32_t turns=std::max(authoredTurns,gradeTurns+1);
if(turns>512)
{error=passage.stableId+" authored spiral radius needs more than 512 turns to meet walkable grade";return false;}
const double startAngle=std::atan2(static_cast<double>(upperMouth[1]-anchorY),
static_cast<double>(upperMouth[0]-anchorX));
const std::uint32_t helixPoints=turns*segments;
for(std::uint32_t point=1;point<=helixPoints;++point)
{
const double t=static_cast<double>(point)/helixPoints;
const double angle=startAngle+static_cast<double>(clockwise?1:-1)
*2.0*3.14159265358979323846*t*turns;
const double radialT=std::min(1.0,static_cast<double>(point)/segments);
const double mouthOffset=std::clamp(static_cast<double>(passageAnchor->values[3]),
0.0,static_cast<double>(helixRadius));
const double radial=mouthOffset+(static_cast<double>(helixRadius)-mouthOffset)
*radialT;
const std::array<std::int64_t,2> p{
static_cast<std::int64_t>(std::llround(anchorX
+radial*std::cos(angle))),
static_cast<std::int64_t>(std::llround(anchorY
+radial*std::sin(angle)))};
pushXY(p);
}
helixEndIndex=xy.size()-1;
hasAnchorHelix=helixEndIndex>0;
}
const std::array<std::int64_t,2> firstRing=ringPoint(phase);
if(hasAnchorHelix&&pathStyle==1u)
{
// Let the anchored helix widen into the winding inter-strate route
// over a full, smooth coil. A direct jump from the root radius to the
// outer worm ring creates a sharp support seam inside the rock neck.
const double anchorX=static_cast<double>(passageAnchor->values[0]);
const double anchorY=static_cast<double>(passageAnchor->values[1]);
const double startAngle=std::atan2(static_cast<double>(upperMouth[1])-anchorY,
static_cast<double>(upperMouth[0])-anchorX);
const double targetX=static_cast<double>(firstRing[0])-anchorX;
const double targetY=static_cast<double>(firstRing[1])-anchorY;
const double targetAngle=std::atan2(targetY,targetX);
const double angleDelta=static_cast<double>(direction)*2.0
*3.14159265358979323846
+std::remainder(targetAngle-startAngle,2.0*3.14159265358979323846);
const double startRadius=static_cast<double>(spiralRadius);
const std::int64_t targetDx=firstRing[0]-passageAnchor->values[0];
const std::int64_t targetDy=firstRing[1]-passageAnchor->values[1];
const double targetRadius=static_cast<double>(PassageIsqrt(
static_cast<std::uint64_t>(targetDx*targetDx+targetDy*targetDy)));
const std::uint32_t transitionSegments=static_cast<std::uint32_t>(
std::clamp(std::ceil(std::abs(angleDelta)*(startRadius+targetRadius)
/(2.0*0.75*qPerMetre)),static_cast<double>(segments),256.0));
for(std::uint32_t step=1;step<=transitionSegments;++step)
{
const double t=static_cast<double>(step)/transitionSegments;
const double smooth=t*t*(3.0-2.0*t);
const double radius=startRadius+(targetRadius-startRadius)*smooth;
const double angle=startAngle+angleDelta*t;
const std::array<std::int64_t,2> point=step==transitionSegments?firstRing:
std::array<std::int64_t,2>{
static_cast<std::int64_t>(std::llround(anchorX+radius*std::cos(angle))),
static_cast<std::int64_t>(std::llround(anchorY+radius*std::sin(angle)))};
pushXY(point);
}
}
pushXY(firstRing);
std::uint64_t totalHorizontal=0;std::uint32_t loops=0;
const std::int64_t verticalDistance=std::abs(lowerMouth[2]-upperMouth[2]);
constexpr std::int64_t TargetGradeQ16=12500; // leave margin below the 15-degree contract.
const std::int64_t verticalDistance=hasAnchorHelix
?std::abs(lowerMouth[2]-helixBedrockZ):std::abs(lowerMouth[2]-upperMouth[2]);
auto measurePath=[&]()
{
std::uint64_t total=0;
for(std::size_t i=1;i<xy.size();++i)
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]);
return total;
};
@@ -696,7 +854,8 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
{
int directionIndex=static_cast<int>(phase)+direction*step;
directionIndex=(directionIndex%16+16)%16;
pushXY(ringPoint(static_cast<std::uint32_t>(directionIndex)));
pushXY(ringPoint(static_cast<std::uint32_t>(directionIndex),
loops+(step==16?1u:0u)));
}
++loops;
}
@@ -710,7 +869,7 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
{
const std::uint32_t candidate=static_cast<std::uint32_t>(
(hashedLowerPhase+offset)%16);
const auto terminal=ringPoint(candidate);
const auto terminal=ringPoint(candidate,loops);
const std::int64_t dx=lowerMouth[0]-terminal[0];
const std::int64_t dy=lowerMouth[1]-terminal[1];
const std::int64_t alignment=dx*lowerTangent[0]+dy*lowerTangent[1];
@@ -728,7 +887,7 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
{
int directionIndex=static_cast<int>(phase)+direction*step;
directionIndex=(directionIndex%16+16)%16;
pushXY(ringPoint(static_cast<std::uint32_t>(directionIndex)));
pushXY(ringPoint(static_cast<std::uint32_t>(directionIndex),loops));
if(static_cast<std::uint32_t>(directionIndex)==lowerPhase)break;
}
pushXY(ringPoint(lowerPhase));
@@ -775,16 +934,33 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
for(std::size_t i=1;i<xy.size();++i)
cumulative[i]=cumulative[i-1]+distanceQ(xy[i][0]-xy[i-1][0],xy[i][1]-xy[i-1][1]);
const std::uint64_t mouthHorizontal=cumulative[lowerMouthIndex];
const std::uint64_t helixHorizontal=hasAnchorHelix
?cumulative[helixEndIndex]:0;
if(mouthHorizontal==0)
{error=passage.stableId+" has no horizontal reach before its lower mouth";return false;}
if(hasAnchorHelix&&helixHorizontal==0)
{error=passage.stableId+" anchor spiral has no horizontal reach";return false;}
passage.controlPoints.reserve(xy.size());
for(std::size_t i=0;i<xy.size();++i)
{
const std::int64_t radius=(i<=1||i+1>=xy.size()-1)?mouthRadius:midRadius;
const std::int64_t playerZ=i<=lowerMouthIndex
?upperMouth[2]+PassageRoundRatio(lowerMouth[2]-upperMouth[2],
static_cast<std::int64_t>(cumulative[i]),static_cast<std::int64_t>(mouthHorizontal))
:lowerLandingPoints[i-lowerMouthIndex-1][2];
const std::int64_t radius=(i<=1||i==lowerMouthIndex
||i+1>=xy.size()-1)?mouthRadius:midRadius;
std::int64_t playerZ=0;
if(i>lowerMouthIndex)
playerZ=lowerLandingPoints[i-lowerMouthIndex-1][2];
else if(hasAnchorHelix&&i<=helixEndIndex)
playerZ=upperMouth[2]+PassageRoundRatio(helixBedrockZ-upperMouth[2],
static_cast<std::int64_t>(cumulative[i]),
static_cast<std::int64_t>(helixHorizontal));
else
{
const std::uint64_t segmentStart=hasAnchorHelix?helixHorizontal:0;
const std::int64_t startZ=hasAnchorHelix?helixBedrockZ:upperMouth[2];
const std::uint64_t segmentLength=mouthHorizontal-segmentStart;
playerZ=startZ+PassageRoundRatio(lowerMouth[2]-startZ,
static_cast<std::int64_t>(cumulative[i]-segmentStart),
static_cast<std::int64_t>(segmentLength));
}
ResolvedLayoutPassagePointV1 point;
point.positionSubvoxels={xy[i][0],xy[i][1],playerZ+radius-playerCenterAboveFloorQ};
point.radiusSubvoxels=radius;passage.controlPoints.push_back(point);