wip: Tunnel Network cave planner on Core caves (L134, route support still failing)

Seed + 21/47 legacy fields -> 30 compound rooms (72 primitives), 3 branches, destination chamber; mouths fixed;
8.67 deg max grade. Passage composer no longer skips native cave layers (incoming passage 21,425/21,425 open).
Open: route-support audit has 380 unsupported samples near the arrival mouth. Not for merge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-31 11:42:04 +01:00
1 parent 0065ee68e6
commit 75c6ad251d
9 files changed
+991 -30

No files matched your search

@@ -496,8 +496,9 @@ namespace VoxelForgeCore
// continuous floor while relaxing the spline chord tolerance and bounding shell work. // continuous floor while relaxing the spline chord tolerance and bounding shell work.
// Version 226 unifies the passage influence reach in metres and converts it to core // Version 226 unifies the passage influence reach in metres and converts it to core
// voxels for every broadphase and proof bound. Version 227 uses the exact smooth-max // voxels for every broadphase and proof bound. Version 227 uses the exact smooth-max
// support width/6 when deriving that reach. // support width/6 when deriving that reach. Version 228 composes deterministic cave
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 227; // graphs from fixed passage mouths and the owner Tunnel Network settings.
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 228;
enum class FieldChannel : std::uint32_t enum class FieldChannel : std::uint32_t
{ {
@@ -991,6 +992,34 @@ namespace VoxelForgeCore
std::array<std::int64_t, 3> boundsMaximumMicrometres{}; std::array<std::int64_t, 3> boundsMaximumMicrometres{};
}; };
// Inputs to the reusable, plan-time cave graph composer. All distances are
// integer micrometres and unit-interval values are Q16 so the resolved graph
// does not depend on worker count or floating-point random distributions.
struct ResolvedCavernPlannerSettingsV1
{
std::int64_t roomSpacingMicrometres = 16000000;
std::int64_t minimumRoomRadiusMicrometres = 2000000;
std::int64_t maximumRoomRadiusMicrometres = 5000000;
std::int64_t destinationRadiusMicrometres = 6000000;
std::int64_t minimumTunnelRadiusMicrometres = 750000;
std::int64_t maximumTunnelRadiusMicrometres = 1000000;
std::int64_t maximumTunnelLengthMicrometres = 45000000;
std::int64_t tunnelWarpMicrometres = 0;
std::int64_t caveWarpMicrometres = 0;
std::int64_t smoothWidthMicrometres = 500000;
std::int64_t irregularityAmplitudeMicrometres = 0;
std::int64_t irregularityWavelengthMicrometres = 0;
std::int32_t roomDensityQ16 = 22938;
std::int32_t tunnelDensityQ16 = 26214;
std::int32_t roomShapeVarietyQ16 = 32768;
std::int32_t roomHeightRatioQ16 = 55706;
std::int32_t roomFloorCutMinimumQ16 = 45875;
std::int32_t roomFloorCutMaximumQ16 = 58982;
std::int32_t tunnelHorizontalBiasQ16 = 32768;
std::int32_t originRoomMaximumConnections = 4;
bool tunnelsFlowTowardOrigin = true;
};
// Immutable material-only deposits. The capsule and its conservative bounds are in // Immutable material-only deposits. The capsule and its conservative bounds are in
// micrometres; bounds include irregularity and the blend envelope. // micrometres; bounds include irregularity and the blend envelope.
struct ResolvedLayoutOreVeinRequestV1 struct ResolvedLayoutOreVeinRequestV1
@@ -1095,7 +1124,7 @@ namespace VoxelForgeCore
inline bool IsNativeCaveFieldStratum(const ResolvedLayoutStratumV2& stratum) noexcept inline bool IsNativeCaveFieldStratum(const ResolvedLayoutStratumV2& stratum) noexcept
{ {
return stratum.generatorKind=="Caves" return (stratum.generatorKind=="Caves"||stratum.generatorKind=="TunnelNetwork")
&&stratum.recipeId=="Core.Caves.PrimitiveField" &&stratum.recipeId=="Core.Caves.PrimitiveField"
&&stratum.recipeVersion==1; &&stratum.recipeVersion==1;
} }
@@ -1256,6 +1285,17 @@ namespace VoxelForgeCore
std::span<const ResolvedCavernRoomV1> rooms, std::span<const ResolvedCavernRoomV1> rooms,
std::span<const ResolvedCavernEdgeV1> edges, std::span<const ResolvedCavernEdgeV1> edges,
std::string& error); std::string& error);
// Build a deterministic, walkable room-and-tunnel graph between the two
// passage mouths that terminate in strateIndex. Compound rooms are emitted
// as overlapping room primitives; the route is planned below a 15 degree
// walking grade and every corridor carries a 4.5 m vertical clearance.
VFCORE_API bool PlanResolvedCavernNetworkV1(
const ResolvedLayoutSnapshotV3& layout, std::int32_t seed,
std::int32_t strateIndex,
const ResolvedCavernPlannerSettingsV1& settings,
std::vector<ResolvedCavernRoomV1>& rooms,
std::vector<ResolvedCavernEdgeV1>& edges,
std::string& error);
VFCORE_API bool ResolveResolvedLayoutOreVeinsV1( VFCORE_API bool ResolveResolvedLayoutOreVeinsV1(
ResolvedLayoutSnapshotV3& layout, std::int32_t seed, ResolvedLayoutSnapshotV3& layout, std::int32_t seed,
std::span<const ResolvedLayoutOreVeinRequestV1> requests, std::span<const ResolvedLayoutOreVeinRequestV1> requests,
@@ -2031,6 +2031,458 @@ bool ResolveResolvedCavernGraphV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
layout=std::move(candidate);return true; layout=std::move(candidate);return true;
} }
bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
std::int32_t seed,std::int32_t strateIndex,
const ResolvedCavernPlannerSettingsV1& settings,
std::vector<ResolvedCavernRoomV1>& rooms,
std::vector<ResolvedCavernEdgeV1>& edges,std::string& error)
{
rooms.clear();edges.clear();error.clear();
const auto stratum=std::find_if(layout.strates.begin(),layout.strates.end(),
[strateIndex](const auto& value){return value.strateIndex==strateIndex;});
if(stratum==layout.strates.end()||!IsNativeCaveFieldStratum(*stratum))
{error="cavern planner target is not a resolved native cave stratum";return false;}
if(settings.roomSpacingMicrometres<=0
||settings.minimumRoomRadiusMicrometres<500000
||settings.maximumRoomRadiusMicrometres<settings.minimumRoomRadiusMicrometres
||settings.maximumRoomRadiusMicrometres>100000000
||settings.destinationRadiusMicrometres<settings.maximumRoomRadiusMicrometres
||settings.destinationRadiusMicrometres>100000000
||settings.maximumTunnelLengthMicrometres<=0
||settings.minimumTunnelRadiusMicrometres<0
||settings.maximumTunnelRadiusMicrometres<settings.minimumTunnelRadiusMicrometres
||settings.smoothWidthMicrometres<0
||settings.smoothWidthMicrometres>10000000
||settings.irregularityAmplitudeMicrometres<0
||settings.irregularityAmplitudeMicrometres>10000000
||(settings.irregularityAmplitudeMicrometres>0
&&settings.irregularityWavelengthMicrometres<=0))
{error="cavern planner settings are outside the bounded room, tunnel or relief ranges";return false;}
const ResolvedLayoutPassageV1* arrivalPassage=nullptr;
const ResolvedLayoutPassageV1* departurePassage=nullptr;
for(const auto& passage:layout.passages)
{
if(passage.lowerStrateIndex==strateIndex&&passage.upperStrateIndex<strateIndex
&&(!arrivalPassage||passage.stableId<arrivalPassage->stableId))
arrivalPassage=&passage;
if(passage.upperStrateIndex==strateIndex&&passage.lowerStrateIndex>strateIndex
&&(!departurePassage||passage.stableId<departurePassage->stableId))
departurePassage=&passage;
}
if(!arrivalPassage||!departurePassage)
{error="cavern planner needs an incoming lower mouth and an outgoing upper mouth in the target strate";return false;}
if(layout.coreVoxelSizeMicrometres==0)
{error="cavern planner requires a nonzero Core voxel size";return false;}
using FPoint=std::array<std::int64_t,3>;
constexpr std::int64_t MicrometresPerMetre=1000000;
constexpr std::int64_t MinimumBoreRadius=1750000;
constexpr std::int64_t MinimumBoreHeight=2200000;
constexpr std::int64_t PlannedBoreHeight=4500000;
constexpr std::int64_t MouthOverlap=3000000;
constexpr std::int64_t MouthFloorOffset=750000;
constexpr std::int64_t MaximumBoreRadius=10000000;
constexpr std::int64_t MaximumWalkGradeDenominator=5; // 1:5 = 11.31 degrees.
const auto integerSqrt=[](std::uint64_t value)
{
std::uint64_t result=0;
std::uint64_t bit=std::uint64_t{1}<<62;
while(bit>value)bit>>=2;
while(bit!=0)
{
if(value>=result+bit)
{value-=result+bit;result=(result>>1)+bit;}
else result>>=1;
bit>>=2;
}
return result;
};
const auto distance2d=[&](const FPoint& a,const FPoint& b)
{
const std::int64_t dx=b[0]-a[0],dy=b[1]-a[1];
const auto squared=static_cast<std::uint64_t>(dx*dx+dy*dy);
return static_cast<std::int64_t>(integerSqrt(squared));
};
const auto qToMicrometres=[&](std::int64_t q)
{return q*static_cast<std::int64_t>(layout.coreVoxelSizeMicrometres)
/WorldAssemblySubvoxelsPerVoxel;};
const auto mouthInnerPoint=[&](const ResolvedLayoutPassageV1& passage,
bool lowerSide,FPoint& point)
{
for(std::size_t axis=0;axis<3;++axis)
point[axis]=qToMicrometres(lowerSide?passage.lowerMouthSubvoxels[axis]
:passage.upperMouthSubvoxels[axis]);
point[2]-=MouthFloorOffset;
if(passage.controlPoints.size()<2)return false;
const std::size_t mouthIndex=lowerSide
?std::min(passage.lowerMouthControlIndex,passage.controlPoints.size()-1):0;
const std::size_t tangentIndex=lowerSide
?(mouthIndex>0?mouthIndex-1:1):1;
const std::int64_t tx=passage.controlPoints[mouthIndex].positionSubvoxels[0]
-passage.controlPoints[tangentIndex].positionSubvoxels[0];
const std::int64_t ty=passage.controlPoints[mouthIndex].positionSubvoxels[1]
-passage.controlPoints[tangentIndex].positionSubvoxels[1];
const auto tangentLength=static_cast<std::int64_t>(integerSqrt(
static_cast<std::uint64_t>(tx*tx+ty*ty)));
if(tangentLength<=0)return false;
point[0]+=tx*MouthOverlap/tangentLength;
point[1]+=ty*MouthOverlap/tangentLength;
return true;
};
FPoint routeStart{},routeEnd{};
if(!mouthInnerPoint(*arrivalPassage,true,routeStart)
||!mouthInnerPoint(*departurePassage,false,routeEnd))
{error="cavern passage mouth is missing a usable horizontal route tangent";return false;}
const std::int64_t dx=routeEnd[0]-routeStart[0];
const std::int64_t dy=routeEnd[1]-routeStart[1];
const std::int64_t dz=routeEnd[2]-routeStart[2];
const std::int64_t straightLength=distance2d(routeStart,routeEnd);
const std::int64_t horizontalBias=std::clamp<std::int64_t>(
settings.tunnelHorizontalBiasQ16,0,65536);
const std::int64_t planGradeDenominator=MaximumWalkGradeDenominator
+horizontalBias/32768;
const std::int64_t requiredLength=std::abs(dz)*planGradeDenominator+1000;
std::int64_t lateral=std::max<std::int64_t>(1000000,
std::max(settings.tunnelWarpMicrometres,settings.caveWarpMicrometres)/2);
const std::uint64_t routeHash=LayoutStampSeed(seed,strateIndex,
"tunnel-network/main-route");
if((routeHash&1u)!=0)lateral=-lateral;
std::int64_t pathAxisX=dx,pathAxisY=dy;
const auto makeRoute=[&](std::int64_t lateralOffset)
{
std::array<FPoint,4> points{};
points[0]=routeStart;points[3]=routeEnd;
std::int64_t axisX=dx,axisY=dy;
std::int64_t axisLength=straightLength;
if(axisLength<=0)
{
axisX=1;axisY=0;axisLength=1;
}
pathAxisX=axisX;pathAxisY=axisY;
const std::int64_t perpX=-axisY*lateralOffset/axisLength;
const std::int64_t perpY= axisX*lateralOffset/axisLength;
for(std::size_t axis=0;axis<3;++axis)
{
const std::int64_t delta=routeEnd[axis]-routeStart[axis];
points[1][axis]=routeStart[axis]+delta/3;
points[2][axis]=routeStart[axis]+(delta*2)/3;
}
points[1][0]+=perpX;points[1][1]+=perpY;
points[2][0]-=perpX;points[2][1]-=perpY;
return points;
};
const auto routeLength=[&](const std::array<FPoint,4>& points)
{
std::int64_t length=0;
for(std::size_t i=1;i<points.size();++i)length+=distance2d(points[i-1],points[i]);
return length;
};
auto routePoints=makeRoute(lateral);
for(int attempt=0;routeLength(routePoints)<requiredLength&&attempt<32;++attempt)
{
if(lateral>0)lateral=std::min<std::int64_t>(lateral*2,1000000000ll);
else lateral=std::max<std::int64_t>(lateral*2,-1000000000ll);
routePoints=makeRoute(lateral);
}
const std::int64_t totalRouteLength=routeLength(routePoints);
if(totalRouteLength<=0||totalRouteLength<requiredLength)
{error="cavern planner cannot fit the fixed mouth floor difference under the 1:5 grade budget";return false;}
std::array<std::int64_t,4> cumulative{};
for(std::size_t i=1;i<routePoints.size();++i)
cumulative[i]=cumulative[i-1]+distance2d(routePoints[i-1],routePoints[i]);
if(cumulative.back()!=totalRouteLength)
{error="cavern planner route length accounting failed";return false;}
const auto pointAt=[&](std::int64_t distance)
{
distance=std::clamp<std::int64_t>(distance,0,totalRouteLength);
std::size_t segment=1;
while(segment<cumulative.size()-1&&distance>cumulative[segment])++segment;
const std::int64_t startDistance=cumulative[segment-1];
const std::int64_t segmentLength=std::max<std::int64_t>(1,
cumulative[segment]-startDistance);
const std::int64_t local=distance-startDistance;
FPoint point{};
for(std::size_t axis=0;axis<2;++axis)
{
const std::int64_t delta=routePoints[segment][axis]
-routePoints[segment-1][axis];
point[axis]=routePoints[segment-1][axis]+delta*local/segmentLength;
}
point[2]=routeStart[2]+dz*distance/totalRouteLength;
return point;
};
std::int64_t spacing=std::min(std::max<std::int64_t>(settings.roomSpacingMicrometres,
4000000),settings.maximumTunnelLengthMicrometres);
spacing=std::max<std::int64_t>(4000000,spacing);
if(spacing>settings.maximumTunnelLengthMicrometres)
spacing=settings.maximumTunnelLengthMicrometres;
if(spacing<=0)
{error="cavern planner has no positive route spacing after applying tunnel length limits";return false;}
std::vector<std::int64_t> nodeDistances;
for(std::size_t leg=1;leg<routePoints.size();++leg)
{
const std::int64_t legStart=cumulative[leg-1];
const std::int64_t legLength=cumulative[leg]-legStart;
const std::int64_t segments=std::max<std::int64_t>(1,
(legLength+spacing-1)/spacing);
if(segments>256)
{error="cavern planner would exceed the 256-node bounded backbone";return false;}
for(std::int64_t part=1;part<=segments;++part)
{
const std::int64_t distance=legStart+legLength*part/segments;
if(distance>0&&distance<totalRouteLength)nodeDistances.push_back(distance);
}
}
nodeDistances.push_back(totalRouteLength/2);
std::sort(nodeDistances.begin(),nodeDistances.end());
nodeDistances.erase(std::unique(nodeDistances.begin(),nodeDistances.end()),nodeDistances.end());
if(nodeDistances.empty())nodeDistances.push_back(totalRouteLength/2);
if(nodeDistances.size()>256)
{error="cavern planner would exceed the 256-node bounded backbone";return false;}
const auto clampQ16=[](std::int32_t value)
{return std::clamp<std::int64_t>(value,0,65536);};
const std::int64_t smooth=std::clamp<std::int64_t>(
std::max<std::int64_t>(1500000,settings.smoothWidthMicrometres),
50000,10000000);
const std::int64_t irregularity=std::clamp<std::int64_t>(
settings.irregularityAmplitudeMicrometres,0,500000);
const std::int64_t routeBoreRadius=std::clamp<std::int64_t>(
std::max(MinimumBoreRadius,settings.minimumTunnelRadiusMicrometres),
MinimumBoreRadius,MaximumBoreRadius);
const std::int64_t maximumRouteBoreRadius=std::clamp<std::int64_t>(
std::max(routeBoreRadius,settings.maximumTunnelRadiusMicrometres),
routeBoreRadius,MaximumBoreRadius);
const auto selectedBoreRadius=[&](const std::string& stableId)
{
const std::uint64_t span=static_cast<std::uint64_t>(
maximumRouteBoreRadius-routeBoreRadius);
return routeBoreRadius+(span==0?0:static_cast<std::int64_t>(
LayoutStampSeed(seed,strateIndex,stableId)%(span+1)));
};
const auto passageBoreRadius=[&](const ResolvedLayoutPassageV1& passage,bool lowerSide)
{
std::int64_t radius=qToMicrometres(passage.mouthRadiusSubvoxels);
if(!passage.controlPoints.empty())
{
const auto control=lowerSide?std::min(passage.lowerMouthControlIndex,
passage.controlPoints.size()-1):std::size_t{0};
radius=std::max(radius,qToMicrometres(passage.controlPoints[control].radiusSubvoxels));
}
return std::clamp<std::int64_t>(std::max(routeBoreRadius,radius),
routeBoreRadius,MaximumBoreRadius);
};
const auto fractional=[](std::int64_t value,std::int64_t q16)
{return value*std::clamp<std::int64_t>(q16,0,65536)/65536;};
struct FRoomNode
{
std::string id;
FPoint point{};
std::int64_t radiusMicrometres=0;
bool destination=false;
};
std::vector<FRoomNode> nodes;nodes.reserve(nodeDistances.size());
const std::int64_t floorCutMinimum=clampQ16(settings.roomFloorCutMinimumQ16);
const std::int64_t floorCutMaximum=std::max(floorCutMinimum,
clampQ16(settings.roomFloorCutMaximumQ16));
const std::int64_t floorRange=floorCutMaximum-floorCutMinimum;
const std::int64_t heightRatio=std::clamp<std::int64_t>(
settings.roomHeightRatioQ16,6554,65536);
const std::int64_t baseRoomSpan=settings.maximumRoomRadiusMicrometres
-settings.minimumRoomRadiusMicrometres;
const std::int64_t mainRouteMidpoint=totalRouteLength/2;
for(std::size_t nodeIndex=0;nodeIndex<nodeDistances.size();++nodeIndex)
{
const std::int64_t distance=nodeDistances[nodeIndex];
FRoomNode node;node.point=pointAt(distance);
node.destination=distance==mainRouteMidpoint;
node.id="tunnel-network/strate-"+std::to_string(strateIndex)+"/room-"
+std::to_string(nodeIndex);
const std::uint64_t hash=LayoutStampSeed(seed,strateIndex,node.id);
node.radiusMicrometres=node.destination?settings.destinationRadiusMicrometres
:settings.minimumRoomRadiusMicrometres
+static_cast<std::int64_t>(hash%static_cast<std::uint64_t>(baseRoomSpan+1));
nodes.push_back(std::move(node));
}
const auto makeCompound=[&](const FRoomNode& node,bool destination,
std::vector<ResolvedCavernRoomV1>& outRooms)
{
const std::uint64_t hash=LayoutStampSeed(seed,strateIndex,node.id+"/compound");
const std::int64_t variety=clampQ16(settings.roomShapeVarietyQ16);
const std::size_t additionalPrimitiveCount=static_cast<std::size_t>(
variety*2/65536);
const std::size_t primitiveCount=2+static_cast<std::size_t>(
hash%(additionalPrimitiveCount+1));
std::uint64_t state=hash;
const std::int64_t radius=node.radiusMicrometres;
const std::int64_t floorCutBase=clampQ16(settings.roomFloorCutMinimumQ16);
const std::int64_t componentCount=static_cast<std::int64_t>(primitiveCount);
for(std::int64_t component=0;component<componentCount;++component)
{
const std::string stableId=node.id+"/primitive-"+std::to_string(component);
const std::uint64_t componentHash=LayoutStampNext(state);
const std::int64_t componentScaleQ16=component==0?65536
:42000+static_cast<std::int64_t>(componentHash%22001);
const std::int64_t componentRadius=std::max<std::int64_t>(500000,
fractional(radius,componentScaleQ16));
const std::int64_t axisScaleQ16=component==0?65536
:47000+static_cast<std::int64_t>((componentHash>>8)%19001);
const bool swapAxes=((componentHash>>24)&1u)!=0;
const std::int64_t halfX=swapAxes?fractional(componentRadius,axisScaleQ16)
:componentRadius;
const std::int64_t halfY=swapAxes?componentRadius
:fractional(componentRadius,axisScaleQ16);
const std::int64_t halfZ=std::max<std::int64_t>(500000,
fractional(componentRadius,heightRatio));
const std::int64_t floorCut=floorCutBase
+(floorRange==0?0:static_cast<std::int64_t>(componentHash%(
static_cast<std::uint64_t>(floorRange+1))));
const std::int64_t offsetMagnitude=component==0?0
:fractional(radius,12000+static_cast<std::int64_t>(componentHash%11001));
const std::int64_t routeLengthXY=std::max<std::int64_t>(1,
static_cast<std::int64_t>(integerSqrt(static_cast<std::uint64_t>(
pathAxisX*pathAxisX+pathAxisY*pathAxisY))));
const std::int64_t centerOffset=(component%2==0?1:-1)*offsetMagnitude;
ResolvedCavernRoomV1 room;room.stableId=stableId;
room.strateIndex=strateIndex;room.floorMicrometres=node.point[2];
room.halfExtentsMicrometres={halfX,halfY,halfZ};
room.centerMicrometres={node.point[0]+(-pathAxisY)*centerOffset/routeLengthXY,
node.point[1]+pathAxisX*centerOffset/routeLengthXY,
node.point[2]+fractional(halfZ,floorCut)};
if(component>0)
{
const std::int64_t headroom=std::max<std::int64_t>(0,
halfZ-fractional(halfZ,floorCut));
const std::int64_t jitterRange=std::min(headroom,halfZ/5);
const std::int64_t ceilingJitter=jitterRange>0
?static_cast<std::int64_t>((componentHash>>32)
%static_cast<std::uint64_t>(jitterRange+1)):0;
room.centerMicrometres[2]+=ceilingJitter;
}
room.smoothWidthMicrometres=smooth;
room.irregularityAmplitudeMicrometres=irregularity;
room.irregularityWavelengthMicrometres=settings.irregularityWavelengthMicrometres;
outRooms.push_back(std::move(room));
}
(void)destination;
};
for(const auto& node:nodes)makeCompound(node,node.destination,rooms);
const auto componentRoomId=[](const FRoomNode& node)
{return node.id+"/primitive-0";};
const auto makeEdge=[&](std::string id,std::string startAnchor,
const FPoint& start,const FRoomNode& endNode,std::int64_t boreRadius)
{
ResolvedCavernEdgeV1 edge;edge.stableId=std::move(id);
edge.startAnchorStableId=std::move(startAnchor);
edge.endRoomStableId=componentRoomId(endNode);edge.strateIndex=strateIndex;
edge.boreRadiusMicrometres=boreRadius;
edge.boreHeightMicrometres=std::max(PlannedBoreHeight,MinimumBoreHeight);
edge.floorSupportDepthMicrometres=0;
edge.smoothWidthMicrometres=smooth;
edge.irregularityAmplitudeMicrometres=irregularity;
edge.irregularityWavelengthMicrometres=settings.irregularityWavelengthMicrometres;
const FPoint end={endNode.point[0],endNode.point[1],endNode.point[2]};
for(std::size_t axis=0;axis<3;++axis)
{
const std::int64_t delta=end[axis]-start[axis];
edge.controlPointsMicrometres[0][axis]=start[axis];
edge.controlPointsMicrometres[1][axis]=start[axis]+delta/3;
edge.controlPointsMicrometres[2][axis]=start[axis]+(delta*2)/3;
edge.controlPointsMicrometres[3][axis]=end[axis];
}
return edge;
};
const auto arrivalPoint=routeStart;
FPoint previousPoint=arrivalPoint;
std::string previousAnchor=arrivalPassage->stableId+"/lower-mouth";
for(std::size_t nodeIndex=0;nodeIndex<nodes.size();++nodeIndex)
{
const auto& node=nodes[nodeIndex];
const bool portal=nodeIndex==0;
const std::string edgeId="tunnel-network/strate-"+std::to_string(strateIndex)
+"/route-edge-"+std::to_string(nodeIndex);
edges.push_back(makeEdge(edgeId,previousAnchor,previousPoint,node,
portal?passageBoreRadius(*arrivalPassage,true):selectedBoreRadius(edgeId)));
previousPoint={node.point[0],node.point[1],node.point[2]};
previousAnchor=componentRoomId(node);
}
if(!nodes.empty())
{
const std::string departureEdgeId="tunnel-network/strate-"
+std::to_string(strateIndex)+"/departure-edge";
edges.push_back(makeEdge(departureEdgeId,departurePassage->stableId+"/upper-mouth",
routeEnd,nodes.back(),passageBoreRadius(*departurePassage,false)));
}
const std::int64_t roomDensity=clampQ16(settings.roomDensityQ16);
const std::int64_t tunnelDensity=clampQ16(settings.tunnelDensityQ16);
const std::int64_t branchThreshold=roomDensity*tunnelDensity/65536;
const std::size_t branchLimit=settings.originRoomMaximumConnections<=0
?32u:static_cast<std::size_t>(std::clamp(settings.originRoomMaximumConnections,0,32));
std::size_t branchCount=0;
for(std::size_t nodeIndex=1;nodeIndex+1<nodes.size()
&&branchCount<branchLimit&&rooms.size()<2048;++nodeIndex)
{
const auto& parent=nodes[nodeIndex];
const std::uint64_t branchHash=LayoutStampSeed(seed,strateIndex,
parent.id+"/side-branch");
if(static_cast<std::int64_t>(branchHash&0xffffu)>=branchThreshold)continue;
const std::int64_t branchLength=std::max<std::int64_t>(
routeBoreRadius*3,std::min(spacing/2,settings.maximumTunnelLengthMicrometres/2));
const std::int64_t direction=(branchHash&0x10000u)?1:-1;
FRoomNode branch;branch.id=parent.id+"/side-"+std::to_string(branchCount);
branch.radiusMicrometres=settings.minimumRoomRadiusMicrometres
+static_cast<std::int64_t>((branchHash>>17)
%static_cast<std::uint64_t>(baseRoomSpan+1));
const std::int64_t pathAxisLength=std::max<std::int64_t>(1,
static_cast<std::int64_t>(integerSqrt(static_cast<std::uint64_t>(
pathAxisX*pathAxisX+pathAxisY*pathAxisY))));
branch.point={parent.point[0]+(-pathAxisY)*branchLength/pathAxisLength*direction,
parent.point[1]+pathAxisX*branchLength/pathAxisLength*direction,
parent.point[2]};
makeCompound(branch,false,rooms);
const std::string branchEdgeId="tunnel-network/strate-"
+std::to_string(strateIndex)+"/side-edge-"+std::to_string(branchCount);
if(settings.tunnelsFlowTowardOrigin)
edges.push_back(makeEdge(branchEdgeId,componentRoomId(branch),
branch.point,parent,selectedBoreRadius(branchEdgeId)));
else
edges.push_back(makeEdge(branchEdgeId,componentRoomId(parent),
parent.point,branch,selectedBoreRadius(branchEdgeId)));
++branchCount;
}
if(nodes.empty()||rooms.empty()||edges.empty())
{error="cavern planner emitted an empty arrival-to-departure graph";return false;}
double maximumGradeDegrees=0.0;
for(const auto& edge:edges)
{
const auto& a=edge.controlPointsMicrometres.front();
const auto& b=edge.controlPointsMicrometres.back();
const std::int64_t run=static_cast<std::int64_t>(integerSqrt(
static_cast<std::uint64_t>((b[0]-a[0])*(b[0]-a[0])
+(b[1]-a[1])*(b[1]-a[1]))));
const std::int64_t rise=std::abs(b[2]-a[2]);
if(run<=0&&rise>0)
{error="cavern planner produced a vertical route edge with no walking run";return false;}
if(run>0&&rise*MaximumWalkGradeDenominator>run)
{error="cavern planner emitted an edge beyond its 1:5 route grade budget";return false;}
if(run>0)maximumGradeDegrees=std::max(maximumGradeDegrees,
std::atan2(static_cast<double>(rise),static_cast<double>(run))
*180.0/3.14159265358979323846);
}
std::printf("[CavernPlannerV1] strate=%d seed=%d mouths=%s->%s route_nodes=%zu room_primitives=%zu edges=%zu branches=%zu route_horizontal_m=%.3f floor_delta_m=%.3f max_grade_deg=%.3f grade_limit_deg=11.310 clearance_radius_m=%.3f clearance_height_m=%.3f destination_radius_m=%.3f status=PASS\n",
strateIndex,seed,arrivalPassage->stableId.c_str(),departurePassage->stableId.c_str(),
nodes.size(),rooms.size(),edges.size(),branchCount,totalRouteLength/1.0e6,
std::abs(dz)/1.0e6,maximumGradeDegrees,routeBoreRadius/1.0e6,PlannedBoreHeight/1.0e6,
settings.destinationRadiusMicrometres/1.0e6);
return true;
}
bool ResolveResolvedLayoutOreVeinsV1(ResolvedLayoutSnapshotV3& layout,std::int32_t seed, bool ResolveResolvedLayoutOreVeinsV1(ResolvedLayoutSnapshotV3& layout,std::int32_t seed,
std::span<const ResolvedLayoutOreVeinRequestV1> requests,std::string& error) std::span<const ResolvedLayoutOreVeinRequestV1> requests,std::string& error)
{ {
+25 -7
View File
@@ -1221,16 +1221,15 @@ public:
double unionDistance=std::numeric_limits<double>::infinity(); double unionDistance=std::numeric_limits<double>::infinity();
double unionShellDistance=std::numeric_limits<double>::infinity(); double unionShellDistance=std::numeric_limits<double>::infinity();
const std::uint32_t reliefSeed=static_cast<std::uint32_t>(Owner?Owner->Seed:0); const std::uint32_t reliefSeed=static_cast<std::uint32_t>(Owner?Owner->Seed:0);
const auto* queryStratum=FindBoundStratumForManagerZ(Z);
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size(); for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();
++passageIndex) ++passageIndex)
{ {
const auto& passage=ResolvedLayout->passages[passageIndex]; const auto& passage=ResolvedLayout->passages[passageIndex];
if(passage.controlPoints.size()<2)continue; if(passage.controlPoints.size()<2)continue;
if(queryStratum&&queryStratum->Resolved // Native graph fields own their local rooms and tunnels, not the
&&queryStratum->Resolved->passageCapabilities.hasNativeCaveField // inter-strate connection above their mouth. Union the shared passage
&&passage.lowerStrateIndex==queryStratum->StrateIndex) // field here as well so the connector remains open through the boundary
continue; // band for both native caves and future native lattice layers.
if(passageProfile)++passageProfile->passageIterations; if(passageProfile)++passageProfile->passageIterations;
if(passageTileScopeMatches&&!passageTileQueries->hasRouteFeature(passageIndex)) if(passageTileScopeMatches&&!passageTileQueries->hasRouteFeature(passageIndex))
continue; continue;
@@ -7488,7 +7487,16 @@ private:
const double delta=(point[axis]-feature.centerMetres[axis])/radius; const double delta=(point[axis]-feature.centerMetres[axis])/radius;
normalizedSquared+=delta*delta;meanRadius+=radius/3.0; normalizedSquared+=delta*delta;meanRadius+=radius/3.0;
} }
const double ellipsoid=(std::sqrt(normalizedSquared)-1.0)*meanRadius; double ellipsoid=(std::sqrt(normalizedSquared)-1.0)*meanRadius;
if(feature.irregularityAmplitudeVoxels>0.0f)
{
const float fade=SmoothUnit((Z-feature.floorVoxels)/0.8f);
ellipsoid+=StampValueNoise(
X-feature.centerVoxels[0],Y-feature.centerVoxels[1],
Z-feature.centerVoxels[2],
feature.irregularityWavelengthVoxels,feature.irregularitySeed)
*feature.irregularityAmplitudeVoxels*metresPerVoxel*fade;
}
const double floor=feature.room->floorMicrometres/1000000.0-point[2]; const double floor=feature.room->floorMicrometres/1000000.0-point[2];
const double width=feature.room->smoothWidthMicrometres/1000000.0; const double width=feature.room->smoothWidthMicrometres/1000000.0;
featureVoid=SmoothMaxMetres(ellipsoid,floor,width); featureVoid=SmoothMaxMetres(ellipsoid,floor,width);
@@ -7526,7 +7534,17 @@ private:
} }
const double normalized=std::sqrt(radialSquared/(radius*radius) const double normalized=std::sqrt(radialSquared/(radius*radius)
+vertical*vertical/(height*height)); +vertical*vertical/(height*height));
const double distance=(normalized-1.0)*std::min(radius,height); double distance=(normalized-1.0)*std::min(radius,height);
if(feature.irregularityAmplitudeVoxels>0.0f)
{
const float floorVoxels=static_cast<float>(nearestFloor/metresPerVoxel);
const float fade=SmoothUnit((Z-floorVoxels)/0.8f);
distance+=StampValueNoise(
X-feature.controlPointsVoxels[0][0],
Y-feature.controlPointsVoxels[0][1],Z-floorVoxels,
feature.irregularityWavelengthVoxels,feature.irregularitySeed)
*feature.irregularityAmplitudeVoxels*metresPerVoxel*fade;
}
const bool clearlyCloser=distance<tube-SegmentDistanceTieToleranceMetres; const bool clearlyCloser=distance<tube-SegmentDistanceTieToleranceMetres;
const bool stableTie=std::abs(distance-tube) const bool stableTie=std::abs(distance-tube)
<=SegmentDistanceTieToleranceMetres&&segmentIndex<nearestSegment; <=SegmentDistanceTieToleranceMetres&&segmentIndex<nearestSegment;
@@ -0,0 +1,11 @@
# Linux134 — Tunnel Network cave planner
The second design fixed the incoming passage at its source. Native cave composition already called the shared passage composer, but that composer skipped passages whose lower layer used `hasNativeCaveField`. Removing that skip lets the passage field join the cave field through the boundary and also applies to native lattice layers. Tunnel Network dispatches `Caves-native` (`Core.Caves.PrimitiveField` v1).
The seed-0 plan is strict-valid: 27 route nodes, 72 primitives in 30 compound rooms, 3 branches, 31 edges, and a 6.75 m destination chamber. The route is 387.067 m long with 59.021 m floor delta and 8.670° maximum grade. The passage centerline check now passes 21,425/21,425 samples. All three passage interfaces, shell/floor, ripple, and blocked controls pass.
**Stopped at the Tunnel Network route-support gate.** `ResolvedStrateRouteAudit` reports 0 blocked capsule samples and 0 grade failures, but 380 unsupported samples. The first support failures are just inside the arrival mouth at `(19.166,-14.650,-754.254) m`, then `(19.080,-15.135,-754.327) m` through `(18.909,-16.106,-754.473) m`. Another failing sample cluster starts at `(17.364,-24.839,-755.788) m`. Overall: 795 center samples, 6,957 support probes, `mouth_binding=PASS`, `status=FAIL`. The arrival-to-departure route is therefore not yet walkable under the support audit. I stopped before the remaining bar.
The planner maps 21/47 legacy fields into room/edge records: `RoomSpacing`, `RoomDensity`, `MinRoomRadius`, `MaxRoomRadius`, `RoomHeightRatio`, `RoomFloorCutMin`, `RoomFloorCutMax`, `RoomShapeVariety`, `OriginRoomRadius`, `OriginRoomMaxConnections`, `TunnelMinRadius`, `TunnelMaxRadius`, `TunnelDensity`, `MaxTunnelLength`, `TunnelWarpStrength`, `TunnelHorizontalBias`, `bTunnelsFlowTowardOrigin`, `CaveWarpStrength`, `SurfaceRoughness`, `RoughnessFrequency`, and `SDFBlendRadius`. The other 26 do not map: `BaseDensity`, `BoundarySealThickness`, `CaveWarpFrequency`, `DomainWarpFrequency`, `DomainWarpStrength`, `FloorBias`, `FloorReliefFrequency`, `FloorReliefStrength`, `RoomMouthRiseBlendVoxels`, `RoomMouthRiseStrength`, `TunnelEndpointZOffset`, `TunnelFloorGentleSlopeThreshold`, `TunnelFloorLedgeCountPreference`, `TunnelFloorMaxLedgeHeight`, `TunnelFloorMaxLedges`, `TunnelFloorTerraceStepHeight`, `VerticalScale`, `WaterLevelRelative`, `WormFrequency`, `WormHorizontalBias`, `WormNetworkRange`, `WormStrength`, `WormThreshold`, `bTunnelFloorEnabled`, `bTunnelFloorTerracingEnabled`, and `RoughnessNoiseType`.
Clang headless build passed (source stamp `e2cf59ad7ff10c1650c0f1cc5b99dab1c53439d128c9f263eb9848b3882723f5`). No GCC build, baselines, determinism matrix, captures, seams/fast tier, timing, or UE host build were run after the route-support failure. Logs: `Saved/Linux/l134/build-clang-second-design.log` and `Saved/Linux/l134/walk-gate-second-design.log`.
@@ -0,0 +1 @@
{"schema_version":3,"world_identity":"owner-world/seed/0","owner_voxel_size_um":125000,"core_voxel_size_um":250000,"strate_layout_chunk_size_um":8000000,"strate_layout_chunk_width_subvoxels":8192,"strates":[{"stable_id":"owner-world/seed/0/strate/0/generator/SurfaceWorld","order":0,"strate_index":0,"generator_kind":"SurfaceWorld","recipe_id":"SurfaceWorld.Heightfield2D","recipe_version":2,"interior_bottom_um":-504000000,"interior_top_um":8000000,"voxel_size_um":250000,"lattice_origin_um":[0,0,0],"ceiling":{"kind":"ceiling","bottom_um":7500000,"top_um":8000000,"present":true,"diggable":true,"protected":false,"solid_density_q16":524288,"behaviour":"solid ceiling from strate BoundarySealThickness; breakable by default","density_side":"interior-facing lower surface","cap_path":"shared resolved-layout density band"},"content":{"kind":"content","bottom_um":-503500000,"top_um":7500000,"present":true,"diggable":true,"protected":false,"solid_density_q16":65536,"behaviour":"resolved generator interior","density_side":"none","cap_path":"none"},"bedrock":{"kind":"bedrock","bottom_um":-504000000,"top_um":-503500000,"present":true,"diggable":true,"protected":false,"solid_density_q16":524288,"behaviour":"solid bedrock floor from strate BoundarySealThickness; breakable by default","density_side":"interior-facing upper surface","cap_path":"shared resolved-layout density band"},"transition_mode":"Gradient","transition_blend_chunks":1,"transition_width_um":8000000,"transition_warp_amplitude_um":0,"transition_interpenetration_um":2000000,"transition_honoured_in_core":true,"transition_falls_back_to_hard":false,"transition_dispatch_path":"shared composition moves both band faces together with the smooth declared-width transition field","bound_dispatch_path":"BuildSurfaceStratumPlan uses resolved layout chunk width from ResolvedLayoutSnapshotV3","query_dispatch_path":"NativeSurfaceDensityAt selects floor(CoreZ / resolved layout chunk width) from ResolvedLayoutSnapshotV3","manager_lookup":"UVoxelStrateManager::GetGeneratorTypeForChunk; transition is controlled by upper strate","manager_chunk_width_um":8000000,"dispatch_chunk_width_um":8000000,"anchors":[{"kind":"cap_profile","stable_id":"biome/4097","units":"1/256 Core voxel","values":[-18022,0,64000,1536,0]},{"kind":"route_point","stable_id":"route/0","units":"1/256 Core voxel","values":[8192,24576,-381223,1024]},{"kind":"route_point","stable_id":"route/1","units":"1/256 Core voxel","values":[8192,24320,-381254,1024]},{"kind":"route_point","stable_id":"route/2","units":"1/256 Core voxel","values":[8192,24064,-381284,1024]},{"kind":"route_point","stable_id":"route/3","units":"1/256 Core voxel","values":[8192,23808,-381302,1024]},{"kind":"route_point","stable_id":"route/4","units":"1/256 Core voxel","values":[8192,23552,-381304,1024]},{"kind":"route_point","stable_id":"route/5","units":"1/256 Core voxel","values":[8192,23296,-381291,1024]},{"kind":"route_point","stable_id":"route/6","units":"1/256 Core voxel","values":[8192,23040,-381270,1024]},{"kind":"route_point","stable_id":"route/7","units":"1/256 Core voxel","values":[8192,22784,-381242,1024]},{"kind":"route_point","stable_id":"route/8","units":"1/256 Core voxel","values":[8192,22528,-381218,1024]},{"kind":"route_point","stable_id":"route/9","units":"1/256 Core voxel","values":[8192,22272,-381192,1024]},{"kind":"route_point","stable_id":"route/10","units":"1/256 Core voxel","values":[8192,22016,-381161,1024]},{"kind":"route_point","stable_id":"route/11","units":"1/256 Core voxel","values":[8192,21760,-381118,1024]},{"kind":"route_point","stable_id":"route/12","units":"1/256 Core voxel","values":[8192,21504,-381122,1024]},{"kind":"route_point","stable_id":"route/13","units":"1/256 Core voxel","values":[8192,21248,-381155,1024]},{"kind":"route_point","stable_id":"route/14","units":"1/256 Core voxel","values":[8192,20992,-381183,1024]},{"kind":"route_point","stable_id":"route/15","units":"1/256 Core voxel","values":[8192,20736,-381207,1024]},{"kind":"route_point","stable_id":"route/16","units":"1/256 Core voxel","values":[8192,20480,-381219,1024]},{"kind":"route_point","stable_id":"route/17","units":"1/256 Core voxel","values":[8192,20224,-381220,1024]},{"kind":"route_point","stable_id":"route/18","units":"1/256 Core voxel","values":[8192,19968,-381202,1024]},{"kind":"route_point","stable_id":"route/19","units":"1/256 Core voxel","values":[8192,19712,-381167,1024]},{"kind":"route_point","stable_id":"route/20","units":"1/256 Core voxel","values":[8192,19456,-381130,1024]},{"kind":"route_point","stable_id":"route/21","units":"1/256 Core voxel","values":[8192,19200,-381101,1024]},{"kind":"route_point","stable_id":"route/22","units":"1/256 Core voxel","values":[8192,18944,-381072,1024]},{"kind":"route_point","stable_id":"route/23","units":"1/256 Core voxel","values":[8192,18688,-381092,1024]},{"kind":"route_point","stable_id":"route/24","units":"1/256 Core voxel","values":[8192,18432,-381149,1024]},{"kind":"route_point","stable_id"Line truncated
+29 -8
View File
@@ -74,6 +74,7 @@ namespace
bool DensityDispatchReport=false; bool DensityDispatchReport=false;
bool CavesRecipe=false; bool CavesRecipe=false;
bool LatticeRecipe=false; bool LatticeRecipe=false;
bool LegacyTunnelNetwork=false;
int LatticeStrateIndex=-1; int LatticeStrateIndex=-1;
bool AuditHeightfieldProfile=false; bool AuditHeightfieldProfile=false;
bool LandmarkChamberDemo=false; bool LandmarkChamberDemo=false;
@@ -1434,6 +1435,7 @@ namespace
" --core-snapshot=FILE (benchmark the exact resolved Core snapshot from Unreal)\n" " --core-snapshot=FILE (benchmark the exact resolved Core snapshot from Unreal)\n"
" --profile-column-overlap --far-horizon-lod8-step=N --force-generate\n" " --profile-column-overlap --far-horizon-lod8-step=N --force-generate\n"
" --seed=N --recipe=surface-world|floating-islands|caves|lattice\n" " --seed=N --recipe=surface-world|floating-islands|caves|lattice\n"
" --legacy-tunnel-network (keep TN on its compiled-analytic comparison path)\n"
" --native-owner-fi (build the existing owner FI plan; leave other strates intact)\n" " --native-owner-fi (build the existing owner FI plan; leave other strates intact)\n"
" --density-dispatch-report (print bound path per strate and tile hashes)\n" " --density-dispatch-report (print bound path per strate and tile hashes)\n"
" --lattice-strate=N (opt one Maze or VerticalShafts strate into Core.Lattice)\n" " --lattice-strate=N (opt one Maze or VerticalShafts strate into Core.Lattice)\n"
@@ -1497,6 +1499,7 @@ namespace
else if(arg=="--native-owner-fi")options.NativeOwnerFloatingIslandsRecipe=true; else if(arg=="--native-owner-fi")options.NativeOwnerFloatingIslandsRecipe=true;
else if(arg=="--density-dispatch-report") else if(arg=="--density-dispatch-report")
{options.DensityDispatchReport=true;options.StreamHash=true;options.StreamHashTiles=true;} {options.DensityDispatchReport=true;options.StreamHash=true;options.StreamHashTiles=true;}
else if(arg=="--legacy-tunnel-network")options.LegacyTunnelNetwork=true;
else if(arg=="--audit-landmark-reachability")options.AuditLandmarkReachability=true; else if(arg=="--audit-landmark-reachability")options.AuditLandmarkReachability=true;
else if(arg=="--audit-native-caves")options.AuditNativeCaves=true; else if(arg=="--audit-native-caves")options.AuditNativeCaves=true;
else if(arg=="--audit-native-lattice")options.AuditNativeLattice=true; else if(arg=="--audit-native-lattice")options.AuditNativeLattice=true;
@@ -1737,7 +1740,8 @@ int main(int argc,char** argv)
options.DataPath.string().c_str(),options.WorkerCount,options.ClipRadius,options.MaxClipLevel, options.DataPath.string().c_str(),options.WorkerCount,options.ClipRadius,options.MaxClipLevel,
options.MaxLevel,options.StreamHash?"on":"off", options.MaxLevel,options.StreamHash?"on":"off",
options.StreamHashAllowUniformSkips?"on":"off",options.ProfileColumnOverlap?"on":"off", options.StreamHashAllowUniformSkips?"on":"off",options.ProfileColumnOverlap?"on":"off",
options.ForceGenerate?"on":"off",options.CavesRecipe?"caves" options.ForceGenerate?"on":"off",options.LegacyTunnelNetwork?"legacy-tunnel-network"
:options.CavesRecipe?"caves"
:options.LatticeRecipe?"lattice" :options.LatticeRecipe?"lattice"
:options.FloatingIslandsRecipe?"floating-islands":"surface-world", :options.FloatingIslandsRecipe?"floating-islands":"surface-world",
options.LatticeStrateIndex, options.LatticeStrateIndex,
@@ -1858,7 +1862,9 @@ int main(int argc,char** argv)
} }
VoxelForgeCore::ResolvedLayoutSnapshotV3 resolvedLayout; VoxelForgeCore::ResolvedLayoutSnapshotV3 resolvedLayout;
{ {
const std::string variant=options.LatticeRecipe const std::string variant=options.LegacyTunnelNetwork
?"legacy-tunnel-network"
:options.LatticeRecipe
?"lattice/"+std::to_string(options.LatticeStrateIndex) ?"lattice/"+std::to_string(options.LatticeStrateIndex)
:options.CavesRecipe?"caves" :options.CavesRecipe?"caves"
:(options.FloatingIslandsRecipe||options.NativeOwnerFloatingIslandsRecipe) :(options.FloatingIslandsRecipe||options.NativeOwnerFloatingIslandsRecipe)
@@ -1871,6 +1877,11 @@ int main(int argc,char** argv)
*ownerWorld,worldAssembly,variant,resolvedLayout,error); *ownerWorld,worldAssembly,variant,resolvedLayout,error);
if(!layoutResolved) if(!layoutResolved)
{std::fprintf(stderr,"Resolved-layout snapshot failed: %s\n",error.c_str());return 2;} {std::fprintf(stderr,"Resolved-layout snapshot failed: %s\n",error.c_str());return 2;}
if(useExportedCorePlan&&!options.NativeOwnerFloatingIslandsRecipe
&&!options.LegacyTunnelNetwork&&!options.CavesRecipe
&&!StandaloneWorldAssemblyPrototype::PlanOwnerTunnelNetworkCaves(
*ownerWorld,resolvedLayout,error))
{std::fprintf(stderr,"Imported owner Tunnel Network cave planning failed: %s\n",error.c_str());return 2;}
if(useExportedCorePlan&&!options.NativeOwnerFloatingIslandsRecipe if(useExportedCorePlan&&!options.NativeOwnerFloatingIslandsRecipe
&&!StandaloneWorldAssemblyPrototype::ValidateExportedCorePlanMatchesOwner( &&!StandaloneWorldAssemblyPrototype::ValidateExportedCorePlanMatchesOwner(
*ownerWorld,worldAssembly,resolvedLayout,error)) *ownerWorld,worldAssembly,resolvedLayout,error))
@@ -1953,8 +1964,11 @@ int main(int argc,char** argv)
{ {
std::vector<VoxelForgeCore::ResolvedCavernRoomV1> rooms; std::vector<VoxelForgeCore::ResolvedCavernRoomV1> rooms;
std::vector<VoxelForgeCore::ResolvedCavernEdgeV1> edges; std::vector<VoxelForgeCore::ResolvedCavernEdgeV1> edges;
if(!MakeNativeCavesSpine(resolvedLayout,rooms,edges,error) if(!MakeNativeCavesSpine(resolvedLayout,rooms,edges,error))
||!VoxelForgeCore::ResolveResolvedCavernGraphV1( {std::fprintf(stderr,"Native caves spine resolution failed: %s\n",error.c_str());return 2;}
rooms.insert(rooms.end(),resolvedLayout.cavernRooms.begin(),resolvedLayout.cavernRooms.end());
edges.insert(edges.end(),resolvedLayout.cavernEdges.begin(),resolvedLayout.cavernEdges.end());
if(!VoxelForgeCore::ResolveResolvedCavernGraphV1(
resolvedLayout,seed,rooms,edges,error)) resolvedLayout,seed,rooms,edges,error))
{std::fprintf(stderr,"Native caves spine resolution failed: %s\n",error.c_str());return 2;} {std::fprintf(stderr,"Native caves spine resolution failed: %s\n",error.c_str());return 2;}
} }
@@ -1965,8 +1979,11 @@ int main(int argc,char** argv)
std::vector<VoxelForgeCore::ResolvedCavernRoomV1> rooms; std::vector<VoxelForgeCore::ResolvedCavernRoomV1> rooms;
std::vector<VoxelForgeCore::ResolvedCavernEdgeV1> edges; std::vector<VoxelForgeCore::ResolvedCavernEdgeV1> edges;
if(!MakeNativeLatticeSpine(resolvedLayout,seed,options.LatticeStrateIndex, if(!MakeNativeLatticeSpine(resolvedLayout,seed,options.LatticeStrateIndex,
options.LatticeOrphanControl,rooms,edges,error) options.LatticeOrphanControl,rooms,edges,error))
||!VoxelForgeCore::ResolveResolvedCavernGraphV1( {std::fprintf(stderr,"Native lattice graph resolution failed: %s\n",error.c_str());return 2;}
rooms.insert(rooms.end(),resolvedLayout.cavernRooms.begin(),resolvedLayout.cavernRooms.end());
edges.insert(edges.end(),resolvedLayout.cavernEdges.begin(),resolvedLayout.cavernEdges.end());
if(!VoxelForgeCore::ResolveResolvedCavernGraphV1(
resolvedLayout,seed,rooms,edges,error)) resolvedLayout,seed,rooms,edges,error))
{std::fprintf(stderr,"Native lattice graph resolution failed: %s\n",error.c_str());return 2;} {std::fprintf(stderr,"Native lattice graph resolution failed: %s\n",error.c_str());return 2;}
} }
@@ -2087,12 +2104,16 @@ int main(int argc,char** argv)
const bool hasNativeLatticeRecipe=std::any_of(resolvedLayout.strates.begin(),resolvedLayout.strates.end(), const bool hasNativeLatticeRecipe=std::any_of(resolvedLayout.strates.begin(),resolvedLayout.strates.end(),
[](const auto& stratum){return VoxelForgeCore::IsNativeLatticeFieldStratum(stratum) [](const auto& stratum){return VoxelForgeCore::IsNativeLatticeFieldStratum(stratum)
&&stratum.passageCapabilities.hasNativeLatticeField;}); &&stratum.passageCapabilities.hasNativeLatticeField;});
const auto actualRecipe=hasNativeLatticeRecipe?std::string_view("lattice") const auto actualRecipe=requestedRecipe=="lattice"&&hasNativeLatticeRecipe
:hasNativeCavesRecipe?std::string_view("caves") ?std::string_view("lattice")
:requestedRecipe=="caves"&&hasNativeCavesRecipe
?std::string_view("caves")
:requestedRecipe=="surface-world"&&hasSurfaceWorldPlan :requestedRecipe=="surface-world"&&hasSurfaceWorldPlan
?std::string_view("surface-world") ?std::string_view("surface-world")
:requestedRecipe=="floating-islands"&&hasFloatingIslands :requestedRecipe=="floating-islands"&&hasFloatingIslands
?std::string_view("floating-islands") ?std::string_view("floating-islands")
:hasNativeLatticeRecipe?std::string_view("lattice")
:hasNativeCavesRecipe?std::string_view("caves")
:hasFloatingIslands?std::string_view("floating-islands") :hasFloatingIslands?std::string_view("floating-islands")
:hasSurfaceWorldPlan?std::string_view("surface-world") :hasSurfaceWorldPlan?std::string_view("surface-world")
:std::string_view("missing"); :std::string_view("missing");
+248 -9
View File
@@ -1595,6 +1595,205 @@ namespace StandaloneStreamBenchmark
return passed; return passed;
} }
inline bool AuditNativeTunnelNetworkField(const VoxelForgeCore::World& shape,
const VoxelForgeCore::ResolvedLayoutSnapshotV3& layout,
std::uint32_t voxelMicrometres)
{
using namespace VoxelForgeCore;
constexpr double Pi=3.14159265358979323846;
const double vm=std::max<std::uint32_t>(1,voxelMicrometres)/1.0e6;
const auto density=[&](double x,double y,double z)
{return static_cast<double>(shape.densityAt({x/vm,y/vm,z/vm}))*vm;};
const auto cave=std::find_if(layout.strates.begin(),layout.strates.end(),
[](const auto& s){return s.generatorKind=="TunnelNetwork"
&&IsNativeCaveFieldStratum(s)&&s.passageCapabilities.hasNativeCaveField;});
if(cave==layout.strates.end()||layout.cavernGraphVocabularyVersion!=1)
{std::printf("[TunnelNetworkCavernAudit] recipe=%s graph=%u status=FAIL\n",
cave==layout.strates.end()?"missing":"present",
layout.cavernGraphVocabularyVersion);return false;}
const ResolvedLayoutPassageV1* arrival=nullptr;
const ResolvedLayoutPassageV1* departure=nullptr;
for(const auto& passage:layout.passages)
{
if(passage.lowerStrateIndex==cave->strateIndex
&&passage.upperStrateIndex<cave->strateIndex)arrival=&passage;
if(passage.upperStrateIndex==cave->strateIndex
&&passage.lowerStrateIndex>cave->strateIndex)departure=&passage;
}
std::vector<const ResolvedCavernRoomV1*> rooms;
std::vector<const ResolvedCavernEdgeV1*> routeEdges;
for(const auto& room:layout.cavernRooms)
if(room.strateIndex==cave->strateIndex)rooms.push_back(&room);
for(const auto& edge:layout.cavernEdges)
if(edge.strateIndex==cave->strateIndex
&&edge.stableId.find("/route-edge-")!=std::string::npos)
routeEdges.push_back(&edge);
const auto routeIndex=[](const ResolvedCavernEdgeV1* edge)
{
const auto marker=edge->stableId.rfind("/route-edge-");
return marker==std::string::npos?std::uint64_t{0}
:static_cast<std::uint64_t>(std::stoull(edge->stableId.substr(marker+12)));
};
std::sort(routeEdges.begin(),routeEdges.end(),[&](const auto* a,const auto* b)
{return routeIndex(a)<routeIndex(b);});
std::unordered_map<std::string,std::size_t> compoundCounts;
std::unordered_map<std::string,double> compoundRadii;
double largestRoomRadius=0.0,largestOtherRoomRadius=0.0;
std::string largestRoomGroup;
for(const auto* room:rooms)
{
const auto marker=room->stableId.rfind("/primitive-");
const std::string group=marker==std::string::npos
?room->stableId:room->stableId.substr(0,marker);
++compoundCounts[group];
const double radius=std::max(room->halfExtentsMicrometres[0],
room->halfExtentsMicrometres[1])/1.0e6;
compoundRadii[group]=std::max(compoundRadii[group],radius);
if(radius>largestRoomRadius)
{largestRoomRadius=radius;largestRoomGroup=group;}
}
for(const auto& [group,radius]:compoundRadii)
if(group!=largestRoomGroup)
largestOtherRoomRadius=std::max(largestOtherRoomRadius,radius);
const bool compoundRooms=!compoundCounts.empty()
&&std::all_of(compoundCounts.begin(),compoundCounts.end(),
[](const auto& row){return row.second>=2&&row.second<=4;});
const bool destination=largestOtherRoomRadius>0.0
&&largestRoomRadius>largestOtherRoomRadius*1.10;
std::vector<const ResolvedLayoutAnchorV2*> routeAnchors;
for(const auto& anchor:cave->anchors)
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
routeAnchors.push_back(&anchor);
double maximumGrade=0.0;
bool gradeOk=routeAnchors.size()>=2;
for(std::size_t i=1;i<routeAnchors.size();++i)
{
const auto& a=routeAnchors[i-1]->values;
const auto& b=routeAnchors[i]->values;
const double run=std::hypot(static_cast<double>(b[0]-a[0]),
static_cast<double>(b[1]-a[1]));
const double rise=std::abs(static_cast<double>(b[2]-a[2]));
if(run<=0.0){gradeOk=false;continue;}
const double grade=std::atan2(rise,run)*180.0/Pi;
maximumGrade=std::max(maximumGrade,grade);
gradeOk=gradeOk&&grade<=15.0+1.0e-6;
}
bool mouthBinding=arrival&&departure&&routeAnchors.size()>=2;
if(mouthBinding)
{
const auto qOffset=static_cast<std::int64_t>(std::llround(
250000.0*WorldAssemblySubvoxelsPerVoxel/layout.coreVoxelSizeMicrometres));
const auto& start=routeAnchors.front()->values;
const auto& end=routeAnchors.back()->values;
mouthBinding=start[0]==arrival->lowerMouthSubvoxels[0]
&&start[1]==arrival->lowerMouthSubvoxels[1]
&&start[2]==arrival->lowerMouthSubvoxels[2]+qOffset
&&end[0]==departure->upperMouthSubvoxels[0]
&&end[1]==departure->upperMouthSubvoxels[1]
&&end[2]==departure->upperMouthSubvoxels[2]+qOffset;
}
std::uint64_t airSamples=0,floorSamples=0,shellSamples=0;
bool centerAir=true,floorSolid=true,shellSolid=true;
for(const auto* edge:routeEdges)
{
const auto& p=edge->controlPointsMicrometres;
const double x=(p[0][0]+3.0*p[1][0]+3.0*p[2][0]+p[3][0])/8.0e6;
const double y=(p[0][1]+3.0*p[1][1]+3.0*p[2][1]+p[3][1])/8.0e6;
const double floor=(p[0][2]+3.0*p[1][2]+3.0*p[2][2]+p[3][2])/8.0e6;
const double bore=edge->boreRadiusMicrometres/1.0e6;
if(density(x,y,floor+1.0)<=0.0)centerAir=false;
++airSamples;
if(density(x,y,floor-0.10)>=0.0)floorSolid=false;
++floorSamples;
const double dx=(p[3][0]-p[0][0])/1.0e6;
const double dy=(p[3][1]-p[0][1])/1.0e6;
const double length=std::hypot(dx,dy);
if(length<=1.0e-6){shellSolid=false;continue;}
const double nx=-dy/length,ny=dx/length;
if(density(x+nx*(bore+0.90),y+ny*(bore+0.90),floor+2.0)>=0.0
||density(x-nx*(bore+0.90),y-ny*(bore+0.90),floor+2.0)>=0.0)
shellSolid=false;
shellSamples+=2;
}
const ResolvedCavernEdgeV1* rippleEdge=nullptr;
double longest=0.0;
for(const auto* edge:routeEdges)
{
const auto& p=edge->controlPointsMicrometres;
const double length=std::hypot(static_cast<double>(p[3][0]-p[0][0]),
static_cast<double>(p[3][1]-p[0][1]));
if(length>longest){longest=length;rippleEdge=edge;}
}
std::vector<double> rippleRadii;
if(rippleEdge)
{
const auto& p=rippleEdge->controlPointsMicrometres;
const double radius=rippleEdge->boreRadiusMicrometres/1.0e6;
constexpr std::array<double,7> stations{{0.18,0.29,0.40,0.51,0.62,0.73,0.84}};
for(double t:stations)
{
const auto bezier=[&](double u,std::size_t axis)
{
const double one=1.0-u;
return (one*one*one*p[0][axis]+3.0*one*one*u*p[1][axis]
+3.0*one*u*u*p[2][axis]+u*u*u*p[3][axis])/1.0e6;
};
const double x=bezier(t,0),y=bezier(t,1),floor=bezier(t,2);
const double tx=bezier(std::min(1.0,t+0.01),0)
-bezier(std::max(0.0,t-0.01),0);
const double ty=bezier(std::min(1.0,t+0.01),1)
-bezier(std::max(0.0,t-0.01),1);
const double tangent=std::hypot(tx,ty);
if(tangent<=1.0e-6)continue;
for(const double side:{-1.0,1.0})
{
double prior=0.20;
double priorDensity=density(x,y,floor+2.0);
bool found=false;
for(double radial=0.25;radial<=radius+1.8;radial+=0.05)
{
const double sampleX=x-side*ty/tangent*radial;
const double sampleY=y+side*tx/tangent*radial;
const double value=density(sampleX,sampleY,floor+2.0);
if(priorDensity>=0.0&&value<0.0)
{
const double fraction=priorDensity/(priorDensity-value);
rippleRadii.push_back(prior+(radial-prior)*fraction);
found=true;break;
}
prior=radial;priorDensity=value;
}
if(!found)continue;
}
}
}
double ripplePeakToPeak=0.0;
if(!rippleRadii.empty())
{
const auto limits=std::minmax_element(rippleRadii.begin(),rippleRadii.end());
ripplePeakToPeak=*limits.second-*limits.first;
}
const bool ripple=rippleRadii.size()>=8&&ripplePeakToPeak>=0.02
&&ripplePeakToPeak<=1.25;
std::printf("[TunnelNetworkCavernAudit] strate=%d recipe=%s rooms=%zu compound_groups=%zu destination_radius_m=%.3f route_edges=%zu route_points=%zu mouth_binding=%s max_grade_deg=%.3f grade_ok=%s center_air=%llu/%llu floor_solid=%llu/%llu shell_solid=%llu/%llu ripple_samples=%zu ripple_peak_to_peak_m=%.4f status=%s\n",
cave->strateIndex,cave->recipeId.c_str(),rooms.size(),compoundCounts.size(),
largestRoomRadius,routeEdges.size(),routeAnchors.size(),mouthBinding?"PASS":"FAIL",
maximumGrade,gradeOk?"PASS":"FAIL",
static_cast<unsigned long long>(airSamples),static_cast<unsigned long long>(airSamples),
static_cast<unsigned long long>(floorSamples),static_cast<unsigned long long>(floorSamples),
static_cast<unsigned long long>(shellSamples),static_cast<unsigned long long>(shellSamples),
rippleRadii.size(),ripplePeakToPeak,
arrival&&departure&&routeEdges.size()>=2&&!rooms.empty()&&compoundRooms
&&destination&&mouthBinding&&gradeOk&&centerAir&&floorSolid&&shellSolid
&&ripple?"PASS":"FAIL");
return arrival&&departure&&routeEdges.size()>=2&&!rooms.empty()&&compoundRooms
&&destination&&mouthBinding&&gradeOk&&centerAir&&floorSolid&&shellSolid&&ripple;
}
inline bool AuditNativeCavesField(const VoxelForgeCore::World& shape, inline bool AuditNativeCavesField(const VoxelForgeCore::World& shape,
const VoxelForgeCore::ResolvedLayoutSnapshotV3& layout,std::uint32_t voxelMicrometres) const VoxelForgeCore::ResolvedLayoutSnapshotV3& layout,std::uint32_t voxelMicrometres)
{ {
@@ -1605,9 +1804,13 @@ namespace StandaloneStreamBenchmark
const auto density=[&](double x,double y,double z) const auto density=[&](double x,double y,double z)
{return static_cast<double>(shape.densityAt({x/vm,y/vm,z/vm}))*vm;}; {return static_cast<double>(shape.densityAt({x/vm,y/vm,z/vm}))*vm;};
const auto cave=std::find_if(layout.strates.begin(),layout.strates.end(), const auto cave=std::find_if(layout.strates.begin(),layout.strates.end(),
[](const auto& s){return s.generatorKind=="Caves" [](const auto& s){return IsNativeCaveFieldStratum(s)
&&s.recipeId=="Core.Caves.PrimitiveField"&&s.recipeVersion==1
&&s.passageCapabilities.hasNativeCaveField;}); &&s.passageCapabilities.hasNativeCaveField;});
if(cave!=layout.strates.end()&&cave->generatorKind=="TunnelNetwork")
return AuditNativeTunnelNetworkField(shape,layout,voxelMicrometres);
if(cave==layout.strates.end()||cave->generatorKind!="Caves")
{std::printf("[CavesAudit] recipe=missing rooms=%zu edges=%zu status=FAIL\n",
layout.cavernRooms.size(),layout.cavernEdges.size());return false;}
if(cave==layout.strates.end()||layout.cavernRooms.size()!=1||layout.cavernEdges.size()!=2) if(cave==layout.strates.end()||layout.cavernRooms.size()!=1||layout.cavernEdges.size()!=2)
{std::printf("[CavesAudit] recipe=%s rooms=%zu edges=%zu status=FAIL\n", {std::printf("[CavesAudit] recipe=%s rooms=%zu edges=%zu status=FAIL\n",
cave==layout.strates.end()?"missing":"present",layout.cavernRooms.size(),layout.cavernEdges.size());return false;} cave==layout.strates.end()?"missing":"present",layout.cavernRooms.size(),layout.cavernEdges.size());return false;}
@@ -4796,6 +4999,7 @@ namespace StandaloneStreamBenchmark
} }
} }
std::uint64_t routePoints=0,routePasses=0,routeWalkPasses=0,routeBindings=0,routeBindingFailures=0; std::uint64_t routePoints=0,routePasses=0,routeWalkPasses=0,routeBindings=0,routeBindingFailures=0;
std::uint64_t tunnelNetworkRoutes=0,tunnelNetworkBlockedControls=0;
for(const auto& stratum:ResolvedLayout->strates) for(const auto& stratum:ResolvedLayout->strates)
{ {
std::vector<const VoxelForgeCore::ResolvedLayoutAnchorV2*> anchors; std::vector<const VoxelForgeCore::ResolvedLayoutAnchorV2*> anchors;
@@ -4859,11 +5063,26 @@ namespace StandaloneStreamBenchmark
supportStepMetres,supportMaximumMetres,minimumSupportProbes, supportStepMetres,supportMaximumMetres,minimumSupportProbes,
supportProbeRadiusMetres); supportProbeRadiusMetres);
bool binding=true; bool binding=true;
for(const auto& passage:ResolvedLayout->passages)
{
const auto equalPosition=[](const auto& a,const auto& b) const auto equalPosition=[](const auto& a,const auto& b)
{return a[0]==b[0]&&a[1]==b[1]&&a[2]==b[2];}; {return a[0]==b[0]&&a[1]==b[1]&&a[2]==b[2];};
const auto boundMouthMatches=[&](const auto& authored,const auto& mouth)
{
if(stratum.generatorKind!="TunnelNetwork")
return equalPosition(authored,mouth);
auto expected=mouth;
expected[2]+=static_cast<std::int64_t>(std::llround(
250000.0*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel
/World.CoreVoxelSizeMicrons));
return equalPosition(authored,expected);
};
for(const auto& passage:ResolvedLayout->passages)
{
if(passage.upperStrateIndex==stratum.strateIndex) if(passage.upperStrateIndex==stratum.strateIndex)
{
if(stratum.generatorKind=="TunnelNetwork")
binding&=boundMouthMatches(anchors.back()->values,
passage.upperMouthSubvoxels);
else
{ {
const auto rootedMouth=std::find_if(stratum.anchors.begin(), const auto rootedMouth=std::find_if(stratum.anchors.begin(),
stratum.anchors.end(),[](const auto& anchor) stratum.anchors.end(),[](const auto& anchor)
@@ -4874,10 +5093,15 @@ namespace StandaloneStreamBenchmark
rootedMouth->values[8],rootedMouth->values[9]}} rootedMouth->values[8],rootedMouth->values[9]}}
:std::array<std::int64_t,3>{{anchors.back()->values[0], :std::array<std::int64_t,3>{{anchors.back()->values[0],
anchors.back()->values[1],anchors.back()->values[2]}}; anchors.back()->values[1],anchors.back()->values[2]}};
binding&=equalPosition(passage.upperMouthSubvoxels, binding&=equalPosition(passage.upperMouthSubvoxels,departure);
departure); }
} }
if(passage.lowerStrateIndex==stratum.strateIndex) if(passage.lowerStrateIndex==stratum.strateIndex)
{
if(stratum.generatorKind=="TunnelNetwork")
binding&=boundMouthMatches(anchors.front()->values,
passage.lowerMouthSubvoxels);
else
{ {
const auto passageArrival=std::find_if(stratum.anchors.begin(), const auto passageArrival=std::find_if(stratum.anchors.begin(),
stratum.anchors.end(),[](const auto& anchor) stratum.anchors.end(),[](const auto& anchor)
@@ -4887,8 +5111,8 @@ namespace StandaloneStreamBenchmark
passageArrival->values[1],passageArrival->values[2]}} passageArrival->values[1],passageArrival->values[2]}}
:std::array<std::int64_t,3>{{anchors.front()->values[0], :std::array<std::int64_t,3>{{anchors.front()->values[0],
anchors.front()->values[1],anchors.front()->values[2]}}; anchors.front()->values[1],anchors.front()->values[2]}};
binding&=equalPosition(passage.lowerMouthSubvoxels, binding&=equalPosition(passage.lowerMouthSubvoxels,arrival);
arrival); }
} }
} }
++routePasses;routeWalkPasses+=routeAudit.passed; ++routePasses;routeWalkPasses+=routeAudit.passed;
@@ -4918,6 +5142,18 @@ namespace StandaloneStreamBenchmark
stratum.strateIndex,routeAudit.firstUnsupported[i][0], stratum.strateIndex,routeAudit.firstUnsupported[i][0],
routeAudit.firstUnsupported[i][1],routeAudit.firstUnsupported[i][2], routeAudit.firstUnsupported[i][1],routeAudit.firstUnsupported[i][2],
routeAudit.firstUnsupported[i][3]); routeAudit.firstUnsupported[i][3]);
if(stratum.generatorKind=="TunnelNetwork")
{
++tunnelNetworkRoutes;
const FWalkAudit control=auditPlayerPath(blockedWorld,route,
supportStartMetres,supportStepMetres,supportMaximumMetres,
minimumSupportProbes,supportProbeRadiusMetres);
const bool detected=control.blocked>0&&!control.passed;
tunnelNetworkBlockedControls+=detected;
std::printf("[TunnelNetworkBlockedRouteControl] strate=%d arrival_to_departure=true blocked_capsule_samples=%llu result=%s expected=FAIL status=%s\n",
stratum.strateIndex,static_cast<unsigned long long>(control.blocked),
control.passed?"PASS":"FAIL",detected?"PASS":"FAIL");
}
} }
for(std::size_t passageIndex=0; for(std::size_t passageIndex=0;
passageIndex<ResolvedLayout->passages.size();++passageIndex) passageIndex<ResolvedLayout->passages.size();++passageIndex)
@@ -4984,13 +5220,16 @@ namespace StandaloneStreamBenchmark
&&shellAuditPassed &&shellAuditPassed
&&routePasses>0&&routePasses==routeWalkPasses&&routePasses==routeBindings &&routePasses>0&&routePasses==routeWalkPasses&&routePasses==routeBindings
&&routeBindingFailures==0 &&routeBindingFailures==0
&&tunnelNetworkRoutes==tunnelNetworkBlockedControls
&&blockedCompiled&&blockedControls==passageCount; &&blockedCompiled&&blockedControls==passageCount;
std::printf("[ResolvedPassageAuditSummary] interfaces=%llu passed=%llu natural=%llu/%llu strate_routes=%llu route_walkable=%llu route_points=%llu route_bindings=%llu route_binding_failures=%llu blocked_controls=%llu/%llu status=%s\n", std::printf("[ResolvedPassageAuditSummary] interfaces=%llu passed=%llu natural=%llu/%llu strate_routes=%llu route_walkable=%llu route_points=%llu route_bindings=%llu route_binding_failures=%llu tunnel_network_block_controls=%llu/%llu blocked_controls=%llu/%llu status=%s\n",
static_cast<unsigned long long>(passageCount),static_cast<unsigned long long>(passagePasses), static_cast<unsigned long long>(passageCount),static_cast<unsigned long long>(passagePasses),
static_cast<unsigned long long>(naturalPasses),static_cast<unsigned long long>(passageCount), static_cast<unsigned long long>(naturalPasses),static_cast<unsigned long long>(passageCount),
static_cast<unsigned long long>(routePasses),static_cast<unsigned long long>(routeWalkPasses), static_cast<unsigned long long>(routePasses),static_cast<unsigned long long>(routeWalkPasses),
static_cast<unsigned long long>(routePoints), static_cast<unsigned long long>(routePoints),
static_cast<unsigned long long>(routeBindings),static_cast<unsigned long long>(routeBindingFailures), static_cast<unsigned long long>(routeBindings),static_cast<unsigned long long>(routeBindingFailures),
static_cast<unsigned long long>(tunnelNetworkBlockedControls),
static_cast<unsigned long long>(tunnelNetworkRoutes),
static_cast<unsigned long long>(blockedControls),static_cast<unsigned long long>(passageCount), static_cast<unsigned long long>(blockedControls),static_cast<unsigned long long>(passageCount),
auditPassed?"PASS":"FAIL"); auditPassed?"PASS":"FAIL");
if(!auditPassed) if(!auditPassed)
+167 -2
View File
@@ -1069,6 +1069,166 @@ namespace StandaloneWorldAssemblyPrototype
return "Unknown"; return "Unknown";
} }
inline bool PlanOwnerTunnelNetworkCaves(const FStandaloneOwnerWorld& owner,
VoxelForgeCore::ResolvedLayoutSnapshotV3& layout,std::string& error)
{
using namespace VoxelForgeCore;
if(!owner.Manager)
{error="Tunnel Network cave planner requires the initialized owner director";return false;}
std::vector<const FStrateSlot*> tunnelSlots;
for(const FStrateSlot& slot:owner.Manager->GetLayout())
{
if(!slot.Definition
||owner.Manager->GetGeneratorTypeForChunk(FIntVector(0,0,slot.TopChunkZ))
!=ECaveGeneratorType::TunnelNetwork)continue;
auto stratum=std::find_if(layout.strates.begin(),layout.strates.end(),
[&](const auto& item){return item.strateIndex==slot.StrateIndex;});
if(stratum==layout.strates.end())
{error="Tunnel Network strate is missing from the resolved layout";return false;}
stratum->generatorKind="TunnelNetwork";
stratum->recipeId="Core.Caves.PrimitiveField";
stratum->recipeVersion=1;
stratum->stableId=layout.worldIdentity+"/strate/"
+std::to_string(slot.StrateIndex)+"/generator/TunnelNetwork";
const bool alreadyPlanned=std::any_of(layout.cavernRooms.begin(),
layout.cavernRooms.end(),[&](const auto& room)
{return room.strateIndex==slot.StrateIndex;})
&&std::any_of(layout.cavernEdges.begin(),layout.cavernEdges.end(),
[&](const auto& edge){return edge.strateIndex==slot.StrateIndex;});
if(!alreadyPlanned)tunnelSlots.push_back(&slot);
}
if(!layout.finalize(error))return false;
const auto toMicrometres=[&](double ownerVoxels)
{
return static_cast<std::int64_t>(std::llround(ownerVoxels
*static_cast<double>(owner.OwnerVoxelSizeMicrons)));
};
const auto toQ16=[](float value)
{
return static_cast<std::int32_t>(std::llround(std::clamp(
static_cast<double>(value),0.0,1.0)*65536.0));
};
if(!tunnelSlots.empty())
{
std::vector<ResolvedCavernRoomV1> plannedRooms=layout.cavernRooms;
std::vector<ResolvedCavernEdgeV1> plannedEdges=layout.cavernEdges;
for(const FStrateSlot* slot:tunnelSlots)
{
const FStrateGenerationParams& params=slot->Definition->GenerationParams;
ResolvedCavernPlannerSettingsV1 settings;
settings.roomSpacingMicrometres=std::max<std::int64_t>(4000000,
toMicrometres(params.RoomSpacing));
settings.minimumRoomRadiusMicrometres=std::max<std::int64_t>(500000,
toMicrometres(params.MinRoomRadius));
settings.maximumRoomRadiusMicrometres=std::max(
settings.minimumRoomRadiusMicrometres,toMicrometres(params.MaxRoomRadius));
settings.destinationRadiusMicrometres=std::max(
toMicrometres(params.OriginRoomRadius),
settings.maximumRoomRadiusMicrometres*135/100);
settings.minimumTunnelRadiusMicrometres=std::max<std::int64_t>(0,
toMicrometres(params.TunnelMinRadius));
settings.maximumTunnelRadiusMicrometres=std::max(
settings.minimumTunnelRadiusMicrometres,toMicrometres(params.TunnelMaxRadius));
settings.maximumTunnelLengthMicrometres=std::max<std::int64_t>(4000000,
toMicrometres(params.MaxTunnelLength));
settings.tunnelWarpMicrometres=std::max<std::int64_t>(0,
toMicrometres(params.TunnelWarpStrength));
settings.caveWarpMicrometres=std::max<std::int64_t>(0,
toMicrometres(params.CaveWarpStrength));
settings.smoothWidthMicrometres=std::max<std::int64_t>(1500000,
toMicrometres(params.SDFBlendRadius));
settings.irregularityAmplitudeMicrometres=std::max<std::int64_t>(0,
toMicrometres(params.SurfaceRoughness));
settings.irregularityWavelengthMicrometres=params.RoughnessFrequency>0.0f
?static_cast<std::int64_t>(std::llround(owner.OwnerVoxelSizeMicrons
/static_cast<double>(params.RoughnessFrequency))):0;
settings.roomDensityQ16=toQ16(params.RoomDensity);
settings.tunnelDensityQ16=toQ16(params.TunnelDensity);
settings.roomShapeVarietyQ16=toQ16(params.RoomShapeVariety);
settings.roomHeightRatioQ16=toQ16(params.RoomHeightRatio);
settings.roomFloorCutMinimumQ16=toQ16(params.RoomFloorCutMin);
settings.roomFloorCutMaximumQ16=toQ16(params.RoomFloorCutMax);
settings.tunnelHorizontalBiasQ16=toQ16(params.TunnelHorizontalBias);
settings.originRoomMaximumConnections=params.OriginRoomMaxConnections;
settings.tunnelsFlowTowardOrigin=params.bTunnelsFlowTowardOrigin;
std::vector<ResolvedCavernRoomV1> rooms;
std::vector<ResolvedCavernEdgeV1> edges;
if(!PlanResolvedCavernNetworkV1(layout,owner.Seed,slot->StrateIndex,
settings,rooms,edges,error))return false;
plannedRooms.insert(plannedRooms.end(),
std::make_move_iterator(rooms.begin()),std::make_move_iterator(rooms.end()));
plannedEdges.insert(plannedEdges.end(),
std::make_move_iterator(edges.begin()),std::make_move_iterator(edges.end()));
}
if(!ResolveResolvedCavernGraphV1(layout,owner.Seed,
plannedRooms,plannedEdges,error))return false;
}
const auto toCoreSubvoxels=[&](std::int64_t micrometres)
{
return static_cast<std::int64_t>(std::llround(
static_cast<double>(micrometres)*WorldAssemblySubvoxelsPerVoxel
/layout.coreVoxelSizeMicrometres));
};
for(const FStrateSlot& slot:owner.Manager->GetLayout())
{
if(!slot.Definition
||owner.Manager->GetGeneratorTypeForChunk(FIntVector(0,0,slot.TopChunkZ))
!=ECaveGeneratorType::TunnelNetwork)continue;
auto stratum=std::find_if(layout.strates.begin(),layout.strates.end(),
[&](const auto& item){return item.strateIndex==slot.StrateIndex;});
if(stratum==layout.strates.end())continue;
const bool hasRoutePoints=std::count_if(stratum->anchors.begin(),
stratum->anchors.end(),[](const auto& anchor)
{return anchor.kind=="route_point"&&anchor.values.size()>=3;})>=2;
if(hasRoutePoints)continue;
const ResolvedLayoutPassageV1* arrival=nullptr;
const ResolvedLayoutPassageV1* departure=nullptr;
for(const auto& passage:layout.passages)
{
if(passage.lowerStrateIndex==slot.StrateIndex
&&passage.upperStrateIndex<slot.StrateIndex)arrival=&passage;
if(passage.upperStrateIndex==slot.StrateIndex
&&passage.lowerStrateIndex>slot.StrateIndex)departure=&passage;
}
if(!arrival||!departure)
{error="Tunnel Network route anchors cannot bind both passage mouths";return false;}
const auto appendRoutePoint=[&](std::string id,
const std::array<std::int64_t,3>& point)
{
ResolvedLayoutAnchorV2 anchor;anchor.kind="route_point";
anchor.stableId="tunnel-network/route/"+std::move(id);
anchor.units="1/256 Core voxel";
anchor.values={point[0],point[1],point[2]};
stratum->anchors.push_back(std::move(anchor));
};
constexpr std::int64_t PlayerCenterAboveMouthFloorMicrometres=250000;
auto start=arrival->lowerMouthSubvoxels;
start[2]+=toCoreSubvoxels(PlayerCenterAboveMouthFloorMicrometres);
appendRoutePoint("000-arrival",start);
for(std::size_t edgeIndex=0;edgeIndex<256;++edgeIndex)
{
const std::string stableId="tunnel-network/strate-"
+std::to_string(slot.StrateIndex)+"/route-edge-"
+std::to_string(edgeIndex);
const auto edge=std::find_if(layout.cavernEdges.begin(),
layout.cavernEdges.end(),[&](const auto& value)
{return value.strateIndex==slot.StrateIndex
&&value.stableId==stableId;});
if(edge==layout.cavernEdges.end())break;
auto point=edge->controlPointsMicrometres.back();
point[2]+=1000000;
appendRoutePoint(std::to_string(edgeIndex+1),
{toCoreSubvoxels(point[0]),toCoreSubvoxels(point[1]),
toCoreSubvoxels(point[2])});
}
auto end=departure->upperMouthSubvoxels;
end[2]+=toCoreSubvoxels(PlayerCenterAboveMouthFloorMicrometres);
appendRoutePoint("999-departure",end);
}
return layout.finalize(error);
}
inline bool ImportExportedCoreWorldAssembly(const VFJson::Value& root, inline bool ImportExportedCoreWorldAssembly(const VFJson::Value& root,
VoxelForgeCore::WorldAssemblyManifest& out,std::string& error) VoxelForgeCore::WorldAssemblyManifest& out,std::string& error)
{ {
@@ -1184,8 +1344,11 @@ namespace StandaloneWorldAssemblyPrototype
if(!slot.Definition)continue; if(!slot.Definition)continue;
const auto type=owner.Manager->GetGeneratorTypeForChunk(FIntVector(0,0,slot.TopChunkZ)); const auto type=owner.Manager->GetGeneratorTypeForChunk(FIntVector(0,0,slot.TopChunkZ));
const auto authored=sealAndDensity(*slot.Definition,type); const auto authored=sealAndDensity(*slot.Definition,type);
const bool nativeCaves=recipeVariant=="caves" const bool nativeSurfaceCaves=recipeVariant=="caves"
&&type==ECaveGeneratorType::SurfaceWorld; &&type==ECaveGeneratorType::SurfaceWorld;
const bool nativeTunnelCaves=type==ECaveGeneratorType::TunnelNetwork
&&recipeVariant!="legacy-tunnel-network"&&recipeVariant!="caves";
const bool nativeCaves=nativeSurfaceCaves||nativeTunnelCaves;
const bool nativeLattice=recipeVariant==("lattice/"+std::to_string(slot.StrateIndex)) const bool nativeLattice=recipeVariant==("lattice/"+std::to_string(slot.StrateIndex))
&&(type==ECaveGeneratorType::Maze||type==ECaveGeneratorType::VerticalShafts); &&(type==ECaveGeneratorType::Maze||type==ECaveGeneratorType::VerticalShafts);
const std::int64_t bottom=static_cast<std::int64_t>(slot.BottomChunkZ)*owner.StrateLayoutChunkSizeMicrometres; const std::int64_t bottom=static_cast<std::int64_t>(slot.BottomChunkZ)*owner.StrateLayoutChunkSizeMicrometres;
@@ -1195,7 +1358,7 @@ namespace StandaloneWorldAssemblyPrototype
if(seal<=0||2*seal>=top-bottom) if(seal<=0||2*seal>=top-bottom)
{error="strate "+std::to_string(slot.StrateIndex)+" has an invalid BoundarySealThickness for two boundary bands";return false;} {error="strate "+std::to_string(slot.StrateIndex)+" has an invalid BoundarySealThickness for two boundary bands";return false;}
ResolvedLayoutStratumV2 s;s.strateIndex=slot.StrateIndex; ResolvedLayoutStratumV2 s;s.strateIndex=slot.StrateIndex;
s.generatorKind=nativeCaves?"Caves":GeneratorKind(type); s.generatorKind=nativeSurfaceCaves?"Caves":GeneratorKind(type);
s.stableId=out.worldIdentity+"/strate/"+std::to_string(slot.StrateIndex) s.stableId=out.worldIdentity+"/strate/"+std::to_string(slot.StrateIndex)
+"/generator/"+s.generatorKind; +"/generator/"+s.generatorKind;
s.recipeId=nativeLattice?"Core.Lattice.PrimitiveField" s.recipeId=nativeLattice?"Core.Lattice.PrimitiveField"
@@ -1407,6 +1570,8 @@ namespace StandaloneWorldAssemblyPrototype
passageConfigs.push_back(config); passageConfigs.push_back(config);
} }
if(!PlanResolvedLayoutPassagesV1(out,owner.Seed,passageConfigs,error))return false; if(!PlanResolvedLayoutPassagesV1(out,owner.Seed,passageConfigs,error))return false;
if(recipeVariant!="legacy-tunnel-network"&&recipeVariant!="caves")
return PlanOwnerTunnelNetworkCaves(owner,out,error);
return out.finalize(error); return out.finalize(error);
} }
+15 -1
View File
@@ -538,6 +538,19 @@ def run_density_dispatch_report(runner: VerificationRunner, binary: Path) -> Non
voxel_size_um = int(strata[0]["voxel_size_um"]) voxel_size_um = int(strata[0]["voxel_size_um"])
if voxel_size_um <= 0: if voxel_size_um <= 0:
raise ValueError(f"invalid voxel_size_um={voxel_size_um}") raise ValueError(f"invalid voxel_size_um={voxel_size_um}")
tunnel_network = strata[2]
if (tunnel_network.get("generator_kind") != "TunnelNetwork"
or tunnel_network.get("recipe_id") != "Core.Caves.PrimitiveField"
or int(tunnel_network.get("recipe_version", 0)) != 1):
failures.append(f"{name}: Tunnel Network did not bind Core cave recipe v1: "
f"generator={tunnel_network.get('generator_kind')} "
f"recipe={tunnel_network.get('recipe_id')} "
f"version={tunnel_network.get('recipe_version')}")
if dispatch.get(2) != ("Caves-native", "TunnelNetwork",
"Core.Caves.PrimitiveField", 1,
"FCavesFieldAdapter"):
failures.append(f"{name}: Tunnel Network dispatch is not native caves: "
f"{dispatch.get(2)}")
except (OSError, json.JSONDecodeError, KeyError, TypeError, ValueError) as error: except (OSError, json.JSONDecodeError, KeyError, TypeError, ValueError) as error:
failures.append(f"{name}: cannot classify tiles from resolved layout: {error}") failures.append(f"{name}: cannot classify tiles from resolved layout: {error}")
continue continue
@@ -604,7 +617,8 @@ def run_density_dispatch_report(runner: VerificationRunner, binary: Path) -> Non
failures.append("no tile centers were attributed to any bound path") failures.append("no tile centers were attributed to any bound path")
runner.require("owner-density-dispatch-report", not failures, runner.require("owner-density-dispatch-report", not failures,
"; ".join(failures) if failures else "; ".join(failures) if failures else
"four owner poses reported per-strate bound paths and tile-center path shares") "four owner poses reported per-strate bound paths and tile-center path shares; "
"TN binds Core.Caves.PrimitiveField v1 through FCavesFieldAdapter")
def run_landmark_reachability(runner: VerificationRunner, def run_landmark_reachability(runner: VerificationRunner,