feat(core): native lattice generator, slice 1 -- helix-ramp spine + small maze (L58); research R27

Core.Lattice.PrimitiveField v1, opt-in per strate (legacy Maze/VerticalShaft stay legacy): resolves the real
arrival and departure mouths first, plans a mandatory spine, then maze cells and two dead ends (9 rooms,
9 edges, stable IDs). The spine descends 20 m through four helix turns (6 m radius, 3 m clear width, 5 m
pitch, 2.6 m clearance; max grade 10.0 deg); each feature is one filleted void (0.57 m measured). Final-field
reachability passes at 883 stations; an orphaned shaft control is rejected; exact intervals (0 misses) and
a whole-surface audit with 0 flags. Whole view 1.69 s (above the 1 s target). fast/medium/full PASS.
Research R27 (self-checked): static stream-to-pool water design.

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-25 15:45:32 +01:00
1 parent 1e3e04bbac
commit 6c6cc37e1d
25 files changed
+280497 -117

No files matched your search

+98 -24
View File
@@ -159,6 +159,16 @@ namespace
for(unsigned char c:stableId){hash^=c;hash*=1099511628211ull;}
return LayoutStampMix(hash);
}
std::uint64_t LayoutLatticeFeatureSeed(std::int32_t seed,std::int32_t strate,
std::uint32_t recipeVersion,std::string_view stableId)
{
std::uint64_t hash=14695981039346656037ull;
TransitionHashWord(hash,static_cast<std::uint32_t>(seed));
TransitionHashWord(hash,static_cast<std::uint32_t>(strate));
TransitionHashWord(hash,recipeVersion);
for(unsigned char c:stableId){hash^=c;hash*=1099511628211ull;}
return LayoutStampMix(hash);
}
std::uint64_t LayoutStampNext(std::uint64_t& state)
{state=LayoutStampMix(state+0x9e3779b97f4a7c15ull);return state;}
bool LayoutStampKindValid(ResolvedSdfStampKindV1 kind)
@@ -611,7 +621,10 @@ namespace
<<room.halfExtentsMicrometres[1]<<','<<room.halfExtentsMicrometres[2]
<<"],\"floor_um\":"<<room.floorMicrometres
<<",\"smooth_width_um\":"<<room.smoothWidthMicrometres
<<",\"irregularity_amplitude_um\":"<<room.irregularityAmplitudeMicrometres
;
if(room.unionWidthMicrometres>0)
out<<",\"union_width_um\":"<<room.unionWidthMicrometres;
out<<",\"irregularity_amplitude_um\":"<<room.irregularityAmplitudeMicrometres
<<",\"irregularity_wavelength_um\":"<<room.irregularityWavelengthMicrometres
<<",\"irregularity_seed\":"<<room.irregularitySeed
<<",\"bounds_min_um\":["<<room.boundsMinimumMicrometres[0]<<','
@@ -646,8 +659,10 @@ namespace
out<<",\"bore_radius_um\":"<<edge.boreRadiusMicrometres
<<",\"bore_height_um\":"<<edge.boreHeightMicrometres
<<",\"floor_support_depth_um\":"<<edge.floorSupportDepthMicrometres
<<",\"smooth_width_um\":"<<edge.smoothWidthMicrometres
<<",\"irregularity_amplitude_um\":"<<edge.irregularityAmplitudeMicrometres
<<",\"smooth_width_um\":"<<edge.smoothWidthMicrometres;
if(edge.unionWidthMicrometres>0)
out<<",\"union_width_um\":"<<edge.unionWidthMicrometres;
out<<",\"irregularity_amplitude_um\":"<<edge.irregularityAmplitudeMicrometres
<<",\"irregularity_wavelength_um\":"<<edge.irregularityWavelengthMicrometres
<<",\"irregularity_seed\":"<<edge.irregularitySeed
<<",\"bounds_min_um\":["<<edge.boundsMinimumMicrometres[0]<<','
@@ -867,8 +882,10 @@ bool ResolvedLayoutSnapshotV3::finalize(std::string& error)
x.lowerInterfaceCapabilities={};
if(x.generatorKind=="SurfaceWorld")
x.passageCapabilities.hasSurfaceLanding=true;
else if(x.generatorKind=="Caves")
else if(IsNativeCaveFieldStratum(x))
x.passageCapabilities.hasNativeCaveField=true;
else if(IsNativeLatticeFieldStratum(x))
x.passageCapabilities.hasNativeLatticeField=true;
else if(x.generatorKind=="FloatingIslands")
{
x.passageCapabilities.hasFloatingIslandLanding=true;
@@ -1245,6 +1262,7 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
validExtents=validExtents&&extent>=500000&&extent<=100000000;
if(!validExtents||room.smoothWidthMicrometres<0
||room.smoothWidthMicrometres>10000000
||room.unionWidthMicrometres<0||room.unionWidthMicrometres>10000000
||room.irregularityAmplitudeMicrometres<0
||room.irregularityAmplitudeMicrometres>10000000
||room.irregularityWavelengthMicrometres<0
@@ -1253,7 +1271,7 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
&&room.irregularityWavelengthMicrometres<=0))
add("cavern_room_parameters",room.stableId+" has invalid or unbounded parameters");
const std::int64_t pad=room.smoothWidthMicrometres
+room.irregularityAmplitudeMicrometres;
+room.unionWidthMicrometres+room.irregularityAmplitudeMicrometres;
for(std::size_t axis=0;axis<3;++axis)
if(room.boundsMinimumMicrometres[axis]
!=room.centerMicrometres[axis]-room.halfExtentsMicrometres[axis]-pad
@@ -1276,11 +1294,15 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
add("cavern_edge_binding",edge.stableId+" does not terminate at a room in its strate");
if(!findStratum(edge.strateIndex))
add("cavern_graph_strate",edge.stableId+" names an unknown strate");
const auto* edgeStratum=findStratum(edge.strateIndex);
const bool nativeLattice=edgeStratum
&&IsNativeLatticeFieldStratum(*edgeStratum);
if(edge.boreRadiusMicrometres<500000||edge.boreRadiusMicrometres>10000000
||edge.boreHeightMicrometres<1000000||edge.boreHeightMicrometres>20000000
||edge.floorSupportDepthMicrometres<0
||edge.floorSupportDepthMicrometres>2000000
||edge.smoothWidthMicrometres<0||edge.smoothWidthMicrometres>10000000
||edge.unionWidthMicrometres<0||edge.unionWidthMicrometres>10000000
||edge.irregularityAmplitudeMicrometres<0
||edge.irregularityAmplitudeMicrometres>10000000
||edge.irregularityWavelengthMicrometres<0
@@ -1290,13 +1312,21 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
add("cavern_edge_parameters",edge.stableId+" has invalid or unbounded parameters");
std::array<std::int64_t,3> low=edge.controlPointsMicrometres[0];
std::array<std::int64_t,3> high=low;
for(const auto& point:edge.controlPointsMicrometres)
if(nativeLattice&&!edge.polylineMicrometres.empty())
{low=edge.polylineMicrometres.front();high=low;}
const auto includeBoundPoint=[&](const auto& point)
{
for(std::size_t axis=0;axis<3;++axis)
{low[axis]=std::min(low[axis],point[axis]);high[axis]=std::max(high[axis],point[axis]);}
};
if(nativeLattice&&!edge.polylineMicrometres.empty())
for(const auto& point:edge.polylineMicrometres)includeBoundPoint(point);
else for(const auto& point:edge.controlPointsMicrometres)includeBoundPoint(point);
const std::int64_t xyPad=edge.boreRadiusMicrometres
+edge.smoothWidthMicrometres+edge.irregularityAmplitudeMicrometres;
const std::int64_t zPad=edge.smoothWidthMicrometres
+edge.smoothWidthMicrometres+edge.unionWidthMicrometres
+edge.irregularityAmplitudeMicrometres;
const std::int64_t zPad=edge.smoothWidthMicrometres
+edge.unionWidthMicrometres+edge.irregularityAmplitudeMicrometres;
for(std::size_t axis=0;axis<3;++axis)
{
const std::int64_t pad=axis==2?zPad:xyPad;
@@ -1314,17 +1344,26 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
||endpoint[1]<room->centerMicrometres[1]-room->halfExtentsMicrometres[1]
||endpoint[1]>room->centerMicrometres[1]+room->halfExtentsMicrometres[1]
||std::abs(endpoint[2]-room->floorMicrometres)>500000)
add("cavern_edge_portal",edge.stableId+" endpoint does not meet its room floor portal");
{
add("cavern_edge_portal",edge.stableId+" endpoint_um=("
+std::to_string(endpoint[0])+","+std::to_string(endpoint[1])+","+
std::to_string(endpoint[2])+") room="+room->stableId+" center_um=("
+std::to_string(room->centerMicrometres[0])+","+
std::to_string(room->centerMicrometres[1])+","+
std::to_string(room->centerMicrometres[2])+") floor_um="+
std::to_string(room->floorMicrometres)+" half_um=("
+std::to_string(room->halfExtentsMicrometres[0])+","+
std::to_string(room->halfExtentsMicrometres[1])+","+
std::to_string(room->halfExtentsMicrometres[2])+")");
}
}
const auto* edgeStratum=findStratum(edge.strateIndex);
if(edgeStratum&&edgeStratum->generatorKind=="Caves"
&&edgeStratum->recipeId=="Core.Caves.PrimitiveField"
&&edgeStratum->recipeVersion==1
if(edgeStratum&&HasNativeGraphField(*edgeStratum)
&&(edge.polylineMicrometres.size()<2
||edge.polylineMicrometres.front()!=edge.controlPointsMicrometres.front()
||edge.polylineMicrometres.back()!=edge.controlPointsMicrometres.back()))
||edge.polylineMicrometres.back()!=edge.controlPointsMicrometres.back()
||edge.polylineMicrometres.size()>65536))
add("cavern_edge_polyline",edge.stableId+
" has no resolve-time polyline with the cubic endpoints");
" has no bounded resolve-time polyline with matching endpoints");
}
}
if(oreVeins.empty())
@@ -1376,7 +1415,12 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
if(!LayoutOreVeinSafe(*this,vein,&safetyReason))
add("ore_vein_placement",vein.stableId+" is unsafe: "+safetyReason);
}
if(strict&&!issues.empty()){error="strict resolved-layout validation rejected "+issues.front().code;return false;}
if(strict&&!issues.empty())
{
error="strict resolved-layout validation rejected "+issues.front().code+": "
+issues.front().detail;
return false;
}
return true;
}
@@ -1485,6 +1529,7 @@ std::optional<ResolvedLayoutSnapshotV3> ResolvedLayoutSnapshotV3::FromJson(
readTriplet("bounds_max_um",room.boundsMaximumMicrometres);
room.floorMicrometres=LayoutInt(v,"floor_um");
room.smoothWidthMicrometres=LayoutInt(v,"smooth_width_um");
room.unionWidthMicrometres=LayoutInt(v,"union_width_um");
room.irregularityAmplitudeMicrometres=LayoutInt(v,"irregularity_amplitude_um");
room.irregularityWavelengthMicrometres=LayoutInt(v,"irregularity_wavelength_um");
room.irregularitySeed=static_cast<std::uint64_t>(LayoutInt(v,"irregularity_seed"));
@@ -1522,6 +1567,7 @@ std::optional<ResolvedLayoutSnapshotV3> ResolvedLayoutSnapshotV3::FromJson(
edge.boreHeightMicrometres=LayoutInt(v,"bore_height_um");
edge.floorSupportDepthMicrometres=LayoutInt(v,"floor_support_depth_um");
edge.smoothWidthMicrometres=LayoutInt(v,"smooth_width_um");
edge.unionWidthMicrometres=LayoutInt(v,"union_width_um");
edge.irregularityAmplitudeMicrometres=LayoutInt(v,"irregularity_amplitude_um");
edge.irregularityWavelengthMicrometres=LayoutInt(v,"irregularity_wavelength_um");
edge.irregularitySeed=static_cast<std::uint64_t>(LayoutInt(v,"irregularity_seed"));
@@ -1842,17 +1888,24 @@ bool ResolveResolvedCavernGraphV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
{
const auto found=std::find_if(candidate.strates.begin(),candidate.strates.end(),
[strateIndex](const auto& stratum){return stratum.strateIndex==strateIndex;});
return found!=candidate.strates.end()&&found->generatorKind=="Caves"
&&found->recipeId=="Core.Caves.PrimitiveField"&&found->recipeVersion==1;
return found!=candidate.strates.end()&&IsNativeCaveFieldStratum(*found);
};
const auto isNativeLatticeStrate=[&candidate](std::int32_t strateIndex)
{
const auto found=std::find_if(candidate.strates.begin(),candidate.strates.end(),
[strateIndex](const auto& stratum){return stratum.strateIndex==strateIndex;});
return found!=candidate.strates.end()&&IsNativeLatticeFieldStratum(*found);
};
for(auto& room:candidate.cavernRooms)
{
// The shared JSON value type stores numbers as IEEE-754 doubles. Keep the
// serialized seed in the exact integer range, as the stamp vocabulary does.
room.irregularitySeed=LayoutStampSeed(seed,room.strateIndex,room.stableId)
room.irregularitySeed=(isNativeLatticeStrate(room.strateIndex)
?LayoutLatticeFeatureSeed(seed,room.strateIndex,1,room.stableId)
:LayoutStampSeed(seed,room.strateIndex,room.stableId))
&0x001fffffffffffffull;
const std::int64_t pad=room.smoothWidthMicrometres
+room.irregularityAmplitudeMicrometres;
+room.unionWidthMicrometres+room.irregularityAmplitudeMicrometres;
for(std::size_t axis=0;axis<3;++axis)
{
room.boundsMinimumMicrometres[axis]=room.centerMicrometres[axis]
@@ -1863,7 +1916,10 @@ bool ResolveResolvedCavernGraphV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
}
for(auto& edge:candidate.cavernEdges)
{
edge.irregularitySeed=LayoutStampSeed(seed,edge.strateIndex,edge.stableId)
const bool nativeLattice=isNativeLatticeStrate(edge.strateIndex);
edge.irregularitySeed=(nativeLattice
?LayoutLatticeFeatureSeed(seed,edge.strateIndex,1,edge.stableId)
:LayoutStampSeed(seed,edge.strateIndex,edge.stableId))
&0x001fffffffffffffull;
using FCurvePoint=std::array<double,3>;
std::array<FCurvePoint,4> curve{};
@@ -1889,6 +1945,8 @@ bool ResolveResolvedCavernGraphV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
}
return std::sqrt(distanceSquared);
};
std::vector<std::array<std::int64_t,3>> authoredPolyline;
if(nativeLattice)authoredPolyline=std::move(edge.polylineMicrometres);
edge.polylineMicrometres.clear();
if(isNativeCavesStrate(edge.strateIndex))
{
@@ -1934,15 +1992,31 @@ bool ResolveResolvedCavernGraphV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
if(!polylineValid)
{error="cavern edge "+edge.stableId+" exceeds the bounded polyline subdivision budget";return false;}
}
else if(nativeLattice)
{
if(authoredPolyline.size()<2||authoredPolyline.size()>65536
||authoredPolyline.front()!=edge.controlPointsMicrometres.front()
||authoredPolyline.back()!=edge.controlPointsMicrometres.back())
{error="lattice edge "+edge.stableId+
" needs a bounded explicit path with matching endpoints";return false;}
edge.polylineMicrometres=std::move(authoredPolyline);
}
std::array<std::int64_t,3> low=edge.controlPointsMicrometres[0];
std::array<std::int64_t,3> high=low;
for(const auto& point:edge.controlPointsMicrometres)
const auto includeBoundPoint=[&](const auto& point)
{
for(std::size_t axis=0;axis<3;++axis)
{low[axis]=std::min(low[axis],point[axis]);high[axis]=std::max(high[axis],point[axis]);}
};
if(nativeLattice&&!edge.polylineMicrometres.empty())
{low=edge.polylineMicrometres.front();high=low;
for(const auto& point:edge.polylineMicrometres)includeBoundPoint(point);}
else for(const auto& point:edge.controlPointsMicrometres)includeBoundPoint(point);
const std::int64_t xyPad=edge.boreRadiusMicrometres
+edge.smoothWidthMicrometres+edge.irregularityAmplitudeMicrometres;
const std::int64_t zPad=edge.smoothWidthMicrometres
+edge.smoothWidthMicrometres+edge.unionWidthMicrometres
+edge.irregularityAmplitudeMicrometres;
const std::int64_t zPad=edge.smoothWidthMicrometres
+edge.unionWidthMicrometres+edge.irregularityAmplitudeMicrometres;
for(std::size_t axis=0;axis<3;++axis)
{
const std::int64_t pad=axis==2?zPad:xyPad;
+278 -65
View File
@@ -378,10 +378,10 @@ public:
}
}
}
else if(Stratum.generatorKind=="Caves"
&&Stratum.recipeId=="Core.Caves.PrimitiveField"
&&Stratum.recipeVersion==1
&&Stratum.passageCapabilities.hasNativeCaveField)
else if(VoxelForgeCore::HasNativeGraphField(Stratum)
&&(VoxelForgeCore::IsNativeCaveFieldStratum(Stratum)
?Stratum.passageCapabilities.hasNativeCaveField
:Stratum.passageCapabilities.hasNativeLatticeField))
{
Bound.Kind=FBoundStratumField::EKind::Caves;
Bound.Evaluator.emplace<FCavesFieldAdapter>(
@@ -1819,14 +1819,28 @@ public:
float ApplyResolvedStrateComposition(float density,float X,float Y,float Z) const
{
const float graphDensity=ApplyResolvedCavernGraph(density,X,Y,Z);
const float composed=ApplyResolvedStratePassageCarves(
ApplyResolvedStrateBoundaryBand(graphDensity,X,Y,Z),X,Y,Z);
const float stamped=StampRuntime.empty()?composed:ApplyResolvedSdfStamps(composed,X,Y,Z);
const auto* bound=FindBoundStratumForManagerZ(Z);
const bool nativeLattice=bound&&bound->Resolved
&&VoxelForgeCore::IsNativeLatticeFieldStratum(*bound->Resolved)
&&bound->Resolved->passageCapabilities.hasNativeLatticeField;
// The native lattice field is a complete host-rock SDF: it owns both the
// voids and the surrounding solid. Replacing its air-side distances with
// the generic stratum transition distance creates a discontinuity at a
// mouth that crosses a wide transition band. Keep that opt-in field whole;
// its host-rock value already supplies the ceiling and bedrock.
const float boundary=nativeLattice?graphDensity
:ApplyResolvedStrateBoundaryBand(graphDensity,X,Y,Z);
// The native lattice owns its resolved arrival/departure portals and their
// supported spine. Reapplying legacy passage carves here can erase the
// tread immediately above a broad legacy mouth or support column.
const float composed=nativeLattice?boundary
:ApplyResolvedStratePassageCarves(boundary,X,Y,Z);
const float stamped=StampRuntime.empty()?composed:ApplyResolvedSdfStamps(composed,X,Y,Z);
if(bound&&bound->Kind==FBoundStratumField::EKind::Caves
&&bound->Resolved&&bound->Resolved->recipeId=="Core.Caves.PrimitiveField"
&&bound->Resolved->recipeVersion==1
&&bound->Resolved->passageCapabilities.hasNativeCaveField)
&&bound->Resolved&&VoxelForgeCore::HasNativeGraphField(*bound->Resolved)
&&(VoxelForgeCore::IsNativeCaveFieldStratum(*bound->Resolved)
?bound->Resolved->passageCapabilities.hasNativeCaveField
:bound->Resolved->passageCapabilities.hasNativeLatticeField))
return stamped;
// The legacy landmark demo retains its compatibility floor cap. Native caves
// already combine each floor with its wall before the single void carve above.
@@ -3040,7 +3054,9 @@ public:
if(NativeFloatingIslandDensityAt(X,Y,Z,Density))return "FloatingIslands-native";
const auto* Bound=FindBoundStratumForZ(Z);
if(Bound&&Bound->Kind==FBoundStratumField::EKind::Caves
&&NativeSurfaceDensityAt(X,Y,Z,Density))return "Caves-native";
&&NativeSurfaceDensityAt(X,Y,Z,Density))
return Bound->Resolved&&VoxelForgeCore::IsNativeLatticeFieldStratum(*Bound->Resolved)
?"Lattice-native":"Caves-native";
if(NativeSurfaceDensityAt(X,Y,Z,Density))return "SurfaceWorld-native";
if(UsesLegacySurfaceField(X,Y,Z))return "SurfaceWorld-legacy";
if(Bound&&Bound->Resolved)
@@ -3162,7 +3178,7 @@ public:
}
bool bHasLegacySurfaceSample=false;
bool bHasNativeIslandSample=false;
bool bHasNativeCavesSample=false;
bool bHasNativeGraphSample=false;
std::array<float,8> NativeIslandDensity{};
std::array<bool,8> HasNativeIslandSample{};
for(int32 Lane=0;Lane<Count;++Lane)
@@ -3172,13 +3188,13 @@ public:
const float SampleZ=static_cast<float>(Z);
bHasLegacySurfaceSample|=UsesLegacySurfaceField(SampleX,SampleY,SampleZ);
const auto* bound=FindBoundStratumForZ(SampleZ);
bHasNativeCavesSample|=bound
bHasNativeGraphSample|=bound
&&bound->Kind==FBoundStratumField::EKind::Caves;
HasNativeIslandSample[static_cast<std::size_t>(Lane)]=NativeFloatingIslandDensityAt(
SampleX,SampleY,SampleZ,NativeIslandDensity[static_cast<std::size_t>(Lane)]);
bHasNativeIslandSample|=HasNativeIslandSample[static_cast<std::size_t>(Lane)];
}
if(bHasLegacySurfaceSample||bHasNativeIslandSample||bHasNativeCavesSample)
if(bHasLegacySurfaceSample||bHasNativeIslandSample||bHasNativeGraphSample)
{
for(int32 Lane=0;Lane<Count;++Lane)
{
@@ -3315,14 +3331,25 @@ private:
{
const VoxelForgeCore::ResolvedCavernRoomV1* room=nullptr;
const VoxelForgeCore::ResolvedCavernEdgeV1* edge=nullptr;
bool nativeCaves=false;
bool nativeGraph=false;
std::array<std::int64_t,3> boundsMinimumMicrometres{},boundsMaximumMicrometres{};
std::array<double,3> centerMetres{},halfExtentsMetres{};
struct FSegment
{
std::array<double,3> start{},end{},minimum{},maximum{};
};
struct FSegmentNode
{
std::array<double,3> minimum{},maximum{};
std::size_t first=0,end=0;
int left=-1,right=-1;
};
std::vector<FSegment> segments;
std::vector<std::uint32_t> segmentOrder;
std::vector<FSegmentNode> segmentNodes;
double minimumFloorMetres=std::numeric_limits<double>::infinity();
double maximumFloorMetres=-std::numeric_limits<double>::infinity();
double maximumSegmentGrade=0.0;
std::array<float,3> centerVoxels{},halfExtentsVoxels{};
std::array<std::array<float,3>,4> controlPointsVoxels{};
float floorVoxels=0.0f;
@@ -3753,22 +3780,22 @@ private:
if(!ResolvedLayout||!Owner||Owner->CoreVoxelSizeMicrons==0
||ResolvedLayout->cavernGraphVocabularyVersion
!=VoxelForgeCore::ResolvedLayoutCavernGraphVocabularyVersionV1)return;
const auto isNativeCavesStrate=[this](std::int32_t strateIndex)
const auto isNativeGraphStrate=[this](std::int32_t strateIndex)
{
const auto found=std::find_if(ResolvedLayout->strates.begin(),
ResolvedLayout->strates.end(),[strateIndex](const auto& stratum)
{return stratum.strateIndex==strateIndex;});
return found!=ResolvedLayout->strates.end()
&&found->generatorKind=="Caves"
&&found->recipeId=="Core.Caves.PrimitiveField"
&&found->recipeVersion==1
&&found->passageCapabilities.hasNativeCaveField;
&&VoxelForgeCore::HasNativeGraphField(*found)
&&(VoxelForgeCore::IsNativeCaveFieldStratum(*found)
?found->passageCapabilities.hasNativeCaveField
:found->passageCapabilities.hasNativeLatticeField);
};
const float micronsPerVoxel=static_cast<float>(Owner->CoreVoxelSizeMicrons);
for(const auto& room:ResolvedLayout->cavernRooms)
{
FCavernRuntimeFeature runtime;runtime.room=&room;
runtime.nativeCaves=isNativeCavesStrate(room.strateIndex);
runtime.nativeGraph=isNativeGraphStrate(room.strateIndex);
runtime.boundsMinimumMicrometres=room.boundsMinimumMicrometres;
runtime.boundsMaximumMicrometres=room.boundsMaximumMicrometres;
for(std::size_t axis=0;axis<3;++axis)
@@ -3794,7 +3821,7 @@ private:
for(const auto& edge:ResolvedLayout->cavernEdges)
{
FCavernRuntimeFeature runtime;runtime.edge=&edge;
runtime.nativeCaves=isNativeCavesStrate(edge.strateIndex);
runtime.nativeGraph=isNativeGraphStrate(edge.strateIndex);
runtime.boundsMinimumMicrometres=edge.boundsMinimumMicrometres;
runtime.boundsMaximumMicrometres=edge.boundsMaximumMicrometres;
for(std::size_t point=0;point<4;++point)
@@ -3827,8 +3854,69 @@ private:
segment.minimum[axis]=std::min(segment.start[axis],segment.end[axis]);
segment.maximum[axis]=std::max(segment.start[axis],segment.end[axis]);
}
runtime.minimumFloorMetres=std::min(runtime.minimumFloorMetres,
std::min(segment.start[2],segment.end[2]));
runtime.maximumFloorMetres=std::max(runtime.maximumFloorMetres,
std::max(segment.start[2],segment.end[2]));
const double horizontal=std::hypot(segment.end[0]-segment.start[0],
segment.end[1]-segment.start[1]);
if(horizontal>1.0e-9)
runtime.maximumSegmentGrade=std::max(runtime.maximumSegmentGrade,
std::abs((segment.end[2]-segment.start[2])/horizontal));
runtime.segments.push_back(segment);
}
runtime.segmentOrder.resize(runtime.segments.size());
for(std::size_t index=0;index<runtime.segmentOrder.size();++index)
runtime.segmentOrder[index]=static_cast<std::uint32_t>(index);
if(!runtime.segmentOrder.empty())
{
const auto buildSegmentNode=[&](const auto& self,std::size_t first,
std::size_t end)->int
{
FCavernRuntimeFeature::FSegmentNode node;
node.first=first;node.end=end;
node.minimum.fill(std::numeric_limits<double>::infinity());
node.maximum.fill(-std::numeric_limits<double>::infinity());
for(std::size_t offset=first;offset<end;++offset)
{
const auto& segment=runtime.segments[
runtime.segmentOrder[offset]];
for(std::size_t axis=0;axis<3;++axis)
{
node.minimum[axis]=std::min(node.minimum[axis],segment.minimum[axis]);
node.maximum[axis]=std::max(node.maximum[axis],segment.maximum[axis]);
}
}
const int nodeIndex=static_cast<int>(runtime.segmentNodes.size());
runtime.segmentNodes.push_back(node);
if(end-first>4)
{
std::size_t splitAxis=0;
for(std::size_t axis=1;axis<3;++axis)
if(node.maximum[axis]-node.minimum[axis]
>node.maximum[splitAxis]-node.minimum[splitAxis])
splitAxis=axis;
const std::size_t middle=first+(end-first)/2;
std::nth_element(runtime.segmentOrder.begin()+first,
runtime.segmentOrder.begin()+middle,
runtime.segmentOrder.begin()+end,
[&](std::uint32_t a,std::uint32_t b)
{
const auto& left=runtime.segments[a];
const auto& right=runtime.segments[b];
const double ca=left.minimum[splitAxis]+left.maximum[splitAxis];
const double cb=right.minimum[splitAxis]+right.maximum[splitAxis];
return ca==cb?a<b:ca<cb;
});
const int left=self(self,first,middle);
const int right=self(self,middle,end);
runtime.segmentNodes[nodeIndex].left=left;
runtime.segmentNodes[nodeIndex].right=right;
}
return nodeIndex;
};
buildSegmentNode(buildSegmentNode,0,runtime.segmentOrder.size());
}
CavernRuntime.push_back(runtime);
}
std::stable_sort(CavernRuntime.begin(),CavernRuntime.end(),[](const auto& a,const auto& b)
@@ -3838,8 +3926,8 @@ private:
const std::int32_t rightStrate=b.room?b.room->strateIndex
:b.edge?b.edge->strateIndex:-1;
if(leftStrate!=rightStrate)return leftStrate<rightStrate;
if(a.nativeCaves!=b.nativeCaves)return a.nativeCaves;
if(!a.nativeCaves)return false;
if(a.nativeGraph!=b.nativeGraph)return a.nativeGraph;
if(!a.nativeGraph)return false;
const std::string& left=a.room?a.room->stableId:a.edge->stableId;
const std::string& right=b.room?b.room->stableId:b.edge->stableId;
return left<right;
@@ -5106,9 +5194,10 @@ public:
}
else if(Candidate->Kind==FBoundStratumField::EKind::Caves)
{
if(!Candidate->Resolved||Candidate->Resolved->recipeId!="Core.Caves.PrimitiveField"
||Candidate->Resolved->recipeVersion!=1
||!Candidate->Resolved->passageCapabilities.hasNativeCaveField
if(!Candidate->Resolved||!VoxelForgeCore::HasNativeGraphField(*Candidate->Resolved)
||(VoxelForgeCore::IsNativeCaveFieldStratum(*Candidate->Resolved)
?!Candidate->Resolved->passageCapabilities.hasNativeCaveField
:!Candidate->Resolved->passageCapabilities.hasNativeLatticeField)
||!std::get_if<FCavesFieldAdapter>(&Candidate->Evaluator))return false;
}
else return false;
@@ -5144,6 +5233,11 @@ public:
FMetricInterval caveVoid{std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::infinity(),true};
bool haveFeature=false;
double latticeFloorSupportLowerBound=std::numeric_limits<double>::infinity();
const auto* resolvedStratum=FindResolvedLayoutStratum(strateIndex);
const bool nativeLattice=resolvedStratum
&&VoxelForgeCore::IsNativeLatticeFieldStratum(*resolvedStratum)
&&resolvedStratum->passageCapabilities.hasNativeLatticeField;
for(const std::uint32_t index:candidates)
{
if(index>=CavernRuntime.size())continue;
@@ -5185,18 +5279,32 @@ public:
if(!(radius>0.0&&height>0.0))return {0.0,0.0,false};
double tubeMinimum=std::numeric_limits<double>::infinity();
double tubeMaximum=std::numeric_limits<double>::infinity();
double floorMinimum=std::numeric_limits<double>::infinity();
double floorMaximum=-std::numeric_limits<double>::infinity();
double maximumGrade=0.0;
for(const auto& segment:feature.segments)
const std::array<double,3> inverseScale={1.0/radius,1.0/radius,
1.0/height};
const std::array<double,3> boxMinimum={lo[0]*inverseScale[0],
lo[1]*inverseScale[1],lo[2]*inverseScale[2]};
const std::array<double,3> boxMaximum={hi[0]*inverseScale[0],
hi[1]*inverseScale[1],hi[2]*inverseScale[2]};
const double scale=std::min(radius,height);
const auto nodeLowerBound=[&](const FCavernRuntimeFeature::FSegmentNode& node)
{
const std::array<double,3> inverseScale={1.0/radius,1.0/radius,
1.0/height};
std::array<double,3> boxMinimum{},boxMaximum{},segmentStart{},segmentEnd{};
double gapSquared=0.0;
for(std::size_t axis=0;axis<3;++axis)
{
const double low=node.minimum[axis]*inverseScale[axis];
const double high=node.maximum[axis]*inverseScale[axis];
const double gap=high<boxMinimum[axis]?boxMinimum[axis]-high
:low>boxMaximum[axis]?low-boxMaximum[axis]:0.0;
gapSquared+=gap*gap;
}
return (std::sqrt(gapSquared)-1.0)*scale;
};
const auto evaluateSegment=[&](std::uint32_t segmentIndex)
{
const auto& segment=feature.segments[segmentIndex];
std::array<double,3> segmentStart{},segmentEnd{};
for(std::size_t axis=0;axis<3;++axis)
{
boxMinimum[axis]=lo[axis]*inverseScale[axis];
boxMaximum[axis]=hi[axis]*inverseScale[axis];
segmentStart[axis]=segment.start[axis]*inverseScale[axis];
segmentEnd[axis]=segment.end[axis]*inverseScale[axis];
}
@@ -5237,19 +5345,33 @@ public:
farthestSquared+=farthest*farthest;
}
const double distanceUpper=std::sqrt(farthestSquared);
const double scale=std::min(radius,height);
tubeMinimum=std::min(tubeMinimum,(distanceLower-1.0)*scale);
tubeMaximum=std::min(tubeMaximum,(distanceUpper-1.0)*scale);
floorMinimum=std::min(floorMinimum,
std::min(segment.start[2],segment.end[2]));
floorMaximum=std::max(floorMaximum,
std::max(segment.start[2],segment.end[2]));
const double horizontal=std::hypot(segment.end[0]-segment.start[0],
segment.end[1]-segment.start[1]);
if(horizontal>1.0e-9)
maximumGrade=std::max(maximumGrade,
std::abs((segment.end[2]-segment.start[2])/horizontal));
}
};
if(feature.segmentNodes.empty())return {0.0,0.0,false};
const auto visitNode=[&](const auto& self,int nodeIndex)->void
{
if(nodeIndex<0||static_cast<std::size_t>(nodeIndex)>=feature.segmentNodes.size())return;
const auto& node=feature.segmentNodes[static_cast<std::size_t>(nodeIndex)];
if(nodeLowerBound(node)>tubeMaximum)return;
if(node.left<0)
{
for(std::size_t offset=node.first;offset<node.end;++offset)
evaluateSegment(feature.segmentOrder[offset]);
return;
}
const double leftBound=nodeLowerBound(feature.segmentNodes[
static_cast<std::size_t>(node.left)]);
const double rightBound=nodeLowerBound(feature.segmentNodes[
static_cast<std::size_t>(node.right)]);
if(leftBound<=rightBound)
{self(self,node.left);self(self,node.right);}
else {self(self,node.right);self(self,node.left);}
};
visitNode(visitNode,0);
const double floorMinimum=feature.minimumFloorMetres;
const double floorMaximum=feature.maximumFloorMetres;
const double maximumGrade=feature.maximumSegmentGrade;
if(!std::isfinite(tubeMinimum)||!std::isfinite(tubeMaximum)
||!std::isfinite(floorMinimum)||!std::isfinite(floorMaximum))
return {0.0,0.0,false};
@@ -5267,6 +5389,23 @@ public:
SmoothMaxMetres(tube.maximum,floor.maximum,width),true};
}
else return {0.0,0.0,false};
if(nativeLattice)
{
const double supportDepth=feature.edge
?feature.edge->floorSupportDepthMicrometres/1000000.0:0.0;
const double floorMinimum=feature.edge
?feature.minimumFloorMetres:std::numeric_limits<double>::infinity();
const double floorMaximum=feature.edge
?feature.maximumFloorMetres:-std::numeric_limits<double>::infinity();
const bool overlapsXY=hi[0]>=feature.boundsMinimumMicrometres[0]/1.0e6
&&lo[0]<=feature.boundsMaximumMicrometres[0]/1.0e6
&&hi[1]>=feature.boundsMinimumMicrometres[1]/1.0e6
&&lo[1]<=feature.boundsMaximumMicrometres[1]/1.0e6;
if(supportDepth>0.0&&overlapsXY
&&hi[2]>=floorMinimum-supportDepth&&lo[2]<=floorMaximum)
latticeFloorSupportLowerBound=std::min(latticeFloorSupportLowerBound,
std::max(-supportDepth,lo[2]-floorMaximum));
}
if(!std::isfinite(featureVoid.minimum)||!std::isfinite(featureVoid.maximum))
return {0.0,0.0,false};
if(!haveFeature)
@@ -5276,8 +5415,12 @@ public:
else
{
const double width=feature.room
?feature.room->smoothWidthMicrometres/1000000.0
:feature.edge->smoothWidthMicrometres/1000000.0;
?(feature.room->unionWidthMicrometres>0
?feature.room->unionWidthMicrometres
:feature.room->smoothWidthMicrometres)/1000000.0
:(feature.edge->unionWidthMicrometres>0
?feature.edge->unionWidthMicrometres
:feature.edge->smoothWidthMicrometres)/1000000.0;
caveVoid.minimum=SmoothMinMetres(caveVoid.minimum,featureVoid.minimum,width);
caveVoid.maximum=SmoothMinMetres(caveVoid.maximum,featureVoid.maximum,width);
}
@@ -5289,6 +5432,11 @@ public:
result.minimum=std::max(HostRockMetres,-caveVoid.maximum);
result.maximum=std::max(HostRockMetres,-caveVoid.minimum);
}
// The final lattice field owns an extra support cap after feature union.
// Lower only the interval minimum when a box could touch that cap; the
// untouched maximum remains a sound bound when only part of the box does.
if(std::isfinite(latticeFloorSupportLowerBound))
result.minimum=std::min(result.minimum,latticeFloorSupportLowerBound);
// Cover floating-point rounding in interval endpoints and the field's fixed-point
// coordinate conversion while keeping the interval tight compared with one micron.
constexpr double OutwardPaddingMetres=1.0e-6;
@@ -5356,9 +5504,10 @@ public:
bIslandOtherStrateOverlap=true;
if(CaveBound&&CaveBound!=Bound)bBadPlan=true;
CaveBound=Bound;
if(!Bound->Resolved||Bound->Resolved->recipeId!="Core.Caves.PrimitiveField"
||Bound->Resolved->recipeVersion!=1
||!Bound->Resolved->passageCapabilities.hasNativeCaveField
if(!Bound->Resolved||!VoxelForgeCore::HasNativeGraphField(*Bound->Resolved)
||(VoxelForgeCore::IsNativeCaveFieldStratum(*Bound->Resolved)
?!Bound->Resolved->passageCapabilities.hasNativeCaveField
:!Bound->Resolved->passageCapabilities.hasNativeLatticeField)
||!std::get_if<FCavesFieldAdapter>(&Bound->Evaluator))bBadPlan=true;
}
else
@@ -6768,9 +6917,10 @@ private:
const auto* bound=FindBoundStratumForManagerZ(Z);
if(!bound)return density;
if(bound->Kind==FBoundStratumField::EKind::Caves
&&bound->Resolved&&bound->Resolved->recipeId=="Core.Caves.PrimitiveField"
&&bound->Resolved->recipeVersion==1
&&bound->Resolved->passageCapabilities.hasNativeCaveField)
&&bound->Resolved&&VoxelForgeCore::HasNativeGraphField(*bound->Resolved)
&&(VoxelForgeCore::IsNativeCaveFieldStratum(*bound->Resolved)
?bound->Resolved->passageCapabilities.hasNativeCaveField
:bound->Resolved->passageCapabilities.hasNativeLatticeField))
return ApplyResolvedNativeCavesField(density,X,Y,Z,*bound,tileQueries,scoped);
const double micronsPerVoxel=Owner->CoreVoxelSizeMicrons;
const std::array<double,3> worldMicrometres={X*micronsPerVoxel,
@@ -6937,6 +7087,10 @@ private:
candidates=&localCandidates;
}
double caveVoid=std::numeric_limits<double>::infinity();
double latticeFloorSupportMinimum=std::numeric_limits<double>::infinity();
const bool nativeLattice=bound.Resolved
&&VoxelForgeCore::IsNativeLatticeFieldStratum(*bound.Resolved)
&&bound.Resolved->passageCapabilities.hasNativeLatticeField;
for(const std::uint32_t index:*candidates)
{
if(index>=CavernRuntime.size())continue;
@@ -6963,9 +7117,16 @@ private:
else if(feature.edge&&!feature.segments.empty())
{
double tube=std::numeric_limits<double>::infinity();
double nearestFloor=0.0,nearestGrade=0.0;
for(const auto& segment:feature.segments)
double nearestFloor=0.0,nearestGrade=0.0,nearestRadial=0.0;
const double radius=feature.edge->boreRadiusMicrometres/1000000.0;
const double height=feature.edge->boreHeightMicrometres/1000000.0;
if(!(radius>0.0&&height>0.0))continue;
const double minScale=std::min(radius,height);
std::uint32_t nearestSegment=std::numeric_limits<std::uint32_t>::max();
const auto evaluateSegment=[&](std::uint32_t segmentIndex)
{
if(segmentIndex>=feature.segments.size())return;
const auto& segment=feature.segments[segmentIndex];
double lengthSquared=0.0,dot=0.0;
for(std::size_t axis=0;axis<3;++axis)
{
@@ -6983,22 +7144,64 @@ private:
if(axis<2)radialSquared+=delta*delta;
else vertical=delta;
}
const double radius=feature.edge->boreRadiusMicrometres/1000000.0;
const double height=feature.edge->boreHeightMicrometres/1000000.0;
if(!(radius>0.0&&height>0.0))continue;
const double normalized=std::sqrt(radialSquared/(radius*radius)
+vertical*vertical/(height*height));
const double distance=(normalized-1.0)*std::min(radius,height);
if(distance<tube)
if(distance<tube||(distance==tube&&segmentIndex<nearestSegment))
{
tube=distance;
tube=distance;nearestSegment=segmentIndex;
nearestFloor=segment.start[2]+t*(segment.end[2]-segment.start[2]);
nearestRadial=std::sqrt(radialSquared);
const double horizontal=std::hypot(
segment.end[0]-segment.start[0],segment.end[1]-segment.start[1]);
nearestGrade=horizontal>1.0e-9
?(segment.end[2]-segment.start[2])/horizontal:0.0;
}
};
const auto nodeLowerBound=[&](const FCavernRuntimeFeature::FSegmentNode& node)
{
double gapSquared=0.0;
for(std::size_t axis=0;axis<3;++axis)
{
const double scale=axis==2?height:radius;
const double q=point[axis]/scale;
const double low=node.minimum[axis]/scale;
const double high=node.maximum[axis]/scale;
const double gap=q<low?low-q:q>high?q-high:0.0;
gapSquared+=gap*gap;
}
return (std::sqrt(gapSquared)-1.0)*minScale;
};
if(!feature.segmentNodes.empty())
{
const auto visitNode=[&](const auto& self,int nodeIndex)->void
{
if(nodeIndex<0||static_cast<std::size_t>(nodeIndex)>=feature.segmentNodes.size())return;
const auto& node=feature.segmentNodes[static_cast<std::size_t>(nodeIndex)];
if(nodeLowerBound(node)>tube)return;
if(node.left<0)
{
for(std::size_t offset=node.first;offset<node.end;++offset)
evaluateSegment(feature.segmentOrder[offset]);
return;
}
const double leftBound=nodeLowerBound(feature.segmentNodes[
static_cast<std::size_t>(node.left)]);
const double rightBound=nodeLowerBound(feature.segmentNodes[
static_cast<std::size_t>(node.right)]);
if(leftBound<=rightBound)
{self(self,node.left);self(self,node.right);}
else {self(self,node.right);self(self,node.left);}
};
visitNode(visitNode,0);
}
else for(std::uint32_t segment=0;segment<feature.segments.size();++segment)
evaluateSegment(segment);
const double supportDepth=feature.edge->floorSupportDepthMicrometres/1000000.0;
if(nativeLattice&&supportDepth>0.0&&nearestRadial<radius-0.12
&&point[2]<nearestFloor&&nearestFloor-point[2]<=supportDepth)
latticeFloorSupportMinimum=std::min(latticeFloorSupportMinimum,
(point[2]-nearestFloor)/std::sqrt(1.0+nearestGrade*nearestGrade));
const double floor=(nearestFloor-point[2])
/std::sqrt(1.0+nearestGrade*nearestGrade);
const double width=feature.edge->smoothWidthMicrometres/1000000.0;
@@ -7007,8 +7210,13 @@ private:
if(!std::isfinite(featureVoid))continue;
if(!std::isfinite(caveVoid))caveVoid=featureVoid;
else caveVoid=SmoothMinMetres(caveVoid,featureVoid,
feature.room?feature.room->smoothWidthMicrometres/1000000.0
:feature.edge->smoothWidthMicrometres/1000000.0);
feature.room
?(feature.room->unionWidthMicrometres>0
?feature.room->unionWidthMicrometres
:feature.room->smoothWidthMicrometres)/1000000.0
:(feature.edge->unionWidthMicrometres>0
?feature.edge->unionWidthMicrometres
:feature.edge->smoothWidthMicrometres)/1000000.0);
}
// A versioned native caves recipe owns a solid host-rock field. The
// resolved interval oracle uses the same finite -32 m solid value; using
@@ -7016,8 +7224,13 @@ private:
// terrain detail into the cave floor and invalidate those proofs.
constexpr double HostRockMetres=-32.0;
const double hostDistance=HostRockMetres;
const double composed=std::isfinite(caveVoid)
double composed=std::isfinite(caveVoid)
?SmoothMaxMetres(hostDistance,-caveVoid,0.0):hostDistance;
// Keep the floor support owned by the lattice route after smooth-unioning
// neighboring room and corridor voids. This cap is limited to each record's
// explicit support depth so separated helix turns remain independent.
if(std::isfinite(latticeFloorSupportMinimum))
composed=std::min(composed,latticeFloorSupportMinimum);
return static_cast<float>(composed/metresPerVoxel);
}
+13 -13
View File
@@ -1435,7 +1435,7 @@ namespace VoxelForgeCore
const int32 firstSurfaceChunk=FloorDiv(boundsMin.Z,LayoutChunkWidth);
const int32 lastSurfaceChunk=FloorDiv(boundsMax.Z,LayoutChunkWidth);
const WorldAssemblyStratumPlanV1* selectedPlan=nullptr;
const ResolvedLayoutStratumV2* selectedCaveStratum=nullptr;
const ResolvedLayoutStratumV2* selectedGraphStratum=nullptr;
for(int32 chunkZ=firstSurfaceChunk;chunkZ<=lastSurfaceChunk;++chunkZ)
{
const int32 strateIndex=impl_->owner->Manager->GetStrateIndex(
@@ -1444,22 +1444,22 @@ namespace VoxelForgeCore
const auto resolved=std::find_if(impl_->resolvedLayout->strates.begin(),
impl_->resolvedLayout->strates.end(),[strateIndex](const auto& stratum)
{return stratum.strateIndex==strateIndex;});
const bool nativeCaves=resolved!=impl_->resolvedLayout->strates.end()
&&resolved->generatorKind=="Caves"
&&resolved->recipeId=="Core.Caves.PrimitiveField"
&&resolved->recipeVersion==1
&&resolved->passageCapabilities.hasNativeCaveField;
const bool nativeGraph=resolved!=impl_->resolvedLayout->strates.end()
&&HasNativeGraphField(*resolved)
&&(IsNativeCaveFieldStratum(*resolved)
?resolved->passageCapabilities.hasNativeCaveField
:resolved->passageCapabilities.hasNativeLatticeField);
const auto managerType=impl_->owner->Manager->GetGeneratorTypeForChunk(
FIntVector(0,0,chunkZ));
if(!nativeCaves&&managerType!=ECaveGeneratorType::SurfaceWorld)return false;
if(nativeCaves)
if(!nativeGraph&&managerType!=ECaveGeneratorType::SurfaceWorld)return false;
if(nativeGraph)
{
if(selectedCaveStratum&&selectedCaveStratum!=&*resolved)return false;
selectedCaveStratum=&*resolved;
if(selectedGraphStratum&&selectedGraphStratum!=&*resolved)return false;
selectedGraphStratum=&*resolved;
if(selectedPlan)return false;
continue;
}
if(selectedCaveStratum)return false;
if(selectedGraphStratum)return false;
const auto found=std::find_if(impl_->worldAssembly->plan.strates.begin(),
impl_->worldAssembly->plan.strates.end(),[strateIndex,chunkZ](const auto& plan)
{return plan.strateIndex==strateIndex&&chunkZ<=plan.topChunkZ
@@ -1468,9 +1468,9 @@ namespace VoxelForgeCore
||(selectedPlan&&selectedPlan!=&*found))return false;
selectedPlan=&*found;
}
if((!selectedCaveStratum&&(!selectedPlan
if((!selectedGraphStratum&&(!selectedPlan
||!Heightfield2D(impl_->worldAssembly->recipe,*selectedPlan).isValid()))
||(selectedCaveStratum&&selectedPlan))
||(selectedGraphStratum&&selectedPlan))
return false;
const double coreVoxelsPerOwnerChunk=static_cast<double>(