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:
1 parent
0065ee68e6
commit
75c6ad251d
9 files changed
+1008
-47
No files matched your search
@@ -496,8 +496,9 @@ namespace VoxelForgeCore
|
||||
// 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
|
||||
// voxels for every broadphase and proof bound. Version 227 uses the exact smooth-max
|
||||
// support width/6 when deriving that reach.
|
||||
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 227;
|
||||
// support width/6 when deriving that reach. Version 228 composes deterministic cave
|
||||
// graphs from fixed passage mouths and the owner Tunnel Network settings.
|
||||
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 228;
|
||||
|
||||
enum class FieldChannel : std::uint32_t
|
||||
{
|
||||
@@ -991,6 +992,34 @@ namespace VoxelForgeCore
|
||||
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
|
||||
// micrometres; bounds include irregularity and the blend envelope.
|
||||
struct ResolvedLayoutOreVeinRequestV1
|
||||
@@ -1095,7 +1124,7 @@ namespace VoxelForgeCore
|
||||
|
||||
inline bool IsNativeCaveFieldStratum(const ResolvedLayoutStratumV2& stratum) noexcept
|
||||
{
|
||||
return stratum.generatorKind=="Caves"
|
||||
return (stratum.generatorKind=="Caves"||stratum.generatorKind=="TunnelNetwork")
|
||||
&&stratum.recipeId=="Core.Caves.PrimitiveField"
|
||||
&&stratum.recipeVersion==1;
|
||||
}
|
||||
@@ -1256,6 +1285,17 @@ namespace VoxelForgeCore
|
||||
std::span<const ResolvedCavernRoomV1> rooms,
|
||||
std::span<const ResolvedCavernEdgeV1> edges,
|
||||
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(
|
||||
ResolvedLayoutSnapshotV3& layout, std::int32_t seed,
|
||||
std::span<const ResolvedLayoutOreVeinRequestV1> requests,
|
||||
|
||||
@@ -2031,6 +2031,458 @@ bool ResolveResolvedCavernGraphV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
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,
|
||||
std::span<const ResolvedLayoutOreVeinRequestV1> requests,std::string& error)
|
||||
{
|
||||
|
||||
@@ -1221,16 +1221,15 @@ public:
|
||||
double unionDistance=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 auto* queryStratum=FindBoundStratumForManagerZ(Z);
|
||||
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();
|
||||
++passageIndex)
|
||||
{
|
||||
const auto& passage=ResolvedLayout->passages[passageIndex];
|
||||
if(passage.controlPoints.size()<2)continue;
|
||||
if(queryStratum&&queryStratum->Resolved
|
||||
&&queryStratum->Resolved->passageCapabilities.hasNativeCaveField
|
||||
&&passage.lowerStrateIndex==queryStratum->StrateIndex)
|
||||
continue;
|
||||
// Native graph fields own their local rooms and tunnels, not the
|
||||
// inter-strate connection above their mouth. Union the shared passage
|
||||
// field here as well so the connector remains open through the boundary
|
||||
// band for both native caves and future native lattice layers.
|
||||
if(passageProfile)++passageProfile->passageIterations;
|
||||
if(passageTileScopeMatches&&!passageTileQueries->hasRouteFeature(passageIndex))
|
||||
continue;
|
||||
@@ -7488,7 +7487,16 @@ private:
|
||||
const double delta=(point[axis]-feature.centerMetres[axis])/radius;
|
||||
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 width=feature.room->smoothWidthMicrometres/1000000.0;
|
||||
featureVoid=SmoothMaxMetres(ellipsoid,floor,width);
|
||||
@@ -7526,7 +7534,17 @@ private:
|
||||
}
|
||||
const double normalized=std::sqrt(radialSquared/(radius*radius)
|
||||
+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 stableTie=std::abs(distance-tube)
|
||||
<=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
|
||||
@@ -74,6 +74,7 @@ namespace
|
||||
bool DensityDispatchReport=false;
|
||||
bool CavesRecipe=false;
|
||||
bool LatticeRecipe=false;
|
||||
bool LegacyTunnelNetwork=false;
|
||||
int LatticeStrateIndex=-1;
|
||||
bool AuditHeightfieldProfile=false;
|
||||
bool LandmarkChamberDemo=false;
|
||||
@@ -1434,6 +1435,7 @@ namespace
|
||||
" --core-snapshot=FILE (benchmark the exact resolved Core snapshot from Unreal)\n"
|
||||
" --profile-column-overlap --far-horizon-lod8-step=N --force-generate\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"
|
||||
" --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"
|
||||
@@ -1497,6 +1499,7 @@ namespace
|
||||
else if(arg=="--native-owner-fi")options.NativeOwnerFloatingIslandsRecipe=true;
|
||||
else if(arg=="--density-dispatch-report")
|
||||
{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-native-caves")options.AuditNativeCaves=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.MaxLevel,options.StreamHash?"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.FloatingIslandsRecipe?"floating-islands":"surface-world",
|
||||
options.LatticeStrateIndex,
|
||||
@@ -1858,7 +1862,9 @@ int main(int argc,char** argv)
|
||||
}
|
||||
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)
|
||||
:options.CavesRecipe?"caves"
|
||||
:(options.FloatingIslandsRecipe||options.NativeOwnerFloatingIslandsRecipe)
|
||||
@@ -1871,6 +1877,11 @@ int main(int argc,char** argv)
|
||||
*ownerWorld,worldAssembly,variant,resolvedLayout,error);
|
||||
if(!layoutResolved)
|
||||
{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
|
||||
&&!StandaloneWorldAssemblyPrototype::ValidateExportedCorePlanMatchesOwner(
|
||||
*ownerWorld,worldAssembly,resolvedLayout,error))
|
||||
@@ -1953,8 +1964,11 @@ int main(int argc,char** argv)
|
||||
{
|
||||
std::vector<VoxelForgeCore::ResolvedCavernRoomV1> rooms;
|
||||
std::vector<VoxelForgeCore::ResolvedCavernEdgeV1> edges;
|
||||
if(!MakeNativeCavesSpine(resolvedLayout,rooms,edges,error)
|
||||
||!VoxelForgeCore::ResolveResolvedCavernGraphV1(
|
||||
if(!MakeNativeCavesSpine(resolvedLayout,rooms,edges,error))
|
||||
{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))
|
||||
{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::ResolvedCavernEdgeV1> edges;
|
||||
if(!MakeNativeLatticeSpine(resolvedLayout,seed,options.LatticeStrateIndex,
|
||||
options.LatticeOrphanControl,rooms,edges,error)
|
||||
||!VoxelForgeCore::ResolveResolvedCavernGraphV1(
|
||||
options.LatticeOrphanControl,rooms,edges,error))
|
||||
{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))
|
||||
{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 auto& stratum){return VoxelForgeCore::IsNativeLatticeFieldStratum(stratum)
|
||||
&&stratum.passageCapabilities.hasNativeLatticeField;});
|
||||
const auto actualRecipe=hasNativeLatticeRecipe?std::string_view("lattice")
|
||||
:hasNativeCavesRecipe?std::string_view("caves")
|
||||
const auto actualRecipe=requestedRecipe=="lattice"&&hasNativeLatticeRecipe
|
||||
?std::string_view("lattice")
|
||||
:requestedRecipe=="caves"&&hasNativeCavesRecipe
|
||||
?std::string_view("caves")
|
||||
:requestedRecipe=="surface-world"&&hasSurfaceWorldPlan
|
||||
?std::string_view("surface-world")
|
||||
:requestedRecipe=="floating-islands"&&hasFloatingIslands
|
||||
?std::string_view("floating-islands")
|
||||
:hasNativeLatticeRecipe?std::string_view("lattice")
|
||||
:hasNativeCavesRecipe?std::string_view("caves")
|
||||
:hasFloatingIslands?std::string_view("floating-islands")
|
||||
:hasSurfaceWorldPlan?std::string_view("surface-world")
|
||||
:std::string_view("missing");
|
||||
|
||||
@@ -1595,6 +1595,205 @@ namespace StandaloneStreamBenchmark
|
||||
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&¢erAir&&floorSolid&&shellSolid
|
||||
&&ripple?"PASS":"FAIL");
|
||||
return arrival&&departure&&routeEdges.size()>=2&&!rooms.empty()&&compoundRooms
|
||||
&&destination&&mouthBinding&&gradeOk&¢erAir&&floorSolid&&shellSolid&&ripple;
|
||||
}
|
||||
|
||||
inline bool AuditNativeCavesField(const VoxelForgeCore::World& shape,
|
||||
const VoxelForgeCore::ResolvedLayoutSnapshotV3& layout,std::uint32_t voxelMicrometres)
|
||||
{
|
||||
@@ -1605,9 +1804,13 @@ namespace StandaloneStreamBenchmark
|
||||
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=="Caves"
|
||||
&&s.recipeId=="Core.Caves.PrimitiveField"&&s.recipeVersion==1
|
||||
[](const auto& s){return IsNativeCaveFieldStratum(s)
|
||||
&&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)
|
||||
{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;}
|
||||
@@ -4796,6 +4999,7 @@ namespace StandaloneStreamBenchmark
|
||||
}
|
||||
}
|
||||
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)
|
||||
{
|
||||
std::vector<const VoxelForgeCore::ResolvedLayoutAnchorV2*> anchors;
|
||||
@@ -4859,36 +5063,56 @@ namespace StandaloneStreamBenchmark
|
||||
supportStepMetres,supportMaximumMetres,minimumSupportProbes,
|
||||
supportProbeRadiusMetres);
|
||||
bool binding=true;
|
||||
const auto equalPosition=[](const auto& a,const auto& b)
|
||||
{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)
|
||||
{
|
||||
const auto equalPosition=[](const auto& a,const auto& b)
|
||||
{return a[0]==b[0]&&a[1]==b[1]&&a[2]==b[2];};
|
||||
if(passage.upperStrateIndex==stratum.strateIndex)
|
||||
{
|
||||
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(stratum.generatorKind=="TunnelNetwork")
|
||||
binding&=boundMouthMatches(anchors.back()->values,
|
||||
passage.upperMouthSubvoxels);
|
||||
else
|
||||
{
|
||||
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 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);
|
||||
if(stratum.generatorKind=="TunnelNetwork")
|
||||
binding&=boundMouthMatches(anchors.front()->values,
|
||||
passage.lowerMouthSubvoxels);
|
||||
else
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
++routePasses;routeWalkPasses+=routeAudit.passed;
|
||||
@@ -4918,6 +5142,18 @@ namespace StandaloneStreamBenchmark
|
||||
stratum.strateIndex,routeAudit.firstUnsupported[i][0],
|
||||
routeAudit.firstUnsupported[i][1],routeAudit.firstUnsupported[i][2],
|
||||
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;
|
||||
passageIndex<ResolvedLayout->passages.size();++passageIndex)
|
||||
@@ -4984,13 +5220,16 @@ namespace StandaloneStreamBenchmark
|
||||
&&shellAuditPassed
|
||||
&&routePasses>0&&routePasses==routeWalkPasses&&routePasses==routeBindings
|
||||
&&routeBindingFailures==0
|
||||
&&tunnelNetworkRoutes==tunnelNetworkBlockedControls
|
||||
&&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>(naturalPasses),static_cast<unsigned long long>(passageCount),
|
||||
static_cast<unsigned long long>(routePasses),static_cast<unsigned long long>(routeWalkPasses),
|
||||
static_cast<unsigned long long>(routePoints),
|
||||
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),
|
||||
auditPassed?"PASS":"FAIL");
|
||||
if(!auditPassed)
|
||||
|
||||
@@ -1069,6 +1069,166 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
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,
|
||||
VoxelForgeCore::WorldAssemblyManifest& out,std::string& error)
|
||||
{
|
||||
@@ -1184,8 +1344,11 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
if(!slot.Definition)continue;
|
||||
const auto type=owner.Manager->GetGeneratorTypeForChunk(FIntVector(0,0,slot.TopChunkZ));
|
||||
const auto authored=sealAndDensity(*slot.Definition,type);
|
||||
const bool nativeCaves=recipeVariant=="caves"
|
||||
const bool nativeSurfaceCaves=recipeVariant=="caves"
|
||||
&&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))
|
||||
&&(type==ECaveGeneratorType::Maze||type==ECaveGeneratorType::VerticalShafts);
|
||||
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)
|
||||
{error="strate "+std::to_string(slot.StrateIndex)+" has an invalid BoundarySealThickness for two boundary bands";return false;}
|
||||
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)
|
||||
+"/generator/"+s.generatorKind;
|
||||
s.recipeId=nativeLattice?"Core.Lattice.PrimitiveField"
|
||||
@@ -1407,6 +1570,8 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
passageConfigs.push_back(config);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -538,6 +538,19 @@ def run_density_dispatch_report(runner: VerificationRunner, binary: Path) -> Non
|
||||
voxel_size_um = int(strata[0]["voxel_size_um"])
|
||||
if voxel_size_um <= 0:
|
||||
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:
|
||||
failures.append(f"{name}: cannot classify tiles from resolved layout: {error}")
|
||||
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")
|
||||
runner.require("owner-density-dispatch-report", not failures,
|
||||
"; ".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,
|
||||
|
||||
Reference in new issue
Block a user