passages: exact relief skip + per-tile BVH node masks (L82); FI/TN LOD0-2 -12%
- Wall relief noise is skipped when its hard-bounded contribution cannot change the final max; mouth surface/tread noise only on paths that use it. - Exact active BVH-node masks cached per tile/passage/segment range; no per-sample lower_bound. - Full-view audit imports the exported Core plan (not the stale 26-point root route): 21,425/21,425 walkability samples pass. Cold (L80 protocol, medians) LOD0-2: SW 1.16->1.11, FI 2.86->2.51, TN 2.88->2.54, VS 1.23->1.22 s. Passage share FI 64.6->58.7%, TN 65.4->58.4%. Hashes identical (v214). TN "slowdown" from L81 not reproduced (5 pairs, 3.08 -> 2.75 s). Fast tier PASS; Linux UE host builds. 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
9cb5e53b17
commit
f572108dc4
10 files changed
+665
-41
No files matched your search
@@ -179,6 +179,11 @@ public:
|
|||||||
std::size_t segmentIndex=0;
|
std::size_t segmentIndex=0;
|
||||||
std::array<std::int64_t,3> minimumSubvoxels{},maximumSubvoxels{};
|
std::array<std::int64_t,3> minimumSubvoxels{},maximumSubvoxels{};
|
||||||
};
|
};
|
||||||
|
struct FSegmentRangeCache
|
||||||
|
{
|
||||||
|
std::ptrdiff_t first=-1,end=-1;
|
||||||
|
std::vector<std::uint8_t> activeNodes;
|
||||||
|
};
|
||||||
|
|
||||||
FScopedPassageTileQueries(const FStandaloneShapeWorld& shape,
|
FScopedPassageTileQueries(const FStandaloneShapeWorld& shape,
|
||||||
const std::array<std::int64_t,3>& minimumSubvoxels,
|
const std::array<std::int64_t,3>& minimumSubvoxels,
|
||||||
@@ -201,6 +206,7 @@ public:
|
|||||||
passageColumns_=passageRoutes_;
|
passageColumns_=passageRoutes_;
|
||||||
passageMouthSupports_=passageRoutes_;
|
passageMouthSupports_=passageRoutes_;
|
||||||
passageMouthSupportSegments_.resize(passageRoutes_.size());
|
passageMouthSupportSegments_.resize(passageRoutes_.size());
|
||||||
|
passageSegmentRangeCaches_.resize(passageRoutes_.size());
|
||||||
if(shape.ResolvedLayout&&!shape.ResolvedLayout->passages.empty())
|
if(shape.ResolvedLayout&&!shape.ResolvedLayout->passages.empty())
|
||||||
{
|
{
|
||||||
constexpr double Q=static_cast<double>(
|
constexpr double Q=static_cast<double>(
|
||||||
@@ -281,6 +287,22 @@ public:
|
|||||||
return passageIndex<passageMouthSupportSegments_.size()
|
return passageIndex<passageMouthSupportSegments_.size()
|
||||||
?passageMouthSupportSegments_[passageIndex]:Empty;
|
?passageMouthSupportSegments_[passageIndex]:Empty;
|
||||||
}
|
}
|
||||||
|
const std::vector<std::uint8_t>* segmentRangeNodeMask(std::size_t passageIndex,
|
||||||
|
std::ptrdiff_t first,std::ptrdiff_t end,int priority) const
|
||||||
|
{
|
||||||
|
if(passageIndex>=passageSegmentRangeCaches_.size()
|
||||||
|
||priority<0||priority>=4)return nullptr;
|
||||||
|
FSegmentRangeCache& cache=passageSegmentRangeCaches_[passageIndex][
|
||||||
|
static_cast<std::size_t>(priority)];
|
||||||
|
if(cache.first!=first||cache.end!=end)
|
||||||
|
{
|
||||||
|
cache.first=first;
|
||||||
|
cache.end=end;
|
||||||
|
cache.activeNodes=shape_->BuildPassageSegmentRangeNodeMask(
|
||||||
|
passageIndex,first,end);
|
||||||
|
}
|
||||||
|
return &cache.activeNodes;
|
||||||
|
}
|
||||||
bool hasMouthSupport(std::size_t passageIndex) const
|
bool hasMouthSupport(std::size_t passageIndex) const
|
||||||
{return passageIndex<passageMouthSupports_.size()&&passageMouthSupports_[passageIndex]!=0;}
|
{return passageIndex<passageMouthSupports_.size()&&passageMouthSupports_[passageIndex]!=0;}
|
||||||
bool hasAnyMouthSupport() const
|
bool hasAnyMouthSupport() const
|
||||||
@@ -317,6 +339,7 @@ public:
|
|||||||
std::vector<std::uint32_t> cavernFeatureIndices_;
|
std::vector<std::uint32_t> cavernFeatureIndices_;
|
||||||
std::vector<std::uint8_t> passageRoutes_,passageColumns_,passageMouthSupports_;
|
std::vector<std::uint8_t> passageRoutes_,passageColumns_,passageMouthSupports_;
|
||||||
std::vector<std::vector<FCandidateSegment>> passageMouthSupportSegments_;
|
std::vector<std::vector<FCandidateSegment>> passageMouthSupportSegments_;
|
||||||
|
mutable std::vector<std::array<FSegmentRangeCache,4>> passageSegmentRangeCaches_;
|
||||||
std::vector<std::size_t> upperMouthSupportPassages_;
|
std::vector<std::size_t> upperMouthSupportPassages_;
|
||||||
std::size_t routeSegmentCandidateCount_=0;
|
std::size_t routeSegmentCandidateCount_=0;
|
||||||
bool routeFeatureOverlap_=false,columnZOverlap_=false;
|
bool routeFeatureOverlap_=false,columnZOverlap_=false;
|
||||||
@@ -1513,17 +1536,46 @@ public:
|
|||||||
?std::clamp(-(qz-(nearestA.positionSubvoxels[2]+bestT*(
|
?std::clamp(-(qz-(nearestA.positionSubvoxels[2]+bestT*(
|
||||||
static_cast<double>(nearestB.positionSubvoxels[2])-nearestA.positionSubvoxels[2])))
|
static_cast<double>(nearestB.positionSubvoxels[2])-nearestA.positionSubvoxels[2])))
|
||||||
/distanceToCenter,0.0,1.0):0.0;
|
/distanceToCenter,0.0,1.0):0.0;
|
||||||
const float relief=VF_FieldPassageWallRelief(static_cast<float>(X),
|
const std::uint32_t passageReliefSeed=static_cast<std::uint32_t>(Owner?Owner->Seed:0);
|
||||||
static_cast<float>(Y),static_cast<float>(Z),
|
float cachedWallRelief=0.0f,cachedMouthSurfaceRelief=0.0f,
|
||||||
static_cast<std::uint32_t>(Owner?Owner->Seed:0));
|
cachedMouthTreadFieldRelief=0.0f;
|
||||||
const float mouthSurfaceRelief=VF_FieldSurfaceRelief(static_cast<float>(X)*1.5f,
|
bool hasCachedWallRelief=false,hasCachedMouthSurfaceRelief=false,
|
||||||
static_cast<float>(Y)*1.5f,static_cast<float>(Z)*1.5f,
|
hasCachedMouthTreadFieldRelief=false;
|
||||||
static_cast<std::uint32_t>(Owner?Owner->Seed:0)^0x4D4F5554u);
|
const auto getWallRelief=[&]()
|
||||||
const float mouthTerrainRelief=mouthSurfaceRelief;
|
{
|
||||||
const float mouthTreadFieldRelief=VF_FieldSurfaceRelief(static_cast<float>(X)*0.75f,
|
if(!hasCachedWallRelief)
|
||||||
static_cast<float>(Y)*0.75f,static_cast<float>(Z)*0.75f,
|
{
|
||||||
static_cast<std::uint32_t>(Owner?Owner->Seed:0)^0x54485244u);
|
cachedWallRelief=VF_FieldPassageWallRelief(static_cast<float>(X),
|
||||||
if(passageProfile)passageProfile->passageReliefFieldCalls+=3;
|
static_cast<float>(Y),static_cast<float>(Z),passageReliefSeed);
|
||||||
|
hasCachedWallRelief=true;
|
||||||
|
if(passageProfile)++passageProfile->passageReliefFieldCalls;
|
||||||
|
}
|
||||||
|
return cachedWallRelief;
|
||||||
|
};
|
||||||
|
const auto getMouthSurfaceRelief=[&]()
|
||||||
|
{
|
||||||
|
if(!hasCachedMouthSurfaceRelief)
|
||||||
|
{
|
||||||
|
cachedMouthSurfaceRelief=VF_FieldSurfaceRelief(
|
||||||
|
static_cast<float>(X)*1.5f,static_cast<float>(Y)*1.5f,
|
||||||
|
static_cast<float>(Z)*1.5f,passageReliefSeed^0x4D4F5554u);
|
||||||
|
hasCachedMouthSurfaceRelief=true;
|
||||||
|
if(passageProfile)++passageProfile->passageReliefFieldCalls;
|
||||||
|
}
|
||||||
|
return cachedMouthSurfaceRelief;
|
||||||
|
};
|
||||||
|
const auto getMouthTreadFieldRelief=[&]()
|
||||||
|
{
|
||||||
|
if(!hasCachedMouthTreadFieldRelief)
|
||||||
|
{
|
||||||
|
cachedMouthTreadFieldRelief=VF_FieldSurfaceRelief(
|
||||||
|
static_cast<float>(X)*0.75f,static_cast<float>(Y)*0.75f,
|
||||||
|
static_cast<float>(Z)*0.75f,passageReliefSeed^0x54485244u);
|
||||||
|
hasCachedMouthTreadFieldRelief=true;
|
||||||
|
if(passageProfile)++passageProfile->passageReliefFieldCalls;
|
||||||
|
}
|
||||||
|
return cachedMouthTreadFieldRelief;
|
||||||
|
};
|
||||||
const auto routeSegmentLengthMetres=[&](std::ptrdiff_t routeIndex)
|
const auto routeSegmentLengthMetres=[&](std::ptrdiff_t routeIndex)
|
||||||
{
|
{
|
||||||
const auto& before=passage.controlPoints[static_cast<std::size_t>(routeIndex)];
|
const auto& before=passage.controlPoints[static_cast<std::size_t>(routeIndex)];
|
||||||
@@ -1639,21 +1691,33 @@ public:
|
|||||||
if(passageProfile)++passageProfile->passageSmoothstepEvaluations;
|
if(passageProfile)++passageProfile->passageSmoothstepEvaluations;
|
||||||
const double floorReliefWeight=floorReliefT*floorReliefT
|
const double floorReliefWeight=floorReliefT*floorReliefT
|
||||||
*(3.0-2.0*floorReliefT);
|
*(3.0-2.0*floorReliefT);
|
||||||
|
const double wallOpening=1.0-verticalWallAlignment;
|
||||||
|
const double wallReliefWeight=0.05+0.95*wallOpening;
|
||||||
|
const bool shortSpanCarve=IsPassageShortSpanSegment(passageIndex,
|
||||||
|
static_cast<std::size_t>(bestIndex));
|
||||||
|
float carveInputAfterBridge=composed;
|
||||||
|
if(shortSpanCarve)
|
||||||
|
carveInputAfterBridge=std::min(composed,static_cast<float>(
|
||||||
|
PassageBridgeSignedDistance(passageIndex,
|
||||||
|
static_cast<std::size_t>(bestIndex),X,Y,Z)/Q));
|
||||||
|
const double maximumWallReliefOffset=0.21*qPerMetre*wallReliefWeight;
|
||||||
|
const double maximumMouthFloorReliefOffset=upperMouthFloorFade
|
||||||
|
?0.20*qPerMetre*mouthFloorBlend*floorReliefWeight:0.0;
|
||||||
|
const double maximumCarvedDensity=-(bestSignedDistance
|
||||||
|
-maximumWallReliefOffset-maximumMouthFloorReliefOffset)/Q;
|
||||||
|
const bool wallReliefCanChangeCarve=!std::isfinite(maximumCarvedDensity)
|
||||||
|
||maximumCarvedDensity>static_cast<double>(carveInputAfterBridge);
|
||||||
|
const float relief=wallReliefCanChangeCarve?getWallRelief():0.0f;
|
||||||
|
const float mouthTerrainRelief=upperMouthFloorFade
|
||||||
|
?getMouthSurfaceRelief():0.0f;
|
||||||
const double floorReliefOffset=std::clamp(
|
const double floorReliefOffset=std::clamp(
|
||||||
static_cast<double>(mouthTerrainRelief),-1.0,1.0)
|
static_cast<double>(mouthTerrainRelief),-1.0,1.0)
|
||||||
*0.20*qPerMetre*mouthFloorBlend*floorReliefWeight
|
*0.20*qPerMetre*mouthFloorBlend*floorReliefWeight
|
||||||
*(upperMouthFloorFade?1.0:0.0);
|
*(upperMouthFloorFade?1.0:0.0);
|
||||||
const double wallOpening=1.0-verticalWallAlignment;
|
|
||||||
const double wallReliefWeight=0.05+0.95*wallOpening;
|
|
||||||
const double roughSignedDistance=bestSignedDistance
|
const double roughSignedDistance=bestSignedDistance
|
||||||
+static_cast<double>(std::clamp(relief,-1.0f,1.0f))*0.21*qPerMetre*wallReliefWeight
|
+static_cast<double>(std::clamp(relief,-1.0f,1.0f))*0.21*qPerMetre*wallReliefWeight
|
||||||
+floorReliefOffset;
|
+floorReliefOffset;
|
||||||
if(IsPassageShortSpanSegment(passageIndex,static_cast<std::size_t>(bestIndex)))
|
if(shortSpanCarve)composed=carveInputAfterBridge;
|
||||||
{
|
|
||||||
const double bridgeDistance=PassageBridgeSignedDistance(passageIndex,
|
|
||||||
static_cast<std::size_t>(bestIndex),X,Y,Z);
|
|
||||||
composed=std::min(composed,static_cast<float>(bridgeDistance/Q));
|
|
||||||
}
|
|
||||||
// Leave native crater ground at the lip. The bore opens through the walls
|
// Leave native crater ground at the lip. The bore opens through the walls
|
||||||
// immediately, while its floor cut eases in along the passage centerline.
|
// immediately, while its floor cut eases in along the passage centerline.
|
||||||
double mouthCarveFade=1.0;
|
double mouthCarveFade=1.0;
|
||||||
@@ -1894,7 +1958,7 @@ public:
|
|||||||
double supportHalfWidth=std::min(radius,
|
double supportHalfWidth=std::min(radius,
|
||||||
supportHalfWidthMetres*qPerMetre);
|
supportHalfWidthMetres*qPerMetre);
|
||||||
const double shoulderWarp=mouthTread
|
const double shoulderWarp=mouthTread
|
||||||
?std::clamp(static_cast<double>(mouthSurfaceRelief),-1.0,1.0)
|
?std::clamp(static_cast<double>(getMouthSurfaceRelief()),-1.0,1.0)
|
||||||
*0.12*qPerMetre:0.0;
|
*0.12*qPerMetre:0.0;
|
||||||
supportHalfWidth=mouthTread
|
supportHalfWidth=mouthTread
|
||||||
?std::clamp(supportHalfWidth+shoulderWarp,
|
?std::clamp(supportHalfWidth+shoulderWarp,
|
||||||
@@ -1950,7 +2014,7 @@ public:
|
|||||||
-0.80*qPerMetre,0.80*qPerMetre);
|
-0.80*qPerMetre,0.80*qPerMetre);
|
||||||
const double floorQ=baseFloorQ
|
const double floorQ=baseFloorQ
|
||||||
+(mouthTread?std::clamp(nativeTreadOffset
|
+(mouthTread?std::clamp(nativeTreadOffset
|
||||||
+std::clamp(static_cast<double>(mouthTreadFieldRelief),-1.0,1.0)
|
+std::clamp(static_cast<double>(getMouthTreadFieldRelief()),-1.0,1.0)
|
||||||
*0.40*qPerMetre,-0.80*qPerMetre,0.0):0.0);
|
*0.40*qPerMetre,-0.80*qPerMetre,0.0):0.0);
|
||||||
const double floorVoxels=floorQ/Q;
|
const double floorVoxels=floorQ/Q;
|
||||||
const bool signedDistanceSupport=sparseRouteSupport||mouthTread;
|
const bool signedDistanceSupport=sparseRouteSupport||mouthTread;
|
||||||
@@ -2073,7 +2137,7 @@ public:
|
|||||||
if(nativeRestoreCapsuleClear)
|
if(nativeRestoreCapsuleClear)
|
||||||
{
|
{
|
||||||
const double nativeSurfaceDetail=std::clamp(
|
const double nativeSurfaceDetail=std::clamp(
|
||||||
static_cast<double>(mouthTerrainRelief),-1.0,1.0)
|
static_cast<double>(getMouthSurfaceRelief()),-1.0,1.0)
|
||||||
*0.18*qPerMetre/Q;
|
*0.18*qPerMetre/Q;
|
||||||
const float nativeGroundWithRelief=static_cast<float>(
|
const float nativeGroundWithRelief=static_cast<float>(
|
||||||
(supportNeedsDeferral?std::max(-1.0f,density):density)
|
(supportNeedsDeferral?std::max(-1.0f,density):density)
|
||||||
@@ -4836,6 +4900,42 @@ private:
|
|||||||
return nodeIndex;
|
return nodeIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::uint8_t> BuildPassageSegmentRangeNodeMask(
|
||||||
|
std::size_t passageIndex,std::ptrdiff_t first,std::ptrdiff_t end) const
|
||||||
|
{
|
||||||
|
std::vector<std::uint8_t> active;
|
||||||
|
if(first>=end||passageIndex>=PassageNearestNodes.size()
|
||||||
|
||PassageNearestNodes[passageIndex].empty())return active;
|
||||||
|
const auto& nodes=PassageNearestNodes[passageIndex];
|
||||||
|
active.assign(nodes.size(),0);
|
||||||
|
const auto visit=[&](auto&& self,int nodeIndex)->bool
|
||||||
|
{
|
||||||
|
if(nodeIndex<0)return false;
|
||||||
|
const std::size_t index=static_cast<std::size_t>(nodeIndex);
|
||||||
|
const auto& node=nodes[index];
|
||||||
|
if(static_cast<std::ptrdiff_t>(node.maximumSegment)<first
|
||||||
|
||static_cast<std::ptrdiff_t>(node.minimumSegment)>=end)return false;
|
||||||
|
bool hasCandidate=false;
|
||||||
|
if(node.left<0)
|
||||||
|
{
|
||||||
|
for(const std::size_t segment:node.segments)
|
||||||
|
hasCandidate=hasCandidate
|
||||||
|
||(static_cast<std::ptrdiff_t>(segment)>=first
|
||||||
|
&&static_cast<std::ptrdiff_t>(segment)<end);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const bool leftHasCandidate=self(self,node.left);
|
||||||
|
const bool rightHasCandidate=self(self,node.right);
|
||||||
|
hasCandidate=leftHasCandidate||rightHasCandidate;
|
||||||
|
}
|
||||||
|
active[index]=static_cast<std::uint8_t>(hasCandidate);
|
||||||
|
return hasCandidate;
|
||||||
|
};
|
||||||
|
visit(visit,0);
|
||||||
|
return active;
|
||||||
|
}
|
||||||
|
|
||||||
int BuildPassageProofNode(std::size_t first,std::size_t end)
|
int BuildPassageProofNode(std::size_t first,std::size_t end)
|
||||||
{
|
{
|
||||||
FPassageProofNode node;node.first=first;node.end=end;
|
FPassageProofNode node;node.first=first;node.end=end;
|
||||||
@@ -5410,35 +5510,53 @@ private:
|
|||||||
template<typename FVisit>
|
template<typename FVisit>
|
||||||
void VisitNearestPassageSpatialNode(std::size_t passageIndex,int nodeIndex,
|
void VisitNearestPassageSpatialNode(std::size_t passageIndex,int nodeIndex,
|
||||||
std::size_t first,std::size_t end,double qx,double qy,double qz,int priority,
|
std::size_t first,std::size_t end,double qx,double qy,double qz,int priority,
|
||||||
double& bestDistance,FVisit& visit) const
|
const std::vector<std::uint8_t>* activeNodes,double& bestDistance,
|
||||||
|
FVisit& visit) const
|
||||||
{
|
{
|
||||||
if(passageIndex>=PassageNearestNodes.size()||nodeIndex<0)return;
|
if(passageIndex>=PassageNearestNodes.size()||nodeIndex<0)return;
|
||||||
const auto& nodes=PassageNearestNodes[passageIndex];
|
const auto& nodes=PassageNearestNodes[passageIndex];
|
||||||
const auto& node=nodes[static_cast<std::size_t>(nodeIndex)];
|
const std::size_t nodeOffset=static_cast<std::size_t>(nodeIndex);
|
||||||
if(node.maximumSegment<first||node.minimumSegment>=end)return;
|
if(activeNodes&&(nodeOffset>=activeNodes->size()||!(*activeNodes)[nodeOffset]))return;
|
||||||
const bool allSegmentsInRange=first<=node.minimumSegment
|
const auto& node=nodes[nodeOffset];
|
||||||
&&node.maximumSegment<end;
|
if(!activeNodes&&(node.maximumSegment<first||node.minimumSegment>=end))return;
|
||||||
const auto candidate=allSegmentsInRange?node.segments.begin()
|
auto candidate=node.segments.begin();
|
||||||
:std::lower_bound(node.segments.begin(),node.segments.end(),first);
|
if(!activeNodes)
|
||||||
if(candidate==node.segments.end()||*candidate>=end)return;
|
{
|
||||||
|
const bool allSegmentsInRange=first<=node.minimumSegment
|
||||||
|
&&node.maximumSegment<end;
|
||||||
|
if(!allSegmentsInRange)
|
||||||
|
candidate=std::lower_bound(node.segments.begin(),node.segments.end(),first);
|
||||||
|
if(candidate==node.segments.end()||*candidate>=end)return;
|
||||||
|
}
|
||||||
const double lowerBound=PassageNearestNodeLowerBound(node,qx,qy,qz);
|
const double lowerBound=PassageNearestNodeLowerBound(node,qx,qy,qz);
|
||||||
if(lowerBound>bestDistance)return;
|
if(lowerBound>bestDistance)return;
|
||||||
if(node.left<0)
|
if(node.left<0)
|
||||||
{
|
{
|
||||||
for(auto it=candidate;it!=node.segments.end()&&*it<end;++it)
|
for(auto it=candidate;it!=node.segments.end();++it)
|
||||||
|
{
|
||||||
|
if(*it<first||*it>=end)continue;
|
||||||
visit(*it,priority);
|
visit(*it,priority);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const double leftBound=PassageNearestNodeLowerBound(
|
const bool leftActive=!activeNodes
|
||||||
nodes[static_cast<std::size_t>(node.left)],qx,qy,qz);
|
||(static_cast<std::size_t>(node.left)<activeNodes->size()
|
||||||
const double rightBound=PassageNearestNodeLowerBound(
|
&&(*activeNodes)[static_cast<std::size_t>(node.left)]);
|
||||||
nodes[static_cast<std::size_t>(node.right)],qx,qy,qz);
|
const bool rightActive=!activeNodes
|
||||||
|
||(static_cast<std::size_t>(node.right)<activeNodes->size()
|
||||||
|
&&(*activeNodes)[static_cast<std::size_t>(node.right)]);
|
||||||
|
const double leftBound=leftActive?PassageNearestNodeLowerBound(
|
||||||
|
nodes[static_cast<std::size_t>(node.left)],qx,qy,qz)
|
||||||
|
:std::numeric_limits<double>::infinity();
|
||||||
|
const double rightBound=rightActive?PassageNearestNodeLowerBound(
|
||||||
|
nodes[static_cast<std::size_t>(node.right)],qx,qy,qz)
|
||||||
|
:std::numeric_limits<double>::infinity();
|
||||||
const int nearChild=leftBound<=rightBound?node.left:node.right;
|
const int nearChild=leftBound<=rightBound?node.left:node.right;
|
||||||
const int farChild=leftBound<=rightBound?node.right:node.left;
|
const int farChild=leftBound<=rightBound?node.right:node.left;
|
||||||
VisitNearestPassageSpatialNode(passageIndex,nearChild,first,end,qx,qy,qz,
|
VisitNearestPassageSpatialNode(passageIndex,nearChild,first,end,qx,qy,qz,
|
||||||
priority,bestDistance,visit);
|
priority,activeNodes,bestDistance,visit);
|
||||||
VisitNearestPassageSpatialNode(passageIndex,farChild,first,end,qx,qy,qz,
|
VisitNearestPassageSpatialNode(passageIndex,farChild,first,end,qx,qy,qz,
|
||||||
priority,bestDistance,visit);
|
priority,activeNodes,bestDistance,visit);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename FVisit>
|
template<typename FVisit>
|
||||||
@@ -5449,8 +5567,12 @@ private:
|
|||||||
if(first>last||passageIndex>=PassageNearestNodes.size()
|
if(first>last||passageIndex>=PassageNearestNodes.size()
|
||||||
||PassageNearestNodes[passageIndex].empty())return;
|
||PassageNearestNodes[passageIndex].empty())return;
|
||||||
auto callback=std::forward<FVisit>(visit);
|
auto callback=std::forward<FVisit>(visit);
|
||||||
|
const auto* tileQueries=ActivePassageTileQueries;
|
||||||
|
const auto* activeNodes=tileQueries&&tileQueries->matches(*this)
|
||||||
|
?tileQueries->segmentRangeNodeMask(passageIndex,first,last+1,priority):nullptr;
|
||||||
VisitNearestPassageSpatialNode(passageIndex,0,static_cast<std::size_t>(first),
|
VisitNearestPassageSpatialNode(passageIndex,0,static_cast<std::size_t>(first),
|
||||||
static_cast<std::size_t>(last+1),qx,qy,qz,priority,bestDistance,callback);
|
static_cast<std::size_t>(last+1),qx,qy,qz,priority,activeNodes,
|
||||||
|
bestDistance,callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PassagePotentialWithinNode(std::size_t passageIndex,int nodeIndex,
|
bool PassagePotentialWithinNode(std::size_t passageIndex,int nodeIndex,
|
||||||
@@ -12312,6 +12434,11 @@ private:
|
|||||||
static_cast<unsigned long long>(Stats.WalkSamples));
|
static_cast<unsigned long long>(Stats.WalkSamples));
|
||||||
return Stats.WalkFailures==0;
|
return Stats.WalkFailures==0;
|
||||||
}
|
}
|
||||||
|
// An imported Unreal-resolved layout is the canonical route source. An empty
|
||||||
|
// passage list is meaningful for blocked controls, so never fall back to the
|
||||||
|
// regenerated legacy owner route in that case. Recipe overrides without an
|
||||||
|
// exported Core plan retain their generated owner route audit.
|
||||||
|
if(Owner&&Owner->bHasUnrealResolvedCorePlan&&ResolvedLayout)return true;
|
||||||
constexpr double SampleStep=0.5;
|
constexpr double SampleStep=0.5;
|
||||||
uint32 PassageIndex=0;
|
uint32 PassageIndex=0;
|
||||||
for(const FVoxelPassage& P:Owner->Manager->GetPassages())
|
for(const FVoxelPassage& P:Owner->Manager->GetPassages())
|
||||||
|
|||||||
@@ -11,5 +11,6 @@ namespace VoxelForgeCore
|
|||||||
// the same command-line overrides as the viewer's reference loader.
|
// the same command-line overrides as the viewer's reference loader.
|
||||||
ResolvedWorldSnapshot MakeStandaloneSnapshotFromParsedJson(const VFJson::Value& jsonRoot,
|
ResolvedWorldSnapshot MakeStandaloneSnapshotFromParsedJson(const VFJson::Value& jsonRoot,
|
||||||
std::int32_t seed,std::int32_t clipRadius,std::int32_t maxLodOverride,
|
std::int32_t seed,std::int32_t clipRadius,std::int32_t maxLodOverride,
|
||||||
bool roundTunnels,bool enhancedTunnelWalls,std::string& error);
|
bool roundTunnels,bool enhancedTunnelWalls,std::string& error,
|
||||||
|
bool hasUnrealResolvedCorePlan=false);
|
||||||
}
|
}
|
||||||
@@ -926,10 +926,12 @@ namespace VoxelForgeCore
|
|||||||
|
|
||||||
ResolvedWorldSnapshot MakeStandaloneSnapshotFromParsedJson(const VFJson::Value& jsonRoot,
|
ResolvedWorldSnapshot MakeStandaloneSnapshotFromParsedJson(const VFJson::Value& jsonRoot,
|
||||||
std::int32_t seed,std::int32_t clipRadius,std::int32_t maxLodOverride,
|
std::int32_t seed,std::int32_t clipRadius,std::int32_t maxLodOverride,
|
||||||
bool roundTunnels,bool enhancedTunnelWalls,std::string& error)
|
bool roundTunnels,bool enhancedTunnelWalls,std::string& error,
|
||||||
|
bool hasUnrealResolvedCorePlan)
|
||||||
{
|
{
|
||||||
auto owner=std::make_shared<FStandaloneOwnerWorld>();
|
auto owner=std::make_shared<FStandaloneOwnerWorld>();
|
||||||
if(!StandaloneOwnerLoader::Resolve(jsonRoot,*owner,error))return {};
|
if(!StandaloneOwnerLoader::Resolve(jsonRoot,*owner,error))return {};
|
||||||
|
owner->bHasUnrealResolvedCorePlan=hasUnrealResolvedCorePlan;
|
||||||
std::shared_ptr<const WorldAssemblyManifest> worldAssembly;
|
std::shared_ptr<const WorldAssemblyManifest> worldAssembly;
|
||||||
if(jsonRoot.Get("world_assembly").IsObject())
|
if(jsonRoot.Get("world_assembly").IsObject())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# Linux82 — samples near passages
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
- Kept the 3D relief definition. The wall relief noise is skipped only when its hard-bounded maximum contribution (including mouth-floor relief and the bridge input) cannot change the final `max`. Mouth surface and tread noise are evaluated only on paths that use them. This is exact; no 2D relief approximation or world change was made.
|
||||||
|
- Cached exact active BVH-node masks per tile, passage, segment range, and priority. Repeated samples reuse the mask and skip the per-sample `lower_bound`; candidate segment ordering and distance tests are unchanged.
|
||||||
|
- The full-view audit now imports the exported Core plan and resolved layout without a recipe override. Snapshot creation preserves that export provenance so generated recipe overrides keep their own route audit.
|
||||||
|
|
||||||
|
## Passage census
|
||||||
|
|
||||||
|
Mean near-LOD passage share of weighted sampler ticks; L81 has two exact-mode runs and L82 has three.
|
||||||
|
|
||||||
|
| World | L81 exact | L82 exact | Change |
|
||||||
|
|---|---:|---:|---:|
|
||||||
|
| FI | 64.56% | 58.67% | −5.89 pp |
|
||||||
|
| TN | 65.35% | 58.40% | −6.95 pp |
|
||||||
|
|
||||||
|
## Cold readiness
|
||||||
|
|
||||||
|
Three interleaved measured pairs per stratum; medians in seconds.
|
||||||
|
|
||||||
|
| Stratum | LOD0–2 L81 → L82 | Whole view L81 → L82 |
|
||||||
|
|---|---:|---:|
|
||||||
|
| SW | 1.158 → 1.110 | 2.525 → 2.414 |
|
||||||
|
| FI | 2.864 → 2.505 | 4.240 → 3.797 |
|
||||||
|
| TN | 2.879 → 2.535 | 4.042 → 3.632 |
|
||||||
|
| VS | 1.232 → 1.224 | 2.261 → 2.227 |
|
||||||
|
|
||||||
|
All 960 LOD0–1 tile hashes per stratum matched L81 for density, mesh, payload, and topology; whole signatures matched with zero mismatches. The exact path kept evaluator version 214 and made no world-data change.
|
||||||
|
|
||||||
|
## TN comparison
|
||||||
|
|
||||||
|
Five interleaved TN-only LOD0–2 pairs (`max-level=2`, default scheduling on both binaries): `90378a5` median 3.078 s; `9cb5e53` median 2.747 s. All five paired deltas favored `9cb5e53` (median −0.281 s). The 3.18→3.51 s slowdown was not reproduced in the requested comparison; this A/B does not isolate an L80-to-L81 pair.
|
||||||
|
|
||||||
|
## Audit and verification
|
||||||
|
|
||||||
|
- Canonical full-view walkability: 21,425/21,425 samples passed; all three resolved passage audits passed. The stale 26-point root route was not used.
|
||||||
|
- Fast tier: PASS, 298.1 s.
|
||||||
|
- Linux `VFHostEditor` build after timing: PASS, 27/27 actions in 70.98 s with `-NoUBA -MaxParallelActions=4`; log rotation was redirected to `/tmp` after the default config path proved read-only. Three existing RealtimeMesh deprecation warnings.
|
||||||
|
- Final source stamp: `99fcbd2fd46e0bb3e250f3ad67e98ade0545e419193f987550356015714c6f1b`.
|
||||||
|
|
||||||
|
## Failed attempts
|
||||||
|
|
||||||
|
- The first TN harness expected the newer near-LOD marker, which `90378a5` does not emit. It was changed to use whole-view readiness with only LOD0–2 enabled; all five pairs then completed.
|
||||||
|
- The first full-view audit used `--recipe=surface-world`, which rebuilt a recipe snapshot and hit the stale root route (65/5,310). Removing the override imported the canonical export and passed.
|
||||||
|
- The first fast-tier launch lacked its round-specific binary. A later diagnostic exposed lost export provenance in snapshot creation; that was threaded through, and the final fast tier passed.
|
||||||
|
- The first UE build could not rotate a log in the read-only user config directory. Retrying with `XDG_CONFIG_HOME` under `/tmp` succeeded.
|
||||||
|
|
||||||
|
Logs and summaries are under `Saved/Linux/l82/`. No commit, push, or stash was made.
|
||||||
|
All headless runs used the requested CPU affinity and 6 GiB address-space cap; UBT used the CPU affinity without the cap. No run reached 30 minutes.
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Repeat the L77 FI/TN phase census on the L82 passage implementation."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[4]
|
||||||
|
OUT = ROOT / "Saved/Linux/l82/census"
|
||||||
|
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||||
|
BINARY = ROOT / "Saved/Linux/l82/profile-bin/VoxelForgeCoreHeadless-clang19"
|
||||||
|
CAMERAS = {
|
||||||
|
"FI": (25.8369140625, 3.19921875, -737.0),
|
||||||
|
"TN": (19.166015625, -14.2001953125, -753.3740234375),
|
||||||
|
}
|
||||||
|
SAMPLE = re.compile(r"^\[L77Sample\] .*?lod=(\d+) .*?phase=(\S+) .*?weight=(\d+)$", re.M)
|
||||||
|
SAMPLER = re.compile(r"^\[L77Sampler\] action=stop .*?samples=(\d+).*?status=PASS$", re.M)
|
||||||
|
|
||||||
|
|
||||||
|
def run(world: str, rep: int) -> dict:
|
||||||
|
OUT.mkdir(parents=True, exist_ok=True)
|
||||||
|
log = OUT / f"l82-phase-{world.lower()}-r{rep}.log"
|
||||||
|
camera = ",".join(f"{value:.12f}" for value in CAMERAS[world]) + ",0,0"
|
||||||
|
command = [
|
||||||
|
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||||
|
"env", "VOXELFORGE_L81_PASSAGE_BOUND_PADDING_VOXELS=0", "timeout", "1740s",
|
||||||
|
str(BINARY), str(DATA), "--world-assembler-prototype", "--stream-benchmark",
|
||||||
|
"--stream-pipeline", "--workers=8", "--clip-radius=3", "--max-clip-level=8",
|
||||||
|
"--max-level=8", f"--camera={camera}", "--profile-lod-work", "--linux77-profile",
|
||||||
|
]
|
||||||
|
start = time.monotonic()
|
||||||
|
with log.open("w", encoding="utf-8") as stream:
|
||||||
|
result = subprocess.run(command, cwd=ROOT, stdout=stream,
|
||||||
|
stderr=subprocess.STDOUT, check=False, timeout=1740)
|
||||||
|
output = log.read_text(encoding="utf-8", errors="replace")
|
||||||
|
matches = [(int(lod), phase, int(weight)) for lod, phase, weight in SAMPLE.findall(output)]
|
||||||
|
sampler = SAMPLER.search(output)
|
||||||
|
if result.returncode or not matches or sampler is None:
|
||||||
|
raise RuntimeError(f"census failed: exit={result.returncode} {log}")
|
||||||
|
near = [row for row in matches if row[0] <= 2]
|
||||||
|
passage = sum(weight for _, phase, weight in near if phase == "passages")
|
||||||
|
total = sum(weight for _, _, weight in near)
|
||||||
|
row = {
|
||||||
|
"world": world, "replicate": rep, "exit_code": result.returncode,
|
||||||
|
"wall_s": round(time.monotonic() - start, 3),
|
||||||
|
"sampler_samples": int(sampler.group(1)), "near_lod_samples": len(near),
|
||||||
|
"near_lod_weighted_ticks": total, "near_lod_passage_weighted_ticks": passage,
|
||||||
|
"near_lod_passage_share_pct": 100.0 * passage / total if total else 0.0,
|
||||||
|
"log": str(log.relative_to(ROOT)),
|
||||||
|
}
|
||||||
|
print("[L82CensusRun] " + json.dumps(row, sort_keys=True), flush=True)
|
||||||
|
return row
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if not BINARY.is_file() or not DATA.is_file():
|
||||||
|
raise SystemExit(f"missing input: {BINARY} or {DATA}")
|
||||||
|
rows = [run(world, rep) for world in CAMERAS for rep in (1, 2, 3)]
|
||||||
|
by_world = {}
|
||||||
|
for world in CAMERAS:
|
||||||
|
values = [row["near_lod_passage_share_pct"] for row in rows if row["world"] == world]
|
||||||
|
by_world[world] = {"runs_pct": values, "mean_pct": sum(values) / len(values)}
|
||||||
|
(OUT / "summary.json").write_text(json.dumps(
|
||||||
|
{"runs": rows, "near_lod_passage_share": by_world}, indent=2) + "\n",
|
||||||
|
encoding="utf-8")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Compare L81 and L82 per-tile hashes over each four-strate LOD0-1 view."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[4]
|
||||||
|
OUT = ROOT / "Saved/Linux/l82/hash-check"
|
||||||
|
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||||
|
BINARIES = {
|
||||||
|
"L81": ROOT / "Saved/Linux/l81/bin/VoxelForgeCoreHeadless-l81-clang",
|
||||||
|
"L82": ROOT / "Saved/Linux/l82/bin/VoxelForgeCoreHeadless-clang19",
|
||||||
|
}
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
TILE_HASH = re.compile(
|
||||||
|
r"^\[PipelineTileHash\] lod=(-?\d+) x=(-?\d+) y=(-?\d+) z=(-?\d+) "
|
||||||
|
r"density=(0x[0-9a-f]+) mesh=(0x[0-9a-f]+) payload=(0x[0-9a-f]+) "
|
||||||
|
r"topology=(0x[0-9a-f]+)$", re.M)
|
||||||
|
WHOLE_HASH = re.compile(
|
||||||
|
r"^\[PipelineWholeViewHash\] tiles=(\d+) density=(0x[0-9a-f]+) "
|
||||||
|
r"mesh=(0x[0-9a-f]+) payload=(0x[0-9a-f]+) topology=(0x[0-9a-f]+)$", re.M)
|
||||||
|
|
||||||
|
|
||||||
|
def run(strate: int, version: str) -> dict:
|
||||||
|
OUT.mkdir(parents=True, exist_ok=True)
|
||||||
|
log = OUT / f"{version.lower()}-s{strate}.log"
|
||||||
|
camera = ",".join(f"{v:.12f}" for v in CAMERAS[strate]) + ",0,0"
|
||||||
|
command = [
|
||||||
|
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||||
|
"env", "VOXELFORGE_L81_PASSAGE_BOUND_PADDING_VOXELS=0", "timeout", "1740s",
|
||||||
|
str(BINARIES[version]), str(DATA), "--world-assembler-prototype",
|
||||||
|
"--stream-benchmark", "--stream-pipeline", "--workers=8", "--clip-radius=3",
|
||||||
|
"--max-clip-level=8", "--max-level=1", f"--camera={camera}",
|
||||||
|
"--stream-hash-tiles",
|
||||||
|
]
|
||||||
|
start = time.monotonic()
|
||||||
|
with log.open("w", encoding="utf-8") as stream:
|
||||||
|
result = subprocess.run(command, cwd=ROOT, stdout=stream,
|
||||||
|
stderr=subprocess.STDOUT, check=False, timeout=1740)
|
||||||
|
output = log.read_text(encoding="utf-8", errors="replace")
|
||||||
|
hashes = {
|
||||||
|
(int(m.group(1)), int(m.group(2)), int(m.group(3)), int(m.group(4))): m.group(5, 6, 7, 8)
|
||||||
|
for m in TILE_HASH.finditer(output)
|
||||||
|
}
|
||||||
|
whole = WHOLE_HASH.search(output)
|
||||||
|
if result.returncode or whole is None or len(hashes) != 960:
|
||||||
|
raise RuntimeError(f"hash run failed or incomplete: exit={result.returncode} {log}")
|
||||||
|
row = {
|
||||||
|
"version": version, "strate": strate, "exit_code": result.returncode,
|
||||||
|
"wall_s": round(time.monotonic() - start, 3), "tile_hash_count": len(hashes),
|
||||||
|
"whole_hash": whole.group(1, 2, 3, 4, 5), "hashes": hashes,
|
||||||
|
"log": str(log.relative_to(ROOT)),
|
||||||
|
}
|
||||||
|
print("[L82HashRun] " + json.dumps(
|
||||||
|
{k: v for k, v in row.items() if k != "hashes"}, sort_keys=True), flush=True)
|
||||||
|
return row
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
for binary in (*BINARIES.values(), DATA):
|
||||||
|
if not binary.is_file():
|
||||||
|
raise SystemExit(f"missing input: {binary}")
|
||||||
|
rows = []
|
||||||
|
comparisons = []
|
||||||
|
for strate in range(4):
|
||||||
|
pair = {version: run(strate, version) for version in ("L81", "L82")}
|
||||||
|
a, b = pair["L81"], pair["L82"]
|
||||||
|
mismatch_keys = sorted(key for key in set(a["hashes"]) | set(b["hashes"])
|
||||||
|
if a["hashes"].get(key) != b["hashes"].get(key))
|
||||||
|
comparison = {
|
||||||
|
"strate": strate, "tiles": len(a["hashes"]),
|
||||||
|
"tile_hashes_identical": not mismatch_keys,
|
||||||
|
"whole_hash_identical": a["whole_hash"] == b["whole_hash"],
|
||||||
|
"mismatch_count": len(mismatch_keys),
|
||||||
|
"first_mismatches": mismatch_keys[:8],
|
||||||
|
"l81_whole_hash": a["whole_hash"], "l82_whole_hash": b["whole_hash"],
|
||||||
|
}
|
||||||
|
comparisons.append(comparison)
|
||||||
|
rows.extend({k: v for k, v in run_data.items() if k != "hashes"}
|
||||||
|
for run_data in pair.values())
|
||||||
|
print("[L82HashComparison] " + json.dumps(comparison, sort_keys=True), flush=True)
|
||||||
|
summary = {"comparisons": comparisons, "runs": rows}
|
||||||
|
(OUT / "summary.json").write_text(json.dumps(summary, indent=2) + "\n",
|
||||||
|
encoding="utf-8")
|
||||||
|
return 0 if all(item["tile_hashes_identical"] and item["whole_hash_identical"]
|
||||||
|
for item in comparisons) else 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Run the full-view surface-world passage validation against resolved routes."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[4]
|
||||||
|
OUT = ROOT / "Saved/Linux/l82/audits"
|
||||||
|
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||||
|
BINARY = ROOT / "Saved/Linux/l82/bin/VoxelForgeCoreHeadless-clang19"
|
||||||
|
RECIPE = re.compile(r"^\[WorldAssemblerRecipeAudit\] requested=surface-world "
|
||||||
|
r"actual=surface-world .*status=PASS$", re.M)
|
||||||
|
WALK = re.compile(r"^\[Walkability\] PASS source=resolved_core_layout (\d+)/(\d+) "
|
||||||
|
r"compiled Core passage centerline samples remain open$", re.M)
|
||||||
|
AUDIT = re.compile(r"^\[ResolvedPassageAudit\].*walkability=PASS$", re.M)
|
||||||
|
SUMMARY = re.compile(r"^\[ResolvedPassageAuditSummary\].*status=PASS$", re.M)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
OUT.mkdir(parents=True, exist_ok=True)
|
||||||
|
log = OUT / "owner-surface-world-full.log"
|
||||||
|
command = [
|
||||||
|
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||||
|
"env", "VOXELFORGE_L81_PASSAGE_BOUND_PADDING_VOXELS=0", "timeout", "1740s",
|
||||||
|
str(BINARY), str(DATA), "--workers=12", "--world-assembler-prototype",
|
||||||
|
"--stream-benchmark", "--stream-pipeline", "--force-generate", "--clip-radius=3",
|
||||||
|
"--max-clip-level=8", "--max-level=-1",
|
||||||
|
"--audit-resolved-layout-passages",
|
||||||
|
]
|
||||||
|
start = time.monotonic()
|
||||||
|
with log.open("w", encoding="utf-8") as stream:
|
||||||
|
result = subprocess.run(command, cwd=ROOT, stdout=stream,
|
||||||
|
stderr=subprocess.STDOUT, check=False, timeout=1740)
|
||||||
|
output = log.read_text(encoding="utf-8", errors="replace")
|
||||||
|
walk = WALK.search(output)
|
||||||
|
rows = {
|
||||||
|
"exit_code": result.returncode,
|
||||||
|
"wall_s": round(time.monotonic() - start, 3),
|
||||||
|
"recipe_pass": RECIPE.search(output) is not None,
|
||||||
|
"canonical_walkability_pass": walk is not None,
|
||||||
|
"canonical_center_samples": int(walk.group(2)) if walk else 0,
|
||||||
|
"resolved_audit_passages": len(AUDIT.findall(output)),
|
||||||
|
"resolved_audit_summary_pass": SUMMARY.search(output) is not None,
|
||||||
|
"legacy_root_route_failure": "[WalkabilityRoute] passage=" in output,
|
||||||
|
"log": str(log.relative_to(ROOT)),
|
||||||
|
}
|
||||||
|
(OUT / "summary.json").write_text(json.dumps(rows, indent=2) + "\n",
|
||||||
|
encoding="utf-8")
|
||||||
|
print("[L82OwnerPassageAudit] " + json.dumps(rows, sort_keys=True), flush=True)
|
||||||
|
return 0 if (result.returncode == 0 and rows["recipe_pass"]
|
||||||
|
and rows["canonical_walkability_pass"]
|
||||||
|
and rows["resolved_audit_passages"] > 0
|
||||||
|
and rows["resolved_audit_summary_pass"]
|
||||||
|
and not rows["legacy_root_route_failure"]) else 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Three interleaved L80-protocol readiness pairs per stratum, L81 vs L82."""
|
||||||
|
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/l82/readiness"
|
||||||
|
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||||
|
BINARIES = {
|
||||||
|
"L81": ROOT / "Saved/Linux/l81/bin/VoxelForgeCoreHeadless-l81-clang",
|
||||||
|
"L82": ROOT / "Saved/Linux/l82/bin/VoxelForgeCoreHeadless-clang19",
|
||||||
|
}
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
PIPELINE = re.compile(r"^\[StreamPipeline\] (.*)$", re.M)
|
||||||
|
FIELDS = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
|
||||||
|
NEAR = re.compile(r"^\[WorldAssemblerNearLODReady\] lods=0-2 tiles=(\d+)/(\d+) "
|
||||||
|
r"cold_ready_s=([0-9.]+)$", re.M)
|
||||||
|
|
||||||
|
|
||||||
|
def run(strate: int, version: str, phase: str, rep: int) -> dict:
|
||||||
|
OUT.mkdir(parents=True, exist_ok=True)
|
||||||
|
log = OUT / f"s{strate}-{version.lower()}-{phase}{rep}.log"
|
||||||
|
camera = ",".join(f"{v:.12f}" for v in CAMERAS[strate]) + ",0,0"
|
||||||
|
command = [
|
||||||
|
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||||
|
"env", "VOXELFORGE_L81_PASSAGE_BOUND_PADDING_VOXELS=0", "timeout", "1740s",
|
||||||
|
str(BINARIES[version]), str(DATA), "--world-assembler-prototype",
|
||||||
|
"--stream-benchmark", "--stream-pipeline", "--workers=8", "--clip-radius=3",
|
||||||
|
"--max-clip-level=8", "--max-level=8", f"--camera={camera}",
|
||||||
|
"--profile-lod-work", "--stream-scheduler-policy=3",
|
||||||
|
]
|
||||||
|
start = time.monotonic()
|
||||||
|
with log.open("w", encoding="utf-8") as stream:
|
||||||
|
result = subprocess.run(command, cwd=ROOT, stdout=stream,
|
||||||
|
stderr=subprocess.STDOUT, check=False, timeout=1740)
|
||||||
|
output = log.read_text(encoding="utf-8", errors="replace")
|
||||||
|
pipeline_match, near_match = PIPELINE.search(output), NEAR.search(output)
|
||||||
|
if result.returncode or pipeline_match is None or near_match is None:
|
||||||
|
raise RuntimeError(f"readiness run failed: exit={result.returncode} {log}")
|
||||||
|
pipeline = dict(FIELDS.findall(pipeline_match.group(1)))
|
||||||
|
row = {
|
||||||
|
"strate": strate, "version": version, "phase": phase, "replicate": rep,
|
||||||
|
"exit_code": result.returncode, "wall_s": round(time.monotonic() - start, 3),
|
||||||
|
"lod02_ready_s": float(near_match.group(3)),
|
||||||
|
"whole_view_ready_s": float(pipeline["whole_view_ready"].removesuffix("s")),
|
||||||
|
"whole_tiles": int(pipeline["whole_tiles"].split("/")[0]),
|
||||||
|
"log": str(log.relative_to(ROOT)),
|
||||||
|
}
|
||||||
|
print("[L82ReadinessRun] " + json.dumps(row, sort_keys=True), flush=True)
|
||||||
|
return row
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
for binary in (*BINARIES.values(), DATA):
|
||||||
|
if not binary.is_file():
|
||||||
|
raise SystemExit(f"missing input: {binary}")
|
||||||
|
for strate in range(4):
|
||||||
|
for version in ("L81", "L82"):
|
||||||
|
run(strate, version, "warmup", 0)
|
||||||
|
rows = []
|
||||||
|
orders = (("L81", "L82"), ("L82", "L81"), ("L81", "L82"))
|
||||||
|
for strate in range(4):
|
||||||
|
for rep, order in enumerate(orders, 1):
|
||||||
|
for version in order:
|
||||||
|
rows.append(run(strate, version, "measured", rep))
|
||||||
|
(OUT / "runs-partial.json").write_text(json.dumps(rows, indent=2) + "\n",
|
||||||
|
encoding="utf-8")
|
||||||
|
summary = []
|
||||||
|
for strate in range(4):
|
||||||
|
group = [row for row in rows if row["strate"] == strate]
|
||||||
|
item = {"strate": strate, "measured_pairs": 3}
|
||||||
|
for version in ("L81", "L82"):
|
||||||
|
subset = [row for row in group if row["version"] == version]
|
||||||
|
for metric in ("lod02_ready_s", "whole_view_ready_s"):
|
||||||
|
values = [row[metric] for row in subset]
|
||||||
|
item[f"{version}_{metric}"] = values
|
||||||
|
item[f"{version}_{metric}_median"] = statistics.median(values)
|
||||||
|
summary.append(item)
|
||||||
|
(OUT / "summary.json").write_text(json.dumps({"runs": rows, "summary": summary},
|
||||||
|
indent=2) + "\n", encoding="utf-8")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Five interleaved TN LOD0-2 readiness pairs: 90378a5 vs 9cb5e53."""
|
||||||
|
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/l82/tn-regression"
|
||||||
|
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||||
|
BINARIES = {
|
||||||
|
"90378a5": ROOT / "Saved/Linux/l76/verify/bin/VoxelForgeCoreHeadless-verify-clang",
|
||||||
|
"9cb5e53": ROOT / "Saved/Linux/l81/bin/VoxelForgeCoreHeadless-l81-clang",
|
||||||
|
}
|
||||||
|
CAMERA = (19.166015625, -14.2001953125, -753.3740234375)
|
||||||
|
PIPELINE = re.compile(r"^\[StreamPipeline\] (.*)$", re.M)
|
||||||
|
FIELDS = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
|
||||||
|
NEAR = re.compile(r"^\[WorldAssemblerNearLODReady\] lods=0-2 tiles=(\d+)/(\d+) "
|
||||||
|
r"cold_ready_s=([0-9.]+)$", re.M)
|
||||||
|
|
||||||
|
|
||||||
|
def run(version: str, rep: int) -> dict:
|
||||||
|
OUT.mkdir(parents=True, exist_ok=True)
|
||||||
|
log = OUT / f"{version}-r{rep}.log"
|
||||||
|
camera = ",".join(f"{v:.12f}" for v in CAMERA) + ",0,0"
|
||||||
|
command = [
|
||||||
|
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||||
|
"env", "VOXELFORGE_L81_PASSAGE_BOUND_PADDING_VOXELS=0", "timeout", "1740s",
|
||||||
|
str(BINARIES[version]), str(DATA), "--world-assembler-prototype",
|
||||||
|
"--stream-benchmark", "--stream-pipeline", "--workers=8", "--clip-radius=3",
|
||||||
|
"--max-clip-level=8", "--max-level=2", f"--camera={camera}",
|
||||||
|
"--profile-lod-work",
|
||||||
|
]
|
||||||
|
start = time.monotonic()
|
||||||
|
with log.open("w", encoding="utf-8") as stream:
|
||||||
|
result = subprocess.run(command, cwd=ROOT, stdout=stream,
|
||||||
|
stderr=subprocess.STDOUT, check=False, timeout=1740)
|
||||||
|
output = log.read_text(encoding="utf-8", errors="replace")
|
||||||
|
pipeline_match, near_match = PIPELINE.search(output), NEAR.search(output)
|
||||||
|
if result.returncode or pipeline_match is None:
|
||||||
|
raise RuntimeError(f"TN run failed: exit={result.returncode} {log}")
|
||||||
|
pipeline = dict(FIELDS.findall(pipeline_match.group(1)))
|
||||||
|
whole_tiles = int(pipeline["whole_tiles"].split("/")[0])
|
||||||
|
if near_match is not None:
|
||||||
|
lod02_ready_s = float(near_match.group(3))
|
||||||
|
lod02_tiles = int(near_match.group(1))
|
||||||
|
else:
|
||||||
|
# 90378a5 predates the explicit near-LOD milestone. With max-level=2,
|
||||||
|
# the complete 0-2 view is the whole-view milestone.
|
||||||
|
lod02_ready_s = float(pipeline["whole_view_ready"].removesuffix("s"))
|
||||||
|
lod02_tiles = whole_tiles
|
||||||
|
if lod02_tiles != 1344 or whole_tiles != 1344:
|
||||||
|
raise RuntimeError(f"TN tile scope mismatch: lod02={lod02_tiles} whole={whole_tiles} {log}")
|
||||||
|
row = {
|
||||||
|
"version": version, "pair": rep, "exit_code": result.returncode,
|
||||||
|
"wall_s": round(time.monotonic() - start, 3),
|
||||||
|
"lod02_ready_s": lod02_ready_s,
|
||||||
|
"whole_view_ready_s": float(pipeline["whole_view_ready"].removesuffix("s")),
|
||||||
|
"whole_tiles": whole_tiles,
|
||||||
|
"log": str(log.relative_to(ROOT)),
|
||||||
|
}
|
||||||
|
print("[L82TNRegressionRun] " + json.dumps(row, sort_keys=True), flush=True)
|
||||||
|
return row
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
for path in (*BINARIES.values(), DATA):
|
||||||
|
if not path.is_file():
|
||||||
|
raise SystemExit(f"missing input: {path}")
|
||||||
|
rows = []
|
||||||
|
for rep in range(1, 6):
|
||||||
|
order = ("90378a5", "9cb5e53") if rep % 2 else ("9cb5e53", "90378a5")
|
||||||
|
for version in order:
|
||||||
|
rows.append(run(version, rep))
|
||||||
|
(OUT / "runs-partial.json").write_text(json.dumps(rows, indent=2) + "\n",
|
||||||
|
encoding="utf-8")
|
||||||
|
by_pair = {rep: {row["version"]: row for row in rows if row["pair"] == rep}
|
||||||
|
for rep in range(1, 6)}
|
||||||
|
paired_ready_delta = [by_pair[rep]["9cb5e53"]["lod02_ready_s"]
|
||||||
|
- by_pair[rep]["90378a5"]["lod02_ready_s"]
|
||||||
|
for rep in range(1, 6)]
|
||||||
|
paired_whole_delta = [by_pair[rep]["9cb5e53"]["whole_view_ready_s"]
|
||||||
|
- by_pair[rep]["90378a5"]["whole_view_ready_s"]
|
||||||
|
for rep in range(1, 6)]
|
||||||
|
summary = {
|
||||||
|
"pairs": 5,
|
||||||
|
"90378a5_lod02_median_s": statistics.median(
|
||||||
|
row["lod02_ready_s"] for row in rows if row["version"] == "90378a5"),
|
||||||
|
"9cb5e53_lod02_median_s": statistics.median(
|
||||||
|
row["lod02_ready_s"] for row in rows if row["version"] == "9cb5e53"),
|
||||||
|
"paired_lod02_delta_s": paired_ready_delta,
|
||||||
|
"paired_lod02_delta_median_s": statistics.median(paired_ready_delta),
|
||||||
|
"90378a5_whole_median_s": statistics.median(
|
||||||
|
row["whole_view_ready_s"] for row in rows if row["version"] == "90378a5"),
|
||||||
|
"9cb5e53_whole_median_s": statistics.median(
|
||||||
|
row["whole_view_ready_s"] for row in rows if row["version"] == "9cb5e53"),
|
||||||
|
"paired_whole_delta_s": paired_whole_delta,
|
||||||
|
"paired_whole_delta_median_s": statistics.median(paired_whole_delta),
|
||||||
|
"scheduling": "both binaries used their default scheduler settings; 90378a5 predates the policy flag",
|
||||||
|
"runs": rows,
|
||||||
|
}
|
||||||
|
(OUT / "summary.json").write_text(json.dumps(summary, indent=2) + "\n",
|
||||||
|
encoding="utf-8")
|
||||||
|
print("[L82TNRegressionSummary] " + json.dumps(
|
||||||
|
{k: v for k, v in summary.items() if k != "runs"}, sort_keys=True), flush=True)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -2242,7 +2242,7 @@ int main(int argc,char** argv)
|
|||||||
std::chrono::duration<double,std::milli>(Clock::now()-planStart).count());
|
std::chrono::duration<double,std::milli>(Clock::now()-planStart).count());
|
||||||
|
|
||||||
auto snapshot=VoxelForgeCore::MakeStandaloneSnapshotFromParsedJson(ownerJsonRoot,seed,
|
auto snapshot=VoxelForgeCore::MakeStandaloneSnapshotFromParsedJson(ownerJsonRoot,seed,
|
||||||
options.ClipRadius,options.MaxClipLevel,false,false,error);
|
options.ClipRadius,options.MaxClipLevel,false,false,error,useExportedCorePlan);
|
||||||
if(!snapshot)
|
if(!snapshot)
|
||||||
{std::fprintf(stderr,"Shape interval build failed: %s\n",error.c_str());return 2;}
|
{std::fprintf(stderr,"Shape interval build failed: %s\n",error.c_str());return 2;}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user