passages: prepared per-segment data + per-column XY projection cache (L98, exact)

Per-world prepared chord/length/radius/Catmull-Rom/floor-profile data (310 segments, ~66 KB,
<0.04 ms); same expression grouping, divisions and square roots, so output is bit-identical
(zero density/tile mismatches, Clang vs GCC equal). No measurable readiness gain - the cost is
the number of samples running passage work, not per-sample arithmetic. Note: the report's
world labels are shifted by one row (the 2,850-tile row is SurfaceWorld). 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:
Fr0zkaandClaude Opus 5.5 committed 2026-10-28 12:50:19 +01:00
1 parent 9d9504ae4e
commit 36140b573f
7 files changed
+1087 -180

No files matched your search

+513 -180
View File
@@ -70,6 +70,20 @@ static bool VF_IsL90PassageCandidateCensusEnabled()
return bEnabled; return bEnabled;
} }
static bool VF_IsL98PassageReuseCountEnabled()
{
static const bool bEnabled=VF_IsL88DiagnosticSwitchEnabled(
"VOXELFORGE_L98_PASSAGE_REUSE_COUNTS");
return bEnabled;
}
static bool VF_IsL98PassagePrepareTraceEnabled()
{
static const bool bEnabled=VF_IsL88DiagnosticSwitchEnabled(
"VOXELFORGE_L98_PASSAGE_PREP_TRACE");
return bEnabled;
}
static double VF_L91PassageSourceFloorVoxels() static double VF_L91PassageSourceFloorVoxels()
{ {
static const double Value=[] static const double Value=[]
@@ -251,6 +265,21 @@ public:
std::ptrdiff_t first=-1,end=-1; std::ptrdiff_t first=-1,end=-1;
std::vector<std::uint8_t> activeNodes; std::vector<std::uint8_t> activeNodes;
}; };
enum class EPreparedSegmentUse : std::uint8_t
{ Carve,Support,Capsule,Mouth };
struct FReuseCounters
{
std::atomic<std::uint64_t> projectionColumns{0};
std::atomic<std::uint64_t> projectionComputes{0};
std::atomic<std::uint64_t> projectionHits{0};
std::atomic<std::uint64_t> floorProfileReads{0};
std::array<std::atomic<std::uint64_t>,4> preparedSegmentUses{};
};
struct FProjectionCacheEntry
{
std::uint64_t generation=0;
double numerator=0.0;
};
FScopedPassageTileQueries(const FStandaloneShapeWorld& shape, FScopedPassageTileQueries(const FStandaloneShapeWorld& shape,
const std::array<std::int64_t,3>& minimumSubvoxels, const std::array<std::int64_t,3>& minimumSubvoxels,
const std::array<std::int64_t,3>& maximumSubvoxels, const std::array<std::int64_t,3>& maximumSubvoxels,
@@ -358,6 +387,14 @@ public:
std::sort(values.begin(),values.end(),[](const auto& a,const auto& b) std::sort(values.begin(),values.end(),[](const auto& a,const auto& b)
{return a.segmentIndex<b.segmentIndex;}); {return a.segmentIndex<b.segmentIndex;});
} }
projectionCacheSize_=(shape.PassageSegmentOffsets.empty()?0
:shape.PassageSegmentOffsets.back())
+(shape.PassageMouthSupportSegmentOffsets.empty()?0
:shape.PassageMouthSupportSegmentOffsets.back())
+(shape.PassageStratumCapsuleSegmentOffsets.empty()?0
:shape.PassageStratumCapsuleSegmentOffsets.back());
if(VF_IsL98PassageReuseCountEnabled())
reuseCounters_=std::make_shared<FReuseCounters>();
ActivePassageTileQueries=this; ActivePassageTileQueries=this;
} }
// Duplicate the immutable per-tile masks onto a density-grid helper worker. Mutable // Duplicate the immutable per-tile masks onto a density-grid helper worker. Mutable
@@ -379,11 +416,32 @@ public:
candidateProfiled_(source.candidateProfiled_), candidateProfiled_(source.candidateProfiled_),
passageSourceFloorVoxels_(source.passageSourceFloorVoxels_), passageSourceFloorVoxels_(source.passageSourceFloorVoxels_),
passageCarveReachMetres_(source.passageCarveReachMetres_), passageCarveReachMetres_(source.passageCarveReachMetres_),
clampCounters_(source.clampCounters_),ownsClampCounters_(false) clampCounters_(source.clampCounters_),ownsClampCounters_(false),
projectionCacheSize_(source.projectionCacheSize_),
reuseCounters_(source.reuseCounters_),ownsReuseCounters_(false)
{ ActivePassageTileQueries=this; } { ActivePassageTileQueries=this; }
~FScopedPassageTileQueries() ~FScopedPassageTileQueries()
{ {
ActivePassageTileQueries=previous_; ActivePassageTileQueries=previous_;
if(ownsReuseCounters_&&reuseCounters_)
std::printf("[L98PassageReuseTile] lod=%d projection_columns=%llu projection_computes=%llu projection_hits=%llu floor_profile_reads=%llu carve_prepared=%llu support_prepared=%llu capsule_prepared=%llu mouth_prepared=%llu\n",
profileLod_,
static_cast<unsigned long long>(reuseCounters_->projectionColumns.load(
std::memory_order_relaxed)),
static_cast<unsigned long long>(reuseCounters_->projectionComputes.load(
std::memory_order_relaxed)),
static_cast<unsigned long long>(reuseCounters_->projectionHits.load(
std::memory_order_relaxed)),
static_cast<unsigned long long>(reuseCounters_->floorProfileReads.load(
std::memory_order_relaxed)),
static_cast<unsigned long long>(reuseCounters_->preparedSegmentUses[0].load(
std::memory_order_relaxed)),
static_cast<unsigned long long>(reuseCounters_->preparedSegmentUses[1].load(
std::memory_order_relaxed)),
static_cast<unsigned long long>(reuseCounters_->preparedSegmentUses[2].load(
std::memory_order_relaxed)),
static_cast<unsigned long long>(reuseCounters_->preparedSegmentUses[3].load(
std::memory_order_relaxed)));
if(ownsClampCounters_&&clampCounters_&&passageSourceFloorVoxels_>0.0) if(ownsClampCounters_&&clampCounters_&&passageSourceFloorVoxels_>0.0)
std::printf("[L91PassageSourceClampTile] lod=%d floor_vox=%.3f source_clamped=%llu source_delta_sum=%.9g deepest_source_vox=%.9g\n", std::printf("[L91PassageSourceClampTile] lod=%d floor_vox=%.3f source_clamped=%llu source_delta_sum=%.9g deepest_source_vox=%.9g\n",
profileLod_,passageSourceFloorVoxels_, profileLod_,passageSourceFloorVoxels_,
@@ -442,6 +500,54 @@ public:
FScopedPassageTileQueries& operator=(const FScopedPassageTileQueries&)=delete; FScopedPassageTileQueries& operator=(const FScopedPassageTileQueries&)=delete;
bool matches(const FStandaloneShapeWorld& shape) const{return shape_==&shape;} bool matches(const FStandaloneShapeWorld& shape) const{return shape_==&shape;}
bool beginProjectionColumn() const
{
const bool previous=projectionColumnActive_;
projectionColumnActive_=true;
++projectionGeneration_;
if(projectionGeneration_==0)
{
for(auto& entry:projectionCache_)entry.generation=0;
projectionGeneration_=1;
}
if(reuseCounters_)
reuseCounters_->projectionColumns.fetch_add(1,std::memory_order_relaxed);
return previous;
}
void endProjectionColumn(bool previous) const
{projectionColumnActive_=previous;}
double projectionNumerator(std::size_t flatIndex,double qx,double qy,
double ax,double ay,double dx,double dy) const
{
const auto compute=[&](){return (qx-ax)*dx+(qy-ay)*dy;};
if(!projectionColumnActive_||!shape_)return compute();
if(projectionCache_.size()!=projectionCacheSize_)
projectionCache_.resize(projectionCacheSize_);
if(flatIndex>=projectionCache_.size())return compute();
FProjectionCacheEntry& entry=projectionCache_[flatIndex];
if(entry.generation==projectionGeneration_)
{
if(reuseCounters_)
reuseCounters_->projectionHits.fetch_add(1,std::memory_order_relaxed);
return entry.numerator;
}
entry.numerator=compute();
entry.generation=projectionGeneration_;
if(reuseCounters_)
reuseCounters_->projectionComputes.fetch_add(1,std::memory_order_relaxed);
return entry.numerator;
}
void recordPreparedSegmentUse(EPreparedSegmentUse use) const
{
if(reuseCounters_)
reuseCounters_->preparedSegmentUses[static_cast<std::size_t>(use)]
.fetch_add(1,std::memory_order_relaxed);
}
void recordPreparedFloorProfileRead() const
{
if(reuseCounters_)
reuseCounters_->floorProfileReads.fetch_add(1,std::memory_order_relaxed);
}
static const FScopedPassageTileQueries* CurrentFor(const FStandaloneShapeWorld& shape) static const FScopedPassageTileQueries* CurrentFor(const FStandaloneShapeWorld& shape)
{ {
const FScopedPassageTileQueries* current=ActivePassageTileQueries; const FScopedPassageTileQueries* current=ActivePassageTileQueries;
@@ -579,6 +685,12 @@ public:
double passageCarveReachMetres_=0.0; double passageCarveReachMetres_=0.0;
mutable std::shared_ptr<FPassageSourceClampCounters> clampCounters_; mutable std::shared_ptr<FPassageSourceClampCounters> clampCounters_;
bool ownsClampCounters_=true; bool ownsClampCounters_=true;
std::size_t projectionCacheSize_=0;
mutable std::vector<FProjectionCacheEntry> projectionCache_;
mutable std::uint64_t projectionGeneration_=0;
mutable bool projectionColumnActive_=false;
std::shared_ptr<FReuseCounters> reuseCounters_;
bool ownsReuseCounters_=true;
mutable std::uint64_t l90Queries_=0,l90NoBrickQueries_=0; mutable std::uint64_t l90Queries_=0,l90NoBrickQueries_=0;
mutable std::uint64_t l90FallbackIterations_=0,l90FallbackNanoseconds_=0, mutable std::uint64_t l90FallbackIterations_=0,l90FallbackNanoseconds_=0,
l90QueryNanoseconds_=0,l90FallbackCarveVisits_=0,l90FallbackTreadVisits_=0, l90QueryNanoseconds_=0,l90FallbackCarveVisits_=0,l90FallbackTreadVisits_=0,
@@ -589,6 +701,23 @@ public:
mutable std::array<std::array<std::uint64_t,17>,4> l90CandidateHistogram_{}; mutable std::array<std::array<std::uint64_t,17>,4> l90CandidateHistogram_{};
}; };
class FScopedPassageColumnXYReuse
{
public:
explicit FScopedPassageColumnXYReuse(const FStandaloneShapeWorld& shape)
:queries_(FScopedPassageTileQueries::CurrentFor(shape))
{
if(queries_)previousActive_=queries_->beginProjectionColumn();
}
~FScopedPassageColumnXYReuse()
{if(queries_)queries_->endProjectionColumn(previousActive_);}
FScopedPassageColumnXYReuse(const FScopedPassageColumnXYReuse&)=delete;
FScopedPassageColumnXYReuse& operator=(const FScopedPassageColumnXYReuse&)=delete;
private:
const FScopedPassageTileQueries* queries_=nullptr;
bool previousActive_=false;
};
bool IsResolvedPassageColumnSample(std::size_t PassageIndex,std::size_t SegmentIndex, bool IsResolvedPassageColumnSample(std::size_t PassageIndex,std::size_t SegmentIndex,
const VoxelForgeCore::Vec3d& Position) const const VoxelForgeCore::Vec3d& Position) const
{ {
@@ -1410,21 +1539,25 @@ public:
||qz<Constants.lastFloorQ-2.0*Passage.mouthRadiusSubvoxels) ||qz<Constants.lastFloorQ-2.0*Passage.mouthRadiusSubvoxels)
continue; continue;
const std::ptrdiff_t LowerMouthIndex=Constants.lowerMouthIndex; const std::ptrdiff_t LowerMouthIndex=Constants.lowerMouthIndex;
const auto FloorProfile=[&](const auto& Point) const auto& FloorProfileQ=Constants.controlFloorProfileQ;
{return Point.positionSubvoxels[2]-Point.radiusSubvoxels const auto CompareFloorProfile=[&](double FloorProfile,double Value)
+1.13*qPerMetre;}; {
const auto CarveHigh=std::lower_bound(Passage.controlPoints.begin(), if(passageTileScopeMatches)
Passage.controlPoints.begin()+LowerMouthIndex+1, passageTileQueries->recordPreparedFloorProfileRead();
return FloorProfile>Value;
};
const auto CarveHigh=std::lower_bound(FloorProfileQ.begin(),
FloorProfileQ.begin()+LowerMouthIndex+1,
qz+Constants.carveSearchMargin, qz+Constants.carveSearchMargin,
[&](const auto& Point,double Value){return FloorProfile(Point)>Value;}); CompareFloorProfile);
const auto CarveLow=std::lower_bound(Passage.controlPoints.begin(), const auto CarveLow=std::lower_bound(FloorProfileQ.begin(),
Passage.controlPoints.begin()+LowerMouthIndex+1, FloorProfileQ.begin()+LowerMouthIndex+1,
qz-Constants.carveSearchMargin, qz-Constants.carveSearchMargin,
[&](const auto& Point,double Value){return FloorProfile(Point)>Value;}); CompareFloorProfile);
const std::ptrdiff_t CarveBegin=std::max<std::ptrdiff_t>(0, const std::ptrdiff_t CarveBegin=std::max<std::ptrdiff_t>(0,
(CarveHigh-Passage.controlPoints.begin())-1); (CarveHigh-FloorProfileQ.begin())-1);
const std::ptrdiff_t CarveEnd=std::min<std::ptrdiff_t>(LowerMouthIndex-1, const std::ptrdiff_t CarveEnd=std::min<std::ptrdiff_t>(LowerMouthIndex-1,
(CarveLow-Passage.controlPoints.begin())+1); (CarveLow-FloorProfileQ.begin())+1);
const double rawSignedDistanceLimit=-static_cast<double>(density)*Q const double rawSignedDistanceLimit=-static_cast<double>(density)*Q
+0.24*qPerMetre; +0.24*qPerMetre;
const double SignedDistanceLimit=passageCarveReachEnabled const double SignedDistanceLimit=passageCarveReachEnabled
@@ -1514,16 +1647,20 @@ public:
if(index>=route.size())return; if(index>=route.size())return;
if(passageProfile)++passageProfile->passageMouthRouteSegmentChecks; if(passageProfile)++passageProfile->passageMouthRouteSegmentChecks;
const auto& segment=route[index]; const auto& segment=route[index];
const double dx=static_cast<double>(segment.b[0]-segment.a[0]); const auto* l98TileQueries=ActivePassageTileQueries;
const double dy=static_cast<double>(segment.b[1]-segment.a[1]); if(l98TileQueries&&l98TileQueries->matches(*this))
const double lengthSq=dx*dx+dy*dy; l98TileQueries->recordPreparedSegmentUse(
const double t=lengthSq>0.0?std::clamp(((qx-segment.a[0])*dx FScopedPassageTileQueries::EPreparedSegmentUse::Mouth);
+(qy-segment.a[1])*dy)/lengthSq,0.0,1.0):0.0; const double dx=segment.preparedDelta[0],dy=segment.preparedDelta[1];
const double centerX=segment.a[0]+t*dx,centerY=segment.a[1]+t*dy; const double lengthSq=segment.horizontalLengthSq;
const double centerZ=segment.a[2] const double t=lengthSq>0.0?std::clamp(
+t*(static_cast<double>(segment.b[2])-segment.a[2]); PassageMouthSupportXYProjectionNumerator(passageIndex,index,qx,qy)
/lengthSq,0.0,1.0):0.0;
const double centerX=segment.preparedA[0]+t*dx,
centerY=segment.preparedA[1]+t*dy;
const double centerZ=segment.preparedA[2]+t*segment.preparedDelta[2];
const double routeRadius=segment.hasRadius const double routeRadius=segment.hasRadius
?segment.a[3]+t*(static_cast<double>(segment.b[3])-segment.a[3]):0.0; ?segment.preparedA[3]+t*segment.preparedDelta[3]:0.0;
const double halfWidth=std::clamp(routeRadius, const double halfWidth=std::clamp(routeRadius,
0.42*qPerMetre,0.56*qPerMetre); 0.42*qPerMetre,0.56*qPerMetre);
const double horizontalX=qx-centerX,horizontalY=qy-centerY; const double horizontalX=qx-centerX,horizontalY=qy-centerY;
@@ -1703,25 +1840,31 @@ public:
{ {
continue; continue;
} }
const auto floorProfile=[&](const auto& point)
{return point.positionSubvoxels[2]-point.radiusSubvoxels+playerCenterAboveFloorQ;};
const std::ptrdiff_t lastIndex=passageConstants.lastIndex; const std::ptrdiff_t lastIndex=passageConstants.lastIndex;
const std::ptrdiff_t lowerMouthIndex=passageConstants.lowerMouthIndex; const std::ptrdiff_t lowerMouthIndex=passageConstants.lowerMouthIndex;
const auto mainEnd=passage.controlPoints.begin()+lowerMouthIndex+1; const auto& floorProfileQ=passageConstants.controlFloorProfileQ;
// The tube radius is measured from its centerline, while floorProfile is // The tube radius is measured from its centerline, while floorProfile is
// one player-center offset away. Search the full vertical radius of the SDF; // one player-center offset away. Search the full vertical radius of the SDF;
// a narrow point window misses earlier turns of a low-pitch helix. // a narrow point window misses earlier turns of a low-pitch helix.
const double carveSearchMargin=passageConstants.carveSearchMargin; const double carveSearchMargin=passageConstants.carveSearchMargin;
const auto carveHigh=std::lower_bound(passage.controlPoints.begin(),mainEnd, const auto compareFloorProfile=[&](double floorProfile,double value)
{
if(passageTileScopeMatches)
passageTileQueries->recordPreparedFloorProfileRead();
return floorProfile>value;
};
const auto carveHigh=std::lower_bound(floorProfileQ.begin(),
floorProfileQ.begin()+lowerMouthIndex+1,
qz+carveSearchMargin, qz+carveSearchMargin,
[&](const auto& point,double value){return floorProfile(point)>value;}); compareFloorProfile);
const auto carveLow=std::lower_bound(passage.controlPoints.begin(),mainEnd, const auto carveLow=std::lower_bound(floorProfileQ.begin(),
floorProfileQ.begin()+lowerMouthIndex+1,
qz-carveSearchMargin, qz-carveSearchMargin,
[&](const auto& point,double value){return floorProfile(point)>value;}); compareFloorProfile);
const std::ptrdiff_t carveBegin=std::max<std::ptrdiff_t>(0, const std::ptrdiff_t carveBegin=std::max<std::ptrdiff_t>(0,
(carveHigh-passage.controlPoints.begin())-1); (carveHigh-floorProfileQ.begin())-1);
const std::ptrdiff_t carveEnd=std::min<std::ptrdiff_t>(lowerMouthIndex-1, const std::ptrdiff_t carveEnd=std::min<std::ptrdiff_t>(lowerMouthIndex-1,
(carveLow-passage.controlPoints.begin())+1); (carveLow-floorProfileQ.begin())+1);
const std::ptrdiff_t mouthSearchBegin=passageConstants.mouthSearchBegin; const std::ptrdiff_t mouthSearchBegin=passageConstants.mouthSearchBegin;
const std::ptrdiff_t mouthSearchEnd=passageConstants.mouthSearchEnd; const std::ptrdiff_t mouthSearchEnd=passageConstants.mouthSearchEnd;
if(l90CandidateProfiled) if(l90CandidateProfiled)
@@ -1801,27 +1944,27 @@ public:
{ {
l90IterationTimer.recordCarve(); l90IterationTimer.recordCarve();
if(passageProfile)++passageProfile->passageCarveSegmentVisits; if(passageProfile)++passageProfile->passageCarveSegmentVisits;
const FPassageSegmentData& segment=GetPassageSegmentData(
passageIndex,segmentIndex);
if(passageTileScopeMatches)
passageTileQueries->recordPreparedSegmentUse(
FScopedPassageTileQueries::EPreparedSegmentUse::Carve);
const std::ptrdiff_t index=static_cast<std::ptrdiff_t>(segmentIndex); const std::ptrdiff_t index=static_cast<std::ptrdiff_t>(segmentIndex);
const auto& a=passage.controlPoints[static_cast<std::size_t>(index)]; const double ax=segment.a[0],ay=segment.a[1],az=segment.a[2];
const auto& b=passage.controlPoints[static_cast<std::size_t>(index+1)]; const double dx=segment.delta[0],dy=segment.delta[1],dz=segment.delta[2];
const double ax=static_cast<double>(a.positionSubvoxels[0]); const double lengthSq=segment.lengthSq;
const double ay=static_cast<double>(a.positionSubvoxels[1]);
const double az=static_cast<double>(a.positionSubvoxels[2]);
const double dx=static_cast<double>(b.positionSubvoxels[0])-ax;
const double dy=static_cast<double>(b.positionSubvoxels[1])-ay;
const double dz=static_cast<double>(b.positionSubvoxels[2])-az;
const double lengthSq=dx*dx+dy*dy+dz*dz;
if(lengthSq<=0.0)return; if(lengthSq<=0.0)return;
const double t=std::clamp(((qx-ax)*dx+(qy-ay)*dy+(qz-az)*dz)/lengthSq,0.0,1.0); const double projectionXY=PassageXYProjectionNumerator(
passageIndex,segmentIndex,qx,qy);
const double t=std::clamp((projectionXY+(qz-az)*dz)/lengthSq,0.0,1.0);
if(!passageTileScopeMatches) if(!passageTileScopeMatches)
{ {
const double horizontalLengthSq=dx*dx+dy*dy; const double horizontalLengthSq=segment.horizontalLengthSq;
const double playerT=horizontalLengthSq>0.0 const double playerT=horizontalLengthSq>0.0
?std::clamp(((qx-ax)*dx+(qy-ay)*dy)/horizontalLengthSq,0.0,1.0):t; ?std::clamp(projectionXY/horizontalLengthSq,0.0,1.0):t;
const double playerDx=qx-(ax+playerT*dx); const double playerDx=qx-(ax+playerT*dx);
const double playerDy=qy-(ay+playerT*dy); const double playerDy=qy-(ay+playerT*dy);
const double playerRadius=static_cast<double>(a.radiusSubvoxels) const double playerRadius=segment.radiusA+playerT*segment.radiusDelta;
+playerT*(b.radiusSubvoxels-a.radiusSubvoxels);
const double playerCenterZ=az+playerT*dz-playerRadius+playerCenterAboveFloorQ; const double playerCenterZ=az+playerT*dz-playerRadius+playerCenterAboveFloorQ;
const double playerVertical=std::max(0.0, const double playerVertical=std::max(0.0,
std::abs(qz-playerCenterZ)-0.54*qPerMetre); std::abs(qz-playerCenterZ)-0.54*qPerMetre);
@@ -1832,23 +1975,21 @@ public:
double cx=ax+t*dx,cy=ay+t*dy,cz=az+t*dz; double cx=ax+t*dx,cy=ay+t*dy,cz=az+t*dz;
const bool smoothCarveJoin=index>2 const bool smoothCarveJoin=index>2
&&index+(internalChainPassage?2:12)<lowerMouthIndex &&index+(internalChainPassage?2:12)<lowerMouthIndex
&&DeterministicSqrt(lengthSq)<=8.0*qPerMetre &&segment.lengthSubvoxels<=8.0*qPerMetre
&&!IsPassageShortSpanSegment(passageIndex,segmentIndex-1) &&!IsPassageShortSpanSegment(passageIndex,segmentIndex-1)
&&!IsPassageShortSpanSegment(passageIndex,segmentIndex) &&!IsPassageShortSpanSegment(passageIndex,segmentIndex)
&&!IsPassageShortSpanSegment(passageIndex,segmentIndex+1); &&!IsPassageShortSpanSegment(passageIndex,segmentIndex+1);
if(smoothCarveJoin) if(smoothCarveJoin)
{ {
if(passageProfile)++passageProfile->passageSmoothCarveJoins; 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; constexpr double SmoothJoinWeight=1.0;
const double t2=t*t,t3=t2*t; const double t2=t*t,t3=t2*t;
for(std::size_t axis=0;axis<3;++axis) for(std::size_t axis=0;axis<3;++axis)
{ {
const double v0=static_cast<double>(p0.positionSubvoxels[axis]); const double v0=segment.catmullBefore[axis];
const double v1=static_cast<double>(a.positionSubvoxels[axis]); const double v1=segment.a[axis];
const double v2=static_cast<double>(b.positionSubvoxels[axis]); const double v2=segment.b[axis];
const double v3=static_cast<double>(p3.positionSubvoxels[axis]); const double v3=segment.catmullAfter[axis];
const double curve=0.5*((2.0*v1)+(-v0+v2)*t const double curve=0.5*((2.0*v1)+(-v0+v2)*t
+(2.0*v0-5.0*v1+4.0*v2-v3)*t2 +(2.0*v0-5.0*v1+4.0*v2-v3)*t2
+(-v0+3.0*v1-3.0*v2+v3)*t3); +(-v0+3.0*v1-3.0*v2+v3)*t3);
@@ -1860,8 +2001,7 @@ public:
} }
} }
const double ex=qx-cx,ey=qy-cy,ez=qz-cz; const double ex=qx-cx,ey=qy-cy,ez=qz-cz;
const double radius=static_cast<double>(a.radiusSubvoxels) const double radius=segment.radiusA+t*segment.radiusDelta;
+t*(b.radiusSubvoxels-a.radiusSubvoxels);
const double signedDistance=DeterministicSqrt(ex*ex+ey*ey+ez*ez)-radius; const double signedDistance=DeterministicSqrt(ex*ex+ey*ey+ez*ez)-radius;
if(signedDistance<bestSignedDistance if(signedDistance<bestSignedDistance
||(signedDistance==bestSignedDistance ||(signedDistance==bestSignedDistance
@@ -1908,14 +2048,13 @@ public:
L90PassageCandidateProgramValid L90PassageCandidateProgramValid
&&PassageCandidateBrickContains(l90CandidateBrick,0,passageIndex, &&PassageCandidateBrickContains(l90CandidateBrick,0,passageIndex,
static_cast<std::int32_t>(bestIndex))); static_cast<std::int32_t>(bestIndex)));
const auto& nearestA=passage.controlPoints[static_cast<std::size_t>(bestIndex)]; const FPassageSegmentData& nearestSegment=PassageSegmentData[passageIndex][
const auto& nearestB=passage.controlPoints[static_cast<std::size_t>(bestIndex+1)]; static_cast<std::size_t>(bestIndex)];
const double nearestRadius=nearestA.radiusSubvoxels+bestT*( const double nearestRadius=nearestSegment.radiusA
static_cast<double>(nearestB.radiusSubvoxels)-nearestA.radiusSubvoxels); +bestT*nearestSegment.radiusDelta;
const double distanceToCenter=std::max(1.0e-9,bestSignedDistance+nearestRadius); const double distanceToCenter=std::max(1.0e-9,bestSignedDistance+nearestRadius);
const double verticalWallAlignment=distanceToCenter>1.0e-9 const double verticalWallAlignment=distanceToCenter>1.0e-9
?std::clamp(-(qz-(nearestA.positionSubvoxels[2]+bestT*( ?std::clamp(-(qz-(nearestSegment.a[2]+bestT*nearestSegment.delta[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 std::uint32_t passageReliefSeed=static_cast<std::uint32_t>(Owner?Owner->Seed:0); const std::uint32_t passageReliefSeed=static_cast<std::uint32_t>(Owner?Owner->Seed:0);
float cachedWallRelief=0.0f,cachedMouthSurfaceRelief=0.0f, float cachedWallRelief=0.0f,cachedMouthSurfaceRelief=0.0f,
@@ -1959,15 +2098,11 @@ public:
}; };
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)]; if(passageTileScopeMatches)
const auto& after=passage.controlPoints[static_cast<std::size_t>(routeIndex+1)]; passageTileQueries->recordPreparedSegmentUse(
const double dx=static_cast<double>(after.positionSubvoxels[0]) FScopedPassageTileQueries::EPreparedSegmentUse::Mouth);
-before.positionSubvoxels[0]; return PassageSegmentData[passageIndex][
const double dy=static_cast<double>(after.positionSubvoxels[1]) static_cast<std::size_t>(routeIndex)].lengthMetres;
-before.positionSubvoxels[1];
const double dz=static_cast<double>(after.positionSubvoxels[2])
-before.positionSubvoxels[2];
return DeterministicSqrt(dx*dx+dy*dy+dz*dz)/qPerMetre;
}; };
double distanceFromOpenMouth=std::numeric_limits<double>::infinity(); double distanceFromOpenMouth=std::numeric_limits<double>::infinity();
if(bestIndex<=2) if(bestIndex<=2)
@@ -2007,19 +2142,22 @@ public:
routeIndex<std::min<std::ptrdiff_t>(3,lowerMouthIndex);++routeIndex) routeIndex<std::min<std::ptrdiff_t>(3,lowerMouthIndex);++routeIndex)
{ {
if(passageProfile)++passageProfile->passageMouthRouteSegmentChecks; if(passageProfile)++passageProfile->passageMouthRouteSegmentChecks;
const auto& before=passage.controlPoints[static_cast<std::size_t>(routeIndex)]; const FPassageSegmentData& routeSegment=PassageSegmentData[
const auto& after=passage.controlPoints[static_cast<std::size_t>(routeIndex+1)]; passageIndex][static_cast<std::size_t>(routeIndex)];
const double ax=static_cast<double>(before.positionSubvoxels[0]); if(passageTileScopeMatches)
const double ay=static_cast<double>(before.positionSubvoxels[1]); passageTileQueries->recordPreparedSegmentUse(
const double az=static_cast<double>(before.positionSubvoxels[2]); FScopedPassageTileQueries::EPreparedSegmentUse::Mouth);
const double dx=static_cast<double>(after.positionSubvoxels[0])-ax; const double ax=routeSegment.a[0],ay=routeSegment.a[1],
const double dy=static_cast<double>(after.positionSubvoxels[1])-ay; az=routeSegment.a[2];
const double dz=static_cast<double>(after.positionSubvoxels[2])-az; const double dx=routeSegment.delta[0],dy=routeSegment.delta[1],
const double horizontalLengthSq=dx*dx+dy*dy; dz=routeSegment.delta[2];
const double horizontalLengthSq=routeSegment.horizontalLengthSq;
const double t=horizontalLengthSq>0.0?std::clamp( const double t=horizontalLengthSq>0.0?std::clamp(
((qx-ax)*dx+(qy-ay)*dy)/horizontalLengthSq,0.0,1.0):0.0; PassageXYProjectionNumerator(passageIndex,
static_cast<std::size_t>(routeIndex),qx,qy)
/horizontalLengthSq,0.0,1.0):0.0;
double projectedX=ax+t*dx,projectedY=ay+t*dy; double projectedX=ax+t*dx,projectedY=ay+t*dy;
const double horizontalLength=DeterministicSqrt(horizontalLengthSq); const double horizontalLength=routeSegment.horizontalLength;
if(horizontalLength>1.0e-6) if(horizontalLength>1.0e-6)
{ {
const double pathRelief=std::clamp(static_cast<double>( const double pathRelief=std::clamp(static_cast<double>(
@@ -2039,8 +2177,7 @@ public:
{ {
nearestEntryPlanDistance=planDistance; nearestEntryPlanDistance=planDistance;
const double centerZ=az+t*dz; const double centerZ=az+t*dz;
const double radius=before.radiusSubvoxels+t*( const double radius=routeSegment.radiusA+t*routeSegment.radiusDelta;
static_cast<double>(after.radiusSubvoxels)-before.radiusSubvoxels);
mouthEntryFloorQ=centerZ-radius+0.20*qPerMetre; mouthEntryFloorQ=centerZ-radius+0.20*qPerMetre;
const double centerDistance=DeterministicSqrt(planDx*planDx const double centerDistance=DeterministicSqrt(planDx*planDx
+planDy*planDy+(qz-centerZ)*(qz-centerZ)); +planDy*planDy+(qz-centerZ)*(qz-centerZ));
@@ -2182,13 +2319,15 @@ public:
{ {
l90IterationTimer.recordTread(); l90IterationTimer.recordTread();
if(passageProfile)++passageProfile->passageSupportSegmentVisits; if(passageProfile)++passageProfile->passageSupportSegmentVisits;
const FPassageSegmentData& segment=GetPassageSegmentData(
passageIndex,static_cast<std::size_t>(index));
if(passageTileScopeMatches)
passageTileQueries->recordPreparedSegmentUse(
FScopedPassageTileQueries::EPreparedSegmentUse::Support);
const auto& segmentA=passage.controlPoints[static_cast<std::size_t>(index)]; 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=segment.delta[0],dy=segment.delta[1],dz=segment.delta[2];
const double dx=static_cast<double>(segmentB.positionSubvoxels[0])-segmentA.positionSubvoxels[0]; const double radiusA=segment.radiusA;
const double dy=static_cast<double>(segmentB.positionSubvoxels[1])-segmentA.positionSubvoxels[1]; const double radiusB=segment.radiusB;
const double dz=static_cast<double>(segmentB.positionSubvoxels[2])-segmentA.positionSubvoxels[2];
const double radiusA=segmentA.radiusSubvoxels;
const double radiusB=segmentB.radiusSubvoxels;
const bool entryMouthShoulder=index<=2; const bool entryMouthShoulder=index<=2;
// Keep the tread within the probe band and below the capsule's // Keep the tread within the probe band and below the capsule's
// lowest sampled point. // lowest sampled point.
@@ -2200,7 +2339,7 @@ public:
double floorOffset=bridgeDeck double floorOffset=bridgeDeck
?PassageBridgeDeckOffsetMetres(passageIndex,static_cast<std::size_t>(index)) ?PassageBridgeDeckOffsetMetres(passageIndex,static_cast<std::size_t>(index))
:std::lerp(internalChainPassage?0.20:0.15,0.20,lowerMouthBlend); :std::lerp(internalChainPassage?0.20:0.15,0.20,lowerMouthBlend);
const double horizontalLengthSq=dx*dx+dy*dy; const double horizontalLengthSq=segment.horizontalLengthSq;
const bool overlapSupportJoin=index+12<lastIndex const bool overlapSupportJoin=index+12<lastIndex
&&!IsPassageShortSpanSegment(passageIndex,static_cast<std::size_t>(index)) &&!IsPassageShortSpanSegment(passageIndex,static_cast<std::size_t>(index))
&&(index==0||(!IsPassageShortSpanSegment(passageIndex, &&(index==0||(!IsPassageShortSpanSegment(passageIndex,
@@ -2208,25 +2347,21 @@ public:
const bool smoothSupportJoin=index>0&&overlapSupportJoin const bool smoothSupportJoin=index>0&&overlapSupportJoin
&&!IsPassageShortSpanSegment(passageIndex,static_cast<std::size_t>(index+1)); &&!IsPassageShortSpanSegment(passageIndex,static_cast<std::size_t>(index+1));
const double projection=horizontalLengthSq>0.0 const double projection=horizontalLengthSq>0.0
?((qx-segmentA.positionSubvoxels[0])*dx ?PassageXYProjectionNumerator(passageIndex,
+(qy-segmentA.positionSubvoxels[1])*dy)/horizontalLengthSq:bestT; static_cast<std::size_t>(index),qx,qy)/horizontalLengthSq:bestT;
// Extend tread segments by a fixed distance through route knots. A // Extend tread segments by a fixed distance through route knots. A
// fractional extension made the support width change with segment length. // fractional extension made the support width change with segment length.
const double overlapFraction=overlapSupportJoin&&horizontalLengthSq>0.0 const double overlapFraction=overlapSupportJoin&&horizontalLengthSq>0.0
?std::min(0.5,2.0*qPerMetre/DeterministicSqrt(horizontalLengthSq)):0.0; ?std::min(0.5,2.0*qPerMetre/segment.horizontalLength):0.0;
const double supportT=horizontalLengthSq>0.0 const double supportT=horizontalLengthSq>0.0
?std::clamp(projection,-overlapFraction,1.0+overlapFraction):bestT; ?std::clamp(projection,-overlapFraction,1.0+overlapFraction):bestT;
double centerX=segmentA.positionSubvoxels[0]+supportT*dx; double centerX=segment.a[0]+supportT*dx;
double centerY=segmentA.positionSubvoxels[1]+supportT*dy; double centerY=segment.a[1]+supportT*dy;
double centerZ=segmentA.positionSubvoxels[2]+supportT*( double centerZ=segment.a[2]+supportT*dz;
static_cast<double>(segmentB.positionSubvoxels[2])-segmentA.positionSubvoxels[2]); double radius=radiusA+supportT*segment.radiusDelta;
double radius=segmentA.radiusSubvoxels+supportT*(
static_cast<double>(segmentB.radiusSubvoxels)-segmentA.radiusSubvoxels);
if(smoothSupportJoin) if(smoothSupportJoin)
{ {
if(passageProfile)++passageProfile->passageSmoothSupportJoins; 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; const double t2=supportT*supportT,t3=t2*supportT;
const auto interpolate=[&](double v0,double v1,double v2,double v3) const auto interpolate=[&](double v0,double v1,double v2,double v3)
{ {
@@ -2235,18 +2370,18 @@ public:
+(-v0+3.0*v1-3.0*v2+v3)*t3); +(-v0+3.0*v1-3.0*v2+v3)*t3);
}; };
const double SmoothSupportHeightWeight=internalChainPassage?0.0:0.25; const double SmoothSupportHeightWeight=internalChainPassage?0.0:0.25;
centerZ+=(interpolate(p0.positionSubvoxels[2],segmentA.positionSubvoxels[2], centerZ+=(interpolate(segment.catmullBefore[2],segment.a[2],
segmentB.positionSubvoxels[2],p3.positionSubvoxels[2])-centerZ) segment.b[2],segment.catmullAfter[2])-centerZ)
*SmoothSupportHeightWeight; *SmoothSupportHeightWeight;
radius+=(interpolate(p0.radiusSubvoxels,segmentA.radiusSubvoxels, radius+=(interpolate(segment.catmullRadiusBefore,radiusA,
segmentB.radiusSubvoxels,p3.radiusSubvoxels)-radius) radiusB,segment.catmullRadiusAfter)-radius)
*SmoothSupportHeightWeight; *SmoothSupportHeightWeight;
} }
double treadRelief=0.0; double treadRelief=0.0;
if(entryMouthShoulder if(entryMouthShoulder
||(index>=lowerMouthIndex-2&&index<=lowerMouthIndex+2)) ||(index>=lowerMouthIndex-2&&index<=lowerMouthIndex+2))
{ {
const double horizontalLength=DeterministicSqrt(horizontalLengthSq); const double horizontalLength=segment.horizontalLength;
if(horizontalLength>1.0e-6) if(horizontalLength>1.0e-6)
{ {
const double pathRelief=std::clamp(static_cast<double>( const double pathRelief=std::clamp(static_cast<double>(
@@ -2272,8 +2407,7 @@ public:
// not turn that tread into a ceiling or mid-air shelf. // not turn that tread into a ceiling or mid-air shelf.
// Sparse authored routes use the full signed distance around the top; // Sparse authored routes use the full signed distance around the top;
// dense routes retain the legacy one-sided tread density. // dense routes retain the legacy one-sided tread density.
const double supportSegmentLength=DeterministicSqrt( const double supportSegmentLength=segment.lengthMetres;
horizontalLengthSq+dz*dz)/qPerMetre;
const double sparseRouteThreshold=VoxelForgeCore::WorldAssemblyEvaluatorVersion>=188 const double sparseRouteThreshold=VoxelForgeCore::WorldAssemblyEvaluatorVersion>=188
&&floatingIslandPassage&&passage.style==0?1.0:1.5; &&floatingIslandPassage&&passage.style==0?1.0:1.5;
const bool sparseRouteSupport=supportSegmentLength>sparseRouteThreshold; const bool sparseRouteSupport=supportSegmentLength>sparseRouteThreshold;
@@ -2311,17 +2445,11 @@ public:
double prefixMetres=0.0; double prefixMetres=0.0;
for(std::ptrdiff_t routeIndex=0;routeIndex<index;++routeIndex) for(std::ptrdiff_t routeIndex=0;routeIndex<index;++routeIndex)
{ {
const auto& before=passage.controlPoints[ if(passageTileScopeMatches)
static_cast<std::size_t>(routeIndex)]; passageTileQueries->recordPreparedSegmentUse(
const auto& after=passage.controlPoints[ FScopedPassageTileQueries::EPreparedSegmentUse::Support);
static_cast<std::size_t>(routeIndex+1)]; prefixMetres+=PassageSegmentData[passageIndex][
const double dx=static_cast<double>(after.positionSubvoxels[0]) static_cast<std::size_t>(routeIndex)].lengthMetres;
-before.positionSubvoxels[0];
const double dy=static_cast<double>(after.positionSubvoxels[1])
-before.positionSubvoxels[1];
const double dz=static_cast<double>(after.positionSubvoxels[2])
-before.positionSubvoxels[2];
prefixMetres+=DeterministicSqrt(dx*dx+dy*dy+dz*dz)/qPerMetre;
} }
distanceFromMouthMetres=std::max(0.0, distanceFromMouthMetres=std::max(0.0,
prefixMetres+supportT*supportSegmentLength); prefixMetres+supportT*supportSegmentLength);
@@ -2332,17 +2460,11 @@ public:
for(std::ptrdiff_t routeIndex=index+1; for(std::ptrdiff_t routeIndex=index+1;
routeIndex<lowerMouthIndex;++routeIndex) routeIndex<lowerMouthIndex;++routeIndex)
{ {
const auto& before=passage.controlPoints[ if(passageTileScopeMatches)
static_cast<std::size_t>(routeIndex)]; passageTileQueries->recordPreparedSegmentUse(
const auto& after=passage.controlPoints[ FScopedPassageTileQueries::EPreparedSegmentUse::Support);
static_cast<std::size_t>(routeIndex+1)]; suffixMetres+=PassageSegmentData[passageIndex][
const double dx=static_cast<double>(after.positionSubvoxels[0]) static_cast<std::size_t>(routeIndex)].lengthMetres;
-before.positionSubvoxels[0];
const double dy=static_cast<double>(after.positionSubvoxels[1])
-before.positionSubvoxels[1];
const double dz=static_cast<double>(after.positionSubvoxels[2])
-before.positionSubvoxels[2];
suffixMetres+=DeterministicSqrt(dx*dx+dy*dy+dz*dz)/qPerMetre;
} }
distanceFromMouthMetres=std::max(0.0, distanceFromMouthMetres=std::max(0.0,
suffixMetres+(1.0-supportT)*supportSegmentLength); suffixMetres+(1.0-supportT)*supportSegmentLength);
@@ -2465,11 +2587,11 @@ public:
// whose floor is just below this density query, not the closest wall. // whose floor is just below this density query, not the closest wall.
const double supportProfileOffset=surfaceToTunnelSupport?1.13:0.93; const double supportProfileOffset=surfaceToTunnelSupport?1.13:0.93;
const double supportProfileTarget=qz+supportProfileOffset*qPerMetre; const double supportProfileTarget=qz+supportProfileOffset*qPerMetre;
const auto supportProfile=std::lower_bound(passage.controlPoints.begin(),mainEnd, const auto supportProfile=std::lower_bound(floorProfileQ.begin(),
supportProfileTarget,[&](const auto& point,double value) floorProfileQ.begin()+lowerMouthIndex+1,supportProfileTarget,
{return floorProfile(point)>value;}); compareFloorProfile);
const std::ptrdiff_t profileIndex=std::clamp<std::ptrdiff_t>( const std::ptrdiff_t profileIndex=std::clamp<std::ptrdiff_t>(
supportProfile-passage.controlPoints.begin(),0,lowerMouthIndex); supportProfile-floorProfileQ.begin(),0,lowerMouthIndex);
// At floor height the nearest 3D centerline can belong to the next // At floor height the nearest 3D centerline can belong to the next
// coil below. The descending floor profile identifies the coil that // coil below. The descending floor profile identifies the coil that
// owns this tread, even when it is more than eight control points away. // owns this tread, even when it is more than eight control points away.
@@ -4470,12 +4592,77 @@ private:
double firstFloorQ=0.0,lastFloorQ=0.0,carveSearchMargin=0.0; double firstFloorQ=0.0,lastFloorQ=0.0,carveSearchMargin=0.0;
std::ptrdiff_t lowerMouthIndex=1,lastIndex=1; std::ptrdiff_t lowerMouthIndex=1,lastIndex=1;
std::ptrdiff_t mouthSearchBegin=0,mouthSearchEnd=0; std::ptrdiff_t mouthSearchBegin=0,mouthSearchEnd=0;
std::vector<double> controlFloorProfileQ;
}; };
struct FPassageSegmentData
{
std::array<double,3> a{},b{},delta{},catmullBefore{},catmullAfter{};
double radiusA=0.0,radiusB=0.0,radiusDelta=0.0;
double catmullRadiusBefore=0.0,catmullRadiusAfter=0.0;
double lengthSq=0.0,horizontalLengthSq=0.0;
double lengthSubvoxels=0.0,horizontalLength=0.0,lengthMetres=0.0;
};
struct FPreparedPassageCapsuleSegment
{
std::array<double,3> a{},delta{};
double horizontalLengthSq=0.0;
};
const FPassageSegmentData& GetPassageSegmentData(std::size_t passageIndex,
std::size_t segmentIndex) const
{return PassageSegmentData[passageIndex][segmentIndex];}
double PassageXYProjectionNumerator(std::size_t passageIndex,
std::size_t segmentIndex,double qx,double qy) const
{
const FPassageSegmentData& segment=GetPassageSegmentData(
passageIndex,segmentIndex);
const std::size_t flatIndex=PassageSegmentOffsets[passageIndex]+segmentIndex;
const auto* tileQueries=ActivePassageTileQueries;
if(tileQueries&&tileQueries->matches(*this))
return tileQueries->projectionNumerator(flatIndex,qx,qy,
segment.a[0],segment.a[1],segment.delta[0],segment.delta[1]);
return (qx-segment.a[0])*segment.delta[0]
+(qy-segment.a[1])*segment.delta[1];
}
double PassageCapsuleRouteXYProjectionNumerator(std::size_t stratumIndex,
std::size_t segmentIndex,double qx,double qy) const
{
const FPreparedPassageCapsuleSegment& segment=
PassageStratumCapsuleSegments[stratumIndex][segmentIndex];
const std::size_t flatIndex=PassageSegmentOffsets.back()
+PassageMouthSupportSegmentOffsets.back()
+PassageStratumCapsuleSegmentOffsets[stratumIndex]+segmentIndex;
const auto* tileQueries=ActivePassageTileQueries;
if(tileQueries&&tileQueries->matches(*this))
return tileQueries->projectionNumerator(flatIndex,qx,qy,
segment.a[0],segment.a[1],segment.delta[0],segment.delta[1]);
return (qx-segment.a[0])*segment.delta[0]
+(qy-segment.a[1])*segment.delta[1];
}
struct FPassageMouthSupportSegment struct FPassageMouthSupportSegment
{ {
std::array<std::int64_t,4> a{},b{}; std::array<std::int64_t,4> a{},b{};
std::array<double,4> preparedA{},preparedDelta{};
double horizontalLengthSq=0.0;
bool hasRadius=false; bool hasRadius=false;
}; };
double PassageMouthSupportXYProjectionNumerator(std::size_t passageIndex,
std::size_t routeIndex,double qx,double qy) const
{
const FPassageMouthSupportSegment& segment=
PassageMouthSupportRoutes[passageIndex][routeIndex];
const std::size_t flatIndex=PassageSegmentOffsets.back()
+PassageMouthSupportSegmentOffsets[passageIndex]+routeIndex;
const auto* tileQueries=ActivePassageTileQueries;
if(tileQueries&&tileQueries->matches(*this))
return tileQueries->projectionNumerator(flatIndex,qx,qy,
segment.preparedA[0],segment.preparedA[1],
segment.preparedDelta[0],segment.preparedDelta[1]);
return (qx-segment.preparedA[0])*segment.preparedDelta[0]
+(qy-segment.preparedA[1])*segment.preparedDelta[1];
}
struct FPassageNearestNode struct FPassageNearestNode
{ {
std::array<double,3> minimum{},maximum{}; std::array<double,3> minimum{},maximum{};
@@ -5520,18 +5707,75 @@ private:
void BuildPassageComposerConstants() void BuildPassageComposerConstants()
{ {
const bool tracePreparation=VF_IsL98PassagePrepareTraceEnabled();
const auto preparationStart=tracePreparation
?std::chrono::steady_clock::now():std::chrono::steady_clock::time_point{};
PassageComposerConstants.clear(); PassageComposerConstants.clear();
if(!ResolvedLayout)return; PassageSegmentData.clear();
PassageSegmentOffsets.clear();
PassageStratumCapsuleSegments.clear();
PassageStratumCapsuleSegmentOffsets.clear();
if(!ResolvedLayout)
{
if(tracePreparation)
std::printf("[L98PassagePrepare] passages=0 segments=0 bytes=0 prepare_ms=%.3f\n",
std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-preparationStart).count());
return;
}
PassageComposerConstants.resize(ResolvedLayout->passages.size()); PassageComposerConstants.resize(ResolvedLayout->passages.size());
PassageSegmentData.resize(ResolvedLayout->passages.size());
PassageSegmentOffsets.resize(ResolvedLayout->passages.size()+1,0);
PassageStratumCapsuleSegments.resize(ResolvedLayout->strates.size());
PassageStratumCapsuleSegmentOffsets.resize(ResolvedLayout->strates.size()+1,0);
constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
const double qPerMetre=Q*1000000.0/ const double qPerMetre=Q*1000000.0/
std::max<std::uint32_t>(1,ResolvedLayout->coreVoxelSizeMicrometres); std::max<std::uint32_t>(1,ResolvedLayout->coreVoxelSizeMicrometres);
const double playerCenterAboveFloorQ=1.13*qPerMetre; const double playerCenterAboveFloorQ=1.13*qPerMetre;
std::size_t preparedSegments=0;
std::size_t preparedFloorSamples=0;
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size(); for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();
++passageIndex) ++passageIndex)
{ {
PassageSegmentOffsets[passageIndex]=preparedSegments;
const auto& passage=ResolvedLayout->passages[passageIndex]; const auto& passage=ResolvedLayout->passages[passageIndex];
if(passage.controlPoints.size()<2)continue; if(passage.controlPoints.size()<2)
{
PassageSegmentOffsets[passageIndex+1]=preparedSegments;
continue;
}
auto& segmentData=PassageSegmentData[passageIndex];
segmentData.resize(passage.controlPoints.size()-1);
for(std::size_t segmentIndex=0;segmentIndex<segmentData.size();++segmentIndex)
{
const auto& a=passage.controlPoints[segmentIndex];
const auto& b=passage.controlPoints[segmentIndex+1];
const auto& p0=passage.controlPoints[segmentIndex==0?0:segmentIndex-1];
const auto& p3=passage.controlPoints[std::min(segmentIndex+2,
passage.controlPoints.size()-1)];
FPassageSegmentData& data=segmentData[segmentIndex];
for(std::size_t axis=0;axis<3;++axis)
{
data.a[axis]=static_cast<double>(a.positionSubvoxels[axis]);
data.b[axis]=static_cast<double>(b.positionSubvoxels[axis]);
data.delta[axis]=data.b[axis]-data.a[axis];
data.catmullBefore[axis]=static_cast<double>(p0.positionSubvoxels[axis]);
data.catmullAfter[axis]=static_cast<double>(p3.positionSubvoxels[axis]);
}
data.radiusA=static_cast<double>(a.radiusSubvoxels);
data.radiusB=static_cast<double>(b.radiusSubvoxels);
data.radiusDelta=static_cast<double>(b.radiusSubvoxels)-a.radiusSubvoxels;
data.catmullRadiusBefore=static_cast<double>(p0.radiusSubvoxels);
data.catmullRadiusAfter=static_cast<double>(p3.radiusSubvoxels);
const double dx=data.delta[0],dy=data.delta[1],dz=data.delta[2];
data.lengthSq=dx*dx+dy*dy+dz*dz;
data.horizontalLengthSq=dx*dx+dy*dy;
data.lengthSubvoxels=DeterministicSqrt(data.lengthSq);
data.horizontalLength=DeterministicSqrt(data.horizontalLengthSq);
data.lengthMetres=data.lengthSubvoxels/qPerMetre;
++preparedSegments;
}
PassageSegmentOffsets[passageIndex+1]=preparedSegments;
const auto supportStratum=[&](std::int32_t strateIndex) const auto supportStratum=[&](std::int32_t strateIndex)
->const VoxelForgeCore::ResolvedLayoutStratumV2* ->const VoxelForgeCore::ResolvedLayoutStratumV2*
{ {
@@ -5563,10 +5807,16 @@ private:
&&supportUpper->passageCapabilities.hasFloatingIslandLanding; &&supportUpper->passageCapabilities.hasFloatingIslandLanding;
constants.surfaceToTunnelSupport=supportUpper&&supportLower constants.surfaceToTunnelSupport=supportUpper&&supportLower
&&supportUpper->lowerInterfaceCapabilities.surfaceToTunnelLandingSupport; &&supportUpper->lowerInterfaceCapabilities.surfaceToTunnelLandingSupport;
constants.firstFloorQ=passage.controlPoints.front().positionSubvoxels[2] constants.controlFloorProfileQ.resize(passage.controlPoints.size());
-passage.controlPoints.front().radiusSubvoxels+playerCenterAboveFloorQ; for(std::size_t pointIndex=0;pointIndex<passage.controlPoints.size();++pointIndex)
constants.lastFloorQ=passage.controlPoints.back().positionSubvoxels[2] {
-passage.controlPoints.back().radiusSubvoxels+playerCenterAboveFloorQ; const auto& point=passage.controlPoints[pointIndex];
constants.controlFloorProfileQ[pointIndex]=point.positionSubvoxels[2]
-point.radiusSubvoxels+playerCenterAboveFloorQ;
++preparedFloorSamples;
}
constants.firstFloorQ=constants.controlFloorProfileQ.front();
constants.lastFloorQ=constants.controlFloorProfileQ.back();
constants.lastIndex=static_cast<std::ptrdiff_t>(passage.controlPoints.size())-1; constants.lastIndex=static_cast<std::ptrdiff_t>(passage.controlPoints.size())-1;
constants.lowerMouthIndex=std::clamp<std::ptrdiff_t>( constants.lowerMouthIndex=std::clamp<std::ptrdiff_t>(
static_cast<std::ptrdiff_t>(passage.lowerMouthControlIndex),1, static_cast<std::ptrdiff_t>(passage.lowerMouthControlIndex),1,
@@ -5578,6 +5828,43 @@ private:
constants.carveSearchMargin=std::max(passage.mouthRadiusSubvoxels, constants.carveSearchMargin=std::max(passage.mouthRadiusSubvoxels,
passage.midRadiusSubvoxels)+0.25*qPerMetre; passage.midRadiusSubvoxels)+0.25*qPerMetre;
} }
std::size_t preparedCapsuleSegments=0;
for(std::size_t stratumIndex=0;stratumIndex<ResolvedLayout->strates.size();
++stratumIndex)
{
PassageStratumCapsuleSegmentOffsets[stratumIndex]=preparedCapsuleSegments;
const auto& stratum=ResolvedLayout->strates[stratumIndex];
std::vector<const VoxelForgeCore::ResolvedLayoutAnchorV2*> routePoints;
for(const auto& anchor:stratum.anchors)
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
routePoints.push_back(&anchor);
auto& prepared=PassageStratumCapsuleSegments[stratumIndex];
if(routePoints.size()>1)prepared.resize(routePoints.size()-1);
for(std::size_t segmentIndex=0;segmentIndex<prepared.size();++segmentIndex)
{
FPreparedPassageCapsuleSegment& segment=prepared[segmentIndex];
for(std::size_t axis=0;axis<3;++axis)
{
segment.a[axis]=routePoints[segmentIndex]->values[axis];
segment.delta[axis]=routePoints[segmentIndex+1]->values[axis]
-segment.a[axis];
}
const double dx=segment.delta[0],dy=segment.delta[1];
segment.horizontalLengthSq=dx*dx+dy*dy;
++preparedCapsuleSegments;
}
PassageStratumCapsuleSegmentOffsets[stratumIndex+1]=preparedCapsuleSegments;
}
if(tracePreparation)
std::printf("[L98PassagePrepare] passages=%zu segments=%zu floor_samples=%zu capsule_route_segments=%zu bytes=%zu prepare_ms=%.3f\n",
PassageSegmentData.size(),preparedSegments,
preparedFloorSamples,
preparedCapsuleSegments,
preparedSegments*sizeof(FPassageSegmentData)
+preparedFloorSamples*sizeof(double)
+preparedCapsuleSegments*sizeof(FPreparedPassageCapsuleSegment),
std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-preparationStart).count());
} }
void BuildPassageSpatialIndices() void BuildPassageSpatialIndices()
@@ -5595,6 +5882,7 @@ private:
PassageNearestNodes.resize(ResolvedLayout?ResolvedLayout->passages.size():0); PassageNearestNodes.resize(ResolvedLayout?ResolvedLayout->passages.size():0);
PassageMouthSupportRoutes.clear(); PassageMouthSupportRoutes.clear();
PassageMouthSupportRoutes.resize(ResolvedLayout?ResolvedLayout->passages.size():0); PassageMouthSupportRoutes.resize(ResolvedLayout?ResolvedLayout->passages.size():0);
PassageMouthSupportSegmentOffsets.assign(PassageMouthSupportRoutes.size()+1,0);
PassageLaterBoreSources.clear(); PassageLaterBoreSources.clear();
PassageColumnSegmentFlags.clear();PassageShortSpanSegmentFlags.clear(); PassageColumnSegmentFlags.clear();PassageShortSpanSegmentFlags.clear();
PassageHasShortSpanFlags.clear(); PassageHasShortSpanFlags.clear();
@@ -5832,6 +6120,36 @@ private:
} }
} }
} }
const bool traceMouthPreparation=VF_IsL98PassagePrepareTraceEnabled();
const auto mouthPreparationStart=traceMouthPreparation
?std::chrono::steady_clock::now():std::chrono::steady_clock::time_point{};
std::size_t mouthSupportSegmentCount=0;
for(std::size_t passageIndex=0;passageIndex<PassageMouthSupportRoutes.size();
++passageIndex)
{
PassageMouthSupportSegmentOffsets[passageIndex]=mouthSupportSegmentCount;
for(auto& segment:PassageMouthSupportRoutes[passageIndex])
{
for(std::size_t axis=0;axis<4;++axis)
{
segment.preparedA[axis]=static_cast<double>(segment.a[axis]);
segment.preparedDelta[axis]=axis<2
?static_cast<double>(segment.b[axis]-segment.a[axis])
:static_cast<double>(segment.b[axis])-segment.a[axis];
}
const double dx=segment.preparedDelta[0];
const double dy=segment.preparedDelta[1];
segment.horizontalLengthSq=dx*dx+dy*dy;
++mouthSupportSegmentCount;
}
PassageMouthSupportSegmentOffsets[passageIndex+1]=mouthSupportSegmentCount;
}
if(traceMouthPreparation)
std::printf("[L98PassageMouthPrepare] segments=%zu bytes=%zu prepare_ms=%.3f\n",
mouthSupportSegmentCount,
mouthSupportSegmentCount*sizeof(FPassageMouthSupportSegment),
std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-mouthPreparationStart).count());
// Precompute the later-bore leaves that can intersect each tread segment. // Precompute the later-bore leaves that can intersect each tread segment.
// Passage support samples normally stay within this source envelope, so the // Passage support samples normally stay within this source envelope, so the
// hot query can test a short list of physical bounds instead of walking a // hot query can test a short list of physical bounds instead of walking a
@@ -6627,19 +6945,23 @@ private:
for(auto it=candidate;it!=node.segments.end()&&*it<end;++it) for(auto it=candidate;it!=node.segments.end()&&*it<end;++it)
{ {
const std::size_t index=*it; const std::size_t index=*it;
const auto& a=passage.controlPoints[index];const auto& b=passage.controlPoints[index+1]; const FPassageSegmentData& segment=GetPassageSegmentData(
const double ax=a.positionSubvoxels[0],ay=a.positionSubvoxels[1],az=a.positionSubvoxels[2]; passageIndex,index);
const double dx=static_cast<double>(b.positionSubvoxels[0])-ax; const auto* l98TileQueries=ActivePassageTileQueries;
const double dy=static_cast<double>(b.positionSubvoxels[1])-ay; if(l98TileQueries&&l98TileQueries->matches(*this))
const double dz=static_cast<double>(b.positionSubvoxels[2])-az; l98TileQueries->recordPreparedSegmentUse(
const double lengthSq=dx*dx+dy*dy+dz*dz; FScopedPassageTileQueries::EPreparedSegmentUse::Capsule);
const double ax=segment.a[0],ay=segment.a[1],az=segment.a[2];
const double dx=segment.delta[0],dy=segment.delta[1],dz=segment.delta[2];
const double lengthSq=segment.lengthSq;
if(lengthSq<=0.0)continue; if(lengthSq<=0.0)continue;
const double horizontalLengthSq=dx*dx+dy*dy; const double horizontalLengthSq=segment.horizontalLengthSq;
const double t=horizontalLengthSq>0.0 const double t=horizontalLengthSq>0.0
?std::clamp(((qx-ax)*dx+(qy-ay)*dy)/horizontalLengthSq,0.0,1.0):0.0; ?std::clamp(PassageXYProjectionNumerator(passageIndex,index,qx,qy)
/horizontalLengthSq,0.0,1.0):0.0;
const double centerX=ax+t*dx,centerY=ay+t*dy; const double centerX=ax+t*dx,centerY=ay+t*dy;
const double centerZ=az+t*dz-(static_cast<double>(a.radiusSubvoxels) const double centerZ=az+t*dz-(segment.radiusA
+t*(static_cast<double>(b.radiusSubvoxels)-a.radiusSubvoxels))+1.13*qPerMetre; +t*segment.radiusDelta)+1.13*qPerMetre;
const double horizontalX=qx-centerX,horizontalY=qy-centerY; const double horizontalX=qx-centerX,horizontalY=qy-centerY;
const double vertical=std::max(0.0,std::abs(qz-centerZ)-0.54*qPerMetre); const double vertical=std::max(0.0,std::abs(qz-centerZ)-0.54*qPerMetre);
const double distance=DeterministicSqrt(horizontalX*horizontalX+horizontalY*horizontalY const double distance=DeterministicSqrt(horizontalX*horizontalX+horizontalY*horizontalY
@@ -6741,27 +7063,27 @@ private:
constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
const double qPerMetre=Q*1000000.0/std::max<std::uint32_t>(1, const double qPerMetre=Q*1000000.0/std::max<std::uint32_t>(1,
ResolvedLayout->coreVoxelSizeMicrometres); ResolvedLayout->coreVoxelSizeMicrometres);
const auto& a=passage.controlPoints[SegmentIndex];
const auto& b=passage.controlPoints[SegmentIndex+1];
const auto upperStratum=std::find_if(ResolvedLayout->strates.begin(), const auto upperStratum=std::find_if(ResolvedLayout->strates.begin(),
ResolvedLayout->strates.end(),[&passage](const auto& value) ResolvedLayout->strates.end(),[&passage](const auto& value)
{return value.strateIndex==passage.upperStrateIndex;}); {return value.strateIndex==passage.upperStrateIndex;});
const bool surfaceWorldUpper=upperStratum!=ResolvedLayout->strates.end() const bool surfaceWorldUpper=upperStratum!=ResolvedLayout->strates.end()
&&upperStratum->passageCapabilities.hasSurfaceLanding; &&upperStratum->passageCapabilities.hasSurfaceLanding;
const double ax=static_cast<double>(a.positionSubvoxels[0]); const FPassageSegmentData& segment=GetPassageSegmentData(PassageIndex,SegmentIndex);
const double ay=static_cast<double>(a.positionSubvoxels[1]); const auto* l98TileQueries=ActivePassageTileQueries;
const double dx=static_cast<double>(b.positionSubvoxels[0])-ax; if(l98TileQueries&&l98TileQueries->matches(*this))
const double dy=static_cast<double>(b.positionSubvoxels[1])-ay; l98TileQueries->recordPreparedSegmentUse(
const double horizontalLengthSq=dx*dx+dy*dy; FScopedPassageTileQueries::EPreparedSegmentUse::Support);
const double ax=segment.a[0],ay=segment.a[1];
const double dx=segment.delta[0],dy=segment.delta[1];
const double horizontalLengthSq=segment.horizontalLengthSq;
const double qx=static_cast<double>(X)*Q,qy=static_cast<double>(Y)*Q; const double qx=static_cast<double>(X)*Q,qy=static_cast<double>(Y)*Q;
const double qz=static_cast<double>(Z)*Q; const double qz=static_cast<double>(Z)*Q;
const double t=horizontalLengthSq>0.0 const double t=horizontalLengthSq>0.0
?std::clamp(((qx-ax)*dx+(qy-ay)*dy)/horizontalLengthSq,0.0,1.0):0.0; ?std::clamp(PassageXYProjectionNumerator(PassageIndex,SegmentIndex,qx,qy)
/horizontalLengthSq,0.0,1.0):0.0;
const double centerX=ax+t*dx,centerY=ay+t*dy; const double centerX=ax+t*dx,centerY=ay+t*dy;
const double centerZ=a.positionSubvoxels[2]+t*( const double centerZ=segment.a[2]+t*segment.delta[2];
static_cast<double>(b.positionSubvoxels[2])-a.positionSubvoxels[2]); const double radius=segment.radiusA+t*segment.radiusDelta;
const double radius=a.radiusSubvoxels+t*(
static_cast<double>(b.radiusSubvoxels)-a.radiusSubvoxels);
// Embed the deck 13 cm above the bore bottom: within the 25 cm support // Embed the deck 13 cm above the bore bottom: within the 25 cm support
// probe, while leaving 16 cm between it and the capsule's lowest sample. // probe, while leaving 16 cm between it and the capsule's lowest sample.
const double floorQ=centerZ-radius const double floorQ=centerZ-radius
@@ -6819,27 +7141,31 @@ private:
const double capsuleRadius=0.37*qPerMetre; const double capsuleRadius=0.37*qPerMetre;
const double capsuleHalfHeight=0.86*qPerMetre; const double capsuleHalfHeight=0.86*qPerMetre;
const double capsuleRadiusSquared=capsuleRadius*capsuleRadius; const double capsuleRadiusSquared=capsuleRadius*capsuleRadius;
const VoxelForgeCore::ResolvedLayoutAnchorV2* previous=nullptr; const std::size_t lowerStratumIndex=static_cast<std::size_t>(
for(const auto& anchor:lower->anchors) lower-ResolvedLayout->strates.begin());
const auto& routeSegments=PassageStratumCapsuleSegments[
lowerStratumIndex];
for(std::size_t segmentIndex=0;segmentIndex<routeSegments.size();
++segmentIndex)
{ {
if(anchor.kind!="route_point"||anchor.values.size()<3)continue; const FPreparedPassageCapsuleSegment& segment=
if(previous) routeSegments[segmentIndex];
{ const auto* l98TileQueries=ActivePassageTileQueries;
const double ax=previous->values[0],ay=previous->values[1]; if(l98TileQueries&&l98TileQueries->matches(*this))
const double dx=static_cast<double>(anchor.values[0])-ax; l98TileQueries->recordPreparedSegmentUse(
const double dy=static_cast<double>(anchor.values[1])-ay; FScopedPassageTileQueries::EPreparedSegmentUse::Capsule);
const double lengthSquared=dx*dx+dy*dy; const double ax=segment.a[0],ay=segment.a[1];
const double t=lengthSquared>0.0?std::clamp( const double dx=segment.delta[0],dy=segment.delta[1];
((qx-ax)*dx+(qy-ay)*dy)/lengthSquared,0.0,1.0):0.0; const double lengthSquared=segment.horizontalLengthSq;
const double routeX=ax+t*dx,routeY=ay+t*dy; const double t=lengthSquared>0.0?std::clamp(
const double routeZ=previous->values[2]+t*( PassageCapsuleRouteXYProjectionNumerator(lowerStratumIndex,
static_cast<double>(anchor.values[2])-previous->values[2]); segmentIndex,qx,qy)/lengthSquared,0.0,1.0):0.0;
const double routeDx=qx-routeX,routeDy=qy-routeY; const double routeX=ax+t*dx,routeY=ay+t*dy;
if(routeDx*routeDx+routeDy*routeDy<=capsuleRadiusSquared const double routeZ=segment.a[2]+t*segment.delta[2];
&&std::abs(qz-routeZ)<=capsuleHalfHeight) const double routeDx=qx-routeX,routeDy=qy-routeY;
return maximumRadius+qPerMetre; if(routeDx*routeDx+routeDy*routeDy<=capsuleRadiusSquared
} &&std::abs(qz-routeZ)<=capsuleHalfHeight)
previous=&anchor; return maximumRadius+qPerMetre;
} }
} }
} }
@@ -12778,6 +13104,7 @@ public:
return; return;
} }
if(Count<=0||OutDensity==nullptr)return; if(Count<=0||OutDensity==nullptr)return;
FScopedPassageColumnXYReuse passageColumnXYReuse(*this);
FScopedRoute l77DispatchRoute(ERoute::Dispatch); FScopedRoute l77DispatchRoute(ERoute::Dispatch);
FScopedPhase l77DispatchPhase(EPhase::Dispatch); FScopedPhase l77DispatchPhase(EPhase::Dispatch);
FScopedDensityQueryProfile queryProfile(EDensityQueryKind::Lattice, FScopedDensityQueryProfile queryProfile(EDensityQueryKind::Lattice,
@@ -13543,7 +13870,13 @@ private:
bool PassageFeatureBoundsFailOpen=false; bool PassageFeatureBoundsFailOpen=false;
std::vector<std::vector<FPassageNearestNode>> PassageNearestNodes; std::vector<std::vector<FPassageNearestNode>> PassageNearestNodes;
std::vector<std::vector<FPassageMouthSupportSegment>> PassageMouthSupportRoutes; std::vector<std::vector<FPassageMouthSupportSegment>> PassageMouthSupportRoutes;
std::vector<std::size_t> PassageMouthSupportSegmentOffsets;
std::vector<FPassageComposerConstants> PassageComposerConstants; std::vector<FPassageComposerConstants> PassageComposerConstants;
std::vector<std::vector<FPassageSegmentData>> PassageSegmentData;
std::vector<std::size_t> PassageSegmentOffsets;
std::vector<std::vector<FPreparedPassageCapsuleSegment>>
PassageStratumCapsuleSegments;
std::vector<std::size_t> PassageStratumCapsuleSegmentOffsets;
std::vector<std::vector<FPassageLaterBoreSource>> PassageLaterBoreSources; std::vector<std::vector<FPassageLaterBoreSource>> PassageLaterBoreSources;
std::vector<FPassageProofBound> PassageProofBounds; std::vector<FPassageProofBound> PassageProofBounds;
std::vector<FPassageProofNode> PassageProofNodes; std::vector<FPassageProofNode> PassageProofNodes;
+89
View File
@@ -18,6 +18,7 @@
#include <new> #include <new>
#include <stdexcept> #include <stdexcept>
#include <sstream> #include <sstream>
#include <cstdlib>
namespace vfcore::detail namespace vfcore::detail
{ {
@@ -2799,6 +2800,94 @@ namespace VoxelForgeCore
options.profilePassageComposer?&out.diagnostics:nullptr,key.lod, options.profilePassageComposer?&out.diagnostics:nullptr,key.lod,
options.diagnosticPassageSourceFloorVoxels, options.diagnosticPassageSourceFloorVoxels,
options.diagnosticPassageCarveReachMetres); options.diagnosticPassageCarveReachMetres);
static std::atomic<bool> l98ProjectionProbeStarted{false};
const char* l98ProjectionProbeSwitch=std::getenv(
"VOXELFORGE_L98_PASSAGE_REUSE_PROBE");
bool expectedProbeStart=false;
if(l98ProjectionProbeSwitch&&l98ProjectionProbeSwitch[0]=='1'
&&l98ProjectionProbeSwitch[1]=='\0'&&!tileShape.IsNativeOnly()
&&impl_->resolvedLayout
&&!impl_->resolvedLayout->passages.empty()
&&l98ProjectionProbeStarted.compare_exchange_strong(expectedProbeStart,true,
std::memory_order_relaxed))
{
constexpr std::int64_t ProbePadding=8
*WorldAssemblySubvoxelsPerVoxel;
std::array<std::int64_t,3> probeMinimum={
std::numeric_limits<std::int64_t>::max(),
std::numeric_limits<std::int64_t>::max(),
std::numeric_limits<std::int64_t>::max()};
std::array<std::int64_t,3> probeMaximum={
std::numeric_limits<std::int64_t>::min(),
std::numeric_limits<std::int64_t>::min(),
std::numeric_limits<std::int64_t>::min()};
const auto includeProbePoint=[&](std::size_t axis,double coordinate,
double radius)
{
if(!std::isfinite(coordinate)||!std::isfinite(radius))return;
probeMinimum[axis]=std::min(probeMinimum[axis],
static_cast<std::int64_t>(std::floor(coordinate-radius))-ProbePadding);
probeMaximum[axis]=std::max(probeMaximum[axis],
static_cast<std::int64_t>(std::ceil(coordinate+radius))+ProbePadding);
};
for(const auto& passage:impl_->resolvedLayout->passages)
for(const auto& point:passage.controlPoints)
for(std::size_t axis=0;axis<3;++axis)
includeProbePoint(axis,static_cast<double>(
point.positionSubvoxels[axis]),
static_cast<double>(point.radiusSubvoxels));
for(const auto& stratum:impl_->resolvedLayout->strates)
for(const auto& anchor:stratum.anchors)
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
for(std::size_t axis=0;axis<3;++axis)
includeProbePoint(axis,anchor.values[axis],0.0);
std::uint64_t probeRuns=0,probeSamples=0,probeMismatches=0;
if(probeMinimum[0]<=probeMaximum[0]
&&probeMinimum[1]<=probeMaximum[1]
&&probeMinimum[2]<=probeMaximum[2])
{
const FStandaloneShapeWorld::FScopedPassageTileQueries probeScope(
tileShape,probeMinimum,probeMaximum,nullptr,key.lod);
constexpr int32 ProbeCount=8;
float runDensity[ProbeCount]{};
for(const auto& passage:impl_->resolvedLayout->passages)
for(std::size_t segmentIndex=0;
segmentIndex+1<passage.controlPoints.size();++segmentIndex)
{
const auto& a=passage.controlPoints[segmentIndex];
const auto& b=passage.controlPoints[segmentIndex+1];
const auto toVoxel=[](double subvoxels)
{return static_cast<std::int32_t>(std::llround(
subvoxels/WorldAssemblySubvoxelsPerVoxel));};
const std::int32_t x=toVoxel(0.5*(
static_cast<double>(a.positionSubvoxels[0])
+static_cast<double>(b.positionSubvoxels[0])));
const std::int32_t y=toVoxel(0.5*(
static_cast<double>(a.positionSubvoxels[1])
+static_cast<double>(b.positionSubvoxels[1])));
const std::int32_t zMid=toVoxel(0.5*(
static_cast<double>(a.positionSubvoxels[2])
+static_cast<double>(b.positionSubvoxels[2])));
const std::int32_t zStart=zMid-ProbeCount/2;
tileShape.DensityAtLatticeZRun(x,y,zStart,1,ProbeCount,runDensity);
++probeRuns;
probeSamples+=ProbeCount;
for(std::int32_t lane=0;lane<ProbeCount;++lane)
{
const float scalarDensity=tileShape.DensityAtLattice(
x,y,zStart+lane);
if(std::bit_cast<std::uint32_t>(scalarDensity)
!=std::bit_cast<std::uint32_t>(runDensity[lane]))
++probeMismatches;
}
}
}
std::printf("[L98PassageReuseProbe] z_runs=%llu samples=%llu scalar_mismatches=%llu status=%s\n",
static_cast<unsigned long long>(probeRuns),
static_cast<unsigned long long>(probeSamples),
static_cast<unsigned long long>(probeMismatches),
probeRuns>0&&probeMismatches==0?"PASS":"FAIL");
}
if(options.profilePassageComposer) if(options.profilePassageComposer)
{ {
out.diagnostics.passageComposerProfiled=true; out.diagnostics.passageComposerProfiled=true;
@@ -0,0 +1,61 @@
# L98 — exact passage building blocks
## Changes and work counts
Passage setup now prepares per-segment chord endpoints/deltas, squared lengths, radii, Catmull–Rom inputs, and floor-profile samples once per world. Capsule-route and mouth-support callbacks also use prepared segment endpoints and deltas. The scalar expression grouping, divisions, and square roots remain in the same order. Relief interpolation sharing was not added.
Measured preparation per world: 310 passage segments, 313 floor samples, and 32 capsule-route segments; the trace reports 66,296 bytes. The 32 mouth-route segments report another 4,608 bytes. Preparation measured 0.025–0.040 ms in the whole-view hash runs (mouth preparation rounded to 0.000 ms).
The streamed-view counters below are prepared-data reads/uses, not baseline arithmetic counts. They directly confirm the prepared paths were exercised. Avoided recomputations are inferred from the replaced per-sample expressions; the old arithmetic was not separately instrumented.
| World | Floor-profile reads | Carve uses | Support/tread uses | Capsule uses | Mouth-support uses |
|---|---:|---:|---:|---:|---:|
| FI | 129,382,181 | 8,255,393 | 5,131,690 | 2,693,464 | 14,773,980 |
| TN | 396,540,009 | 50,346,149 | 19,176,668 | 11,657,176 | 63,632,356 |
| SW | 318,412,931 | 50,093,885 | 17,065,041 | 10,323,467 | 60,102,531 |
| VS | 99,874,533 | 5,141,277 | 3,142,836 | 662,072 | 8,984,390 |
The XY cache keeps the scalar numerator grouping `(qx-ax)*dx + (qy-ay)*dy`; clamp, winner, reach, and composition remain per lane. A direct probe exercised 310 z-runs × 8 samples in each world: 2,558 numerator computations and 31,085 cache hits, with zero density mismatches against scalar evaluation. The measured hit fraction was 92.4% of numerator requests. The ordinary streamed view does not call the z-run API, so these XY reuse counts come from the opt-in direct probe, not natural view traffic.
## Exactness
Compared rebuilt committed-HEAD baseline to L98 Clang, then L98 Clang to L98 GCC. Density tile hashes aggregate the density sample bit patterns. All comparisons had zero tile-hash mismatches at every LOD, zero LOD0–2 density-tile mismatches, and identical whole-view density, mesh, payload, and topology hashes.
| World | Whole-view tiles | LOD0–2 density mismatches | Whole-view density | Mesh | Payload | Topology |
|---|---:|---:|---|---|---|---|
| FI | 2,850 | 0 | `40b76d0072a2adcd` | `8559df0850d4d7f1` | `7700965ff31ac702` | `0e7f2a16b4cc71a9` |
| TN | 2,249 | 0 | `99ca213b21ce86e4` | `2977525405b79fff` | `3d7d2efb581710bd` | `af1916c949224871` |
| SW | 2,233 | 0 | `8d23c26f5c092be1` | `15c3aad2e29d9ea0` | `3d48f68d511c21df` | `5b1584676884edd8` |
| VS | 2,329 | 0 | `876b44981d106be3` | `aaef57cb08bd6845` | `b19617df9f4acbb1` | `e8eee991260adff4` |
The direct XY probe also reported 2,480 sample comparisons and zero mismatches for each world. Hash runs and probe logs are under `Saved/Linux/l98/hashes/` and `Saved/Linux/l98/reuse-counts/`.
## L77 passage-share census
Mean near-LOD0–2 passage share, three profile runs per world and binary:
| World | HEAD before | L98 after | Change |
|---|---:|---:|---:|
| FI | 57.82% | 56.74% | −1.08 pp |
| TN | 55.94% | 55.70% | −0.24 pp |
This is a census comparison, not evidence of a timing gain. Logs and raw run values are in `Saved/Linux/l98/census/`.
## Cold readiness
Median seconds from three interleaved HEAD/L98 pairs per cell. Each binary/stratum/worker-count cell had a warmup. Cold timing includes world/layout setup and passage preparation. The LOD0–2 and whole-view tile counts are included below.
| World | LOD0–2 / whole tiles | W8 HEAD → L98 (LOD0–2 / whole s) | W12 HEAD → L98 (LOD0–2 / whole s) |
|---|---:|---:|---:|
| FI | 1,408 / 2,850 | 1.070 / 2.341 → 1.099 / 2.369 | 1.029 / 2.147 → 1.005 / 2.123 |
| TN | 1,216 / 2,249 | 2.672 / 4.034 → 2.680 / 4.059 | 2.211 / 3.357 → 2.207 / 3.357 |
| SW | 1,344 / 2,233 | 2.713 / 3.929 → 2.496 / 3.623 | 2.255 / 3.313 → 2.176 / 3.183 |
| VS | 1,344 / 2,329 | 1.292 / 2.315 → 1.264 / 2.257 | 1.094 / 2.015 → 1.107 / 2.041 |
All 48 measured readiness runs completed. Results are close and mixed across worlds; they do not show a broad readiness speedup. Full results and logs are in `Saved/Linux/l98/readiness/`.
## Verification and failed attempts
- Fast tier: PASS (269 pass checks, 5 diagnostic checks), 80 runs, 303.5 s. The first invocation stopped before checks because its output directory had no built headless executable; building it and rerunning passed. Summary: `Saved/Linux/l98/verify-fast/fast-summary.json`.
- Linux UE build: `VFHostEditor Linux Development`, `-NoUBA -MaxParallelActions=4`, succeeded in 36.60 s after timing. The first attempt stopped before compilation when UBT could not rotate its default log in the read-only user config directory. Retry used an explicit log at `Saved/Linux/l98/unreal-build-ubt.log`; no `--as` limit was used.
- No measured run exceeded the 30-minute limit. All experiment processes used `taskset -c 0-11`; headless runs also used the 6 GiB address-space limit.
@@ -0,0 +1,91 @@
#!/usr/bin/env python3
"""Three interleaved L77 passage-share censuses for pre-L98 and L98 Core."""
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/l98/census"
DATA = ROOT / "Standalone/data/owner_world.json"
BINARIES = {
"pre": ROOT / "Saved/Linux/l98/profile-pre-bin/VoxelForgeCoreHeadless-l98-pre-profile",
"post": ROOT / "Saved/Linux/l98/profile-post-bin/VoxelForgeCoreHeadless-l98-post-profile",
}
CAMERAS = {
"FI": (25.8369140625, 3.19921875, -737.0),
"TN": (19.166015625, -14.2001953125, -753.3740234375),
}
SAMPLE = re.compile(r"^\[L77Sample\] pc=(0x\w+) 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, label: str, rep: int) -> dict:
OUT.mkdir(parents=True, exist_ok=True)
log = OUT / f"{label}-{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", "timeout", "1740s", str(BINARIES[label]), 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")
samples = [(int(lod), phase, int(weight))
for _pc, lod, phase, weight in SAMPLE.findall(output)]
sampler = SAMPLER.search(output)
if result.returncode or not samples or sampler is None:
raise RuntimeError(f"census failed: exit={result.returncode} {log}")
near = [row for row in samples 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, "binary": label, "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,
"log": str(log.relative_to(ROOT)),
}
print("[L98CensusRun] " + 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}")
rows = []
orders = (("pre", "post"), ("post", "pre"), ("pre", "post"))
for rep, order in enumerate(orders, 1):
for label in order:
for world in CAMERAS:
rows.append(run(world, label, rep))
(OUT / "runs-partial.json").write_text(json.dumps(rows, indent=2) + "\n",
encoding="utf-8")
summary = {}
for world in CAMERAS:
summary[world] = {}
for label in BINARIES:
values = [row["near_lod_passage_share_pct"] for row in rows
if row["world"] == world and row["binary"] == label]
summary[world][label] = {
"runs_pct": values, "mean_pct": sum(values) / len(values),
}
(OUT / "summary.json").write_text(json.dumps(
{"runs": rows, "near_lod_passage_share": summary}, indent=2) + "\n",
encoding="utf-8")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,126 @@
#!/usr/bin/env python3
"""Compare L97 baseline and L98 Clang/GCC whole-view tile hashes at LOD0-2."""
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/l98/hashes"
DATA = ROOT / "Standalone/data/owner_world.json"
BINARIES = {
"HEAD": ROOT / "Saved/Linux/l98/pre-bin/VoxelForgeCoreHeadless-l98-pre-clang",
"L98-Clang": ROOT / "Saved/Linux/l98/bin/VoxelForgeCoreHeadless-l98-clang19",
"L98-GCC": ROOT / "Saved/Linux/l98/bin/VoxelForgeCoreHeadless-l98-gcc",
}
CAMERAS = {
0: (8.0, 16.0, -372.2626953125),
1: (25.8369140625, 3.19921875, -737.0),
2: (19.166015625, -14.2001953125, -753.3740234375),
3: (-20.0625, -5.068359375, -900.353515625),
}
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)
FIELDS = ("density", "mesh", "payload", "topology")
def run(strate: int, label: str) -> dict:
OUT.mkdir(parents=True, exist_ok=True)
log = OUT / f"{label.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_L98_PASSAGE_PREP_TRACE=1", "timeout", "1740s",
str(BINARIES[label]), str(DATA), "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", "--workers=8", "--clip-radius=3",
"--max-clip-level=8", "--max-level=8", 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) != int(whole.group(1)):
raise RuntimeError(f"hash run failed or incomplete: exit={result.returncode} {log}")
prep = re.findall(r"^\[L98PassagePrepare\] (.*)$", output, re.M)
mouth_prep = re.findall(r"^\[L98PassageMouthPrepare\] (.*)$", output, re.M)
row = {
"binary": label, "strate": strate, "exit_code": result.returncode,
"wall_s": round(time.monotonic() - start, 3), "tile_hash_count": len(hashes),
"tile_hashes_by_lod": {str(lod): sum(1 for key in hashes if key[0] == lod)
for lod in sorted({key[0] for key in hashes})},
"whole_hash": whole.group(1, 2, 3, 4, 5), "hashes": hashes,
"prepare_traces": prep, "mouth_prepare_traces": mouth_prep,
"log": str(log.relative_to(ROOT)),
}
print("[L98HashRun] " + json.dumps(
{k: v for k, v in row.items() if k != "hashes"},
sort_keys=True), flush=True)
return row
def compare(strate: int, left_label: str, right_label: str,
left: dict, right: dict) -> dict:
mismatch_keys = sorted(key for key in set(left["hashes"]) | set(right["hashes"])
if left["hashes"].get(key) != right["hashes"].get(key))
component_mismatches = {}
for component_index, component in enumerate(FIELDS):
component_mismatches[component] = sum(
left["hashes"].get(key, (None,) * 4)[component_index]
!= right["hashes"].get(key, (None,) * 4)[component_index]
for key in set(left["hashes"]) | set(right["hashes"]))
near_density_mismatches = sum(
key[0] <= 2 and left["hashes"].get(key, (None,) * 4)[0]
!= right["hashes"].get(key, (None,) * 4)[0]
for key in set(left["hashes"]) | set(right["hashes"]))
item = {
"strate": strate, "left": left_label, "right": right_label,
"tiles_left": len(left["hashes"]), "tiles_right": len(right["hashes"]),
"whole_hash_identical": left["whole_hash"] == right["whole_hash"],
"tile_hashes_identical": not mismatch_keys,
"mismatch_tile_count": len(mismatch_keys),
"component_mismatch_tile_counts": component_mismatches,
"lod0_2_density_mismatch_tile_count": near_density_mismatches,
"first_mismatches": mismatch_keys[:8],
"left_whole_hash": left["whole_hash"], "right_whole_hash": right["whole_hash"],
}
print("[L98HashComparison] " + json.dumps(item, sort_keys=True), flush=True)
return item
def main() -> int:
for binary in (*BINARIES.values(), DATA):
if not binary.is_file():
raise SystemExit(f"missing input: {binary}")
runs, comparisons = [], []
for strate in CAMERAS:
by_binary = {label: run(strate, label) for label in BINARIES}
comparisons.append(compare(strate, "HEAD", "L98-Clang",
by_binary["HEAD"], by_binary["L98-Clang"]))
comparisons.append(compare(strate, "L98-Clang", "L98-GCC",
by_binary["L98-Clang"], by_binary["L98-GCC"]))
runs.extend({key: value for key, value in row.items() if key != "hashes"}
for row in by_binary.values())
summary = {"comparisons": comparisons, "runs": runs}
(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,102 @@
#!/usr/bin/env python3
"""L80 cold-readiness protocol: three interleaved L97/L98 pairs at W8 and W12."""
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/l98/readiness"
DATA = ROOT / "Standalone/data/owner_world.json"
BINARIES = {
"HEAD": ROOT / "Saved/Linux/l98/pre-bin/VoxelForgeCoreHeadless-l98-pre-clang",
"L98": ROOT / "Saved/Linux/l98/bin/VoxelForgeCoreHeadless-l98-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, workers: int, label: str, phase: str, rep: int) -> dict:
OUT.mkdir(parents=True, exist_ok=True)
log = OUT / f"w{workers}-s{strate}-{label.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", "timeout", "1740s", str(BINARIES[label]), str(DATA),
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
f"--workers={workers}", "--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, "workers": workers, "binary": label, "phase": phase,
"replicate": rep, "exit_code": result.returncode,
"wall_s": round(time.monotonic() - start, 3),
"lod02_tiles": f"{near_match.group(1)}/{near_match.group(2)}",
"lod02_ready_s": float(near_match.group(3)),
"whole_view_ready_s": float(pipeline["whole_view_ready"].removesuffix("s")),
"whole_tiles": pipeline["whole_tiles"].split("/")[0],
"log": str(log.relative_to(ROOT)),
}
print("[L98ReadinessRun] " + 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}")
rows = []
orders = (("HEAD", "L98"), ("L98", "HEAD"), ("HEAD", "L98"))
for workers in (8, 12):
for strate in CAMERAS:
for label in BINARIES:
run(strate, workers, label, "warmup", 0)
for strate in CAMERAS:
for rep, order in enumerate(orders, 1):
for label in order:
rows.append(run(strate, workers, label, "measured", rep))
(OUT / "runs-partial.json").write_text(json.dumps(rows, indent=2) + "\n",
encoding="utf-8")
summary = []
for workers in (8, 12):
for strate in CAMERAS:
group = [row for row in rows
if row["workers"] == workers and row["strate"] == strate]
item = {"workers": workers, "strate": strate, "measured_pairs": 3}
for label in BINARIES:
subset = [row for row in group if row["binary"] == label]
for metric in ("lod02_ready_s", "whole_view_ready_s"):
values = [row[metric] for row in subset]
item[f"{label}_{metric}_runs"] = values
item[f"{label}_{metric}_median"] = statistics.median(values)
item[f"{label}_process_wall_s_runs"] = [row["wall_s"] for row in subset]
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,105 @@
#!/usr/bin/env python3
"""Count prepared-segment use and XY projection reuse in LOD0-2 world views."""
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/l98/reuse-counts"
DATA = ROOT / "Standalone/data/owner_world.json"
BINARY = ROOT / "Saved/Linux/l98/bin/VoxelForgeCoreHeadless-l98-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),
}
REUSE = re.compile(
r"^\[L98PassageReuseTile\] lod=(-?\d+) projection_columns=(\d+) "
r"projection_computes=(\d+) projection_hits=(\d+) floor_profile_reads=(\d+) "
r"carve_prepared=(\d+) support_prepared=(\d+) capsule_prepared=(\d+) "
r"mouth_prepared=(\d+)$", re.M)
PREP = re.compile(r"^\[L98PassagePrepare\] (.*)$", re.M)
MOUTH_PREP = re.compile(r"^\[L98PassageMouthPrepare\] (.*)$", re.M)
PIPELINE = re.compile(r"^\[StreamPipeline\] (.*)$", re.M)
PROBE = re.compile(r"^\[L98PassageReuseProbe\] z_runs=(\d+) samples=(\d+) "
r"scalar_mismatches=(\d+) status=(\S+)$", re.M)
def run(strate: int) -> dict:
OUT.mkdir(parents=True, exist_ok=True)
log = OUT / f"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_L98_PASSAGE_REUSE_COUNTS=1",
"VOXELFORGE_L98_PASSAGE_PREP_TRACE=1",
"VOXELFORGE_L98_PASSAGE_REUSE_PROBE=1", "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}",
]
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")
tile_rows = [tuple(map(int, match.groups())) for match in REUSE.finditer(output)]
prep = PREP.findall(output)
mouth_prep = MOUTH_PREP.findall(output)
pipeline = PIPELINE.search(output)
probe = PROBE.search(output)
if (result.returncode or not tile_rows or not prep or pipeline is None or probe is None
or probe.group(4) != "PASS"):
raise RuntimeError(f"reuse-count run failed or incomplete: exit={result.returncode} {log}")
totals = [sum(row[index] for row in tile_rows) for index in range(1, 9)]
lod_totals = {}
for lod in sorted({row[0] for row in tile_rows}):
lod_rows = [row for row in tile_rows if row[0] == lod]
lod_totals[str(lod)] = {
"tile_scope_rows": len(lod_rows),
"projection_columns": sum(row[1] for row in lod_rows),
"projection_computes": sum(row[2] for row in lod_rows),
"projection_hits": sum(row[3] for row in lod_rows),
"floor_profile_reads": sum(row[4] for row in lod_rows),
"carve_prepared_uses": sum(row[5] for row in lod_rows),
"support_prepared_uses": sum(row[6] for row in lod_rows),
"capsule_prepared_uses": sum(row[7] for row in lod_rows),
"mouth_prepared_uses": sum(row[8] for row in lod_rows),
}
row = {
"strate": strate, "exit_code": result.returncode,
"wall_s": round(time.monotonic() - start, 3), "tile_scope_rows": len(tile_rows),
"lod_scope_rows": {str(lod): sum(1 for row in tile_rows if row[0] == lod)
for lod in sorted({row[0] for row in tile_rows})},
"lod_totals": lod_totals,
"projection_columns": totals[0], "projection_computes": totals[1],
"projection_hits": totals[2], "floor_profile_reads": totals[3],
"carve_prepared_uses": totals[4], "support_prepared_uses": totals[5],
"capsule_prepared_uses": totals[6], "mouth_prepared_uses": totals[7],
"projection_probe": {
"z_runs": int(probe.group(1)), "samples": int(probe.group(2)),
"scalar_mismatches": int(probe.group(3)), "status": probe.group(4),
},
"prepare_traces": prep, "mouth_prepare_traces": mouth_prep,
"stream_pipeline": pipeline.group(1), "log": str(log.relative_to(ROOT)),
}
print("[L98ReuseCountRun] " + 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(strate) for strate in CAMERAS]
(OUT / "summary.json").write_text(json.dumps({"runs": rows}, indent=2) + "\n",
encoding="utf-8")
return 0
if __name__ == "__main__":
raise SystemExit(main())