feat(core): native Core caves generator, slice 1 -- spine + room, whole view 0.73 s (L54); research R23

Core.Caves.PrimitiveField v1, opt-in per strate (legacy TunnelNetwork assets stay legacy): solid host rock
and one smooth union of two elliptical spine tubes and an ellipsoid room, each intersected with its rising
floor by cubic smooth-max (W 2.4 m, measured fillet 0.70 m, no trailing floor cap). Bound in the L40 field
table and L42 capabilities, BVH feature candidates, isTileShapeFree aware, exact interval boxes (0 misses).
Capsule flood fill arrival -> departure passes (headroom >= 0.3 m, grade <= 15 deg); the generalized
whole-surface audit passes with its controls failing. Cold whole view 0.727 s, near ring 0.19 s. Graph
ordering/serialization changes are scoped to native caves (the flagged L47 demo is unchanged); verify
tiers write captures under --out only. fast/medium/full PASS, Clang and GCC.
Research R23 (deepened): per-tile LOD0 kernel speed plan.

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 04:10:19 +01:00
1 parent 20f6c98ec5
commit 9458217d34
24 files changed
+471132 -55

No files matched your search

@@ -841,6 +841,9 @@ namespace VoxelForgeCore
std::int32_t strateIndex = -1;
// A single cubic Bezier in XY. Its four Z values are the cubic floor profile.
std::array<std::array<std::int64_t, 3>, 4> controlPointsMicrometres{};
// Resolve-time, error-bounded approximation of the cubic centerline. Each
// consecutive pair is a physical segment used by the native cave field.
std::vector<std::array<std::int64_t, 3>> polylineMicrometres;
std::int64_t boreRadiusMicrometres = 0;
std::int64_t boreHeightMicrometres = 0;
std::int64_t floorSupportDepthMicrometres = 0;
@@ -912,6 +915,7 @@ namespace VoxelForgeCore
bool hasSurfaceLanding = false;
bool hasFloatingIslandLanding = false;
bool usePassageAnchorForColumns = false;
bool hasNativeCaveField = false;
};
struct ResolvedLayoutInterfaceCapabilitiesV1
+106 -1
View File
@@ -621,7 +621,18 @@ namespace
if(point)out<<',';const auto& p=edge.controlPointsMicrometres[point];
out<<'['<<p[0]<<','<<p[1]<<','<<p[2]<<']';
}
out<<"],\"bore_radius_um\":"<<edge.boreRadiusMicrometres
out<<']';
if(!edge.polylineMicrometres.empty())
{
out<<",\"polyline_um\":[";
for(std::size_t point=0;point<edge.polylineMicrometres.size();++point)
{
if(point)out<<',';const auto& p=edge.polylineMicrometres[point];
out<<'['<<p[0]<<','<<p[1]<<','<<p[2]<<']';
}
out<<']';
}
out<<",\"bore_radius_um\":"<<edge.boreRadiusMicrometres
<<",\"bore_height_um\":"<<edge.boreHeightMicrometres
<<",\"floor_support_depth_um\":"<<edge.floorSupportDepthMicrometres
<<",\"smooth_width_um\":"<<edge.smoothWidthMicrometres
@@ -845,6 +856,8 @@ bool ResolvedLayoutSnapshotV3::finalize(std::string& error)
x.lowerInterfaceCapabilities={};
if(x.generatorKind=="SurfaceWorld")
x.passageCapabilities.hasSurfaceLanding=true;
else if(x.generatorKind=="Caves")
x.passageCapabilities.hasNativeCaveField=true;
else if(x.generatorKind=="FloatingIslands")
{
x.passageCapabilities.hasFloatingIslandLanding=true;
@@ -1235,6 +1248,15 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
||std::abs(endpoint[2]-room->floorMicrometres)>500000)
add("cavern_edge_portal",edge.stableId+" endpoint does not meet its room floor portal");
}
const auto* edgeStratum=findStratum(edge.strateIndex);
if(edgeStratum&&edgeStratum->generatorKind=="Caves"
&&edgeStratum->recipeId=="Core.Caves.PrimitiveField"
&&edgeStratum->recipeVersion==1
&&(edge.polylineMicrometres.size()<2
||edge.polylineMicrometres.front()!=edge.controlPointsMicrometres.front()
||edge.polylineMicrometres.back()!=edge.controlPointsMicrometres.back()))
add("cavern_edge_polyline",edge.stableId+
" has no resolve-time polyline with the cubic endpoints");
}
}
if(oreVeins.empty())
@@ -1409,6 +1431,13 @@ std::optional<ResolvedLayoutSnapshotV3> ResolvedLayoutSnapshotV3::FromJson(
for(std::size_t axis=0;axis<3&&axis<point.size();++axis)
edge.controlPointsMicrometres[i][axis]=point[axis].Integer();
}
for(const auto& item:v.Get("polyline_um").AsArray())
{
const auto& point=item.AsArray();
if(point.size()!=3)continue;
edge.polylineMicrometres.push_back({point[0].Integer(),
point[1].Integer(),point[2].Integer()});
}
const auto readTriplet=[&](std::string_view key,std::array<std::int64_t,3>& target)
{
const auto& values=v.Get(key).AsArray();
@@ -1594,6 +1623,13 @@ bool ResolveResolvedCavernGraphV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
candidate.cavernGraphVocabularyVersion=ResolvedLayoutCavernGraphVocabularyVersionV1;
candidate.cavernRooms.assign(rooms.begin(),rooms.end());
candidate.cavernEdges.assign(edges.begin(),edges.end());
const auto isNativeCavesStrate=[&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()&&found->generatorKind=="Caves"
&&found->recipeId=="Core.Caves.PrimitiveField"&&found->recipeVersion==1;
};
for(auto& room:candidate.cavernRooms)
{
// The shared JSON value type stores numbers as IEEE-754 doubles. Keep the
@@ -1614,6 +1650,75 @@ bool ResolveResolvedCavernGraphV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
{
edge.irregularitySeed=LayoutStampSeed(seed,edge.strateIndex,edge.stableId)
&0x001fffffffffffffull;
using FCurvePoint=std::array<double,3>;
std::array<FCurvePoint,4> curve{};
for(std::size_t point=0;point<curve.size();++point)
for(std::size_t axis=0;axis<3;++axis)
curve[point][axis]=static_cast<double>(edge.controlPointsMicrometres[point][axis]);
const auto pointSegmentDistance=[](const FCurvePoint& point,
const FCurvePoint& a,const FCurvePoint& b)
{
double lengthSquared=0.0,dot=0.0;
for(std::size_t axis=0;axis<3;++axis)
{
const double delta=b[axis]-a[axis];
lengthSquared+=delta*delta;dot+=(point[axis]-a[axis])*delta;
}
const double t=lengthSquared>0.0
?std::clamp(dot/lengthSquared,0.0,1.0):0.0;
double distanceSquared=0.0;
for(std::size_t axis=0;axis<3;++axis)
{
const double delta=point[axis]-(a[axis]+t*(b[axis]-a[axis]));
distanceSquared+=delta*delta;
}
return std::sqrt(distanceSquared);
};
edge.polylineMicrometres.clear();
if(isNativeCavesStrate(edge.strateIndex))
{
edge.polylineMicrometres.push_back(edge.controlPointsMicrometres.front());
constexpr double MaximumChordErrorMicrometres=10000.0;
bool polylineValid=true;
const auto subdivide=[&](const auto& self,const std::array<FCurvePoint,4>& points,
int depth)->void
{
if(!polylineValid)return;
const double flatness=std::max(pointSegmentDistance(points[1],points[0],points[3]),
pointSegmentDistance(points[2],points[0],points[3]));
if(flatness<=MaximumChordErrorMicrometres)
{
std::array<std::int64_t,3> endpoint{};
for(std::size_t axis=0;axis<3;++axis)
{
if(!std::isfinite(points[3][axis])
||std::abs(points[3][axis])>9.0e15)
{polylineValid=false;return;}
endpoint[axis]=static_cast<std::int64_t>(std::llround(points[3][axis]));
}
edge.polylineMicrometres.push_back(endpoint);
if(edge.polylineMicrometres.size()>65536)polylineValid=false;
return;
}
if(depth>=20){polylineValid=false;return;}
std::array<FCurvePoint,4> left{},right{};
FCurvePoint a{},b{},c{},d{},e{},mid{};
for(std::size_t axis=0;axis<3;++axis)
{
a[axis]=(points[0][axis]+points[1][axis])*0.5;
b[axis]=(points[1][axis]+points[2][axis])*0.5;
c[axis]=(points[2][axis]+points[3][axis])*0.5;
d[axis]=(a[axis]+b[axis])*0.5;
e[axis]=(b[axis]+c[axis])*0.5;
mid[axis]=(d[axis]+e[axis])*0.5;
}
left={points[0],a,d,mid};right={mid,e,c,points[3]};
self(self,left,depth+1);self(self,right,depth+1);
};
subdivide(subdivide,curve,0);
if(!polylineValid)
{error="cavern edge "+edge.stableId+" exceeds the bounded polyline subdivision budget";return false;}
}
std::array<std::int64_t,3> low=edge.controlPointsMicrometres[0];
std::array<std::int64_t,3> high=low;
for(const auto& point:edge.controlPointsMicrometres)
+553 -34
View File
@@ -133,13 +133,28 @@ public:
}
};
struct FCavesFieldAdapter
{
std::uint32_t CoreVoxelSizeMicrometres=0;
bool densityAt(float X,float Y,float Z,float& OutDensity) const noexcept
{
if(CoreVoxelSizeMicrometres==0||!std::isfinite(X)
||!std::isfinite(Y)||!std::isfinite(Z))return false;
const double metresPerVoxel=CoreVoxelSizeMicrometres/1000000.0;
// A negative metre distance denotes host rock. Cave features are composed
// into this solid adapter once, in ApplyResolvedCavernGraph.
OutDensity=static_cast<float>(-32.0/metresPerVoxel);
return std::isfinite(OutDensity);
}
};
using FGeneratorFieldAdapterV1=std::variant<std::monostate,
FSurfaceWorldFieldAdapter,FFloatingIslandFieldAdapter,
FLegacyGeneratorFieldAdapter>;
FCavesFieldAdapter,FLegacyGeneratorFieldAdapter>;
struct FBoundStratumField
{
enum class EKind : uint8 { Legacy, SurfaceWorld, FloatingIslands };
enum class EKind : uint8 { Legacy, SurfaceWorld, FloatingIslands, Caves };
const VoxelForgeCore::ResolvedLayoutStratumV2* Resolved=nullptr;
const VoxelForgeCore::WorldAssemblyStratumPlanV1* SurfacePlan=nullptr;
const VoxelForgeCore::WorldAssemblyFloatingIslandPlanV1* IslandPlan=nullptr;
@@ -362,6 +377,15 @@ public:
}
}
}
else if(Stratum.generatorKind=="Caves"
&&Stratum.recipeId=="Core.Caves.PrimitiveField"
&&Stratum.recipeVersion==1
&&Stratum.passageCapabilities.hasNativeCaveField)
{
Bound.Kind=FBoundStratumField::EKind::Caves;
Bound.Evaluator.emplace<FCavesFieldAdapter>(
FCavesFieldAdapter{ResolvedLayout->coreVoxelSizeMicrometres});
}
if(std::holds_alternative<std::monostate>(Bound.Evaluator))
Bound.Evaluator.emplace<FLegacyGeneratorFieldAdapter>(
FLegacyGeneratorFieldAdapter{Owner?Owner->Generator.get():nullptr});
@@ -1791,8 +1815,14 @@ public:
const float composed=ApplyResolvedStratePassageCarves(
ApplyResolvedStrateBoundaryBand(graphDensity,X,Y,Z),X,Y,Z);
const float stamped=StampRuntime.empty()?composed:ApplyResolvedSdfStamps(composed,X,Y,Z);
// Keep the explicit graph floor cap after smooth unions, relief, passages,
// and decorative stamps so nothing can reopen the tunnel floor below profile.
const auto* bound=FindBoundStratumForManagerZ(Z);
if(bound&&bound->Kind==FBoundStratumField::EKind::Caves
&&bound->Resolved&&bound->Resolved->recipeId=="Core.Caves.PrimitiveField"
&&bound->Resolved->recipeVersion==1
&&bound->Resolved->passageCapabilities.hasNativeCaveField)
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.
return ApplyResolvedCavernFloorCaps(stamped,X,Y,Z);
}
@@ -2999,9 +3029,11 @@ public:
if(SharedDensitySource)return SharedDensitySource->DensityDispatchPathAt(X,Y,Z);
float Density=0.0f;
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";
if(NativeSurfaceDensityAt(X,Y,Z,Density))return "SurfaceWorld-native";
if(UsesLegacySurfaceField(X,Y,Z))return "SurfaceWorld-legacy";
const FBoundStratumField* Bound=FindBoundStratumForZ(Z);
if(Bound&&Bound->Resolved)
{
const std::string& Kind=Bound->Resolved->generatorKind;
@@ -3121,6 +3153,7 @@ public:
}
bool bHasLegacySurfaceSample=false;
bool bHasNativeIslandSample=false;
bool bHasNativeCavesSample=false;
std::array<float,8> NativeIslandDensity{};
std::array<bool,8> HasNativeIslandSample{};
for(int32 Lane=0;Lane<Count;++Lane)
@@ -3129,11 +3162,14 @@ public:
const float SampleY=static_cast<float>(Y);
const float SampleZ=static_cast<float>(Z);
bHasLegacySurfaceSample|=UsesLegacySurfaceField(SampleX,SampleY,SampleZ);
const auto* bound=FindBoundStratumForZ(SampleZ);
bHasNativeCavesSample|=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)
if(bHasLegacySurfaceSample||bHasNativeIslandSample||bHasNativeCavesSample)
{
for(int32 Lane=0;Lane<Count;++Lane)
{
@@ -3269,7 +3305,14 @@ private:
{
const VoxelForgeCore::ResolvedCavernRoomV1* room=nullptr;
const VoxelForgeCore::ResolvedCavernEdgeV1* edge=nullptr;
bool nativeCaves=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{};
};
std::vector<FSegment> segments;
std::array<float,3> centerVoxels{},halfExtentsVoxels{};
std::array<std::array<float,3>,4> controlPointsVoxels{};
float floorVoxels=0.0f;
@@ -3280,6 +3323,17 @@ private:
float irregularityWavelengthVoxels=0.0f;
std::uint64_t irregularitySeed=0;
};
struct FCavernSpatialNode
{
std::array<std::int64_t,3> minimumMicrometres{},maximumMicrometres{};
std::size_t first=0,end=0;
int left=-1,right=-1;
};
struct FMetricInterval
{
double minimum=0.0,maximum=0.0;
bool known=true;
};
int BuildStampSpatialNode(std::size_t first,std::size_t end)
{
@@ -3682,18 +3736,32 @@ private:
void BuildCavernSpatialIndex()
{
CavernRuntime.clear();
CavernRuntime.clear();CavernSpatialNodes.clear();CavernSpatialOrder.clear();
if(!ResolvedLayout||!Owner||Owner->CoreVoxelSizeMicrons==0
||ResolvedLayout->cavernGraphVocabularyVersion
!=VoxelForgeCore::ResolvedLayoutCavernGraphVocabularyVersionV1)return;
const auto isNativeCavesStrate=[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;
};
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.boundsMinimumMicrometres=room.boundsMinimumMicrometres;
runtime.boundsMaximumMicrometres=room.boundsMaximumMicrometres;
for(std::size_t axis=0;axis<3;++axis)
{
runtime.centerMetres[axis]=room.centerMicrometres[axis]/1000000.0;
runtime.halfExtentsMetres[axis]=room.halfExtentsMicrometres[axis]/1000000.0;
runtime.centerVoxels[axis]=static_cast<float>(
static_cast<double>(room.centerMicrometres[axis])/micronsPerVoxel);
runtime.halfExtentsVoxels[axis]=static_cast<float>(
@@ -3713,6 +3781,7 @@ private:
for(const auto& edge:ResolvedLayout->cavernEdges)
{
FCavernRuntimeFeature runtime;runtime.edge=&edge;
runtime.nativeCaves=isNativeCavesStrate(edge.strateIndex);
runtime.boundsMinimumMicrometres=edge.boundsMinimumMicrometres;
runtime.boundsMaximumMicrometres=edge.boundsMaximumMicrometres;
for(std::size_t point=0;point<4;++point)
@@ -3733,8 +3802,115 @@ private:
runtime.irregularityWavelengthVoxels=static_cast<float>(
static_cast<double>(edge.irregularityWavelengthMicrometres)/micronsPerVoxel);
runtime.irregularitySeed=edge.irregularitySeed;
runtime.segments.reserve(edge.polylineMicrometres.size()>1
?edge.polylineMicrometres.size()-1:0);
for(std::size_t point=1;point<edge.polylineMicrometres.size();++point)
{
FCavernRuntimeFeature::FSegment segment;
for(std::size_t axis=0;axis<3;++axis)
{
segment.start[axis]=edge.polylineMicrometres[point-1][axis]/1000000.0;
segment.end[axis]=edge.polylineMicrometres[point][axis]/1000000.0;
segment.minimum[axis]=std::min(segment.start[axis],segment.end[axis]);
segment.maximum[axis]=std::max(segment.start[axis],segment.end[axis]);
}
runtime.segments.push_back(segment);
}
CavernRuntime.push_back(runtime);
}
std::stable_sort(CavernRuntime.begin(),CavernRuntime.end(),[](const auto& a,const auto& b)
{
const std::int32_t leftStrate=a.room?a.room->strateIndex
:a.edge?a.edge->strateIndex:-1;
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;
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;
});
CavernSpatialOrder.resize(CavernRuntime.size());
for(std::size_t i=0;i<CavernSpatialOrder.size();++i)
CavernSpatialOrder[i]=static_cast<std::uint32_t>(i);
if(!CavernSpatialOrder.empty())BuildCavernSpatialNode(0,CavernSpatialOrder.size());
}
int BuildCavernSpatialNode(std::size_t first,std::size_t end)
{
FCavernSpatialNode node;node.first=first;node.end=end;
node.minimumMicrometres.fill(std::numeric_limits<std::int64_t>::max());
node.maximumMicrometres.fill(std::numeric_limits<std::int64_t>::min());
for(std::size_t offset=first;offset<end;++offset)
{
const auto& feature=CavernRuntime[CavernSpatialOrder[offset]];
for(std::size_t axis=0;axis<3;++axis)
{
node.minimumMicrometres[axis]=std::min(node.minimumMicrometres[axis],
feature.boundsMinimumMicrometres[axis]);
node.maximumMicrometres[axis]=std::max(node.maximumMicrometres[axis],
feature.boundsMaximumMicrometres[axis]);
}
}
const int index=static_cast<int>(CavernSpatialNodes.size());
CavernSpatialNodes.push_back(node);
if(end-first>8)
{
std::size_t axis=0;
for(std::size_t candidate=1;candidate<3;++candidate)
if(static_cast<long double>(node.maximumMicrometres[candidate])
-node.minimumMicrometres[candidate]
>static_cast<long double>(node.maximumMicrometres[axis])
-node.minimumMicrometres[axis])axis=candidate;
const std::size_t middle=first+(end-first)/2;
std::nth_element(CavernSpatialOrder.begin()+first,
CavernSpatialOrder.begin()+middle,CavernSpatialOrder.begin()+end,
[this,axis](std::uint32_t leftIndex,std::uint32_t rightIndex)
{
const auto& left=CavernRuntime[leftIndex];
const auto& right=CavernRuntime[rightIndex];
const long double leftCenter=static_cast<long double>(
left.boundsMinimumMicrometres[axis])
+left.boundsMaximumMicrometres[axis];
const long double rightCenter=static_cast<long double>(
right.boundsMinimumMicrometres[axis])
+right.boundsMaximumMicrometres[axis];
const std::string& leftId=left.room?left.room->stableId:left.edge->stableId;
const std::string& rightId=right.room?right.room->stableId:right.edge->stableId;
return leftCenter==rightCenter?leftId<rightId:leftCenter<rightCenter;
});
CavernSpatialNodes[index].left=BuildCavernSpatialNode(first,middle);
CavernSpatialNodes[index].right=BuildCavernSpatialNode(middle,end);
}
return index;
}
void QueryCavernSpatialNode(int nodeIndex,
const std::array<std::int64_t,3>& minimum,
const std::array<std::int64_t,3>& maximum,
std::vector<std::uint32_t>& result) const
{
if(nodeIndex<0)return;
const auto& node=CavernSpatialNodes[static_cast<std::size_t>(nodeIndex)];
for(std::size_t axis=0;axis<3;++axis)
if(node.maximumMicrometres[axis]<minimum[axis]
||node.minimumMicrometres[axis]>maximum[axis])return;
if(node.left<0)
{
for(std::size_t offset=node.first;offset<node.end;++offset)
{
const auto index=CavernSpatialOrder[offset];
const auto& feature=CavernRuntime[index];bool overlaps=true;
for(std::size_t axis=0;axis<3;++axis)
overlaps=overlaps&&feature.boundsMinimumMicrometres[axis]<=maximum[axis]
&&feature.boundsMaximumMicrometres[axis]>=minimum[axis];
if(overlaps)result.push_back(index);
}
return;
}
QueryCavernSpatialNode(node.left,minimum,maximum,result);
QueryCavernSpatialNode(node.right,minimum,maximum,result);
}
std::vector<std::uint32_t> BuildCavernFeatureTileList(
@@ -3742,7 +3918,7 @@ private:
const std::array<std::int64_t,3>& maximumSubvoxels) const
{
std::vector<std::uint32_t> result;
if(CavernRuntime.empty())return result;
if(CavernRuntime.empty()||CavernSpatialNodes.empty())return result;
if(!Owner||Owner->CoreVoxelSizeMicrons==0)
{
result.resize(CavernRuntime.size());
@@ -3750,7 +3926,7 @@ private:
return result;
}
constexpr long double Q=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
std::array<long double,3> minimumMicrometres{},maximumMicrometres{};
std::array<std::int64_t,3> minimumMicrometres{},maximumMicrometres{};
for(std::size_t axis=0;axis<3;++axis)
{
if(minimumSubvoxels[axis]>maximumSubvoxels[axis])
@@ -3760,21 +3936,17 @@ private:
result[i]=static_cast<std::uint32_t>(i);
return result;
}
minimumMicrometres[axis]=static_cast<long double>(minimumSubvoxels[axis])
*Owner->CoreVoxelSizeMicrons/Q;
maximumMicrometres[axis]=static_cast<long double>(maximumSubvoxels[axis])
*Owner->CoreVoxelSizeMicrons/Q;
}
for(std::size_t index=0;index<CavernRuntime.size();++index)
{
const auto& feature=CavernRuntime[index];bool overlap=true;
for(std::size_t axis=0;axis<3;++axis)
overlap=overlap&&feature.boundsMaximumMicrometres[axis]
>=minimumMicrometres[axis]
&&feature.boundsMinimumMicrometres[axis]
<=maximumMicrometres[axis];
if(overlap)result.push_back(static_cast<std::uint32_t>(index));
minimumMicrometres[axis]=static_cast<std::int64_t>(std::floor(
static_cast<long double>(minimumSubvoxels[axis])
*Owner->CoreVoxelSizeMicrons/Q));
maximumMicrometres[axis]=static_cast<std::int64_t>(std::ceil(
static_cast<long double>(maximumSubvoxels[axis])
*Owner->CoreVoxelSizeMicrons/Q));
}
QueryCavernSpatialNode(0,minimumMicrometres,maximumMicrometres,result);
// Runtime order is legacy insertion order for legacy cavern graphs and
// canonical stable-ID order within native caves strates.
std::sort(result.begin(),result.end());
return result;
}
@@ -4892,6 +5064,7 @@ public:
static_cast<std::int64_t>(BMax)*Q};
if(!BuildStampTileList(minimum,maximum).empty())return false;
}
std::vector<std::uint32_t> caveCandidates;
if(!CavernRuntime.empty())
{
constexpr std::int64_t Q=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
@@ -4912,9 +5085,20 @@ public:
for(int32 ChunkZ=FirstChunk;ChunkZ<=LastChunk;++ChunkZ)
{
const auto* Candidate=FindBoundStratumForChunk(ChunkZ);
if(!Candidate||Candidate->Kind!=FBoundStratumField::EKind::SurfaceWorld
||!Candidate->SurfacePlan||ChunkZ<Candidate->SurfacePlan->bottomChunkZ
if(!Candidate)return false;
if(Candidate->Kind==FBoundStratumField::EKind::SurfaceWorld)
{
if(!Candidate->SurfacePlan||ChunkZ<Candidate->SurfacePlan->bottomChunkZ
||ChunkZ>Candidate->SurfacePlan->topChunkZ)return false;
}
else if(Candidate->Kind==FBoundStratumField::EKind::Caves)
{
if(!Candidate->Resolved||Candidate->Resolved->recipeId!="Core.Caves.PrimitiveField"
||Candidate->Resolved->recipeVersion!=1
||!Candidate->Resolved->passageCapabilities.hasNativeCaveField
||!std::get_if<FCavesFieldAdapter>(&Candidate->Evaluator))return false;
}
else return false;
if(BoundPlan&&BoundPlan!=Candidate)return false;
BoundPlan=Candidate;
}
@@ -4932,6 +5116,178 @@ public:
return BoundPlan!=nullptr;
}
FMetricInterval ClassifyNativeCaveFieldBox(const int32 BMin[3],const int32 BMax[3],
std::int32_t strateIndex,const std::vector<std::uint32_t>& candidates) const
{
const double metresPerVoxel=Owner&&Owner->CoreVoxelSizeMicrons>0
?Owner->CoreVoxelSizeMicrons/1000000.0:0.0;
if(!(metresPerVoxel>0.0))return {0.0,0.0,false};
std::array<double,3> lo{},hi{};
for(std::size_t axis=0;axis<3;++axis)
{
lo[axis]=static_cast<double>(BMin[axis])*metresPerVoxel;
hi[axis]=static_cast<double>(BMax[axis])*metresPerVoxel;
}
FMetricInterval caveVoid{std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::infinity(),true};
bool haveFeature=false;
for(const std::uint32_t index:candidates)
{
if(index>=CavernRuntime.size())continue;
const auto& feature=CavernRuntime[index];
const std::int32_t featureStrate=feature.room?feature.room->strateIndex
:feature.edge?feature.edge->strateIndex:-1;
if(featureStrate!=strateIndex)continue;
FMetricInterval featureVoid;
if(feature.room)
{
double normalizedMinimumSquared=0.0,normalizedMaximumSquared=0.0;
double meanRadius=0.0;
for(std::size_t axis=0;axis<3;++axis)
{
const double radius=feature.halfExtentsMetres[axis];
if(!(radius>0.0))return {0.0,0.0,false};
const double center=feature.centerMetres[axis];
const double deltaMinimum=center<lo[axis]?lo[axis]-center
:center>hi[axis]?center-hi[axis]:0.0;
const double deltaMaximum=std::max(std::abs(lo[axis]-center),
std::abs(hi[axis]-center));
normalizedMinimumSquared+=(deltaMinimum/radius)*(deltaMinimum/radius);
normalizedMaximumSquared+=(deltaMaximum/radius)*(deltaMaximum/radius);
meanRadius+=radius/3.0;
}
const FMetricInterval ellipsoid{
(std::sqrt(normalizedMinimumSquared)-1.0)*meanRadius,
(std::sqrt(normalizedMaximumSquared)-1.0)*meanRadius,true};
const double floorZ=feature.room->floorMicrometres/1000000.0;
const FMetricInterval floor={floorZ-hi[2],floorZ-lo[2],true};
const double width=feature.room->smoothWidthMicrometres/1000000.0;
featureVoid={SmoothMaxMetres(ellipsoid.minimum,floor.minimum,width),
SmoothMaxMetres(ellipsoid.maximum,floor.maximum,width),true};
}
else if(feature.edge&&!feature.segments.empty())
{
const double radius=feature.edge->boreRadiusMicrometres/1000000.0;
const double height=feature.edge->boreHeightMicrometres/1000000.0;
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};
std::array<double,3> boxMinimum{},boxMaximum{},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];
}
double gapSquared=0.0;
for(std::size_t axis=0;axis<3;++axis)
{
const double segmentMinimum=std::min(segmentStart[axis],segmentEnd[axis]);
const double segmentMaximum=std::max(segmentStart[axis],segmentEnd[axis]);
const double gap=segmentMaximum<boxMinimum[axis]
?boxMinimum[axis]-segmentMaximum
:segmentMinimum>boxMaximum[axis]
?segmentMinimum-boxMaximum[axis]:0.0;
gapSquared+=gap*gap;
}
const double distanceLower=std::sqrt(gapSquared);
const std::array<double,3> boxCenter={
(boxMinimum[0]+boxMaximum[0])*0.5,
(boxMinimum[1]+boxMaximum[1])*0.5,
(boxMinimum[2]+boxMaximum[2])*0.5};
double lengthSquared=0.0,dot=0.0;
for(std::size_t axis=0;axis<3;++axis)
{
const double delta=segmentEnd[axis]-segmentStart[axis];
lengthSquared+=delta*delta;
dot+=(boxCenter[axis]-segmentStart[axis])*delta;
}
const double t=lengthSquared>0.0
?std::clamp(dot/lengthSquared,0.0,1.0):0.0;
double farthestSquared=0.0;
for(std::size_t axis=0;axis<3;++axis)
{
const double fixedPoint=segmentStart[axis]
+t*(segmentEnd[axis]-segmentStart[axis]);
const double farthest=std::max(
std::abs(boxMinimum[axis]-fixedPoint),
std::abs(boxMaximum[axis]-fixedPoint));
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(!std::isfinite(tubeMinimum)||!std::isfinite(tubeMaximum)
||!std::isfinite(floorMinimum)||!std::isfinite(floorMaximum))
return {0.0,0.0,false};
const double scaleMinimum=1.0/std::sqrt(1.0+maximumGrade*maximumGrade);
const double numeratorMinimum=floorMinimum-hi[2];
const double numeratorMaximum=floorMaximum-lo[2];
const std::array<double,4> products={numeratorMinimum*scaleMinimum,
numeratorMinimum,numeratorMaximum*scaleMinimum,numeratorMaximum};
const FMetricInterval floor={
*std::min_element(products.begin(),products.end()),
*std::max_element(products.begin(),products.end()),true};
const FMetricInterval tube={tubeMinimum,tubeMaximum,true};
const double width=feature.edge->smoothWidthMicrometres/1000000.0;
featureVoid={SmoothMaxMetres(tube.minimum,floor.minimum,width),
SmoothMaxMetres(tube.maximum,floor.maximum,width),true};
}
else return {0.0,0.0,false};
if(!std::isfinite(featureVoid.minimum)||!std::isfinite(featureVoid.maximum))
return {0.0,0.0,false};
if(!haveFeature)
{
caveVoid=featureVoid;haveFeature=true;
}
else
{
const double width=feature.room
?feature.room->smoothWidthMicrometres/1000000.0
:feature.edge->smoothWidthMicrometres/1000000.0;
caveVoid.minimum=SmoothMinMetres(caveVoid.minimum,featureVoid.minimum,width);
caveVoid.maximum=SmoothMinMetres(caveVoid.maximum,featureVoid.maximum,width);
}
}
constexpr double HostRockMetres=-32.0;
FMetricInterval result{HostRockMetres,HostRockMetres,true};
if(haveFeature)
{
result.minimum=std::max(HostRockMetres,-caveVoid.maximum);
result.maximum=std::max(HostRockMetres,-caveVoid.minimum);
}
// 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;
result.minimum=std::nextafter(result.minimum-OutwardPaddingMetres,
-std::numeric_limits<double>::infinity());
result.maximum=std::nextafter(result.maximum+OutwardPaddingMetres,
std::numeric_limits<double>::infinity());
result.known=std::isfinite(result.minimum)&&std::isfinite(result.maximum)
&&result.minimum<=result.maximum;
return result;
}
FBoxClassification ClassifyBoxDetailed(const FIntVector& Origin,int32 Step,int32 Cells,
bool bUseNativeLatticeProof=false) const
{
@@ -4946,8 +5302,11 @@ public:
const VoxelForgeCore::WorldAssemblyFloatingIslandPlanV1* IslandPlan=nullptr;
const FSurfaceWorldFieldAdapter* SurfaceField=nullptr;
const FFloatingIslandFieldAdapter* IslandField=nullptr;
bool bSurfaceOverlap=false,bIslandOverlap=false,bOtherStrateOverlap=false;
const FBoundStratumField* CaveBound=nullptr;
bool bSurfaceOverlap=false,bIslandOverlap=false,bCavesOverlap=false;
bool bOtherStrateOverlap=false,bLegacyOverlap=false,bCaveOtherStrateOverlap=false;
bool bIslandOtherStrateOverlap=false,bBadPlan=false;
std::vector<std::uint32_t> caveCandidates;
if(Owner&&ResolvedLayout)
{
const float LayoutWidth=static_cast<float>(GetResolvedLayoutChunkWidthVoxels());
@@ -4978,14 +5337,28 @@ public:
IslandField=std::get_if<FFloatingIslandFieldAdapter>(
&Bound->Evaluator);
}
else
{bOtherStrateOverlap=true;bIslandOtherStrateOverlap=true;}
else if(Bound&&Bound->Kind==FBoundStratumField::EKind::Caves)
{
bCavesOverlap=true;bOtherStrateOverlap=true;
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
||!std::get_if<FCavesFieldAdapter>(&Bound->Evaluator))bBadPlan=true;
}
else
{bOtherStrateOverlap=true;bIslandOtherStrateOverlap=true;bLegacyOverlap=true;}
}
bCaveOtherStrateOverlap=bCavesOverlap
&&(bSurfaceOverlap||bIslandOverlap||bLegacyOverlap);
}
const auto Finish=[&](EVoxelTileClass Verdict,bool bUnknown=false)
{
const bool bNative=(bSurfaceOverlap&&!bOtherStrateOverlap)
||(bIslandOverlap&&!bIslandOtherStrateOverlap);
||(bIslandOverlap&&!bIslandOtherStrateOverlap)
||(bCavesOverlap&&!bCaveOtherStrateOverlap);
FBoxClassification Result{Verdict,bNative,bUnknown};
auto& Counts=ThreadClassificationStats();
Counts.Milliseconds+=std::chrono::duration<double,std::milli>(Clock::now()-Start).count();
@@ -5020,7 +5393,8 @@ public:
static_cast<std::int64_t>(BMax[0])*Q,
static_cast<std::int64_t>(BMax[1])*Q,
static_cast<std::int64_t>(BMax[2])*Q};
if(!BuildCavernFeatureTileList(minimum,maximum).empty())
caveCandidates=BuildCavernFeatureTileList(minimum,maximum);
if(!caveCandidates.empty()&&!bCavesOverlap)
return Finish(EVoxelTileClass::Mixed,true);
}
if(ResolvedPassageIntersectsBox(BMin,BMax))
@@ -5050,7 +5424,10 @@ public:
&&BMin[1]>=NativeOnlyMinimum.Y&&BMin[2]>=NativeOnlyMinimum.Z
&&BMax[0]<=NativeOnlyMaximum.X&&BMax[1]<=NativeOnlyMaximum.Y
&&BMax[2]<=NativeOnlyMaximum.Z;
if(!bInsideProofBox||!bSurfaceOverlap||bIslandOverlap||bOtherStrateOverlap
const bool bSupportedNative=(bSurfaceOverlap&&!bOtherStrateOverlap)
||(bIslandOverlap&&!bIslandOtherStrateOverlap)
||(bCavesOverlap&&!bCaveOtherStrateOverlap);
if(!bInsideProofBox||!bSupportedNative
||bBadPlan||!EditBrushes.empty())
{
RecordNativeOnlyViolation();
@@ -5060,13 +5437,15 @@ public:
// A box crossing recipes cannot use one field interval; unknown composition stays Mixed.
// Une boite qui traverse plusieurs recettes reste Mixed, car l'intervalle d'un seul champ ne suffit pas.
if(bBadPlan||(bSurfaceOverlap&&(bIslandOverlap||bOtherStrateOverlap))
||(bIslandOverlap&&bIslandOtherStrateOverlap))
||(bIslandOverlap&&bIslandOtherStrateOverlap)
||(bCavesOverlap&&(bCaveOtherStrateOverlap||bSurfaceOverlap||bIslandOverlap)))
return Finish(EVoxelTileClass::Mixed,true);
// The native interval path below is the exact field selected first by DensityAtLattice
// for SurfaceWorld and FloatingIslands. Legacy analytic primitives/layer bounds cannot
// modify that field; only the explicit edit transaction above can. Keep their guards on
// the legacy classifier path, where those overlays do participate in density.
if(!bNativeOnly&&(!bUseNativeLatticeProof||(!bSurfaceOverlap&&!bIslandOverlap)))
if(!bNativeOnly&&!bCavesOverlap&&(!bUseNativeLatticeProof
||(!bSurfaceOverlap&&!bIslandOverlap)))
{
for(int32 BZ=FloorDiv(BMin[2],ActiveSpatialBinSize);BZ<=FloorDiv(BMax[2],ActiveSpatialBinSize);++BZ)
for(int32 BY=FloorDiv(BMin[1],ActiveSpatialBinSize);BY<=FloorDiv(BMax[1],ActiveSpatialBinSize);++BY)
@@ -5144,6 +5523,17 @@ public:
if(Interval.maximumDensitySubvoxels<0)return Finish(EVoxelTileClass::AllSolid);
return Finish(EVoxelTileClass::Mixed);
}
if(bCavesOverlap)
{
if(!CaveBound||!std::get_if<FCavesFieldAdapter>(&CaveBound->Evaluator))
return Finish(EVoxelTileClass::Mixed,true);
const auto interval=ClassifyNativeCaveFieldBox(BMin,BMax,
CaveBound->StrateIndex,caveCandidates);
if(!interval.known)return Finish(EVoxelTileClass::Mixed,true);
if(interval.minimum>0.0)return Finish(EVoxelTileClass::AllAir);
if(interval.maximum<0.0)return Finish(EVoxelTileClass::AllSolid);
return Finish(EVoxelTileClass::Mixed);
}
const bool bRock=EvaluateFast((BMin[0]+BMax[0])*0.5,
(BMin[1]+BMax[1])*0.5,(BMin[2]+BMax[2])*0.5);
return Finish(bRock?EVoxelTileClass::AllSolid:EVoxelTileClass::AllAir);
@@ -5662,7 +6052,13 @@ private:
if(!(LayoutWidth>0.0f))return false;
const int32 ChunkZ=FMath::FloorToInt(Z/LayoutWidth);
const FBoundStratumField* Bound=FindBoundStratumForChunk(ChunkZ);
if(!Bound||Bound->Kind!=FBoundStratumField::EKind::SurfaceWorld
if(!Bound)return false;
if(Bound->Kind==FBoundStratumField::EKind::Caves)
{
const auto* Field=std::get_if<FCavesFieldAdapter>(&Bound->Evaluator);
return Field&&Field->densityAt(X,Y,Z,OutDensity);
}
if(Bound->Kind!=FBoundStratumField::EKind::SurfaceWorld
||!Bound->SurfacePlan||ChunkZ<Bound->SurfacePlan->bottomChunkZ
||ChunkZ>Bound->SurfacePlan->topChunkZ)return false;
const auto* Field=std::get_if<FSurfaceWorldFieldAdapter>(&Bound->Evaluator);
@@ -6194,6 +6590,11 @@ private:
if(scoped&&!tileQueries->hasCavernCandidates())return density;
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)
return ApplyResolvedNativeCavesField(density,X,Y,Z,*bound,tileQueries,scoped);
const double micronsPerVoxel=Owner->CoreVoxelSizeMicrons;
const std::array<double,3> worldMicrometres={X*micronsPerVoxel,
Y*micronsPerVoxel,Z*micronsPerVoxel};
@@ -6327,6 +6728,122 @@ private:
return composed;
}
static double SmoothMinMetres(double a,double b,double width)
{
if(!(width>0.0))return std::min(a,b);
const double h=std::max(width-std::abs(a-b),0.0)/width;
return std::min(a,b)-(h*h*h)*(width/6.0);
}
static double SmoothMaxMetres(double a,double b,double width)
{return -SmoothMinMetres(-a,-b,width);}
float ApplyResolvedNativeCavesField(float density,float X,float Y,float Z,
const FBoundStratumField& bound,const FScopedPassageTileQueries* tileQueries,
bool scoped) const
{
(void)density;
const double metresPerVoxel=Owner->CoreVoxelSizeMicrons/1000000.0;
const std::array<double,3> point={X*metresPerVoxel,Y*metresPerVoxel,
Z*metresPerVoxel};
std::vector<std::uint32_t> localCandidates;
const std::vector<std::uint32_t>* candidates=nullptr;
if(scoped)candidates=&tileQueries->cavernFeatureCandidates();
else
{
constexpr double Q=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
const std::array<std::int64_t,3> coordinate={
static_cast<std::int64_t>(std::llround(X*Q)),
static_cast<std::int64_t>(std::llround(Y*Q)),
static_cast<std::int64_t>(std::llround(Z*Q))};
localCandidates=BuildCavernFeatureTileList(coordinate,coordinate);
candidates=&localCandidates;
}
double caveVoid=std::numeric_limits<double>::infinity();
for(const std::uint32_t index:*candidates)
{
if(index>=CavernRuntime.size())continue;
const auto& feature=CavernRuntime[index];
const std::int32_t strateIndex=feature.room?feature.room->strateIndex
:feature.edge?feature.edge->strateIndex:-1;
if(strateIndex!=bound.StrateIndex)continue;
double featureVoid=std::numeric_limits<double>::infinity();
if(feature.room)
{
double normalizedSquared=0.0,meanRadius=0.0;
for(std::size_t axis=0;axis<3;++axis)
{
const double radius=feature.halfExtentsMetres[axis];
if(!(radius>0.0)){normalizedSquared=std::numeric_limits<double>::infinity();break;}
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;
const double floor=feature.room->floorMicrometres/1000000.0-point[2];
const double width=feature.room->smoothWidthMicrometres/1000000.0;
featureVoid=SmoothMaxMetres(ellipsoid,floor,width);
}
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 lengthSquared=0.0,dot=0.0;
for(std::size_t axis=0;axis<3;++axis)
{
const double delta=segment.end[axis]-segment.start[axis];
lengthSquared+=delta*delta;
dot+=(point[axis]-segment.start[axis])*delta;
}
const double t=lengthSquared>0.0
?std::clamp(dot/lengthSquared,0.0,1.0):0.0;
double radialSquared=0.0,vertical=0.0;
for(std::size_t axis=0;axis<3;++axis)
{
const double delta=point[axis]-(segment.start[axis]
+t*(segment.end[axis]-segment.start[axis]));
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)
{
tube=distance;
nearestFloor=segment.start[2]+t*(segment.end[2]-segment.start[2]);
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 double floor=(nearestFloor-point[2])
/std::sqrt(1.0+nearestGrade*nearestGrade);
const double width=feature.edge->smoothWidthMicrometres/1000000.0;
featureVoid=SmoothMaxMetres(tube,floor,width);
}
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);
}
// A versioned native caves recipe owns a solid host-rock field. The
// resolved interval oracle uses the same finite -32 m solid value; using
// the underlying legacy generator density here would leak its unrelated
// 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)
?SmoothMaxMetres(hostDistance,-caveVoid,0.0):hostDistance;
return static_cast<float>(composed/metresPerVoxel);
}
float ApplyResolvedCavernFloorCaps(float density,float X,float Y,float Z) const
{
if(CavernRuntime.empty()||!Owner||Owner->CoreVoxelSizeMicrons==0)return density;
@@ -9725,6 +10242,8 @@ private:
std::vector<std::uint32_t> OreVeinSpatialOrder;
std::vector<FOreVeinRuntime> OreVeinRuntime;
std::vector<FCavernRuntimeFeature> CavernRuntime;
std::vector<FCavernSpatialNode> CavernSpatialNodes;
std::vector<std::uint32_t> CavernSpatialOrder;
std::vector<VoxelForgeCore::WorldAssemblyFeatureBoundsV1> PassageFeatureBounds;
std::vector<FPassageTileFeature> PassageTileFeatures;
std::vector<std::uint32_t> PassageFeatureOrder;
+26 -3
View File
@@ -1421,13 +1421,31 @@ namespace VoxelForgeCore
const int32 firstSurfaceChunk=FloorDiv(boundsMin.Z,LayoutChunkWidth);
const int32 lastSurfaceChunk=FloorDiv(boundsMax.Z,LayoutChunkWidth);
const WorldAssemblyStratumPlanV1* selectedPlan=nullptr;
const ResolvedLayoutStratumV2* selectedCaveStratum=nullptr;
for(int32 chunkZ=firstSurfaceChunk;chunkZ<=lastSurfaceChunk;++chunkZ)
{
if(impl_->owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,chunkZ))
!=ECaveGeneratorType::SurfaceWorld)return false;
const int32 strateIndex=impl_->owner->Manager->GetStrateIndex(
static_cast<float>((static_cast<double>(chunkZ)+0.5)
*impl_->resolvedLayout->strateLayoutChunkSizeMicrometres/10000.0));
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 auto managerType=impl_->owner->Manager->GetGeneratorTypeForChunk(
FIntVector(0,0,chunkZ));
if(!nativeCaves&&managerType!=ECaveGeneratorType::SurfaceWorld)return false;
if(nativeCaves)
{
if(selectedCaveStratum&&selectedCaveStratum!=&*resolved)return false;
selectedCaveStratum=&*resolved;
if(selectedPlan)return false;
continue;
}
if(selectedCaveStratum)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
@@ -1436,7 +1454,9 @@ namespace VoxelForgeCore
||(selectedPlan&&selectedPlan!=&*found))return false;
selectedPlan=&*found;
}
if(!selectedPlan||!Heightfield2D(impl_->worldAssembly->recipe,*selectedPlan).isValid())
if((!selectedCaveStratum&&(!selectedPlan
||!Heightfield2D(impl_->worldAssembly->recipe,*selectedPlan).isValid()))
||(selectedCaveStratum&&selectedPlan))
return false;
const double coreVoxelsPerOwnerChunk=static_cast<double>(
@@ -1454,10 +1474,13 @@ namespace VoxelForgeCore
tileMinimum[2]*scale};
const std::int64_t subMaximum[3]={tileMaximum[0]*scale,tileMaximum[1]*scale,
tileMaximum[2]*scale};
if(selectedPlan)
{
for(const WorldAssemblyFeatureV1& feature:selectedPlan->features)
if(IntersectsBox(feature.minimumSubvoxels,feature.maximumSubvoxels,
subMinimum,subMaximum))return false;
if(RouteTouchesBox(*selectedPlan,subMinimum,subMaximum))return false;
}
if(ResolvedStampTouchesBox(*impl_->resolvedLayout,boundsMin,boundsMax,
impl_->owner->CoreVoxelSizeMicrons))return false;
if(ResolvedCavernGraphTouchesBox(*impl_->resolvedLayout,boundsMin,boundsMax,
@@ -0,0 +1,728 @@
#!/usr/bin/env python3
"""Audit every LOD0 mesh triangle across a resolved multi-feature cave graph."""
from __future__ import annotations
import argparse
import array
import json
import math
from pathlib import Path
import struct
import sys
ROOT = Path(__file__).resolve().parents[4]
CELL_M = 1.0
CORRIDOR_MARGIN_M = 3.0
OPEN_GROUND_EXTENSION_M = 5.0
WELD_M = 0.0001
class Triangle:
__slots__ = ("verts", "center", "area", "normal", "smooth", "quality",
"route_distance", "route_lateral", "route_s", "floor_z",
"route_radius", "route_height", "route_edge", "in_region", "reference")
def __init__(self, verts, area, normal, smooth, quality):
self.verts = verts
self.center = tuple(sum(v[i] for v in verts) / 3.0 for i in range(3))
self.area = area
self.normal = normal
self.smooth = smooth
self.quality = quality
self.route_distance = 0.0
self.route_lateral = 0.0
self.route_s = 0.0
self.floor_z = 0.0
self.route_radius = 0.0
self.route_height = 0.0
self.route_edge = -1
self.in_region = False
self.reference = False
def normalize(value):
length = math.sqrt(sum(item * item for item in value))
return tuple(item / length for item in value) if length > 1.0e-12 else (0.0, 0.0, 0.0)
def dot(a, b):
return sum(a[i] * b[i] for i in range(3))
def angle_degrees(a, b):
return math.degrees(math.acos(max(-1.0, min(1.0, dot(a, b)))))
def quantile(values, fraction):
if not values:
return 0.0
ordered = sorted(values)
return ordered[min(len(ordered) - 1, max(0, int(math.ceil(fraction * len(ordered))) - 1))]
def read_mesh(path: Path):
triangles = []
selected_tiles = 0
lod_counts = {}
with path.open("rb") as stream:
prefix = stream.read(60)
if len(prefix) != 60 or prefix[:8] != b"VFMSH001":
raise ValueError(f"{path}: invalid VFMSH001 mesh header")
version, voxel_um, tile_count = struct.unpack_from("<3I", prefix, 8)
if version != 1 or not voxel_um or tile_count > 1_000_000:
raise ValueError(f"{path}: unsupported mesh version or tile count")
for _ in range(tile_count):
header = stream.read(36)
if len(header) != 36:
raise ValueError(f"{path}: truncated tile header")
key = struct.unpack_from("<4i", header, 0)
surface_count, _surface_aux, vertex_count, index_count, mask_count = \
struct.unpack_from("<5I", header, 16)
lod_counts[key[3]] = lod_counts.get(key[3], 0) + 1
position_bytes = stream.read(vertex_count * 12)
normal_bytes = stream.read(vertex_count * 12)
index_bytes = stream.read(index_count * 4)
masks = stream.read(mask_count)
if (len(position_bytes) != vertex_count * 12
or len(normal_bytes) != vertex_count * 12
or len(index_bytes) != index_count * 4
or len(masks) != mask_count):
raise ValueError(f"{path}: truncated tile payload")
if key[3] != 0:
continue
selected_tiles += 1
positions = array.array("f")
positions.frombytes(position_bytes)
normals = array.array("f")
normals.frombytes(normal_bytes)
indices = array.array("I")
indices.frombytes(index_bytes)
if sys.byteorder != "little":
positions.byteswap()
normals.byteswap()
indices.byteswap()
for offset in range(0, index_count - 2, 3):
if masks and masks[offset // 3]:
continue
ids = indices[offset:offset + 3]
if max(ids) >= surface_count:
continue
verts = tuple(tuple(float(positions[index * 3 + axis]) for axis in range(3))
for index in ids)
edge_a = tuple(verts[1][axis] - verts[0][axis] for axis in range(3))
edge_b = tuple(verts[2][axis] - verts[0][axis] for axis in range(3))
cross = (edge_a[1] * edge_b[2] - edge_a[2] * edge_b[1],
edge_a[2] * edge_b[0] - edge_a[0] * edge_b[2],
edge_a[0] * edge_b[1] - edge_a[1] * edge_b[0])
cross_length = math.sqrt(dot(cross, cross))
if cross_length <= 1.0e-12:
continue
face_normal = tuple(value / cross_length for value in cross)
area = cross_length * 0.5
smooth_normal = normalize(tuple(
sum(float(normals[index * 3 + axis]) for index in ids) for axis in range(3)))
if dot(face_normal, smooth_normal) < 0.0:
face_normal = tuple(-value for value in face_normal)
edge_squares = 0.0
for a, b in ((verts[0], verts[1]), (verts[1], verts[2]),
(verts[2], verts[0])):
edge_squares += sum((a[i] - b[i]) ** 2 for i in range(3))
quality = 4.0 * math.sqrt(3.0) * area / max(edge_squares, 1.0e-15)
triangles.append(Triangle(verts, area, face_normal, smooth_normal, quality))
if stream.read(1):
raise ValueError(f"{path}: trailing bytes after mesh tiles")
if not triangles:
raise ValueError(f"{path}: no LOD0 surface triangles")
return triangles, voxel_um, selected_tiles, lod_counts
def bezier(points, t):
one = 1.0 - t
weights = (one ** 3, 3.0 * one * one * t, 3.0 * one * t * t, t ** 3)
return tuple(sum(weights[i] * points[i][axis] for i in range(4)) for axis in range(3))
def make_route(layout, phase_m=0.0):
edges = layout.get("cavern_edges", [])
rooms = layout.get("cavern_rooms", [])
if not edges or not rooms:
raise ValueError("cavern audit needs at least one resolved room and edge")
segments = []
routes = []
for edge_index, edge in enumerate(sorted(edges, key=lambda item: item["stable_id"])):
points = [[value / 1_000_000.0 for value in row]
for row in edge["control_points_um"]]
bore_radius = edge["bore_radius_um"] / 1_000_000.0
bore_height = edge["bore_height_um"] / 1_000_000.0
samples = [bezier(points, index / 160.0) for index in range(161)]
horizontal_s = [0.0]
for a, b in zip(samples, samples[1:]):
horizontal_s.append(horizontal_s[-1] + math.hypot(b[0] - a[0], b[1] - a[1]))
first_dx = samples[1][0] - samples[0][0]
first_dy = samples[1][1] - samples[0][1]
first_len = max(1.0e-9, math.hypot(first_dx, first_dy))
extension = (samples[0][0] - first_dx / first_len * OPEN_GROUND_EXTENSION_M,
samples[0][1] - first_dy / first_len * OPEN_GROUND_EXTENSION_M,
samples[0][2] - (samples[1][2] - samples[0][2]) / first_len
* OPEN_GROUND_EXTENSION_M)
route_points = [extension, *samples]
route_s = [-OPEN_GROUND_EXTENSION_M, *horizontal_s]
for i, (a, b) in enumerate(zip(route_points, route_points[1:])):
segments.append((a, b, route_s[i], route_s[i + 1],
bore_radius, bore_height, edge_index))
routes.append({"edge": edge, "points": samples, "radius": bore_radius,
"height": bore_height, "length": horizontal_s[-1], "index": edge_index})
resolved_rooms = [{"room": room,
"center": tuple(value / 1_000_000.0 for value in room["center_um"]),
"half": tuple(value / 1_000_000.0 for value in room["half_extents_um"]),
"floor": room["floor_um"] / 1_000_000.0}
for room in sorted(rooms, key=lambda item: item["stable_id"])]
return {
"segments": segments,
"edges": routes,
"rooms": resolved_rooms,
"phase_m": phase_m,
}
def route_nearest(point, route):
best = None
for a, b, s0, s1, radius, height, edge_index in route["segments"]:
dx, dy = b[0] - a[0], b[1] - a[1]
denominator = dx * dx + dy * dy
t = 0.0 if denominator <= 1.0e-12 else max(0.0, min(1.0,
((point[0] - a[0]) * dx + (point[1] - a[1]) * dy) / denominator))
x, y = a[0] + dx * t, a[1] + dy * t
distance = math.hypot(point[0] - x, point[1] - y)
if best is None or distance < best[0]:
segment_length = math.sqrt(max(denominator, 1.0e-12))
lateral = (dx * (point[1] - y) - dy * (point[0] - x)) / segment_length
best = (distance, s0 + (s1 - s0) * t,
a[2] + (b[2] - a[2]) * t + route["phase_m"],
lateral, radius, height, edge_index,
route["edges"][edge_index]["length"])
return best
def mark_region(triangles, route):
for tri in triangles:
x, y, z = tri.center
nearest = route_nearest(tri.center, route)
distance, distance_s, floor_z, lateral, radius, height, edge_index, edge_length = nearest
tri.route_distance = distance
tri.route_lateral = lateral
tri.route_s = distance_s
tri.floor_z = floor_z
tri.route_radius = radius
tri.route_height = height
tri.route_edge = edge_index
corridor = (-OPEN_GROUND_EXTENSION_M <= distance_s <= edge_length
and distance <= radius + CORRIDOR_MARGIN_M
and floor_z - CORRIDOR_MARGIN_M <= z
<= floor_z + height + CORRIDOR_MARGIN_M)
room_box = any(abs(x - room["center"][0]) <= room["half"][0] + CORRIDOR_MARGIN_M
and abs(y - room["center"][1]) <= room["half"][1] + CORRIDOR_MARGIN_M
and room["floor"] - CORRIDOR_MARGIN_M <= z
<= room["floor"] + 2.0 * room["half"][2] + CORRIDOR_MARGIN_M
for room in route["rooms"])
tri.in_region = corridor or room_box
inside_room = any(abs(x - room["center"][0]) <= room["half"][0] + 0.45
and abs(y - room["center"][1]) <= room["half"][1] + 0.45
for room in route["rooms"])
outside_bore = distance > radius + 0.45 and not inside_room
# Keep the reference on native, ground-facing terrain. The outer mesh
# also contains steep cavern walls and room edges, which are features.
tri.reference = tri.in_region and outside_bore and tri.smooth[2] >= 0.45
def vertex_key(point):
return tuple(int(round(value / WELD_M)) for value in point)
def make_edges(triangles, indices):
edge_map = {}
for index in indices:
tri = triangles[index]
points = tri.verts
for a, b in ((0, 1), (1, 2), (2, 0)):
ka, kb = vertex_key(points[a]), vertex_key(points[b])
if ka == kb:
continue
key = (ka, kb) if ka < kb else (kb, ka)
delta = tuple(points[a][axis] - points[b][axis] for axis in range(3))
length = math.sqrt(dot(delta, delta))
midpoint = tuple((points[a][axis] + points[b][axis]) * 0.5 for axis in range(3))
edge_map.setdefault(key, []).append((index, length, midpoint))
pairs = []
for incident in edge_map.values():
unique = {}
for index, length, midpoint in incident:
unique[index] = (length, midpoint)
incident = list(unique.items())
if len(incident) < 2:
continue
if len(incident) > 4:
incident = incident[:4]
for left in range(len(incident)):
for right in range(left + 1, len(incident)):
i, (length, midpoint) = incident[left]
j, _ = incident[right]
angle = max(angle_degrees(triangles[i].normal, triangles[j].normal),
0.75 * angle_degrees(triangles[i].smooth, triangles[j].smooth))
pairs.append((i, j, angle, length, midpoint))
return pairs
def cell_of(point):
return tuple(math.floor(value / CELL_M) for value in point)
def score_surface(triangles, considered, pairs, edge_cutoff, rough_cutoff,
ref_cell_density):
considered_set = set(considered)
variation = {index: 0.0 for index in considered}
max_jump = {index: 0.0 for index in considered}
severe_count = {index: 0.0 for index in considered}
for i, j, angle, length, _midpoint in pairs:
if i not in considered_set or j not in considered_set:
continue
weighted = angle * length * 0.5
variation[i] += weighted
variation[j] += weighted
max_jump[i] = max(max_jump[i], angle)
max_jump[j] = max(max_jump[j], angle)
if angle >= edge_cutoff:
severe_count[i] += 0.5
severe_count[j] += 0.5
cells = {}
for index in considered:
tri = triangles[index]
key = cell_of(tri.center)
cell = cells.setdefault(key, {"area": 0.0, "variation": 0.0, "severe": 0.0,
"sliver_area": 0.0, "slivers": 0, "triangles": 0, "max_jump": 0.0,
"reference_area": 0.0, "reference_variation": 0.0,
"reference_severe": 0.0, "reference_sliver_area": 0.0,
"reference_slivers": 0})
cell["area"] += tri.area
cell["variation"] += variation[index]
cell["severe"] += severe_count[index]
cell["triangles"] += 1
cell["max_jump"] = max(cell["max_jump"], max_jump[index])
sliver = tri.quality < 0.07 and tri.area < 0.12
if sliver:
cell["sliver_area"] += tri.area
cell["slivers"] += 1
if tri.reference:
cell["reference_area"] += tri.area
cell["reference_variation"] += variation[index]
cell["reference_severe"] += severe_count[index]
if sliver:
cell["reference_sliver_area"] += tri.area
cell["reference_slivers"] += 1
ref_rough = []
ref_density = []
ref_sliver_fraction = []
for cell in cells.values():
if cell["reference_area"] >= max(0.15, cell["area"] * 0.65):
ref_rough.append(cell["reference_variation"] / max(cell["reference_area"], 1.0e-9))
ref_density.append(cell["reference_severe"] / max(cell["reference_area"], 1.0e-9))
ref_sliver_fraction.append(cell["reference_sliver_area"]
/ max(cell["reference_area"], 1.0e-9))
reference_median = quantile(ref_rough, 0.5)
reference_mad = quantile([abs(value - reference_median) for value in ref_rough], 0.5)
cell_rough_limit = max(10.0, quantile(ref_rough, 0.995) * 1.5,
reference_median + 8.0 * reference_mad)
edge_density_limit = max(1.5, quantile(ref_density, 0.995) * 1.7,
ref_cell_density * 1.7)
sliver_limit = max(0.22, quantile(ref_sliver_fraction, 0.995) + 0.18)
hot = set()
for key, cell in cells.items():
area = max(cell["area"], 1.0e-9)
roughness = cell["variation"] / area
edge_density = cell["severe"] / area
sliver_fraction = cell["sliver_area"] / area
rough_cluster = roughness > cell_rough_limit and cell["severe"] >= 2.0
edge_cluster = cell["severe"] >= 4.0 and edge_density > edge_density_limit
sliver_cluster = cell["slivers"] >= 5 and sliver_fraction > sliver_limit
if rough_cluster or edge_cluster or sliver_cluster:
hot.add(key)
cell["roughness_deg_per_m"] = roughness
cell["severe_edge_density_m2"] = edge_density
cell["sliver_area_fraction"] = sliver_fraction
cell["reasons"] = [name for name, active in (("normal-variation", rough_cluster),
("edge-density", edge_cluster), ("slivers", sliver_cluster)) if active]
clusters = []
flagged_cells = set()
remaining = set(hot)
while remaining:
stack = [remaining.pop()]
members = []
while stack:
current = stack.pop()
members.append(current)
x, y, z = current
neighbors = [(x + dx, y + dy, z + dz)
for dx in (-1, 0, 1) for dy in (-1, 0, 1) for dz in (-1, 0, 1)
if dx or dy or dz]
for neighbor in neighbors:
if neighbor in remaining:
remaining.remove(neighbor)
stack.append(neighbor)
cells_in_cluster = [cells[key] for key in members]
cluster_area = sum(cell["area"] for cell in cells_in_cluster)
cluster_severe = sum(cell["severe"] for cell in cells_in_cluster)
cluster_slivers = sum(cell["slivers"] for cell in cells_in_cluster)
accepted = len(members) >= 2 and cluster_area >= 0.25 and \
(cluster_severe >= 8.0 or cluster_slivers >= 12)
if accepted:
flagged_cells.update(members)
clusters.append({"cells": len(members), "area_m2": cluster_area,
"severe_edges": cluster_severe, "sliver_triangles": cluster_slivers,
"bounds_m": [[min(key[i] for key in members) * CELL_M,
(max(key[i] for key in members) + 1) * CELL_M]
for i in range(3)],
"reasons": sorted({reason for cell in cells_in_cluster
for reason in cell["reasons"]})})
clusters.sort(key=lambda cluster: (cluster["severe_edges"], cluster["area_m2"]), reverse=True)
flagged_triangles = [index for index in considered
if cell_of(triangles[index].center) in flagged_cells]
return {"cells": cells, "hot_cells": hot, "flagged_cells": flagged_cells,
"clusters": clusters, "hot_triangles": flagged_triangles,
"thresholds": {"edge_angle_deg": edge_cutoff,
"roughness_deg_per_m": cell_rough_limit,
"severe_edge_density_m2": edge_density_limit,
"sliver_area_fraction": sliver_limit,
"reference_cell_roughness_p995_deg_per_m": quantile(ref_rough, 0.995),
"reference_cell_roughness_median_deg_per_m": reference_median}}
def floor_profile_audit(triangles, considered, region_indices, route):
"""Measure the whole floor patch as area-weighted bins, not selected rails."""
if route is None:
return {"present": False, "detected": False, "triangles": 0, "bins": 0,
"lateral_bands": 0, "ripple_amplitude_m": 0.0,
"max_adjacent_step_m": 0.0}
along_step = 0.25
lateral_step = 0.5
bins = {}
floor_triangles = 0
wall_triangles = 0
wall_bins = set()
for index in region_indices:
tri = triangles[index]
height = tri.center[2] - tri.floor_z
if (0.0 <= tri.route_s <= min(10.0,
route["edges"][tri.route_edge]["length"])
and tri.route_radius - 0.9 <= tri.route_distance
<= tri.route_radius + 1.0
and 0.45 <= height <= tri.route_height + 0.5
and abs(tri.smooth[2]) <= 0.45):
wall_triangles += 1
wall_bins.add(int(tri.route_s))
for index in considered:
tri = triangles[index]
if (tri.route_s < 0.0 or tri.route_s > min(10.0,
route["edges"][tri.route_edge]["length"])
or tri.route_distance > min(1.9, tri.route_radius - 0.1)
or abs(tri.center[2] - tri.floor_z) > 0.60
or tri.smooth[2] < 0.55):
continue
floor_triangles += 1
along_bin = int(math.floor(tri.route_s / along_step))
lateral_bin = int(math.floor((tri.route_lateral + 2.0) / lateral_step))
key = (lateral_bin, along_bin)
row = bins.setdefault(key, [0.0, 0.0, 0])
residual = tri.center[2] - tri.floor_z
row[0] += residual * tri.area
row[1] += tri.area
row[2] += 1
by_lateral = {}
for (lateral_bin, along_bin), (weighted_residual, area, count) in bins.items():
if area < 0.002:
continue
by_lateral.setdefault(lateral_bin, {})[along_bin] = weighted_residual / area
amplitudes = []
steps = []
valid_bins = 0
for values in by_lateral.values():
ordered = sorted(values.items())
run = []
runs = []
for item in ordered:
if run and item[0] != run[-1][0] + 1:
runs.append(run)
run = []
run.append(item)
if run:
runs.append(run)
for segment in runs:
if len(segment) < 8:
continue
valid_bins += len(segment)
raw = [value for _, value in segment]
highpass = []
for position, value in enumerate(raw):
nearby = raw[max(0, position - 4):min(len(raw), position + 5)]
highpass.append(value - sum(nearby) / len(nearby))
amplitudes.append(max(highpass) - min(highpass))
steps.extend(abs(raw[i] - raw[i - 1]) for i in range(1, len(raw)))
max_ripple = max(amplitudes, default=0.0) * 0.5
max_step = max(steps, default=0.0)
present = (floor_triangles >= 24 and valid_bins >= 8
and wall_triangles >= 24 and len(wall_bins) >= 3)
detected = present and (max_ripple > 0.015 or max_step > 0.050)
return {"present": present, "detected": detected, "triangles": floor_triangles,
"bins": valid_bins, "lateral_bands": len(by_lateral),
"wall_triangles": wall_triangles, "wall_bins": len(wall_bins),
"ripple_amplitude_m": max_ripple, "max_adjacent_step_m": max_step,
"thresholds": {"ripple_amplitude_m": 0.015, "adjacent_step_m": 0.050}}
def wall_band_measure(pairs, route, edge_cutoff):
"""Locate high dihedrals around the bore-foot and the L47 0.25 m band edge."""
if route is None:
return {}
bins = {}
for _i, _j, angle, _length, midpoint in pairs:
distance, route_s, floor_z, _lateral, radius, _height, _edge, _edge_length = \
route_nearest(midpoint, route)
height = midpoint[2] - floor_z
if (0.0 <= route_s <= 10.0 and abs(distance - radius) <= 0.75
and -0.25 <= height <= 0.875):
index = int(math.floor((height + 0.25) / 0.125))
entry = bins.setdefault(index, [])
entry.append(angle)
result = {}
for index, angles in sorted(bins.items()):
center_height = (index + 0.5) * 0.125 - 0.25
result[f"{center_height:.3f}"] = {
"edges": len(angles),
"over_reference_cutoff": sum(value >= edge_cutoff for value in angles),
"p95_dihedral_deg": quantile(angles, 0.95),
"max_dihedral_deg": max(angles, default=0.0),
}
return result
def radial_wall_measure(pairs, route, edge_cutoff):
"""Measure dihedrals across the hard bore-radius gate at the entrance foot."""
if route is None:
return {}
bins = {}
for _i, _j, angle, _length, midpoint in pairs:
distance, route_s, floor_z, _lateral, radius, _height, _edge, _edge_length = \
route_nearest(midpoint, route)
height = midpoint[2] - floor_z
if (-5.0 <= route_s <= 3.0 and -1.0 <= height <= 0.75
and abs(distance - radius) <= 1.0):
index = int(math.floor((distance - (radius - 1.0)) / 0.125))
bins.setdefault(index, []).append(angle)
return {f"{radius - 1.0 + (index + 0.5) * 0.125:.3f}": {
"edges": len(angles),
"over_reference_cutoff": sum(value >= edge_cutoff for value in angles),
"p95_dihedral_deg": quantile(angles, 0.95),
"max_dihedral_deg": max(angles, default=0.0),
} for index, angles in sorted(bins.items())}
def svg_for(path, triangles, route, hot_triangles, label):
selected = [triangles[index] for index in hot_triangles]
all_region = [tri for tri in triangles if tri.in_region]
points = [point for tri in all_region for point in tri.verts]
if not points:
return
xmin, xmax = min(p[0] for p in points), max(p[0] for p in points)
ymin, ymax = min(p[1] for p in points), max(p[1] for p in points)
width, height, margin = 1200.0, 900.0, 35.0
sx = (width - 2 * margin) / max(1.0e-6, xmax - xmin)
sy = (height - 2 * margin) / max(1.0e-6, ymax - ymin)
scale = min(sx, sy)
draw_w, draw_h = (xmax - xmin) * scale, (ymax - ymin) * scale
ox, oy = (width - draw_w) * 0.5, (height - draw_h) * 0.5
def project(point):
return ox + (point[0] - xmin) * scale, height - oy - (point[1] - ymin) * scale
lines = [f'<svg xmlns="http://www.w3.org/2000/svg" width="{int(width)}" height="{int(height)}" viewBox="0 0 {int(width)} {int(height)}">',
'<rect width="100%" height="100%" fill="#fbfbfa"/>',
f'<text x="28" y="28" font-family="sans-serif" font-size="19" fill="#222">{label}: whole-surface defect clusters (XY plan)</text>',
'<g fill="#c8ced2" fill-opacity="0.24" stroke="#8b959b" stroke-opacity="0.12" stroke-width="0.3">']
for tri in all_region:
coords = [project(p) for p in tri.verts]
lines.append('<polygon points="' + " ".join(f"{x:.1f},{y:.1f}" for x, y in coords)
+ '"/>')
lines.append('</g><g fill="#d7191c" fill-opacity="0.78" stroke="#7f0000" stroke-width="0.4">')
for tri in selected:
coords = [project(p) for p in tri.verts]
lines.append('<polygon points="' + " ".join(f"{x:.1f},{y:.1f}" for x, y in coords)
+ '"/>')
lines.append('</g>')
for feature in route["edges"]:
route_coords = [project(p) for p in feature["points"]]
lines.append('<polyline points="' + " ".join(f"{x:.1f},{y:.1f}" for x, y in route_coords)
+ '" fill="none" stroke="#1769aa" stroke-width="2"/>')
lines.append(f'<text x="28" y="{int(height - 24)}" font-family="sans-serif" font-size="15" fill="#333">Blue: intended route · Red: flagged triangle cells · Gray: remaining ROI triangles · flagged triangles: {len(selected)}</text>')
lines.append('</svg>')
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
def audit_case(label, mesh_path, layout_path, expect, focus, svg_dir, phase):
triangles, voxel_um, tile_count, lod_counts = read_mesh(mesh_path)
route = None
if layout_path != Path("-"):
layout = json.loads(layout_path.read_text(encoding="utf-8"))
route = make_route(layout, phase * voxel_um / 1_000_000.0)
mark_region(triangles, route)
region_indices = [i for i, tri in enumerate(triangles) if tri.in_region]
else:
region_indices = list(range(len(triangles)))
for tri in triangles:
tri.in_region = True
tri.reference = True
if len(region_indices) < 24:
raise ValueError(f"{label}: only {len(region_indices)} triangles intersect the ROI")
reference_indices = [i for i in region_indices if triangles[i].reference]
if len(reference_indices) < 24:
reference_indices = region_indices
for index in region_indices:
triangles[index].reference = True
edge_pairs = make_edges(triangles, region_indices)
reference_edges = [edge[2] for edge in edge_pairs
if triangles[edge[0]].reference and triangles[edge[1]].reference]
edge_cutoff = max(35.0, quantile(reference_edges, 0.995))
reference_set = set(reference_indices)
ref_roughness = {index: 0.0 for index in reference_indices}
for i, j, angle, length, _midpoint in edge_pairs:
if i in reference_set and j in reference_set:
ref_roughness[i] += angle * length * 0.5
ref_roughness[j] += angle * length * 0.5
ref_values = [ref_roughness[index] / max(triangles[index].area, 1.0e-9)
for index in reference_indices]
rough_cutoff = 10.0
ref_density = 0.0
ref_area = sum(triangles[index].area for index in reference_indices)
ref_severe = 0.0
for i, j, angle, _length, _midpoint in edge_pairs:
if i in reference_set and j in reference_set and angle >= edge_cutoff:
ref_severe += 1.0
ref_density = ref_severe / max(ref_area, 1.0e-9)
if focus == "floor":
considered = [i for i in region_indices if route is not None
and triangles[i].route_s >= 1.5
and triangles[i].route_s <= 10.0
and triangles[i].route_distance <= 1.25
and abs(triangles[i].center[2] - triangles[i].floor_z) <= 0.24
and triangles[i].smooth[2] >= 0.55]
else:
considered = region_indices
if len(considered) < 24:
raise ValueError(f"{label}: focus={focus} has only {len(considered)} triangles")
scores = score_surface(triangles, considered, edge_pairs, edge_cutoff,
rough_cutoff, ref_density)
floor_profile = floor_profile_audit(triangles, considered, region_indices, route)
wall_band = wall_band_measure(edge_pairs, route, edge_cutoff)
radial_wall = radial_wall_measure(edge_pairs, route, edge_cutoff)
found = bool(scores["clusters"]) or floor_profile["detected"]
passed = expect == "measure" or (expect == "fail" and found) or (expect == "pass" and not found)
if svg_dir and route is not None:
svg_for(svg_dir / f"flagged-regions-{label}.svg", triangles, route,
scores["hot_triangles"], label)
cluster_summary = scores["clusters"][:8]
result = {
"label": label,
"mesh": str(mesh_path),
"layout": None if layout_path == Path("-") else str(layout_path),
"focus": focus,
"grid_phase_h": phase,
"grid_phase_m": phase * voxel_um / 1_000_000.0,
"expected": expect,
"observed": "fail" if found else "pass",
"status": "PASS" if passed else "FAIL",
"voxel_um": voxel_um,
"lod0_tiles": tile_count,
"lod_tile_counts": lod_counts,
"triangles_loaded_lod0": len(triangles),
"triangles_in_roi": len(region_indices),
"triangles_audited": len(considered),
"adjacent_pairs": len(edge_pairs),
"reference_triangles": len(reference_indices),
"reference_edge_p995_deg": quantile(reference_edges, 0.995),
"reference_p995_normal_variation_deg_per_m": quantile(ref_values, 0.995),
"thresholds": scores["thresholds"],
"flagged_cells": len(scores["hot_cells"]),
"flagged_triangles": len(scores["hot_triangles"]),
"floor_profile": floor_profile,
"wall_band_height_histogram": wall_band,
"radial_wall_histogram": radial_wall,
"clusters": cluster_summary,
}
print(f"[WholeSurfaceAudit] label={label} focus={focus} phase_h={phase:+.1f} "
f"edges={len(route['edges']) if route else 0} rooms={len(route['rooms']) if route else 0} "
f"lod0_tiles={tile_count} "
f"triangles_roi={len(region_indices)} audited={len(considered)} "
f"adjacent_pairs={len(edge_pairs)} reference={len(reference_indices)} "
f"edge_cutoff_deg={edge_cutoff:.2f} "
f"roughness_cutoff_deg_m={scores['thresholds']['roughness_deg_per_m']:.2f} "
f"hot_cells={len(scores['hot_cells'])} flagged_triangles={len(scores['hot_triangles'])} "
f"clusters={len(scores['clusters'])} observed={result['observed'].upper()} "
f"expected={expect.upper()} status={result['status']}")
print(f"[WholeSurfaceFloorProfile] label={label} triangles={floor_profile['triangles']} "
f"bins={floor_profile['bins']} lateral_bands={floor_profile['lateral_bands']} "
f"wall_triangles={floor_profile.get('wall_triangles', 0)} "
f"wall_bins={floor_profile.get('wall_bins', 0)} "
f"ripple_amplitude_m={floor_profile['ripple_amplitude_m']:.4f} "
f"max_adjacent_step_m={floor_profile['max_adjacent_step_m']:.4f} "
f"observed={'FAIL' if floor_profile['detected'] else ('PASS' if floor_profile['present'] else 'NOT_PRESENT')}")
if wall_band:
print(f"[WholeSurfaceWallBandMeasure] label={label} phase_h={phase:+.1f} "
f"voxel_m={voxel_um / 1_000_000.0:.3f} recarve_edge_height_m=0.250 "
f"height_histogram={json.dumps(wall_band, sort_keys=True, separators=(',', ':'))}")
print(f"[WholeSurfaceRadialWallMeasure] label={label} phase_h={phase:+.1f} "
f"route_s_m=-5..3 height_above_floor_m=-1..0.75 "
f"radial_histogram={json.dumps(radial_wall, sort_keys=True, separators=(',', ':'))}")
for index, cluster in enumerate(cluster_summary):
print(f"[WholeSurfaceCluster] label={label} index={index} cells={cluster['cells']} "
f"area_m2={cluster['area_m2']:.3f} severe_edges={cluster['severe_edges']:.1f} "
f"slivers={cluster['sliver_triangles']} bounds_m={cluster['bounds_m']} "
f"reasons={','.join(cluster['reasons'])}")
hot_cells = scores["cells"]
for key in sorted(scores["hot_cells"], key=lambda item: (
hot_cells[item]["severe"], hot_cells[item]["variation"]), reverse=True)[:12]:
cell = hot_cells[key]
local = route_nearest(tuple((axis + 0.5) * CELL_M for axis in key), route) if route else None
print(f"[WholeSurfaceHotCell] label={label} cell_m=({key[0]},{key[1]},{key[2]}) "
f"route_s_m={local[1] if local else 0.0:.2f} radial_m={local[0] if local else 0.0:.2f} "
f"height_above_floor_m={(key[2] + 0.5 - local[2]) if local else 0.0:.2f} "
f"area_m2={cell['area']:.3f} roughness_deg_m={cell['roughness_deg_per_m']:.2f} "
f"severe_edges={cell['severe']:.1f} edge_density_m2={cell['severe_edge_density_m2']:.2f} "
f"slivers={cell['slivers']} max_jump_deg={cell['max_jump']:.2f} "
f"reasons={','.join(cell['reasons'])}")
return result
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--case", nargs=5, action="append", required=True,
metavar=("LABEL", "MESH", "LAYOUT_OR_DASH", "EXPECT", "FOCUS"))
parser.add_argument("--json", type=Path, required=True)
parser.add_argument("--svg-dir", type=Path)
parser.add_argument("--phase", type=float, choices=(-0.5, 0.0, 0.5), default=0.0,
help="vertical analysis phase in multiples of the base voxel spacing")
args = parser.parse_args()
results = []
for label, mesh, layout, expect, focus in args.case:
if expect not in ("pass", "fail", "measure") or focus not in ("whole", "floor"):
parser.error("EXPECT must be pass/fail/measure and FOCUS must be whole/floor")
results.append(audit_case(label, Path(mesh), Path(layout), expect, focus,
args.svg_dir, args.phase))
args.json.parent.mkdir(parents=True, exist_ok=True)
args.json.write_text(json.dumps({"audit": "whole-surface-l54-v1", "cases": results},
indent=2) + "\n", encoding="utf-8")
failures = [result for result in results if result["status"] != "PASS"]
print(f"[WholeSurfaceAuditSummary] cases={len(results)} failed={len(failures)} "
f"json={args.json} status={'FAIL' if failures else 'PASS'}")
return 1 if failures else 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,55 @@
#!/usr/bin/env python3
"""Build the isolated L54 native-caves fixture from the canonical owner input."""
from __future__ import annotations
import argparse
import hashlib
import json
from pathlib import Path
def sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(block)
return digest.hexdigest()
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--owner", type=Path, required=True)
parser.add_argument("--out", type=Path, required=True)
args = parser.parse_args()
root = json.loads(args.owner.read_text(encoding="utf-8"))
strates = root.get("strates")
if not isinstance(strates, list):
raise ValueError("owner input has no strates array")
cave_candidates = [item for item in strates
if isinstance(item, dict) and int(item.get("index", -1)) == 1
and int(item.get("asset", {}).get("GeneratorType", -1)) == 4]
if len(cave_candidates) != 1:
raise ValueError("fixture requires exactly one SurfaceWorld cave strate at index 1")
upper_candidates = [item for item in strates
if isinstance(item, dict) and int(item.get("index", -1)) == 0]
if len(upper_candidates) != 1:
raise ValueError("fixture requires one adjacent upper strate at index 0")
passage = upper_candidates[0].get("passage_config")
asset_passage = upper_candidates[0].get("asset", {}).get("PassageConfig")
if (not isinstance(passage, dict) or not isinstance(asset_passage, dict)
or int(passage.get("Connections", -1)) != 1
or int(asset_passage.get("Connections", -1)) != 1):
raise ValueError("upper-strate passage config differs from the reviewed owner fixture")
passage["Connections"] = 2
asset_passage["Connections"] = 2
args.out.parent.mkdir(parents=True, exist_ok=True)
args.out.write_text(json.dumps(root, indent=2, sort_keys=True) + "\n",
encoding="utf-8")
print(f"[CavesFixtureBuild] source={args.owner} source_sha256={sha256(args.owner)} "
f"fixture={args.out} fixture_sha256={sha256(args.out)} changed=upper-strate-0-connections:1->2 "
"owner_input_untouched=true status=PASS")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,228 @@
#!/usr/bin/env python3
"""Export and capture the L54 native cave spine and chamber from reviewed views."""
from __future__ import annotations
import argparse
import json
import math
import os
from pathlib import Path
import struct
import subprocess
import sys
import time
ROOT = Path(__file__).resolve().parents[4]
LIMIT = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--"]
Q = 1_000_000.0
sys.path.insert(0, str(ROOT / "Standalone/Tools/verify"))
from runner import bounded_run, current_stamp, first_stamp
def bezier(points: list[list[float]], t: float) -> tuple[float, float, float]:
one = 1.0 - t
weights = (one ** 3, 3.0 * one * one * t, 3.0 * one * t * t, t ** 3)
return tuple(sum(weights[i] * points[i][axis] for i in range(4)) for axis in range(3))
def tangent(points: list[list[float]], t: float) -> tuple[float, float, float]:
one = 1.0 - t
return tuple(3.0 * one ** 2 * (points[1][axis] - points[0][axis])
+ 6.0 * one * t * (points[2][axis] - points[1][axis])
+ 3.0 * t ** 2 * (points[3][axis] - points[2][axis]) for axis in range(3))
def camera_angles(camera: tuple[float, float, float],
target: tuple[float, float, float]) -> tuple[float, float]:
dx, dy, dz = (target[i] - camera[i] for i in range(3))
return math.degrees(math.atan2(dx, dy)), math.degrees(math.atan2(dz, math.hypot(dx, dy)))
def fmt(values: tuple[float, ...]) -> str:
return ",".join(f"{value:.6f}" for value in values)
def filter_mesh_y_leq(source: Path, destination: Path, cut_y: float) -> int:
"""Keep triangles on the negative-Y side for an uncapped, inspectable cutaway."""
kept = 0
with source.open("rb") as src, destination.open("wb") as dst:
prefix = src.read(60)
if len(prefix) != 60 or prefix[:8] != b"VFMSH001":
raise ValueError("cutaway requires a VFMSH001 Core mesh")
version, voxel_um, tile_count = struct.unpack_from("<3I", prefix, 8)
if version != 1 or voxel_um <= 0:
raise ValueError("cutaway mesh version or voxel size is invalid")
dst.write(prefix)
for _ in range(tile_count):
header = src.read(36)
if len(header) != 36:
raise ValueError("cutaway mesh tile header is truncated")
key = struct.unpack_from("<4i", header, 0)
surface_count, _skirt_count, vertex_count, index_count, mask_count = \
struct.unpack_from("<5I", header, 16)
positions = src.read(vertex_count * 12)
normals = src.read(vertex_count * 12)
index_bytes = src.read(index_count * 4)
masks = src.read(mask_count)
if (len(positions) != vertex_count * 12
or len(normals) != vertex_count * 12
or len(index_bytes) != index_count * 4
or len(masks) != mask_count):
raise ValueError("cutaway mesh tile payload is truncated")
coords = list(struct.iter_unpack("<3f", positions))
indices = struct.unpack(f"<{index_count}I", index_bytes) if index_count else ()
kept_indices = []
kept_masks = bytearray()
for triangle in range(index_count // 3):
ids = indices[triangle * 3:triangle * 3 + 3]
if max(ids) >= vertex_count:
raise ValueError("cutaway triangle references an invalid vertex")
if sum(coords[index][1] for index in ids) / 3.0 > cut_y:
continue
kept_indices.extend(ids)
kept += 1
if masks:
kept_masks.append(masks[triangle])
skirt_count = sum(kept_masks)
output_header = struct.pack("<4i5I", *key, surface_count, skirt_count,
vertex_count, len(kept_indices), len(kept_masks))
dst.write(output_header)
dst.write(positions)
dst.write(normals)
if kept_indices:
dst.write(struct.pack(f"<{len(kept_indices)}I", *kept_indices))
dst.write(kept_masks)
if src.read(1):
raise ValueError("cutaway mesh contains trailing bytes")
if kept == 0:
destination.unlink(missing_ok=True)
raise ValueError("cutaway plane retained no triangles")
return kept
def run_step(name: str, command: list[str], log: Path, *, env: dict[str, str],
stamped: bool = False) -> str:
expected = current_stamp() if stamped else None
if stamped:
binary = Path(command[0])
probe = bounded_run([str(binary), "--print-source-stamp"], timeout=15)
if probe is None or probe.returncode != 0 or first_stamp(probe.stdout or "") != expected:
raise RuntimeError(f"{name} refused an unstamped or stale executable: {binary}")
started = time.monotonic()
log.parent.mkdir(parents=True, exist_ok=True)
with log.open("w", encoding="utf-8") as stream:
result = subprocess.run([*LIMIT, "timeout", "1740s", *map(str, command)],
cwd=ROOT, env=env, stdout=stream, stderr=subprocess.STDOUT,
timeout=1745, check=False)
elapsed = time.monotonic() - started
output = log.read_text(encoding="utf-8", errors="replace")
if stamped and (first_stamp(output) != expected or current_stamp() != expected):
raise RuntimeError(f"{name} source stamp mismatch; see {log}")
print(f"[L54CavesCaptureRun] step={name} elapsed={elapsed:.1f}s "
f"exit={result.returncode} log={log}", flush=True)
if result.returncode != 0:
raise RuntimeError(f"{name} failed; see {log}")
return output
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--out", type=Path, required=True)
parser.add_argument("--fixture", type=Path, required=True)
parser.add_argument("--layout", type=Path, required=True)
parser.add_argument("--headless", type=Path, required=True)
parser.add_argument("--renderer", type=Path, required=True)
parser.add_argument("--artifact-dir", type=Path, default=Path(__file__).resolve().parent / "captures")
args = parser.parse_args()
out, artifact_dir = args.out.resolve(), args.artifact_dir.resolve()
capture_dir, mesh_dir = out / "capture-png", out / "caves-meshes"
for directory in (capture_dir, mesh_dir, artifact_dir):
directory.mkdir(parents=True, exist_ok=True)
layout = json.loads(args.layout.read_text(encoding="utf-8"))
edges = sorted(layout.get("cavern_edges", []), key=lambda row: row["stable_id"])
rooms = layout.get("cavern_rooms", [])
if len(edges) != 2 or len(rooms) != 1:
raise RuntimeError("expected the resolved one-room, two-edge cave spine")
controls = [[value / Q for value in point] for point in edges[0]["control_points_um"]]
room = rooms[0]
room_center = tuple(value / Q for value in room["center_um"])
room_floor = room["floor_um"] / Q
mesh = mesh_dir / "caves-whole-view.vfmesh"
mesh_layout = out / "layouts" / "caves-capture-layout.json"
env = os.environ.copy()
env["LP_NUM_THREADS"] = "12"
env["XDG_CACHE_HOME"] = str(out / "xdg-cache")
run_step("whole-cave-export", [args.headless, args.fixture,
"--workers=12", "--world-assembler-prototype", "--stream-benchmark",
"--stream-pipeline", "--force-generate", "--clip-radius=4",
"--max-clip-level=0", "--max-level=0", "--render-distance-chunks=0",
"--recipe=caves", "--camera=41,-4,-160,0,0",
f"--resolved-layout-output={mesh_layout}", f"--mesh-export={mesh}"],
out / "capture-caves-export.log", env=env, stamped=True)
if not mesh.is_file() or mesh.stat().st_size == 0:
raise RuntimeError("whole-cave export produced no mesh")
cutaway_mesh = mesh_dir / "caves-cutaway-y-minus5.vfmesh"
cutaway_triangles = filter_mesh_y_leq(mesh, cutaway_mesh, -5.0)
print(f"[L54CavesCutaway] plane_y_m=-5.000 retained_triangles={cutaway_triangles} "
f"mesh={cutaway_mesh}", flush=True)
eye_t = 0.50
eye_floor = bezier(controls, eye_t)
eye_direction = tangent(controls, eye_t)
eye = (eye_floor[0], eye_floor[1], eye_floor[2] + 1.45)
eye_yaw = math.degrees(math.atan2(eye_direction[0], eye_direction[1]))
eye_target = (eye[0] + 10.0 * math.sin(math.radians(eye_yaw)),
eye[1] + 10.0 * math.cos(math.radians(eye_yaw)), eye[2])
arrival_portal = [value / Q for value in edges[0]["control_points_um"][-1]]
entrance = (arrival_portal[0], arrival_portal[1], room_floor + 1.45)
entrance_target = (room_center[0], room_center[1], room_floor + 1.45)
overview_target = (room_center[0] - 8.0, room_center[1], room_floor + 1.5)
overview = (room_center[0] - 23.0, room_center[1] + 27.0, room_floor + 19.0)
views = (
("spine-eye", cutaway_mesh, eye, eye_target, 20.0, 80.0),
("room-from-entrance", mesh, entrance, entrance_target, 18.0, 90.0),
("cutaway-overview", cutaway_mesh, overview, overview_target, 48.0, 65.0),
)
quality = ROOT / "Standalone/Tools/verify/capture_quality.py"
for name, render_mesh, camera, target, focus_radius, fov in views:
yaw, pitch = camera_angles(camera, target)
if name == "spine-eye":
yaw, pitch = eye_yaw, 0.0
png = capture_dir / f"caves-{name}.png"
image = artifact_dir / f"caves-{name}.jpg"
command = [args.renderer, render_mesh, png, "--size=1600x900",
f"--fov-deg={fov:.1f}",
f"--camera-m={fmt(camera)}", f"--yaw-deg={yaw:.6f}",
f"--pitch-deg={pitch - 4.0 if name == 'spine-eye' else pitch:.6f}",
"--tile-skirts=off", "--shade=readable", "--headlamp=off",
"--light-direction=1,-0.6,0.8", "--height-tint=on",
f"--height-center-m={room_floor:.6f}",
"--height-range-m=12", "--tile-edges=off",
f"--focus-region={fmt((*target, focus_radius))}"]
run_step(f"{name}-render", command, out / f"capture-{name}-render.log", env=env,
stamped=True)
run_step(f"{name}-jpeg", ["ffmpeg", "-nostdin", "-hide_banner", "-loglevel",
"error", "-y", "-i", png, "-frames:v", "1", "-q:v", "4", image],
out / f"capture-{name}-jpeg.log", env=env)
quality_output = run_step(f"{name}-quality", [sys.executable, quality, image],
out / f"capture-{name}-quality.log", env=env)
quality_lines = [line for line in quality_output.splitlines()
if line.startswith("[CaptureQuality]")]
if not quality_lines or not quality_lines[-1].endswith("status=PASS"):
raise RuntimeError(f"capture quality gate failed for {name}")
print(f"[L54CavesCapture] view={name} file={image} camera_m=({fmt(camera)}) "
f"target_m=({fmt(target)}) bytes={image.stat().st_size} status=PASS", flush=True)
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except Exception as error:
print(f"[L54CavesCapture] status=FAIL error={error}", file=sys.stderr, flush=True)
raise SystemExit(1)
@@ -0,0 +1,53 @@
# VoxelForge L54 — native Core caves, slice 1
## Field and bounds
`Core.Caves.PrimitiveField` v1 is opt-in on a `Caves` strate. Density is negative in solid and positive in air. The field has solid host rock at `-32 m`, a smooth union of two elliptical spine tubes and one ellipsoid room, and uses the shared `shared-bedrock` layer. Legacy `TunnelNetwork` stays legacy until a strate selects this recipe. The recipe is bound in the L40 field table and L42 capabilities; `isTileShapeFree` recognizes it. Feature candidates use a BVH.
Conservative feature AABBs in metres, including blend margins:
| Feature | Minimum (x, y, z) | Maximum (x, y, z) |
|---|---|---|
| Room | (55.605, -13.758, -161.604) | (74.405, 4.042, -147.804) |
| Arrival spine | (6.807, -15.258, -167.594) | (71.869, 0.735, -151.304) |
| Departure spine | (6.139, -9.388, -160.604) | (71.941, 5.542, -145.726) |
Room center is `(65.005, -4.858, -154.704) m`, with half extents `(7, 6.5, 4.5) m`. Each tube has 4.5 m horizontal and vertical radii.
## Floor and reachability
Each void is intersected with its rising floor by cubic smooth-max, `W = 2.4 m`. The isolated formula predicts a `0.848528 m` fillet; the composed field measures `0.701737 m`, above the `0.5 m` requirement. The spine starts 3.0 m inside each resolved mouth, starts 0.75 m below the mouth floor, and interpolates to room floor height. Native caves bypass the legacy trailing floor cap.
A 0.50 m capsule flood fill connects arrival `passage/0-1/0` to departure `passage/0-1/1`: 4,003 candidate cells, 3,426 walkable cells, a 203-cell route, grade at most 15°, and minimum headroom 0.300 m. Six interval boxes, each checked against 343 brute-force samples, had zero misses; the false all-air and all-solid controls were rejected. The fixture transition audit passed 4,221 columns, skipped four passage columns, found zero holes or air samples, and rejected both band-width controls.
## Surface audit
The whole-surface audit covers two edges and one room. The base mesh audited 93,205 triangles: four hot cells, zero flagged triangles, zero clusters, floor ripple `0.0008 m`, and maximum adjacent step `0.0017 m`. Both analytical `phase_h = ±0.5 m` audits passed with zero flagged triangles. L45 ramp, L46 entrance, and L47 wall-foot controls failed as expected; the plain-native control passed. The phase option offsets the analytical floor reference; it does not remesh on a shifted lattice.
Maps: [base](captures/flagged-regions-caves-base.svg), [minus half](captures/flagged-regions-caves-minus-half.svg), [plus half](captures/flagged-regions-caves-plus-half.svg).
## Landmark regression and capture routing
The L47 geometry change came from sorting the shared runtime feature list by stable ID: smooth room/edge composition is order-sensitive, and the legacy graph changed from its original room-then-edge order. The native floor-cap bypass already checks generator kind, recipe ID/version, and the native-cave capability; L47 remains `SurfaceWorld`. Serializing the new polyline for every graph also changed non-caves layout identities. Stable-ID ordering and polyline serialization are now scoped to the opt-in native caves recipe; legacy graphs keep their prior order and layout representation. L47 is back at layout ID `f760ed65997290b7`; `tunnel-wall-edge` now measures `0.6446 m` straight run and its audit passes.
Verifier capture stages write under `--out/captures`. The two modified Linux47 JPEGs were restored from `HEAD`; no research capture folder is written by a tier. Final cave images: [spine at eye height](captures/caves-spine-eye.jpg), [room from its entrance](captures/caves-room-from-entrance.jpg), and [cut-away overview](captures/caves-cutaway-overview.jpg). All three pass image quality gates and were visually checked. The spine frame shows floor, wall, and ceiling; the room frame looks from its arrival entrance; the overview shows the chamber and connected passages. Primitive rock has no texture detail.
## Speed, hashes, and tiers
Clang W12 measured `0.127 s` first tile, `0.177 s` near ring, `0.706 s` cold whole view, and moving latency p50/p95 of `1.349/1.354 ms`. The fast owner hash diagnostics match L53 for Floating Islands and Surface World. The L47 layout ID also matches its L53 value.
Clean Clang 19 and GCC builds used FP contraction off and 12 jobs. Final source stamp: `efb45f41121eb859701d92b9ced95fbc64eb4110aa9166a9b57f83c1cb227e85`.
Fast passed in 159.0 s, medium in 925.5 s, and full in 1,123.1 s on that stamp. Full passed the GCC supplement, including four seeded cave cameras matching Clang at W1/W8/W12, plus the 30-cell and crossing matrices. Build and tier logs are under `Saved/Linux/l54/`.
## Failed runs
The first full run before the L47 fix stopped at the legacy capture audit; the final full run passes. An early stale-layout capture was all-black, and an early cut-away export used the wrong mesh path; both were corrected before the final captures. A wide-radius transition audit exceeded 6 GiB; the radius-zero Caves fixture audit passed.
## Orchestrator review (Claude)
Accepted after a finishing pass. The stamp matches the final Clang and GCC builds (`efb45f41…`); fast, medium and full
all PASS on it, including the GCC supplement. **Native caves whole view: 0.727 s,** the first generator under the
1 s target. The first full run caught a real side effect: stable-ID sorting and polyline serialisation changed the
flagged L47 landmark demo, because smooth composition is order-sensitive. Both are now scoped to native caves. The
verifier no longer writes into tracked research folders. Process note: about 34 trial builds against a 15 budget.
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

File diff suppressed because it is too large. Load diff

After

Width:  |  Height:  |  Size: 3.1 MiB

File diff suppressed because it is too large. Load diff

After

Width:  |  Height:  |  Size: 2.7 MiB

File diff suppressed because it is too large. Load diff

After

Width:  |  Height:  |  Size: 3.1 MiB

File diff suppressed because it is too large. Load diff

After

Width:  |  Height:  |  Size: 5.0 MiB

File diff suppressed because it is too large. Load diff

After

Width:  |  Height:  |  Size: 5.0 MiB

File diff suppressed because it is too large. Load diff

After

Width:  |  Height:  |  Size: 5.0 MiB

File diff suppressed because it is too large. Load diff

After

Width:  |  Height:  |  Size: 1.2 MiB

@@ -0,0 +1,91 @@
# VoxelForge R23 — Making Each Tile Cheaper
## LOD0 stage model
`World::generateTile` activates the shared column caches, binds the shape world, runs tile classification, calls `GenerateMesh`, and copies the mesher timings into diagnostics. The mesher builds a 32³-cell LOD0 tile with a 35³ padded density grid, then classifies cells, computes gradients, interpolates edges, streams triangles, emits skirts, and returns arrays for assembly. See [World::generateTile](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:1983>) and [UVoxelMarchingCubesMesher::GenerateMesh](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp:117>).
L52 used 512 LOD0 tiles. These are **summed worker times**, not wall times. Stage shares below use each recipe’s LOD0 worker sum as the denominator. Gradient, interpolation, and dedup/stream timings are sampled and extrapolated; their subtotals do not add exactly to `mc_exclusive`. `mc_other` is a residual, not an allocation timer.
| LOD0 stage | SW worker ms (% of 2,031.707 ms) | FI worker ms (% of 28,633.183 ms) |
|---|---:|---:|
| MC exclusive, excluding skirts | 1,284.636 (63.23%) | 27,019.018 (94.36%) |
| Density grid | 608.641 (29.96%) | 26,703.171 (93.26%) |
| Cell classification | 131.076 (6.45%) | 104.673 (0.37%) |
| Gradient normals | 49.508 (2.44%) | 24.732 (0.09%) |
| Edge interpolation, including midpoint re-queries | 370.383 (18.23%) | 30.921 (0.11%) |
| Vertex dedup and triangle streaming | 95.139 (4.68%) | 66.466 (0.23%) |
| MC residual (`other`) | 47.487 (2.34%) | 106.111 (0.37%) |
| Skirts | 650.112 (32.00%) | 1,094.614 (3.82%) |
| Mesh assembly | 10.458 (0.52%) | 13.247 (0.05%) |
The direct profile rows and counts are in the [SW LOD0 log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l52/profile/final-surface-world.log:72>) and [FI LOD0 log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l52/profile/final-floating-islands.log:78>).
| LOD0 count | SW | FI |
|---|---:|---:|
| Density-grid samples | 21,952,000 (42,875/tile; full 35³ grid) | 17,883,062 (34,928/tile; 81.46% of full grid) |
| Candidate cell slots before proof skips | Up to 16,777,216 | Up to 16,777,216 |
| Classified and surface cells | Not logged | Not logged |
| Gradient queries / fallback density probes | Not logged | Not logged |
| Midpoint density re-queries | Not logged | Not logged |
| Generated skirt triangles | 179,600 | 198,464 |
`density_samples` is the mesher’s grid-fill count. It does not count gradient fallbacks, midpoint re-queries, or skirt probes; shared-density-grid copies also do not increment it. `CellClassificationCalls` and `SurfaceCellCalls` exist locally but are not emitted. Gradient fallback calls and memo hits, and midpoint calls, already exist in `FVoxelMarchingCubesStageTimings` and Core diagnostics; the L52 LOD logger omits them. Add per-LOD output for those counters, proof-skipped cells, active cells, and unique midpoint edges. The skirt timer and triangle count are logged, but skirt edge scans and density probes are not.
The generic heightfield column counters are zero at LOD0 for both recipes. That does **not** mean FI’s island column cache is idle: `IslandDensity` uses a separate four-way TLS cache and a four-way shared cache, activated by `World::generateTile`. The shared cache takes a per-set spin lock on misses. Add local hits/misses, shared hits/misses/computes, conflict replacements, and lock-wait time at [IslandColumnCache](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/FloatingIslandsInternal.h:461>) and [TSharedColumnValueCache](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/include/VoxelForgeCore/SharedColumnCache.h:43>).
The LOD0 grid costs about **0.0277 worker-seconds per million samples for SW** and **1.493 worker-seconds per million for FI**, at the current sample mix. Before-to-final FI profiles show samples falling from 18.951M to 17.883M and grid time from 28.335s to 26.703s. This was a package change, not an isolated proof A/B. The current proof audit checked 27,727 blocks and 36.9M samples with zero violations across Clang and GCC; it does not report LOD0 counts by skip reason. See the [L52 report](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux52/REPORT.md:36>).
Tile classification is a separate all-LOD aggregate: L52 reports 0.622 ms for SW and 22.465 ms for FI. FI’s all-LOD classifier counted 14,119 air, 18,857 mixed, and 240 unknown boxes, with 216 whole-tile skips. These are not LOD0 cell counts, and `classification_worker_ms` can overlap mesher proof work. **Correction to R22:** the FI figure is 22.465 milliseconds, not 22.5 worker-seconds.
## Techniques and measured opportunity
There is no isolated L52 A/B result for the new techniques below. I give the measured worker-time budget or a counter-based saving formula rather than inventing a point forecast.
- **Proof-guided sparse sampling — highest FI opportunity.** The existing 8³ block proofs and required-sample mask are in [GenerateMesh](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp:556>), [sample-mask setup](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp:911>), and [grid fill](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp:1150>). Measure smaller proof blocks and active sample runs; the current proof mask disables the X8 batch path. At the current sample mix, each additional million samples avoided corresponds to about 1.493 FI or 0.0277 SW worker-seconds, before proof overhead. The earlier FI package delta corresponds to 1.632 worker-seconds less grid time, but cannot be attributed to this change alone. Keep exact samples for retained cells and the one-sample gradient halo; mixed or unknown blocks must fall back. The [NVIDIA sparse-MC design](https://github.com/NVIDIA/warp/blob/main/design/sparse-marching-cubes.md) gives the Lipschitz pruning bound; do not assume `L=1` for composed or roughened fields unless that bound is proven for the recipe.
- **SIMD density evaluation — worth measuring for FI.** The batch route is in [GenerateMesh](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp:1200>) and [DensityAtLatticeX8](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:3132>); FI’s expensive island evaluation is in [IslandDensity](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/FloatingIslandsInternal.h:743>). The current FI X8 API still evaluates native-island lanes serially, and proof-masked fills disable X8 batching. Both the i9-12900H and Ryzen 7 5800X3D support AVX2, which is eight float32 lanes; a 16-lane batch is two AVX2 vectors, not a wider native instruction. The Intel specs list AVX2 on a 6P+8E/20-thread part; AMD lists AVX2/FMA3 on the 5800X3D ([Intel](https://www.intel.com/content/www/us/en/products/sku/132214/intel-core-i912900h-processor-24m-cache-up-to-5-00-ghz/specifications.html), [AMD](https://www.amd.com/en/products/processors/desktops/ryzen/5000-series/amd-ryzen-7-5800x3d.html)). The grid-stage ceilings are 0.609 SW and 26.703 FI worker-seconds; each measured 10% grid-stage cut would save 0.061 and 2.670 worker-seconds, respectively. That is a sensitivity, not a forecast. FastNoise2 reports AVX2 3D value noise at 494.49M samples/s versus 64.13M for FastNoise Lite, and Perlin at 261.10M versus 47.93M on an i7-7820X; those are noise-kernel numbers, not this composite SDF ([FastNoise2](https://github.com/Auburn/FastNoise2)). Both CPUs have AVX2 width 8; benchmarking each is necessary.
- **Cache midpoint density queries by edge — exact SW candidate.** The midpoint re-query is inside [edge interpolation](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp:1735>). Cache only the density result for repeated identical midpoint inputs; keep the existing orientation-specific interpolation arithmetic. `EdgeMidpointDensityCalls` exists but is not logged. The entire interpolation stage is a hard ceiling of 0.370 worker-seconds SW and 0.031 FI, or 30.9 and 2.6 ms of ideal W12 drain. The actual query saving is unknown until attempts, unique edges, hits, field-evaluation time, and total interpolation time are reported.
- **MC kernel layout / Flying Edges.** The cell loop uses AVX2 to classify eight X cells at once and only processes surface cells; the interpolation and `GetOrCreateVertex` path is at [GenerateMesh](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp:1575>) and [vertex dedup](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp:242>). Row-major X-fast storage and TLS scratch reuse already exist. The [Newman et al. SIMD MC paper](https://doi.org/10.1016/j.cag.2003.12.008) reports roughly 2× over compiler-optimized C and 4× over unoptimized C on older x86. The [Flying Edges paper](https://doi.org/10.1109/LDAV.2015.7348069) reports 6.76s versus 2.39s for Synchronized Templates at 36 threads on a 2048³ plasma volume. Those are not this field or a direct forecast. The current non-grid MC budget is 0.676 SW and 0.316 FI worker-seconds, at most 56 and 26 ms ideal W12 drain. Flying Edges can avoid repeated edge work and point merging, but changes output ordering and likely needs a recipe version bump.
- **Grid gradients versus analytic gradients.** The current [GradAt path](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp:1485>) already computes central differences from cached density-grid samples and memoizes them. The [Round 18 report](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Round18/REPORT.md:96>) measured fallback probes falling from 11,474 to 3,018 per sampled p95 and normal cost from 65.10 to 24.16 ns/sample. The remaining L52 gradient-stage ceiling is only 49.5 ms SW and 24.7 ms FI worker-time. Analytic gradients change normals. For comparison, GPU Gems describes central differences as six volume samples; ExaBrick reports 2.70s central-difference versus 130ms analytic rendering, but that workload pays additional ray traversals, unlike our already-filled grid ([GPU Gems](https://developer.nvidia.com/gpugems/gpugems3/part-i-geometry/chapter-1-generating-complex-procedural-terrains-using-gpu), [ExaBrick paper](https://pds.uni-koeln.de/sites/home/szellma1/exabrick-compressed.pdf)).
- **Skirt-face culling — strong SW candidate, versioned output.** Skirts take 0.650 SW and 1.095 FI worker-seconds at LOD0. The viewer already computes face visibility at [ComputeVisibleSkirtFaceMask](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/StandaloneViewer.cpp:105>) and filters triangles at draw time; mesher extrusion is at [EmitSkirts](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp:1960>). A 125-tile mixed-LOD capture found 87.4% of SW and 87.0% of FI skirt triangles hidden. Applied linearly to all LOD skirt timers, that gives a **modeled** 1.55/2.32 worker-seconds gross, or 0.129/0.194s ideal W12 drain. Triangle share is not a time share; edge scanning remains. Pass the neighbor mask into meshing, include it in mesh-cache identity, and rebuild when neighbor coverage changes. The captured percentages are from [SW](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l52/capture-owner-sw-arrival-render.log:7>) and [FI](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l52/capture-owner-fi-arrival-render.log:7>).
- **Transvoxel, Surface Nets, dual contouring.** These are not first-round LOD0 speed wins. Transvoxel handles 2:1 LOD boundaries, using nine high-resolution samples and 512 cases grouped into 73 classes; it could replace some seam skirts but adds transition-cell work ([Transvoxel](https://transvoxel.org/)). On an RTX 5090 sphere benchmark, uniform 512³ MC took 5.4 ms versus 6.9 ms for Surface Nets and 7.0 ms for one dual-contouring variant; sparse results were 1.9, 2.2, and 2.3 ms, with 300k active cells versus 134M dense cells ([isoext benchmark](https://guangyancai.github.io/isoext/performance.html)). These GPU results are not a CPU prediction. All three alternatives change topology or seam geometry. Dreams’ SIGGRAPH slides describe GPU MC with compaction, while Astroneer identifies MC as a core algorithm to optimize; neither supplies a comparable CPU speedup ([Dreams](https://advances.realtimerendering.com/s2015/AlexEvans_SIGGRAPH-2015-sml.pdf), [Astroneer](https://blog.astroneer.space/p/going-faster/)).
Per-tile scratch is already substantially reused: density grid, required-sample mask, gradient memo, vertex maps, and triangle arrays are thread-local and retain capacity. A per-tile `SurfaceVertexDensitySamples` vector still allocates, but there is no allocator-only timer. Add scratch growth/allocation counts and bytes; do not treat `mc_other` as allocator cost.
## Exactness and determinism
| Technique | Output class | Clang/GCC risk with FP contraction off |
|---|---|---|
| Proven block/sample omission with original samples, traversal, and gradient halo | Bit-identical | Low if bounds are sound; audit every recipe, including new caves. |
| SIMD with the same per-lane operation order | Bit-identical possible | Avoid FMA, reassociation, approximate reciprocal/sqrt, and different transcendental implementations. Existing X8 results and L52 Clang/GCC hashes pass; GCC’s default contraction previously broke SW W1 LOD2. |
| Reuse cached midpoint-density result for identical inputs | Bit-identical | Key exact input bits; preserve existing interpolation arithmetic. |
| Remove or replace midpoint re-query; analytic gradients | Slight change; version bump | Peer-identical remains required; normals or vertex positions can change. |
| Scratch/cache/layout reuse | Bit-identical | Reset state fully; retain output order and arithmetic. |
| Hidden skirt-face generation skip | Visible output can match for the same neighbor set; mesh payload changes, so bump version/cache identity | Neighbor masks and invalidation must be deterministic. |
| Flying Edges, Surface Nets, dual contouring, Transvoxel | Geometry/order changes; version bump | Use stable traversal and tables, then require Clang/GCC peer hashes. |
Current builds use `-fno-fast-math` and FP contraction off; explicit FMA intrinsics can still fuse. Keep the [L52 Clang/GCC W1/W8/W12 matrix](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux52/REPORT.md:55>) as the determinism gate.
## Sequenced plan and projections
Use the unprofiled paired W12 medians from R22 as the whole-view baseline: **SW 1.718s; FI 5.614s**. Convert worker-time deltas to wall-time only as an ideal W12 estimate; retain paired unprofiled whole-view measurements as the score.
1. **Round 1 — instrument and establish baseline.** Emit per-LOD candidate/classified/surface/proof-skipped cells; sample omissions by reason; X8 calls and active lanes; `GradAt` calls, memo hits/misses and fallback probes; midpoint attempts/unique edges/hits and query time; skirt face candidates/probes/times; FI local/shared cache stats and lock waits; scratch allocations/capacity growth. Keep profile timers from overlapping or label them as such. Gate: counters reconcile with existing sample and skirt totals, output hashes match, and unprofiled W12 stays at 1.718/5.614s.
2. **Round 2 — isolate kernel changes.** For FI, A/B proof block sizes and active-lane batching, measuring samples removed and net `mc_grid_ms`; for SW, A/B the exact midpoint-density memo, measuring query hits and interpolation time. Gate: zero proof violations, Clang/GCC W1/W8/W12 hashes match, and whole-view medians improve without profile-only gains. A 10% FI grid-stage reduction is a useful pass threshold: 2.670 worker-seconds, or 0.223s ideal W12. Without that result, credit no FI saving. Credit SW only from its measured A/B delta; its full interpolation-stage ceiling is 31ms ideal W12.
3. **Round 3 — cull hidden skirt faces before extrusion.** Pass the viewer’s neighbor mask into the mesher and cache key. Gate: fixed-view captures match visible output, neighbor/LOD transitions have no gaps, mask changes invalidate the mesh, and whole-view W12 medians improve. The capture-based scenario is 0.129s SW and 0.194s FI; treat it as optimistic until measured.
| Stage | Conditional whole-view projection |
|---|---|
| Round 1, counters only | SW 1.718s; FI 5.614s |
| Round 2, FI grid gate passes; no SW delta yet measured | SW 1.718s; FI about 5.39s; if FI gate fails, 5.61s |
| Round 3, skirt scenario also passes | SW about 1.59s; FI about 5.20s if Round 2 passed, or 5.42s if it did not |
| Any gate fails | Retain the last measured baseline for that recipe |
These projections are scenarios, not forecasts. Even removing all LOD0 worker work would save at most about 0.17s SW or 2.39s FI on an ideal 12-worker drain; LOD0-only changes cannot take SW from 1.718s to 1s.
@@ -0,0 +1,68 @@
# VoxelForge — Research R23: making each tile cheaper
## Stage model
[`World::generateTile`](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:1983>) sets the shape and optional shared-cache scope, classifies the tile, calls the mesher, then copies mesh data into the tile payload. The LOD0 mesher path is [`GenerateMesh`](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp:117>).
LOD0 uses a 32³ cell tile and a 35³ density grid: 42,875 samples per fully sampled tile. FI sampled 17.883M points across 512 LOD0 jobs, an average of 34,928 per job (81.5% of the full grid); SW sampled all 21.952M. FI’s proof mask marks active cells and their one-sample gradient halo. Proof-mask construction is outside the density-grid timer, in the residual work.
The density fill, proof mask and X-batch gates are in [`VoxelMarchingCubesMesher.cpp`](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp:888>). The cell loop classifies eight X-adjacent cells with AVX2, then computes gradients, interpolation, deduplication and streams triangles for surface cells. The mesher reads gradients from the density grid and memoizes them; it re-queries only for proxy-band fallback samples. Edge interpolation can re-query at the midpoint when `T` is between 0.375 and 0.625. The skirt pass clips and emits boundary geometry; final triangle and skirt-mask assembly follows. These stages are in [`VoxelMarchingCubesMesher.cpp`](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp:1484>). Round18 found only 35 midpoint probes in its audit, so that re-query has little measured headroom.
The table shows L52 totals for 512 LOD0 jobs. Shares use each recipe’s `worker_sum_s` as denominator. Grid and skirt timers are direct; some mesher substage buckets are sampled or extrapolated. Those buckets exceed the outer mesher timer by about 17 ms in both recipes, so treat shares as approximate.
| Stage | Code | SW total / share | FI total / share |
|---|---|---:|---:|
| Density grid | `GenerateMesh`, fill and grid timer | 608.641 ms / 30.0% | 26,703.171 ms / 93.3% |
| Cell-case classification | AVX2 X rows | 131.076 ms / 6.45% | 104.673 ms / 0.37% |
| Gradients and normals | central differences, memo | 49.508 ms / 2.44% | 24.732 ms / 0.09% |
| Edge interpolation | edge crossings and midpoint probes | 370.383 ms / 18.23% | 30.921 ms / 0.11% |
| Vertex dedup and stream | vertex maps, triangle bins | 95.139 ms / 4.68% | 66.466 ms / 0.23% |
| Other mesher work | setup, proof-mask work, finalization residual | 47.487 ms / 2.34% | 106.111 ms / 0.37% |
| Skirts | boundary scan, clipping, emission | 650.112 ms / 32.0% | 1,094.614 ms / 3.82% |
| Core mesh assembly | mesh-to-payload copy | 10.458 ms / 0.51% | 13.247 ms / 0.05% |
The LOD0 worker sums are 2.032 seconds for SW and 28.633 seconds for FI. With 12 workers, their ideal drains are 0.169 and 2.386 seconds. SW’s LOD0 work alone cannot close R22’s roughly 0.72-second whole-view gap to one second; the shared kernel needs to carry into other LODs as well.
Tile and block classification is separate from the cell-case stage. L52’s final logs report `classification_worker_ms=0.622` for SW and `22.465` for FI across 960 full-detail tiles, with no LOD0 split. [`ClassifyBoxDetailed`](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:5273>) records milliseconds. The Linux52 report’s prose calls the FI figure 22.47 worker-seconds; that conflicts with the final log field and source units, so I have not counted it as seconds.
The X8 density API is not generally eight-lane SIMD: its native-only route loops over scalar `DensityAtLattice` calls. FI’s proof-masked fill disables the X-batch route. The cell classifier is genuine AVX2; a separate primitive Z-run helper uses four double lanes. The relevant code is [`ShapeWorldInternal.h`](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:3132>) and [`ShapeWorldInternal.h`](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:9407>).
The world-level shared column cache is scoped in [`VoxelForgeCore.cpp`](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:1055>) and used by `Heightfield2D::point` in [`WorldAssemblerInternal.h`](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/WorldAssemblerInternal.h:2576>). L52’s LOD0 heightfield-column counters are zero. FI also has an island XY column cache in [`FloatingIslandsInternal.h`](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/FloatingIslandsInternal.h:477>); its hit rate and cost are not reported. An optional shared 3D density grid exists, but Core passes null, and using it disables proof-mask sampling.
## Techniques and evidence
- **SIMD field evaluation:** both the i9-12900H and Ryzen 7 5800X3D list AVX2 support; the AVX2 width is eight floats, so X16 means two vectors rather than a wider instruction. [Intel specs](https://www.intel.com/content/www/us/en/products/sku/132214/intel-core-i912900h-processor-24m-cache-up-to-5-00-ghz/specifications.html), [AMD specs](https://www.amd.com/en/products/processors/desktops/ryzen/5000-series/amd-ryzen-7-5800x3d.html). FastNoise2 reports 494.49M 3D value samples/s with AVX2 versus 64.13M for FastNoiseLite on an i7-7820X; this is an illustration, not a VoxelForge forecast. [FastNoise2 benchmark](https://github.com/Auburn/FastNoise2). An earlier in-repo packed X-row path reduced its LOD0 grid timer 62.4% with matching W1/W8 hashes; its workload differs from L52. [Round9 report](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/OwnerShapeWorld/speed_round/round9/REPORT.md>).
- **Sparse sampling:** refine the existing conservative 8³ block proofs to smaller mixed blocks, retaining exact fallback for unknown blocks and the gradient halo. The method is already proven for the current block sizes: L52’s brute-force audit checked 36.9M samples with zero violations under Clang and GCC. [Linux52 report](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux52/REPORT.md>). Generic Lipschitz pruning assumes a valid field bound; use VoxelForge’s exact box proofs rather than assuming every field is a unit-distance SDF. [NVIDIA Warp sparse MC design](https://github.com/NVIDIA/warp/blob/main/design/sparse-marching-cubes.md).
- **Alternative extraction:** Flying Edges’ four-pass structure intersects each edge once and preallocates output; its paper reports up to 5.22× serial and 8.26× at 36 threads versus its optimized MC baseline on CT angiography data. VTK warns that its output may include degenerate triangles and differ from MC. These are much larger workloads than a 32³ tile. [Paper](https://doi.org/10.1109/LDAV.2015.7348069), [VTK documentation](https://vtk.org/doc/nightly/html/classvtkFlyingEdges3D.html). Surface Nets and dual contouring change vertex placement and mesh topology. On an RTX 5090 sphere benchmark at 512³, uniform MC took 5.4 ms, Surface Nets 6.9 ms and dual contouring 7.0 ms. [Isoext performance](https://guangyancai.github.io/isoext/performance.html). Transvoxel solves 2:1 LOD seams using nine high-resolution samples and 512 cases in 73 classes; it does not reduce interior LOD0 sampling. [Transvoxel](https://transvoxel.org/).
- **Reuse and layout:** density, proof-mask and gradient arrays, vertex maps and triangle bins already retain worker-local capacity. The grid is X-fast and cell traversal follows X rows. The visible per-tile scratch allocation is `SurfaceVertexDensitySamples`; allocation time is not isolated. The viewer already computes visible skirt faces, but generation currently emits all skirt faces. [Viewer mask](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/StandaloneViewer.cpp:105>).
## Exactness
| Technique | Bit-identical? | Conditions |
|---|---|---|
| AVX2 arithmetic batches | Yes, conditionally | Preserve each lane’s operation order; keep contraction off; avoid approximate math. |
| Grid-derived gradients and memoization | Yes; already implemented | Preserve central differences and the proxy-band fallback. |
| Finer proof blocks | Yes, conditionally | Use conservative proofs, exact fallback and the same halo. |
| Scratch reuse and cache changes | Yes | Reset all validity and key state; cache replacement must affect residency only. |
| Vectorized `sin`/`cos` | Not guaranteed | SIMD math can shift bits. Pin the implementation and compare Clang/GCC peers. |
| Hidden skirt-face omission | Mesh output changes | Rendered output can match for a fixed neighbor mask, but hashes and cache identity change. |
| Flying Edges, Surface Nets, dual contouring or Transvoxel | No | Geometry or topology changes; peer determinism is possible with a fixed implementation and version bump. |
VoxelForge already disables FP contraction for Core; GCC and Clang document that contraction controls FMA formation. Those flags do not fix libm or vector-trig results across implementations. [GCC](https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html), [Clang](https://clang.llvm.org/docs/UsersManual.html#controlling-floating-point-behavior), [FastNoise2 reproducibility notes](https://github.com/Auburn/FastNoise2/wiki/FAQ).
## Ranked plan
Savings are scenarios in **worker-seconds across the same 512 LOD0 jobs**. Divide by 12 for ideal-drain wall time. They are not additive.
| Rank | Change and code location | SW / FI scenario | Risk and measurement |
|---|---|---:|---|
| 1 | Add mask-aware AVX2 field batches in mesher fill and field evaluators: [`VoxelMarchingCubesMesher.cpp`](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp:911>), [`ShapeWorldInternal.h`](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:3132>), FI [`IslandDensity`](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/FloatingIslandsInternal.h:743>). | If grid fill speeds up 1.25–1.5×: **0.12–0.20 / 5.34–8.90 s** | Current X8 API and FI proof mask fall back to scalar work. Preserve lane operation order first; pin vector math if trig is vectorized. Count active lanes and compare grid timers, then paired unprofiled medians and hashes under Clang/GCC on both CPUs. |
| 2 | Refine 8³ proofs to 4³ in mixed blocks; retain unknown fallback and halo. | If FI grid work falls another 5–15%: **≈0 / 1.3–4.0 s** | Sensitivity range only; finer proofs may cost more than they save. Measure samples and classifier time, then repeat the brute-force proof audit and hash matrix. |
| 3 | Pass the viewer’s visible-face mask into `GenerateMesh` and skip hidden skirt emission. | **h×0.650 / h×1.095 s**, where `h` is the hidden fraction | L52 has no hidden-face histogram. Measure mask fractions and skirt time under fixed adjacency; include the mask in any mesh-cache key. |
| 4 | Prototype Flying Edges after grid fill. | If the post-grid MC work became 2.5× faster: **≈0.41 / 0.19 s** | Literature results may not transfer to 32³ tiles; output ordering and degenerates differ. Benchmark prefilled grids and whole tiles; version the changed mesh. |
| 5 | Instrument and reuse remaining scratch; add FI island-cache hit/miss timing. | **Unmeasured / unmeasured** | Existing buffers already retain capacity. Count allocations and cache hits before selecting a change; compare output hashes after any scratch reuse. |
**Read-only pass complete.** No source edits, scripts, tests, builds or binaries were run.
+191 -9
View File
@@ -56,12 +56,14 @@ namespace
bool HasRenderDistanceOverride=false;
int RenderDistanceOverride=0;
bool FloatingIslandsRecipe=false;
bool CavesRecipe=false;
bool AuditHeightfieldProfile=false;
bool LandmarkChamberDemo=false;
bool LandmarkL45RampControl=false;
bool OreVeinsDemo=false;
bool AuditOreVeinYield=false;
bool AuditLandmarkReachability=false;
bool AuditNativeCaves=false;
bool TlsProbeRequested=false;
int TlsProbeWorkers=0;
std::string TlsProbeTouch="none";
@@ -779,6 +781,163 @@ namespace
return true;
}
bool MakeNativeCavesSpine(const VoxelForgeCore::ResolvedLayoutSnapshotV3& layout,
std::vector<VoxelForgeCore::ResolvedCavernRoomV1>& rooms,
std::vector<VoxelForgeCore::ResolvedCavernEdgeV1>& edges,
std::string& error)
{
using namespace VoxelForgeCore;
rooms.clear();edges.clear();error.clear();
const auto cave=std::find_if(layout.strates.begin(),layout.strates.end(),
[](const auto& value){return value.generatorKind=="Caves"
&&value.recipeId=="Core.Caves.PrimitiveField"&&value.recipeVersion==1;});
if(cave==layout.strates.end())
{error="native caves recipe has no version-1 cave strate";return false;}
std::vector<const ResolvedLayoutPassageV1*> mouths;
for(const auto& passage:layout.passages)
if(passage.lowerStrateIndex==cave->strateIndex)
mouths.push_back(&passage);
std::sort(mouths.begin(),mouths.end(),[](const auto* a,const auto* b)
{return a->stableId<b->stableId;});
if(mouths.size()!=2)
{
std::fprintf(stderr,"[CavesMouthBindingAudit] cave_strate=%d passages=%zu matching=%zu",
cave->strateIndex,layout.passages.size(),mouths.size());
for(const auto& passage:layout.passages)
std::fprintf(stderr," [%s upper=%d lower=%d]",passage.stableId.c_str(),
passage.upperStrateIndex,passage.lowerStrateIndex);
std::fprintf(stderr,"\n");
error="native caves spine fixture requires exactly two resolved arrival/departure mouths";return false;
}
const double micrometresPerSubvoxel=static_cast<double>(layout.coreVoxelSizeMicrometres)
/WorldAssemblySubvoxelsPerVoxel;
std::array<std::array<std::int64_t,3>,2> mouthMicrometres{};
for(std::size_t mouth=0;mouth<2;++mouth)
for(std::size_t axis=0;axis<3;++axis)
mouthMicrometres[mouth][axis]=static_cast<std::int64_t>(std::llround(
mouths[mouth]->lowerMouthSubvoxels[axis]*micrometresPerSubvoxel));
const double x0=mouthMicrometres[0][0]/1000000.0;
const double y0=mouthMicrometres[0][1]/1000000.0;
const double x1=mouthMicrometres[1][0]/1000000.0;
const double y1=mouthMicrometres[1][1]/1000000.0;
const double floorZ=0.5*(mouthMicrometres[0][2]+mouthMicrometres[1][2])/1000000.0;
const double dx=x1-x0,dy=y1-y0;
const double length=std::hypot(dx,dy);
std::printf("[CavesResolvedMouths] arrival=%s xyz_m=(%.3f,%.3f,%.3f) departure=%s xyz_m=(%.3f,%.3f,%.3f) horizontal_separation_m=%.3f\n",
mouths[0]->stableId.c_str(),x0,y0,mouthMicrometres[0][2]/1e6,
mouths[1]->stableId.c_str(),x1,y1,mouthMicrometres[1][2]/1e6,length);
const double mouthZ0=mouthMicrometres[0][2]/1000000.0;
const double mouthZ1=mouthMicrometres[1][2]/1000000.0;
const std::array<double,2> startFloorZ={mouthZ0-0.75,mouthZ1-0.75};
double directionX=0.0,directionY=0.0;
for(const auto* passage:mouths)
{
if(passage->controlPoints.size()<2)
{error="native caves lower mouth has no resolved passage tangent";return false;}
const auto last=std::min(passage->lowerMouthControlIndex,
passage->controlPoints.size()-1);
const auto previous=last>0?last-1:passage->controlPoints.size()-2;
const double tx=static_cast<double>(passage->controlPoints[last].positionSubvoxels[0]
-passage->controlPoints[previous].positionSubvoxels[0]);
const double ty=static_cast<double>(passage->controlPoints[last].positionSubvoxels[1]
-passage->controlPoints[previous].positionSubvoxels[1]);
const double tangentLength=std::hypot(tx,ty);
if(!(tangentLength>1.0e-9))
{error="native caves lower mouth has a degenerate passage tangent";return false;}
directionX+=tx/tangentLength;directionY+=ty/tangentLength;
}
const double directionLength=std::hypot(directionX,directionY);
if(!(directionLength>1.0e-9))
{error="native caves lower mouths have opposing passage tangents";return false;}
directionX/=directionLength;directionY/=directionLength;
if(std::hypot(length,mouthZ1-mouthZ0)<4.0)
{error="native caves arrival and departure mouths resolve to the same location";return false;}
ResolvedCavernRoomV1 room;
room.stableId="caves/spine/chamber-v1";room.strateIndex=cave->strateIndex;
const double roomOffset=std::max(32.0,
std::max(std::abs(floorZ-startFloorZ[0]),
std::abs(floorZ-startFloorZ[1]))/0.2679491924311227+27.0);
const double perpendicularX=-directionY;
const double perpendicularY=directionX;
room.centerMicrometres={static_cast<std::int64_t>(std::llround(
((x0+x1)*0.5+directionX*roomOffset)*1000000.0)),
static_cast<std::int64_t>(std::llround(
((y0+y1)*0.5+directionY*roomOffset)*1000000.0)),
static_cast<std::int64_t>(std::llround((floorZ+3.5)*1000000.0))};
room.halfExtentsMicrometres={7000000,6500000,4500000};
room.floorMicrometres=static_cast<std::int64_t>(std::llround(floorZ*1000000.0));
room.smoothWidthMicrometres=2400000;
rooms.push_back(room);
for(std::size_t route=0;route<2;++route)
{
const auto& mouth=mouthMicrometres[route];
const auto* passage=mouths[route];
const auto tangentLast=std::min(passage->lowerMouthControlIndex,
passage->controlPoints.size()-1);
const auto tangentPrevious=tangentLast>0?tangentLast-1:
passage->controlPoints.size()-2;
const double tangentX=static_cast<double>(
passage->controlPoints[tangentLast].positionSubvoxels[0]
-passage->controlPoints[tangentPrevious].positionSubvoxels[0]);
const double tangentY=static_cast<double>(
passage->controlPoints[tangentLast].positionSubvoxels[1]
-passage->controlPoints[tangentPrevious].positionSubvoxels[1]);
const double tangentLength=std::hypot(tangentX,tangentY);
if(!(tangentLength>1.0e-9))
{error="native caves lower-mouth overlap has a degenerate passage tangent";return false;}
constexpr double mouthOverlap=3.0;
const double startX=mouth[0]/1000000.0+tangentX/tangentLength*mouthOverlap;
const double startY=mouth[1]/1000000.0+tangentY/tangentLength*mouthOverlap;
const double roomX=room.centerMicrometres[0]/1000000.0;
const double roomY=room.centerMicrometres[1]/1000000.0;
const double toRoomX=roomX-startX,toRoomY=roomY-startY;
const double toRoomLength=std::hypot(toRoomX,toRoomY);
if(!(toRoomLength>5.0))
{error="native caves mouth does not clear the chamber portal";return false;}
const double portalSide=route==0?-3.5:3.5;
const double portalX=roomX+perpendicularX*portalSide;
const double portalY=roomY+perpendicularY*portalSide;
const double endZ=floorZ;
const double portalRun=std::hypot(portalX-startX,portalY-startY);
const double edgeStartFloorZ=startFloorZ[route]
+(endZ-startFloorZ[route])*mouthOverlap/(portalRun+mouthOverlap);
const double verticalRun=std::abs(endZ-edgeStartFloorZ);
const double maximumGrade=verticalRun/portalRun;
if(maximumGrade>0.2679492)
{error="native caves spine segment exceeds the 15 degree walk grade";return false;}
const double edgeX=portalX-startX,edgeY=portalY-startY;
ResolvedCavernEdgeV1 edge;
edge.stableId=route==0?"caves/spine/arrival-edge-v1"
:"caves/spine/departure-edge-v1";
edge.startAnchorStableId=mouths[route]->stableId+"/lower-mouth";
edge.endRoomStableId=room.stableId;edge.strateIndex=cave->strateIndex;
// Match the resolved lower-mouth opening so the native spine does not
// create a sudden shoulder where the legacy passage ends.
edge.boreRadiusMicrometres=4500000;
edge.boreHeightMicrometres=4500000;
edge.floorSupportDepthMicrometres=0;
edge.smoothWidthMicrometres=2400000;
const std::array<std::array<double,3>,4> points={{
{startX,startY,edgeStartFloorZ},
{startX+edgeX/3.0,startY+edgeY/3.0,
edgeStartFloorZ+(endZ-edgeStartFloorZ)/3.0},
{startX+2.0*edgeX/3.0,startY+2.0*edgeY/3.0,
edgeStartFloorZ+2.0*(endZ-edgeStartFloorZ)/3.0},
{portalX,portalY,endZ}}};
for(std::size_t point=0;point<points.size();++point)
for(std::size_t axis=0;axis<3;++axis)
edge.controlPointsMicrometres[point][axis]=static_cast<std::int64_t>(
std::llround(points[point][axis]*1000000.0));
edges.push_back(std::move(edge));
}
std::printf("[CavesSpinePlan] recipe=Core.Caves.PrimitiveField version=1 density_sign=negative_solid_positive_air arrival=%s departure=%s room=%s edges=%zu bore_radius_m=4.500 bore_height_m=4.500 mouth_overlap_m=3.000 horizontal_separation_m=%.3f heading=(%.3f,%.3f) room_offset_m=%.3f lower_mouth_floor_offset_m=-0.750 floor_blend_width_m=2.400000 ideal_fillet_radius_m=0.848528 grade_limit_deg=15.000 shared_layer=shared-bedrock status=PASS\n",
mouths[0]->stableId.c_str(),mouths[1]->stableId.c_str(),room.stableId.c_str(),
edges.size(),length,directionX,directionY,roomOffset);
return true;
}
bool MakeOreVeinDemoRequests(
const VoxelForgeCore::ResolvedLayoutSnapshotV3& layout,
std::vector<VoxelForgeCore::ResolvedLayoutOreVeinRequestV1>& requests,
@@ -964,7 +1123,7 @@ namespace
" --mesh-export=FILE (write the exact generated tile meshes for offline rendering)\n"
" --world-assembler-manifest-output=FILE (write the canonical Core recipe and plan)\n"
" --profile-column-overlap --far-horizon-lod8-step=N --force-generate\n"
" --seed=N --recipe=surface-world|floating-islands\n"
" --seed=N --recipe=surface-world|floating-islands|caves\n"
" --landmark-chamber-demo (opt-in SW cavern room and swept tunnel)\n"
" --ore-veins-demo (opt-in material-only SW capsule veins)\n"
" --audit-ore-vein-yield (measure one deterministic sphere carve)\n"
@@ -977,6 +1136,7 @@ namespace
" --audit-resolved-layout-passages (capsule, support, route-binding and blocked-control audit)\n"
" --audit-heightfield-profile (brute-force the profiled field intervals and route)\n"
" --audit-landmark-reachability (capsule, support, grade and buried-L43 control)\n"
" --audit-native-caves (arrival-to-departure capsule flood fill and interval oracle)\n"
" --audit-surface-clearance (sample biome terrain/caps against the resolved ceiling)\n"
" --auto-surface-camera (place the 27-tile cube on this seed's surface)\n"
" --mesh-coverage (report non-empty tiles and indexed triangles)\n"
@@ -1003,6 +1163,7 @@ namespace
else if(arg=="--audit-resolved-layout-boundaries")options.AuditResolvedLayoutBoundaries=true;
else if(arg=="--audit-resolved-layout-passages")options.AuditResolvedLayoutPassages=true;
else if(arg=="--audit-landmark-reachability")options.AuditLandmarkReachability=true;
else if(arg=="--audit-native-caves")options.AuditNativeCaves=true;
else if(arg=="--audit-surface-clearance")options.AuditSurfaceClearance=true;
else if(arg=="--audit-strate-transitions")options.AuditStrateTransitions=true;
else if(arg.starts_with("--transition-mode="))
@@ -1037,8 +1198,12 @@ namespace
else if(arg.starts_with("--recipe="))
{
const std::string_view recipe=arg.substr(std::string_view("--recipe=").size());
if(recipe=="surface-world")options.FloatingIslandsRecipe=false;
else if(recipe=="floating-islands")options.FloatingIslandsRecipe=true;
if(recipe=="surface-world")
{options.FloatingIslandsRecipe=false;options.CavesRecipe=false;}
else if(recipe=="floating-islands")
{options.FloatingIslandsRecipe=true;options.CavesRecipe=false;}
else if(recipe=="caves")
{options.FloatingIslandsRecipe=false;options.CavesRecipe=true;}
else {std::fprintf(stderr,"Invalid --recipe value: %.*s\n",static_cast<int>(recipe.size()),recipe.data());return false;}
}
else if(arg=="--landmark-chamber-demo")options.LandmarkChamberDemo=true;
@@ -1154,7 +1319,8 @@ int main(int argc,char** argv)
std::printf("[HeadlessConfig] data=%s workers=%d clip_radius=%d max_clip_level=%d max_level=%d hash=%s column_overlap=%s force_generate=%s recipe=%s shared_column_cache=%s camera_step_m=(%.3f,%.3f,%.3f) seed_override=%s render_distance_override=%s\n",
options.DataPath.string().c_str(),options.WorkerCount,options.ClipRadius,options.MaxClipLevel,
options.MaxLevel,options.StreamHash?"on":"off",options.ProfileColumnOverlap?"on":"off",
options.ForceGenerate?"on":"off",options.FloatingIslandsRecipe?"floating-islands":"surface-world",
options.ForceGenerate?"on":"off",options.CavesRecipe?"caves"
:options.FloatingIslandsRecipe?"floating-islands":"surface-world",
options.UseSharedColumnCache?"on":"off",options.CameraStepMeters.X,options.CameraStepMeters.Y,
options.CameraStepMeters.Z,
options.HasSeedOverride?"yes":"no",options.HasRenderDistanceOverride?"yes":"no");
@@ -1227,7 +1393,8 @@ int main(int argc,char** argv)
}
VoxelForgeCore::ResolvedLayoutSnapshotV3 resolvedLayout;
{
const std::string_view variant=options.FloatingIslandsRecipe?"floating-islands":"surface-world";
const std::string_view variant=options.CavesRecipe?"caves"
:options.FloatingIslandsRecipe?"floating-islands":"surface-world";
if(!StandaloneWorldAssemblyPrototype::BuildResolvedLayoutSnapshot(
*ownerWorld,worldAssembly,variant,resolvedLayout,error))
{std::fprintf(stderr,"Resolved-layout snapshot failed: %s\n",error.c_str());return 2;}
@@ -1277,6 +1444,15 @@ int main(int argc,char** argv)
resolvedLayout.cavernRooms.size(),resolvedLayout.cavernEdges.size(),
resolvedLayout.stamps.size(),resolvedLayout.cavernGraphVocabularyVersion);
}
if(options.CavesRecipe)
{
std::vector<VoxelForgeCore::ResolvedCavernRoomV1> rooms;
std::vector<VoxelForgeCore::ResolvedCavernEdgeV1> edges;
if(!MakeNativeCavesSpine(resolvedLayout,rooms,edges,error)
||!VoxelForgeCore::ResolveResolvedCavernGraphV1(
resolvedLayout,seed,rooms,edges,error))
{std::fprintf(stderr,"Native caves spine resolution failed: %s\n",error.c_str());return 2;}
}
if(options.OreVeinsDemo)
{
std::vector<VoxelForgeCore::ResolvedLayoutOreVeinRequestV1> requests;
@@ -1371,8 +1547,9 @@ int main(int argc,char** argv)
slot.StrateIndex,resolvedStrate->generatorKind.c_str(),
static_cast<std::uint32_t>(slot.Definition->PassageConfig.Style));
}
const auto requestedRecipe=options.FloatingIslandsRecipe
?std::string_view("floating-islands"):std::string_view("surface-world");
const auto requestedRecipe=options.CavesRecipe?std::string_view("caves")
:options.FloatingIslandsRecipe?std::string_view("floating-islands")
:std::string_view("surface-world");
const auto topStratum=std::max_element(resolvedLayout.strates.begin(),resolvedLayout.strates.end(),
[](const auto& a,const auto& b)
{return a.interiorTopMicrometres<b.interiorTopMicrometres;});
@@ -1386,7 +1563,12 @@ int main(int argc,char** argv)
&&std::any_of(worldAssembly.plan.strates.begin(),worldAssembly.plan.strates.end(),
[&](const auto& plan){return plan.strateIndex==stratum.strateIndex;});
});
const auto actualRecipe=hasFloatingIslands?std::string_view("floating-islands")
const bool hasNativeCavesRecipe=std::any_of(resolvedLayout.strates.begin(),resolvedLayout.strates.end(),
[](const auto& stratum){return stratum.generatorKind=="Caves"
&&stratum.recipeId=="Core.Caves.PrimitiveField"&&stratum.recipeVersion==1
&&stratum.passageCapabilities.hasNativeCaveField;});
const auto actualRecipe=hasNativeCavesRecipe?std::string_view("caves")
:hasFloatingIslands?std::string_view("floating-islands")
:hasSurfaceWorldPlan?std::string_view("surface-world")
:std::string_view("missing");
const char* topGenerator=topStratum==resolvedLayout.strates.end()
@@ -1586,5 +1768,5 @@ int main(int argc,char** argv)
options.AuditRouteParity,options.AuditResolvedLayoutBoundaries,
options.AuditResolvedLayoutPassages,options.AuditStrateTransitions,
&resolvedLayout,options.StreamHashTiles,options.AuditLandmarkReachability,
options.AuditOreVeinYield);
options.AuditOreVeinYield,options.AuditNativeCaves);
}
+322 -1
View File
@@ -1089,6 +1089,320 @@ namespace StandaloneStreamBenchmark
return passed;
}
inline bool AuditNativeCavesField(const VoxelForgeCore::World& shape,
const VoxelForgeCore::ResolvedLayoutSnapshotV3& layout,std::uint32_t voxelMicrometres)
{
using namespace VoxelForgeCore;
const double vm=std::max<std::uint32_t>(1,voxelMicrometres)/1e6;
const auto metres=[](const std::array<std::int64_t,3>& p)
{return std::array<double,3>{p[0]/1e6,p[1]/1e6,p[2]/1e6};};
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
&&s.passageCapabilities.hasNativeCaveField;});
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;}
std::vector<const ResolvedLayoutPassageV1*> mouths;
for(const auto& p:layout.passages)if(p.lowerStrateIndex==cave->strateIndex)mouths.push_back(&p);
std::sort(mouths.begin(),mouths.end(),[](auto* a,auto* b){return a->stableId<b->stableId;});
if(mouths.size()!=2)return false;
std::array<const ResolvedCavernEdgeV1*,2> edges{};
for(std::size_t i=0;i<2;++i)
{
const auto it=std::find_if(layout.cavernEdges.begin(),layout.cavernEdges.end(),
[&](const auto& e){return e.startAnchorStableId==mouths[i]->stableId+"/lower-mouth";});
if(it==layout.cavernEdges.end()||it->polylineMicrometres.size()<2
||it->strateIndex!=cave->strateIndex)return false;
edges[i]=&*it;
}
const auto& room=layout.cavernRooms.front();
const auto rc=metres(room.centerMicrometres);
const double floor=room.floorMicrometres/1e6;
struct Segment {std::array<double,3> a,b;};
std::vector<Segment> segments;
std::array<double,4> bounds={1e30,-1e30,1e30,-1e30};
double minFloor=floor,maxFloor=floor;
for(const auto* edge:edges)
{
for(std::size_t i=0;i<edge->polylineMicrometres.size();++i)
{
const auto p=metres(edge->polylineMicrometres[i]);
bounds[0]=std::min(bounds[0],p[0]);bounds[1]=std::max(bounds[1],p[0]);
bounds[2]=std::min(bounds[2],p[1]);bounds[3]=std::max(bounds[3],p[1]);
minFloor=std::min(minFloor,p[2]);maxFloor=std::max(maxFloor,p[2]);
if(i)segments.push_back({metres(edge->polylineMicrometres[i-1]),p});
}
}
const double roomX=room.halfExtentsMicrometres[0]/1e6;
const double roomY=room.halfExtentsMicrometres[1]/1e6;
bounds[0]=std::min(bounds[0],rc[0]-roomX);bounds[1]=std::max(bounds[1],rc[0]+roomX);
bounds[2]=std::min(bounds[2],rc[1]-roomY);bounds[3]=std::max(bounds[3],rc[1]+roomY);
constexpr double H=0.50,GradeLimit=0.2679491924311227;
bounds[0]-=4.0;bounds[1]+=4.0;bounds[2]-=4.0;bounds[3]+=4.0;
const int minX=static_cast<int>(std::floor(bounds[0]/H));
const int maxX=static_cast<int>(std::ceil(bounds[1]/H));
const int minY=static_cast<int>(std::floor(bounds[2]/H));
const int maxY=static_cast<int>(std::ceil(bounds[3]/H));
const int nx=maxX-minX+1,ny=maxY-minY+1;
if(nx<=0||ny<=0||static_cast<std::uint64_t>(nx)*ny>4000000)return false;
struct Node {int gx=0,gy=0;double x=0.0,y=0.0,floor=0.0;bool walkable=false;};
std::vector<Node> nodes;
std::vector<std::vector<int>> columns(static_cast<std::size_t>(nx)*ny);
const auto columnIndex=[&](int x,int y){return static_cast<std::size_t>(y-minY)*nx+(x-minX);};
const double bore=edges[0]->boreRadiusMicrometres/1e6;
std::uint64_t capsules=0,supports=0,floorQueries=0;
const auto testCapsule=[&](double x,double y,double surface)
{
bool clear=true;constexpr double radius=0.34,height=1.76;
for(double h=0.08;h<=height+1e-9;h+=0.18)
{
double cap=0;if(h<radius)cap=radius-h;else if(h>height-radius)cap=h-(height-radius);
const double radial=std::sqrt(std::max(0.0,radius*radius-cap*cap));
for(double oy=-radial;oy<=radial+1e-9;oy+=0.17)
for(double ox=-radial;ox<=radial+1e-9;ox+=0.17)
{if(ox*ox+oy*oy>radial*radial+1e-9)continue;++capsules;
if(density(x+ox,y+oy,surface+0.12+h)<0.025)clear=false;}
}
constexpr std::array<std::array<double,2>,5> head={{{0,0},{0.24,0},{-0.24,0},{0,0.24},{0,-0.24}}};
for(auto o:head)for(double h=0;h<=0.30+1e-9;h+=0.05)
if(density(x+o[0],y+o[1],surface+0.12+height+h)<0)clear=false;
constexpr std::array<std::array<double,2>,5> foot={{{0,0},{0.28,0},{-0.28,0},{0,0.28},{0,-0.28}}};
unsigned support=0;
for(auto o:foot){++supports;bool hit=false;for(double d=0.1;d<=0.55;d+=0.08)
if(density(x+o[0],y+o[1],surface-d)<0){hit=true;break;}if(hit)++support;}
return clear&&support>=3;
};
for(int gy=minY;gy<=maxY;++gy)for(int gx=minX;gx<=maxX;++gx)
{
const double x=gx*H,y=gy*H;double nearest=1e30;
for(const auto& s:segments)
{
const double dx=s.b[0]-s.a[0],dy=s.b[1]-s.a[1],l2=dx*dx+dy*dy;
const double t=l2>0?std::clamp(((x-s.a[0])*dx+(y-s.a[1])*dy)/l2,0.0,1.0):0.0;
nearest=std::min(nearest,std::hypot(x-(s.a[0]+t*dx),y-(s.a[1]+t*dy)));
}
const bool roomXY=std::abs(x-rc[0])<=roomX+0.5&&std::abs(y-rc[1])<=roomY+0.5;
if(nearest>bore+0.9&&!roomXY)continue;
double priorZ=minFloor-0.75,priorDensity=density(x,y,priorZ);
for(double z=priorZ+0.05;z<=maxFloor+0.75+1e-9;z+=0.05)
{
const double value=density(x,y,z);++floorQueries;
if(priorDensity<0.0&&value>=0.0)
{
double lo=z-0.05,hi=z;
for(int q=0;q<12;++q){const double mid=(lo+hi)*0.5;if(density(x,y,mid)<0.0)lo=mid;else hi=mid;}
const double surface=(lo+hi)*0.5;
Node node;node.gx=gx;node.gy=gy;node.x=x;node.y=y;node.floor=surface;
node.walkable=testCapsule(x,y,surface);
const int id=static_cast<int>(nodes.size());nodes.push_back(node);
columns[columnIndex(gx,gy)].push_back(id);
}
priorZ=z;priorDensity=value;
}
}
const auto mouth=[&](const ResolvedLayoutPassageV1& p)
{return std::array<double,3>{p.lowerMouthSubvoxels[0]*vm/WorldAssemblySubvoxelsPerVoxel,
p.lowerMouthSubvoxels[1]*vm/WorldAssemblySubvoxelsPerVoxel,
p.lowerMouthSubvoxels[2]*vm/WorldAssemblySubvoxelsPerVoxel};};
const auto start=mouth(*mouths[0]),end=mouth(*mouths[1]);
std::vector<int> starts,goals;
double nearestStart=std::numeric_limits<double>::infinity(),nearestEnd=nearestStart;
int nearestStartNode=-1,nearestEndNode=-1;
for(std::size_t id=0;id<nodes.size();++id)
{
const auto& n=nodes[id];if(!n.walkable)continue;
const double ds=std::hypot(std::hypot(n.x-start[0],n.y-start[1]),n.floor-start[2]);
const double de=std::hypot(std::hypot(n.x-end[0],n.y-end[1]),n.floor-end[2]);
if(ds<nearestStart){nearestStart=ds;nearestStartNode=static_cast<int>(id);}
if(de<nearestEnd){nearestEnd=de;nearestEndNode=static_cast<int>(id);}
if(ds<=2.0)starts.push_back(static_cast<int>(id));
if(de<=2.0)goals.push_back(static_cast<int>(id));
}
constexpr std::array<std::array<int,2>,8> dirs={{{1,0},{-1,0},{0,1},{0,-1},{1,1},{1,-1},{-1,1},{-1,-1}}};
const auto flood=[&](bool enforceGrade,std::uint64_t* rejected)
{
std::vector<int> parent(nodes.size(),-1);std::queue<int> open;
for(int id:starts){parent[static_cast<std::size_t>(id)]=-2;open.push(id);}
while(!open.empty())
{
const int current=open.front();open.pop();const auto& a=nodes[static_cast<std::size_t>(current)];
for(const auto& d:dirs)
{
const int gx=a.gx+d[0],gy=a.gy+d[1];if(gx<minX||gx>maxX||gy<minY||gy>maxY)continue;
const double run=H*std::hypot(d[0],d[1]);
for(const int next:columns[columnIndex(gx,gy)])
{
if(parent[static_cast<std::size_t>(next)]!=-1)continue;
const auto& b=nodes[static_cast<std::size_t>(next)];if(!b.walkable)continue;
if(enforceGrade&&std::abs(b.floor-a.floor)/run>GradeLimit)
{if(rejected)++*rejected;continue;}
parent[static_cast<std::size_t>(next)]=current;open.push(next);
}
}
}
return parent;
};
auto rawParent=flood(false,nullptr);std::uint64_t gradeRejected=0;
auto parent=flood(true,&gradeRejected);
const auto reachableGoal=[&](const std::vector<int>& parents)
{
int selected=-1;double distance=std::numeric_limits<double>::infinity();
for(int candidate:goals)if(parents[static_cast<std::size_t>(candidate)]!=-1)
{
const auto& n=nodes[static_cast<std::size_t>(candidate)];
const double d=std::hypot(std::hypot(n.x-end[0],n.y-end[1]),n.floor-end[2]);
if(d<distance){distance=d;selected=candidate;}
}
return std::pair<int,double>{selected,distance};
};
const auto [rawGoal,rawGoalD]=reachableGoal(rawParent);
const auto [goal,goalD]=reachableGoal(parent);
const bool rawReached=rawGoal>=0;
std::size_t gradedReachable=0,roomWalkable=0,roomReachable=0;
double reachableFloorMin=std::numeric_limits<double>::infinity();
double reachableFloorMax=-std::numeric_limits<double>::infinity();
for(std::size_t id=0;id<nodes.size();++id)
{
const auto& n=nodes[id];if(!n.walkable)continue;
const bool insideRoom=std::abs(n.x-rc[0])<=roomX+0.5
&&std::abs(n.y-rc[1])<=roomY+0.5;
if(insideRoom)++roomWalkable;
if(parent[id]!=-1)
{
++gradedReachable;reachableFloorMin=std::min(reachableFloorMin,n.floor);
reachableFloorMax=std::max(reachableFloorMax,n.floor);
if(insideRoom)++roomReachable;
}
}
std::printf("[CavesFloodFillGraph] graded_reachable=%zu room_walkable=%zu room_reachable=%zu reachable_floor_m=%.3f..%.3f arrival_floor_m=%.3f departure_floor_m=%.3f\n",
gradedReachable,roomWalkable,roomReachable,reachableFloorMin,reachableFloorMax,
nearestStartNode>=0?nodes[static_cast<std::size_t>(nearestStartNode)].floor:0.0,
nearestEndNode>=0?nodes[static_cast<std::size_t>(nearestEndNode)].floor:0.0);
for(std::size_t edgeIndex=0;edgeIndex<edges.size();++edgeIndex)
{
const auto& polyline=edges[edgeIndex]->polylineMicrometres;
std::vector<std::array<double,3>> linePoints;linePoints.reserve(polyline.size());
std::vector<double> cumulativeLength(polyline.size(),0.0);
for(const auto& p:polyline)linePoints.push_back(metres(p));
for(std::size_t i=1;i<linePoints.size();++i)
cumulativeLength[i]=cumulativeLength[i-1]+std::hypot(
std::hypot(linePoints[i][0]-linePoints[i-1][0],
linePoints[i][1]-linePoints[i-1][1]),
linePoints[i][2]-linePoints[i-1][2]);
std::printf("[CavesFloodFillProgress] edge=%s",
edges[edgeIndex]->stableId.c_str());
for(int sample=0;sample<=10;++sample)
{
const double distance=cumulativeLength.back()*sample/10.0;
const auto upper=std::lower_bound(cumulativeLength.begin(),
cumulativeLength.end(),distance);
const std::size_t upperIndex=std::clamp<std::size_t>(
static_cast<std::size_t>(upper-cumulativeLength.begin()),1,
cumulativeLength.size()-1);
const std::size_t lowerIndex=upperIndex-1;
const double span=cumulativeLength[upperIndex]-cumulativeLength[lowerIndex];
const double t=span>0.0?(distance-cumulativeLength[lowerIndex])/span:0.0;
const std::array<double,3> target={
linePoints[lowerIndex][0]+(linePoints[upperIndex][0]-linePoints[lowerIndex][0])*t,
linePoints[lowerIndex][1]+(linePoints[upperIndex][1]-linePoints[lowerIndex][1])*t,
linePoints[lowerIndex][2]+(linePoints[upperIndex][2]-linePoints[lowerIndex][2])*t};
double nearestWalkable=std::numeric_limits<double>::infinity();
double nearestReachable=nearestWalkable;
int nearestWalkableNode=-1,nearestReachableNode=-1;
for(std::size_t id=0;id<nodes.size();++id)
{
const auto& n=nodes[id];if(!n.walkable)continue;
const double d=std::hypot(std::hypot(n.x-target[0],n.y-target[1]),
n.floor-target[2]);
if(d<nearestWalkable){nearestWalkable=d;nearestWalkableNode=static_cast<int>(id);}
if(parent[id]!=-1&&d<nearestReachable)
{nearestReachable=d;nearestReachableNode=static_cast<int>(id);}
}
const double fieldFloor=density(target[0],target[1],target[2]);
const double fieldAbove=density(target[0],target[1],target[2]+0.3);
const double walkFloor=nearestWalkableNode>=0
?nodes[static_cast<std::size_t>(nearestWalkableNode)].floor:0.0;
const double reachFloor=nearestReachableNode>=0
?nodes[static_cast<std::size_t>(nearestReachableNode)].floor:0.0;
std::printf(" t%d=%.2f/%.2f(f%.2f/r%.2f):%.2f/%.2f",sample,
nearestWalkable,nearestReachable,walkFloor,reachFloor,fieldFloor,fieldAbove);
}
std::printf("\n");
}
double rawMaximumGrade=0.0;std::size_t rawPathCells=0;
if(rawReached)for(int p=rawGoal;p>=0;){++rawPathCells;const int prior=rawParent[static_cast<std::size_t>(p)];if(prior==-2)break;
const auto& a=nodes[static_cast<std::size_t>(p)];const auto& b=nodes[static_cast<std::size_t>(prior)];
const double run=H*std::hypot(a.gx-b.gx,a.gy-b.gy);rawMaximumGrade=std::max(rawMaximumGrade,
std::atan2(std::abs(a.floor-b.floor),run)*180.0/3.14159265358979323846);p=prior;}
const bool reached=goal>=0;std::vector<int> path;
if(reached){for(int p=goal;p>=0;){path.push_back(p);const int prior=parent[static_cast<std::size_t>(p)];if(prior==-2)break;p=prior;}std::reverse(path.begin(),path.end());}
double headroom=std::numeric_limits<double>::infinity();
for(int id:path){const auto& n=nodes[static_cast<std::size_t>(id)];double h=0.30;
for(double rise=0;rise<=0.30+1e-9;rise+=0.05)if(density(n.x,n.y,n.floor+0.12+1.76+rise)<0){h=std::max(0.0,rise-0.05);break;}
headroom=std::min(headroom,h);}
std::printf("[CavesFloodFillReachability] ungraded_connected=%s ungraded_path_cells=%zu ungraded_max_grade_deg=%.3f grade_rejected_edges=%llu\n",
rawReached?"yes":"no",rawPathCells,rawMaximumGrade,static_cast<unsigned long long>(gradeRejected));
std::printf("[CavesFloodFillEndpoints] start_seeds=%zu departure_candidates=%zu raw_goal=%s graded_goal=%s nearest_arrival_m=%.3f nearest_departure_m=%.3f selected_departure_m=%.3f raw_selected_departure_m=%.3f\n",
starts.size(),goals.size(),rawGoal>=0?"yes":"no",goal>=0?"yes":"no",nearestStart,nearestEnd,goalD,rawGoalD);
// Fit the actual zero contour in the wall-foot cross section; the local quadratic
// curvature is the fillet radius, W/(2 sqrt(2)) for this cubic smooth-max.
const auto& edge=*edges[0];const auto p0=metres(edge.polylineMicrometres[edge.polylineMicrometres.size()/2-1]);
const auto p1=metres(edge.polylineMicrometres[edge.polylineMicrometres.size()/2]);
const double tx=p1[0]-p0[0],ty=p1[1]-p0[1],tl=std::hypot(tx,ty),width=edge.smoothWidthMicrometres/1e6;
double measured=0;
const double horizontal=std::hypot(tx,ty),grade=horizontal>1e-9?(p1[2]-p0[2])/horizontal:0.0;
if(tl>1e-9&&horizontal>1e-9){const double nx=-ty/horizontal,ny=tx/horizontal;
const double upX=-grade*tx/horizontal/std::sqrt(1.0+grade*grade);
const double upY=-grade*ty/horizontal/std::sqrt(1.0+grade*grade);
const double upZ=1.0/std::sqrt(1.0+grade*grade);
const double cx=(p0[0]+p1[0])*0.5,cy=(p0[1]+p1[1])*0.5,cz=(p0[2]+p1[2])*0.5,r=edge.boreRadiusMicrometres/1e6;
const auto contour=[&](double v){double lo=0,hi=width;const auto f=[&](double u){const double in=(u+v)/std::sqrt(2.0),rise=(u-v)/std::sqrt(2.0);return density(cx+nx*(r-in)+upX*rise,cy+ny*(r-in)+upY*rise,cz+upZ*rise);};
if(!(f(lo)<0&&f(hi)>=0))return std::numeric_limits<double>::quiet_NaN();for(int i=0;i<28;++i){double m=(lo+hi)*0.5;if(f(m)<0)lo=m;else hi=m;}return (lo+hi)*0.5;};
constexpr double delta=0.02;const double a=contour(-delta),b=contour(0),c=contour(delta);if(std::isfinite(a)&&std::isfinite(b)&&std::isfinite(c))measured=delta*delta/(a+c-2*b);}
const double expected=width/(2.0*std::sqrt(2.0));
std::printf("[CavesFilletAudit] smooth_max=cubic width_m=%.6f measured_composed_radius_m=%.6f isolated_formula_radius_m=%.6f status=%s\n",
width,measured,expected,measured>=0.5?"PASS":"FAIL");
struct Check {const char* name;std::array<double,3> p;};
const auto lineStart=metres(edge.polylineMicrometres.front());
const auto lineEnd=metres(edge.polylineMicrometres.back());
const std::array<double,3> mid={
(lineStart[0]+lineEnd[0])*0.5,(lineStart[1]+lineEnd[1])*0.5,
(lineStart[2]+lineEnd[2])*0.5};
const std::array<Check,6> checks={{{"host-rock",{bounds[1]+1.5,rc[1],floor+2}},
{"spine-air",{mid[0],mid[1],mid[2]+1.6}},
{"room-air-center",{rc[0],rc[1],floor+2}},
{"room-air-east",{rc[0]+2.0,rc[1],floor+2}},
{"room-air-north",{rc[0],rc[1]+2.0,floor+2}},
{"wall-foot",{mid[0],mid[1]+bore-0.25,mid[2]+0.25}}}};
unsigned air=0,solid=0,miss=0,wallMixed=0;bool falseAir=false,falseSolid=false;
for(const auto& c:checks){const Vec3i o{static_cast<int>(std::llround(c.p[0]/vm)),static_cast<int>(std::llround(c.p[1]/vm)),static_cast<int>(std::llround(c.p[2]/vm))};
const auto verdict=shape.classifyBox(o,1,1);bool allAir=true,allSolid=true;std::uint64_t samples=0;
for(int z=0;z<=6;++z)for(int y=0;y<=6;++y)for(int x=0;x<=6;++x){const Vec3d q{o.x-1.0+0.5*x,o.y-1.0+0.5*y,o.z-1.0+0.5*z};const double d=density(q.x*vm,q.y*vm,q.z*vm);allAir&=d>=0;allSolid&=d<0;++samples;}
const bool bad=(verdict==TileClass::allAir&&!allAir)||(verdict==TileClass::allSolid&&!allSolid);miss+=bad;air+=verdict==TileClass::allAir;solid+=verdict==TileClass::allSolid;
if(std::string_view(c.name)=="host-rock")falseAir=verdict==TileClass::allSolid&&allSolid;
if(std::string_view(c.name).starts_with("room-air"))
falseSolid|=verdict==TileClass::allAir&&allAir;
if(std::string_view(c.name)=="wall-foot")wallMixed=verdict==TileClass::mixed;
std::printf("[CavesIntervalBox] id=%s proof=%s brute_air=%s brute_solid=%s samples=%llu miss=%s\n",c.name,
verdict==TileClass::allAir?"all-air":verdict==TileClass::allSolid?"all-solid":"mixed",allAir?"yes":"no",allSolid?"yes":"no",static_cast<unsigned long long>(samples),bad?"yes":"no");}
const bool intervalOk=air>=2&&solid>=1&&miss==0&&wallMixed&&falseAir&&falseSolid;
const bool reachable=reached&&path.size()>=2&&headroom>=0.30;
std::printf("[CavesCapsuleFloodFill] arrival=%s departure=%s grid_m=%.2f candidates=%zu walkable=%zu path_cells=%zu floor_queries=%llu capsule_samples=%llu support_samples=%llu max_grade_deg=15.000 min_headroom_m=%.3f required_headroom_m=0.300 reachable=%s status=%s\n",
mouths[0]->stableId.c_str(),mouths[1]->stableId.c_str(),H,nodes.size(),
std::count_if(nodes.begin(),nodes.end(),[](const auto& n){return n.walkable;}),path.size(),
static_cast<unsigned long long>(floorQueries),static_cast<unsigned long long>(capsules),
static_cast<unsigned long long>(supports),headroom,
reachable?"yes":"no",reachable?"PASS":"FAIL");
std::printf("[CavesIntervalOracle] boxes=%zu all_air=%u all_solid=%u misses=%u false_all_air=%s false_all_solid=%s wall_foot=%s status=%s\n",
checks.size(),air,solid,miss,falseAir?"REJECTED":"FAILED",falseSolid?"REJECTED":"FAILED",wallMixed?"mixed":"not-mixed",intervalOk?"PASS":"FAIL");
return reachable&&intervalOk&&measured>=0.5;
}
inline int RunPipelinedStreamingBenchmark(std::shared_ptr<FStandaloneOwnerWorld> OwnerWorld,
const VoxelForgeCore::ResolvedWorldSnapshot& CoreSnapshot,
const FVector& CameraPositionVoxels,int32 WorkerCount,int32 MaxLevel,
@@ -1108,7 +1422,7 @@ namespace StandaloneStreamBenchmark
bool bAuditStrateTransitions=false,
const VoxelForgeCore::ResolvedLayoutSnapshotV3* ResolvedLayout=nullptr,
bool bPrintTileHashes=false,bool bAuditLandmarkReachability=false,
bool bAuditOreVeinYield=false)
bool bAuditOreVeinYield=false,bool bAuditNativeCaves=false)
{
using Clock=std::chrono::steady_clock;
FStandaloneOwnerWorld& World=*OwnerWorld;
@@ -1293,6 +1607,13 @@ namespace StandaloneStreamBenchmark
Streamer.Stop();
std::fprintf(stderr,"[StreamPipeline] full shape build failed: %s\n",BuildError.c_str());return 2;
}
if(bAuditNativeCaves)
{
Streamer.Stop();
if(!ResolvedLayout||!CoreSnapshot.resolvedLayout())
{std::fprintf(stderr,"[CavesAudit] missing resolved layout\n");return 2;}
return AuditNativeCavesField(Shape,*ResolvedLayout,World.CoreVoxelSizeMicrons)?0:14;
}
if(bAuditOreVeinYield)
{
if(!ResolvedLayout||ResolvedLayout->oreVeins.empty())
+11 -5
View File
@@ -1015,17 +1015,23 @@ 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"
&&type==ECaveGeneratorType::SurfaceWorld;
const std::int64_t bottom=static_cast<std::int64_t>(slot.BottomChunkZ)*owner.StrateLayoutChunkSizeMicrometres;
const std::int64_t top=static_cast<std::int64_t>(slot.TopChunkZ+1)*owner.StrateLayoutChunkSizeMicrometres;
const std::int64_t seal=static_cast<std::int64_t>(std::llround(
std::max(0.0f,authored[0])*owner.OwnerVoxelSizeMicrons));
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=GeneratorKind(type);
s.stableId=out.worldIdentity+"/strate/"+std::to_string(slot.StrateIndex)+"/generator/"+GeneratorKind(type);
s.recipeId=type==ECaveGeneratorType::SurfaceWorld?"SurfaceWorld.Heightfield2D"
ResolvedLayoutStratumV2 s;s.strateIndex=slot.StrateIndex;
s.generatorKind=nativeCaves?"Caves":GeneratorKind(type);
s.stableId=out.worldIdentity+"/strate/"+std::to_string(slot.StrateIndex)
+"/generator/"+s.generatorKind;
s.recipeId=nativeCaves?"Core.Caves.PrimitiveField"
:type==ECaveGeneratorType::SurfaceWorld?"SurfaceWorld.Heightfield2D"
:type==ECaveGeneratorType::FloatingIslands?"FloatingIslands.ClusterField":"legacy."+std::string(GeneratorKind(type));
s.recipeVersion=type==ECaveGeneratorType::SurfaceWorld?assembly.recipe.schemaVersion
s.recipeVersion=nativeCaves?1
:type==ECaveGeneratorType::SurfaceWorld?assembly.recipe.schemaVersion
:type==ECaveGeneratorType::FloatingIslands&&!assembly.floatingIslands.empty()
?assembly.floatingIslands.front().manifest.recipe.geometryVersion:0;
s.voxelSizeMicrometres=owner.CoreVoxelSizeMicrons;s.latticeOriginMicrometres={0,0,0};
@@ -1144,7 +1150,7 @@ namespace StandaloneWorldAssemblyPrototype
s.content={"content",bottom+seal,top-seal,true,true,false,"resolved generator content","none","none",solidDensity};
s.bedrock={"bedrock",bottom,bottom+seal,true,true,false,"solid floor from strate BoundarySealThickness; breakable by default",
"interior-facing upper face","shared resolved-layout density band",solidDensity};
if(type==ECaveGeneratorType::SurfaceWorld)
if(type==ECaveGeneratorType::SurfaceWorld&&!nativeCaves)
{
if(assembly.recipe.surfaceProfile.version!=0)
{
+276 -2
View File
@@ -25,6 +25,7 @@ VERIFY_DIR = ROOT / "Standalone/Tools/verify"
LINUX37 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux37"
LINUX47 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux47"
LINUX53 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux53"
LINUX54 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux54"
OWNER = ROOT / "Standalone/data/owner_world.json"
OUT_DEFAULT = ROOT / "Saved/Linux/l42"
@@ -62,6 +63,7 @@ TILE_MATERIAL_HASH_RE = re.compile(
r"material=(0x[0-9a-f]+)$", re.M)
CAMERA_SWEEP_SEED = 0x4C3434
PROFILE_CAMERA_SWEEP_SEED = 0x4C3533
CAVES_CAMERA_SWEEP_SEED = 0x4C3534
PROFILE_NAMES = ("periodicHills", "mirroredRelief", "flatField")
RECIPE_RE = re.compile(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$", re.M)
@@ -391,6 +393,16 @@ def run_hash_pairs(runner: VerificationRunner,
f"baseline_hashes={len(old_hashes)}; worker determinism is gated"}
runner.steps.append(diagnostic)
print(f"[{runner.tier}] {diagnostic['name']} {diagnostic['detail']}", flush=True)
l53_baseline = ROOT / "Saved/Linux/l53" / f"owner-hash-{recipe}-style{style}-w12.log"
l53_hashes = tuple(HASH_RE.findall(l53_baseline.read_text(
encoding="utf-8", errors="replace")))
same_l53 = hashes[12] == l53_hashes and len(l53_hashes) == 12
l53_diagnostic = {"name": f"owner-hash-{recipe}-matches-L53-diagnostic",
"status": "DIAGNOSTIC", "detail": f"matches={same_l53} "
f"baseline={l53_baseline.name} baseline_hashes={len(l53_hashes)}"}
runner.steps.append(l53_diagnostic)
print(f"[{runner.tier}] {l53_diagnostic['name']} {l53_diagnostic['detail']}",
flush=True)
def run_default_profile_identity(runner: VerificationRunner, binary: Path) -> None:
@@ -598,6 +610,258 @@ def cavern_audit_cameras(layout_path: Path) -> list[tuple[float, float, float, f
return cameras
def build_caves_fixture(runner: VerificationRunner, binary: Path) -> Path:
fixture = OUT / "fixtures" / "caves.json"
builder = LINUX54 / "BuildCaveFixtureL54.py"
runner.run_stage("build-native-caves-fixture-l54", builder,
["--owner", str(OWNER), "--out", str(fixture)], binary=binary,
data=OWNER, recipe="caves", style=None, env=env_for(),
script_inputs=[builder, OWNER])
return fixture
def run_native_caves_field_audit(runner: VerificationRunner, binary: Path) -> tuple[Path, Path]:
fixture = build_caves_fixture(runner, binary)
compiler = compiler_name(binary)
layout_path = OUT / "layouts" / f"native-caves-field-{compiler}.json"
row, output = runner.run_cpp(f"native-caves-field-{compiler}", binary, fixture,
"caves", ["--audit-native-caves", "--max-clip-level=0",
"--render-distance-chunks=0"],
expected_style=2, expected_strate_styles={"0": 2, "1": 1}, workers=12,
max_level=0, layout_path=layout_path,
script_inputs=[LINUX54 / "BuildCaveFixtureL54.py"])
plan = re.search(r"^\[CavesSpinePlan\] .*recipe=Core\.Caves\.PrimitiveField version=1 "
r"density_sign=negative_solid_positive_air .*edges=2 .*bore_radius_m=4\.500 "
r"bore_height_m=4\.500 .*floor_blend_width_m=([0-9.]+) "
r"ideal_fillet_radius_m=([0-9.]+) .*shared_layer=shared-bedrock status=PASS$",
output, re.M)
runner.require(f"native-caves-field-{compiler}:version-and-binding", bool(plan),
"native caves recipe v1, negative-solid/positive-air, two spine edges, shared layer")
fillet = re.search(r"^\[CavesFilletAudit\].*measured_composed_radius_m=([0-9.]+) "
r".*status=PASS$", output, re.M)
runner.require(f"native-caves-field-{compiler}:measured-fillet",
bool(fillet and float(fillet.group(1)) >= 0.5),
f"measured_composed_radius_m={fillet.group(1) if fillet else 'MISSING'} required>=0.500")
flood = re.search(r"^\[CavesCapsuleFloodFill\].*min_headroom_m=([0-9.]+) "
r"required_headroom_m=0\.300 reachable=yes status=PASS$", output, re.M)
runner.require(f"native-caves-field-{compiler}:arrival-to-departure",
bool(flood and float(flood.group(1)) >= 0.3),
f"reachable={bool(flood)} min_headroom_m={flood.group(1) if flood else 'MISSING'}")
runner.require(f"native-caves-field-{compiler}:interval-oracle-and-controls",
all_match(output, r"^\[CavesIntervalOracle\] .*misses=0 "
r"false_all_air=REJECTED false_all_solid=REJECTED .*status=PASS$"),
"brute-force interval proofs have zero misses and reject both false-proof controls")
layout = json.loads(layout_path.read_text(encoding="utf-8"))
graph_ok = (layout.get("cavern_graph_vocabulary_version") == 1
and len(layout.get("cavern_rooms", [])) == 1
and len(layout.get("cavern_edges", [])) == 2)
runner.require(f"native-caves-field-{compiler}:resolved-graph", graph_ok,
f"vocabulary={layout.get('cavern_graph_vocabulary_version')} "
f"rooms={len(layout.get('cavern_rooms', []))} edges={len(layout.get('cavern_edges', []))}")
if plan:
row["floor_blend_width_m"] = float(plan.group(1))
row["ideal_fillet_radius_m"] = float(plan.group(2))
if fillet:
row["measured_composed_fillet_radius_m"] = float(fillet.group(1))
if flood:
row["minimum_headroom_m"] = float(flood.group(1))
return fixture, layout_path
def cave_camera_positions(layout_path: Path, count: int = 4) -> list[tuple[float, float, float, float, float]]:
layout = json.loads(layout_path.read_text(encoding="utf-8"))
edges = sorted(layout.get("cavern_edges", []), key=lambda edge: edge["stable_id"])
if layout.get("cavern_graph_vocabulary_version") != 1 or len(edges) != 2:
raise VerificationFailure(f"missing two resolved cave spine edges in {layout_path}")
rng = _SplitMix64(CAVES_CAMERA_SWEEP_SEED)
cameras = []
for index in range(count):
edge = edges[index % len(edges)]
points = [[value / 1_000_000.0 for value in point]
for point in edge["control_points_um"]]
t = rng.between(0.30, 0.72)
one = 1.0 - t
position = [one ** 3 * points[0][axis]
+ 3.0 * one ** 2 * t * points[1][axis]
+ 3.0 * one * t ** 2 * points[2][axis]
+ t ** 3 * points[3][axis] for axis in range(3)]
tangent = [3.0 * one ** 2 * (points[1][axis] - points[0][axis])
+ 6.0 * one * t * (points[2][axis] - points[1][axis])
+ 3.0 * t ** 2 * (points[3][axis] - points[2][axis])
for axis in range(3)]
horizontal = math.hypot(tangent[0], tangent[1])
if horizontal <= 1.0e-9:
raise VerificationFailure(f"degenerate cave camera tangent in {layout_path}")
side = (-tangent[1] / horizontal, tangent[0] / horizontal)
lateral = rng.between(-1.0, 1.0)
x = position[0] + side[0] * lateral
y = position[1] + side[1] * lateral
z = position[2] + rng.between(1.40, 1.60)
yaw = math.degrees(math.atan2(tangent[0], tangent[1])) + rng.between(-18.0, 18.0)
pitch = rng.between(-8.0, 8.0)
cameras.append((x, y, z, yaw, pitch))
return cameras
def run_caves_determinism(runner: VerificationRunner, binary: Path, fixture: Path,
layout_path: Path, *, compare_reference: bool) -> None:
compiler = compiler_name(binary)
reference_path = OUT / "caves-determinism-reference.json"
if compare_reference:
try:
reference = json.loads(reference_path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as error:
raise VerificationFailure(f"missing Clang cave camera reference: {error}")
else:
reference = None
current: dict[str, dict[str, dict[str, list[str]]]] = {}
for camera_index, camera in enumerate(cave_camera_positions(layout_path)):
x, y, z, yaw, pitch = camera
camera_flag = f"--camera={x:.6f},{y:.6f},{z:.6f},{yaw:.3f},{pitch:.3f}"
signatures: dict[int, dict[tuple[int, int, int, int], tuple[str, ...]]] = {}
key = f"p{camera_index:02d}"
current[key] = {}
for workers in (1, 8, 12):
name = (f"caves-camera-{compiler}-s{CAVES_CAMERA_SWEEP_SEED:06x}-"
f"p{camera_index:02d}-w{workers}")
row, output = runner.run_cpp(name, binary, fixture, "caves",
["--stream-hash-tiles", camera_flag, "--clip-radius=3",
"--max-clip-level=0", "--max-level=0", "--render-distance-chunks=0"],
expected_style=2, expected_strate_styles={"0": 2, "1": 1},
workers=workers, max_level=0,
layout_path=OUT / "layouts" / f"{name}.json")
row["camera_seed"] = f"0x{CAVES_CAMERA_SWEEP_SEED:06x}"
row["camera_index"] = camera_index
row["camera_position_m"] = [round(value, 6) for value in camera[:3]]
row["camera_region"] = "inside-native-caves-spine"
tiles = parse_tile_hashes(output)
whole = re.search(r"^\[PipelineWholeViewHash\] tiles=(\d+)", output, re.M)
expected_rows = int(whole.group(1)) if whole else -1
runner.require(f"{name}:tile-hash-rows",
bool(tiles) and len(tiles) == expected_rows,
f"rows={len(tiles)} expected={expected_rows} camera_m={camera[:3]}")
signatures[workers] = tiles
current[key][str(workers)] = {
"/".join(map(str, tile)): list(value)
for tile, value in sorted(tiles.items())}
for left, right in ((1, 8), (1, 12), (8, 12)):
runner.require(f"caves-camera-{compiler}-{key}:W{left}-W{right}",
signatures[left] == signatures[right],
f"camera_m=({x:.3f},{y:.3f},{z:.3f}) W{left}=W{right} "
"density/mesh/payload/topology tile hashes match")
if compare_reference:
runner.require("caves-camera-gcc:matches-clang-peers", current == reference,
"random cave camera tile hashes match Clang at W1/W8/W12")
else:
reference_path.write_text(json.dumps(current, indent=2, sort_keys=True) + "\n",
encoding="utf-8")
def run_caves_performance(runner: VerificationRunner, binary: Path,
fixture: Path, layout_path: Path) -> None:
compiler = compiler_name(binary)
camera = cave_camera_positions(layout_path, count=1)[0]
x, y, z, yaw, pitch = camera
camera_flag = f"--camera={x:.6f},{y:.6f},{z:.6f},{yaw:.3f},{pitch:.3f}"
row, output = runner.run_cpp(f"caves-speed-{compiler}-w12", binary, fixture,
"caves", [camera_flag, "--clip-radius=4", "--max-clip-level=0",
"--max-level=0", "--render-distance-chunks=0",
"--camera-step=24,0,0", "--region-crossing"],
expected_style=2, expected_strate_styles={"0": 2, "1": 1}, workers=12,
max_level=0, layout_path=OUT / "layouts" / f"caves-speed-{compiler}-w12.json")
stream = re.search(r"^\[StreamPipeline\] .*first_tile_ready=([0-9.]+)s "
r".*neighborhood_ready=([0-9.]+)s .*whole_view_ready=([0-9.]+)s "
r".*cache_hits=0", output, re.M)
moving = re.search(r"^\[MovingTileLatency\] entering_tiles=(\d+) "
r"p50_ms=([0-9.]+) p95_ms=([0-9.]+) metric=submit_to_completion_observed$",
output, re.M)
runner.require(f"caves-speed-{compiler}:cold-and-moving-records",
bool(stream and moving and int(moving.group(1)) > 0),
f"cold_first_tile_s={stream.group(1) if stream else 'MISSING'} "
f"near_ring_s={stream.group(2) if stream else 'MISSING'} "
f"cold_whole_view_s={stream.group(3) if stream else 'MISSING'} "
f"moving_p50_ms={moving.group(2) if moving else 'MISSING'} "
f"moving_p95_ms={moving.group(3) if moving else 'MISSING'}")
if stream and moving:
row["speed_metrics"] = {
"cold_first_tile_seconds": float(stream.group(1)),
"near_ring_seconds": float(stream.group(2)),
"cold_whole_view_seconds": float(stream.group(3)),
"moving_entering_tiles": int(moving.group(1)),
"moving_p50_ms": float(moving.group(2)),
"moving_p95_ms": float(moving.group(3)),
"workers": 12,
"camera_m": [round(value, 6) for value in camera[:3]],
}
def run_caves_surface_evidence(runner: VerificationRunner, fixture: Path,
layout_path: Path) -> None:
capture_script = LINUX54 / "CaptureNativeCavesL54.py"
artifact_dir = OUT / "captures"
_, capture_output = runner.run_stage("capture-native-caves-l54", capture_script,
["--out", str(OUT), "--fixture", str(fixture), "--layout", str(layout_path),
"--headless", str(CLANG), "--renderer", str(RENDERER),
"--artifact-dir", str(artifact_dir)], binary=CLANG, data=fixture,
recipe="caves", style=2, env=env_for(capture=True),
script_inputs=[capture_script, VERIFY_DIR / "capture_quality.py"])
capture_rows = re.findall(r"^\[L54CavesCapture\] view=([^ ]+) .*status=PASS$",
capture_output, re.M)
runner.require("capture-native-caves-l54:coverage",
set(capture_rows) == {"spine-eye", "room-from-entrance", "cutaway-overview"}
and len(capture_rows) == 3,
f"views={capture_rows}")
script = LINUX54 / "AuditWholeSurfaceL54.py"
audit_layout = ROOT / "Saved/Linux/l48/layouts/landmark-reachability-clang.json"
controls = (
("L45-ramp-control", ROOT / "Saved/Linux/l45/landmark-l45.vfmesh", "fail"),
("L46-entrance-control", ROOT / "Saved/Linux/l46/landmark-l46.vfmesh", "fail"),
("L47-wall-foot-control", ROOT / "Saved/Linux/l47/landmark-l47.vfmesh", "fail"),
("plain-native-control", ROOT / "Saved/Linux/l48/control-plain-native.vfmesh", "pass"),
)
mesh = OUT / "caves-meshes" / "caves-whole-view.vfmesh"
cases = [("caves-base", mesh, layout_path, "measure", "whole")]
cases.extend((name, control_mesh, audit_layout, expect, "whole")
for name, control_mesh, expect in controls)
svg_dir = artifact_dir
base_args = ["--json", str(OUT / "whole-surface-caves-base.json"),
"--svg-dir", str(svg_dir)]
for label, case_mesh, case_layout, expect, focus in cases:
base_args.extend(("--case", label, str(case_mesh), str(case_layout), expect, focus))
_, base_output = runner.run_stage("whole-surface-caves-base-l54", script, base_args,
binary=CLANG, data=fixture, recipe="caves", style=2,
layout_path=layout_path, env=env_for(),
script_inputs=[script, mesh, layout_path, audit_layout,
*(item[1] for item in controls)])
candidate = re.search(r"^\[WholeSurfaceAudit\] label=caves-base .*"
r"observed=(PASS|FAIL) expected=MEASURE status=PASS$", base_output, re.M)
runner.require("whole-surface-caves-base-l54:pass",
bool(candidate and candidate.group(1) == "PASS"),
f"observed={candidate.group(1) if candidate else 'MISSING'}")
for label, _control_mesh, expectation in controls:
match = re.search(rf"^\[WholeSurfaceAudit\] label={re.escape(label)} .*"
rf"observed=(PASS|FAIL) expected={expectation.upper()} status=PASS$",
base_output, re.M)
runner.require(f"whole-surface-caves-base-l54:{label}", bool(match),
f"control_expectation={expectation} observed={match.group(1) if match else 'MISSING'}")
for phase, label in ((-0.5, "minus-half"), (0.5, "plus-half")):
phase_args = ["--json", str(OUT / f"whole-surface-caves-{label}.json"),
"--svg-dir", str(svg_dir), "--phase", str(phase),
"--case", f"caves-{label}", str(mesh), str(layout_path), "measure", "whole"]
_, phase_output = runner.run_stage(f"whole-surface-caves-{label}-l54", script,
phase_args, binary=CLANG, data=fixture, recipe="caves", style=2,
layout_path=layout_path, env=env_for(),
script_inputs=[script, mesh, layout_path])
match = re.search(r"^\[WholeSurfaceAudit\] label=caves-" + label + r" .*"
r"phase_h=[+-]0\.5 .*observed=(PASS|FAIL) expected=MEASURE status=PASS$",
phase_output, re.M)
runner.require(f"whole-surface-caves-{label}-l54:pass",
bool(match and match.group(1) == "PASS"),
f"observed={match.group(1) if match else 'MISSING'}")
def run_random_camera_sweep(runner: VerificationRunner, binaries: tuple[Path, ...],
camera_count: int,
configurations: tuple[tuple[int, int, int], ...]) -> None:
@@ -885,7 +1149,7 @@ def run_profile_fixtures(runner: VerificationRunner, binary: Path) -> dict[str,
def run_profile_captures(runner: VerificationRunner) -> None:
script = LINUX53 / "CaptureHeightfieldProfilesL53.py"
quality = VERIFY_DIR / "capture_quality.py"
artifacts = LINUX53 / "captures"
artifacts = OUT / "captures"
_, output = runner.run_stage("capture-profiled-heightfields-l53", script,
["--out", str(OUT), "--fixture-dir", str(OUT / "fixtures"),
"--artifact-dir", str(artifacts), "--headless", str(CLANG),
@@ -1144,7 +1408,7 @@ def run_captures_and_mesh_audits(runner: VerificationRunner) -> None:
landmark_layout = OUT / "layouts" / "landmark-reachability-clang.json"
runner.run_stage("capture-landmark-l47", landmark_script,
["--layout", str(landmark_layout), "--artifact-dir", str(OUT),
"--capture-dir", str(LINUX47 / "captures"),
"--capture-dir", str(OUT / "captures"),
"--data", str(OWNER)],
binary=CLANG, data=OWNER, recipe="surface-world", style=1,
layout_path=landmark_layout, env=env_for(capture=True),
@@ -1345,6 +1609,10 @@ def run_medium_suite(runner: VerificationRunner, *, both_compilers: bool = False
run_profile_random_camera_sweep(runner, CLANG, compare_reference=False)
run_random_camera_sweep(runner, (CLANG,), 5,
((3, 0, 0), (2, 1, 1), (3, 2, 1)))
fixture, layout_path = run_native_caves_field_audit(runner, CLANG)
run_caves_determinism(runner, CLANG, fixture, layout_path, compare_reference=False)
run_caves_performance(runner, CLANG, fixture, layout_path)
run_caves_surface_evidence(runner, fixture, layout_path)
def run_gcc_supplement(runner: VerificationRunner) -> None:
@@ -1357,6 +1625,11 @@ def run_gcc_supplement(runner: VerificationRunner) -> None:
run_profile_fixtures(runner, GCC)
run_profile_determinism(runner, GCC, compare_reference=True)
run_profile_random_camera_sweep(runner, GCC, compare_reference=True)
fixture = OUT / "fixtures" / "caves.json"
layout_path = OUT / "layouts" / "native-caves-field-clang.json"
if not fixture.is_file() or not layout_path.is_file():
fixture, layout_path = run_native_caves_field_audit(runner, CLANG)
run_caves_determinism(runner, GCC, fixture, layout_path, compare_reference=True)
def _tier_deadline(_signum: int, _frame: object) -> None:
@@ -1385,6 +1658,7 @@ def main() -> int:
run_ore_vein_hash_controls(runner)
run_random_camera_sweep(runner, (CLANG,), 3, ((3, 0, 0),))
run_ring(runner, full=False)
run_native_caves_field_audit(runner, CLANG)
elif ARGS.tier == "medium":
run_medium_suite(runner, both_compilers=False)
else: