From 9d5455ec687692230e792d80f50c188b31cebdc1 Mon Sep 17 00:00:00 2001 From: Fr0zka Date: Tue, 27 Oct 2026 14:34:49 +0100 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1 --- .../include/VoxelForgeCore/VoxelForgeCore.h | 3 + Standalone/Core/src/ShapeWorldInternal.h | 489 ++++++++++++++++-- Standalone/Core/src/VoxelForgeCore.cpp | 19 +- .../WorldAssemblerM3/Linux75/FalsifierL75.py | 171 ++++++ .../WorldAssemblerM3/Linux75/REPORT.md | 85 +++ .../Linux75/RunAuditSweepL75.py | 111 ++++ .../Linux75/RunHashSweepL75.py | 163 ++++++ .../Linux75/RunTimingPairsL75.py | 96 ++++ .../Linux75/RunTnBlockRegressionL75.py | 85 +++ .../Linux75/RunVetoAuditL75.py | 104 ++++ .../RunTimingPairsL75.cpython-313.pyc | Bin 0 -> 7070 bytes .../RunVetoAuditL75.cpython-313.pyc | Bin 0 -> 8071 bytes Standalone/Source/StreamingWorld.h | 3 + .../Private/VoxelMarchingCubesMesher.cpp | 44 ++ ...ating-islands-style2-l75-fullgrid.baseline | 12 + Standalone/Tools/verify/verify.py | 9 +- 16 files changed, 1335 insertions(+), 59 deletions(-) create mode 100644 Standalone/Research/WorldAssemblerM3/Linux75/FalsifierL75.py create mode 100644 Standalone/Research/WorldAssemblerM3/Linux75/REPORT.md create mode 100644 Standalone/Research/WorldAssemblerM3/Linux75/RunAuditSweepL75.py create mode 100644 Standalone/Research/WorldAssemblerM3/Linux75/RunHashSweepL75.py create mode 100644 Standalone/Research/WorldAssemblerM3/Linux75/RunTimingPairsL75.py create mode 100644 Standalone/Research/WorldAssemblerM3/Linux75/RunTnBlockRegressionL75.py create mode 100644 Standalone/Research/WorldAssemblerM3/Linux75/RunVetoAuditL75.py create mode 100644 Standalone/Research/WorldAssemblerM3/Linux75/__pycache__/RunTimingPairsL75.cpython-313.pyc create mode 100644 Standalone/Research/WorldAssemblerM3/Linux75/__pycache__/RunVetoAuditL75.cpython-313.pyc create mode 100644 Standalone/Tools/verify/baselines/owner-hash/canonical/owner-hash-floating-islands-style2-l75-fullgrid.baseline diff --git a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h index 876bd2f..6be10a4 100644 --- a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h +++ b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h @@ -252,6 +252,9 @@ namespace VoxelForgeCore std::uint32_t farHorizonVersion = 0; bool farShellRepairSkipped = false; bool tileWasClassified = false; + std::uint8_t tileProofVetoReason = 0; + bool tileProofSafe = false; + bool tileLegacyBlockProofRelevant = false; bool meshingSkipped = false; bool featureRefinedResolution = false; bool heightfieldBandLimited = false; diff --git a/Standalone/Core/src/ShapeWorldInternal.h b/Standalone/Core/src/ShapeWorldInternal.h index 35fb773..dda8174 100644 --- a/Standalone/Core/src/ShapeWorldInternal.h +++ b/Standalone/Core/src/ShapeWorldInternal.h @@ -1570,8 +1570,29 @@ public: *floorAlignment*floorAlignment*floorBelowWeight*mouthWalkLaneWeight; const float fullyCarvedDensity=std::max(composed, static_cast(-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, static_cast(floorCarveWeight)); + if(bL75TraceTnPassage&&X==-80.0f&&Y==-20.0f&&Z==-3218.0f + &&composed!=passageInput) + { + const auto& traceA=passage.controlPoints[static_cast(bestIndex)]; + const auto& traceB=passage.controlPoints[static_cast(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(std::min(traceA.positionSubvoxels[0],traceB.positionSubvoxels[0])), + static_cast(std::min(traceA.positionSubvoxels[1],traceB.positionSubvoxels[1])), + static_cast(std::min(traceA.positionSubvoxels[2],traceB.positionSubvoxels[2])), + static_cast(std::max(traceA.positionSubvoxels[0],traceB.positionSubvoxels[0])), + static_cast(std::max(traceA.positionSubvoxels[1],traceB.positionSubvoxels[1])), + static_cast(std::max(traceA.positionSubvoxels[2],traceB.positionSubvoxels[2]))); + } const float composedAfterCarve=composed; const auto considerSupportSegment=[&](std::ptrdiff_t index) { @@ -1999,15 +2020,31 @@ public: const float composed=nativeLattice?boundary :ApplyResolvedStratePassageCarves(boundary,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 &&bound->Resolved&&VoxelForgeCore::HasNativeGraphField(*bound->Resolved) &&(VoxelForgeCore::IsNativeCaveFieldStratum(*bound->Resolved) ?bound->Resolved->passageCapabilities.hasNativeCaveField :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; + } // The legacy landmark demo retains its compatibility floor cap. Native caves // already combine each floor with its wall before the single void carve above. - return ApplyResolvedCavernFloorCaps(stamped,X,Y,Z); + 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 @@ -2157,6 +2194,14 @@ public: bool bNativeSurface=false; bool bUnknown=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 { @@ -2497,6 +2542,7 @@ public: WorkerCount=std::clamp(Workers,1,16);DetailVariant=Variant; Scheduler=InScheduler;RoundTunnelsEnabled=bInRoundTunnels; bUseNativeManifestFields=true; + UnbinnedSpatialPrimitiveIds.clear(); BindResolvedStratumFields(); bAnalyticOnly=false; bProfileDetailBand=bInProfileDetailBand; @@ -3323,6 +3369,44 @@ public: return LegacySurfaceDensityAt(static_cast(X),static_cast(Y), static_cast(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(Y-Min[1])*Size[0] + +static_cast(X-Min[0]); + const size_t Zi=static_cast(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(X), + static_cast(Y),static_cast(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(SurfaceDistanceQ[Zi*static_cast(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||XPrimitive.Max[0] + ||YPrimitive.Max[1] + ||ZPrimitive.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) return PackedDensityAt(X,Y,Z,IsRock(X,Y,Z),static_cast(X), static_cast(Y),static_cast(Z)); @@ -4487,6 +4571,10 @@ private: const double qPerMetre=Q*voxelsPerMetre; const double bridgeHalfWidthVoxels=3.5*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()); PassageShortSpanSegmentFlags.resize(ResolvedLayout->passages.size()); for(std::size_t passageIndex=0;passageIndexpassages.size();++passageIndex) @@ -4507,24 +4595,119 @@ private: const auto& a=passage.controlPoints[segment]; const auto& b=passage.controlPoints[segment+1]; const double radius=static_cast(std::max(a.radiusSubvoxels,b.radiusSubvoxels))/Q; - const double horizontalExtent=std::max(radius,bridgeHalfWidthVoxels); + const std::ptrdiff_t lastControl=static_cast( + passage.controlPoints.size())-1; + const std::ptrdiff_t lowerMouthControl=std::clamp( + static_cast(passage.lowerMouthControlIndex),1,lastControl); + const double dx=static_cast(b.positionSubvoxels[0]) + -a.positionSubvoxels[0]; + const double dy=static_cast(b.positionSubvoxels[1]) + -a.positionSubvoxels[1]; + const double dz=static_cast(b.positionSubvoxels[2]) + -a.positionSubvoxels[2]; + const bool bMayUseSmoothCarve=segment>2 + &&static_cast(segment)+2( + std::min(3,lowerMouthControl)) + ?passageMouthFloorReliefVoxels:0.0; + constexpr int32 ProofSubdivisions=4; + constexpr double OutwardPaddingVoxels=1.0e-6; const double footDepth=std::max(voxelsPerMetre,bridgeFootDepthVoxels); - FPassageProofBound proof; - proof.minimum={std::min(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q-horizontalExtent, - std::min(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q-horizontalExtent, - std::min(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q-radius-footDepth}; - proof.maximum={std::max(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q+horizontalExtent, - std::max(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q+horizontalExtent, - std::max(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q+radius}; - PassageProofBounds.push_back(proof); + std::array featureMinimum={ + std::numeric_limits::infinity(), + std::numeric_limits::infinity(), + std::numeric_limits::infinity()}; + std::array featureMaximum={ + -std::numeric_limits::infinity(), + -std::numeric_limits::infinity(), + -std::numeric_limits::infinity()}; + const auto& p0=bMayUseSmoothCarve + ?passage.controlPoints[segment-1]:a; + const auto& p3=bMayUseSmoothCarve + ?passage.controlPoints[segment+2]:b; + for(int32 proofPart=0;proofPart(proofPart)/ProofSubdivisions; + const double t1=static_cast(proofPart+1)/ProofSubdivisions; + std::array centerMinimum{},centerMaximum{}; + for(std::size_t axis=0;axis<3;++axis) + { + const double v0=static_cast(p0.positionSubvoxels[axis])/Q; + const double v1=static_cast(a.positionSubvoxels[axis])/Q; + const double v2=static_cast(b.positionSubvoxels[axis])/Q; + const double v3=static_cast(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(a.radiusSubvoxels) + +t0*(static_cast(b.radiusSubvoxels)-a.radiusSubvoxels))/Q; + const double radiusAt1=(static_cast(a.radiusSubvoxels) + +t1*(static_cast(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 featureMin={ - static_cast(std::floor(proof.minimum[0]*Q)), - static_cast(std::floor(proof.minimum[1]*Q)), - static_cast(std::floor(proof.minimum[2]*Q))}; + static_cast(std::floor(featureMinimum[0]*Q)), + static_cast(std::floor(featureMinimum[1]*Q)), + static_cast(std::floor(featureMinimum[2]*Q))}; const std::array featureMax={ - static_cast(std::ceil(proof.maximum[0]*Q)), - static_cast(std::ceil(proof.maximum[1]*Q)), - static_cast(std::ceil(proof.maximum[2]*Q))}; + static_cast(std::ceil(featureMaximum[0]*Q)), + static_cast(std::ceil(featureMaximum[1]*Q)), + static_cast(std::ceil(featureMaximum[2]*Q))}; PassageFeatureBounds.push_back({static_cast(PassageTileFeatures.size()), featureMin,featureMax}); PassageTileFeatures.push_back({passageIndex,segment,false}); @@ -5965,8 +6148,22 @@ public: { if(SharedDensitySource) 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; const auto Start=Clock::now(); + std::uint8_t LegacyProofVeto=static_cast(ELegacyProofVeto::None); static const bool bL74TightNegativeControl=[] { 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"); return Enabled; }(); - const int32 Pad=std::max(Step,1); - int32 BMin[3]={Origin.X-Pad,Origin.Y-Pad,Origin.Z-Pad}; - int32 BMax[3]={Origin.X+(Cells+1)*Step,Origin.Y+(Cells+1)*Step,Origin.Z+(Cells+1)*Step}; + const int32 BMin[3]={InputMin[0],InputMin[1],InputMin[2]}; + const int32 BMax[3]={InputMax[0],InputMax[1],InputMax[2]}; + 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::WorldAssemblyFloatingIslandPlanV1* IslandPlan=nullptr; const FSurfaceWorldFieldAdapter* SurfaceField=nullptr; @@ -5986,6 +6195,11 @@ public: bool bSurfaceOverlap=false,bIslandOverlap=false,bCavesOverlap=false; bool bOtherStrateOverlap=false,bLegacyOverlap=false,bCaveOtherStrateOverlap=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 caveCandidates; if(Owner&&ResolvedLayout) { @@ -5995,8 +6209,13 @@ public: for(int32 ChunkZ=FirstChunk;ChunkZ<=LastChunk;++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) { + bLegacyFieldCandidate=false; bSurfaceOverlap=true;bIslandOtherStrateOverlap=true; const auto* Candidate=Bound->SurfacePlan; if(!Candidate||ChunkZbottomChunkZ @@ -6016,9 +6235,14 @@ public: IslandPlan=Candidate; IslandField=std::get_if( &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) { + bLegacyFieldCandidate=false; bCavesOverlap=true;bOtherStrateOverlap=true; bIslandOtherStrateOverlap=true; if(CaveBound&&CaveBound!=Bound)bBadPlan=true; @@ -6030,11 +6254,21 @@ public: ||!std::get_if(&Bound->Evaluator))bBadPlan=true; } 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 &&(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, bool bLegacyAnalyticProof=false) { @@ -6045,6 +6279,10 @@ public: ||Verdict==EVoxelTileClass::AllSolid; FBoxClassification Result{Verdict,bNative,bUnknown, bLegacyAnalyticProof||(bNative&&!bUnknown&&bUniform)}; + Result.bLegacyFieldCandidate=bLegacyFieldCandidate; + Result.bLegacyBlockProofRelevant=bLegacyBlockProofRelevant; + Result.LegacyProofVeto=Result.bSkipSafe + ?static_cast(ELegacyProofVeto::None):LegacyProofVeto; auto& Counts=ThreadClassificationStats(); Counts.Milliseconds+=std::chrono::duration(Clock::now()-Start).count(); if(bUnknown)++Counts.UnknownBoxes; @@ -6053,6 +6291,73 @@ public: else ++Counts.MixedBoxes; return Result; }; + if(SampleStep<=2&&bStratumSlicingExpectedToPay&&Owner&&ResolvedLayout + &&SampleStep>0&&BMax[2]>=BMin[2] + &&(static_cast(BMax[2])-BMin[2])%SampleStep==0) + { + const double LayoutWidth=GetResolvedLayoutChunkWidthVoxels(); + if(LayoutWidth>0.0&&std::isfinite(LayoutWidth)) + { + std::vector> StratumSlices; + int32 SliceStart=BMin[2]; + auto BoundAt=[&](int32 Z) + { + const int32 ChunkZ=FMath::FloorToInt(static_cast(Z)/LayoutWidth); + return FindBoundStratumForChunk(ChunkZ); + }; + const FBoundStratumField* PreviousBound=BoundAt(SliceStart); + for(std::int64_t Z=static_cast(BMin[2])+SampleStep; + Z<=BMax[2];Z+=SampleStep) + { + const auto* Bound=BoundAt(static_cast(Z)); + if(Bound==PreviousBound)continue; + StratumSlices.push_back({SliceStart,static_cast(Z-SampleStep)}); + SliceStart=static_cast(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(ELegacyProofVeto::StratumCrossing); + return Finish(EVoxelTileClass::Mixed,Part.bUnknown); + } + if(!bHaveVerdict) + { + CommonVerdict=Part.Verdict; + bHaveVerdict=true; + } + else if(Part.Verdict!=CommonVerdict) + { + LegacyProofVeto=static_cast( + 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 stampCandidates; if(!StampSpatialBounds.empty()) { @@ -6075,7 +6380,10 @@ public: &&stamp.record->kind !=VoxelForgeCore::ResolvedSdfStampKindV1::fillPlatform) ||!SurfacePlan||stamp.record->strateIndex!=SurfacePlan->strateIndex) + { + LegacyProofVeto=static_cast(ELegacyProofVeto::ComposerCandidate); return Finish(EVoxelTileClass::Mixed,true); + } } } if(!CavernRuntime.empty()) @@ -6091,10 +6399,19 @@ public: static_cast(BMax[2])*Q}; caveCandidates=BuildCavernFeatureTileList(minimum,maximum); if(!caveCandidates.empty()&&!bCavesOverlap&&!bL74TightNegativeControl) + { + LegacyProofVeto=static_cast(ELegacyProofVeto::ComposerCandidate); 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(ELegacyProofVeto::PassageBounds); return Finish(EVoxelTileClass::Mixed,true); + } if(!EditBrushes.empty()&&EditBoundsMin.X<=BMax[0]&&EditBoundsMax.X>=BMin[0] &&EditBoundsMin.Y<=BMax[1]&&EditBoundsMax.Y>=BMin[1] &&EditBoundsMin.Z<=BMax[2]&&EditBoundsMax.Z>=BMin[2]) @@ -6111,6 +6428,7 @@ public: if(ZMinMicrometres>=Band->bottomMicrometres &&ZMaxMicrometrestopMicrometres) return Finish(EVoxelTileClass::AllSolid); + LegacyProofVeto=static_cast(ELegacyProofVeto::StratumCrossing); return Finish(EVoxelTileClass::Mixed); } } @@ -6151,7 +6469,10 @@ public: Lower->ceiling.topMicrometres)+MaximumDisplacement; if(ZMaximumMicrometres>=WindowMinimum &&ZMinimumMicrometres<=WindowMaximum) + { + LegacyProofVeto=static_cast(ELegacyProofVeto::Transition); return Finish(EVoxelTileClass::Mixed,true); + } } } const auto TryLegacyAnalyticProof=[&]() -> std::optional @@ -6161,28 +6482,45 @@ public: // terms. SurfaceWorld op stacks, native adapters, and any active composer // feature fail closed and keep using their existing path. if(!WorldAssembly||!Owner||!Owner->Manager - ||!ResolvedLayout||!Owner->Generator||bSurfaceOverlap||bCavesOverlap - ||(!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(BMin[2])/LayoutWidth); - const int32 LastChunk=FMath::FloorToInt(static_cast(BMax[2])/LayoutWidth); - for(int32 ChunkZ=FirstChunk;ChunkZ<=LastChunk;++ChunkZ) + ||!ResolvedLayout||!Owner->Generator) { - const FBoundStratumField* Bound=FindBoundStratumForChunk(ChunkZ); - if(!Bound||Bound->Kind==FBoundStratumField::EKind::SurfaceWorld) - 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; + LegacyProofVeto=static_cast(ELegacyProofVeto::Other); + return std::nullopt; } + if(!stampCandidates.empty()||!caveCandidates.empty()) + { + LegacyProofVeto=static_cast(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(bCrossedFieldKinds||bBadPlan + ?ELegacyProofVeto::StratumCrossing:ELegacyProofVeto::Other); + return std::nullopt; + } + if(!bLegacyFieldCandidate) + { + LegacyProofVeto=static_cast(bBadPlan||bMissingStratumBound + ?ELegacyProofVeto::StratumCrossing:ELegacyProofVeto::Other); + return std::nullopt; + } + if(!EditBrushes.empty()||LayerRanges.empty()||ActiveSpatialBinSize<=0) + { + LegacyProofVeto=static_cast(ELegacyProofVeto::Other); + return std::nullopt; + } + const double LayoutWidth=GetResolvedLayoutChunkWidthVoxels(); + if(!(LayoutWidth>0.0)||!std::isfinite(LayoutWidth)) + { + LegacyProofVeto=static_cast(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); BZ<=FloorDiv(BMax[2],ActiveSpatialBinSize);++BZ) for(int32 BY=FloorDiv(BMin[1],ActiveSpatialBinSize); @@ -6200,12 +6538,43 @@ public: Overlap=Overlap &&Primitive.Max[Axis]+DetailBand>=BMin[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(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(ELegacyProofVeto::PrimitiveOverlap); + return std::nullopt; } } const FInterval& Layer=LayerRanges.front(); - if(Layer.Hi<=Layer.Lo)return std::nullopt; + if(Layer.Hi<=Layer.Lo) + { + LegacyProofVeto=static_cast(ELegacyProofVeto::Other); + return std::nullopt; + } const auto BaseAt=[&](double Z) {return std::min(Z-static_cast(Layer.Lo), static_cast(Layer.Hi)-Z);}; @@ -6217,19 +6586,29 @@ public: const double Peak=(static_cast(Layer.Lo)+Layer.Hi)*0.5; if(Peak>=Z0&&Peak<=Z1)BaseMaximum=std::max(BaseMaximum,BaseAt(Peak)); // With no active primitive, smooth-min sees only its +100000 sentinel; - // its largest possible downward correction is 12/6 = 2 voxels. Keep - // the entire resulting field outside the measured detail support (4.125). + // outside the width-12 transition it is exactly the base field. Charge its + // 12/6 maximum only if the base interval can approach that sentinel. 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; const double SolidMargin=bL74TightNegativeControl ?SafetyEpsilon:DetailSupport+SmoothMinSlack+SafetyEpsilon; const double AirMargin=bL74TightNegativeControl ?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) return EVoxelTileClass::AllSolid; if(BaseMaximum<-AirMargin) return EVoxelTileClass::AllAir; + if(!bL74TightNegativeControl&&SmoothMinSlack>0.0 + &&BaseMinimum>DetailSupport+SafetyEpsilon) + LegacyProofVeto=static_cast(ELegacyProofVeto::SmoothMinSlack); + else + LegacyProofVeto=static_cast(ELegacyProofVeto::DetailEnvelope); return std::nullopt; }; if(const auto LegacyVerdict=TryLegacyAnalyticProof()) @@ -6239,7 +6618,10 @@ public: if(bBadPlan||(bSurfaceOverlap&&(bIslandOverlap||bOtherStrateOverlap)) ||(bIslandOverlap&&bIslandOtherStrateOverlap) ||(bCavesOverlap&&(bCaveOtherStrateOverlap||bSurfaceOverlap||bIslandOverlap))) + { + LegacyProofVeto=static_cast(ELegacyProofVeto::StratumCrossing); return Finish(EVoxelTileClass::Mixed,true); + } // The native interval path below is the exact field selected first by DensityAtLattice // for SurfaceWorld and FloatingIslands. Legacy analytic primitives/layer bounds cannot // modify that field; only the explicit edit transaction above can. Keep their guards on @@ -6317,7 +6699,7 @@ public: static_cast(BMax[0])*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel, static_cast(BMax[1])*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel, static_cast(BMax[2])*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel, - std::max(Step,1)); + std::max(SampleStep,1)); switch(Verdict) { case VoxelForgeCore::HeightfieldBoxClass::allAir: @@ -9217,7 +9599,14 @@ private: // primitive from analytic queries outside the packed near region. // Les longues galeries diagonales ont une grande AABB mais un tube fin. // 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(BinResolution); const double AxisStep=std::max(1.0,BinWidth*0.5); const int32 AxisSamples=std::max(1,static_cast(std::ceil( @@ -9269,6 +9658,9 @@ private: 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() @@ -11460,6 +11852,7 @@ private: std::vector PassageVoidCrossings; std::array AxisBins; std::unordered_map SpatialBins; + std::vector UnbinnedSpatialPrimitiveIds; std::unordered_map CriticalAirBins; mutable std::mutex FarFieldGpuInputMutex; mutable std::shared_ptr CachedFarFieldGpuInput; diff --git a/Standalone/Core/src/VoxelForgeCore.cpp b/Standalone/Core/src/VoxelForgeCore.cpp index b29ac41..7873fc9 100644 --- a/Standalone/Core/src/VoxelForgeCore.cpp +++ b/Standalone/Core/src/VoxelForgeCore.cpp @@ -2364,6 +2364,10 @@ namespace VoxelForgeCore const double columnMillisecondsBeforeClassification= VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds; 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, VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds -columnMillisecondsBeforeClassification); @@ -2443,11 +2447,10 @@ namespace VoxelForgeCore return out; } } - // Keep sub-tile early-outs on native fields whose block interval is complete. - // The legacy compiled field has sparse tunnel/composer terms that can change a - // small block without making the current whole-tile bound fail; leave those blocks - // to the exact grid and use only the separately falsified whole-tile proof above. - if(bHasWorldAssembly&&step<=2&&bNativeSurfaceTile) + // Preserve the committed native-field block path. Legacy block certificates + // remain audit-only until their cost is repaid against the full-grid path. + if(bHasWorldAssembly&&step<=2 + &&(bNativeSurfaceTile||options.auditUniformBlockProofs)) mesher.bUseBlockEarlyOut=true; } const auto MeshingStart=std::chrono::steady_clock::now(); @@ -3077,13 +3080,15 @@ namespace VoxelForgeCore /10000.0; const int32 stratum=world.Manager ?world.Manager->GetStrateIndex(static_cast(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(), tileUniform&&tileHasSign?1:0,tileUniform&&tileHasSign&&tileAir?1:0, static_cast(uniformBlocks8), static_cast(uniformSamples8), static_cast(uniformBlocks4), - static_cast(uniformSamples4)); + static_cast(uniformSamples4), + out.diagnostics.tileProofSafe?1:0, + static_cast(out.diagnostics.tileProofVetoReason)); out.densityHash=densityHash;out.meshHash=HashViewerMesh(mesh); out.payloadHash=HashPayload(mesh);out.topologyHash=HashTopology(mesh); out.materialHash=HashMaterialSamples(out.vertices); diff --git a/Standalone/Research/WorldAssemblerM3/Linux75/FalsifierL75.py b/Standalone/Research/WorldAssemblerM3/Linux75/FalsifierL75.py new file mode 100644 index 0000000..d7ac5c1 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux75/FalsifierL75.py @@ -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()) diff --git a/Standalone/Research/WorldAssemblerM3/Linux75/REPORT.md b/Standalone/Research/WorldAssemblerM3/Linux75/REPORT.md new file mode 100644 index 0000000..e990034 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux75/REPORT.md @@ -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. diff --git a/Standalone/Research/WorldAssemblerM3/Linux75/RunAuditSweepL75.py b/Standalone/Research/WorldAssemblerM3/Linux75/RunAuditSweepL75.py new file mode 100644 index 0000000..53d7227 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux75/RunAuditSweepL75.py @@ -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()) diff --git a/Standalone/Research/WorldAssemblerM3/Linux75/RunHashSweepL75.py b/Standalone/Research/WorldAssemblerM3/Linux75/RunHashSweepL75.py new file mode 100644 index 0000000..8628aaf --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux75/RunHashSweepL75.py @@ -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()) diff --git a/Standalone/Research/WorldAssemblerM3/Linux75/RunTimingPairsL75.py b/Standalone/Research/WorldAssemblerM3/Linux75/RunTimingPairsL75.py new file mode 100644 index 0000000..4bdc835 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux75/RunTimingPairsL75.py @@ -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()) diff --git a/Standalone/Research/WorldAssemblerM3/Linux75/RunTnBlockRegressionL75.py b/Standalone/Research/WorldAssemblerM3/Linux75/RunTnBlockRegressionL75.py new file mode 100644 index 0000000..49eaac1 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux75/RunTnBlockRegressionL75.py @@ -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()) diff --git a/Standalone/Research/WorldAssemblerM3/Linux75/RunVetoAuditL75.py b/Standalone/Research/WorldAssemblerM3/Linux75/RunVetoAuditL75.py new file mode 100644 index 0000000..5693cf0 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux75/RunVetoAuditL75.py @@ -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()) diff --git a/Standalone/Research/WorldAssemblerM3/Linux75/__pycache__/RunTimingPairsL75.cpython-313.pyc b/Standalone/Research/WorldAssemblerM3/Linux75/__pycache__/RunTimingPairsL75.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ca984dd38a530433e1f233049554c4e86a2a53d6 GIT binary patch literal 7070 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 BH}U`g literal 0 HcmV?d00001 diff --git a/Standalone/Research/WorldAssemblerM3/Linux75/__pycache__/RunVetoAuditL75.cpython-313.pyc b/Standalone/Research/WorldAssemblerM3/Linux75/__pycache__/RunVetoAuditL75.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..646d797be969e6eba1085d1827678b0975020365 GIT binary patch literal 8071 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 literal 0 HcmV?d00001 diff --git a/Standalone/Source/StreamingWorld.h b/Standalone/Source/StreamingWorld.h index 876f894..37c5024 100644 --- a/Standalone/Source/StreamingWorld.h +++ b/Standalone/Source/StreamingWorld.h @@ -891,6 +891,9 @@ private: Options.bandMinZVoxel=Job.BandZMin;Options.bandMaxZVoxel=Job.BandZMax; Options.hashDensity=bHashDensity; 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.skipMaterialGroups=bSkipMaterialGroups;Options.skipUv=bSkipUV;Options.skipColors=bSkipColors; Options.captureColumnCoordinates=bCaptureColumnCoordinates; diff --git a/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp b/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp index ba8caf5..6f0f9a2 100644 --- a/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp +++ b/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp @@ -17,6 +17,7 @@ #include "Misc/CommandLine.h" #include #include +#include #include #include @@ -28,6 +29,15 @@ namespace GVoxelForgeUseOperatorBlock, TEXT("Use the operator-major density block path for non-shared mesher grids.")); 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; bool GVoxelForgeRawSkirtsSwitchParsed = false; @@ -644,10 +654,44 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels, bBlockUnknown=ShapeVerdict.bUnknown; bBlockNative=ShapeVerdict.bNativeSurface; 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(ProbeX),static_cast(ProbeY), + static_cast(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 ||Verdict==EVoxelTileClass::AllAir) &&!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; bAllRelevantBlocksProofSafe=false; } diff --git a/Standalone/Tools/verify/baselines/owner-hash/canonical/owner-hash-floating-islands-style2-l75-fullgrid.baseline b/Standalone/Tools/verify/baselines/owner-hash/canonical/owner-hash-floating-islands-style2-l75-fullgrid.baseline new file mode 100644 index 0000000..b69a4b0 --- /dev/null +++ b/Standalone/Tools/verify/baselines/owner-hash/canonical/owner-hash-floating-islands-style2-l75-fullgrid.baseline @@ -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 diff --git a/Standalone/Tools/verify/verify.py b/Standalone/Tools/verify/verify.py index 1285159..cf51d06 100644 --- a/Standalone/Tools/verify/verify.py +++ b/Standalone/Tools/verify/verify.py @@ -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", "L67-v212") if recipe == "floating-islands" and style == 2: - # L74 disables a legacy sub-tile shortcut after its falsifier found false solids. - # Keep the old fixture intact and pin the restored full-grid density signature. - return (BASELINES / "owner-hash/canonical/owner-hash-floating-islands-style2-l74-fullgrid.baseline", - "L74-full-grid") + # L75 explicitly materializes every reference density sample. The older L74 + # signature omitted samples skipped inside native uniform blocks; retain that + # fixture and pin the current full-grid signature separately. + 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}")