streaming: deeper ready queue + startup apply cap; tile-local mouth-support culling (L72)
- Unreal: ready queue depth 8 -> 16 (cap 4 worker-sets / 64), startup mesh-apply cap 12 -> 24 within the unchanged 14 ms budget. Owner SW arrival, same 2,850 keys: initial set ready 4.78 -> 3.74 s, queue p95 123 -> 35 ms, request-to-apply p95 139 -> 45 ms; walkable gate 1.07 -> 1.11 s; per-frame apply p95 0.73 ms. - Core composer: mouth-support segments filtered by tile bounds, mouth relief result reused (route checks -80/-87% in passage tiles). Hashes identical W1/W8/W12, Clang and GCC. - Opt-in per-tile composer counters. Verify control meshes stored as .xz (78.5 -> 18.4 MB). - Fast tier 269 PASS. 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
971206c2ea
commit
d970d75be8
26 files changed
+383
-82
No files matched your search
@@ -131,6 +131,31 @@ namespace VoxelForgeCore
|
||||
std::uint64_t mixedBoxes = 0;
|
||||
std::uint64_t unknownBoxes = 0;
|
||||
std::uint64_t fieldCalls = 0;
|
||||
// Opt-in composer diagnostics. These stay zero during ordinary generation.
|
||||
bool passageComposerProfiled = false;
|
||||
std::uint64_t passageTileFeatureCount = 0;
|
||||
std::uint64_t passageTileRouteSegmentCandidates = 0;
|
||||
std::uint64_t passageTileMouthSegmentCandidates = 0;
|
||||
std::uint64_t passageComposerQueries = 0;
|
||||
std::uint64_t passageIterations = 0;
|
||||
std::uint64_t passageUpperMouthFilterChecks = 0;
|
||||
std::uint64_t passageColumnCandidates = 0;
|
||||
std::uint64_t passageColumnDistanceCalls = 0;
|
||||
std::uint64_t passageNegativeBoundChecks = 0;
|
||||
std::uint64_t passageNegativeBoundRejects = 0;
|
||||
std::uint64_t passagePositiveBoundChecks = 0;
|
||||
std::uint64_t passagePositiveBoundRejects = 0;
|
||||
std::uint64_t passageCarveSegmentVisits = 0;
|
||||
std::uint64_t passageSmoothCarveJoins = 0;
|
||||
std::uint64_t passageSupportSegmentVisits = 0;
|
||||
std::uint64_t passageSmoothSupportJoins = 0;
|
||||
std::uint64_t passageSupportCapsuleChecks = 0;
|
||||
std::uint64_t passageLaterBoreChecks = 0;
|
||||
std::uint64_t passageOtherCapsuleChecks = 0;
|
||||
std::uint64_t passageMouthSupportCalls = 0;
|
||||
std::uint64_t passageMouthRouteSegmentChecks = 0;
|
||||
std::uint64_t passageReliefFieldCalls = 0;
|
||||
std::uint64_t passageSmoothstepEvaluations = 0;
|
||||
std::uint64_t floatFieldCalls = 0;
|
||||
std::uint64_t latticeFieldCalls = 0;
|
||||
std::uint64_t unclampedFieldCalls = 0;
|
||||
@@ -1745,6 +1770,8 @@ namespace VoxelForgeCore
|
||||
// Separate cold pass only; samples the MC inner stages without changing output.
|
||||
// Passe froide separee seulement; mesure les etapes internes MC sans changer la sortie.
|
||||
bool profileMesherStages = false;
|
||||
// Research-only: count passage-composer work per generated tile.
|
||||
bool profilePassageComposer = false;
|
||||
// Research-only: collect tile-drain kernel counters and floating-island cache metrics.
|
||||
// Inactive on ordinary generation so the hot path only pays the option check.
|
||||
bool instrumentTileDrain = false;
|
||||
|
||||
@@ -172,11 +172,19 @@ public:
|
||||
class FScopedPassageTileQueries
|
||||
{
|
||||
public:
|
||||
struct FCandidateSegment
|
||||
{
|
||||
std::size_t segmentIndex=0;
|
||||
std::array<std::int64_t,3> minimumSubvoxels{},maximumSubvoxels{};
|
||||
};
|
||||
|
||||
FScopedPassageTileQueries(const FStandaloneShapeWorld& shape,
|
||||
const std::array<std::int64_t,3>& minimumSubvoxels,
|
||||
const std::array<std::int64_t,3>& maximumSubvoxels)
|
||||
const std::array<std::int64_t,3>& maximumSubvoxels,
|
||||
VoxelForgeCore::TileGenerationStats* profileStats=nullptr)
|
||||
:shape_(&shape),minimumSubvoxels_(minimumSubvoxels),
|
||||
maximumSubvoxels_(maximumSubvoxels),previous_(ActivePassageTileQueries)
|
||||
maximumSubvoxels_(maximumSubvoxels),previous_(ActivePassageTileQueries),
|
||||
profileStats_(profileStats)
|
||||
{
|
||||
mayContainResolvedBoundaryBand_=shape.ResolvedBoundaryBandsIntersectSubvoxelRange(
|
||||
minimumSubvoxels_[2],maximumSubvoxels_[2]);
|
||||
@@ -190,6 +198,7 @@ public:
|
||||
passageRoutes_.assign(shape.ResolvedLayout?shape.ResolvedLayout->passages.size():0,0);
|
||||
passageColumns_=passageRoutes_;
|
||||
passageMouthSupports_=passageRoutes_;
|
||||
passageMouthSupportSegments_.resize(passageRoutes_.size());
|
||||
for(const auto& crossing:shape.PassageVoidCrossings)
|
||||
if(crossing.needsColumn
|
||||
&&crossing.bottomZSubvoxels<=maximumSubvoxels_[2]
|
||||
@@ -200,14 +209,27 @@ public:
|
||||
if(featureIndex>=shape.PassageTileFeatures.size())continue;
|
||||
const auto& feature=shape.PassageTileFeatures[featureIndex];
|
||||
if(feature.passageIndex>=passageRoutes_.size())continue;
|
||||
const auto& featureBounds=shape.PassageFeatureBounds[featureIndex];
|
||||
const FCandidateSegment candidate{feature.segmentIndex,
|
||||
featureBounds.minimumSubvoxels,featureBounds.maximumSubvoxels};
|
||||
if(feature.bColumn)passageColumns_[feature.passageIndex]=1;
|
||||
else if(feature.bMouthSupport)passageMouthSupports_[feature.passageIndex]=1;
|
||||
else if(feature.bMouthSupport)
|
||||
{
|
||||
passageMouthSupports_[feature.passageIndex]=1;
|
||||
passageMouthSupportSegments_[feature.passageIndex].push_back(candidate);
|
||||
}
|
||||
else
|
||||
{
|
||||
passageRoutes_[feature.passageIndex]=1;
|
||||
++routeSegmentCandidateCount_;
|
||||
routeFeatureOverlap_=true;
|
||||
}
|
||||
}
|
||||
for(auto& values:passageMouthSupportSegments_)
|
||||
{
|
||||
std::sort(values.begin(),values.end(),[](const auto& a,const auto& b)
|
||||
{return a.segmentIndex<b.segmentIndex;});
|
||||
}
|
||||
ActivePassageTileQueries=this;
|
||||
}
|
||||
~FScopedPassageTileQueries(){ActivePassageTileQueries=previous_;}
|
||||
@@ -220,6 +242,12 @@ public:
|
||||
const auto& candidates=columnFeature?passageColumns_:passageRoutes_;
|
||||
return passageIndex<candidates.size()&&candidates[passageIndex]!=0;
|
||||
}
|
||||
const std::vector<FCandidateSegment>& mouthSupportSegments(std::size_t passageIndex) const
|
||||
{
|
||||
static const std::vector<FCandidateSegment> Empty;
|
||||
return passageIndex<passageMouthSupportSegments_.size()
|
||||
?passageMouthSupportSegments_[passageIndex]:Empty;
|
||||
}
|
||||
bool hasMouthSupport(std::size_t passageIndex) const
|
||||
{return passageIndex<passageMouthSupports_.size()&&passageMouthSupports_[passageIndex]!=0;}
|
||||
bool hasAnyMouthSupport() const
|
||||
@@ -235,6 +263,9 @@ public:
|
||||
&&shape_->ResolvedBoundaryBandMayAffectAtZMicrometres(zMicrometres);
|
||||
}
|
||||
std::size_t featureCount() const{return featureIndices_.size();}
|
||||
std::size_t routeSegmentCandidateCount() const{return routeSegmentCandidateCount_;}
|
||||
VoxelForgeCore::TileGenerationStats* profileStats() const noexcept
|
||||
{return profileStats_;}
|
||||
bool hasStampCandidates() const{return !stampIndices_.empty();}
|
||||
const std::vector<std::uint32_t>& stampCandidates() const{return stampIndices_;}
|
||||
bool hasCavernCandidates() const{return !cavernFeatureIndices_.empty();}
|
||||
@@ -248,10 +279,13 @@ public:
|
||||
std::vector<std::uint32_t> stampIndices_;
|
||||
std::vector<std::uint32_t> cavernFeatureIndices_;
|
||||
std::vector<std::uint8_t> passageRoutes_,passageColumns_,passageMouthSupports_;
|
||||
std::vector<std::vector<FCandidateSegment>> passageMouthSupportSegments_;
|
||||
std::size_t routeSegmentCandidateCount_=0;
|
||||
bool routeFeatureOverlap_=false,columnZOverlap_=false;
|
||||
bool mayContainResolvedBoundaryBand_=true;
|
||||
bool mayContainTransitionPassageFade_=true;
|
||||
const FScopedPassageTileQueries* previous_;
|
||||
VoxelForgeCore::TileGenerationStats* profileStats_=nullptr;
|
||||
};
|
||||
|
||||
bool IsResolvedPassageColumnSample(std::size_t PassageIndex,std::size_t SegmentIndex,
|
||||
@@ -953,9 +987,13 @@ public:
|
||||
const auto* passageTileQueries=ActivePassageTileQueries;
|
||||
const bool passageTileScopeMatches=passageTileQueries
|
||||
&&passageTileQueries->matches(*this);
|
||||
auto* passageProfile=passageTileScopeMatches
|
||||
?passageTileQueries->profileStats():nullptr;
|
||||
if(passageProfile)++passageProfile->passageComposerQueries;
|
||||
bool queryNearUpperMouthSupport=false;
|
||||
for(const auto& passage:ResolvedLayout->passages)
|
||||
{
|
||||
if(passageProfile)++passageProfile->passageUpperMouthFilterChecks;
|
||||
const double dx=qx-static_cast<double>(passage.upperMouthSubvoxels[0]);
|
||||
const double dy=qy-static_cast<double>(passage.upperMouthSubvoxels[1]);
|
||||
const double dz=qz-static_cast<double>(passage.upperMouthSubvoxels[2]);
|
||||
@@ -972,26 +1010,20 @@ public:
|
||||
const double playerCenterAboveFloorQ=1.13*qPerMetre;
|
||||
float composed=density;
|
||||
float deferredSupportDensity=std::numeric_limits<float>::infinity();
|
||||
const auto composeRouteMouthSupport=[&](std::int32_t strateIndex,
|
||||
const std::array<std::int64_t,3>& mouth)
|
||||
const auto composeRouteMouthSupport=[&](std::size_t passageIndex)
|
||||
{
|
||||
// Surface-route treads may reinforce native solid terrain only.
|
||||
if(!(density<0.0f))return;
|
||||
if(passageIndex>=ResolvedLayout->passages.size()
|
||||
||passageIndex>=PassageMouthSupportRoutes.size())return;
|
||||
const auto& passage=ResolvedLayout->passages[passageIndex];
|
||||
const auto& mouth=passage.upperMouthSubvoxels;
|
||||
const double mouthDx=qx-static_cast<double>(mouth[0]);
|
||||
const double mouthDy=qy-static_cast<double>(mouth[1]);
|
||||
const double routeSupportReach=13.0*qPerMetre;
|
||||
if(mouthDx*mouthDx+mouthDy*mouthDy>routeSupportReach*routeSupportReach)return;
|
||||
const auto stratum=std::find_if(ResolvedLayout->strates.begin(),
|
||||
ResolvedLayout->strates.end(),[strateIndex](const auto& value)
|
||||
{return value.strateIndex==strateIndex;});
|
||||
if(stratum==ResolvedLayout->strates.end())return;
|
||||
std::vector<const VoxelForgeCore::ResolvedLayoutAnchorV2*> route;
|
||||
route.reserve(stratum->anchors.size());
|
||||
for(const auto& anchor:stratum->anchors)
|
||||
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
|
||||
route.push_back(&anchor);
|
||||
if(route.size()<2)return;
|
||||
const std::size_t lastSegment=route.size()-1;
|
||||
const auto& route=PassageMouthSupportRoutes[passageIndex];
|
||||
if(route.empty())return;
|
||||
constexpr double MouthSupportBlendMetres=0.45;
|
||||
// Route anchors encode the player center at half the authored capsule
|
||||
// height above the tread. Keep the support deck below that same foot
|
||||
@@ -999,18 +1031,21 @@ public:
|
||||
constexpr double RouteCenterAboveFootMetres=0.88;
|
||||
const double mouthBlendWidth=MouthSupportBlendMetres*qPerMetre;
|
||||
double mouthSupportDistance=std::numeric_limits<double>::infinity();
|
||||
for(std::size_t index=0;index<lastSegment;++index)
|
||||
const auto evaluateSegment=[&](std::size_t index)
|
||||
{
|
||||
const auto& a=*route[index];const auto& b=*route[index+1];
|
||||
const double dx=static_cast<double>(b.values[0]-a.values[0]);
|
||||
const double dy=static_cast<double>(b.values[1]-a.values[1]);
|
||||
if(index>=route.size())return;
|
||||
if(passageProfile)++passageProfile->passageMouthRouteSegmentChecks;
|
||||
const auto& segment=route[index];
|
||||
const double dx=static_cast<double>(segment.b[0]-segment.a[0]);
|
||||
const double dy=static_cast<double>(segment.b[1]-segment.a[1]);
|
||||
const double lengthSq=dx*dx+dy*dy;
|
||||
const double t=lengthSq>0.0?std::clamp(((qx-a.values[0])*dx
|
||||
+(qy-a.values[1])*dy)/lengthSq,0.0,1.0):0.0;
|
||||
const double centerX=a.values[0]+t*dx,centerY=a.values[1]+t*dy;
|
||||
const double centerZ=a.values[2]+t*(static_cast<double>(b.values[2])-a.values[2]);
|
||||
const double routeRadius=a.values.size()>=4
|
||||
?a.values[3]+t*(static_cast<double>(b.values[3])-a.values[3]):0.0;
|
||||
const double t=lengthSq>0.0?std::clamp(((qx-segment.a[0])*dx
|
||||
+(qy-segment.a[1])*dy)/lengthSq,0.0,1.0):0.0;
|
||||
const double centerX=segment.a[0]+t*dx,centerY=segment.a[1]+t*dy;
|
||||
const double centerZ=segment.a[2]
|
||||
+t*(static_cast<double>(segment.b[2])-segment.a[2]);
|
||||
const double routeRadius=segment.hasRadius
|
||||
?segment.a[3]+t*(static_cast<double>(segment.b[3])-segment.a[3]):0.0;
|
||||
const double halfWidth=std::clamp(routeRadius,
|
||||
0.42*qPerMetre,0.56*qPerMetre);
|
||||
const double horizontalX=qx-centerX,horizontalY=qy-centerY;
|
||||
@@ -1022,12 +1057,29 @@ public:
|
||||
const double topDistance=qz-floorQ;
|
||||
const double bottomDistance=floorQ-qPerMetre-qz;
|
||||
const double verticalDistance=std::max(topDistance,bottomDistance);
|
||||
if(sideDistance>mouthBlendWidth||verticalDistance>mouthBlendWidth)continue;
|
||||
if(sideDistance>mouthBlendWidth||verticalDistance>mouthBlendWidth)return;
|
||||
const double outside=DeterministicSqrt(
|
||||
std::max(sideDistance,0.0)*std::max(sideDistance,0.0)
|
||||
+std::max(verticalDistance,0.0)*std::max(verticalDistance,0.0));
|
||||
const double inside=std::min(std::max(sideDistance,verticalDistance),0.0);
|
||||
mouthSupportDistance=std::min(mouthSupportDistance,outside+inside);
|
||||
};
|
||||
if(passageTileScopeMatches)
|
||||
{
|
||||
for(const auto& candidate:passageTileQueries->mouthSupportSegments(passageIndex))
|
||||
{
|
||||
const bool containsPoint=qx>=candidate.minimumSubvoxels[0]
|
||||
&&qx<=candidate.maximumSubvoxels[0]
|
||||
&&qy>=candidate.minimumSubvoxels[1]
|
||||
&&qy<=candidate.maximumSubvoxels[1]
|
||||
&&qz>=candidate.minimumSubvoxels[2]
|
||||
&&qz<=candidate.maximumSubvoxels[2];
|
||||
if(containsPoint)evaluateSegment(candidate.segmentIndex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for(std::size_t index=0;index<route.size();++index)evaluateSegment(index);
|
||||
}
|
||||
if(std::isfinite(mouthSupportDistance))
|
||||
composed=SmoothMinDistance(composed,
|
||||
@@ -1037,6 +1089,7 @@ public:
|
||||
// The connected passage tread continues the surface route into the tunnel.
|
||||
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();++passageIndex)
|
||||
{
|
||||
if(passageProfile)++passageProfile->passageIterations;
|
||||
const auto& passage=ResolvedLayout->passages[passageIndex];
|
||||
if(passage.controlPoints.size()<2)continue;
|
||||
const auto supportStratum=[&](std::int32_t strateIndex)
|
||||
@@ -1071,6 +1124,7 @@ public:
|
||||
const auto laterPassageMayCarve=[&](std::ptrdiff_t supportSegment,
|
||||
double sampleX,double sampleY,double sampleZ)
|
||||
{
|
||||
if(passageProfile)++passageProfile->passageLaterBoreChecks;
|
||||
return PassageSupportMayBeCarvedByLaterBore(passageIndex,
|
||||
supportSegment,sampleX,sampleY,sampleZ,0.35*qPerMetre);
|
||||
};
|
||||
@@ -1086,6 +1140,7 @@ public:
|
||||
{
|
||||
if(!crossing.needsColumn||crossing.passageIndex!=passageIndex
|
||||
||qz>crossing.topZSubvoxels||qz<crossing.bottomZSubvoxels)continue;
|
||||
if(passageProfile)++passageProfile->passageColumnCandidates;
|
||||
#if defined(VF_L30_ENABLE_COST_COUNTERS)
|
||||
++ThreadClassificationStats().ColumnZOverlapCalls;
|
||||
#endif
|
||||
@@ -1120,6 +1175,7 @@ public:
|
||||
#else
|
||||
if(horizontalSquared>exteriorLimit*exteriorLimit)continue;
|
||||
#endif
|
||||
if(passageProfile)++passageProfile->passageColumnDistanceCalls;
|
||||
columnDistance=PassageColumnSignedDistance(crossing,X,Y,Z);
|
||||
}
|
||||
else
|
||||
@@ -1127,6 +1183,7 @@ public:
|
||||
#if defined(VF_L30_ENABLE_COST_COUNTERS)
|
||||
++ThreadClassificationStats().ColumnSignedDistanceCalls;
|
||||
#endif
|
||||
if(passageProfile)++passageProfile->passageColumnDistanceCalls;
|
||||
columnDistance=PassageColumnSignedDistance(crossing,X,Y,Z);
|
||||
}
|
||||
composed=std::min(composed,static_cast<float>(columnDistance/Q));
|
||||
@@ -1166,12 +1223,16 @@ public:
|
||||
const std::ptrdiff_t mouthSearchBegin=std::max<std::ptrdiff_t>(0,lowerMouthIndex-8);
|
||||
const std::ptrdiff_t mouthSearchEnd=std::min<std::ptrdiff_t>(lastIndex,
|
||||
lowerMouthIndex+8);
|
||||
if(density<0.0f&&tilePassageFree
|
||||
&&!PassageMayAffectDensity(passageIndex,carveBegin,carveEnd,
|
||||
if(density<0.0f&&tilePassageFree)
|
||||
{
|
||||
if(passageProfile)++passageProfile->passageNegativeBoundChecks;
|
||||
if(!PassageMayAffectDensity(passageIndex,carveBegin,carveEnd,
|
||||
lowerMouthIndex,mouthSearchBegin,mouthSearchEnd,lastIndex,qx,qy,qz,
|
||||
-static_cast<double>(composed)*Q+0.24*qPerMetre))
|
||||
{
|
||||
continue;
|
||||
{
|
||||
if(passageProfile)++passageProfile->passageNegativeBoundRejects;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// For a positive source sample, the tube can only change it within relief reach;
|
||||
// the supported column tread is within 2.25 m of its centerline. A short-span
|
||||
@@ -1181,10 +1242,18 @@ public:
|
||||
// n'a pas cette borne, donc sa requête complète est conservée.
|
||||
const bool canCullPositiveRoute=passageTileScopeMatches&&density>=0.0f
|
||||
&&!PassageHasShortSpanSegments(passageIndex);
|
||||
if(canCullPositiveRoute&&!PassageMayAffectDensity(passageIndex,carveBegin,carveEnd,
|
||||
lowerMouthIndex,mouthSearchBegin,mouthSearchEnd,lastIndex,qx,qy,qz,
|
||||
std::max(2.25*qPerMetre,-static_cast<double>(composed)*Q
|
||||
+0.24*qPerMetre)))continue;
|
||||
if(canCullPositiveRoute)
|
||||
{
|
||||
if(passageProfile)++passageProfile->passagePositiveBoundChecks;
|
||||
if(!PassageMayAffectDensity(passageIndex,carveBegin,carveEnd,
|
||||
lowerMouthIndex,mouthSearchBegin,mouthSearchEnd,lastIndex,qx,qy,qz,
|
||||
std::max(2.25*qPerMetre,-static_cast<double>(composed)*Q
|
||||
+0.24*qPerMetre)))
|
||||
{
|
||||
if(passageProfile)++passageProfile->passagePositiveBoundRejects;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
double bestSignedDistance=std::numeric_limits<double>::infinity();
|
||||
double bestPlayerCapsuleSignedDistance=std::numeric_limits<double>::infinity();
|
||||
double bestT=0.0;
|
||||
@@ -1208,10 +1277,12 @@ public:
|
||||
lowerMouthDx*lowerMouthDx+lowerMouthDy*lowerMouthDy
|
||||
+lowerMouthDz*lowerMouthDz)/qPerMetre;
|
||||
const double lowerMouthT=std::clamp((20.0-lowerMouthDistance)/12.0,0.0,1.0);
|
||||
if(passageProfile)++passageProfile->passageSmoothstepEvaluations;
|
||||
const double lowerMouthBlend=lowerMouthT*lowerMouthT
|
||||
*(3.0-2.0*lowerMouthT);
|
||||
const auto considerCarveSegment=[&](std::size_t segmentIndex,int scanPhase)
|
||||
{
|
||||
if(passageProfile)++passageProfile->passageCarveSegmentVisits;
|
||||
const std::ptrdiff_t index=static_cast<std::ptrdiff_t>(segmentIndex);
|
||||
const auto& a=passage.controlPoints[static_cast<std::size_t>(index)];
|
||||
const auto& b=passage.controlPoints[static_cast<std::size_t>(index+1)];
|
||||
@@ -1249,6 +1320,7 @@ public:
|
||||
&&!IsPassageShortSpanSegment(passageIndex,segmentIndex+1);
|
||||
if(smoothCarveJoin)
|
||||
{
|
||||
if(passageProfile)++passageProfile->passageSmoothCarveJoins;
|
||||
const auto& p0=passage.controlPoints[static_cast<std::size_t>(index-1)];
|
||||
const auto& p3=passage.controlPoints[static_cast<std::size_t>(index+2)];
|
||||
constexpr double SmoothJoinWeight=1.0;
|
||||
@@ -1326,12 +1398,11 @@ public:
|
||||
const float mouthSurfaceRelief=VF_FieldSurfaceRelief(static_cast<float>(X)*1.5f,
|
||||
static_cast<float>(Y)*1.5f,static_cast<float>(Z)*1.5f,
|
||||
static_cast<std::uint32_t>(Owner?Owner->Seed:0)^0x4D4F5554u);
|
||||
const float mouthTerrainRelief=VF_FieldSurfaceRelief(static_cast<float>(X)*1.5f,
|
||||
static_cast<float>(Y)*1.5f,static_cast<float>(Z)*1.5f,
|
||||
static_cast<std::uint32_t>(Owner?Owner->Seed:0)^0x4D4F5554u);
|
||||
const float mouthTerrainRelief=mouthSurfaceRelief;
|
||||
const float mouthTreadFieldRelief=VF_FieldSurfaceRelief(static_cast<float>(X)*0.75f,
|
||||
static_cast<float>(Y)*0.75f,static_cast<float>(Z)*0.75f,
|
||||
static_cast<std::uint32_t>(Owner?Owner->Seed:0)^0x54485244u);
|
||||
if(passageProfile)passageProfile->passageReliefFieldCalls+=3;
|
||||
const auto routeSegmentLengthMetres=[&](std::ptrdiff_t routeIndex)
|
||||
{
|
||||
const auto& before=passage.controlPoints[static_cast<std::size_t>(routeIndex)];
|
||||
@@ -1381,6 +1452,7 @@ public:
|
||||
for(std::ptrdiff_t routeIndex=0;
|
||||
routeIndex<std::min<std::ptrdiff_t>(3,lowerMouthIndex);++routeIndex)
|
||||
{
|
||||
if(passageProfile)++passageProfile->passageMouthRouteSegmentChecks;
|
||||
const auto& before=passage.controlPoints[static_cast<std::size_t>(routeIndex)];
|
||||
const auto& after=passage.controlPoints[static_cast<std::size_t>(routeIndex+1)];
|
||||
const double ax=static_cast<double>(before.positionSubvoxels[0]);
|
||||
@@ -1402,6 +1474,7 @@ public:
|
||||
static_cast<float>((az+t*dz)/qPerMetre*1.5),
|
||||
static_cast<std::uint32_t>(Owner?Owner->Seed:0)^0x4D4F5554u)),
|
||||
-1.0,1.0);
|
||||
if(passageProfile)++passageProfile->passageReliefFieldCalls;
|
||||
const double lateralOffset=pathRelief*0.18*qPerMetre;
|
||||
projectedX-=dy/horizontalLength*lateralOffset;
|
||||
projectedY+=dx/horizontalLength*lateralOffset;
|
||||
@@ -1437,10 +1510,12 @@ public:
|
||||
if(std::isfinite(distanceFromOpenMouth))
|
||||
{
|
||||
const double fade=std::clamp(distanceFromOpenMouth/11.0,0.0,1.0);
|
||||
if(passageProfile)++passageProfile->passageSmoothstepEvaluations;
|
||||
const double smooth=fade*fade*(3.0-2.0*fade);
|
||||
mouthFloorBlend=1.0-smooth;
|
||||
}
|
||||
const double floorReliefT=std::clamp((verticalWallAlignment-0.55)/0.45,0.0,1.0);
|
||||
if(passageProfile)++passageProfile->passageSmoothstepEvaluations;
|
||||
const double floorReliefWeight=floorReliefT*floorReliefT
|
||||
*(3.0-2.0*floorReliefT);
|
||||
const double floorReliefOffset=std::clamp(
|
||||
@@ -1466,6 +1541,7 @@ public:
|
||||
constexpr double mouthCarveFadeMetres=8.0;
|
||||
mouthCarveFade=std::clamp(distanceFromOpenMouth/mouthCarveFadeMetres,
|
||||
0.0,1.0);
|
||||
if(passageProfile)++passageProfile->passageSmoothstepEvaluations;
|
||||
mouthCarveFade=mouthCarveFade*mouthCarveFade*(3.0-2.0*mouthCarveFade);
|
||||
}
|
||||
const double floorAlignment=std::max(verticalWallAlignment,
|
||||
@@ -1477,6 +1553,7 @@ public:
|
||||
const double belowFloor=(mouthEntryFloorQ-qz)/qPerMetre;
|
||||
const double supportRamp=std::clamp(belowFloor/0.15,0.0,1.0);
|
||||
const double supportFade=std::clamp((1.0-belowFloor)/0.35,0.0,1.0);
|
||||
if(passageProfile)passageProfile->passageSmoothstepEvaluations+=2;
|
||||
const double rampSmooth=supportRamp*supportRamp*(3.0-2.0*supportRamp);
|
||||
const double fadeSmooth=supportFade*supportFade*(3.0-2.0*supportFade);
|
||||
floorBelowWeight=rampSmooth*fadeSmooth;
|
||||
@@ -1485,6 +1562,7 @@ public:
|
||||
// former bore-wide alignment feather left a straight platform-width
|
||||
// bridge across the FI mouth.
|
||||
double mouthWalkLaneT=std::clamp((1.05-mouthEntryPlanDistance)/0.60,0.0,1.0);
|
||||
if(passageProfile)++passageProfile->passageSmoothstepEvaluations;
|
||||
mouthWalkLaneT=mouthWalkLaneT*mouthWalkLaneT*(3.0-2.0*mouthWalkLaneT);
|
||||
const double mouthWalkLaneWeight=upperMouthFloorFade?mouthWalkLaneT:0.0;
|
||||
const double floorCarveWeight=1.0-(1.0-mouthCarveFade)
|
||||
@@ -1496,6 +1574,7 @@ public:
|
||||
const float composedAfterCarve=composed;
|
||||
const auto considerSupportSegment=[&](std::ptrdiff_t index)
|
||||
{
|
||||
if(passageProfile)++passageProfile->passageSupportSegmentVisits;
|
||||
const auto& segmentA=passage.controlPoints[static_cast<std::size_t>(index)];
|
||||
const auto& segmentB=passage.controlPoints[static_cast<std::size_t>(index+1)];
|
||||
const double dx=static_cast<double>(segmentB.positionSubvoxels[0])-segmentA.positionSubvoxels[0];
|
||||
@@ -1538,6 +1617,7 @@ public:
|
||||
static_cast<double>(segmentB.radiusSubvoxels)-segmentA.radiusSubvoxels);
|
||||
if(smoothSupportJoin)
|
||||
{
|
||||
if(passageProfile)++passageProfile->passageSmoothSupportJoins;
|
||||
const auto& p0=passage.controlPoints[static_cast<std::size_t>(index-1)];
|
||||
const auto& p3=passage.controlPoints[static_cast<std::size_t>(index+2)];
|
||||
const double t2=supportT*supportT,t3=t2*supportT;
|
||||
@@ -1568,6 +1648,7 @@ public:
|
||||
static_cast<float>(centerZ/qPerMetre*1.5),
|
||||
static_cast<std::uint32_t>(Owner?Owner->Seed:0)^0x4D4F5554u)),
|
||||
-1.0,1.0);
|
||||
if(passageProfile)++passageProfile->passageReliefFieldCalls;
|
||||
treadRelief=pathRelief;
|
||||
const double lateralOffset=pathRelief*0.18*qPerMetre;
|
||||
centerX-=dy/horizontalLength*lateralOffset;
|
||||
@@ -1660,8 +1741,10 @@ public:
|
||||
suffixMetres+(1.0-supportT)*supportSegmentLength);
|
||||
}
|
||||
double mouthReliefT=std::clamp(distanceFromMouthMetres/8.0,0.0,1.0);
|
||||
if(passageProfile)++passageProfile->passageSmoothstepEvaluations;
|
||||
mouthReliefT=mouthReliefT*mouthReliefT*(3.0-2.0*mouthReliefT);
|
||||
double mouthSupportT=std::clamp((distanceFromMouthMetres-0.25)/4.0,0.0,1.0);
|
||||
if(passageProfile)++passageProfile->passageSmoothstepEvaluations;
|
||||
mouthSupportT=mouthSupportT*mouthSupportT*(3.0-2.0*mouthSupportT);
|
||||
const double mouthSupportWidth=std::lerp(0.82,1.00,mouthSupportT);
|
||||
const double supportHalfWidthMetres=mouthTread?mouthSupportWidth
|
||||
@@ -1687,6 +1770,7 @@ public:
|
||||
?std::clamp((horizontalDistance
|
||||
-(supportHalfWidth-supportEdgeBlendMetres*qPerMetre))
|
||||
/(supportEdgeBlendMetres*qPerMetre),0.0,1.0):0.0;
|
||||
if(passageProfile)++passageProfile->passageSmoothstepEvaluations;
|
||||
const double supportEdgeSmooth=supportEdgeT*supportEdgeT
|
||||
*(3.0-2.0*supportEdgeT);
|
||||
const double mouthSupportThicknessVoxels=std::lerp(
|
||||
@@ -1700,6 +1784,7 @@ public:
|
||||
{
|
||||
const double mouthBlendT=std::clamp(
|
||||
distanceFromMouthMetres/0.75,0.0,1.0);
|
||||
if(passageProfile)++passageProfile->passageSmoothstepEvaluations;
|
||||
mouthSupportBlend=0.75+0.25*mouthBlendT*mouthBlendT
|
||||
*(3.0-2.0*mouthBlendT);
|
||||
}
|
||||
@@ -1802,12 +1887,17 @@ public:
|
||||
const double capsuleQueryZ=bestSupportUsesSignedDistance?bestSupportFloorQ:qz;
|
||||
const bool supportCapsuleProfileAvailable=passageIndex<PassageNearestNodes.size()
|
||||
&&!PassageNearestNodes[passageIndex].empty();
|
||||
const bool capsuleAllowsSupport=(passageTileScopeMatches
|
||||
bool capsuleAllowsSupport;
|
||||
if(passageTileScopeMatches
|
||||
||(bestSupportUsesSignedDistance&&supportCapsuleProfileAvailable))
|
||||
?!PassagePlayerCapsuleBlocksSupport(passageIndex,carveBegin,carveEnd,
|
||||
lowerMouthIndex,mouthSearchBegin,mouthSearchEnd,lastIndex,qx,qy,
|
||||
capsuleQueryZ,qPerMetre,capsuleClearanceMetres)
|
||||
:bestPlayerCapsuleSignedDistance>=capsuleClearanceMetres*qPerMetre;
|
||||
{
|
||||
if(passageProfile)++passageProfile->passageSupportCapsuleChecks;
|
||||
capsuleAllowsSupport=!PassagePlayerCapsuleBlocksSupport(passageIndex,
|
||||
carveBegin,carveEnd,lowerMouthIndex,mouthSearchBegin,mouthSearchEnd,
|
||||
lastIndex,qx,qy,capsuleQueryZ,qPerMetre,capsuleClearanceMetres);
|
||||
}
|
||||
else capsuleAllowsSupport=
|
||||
bestPlayerCapsuleSignedDistance>=capsuleClearanceMetres*qPerMetre;
|
||||
bool supportApplied=false;
|
||||
bool supportNeedsDeferral=false;
|
||||
// Outside bridge and short-span segments, add the tread only where the
|
||||
@@ -1826,9 +1916,12 @@ public:
|
||||
// and noise. The capsule test keeps neighbouring coils open.
|
||||
const bool nativeRestoreCapsuleClear=density<0.0f
|
||||
&&bestSupportNativeBlend>0.0&&capsuleAllowsSupport
|
||||
&&!PassagePlayerCapsuleBlocksSupport(passageIndex,carveBegin,
|
||||
carveEnd,lowerMouthIndex,mouthSearchBegin,mouthSearchEnd,
|
||||
lastIndex,qx,qy,qz,qPerMetre,capsuleClearanceMetres);
|
||||
&&([&](){
|
||||
if(passageProfile)++passageProfile->passageSupportCapsuleChecks;
|
||||
return !PassagePlayerCapsuleBlocksSupport(passageIndex,carveBegin,
|
||||
carveEnd,lowerMouthIndex,mouthSearchBegin,mouthSearchEnd,
|
||||
lastIndex,qx,qy,qz,qPerMetre,capsuleClearanceMetres);
|
||||
})();
|
||||
if(nativeRestoreCapsuleClear)
|
||||
{
|
||||
const double nativeSurfaceDetail=std::clamp(
|
||||
@@ -1851,6 +1944,7 @@ public:
|
||||
}
|
||||
if(supportApplied)
|
||||
{
|
||||
if(passageProfile)++passageProfile->passageOtherCapsuleChecks;
|
||||
if(PassageOtherCapsuleBlocksSupport(passageIndex,
|
||||
bestSupportSegmentIndex,qx,qy,qz,qPerMetre,capsuleClearanceMetres))
|
||||
{
|
||||
@@ -1875,8 +1969,10 @@ public:
|
||||
const bool tileHasSupport=passageTileScopeMatches;
|
||||
if(!tileHasSupport
|
||||
||ActivePassageTileQueries->hasMouthSupport(passageIndex))
|
||||
composeRouteMouthSupport(passage.upperStrateIndex,
|
||||
passage.upperMouthSubvoxels);
|
||||
{
|
||||
if(passageProfile)++passageProfile->passageMouthSupportCalls;
|
||||
composeRouteMouthSupport(passageIndex);
|
||||
}
|
||||
}
|
||||
composed=std::min(composed,deferredSupportDensity);
|
||||
return composed;
|
||||
@@ -3332,6 +3428,11 @@ private:
|
||||
std::size_t passageIndex=0,segmentIndex=0;
|
||||
bool bColumn=false,bMouthSupport=false;
|
||||
};
|
||||
struct FPassageMouthSupportSegment
|
||||
{
|
||||
std::array<std::int64_t,4> a{},b{};
|
||||
bool hasRadius=false;
|
||||
};
|
||||
struct FPassageNearestNode
|
||||
{
|
||||
std::array<double,3> minimum{},maximum{};
|
||||
@@ -4309,6 +4410,8 @@ private:
|
||||
PassageNearestNodes.clear();
|
||||
PassageProofBounds.clear();PassageProofNodes.clear();
|
||||
PassageNearestNodes.resize(ResolvedLayout?ResolvedLayout->passages.size():0);
|
||||
PassageMouthSupportRoutes.clear();
|
||||
PassageMouthSupportRoutes.resize(ResolvedLayout?ResolvedLayout->passages.size():0);
|
||||
PassageLaterBoreSources.clear();
|
||||
PassageColumnSegmentFlags.clear();PassageShortSpanSegmentFlags.clear();
|
||||
PassageHasShortSpanFlags.clear();
|
||||
@@ -4316,6 +4419,7 @@ private:
|
||||
constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
||||
const double voxelsPerMetre=Owner&&Owner->CoreVoxelSizeMicrons>0
|
||||
?1000000.0/Owner->CoreVoxelSizeMicrons:4.0;
|
||||
const double qPerMetre=Q*voxelsPerMetre;
|
||||
const double bridgeHalfWidthVoxels=3.5*voxelsPerMetre;
|
||||
const double bridgeFootDepthVoxels=(5.0-0.13)*voxelsPerMetre;
|
||||
PassageColumnSegmentFlags.resize(ResolvedLayout->passages.size());
|
||||
@@ -4367,9 +4471,12 @@ private:
|
||||
{
|
||||
std::array<const VoxelForgeCore::ResolvedLayoutAnchorV2*,5> route{};
|
||||
std::size_t routeCount=0;
|
||||
std::vector<const VoxelForgeCore::ResolvedLayoutAnchorV2*> allRoutePoints;
|
||||
allRoutePoints.reserve(upperStratum->anchors.size());
|
||||
for(const auto& anchor:upperStratum->anchors)
|
||||
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
|
||||
{
|
||||
allRoutePoints.push_back(&anchor);
|
||||
if(routeCount<route.size())route[routeCount++]=&anchor;
|
||||
else
|
||||
{
|
||||
@@ -4379,6 +4486,25 @@ private:
|
||||
}
|
||||
if(routeCount>=2)
|
||||
{
|
||||
auto& supportRoutes=PassageMouthSupportRoutes[passageIndex];
|
||||
supportRoutes.reserve(allRoutePoints.size()-1);
|
||||
for(std::size_t index=0;index+1<allRoutePoints.size();++index)
|
||||
{
|
||||
FPassageMouthSupportSegment segment;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
segment.a[axis]=allRoutePoints[index]->values[axis];
|
||||
segment.b[axis]=allRoutePoints[index+1]->values[axis];
|
||||
}
|
||||
segment.hasRadius=allRoutePoints[index]->values.size()>=4
|
||||
&&allRoutePoints[index+1]->values.size()>=4;
|
||||
if(segment.hasRadius)
|
||||
{
|
||||
segment.a[3]=allRoutePoints[index]->values[3];
|
||||
segment.b[3]=allRoutePoints[index+1]->values[3];
|
||||
}
|
||||
supportRoutes.push_back(segment);
|
||||
}
|
||||
const double mouthReach=13.0*voxelsPerMetre*Q;
|
||||
double minimumZ=std::numeric_limits<double>::infinity();
|
||||
double maximumZ=-std::numeric_limits<double>::infinity();
|
||||
@@ -4395,17 +4521,32 @@ private:
|
||||
supportProof.maximum={(mouthX+mouthReach)/Q,(mouthY+mouthReach)/Q,
|
||||
(maximumZ+3.0*voxelsPerMetre*Q)/Q};
|
||||
PassageProofBounds.push_back(supportProof);
|
||||
const std::array<std::int64_t,3> featureMin={
|
||||
static_cast<std::int64_t>(std::floor(supportProof.minimum[0]*Q)),
|
||||
static_cast<std::int64_t>(std::floor(supportProof.minimum[1]*Q)),
|
||||
static_cast<std::int64_t>(std::floor(supportProof.minimum[2]*Q))};
|
||||
const std::array<std::int64_t,3> featureMax={
|
||||
static_cast<std::int64_t>(std::ceil(supportProof.maximum[0]*Q)),
|
||||
static_cast<std::int64_t>(std::ceil(supportProof.maximum[1]*Q)),
|
||||
static_cast<std::int64_t>(std::ceil(supportProof.maximum[2]*Q))};
|
||||
PassageFeatureBounds.push_back({static_cast<std::uint64_t>(PassageTileFeatures.size()),
|
||||
featureMin,featureMax});
|
||||
PassageTileFeatures.push_back({passageIndex,0,false,true});
|
||||
for(std::size_t index=0;index<supportRoutes.size();++index)
|
||||
{
|
||||
const auto& segment=supportRoutes[index];
|
||||
constexpr double HorizontalReachMetres=1.01;
|
||||
constexpr double MouthSupportTopOffsetMetres=0.68;
|
||||
constexpr double MouthSupportBottomOffsetMetres=2.58;
|
||||
const std::array<std::int64_t,3> featureMinimum={
|
||||
std::min(segment.a[0],segment.b[0])-static_cast<std::int64_t>(
|
||||
std::ceil(HorizontalReachMetres*qPerMetre)),
|
||||
std::min(segment.a[1],segment.b[1])-static_cast<std::int64_t>(
|
||||
std::ceil(HorizontalReachMetres*qPerMetre)),
|
||||
static_cast<std::int64_t>(std::floor(
|
||||
std::min(segment.a[2],segment.b[2])
|
||||
-MouthSupportBottomOffsetMetres*qPerMetre))};
|
||||
const std::array<std::int64_t,3> featureMaximum={
|
||||
std::max(segment.a[0],segment.b[0])+static_cast<std::int64_t>(
|
||||
std::ceil(HorizontalReachMetres*qPerMetre)),
|
||||
std::max(segment.a[1],segment.b[1])+static_cast<std::int64_t>(
|
||||
std::ceil(HorizontalReachMetres*qPerMetre)),
|
||||
static_cast<std::int64_t>(std::ceil(
|
||||
std::max(segment.a[2],segment.b[2])
|
||||
-MouthSupportTopOffsetMetres*qPerMetre))};
|
||||
PassageFeatureBounds.push_back({static_cast<std::uint64_t>(
|
||||
PassageTileFeatures.size()),featureMinimum,featureMaximum});
|
||||
PassageTileFeatures.push_back({passageIndex,index,false,true});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4414,7 +4555,6 @@ private:
|
||||
// hot query can test a short list of physical bounds instead of walking a
|
||||
// full spatial tree for every density sample.
|
||||
PassageLaterBoreSources.resize(ResolvedLayout->passages.size());
|
||||
const double qPerMetre=Q*voxelsPerMetre;
|
||||
for(std::size_t sourcePassageIndex=0;
|
||||
sourcePassageIndex<ResolvedLayout->passages.size();++sourcePassageIndex)
|
||||
{
|
||||
@@ -11170,6 +11310,7 @@ private:
|
||||
std::vector<FPassageFeatureNode> PassageFeatureNodes;
|
||||
bool PassageFeatureBoundsFailOpen=false;
|
||||
std::vector<std::vector<FPassageNearestNode>> PassageNearestNodes;
|
||||
std::vector<std::vector<FPassageMouthSupportSegment>> PassageMouthSupportRoutes;
|
||||
std::vector<std::vector<FPassageLaterBoreSource>> PassageLaterBoreSources;
|
||||
std::vector<FPassageProofBound> PassageProofBounds;
|
||||
std::vector<FPassageProofNode> PassageProofNodes;
|
||||
|
||||
@@ -2620,7 +2620,22 @@ namespace VoxelForgeCore
|
||||
// GenerateMesh n'échantillonne que origin..origin+extent et la marge de normale d'un pas ;
|
||||
// queryMinimum/queryMaximum ci-dessus couvrent exactement ce domaine inclusif.
|
||||
const FStandaloneShapeWorld::FScopedPassageTileQueries passageFeatureScope(
|
||||
tileShape,queryMinimum,queryMaximum);
|
||||
tileShape,queryMinimum,queryMaximum,
|
||||
options.profilePassageComposer?&out.diagnostics:nullptr);
|
||||
if(options.profilePassageComposer)
|
||||
{
|
||||
out.diagnostics.passageComposerProfiled=true;
|
||||
out.diagnostics.passageTileFeatureCount=passageFeatureScope.featureCount();
|
||||
if(impl_->resolvedLayout)
|
||||
{
|
||||
out.diagnostics.passageTileRouteSegmentCandidates=
|
||||
passageFeatureScope.routeSegmentCandidateCount();
|
||||
for(std::size_t passageIndex=0;
|
||||
passageIndex<impl_->resolvedLayout->passages.size();++passageIndex)
|
||||
out.diagnostics.passageTileMouthSegmentCandidates+=
|
||||
passageFeatureScope.mouthSupportSegments(passageIndex).size();
|
||||
}
|
||||
}
|
||||
const bool bDiagnosticColumnSampling=options.diagnosticGlobalOctaveDrop!=0
|
||||
||options.diagnosticColumnInterpolationStride>1;
|
||||
const std::uint32_t PreviousOctaveDrop=bDiagnosticColumnSampling
|
||||
|
||||
@@ -4,10 +4,11 @@ import argparse
|
||||
import importlib.util
|
||||
import math
|
||||
from collections import defaultdict, deque
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
BASELINES = ROOT / "Standalone/Tools/verify/baselines"
|
||||
BASELINES = Path(os.environ.get("VOXELFORGE_VERIFY_OUT", ROOT / "Saved/Linux/l37")) / "baselines"
|
||||
PROFILE_PATH = ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingTerraceL32.py"
|
||||
SPEC = importlib.util.spec_from_file_location("ring_profile_l37", PROFILE_PATH)
|
||||
PROFILE = importlib.util.module_from_spec(SPEC)
|
||||
|
||||
@@ -13,9 +13,9 @@ from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
BASELINES = ROOT / "Standalone/Tools/verify/baselines"
|
||||
OUT = Path(os.environ.get("VOXELFORGE_L37_OUT",
|
||||
os.environ.get("VOXELFORGE_VERIFY_OUT", ROOT / "Saved/Linux/l37")))
|
||||
BASELINES = OUT / "baselines"
|
||||
BIN = OUT / "bin"
|
||||
CAPTURES = Path(os.environ.get("VOXELFORGE_L37_CAPTURE_DIR",
|
||||
OUT / "captures"))
|
||||
|
||||
@@ -8,10 +8,11 @@ import json
|
||||
import math
|
||||
import re
|
||||
import argparse
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
BASELINES = ROOT / "Standalone/Tools/verify/baselines"
|
||||
BASELINES = Path(os.environ.get("VOXELFORGE_VERIFY_OUT", ROOT / "Saved/Linux/l37")) / "baselines"
|
||||
BASE_PATH = ROOT / "Standalone/Research/WorldAssemblerM3/Linux37/RunMouthFragmentAuditL37.py"
|
||||
SPEC = importlib.util.spec_from_file_location("mouth_fragment_l37", BASE_PATH)
|
||||
BASE = importlib.util.module_from_spec(SPEC)
|
||||
|
||||
@@ -10,8 +10,8 @@ import os
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
BASELINES = ROOT / "Standalone/Tools/verify/baselines"
|
||||
OUT = Path(os.environ.get("VOXELFORGE_VERIFY_OUT", ROOT / "Saved/Linux/l37"))
|
||||
BASELINES = OUT / "baselines"
|
||||
BIN = Path(os.environ.get("VOXELFORGE_VERIFY_HEADLESS",
|
||||
OUT / "bin/VoxelForgeCoreHeadless-l37-final-clang"))
|
||||
OWNER = Path(os.environ.get("VOXELFORGE_OWNER_DATA",
|
||||
|
||||
@@ -14,7 +14,7 @@ import time
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
BASELINES = ROOT / "Standalone/Tools/verify/baselines"
|
||||
BASELINES = Path(os.environ.get("VOXELFORGE_VERIFY_OUT", ROOT / "Saved/Linux/l47")) / "baselines"
|
||||
VERIFY = ROOT / "Standalone/Tools/verify"
|
||||
LINUX37 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux37"
|
||||
sys.path.insert(0, str(VERIFY))
|
||||
|
||||
@@ -6,12 +6,13 @@ import importlib.util
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
BASELINES = ROOT / "Standalone/Tools/verify/baselines"
|
||||
BASELINES = Path(os.environ.get("VOXELFORGE_VERIFY_OUT", ROOT / "Saved/Linux/l47")) / "baselines"
|
||||
PROFILE_PATH = ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingTerraceL32.py"
|
||||
spec = importlib.util.spec_from_file_location("ring_profile", PROFILE_PATH)
|
||||
if spec is None or spec.loader is None:
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# Linux 72 report
|
||||
|
||||
## Unreal timeline
|
||||
|
||||
Three L71 and three L72 traces used the same 2,850 tile keys. The readback value `streaming_background_worker_cap=1` selects the TaskGraph background lane; Unreal reported eight background workers and eight effective tile slots. The delay was queue admission and result draining, not RMC construction. Raising ready queue depth from 8 to 16 and the startup apply count cap from 12 to 24 moved readiness forward. The hard game-thread apply budget stays 14 ms, and the moving-player admission cap still prevents a ready backlog during fast travel.
|
||||
|
||||
| Median milestone | L71 | L72 |
|
||||
|---|---:|---:|
|
||||
| Last tile requested | 4.577 s | 3.680 s |
|
||||
| Last worker started | 4.583 s | 3.696 s |
|
||||
| Last worker finished | 4.597 s | 3.712 s |
|
||||
| Last tile applied | 4.754 s | 3.724 s |
|
||||
| Initial set ready | 4.775 s | 3.741 s |
|
||||
| Walkable gate ready | 1.068 s | 1.107 s |
|
||||
| Queue p95 | 122.7 ms | 34.8 ms |
|
||||
| Request-to-apply p95 | 139.1 ms | 45.3 ms |
|
||||
|
||||
The worker-finish-to-last-apply tail fell from 157 ms to 11 ms. L72 per-frame apply work was p95 0.73 ms, max 6.26 ms, below the 14 ms cap; the cap was not hit. Commandlet frame interval p95 was 18.85 ms. Initial-set readiness still takes 3.74 s (about 199 p95 frame intervals), so the roughly 1 s target is not met. Against the 2.41 s headless stride-0 whole-view median, about 1.33 s remains in the Unreal path. RMC build p95 was 0.32 ms.
|
||||
|
||||
Headless whole-view timing used three interleaved before/after pairs per stratum:
|
||||
|
||||
| Stratum | Tiles | Before median | After median |
|
||||
|---|---:|---:|---:|
|
||||
| 0 | 2,850 | 2.538 s | 2.412 s |
|
||||
| 1 | 2,249 | 6.382 s | 6.343 s |
|
||||
| 2 | 2,233 | 5.718 s | 5.443 s |
|
||||
| 3 | 2,329 | 2.661 s | 2.624 s |
|
||||
|
||||
## Passage composer
|
||||
|
||||
The L71 40× compares 18.8 ms for nonempty passage tiles with 0.5 ms across all LOD0 tiles; 441 of 512 LOD0 tiles were empty. Empty tiles can stop after classification, while a nonempty passage tile fills and meshes a density grid. It is not a matched per-sample comparison. These profiles do not isolate the passage-specific share of total tile time: passage controls have about twice the samples of the terrain control, and no matched nonempty terrain control was profiled. Native calls were 27,367 for terrain (17,680 samples) and 43,243/44,767 for passage tiles (36,439/37,975 samples).
|
||||
|
||||
Within the composer, route-mouth checks were a large avoidable scan. The shared composer now filters mouth-support segments against per-tile bounds and reuses the identical mouth relief result. Passage carve/support visits and smoothstep counts stayed the same. The exact counters are below; they show reduced work, but do not identify the dominant timed composer term.
|
||||
|
||||
| Tile (LOD0) | Samples | Native calls before/after | Mouth route checks before → after | Relief calls before → after |
|
||||
|---|---:|---:|---:|---:|
|
||||
| Terrain `(5,-2,-47)` | 17,680 | 27,367 | — | — |
|
||||
| Passage `(1,0,-47)` | 36,439 | 43,243 | 612,242 → 123,090 | 384,444 → 343,414 |
|
||||
| Passage `(0,1,-47)` | 37,975 | 44,767 | 1,042,612 → 131,334 | 407,910 → 364,554 |
|
||||
|
||||
Field calls and world hashes are unchanged. The two passage samples reduced route checks by 80% and 87%. A single instrumented worker-time comparison was mixed (100.0→91.8 ms and 155.2→157.1 ms), so it does not establish a stable per-tile wall-time gain. L71 identified near-field density-grid sampling and meshing as the largest cost; L72 does not isolate which composer term takes the most time.
|
||||
|
||||
## Integrity and housekeeping
|
||||
|
||||
- Hashes matched before/after at W1, W8, and W12 with Clang and GCC: all 2,850 keys and density, mesh, payload, and topology hashes matched.
|
||||
- Fast tier passed in 270.2 s (269 PASS, 5 DIAGNOSTIC); gates and thresholds are unchanged.
|
||||
- Compressed all 11 verification meshes with Python `lzma`: 78.54 MB → 18.37 MB (23.4% of original). `verify.py` expands them into its output baselines folder before use.
|
||||
|
||||
## Source changes
|
||||
|
||||
- `Source/VoxelForge/Private/VoxelWorld.cpp`: queue depth and startup apply count tuning, retaining the 14 ms hard budget and moving-player admission behavior.
|
||||
- `Standalone/Core/src/ShapeWorldInternal.h`: tile-local passage support segment filtering and reused mouth relief evaluation.
|
||||
- `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h`, `Standalone/Core/src/VoxelForgeCore.cpp`, `Standalone/Source/StreamingWorld.h`, `Standalone/Source/StreamBenchmarkPipeline.h`, `Standalone/Source/StreamBenchmarkHeadless.cpp`: opt-in per-tile composer counters.
|
||||
- `Standalone/Tools/verify/verify.py` and the Linux37/Linux47 audit scripts: decompressed baseline path handling.
|
||||
|
||||
## Failed and rejected runs
|
||||
|
||||
- The first Unreal build referenced unavailable .NET 8.0.300; retry with installed 8.0.412 succeeded.
|
||||
- An initial hash invocation omitted `--stream-hash-tiles` and emitted no hash rows; corrected six-config matrix passed.
|
||||
- The first fast-tier attempt lacked built executables, then an audit helper still read the compressed baseline path. Both were corrected; the rerun passed.
|
||||
- A no-feature passage cull changed density hashes in 433 tiles and was reverted. A point-bound trial rejected no negative LOD0 samples, and a carve-seed trial added one candidate visit per query; both were dropped. A depth-32 queue probe worsened LOD0 request-to-apply p95 (102.6 ms) and walkable readiness (1.204 s), so depth 16 was retained.
|
||||
@@ -46,6 +46,7 @@ namespace
|
||||
bool ScreenSpaceGate=false;
|
||||
bool AuditUniformBlockProofs=false;
|
||||
bool ProfileLodWork=false;
|
||||
bool ProfilePassageComposer=false;
|
||||
bool InstrumentTileDrain=false;
|
||||
bool ExactMidpointDensityMemo=false;
|
||||
bool ActiveLaneBatching=false;
|
||||
@@ -1439,6 +1440,7 @@ namespace
|
||||
" --audit-uniform-block-proofs (fill and check every interval-certified block)\n"
|
||||
" --profile-lod-work (report per-LOD density sample counts)\n"
|
||||
" --instrument-tile-drain (opt-in CpuTileTimeline and per-tile stages/counters)\n"
|
||||
" --profile-passage-composer (emit per-tile passage work counters)\n"
|
||||
" --exact-midpoint-density-memo (exact-input midpoint query memo A/B)\n"
|
||||
" --active-lane-batching --proof-block-cells=N (proof-masked density fill A/B)\n"
|
||||
" --parallel-sign-edge-repair --serial-sign-edge-repair\n"
|
||||
@@ -1494,6 +1496,8 @@ namespace
|
||||
else if(arg=="--audit-uniform-block-proofs")options.AuditUniformBlockProofs=true;
|
||||
else if(arg=="--profile-lod-work")options.ProfileLodWork=true;
|
||||
else if(arg=="--instrument-tile-drain")options.InstrumentTileDrain=true;
|
||||
else if(arg=="--profile-passage-composer")
|
||||
{options.ProfilePassageComposer=true;options.InstrumentTileDrain=true;}
|
||||
else if(arg=="--exact-midpoint-density-memo")options.ExactMidpointDensityMemo=true;
|
||||
else if(arg=="--active-lane-batching")options.ActiveLaneBatching=true;
|
||||
else if(arg=="--parallel-sign-edge-repair")
|
||||
@@ -2214,5 +2218,5 @@ int main(int argc,char** argv)
|
||||
options.ProofBlockCells,options.HasSignEdgeRepairOverride
|
||||
?options.ParallelSignEdgeRepair:options.FloatingIslandsRecipe,
|
||||
options.AuditNativeLattice,options.LatticeOrphanControl,
|
||||
options.CaptureSkipWalkability);
|
||||
options.CaptureSkipWalkability,options.ProfilePassageComposer);
|
||||
}
|
||||
@@ -1872,7 +1872,8 @@ namespace StandaloneStreamBenchmark
|
||||
bool bExactMidpointDensityMemo=false,
|
||||
bool bActiveLaneBatching=false,int32 ProofBlockCells=8,
|
||||
bool bParallelSignEdgeRepair=false,bool bAuditNativeLattice=false,
|
||||
bool bLatticeOrphanControl=false,bool bCaptureSkipWalkability=false)
|
||||
bool bLatticeOrphanControl=false,bool bCaptureSkipWalkability=false,
|
||||
bool bProfilePassageComposer=false)
|
||||
{
|
||||
using Clock=std::chrono::steady_clock;
|
||||
FStandaloneOwnerWorld& World=*OwnerWorld;
|
||||
@@ -2037,6 +2038,7 @@ namespace StandaloneStreamBenchmark
|
||||
FarHorizonLOD8InteriorStepVoxels,bUseSharedColumnCache);
|
||||
Streamer.SetUniformBlockProofAuditEnabled(bAuditUniformBlockProofs);
|
||||
Streamer.SetTileDrainInstrumentationEnabled(bInstrumentTileDrain);
|
||||
Streamer.SetPassageComposerProfilingEnabled(bProfilePassageComposer);
|
||||
Streamer.SetExactMidpointDensityMemoEnabled(bExactMidpointDensityMemo);
|
||||
Streamer.SetActiveLaneBatchingEnabled(bActiveLaneBatching);
|
||||
Streamer.SetProofBlockCells(ProofBlockCells);
|
||||
|
||||
@@ -710,6 +710,8 @@ public:
|
||||
{bProfileMesherStages=Enabled;}
|
||||
void SetTileDrainInstrumentationEnabled(bool Enabled)
|
||||
{bInstrumentTileDrain=Enabled;}
|
||||
void SetPassageComposerProfilingEnabled(bool Enabled)
|
||||
{bProfilePassageComposer=Enabled;}
|
||||
void SetExactMidpointDensityMemoEnabled(bool Enabled)
|
||||
{bExactMidpointDensityMemo=Enabled;}
|
||||
void SetActiveLaneBatchingEnabled(bool Enabled)
|
||||
@@ -890,6 +892,7 @@ private:
|
||||
Options.useSharedColumnCache=bSharedColumnCache;
|
||||
Options.lod8HorizonInteriorStepVoxels=Lod8HorizonInteriorStepVoxels;
|
||||
Options.profileMesherStages=(bProfileMesherStages||bInstrumentTileDrain)&&!ForceGenerate;
|
||||
Options.profilePassageComposer=bProfilePassageComposer;
|
||||
Options.instrumentTileDrain=bInstrumentTileDrain&&!ForceGenerate;
|
||||
Options.exactMidpointDensityMemo=bExactMidpointDensityMemo&&!ForceGenerate;
|
||||
Options.activeLaneBatching=bActiveLaneBatching&&!ForceGenerate;
|
||||
@@ -1100,6 +1103,34 @@ private:
|
||||
static_cast<unsigned long long>(D.islandSharedCacheComputes),
|
||||
static_cast<unsigned long long>(D.islandSharedCacheConflictReplacements),
|
||||
static_cast<double>(D.islandSharedCacheLockWaitNanoseconds)/1000.0);
|
||||
if(D.passageComposerProfiled)
|
||||
std::printf("[PassageComposerTile] key=(%d,%d,%d,lod%d) worker_ms=%.3f samples=%llu native_field_calls=%llu features=%llu route_candidates=%llu mouth_candidates=%llu queries=%llu passage_iterations=%llu upper_mouth_checks=%llu column_candidates=%llu column_distance_calls=%llu negative_bounds=%llu negative_rejects=%llu positive_bounds=%llu positive_rejects=%llu carve_segment_visits=%llu smooth_carve_joins=%llu support_segment_visits=%llu smooth_support_joins=%llu support_capsule_checks=%llu later_bore_checks=%llu other_capsule_checks=%llu mouth_support_calls=%llu mouth_route_segment_checks=%llu relief_field_calls=%llu smoothstep_evaluations=%llu\n",
|
||||
Event.Key.Coord.X,Event.Key.Coord.Y,Event.Key.Coord.Z,Event.Key.Level,
|
||||
Event.WorkerMilliseconds,static_cast<unsigned long long>(Event.DensitySamples),
|
||||
static_cast<unsigned long long>(D.fieldCalls),
|
||||
static_cast<unsigned long long>(D.passageTileFeatureCount),
|
||||
static_cast<unsigned long long>(D.passageTileRouteSegmentCandidates),
|
||||
static_cast<unsigned long long>(D.passageTileMouthSegmentCandidates),
|
||||
static_cast<unsigned long long>(D.passageComposerQueries),
|
||||
static_cast<unsigned long long>(D.passageIterations),
|
||||
static_cast<unsigned long long>(D.passageUpperMouthFilterChecks),
|
||||
static_cast<unsigned long long>(D.passageColumnCandidates),
|
||||
static_cast<unsigned long long>(D.passageColumnDistanceCalls),
|
||||
static_cast<unsigned long long>(D.passageNegativeBoundChecks),
|
||||
static_cast<unsigned long long>(D.passageNegativeBoundRejects),
|
||||
static_cast<unsigned long long>(D.passagePositiveBoundChecks),
|
||||
static_cast<unsigned long long>(D.passagePositiveBoundRejects),
|
||||
static_cast<unsigned long long>(D.passageCarveSegmentVisits),
|
||||
static_cast<unsigned long long>(D.passageSmoothCarveJoins),
|
||||
static_cast<unsigned long long>(D.passageSupportSegmentVisits),
|
||||
static_cast<unsigned long long>(D.passageSmoothSupportJoins),
|
||||
static_cast<unsigned long long>(D.passageSupportCapsuleChecks),
|
||||
static_cast<unsigned long long>(D.passageLaterBoreChecks),
|
||||
static_cast<unsigned long long>(D.passageOtherCapsuleChecks),
|
||||
static_cast<unsigned long long>(D.passageMouthSupportCalls),
|
||||
static_cast<unsigned long long>(D.passageMouthRouteSegmentChecks),
|
||||
static_cast<unsigned long long>(D.passageReliefFieldCalls),
|
||||
static_cast<unsigned long long>(D.passageSmoothstepEvaluations));
|
||||
}
|
||||
void RecordTimelineEvent(FStandaloneCpuTileTimelineEvent::EType Type,
|
||||
const FVoxelTileKey& Key,int32 WorkerId,size_t QueueDepth,size_t ActiveWorkers,
|
||||
@@ -1138,6 +1169,7 @@ private:
|
||||
int32 Lod8HorizonInteriorStepVoxels=0;
|
||||
bool bProfileMesherStages=false;
|
||||
bool bInstrumentTileDrain=false;
|
||||
bool bProfilePassageComposer=false;
|
||||
bool bExactMidpointDensityMemo=false;
|
||||
bool bActiveLaneBatching=false;
|
||||
int32 ProofBlockCells=8;
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -6,6 +6,7 @@ import argparse
|
||||
import atexit
|
||||
import hashlib
|
||||
import json
|
||||
import lzma
|
||||
import math
|
||||
import os
|
||||
from pathlib import Path
|
||||
@@ -22,7 +23,8 @@ import zlib
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[3]
|
||||
VERIFY_DIR = ROOT / "Standalone/Tools/verify"
|
||||
BASELINES = VERIFY_DIR / "baselines"
|
||||
BASELINE_SOURCE = VERIFY_DIR / "baselines"
|
||||
BASELINES = BASELINE_SOURCE
|
||||
LINUX37 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux37"
|
||||
LINUX47 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux47"
|
||||
LINUX53 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux53"
|
||||
@@ -54,11 +56,25 @@ ARGS = PARSER.parse_args()
|
||||
if ARGS.sanitize and ARGS.tier != "full":
|
||||
PARSER.error("--sanitize is available only with the full tier; use the sanitize tier on its own")
|
||||
OUT = ARGS.out.resolve()
|
||||
BASELINES = OUT / "baselines"
|
||||
ROUND_NAME = OUT.name
|
||||
os.environ["VOXELFORGE_VERIFY_OUT"] = str(OUT)
|
||||
os.environ["PYTHONDONTWRITEBYTECODE"] = "1"
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
|
||||
def prepare_baselines() -> None:
|
||||
"""Copy pinned inputs into the output tree and expand compressed mesh fixtures."""
|
||||
shutil.copytree(BASELINE_SOURCE, BASELINES, dirs_exist_ok=True)
|
||||
for compressed in sorted(BASELINES.rglob("*.vfmesh.xz")):
|
||||
expanded = compressed.with_suffix("")
|
||||
expanded.parent.mkdir(parents=True, exist_ok=True)
|
||||
with lzma.open(compressed, "rb") as source, expanded.open("wb") as target:
|
||||
shutil.copyfileobj(source, target, 1024 * 1024)
|
||||
|
||||
|
||||
prepare_baselines()
|
||||
|
||||
sys.path.insert(0, str(VERIFY_DIR))
|
||||
from owner_step import measure as measure_owner_step
|
||||
from runner import VerificationFailure, VerificationRunner, bounded_run
|
||||
|
||||
Reference in new issue
Block a user