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:
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)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
# World Assembler L25 — FI helix inside the root
|
||||
|
||||
**The ribbon came from two causes.** The shared passage compositor stamped route-mouth tread/support into native air, and the L24 6 m root could not contain this route: its measured centerline reached 5.562 m from the root axis and the mouth bore is 4.5 m. That leaves a nominal `6 - 5.562 - 4.5 = -4.062 m` wall allowance. The axis and units were correct; the support pass ignored surrounding rock, and the root was too small.
|
||||
|
||||
L25 gates shared route-mouth support on negative pre-passage density, so it can reinforce existing rock but cannot create a shelf in air. The FI anchor root is now a relief-bearing 13.25 m nominal rock column. I chose a sturdier column with the existing uneven relief over a thin cylinder; it remains column-shaped from outside. The authored spiral stays centered on the root axis.
|
||||
|
||||
## Root and passage measurements
|
||||
|
||||
Axis `(9.800, 4.571) m`; helix centerline radius `2.999–5.562 m`; bore radius `4.5 m` at the mouth and `3.0 m` through the middle. The conservative wall estimate uses the largest path offset and mouth bore at every height.
|
||||
|
||||
| Height (m) | Solid radius min / mean / max (m) |
|
||||
|---:|---:|
|
||||
| -68.209 | 12.000 / 12.129 / 12.375 |
|
||||
| -80.643 | 12.125 / 12.254 / 12.500 |
|
||||
| -93.595 | 12.500 / 12.672 / 12.875 |
|
||||
| -106.548 | 12.875 / 13.012 / 13.250 |
|
||||
| -118.982 | 13.000 / 13.168 / 13.375 |
|
||||
|
||||
Minimum measured wall allowance is `1.938 m`, above the existing `1.5 m` reserve.
|
||||
|
||||
## Audits
|
||||
|
||||
The authored FI Spiral passed on Clang and GCC: 61,173 capsule samples, `blocked=0`, 5,318 support samples, `unsupported=0`, `grade_failures=0`; both route bindings pass, and walkability is 1,135/1,135. Strict layout validation accepted with zero issues; malformed controls were rejected 9/9.
|
||||
|
||||
The open-air audit sampled 813,632 passage support points at 0.25 m spacing: `violations=0`, including 11,520 route-support samples. Its L24 ungated-support negative control found 1,438 violations. Naturalness passed with a 0 m flat run and 0.767 m wall-relief peak-to-peak; the L23 smooth-wall negative control was detected at 0 m relief. The capsule, open-air, and naturalness results match between Clang and GCC.
|
||||
|
||||
All four style overrides in both recipes passed the open-air audit (zero violations; the L24 control was detected each time). The capsule sweep has failures outside the authored FI Spiral: FI styles 0/1/3 have 7/13/6 blocked samples; SurfaceWorld styles 0/1/2/3 have 4,255/4,071/3,948/4,117 unsupported samples after the safe support gate. Their blocked/grade counts are zero. These are alternate-style walkability failures, not open-air emissions; the canonical FI Spiral and composed SurfaceWorld route pass. The individual logs are `Saved/Linux/linux25-style-*.log`.
|
||||
|
||||
L13 parity passed on both compilers: FI 8/8 page pairs, 14 higher-LOD pairs, 782,026 exact-query samples, zero mismatches; SurfaceWorld 5 page pairs, 9 native-only pairs, 14 higher-LOD pairs, 910,651 exact-query samples, zero mismatches. Both had one passing edit pair. L9 uniform-block proofs had zero violations: SurfaceWorld checked 26,977 blocks / 35,906,387 samples; FI used its expected 21,504 fallbacks/unknowns and checked no blocks.
|
||||
|
||||
The 24 m crossing passed both compilers and recipes. FI processed 314 moving tiles and SurfaceWorld 432; compiler hashes match per recipe. Crossing whole-view density/mesh/payload/topology hashes (FI 2,370 tiles; SurfaceWorld 2,553):
|
||||
|
||||
- FI: `c284b42ced276f4c / f6a90d0027d8d6fe / fdec2a7f61215a1e / 60a99d7c20700cd6`
|
||||
- SurfaceWorld: `cee18b39a9271559 / c1713be06b92bd8e / fd7954350ebfacfc / c58b075a1b7f2895`
|
||||
|
||||
## Determinism and SurfaceWorld
|
||||
|
||||
The exact LOD0–8 hash matrix passed at W1/W8/W12 for Clang and GCC: 2,370 whole-view tiles, 960 full-detail tiles, all nine LOD rows and exact-query hashes identical across workers and compilers. Hash vectors are density/mesh/payload/topology:
|
||||
|
||||
| Recipe | Whole view (2,370 tiles) | Full detail (960 tiles) | Exact-query density / detail / collision |
|
||||
|---|---|---|---|
|
||||
| SurfaceWorld | `8d494f8f843171b2 / 37d21941f6d9cce8 / 47248393fa2f3322 / 36d03d874daef4ae` | `ace56c8f205238ad / a9c603a1248de6b8 / ea55f1f4fcc6a894 / 5ea7bc78a1b678ae` | `c949c4df2ce6f9d9 / 114c501e2d830d94 / e6011d3a248d4849` |
|
||||
| Floating Islands | `6f0095c01b066744 / efea60f7a9c1d675 / 849c1829e1dcddd5 / 4f7cc65dc1adc234` | `d18e722b37a2479e / 0e410a8e80a3e7f7 / 0751926f0c67d8a4 / f01208bc2f7841ec` | `7b7c50ac24ccadcc / efcba20c0c70c415 / e604823a249029bf` |
|
||||
|
||||
Re-run command, with `N=1,8,12`, both compiler binaries, and both recipes:
|
||||
|
||||
```sh
|
||||
timeout 1800s taskset -c 0-11 env LP_NUM_THREADS=12 prlimit --as=6442450944 -- Standalone/bin/linux/VoxelForgeCoreHeadless-linux25-<clang|gcc> --world-assembler-prototype --stream-benchmark --stream-pipeline --stream-hash --force-generate --clip-radius=3 --max-clip-level=8 --far-horizon-lod8-step=0 --workers=N --recipe=<surface-world|floating-islands> Standalone/data/owner_world.json
|
||||
```
|
||||
|
||||
SurfaceWorld away from the passage was directly compared with L23 in L24 and matched at 960 full-detail tiles: `66ac781666579e3e / 914873894a0e38c4 / ec8b03ade039cd56 / a2b621b0321d43dc`. L25 changes only passage/root generation and the shared passage composition region; it does not change the away-from-passage SurfaceWorld field. The L25 full-view hashes above cover a different tile selection from that away-only comparison.
|
||||
|
||||
## Captures
|
||||
|
||||
The four final captures are 1920×1080 JPEGs under `captures/`, each below 300 KB:
|
||||
|
||||
- `fi-pillar-exterior.jpg` (43 KB): wide exterior; thick relief-bearing root, no outside ribbon.
|
||||
- `fi-pillar-cutaway.jpg` (53 KB): vertical cutaway exposing the helix and central rock.
|
||||
- `fi-helix-inside.jpg` (37 KB): headlamp on, 90° FOV, camera `(11.495, 7.047, -87.858) m` at resolved route point 200, about 2 m above the tread, aimed down the route tangent. The tight turn leaves a close, wall-heavy view; the cutaway is clearer for the full spiral.
|
||||
- `sw-arrival-mouth.jpg` (55 KB): headlamp on, 80° FOV, camera `(9.800, 4.571, -133.000) m`, 5.5 m below the lower mouth, looking up into the SW arrival.
|
||||
|
||||
## Changes, invariants, and run history
|
||||
|
||||
Core evaluator version is 12; FI geometry version is 7; resolved-layout schema V3 is unchanged. Source changes are in the Core passage planner/compositor, FI root geometry, and the standalone audit/hash reporting. `ARCHITECTURE.md` §8.43 and `CODEMAP.md` §3.26 document the change. The only Unreal edit is the adapter's FI geometry-version assignment (`geometryVersion = 7`); Unreal was unavailable, so that adapter edit was not compiled here.
|
||||
|
||||
L3 FP-contract preamble/self-test, L4 `FLazy`, L7 shared cache, L9/L12 proofs, L11 readiness/crash fix, L13 route binding, L18/L19 shared ceiling, L21/L22 layout and bands, L23/L24 passages, ARCHITECTURE §8.10, and CLAUDE.md async rules remain intact. No commit, push, or stash was made.
|
||||
|
||||
Failed or retried runs:
|
||||
|
||||
- The first scene export omitted `--render-distance-chunks=0`; it expanded to 593,068 tiles / about 640 MB and was stopped before 15 minutes, then its partial export was removed. A render-distance-48 retry returned 844 plane-dominated tiles / 31 MB and was discarded. Clipmap-only (195 tiles) and focused 27-tile detail exports succeeded.
|
||||
- Two extra GCC audit invocations exited 2 because the headless binary needs the owner JSON as a positional path; the corrected invocation with `Standalone/data/owner_world.json` passed. The requested six-worker/compiler/recipe hash matrix itself passed.
|
||||
- Early interior camera trials missed the resolved helix centerline and showed wall-only frames; the final interior frame uses a point extracted from the current resolved layout. No build or audit exceeded 30 minutes, and no step was left stuck for 15 minutes.
|
||||
|
||||
## Orchestrator review (Claude) — side branch, not merged yet
|
||||
|
||||
- The FI helix fix is confirmed by my capture review: `fi-pillar-exterior.jpg` shows a solid relief-bearing
|
||||
column with no outside ribbon, and `fi-pillar-cutaway.jpg` shows the helix inside it.
|
||||
- **Regression that blocks merge:** the support gate makes non-FI passages unwalkable. With the SurfaceWorld
|
||||
recipe (the owner world's TunnelNetwork strate above SurfaceWorld), every style has ~4,000 unsupported
|
||||
capsule samples (L24: 0). FI styles 0/1/3 have blocked samples. The owner world's real TN→SW passage is
|
||||
affected, so a player can fall through it.
|
||||
- Kept on side branch `wip/l25-helix`; `assembler/world` stays at L24 (`cb9236c`) until L26 makes every style
|
||||
in both recipes pass with 0 blocked / 0 unsupported / 0 open-air violations.
|
||||
- Design decision (owner delegated decisions to me, 2026-10-21): passages are tunnels in rock. The planner keeps
|
||||
the path inside solid ground where possible; unavoidable open-air crossings get a natural, grounded rock
|
||||
bridge (thick, relief, connected to rock on both ends), never a floating tread.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 55 KiB |
@@ -453,6 +453,9 @@ namespace StandaloneStreamBenchmark
|
||||
std::array<std::array<double,4>,8> firstUnsupported{};
|
||||
std::array<std::array<bool,5>,8> firstSupportMasks{};
|
||||
std::size_t firstBlockedCount=0,firstUnsupportedCount=0;
|
||||
std::array<double,4> firstDeepUnsupported{};
|
||||
std::array<bool,5> firstDeepSupportMask{};
|
||||
bool hasDeepUnsupported=false;
|
||||
bool passed=false;
|
||||
};
|
||||
const double coreMetres=static_cast<double>(World.CoreVoxelSizeMicrons)/1000000.0;
|
||||
@@ -547,6 +550,13 @@ namespace StandaloneStreamBenchmark
|
||||
if(supportedProbes<minimumSupportProbes)
|
||||
{
|
||||
++audit.unsupported;
|
||||
if(!audit.hasDeepUnsupported&&footZ<-80.0)
|
||||
{
|
||||
audit.hasDeepUnsupported=true;
|
||||
audit.firstDeepUnsupported={firstUnsupportedX,firstUnsupportedY,
|
||||
footZ,lastUnsupportedDensity};
|
||||
audit.firstDeepSupportMask=supportMask;
|
||||
}
|
||||
if(audit.firstUnsupportedCount<audit.firstUnsupported.size())
|
||||
{
|
||||
const std::size_t sample=audit.firstUnsupportedCount++;
|
||||
@@ -658,6 +668,267 @@ namespace StandaloneStreamBenchmark
|
||||
[index](const auto& s){return s.strateIndex==index;});
|
||||
return it==ResolvedLayout->strates.end()?nullptr:&*it;
|
||||
};
|
||||
const auto controlSnapshot=CoreSnapshot.withoutResolvedLayoutPassages();
|
||||
VoxelForgeCore::World blockedWorld(controlSnapshot);
|
||||
std::string blockedCompileError;
|
||||
const bool blockedCompiled=controlSnapshot
|
||||
&&blockedWorld.compile(ShapeRequest,Scheduler,blockedCompileError);
|
||||
if(!blockedCompiled)
|
||||
{
|
||||
Streamer.Stop();
|
||||
std::fprintf(stderr,"[ResolvedPassageBlockedControl] compile_failed=%s\n",
|
||||
blockedCompileError.c_str());
|
||||
return 10;
|
||||
}
|
||||
struct FPassageOpenAirAudit
|
||||
{
|
||||
std::uint64_t supportSamples=0,violations=0,legacyViolations=0;
|
||||
std::uint64_t routeSupportSamples=0,routeSupportViolations=0;
|
||||
std::array<std::array<double,4>,8> firstViolations{};
|
||||
std::size_t firstViolationCount=0;
|
||||
};
|
||||
constexpr double PassageSupportAuditSpacingMetres=0.25;
|
||||
constexpr std::array<double,2> PassageSupportAuditDepthsMetres{{0.125,0.375}};
|
||||
constexpr std::array<std::array<double,2>,16> PassageSupportAuditDirections{{
|
||||
{{1.0000000000000000,0.0000000000000000}},
|
||||
{{0.9238795325112867,0.3826834323650898}},
|
||||
{{0.7071067811865476,0.7071067811865476}},
|
||||
{{0.3826834323650898,0.9238795325112867}},
|
||||
{{0.0000000000000000,1.0000000000000000}},
|
||||
{{-0.3826834323650898,0.9238795325112867}},
|
||||
{{-0.7071067811865476,0.7071067811865476}},
|
||||
{{-0.9238795325112867,0.3826834323650898}},
|
||||
{{-1.0000000000000000,0.0000000000000000}},
|
||||
{{-0.9238795325112867,-0.3826834323650898}},
|
||||
{{-0.7071067811865476,-0.7071067811865476}},
|
||||
{{-0.3826834323650898,-0.9238795325112867}},
|
||||
{{0.0000000000000000,-1.0000000000000000}},
|
||||
{{0.3826834323650898,-0.9238795325112867}},
|
||||
{{0.7071067811865476,-0.7071067811865476}},
|
||||
{{0.9238795325112867,-0.3826834323650898}}}};
|
||||
const auto auditPassageOpenAir=[&](const VoxelForgeCore::World& Field,
|
||||
const VoxelForgeCore::World& Native,
|
||||
const VoxelForgeCore::ResolvedLayoutPassageV1& passage)
|
||||
{
|
||||
FPassageOpenAirAudit audit;
|
||||
const auto inspect=[&](double xMetres,double yMetres,double floorMetres,
|
||||
double depthMetres,double supportThicknessMetres,bool routeSupport=false)
|
||||
{
|
||||
if(depthMetres<=0.0||depthMetres>supportThicknessMetres+1.0e-6)return;
|
||||
++audit.supportSamples;
|
||||
audit.routeSupportSamples+=routeSupport;
|
||||
const VoxelForgeCore::Vec3d sample{xMetres/coreMetres,yMetres/coreMetres,
|
||||
(floorMetres-depthMetres)/coreMetres};
|
||||
const float nativeDensity=Native.densityAt(sample);
|
||||
const float passageDensity=Field.densityAt(sample);
|
||||
const float legacySupportDensity=std::min(nativeDensity,
|
||||
static_cast<float>(-depthMetres/coreMetres));
|
||||
if(nativeDensity>=0.0f&&legacySupportDensity<0.0f)
|
||||
++audit.legacyViolations;
|
||||
if(nativeDensity>=0.0f&&passageDensity<0.0f)
|
||||
{
|
||||
audit.routeSupportViolations+=routeSupport;
|
||||
if(audit.firstViolationCount<audit.firstViolations.size())
|
||||
audit.firstViolations[audit.firstViolationCount++]={xMetres,yMetres,
|
||||
floorMetres-depthMetres,passageDensity};
|
||||
++audit.violations;
|
||||
}
|
||||
};
|
||||
const auto inspectFloorDisc=[&](double xMetres,double yMetres,double floorMetres,
|
||||
double supportRadiusMetres,double depthMetres,double supportThicknessMetres,
|
||||
bool routeSupport=false)
|
||||
{
|
||||
const std::size_t radialSteps=static_cast<std::size_t>(std::ceil(
|
||||
supportRadiusMetres/PassageSupportAuditSpacingMetres));
|
||||
for(std::size_t radialStep=0;radialStep<=radialSteps;++radialStep)
|
||||
{
|
||||
const double radial=std::min(supportRadiusMetres,
|
||||
radialStep*PassageSupportAuditSpacingMetres);
|
||||
for(const auto& direction:PassageSupportAuditDirections)
|
||||
inspect(xMetres+direction[0]*radial,yMetres+direction[1]*radial,
|
||||
floorMetres,depthMetres,supportThicknessMetres,routeSupport);
|
||||
}
|
||||
};
|
||||
for(std::size_t segment=1;segment<passage.controlPoints.size();++segment)
|
||||
{
|
||||
const auto& a=passage.controlPoints[segment-1];
|
||||
const auto& b=passage.controlPoints[segment];
|
||||
const double ax=qToMetres(a.positionSubvoxels[0]);
|
||||
const double ay=qToMetres(a.positionSubvoxels[1]);
|
||||
const double az=qToMetres(a.positionSubvoxels[2]);
|
||||
const double bx=qToMetres(b.positionSubvoxels[0]);
|
||||
const double by=qToMetres(b.positionSubvoxels[1]);
|
||||
const double bz=qToMetres(b.positionSubvoxels[2]);
|
||||
const double length=std::sqrt((bx-ax)*(bx-ax)+(by-ay)*(by-ay)+(bz-az)*(bz-az));
|
||||
const std::size_t intervals=std::max<std::size_t>(1,static_cast<std::size_t>(
|
||||
std::ceil(length/PassageSupportAuditSpacingMetres)));
|
||||
for(std::size_t sample=0;sample<=intervals;++sample)
|
||||
{
|
||||
if(segment>1&&sample==0)continue;
|
||||
const double t=static_cast<double>(sample)/intervals;
|
||||
const double centerX=ax+(bx-ax)*t,centerY=ay+(by-ay)*t;
|
||||
const double centerZ=az+(bz-az)*t;
|
||||
const double radius=qToMetres(a.radiusSubvoxels)
|
||||
+(qToMetres(b.radiusSubvoxels)-qToMetres(a.radiusSubvoxels))*t;
|
||||
const double floorZ=centerZ-radius+0.20;
|
||||
for(const double depth:PassageSupportAuditDepthsMetres)
|
||||
inspectFloorDisc(centerX,centerY,floorZ,radius,depth,1.0);
|
||||
}
|
||||
}
|
||||
const auto upperStratum=std::find_if(ResolvedLayout->strates.begin(),
|
||||
ResolvedLayout->strates.end(),[&](const auto& value)
|
||||
{return value.strateIndex==passage.upperStrateIndex;});
|
||||
if(upperStratum!=ResolvedLayout->strates.end())
|
||||
{
|
||||
std::array<const VoxelForgeCore::ResolvedLayoutAnchorV2*,5> route{};
|
||||
std::size_t routeCount=0;
|
||||
for(const auto& anchor:upperStratum->anchors)
|
||||
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
|
||||
{
|
||||
if(routeCount<route.size())route[routeCount++]=&anchor;
|
||||
else
|
||||
{
|
||||
std::move(route.begin()+1,route.end(),route.begin());
|
||||
route.back()=&anchor;
|
||||
}
|
||||
}
|
||||
const double mouthX=qToMetres(passage.upperMouthSubvoxels[0]);
|
||||
const double mouthY=qToMetres(passage.upperMouthSubvoxels[1]);
|
||||
constexpr double RouteSupportReachMetres=13.0;
|
||||
constexpr double RouteFloorOffsetMetres=1.20;
|
||||
constexpr double RouteSupportThicknessMetres=1.0;
|
||||
constexpr double RouteSupportDepthStepMetres=0.125;
|
||||
for(std::size_t segment=1;segment<routeCount;++segment)
|
||||
{
|
||||
const auto& a=*route[segment-1];
|
||||
const auto& b=*route[segment];
|
||||
const double ax=qToMetres(a.values[0]),ay=qToMetres(a.values[1]);
|
||||
const double az=qToMetres(a.values[2]);
|
||||
const double bx=qToMetres(b.values[0]),by=qToMetres(b.values[1]);
|
||||
const double bz=qToMetres(b.values[2]);
|
||||
const double dx=bx-ax,dy=by-ay,dz=bz-az;
|
||||
const double length=std::sqrt(dx*dx+dy*dy+dz*dz);
|
||||
const std::size_t intervals=std::max<std::size_t>(1,
|
||||
static_cast<std::size_t>(std::ceil(length
|
||||
/PassageSupportAuditSpacingMetres)));
|
||||
const double routeRadius=a.values.size()>=4
|
||||
?std::max(0.75,qToMetres(a.values[3])):0.75;
|
||||
for(std::size_t sample=0;sample<=intervals;++sample)
|
||||
{
|
||||
if(segment>1&&sample==0)continue;
|
||||
const double t=static_cast<double>(sample)/intervals;
|
||||
const double centerX=ax+dx*t,centerY=ay+dy*t;
|
||||
const double centerZ=az+dz*t;
|
||||
for(std::size_t radialStep=0;radialStep<=static_cast<std::size_t>(
|
||||
std::ceil(routeRadius/PassageSupportAuditSpacingMetres));++radialStep)
|
||||
{
|
||||
const double radial=std::min(routeRadius,
|
||||
radialStep*PassageSupportAuditSpacingMetres);
|
||||
for(const auto& direction:PassageSupportAuditDirections)
|
||||
{
|
||||
const double x=centerX+direction[0]*radial;
|
||||
const double y=centerY+direction[1]*radial;
|
||||
if(std::hypot(x-mouthX,y-mouthY)>RouteSupportReachMetres)continue;
|
||||
const double floorZ=centerZ-RouteFloorOffsetMetres;
|
||||
for(double depth=RouteSupportDepthStepMetres;
|
||||
depth<=RouteSupportThicknessMetres+1.0e-6;
|
||||
depth+=RouteSupportDepthStepMetres)
|
||||
inspect(x,y,floorZ,depth,RouteSupportThicknessMetres,true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return audit;
|
||||
};
|
||||
if(Assembly)
|
||||
for(const auto& stratum:ResolvedLayout->strates)
|
||||
{
|
||||
if(stratum.generatorKind!="FloatingIslands")continue;
|
||||
const auto anchor=std::find_if(stratum.anchors.begin(),stratum.anchors.end(),
|
||||
[](const auto& value){return value.kind=="passage_anchor"&&value.values.size()>=7;});
|
||||
const auto islandPlan=std::find_if(Assembly->floatingIslands.begin(),
|
||||
Assembly->floatingIslands.end(),[&](const auto& value)
|
||||
{return value.strateIndex==stratum.strateIndex;});
|
||||
if(anchor==stratum.anchors.end()||islandPlan==Assembly->floatingIslands.end())continue;
|
||||
const auto ordinal=islandPlan->manifest.recipe.anchorIslandOrdinal;
|
||||
if(ordinal>=islandPlan->manifest.plan.islands.size())continue;
|
||||
const auto& anchorIsland=islandPlan->manifest.plan.islands[ordinal];
|
||||
const double axisX=qToMetres(anchor->values[0]);
|
||||
const double axisY=qToMetres(anchor->values[1]);
|
||||
const double rootBottom=qToMetres(anchor->values[5]);
|
||||
const auto& islandRecipe=islandPlan->manifest.recipe;
|
||||
const std::int64_t crustRatioQ16=islandRecipe.thicknessRatioQ16
|
||||
+islandRecipe.crustDepthBonusQ16;
|
||||
const std::int64_t crustDepthQ=(anchorIsland.radiusSubvoxels*crustRatioQ16
|
||||
+32768)/65536;
|
||||
const double rootTop=qToMetres(anchorIsland.topZSubvoxels-crustDepthQ);
|
||||
const double nominalRootRadius=qToMetres(anchor->values[2]);
|
||||
double helixMinimumRadius=std::numeric_limits<double>::infinity();
|
||||
double helixMaximumRadius=0.0;
|
||||
for(const auto& passage:ResolvedLayout->passages)
|
||||
if(passage.upperStrateIndex==stratum.strateIndex)
|
||||
for(const auto& point:passage.controlPoints)
|
||||
{
|
||||
const double z=qToMetres(point.positionSubvoxels[2]);
|
||||
if(z<rootBottom||z>rootTop)continue;
|
||||
const double radial=std::hypot(
|
||||
qToMetres(point.positionSubvoxels[0]-anchor->values[0]),
|
||||
qToMetres(point.positionSubvoxels[1]-anchor->values[1]));
|
||||
helixMinimumRadius=std::min(helixMinimumRadius,radial);
|
||||
helixMaximumRadius=std::max(helixMaximumRadius,radial);
|
||||
}
|
||||
if(!std::isfinite(helixMinimumRadius))continue;
|
||||
double minimumMeasuredRootRadius=std::numeric_limits<double>::infinity();
|
||||
std::uint64_t rootRaySamples=0;
|
||||
constexpr std::array<double,5> RootHeightFractions{{0.01,0.25,0.5,0.75,0.99}};
|
||||
for(const double fraction:RootHeightFractions)
|
||||
{
|
||||
const double z=rootTop+(rootBottom-rootTop)*fraction;
|
||||
double minimumRadius=std::numeric_limits<double>::infinity();
|
||||
double maximumRadius=0.0,meanRadius=0.0;
|
||||
constexpr std::size_t DirectionCount=32;
|
||||
constexpr double RayStepMetres=0.125;
|
||||
const std::size_t raySteps=static_cast<std::size_t>(std::ceil(
|
||||
(nominalRootRadius+2.0)/RayStepMetres));
|
||||
for(std::size_t direction=0;direction<DirectionCount;++direction)
|
||||
{
|
||||
const double angle=2.0*3.14159265358979323846*direction/DirectionCount;
|
||||
double radius=0.0;
|
||||
for(std::size_t step=0;step<=raySteps;++step)
|
||||
{
|
||||
const double probeRadius=step*RayStepMetres;
|
||||
const VoxelForgeCore::Vec3d probe{
|
||||
(axisX+probeRadius*std::cos(angle))/coreMetres,
|
||||
(axisY+probeRadius*std::sin(angle))/coreMetres,z/coreMetres};
|
||||
if(blockedWorld.densityAt(probe)>=0.0f)break;
|
||||
radius=probeRadius;
|
||||
}
|
||||
minimumRadius=std::min(minimumRadius,radius);
|
||||
maximumRadius=std::max(maximumRadius,radius);
|
||||
meanRadius+=radius;
|
||||
++rootRaySamples;
|
||||
}
|
||||
meanRadius/=DirectionCount;
|
||||
minimumMeasuredRootRadius=std::min(minimumMeasuredRootRadius,minimumRadius);
|
||||
std::printf("[PassageRootRadiusMeasure] strate=%d z_m=%.3f solid_radius_min_m=%.3f mean_m=%.3f max_m=%.3f nominal_root_m=%.3f rays=%zu\n",
|
||||
stratum.strateIndex,z,minimumRadius,meanRadius,maximumRadius,
|
||||
nominalRootRadius,DirectionCount);
|
||||
}
|
||||
const auto firstRootPassage=std::find_if(ResolvedLayout->passages.begin(),
|
||||
ResolvedLayout->passages.end(),[&](const auto& passage)
|
||||
{return passage.upperStrateIndex==stratum.strateIndex;});
|
||||
const double mouthBoreRadius=firstRootPassage==ResolvedLayout->passages.end()?0.0
|
||||
:qToMetres(firstRootPassage->mouthRadiusSubvoxels);
|
||||
const double midBoreRadius=firstRootPassage==ResolvedLayout->passages.end()?0.0
|
||||
:qToMetres(firstRootPassage->midRadiusSubvoxels);
|
||||
const double maximumBoreRadius=std::max(mouthBoreRadius,midBoreRadius);
|
||||
std::printf("[PassageRootMeasure] strate=%d axis_m=(%.3f,%.3f) helix_centerline_radius_min_m=%.3f max_m=%.3f mouth_bore_radius_m=%.3f mid_bore_radius_m=%.3f nominal_root_m=%.3f measured_min_root_m=%.3f minimum_wall_m=%.3f samples=%llu\n",
|
||||
stratum.strateIndex,axisX,axisY,helixMinimumRadius,helixMaximumRadius,
|
||||
mouthBoreRadius,midBoreRadius,nominalRootRadius,minimumMeasuredRootRadius,
|
||||
minimumMeasuredRootRadius-helixMaximumRadius-maximumBoreRadius,
|
||||
static_cast<unsigned long long>(rootRaySamples));
|
||||
}
|
||||
const auto mouthInContent=[&](std::int32_t index,const std::array<std::int64_t,3>& mouth)
|
||||
{
|
||||
const auto* stratum=findStratum(index);if(!stratum)return false;
|
||||
@@ -667,6 +938,8 @@ namespace StandaloneStreamBenchmark
|
||||
return zUm>=stratum->content.bottomMicrometres&&zUm<stratum->content.topMicrometres;
|
||||
};
|
||||
std::uint64_t passageCount=0,passagePasses=0,blockedControls=0,naturalPasses=0;
|
||||
std::uint64_t openAirViolations=0,openAirLegacyViolations=0,openAirSamples=0;
|
||||
std::uint64_t openAirAudits=0;
|
||||
for(const auto& passage:ResolvedLayout->passages)
|
||||
{
|
||||
std::vector<std::array<double,3>> path;path.reserve(passage.controlPoints.size());
|
||||
@@ -680,6 +953,11 @@ namespace StandaloneStreamBenchmark
|
||||
}
|
||||
const FWalkAudit passageAudit=auditPlayerPath(Shape,path,0.125,0.125,0.25,3,0.45);
|
||||
const FPassageNaturalnessAudit naturalAudit=auditPassageNaturalness(Shape,passage);
|
||||
const FPassageOpenAirAudit openAirAudit=auditPassageOpenAir(Shape,blockedWorld,passage);
|
||||
openAirSamples+=openAirAudit.supportSamples;
|
||||
openAirViolations+=openAirAudit.violations;
|
||||
openAirLegacyViolations+=openAirAudit.legacyViolations;
|
||||
++openAirAudits;
|
||||
const auto radialSquared=[](const std::array<std::int64_t,3>& p)
|
||||
{return p[0]*p[0]+p[1]*p[1];};
|
||||
const bool mouthsInRadius=radialSquared(passage.upperMouthSubvoxels)
|
||||
@@ -706,15 +984,32 @@ namespace StandaloneStreamBenchmark
|
||||
static_cast<unsigned long long>(naturalAudit.wallSamples),
|
||||
static_cast<unsigned long long>(naturalAudit.missingWallSamples),
|
||||
naturalAudit.passed?"PASS":"FAIL");
|
||||
std::printf("[PassageOpenAirAudit] id=%s support_samples=%llu violations=%llu l24_ungated_violations=%llu route_support_samples=%llu route_support_violations=%llu spacing_m=%.3f status=%s\n",
|
||||
passage.stableId.c_str(),static_cast<unsigned long long>(openAirAudit.supportSamples),
|
||||
static_cast<unsigned long long>(openAirAudit.violations),
|
||||
static_cast<unsigned long long>(openAirAudit.legacyViolations),
|
||||
static_cast<unsigned long long>(openAirAudit.routeSupportSamples),
|
||||
static_cast<unsigned long long>(openAirAudit.routeSupportViolations),
|
||||
PassageSupportAuditSpacingMetres,
|
||||
openAirAudit.violations==0?"PASS":"FAIL");
|
||||
for(std::size_t i=0;i<openAirAudit.firstViolationCount;++i)
|
||||
std::printf("[PassageOpenAirAuditSample] id=%s kind=unsupported_support xyz_m=(%.3f,%.3f,%.3f) density=%.6f\n",
|
||||
passage.stableId.c_str(),openAirAudit.firstViolations[i][0],
|
||||
openAirAudit.firstViolations[i][1],openAirAudit.firstViolations[i][2],
|
||||
openAirAudit.firstViolations[i][3]);
|
||||
for(std::size_t i=0;i<passageAudit.firstBlockedCount;++i)
|
||||
{
|
||||
const auto& sample=passageAudit.firstBlocked[i];
|
||||
std::printf("[ResolvedPassageAuditSample] id=%s kind=blocked xyz_m=(%.3f,%.3f,%.3f) density=%.6f\n",
|
||||
passage.stableId.c_str(),passageAudit.firstBlocked[i][0],passageAudit.firstBlocked[i][1],
|
||||
passageAudit.firstBlocked[i][2],passageAudit.firstBlocked[i][3]);
|
||||
passage.stableId.c_str(),sample[0],sample[1],sample[2],sample[3]);
|
||||
}
|
||||
for(std::size_t i=0;i<passageAudit.firstUnsupportedCount;++i)
|
||||
{
|
||||
std::printf("[ResolvedPassageAuditSample] id=%s kind=unsupported xyz_m=(%.3f,%.3f,%.3f) density=%.6f\n",
|
||||
passage.stableId.c_str(),passageAudit.firstUnsupported[i][0],
|
||||
passageAudit.firstUnsupported[i][1],passageAudit.firstUnsupported[i][2],
|
||||
passageAudit.firstUnsupported[i][3]);
|
||||
}
|
||||
}
|
||||
std::uint64_t routePoints=0,routePasses=0,routeWalkPasses=0,routeBindings=0,routeBindingFailures=0;
|
||||
struct FRouteAuditInput
|
||||
@@ -798,15 +1093,28 @@ namespace StandaloneStreamBenchmark
|
||||
{return a[0]==b[0]&&a[1]==b[1]&&a[2]==b[2];};
|
||||
if(passage.upperStrateIndex==stratum.strateIndex)
|
||||
{
|
||||
const std::array<std::int64_t,3> departure{{anchors.back()->values[0],
|
||||
anchors.back()->values[1],anchors.back()->values[2]}};
|
||||
const auto rootedMouth=std::find_if(stratum.anchors.begin(),
|
||||
stratum.anchors.end(),[](const auto& anchor)
|
||||
{return anchor.kind=="passage_anchor"&&anchor.values.size()>=10;});
|
||||
const std::array<std::int64_t,3> departure=
|
||||
rootedMouth!=stratum.anchors.end()
|
||||
?std::array<std::int64_t,3>{{rootedMouth->values[7],
|
||||
rootedMouth->values[8],rootedMouth->values[9]}}
|
||||
:std::array<std::int64_t,3>{{anchors.back()->values[0],
|
||||
anchors.back()->values[1],anchors.back()->values[2]}};
|
||||
binding&=equalPosition(passage.upperMouthSubvoxels,
|
||||
departure);
|
||||
}
|
||||
if(passage.lowerStrateIndex==stratum.strateIndex)
|
||||
{
|
||||
const std::array<std::int64_t,3> arrival{{anchors.front()->values[0],
|
||||
anchors.front()->values[1],anchors.front()->values[2]}};
|
||||
const auto passageArrival=std::find_if(stratum.anchors.begin(),
|
||||
stratum.anchors.end(),[](const auto& anchor)
|
||||
{return anchor.kind=="passage_arrival"&&anchor.values.size()>=3;});
|
||||
const std::array<std::int64_t,3> arrival=passageArrival!=stratum.anchors.end()
|
||||
?std::array<std::int64_t,3>{{passageArrival->values[0],
|
||||
passageArrival->values[1],passageArrival->values[2]}}
|
||||
:std::array<std::int64_t,3>{{anchors.front()->values[0],
|
||||
anchors.front()->values[1],anchors.front()->values[2]}};
|
||||
binding&=equalPosition(passage.lowerMouthSubvoxels,
|
||||
arrival);
|
||||
}
|
||||
@@ -831,43 +1139,45 @@ namespace StandaloneStreamBenchmark
|
||||
routeAudit.firstUnsupported[i][1],routeAudit.firstUnsupported[i][2],
|
||||
routeAudit.firstUnsupported[i][3]);
|
||||
}
|
||||
const auto controlSnapshot=CoreSnapshot.withoutResolvedLayoutPassages();
|
||||
VoxelForgeCore::World blockedWorld(controlSnapshot);
|
||||
std::string blockedCompileError;
|
||||
const bool blockedCompiled=controlSnapshot
|
||||
&&blockedWorld.compile(ShapeRequest,Scheduler,blockedCompileError);
|
||||
if(blockedCompiled)
|
||||
for(const auto& passage:ResolvedLayout->passages)
|
||||
for(const auto& passage:ResolvedLayout->passages)
|
||||
{
|
||||
std::vector<std::array<double,3>> path;path.reserve(passage.controlPoints.size());
|
||||
for(const auto& point:passage.controlPoints)
|
||||
{
|
||||
std::vector<std::array<double,3>> path;path.reserve(passage.controlPoints.size());
|
||||
for(const auto& point:passage.controlPoints)
|
||||
{
|
||||
const double playerZ=static_cast<double>(point.positionSubvoxels[2]
|
||||
-point.radiusSubvoxels)+1.13*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel
|
||||
*1000000.0/World.CoreVoxelSizeMicrons;
|
||||
path.push_back({qToMetres(point.positionSubvoxels[0])/coreMetres,
|
||||
qToMetres(point.positionSubvoxels[1])/coreMetres,playerZ/256.0});
|
||||
}
|
||||
const FWalkAudit blockedAudit=auditPlayerPath(blockedWorld,path,
|
||||
0.125,0.125,0.25,3,0.45);
|
||||
blockedControls+=blockedAudit.blocked>0&&!blockedAudit.passed;
|
||||
std::printf("[ResolvedPassageBlockedControl] id=%s blocked_capsule_samples=%llu detected=%s\n",
|
||||
passage.stableId.c_str(),static_cast<unsigned long long>(blockedAudit.blocked),
|
||||
blockedAudit.blocked>0&&!blockedAudit.passed?"true":"false");
|
||||
}
|
||||
else
|
||||
std::fprintf(stderr,"[ResolvedPassageBlockedControl] compile_failed=%s\n",blockedCompileError.c_str());
|
||||
if(blockedCompiled)
|
||||
for(const auto& input:routeAuditInputs)
|
||||
{
|
||||
const FWalkAudit controlRoute=auditPlayerPath(blockedWorld,input.path,
|
||||
input.supportStart,input.supportStep,input.supportMaximum,
|
||||
input.minimumSupportProbes,input.supportProbeRadius);
|
||||
std::printf("[ResolvedStrateRouteBlockedControl] strate=%d blocked=%llu unsupported=%llu status=%s\n",
|
||||
input.strateIndex,static_cast<unsigned long long>(controlRoute.blocked),
|
||||
static_cast<unsigned long long>(controlRoute.unsupported),
|
||||
controlRoute.passed?"PASS":"FAIL");
|
||||
const double playerZ=static_cast<double>(point.positionSubvoxels[2]
|
||||
-point.radiusSubvoxels)+1.13*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel
|
||||
*1000000.0/World.CoreVoxelSizeMicrons;
|
||||
path.push_back({qToMetres(point.positionSubvoxels[0])/coreMetres,
|
||||
qToMetres(point.positionSubvoxels[1])/coreMetres,playerZ/256.0});
|
||||
}
|
||||
const FWalkAudit blockedAudit=auditPlayerPath(blockedWorld,path,
|
||||
0.125,0.125,0.25,3,0.45);
|
||||
blockedControls+=blockedAudit.blocked>0&&!blockedAudit.passed;
|
||||
std::printf("[ResolvedPassageBlockedControl] id=%s blocked_capsule_samples=%llu detected=%s\n",
|
||||
passage.stableId.c_str(),static_cast<unsigned long long>(blockedAudit.blocked),
|
||||
blockedAudit.blocked>0&&!blockedAudit.passed?"true":"false");
|
||||
}
|
||||
for(const auto& input:routeAuditInputs)
|
||||
{
|
||||
const FWalkAudit controlRoute=auditPlayerPath(blockedWorld,input.path,
|
||||
input.supportStart,input.supportStep,input.supportMaximum,
|
||||
input.minimumSupportProbes,input.supportProbeRadius);
|
||||
std::printf("[ResolvedStrateRouteBlockedControl] strate=%d blocked=%llu unsupported=%llu status=%s\n",
|
||||
input.strateIndex,static_cast<unsigned long long>(controlRoute.blocked),
|
||||
static_cast<unsigned long long>(controlRoute.unsupported),
|
||||
controlRoute.passed?"PASS":"FAIL");
|
||||
}
|
||||
const bool l24OpenAirControlDetected=openAirLegacyViolations>0;
|
||||
std::printf("[PassageOpenAirNegativeControl] source=L24_ungated_support unsupported_samples=%llu detected=%s\n",
|
||||
static_cast<unsigned long long>(openAirLegacyViolations),
|
||||
l24OpenAirControlDetected?"true":"false");
|
||||
std::printf("[PassageOpenAirAuditSummary] interfaces=%llu samples=%llu violations=%llu l24_negative_control=%s status=%s\n",
|
||||
static_cast<unsigned long long>(openAirAudits),
|
||||
static_cast<unsigned long long>(openAirSamples),
|
||||
static_cast<unsigned long long>(openAirViolations),
|
||||
l24OpenAirControlDetected?"DETECTED":"MISSED",
|
||||
openAirAudits==passageCount&&openAirViolations==0&&l24OpenAirControlDetected
|
||||
?"PASS":"FAIL");
|
||||
const bool l23SmoothWallDetected=!looksNatural(0.0,0.0);
|
||||
std::printf("[PassageNaturalnessNegativeControl] source=L23_smooth_unrelieved_wall relief_p2p_m=0.000 threshold_m=%.3f detected=%s\n",
|
||||
MinimumWallReliefMetres,l23SmoothWallDetected?"true":"false");
|
||||
@@ -877,6 +1187,7 @@ namespace StandaloneStreamBenchmark
|
||||
MinimumWallReliefMetres,naturalPasses==passageCount&&l23SmoothWallDetected?"PASS":"FAIL");
|
||||
const bool auditPassed=passageCount>0&&passagePasses==passageCount
|
||||
&&naturalPasses==passageCount&&l23SmoothWallDetected
|
||||
&&openAirAudits==passageCount&&openAirViolations==0&&l24OpenAirControlDetected
|
||||
&&routePasses>0&&routePasses==routeWalkPasses&&routePasses==routeBindings
|
||||
&&routeBindingFailures==0
|
||||
&&blockedCompiled&&blockedControls==passageCount;
|
||||
|
||||
@@ -540,7 +540,7 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
FIntVector(0,0,islandSlot->TopChunkZ));
|
||||
const FStratePassageConfig& passage=islandSlot->Definition->PassageConfig;
|
||||
recipe.seed=static_cast<std::uint32_t>(owner.Seed);
|
||||
recipe.geometryVersion=5;
|
||||
recipe.geometryVersion=7;
|
||||
recipe.ownerVoxelSizeMicrons=owner.OwnerVoxelSizeMicrons;
|
||||
recipe.coreVoxelSizeMicrons=owner.CoreVoxelSizeMicrons;
|
||||
recipe.ownerToCoreScaleNumerator=owner.OwnerVoxelSizeMicrons/static_cast<std::uint32_t>(divisor);
|
||||
@@ -573,17 +573,22 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
recipe.anchorPlacementRadiusSubvoxels=ownerVoxelsToCoreQ(passage.DistanceMax);
|
||||
const std::int64_t spiralRadius=ownerVoxelsToCoreQ(passage.SpiralRadius);
|
||||
const std::int64_t mouthBore=std::max(ownerVoxelsToCoreQ(passage.MouthRadius),
|
||||
ownerVoxelsToCoreQ(12.0));
|
||||
ownerVoxelsToCoreQ(36.0));
|
||||
const std::int64_t bore=std::max(ownerVoxelsToCoreQ(passage.MidRadius),
|
||||
ownerVoxelsToCoreQ(12.0));
|
||||
const std::int64_t wallReserve=ownerVoxelsToCoreQ(12.0);
|
||||
// Put the mouth near the island rim, with enough island and root around the
|
||||
// first coil to read as a cave entrance instead of a hole in open air.
|
||||
recipe.anchorMouthOffsetSubvoxels=spiralRadius+mouthBore+ownerVoxelsToCoreQ(4.0);
|
||||
const std::int64_t excursion=std::max(ownerVoxelsToCoreQ(passage.Wander),spiralRadius);
|
||||
recipe.anchorRootRadiusSubvoxels=excursion+bore+wallReserve;
|
||||
ownerVoxelsToCoreQ(24.0));
|
||||
const std::int64_t wallReserve=ownerVoxelsToCoreQ(20.0);
|
||||
// Retain a 4.5-metre mouth for capsule clearance while keeping the long
|
||||
// ramp at three metres to leave more rock around the first coil.
|
||||
recipe.anchorMouthOffsetSubvoxels=ownerVoxelsToCoreQ(40.0);
|
||||
const std::int64_t supportOverhang=ownerVoxelsToCoreQ(16.0);
|
||||
const std::int64_t rootTaperReserve=ownerVoxelsToCoreQ(10.0);
|
||||
const std::int64_t mouthEnvelope=recipe.anchorMouthOffsetSubvoxels+mouthBore;
|
||||
const std::int64_t treadEnvelope=spiralRadius+bore+supportOverhang;
|
||||
recipe.anchorRootRadiusSubvoxels=std::max(mouthEnvelope,treadEnvelope)
|
||||
+wallReserve+rootTaperReserve;
|
||||
recipe.anchorIslandMinimumRadiusSubvoxels=std::max(recipe.minimumRadiusSubvoxels,
|
||||
recipe.anchorMouthOffsetSubvoxels+mouthBore+ownerVoxelsToCoreQ(4.0));
|
||||
std::max(recipe.anchorMouthOffsetSubvoxels+mouthBore+ownerVoxelsToCoreQ(4.0),
|
||||
recipe.anchorRootRadiusSubvoxels+wallReserve));
|
||||
recipe.anchorRootBottomZSubvoxels=recipe.layoutMinimumZSubvoxels
|
||||
+recipe.boundarySealSubvoxels;
|
||||
|
||||
@@ -713,6 +718,39 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
[&](const auto& p){return p.strateIndex==slot.StrateIndex;});
|
||||
if(it!=assembly.floatingIslands.end())
|
||||
{
|
||||
std::size_t passageMouthRouteIndex=it->manifest.plan.route.empty()?0
|
||||
:it->manifest.plan.route.size()-1;
|
||||
std::size_t resolvedRoutePointCount=it->manifest.plan.route.size();
|
||||
if(it->manifest.recipe.geometryVersion>=5
|
||||
&&!it->manifest.plan.route.empty()
|
||||
&&it->manifest.recipe.anchorIslandOrdinal<it->manifest.plan.islands.size())
|
||||
{
|
||||
const auto& rootedIsland=it->manifest.plan.islands[
|
||||
it->manifest.recipe.anchorIslandOrdinal];
|
||||
const double qPerMetre=static_cast<double>(
|
||||
VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel)*1000000.0
|
||||
/owner.CoreVoxelSizeMicrons;
|
||||
const double thresholdQ=static_cast<double>(
|
||||
it->manifest.recipe.anchorMouthOffsetSubvoxels)+qPerMetre;
|
||||
for(std::size_t reverse=it->manifest.plan.route.size();reverse>1;--reverse)
|
||||
{
|
||||
const std::size_t index=reverse-1;
|
||||
const auto& point=it->manifest.plan.route[index].positionSubvoxels;
|
||||
const double dx=static_cast<double>(point[0]-rootedIsland.centerSubvoxels[0]);
|
||||
const double dy=static_cast<double>(point[1]-rootedIsland.centerSubvoxels[1]);
|
||||
if(std::hypot(dx,dy)>thresholdQ)
|
||||
{
|
||||
passageMouthRouteIndex=std::min(index+1,
|
||||
it->manifest.plan.route.size()-1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Stop the island surface route immediately before it enters
|
||||
// the outer coil. The helical passage owns the walkable path
|
||||
// from its mouth inward.
|
||||
resolvedRoutePointCount=passageMouthRouteIndex>1
|
||||
?passageMouthRouteIndex-1:passageMouthRouteIndex;
|
||||
}
|
||||
const auto recipeBottom=fixedToMicrons(it->manifest.recipe.layoutMinimumZSubvoxels);
|
||||
const auto recipeTop=fixedToMicrons(it->manifest.recipe.layoutMaximumZSubvoxels);
|
||||
if(recipeBottom!=bottom||recipeTop!=top)
|
||||
@@ -737,10 +775,17 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
it->manifest.recipe.anchorPlacementRadiusSubvoxels,
|
||||
it->manifest.recipe.anchorRootBottomZSubvoxels,
|
||||
a.radiusSubvoxels};
|
||||
if(!it->manifest.plan.route.empty())
|
||||
{
|
||||
const auto& routeMouth=it->manifest.plan.route[
|
||||
passageMouthRouteIndex].positionSubvoxels;
|
||||
passageAnchor.values.insert(passageAnchor.values.end(),
|
||||
{routeMouth[0],routeMouth[1],routeMouth[2]});
|
||||
}
|
||||
s.anchors.push_back(std::move(passageAnchor));
|
||||
}
|
||||
}
|
||||
for(std::size_t n=0;n<it->manifest.plan.route.size();++n)
|
||||
for(std::size_t n=0;n<resolvedRoutePointCount;++n)
|
||||
{
|
||||
const auto& p=it->manifest.plan.route[n];ResolvedLayoutAnchorV2 anchor;
|
||||
anchor.kind="route_point";anchor.stableId="route/"+std::to_string(n);
|
||||
@@ -788,6 +833,32 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
{const auto& p=plan->route[n];ResolvedLayoutAnchorV2 a;a.kind="route_point";a.stableId="route/"+std::to_string(n);
|
||||
a.units="1/256 Core voxel";a.values={p.positionSubvoxels[0],p.positionSubvoxels[1],p.positionSubvoxels[2],p.radiusSubvoxels};
|
||||
s.anchors.push_back(std::move(a));}
|
||||
const auto upperAnchorStratum=std::find_if(out.strates.begin(),out.strates.end(),
|
||||
[](const auto& candidate)
|
||||
{return candidate.generatorKind=="FloatingIslands"
|
||||
&&std::any_of(candidate.anchors.begin(),candidate.anchors.end(),
|
||||
[](const auto& anchor){return anchor.kind=="passage_anchor"
|
||||
&&anchor.values.size()>=7;});});
|
||||
if(upperAnchorStratum!=out.strates.end())
|
||||
{
|
||||
const auto rootAnchor=std::find_if(upperAnchorStratum->anchors.begin(),
|
||||
upperAnchorStratum->anchors.end(),[](const auto& anchor)
|
||||
{return anchor.kind=="passage_anchor"&&anchor.values.size()>=7;});
|
||||
const std::int64_t arrivalZMicrometres=top-seal-1250000;
|
||||
if(rootAnchor!=upperAnchorStratum->anchors.end()
|
||||
&&arrivalZMicrometres>=bottom+seal&&arrivalZMicrometres<top-seal)
|
||||
{
|
||||
ResolvedLayoutAnchorV2 arrival;arrival.kind="passage_arrival";
|
||||
arrival.stableId="passage-arrival/from-strate/"
|
||||
+std::to_string(upperAnchorStratum->strateIndex);
|
||||
arrival.units="1/256 Core voxel";
|
||||
arrival.values={rootAnchor->values[0],rootAnchor->values[1],
|
||||
static_cast<std::int64_t>(std::llround(
|
||||
static_cast<double>(arrivalZMicrometres)
|
||||
*WorldAssemblySubvoxelsPerVoxel/owner.CoreVoxelSizeMicrons))};
|
||||
s.anchors.push_back(std::move(arrival));
|
||||
}
|
||||
}
|
||||
}
|
||||
out.strates.push_back(std::move(s));
|
||||
}
|
||||
@@ -803,9 +874,9 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
config.connections=authored.Connections;
|
||||
config.style=static_cast<std::uint32_t>(authored.Style);
|
||||
config.mouthRadiusSubvoxels=std::max(ownerVoxelsToCoreQ(authored.MouthRadius),
|
||||
ownerVoxelsToCoreQ(12.0));
|
||||
ownerVoxelsToCoreQ(36.0));
|
||||
config.midRadiusSubvoxels=std::max(ownerVoxelsToCoreQ(authored.MidRadius),
|
||||
ownerVoxelsToCoreQ(12.0));
|
||||
ownerVoxelsToCoreQ(24.0));
|
||||
config.reachMinimumSubvoxels=ownerVoxelsToCoreQ(authored.ReachMin);
|
||||
config.reachMaximumSubvoxels=ownerVoxelsToCoreQ(authored.ReachMax);
|
||||
config.distanceMinimumSubvoxels=ownerVoxelsToCoreQ(authored.DistanceMin);
|
||||
|
||||
Reference in new issue
Block a user