skip: passage proof bounds include wall/floor relief (L75 soundness fix); FI -7.6%
L74's passage bound omitted the 0.21 m wall relief and 0.20 m floor relief, so its whole-tile
proof was unsound in principle (no tested view hit it). Passage support is now bounded per route
segment by four parameter-subinterval boxes using Catmull-Rom derivative extrema, local max
radius and relief padding. Stratum slicing only at LOD0-2 in FI/VS. Legacy sub-tile block
certificates are audit-only (runtime version was slower). TN regression (-80,-20,-3218) kept.
Exact full-grid vs skip hashes on every tile, 4 views, Clang/GCC, W1/W8/W12; control caught;
80-camera sweep 0 violations. Whole view vs c67b200: SW -1.3%, FI -7.6%, TN -2.3%, VS +0.2%.
Compiles in the Linux UE host (VFHostEditor, 27/27 actions).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
1 parent
c67b2006aa
commit
9d5455ec68
16 files changed
+1335
-59
No files matched your search
@@ -252,6 +252,9 @@ namespace VoxelForgeCore
|
|||||||
std::uint32_t farHorizonVersion = 0;
|
std::uint32_t farHorizonVersion = 0;
|
||||||
bool farShellRepairSkipped = false;
|
bool farShellRepairSkipped = false;
|
||||||
bool tileWasClassified = false;
|
bool tileWasClassified = false;
|
||||||
|
std::uint8_t tileProofVetoReason = 0;
|
||||||
|
bool tileProofSafe = false;
|
||||||
|
bool tileLegacyBlockProofRelevant = false;
|
||||||
bool meshingSkipped = false;
|
bool meshingSkipped = false;
|
||||||
bool featureRefinedResolution = false;
|
bool featureRefinedResolution = false;
|
||||||
bool heightfieldBandLimited = false;
|
bool heightfieldBandLimited = false;
|
||||||
|
|||||||
@@ -1570,8 +1570,29 @@ public:
|
|||||||
*floorAlignment*floorAlignment*floorBelowWeight*mouthWalkLaneWeight;
|
*floorAlignment*floorAlignment*floorBelowWeight*mouthWalkLaneWeight;
|
||||||
const float fullyCarvedDensity=std::max(composed,
|
const float fullyCarvedDensity=std::max(composed,
|
||||||
static_cast<float>(-roughSignedDistance/Q));
|
static_cast<float>(-roughSignedDistance/Q));
|
||||||
|
static const bool bL75TraceTnPassage=[]
|
||||||
|
{
|
||||||
|
const char* Value=std::getenv("VOXELFORGE_L75_TN_BLOCK_REGRESSION");
|
||||||
|
return Value&&Value[0]=='1'&&Value[1]=='\0';
|
||||||
|
}();
|
||||||
|
const float passageInput=composed;
|
||||||
composed=std::lerp(composed,fullyCarvedDensity,
|
composed=std::lerp(composed,fullyCarvedDensity,
|
||||||
static_cast<float>(floorCarveWeight));
|
static_cast<float>(floorCarveWeight));
|
||||||
|
if(bL75TraceTnPassage&&X==-80.0f&&Y==-20.0f&&Z==-3218.0f
|
||||||
|
&&composed!=passageInput)
|
||||||
|
{
|
||||||
|
const auto& traceA=passage.controlPoints[static_cast<std::size_t>(bestIndex)];
|
||||||
|
const auto& traceB=passage.controlPoints[static_cast<std::size_t>(bestIndex+1)];
|
||||||
|
std::printf("[L75TNPassageTrace] passage=%zu segment=%td t=%.9g input=%.9g output=%.9g signed=%.9g rough=%.9g carve_weight=%.9g bounds=(%lld,%lld,%lld)..(%lld,%lld,%lld)\n",
|
||||||
|
passageIndex,bestIndex,bestT,passageInput,composed,bestSignedDistance,
|
||||||
|
roughSignedDistance,floorCarveWeight,
|
||||||
|
static_cast<long long>(std::min(traceA.positionSubvoxels[0],traceB.positionSubvoxels[0])),
|
||||||
|
static_cast<long long>(std::min(traceA.positionSubvoxels[1],traceB.positionSubvoxels[1])),
|
||||||
|
static_cast<long long>(std::min(traceA.positionSubvoxels[2],traceB.positionSubvoxels[2])),
|
||||||
|
static_cast<long long>(std::max(traceA.positionSubvoxels[0],traceB.positionSubvoxels[0])),
|
||||||
|
static_cast<long long>(std::max(traceA.positionSubvoxels[1],traceB.positionSubvoxels[1])),
|
||||||
|
static_cast<long long>(std::max(traceA.positionSubvoxels[2],traceB.positionSubvoxels[2])));
|
||||||
|
}
|
||||||
const float composedAfterCarve=composed;
|
const float composedAfterCarve=composed;
|
||||||
const auto considerSupportSegment=[&](std::ptrdiff_t index)
|
const auto considerSupportSegment=[&](std::ptrdiff_t index)
|
||||||
{
|
{
|
||||||
@@ -1999,15 +2020,31 @@ public:
|
|||||||
const float composed=nativeLattice?boundary
|
const float composed=nativeLattice?boundary
|
||||||
:ApplyResolvedStratePassageCarves(boundary,X,Y,Z);
|
:ApplyResolvedStratePassageCarves(boundary,X,Y,Z);
|
||||||
const float stamped=StampRuntime.empty()?composed:ApplyResolvedSdfStamps(composed,X,Y,Z);
|
const float stamped=StampRuntime.empty()?composed:ApplyResolvedSdfStamps(composed,X,Y,Z);
|
||||||
|
static const bool bL75TraceTnComposition=[]
|
||||||
|
{
|
||||||
|
const char* Value=std::getenv("VOXELFORGE_L75_TN_BLOCK_REGRESSION");
|
||||||
|
return Value&&Value[0]=='1'&&Value[1]=='\0';
|
||||||
|
}();
|
||||||
|
const bool bTraceTnComposition=bL75TraceTnComposition
|
||||||
|
&&X==-80.0f&&Y==-20.0f&&Z==-3218.0f;
|
||||||
if(bound&&bound->Kind==FBoundStratumField::EKind::Caves
|
if(bound&&bound->Kind==FBoundStratumField::EKind::Caves
|
||||||
&&bound->Resolved&&VoxelForgeCore::HasNativeGraphField(*bound->Resolved)
|
&&bound->Resolved&&VoxelForgeCore::HasNativeGraphField(*bound->Resolved)
|
||||||
&&(VoxelForgeCore::IsNativeCaveFieldStratum(*bound->Resolved)
|
&&(VoxelForgeCore::IsNativeCaveFieldStratum(*bound->Resolved)
|
||||||
?bound->Resolved->passageCapabilities.hasNativeCaveField
|
?bound->Resolved->passageCapabilities.hasNativeCaveField
|
||||||
:bound->Resolved->passageCapabilities.hasNativeLatticeField))
|
:bound->Resolved->passageCapabilities.hasNativeLatticeField))
|
||||||
|
{
|
||||||
|
if(bTraceTnComposition)
|
||||||
|
std::printf("[L75TNCompositionTrace] input=%.9g graph=%.9g boundary=%.9g passage=%.9g stamp=%.9g native=1\n",
|
||||||
|
density,graphDensity,boundary,composed,stamped);
|
||||||
return stamped;
|
return stamped;
|
||||||
|
}
|
||||||
// The legacy landmark demo retains its compatibility floor cap. Native caves
|
// The legacy landmark demo retains its compatibility floor cap. Native caves
|
||||||
// already combine each floor with its wall before the single void carve above.
|
// already combine each floor with its wall before the single void carve above.
|
||||||
return ApplyResolvedCavernFloorCaps(stamped,X,Y,Z);
|
const float final=ApplyResolvedCavernFloorCaps(stamped,X,Y,Z);
|
||||||
|
if(bTraceTnComposition)
|
||||||
|
std::printf("[L75TNCompositionTrace] input=%.9g graph=%.9g boundary=%.9g passage=%.9g stamp=%.9g final=%.9g native=0\n",
|
||||||
|
density,graphDensity,boundary,composed,stamped,final);
|
||||||
|
return final;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ResolvedPassageIntersectsBox(const int32 minimum[3],const int32 maximum[3]) const
|
bool ResolvedPassageIntersectsBox(const int32 minimum[3],const int32 maximum[3]) const
|
||||||
@@ -2157,6 +2194,14 @@ public:
|
|||||||
bool bNativeSurface=false;
|
bool bNativeSurface=false;
|
||||||
bool bUnknown=false;
|
bool bUnknown=false;
|
||||||
bool bSkipSafe=false;
|
bool bSkipSafe=false;
|
||||||
|
bool bLegacyFieldCandidate=false;
|
||||||
|
bool bLegacyBlockProofRelevant=false;
|
||||||
|
std::uint8_t LegacyProofVeto=0;
|
||||||
|
};
|
||||||
|
enum class ELegacyProofVeto : std::uint8_t
|
||||||
|
{
|
||||||
|
None=0,DetailEnvelope=1,ComposerCandidate=2,PassageBounds=3,
|
||||||
|
PrimitiveOverlap=4,Transition=5,StratumCrossing=6,SmoothMinSlack=7,Other=8
|
||||||
};
|
};
|
||||||
struct FClassificationThreadStats
|
struct FClassificationThreadStats
|
||||||
{
|
{
|
||||||
@@ -2497,6 +2542,7 @@ public:
|
|||||||
WorkerCount=std::clamp(Workers,1,16);DetailVariant=Variant;
|
WorkerCount=std::clamp(Workers,1,16);DetailVariant=Variant;
|
||||||
Scheduler=InScheduler;RoundTunnelsEnabled=bInRoundTunnels;
|
Scheduler=InScheduler;RoundTunnelsEnabled=bInRoundTunnels;
|
||||||
bUseNativeManifestFields=true;
|
bUseNativeManifestFields=true;
|
||||||
|
UnbinnedSpatialPrimitiveIds.clear();
|
||||||
BindResolvedStratumFields();
|
BindResolvedStratumFields();
|
||||||
bAnalyticOnly=false;
|
bAnalyticOnly=false;
|
||||||
bProfileDetailBand=bInProfileDetailBand;
|
bProfileDetailBand=bInProfileDetailBand;
|
||||||
@@ -3323,6 +3369,44 @@ public:
|
|||||||
return LegacySurfaceDensityAt(static_cast<float>(X),static_cast<float>(Y),
|
return LegacySurfaceDensityAt(static_cast<float>(X),static_cast<float>(Y),
|
||||||
static_cast<float>(Z));
|
static_cast<float>(Z));
|
||||||
const bool bPacked=!bAnalyticOnly&&Contains(X,Y,Z);
|
const bool bPacked=!bAnalyticOnly&&Contains(X,Y,Z);
|
||||||
|
static const bool bL75TraceTnDensity=[]
|
||||||
|
{
|
||||||
|
const char* Value=std::getenv("VOXELFORGE_L75_TN_BLOCK_REGRESSION");
|
||||||
|
return Value&&Value[0]=='1'&&Value[1]=='\0';
|
||||||
|
}();
|
||||||
|
if(bL75TraceTnDensity&&X==-80&&Y==-20&&Z==-3218)
|
||||||
|
{
|
||||||
|
if(!bPacked)
|
||||||
|
std::printf("[L75TNDensityTrace] packed=0\n");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const size_t Column=static_cast<size_t>(Y-Min[1])*Size[0]
|
||||||
|
+static_cast<size_t>(X-Min[0]);
|
||||||
|
const size_t Zi=static_cast<size_t>(Z-Min[2]);
|
||||||
|
const size_t Word=Column*WordsPerZ+(Zi>>6);
|
||||||
|
const uint64 Mask=uint64{1}<<(Zi&63u);
|
||||||
|
const bool bRock=(RockWords[Word]&Mask)!=0;
|
||||||
|
const bool bCandidate=(CandidateWords[Word]&Mask)!=0;
|
||||||
|
const float Analytic=AnalyticDensityAt(static_cast<float>(X),
|
||||||
|
static_cast<float>(Y),static_cast<float>(Z),false);
|
||||||
|
std::printf("[L75TNDensityTrace] packed=%d rock=%d candidate=%d proxy=%d analytic=%.9g\n",
|
||||||
|
bPacked?1:0,bRock?1:0,bCandidate?1:0,
|
||||||
|
bCandidate?static_cast<int>(SurfaceDistanceQ[Zi*static_cast<size_t>(Size[1])*Size[0]
|
||||||
|
+Column]):0,Analytic);
|
||||||
|
for(const uint32 Id:GetLineCandidates(2,X,Y))
|
||||||
|
{
|
||||||
|
if(Id>=Primitives.size())continue;
|
||||||
|
const FPrimitive& Primitive=Primitives[Id];
|
||||||
|
if(!Primitive.bCarve||X<Primitive.Min[0]||X>Primitive.Max[0]
|
||||||
|
||Y<Primitive.Min[1]||Y>Primitive.Max[1]
|
||||||
|
||Z<Primitive.Min[2]||Z>Primitive.Max[2])continue;
|
||||||
|
std::printf("[L75TNDensityTrace] primitive=%u carve=1 critical=%d bounds=(%d,%d,%d)..(%d,%d,%d) contains=%d sdf=%.9g\n",
|
||||||
|
Id,Primitive.bCritical?1:0,Primitive.Min[0],Primitive.Min[1],Primitive.Min[2],
|
||||||
|
Primitive.Max[0],Primitive.Max[1],Primitive.Max[2],
|
||||||
|
ShapeContains(Primitive,X,Y,Z)?1:0,ShapeDistance(Primitive,X,Y,Z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if(bPacked)
|
if(bPacked)
|
||||||
return PackedDensityAt(X,Y,Z,IsRock(X,Y,Z),static_cast<float>(X),
|
return PackedDensityAt(X,Y,Z,IsRock(X,Y,Z),static_cast<float>(X),
|
||||||
static_cast<float>(Y),static_cast<float>(Z));
|
static_cast<float>(Y),static_cast<float>(Z));
|
||||||
@@ -4487,6 +4571,10 @@ private:
|
|||||||
const double qPerMetre=Q*voxelsPerMetre;
|
const double qPerMetre=Q*voxelsPerMetre;
|
||||||
const double bridgeHalfWidthVoxels=3.5*voxelsPerMetre;
|
const double bridgeHalfWidthVoxels=3.5*voxelsPerMetre;
|
||||||
const double bridgeFootDepthVoxels=(5.0-0.13)*voxelsPerMetre;
|
const double bridgeFootDepthVoxels=(5.0-0.13)*voxelsPerMetre;
|
||||||
|
// PassageWallRelief is bounded by 0.21 m along the route. Mouth floor
|
||||||
|
// relief is bounded by 0.20 m and only applies in the upper entry run.
|
||||||
|
const double passageWallReliefVoxels=0.21*voxelsPerMetre;
|
||||||
|
const double passageMouthFloorReliefVoxels=0.20*voxelsPerMetre;
|
||||||
PassageColumnSegmentFlags.resize(ResolvedLayout->passages.size());
|
PassageColumnSegmentFlags.resize(ResolvedLayout->passages.size());
|
||||||
PassageShortSpanSegmentFlags.resize(ResolvedLayout->passages.size());
|
PassageShortSpanSegmentFlags.resize(ResolvedLayout->passages.size());
|
||||||
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();++passageIndex)
|
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();++passageIndex)
|
||||||
@@ -4507,24 +4595,119 @@ private:
|
|||||||
const auto& a=passage.controlPoints[segment];
|
const auto& a=passage.controlPoints[segment];
|
||||||
const auto& b=passage.controlPoints[segment+1];
|
const auto& b=passage.controlPoints[segment+1];
|
||||||
const double radius=static_cast<double>(std::max(a.radiusSubvoxels,b.radiusSubvoxels))/Q;
|
const double radius=static_cast<double>(std::max(a.radiusSubvoxels,b.radiusSubvoxels))/Q;
|
||||||
const double horizontalExtent=std::max(radius,bridgeHalfWidthVoxels);
|
const std::ptrdiff_t lastControl=static_cast<std::ptrdiff_t>(
|
||||||
|
passage.controlPoints.size())-1;
|
||||||
|
const std::ptrdiff_t lowerMouthControl=std::clamp<std::ptrdiff_t>(
|
||||||
|
static_cast<std::ptrdiff_t>(passage.lowerMouthControlIndex),1,lastControl);
|
||||||
|
const double dx=static_cast<double>(b.positionSubvoxels[0])
|
||||||
|
-a.positionSubvoxels[0];
|
||||||
|
const double dy=static_cast<double>(b.positionSubvoxels[1])
|
||||||
|
-a.positionSubvoxels[1];
|
||||||
|
const double dz=static_cast<double>(b.positionSubvoxels[2])
|
||||||
|
-a.positionSubvoxels[2];
|
||||||
|
const bool bMayUseSmoothCarve=segment>2
|
||||||
|
&&static_cast<std::ptrdiff_t>(segment)+2<lowerMouthControl
|
||||||
|
&&DeterministicSqrt(dx*dx+dy*dy+dz*dz)<=8.0*qPerMetre;
|
||||||
|
const double mouthFloorReliefVoxels=segment<static_cast<std::size_t>(
|
||||||
|
std::min<std::ptrdiff_t>(3,lowerMouthControl))
|
||||||
|
?passageMouthFloorReliefVoxels:0.0;
|
||||||
|
constexpr int32 ProofSubdivisions=4;
|
||||||
|
constexpr double OutwardPaddingVoxels=1.0e-6;
|
||||||
const double footDepth=std::max(voxelsPerMetre,bridgeFootDepthVoxels);
|
const double footDepth=std::max(voxelsPerMetre,bridgeFootDepthVoxels);
|
||||||
FPassageProofBound proof;
|
std::array<double,3> featureMinimum={
|
||||||
proof.minimum={std::min(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q-horizontalExtent,
|
std::numeric_limits<double>::infinity(),
|
||||||
std::min(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q-horizontalExtent,
|
std::numeric_limits<double>::infinity(),
|
||||||
std::min(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q-radius-footDepth};
|
std::numeric_limits<double>::infinity()};
|
||||||
proof.maximum={std::max(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q+horizontalExtent,
|
std::array<double,3> featureMaximum={
|
||||||
std::max(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q+horizontalExtent,
|
-std::numeric_limits<double>::infinity(),
|
||||||
std::max(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q+radius};
|
-std::numeric_limits<double>::infinity(),
|
||||||
PassageProofBounds.push_back(proof);
|
-std::numeric_limits<double>::infinity()};
|
||||||
|
const auto& p0=bMayUseSmoothCarve
|
||||||
|
?passage.controlPoints[segment-1]:a;
|
||||||
|
const auto& p3=bMayUseSmoothCarve
|
||||||
|
?passage.controlPoints[segment+2]:b;
|
||||||
|
for(int32 proofPart=0;proofPart<ProofSubdivisions;++proofPart)
|
||||||
|
{
|
||||||
|
const double t0=static_cast<double>(proofPart)/ProofSubdivisions;
|
||||||
|
const double t1=static_cast<double>(proofPart+1)/ProofSubdivisions;
|
||||||
|
std::array<double,3> centerMinimum{},centerMaximum{};
|
||||||
|
for(std::size_t axis=0;axis<3;++axis)
|
||||||
|
{
|
||||||
|
const double v0=static_cast<double>(p0.positionSubvoxels[axis])/Q;
|
||||||
|
const double v1=static_cast<double>(a.positionSubvoxels[axis])/Q;
|
||||||
|
const double v2=static_cast<double>(b.positionSubvoxels[axis])/Q;
|
||||||
|
const double v3=static_cast<double>(p3.positionSubvoxels[axis])/Q;
|
||||||
|
const double c0=v1;
|
||||||
|
const double c1=0.5*(-v0+v2);
|
||||||
|
const double c2=0.5*(2.0*v0-5.0*v1+4.0*v2-v3);
|
||||||
|
const double c3=0.5*(-v0+3.0*v1-3.0*v2+v3);
|
||||||
|
const auto evaluate=[&](double t)
|
||||||
|
{
|
||||||
|
return bMayUseSmoothCarve
|
||||||
|
?((c3*t+c2)*t+c1)*t+c0:v1+t*(v2-v1);
|
||||||
|
};
|
||||||
|
double low=std::min(evaluate(t0),evaluate(t1));
|
||||||
|
double high=std::max(evaluate(t0),evaluate(t1));
|
||||||
|
const double qa=3.0*c3,qb=2.0*c2,qc=c1;
|
||||||
|
const auto includeRoot=[&](double t)
|
||||||
|
{
|
||||||
|
if(t<=t0||t>=t1||!std::isfinite(t))return;
|
||||||
|
const double value=evaluate(t);
|
||||||
|
low=std::min(low,value);high=std::max(high,value);
|
||||||
|
};
|
||||||
|
if(bMayUseSmoothCarve)
|
||||||
|
{
|
||||||
|
if(std::abs(qa)<=1.0e-12)
|
||||||
|
{
|
||||||
|
if(std::abs(qb)>1.0e-12)includeRoot(-qc/qb);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const double discriminant=qb*qb-4.0*qa*qc;
|
||||||
|
if(discriminant>=0.0)
|
||||||
|
{
|
||||||
|
const double root=std::sqrt(discriminant);
|
||||||
|
includeRoot((-qb-root)/(2.0*qa));
|
||||||
|
includeRoot((-qb+root)/(2.0*qa));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
centerMinimum[axis]=low-OutwardPaddingVoxels;
|
||||||
|
centerMaximum[axis]=high+OutwardPaddingVoxels;
|
||||||
|
}
|
||||||
|
const double radiusAt0=(static_cast<double>(a.radiusSubvoxels)
|
||||||
|
+t0*(static_cast<double>(b.radiusSubvoxels)-a.radiusSubvoxels))/Q;
|
||||||
|
const double radiusAt1=(static_cast<double>(a.radiusSubvoxels)
|
||||||
|
+t1*(static_cast<double>(b.radiusSubvoxels)-a.radiusSubvoxels))/Q;
|
||||||
|
const double partRadius=std::max(radiusAt0,radiusAt1);
|
||||||
|
const double relievedRadius=partRadius+passageWallReliefVoxels
|
||||||
|
+mouthFloorReliefVoxels+OutwardPaddingVoxels;
|
||||||
|
const double horizontalExtent=std::max(relievedRadius,
|
||||||
|
bridgeHalfWidthVoxels+OutwardPaddingVoxels);
|
||||||
|
FPassageProofBound proof;
|
||||||
|
proof.minimum={centerMinimum[0]-horizontalExtent,
|
||||||
|
centerMinimum[1]-horizontalExtent,
|
||||||
|
centerMinimum[2]-partRadius-footDepth-passageWallReliefVoxels
|
||||||
|
-mouthFloorReliefVoxels-OutwardPaddingVoxels};
|
||||||
|
proof.maximum={centerMaximum[0]+horizontalExtent,
|
||||||
|
centerMaximum[1]+horizontalExtent,
|
||||||
|
centerMaximum[2]+partRadius+passageWallReliefVoxels
|
||||||
|
+mouthFloorReliefVoxels+OutwardPaddingVoxels};
|
||||||
|
for(std::size_t axis=0;axis<3;++axis)
|
||||||
|
{
|
||||||
|
featureMinimum[axis]=std::min(featureMinimum[axis],proof.minimum[axis]);
|
||||||
|
featureMaximum[axis]=std::max(featureMaximum[axis],proof.maximum[axis]);
|
||||||
|
}
|
||||||
|
PassageProofBounds.push_back(proof);
|
||||||
|
}
|
||||||
const std::array<std::int64_t,3> featureMin={
|
const std::array<std::int64_t,3> featureMin={
|
||||||
static_cast<std::int64_t>(std::floor(proof.minimum[0]*Q)),
|
static_cast<std::int64_t>(std::floor(featureMinimum[0]*Q)),
|
||||||
static_cast<std::int64_t>(std::floor(proof.minimum[1]*Q)),
|
static_cast<std::int64_t>(std::floor(featureMinimum[1]*Q)),
|
||||||
static_cast<std::int64_t>(std::floor(proof.minimum[2]*Q))};
|
static_cast<std::int64_t>(std::floor(featureMinimum[2]*Q))};
|
||||||
const std::array<std::int64_t,3> featureMax={
|
const std::array<std::int64_t,3> featureMax={
|
||||||
static_cast<std::int64_t>(std::ceil(proof.maximum[0]*Q)),
|
static_cast<std::int64_t>(std::ceil(featureMaximum[0]*Q)),
|
||||||
static_cast<std::int64_t>(std::ceil(proof.maximum[1]*Q)),
|
static_cast<std::int64_t>(std::ceil(featureMaximum[1]*Q)),
|
||||||
static_cast<std::int64_t>(std::ceil(proof.maximum[2]*Q))};
|
static_cast<std::int64_t>(std::ceil(featureMaximum[2]*Q))};
|
||||||
PassageFeatureBounds.push_back({static_cast<std::uint64_t>(PassageTileFeatures.size()),
|
PassageFeatureBounds.push_back({static_cast<std::uint64_t>(PassageTileFeatures.size()),
|
||||||
featureMin,featureMax});
|
featureMin,featureMax});
|
||||||
PassageTileFeatures.push_back({passageIndex,segment,false});
|
PassageTileFeatures.push_back({passageIndex,segment,false});
|
||||||
@@ -5965,8 +6148,22 @@ public:
|
|||||||
{
|
{
|
||||||
if(SharedDensitySource)
|
if(SharedDensitySource)
|
||||||
return SharedDensitySource->ClassifyBoxDetailed(Origin,Step,Cells,bUseNativeLatticeProof);
|
return SharedDensitySource->ClassifyBoxDetailed(Origin,Step,Cells,bUseNativeLatticeProof);
|
||||||
|
const int32 Pad=std::max(Step,1);
|
||||||
|
const int32 BMin[3]={Origin.X-Pad,Origin.Y-Pad,Origin.Z-Pad};
|
||||||
|
const int32 BMax[3]={Origin.X+(Cells+1)*Step,Origin.Y+(Cells+1)*Step,
|
||||||
|
Origin.Z+(Cells+1)*Step};
|
||||||
|
return ClassifyBoundsDetailed(BMin,BMax,std::max(Step,1),bUseNativeLatticeProof);
|
||||||
|
}
|
||||||
|
|
||||||
|
FBoxClassification ClassifyBoundsDetailed(const int32 InputMin[3],
|
||||||
|
const int32 InputMax[3],int32 SampleStep,bool bUseNativeLatticeProof=false) const
|
||||||
|
{
|
||||||
|
if(SharedDensitySource)
|
||||||
|
return SharedDensitySource->ClassifyBoundsDetailed(
|
||||||
|
InputMin,InputMax,SampleStep,bUseNativeLatticeProof);
|
||||||
using Clock=std::chrono::steady_clock;
|
using Clock=std::chrono::steady_clock;
|
||||||
const auto Start=Clock::now();
|
const auto Start=Clock::now();
|
||||||
|
std::uint8_t LegacyProofVeto=static_cast<std::uint8_t>(ELegacyProofVeto::None);
|
||||||
static const bool bL74TightNegativeControl=[]
|
static const bool bL74TightNegativeControl=[]
|
||||||
{
|
{
|
||||||
const char* Value=std::getenv("VOXELFORGE_L74_TIGHT_PROOF_CONTROL");
|
const char* Value=std::getenv("VOXELFORGE_L74_TIGHT_PROOF_CONTROL");
|
||||||
@@ -5975,9 +6172,21 @@ public:
|
|||||||
std::printf("[L74NegativeControl] enabled=1 bound=base-sign-only-detail-and-primitive-envelope-omitted\n");
|
std::printf("[L74NegativeControl] enabled=1 bound=base-sign-only-detail-and-primitive-envelope-omitted\n");
|
||||||
return Enabled;
|
return Enabled;
|
||||||
}();
|
}();
|
||||||
const int32 Pad=std::max(Step,1);
|
const int32 BMin[3]={InputMin[0],InputMin[1],InputMin[2]};
|
||||||
int32 BMin[3]={Origin.X-Pad,Origin.Y-Pad,Origin.Z-Pad};
|
const int32 BMax[3]={InputMax[0],InputMax[1],InputMax[2]};
|
||||||
int32 BMax[3]={Origin.X+(Cells+1)*Step,Origin.Y+(Cells+1)*Step,Origin.Z+(Cells+1)*Step};
|
static const bool bL75TraceTnBlock=[]
|
||||||
|
{
|
||||||
|
const char* Value=std::getenv("VOXELFORGE_L75_TN_BLOCK_REGRESSION");
|
||||||
|
return Value&&Value[0]=='1'&&Value[1]=='\0';
|
||||||
|
}();
|
||||||
|
constexpr int32 ProbeX=-80,ProbeY=-20,ProbeZ=-3218;
|
||||||
|
const bool bTraceTnBlock=bL75TraceTnBlock
|
||||||
|
&&ProbeX>=BMin[0]&&ProbeX<=BMax[0]
|
||||||
|
&&ProbeY>=BMin[1]&&ProbeY<=BMax[1]
|
||||||
|
&&ProbeZ>=BMin[2]&&ProbeZ<=BMax[2];
|
||||||
|
if(bTraceTnBlock)
|
||||||
|
std::printf("[L75TNProofTrace] phase=enter step=%d bounds=(%d,%d,%d)..(%d,%d,%d)\n",
|
||||||
|
SampleStep,BMin[0],BMin[1],BMin[2],BMax[0],BMax[1],BMax[2]);
|
||||||
const VoxelForgeCore::WorldAssemblyStratumPlanV1* SurfacePlan=nullptr;
|
const VoxelForgeCore::WorldAssemblyStratumPlanV1* SurfacePlan=nullptr;
|
||||||
const VoxelForgeCore::WorldAssemblyFloatingIslandPlanV1* IslandPlan=nullptr;
|
const VoxelForgeCore::WorldAssemblyFloatingIslandPlanV1* IslandPlan=nullptr;
|
||||||
const FSurfaceWorldFieldAdapter* SurfaceField=nullptr;
|
const FSurfaceWorldFieldAdapter* SurfaceField=nullptr;
|
||||||
@@ -5986,6 +6195,11 @@ public:
|
|||||||
bool bSurfaceOverlap=false,bIslandOverlap=false,bCavesOverlap=false;
|
bool bSurfaceOverlap=false,bIslandOverlap=false,bCavesOverlap=false;
|
||||||
bool bOtherStrateOverlap=false,bLegacyOverlap=false,bCaveOtherStrateOverlap=false;
|
bool bOtherStrateOverlap=false,bLegacyOverlap=false,bCaveOtherStrateOverlap=false;
|
||||||
bool bIslandOtherStrateOverlap=false,bBadPlan=false;
|
bool bIslandOtherStrateOverlap=false,bBadPlan=false;
|
||||||
|
bool bMissingStratumBound=false;
|
||||||
|
bool bStratumSlicingExpectedToPay=false;
|
||||||
|
bool bLegacyFieldCandidate=WorldAssembly&&Owner&&Owner->Manager
|
||||||
|
&&ResolvedLayout&&Owner->Generator;
|
||||||
|
bool bLegacyBlockProofRelevant=false;
|
||||||
std::vector<std::uint32_t> caveCandidates;
|
std::vector<std::uint32_t> caveCandidates;
|
||||||
if(Owner&&ResolvedLayout)
|
if(Owner&&ResolvedLayout)
|
||||||
{
|
{
|
||||||
@@ -5995,8 +6209,13 @@ public:
|
|||||||
for(int32 ChunkZ=FirstChunk;ChunkZ<=LastChunk;++ChunkZ)
|
for(int32 ChunkZ=FirstChunk;ChunkZ<=LastChunk;++ChunkZ)
|
||||||
{
|
{
|
||||||
const FBoundStratumField* Bound=FindBoundStratumForChunk(ChunkZ);
|
const FBoundStratumField* Bound=FindBoundStratumForChunk(ChunkZ);
|
||||||
|
if(Bound&&Bound->Resolved
|
||||||
|
&&(Bound->Resolved->generatorKind=="FloatingIslands"
|
||||||
|
||Bound->Resolved->generatorKind=="VerticalShafts"))
|
||||||
|
bStratumSlicingExpectedToPay=true;
|
||||||
if(Bound&&Bound->Kind==FBoundStratumField::EKind::SurfaceWorld)
|
if(Bound&&Bound->Kind==FBoundStratumField::EKind::SurfaceWorld)
|
||||||
{
|
{
|
||||||
|
bLegacyFieldCandidate=false;
|
||||||
bSurfaceOverlap=true;bIslandOtherStrateOverlap=true;
|
bSurfaceOverlap=true;bIslandOtherStrateOverlap=true;
|
||||||
const auto* Candidate=Bound->SurfacePlan;
|
const auto* Candidate=Bound->SurfacePlan;
|
||||||
if(!Candidate||ChunkZ<Candidate->bottomChunkZ
|
if(!Candidate||ChunkZ<Candidate->bottomChunkZ
|
||||||
@@ -6016,9 +6235,14 @@ public:
|
|||||||
IslandPlan=Candidate;
|
IslandPlan=Candidate;
|
||||||
IslandField=std::get_if<FFloatingIslandFieldAdapter>(
|
IslandField=std::get_if<FFloatingIslandFieldAdapter>(
|
||||||
&Bound->Evaluator);
|
&Bound->Evaluator);
|
||||||
|
if(Bound->IslandPlan)bLegacyFieldCandidate=false;
|
||||||
|
else if(Bound->Resolved
|
||||||
|
&&Bound->Resolved->generatorKind=="TunnelNetwork")
|
||||||
|
bLegacyBlockProofRelevant=true;
|
||||||
}
|
}
|
||||||
else if(Bound&&Bound->Kind==FBoundStratumField::EKind::Caves)
|
else if(Bound&&Bound->Kind==FBoundStratumField::EKind::Caves)
|
||||||
{
|
{
|
||||||
|
bLegacyFieldCandidate=false;
|
||||||
bCavesOverlap=true;bOtherStrateOverlap=true;
|
bCavesOverlap=true;bOtherStrateOverlap=true;
|
||||||
bIslandOtherStrateOverlap=true;
|
bIslandOtherStrateOverlap=true;
|
||||||
if(CaveBound&&CaveBound!=Bound)bBadPlan=true;
|
if(CaveBound&&CaveBound!=Bound)bBadPlan=true;
|
||||||
@@ -6030,11 +6254,21 @@ public:
|
|||||||
||!std::get_if<FCavesFieldAdapter>(&Bound->Evaluator))bBadPlan=true;
|
||!std::get_if<FCavesFieldAdapter>(&Bound->Evaluator))bBadPlan=true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{bOtherStrateOverlap=true;bIslandOtherStrateOverlap=true;bLegacyOverlap=true;}
|
{
|
||||||
|
if(!Bound){bLegacyFieldCandidate=false;bMissingStratumBound=true;}
|
||||||
|
else if(Bound->Resolved
|
||||||
|
&&Bound->Resolved->generatorKind=="TunnelNetwork")
|
||||||
|
bLegacyBlockProofRelevant=true;
|
||||||
|
bOtherStrateOverlap=true;bIslandOtherStrateOverlap=true;bLegacyOverlap=true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
bCaveOtherStrateOverlap=bCavesOverlap
|
bCaveOtherStrateOverlap=bCavesOverlap
|
||||||
&&(bSurfaceOverlap||bIslandOverlap||bLegacyOverlap);
|
&&(bSurfaceOverlap||bIslandOverlap||bLegacyOverlap);
|
||||||
}
|
}
|
||||||
|
if(bTraceTnBlock)
|
||||||
|
std::printf("[L75TNProofTrace] phase=field_bounds legacy=%d island=%d surface=%d caves=%d missing=%d bad_plan=%d\n",
|
||||||
|
bLegacyFieldCandidate?1:0,bIslandOverlap?1:0,bSurfaceOverlap?1:0,
|
||||||
|
bCavesOverlap?1:0,bMissingStratumBound?1:0,bBadPlan?1:0);
|
||||||
const auto Finish=[&](EVoxelTileClass Verdict,bool bUnknown=false,
|
const auto Finish=[&](EVoxelTileClass Verdict,bool bUnknown=false,
|
||||||
bool bLegacyAnalyticProof=false)
|
bool bLegacyAnalyticProof=false)
|
||||||
{
|
{
|
||||||
@@ -6045,6 +6279,10 @@ public:
|
|||||||
||Verdict==EVoxelTileClass::AllSolid;
|
||Verdict==EVoxelTileClass::AllSolid;
|
||||||
FBoxClassification Result{Verdict,bNative,bUnknown,
|
FBoxClassification Result{Verdict,bNative,bUnknown,
|
||||||
bLegacyAnalyticProof||(bNative&&!bUnknown&&bUniform)};
|
bLegacyAnalyticProof||(bNative&&!bUnknown&&bUniform)};
|
||||||
|
Result.bLegacyFieldCandidate=bLegacyFieldCandidate;
|
||||||
|
Result.bLegacyBlockProofRelevant=bLegacyBlockProofRelevant;
|
||||||
|
Result.LegacyProofVeto=Result.bSkipSafe
|
||||||
|
?static_cast<std::uint8_t>(ELegacyProofVeto::None):LegacyProofVeto;
|
||||||
auto& Counts=ThreadClassificationStats();
|
auto& Counts=ThreadClassificationStats();
|
||||||
Counts.Milliseconds+=std::chrono::duration<double,std::milli>(Clock::now()-Start).count();
|
Counts.Milliseconds+=std::chrono::duration<double,std::milli>(Clock::now()-Start).count();
|
||||||
if(bUnknown)++Counts.UnknownBoxes;
|
if(bUnknown)++Counts.UnknownBoxes;
|
||||||
@@ -6053,6 +6291,73 @@ public:
|
|||||||
else ++Counts.MixedBoxes;
|
else ++Counts.MixedBoxes;
|
||||||
return Result;
|
return Result;
|
||||||
};
|
};
|
||||||
|
if(SampleStep<=2&&bStratumSlicingExpectedToPay&&Owner&&ResolvedLayout
|
||||||
|
&&SampleStep>0&&BMax[2]>=BMin[2]
|
||||||
|
&&(static_cast<std::int64_t>(BMax[2])-BMin[2])%SampleStep==0)
|
||||||
|
{
|
||||||
|
const double LayoutWidth=GetResolvedLayoutChunkWidthVoxels();
|
||||||
|
if(LayoutWidth>0.0&&std::isfinite(LayoutWidth))
|
||||||
|
{
|
||||||
|
std::vector<std::pair<int32,int32>> StratumSlices;
|
||||||
|
int32 SliceStart=BMin[2];
|
||||||
|
auto BoundAt=[&](int32 Z)
|
||||||
|
{
|
||||||
|
const int32 ChunkZ=FMath::FloorToInt(static_cast<float>(Z)/LayoutWidth);
|
||||||
|
return FindBoundStratumForChunk(ChunkZ);
|
||||||
|
};
|
||||||
|
const FBoundStratumField* PreviousBound=BoundAt(SliceStart);
|
||||||
|
for(std::int64_t Z=static_cast<std::int64_t>(BMin[2])+SampleStep;
|
||||||
|
Z<=BMax[2];Z+=SampleStep)
|
||||||
|
{
|
||||||
|
const auto* Bound=BoundAt(static_cast<int32>(Z));
|
||||||
|
if(Bound==PreviousBound)continue;
|
||||||
|
StratumSlices.push_back({SliceStart,static_cast<int32>(Z-SampleStep)});
|
||||||
|
SliceStart=static_cast<int32>(Z);
|
||||||
|
PreviousBound=Bound;
|
||||||
|
}
|
||||||
|
if(!StratumSlices.empty())
|
||||||
|
{
|
||||||
|
StratumSlices.push_back({SliceStart,BMax[2]});
|
||||||
|
EVoxelTileClass CommonVerdict=EVoxelTileClass::Mixed;
|
||||||
|
bool bHaveVerdict=false,bAllNative=true,bAllLegacy=true,bAnyLegacy=false;
|
||||||
|
for(const auto& Slice:StratumSlices)
|
||||||
|
{
|
||||||
|
const int32 SliceMinimum[3]={BMin[0],BMin[1],Slice.first};
|
||||||
|
const int32 SliceMaximum[3]={BMax[0],BMax[1],Slice.second};
|
||||||
|
const auto Part=ClassifyBoundsDetailed(SliceMinimum,SliceMaximum,
|
||||||
|
SampleStep,bUseNativeLatticeProof);
|
||||||
|
if(!Part.bSkipSafe||Part.bUnknown
|
||||||
|
||(Part.Verdict!=EVoxelTileClass::AllSolid
|
||||||
|
&&Part.Verdict!=EVoxelTileClass::AllAir))
|
||||||
|
{
|
||||||
|
LegacyProofVeto=Part.LegacyProofVeto
|
||||||
|
?Part.LegacyProofVeto
|
||||||
|
:static_cast<std::uint8_t>(ELegacyProofVeto::StratumCrossing);
|
||||||
|
return Finish(EVoxelTileClass::Mixed,Part.bUnknown);
|
||||||
|
}
|
||||||
|
if(!bHaveVerdict)
|
||||||
|
{
|
||||||
|
CommonVerdict=Part.Verdict;
|
||||||
|
bHaveVerdict=true;
|
||||||
|
}
|
||||||
|
else if(Part.Verdict!=CommonVerdict)
|
||||||
|
{
|
||||||
|
LegacyProofVeto=static_cast<std::uint8_t>(
|
||||||
|
ELegacyProofVeto::StratumCrossing);
|
||||||
|
return Finish(EVoxelTileClass::Mixed);
|
||||||
|
}
|
||||||
|
bAllNative=bAllNative&&Part.bNativeSurface;
|
||||||
|
bAllLegacy=bAllLegacy&&Part.bLegacyFieldCandidate;
|
||||||
|
bAnyLegacy=bAnyLegacy||Part.bLegacyBlockProofRelevant;
|
||||||
|
}
|
||||||
|
auto Result=Finish(CommonVerdict,false,true);
|
||||||
|
Result.bNativeSurface=bAllNative;
|
||||||
|
Result.bLegacyFieldCandidate=bAllLegacy;
|
||||||
|
Result.bLegacyBlockProofRelevant=bAnyLegacy;
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
std::vector<std::uint32_t> stampCandidates;
|
std::vector<std::uint32_t> stampCandidates;
|
||||||
if(!StampSpatialBounds.empty())
|
if(!StampSpatialBounds.empty())
|
||||||
{
|
{
|
||||||
@@ -6075,7 +6380,10 @@ public:
|
|||||||
&&stamp.record->kind
|
&&stamp.record->kind
|
||||||
!=VoxelForgeCore::ResolvedSdfStampKindV1::fillPlatform)
|
!=VoxelForgeCore::ResolvedSdfStampKindV1::fillPlatform)
|
||||||
||!SurfacePlan||stamp.record->strateIndex!=SurfacePlan->strateIndex)
|
||!SurfacePlan||stamp.record->strateIndex!=SurfacePlan->strateIndex)
|
||||||
|
{
|
||||||
|
LegacyProofVeto=static_cast<std::uint8_t>(ELegacyProofVeto::ComposerCandidate);
|
||||||
return Finish(EVoxelTileClass::Mixed,true);
|
return Finish(EVoxelTileClass::Mixed,true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!CavernRuntime.empty())
|
if(!CavernRuntime.empty())
|
||||||
@@ -6091,10 +6399,19 @@ public:
|
|||||||
static_cast<std::int64_t>(BMax[2])*Q};
|
static_cast<std::int64_t>(BMax[2])*Q};
|
||||||
caveCandidates=BuildCavernFeatureTileList(minimum,maximum);
|
caveCandidates=BuildCavernFeatureTileList(minimum,maximum);
|
||||||
if(!caveCandidates.empty()&&!bCavesOverlap&&!bL74TightNegativeControl)
|
if(!caveCandidates.empty()&&!bCavesOverlap&&!bL74TightNegativeControl)
|
||||||
|
{
|
||||||
|
LegacyProofVeto=static_cast<std::uint8_t>(ELegacyProofVeto::ComposerCandidate);
|
||||||
return Finish(EVoxelTileClass::Mixed,true);
|
return Finish(EVoxelTileClass::Mixed,true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if(ResolvedPassageIntersectsBox(BMin,BMax))
|
const bool bPassageOverlap=ResolvedPassageIntersectsBox(BMin,BMax);
|
||||||
|
if(bTraceTnBlock)
|
||||||
|
std::printf("[L75TNProofTrace] phase=passage intersects=%d\n",bPassageOverlap?1:0);
|
||||||
|
if(bPassageOverlap)
|
||||||
|
{
|
||||||
|
LegacyProofVeto=static_cast<std::uint8_t>(ELegacyProofVeto::PassageBounds);
|
||||||
return Finish(EVoxelTileClass::Mixed,true);
|
return Finish(EVoxelTileClass::Mixed,true);
|
||||||
|
}
|
||||||
if(!EditBrushes.empty()&&EditBoundsMin.X<=BMax[0]&&EditBoundsMax.X>=BMin[0]
|
if(!EditBrushes.empty()&&EditBoundsMin.X<=BMax[0]&&EditBoundsMax.X>=BMin[0]
|
||||||
&&EditBoundsMin.Y<=BMax[1]&&EditBoundsMax.Y>=BMin[1]
|
&&EditBoundsMin.Y<=BMax[1]&&EditBoundsMax.Y>=BMin[1]
|
||||||
&&EditBoundsMin.Z<=BMax[2]&&EditBoundsMax.Z>=BMin[2])
|
&&EditBoundsMin.Z<=BMax[2]&&EditBoundsMax.Z>=BMin[2])
|
||||||
@@ -6111,6 +6428,7 @@ public:
|
|||||||
if(ZMinMicrometres>=Band->bottomMicrometres
|
if(ZMinMicrometres>=Band->bottomMicrometres
|
||||||
&&ZMaxMicrometres<Band->topMicrometres)
|
&&ZMaxMicrometres<Band->topMicrometres)
|
||||||
return Finish(EVoxelTileClass::AllSolid);
|
return Finish(EVoxelTileClass::AllSolid);
|
||||||
|
LegacyProofVeto=static_cast<std::uint8_t>(ELegacyProofVeto::StratumCrossing);
|
||||||
return Finish(EVoxelTileClass::Mixed);
|
return Finish(EVoxelTileClass::Mixed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6151,7 +6469,10 @@ public:
|
|||||||
Lower->ceiling.topMicrometres)+MaximumDisplacement;
|
Lower->ceiling.topMicrometres)+MaximumDisplacement;
|
||||||
if(ZMaximumMicrometres>=WindowMinimum
|
if(ZMaximumMicrometres>=WindowMinimum
|
||||||
&&ZMinimumMicrometres<=WindowMaximum)
|
&&ZMinimumMicrometres<=WindowMaximum)
|
||||||
|
{
|
||||||
|
LegacyProofVeto=static_cast<std::uint8_t>(ELegacyProofVeto::Transition);
|
||||||
return Finish(EVoxelTileClass::Mixed,true);
|
return Finish(EVoxelTileClass::Mixed,true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const auto TryLegacyAnalyticProof=[&]() -> std::optional<EVoxelTileClass>
|
const auto TryLegacyAnalyticProof=[&]() -> std::optional<EVoxelTileClass>
|
||||||
@@ -6161,28 +6482,45 @@ public:
|
|||||||
// terms. SurfaceWorld op stacks, native adapters, and any active composer
|
// terms. SurfaceWorld op stacks, native adapters, and any active composer
|
||||||
// feature fail closed and keep using their existing path.
|
// feature fail closed and keep using their existing path.
|
||||||
if(!WorldAssembly||!Owner||!Owner->Manager
|
if(!WorldAssembly||!Owner||!Owner->Manager
|
||||||
||!ResolvedLayout||!Owner->Generator||bSurfaceOverlap||bCavesOverlap
|
||!ResolvedLayout||!Owner->Generator)
|
||||||
||(!bL74TightNegativeControl
|
|
||||||
&&(!stampCandidates.empty()||!caveCandidates.empty()))
|
|
||||||
||!EditBrushes.empty()||LayerRanges.empty()
|
|
||||||
||ActiveSpatialBinSize<=0)return std::nullopt;
|
|
||||||
const double LayoutWidth=GetResolvedLayoutChunkWidthVoxels();
|
|
||||||
if(!(LayoutWidth>0.0)||!std::isfinite(LayoutWidth))return std::nullopt;
|
|
||||||
const int32 FirstChunk=FMath::FloorToInt(static_cast<float>(BMin[2])/LayoutWidth);
|
|
||||||
const int32 LastChunk=FMath::FloorToInt(static_cast<float>(BMax[2])/LayoutWidth);
|
|
||||||
for(int32 ChunkZ=FirstChunk;ChunkZ<=LastChunk;++ChunkZ)
|
|
||||||
{
|
{
|
||||||
const FBoundStratumField* Bound=FindBoundStratumForChunk(ChunkZ);
|
LegacyProofVeto=static_cast<std::uint8_t>(ELegacyProofVeto::Other);
|
||||||
if(!Bound||Bound->Kind==FBoundStratumField::EKind::SurfaceWorld)
|
return std::nullopt;
|
||||||
return std::nullopt;
|
|
||||||
if(Bound->Kind==FBoundStratumField::EKind::FloatingIslands)
|
|
||||||
{
|
|
||||||
// A missing FI plan is the owner's compiled-analytic fallback. Any
|
|
||||||
// installed plan selects the native FI field and is outside this proof.
|
|
||||||
if(Bound->IslandPlan)return std::nullopt;
|
|
||||||
}
|
|
||||||
if(Bound->Kind==FBoundStratumField::EKind::Caves)return std::nullopt;
|
|
||||||
}
|
}
|
||||||
|
if(!stampCandidates.empty()||!caveCandidates.empty())
|
||||||
|
{
|
||||||
|
LegacyProofVeto=static_cast<std::uint8_t>(ELegacyProofVeto::ComposerCandidate);
|
||||||
|
if(!bL74TightNegativeControl)return std::nullopt;
|
||||||
|
}
|
||||||
|
if(bSurfaceOverlap||bCavesOverlap)
|
||||||
|
{
|
||||||
|
const bool bCrossedFieldKinds=(bSurfaceOverlap
|
||||||
|
&&(bIslandOverlap||bCavesOverlap||bLegacyOverlap))
|
||||||
|
||(bIslandOverlap&&(bSurfaceOverlap||bCavesOverlap||bLegacyOverlap))
|
||||||
|
||(bCavesOverlap&&(bSurfaceOverlap||bIslandOverlap||bLegacyOverlap));
|
||||||
|
LegacyProofVeto=static_cast<std::uint8_t>(bCrossedFieldKinds||bBadPlan
|
||||||
|
?ELegacyProofVeto::StratumCrossing:ELegacyProofVeto::Other);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
if(!bLegacyFieldCandidate)
|
||||||
|
{
|
||||||
|
LegacyProofVeto=static_cast<std::uint8_t>(bBadPlan||bMissingStratumBound
|
||||||
|
?ELegacyProofVeto::StratumCrossing:ELegacyProofVeto::Other);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
if(!EditBrushes.empty()||LayerRanges.empty()||ActiveSpatialBinSize<=0)
|
||||||
|
{
|
||||||
|
LegacyProofVeto=static_cast<std::uint8_t>(ELegacyProofVeto::Other);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
const double LayoutWidth=GetResolvedLayoutChunkWidthVoxels();
|
||||||
|
if(!(LayoutWidth>0.0)||!std::isfinite(LayoutWidth))
|
||||||
|
{
|
||||||
|
LegacyProofVeto=static_cast<std::uint8_t>(ELegacyProofVeto::Other);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
// The earlier chunk scan establishes that this padded box selects only the
|
||||||
|
// compiled legacy field. Native fields and missing chunk bounds stay fail-closed.
|
||||||
for(int32 BZ=FloorDiv(BMin[2],ActiveSpatialBinSize);
|
for(int32 BZ=FloorDiv(BMin[2],ActiveSpatialBinSize);
|
||||||
BZ<=FloorDiv(BMax[2],ActiveSpatialBinSize);++BZ)
|
BZ<=FloorDiv(BMax[2],ActiveSpatialBinSize);++BZ)
|
||||||
for(int32 BY=FloorDiv(BMin[1],ActiveSpatialBinSize);
|
for(int32 BY=FloorDiv(BMin[1],ActiveSpatialBinSize);
|
||||||
@@ -6200,12 +6538,43 @@ public:
|
|||||||
Overlap=Overlap
|
Overlap=Overlap
|
||||||
&&Primitive.Max[Axis]+DetailBand>=BMin[Axis]
|
&&Primitive.Max[Axis]+DetailBand>=BMin[Axis]
|
||||||
&&Primitive.Min[Axis]-DetailBand<=BMax[Axis];
|
&&Primitive.Min[Axis]-DetailBand<=BMax[Axis];
|
||||||
if(Overlap&&!bL74TightNegativeControl)return std::nullopt;
|
if(bTraceTnBlock&&Overlap)
|
||||||
|
std::printf("[L75TNProofTrace] phase=primitive_overlap id=%u bounds=(%d,%d,%d)..(%d,%d,%d)\n",
|
||||||
|
Id,Primitive.Min[0],Primitive.Min[1],Primitive.Min[2],
|
||||||
|
Primitive.Max[0],Primitive.Max[1],Primitive.Max[2]);
|
||||||
|
if(Overlap&&!bL74TightNegativeControl)
|
||||||
|
{
|
||||||
|
LegacyProofVeto=static_cast<std::uint8_t>(ELegacyProofVeto::PrimitiveOverlap);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(const uint32 Id:UnbinnedSpatialPrimitiveIds)
|
||||||
|
{
|
||||||
|
if(Id>=Primitives.size())continue;
|
||||||
|
const FPrimitive& Primitive=Primitives[Id];
|
||||||
|
bool Overlap=true;
|
||||||
|
for(int32 Axis=0;Axis<3;++Axis)
|
||||||
|
Overlap=Overlap
|
||||||
|
&&Primitive.Max[Axis]+DetailBand>=BMin[Axis]
|
||||||
|
&&Primitive.Min[Axis]-DetailBand<=BMax[Axis];
|
||||||
|
if(bTraceTnBlock&&Overlap)
|
||||||
|
std::printf("[L75TNProofTrace] phase=unbinned_primitive_overlap id=%u bounds=(%d,%d,%d)..(%d,%d,%d)\n",
|
||||||
|
Id,Primitive.Min[0],Primitive.Min[1],Primitive.Min[2],
|
||||||
|
Primitive.Max[0],Primitive.Max[1],Primitive.Max[2]);
|
||||||
|
if(Overlap&&!bL74TightNegativeControl)
|
||||||
|
{
|
||||||
|
LegacyProofVeto=static_cast<std::uint8_t>(ELegacyProofVeto::PrimitiveOverlap);
|
||||||
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const FInterval& Layer=LayerRanges.front();
|
const FInterval& Layer=LayerRanges.front();
|
||||||
if(Layer.Hi<=Layer.Lo)return std::nullopt;
|
if(Layer.Hi<=Layer.Lo)
|
||||||
|
{
|
||||||
|
LegacyProofVeto=static_cast<std::uint8_t>(ELegacyProofVeto::Other);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
const auto BaseAt=[&](double Z)
|
const auto BaseAt=[&](double Z)
|
||||||
{return std::min(Z-static_cast<double>(Layer.Lo),
|
{return std::min(Z-static_cast<double>(Layer.Lo),
|
||||||
static_cast<double>(Layer.Hi)-Z);};
|
static_cast<double>(Layer.Hi)-Z);};
|
||||||
@@ -6217,19 +6586,29 @@ public:
|
|||||||
const double Peak=(static_cast<double>(Layer.Lo)+Layer.Hi)*0.5;
|
const double Peak=(static_cast<double>(Layer.Lo)+Layer.Hi)*0.5;
|
||||||
if(Peak>=Z0&&Peak<=Z1)BaseMaximum=std::max(BaseMaximum,BaseAt(Peak));
|
if(Peak>=Z0&&Peak<=Z1)BaseMaximum=std::max(BaseMaximum,BaseAt(Peak));
|
||||||
// With no active primitive, smooth-min sees only its +100000 sentinel;
|
// With no active primitive, smooth-min sees only its +100000 sentinel;
|
||||||
// its largest possible downward correction is 12/6 = 2 voxels. Keep
|
// outside the width-12 transition it is exactly the base field. Charge its
|
||||||
// the entire resulting field outside the measured detail support (4.125).
|
// 12/6 maximum only if the base interval can approach that sentinel.
|
||||||
constexpr double DetailSupport=DetailSupremum+0.125;
|
constexpr double DetailSupport=DetailSupremum+0.125;
|
||||||
constexpr double SmoothMinSlack=StrataCaveBlendRadius/6.0;
|
constexpr double SmoothMinSentinel=100000.0;
|
||||||
|
const double SmoothMinSlack=BaseMaximum>SmoothMinSentinel
|
||||||
|
-StrataCaveBlendRadius?StrataCaveBlendRadius/6.0:0.0;
|
||||||
constexpr double SafetyEpsilon=1.0e-3;
|
constexpr double SafetyEpsilon=1.0e-3;
|
||||||
const double SolidMargin=bL74TightNegativeControl
|
const double SolidMargin=bL74TightNegativeControl
|
||||||
?SafetyEpsilon:DetailSupport+SmoothMinSlack+SafetyEpsilon;
|
?SafetyEpsilon:DetailSupport+SmoothMinSlack+SafetyEpsilon;
|
||||||
const double AirMargin=bL74TightNegativeControl
|
const double AirMargin=bL74TightNegativeControl
|
||||||
?SafetyEpsilon:DetailSupport+SafetyEpsilon;
|
?SafetyEpsilon:DetailSupport+SafetyEpsilon;
|
||||||
|
if(bTraceTnBlock)
|
||||||
|
std::printf("[L75TNProofTrace] phase=legacy_interval base_min=%.9f base_max=%.9f detail=%.9f smooth_slack=%.9f solid_margin=%.9f air_margin=%.9f\n",
|
||||||
|
BaseMinimum,BaseMaximum,DetailSupport,SmoothMinSlack,SolidMargin,AirMargin);
|
||||||
if(BaseMinimum>SolidMargin)
|
if(BaseMinimum>SolidMargin)
|
||||||
return EVoxelTileClass::AllSolid;
|
return EVoxelTileClass::AllSolid;
|
||||||
if(BaseMaximum<-AirMargin)
|
if(BaseMaximum<-AirMargin)
|
||||||
return EVoxelTileClass::AllAir;
|
return EVoxelTileClass::AllAir;
|
||||||
|
if(!bL74TightNegativeControl&&SmoothMinSlack>0.0
|
||||||
|
&&BaseMinimum>DetailSupport+SafetyEpsilon)
|
||||||
|
LegacyProofVeto=static_cast<std::uint8_t>(ELegacyProofVeto::SmoothMinSlack);
|
||||||
|
else
|
||||||
|
LegacyProofVeto=static_cast<std::uint8_t>(ELegacyProofVeto::DetailEnvelope);
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
};
|
};
|
||||||
if(const auto LegacyVerdict=TryLegacyAnalyticProof())
|
if(const auto LegacyVerdict=TryLegacyAnalyticProof())
|
||||||
@@ -6239,7 +6618,10 @@ public:
|
|||||||
if(bBadPlan||(bSurfaceOverlap&&(bIslandOverlap||bOtherStrateOverlap))
|
if(bBadPlan||(bSurfaceOverlap&&(bIslandOverlap||bOtherStrateOverlap))
|
||||||
||(bIslandOverlap&&bIslandOtherStrateOverlap)
|
||(bIslandOverlap&&bIslandOtherStrateOverlap)
|
||||||
||(bCavesOverlap&&(bCaveOtherStrateOverlap||bSurfaceOverlap||bIslandOverlap)))
|
||(bCavesOverlap&&(bCaveOtherStrateOverlap||bSurfaceOverlap||bIslandOverlap)))
|
||||||
|
{
|
||||||
|
LegacyProofVeto=static_cast<std::uint8_t>(ELegacyProofVeto::StratumCrossing);
|
||||||
return Finish(EVoxelTileClass::Mixed,true);
|
return Finish(EVoxelTileClass::Mixed,true);
|
||||||
|
}
|
||||||
// The native interval path below is the exact field selected first by DensityAtLattice
|
// The native interval path below is the exact field selected first by DensityAtLattice
|
||||||
// for SurfaceWorld and FloatingIslands. Legacy analytic primitives/layer bounds cannot
|
// for SurfaceWorld and FloatingIslands. Legacy analytic primitives/layer bounds cannot
|
||||||
// modify that field; only the explicit edit transaction above can. Keep their guards on
|
// modify that field; only the explicit edit transaction above can. Keep their guards on
|
||||||
@@ -6317,7 +6699,7 @@ public:
|
|||||||
static_cast<int64>(BMax[0])*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel,
|
static_cast<int64>(BMax[0])*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel,
|
||||||
static_cast<int64>(BMax[1])*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel,
|
static_cast<int64>(BMax[1])*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel,
|
||||||
static_cast<int64>(BMax[2])*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel,
|
static_cast<int64>(BMax[2])*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel,
|
||||||
std::max(Step,1));
|
std::max(SampleStep,1));
|
||||||
switch(Verdict)
|
switch(Verdict)
|
||||||
{
|
{
|
||||||
case VoxelForgeCore::HeightfieldBoxClass::allAir:
|
case VoxelForgeCore::HeightfieldBoxClass::allAir:
|
||||||
@@ -9217,7 +9599,14 @@ private:
|
|||||||
// primitive from analytic queries outside the packed near region.
|
// primitive from analytic queries outside the packed near region.
|
||||||
// Les longues galeries diagonales ont une grande AABB mais un tube fin.
|
// Les longues galeries diagonales ont une grande AABB mais un tube fin.
|
||||||
// Echantillonner l'axe avec une marge prudente garde les requetes analytiques.
|
// Echantillonner l'axe avec une marge prudente garde les requetes analytiques.
|
||||||
if(!P.bCapsule)continue;
|
if(!P.bCapsule)
|
||||||
|
{
|
||||||
|
// Large non-capsule bounds are deliberately omitted from point-query bins,
|
||||||
|
// but interval proofs still need a complete candidate set. Keep these IDs
|
||||||
|
// separate so block and tile proofs cannot mistake an omitted carve for air-free rock.
|
||||||
|
UnbinnedSpatialPrimitiveIds.push_back(Id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const double BinWidth=static_cast<double>(BinResolution);
|
const double BinWidth=static_cast<double>(BinResolution);
|
||||||
const double AxisStep=std::max(1.0,BinWidth*0.5);
|
const double AxisStep=std::max(1.0,BinWidth*0.5);
|
||||||
const int32 AxisSamples=std::max(1,static_cast<int32>(std::ceil(
|
const int32 AxisSamples=std::max(1,static_cast<int32>(std::ceil(
|
||||||
@@ -9269,6 +9658,9 @@ private:
|
|||||||
if(bCriticalAir)CriticalAirBins[Key].push_back(Id);
|
if(bCriticalAir)CriticalAirBins[Key].push_back(Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
std::sort(UnbinnedSpatialPrimitiveIds.begin(),UnbinnedSpatialPrimitiveIds.end());
|
||||||
|
UnbinnedSpatialPrimitiveIds.erase(std::unique(UnbinnedSpatialPrimitiveIds.begin(),
|
||||||
|
UnbinnedSpatialPrimitiveIds.end()),UnbinnedSpatialPrimitiveIds.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
void BuildFarParentShapes()
|
void BuildFarParentShapes()
|
||||||
@@ -11460,6 +11852,7 @@ private:
|
|||||||
std::vector<FStandalonePassageVoidCrossing> PassageVoidCrossings;
|
std::vector<FStandalonePassageVoidCrossing> PassageVoidCrossings;
|
||||||
std::array<FAxisBins,3> AxisBins;
|
std::array<FAxisBins,3> AxisBins;
|
||||||
std::unordered_map<FBinKey,FBinItems,FBinHash> SpatialBins;
|
std::unordered_map<FBinKey,FBinItems,FBinHash> SpatialBins;
|
||||||
|
std::vector<uint32> UnbinnedSpatialPrimitiveIds;
|
||||||
std::unordered_map<FBinKey,FBinItems,FBinHash> CriticalAirBins;
|
std::unordered_map<FBinKey,FBinItems,FBinHash> CriticalAirBins;
|
||||||
mutable std::mutex FarFieldGpuInputMutex;
|
mutable std::mutex FarFieldGpuInputMutex;
|
||||||
mutable std::shared_ptr<const VoxelForgeCore::FarFieldWorldInput> CachedFarFieldGpuInput;
|
mutable std::shared_ptr<const VoxelForgeCore::FarFieldWorldInput> CachedFarFieldGpuInput;
|
||||||
|
|||||||
@@ -2364,6 +2364,10 @@ namespace VoxelForgeCore
|
|||||||
const double columnMillisecondsBeforeClassification=
|
const double columnMillisecondsBeforeClassification=
|
||||||
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds;
|
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds;
|
||||||
const auto Classification=tileShape.ClassifyBoxDetailed(origin,step,cells);
|
const auto Classification=tileShape.ClassifyBoxDetailed(origin,step,cells);
|
||||||
|
out.diagnostics.tileProofVetoReason=Classification.LegacyProofVeto;
|
||||||
|
out.diagnostics.tileProofSafe=Classification.bSkipSafe;
|
||||||
|
out.diagnostics.tileLegacyBlockProofRelevant=
|
||||||
|
Classification.bLegacyBlockProofRelevant;
|
||||||
classificationColumnComputeMilliseconds=std::max(0.0,
|
classificationColumnComputeMilliseconds=std::max(0.0,
|
||||||
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds
|
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds
|
||||||
-columnMillisecondsBeforeClassification);
|
-columnMillisecondsBeforeClassification);
|
||||||
@@ -2443,11 +2447,10 @@ namespace VoxelForgeCore
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Keep sub-tile early-outs on native fields whose block interval is complete.
|
// Preserve the committed native-field block path. Legacy block certificates
|
||||||
// The legacy compiled field has sparse tunnel/composer terms that can change a
|
// remain audit-only until their cost is repaid against the full-grid path.
|
||||||
// small block without making the current whole-tile bound fail; leave those blocks
|
if(bHasWorldAssembly&&step<=2
|
||||||
// to the exact grid and use only the separately falsified whole-tile proof above.
|
&&(bNativeSurfaceTile||options.auditUniformBlockProofs))
|
||||||
if(bHasWorldAssembly&&step<=2&&bNativeSurfaceTile)
|
|
||||||
mesher.bUseBlockEarlyOut=true;
|
mesher.bUseBlockEarlyOut=true;
|
||||||
}
|
}
|
||||||
const auto MeshingStart=std::chrono::steady_clock::now();
|
const auto MeshingStart=std::chrono::steady_clock::now();
|
||||||
@@ -3077,13 +3080,15 @@ namespace VoxelForgeCore
|
|||||||
/10000.0;
|
/10000.0;
|
||||||
const int32 stratum=world.Manager
|
const int32 stratum=world.Manager
|
||||||
?world.Manager->GetStrateIndex(static_cast<float>(centerZ)):-1;
|
?world.Manager->GetStrateIndex(static_cast<float>(centerZ)):-1;
|
||||||
std::printf("[UniformGridCeiling] strate=%d lod=%d key=(%d,%d,%d) samples=%zu uniform=%d air=%d blocks8=%llu samples8=%llu blocks4=%llu samples4=%llu\n",
|
std::printf("[UniformGridCeiling] strate=%d lod=%d key=(%d,%d,%d) samples=%zu uniform=%d air=%d blocks8=%llu samples8=%llu blocks4=%llu samples4=%llu proof_safe=%d proof_veto=%u\n",
|
||||||
stratum,key.lod,key.coordinate.x,key.coordinate.y,key.coordinate.z,values.size(),
|
stratum,key.lod,key.coordinate.x,key.coordinate.y,key.coordinate.z,values.size(),
|
||||||
tileUniform&&tileHasSign?1:0,tileUniform&&tileHasSign&&tileAir?1:0,
|
tileUniform&&tileHasSign?1:0,tileUniform&&tileHasSign&&tileAir?1:0,
|
||||||
static_cast<unsigned long long>(uniformBlocks8),
|
static_cast<unsigned long long>(uniformBlocks8),
|
||||||
static_cast<unsigned long long>(uniformSamples8),
|
static_cast<unsigned long long>(uniformSamples8),
|
||||||
static_cast<unsigned long long>(uniformBlocks4),
|
static_cast<unsigned long long>(uniformBlocks4),
|
||||||
static_cast<unsigned long long>(uniformSamples4));
|
static_cast<unsigned long long>(uniformSamples4),
|
||||||
|
out.diagnostics.tileProofSafe?1:0,
|
||||||
|
static_cast<unsigned>(out.diagnostics.tileProofVetoReason));
|
||||||
out.densityHash=densityHash;out.meshHash=HashViewerMesh(mesh);
|
out.densityHash=densityHash;out.meshHash=HashViewerMesh(mesh);
|
||||||
out.payloadHash=HashPayload(mesh);out.topologyHash=HashTopology(mesh);
|
out.payloadHash=HashPayload(mesh);out.topologyHash=HashTopology(mesh);
|
||||||
out.materialHash=HashMaterialSamples(out.vertices);
|
out.materialHash=HashMaterialSamples(out.vertices);
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Compare full-grid and proof-skip mesh hashes for all four owner arrivals."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[4]
|
||||||
|
OUT = ROOT / "Saved/Linux/l75/logs"
|
||||||
|
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||||
|
BINS = {
|
||||||
|
"clang": ROOT / "Saved/Linux/l75/bin/VoxelForgeCoreHeadless-l75-clang",
|
||||||
|
"gcc": ROOT / "Saved/Linux/l75/bin-gcc/VoxelForgeCoreHeadless-l75-gcc",
|
||||||
|
}
|
||||||
|
CAMERAS = {
|
||||||
|
0: (8.0, 16.0, -372.2626953125),
|
||||||
|
1: (25.8369140625, 3.19921875, -737.0),
|
||||||
|
2: (19.166015625, -14.2001953125, -753.3740234375),
|
||||||
|
3: (-20.0625, -5.068359375, -900.353515625),
|
||||||
|
}
|
||||||
|
HASH_ROW = re.compile(
|
||||||
|
r"\[PipelineTileHash\] lod=(\d+) x=(-?\d+) y=(-?\d+) z=(-?\d+) "
|
||||||
|
r"density=(0x[0-9a-fA-F]+) mesh=(0x[0-9a-fA-F]+) "
|
||||||
|
r"payload=(0x[0-9a-fA-F]+) topology=(0x[0-9a-fA-F]+)"
|
||||||
|
)
|
||||||
|
EMPTY_DENSITY_HASH = "0xcbf29ce484222325"
|
||||||
|
|
||||||
|
|
||||||
|
def read_hashes(path: Path) -> tuple[dict, set[tuple[int, int, int, int]]]:
|
||||||
|
hashes = {}
|
||||||
|
generated_density = set()
|
||||||
|
for line in path.read_text(encoding="utf-8", errors="replace").splitlines():
|
||||||
|
row = HASH_ROW.search(line)
|
||||||
|
if row:
|
||||||
|
lod, x, y, z = (int(row.group(i)) for i in range(1, 5))
|
||||||
|
hashes[(lod, x, y, z)] = {
|
||||||
|
"density": row.group(5).lower(), "mesh": row.group(6).lower(),
|
||||||
|
"payload": row.group(7).lower(), "topology": row.group(8).lower(),
|
||||||
|
}
|
||||||
|
if row.group(5).lower() != EMPTY_DENSITY_HASH:
|
||||||
|
generated_density.add((lod, x, y, z))
|
||||||
|
return hashes, generated_density
|
||||||
|
|
||||||
|
|
||||||
|
def run(compiler: str, view: int, workers: int, allow_skips: bool,
|
||||||
|
negative_control: bool = False) -> Path:
|
||||||
|
camera = ",".join(f"{value:.12f}" for value in CAMERAS[view]) + ",0,0"
|
||||||
|
mode = "negative" if negative_control else "skip" if allow_skips else "reference"
|
||||||
|
log = OUT / f"{mode}-{compiler}-view{view}-w{workers}.log"
|
||||||
|
command = [
|
||||||
|
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||||
|
"timeout", "1740s", str(BINS[compiler]), str(DATA),
|
||||||
|
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
|
||||||
|
"--force-generate", f"--workers={workers}", "--clip-radius=3",
|
||||||
|
"--max-clip-level=8", "--max-level=8", f"--camera={camera}",
|
||||||
|
"--stream-hash-tiles-allow-skips" if allow_skips or negative_control
|
||||||
|
else "--stream-hash-tiles",
|
||||||
|
]
|
||||||
|
import os
|
||||||
|
environment = os.environ.copy()
|
||||||
|
if negative_control:
|
||||||
|
environment["VOXELFORGE_L74_TIGHT_PROOF_CONTROL"] = "1"
|
||||||
|
started = time.monotonic()
|
||||||
|
with log.open("w", encoding="utf-8") as output:
|
||||||
|
result = subprocess.run(command, cwd=ROOT, env=environment, stdout=output,
|
||||||
|
stderr=subprocess.STDOUT, check=False, timeout=1740)
|
||||||
|
hashes, _ = read_hashes(log)
|
||||||
|
print(json.dumps({"compiler": compiler, "view": view, "workers": workers,
|
||||||
|
"mode": mode, "exit_code": result.returncode,
|
||||||
|
"hash_rows": len(hashes), "wall_s": round(time.monotonic()-started, 3),
|
||||||
|
"log": str(log.relative_to(ROOT))}, sort_keys=True), flush=True)
|
||||||
|
if result.returncode != 0 or not hashes:
|
||||||
|
raise SystemExit(f"hash generation failed; see {log}")
|
||||||
|
return log
|
||||||
|
|
||||||
|
|
||||||
|
def compare(reference: Path, candidate: Path) -> dict:
|
||||||
|
reference_hashes, _ = read_hashes(reference)
|
||||||
|
candidate_hashes, candidate_generated = read_hashes(candidate)
|
||||||
|
missing = sorted(reference_hashes.keys() - candidate_hashes.keys())
|
||||||
|
extra = sorted(candidate_hashes.keys() - reference_hashes.keys())
|
||||||
|
mismatch_counts = {field: 0 for field in ("mesh", "payload", "topology", "density_processed")}
|
||||||
|
mismatch_examples = []
|
||||||
|
for key in sorted(reference_hashes.keys() & candidate_hashes.keys()):
|
||||||
|
a, b = reference_hashes[key], candidate_hashes[key]
|
||||||
|
fields = ["mesh", "payload", "topology"]
|
||||||
|
if key in candidate_generated:
|
||||||
|
fields.append("density")
|
||||||
|
for field in fields:
|
||||||
|
if a[field] != b[field]:
|
||||||
|
label = "density_processed" if field == "density" else field
|
||||||
|
mismatch_counts[label] += 1
|
||||||
|
if len(mismatch_examples) < 8:
|
||||||
|
mismatch_examples.append({"key": key, "field": field,
|
||||||
|
"reference": a[field], "candidate": b[field]})
|
||||||
|
return {"reference": str(reference.relative_to(ROOT)),
|
||||||
|
"candidate": str(candidate.relative_to(ROOT)),
|
||||||
|
"reference_tiles": len(reference_hashes), "candidate_tiles": len(candidate_hashes),
|
||||||
|
"missing_keys": missing[:8], "missing_key_count": len(missing),
|
||||||
|
"extra_keys": extra[:8], "extra_key_count": len(extra),
|
||||||
|
"mismatch_counts": mismatch_counts, "mismatch_examples": mismatch_examples,
|
||||||
|
"mesh_topology_equal": not missing and not extra
|
||||||
|
and mismatch_counts["mesh"] == 0 and mismatch_counts["topology"] == 0}
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--phase", choices=("matrix", "workers", "negative"), required=True)
|
||||||
|
args = parser.parse_args()
|
||||||
|
OUT.mkdir(parents=True, exist_ok=True)
|
||||||
|
missing = [str(path) for path in [DATA, *BINS.values()] if not path.is_file()]
|
||||||
|
if missing:
|
||||||
|
raise SystemExit("missing input: " + ", ".join(missing))
|
||||||
|
comparisons = []
|
||||||
|
if args.phase == "matrix":
|
||||||
|
references = {}
|
||||||
|
skip_eight = {}
|
||||||
|
for compiler in ("clang", "gcc"):
|
||||||
|
for view in range(4):
|
||||||
|
reference = run(compiler, view, 8, False)
|
||||||
|
skip = run(compiler, view, 8, True)
|
||||||
|
references[(compiler, view)] = reference
|
||||||
|
skip_eight[(compiler, view)] = skip
|
||||||
|
comparisons.append(compare(reference, skip))
|
||||||
|
for view in range(4):
|
||||||
|
comparisons.append(compare(references[("clang", view)],
|
||||||
|
references[("gcc", view)]))
|
||||||
|
comparisons.append(compare(skip_eight[("clang", view)],
|
||||||
|
skip_eight[("gcc", view)]))
|
||||||
|
(OUT / "falsifier-matrix.json").write_text(json.dumps(comparisons, indent=2)+"\n",
|
||||||
|
encoding="utf-8")
|
||||||
|
elif args.phase == "workers":
|
||||||
|
for compiler in ("clang", "gcc"):
|
||||||
|
for view in range(4):
|
||||||
|
reference = OUT / f"reference-{compiler}-view{view}-w8.log"
|
||||||
|
if not reference.is_file():
|
||||||
|
raise SystemExit(f"missing reference from matrix phase: {reference}")
|
||||||
|
baseline = OUT / f"skip-{compiler}-view{view}-w8.log"
|
||||||
|
for workers in (1, 12):
|
||||||
|
candidate = run(compiler, view, workers, True)
|
||||||
|
comparisons.append(compare(reference, candidate))
|
||||||
|
comparisons.append(compare(baseline, candidate))
|
||||||
|
(OUT / "falsifier-workers.json").write_text(json.dumps(comparisons, indent=2)+"\n",
|
||||||
|
encoding="utf-8")
|
||||||
|
else:
|
||||||
|
for view in range(4):
|
||||||
|
reference = OUT / f"reference-clang-view{view}-w8.log"
|
||||||
|
candidate = run("clang", view, 8, True, negative_control=True)
|
||||||
|
comparisons.append(compare(reference, candidate))
|
||||||
|
(OUT / "falsifier-negative-control.json").write_text(
|
||||||
|
json.dumps(comparisons, indent=2)+"\n", encoding="utf-8")
|
||||||
|
caught = any(not row["mesh_topology_equal"] for row in comparisons)
|
||||||
|
print(f"[L74NegativeControlResult] caught={int(caught)}", flush=True)
|
||||||
|
if not caught:
|
||||||
|
return 2
|
||||||
|
return 0
|
||||||
|
failures = [row for row in comparisons if not row["mesh_topology_equal"]
|
||||||
|
or row["missing_key_count"] or row["extra_key_count"]
|
||||||
|
or any(row["mismatch_counts"].values())]
|
||||||
|
print(f"[L74Falsifier] phase={args.phase} comparisons={len(comparisons)} "
|
||||||
|
f"mesh_topology_failures={len(failures)}", flush=True)
|
||||||
|
return 0 if not failures else 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
# L75 report
|
||||||
|
|
||||||
|
## Whole-tile veto census
|
||||||
|
|
||||||
|
Across 9,661 sampled tiles, 4,917 were uniform in the full-grid sample. The proof certified 3,093 (63% of those uniform tiles, 32% of all visits); 1,824 uniform tiles were vetoed.
|
||||||
|
|
||||||
|
| First veto | Missed uniform tiles |
|
||||||
|
|---|---:|
|
||||||
|
| Detail envelope too wide | 0 |
|
||||||
|
| Composer candidate | 0 |
|
||||||
|
| Passage bounds | 289 |
|
||||||
|
| Primitive overlap | 236 |
|
||||||
|
| Transition | 141 |
|
||||||
|
| Stratum crossing | 924 |
|
||||||
|
| Smooth-min slack | 0 |
|
||||||
|
| Other / native interval inconclusive | 234 |
|
||||||
|
|
||||||
|
The 234 `other` cases are SW native interval queries that remain inconclusive. Per-stratum veto rows are in `Saved/Linux/l75/logs/veto-audit.json`.
|
||||||
|
|
||||||
|
## Changes and sub-tile proof
|
||||||
|
|
||||||
|
The dominant veto was stratum crossing. The classifier now slices its z interval at exact stratum boundaries and evaluates each slice; this reduced crossing vetoes from 1,354 to 924. Passage bounds now include the 0.21 m wall and 0.20 m floor relief, plus Catmull-Rom centerline extrema. The resulting bounds remain conservative; passage vetoes rose to 289 because the old bounds omitted real carve support.
|
||||||
|
|
||||||
|
Sub-tile checks use a block-local legacy interval and the same detail, composer, passage, primitive, transition, stratum, and smooth-min vetoes. A certified block is skipped; an inconclusive block falls back to full density sampling. Runtime legacy checks are enabled for TN, where the regression was found; the audit override exercises them in all four views.
|
||||||
|
|
||||||
|
The permanent TN regression is at `(-80,-20,-3218)`: density is `+0.0633840263` (air). The failed proofs bounded the nominal passage route but missed its wall/floor relief, which extends the carve outside that bound. All four containing blocks now classify mixed and sample the point. See `Saved/Linux/l75/logs/tn-block-regression.log`.
|
||||||
|
|
||||||
|
## Falsification and timing
|
||||||
|
|
||||||
|
Clang/GCC full-grid versus skip comparisons matched mesh, payload, and topology hashes on every tile in all four views. W1/W8/W12 comparisons also had zero mismatches. The deliberately too-tight negative control was caught in all four views. The 20-position-per-view sweep completed 80/80 positions with zero block-proof violations; hit rates were SW 83.68%, FI 69.16%, TN 73.80%, VS 73.64%. The fast tier passed in 318.6 s.
|
||||||
|
|
||||||
|
Three interleaved pairs per view compared the rebuilt `c67b200` baseline (source stamp `aacea94c`) with L75 (`ebc12def`). Whole-view medians:
|
||||||
|
|
||||||
|
| View | Before | L75 | Change |
|
||||||
|
|---|---:|---:|---:|
|
||||||
|
| SW | 2.405 s | 2.510 s | +4.37% |
|
||||||
|
| FI | 4.839 s | 5.351 s | +10.58% |
|
||||||
|
| TN | 4.477 s | 4.976 s | +11.15% |
|
||||||
|
| VS | 2.464 s | 3.187 s | +29.34% |
|
||||||
|
|
||||||
|
The L75 proof work improved coverage but did not improve whole-view time. Full-detail unknown boxes rose from c67 to L75: SW 1,424→1,493, FI 9,391→15,636, TN 3,760→11,342, and VS 176→5,543. Further work should isolate the conservative primitive bounds and full-sampling fallbacks before claiming a speedup. Far LOD refinement was left unchanged.
|
||||||
|
|
||||||
|
## Builds, source changes, and failed runs
|
||||||
|
|
||||||
|
Clang 19 and GCC 14 builds passed at source stamp `ebc12def`. The fast tier passed. After timing, the requested `VFHostEditor` Linux Development build was launched with `-MaxParallelActions=4` and the required process limits. Bundled CoreCLR failed before UnrealBuildTool or C++ compilation with `GC heap initialization failed (0x8007000E)` / `Failed to create CoreCLR`; retrying with bounded .NET GC settings had the same failure. The UE compile is therefore unverified under the mandated 6 GiB address-space limit. Log: `Saved/Linux/l75/ue-build-driver.log`.
|
||||||
|
|
||||||
|
No top-level Unreal module `Source/` files changed. The standalone source changes are `Standalone/Source/StreamingWorld.h` and `Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp`. The cached L74 binary failed its owner-data preflight and was excluded; the timing baseline was rebuilt from `c67b200`. No final hash, sweep, negative-control, or fast-tier run failed.
|
||||||
|
|
||||||
|
## Net subset (follow-up)
|
||||||
|
|
||||||
|
Kept the corrected passage proof and TN regression. Passage support is now stored as four parameter-subinterval bounds per route segment. Each bound uses Catmull-Rom derivative extrema where smoothing can occur, otherwise linear endpoints, plus the local maximum radius and outward padding. The 0.21 m wall relief is included along the route; the 0.20 m floor relief is added only for upper-entry segments where that field can apply. The four proof boxes are a conservative union; runtime passage-feature bounds also use their union. The refreshed sampled-tile audit found 5,028 uniform tiles and 1,881 misses:
|
||||||
|
|
||||||
|
| First veto | Missed uniform tiles |
|
||||||
|
|---|---:|
|
||||||
|
| Detail envelope | 0 |
|
||||||
|
| Composer candidate | 0 |
|
||||||
|
| Passage bounds | 313 |
|
||||||
|
| Primitive overlap | 199 |
|
||||||
|
| Transition | 0 |
|
||||||
|
| Stratum crossing | 1,135 |
|
||||||
|
| Smooth-min slack | 0 |
|
||||||
|
| Other / native interval inconclusive | 234 |
|
||||||
|
|
||||||
|
Sub-tile legacy block certificates are audit-only. The committed native block path remains enabled, with its full-grid fallback when any block candidate is uncertified. Stratum slicing is limited to LOD0–2 in FI and VS; SW and TN use the conservative crossing veto. The TN probe `(-80,-20,-3218)` remains air at `+0.0633840263`; all four covering blocks were Mixed/unknown, with zero violations across 62,261,518 audited samples.
|
||||||
|
|
||||||
|
Final evidence passed on source stamp `17470bc8`:
|
||||||
|
|
||||||
|
- Clang/GCC full-grid versus skip: 16 comparisons, exact mesh/payload/topology hashes on every tile; zero mismatches.
|
||||||
|
- W1/W8/W12: 32 comparisons, zero mismatches.
|
||||||
|
- Negative control: caught in all four views.
|
||||||
|
- Sweep: 20 positions per view, 80/80 zero violations and exact tile hashes. Hit rates were SW 24.13%, FI 22.05%, TN 25.88%, VS 22.32%.
|
||||||
|
- Fast tier: PASS, 318.9 s.
|
||||||
|
|
||||||
|
Three interleaved whole-view pairs against rebuilt `c67b200`:
|
||||||
|
|
||||||
|
| View | Before median | Net subset | Change |
|
||||||
|
|---|---:|---:|---:|
|
||||||
|
| SW | 2.441 s | 2.410 s | −1.27% |
|
||||||
|
| FI | 4.975 s | 4.596 s | −7.62% |
|
||||||
|
| TN | 4.569 s | 4.462 s | −2.34% |
|
||||||
|
| VS | 2.479 s | 2.484 s | +0.20% |
|
||||||
|
|
||||||
|
The UE `VFHostEditor` Linux Development build succeeded after timing, with `-MaxParallelActions=4` and no address-space limit (27/27 actions, 68.4 s). It emitted three `CreateSectionGroup` deprecation warnings at `VoxelWorld.cpp`. No top-level Unreal `Source/` files changed; the standalone `Source/` changes are `Standalone/Source/StreamingWorld.h` and `Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp`.
|
||||||
|
|
||||||
|
Failed experiments: the original L75 runtime legacy-block configuration and the mixed-parent native-block gate regressed whole-view timing, so they were dropped. The earlier UBT attempt under the 6 GiB address-space cap failed before C++ compilation because CoreCLR could not initialize; the authorized uncapped UBT build passed.
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Audit certified legacy density blocks across 20 deterministic camera positions per stratum."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import statistics
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[4]
|
||||||
|
OUT = ROOT / "Saved/Linux/l75/logs"
|
||||||
|
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||||
|
BIN = ROOT / "Saved/Linux/l75/bin/VoxelForgeCoreHeadless-l75-clang"
|
||||||
|
CAMERAS = {
|
||||||
|
0: (8.0, 16.0, -372.2626953125),
|
||||||
|
1: (25.8369140625, 3.19921875, -737.0),
|
||||||
|
2: (19.166015625, -14.2001953125, -753.3740234375),
|
||||||
|
3: (-20.0625, -5.068359375, -900.353515625),
|
||||||
|
}
|
||||||
|
OFFSETS = [(x, y) for y in (0.1, 4.1, 8.1, 12.1)
|
||||||
|
for x in (0.1, 4.1, 8.1, 12.1, 16.1)]
|
||||||
|
AUDIT = re.compile(
|
||||||
|
r"\[UniformBlockProofAudit\] checked_blocks=(\d+) checked_samples=(\d+) "
|
||||||
|
r"fallback_blocks=(\d+) unknown_blocks=(\d+) violations=(\d+)"
|
||||||
|
)
|
||||||
|
TILE = re.compile(
|
||||||
|
r"\[WorldAssemblerTilePipeline\] .*?solid_boxes=(\d+) air_boxes=(\d+) "
|
||||||
|
r"mixed_boxes=(\d+) unknown_boxes=(\d+) whole_tile_skips=(\d+).*?"
|
||||||
|
r"classification_worker_ms=([0-9.]+)"
|
||||||
|
)
|
||||||
|
VIOLATION = re.compile(r"\[UniformBlockProofViolation\]")
|
||||||
|
|
||||||
|
|
||||||
|
def run(view: int, position: int, offset: tuple[int, int]) -> dict:
|
||||||
|
base = CAMERAS[view]
|
||||||
|
camera = ",".join(f"{value:.12f}" for value in
|
||||||
|
(base[0] + offset[0], base[1] + offset[1], base[2])) + ",0,0"
|
||||||
|
log = OUT / f"audit-s{view}-p{position:02d}.log"
|
||||||
|
command = [
|
||||||
|
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", "timeout",
|
||||||
|
"1740s", str(BIN), str(DATA), "--world-assembler-prototype", "--stream-benchmark",
|
||||||
|
"--stream-pipeline", "--workers=8", "--clip-radius=3",
|
||||||
|
"--max-clip-level=8", "--max-level=8", f"--camera={camera}",
|
||||||
|
"--audit-uniform-block-proofs", "--profile-lod-work",
|
||||||
|
]
|
||||||
|
started = time.monotonic()
|
||||||
|
with log.open("w", encoding="utf-8") as output:
|
||||||
|
result = subprocess.run(command, cwd=ROOT, stdout=output, stderr=subprocess.STDOUT,
|
||||||
|
check=False, timeout=1740)
|
||||||
|
content = log.read_text(encoding="utf-8", errors="replace")
|
||||||
|
audit = AUDIT.search(content)
|
||||||
|
tile = TILE.search(content)
|
||||||
|
if result.returncode != 0 or audit is None or tile is None:
|
||||||
|
raise SystemExit(f"audit run failed or summary is missing; see {log}")
|
||||||
|
checked, samples, fallback, unknown, violations = map(int, audit.groups())
|
||||||
|
solid, air, mixed, tile_unknown, tile_skips = map(int, tile.groups()[:5])
|
||||||
|
classifier_ms = float(tile.group(6))
|
||||||
|
violation_lines = len(VIOLATION.findall(content))
|
||||||
|
row = {
|
||||||
|
"strate": view, "position": position, "offset_m": list(offset),
|
||||||
|
"exit_code": result.returncode, "wall_s": round(time.monotonic() - started, 3),
|
||||||
|
"checked_blocks": checked, "checked_samples": samples,
|
||||||
|
"fallback_blocks": fallback, "unknown_blocks": unknown,
|
||||||
|
"violations": violations + violation_lines,
|
||||||
|
"block_proof_hit_pct": round(100.0 * checked / max(checked + fallback, 1), 2),
|
||||||
|
"tile_solid_boxes": solid, "tile_air_boxes": air, "tile_mixed_boxes": mixed,
|
||||||
|
"tile_unknown_boxes": tile_unknown, "tile_whole_skips": tile_skips,
|
||||||
|
"classification_worker_ms": classifier_ms,
|
||||||
|
"log": str(log.relative_to(ROOT)),
|
||||||
|
}
|
||||||
|
print("[L75AuditSweepRun] " + json.dumps(row, sort_keys=True), flush=True)
|
||||||
|
if row["violations"] != 0:
|
||||||
|
raise SystemExit(f"uniform block proof violation; see {log}")
|
||||||
|
return row
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
OUT.mkdir(parents=True, exist_ok=True)
|
||||||
|
if not DATA.is_file() or not BIN.is_file():
|
||||||
|
raise SystemExit("missing owner world or L75 Clang headless binary")
|
||||||
|
rows = [run(view, index, offset) for view in range(4)
|
||||||
|
for index, offset in enumerate(OFFSETS)]
|
||||||
|
summary = []
|
||||||
|
for view in range(4):
|
||||||
|
group = [row for row in rows if row["strate"] == view]
|
||||||
|
checked = sum(row["checked_blocks"] for row in group)
|
||||||
|
fallback = sum(row["fallback_blocks"] for row in group)
|
||||||
|
costs = [row["classification_worker_ms"] for row in group]
|
||||||
|
summary.append({
|
||||||
|
"strate": view, "positions": len(group), "violations": sum(
|
||||||
|
row["violations"] for row in group), "checked_blocks": checked,
|
||||||
|
"fallback_blocks": fallback, "proof_hit_pct": round(
|
||||||
|
100.0 * checked / max(checked + fallback, 1), 2),
|
||||||
|
"classifier_ms_p50": round(statistics.median(costs), 3),
|
||||||
|
"classifier_ms_p95": round(sorted(costs)[int(0.95 * (len(costs) - 1))], 3),
|
||||||
|
})
|
||||||
|
if any(row["checked_blocks"] == 0 for row in summary):
|
||||||
|
raise SystemExit("block interval audit did not check a proof block for every strate")
|
||||||
|
result = {"runs": rows, "summary": summary}
|
||||||
|
(OUT / "audit-sweep.json").write_text(json.dumps(result, indent=2) + "\n",
|
||||||
|
encoding="utf-8")
|
||||||
|
for row in summary:
|
||||||
|
print("[L75AuditSweepSummary] " + json.dumps(row, sort_keys=True), flush=True)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Exact full-grid versus whole-tile skip sweep at 20 camera positions per stratum."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import statistics
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[4]
|
||||||
|
OUT = ROOT / "Saved/Linux/l75/logs"
|
||||||
|
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||||
|
BIN = ROOT / "Saved/Linux/l75/bin/VoxelForgeCoreHeadless-l75-clang"
|
||||||
|
CAMERAS = {
|
||||||
|
0: (8.0, 16.0, -372.2626953125),
|
||||||
|
1: (25.8369140625, 3.19921875, -737.0),
|
||||||
|
2: (19.166015625, -14.2001953125, -753.3740234375),
|
||||||
|
3: (-20.0625, -5.068359375, -900.353515625),
|
||||||
|
}
|
||||||
|
OFFSETS = [(x, y) for y in (0.1, 4.1, 8.1, 12.1)
|
||||||
|
for x in (0.1, 4.1, 8.1, 12.1, 16.1)]
|
||||||
|
HASH = re.compile(
|
||||||
|
r"\[PipelineTileHash\] lod=(\d+) x=(-?\d+) y=(-?\d+) z=(-?\d+) "
|
||||||
|
r"density=(0x[0-9a-fA-F]+) mesh=(0x[0-9a-fA-F]+) "
|
||||||
|
r"payload=(0x[0-9a-fA-F]+) topology=(0x[0-9a-fA-F]+)"
|
||||||
|
)
|
||||||
|
EMPTY_DENSITY_HASH = "0xcbf29ce484222325"
|
||||||
|
PIPELINE = re.compile(r"\[StreamPipeline\] (.*)")
|
||||||
|
TILE_PIPELINE = re.compile(r"\[WorldAssemblerTilePipeline\] (.*)")
|
||||||
|
PAIR = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
|
||||||
|
|
||||||
|
|
||||||
|
def read_hashes(path: Path) -> tuple[dict, set[tuple[int, int, int, int]]]:
|
||||||
|
hashes = {}
|
||||||
|
generated = set()
|
||||||
|
for line in path.read_text(encoding="utf-8", errors="replace").splitlines():
|
||||||
|
match = HASH.search(line)
|
||||||
|
if match:
|
||||||
|
lod, x, y, z = (int(match.group(index)) for index in range(1, 5))
|
||||||
|
hashes[(lod, x, y, z)] = {
|
||||||
|
"density": match.group(5).lower(), "mesh": match.group(6).lower(),
|
||||||
|
"payload": match.group(7).lower(), "topology": match.group(8).lower(),
|
||||||
|
}
|
||||||
|
if match.group(5).lower() != EMPTY_DENSITY_HASH:
|
||||||
|
generated.add((lod, x, y, z))
|
||||||
|
return hashes, generated
|
||||||
|
|
||||||
|
|
||||||
|
def parse_summary(path: Path) -> dict:
|
||||||
|
content = path.read_text(encoding="utf-8", errors="replace")
|
||||||
|
pipeline = PIPELINE.search(content)
|
||||||
|
tile_pipeline = TILE_PIPELINE.search(content)
|
||||||
|
if pipeline is None or tile_pipeline is None:
|
||||||
|
raise SystemExit(f"pipeline summaries missing; see {path}")
|
||||||
|
values = dict(PAIR.findall(pipeline.group(1)))
|
||||||
|
tile_values = dict(PAIR.findall(tile_pipeline.group(1)))
|
||||||
|
tiles = values.get("whole_tiles", "0/0").split("/")
|
||||||
|
return {
|
||||||
|
"tiles": int(tiles[1]) if len(tiles) == 2 else 0,
|
||||||
|
"whole_tile_skips": int(tile_values.get("whole_tile_skips", "0")),
|
||||||
|
"classification_worker_ms": float(tile_values.get("classification_worker_ms", "0")),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def run(mode: str, view: int, position: int, offset: tuple[float, float]) -> dict:
|
||||||
|
base = CAMERAS[view]
|
||||||
|
xyz = (base[0] + offset[0], base[1] + offset[1], base[2])
|
||||||
|
camera = ",".join(f"{value:.12f}" for value in xyz) + ",0,0"
|
||||||
|
log = OUT / f"sweep-{mode}-s{view}-p{position:02d}.log"
|
||||||
|
hash_flag = "--stream-hash-tiles" if mode == "reference" \
|
||||||
|
else "--stream-hash-tiles-allow-skips"
|
||||||
|
command = [
|
||||||
|
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", "timeout",
|
||||||
|
"1740s", str(BIN), str(DATA), "--world-assembler-prototype", "--stream-benchmark",
|
||||||
|
"--stream-pipeline", "--force-generate", "--workers=8", "--clip-radius=3",
|
||||||
|
"--max-clip-level=8", "--max-level=8", f"--camera={camera}", hash_flag,
|
||||||
|
]
|
||||||
|
started = time.monotonic()
|
||||||
|
with log.open("w", encoding="utf-8") as output:
|
||||||
|
result = subprocess.run(command, cwd=ROOT, stdout=output, stderr=subprocess.STDOUT,
|
||||||
|
check=False, timeout=1740)
|
||||||
|
hashes, _ = read_hashes(log)
|
||||||
|
if result.returncode != 0 or not hashes:
|
||||||
|
raise SystemExit(f"{mode} hash generation failed; see {log}")
|
||||||
|
summary = parse_summary(log)
|
||||||
|
row = {
|
||||||
|
"mode": mode, "strate": view, "position": position, "offset_m": list(offset),
|
||||||
|
"camera_m": list(xyz), "exit_code": result.returncode,
|
||||||
|
"wall_s": round(time.monotonic() - started, 3), "hash_rows": len(hashes),
|
||||||
|
**summary, "log": str(log.relative_to(ROOT)),
|
||||||
|
}
|
||||||
|
print("[L75HashSweepRun] " + json.dumps(row, sort_keys=True), flush=True)
|
||||||
|
return row
|
||||||
|
|
||||||
|
|
||||||
|
def compare(reference: Path, candidate: Path) -> dict:
|
||||||
|
expected, _ = read_hashes(reference)
|
||||||
|
actual, candidate_generated = read_hashes(candidate)
|
||||||
|
missing = expected.keys() - actual.keys()
|
||||||
|
extra = actual.keys() - expected.keys()
|
||||||
|
mismatches = {field: 0 for field in ("mesh", "payload", "topology", "density_processed")}
|
||||||
|
for key in expected.keys() & actual.keys():
|
||||||
|
fields = ["mesh", "payload", "topology"]
|
||||||
|
if key in candidate_generated:
|
||||||
|
fields.append("density")
|
||||||
|
for field in fields:
|
||||||
|
if expected[key][field] != actual[key][field]:
|
||||||
|
mismatches["density_processed" if field == "density" else field] += 1
|
||||||
|
return {
|
||||||
|
"reference": str(reference.relative_to(ROOT)),
|
||||||
|
"candidate": str(candidate.relative_to(ROOT)),
|
||||||
|
"reference_tiles": len(expected), "candidate_tiles": len(actual),
|
||||||
|
"missing_keys": len(missing), "extra_keys": len(extra),
|
||||||
|
"mismatches": mismatches,
|
||||||
|
"equal": not missing and not extra and not any(mismatches.values()),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if not DATA.is_file() or not BIN.is_file():
|
||||||
|
raise SystemExit("missing owner world or L75 Clang headless binary")
|
||||||
|
references, candidates, comparisons = [], [], []
|
||||||
|
for view in range(4):
|
||||||
|
for position, offset in enumerate(OFFSETS):
|
||||||
|
reference = run("reference", view, position, offset)
|
||||||
|
candidate = run("skip", view, position, offset)
|
||||||
|
comparison = compare(ROOT / reference["log"], ROOT / candidate["log"])
|
||||||
|
comparison.update(strate=view, position=position)
|
||||||
|
references.append(reference)
|
||||||
|
candidates.append(candidate)
|
||||||
|
comparisons.append(comparison)
|
||||||
|
print("[L75HashSweepCompare] " + json.dumps(comparison, sort_keys=True), flush=True)
|
||||||
|
if not comparison["equal"]:
|
||||||
|
raise SystemExit(f"whole-tile proof falsified; see {comparison['candidate']}")
|
||||||
|
summaries = []
|
||||||
|
for view in range(4):
|
||||||
|
rows = [row for row in candidates if row["strate"] == view]
|
||||||
|
skips = sum(row["whole_tile_skips"] for row in rows)
|
||||||
|
tiles = sum(row["tiles"] for row in rows)
|
||||||
|
costs = [row["classification_worker_ms"] for row in rows]
|
||||||
|
summaries.append({
|
||||||
|
"strate": view, "camera_positions": len(rows), "hash_comparisons": len(rows),
|
||||||
|
"violations": sum(not row["equal"] for row in comparisons
|
||||||
|
if row["strate"] == view),
|
||||||
|
"whole_tile_proof_hits": skips, "tiles_visited": tiles,
|
||||||
|
"proof_hit_pct": round(100.0 * skips / max(tiles, 1), 2),
|
||||||
|
"classifier_ms_p50": round(statistics.median(costs), 3),
|
||||||
|
"classifier_ms_p95": round(sorted(costs)[int(0.95 * (len(costs) - 1))], 3),
|
||||||
|
})
|
||||||
|
result = {"runs": {"reference": references, "skip": candidates},
|
||||||
|
"comparisons": comparisons, "summary": summaries}
|
||||||
|
(OUT / "hash-sweep.json").write_text(json.dumps(result, indent=2) + "\n",
|
||||||
|
encoding="utf-8")
|
||||||
|
for row in summaries:
|
||||||
|
print("[L75HashSweepSummary] " + json.dumps(row, sort_keys=True), flush=True)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Run three interleaved full-view pairs on committed L74 and current L75 builds."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import statistics
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[4]
|
||||||
|
OUT = ROOT / "Saved/Linux/l75/logs/timing"
|
||||||
|
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||||
|
BINARIES = {
|
||||||
|
"before": ROOT / "Saved/Linux/l75/bin-c67-before/VoxelForgeCoreHeadless-l74-clang19",
|
||||||
|
"after": ROOT / "Saved/Linux/l75/bin/VoxelForgeCoreHeadless-l75-clang",
|
||||||
|
}
|
||||||
|
CAMERAS = {
|
||||||
|
0: (8.0, 16.0, -372.2626953125),
|
||||||
|
1: (25.8369140625, 3.19921875, -737.0),
|
||||||
|
2: (19.166015625, -14.2001953125, -753.3740234375),
|
||||||
|
3: (-20.0625, -5.068359375, -900.353515625),
|
||||||
|
}
|
||||||
|
ORDERS = (("before", "after"), ("after", "before"), ("before", "after"))
|
||||||
|
PIPELINE = re.compile(r"\[StreamPipeline\] (.*)")
|
||||||
|
PAIR = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
|
||||||
|
|
||||||
|
|
||||||
|
def run(strate: int, pair: int, mode: str) -> dict:
|
||||||
|
label = f"strate{strate}-pair{pair}-{mode}"
|
||||||
|
log = OUT / f"{label}.log"
|
||||||
|
camera = ",".join(f"{value:.12f}" for value in CAMERAS[strate]) + ",0,0"
|
||||||
|
command = [
|
||||||
|
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", "timeout",
|
||||||
|
"1740s", str(BINARIES[mode]), str(DATA), "--world-assembler-prototype",
|
||||||
|
"--stream-benchmark", "--stream-pipeline", "--force-generate", "--workers=8",
|
||||||
|
"--clip-radius=3", "--max-clip-level=8", "--max-level=8", f"--camera={camera}",
|
||||||
|
]
|
||||||
|
started = time.monotonic()
|
||||||
|
with log.open("w", encoding="utf-8") as output:
|
||||||
|
result = subprocess.run(command, cwd=ROOT, stdout=output, stderr=subprocess.STDOUT,
|
||||||
|
check=False, timeout=1740)
|
||||||
|
content = log.read_text(encoding="utf-8", errors="replace")
|
||||||
|
match = PIPELINE.search(content)
|
||||||
|
if result.returncode != 0 or match is None:
|
||||||
|
raise SystemExit(f"timing run failed or missing StreamPipeline summary; see {log}")
|
||||||
|
pipeline = dict(PAIR.findall(match.group(1)))
|
||||||
|
row = {
|
||||||
|
"strate": strate, "pair": pair, "mode": mode, "exit_code": result.returncode,
|
||||||
|
"process_wall_s": round(time.monotonic() - started, 3),
|
||||||
|
"whole_view_ready_s": float(pipeline["whole_view_ready"].removesuffix("s")),
|
||||||
|
"stream_wall_s": float(pipeline["stream_wall"].removesuffix("s")),
|
||||||
|
"whole_tiles": int(pipeline["whole_tiles"].split("/")[0]),
|
||||||
|
"source_stamp": re.search(r"^\[VoxelForgeSourceStamp\] sha256=(\S+)$",
|
||||||
|
content, re.M),
|
||||||
|
"log": str(log.relative_to(ROOT)),
|
||||||
|
}
|
||||||
|
row["source_stamp"] = row["source_stamp"].group(1) if row["source_stamp"] else None
|
||||||
|
print("[L75TimingPairRun] " + json.dumps(row, sort_keys=True), flush=True)
|
||||||
|
return row
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
OUT.mkdir(parents=True, exist_ok=True)
|
||||||
|
missing = [str(path) for path in [DATA, *BINARIES.values()] if not path.is_file()]
|
||||||
|
if missing:
|
||||||
|
raise SystemExit("missing input: " + ", ".join(missing))
|
||||||
|
rows = []
|
||||||
|
for strate in range(4):
|
||||||
|
for pair, order in enumerate(ORDERS, 1):
|
||||||
|
for mode in order:
|
||||||
|
rows.append(run(strate, pair, mode))
|
||||||
|
summary = []
|
||||||
|
for strate in range(4):
|
||||||
|
group = [row for row in rows if row["strate"] == strate]
|
||||||
|
before = [row["whole_view_ready_s"] for row in group if row["mode"] == "before"]
|
||||||
|
after = [row["whole_view_ready_s"] for row in group if row["mode"] == "after"]
|
||||||
|
summary.append({"strate": strate, "pairs": len(before),
|
||||||
|
"before_s": [round(value, 3) for value in before],
|
||||||
|
"after_s": [round(value, 3) for value in after],
|
||||||
|
"before_median_s": round(statistics.median(before), 3),
|
||||||
|
"after_median_s": round(statistics.median(after), 3),
|
||||||
|
"change_pct": round(100.0 * (statistics.median(after)
|
||||||
|
/ statistics.median(before) - 1.0), 2)})
|
||||||
|
result = {"runs": rows, "summary": summary}
|
||||||
|
(OUT / "summary.json").write_text(json.dumps(result, indent=2) + "\n",
|
||||||
|
encoding="utf-8")
|
||||||
|
for row in summary:
|
||||||
|
print("[L75TimingPairSummary] " + json.dumps(row, sort_keys=True), flush=True)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Keep the L74 false-solid TN lattice sample under a permanent block audit."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[4]
|
||||||
|
OUT = ROOT / "Saved/Linux/l75/logs"
|
||||||
|
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||||
|
BIN = ROOT / "Saved/Linux/l75/bin/VoxelForgeCoreHeadless-l75-clang"
|
||||||
|
PROBE = (-80, -20, -3218)
|
||||||
|
EXPECTED_DENSITY = 0.063384
|
||||||
|
CAMERA_METERS = (19.266015625, -14.1001953125, -753.3740234375)
|
||||||
|
REGRESSION = re.compile(r"\[L75TNBlockRegression\] (.*)")
|
||||||
|
AUDIT = re.compile(r"\[UniformBlockProofAudit\] checked_blocks=(\d+) checked_samples=(\d+) "
|
||||||
|
r"fallback_blocks=(\d+) unknown_blocks=(\d+) violations=(\d+)")
|
||||||
|
PAIR = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if not DATA.is_file() or not BIN.is_file():
|
||||||
|
raise SystemExit("missing owner world or L75 Clang headless binary")
|
||||||
|
OUT.mkdir(parents=True, exist_ok=True)
|
||||||
|
camera = ",".join(f"{value:.12f}" for value in CAMERA_METERS) + ",0,0"
|
||||||
|
log = OUT / "tn-block-regression.log"
|
||||||
|
command = [
|
||||||
|
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", "timeout",
|
||||||
|
"1740s", str(BIN), str(DATA), "--world-assembler-prototype",
|
||||||
|
"--stream-benchmark", "--stream-pipeline", "--stream-hash-tiles",
|
||||||
|
"--audit-uniform-block-proofs", "--workers=8", "--clip-radius=3",
|
||||||
|
"--max-clip-level=8", "--max-level=8", f"--camera={camera}",
|
||||||
|
]
|
||||||
|
environment = os.environ.copy()
|
||||||
|
environment["VOXELFORGE_L75_TN_BLOCK_REGRESSION"] = "1"
|
||||||
|
started = time.monotonic()
|
||||||
|
with log.open("w", encoding="utf-8") as output:
|
||||||
|
result = subprocess.run(command, cwd=ROOT, env=environment, stdout=output,
|
||||||
|
stderr=subprocess.STDOUT, check=False, timeout=1740)
|
||||||
|
content = log.read_text(encoding="utf-8", errors="replace")
|
||||||
|
probes = []
|
||||||
|
for line in content.splitlines():
|
||||||
|
match = REGRESSION.search(line)
|
||||||
|
if match:
|
||||||
|
row = dict(PAIR.findall(match.group(1)))
|
||||||
|
row.update(density=float(row["density"]), proof_safe=int(row["proof_safe"]))
|
||||||
|
row["tile_origin"] = tuple(int(value) for value in
|
||||||
|
row["tile_origin"].strip("()").split(","))
|
||||||
|
probes.append(row)
|
||||||
|
audit_match = AUDIT.search(content)
|
||||||
|
if not probes or audit_match is None:
|
||||||
|
raise SystemExit(f"TN regression did not cover the target lattice sample; see {log}")
|
||||||
|
checked, samples, fallback, unknown, violations = map(int, audit_match.groups())
|
||||||
|
density_matches = all(abs(row["density"] - EXPECTED_DENSITY) <= 1.0e-6
|
||||||
|
for row in probes)
|
||||||
|
false_solid_proofs = sum(row["proof_safe"] and row["verdict"] == "solid"
|
||||||
|
for row in probes)
|
||||||
|
row = {
|
||||||
|
"probe": PROBE, "expected_density": EXPECTED_DENSITY,
|
||||||
|
"observed_densities": [probe["density"] for probe in probes],
|
||||||
|
"block_intervals": [{key: probe[key] for key in
|
||||||
|
("tile_origin", "step", "block", "verdict", "proof_safe", "unknown")}
|
||||||
|
for probe in probes],
|
||||||
|
"checked_blocks": checked, "checked_samples": samples,
|
||||||
|
"fallback_blocks": fallback, "unknown_blocks": unknown,
|
||||||
|
"violations": violations, "false_solid_proofs": false_solid_proofs,
|
||||||
|
"exit_code": result.returncode,
|
||||||
|
"wall_s": round(time.monotonic() - started, 3),
|
||||||
|
"log": str(log.relative_to(ROOT)),
|
||||||
|
}
|
||||||
|
(OUT / "tn-block-regression.json").write_text(json.dumps(row, indent=2) + "\n",
|
||||||
|
encoding="utf-8")
|
||||||
|
print("[L75TNRegression] " + json.dumps(row, sort_keys=True), flush=True)
|
||||||
|
if result.returncode != 0 or not density_matches or false_solid_proofs or violations:
|
||||||
|
raise SystemExit(f"TN block regression failed; see {log}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Count first whole-tile proof vetoes among full-grid uniform observations."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
from collections import Counter
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[4]
|
||||||
|
OUT = ROOT / "Saved/Linux/l75/logs"
|
||||||
|
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||||
|
BIN = ROOT / "Saved/Linux/l75/bin/VoxelForgeCoreHeadless-l75-clang"
|
||||||
|
CAMERAS = {
|
||||||
|
0: (8.0, 16.0, -372.2626953125),
|
||||||
|
1: (25.8369140625, 3.19921875, -737.0),
|
||||||
|
2: (19.166015625, -14.2001953125, -753.3740234375),
|
||||||
|
3: (-20.0625, -5.068359375, -900.353515625),
|
||||||
|
}
|
||||||
|
ROW = re.compile(r"\[UniformGridCeiling\] (.*)")
|
||||||
|
PAIR = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
|
||||||
|
REASONS = {
|
||||||
|
0: "none_or_certified", 1: "detail_envelope", 2: "composer_candidate",
|
||||||
|
3: "passage_bounds", 4: "primitive_overlap", 5: "transition",
|
||||||
|
6: "stratum_crossing", 7: "smooth_min_slack", 8: "other",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def run(view: int) -> list[dict]:
|
||||||
|
camera = ",".join(f"{value:.12f}" for value in CAMERAS[view]) + ",0,0"
|
||||||
|
log = OUT / f"veto-view{view}.log"
|
||||||
|
command = [
|
||||||
|
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", "timeout",
|
||||||
|
"1740s", str(BIN), str(DATA), "--world-assembler-prototype",
|
||||||
|
"--stream-benchmark", "--stream-pipeline", "--stream-hash-tiles",
|
||||||
|
"--force-generate", "--workers=8", "--clip-radius=3", "--max-clip-level=8",
|
||||||
|
"--max-level=8", f"--camera={camera}",
|
||||||
|
]
|
||||||
|
started = time.monotonic()
|
||||||
|
with log.open("w", encoding="utf-8") as output:
|
||||||
|
result = subprocess.run(command, cwd=ROOT, stdout=output,
|
||||||
|
stderr=subprocess.STDOUT, check=False, timeout=1740)
|
||||||
|
rows = []
|
||||||
|
for line in log.read_text(encoding="utf-8", errors="replace").splitlines():
|
||||||
|
match = ROW.search(line)
|
||||||
|
if not match:
|
||||||
|
continue
|
||||||
|
row = dict(PAIR.findall(match.group(1)))
|
||||||
|
key_value = row["key"].strip("()")
|
||||||
|
row["key"] = tuple(int(value) for value in key_value.split(","))
|
||||||
|
row = {key: (value if key == "key" else int(value))
|
||||||
|
for key, value in row.items()}
|
||||||
|
row.update(view=view, log=str(log.relative_to(ROOT)))
|
||||||
|
rows.append(row)
|
||||||
|
print(json.dumps({"view": view, "exit_code": result.returncode,
|
||||||
|
"wall_s": round(time.monotonic() - started, 3),
|
||||||
|
"tiles": len(rows), "log": str(log.relative_to(ROOT))},
|
||||||
|
sort_keys=True), flush=True)
|
||||||
|
if result.returncode != 0 or not rows:
|
||||||
|
raise SystemExit(f"veto audit view {view} failed or emitted no rows; see {log}")
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if not DATA.is_file() or not BIN.is_file():
|
||||||
|
raise SystemExit("missing owner world or L75 Clang headless binary")
|
||||||
|
OUT.mkdir(parents=True, exist_ok=True)
|
||||||
|
all_rows = [row for view in range(4) for row in run(view)]
|
||||||
|
missed = [row for row in all_rows if row["samples"] > 0 and row["uniform"]
|
||||||
|
and not row["proof_safe"]]
|
||||||
|
counts = Counter(row["proof_veto"] for row in missed)
|
||||||
|
by_strate = []
|
||||||
|
for view in range(4):
|
||||||
|
group = [row for row in missed if row["view"] == view]
|
||||||
|
by_strate.append({
|
||||||
|
"strate": view,
|
||||||
|
"missed_uniform_tiles": len(group),
|
||||||
|
"vetoes": {REASONS.get(reason, f"unknown_{reason}"): count
|
||||||
|
for reason, count in sorted(Counter(
|
||||||
|
row["proof_veto"] for row in group).items())},
|
||||||
|
})
|
||||||
|
summary = {
|
||||||
|
"observations": len(all_rows),
|
||||||
|
"sampled_tiles": sum(row["samples"] > 0 for row in all_rows),
|
||||||
|
"true_uniform_tiles": sum(row["samples"] > 0 and row["uniform"]
|
||||||
|
for row in all_rows),
|
||||||
|
"missed_uniform_tiles": len(missed),
|
||||||
|
"vetoes": {REASONS.get(reason, f"unknown_{reason}"): count
|
||||||
|
for reason, count in sorted(counts.items())},
|
||||||
|
"by_strate": by_strate,
|
||||||
|
"missed_tiles": missed,
|
||||||
|
}
|
||||||
|
(OUT / "veto-audit.json").write_text(json.dumps(summary, indent=2) + "\n",
|
||||||
|
encoding="utf-8")
|
||||||
|
print("[L75VetoSummary] " + json.dumps({key: value for key, value in summary.items()
|
||||||
|
if key != "missed_tiles"}, sort_keys=True), flush=True)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -891,6 +891,9 @@ private:
|
|||||||
Options.bandMinZVoxel=Job.BandZMin;Options.bandMaxZVoxel=Job.BandZMax;
|
Options.bandMinZVoxel=Job.BandZMin;Options.bandMaxZVoxel=Job.BandZMax;
|
||||||
Options.hashDensity=bHashDensity;
|
Options.hashDensity=bHashDensity;
|
||||||
Options.allowUniformSkipsWhenHashing=bHashDensity&&bAllowUniformSkipsWhenHashing;
|
Options.allowUniformSkipsWhenHashing=bHashDensity&&bAllowUniformSkipsWhenHashing;
|
||||||
|
// The reference hash mode must materialize every density sample, including
|
||||||
|
// tiles whose sub-block intervals independently prove a uniform sign.
|
||||||
|
Options.forceFullDensityCapture=bHashDensity&&!bAllowUniformSkipsWhenHashing;
|
||||||
Options.skipUvAndColors=bSkipUvAndColors;
|
Options.skipUvAndColors=bSkipUvAndColors;
|
||||||
Options.skipMaterialGroups=bSkipMaterialGroups;Options.skipUv=bSkipUV;Options.skipColors=bSkipColors;
|
Options.skipMaterialGroups=bSkipMaterialGroups;Options.skipUv=bSkipUV;Options.skipColors=bSkipColors;
|
||||||
Options.captureColumnCoordinates=bCaptureColumnCoordinates;
|
Options.captureColumnCoordinates=bCaptureColumnCoordinates;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
#include "Misc/CommandLine.h"
|
#include "Misc/CommandLine.h"
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <bit>
|
#include <bit>
|
||||||
|
#include <cstdlib>
|
||||||
#include <immintrin.h>
|
#include <immintrin.h>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
@@ -28,6 +29,15 @@ namespace
|
|||||||
GVoxelForgeUseOperatorBlock,
|
GVoxelForgeUseOperatorBlock,
|
||||||
TEXT("Use the operator-major density block path for non-shared mesher grids."));
|
TEXT("Use the operator-major density block path for non-shared mesher grids."));
|
||||||
bool GVoxelForgeBlockSwitchParsed = false;
|
bool GVoxelForgeBlockSwitchParsed = false;
|
||||||
|
bool VF_IsL75TnBlockRegressionEnabled()
|
||||||
|
{
|
||||||
|
static const bool Enabled=[]
|
||||||
|
{
|
||||||
|
const char* Value=std::getenv("VOXELFORGE_L75_TN_BLOCK_REGRESSION");
|
||||||
|
return Value&&Value[0]=='1'&&Value[1]=='\0';
|
||||||
|
}();
|
||||||
|
return Enabled;
|
||||||
|
}
|
||||||
int32 GVoxelForgeRawSkirtsForAudit = 0;
|
int32 GVoxelForgeRawSkirtsForAudit = 0;
|
||||||
bool GVoxelForgeRawSkirtsSwitchParsed = false;
|
bool GVoxelForgeRawSkirtsSwitchParsed = false;
|
||||||
|
|
||||||
@@ -644,10 +654,44 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
bBlockUnknown=ShapeVerdict.bUnknown;
|
bBlockUnknown=ShapeVerdict.bUnknown;
|
||||||
bBlockNative=ShapeVerdict.bNativeSurface;
|
bBlockNative=ShapeVerdict.bNativeSurface;
|
||||||
bBlockProofSafe=ShapeVerdict.bSkipSafe;
|
bBlockProofSafe=ShapeVerdict.bSkipSafe;
|
||||||
|
if(VF_IsL75TnBlockRegressionEnabled())
|
||||||
|
{
|
||||||
|
constexpr int32 ProbeX=-80,ProbeY=-20,ProbeZ=-3218;
|
||||||
|
const auto IsCapturedGridPoint=[&](int32 Value,int32 Start)
|
||||||
|
{
|
||||||
|
const int32 Delta=Value-Start;
|
||||||
|
return Delta%Step==0&&Delta/Step>=-1
|
||||||
|
&&Delta/Step<=BlockCells+1;
|
||||||
|
};
|
||||||
|
if(IsCapturedGridPoint(ProbeX,BlockOrigin.X)
|
||||||
|
&&IsCapturedGridPoint(ProbeY,BlockOrigin.Y)
|
||||||
|
&&IsCapturedGridPoint(ProbeZ,BlockOrigin.Z))
|
||||||
|
{
|
||||||
|
const float Density=ShapeWorldContext->DensityAtLattice(
|
||||||
|
ProbeX,ProbeY,ProbeZ);
|
||||||
|
const std::string Dispatch=ShapeWorldContext->DensityDispatchPathAt(
|
||||||
|
static_cast<float>(ProbeX),static_cast<float>(ProbeY),
|
||||||
|
static_cast<float>(ProbeZ));
|
||||||
|
const EVoxelTileClass GeneratorVerdict=Generator->ClassifyTile(
|
||||||
|
BlockOrigin,Step,BlockCells);
|
||||||
|
const char* Label=Verdict==EVoxelTileClass::AllSolid?"solid":
|
||||||
|
Verdict==EVoxelTileClass::AllAir?"air":"mixed";
|
||||||
|
const char* GeneratorLabel=GeneratorVerdict==EVoxelTileClass::AllSolid
|
||||||
|
?"solid":GeneratorVerdict==EVoxelTileClass::AllAir?"air":"mixed";
|
||||||
|
std::printf("[L75TNBlockRegression] tile_origin=(%d,%d,%d) step=%d cells=%d block=(%d,%d,%d) verdict=%s proof_safe=%d unknown=%d density=%.9g dispatch=%s generator_verdict=%s\n",
|
||||||
|
OriginVoxels.X,OriginVoxels.Y,OriginVoxels.Z,Step,
|
||||||
|
CellsPerAxis,BlockX,BlockY,BlockZ,Label,
|
||||||
|
bBlockProofSafe?1:0,bBlockUnknown?1:0,Density,
|
||||||
|
Dispatch.c_str(),GeneratorLabel);
|
||||||
|
}
|
||||||
|
}
|
||||||
if((Verdict==EVoxelTileClass::AllSolid
|
if((Verdict==EVoxelTileClass::AllSolid
|
||||||
||Verdict==EVoxelTileClass::AllAir)
|
||Verdict==EVoxelTileClass::AllAir)
|
||||||
&&!bBlockProofSafe)
|
&&!bBlockProofSafe)
|
||||||
{
|
{
|
||||||
|
// An uncertified uniform candidate makes the parent block grid
|
||||||
|
// unusable as a sampling certificate. Keep the committed fallback:
|
||||||
|
// fill the full density grid for this tile.
|
||||||
bCanSkipProofBlockGrid=false;
|
bCanSkipProofBlockGrid=false;
|
||||||
bAllRelevantBlocksProofSafe=false;
|
bAllRelevantBlocksProofSafe=false;
|
||||||
}
|
}
|
||||||
|
|||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
[PipelineFullDetailHash] tiles=960 density=0x151044001ea90cd5 mesh=0xa818654be89790ac payload=0x370ff80451105a39 topology=0x06e507557aa70173
|
||||||
|
[PipelineWholeViewHash] tiles=2370 density=0xf7bb51a4667eac72 mesh=0x937b616fe3c0bdc2 payload=0x230c904a12575f05 topology=0x6284613657ddb7fe
|
||||||
|
[PipelineLODHash] lod=0 tiles=512 density=0x2fd4c78685a43cfe mesh=0x50478cff1c1f8ca0 payload=0x54e48e57e0af26de topology=0x860c011cfdb9833a
|
||||||
|
[PipelineLODHash] lod=1 tiles=448 density=0x64bb55bd41e9fd42 mesh=0x17e89b7087bfe711 payload=0x2389c109eb780db6 topology=0x7eccd4cf560d0630
|
||||||
|
[PipelineLODHash] lod=2 tiles=320 density=0x1edcfc338a395eaf mesh=0xc647d1e056e370cd payload=0xf81bba773df803ee topology=0xac68f4c0b6647735
|
||||||
|
[PipelineLODHash] lod=3 tiles=208 density=0x580ede5c492925c7 mesh=0x30aeb3422a9382ef payload=0xb09b16ae1d40ac0d topology=0xd85e165b364261b5
|
||||||
|
[PipelineLODHash] lod=4 tiles=240 density=0xfb11591772a3d18e mesh=0xebf2baee8764591b payload=0xe491d7ac9c0061ad topology=0xd618ac33b95ce494
|
||||||
|
[PipelineLODHash] lod=5 tiles=144 density=0x2f8b7b7fb1e67258 mesh=0x4cd508cbe97344fd payload=0x2b6e96a6050458c9 topology=0x4ff6d0eda8988573
|
||||||
|
[PipelineLODHash] lod=6 tiles=96 density=0x0114a34c8b8d6113 mesh=0xb1fec4d01ae1fa44 payload=0x3e50cfd429b8e950 topology=0x08090c96d15c1618
|
||||||
|
[PipelineLODHash] lod=7 tiles=96 density=0x3836ed8cfce73418 mesh=0x40bc6589c5fc1aac payload=0x677349bcae16790f topology=0x36e3aa53192e6a6b
|
||||||
|
[PipelineLODHash] lod=8 tiles=306 density=0x144f3c1fd722708b mesh=0xef77b28bf7fd9453 payload=0xe3f47b37fcd61489 topology=0x5a3c73e295494e04
|
||||||
|
[PipelineExactQueryHash] probes=9 density=0x7b7c50ac24ccadcc unclamped=0x7b7c50ac24ccadcc detail=0xefcba20c0c70c415 collision=0xe604823a249029bf path=direct-Core-queries
|
||||||
@@ -125,10 +125,11 @@ def owner_hash_baseline(recipe: str, style: int) -> tuple[Path, str]:
|
|||||||
return (BASELINES / "owner-hash/canonical/owner-hash-surface-world-style1-v212.baseline",
|
return (BASELINES / "owner-hash/canonical/owner-hash-surface-world-style1-v212.baseline",
|
||||||
"L67-v212")
|
"L67-v212")
|
||||||
if recipe == "floating-islands" and style == 2:
|
if recipe == "floating-islands" and style == 2:
|
||||||
# L74 disables a legacy sub-tile shortcut after its falsifier found false solids.
|
# L75 explicitly materializes every reference density sample. The older L74
|
||||||
# Keep the old fixture intact and pin the restored full-grid density signature.
|
# signature omitted samples skipped inside native uniform blocks; retain that
|
||||||
return (BASELINES / "owner-hash/canonical/owner-hash-floating-islands-style2-l74-fullgrid.baseline",
|
# fixture and pin the current full-grid signature separately.
|
||||||
"L74-full-grid")
|
return (BASELINES / "owner-hash/canonical/owner-hash-floating-islands-style2-l75-fullgrid.baseline",
|
||||||
|
"L75-full-grid")
|
||||||
raise VerificationFailure(f"no pinned owner hash baseline for {recipe} style {style}")
|
raise VerificationFailure(f"no pinned owner hash baseline for {recipe} style {style}")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user