passages: four-strate passage floors supported (L67, evaluator v212); 1-2 wall beat still red
- Shared composer: a passage's restored support is applied after a physically overlapping later bore, instead of being carved away by it (0-1: 59 -> 0 unsupported). - Capsule clearance for support restoration is tested against nearby route segments only (1-2: 3 -> 0, 2-3: 2 -> 0 unsupported). - L66 index switches replaced by a physical criterion (both mouths inside their strata bands, neither a SurfaceWorld landing). - Audit: floor zero-crossing refined by bisection (the 0.2498 m "step" was the gauge); wall ripple keeps the authored radius, cubic detrend per 32 m window. Controls detected: L35 ribs, beads, synthetic 0.25 m step, L36 washboard, L23 terrace. - Result: 0-1 and 2-3 pass all audits; 1-2 fails wall ripple 7.27 cm (limit 3.5) from an authored-radius beat at 7.06 m. Fast tier was green before the final gauge change and was not re-run after it. 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
5e2fc53d39
commit
087333bf8a
17 files changed
+1439
-43
No files matched your search
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <iomanip>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
@@ -2459,18 +2460,29 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
}
|
||||
std::uint32_t phase=static_cast<std::uint32_t>(PassageSeed(seed,
|
||||
upper.strateIndex,lower.strateIndex,connection,0x50484153u)%16);
|
||||
if(hasUpperRoute)
|
||||
// Authored tangents and island anchors already determine the phase.
|
||||
// A generated mouth in a native cave uses its radial approach; an
|
||||
// island landing uses the tangent fit below to ease off the crater rim.
|
||||
const bool generatedIslandWormEntry=!hasUpperRoute&&!hasUpperTangent
|
||||
&&!anchoredSpiral&&upper.generatorKind=="FloatingIslands";
|
||||
if(hasUpperRoute||(anchoredIslandPassage&&!anchoredSpiral)
|
||||
||(!hasUpperRoute&&!anchoredIslandPassage&&!anchoredSpiral
|
||||
&&upper.generatorKind!="FloatingIslands"))
|
||||
{
|
||||
std::int64_t bestAlignment=std::numeric_limits<std::int64_t>::min();
|
||||
const std::uint32_t hashedPhase=phase;
|
||||
const std::int64_t phaseX=style==0&&anchoredIslandPassage
|
||||
&&!anchoredSpiral&&passageAnchor
|
||||
?upperMouth[0]-passageAnchor->values[0]
|
||||
:(hasUpperTangent?upperTangent[0]:upperMouth[0]);
|
||||
:(hasUpperTangent?upperTangent[0]:upperMouth[0]
|
||||
-(anchoredIslandPassage&&passageAnchor
|
||||
?passageAnchor->values[0]:0));
|
||||
const std::int64_t phaseY=style==0&&anchoredIslandPassage
|
||||
&&!anchoredSpiral&&passageAnchor
|
||||
?upperMouth[1]-passageAnchor->values[1]
|
||||
:(hasUpperTangent?upperTangent[1]:upperMouth[1]);
|
||||
:(hasUpperTangent?upperTangent[1]:upperMouth[1]
|
||||
-(anchoredIslandPassage&&passageAnchor
|
||||
?passageAnchor->values[1]:0));
|
||||
for(std::uint32_t offset=0;offset<16;++offset)
|
||||
{
|
||||
const std::uint32_t candidate=static_cast<std::uint32_t>(
|
||||
@@ -2528,7 +2540,7 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
clockwise=clockwiseTangentX*upperTangent[0]
|
||||
+clockwiseTangentY*upperTangent[1]>=0.0;
|
||||
}
|
||||
const int direction=clockwise?1:-1;
|
||||
int direction=clockwise?1:-1;
|
||||
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);
|
||||
@@ -2597,6 +2609,267 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
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))};
|
||||
};
|
||||
if(generatedIslandWormEntry)
|
||||
{
|
||||
struct FPhaseScore
|
||||
{
|
||||
std::uint32_t phase=0;
|
||||
int direction=1;
|
||||
std::uint64_t routeLength=0;
|
||||
double clearance=0.0;
|
||||
double tangent=0.0;
|
||||
};
|
||||
std::array<FPhaseScore,32> phaseScores{};
|
||||
std::size_t phaseScoreCount=0;
|
||||
const std::uint32_t hashedPhase=phase;
|
||||
const int hashedDirection=direction;
|
||||
const std::int64_t entryVerticalDistance=std::abs(
|
||||
lowerMouth[2]-upperMouth[2]);
|
||||
const std::int64_t entryRouteGradeQ16=profiledSurfaceJoin?8000
|
||||
:style==0&&anchoredIslandPassage&&!anchoredSpiral?15500
|
||||
:TargetGradeQ16;
|
||||
const auto candidateRouteLength=[&](std::uint32_t candidate,int candidateDirection)
|
||||
{
|
||||
std::vector<std::array<std::int64_t,2>> candidateXY;
|
||||
const auto append=[&](std::array<std::int64_t,2> point)
|
||||
{if(candidateXY.empty()||candidateXY.back()!=point)
|
||||
candidateXY.push_back(point);};
|
||||
append({upperMouth[0],upperMouth[1]});
|
||||
append(ringPoint(candidate));
|
||||
const auto measure=[&]()
|
||||
{
|
||||
std::uint64_t length=0;
|
||||
for(std::size_t i=1;i<candidateXY.size();++i)
|
||||
length+=distanceQ(candidateXY[i][0]-candidateXY[i-1][0],
|
||||
candidateXY[i][1]-candidateXY[i-1][1]);
|
||||
length+=distanceQ(lowerMouth[0]-candidateXY.back()[0],
|
||||
lowerMouth[1]-candidateXY.back()[1]);
|
||||
return length;
|
||||
};
|
||||
std::uint32_t loops=0;
|
||||
std::uint64_t length=0;
|
||||
while(loops<256)
|
||||
{
|
||||
length=measure();
|
||||
if(length*static_cast<std::uint64_t>(entryRouteGradeQ16)
|
||||
>=static_cast<std::uint64_t>(entryVerticalDistance)*65536ull)break;
|
||||
for(int step=1;step<=16;++step)
|
||||
{
|
||||
int directionIndex=static_cast<int>(candidate)
|
||||
+candidateDirection*step;
|
||||
directionIndex=(directionIndex%16+16)%16;
|
||||
append(ringPoint(static_cast<std::uint32_t>(directionIndex),
|
||||
loops+(step==16?1u:0u)));
|
||||
}
|
||||
++loops;
|
||||
}
|
||||
if(loops>=256)return std::uint64_t{0};
|
||||
const std::int64_t mouthPlanSeparation=distanceQ(
|
||||
lowerMouth[0]-upperMouth[0],lowerMouth[1]-upperMouth[1]);
|
||||
if(!hasLowerTangent&&!anchoredSpiral&&pathStyle==1u&&loops<=1
|
||||
&&mouthPlanSeparation<=2*mouthRadius
|
||||
&&entryVerticalDistance<=2*mouthRadius+qPerMetre/2)
|
||||
{
|
||||
double bestExitScore=-std::numeric_limits<double>::infinity();
|
||||
std::uint32_t lowerPhase=candidate;
|
||||
const auto firstRing=ringPoint(candidate);
|
||||
for(std::uint32_t exitCandidate=0;exitCandidate<16;++exitCandidate)
|
||||
{
|
||||
if(exitCandidate==candidate)continue;
|
||||
const std::uint32_t previous=static_cast<std::uint32_t>(
|
||||
(static_cast<int>(exitCandidate)-candidateDirection+16)%16);
|
||||
const auto terminal=ringPoint(exitCandidate,loops);
|
||||
const auto beforeTerminal=ringPoint(previous,loops);
|
||||
const double incomingX=static_cast<double>(terminal[0]-beforeTerminal[0]);
|
||||
const double incomingY=static_cast<double>(terminal[1]-beforeTerminal[1]);
|
||||
const double outgoingX=static_cast<double>(lowerMouth[0]-terminal[0]);
|
||||
const double outgoingY=static_cast<double>(lowerMouth[1]-terminal[1]);
|
||||
const double incomingLength=std::hypot(incomingX,incomingY);
|
||||
const double outgoingLength=std::hypot(outgoingX,outgoingY);
|
||||
if(incomingLength<=1.0e-6||outgoingLength<=1.0e-6)continue;
|
||||
const double alignment=(incomingX*outgoingX+incomingY*outgoingY)
|
||||
/(incomingLength*outgoingLength);
|
||||
const double separation=std::hypot(
|
||||
static_cast<double>(terminal[0]-firstRing[0]),
|
||||
static_cast<double>(terminal[1]-firstRing[1]));
|
||||
const double score=alignment+0.20*std::clamp(
|
||||
separation/(2.0*qPerMetre),0.0,2.0);
|
||||
if(score>bestExitScore)
|
||||
{bestExitScore=score;lowerPhase=exitCandidate;}
|
||||
}
|
||||
for(int step=1;step<=16;++step)
|
||||
{
|
||||
int directionIndex=static_cast<int>(candidate)
|
||||
+candidateDirection*step;
|
||||
directionIndex=(directionIndex%16+16)%16;
|
||||
append(ringPoint(static_cast<std::uint32_t>(directionIndex),loops));
|
||||
if(static_cast<std::uint32_t>(directionIndex)==lowerPhase)break;
|
||||
}
|
||||
}
|
||||
return measure();
|
||||
};
|
||||
for(std::uint32_t offset=0;offset<16;++offset)
|
||||
{
|
||||
const std::uint32_t candidate=static_cast<std::uint32_t>(
|
||||
(hashedPhase+16-offset)%16);
|
||||
const auto point=ringPoint(candidate);
|
||||
const double entryX=static_cast<double>(point[0]-upperMouth[0]);
|
||||
const double entryY=static_cast<double>(point[1]-upperMouth[1]);
|
||||
const double entryLength=std::hypot(entryX,entryY);
|
||||
if(entryLength<=1.0e-6)continue;
|
||||
const auto entryDistance=distanceQ(point[0]-upperMouth[0],
|
||||
point[1]-upperMouth[1]);
|
||||
for(const int candidateDirection:{hashedDirection,-hashedDirection})
|
||||
{
|
||||
const std::uint64_t routeLength=candidateRouteLength(candidate,
|
||||
candidateDirection);
|
||||
if(routeLength==0)continue;
|
||||
const std::uint32_t next=static_cast<std::uint32_t>(
|
||||
(static_cast<int>(candidate)+candidateDirection+16)%16);
|
||||
const auto nextPoint=ringPoint(next);
|
||||
const double tangentX=static_cast<double>(nextPoint[0]-point[0]);
|
||||
const double tangentY=static_cast<double>(nextPoint[1]-point[1]);
|
||||
const double tangentLength=std::hypot(tangentX,tangentY);
|
||||
if(tangentLength<=1.0e-6)continue;
|
||||
const double tangentAlignment=(entryX*tangentX+entryY*tangentY)
|
||||
/(entryLength*tangentLength);
|
||||
const std::int64_t firstAxisZ=upperMouth[2]+mouthRadius
|
||||
-playerCenterAboveFloorQ;
|
||||
const std::int64_t secondAxisZ=firstAxisZ+PassageRoundRatio(
|
||||
lowerMouth[2]-upperMouth[2],static_cast<std::int64_t>(entryDistance),
|
||||
static_cast<std::int64_t>(routeLength));
|
||||
double candidateTreadClearance=std::numeric_limits<double>::infinity();
|
||||
const int entrySamples=std::max(1,static_cast<int>(std::ceil(
|
||||
entryLength/(0.25*qPerMetre))));
|
||||
for(int sample=0;sample<=entrySamples;++sample)
|
||||
{
|
||||
const double t=static_cast<double>(sample)/entrySamples;
|
||||
const double qx=upperMouth[0]+t*entryX;
|
||||
const double qy=upperMouth[1]+t*entryY;
|
||||
const double qz=firstAxisZ+t*(secondAxisZ-firstAxisZ)
|
||||
-mouthRadius+0.20*qPerMetre;
|
||||
for(const auto& previousPassage:layout.passages)
|
||||
{
|
||||
if(previousPassage.stableId==passage.stableId
|
||||
||previousPassage.controlPoints.size()<2)continue;
|
||||
for(std::size_t previousSegment=0;
|
||||
previousSegment+1<previousPassage.controlPoints.size();
|
||||
++previousSegment)
|
||||
{
|
||||
const auto& previousA=previousPassage.controlPoints[previousSegment];
|
||||
const auto& previousB=previousPassage.controlPoints[previousSegment+1];
|
||||
const double ax=static_cast<double>(previousA.positionSubvoxels[0]);
|
||||
const double ay=static_cast<double>(previousA.positionSubvoxels[1]);
|
||||
const double az=static_cast<double>(previousA.positionSubvoxels[2]);
|
||||
const double dx=static_cast<double>(previousB.positionSubvoxels[0])-ax;
|
||||
const double dy=static_cast<double>(previousB.positionSubvoxels[1])-ay;
|
||||
const double dz=static_cast<double>(previousB.positionSubvoxels[2])-az;
|
||||
const double horizontalLengthSquared=dx*dx+dy*dy;
|
||||
const double projection=horizontalLengthSquared>0.0
|
||||
?std::clamp(((qx-ax)*dx+(qy-ay)*dy)
|
||||
/horizontalLengthSquared,0.0,1.0):0.0;
|
||||
const double centerX=ax+projection*dx;
|
||||
const double centerY=ay+projection*dy;
|
||||
const double centerZ=az+projection*dz
|
||||
-(previousA.radiusSubvoxels+projection*(
|
||||
static_cast<double>(previousB.radiusSubvoxels)
|
||||
-previousA.radiusSubvoxels))
|
||||
+1.13*qPerMetre;
|
||||
const double vertical=std::max(0.0,
|
||||
std::abs(qz-centerZ)-0.54*qPerMetre);
|
||||
const double clearance=std::sqrt(
|
||||
(qx-centerX)*(qx-centerX)+(qy-centerY)*(qy-centerY)
|
||||
+vertical*vertical)-0.34*qPerMetre;
|
||||
candidateTreadClearance=std::min(candidateTreadClearance,
|
||||
clearance);
|
||||
}
|
||||
}
|
||||
}
|
||||
double previousTreadClearance=std::numeric_limits<double>::infinity();
|
||||
const double entryXQ=entryX,entryYQ=entryY;
|
||||
const double entryZQ=static_cast<double>(secondAxisZ-firstAxisZ);
|
||||
const double entryLengthSquared=entryXQ*entryXQ+entryYQ*entryYQ
|
||||
+entryZQ*entryZQ;
|
||||
const double mouthStartT=std::clamp(
|
||||
mouthRadius/entryLength,0.0,1.0);
|
||||
for(const auto& previousPassage:layout.passages)
|
||||
{
|
||||
if(previousPassage.stableId==passage.stableId
|
||||
||previousPassage.controlPoints.size()<2)continue;
|
||||
for(std::size_t previousSegment=0;
|
||||
previousSegment+1<previousPassage.controlPoints.size();
|
||||
++previousSegment)
|
||||
{
|
||||
const auto& previousA=previousPassage.controlPoints[previousSegment];
|
||||
const auto& previousB=previousPassage.controlPoints[previousSegment+1];
|
||||
const double ax=static_cast<double>(previousA.positionSubvoxels[0]);
|
||||
const double ay=static_cast<double>(previousA.positionSubvoxels[1]);
|
||||
const double az=static_cast<double>(previousA.positionSubvoxels[2]);
|
||||
const double dx=static_cast<double>(previousB.positionSubvoxels[0])-ax;
|
||||
const double dy=static_cast<double>(previousB.positionSubvoxels[1])-ay;
|
||||
const double dz=static_cast<double>(previousB.positionSubvoxels[2])-az;
|
||||
for(int sample=0;sample<=8;++sample)
|
||||
{
|
||||
const double t=static_cast<double>(sample)/8.0;
|
||||
const double px=ax+t*dx;
|
||||
const double py=ay+t*dy;
|
||||
const double pz=az+t*dz
|
||||
-(previousA.radiusSubvoxels+t*(
|
||||
static_cast<double>(previousB.radiusSubvoxels)
|
||||
-previousA.radiusSubvoxels))
|
||||
+0.20*qPerMetre;
|
||||
const double projection=entryLengthSquared>0.0
|
||||
?std::clamp(((px-upperMouth[0])*entryXQ
|
||||
+(py-upperMouth[1])*entryYQ
|
||||
+(pz-firstAxisZ)*entryZQ)/entryLengthSquared,
|
||||
mouthStartT,1.0):mouthStartT;
|
||||
const double ex=px-(upperMouth[0]+projection*entryXQ);
|
||||
const double ey=py-(upperMouth[1]+projection*entryYQ);
|
||||
const double ez=pz-(firstAxisZ+projection*entryZQ);
|
||||
const double clearance=std::sqrt(ex*ex+ey*ey+ez*ez)
|
||||
-mouthRadius;
|
||||
previousTreadClearance=std::min(previousTreadClearance,
|
||||
clearance);
|
||||
}
|
||||
}
|
||||
}
|
||||
const double clearanceScore=std::isfinite(candidateTreadClearance)
|
||||
&&std::isfinite(previousTreadClearance)
|
||||
?std::min(candidateTreadClearance,previousTreadClearance)/qPerMetre
|
||||
:0.0;
|
||||
if(phaseScoreCount<phaseScores.size())
|
||||
phaseScores[phaseScoreCount++]={candidate,candidateDirection,
|
||||
routeLength,clearanceScore,tangentAlignment};
|
||||
}
|
||||
}
|
||||
if(phaseScoreCount>0)
|
||||
{
|
||||
double bestClearance=-std::numeric_limits<double>::infinity();
|
||||
for(std::size_t index=0;index<phaseScoreCount;++index)
|
||||
bestClearance=std::max(bestClearance,phaseScores[index].clearance);
|
||||
constexpr double ClearanceToleranceMetres=0.10;
|
||||
const FPhaseScore* selected=nullptr;
|
||||
for(std::size_t index=0;index<phaseScoreCount;++index)
|
||||
{
|
||||
const auto& candidate=phaseScores[index];
|
||||
if(candidate.clearance+ClearanceToleranceMetres<bestClearance)continue;
|
||||
const bool shorter=!selected||candidate.routeLength<selected->routeLength;
|
||||
const bool stableLengthTie=selected
|
||||
&&candidate.routeLength==selected->routeLength
|
||||
&&(candidate.clearance>selected->clearance+1.0e-6
|
||||
||(std::abs(candidate.clearance-selected->clearance)<=1.0e-6
|
||||
&&candidate.tangent>selected->tangent));
|
||||
if(shorter||stableLengthTie)selected=&candidate;
|
||||
}
|
||||
if(selected)
|
||||
{
|
||||
phase=selected->phase;
|
||||
direction=selected->direction;
|
||||
clockwise=direction>0;
|
||||
}
|
||||
}
|
||||
}
|
||||
direction=clockwise?1:-1;
|
||||
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);};
|
||||
@@ -2731,6 +3004,12 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
}
|
||||
++loops;
|
||||
}
|
||||
const std::int64_t mouthPlanSeparation=distanceQ(
|
||||
lowerMouth[0]-upperMouth[0],lowerMouth[1]-upperMouth[1]);
|
||||
const bool shortGeneratedClosure=!hasLowerTangent&&!hasAnchorHelix
|
||||
&&pathStyle==1u&&loops<=1
|
||||
&&mouthPlanSeparation<=2*mouthRadius
|
||||
&&verticalDistance<=2*mouthRadius+qPerMetre/2;
|
||||
if(hasLowerTangent)
|
||||
{
|
||||
std::uint32_t lowerPhase=static_cast<std::uint32_t>(PassageSeed(seed,
|
||||
@@ -2789,6 +3068,48 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
}
|
||||
pushXY(ringPoint(lowerPhase));
|
||||
}
|
||||
else if(shortGeneratedClosure)
|
||||
{
|
||||
// A one-turn generated route whose mouths share a plan location
|
||||
// must leave and rejoin from different ring tangents. Returning on
|
||||
// the entry chord creates a hairpin that overlaps its own tread.
|
||||
double bestExitScore=-std::numeric_limits<double>::infinity();
|
||||
std::uint32_t lowerPhase=phase;
|
||||
const auto firstRing=ringPoint(phase,loops);
|
||||
for(std::uint32_t candidate=0;candidate<16;++candidate)
|
||||
{
|
||||
if(candidate==phase)continue;
|
||||
const std::uint32_t previous=static_cast<std::uint32_t>(
|
||||
(static_cast<int>(candidate)-direction+16)%16);
|
||||
const auto terminal=ringPoint(candidate,loops);
|
||||
const auto beforeTerminal=ringPoint(previous,loops);
|
||||
const double incomingX=static_cast<double>(terminal[0]-beforeTerminal[0]);
|
||||
const double incomingY=static_cast<double>(terminal[1]-beforeTerminal[1]);
|
||||
const double outgoingX=static_cast<double>(lowerMouth[0]-terminal[0]);
|
||||
const double outgoingY=static_cast<double>(lowerMouth[1]-terminal[1]);
|
||||
const double incomingLength=std::hypot(incomingX,incomingY);
|
||||
const double outgoingLength=std::hypot(outgoingX,outgoingY);
|
||||
if(incomingLength<=1.0e-6||outgoingLength<=1.0e-6)continue;
|
||||
const double tangentAlignment=(incomingX*outgoingX+incomingY*outgoingY)
|
||||
/(incomingLength*outgoingLength);
|
||||
const double separation=std::hypot(
|
||||
static_cast<double>(terminal[0]-firstRing[0]),
|
||||
static_cast<double>(terminal[1]-firstRing[1]));
|
||||
const double score=tangentAlignment
|
||||
+0.20*std::clamp(separation/(2.0*qPerMetre),0.0,2.0);
|
||||
if(score>bestExitScore)
|
||||
{bestExitScore=score;lowerPhase=candidate;}
|
||||
}
|
||||
for(int step=1;step<=16;++step)
|
||||
{
|
||||
int directionIndex=static_cast<int>(phase)+direction*step;
|
||||
directionIndex=(directionIndex%16+16)%16;
|
||||
pushXY(ringPoint(static_cast<std::uint32_t>(directionIndex),loops));
|
||||
if(static_cast<std::uint32_t>(directionIndex)==lowerPhase)break;
|
||||
}
|
||||
std::printf("[ResolvedPassageGeneratedExitApproach] id=%s turns=%u phase=%u exit_phase=%u tangent_score=%.3f status=PASS\n",
|
||||
passage.stableId.c_str(),loops,phase,lowerPhase,bestExitScore);
|
||||
}
|
||||
pushXY({lowerMouth[0],lowerMouth[1]});totalHorizontal=measurePath();
|
||||
std::size_t lowerMouthIndex=xy.size()-1;
|
||||
std::vector<std::array<std::int64_t,3>> lowerLandingPoints;
|
||||
|
||||
Reference in new issue
Block a user