wip(assembler): round L35 -- smooth passage walls without regular ribs (evaluator v21)
The shared passage carve took the minimum of piecewise-linear capsules (scallops at joins) and added wall relief on regular 8 m / 4 m octaves, which read as evenly spaced ribs. Interior sweep joins are now Catmull-Rom smoothed and the relief uses irregular rotated 43/29/19 m scales; the natural wobble remains. New axial ripple audit (0.125 m radius samples, 0.5-8 m periods): beaded control and L34 geometry fail, all 24 passage rows pass. FI mouth tread apron tapered. 30/30 determinism, 16/16 invariants, ring audits LOD0-8 pass; cost within noise. 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
49e29fcd36
commit
ba017077d1
28 files changed
+1727
-17
No files matched your search
@@ -911,7 +911,34 @@ public:
|
||||
DeterministicSqrt(playerDx*playerDx+playerDy*playerDy
|
||||
+playerVertical*playerVertical)-0.34*qPerMetre);
|
||||
}
|
||||
const double cx=ax+t*dx,cy=ay+t*dy,cz=az+t*dz;
|
||||
double cx=ax+t*dx,cy=ay+t*dy,cz=az+t*dz;
|
||||
const bool smoothCarveJoin=index>2&&index+12<lastIndex
|
||||
&&DeterministicSqrt(lengthSq)<=8.0*qPerMetre
|
||||
&&!IsPassageShortSpanSegment(passageIndex,segmentIndex-1)
|
||||
&&!IsPassageShortSpanSegment(passageIndex,segmentIndex)
|
||||
&&!IsPassageShortSpanSegment(passageIndex,segmentIndex+1);
|
||||
if(smoothCarveJoin)
|
||||
{
|
||||
const auto& p0=passage.controlPoints[static_cast<std::size_t>(index-1)];
|
||||
const auto& p3=passage.controlPoints[static_cast<std::size_t>(index+2)];
|
||||
constexpr double SmoothJoinWeight=1.0;
|
||||
const double t2=t*t,t3=t2*t;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
const double v0=static_cast<double>(p0.positionSubvoxels[axis]);
|
||||
const double v1=static_cast<double>(a.positionSubvoxels[axis]);
|
||||
const double v2=static_cast<double>(b.positionSubvoxels[axis]);
|
||||
const double v3=static_cast<double>(p3.positionSubvoxels[axis]);
|
||||
const double curve=0.5*((2.0*v1)+(-v0+v2)*t
|
||||
+(2.0*v0-5.0*v1+4.0*v2-v3)*t2
|
||||
+(-v0+3.0*v1-3.0*v2+v3)*t3);
|
||||
const double line=axis==0?cx:axis==1?cy:cz;
|
||||
const double blended=line+(curve-line)*SmoothJoinWeight;
|
||||
if(axis==0)cx=blended;
|
||||
else if(axis==1)cy=blended;
|
||||
else cz=blended;
|
||||
}
|
||||
}
|
||||
const double ex=qx-cx,ey=qy-cy,ez=qz-cz;
|
||||
const double radius=static_cast<double>(a.radiusSubvoxels)
|
||||
+t*(b.radiusSubvoxels-a.radiusSubvoxels);
|
||||
@@ -963,11 +990,11 @@ public:
|
||||
?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),
|
||||
const float relief=VF_FieldPassageWallRelief(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;
|
||||
+static_cast<double>(std::clamp(relief,-1.0f,1.0f))*0.21*qPerMetre*wallReliefWeight;
|
||||
if(IsPassageShortSpanSegment(passageIndex,static_cast<std::size_t>(bestIndex)))
|
||||
{
|
||||
const double bridgeDistance=PassageBridgeSignedDistance(passageIndex,
|
||||
@@ -986,6 +1013,7 @@ public:
|
||||
const double dy=static_cast<double>(segmentB.positionSubvoxels[1])-segmentA.positionSubvoxels[1];
|
||||
const double radiusA=segmentA.radiusSubvoxels;
|
||||
const double radiusB=segmentB.radiusSubvoxels;
|
||||
const bool entryMouthShoulder=index<=2;
|
||||
// Keep the tread within the probe band and below the capsule's
|
||||
// lowest sampled point.
|
||||
const double floorOffset=IsPassageShortSpanSegment(passageIndex,
|
||||
@@ -1000,10 +1028,33 @@ public:
|
||||
:bestT;
|
||||
const double centerX=segmentA.positionSubvoxels[0]+supportT*dx;
|
||||
const double centerY=segmentA.positionSubvoxels[1]+supportT*dy;
|
||||
const double centerZ=segmentA.positionSubvoxels[2]+supportT*(
|
||||
double centerZ=segmentA.positionSubvoxels[2]+supportT*(
|
||||
static_cast<double>(segmentB.positionSubvoxels[2])-segmentA.positionSubvoxels[2]);
|
||||
const double radius=segmentA.radiusSubvoxels+supportT*(
|
||||
double radius=segmentA.radiusSubvoxels+supportT*(
|
||||
static_cast<double>(segmentB.radiusSubvoxels)-segmentA.radiusSubvoxels);
|
||||
const bool smoothSupportJoin=index>0&&index+12<lastIndex
|
||||
&&!IsPassageShortSpanSegment(passageIndex,static_cast<std::size_t>(index-1))
|
||||
&&!IsPassageShortSpanSegment(passageIndex,static_cast<std::size_t>(index))
|
||||
&&!IsPassageShortSpanSegment(passageIndex,static_cast<std::size_t>(index+1));
|
||||
if(smoothSupportJoin)
|
||||
{
|
||||
const auto& p0=passage.controlPoints[static_cast<std::size_t>(index-1)];
|
||||
const auto& p3=passage.controlPoints[static_cast<std::size_t>(index+2)];
|
||||
const double t2=supportT*supportT,t3=t2*supportT;
|
||||
const auto interpolate=[&](double v0,double v1,double v2,double v3)
|
||||
{
|
||||
return 0.5*((2.0*v1)+(-v0+v2)*supportT
|
||||
+(2.0*v0-5.0*v1+4.0*v2-v3)*t2
|
||||
+(-v0+3.0*v1-3.0*v2+v3)*t3);
|
||||
};
|
||||
constexpr double SmoothSupportHeightWeight=0.25;
|
||||
centerZ+=(interpolate(p0.positionSubvoxels[2],segmentA.positionSubvoxels[2],
|
||||
segmentB.positionSubvoxels[2],p3.positionSubvoxels[2])-centerZ)
|
||||
*SmoothSupportHeightWeight;
|
||||
radius+=(interpolate(p0.radiusSubvoxels,segmentA.radiusSubvoxels,
|
||||
segmentB.radiusSubvoxels,p3.radiusSubvoxels)-radius)
|
||||
*SmoothSupportHeightWeight;
|
||||
}
|
||||
const double horizontalX=qx-centerX,horizontalY=qy-centerY;
|
||||
const double horizontalDistance=DeterministicSqrt(horizontalX*horizontalX
|
||||
+horizontalY*horizontalY);
|
||||
@@ -1016,16 +1067,42 @@ public:
|
||||
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 supportEdgeNoise=entryMouthShoulder?std::clamp(
|
||||
static_cast<double>(VF_FieldPassageGroundGrain(static_cast<float>(X),
|
||||
static_cast<float>(Y),
|
||||
static_cast<std::uint32_t>(Owner?Owner->Seed:0))),-1.0,1.0):0.0;
|
||||
const double supportHalfWidthMetres=lowerMouthBodyOverlap?0.90
|
||||
:entryMouthShoulder?1.20+0.20*supportEdgeNoise:1.25;
|
||||
const double supportHalfWidth=std::min(radius,
|
||||
(lowerMouthBodyOverlap?0.90:1.25)*qPerMetre);
|
||||
if(Z<floorVoxels-supportThicknessVoxels||Z>floorVoxels+1.0/Q)return;
|
||||
supportHalfWidthMetres*qPerMetre);
|
||||
// Keep the walk core within a 0.50 m span with shallow relief, then
|
||||
// taper the outer shoulder; capsule and probe widths remain supported.
|
||||
const double supportEdgeCoreMetres=entryMouthShoulder?0.60:0.90;
|
||||
const double supportEdgeBlendMetres=std::min(
|
||||
entryMouthShoulder?0.85:0.35,
|
||||
std::max(0.0,supportHalfWidth/qPerMetre-supportEdgeCoreMetres));
|
||||
const double supportEdgeT=supportEdgeBlendMetres>1.0e-6
|
||||
?std::clamp((horizontalDistance
|
||||
-(supportHalfWidth-supportEdgeBlendMetres*qPerMetre))
|
||||
/(supportEdgeBlendMetres*qPerMetre),0.0,1.0):0.0;
|
||||
const double supportEdgeSmooth=supportEdgeT*supportEdgeT
|
||||
*(3.0-2.0*supportEdgeT);
|
||||
const double localSupportThicknessVoxels=entryMouthShoulder
|
||||
?supportThicknessVoxels*(1.0-0.72*supportEdgeSmooth)
|
||||
:supportThicknessVoxels;
|
||||
const double supportEdgeDepth=entryMouthShoulder
|
||||
?0.35+0.15*supportEdgeNoise:0.35;
|
||||
const double floorQ=centerZ-radius+floorOffset*qPerMetre
|
||||
+supportEdgeNoise*(horizontalDistance>0.60*qPerMetre
|
||||
?0.35:0.14)*qPerMetre
|
||||
-supportEdgeSmooth*supportEdgeDepth*qPerMetre;
|
||||
const double floorVoxels=floorQ/Q;
|
||||
if(Z<floorVoxels-localSupportThicknessVoxels||Z>floorVoxels+1.0/Q)return;
|
||||
if(horizontalDistance>supportHalfWidth)return;
|
||||
const double supportDistanceSq=horizontalDistance*horizontalDistance
|
||||
+(Z-floorVoxels)*(Z-floorVoxels);
|
||||
@@ -1034,7 +1111,7 @@ public:
|
||||
hasSupportFloor=true;
|
||||
bestSupportSegmentIndex=index;
|
||||
bestSupportDensity=std::clamp(Z-floorVoxels,
|
||||
-supportThicknessVoxels,0.0);
|
||||
-localSupportThicknessVoxels,0.0);
|
||||
};
|
||||
std::ptrdiff_t supportCenterIndex=bestIndex;
|
||||
if(bestIndex<lowerMouthIndex)
|
||||
@@ -4541,9 +4618,10 @@ private:
|
||||
const float StrataRelief=1.35f*(Relief+0.5f*OwnerRelief);
|
||||
const float CaveRelief=1.00f*Relief;
|
||||
const float FloorRelief=0.55f*Relief;
|
||||
// Passage walls keep the broad coherent relief bands but omit the shortest
|
||||
// octave, which makes repeated rings on the authored shafts.
|
||||
const float PassageRelief=0.75f*PassageBandRelief;
|
||||
// The shared passage sweep supplies its own irregular relief. Keep only a
|
||||
// small trace of the terrain value-noise bands so their cell edges cannot
|
||||
// dominate the passage wall and read as repeated rings.
|
||||
const float PassageRelief=0.10f*PassageBandRelief;
|
||||
return StrataWeight*StrataRelief+CaveWeight*CaveRelief
|
||||
+FloorWeight*FloorRelief+Passage*PassageRelief;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user