wip(L28): passage columns/routes culled per tile (BVH); cost back near L24, bit-identical to L27

GenerateMesh scopes passage queries to the tile's sample bounds with a feature AABB BVH plus
per-passage segment BVHs; L9 proof boxes use a passage-bound BVH. Whole view vs L24: FI ~0%, SW
+3.7% (reviewer interleaved). All hashes equal L27; all passage/route/proof audits pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-21 07:42:16 +02:00
1 parent 07b3fe2852
commit f23ab7b9fa
5 files changed
+938 -49

No files matched your search

+17
View File
@@ -2034,6 +2034,23 @@ adapter edit is required. The L26 report contains the eight-style table, hashes,
[`Standalone/Research/WorldAssemblerM3/Linux26/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux26/REPORT.md) [`Standalone/Research/WorldAssemblerM3/Linux26/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux26/REPORT.md)
records the final evidence. records the final evidence.
### 8.45 Tile-bounded passage evaluation — World Assembler M3 Linux L28
Each mesh tile scopes passage composition to the inclusive density-sample AABB, including the
mesher's normal halo. A conservative feature AABB BVH selects route segments, columns and mouth
support for that tile. A per-passage nearest-segment BVH bounds the long helix search; a separate
passage-bound BVH rejects far L9 proof boxes. An unindexed native-only proof object uses a conservative
linear fallback. No field, union order, serialized layout or recipe version changes.
The column's exact complex SDF is evaluated only in XY/Z feature candidates. A cheap density-aware
radial guard remains for samples sharing the column's Z slab: a positive column distance can still
lower positive base density, so skipping that guard needs an exact bound on the base field. This is
the remaining SurfaceWorld cost noted in the Linux L28 report. The passage candidate lists and
proof bounds preserve the L27 output.
[Linux28 report](Standalone/Research/WorldAssemblerM3/Linux28/REPORT.md)
records counts, parity, costs and audit caveats.
## 9. Multiplayer model (listen-server first, dedicated-friendly) ## 9. Multiplayer model (listen-server first, dedicated-friendly)
> **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a > **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a
+9
View File
@@ -1208,6 +1208,15 @@ The shared evaluator is version 13; FloatingIslands geometry remains version 7 a
schema V3 is unchanged. See ARCHITECTURE §8.44 and schema V3 is unchanged. See ARCHITECTURE §8.44 and
[`Standalone/Research/WorldAssemblerM3/Linux26/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux26/REPORT.md). [`Standalone/Research/WorldAssemblerM3/Linux26/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux26/REPORT.md).
### 3.28 Tile-bounded passage evaluation — World Assembler M3 Linux L28
| Symbol | Canonical file | Role |
|---|---|---|
| FScopedPassageTileQueries / BuildPassageFeatureTileList / ResolvedPassageIntersectsBox | Standalone/Core/src/ShapeWorldInternal.h; scope entered by GenerateMesh in Standalone/Core/src/VoxelForgeCore.cpp | Builds conservative per-tile route, column and mouth-support candidates from an AABB BVH; long paths use a nearest-segment BVH and L9 box checks use a separate passage-bound BVH. Empty native-only proof indexes fall back conservatively. The density arithmetic and serialized versions stay unchanged. |
See ARCHITECTURE §8.45 and
[Linux28 report](Standalone/Research/WorldAssemblerM3/Linux28/REPORT.md).
## 4. The density pipeline (most-edited hot path) ## 4. The density pipeline (most-edited hot path)
### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749 ### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749
+781 -49
View File
@@ -44,6 +44,65 @@ public:
const std::vector<FStandalonePassageVoidCrossing>& GetPassageVoidCrossings() const const std::vector<FStandalonePassageVoidCrossing>& GetPassageVoidCrossings() const
{return PassageVoidCrossings;} {return PassageVoidCrossings;}
class FScopedPassageTileQueries
{
public:
FScopedPassageTileQueries(const FStandaloneShapeWorld& shape,
const std::array<std::int64_t,3>& minimumSubvoxels,
const std::array<std::int64_t,3>& maximumSubvoxels)
:shape_(&shape),minimumSubvoxels_(minimumSubvoxels),
maximumSubvoxels_(maximumSubvoxels),previous_(ActivePassageTileQueries)
{
featureIndices_=shape.BuildPassageFeatureTileList(
minimumSubvoxels_,maximumSubvoxels_);
passageRoutes_.assign(shape.ResolvedLayout?shape.ResolvedLayout->passages.size():0,0);
passageColumns_=passageRoutes_;
passageMouthSupports_=passageRoutes_;
for(const auto& crossing:shape.PassageVoidCrossings)
if(crossing.needsColumn
&&crossing.bottomZSubvoxels<=maximumSubvoxels_[2]
&&crossing.topZSubvoxels>=minimumSubvoxels_[2])
columnZOverlap_=true;
for(const std::uint32_t featureIndex:featureIndices_)
{
if(featureIndex>=shape.PassageTileFeatures.size())continue;
const auto& feature=shape.PassageTileFeatures[featureIndex];
if(feature.passageIndex>=passageRoutes_.size())continue;
if(feature.bColumn)passageColumns_[feature.passageIndex]=1;
else if(feature.bMouthSupport)passageMouthSupports_[feature.passageIndex]=1;
else
{
passageRoutes_[feature.passageIndex]=1;
routeFeatureOverlap_=true;
}
}
ActivePassageTileQueries=this;
}
~FScopedPassageTileQueries(){ActivePassageTileQueries=previous_;}
FScopedPassageTileQueries(const FScopedPassageTileQueries&)=delete;
FScopedPassageTileQueries& operator=(const FScopedPassageTileQueries&)=delete;
bool matches(const FStandaloneShapeWorld& shape) const{return shape_==&shape;}
bool hasFeature(std::size_t passageIndex,bool columnFeature) const
{
const auto& candidates=columnFeature?passageColumns_:passageRoutes_;
return passageIndex<candidates.size()&&candidates[passageIndex]!=0;
}
bool hasMouthSupport(std::size_t passageIndex) const
{return passageIndex<passageMouthSupports_.size()&&passageMouthSupports_[passageIndex]!=0;}
bool hasRouteFeatureOverlap() const{return routeFeatureOverlap_;}
bool hasColumnZOverlap() const{return columnZOverlap_;}
std::size_t featureCount() const{return featureIndices_.size();}
private:
const FStandaloneShapeWorld* shape_;
std::array<std::int64_t,3> minimumSubvoxels_,maximumSubvoxels_;
std::vector<std::uint32_t> featureIndices_;
std::vector<std::uint8_t> passageRoutes_,passageColumns_,passageMouthSupports_;
bool routeFeatureOverlap_=false,columnZOverlap_=false;
const FScopedPassageTileQueries* previous_;
};
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
{ {
@@ -126,6 +185,12 @@ public:
{ {
if(!ResolvedLayout||ResolvedLayout->passages.empty()||!std::isfinite(X) if(!ResolvedLayout||ResolvedLayout->passages.empty()||!std::isfinite(X)
||!std::isfinite(Y)||!std::isfinite(Z))return density; ||!std::isfinite(Y)||!std::isfinite(Z))return density;
const auto* passageTileQueries=ActivePassageTileQueries;
const bool passageTileScopeMatches=passageTileQueries
&&passageTileQueries->matches(*this);
if(passageTileScopeMatches&&density>=0.0f
&&!passageTileQueries->hasRouteFeatureOverlap()
&&!passageTileQueries->hasColumnZOverlap())return density;
constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
const double qx=static_cast<double>(X)*Q,qy=static_cast<double>(Y)*Q,qz=static_cast<double>(Z)*Q; const double qx=static_cast<double>(X)*Q,qy=static_cast<double>(Y)*Q,qz=static_cast<double>(Z)*Q;
const double qPerMetre=Q*1000000.0/std::max<std::uint32_t>(1,ResolvedLayout->coreVoxelSizeMicrometres); const double qPerMetre=Q*1000000.0/std::max<std::uint32_t>(1,ResolvedLayout->coreVoxelSizeMicrometres);
@@ -190,9 +255,14 @@ public:
// Keep the native surface route connected to the entrance. Passage carving // Keep the native surface route connected to the entrance. Passage carving
// then removes only the parts that overlap the tunnel, whose own tread owns // then removes only the parts that overlap the tunnel, whose own tread owns
// the interior ramp. // the interior ramp.
for(const auto& passage:ResolvedLayout->passages) for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();++passageIndex)
{
const auto& passage=ResolvedLayout->passages[passageIndex];
const bool tileHasSupport=passageTileScopeMatches;
if(!tileHasSupport||ActivePassageTileQueries->hasMouthSupport(passageIndex))
composeRouteMouthSupport(passage.upperStrateIndex, composeRouteMouthSupport(passage.upperStrateIndex,
passage.upperMouthSubvoxels,true); passage.upperMouthSubvoxels,true);
}
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();++passageIndex) for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();++passageIndex)
{ {
const auto& passage=ResolvedLayout->passages[passageIndex]; const auto& passage=ResolvedLayout->passages[passageIndex];
@@ -205,9 +275,31 @@ public:
{ {
if(!crossing.needsColumn||crossing.passageIndex!=passageIndex if(!crossing.needsColumn||crossing.passageIndex!=passageIndex
||qz>crossing.topZSubvoxels||qz<crossing.bottomZSubvoxels)continue; ||qz>crossing.topZSubvoxels||qz<crossing.bottomZSubvoxels)continue;
const double columnDistance=PassageColumnSignedDistance(crossing,X,Y,Z); double columnDistance;
const bool tileProvesColumnExterior=passageTileScopeMatches
&&!ActivePassageTileQueries->hasFeature(passageIndex,true);
if(tileProvesColumnExterior)
{
if(composed<0.0f)continue;
const double dx=qx-crossing.centerXSubvoxels;
const double dy=qy-crossing.centerYSubvoxels;
const double horizontalSquared=dx*dx+dy*dy;
const double maximumRadius=static_cast<double>(crossing.maximumRadiusSubvoxels);
const double exteriorLimit=maximumRadius
+static_cast<double>(composed)*Q+1.0e-4;
if(horizontalSquared>exteriorLimit*exteriorLimit)continue;
columnDistance=PassageColumnSignedDistance(crossing,X,Y,Z);
}
else
columnDistance=PassageColumnSignedDistance(crossing,X,Y,Z);
composed=std::min(composed,static_cast<float>(columnDistance/Q)); composed=std::min(composed,static_cast<float>(columnDistance/Q));
} }
const bool tilePassageFree=passageTileScopeMatches
&&!ActivePassageTileQueries->hasFeature(passageIndex,false);
if(composed>=0.0f&&tilePassageFree)
{
continue;
}
if(qz>firstFloorQ+2.0*passage.mouthRadiusSubvoxels if(qz>firstFloorQ+2.0*passage.mouthRadiusSubvoxels
||qz<lastFloorQ-2.0*passage.mouthRadiusSubvoxels) ||qz<lastFloorQ-2.0*passage.mouthRadiusSubvoxels)
{ {
@@ -234,15 +326,40 @@ public:
(carveHigh-passage.controlPoints.begin())-1); (carveHigh-passage.controlPoints.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-passage.controlPoints.begin())+1);
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,
lowerMouthIndex,mouthSearchBegin,mouthSearchEnd,lastIndex,qx,qy,qz,
-static_cast<double>(composed)*Q+0.24*qPerMetre))
{
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
// bridge's positive exterior SDF is not bounded this way, so keep its full query.
// Pour un échantillon source positif, le tube ne change le champ qu'à portée du relief ;
// le plancher du pilier reste à 2,25 m de son axe. Le SDF extérieur d'une courte passerelle
// 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;
double bestSignedDistance=std::numeric_limits<double>::infinity(); double bestSignedDistance=std::numeric_limits<double>::infinity();
double bestPlayerCapsuleSignedDistance=std::numeric_limits<double>::infinity(); double bestPlayerCapsuleSignedDistance=std::numeric_limits<double>::infinity();
double bestT=0.0; double bestT=0.0;
std::ptrdiff_t bestIndex=0; std::ptrdiff_t bestIndex=0;
int bestScanPhase=std::numeric_limits<int>::max();
std::ptrdiff_t bestScanIndex=std::numeric_limits<std::ptrdiff_t>::max();
std::ptrdiff_t bestSupportSegmentIndex=0; std::ptrdiff_t bestSupportSegmentIndex=0;
bool hasSupportFloor=false; bool hasSupportFloor=false;
double bestSupportDensity=0.0; double bestSupportDensity=0.0;
const auto considerCarveSegment=[&](std::ptrdiff_t index) const auto considerCarveSegment=[&](std::size_t segmentIndex,int scanPhase)
{ {
const std::ptrdiff_t index=static_cast<std::ptrdiff_t>(segmentIndex);
const auto& a=passage.controlPoints[static_cast<std::size_t>(index)]; const auto& a=passage.controlPoints[static_cast<std::size_t>(index)];
const auto& b=passage.controlPoints[static_cast<std::size_t>(index+1)]; const auto& b=passage.controlPoints[static_cast<std::size_t>(index+1)];
const double ax=static_cast<double>(a.positionSubvoxels[0]); const double ax=static_cast<double>(a.positionSubvoxels[0]);
@@ -254,10 +371,8 @@ public:
const double lengthSq=dx*dx+dy*dy+dz*dz; 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 t=std::clamp(((qx-ax)*dx+(qy-ay)*dy+(qz-az)*dz)/lengthSq,0.0,1.0);
const double cx=ax+t*dx,cy=ay+t*dy,cz=az+t*dz; if(!passageTileScopeMatches)
const double ex=qx-cx,ey=qy-cy,ez=qz-cz; {
const double radius=static_cast<double>(a.radiusSubvoxels)
+t*(b.radiusSubvoxels-a.radiusSubvoxels);
const double horizontalLengthSq=dx*dx+dy*dy; const double horizontalLengthSq=dx*dx+dy*dy;
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(((qx-ax)*dx+(qy-ay)*dy)/horizontalLengthSq,0.0,1.0):t;
@@ -268,31 +383,52 @@ public:
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);
const double playerCapsuleDistance=DeterministicSqrt(
playerDx*playerDx+playerDy*playerDy+playerVertical*playerVertical)
-0.34*qPerMetre;
bestPlayerCapsuleSignedDistance=std::min(bestPlayerCapsuleSignedDistance, bestPlayerCapsuleSignedDistance=std::min(bestPlayerCapsuleSignedDistance,
playerCapsuleDistance); DeterministicSqrt(playerDx*playerDx+playerDy*playerDy
+playerVertical*playerVertical)-0.34*qPerMetre);
}
const double cx=ax+t*dx,cy=ay+t*dy,cz=az+t*dz;
const double ex=qx-cx,ey=qy-cy,ez=qz-cz;
const double radius=static_cast<double>(a.radiusSubvoxels)
+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
{bestSignedDistance=signedDistance;bestT=t;bestIndex=index;} ||(signedDistance==bestSignedDistance
&&(scanPhase<bestScanPhase
||(scanPhase==bestScanPhase&&index<bestScanIndex))))
{
bestSignedDistance=signedDistance;bestT=t;bestIndex=index;
bestScanPhase=scanPhase;bestScanIndex=index;
}
}; };
for(std::ptrdiff_t index=carveBegin;index<=carveEnd;++index)
considerCarveSegment(index);
// Alternate styles can bend locally at either mouth, so floorProfile is // Alternate styles can bend locally at either mouth, so floorProfile is
// not monotone over those short endpoint runs. Keep the binary search for // not monotone over those short endpoint runs. Keep the binary search for
// the long descent and inspect both endpoint neighborhoods directly. // the long descent and inspect both endpoint neighborhoods directly.
for(std::ptrdiff_t index=0;index<std::min<std::ptrdiff_t>(8,lowerMouthIndex);++index)
considerCarveSegment(index);
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);
for(std::ptrdiff_t index=mouthSearchBegin;index<mouthSearchEnd;++index)
considerCarveSegment(index);
// The route-bound landing can rise and fall after the lower mouth. Scan // The route-bound landing can rise and fall after the lower mouth. Scan
// that short tail directly; binary search is only valid on the descent. // that short tail directly; binary search is only valid on the descent.
if(passageTileScopeMatches)
{
VisitNearestPassageSegmentRange(passageIndex,carveBegin,carveEnd,qx,qy,qz,
0,bestSignedDistance,considerCarveSegment);
VisitNearestPassageSegmentRange(passageIndex,0,
std::min<std::ptrdiff_t>(8,lowerMouthIndex)-1,qx,qy,qz,
1,bestSignedDistance,considerCarveSegment);
VisitNearestPassageSegmentRange(passageIndex,mouthSearchBegin,mouthSearchEnd-1,
qx,qy,qz,2,bestSignedDistance,considerCarveSegment);
VisitNearestPassageSegmentRange(passageIndex,lowerMouthIndex,lastIndex-1,
qx,qy,qz,3,bestSignedDistance,considerCarveSegment);
}
else
{
for(std::ptrdiff_t index=carveBegin;index<=carveEnd;++index)
considerCarveSegment(static_cast<std::size_t>(index),0);
for(std::ptrdiff_t index=0;index<std::min<std::ptrdiff_t>(8,lowerMouthIndex);++index)
considerCarveSegment(static_cast<std::size_t>(index),1);
for(std::ptrdiff_t index=mouthSearchBegin;index<mouthSearchEnd;++index)
considerCarveSegment(static_cast<std::size_t>(index),2);
for(std::ptrdiff_t index=lowerMouthIndex;index<lastIndex;++index) for(std::ptrdiff_t index=lowerMouthIndex;index<lastIndex;++index)
considerCarveSegment(index); considerCarveSegment(static_cast<std::size_t>(index),3);
}
if(!std::isfinite(bestSignedDistance))continue; if(!std::isfinite(bestSignedDistance))continue;
const auto& nearestA=passage.controlPoints[static_cast<std::size_t>(bestIndex)]; const auto& nearestA=passage.controlPoints[static_cast<std::size_t>(bestIndex)];
const auto& nearestB=passage.controlPoints[static_cast<std::size_t>(bestIndex+1)]; const auto& nearestB=passage.controlPoints[static_cast<std::size_t>(bestIndex+1)];
@@ -405,8 +541,12 @@ public:
const bool shortSpanFloorSupport=hasSupportFloor const bool shortSpanFloorSupport=hasSupportFloor
&&IsPassageShortSpanSegment(passageIndex, &&IsPassageShortSpanSegment(passageIndex,
static_cast<std::size_t>(bestSupportSegmentIndex)); static_cast<std::size_t>(bestSupportSegmentIndex));
if(hasSupportFloor&&bestPlayerCapsuleSignedDistance>=0.05*qPerMetre const bool capsuleAllowsSupport=passageTileScopeMatches
&&(density<0.0f||passageColumnFloorSupport||shortSpanFloorSupport)) ?!PassagePlayerCapsuleBlocksSupport(passageIndex,carveBegin,carveEnd,
lowerMouthIndex,mouthSearchBegin,mouthSearchEnd,lastIndex,qx,qy,qz,qPerMetre)
:bestPlayerCapsuleSignedDistance>=0.05*qPerMetre;
if(hasSupportFloor&&(density<0.0f||passageColumnFloorSupport||shortSpanFloorSupport)
&&capsuleAllowsSupport)
composed=std::min(composed,static_cast<float>(bestSupportDensity)); composed=std::min(composed,static_cast<float>(bestSupportDensity));
} }
return composed; return composed;
@@ -418,21 +558,28 @@ public:
bool ResolvedPassageIntersectsBox(const int32 minimum[3],const int32 maximum[3]) const bool ResolvedPassageIntersectsBox(const int32 minimum[3],const int32 maximum[3]) const
{ {
if(!ResolvedLayout||ResolvedLayout->passages.empty())return false; if(!ResolvedLayout||ResolvedLayout->passages.empty())return false;
if(PassageProofNodes.empty())
{
// Native-only readiness tiles have not built the passage BVH. Keep their
// exact conservative fallback instead of indexing an empty tree.
constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
const double voxelsPerMetre=Owner&&Owner->CoreVoxelSizeMicrons>0 const double voxelsPerMetre=Owner&&Owner->CoreVoxelSizeMicrons>0
?1000000.0/Owner->CoreVoxelSizeMicrons:4.0; ?1000000.0/Owner->CoreVoxelSizeMicrons:4.0;
const double bridgeHalfWidthVoxels=3.5*voxelsPerMetre; const double bridgeHalfWidthVoxels=3.5*voxelsPerMetre;
const double bridgeFootDepthVoxels=(5.0-0.13)*voxelsPerMetre; const double bridgeFootDepthVoxels=(5.0-0.13)*voxelsPerMetre;
const auto overlaps=[&](const double low[3],const double high[3])
{
for(int axis=0;axis<3;++axis)
if(high[axis]<minimum[axis]||low[axis]>maximum[axis])return false;
return true;
};
for(const auto& passage:ResolvedLayout->passages) for(const auto& passage:ResolvedLayout->passages)
{ {
for(std::size_t i=1;i<passage.controlPoints.size();++i) for(std::size_t i=1;i<passage.controlPoints.size();++i)
{ {
const auto& a=passage.controlPoints[i-1];const auto& b=passage.controlPoints[i]; const auto& a=passage.controlPoints[i-1];const auto& b=passage.controlPoints[i];
const double radius=static_cast<double>(std::max(a.radiusSubvoxels,b.radiusSubvoxels))/Q; const double radius=static_cast<double>(std::max(a.radiusSubvoxels,b.radiusSubvoxels))/Q;
const double floorThicknessVoxels=Owner&&Owner->CoreVoxelSizeMicrons>0 const double floorThicknessVoxels=voxelsPerMetre;
?1000000.0/Owner->CoreVoxelSizeMicrons:4.0;
// Short support spans can extend to a 3.5 m half-width and almost 5 m
// below the bore floor. Keep those cells shape-dependent for classification.
const double horizontalExtent=std::max(radius,bridgeHalfWidthVoxels); const double horizontalExtent=std::max(radius,bridgeHalfWidthVoxels);
const double footDepth=std::max(floorThicknessVoxels,bridgeFootDepthVoxels); const double footDepth=std::max(floorThicknessVoxels,bridgeFootDepthVoxels);
const double low[3]={ const double low[3]={
@@ -443,10 +590,43 @@ public:
std::max(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q+horizontalExtent, std::max(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q+horizontalExtent,
std::max(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q+horizontalExtent, std::max(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q+horizontalExtent,
std::max(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q+radius}; std::max(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q+radius};
bool overlap=true; if(overlaps(low,high))return true;
for(int axis=0;axis<3;++axis) }
overlap&=high[axis]>=minimum[axis]&&low[axis]<=maximum[axis]; const auto upperStratum=std::find_if(ResolvedLayout->strates.begin(),
if(overlap)return true; ResolvedLayout->strates.end(),[&passage](const auto& value)
{return value.strateIndex==passage.upperStrateIndex;});
if(upperStratum!=ResolvedLayout->strates.end())
{
std::array<const VoxelForgeCore::ResolvedLayoutAnchorV2*,5> route{};
std::size_t routeCount=0;
for(const auto& anchor:upperStratum->anchors)
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
{
if(routeCount<route.size())route[routeCount++]=&anchor;
else
{
std::move(route.begin()+1,route.end(),route.begin());
route.back()=&anchor;
}
}
if(routeCount>=2)
{
const double mouthReach=13.0*voxelsPerMetre*Q;
double minimumZ=std::numeric_limits<double>::infinity();
double maximumZ=-std::numeric_limits<double>::infinity();
for(std::size_t index=0;index<routeCount;++index)
{
minimumZ=std::min(minimumZ,static_cast<double>(route[index]->values[2]));
maximumZ=std::max(maximumZ,static_cast<double>(route[index]->values[2]));
}
const double mouthX=passage.upperMouthSubvoxels[0];
const double mouthY=passage.upperMouthSubvoxels[1];
const double low[3]={(mouthX-mouthReach)/Q,(mouthY-mouthReach)/Q,
(minimumZ-3.0*voxelsPerMetre*Q)/Q};
const double high[3]={(mouthX+mouthReach)/Q,(mouthY+mouthReach)/Q,
(maximumZ+3.0*voxelsPerMetre*Q)/Q};
if(overlaps(low,high))return true;
}
} }
} }
for(const auto& crossing:PassageVoidCrossings) for(const auto& crossing:PassageVoidCrossings)
@@ -457,13 +637,16 @@ public:
crossing.centerYSubvoxels/Q-radius,crossing.bottomZSubvoxels/Q}; crossing.centerYSubvoxels/Q-radius,crossing.bottomZSubvoxels/Q};
const double high[3]={crossing.centerXSubvoxels/Q+radius, const double high[3]={crossing.centerXSubvoxels/Q+radius,
crossing.centerYSubvoxels/Q+radius,crossing.topZSubvoxels/Q}; crossing.centerYSubvoxels/Q+radius,crossing.topZSubvoxels/Q};
bool overlap=true; if(overlaps(low,high))return true;
for(int axis=0;axis<3;++axis)
overlap&=high[axis]>=minimum[axis]&&low[axis]<=maximum[axis];
if(overlap)return true;
} }
return false; return false;
} }
const double low[3]={static_cast<double>(minimum[0]),static_cast<double>(minimum[1]),
static_cast<double>(minimum[2])};
const double high[3]={static_cast<double>(maximum[0]),static_cast<double>(maximum[1]),
static_cast<double>(maximum[2])};
return PassageProofBoundsOverlapNode(0,low,high);
}
static bool MakeResolvedStratumBounds(double BottomZ,double TopZ,double SealThickness, static bool MakeResolvedStratumBounds(double BottomZ,double TopZ,double SealThickness,
FResolvedStratumBounds& Out) FResolvedStratumBounds& Out)
@@ -1723,20 +1906,557 @@ public:
} }
private: private:
struct FPassageTileFeature
{
std::size_t passageIndex=0,segmentIndex=0;
bool bColumn=false,bMouthSupport=false;
};
struct FPassageNearestNode
{
std::array<double,3> minimum{},maximum{};
double maximumRadius=0.0;
std::vector<std::size_t> segments;
int left=-1,right=-1;
};
struct FPassageProofBound
{
std::array<double,3> minimum{},maximum{};
};
struct FPassageProofNode
{
std::array<double,3> minimum{},maximum{};
std::size_t first=0,end=0;
int left=-1,right=-1;
};
struct FPassageFeatureNode
{
std::array<std::int64_t,3> minimum{},maximum{};
std::size_t first=0,end=0;
int left=-1,right=-1;
};
int BuildPassageNearestNode(std::size_t passageIndex,std::vector<std::size_t> segments)
{
const auto& points=ResolvedLayout->passages[passageIndex].controlPoints;
FPassageNearestNode node;
node.minimum.fill(std::numeric_limits<double>::infinity());
node.maximum.fill(-std::numeric_limits<double>::infinity());
for(const std::size_t segment:segments)
{
const auto& a=points[segment];const auto& b=points[segment+1];
for(std::size_t axis=0;axis<3;++axis)
{
node.minimum[axis]=std::min({node.minimum[axis],
static_cast<double>(a.positionSubvoxels[axis]),
static_cast<double>(b.positionSubvoxels[axis])});
node.maximum[axis]=std::max({node.maximum[axis],
static_cast<double>(a.positionSubvoxels[axis]),
static_cast<double>(b.positionSubvoxels[axis])});
}
node.maximumRadius=std::max({node.maximumRadius,
static_cast<double>(a.radiusSubvoxels),static_cast<double>(b.radiusSubvoxels)});
}
node.segments=segments;
std::sort(node.segments.begin(),node.segments.end());
const int nodeIndex=static_cast<int>(PassageNearestNodes[passageIndex].size());
PassageNearestNodes[passageIndex].push_back(std::move(node));
if(segments.size()>4)
{
std::size_t splitAxis=0;
for(std::size_t axis=1;axis<3;++axis)
if(PassageNearestNodes[passageIndex][nodeIndex].maximum[axis]
-PassageNearestNodes[passageIndex][nodeIndex].minimum[axis]
>PassageNearestNodes[passageIndex][nodeIndex].maximum[splitAxis]
-PassageNearestNodes[passageIndex][nodeIndex].minimum[splitAxis])splitAxis=axis;
const std::size_t middle=segments.size()/2;
const auto center=[&](std::size_t segment)
{return static_cast<double>(points[segment].positionSubvoxels[splitAxis])
+static_cast<double>(points[segment+1].positionSubvoxels[splitAxis]);};
std::nth_element(segments.begin(),segments.begin()+middle,segments.end(),
[&](std::size_t a,std::size_t b)
{const double ca=center(a),cb=center(b);return ca==cb?a<b:ca<cb;});
std::vector<std::size_t> leftSegments(segments.begin(),segments.begin()+middle);
std::vector<std::size_t> rightSegments(segments.begin()+middle,segments.end());
const int left=BuildPassageNearestNode(passageIndex,std::move(leftSegments));
const int right=BuildPassageNearestNode(passageIndex,std::move(rightSegments));
PassageNearestNodes[passageIndex][nodeIndex].left=left;
PassageNearestNodes[passageIndex][nodeIndex].right=right;
}
return nodeIndex;
}
int BuildPassageProofNode(std::size_t first,std::size_t end)
{
FPassageProofNode node;node.first=first;node.end=end;
node.minimum.fill(std::numeric_limits<double>::infinity());
node.maximum.fill(-std::numeric_limits<double>::infinity());
for(std::size_t index=first;index<end;++index)
for(std::size_t axis=0;axis<3;++axis)
{
node.minimum[axis]=std::min(node.minimum[axis],PassageProofBounds[index].minimum[axis]);
node.maximum[axis]=std::max(node.maximum[axis],PassageProofBounds[index].maximum[axis]);
}
const int nodeIndex=static_cast<int>(PassageProofNodes.size());
PassageProofNodes.push_back(node);
if(end-first>8)
{
std::size_t splitAxis=0;
for(std::size_t axis=1;axis<3;++axis)
if(node.maximum[axis]-node.minimum[axis]
>node.maximum[splitAxis]-node.minimum[splitAxis])splitAxis=axis;
const std::size_t middle=first+(end-first)/2;
std::nth_element(PassageProofBounds.begin()+first,PassageProofBounds.begin()+middle,
PassageProofBounds.begin()+end,[splitAxis](const auto& a,const auto& b)
{return a.minimum[splitAxis]+a.maximum[splitAxis]
<b.minimum[splitAxis]+b.maximum[splitAxis];});
const int left=BuildPassageProofNode(first,middle);
const int right=BuildPassageProofNode(middle,end);
PassageProofNodes[nodeIndex].left=left;
PassageProofNodes[nodeIndex].right=right;
}
return nodeIndex;
}
int BuildPassageFeatureNode(std::size_t first,std::size_t end)
{
FPassageFeatureNode node;node.first=first;node.end=end;
node.minimum.fill(std::numeric_limits<std::int64_t>::max());
node.maximum.fill(std::numeric_limits<std::int64_t>::min());
for(std::size_t offset=first;offset<end;++offset)
{
const auto& feature=PassageFeatureBounds[PassageFeatureOrder[offset]];
for(std::size_t axis=0;axis<3;++axis)
{
node.minimum[axis]=std::min(node.minimum[axis],feature.minimumSubvoxels[axis]);
node.maximum[axis]=std::max(node.maximum[axis],feature.maximumSubvoxels[axis]);
}
}
const int nodeIndex=static_cast<int>(PassageFeatureNodes.size());
PassageFeatureNodes.push_back(node);
if(end-first>8)
{
std::size_t splitAxis=0;
for(std::size_t axis=1;axis<3;++axis)
if(static_cast<long double>(node.maximum[axis])-node.minimum[axis]
>static_cast<long double>(node.maximum[splitAxis])-node.minimum[splitAxis])
splitAxis=axis;
const std::size_t middle=first+(end-first)/2;
std::nth_element(PassageFeatureOrder.begin()+first,PassageFeatureOrder.begin()+middle,
PassageFeatureOrder.begin()+end,[this,splitAxis](std::uint32_t a,std::uint32_t b)
{
const auto& left=PassageFeatureBounds[a];
const auto& right=PassageFeatureBounds[b];
const double leftCenter=static_cast<double>(left.minimumSubvoxels[splitAxis])*0.5
+static_cast<double>(left.maximumSubvoxels[splitAxis])*0.5;
const double rightCenter=static_cast<double>(right.minimumSubvoxels[splitAxis])*0.5
+static_cast<double>(right.maximumSubvoxels[splitAxis])*0.5;
return leftCenter==rightCenter?a<b:leftCenter<rightCenter;
});
const int left=BuildPassageFeatureNode(first,middle);
const int right=BuildPassageFeatureNode(middle,end);
PassageFeatureNodes[nodeIndex].left=left;
PassageFeatureNodes[nodeIndex].right=right;
}
return nodeIndex;
}
void QueryPassageFeatureNode(int nodeIndex,
const std::array<std::int64_t,3>& minimum,
const std::array<std::int64_t,3>& maximum,
std::vector<std::uint32_t>& result) const
{
if(nodeIndex<0)return;
const auto& node=PassageFeatureNodes[static_cast<std::size_t>(nodeIndex)];
for(std::size_t axis=0;axis<3;++axis)
if(node.maximum[axis]<minimum[axis]||node.minimum[axis]>maximum[axis])return;
if(node.left<0)
{
for(std::size_t offset=node.first;offset<node.end;++offset)
{
const std::uint32_t featureIndex=PassageFeatureOrder[offset];
const auto& feature=PassageFeatureBounds[featureIndex];
bool overlaps=true;
for(std::size_t axis=0;axis<3;++axis)
overlaps=overlaps&&feature.minimumSubvoxels[axis]<=maximum[axis]
&&feature.maximumSubvoxels[axis]>=minimum[axis];
if(overlaps)result.push_back(featureIndex);
}
return;
}
QueryPassageFeatureNode(node.left,minimum,maximum,result);
QueryPassageFeatureNode(node.right,minimum,maximum,result);
}
std::vector<std::uint32_t> BuildPassageFeatureTileList(
const std::array<std::int64_t,3>& minimum,
const std::array<std::int64_t,3>& maximum) const
{
std::vector<std::uint32_t> result;
if(PassageFeatureBounds.empty())return result;
bool failOpen=PassageFeatureBoundsFailOpen;
for(std::size_t axis=0;axis<3;++axis)failOpen=failOpen||minimum[axis]>maximum[axis];
if(failOpen)
{
result.resize(PassageFeatureBounds.size());
for(std::size_t i=0;i<result.size();++i)result[i]=static_cast<std::uint32_t>(i);
return result;
}
if(!PassageFeatureNodes.empty())QueryPassageFeatureNode(0,minimum,maximum,result);
return result;
}
void BuildPassageSpatialIndices()
{
PassageTileFeatures.clear();PassageFeatureBounds.clear();
PassageFeatureOrder.clear();PassageFeatureNodes.clear();
PassageFeatureBoundsFailOpen=false;
PassageNearestNodes.clear();
PassageProofBounds.clear();PassageProofNodes.clear();
PassageNearestNodes.resize(ResolvedLayout?ResolvedLayout->passages.size():0);
PassageColumnSegmentFlags.clear();PassageShortSpanSegmentFlags.clear();
PassageHasShortSpanFlags.clear();
if(!ResolvedLayout||ResolvedLayout->passages.empty())return;
constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
const double voxelsPerMetre=Owner&&Owner->CoreVoxelSizeMicrons>0
?1000000.0/Owner->CoreVoxelSizeMicrons:4.0;
const double bridgeHalfWidthVoxels=3.5*voxelsPerMetre;
const double bridgeFootDepthVoxels=(5.0-0.13)*voxelsPerMetre;
PassageColumnSegmentFlags.resize(ResolvedLayout->passages.size());
PassageShortSpanSegmentFlags.resize(ResolvedLayout->passages.size());
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();++passageIndex)
{
const auto& passage=ResolvedLayout->passages[passageIndex];
const std::size_t segmentCount=passage.controlPoints.size()>1
?passage.controlPoints.size()-1:0;
PassageColumnSegmentFlags[passageIndex].assign(segmentCount,0);
PassageShortSpanSegmentFlags[passageIndex].assign(segmentCount,0);
if(segmentCount>0)
{
std::vector<std::size_t> segmentIds(segmentCount);
for(std::size_t i=0;i<segmentCount;++i)segmentIds[i]=i;
BuildPassageNearestNode(passageIndex,std::move(segmentIds));
}
for(std::size_t segment=0;segment<segmentCount;++segment)
{
const auto& a=passage.controlPoints[segment];
const auto& b=passage.controlPoints[segment+1];
const double radius=static_cast<double>(std::max(a.radiusSubvoxels,b.radiusSubvoxels))/Q;
const double horizontalExtent=std::max(radius,bridgeHalfWidthVoxels);
const double footDepth=std::max(voxelsPerMetre,bridgeFootDepthVoxels);
FPassageProofBound proof;
proof.minimum={std::min(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q-horizontalExtent,
std::min(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q-horizontalExtent,
std::min(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q-radius-footDepth};
proof.maximum={std::max(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q+horizontalExtent,
std::max(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q+horizontalExtent,
std::max(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q+radius};
PassageProofBounds.push_back(proof);
const std::array<std::int64_t,3> featureMin={
static_cast<std::int64_t>(std::floor(proof.minimum[0]*Q)),
static_cast<std::int64_t>(std::floor(proof.minimum[1]*Q)),
static_cast<std::int64_t>(std::floor(proof.minimum[2]*Q))};
const std::array<std::int64_t,3> featureMax={
static_cast<std::int64_t>(std::ceil(proof.maximum[0]*Q)),
static_cast<std::int64_t>(std::ceil(proof.maximum[1]*Q)),
static_cast<std::int64_t>(std::ceil(proof.maximum[2]*Q))};
PassageFeatureBounds.push_back({static_cast<std::uint64_t>(PassageTileFeatures.size()),
featureMin,featureMax});
PassageTileFeatures.push_back({passageIndex,segment,false});
}
const auto upperStratum=std::find_if(ResolvedLayout->strates.begin(),
ResolvedLayout->strates.end(),[&passage](const auto& value)
{return value.strateIndex==passage.upperStrateIndex;});
if(upperStratum!=ResolvedLayout->strates.end())
{
std::array<const VoxelForgeCore::ResolvedLayoutAnchorV2*,5> route{};
std::size_t routeCount=0;
for(const auto& anchor:upperStratum->anchors)
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
{
if(routeCount<route.size())route[routeCount++]=&anchor;
else
{
std::move(route.begin()+1,route.end(),route.begin());
route.back()=&anchor;
}
}
if(routeCount>=2)
{
const double mouthReach=13.0*voxelsPerMetre*Q;
double minimumZ=std::numeric_limits<double>::infinity();
double maximumZ=-std::numeric_limits<double>::infinity();
for(std::size_t index=0;index<routeCount;++index)
{
minimumZ=std::min(minimumZ,static_cast<double>(route[index]->values[2]));
maximumZ=std::max(maximumZ,static_cast<double>(route[index]->values[2]));
}
const double mouthX=passage.upperMouthSubvoxels[0];
const double mouthY=passage.upperMouthSubvoxels[1];
FPassageProofBound supportProof;
supportProof.minimum={(mouthX-mouthReach)/Q,(mouthY-mouthReach)/Q,
(minimumZ-3.0*voxelsPerMetre*Q)/Q};
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(const auto& crossing:PassageVoidCrossings)
{
if(crossing.passageIndex<PassageColumnSegmentFlags.size())
{
auto& flags= crossing.needsColumn
?PassageColumnSegmentFlags[crossing.passageIndex]
:PassageShortSpanSegmentFlags[crossing.passageIndex];
const std::size_t first=std::min(crossing.firstSegment,flags.size());
const std::size_t last=std::min(crossing.lastSegment,flags.empty()?0:flags.size()-1);
if(!flags.empty()&&first<=last)
std::fill(flags.begin()+first,flags.begin()+last+1,1);
}
if(!crossing.needsColumn)continue;
FPassageProofBound proof;
const double radius=static_cast<double>(crossing.maximumRadiusSubvoxels)/Q;
proof.minimum={crossing.centerXSubvoxels/Q-radius,crossing.centerYSubvoxels/Q-radius,
crossing.bottomZSubvoxels/Q};
proof.maximum={crossing.centerXSubvoxels/Q+radius,crossing.centerYSubvoxels/Q+radius,
crossing.topZSubvoxels/Q};
PassageProofBounds.push_back(proof);
const std::array<std::int64_t,3> featureMin={
static_cast<std::int64_t>(std::floor(proof.minimum[0]*Q)),
static_cast<std::int64_t>(std::floor(proof.minimum[1]*Q)),
static_cast<std::int64_t>(std::floor(proof.minimum[2]*Q))};
const std::array<std::int64_t,3> featureMax={
static_cast<std::int64_t>(std::ceil(proof.maximum[0]*Q)),
static_cast<std::int64_t>(std::ceil(proof.maximum[1]*Q)),
static_cast<std::int64_t>(std::ceil(proof.maximum[2]*Q))};
PassageFeatureBounds.push_back({static_cast<std::uint64_t>(PassageTileFeatures.size()),
featureMin,featureMax});
PassageTileFeatures.push_back({crossing.passageIndex,0,true});
}
PassageHasShortSpanFlags.assign(PassageShortSpanSegmentFlags.size(),0);
for(std::size_t passageIndex=0;passageIndex<PassageShortSpanSegmentFlags.size();++passageIndex)
PassageHasShortSpanFlags[passageIndex]=static_cast<std::uint8_t>(
std::any_of(PassageShortSpanSegmentFlags[passageIndex].begin(),
PassageShortSpanSegmentFlags[passageIndex].end(),
[](std::uint8_t flag){return flag!=0;}));
for(std::size_t i=0;i<PassageFeatureBounds.size();++i)
PassageFeatureOrder.push_back(static_cast<std::uint32_t>(i));
for(const auto& feature:PassageFeatureBounds)
for(std::size_t axis=0;axis<3;++axis)
PassageFeatureBoundsFailOpen=PassageFeatureBoundsFailOpen
||feature.minimumSubvoxels[axis]>feature.maximumSubvoxels[axis];
if(!PassageFeatureOrder.empty())BuildPassageFeatureNode(0,PassageFeatureOrder.size());
if(!PassageProofBounds.empty())BuildPassageProofNode(0,PassageProofBounds.size());
}
double PassageNearestNodeLowerBound(const FPassageNearestNode& node,
double qx,double qy,double qz) const
{
const double dx=qx<node.minimum[0]?node.minimum[0]-qx:qx>node.maximum[0]?qx-node.maximum[0]:0.0;
const double dy=qy<node.minimum[1]?node.minimum[1]-qy:qy>node.maximum[1]?qy-node.maximum[1]:0.0;
const double dz=qz<node.minimum[2]?node.minimum[2]-qz:qz>node.maximum[2]?qz-node.maximum[2]:0.0;
return DeterministicSqrt(dx*dx+dy*dy+dz*dz)-node.maximumRadius-1.0e-6;
}
template<typename FVisit>
void VisitNearestPassageSpatialNode(std::size_t passageIndex,int nodeIndex,
std::size_t first,std::size_t end,double qx,double qy,double qz,int priority,
double& bestDistance,FVisit& visit) const
{
if(passageIndex>=PassageNearestNodes.size()||nodeIndex<0)return;
const auto& nodes=PassageNearestNodes[passageIndex];
const auto& node=nodes[static_cast<std::size_t>(nodeIndex)];
const auto candidate=std::lower_bound(node.segments.begin(),node.segments.end(),first);
if(candidate==node.segments.end()||*candidate>=end)return;
const double lowerBound=PassageNearestNodeLowerBound(node,qx,qy,qz);
if(lowerBound>bestDistance)return;
if(node.left<0)
{
for(auto it=candidate;it!=node.segments.end()&&*it<end;++it)
visit(*it,priority);
return;
}
const double leftBound=PassageNearestNodeLowerBound(
nodes[static_cast<std::size_t>(node.left)],qx,qy,qz);
const double rightBound=PassageNearestNodeLowerBound(
nodes[static_cast<std::size_t>(node.right)],qx,qy,qz);
const int nearChild=leftBound<=rightBound?node.left:node.right;
const int farChild=leftBound<=rightBound?node.right:node.left;
VisitNearestPassageSpatialNode(passageIndex,nearChild,first,end,qx,qy,qz,
priority,bestDistance,visit);
VisitNearestPassageSpatialNode(passageIndex,farChild,first,end,qx,qy,qz,
priority,bestDistance,visit);
}
template<typename FVisit>
void VisitNearestPassageSegmentRange(std::size_t passageIndex,std::ptrdiff_t first,
std::ptrdiff_t last,double qx,double qy,double qz,int priority,
double& bestDistance,FVisit&& visit) const
{
if(first>last||passageIndex>=PassageNearestNodes.size()
||PassageNearestNodes[passageIndex].empty())return;
auto callback=std::forward<FVisit>(visit);
VisitNearestPassageSpatialNode(passageIndex,0,static_cast<std::size_t>(first),
static_cast<std::size_t>(last+1),qx,qy,qz,priority,bestDistance,callback);
}
bool PassagePotentialWithinNode(std::size_t passageIndex,int nodeIndex,
std::size_t first,std::size_t end,double qx,double qy,double qz,
double signedDistanceLimit) const
{
if(passageIndex>=PassageNearestNodes.size()||nodeIndex<0)return false;
const auto& nodes=PassageNearestNodes[passageIndex];
const auto& node=nodes[static_cast<std::size_t>(nodeIndex)];
const auto candidate=std::lower_bound(node.segments.begin(),node.segments.end(),first);
if(candidate==node.segments.end()||*candidate>=end)return false;
if(PassageNearestNodeLowerBound(node,qx,qy,qz)>signedDistanceLimit)return false;
if(node.left<0)return true;
const double leftBound=PassageNearestNodeLowerBound(
nodes[static_cast<std::size_t>(node.left)],qx,qy,qz);
const double rightBound=PassageNearestNodeLowerBound(
nodes[static_cast<std::size_t>(node.right)],qx,qy,qz);
const int nearChild=leftBound<=rightBound?node.left:node.right;
const int farChild=leftBound<=rightBound?node.right:node.left;
return PassagePotentialWithinNode(passageIndex,nearChild,first,end,qx,qy,qz,
signedDistanceLimit)
||PassagePotentialWithinNode(passageIndex,farChild,first,end,qx,qy,qz,
signedDistanceLimit);
}
bool PassageMayAffectDensity(std::size_t passageIndex,std::ptrdiff_t carveBegin,
std::ptrdiff_t carveEnd,std::ptrdiff_t lowerMouthIndex,
std::ptrdiff_t mouthSearchBegin,std::ptrdiff_t mouthSearchEnd,std::ptrdiff_t lastIndex,
double qx,double qy,double qz,double signedDistanceLimit) const
{
const auto possible=[&](std::ptrdiff_t first,std::ptrdiff_t last)
{
return first<=last&&passageIndex<PassageNearestNodes.size()
&&!PassageNearestNodes[passageIndex].empty()
&&PassagePotentialWithinNode(passageIndex,0,static_cast<std::size_t>(first),
static_cast<std::size_t>(last+1),qx,qy,qz,signedDistanceLimit);
};
return possible(carveBegin,carveEnd)
||possible(0,std::min<std::ptrdiff_t>(8,lowerMouthIndex)-1)
||possible(mouthSearchBegin,mouthSearchEnd-1)
||possible(lowerMouthIndex,lastIndex-1);
}
bool PassagePlayerCapsuleBlocksSupportNode(std::size_t passageIndex,int nodeIndex,
std::size_t first,std::size_t end,double qx,double qy,double qz,double qPerMetre) const
{
if(passageIndex>=PassageNearestNodes.size()||nodeIndex<0)return false;
const auto& nodes=PassageNearestNodes[passageIndex];
const auto& node=nodes[static_cast<std::size_t>(nodeIndex)];
auto candidate=std::lower_bound(node.segments.begin(),node.segments.end(),first);
if(candidate==node.segments.end()||*candidate>=end)return false;
const double xyReach=0.39*qPerMetre;
if(qx+xyReach<node.minimum[0]||qx-xyReach>node.maximum[0]
||qy+xyReach<node.minimum[1]||qy-xyReach>node.maximum[1])return false;
const double minimumCenterZ=node.minimum[2]-node.maximumRadius+0.20*qPerMetre;
const double maximumCenterZ=node.maximum[2]+2.06*qPerMetre;
if(qz<minimumCenterZ||qz>maximumCenterZ)return false;
const auto& passage=ResolvedLayout->passages[passageIndex];
if(node.left<0)
{
for(auto it=candidate;it!=node.segments.end()&&*it<end;++it)
{
const std::size_t index=*it;
const auto& a=passage.controlPoints[index];const auto& b=passage.controlPoints[index+1];
const double ax=a.positionSubvoxels[0],ay=a.positionSubvoxels[1],az=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)continue;
const double horizontalLengthSq=dx*dx+dy*dy;
const double t=horizontalLengthSq>0.0
?std::clamp(((qx-ax)*dx+(qy-ay)*dy)/horizontalLengthSq,0.0,1.0):0.0;
const double centerX=ax+t*dx,centerY=ay+t*dy;
const double centerZ=az+t*dz-(static_cast<double>(a.radiusSubvoxels)
+t*(static_cast<double>(b.radiusSubvoxels)-a.radiusSubvoxels))+1.13*qPerMetre;
const double horizontalX=qx-centerX,horizontalY=qy-centerY;
const double vertical=std::max(0.0,std::abs(qz-centerZ)-0.54*qPerMetre);
const double distance=DeterministicSqrt(horizontalX*horizontalX+horizontalY*horizontalY
+vertical*vertical)-0.34*qPerMetre;
if(distance<0.05*qPerMetre)return true;
}
return false;
}
return PassagePlayerCapsuleBlocksSupportNode(passageIndex,node.left,first,end,
qx,qy,qz,qPerMetre)
||PassagePlayerCapsuleBlocksSupportNode(passageIndex,node.right,first,end,
qx,qy,qz,qPerMetre);
}
bool PassagePlayerCapsuleBlocksSupport(std::size_t passageIndex,
std::ptrdiff_t carveBegin,std::ptrdiff_t carveEnd,std::ptrdiff_t lowerMouthIndex,
std::ptrdiff_t mouthSearchBegin,std::ptrdiff_t mouthSearchEnd,std::ptrdiff_t lastIndex,
double qx,double qy,double qz,double qPerMetre) const
{
const auto blocked=[&](std::ptrdiff_t first,std::ptrdiff_t last)
{
return first<=last&&PassagePlayerCapsuleBlocksSupportNode(passageIndex,0,
static_cast<std::size_t>(first),static_cast<std::size_t>(last+1),
qx,qy,qz,qPerMetre);
};
return blocked(carveBegin,carveEnd)
||blocked(0,std::min<std::ptrdiff_t>(8,lowerMouthIndex)-1)
||blocked(mouthSearchBegin,mouthSearchEnd-1)
||blocked(lowerMouthIndex,lastIndex-1);
}
bool PassageProofBoundsOverlapNode(int nodeIndex,const double minimum[3],
const double maximum[3]) const
{
if(nodeIndex<0)return false;
const auto& node=PassageProofNodes[static_cast<std::size_t>(nodeIndex)];
for(std::size_t axis=0;axis<3;++axis)
if(node.maximum[axis]<minimum[axis]||node.minimum[axis]>maximum[axis])return false;
if(node.left<0)
{
for(std::size_t index=node.first;index<node.end;++index)
{
bool overlap=true;
for(std::size_t axis=0;axis<3;++axis)
overlap=overlap&&PassageProofBounds[index].maximum[axis]>=minimum[axis]
&&PassageProofBounds[index].minimum[axis]<=maximum[axis];
if(overlap)return true;
}
return false;
}
return PassageProofBoundsOverlapNode(node.left,minimum,maximum)
||PassageProofBoundsOverlapNode(node.right,minimum,maximum);
}
bool IsPassageColumnSegment(std::size_t PassageIndex,std::size_t SegmentIndex) const bool IsPassageColumnSegment(std::size_t PassageIndex,std::size_t SegmentIndex) const
{ {
return std::any_of(PassageVoidCrossings.begin(),PassageVoidCrossings.end(), return PassageIndex<PassageColumnSegmentFlags.size()
[PassageIndex,SegmentIndex](const auto& crossing) &&SegmentIndex<PassageColumnSegmentFlags[PassageIndex].size()
{return crossing.needsColumn&&crossing.passageIndex==PassageIndex &&PassageColumnSegmentFlags[PassageIndex][SegmentIndex]!=0;
&&SegmentIndex>=crossing.firstSegment&&SegmentIndex<=crossing.lastSegment;});
} }
bool IsPassageShortSpanSegment(std::size_t PassageIndex,std::size_t SegmentIndex) const bool IsPassageShortSpanSegment(std::size_t PassageIndex,std::size_t SegmentIndex) const
{ {
return std::any_of(PassageVoidCrossings.begin(),PassageVoidCrossings.end(), return PassageIndex<PassageShortSpanSegmentFlags.size()
[PassageIndex,SegmentIndex](const auto& crossing) &&SegmentIndex<PassageShortSpanSegmentFlags[PassageIndex].size()
{return !crossing.needsColumn&&crossing.passageIndex==PassageIndex &&PassageShortSpanSegmentFlags[PassageIndex][SegmentIndex]!=0;
&&SegmentIndex>=crossing.firstSegment&&SegmentIndex<=crossing.lastSegment;}); }
bool PassageHasShortSpanSegments(std::size_t PassageIndex) const
{
return PassageIndex<PassageHasShortSpanFlags.size()
&&PassageHasShortSpanFlags[PassageIndex]!=0;
} }
double PassageBridgeDeckOffsetMetres(std::size_t PassageIndex, double PassageBridgeDeckOffsetMetres(std::size_t PassageIndex,
@@ -1814,7 +2534,6 @@ private:
return qz-crossing.topZSubvoxels; return qz-crossing.topZSubvoxels;
if(qz<crossing.bottomZSubvoxels) if(qz<crossing.bottomZSubvoxels)
return crossing.bottomZSubvoxels-qz; return crossing.bottomZSubvoxels-qz;
// The lower-strate route is already walkable in its native field and binds // The lower-strate route is already walkable in its native field and binds
// directly to the passage mouth. Preserve that player capsule as the column // directly to the passage mouth. Preserve that player capsule as the column
// flares into the ground, so the pillar joins the route instead of sealing it. // flares into the ground, so the pillar joins the route instead of sealing it.
@@ -1915,14 +2634,15 @@ private:
void BuildPassageVoidCrossingPlan() void BuildPassageVoidCrossingPlan()
{ {
PassageVoidCrossings.clear(); PassageVoidCrossings.clear();
if(!Owner||!ResolvedLayout||ResolvedLayout->passages.empty() if(!Owner||!ResolvedLayout||ResolvedLayout->passages.empty())return;
||!Owner->PassageVoidCrossingCache)return; if(!Owner->PassageVoidCrossingCache)
{BuildPassageSpatialIndices();return;}
auto& cache=*Owner->PassageVoidCrossingCache; auto& cache=*Owner->PassageVoidCrossingCache;
std::lock_guard<std::mutex> lock(cache.mutex); std::lock_guard<std::mutex> lock(cache.mutex);
const std::string cacheKey=ResolvedLayout->stableIdentity+"/passage-void-columns-v1"; const std::string cacheKey=ResolvedLayout->stableIdentity+"/passage-void-columns-v1";
const auto cached=cache.byLayout.find(cacheKey); const auto cached=cache.byLayout.find(cacheKey);
if(cached!=cache.byLayout.end()) if(cached!=cache.byLayout.end())
{PassageVoidCrossings=cached->second;return;} {PassageVoidCrossings=cached->second;BuildPassageSpatialIndices();return;}
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,
@@ -2109,6 +2829,7 @@ private:
passage.stableId.c_str()); passage.stableId.c_str());
} }
cache.byLayout.emplace(cacheKey,PassageVoidCrossings); cache.byLayout.emplace(cacheKey,PassageVoidCrossings);
BuildPassageSpatialIndices();
} }
static uint64 PackLatticeKey(int32 X,int32 Y,int32 Z) static uint64 PackLatticeKey(int32 X,int32 Y,int32 Z)
@@ -6373,4 +7094,15 @@ private:
uint64 GpuVisualDetailNodeCount=0,CpuVisualDetailNodeCount=0; uint64 GpuVisualDetailNodeCount=0,CpuVisualDetailNodeCount=0;
FBuildStats Stats; FBuildStats Stats;
bool bAnalyticOnly=false; bool bAnalyticOnly=false;
inline static thread_local const FScopedPassageTileQueries* ActivePassageTileQueries=nullptr;
std::vector<VoxelForgeCore::WorldAssemblyFeatureBoundsV1> PassageFeatureBounds;
std::vector<FPassageTileFeature> PassageTileFeatures;
std::vector<std::uint32_t> PassageFeatureOrder;
std::vector<FPassageFeatureNode> PassageFeatureNodes;
bool PassageFeatureBoundsFailOpen=false;
std::vector<std::vector<FPassageNearestNode>> PassageNearestNodes;
std::vector<FPassageProofBound> PassageProofBounds;
std::vector<FPassageProofNode> PassageProofNodes;
std::vector<std::vector<std::uint8_t>> PassageColumnSegmentFlags,PassageShortSpanSegmentFlags;
std::vector<std::uint8_t> PassageHasShortSpanFlags;
}; };
+6
View File
@@ -2137,6 +2137,12 @@ namespace VoxelForgeCore
out.diagnostics.featureListMilliseconds=std::chrono::duration<double,std::milli>( out.diagnostics.featureListMilliseconds=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-FeatureListStart).count(); std::chrono::steady_clock::now()-FeatureListStart).count();
const ScopedWorldAssemblyFeatureQueriesV1 featureScope(featureQueries); const ScopedWorldAssemblyFeatureQueriesV1 featureScope(featureQueries);
// GenerateMesh samples only origin..origin+extent plus the one-step normal halo;
// queryMinimum/queryMaximum above are exactly that inclusive domain.
// 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);
const bool bDiagnosticColumnSampling=options.diagnosticGlobalOctaveDrop!=0 const bool bDiagnosticColumnSampling=options.diagnosticGlobalOctaveDrop!=0
||options.diagnosticColumnInterpolationStride>1; ||options.diagnosticColumnInterpolationStride>1;
const std::uint32_t PreviousOctaveDrop=bDiagnosticColumnSampling const std::uint32_t PreviousOctaveDrop=bDiagnosticColumnSampling
@@ -0,0 +1,125 @@
# Linux L28 — tile-bounded columns and passages
Branch: wip/l25-helix, based on L27. No commit. No Unreal files changed.
## Cost and culling
The plain interleaved baseline puts the L27 overhead in tile streaming/meshing, not shape
compilation: mean whole-view time was 2.058 s versus L24's 1.527 s for SurfaceWorld (+34.8%),
and 19.961 s versus 16.699 s for FloatingIslands (+19.5%). Shape time stayed near 0.46 s
(SurfaceWorld) and 0.97 s (FloatingIslands). The initial extra work was passage composition
inside the map-wide density sampling loop.
GenerateMesh now enters a passage-query scope using the exact inclusive tile sample bounds,
including the normal halo. A conservative feature AABB BVH supplies candidate route segments,
columns and mouth support. Long route nearest-distance searches use a per-passage segment BVH.
L9 proof boxes use a separate passage-bound BVH; disjoint roots reject far boxes without scanning
segments. If a native-only negative-control shape has no proof index, its box check falls back to
a conservative linear test.
Instrumented full-view counts (diagnostic build; 2,370 desired tiles):
| Recipe | Composition samples | Column guard: calls / tiles | Exact column SDF: calls / tiles | Route band samples / tiles | Route SDF unions / tiles | Route samples skipped / tiles | Feature candidates: tiles / total |
|---|---:|---:|---:|---:|---:|---:|---:|
| SurfaceWorld | 12,039,941 | 1,885,887 / 692 | 3,017 / 4 | 1,380,907 / 870 | 164,337 / 543 | 7,470,572 / 1,792 | 8 / 1,222 |
| FloatingIslands | 58,019,623 | 7,619,575 / 689 | 7,213 / 6 | 1,902,086 / 708 | 62,507 / 89 | 54,848,675 / 2,212 | 14 / 1,331 |
The exact column SDF is spatially culled. The cheaper radial guard remains in tiles sharing the
column's Z slab because positive SDF values can lower positive base density; a broader tile skip
would need an exact bound on that field. This is part of the remaining SurfaceWorld cost.
Per composition sample, the profile counted 0.125 carve-segment tests, 0.036 support-segment tests
and 0.0136 route SDF unions in SurfaceWorld; FloatingIslands counted 0.0125, 0.0028 and 0.00108.
Column guards ran on 15.7% / 13.1% of samples, while the complex column SDF ran on 0.025% / 0.012%.
## Interleaved whole-view cost
Five W12 quartets ran in order L24, L28a, fresh L27-source build, L28b. Hashing was off.
Values below are means of the five samples; raw samples are in
Saved/Linux/linux28-r28-finalfix-cost5-{recipe}-{l24,l27,r28a,r28b}-p1..p5.log.
L24 used Saved/Linux/l24-source/bin/linux/VoxelForgeCoreHeadless-linux24-clang19;
L27 was rebuilt from the current L27 source for this comparison.
| Recipe | L24 | L27 | L28a | L28b | L28a / L28b vs L24 |
|---|---:|---:|---:|---:|---:|
| SurfaceWorld | 1.527 s | 2.058 s | 1.592 s | 1.605 s | +4.30% / +5.12% |
| FloatingIslands | 16.699 s | 19.961 s | 16.651 s | 16.787 s | -0.29% / +0.53% |
FloatingIslands returned to L24 cost. SurfaceWorld misses the ~3% target by 1.3–2.1 percentage
points. Its remaining excess is about 69–80 ms in streaming wall time and 13–15 ms in shape
time; the density-aware radial guard and per-tile query setup/traversal remain. The guard cannot
be removed bit-exactly without a proven upper bound on the incoming positive density.
## Bit parity and audits
Final Clang and GCC binaries match L27 for both recipes at W1, W8 and W12: all 12 hash records per
run match (full detail, whole view, LOD0–8, exact query). This is 12 compiler/recipe/worker
combinations with zero mismatches. Logs:
Saved/Linux/linux28-r28-hash-finalfix-{clang,gcc}-{surface-world,floating-islands}-w{1,8,12}.log.
Representative hashes, equal to L27:
| Recipe | Full detail: density / mesh / payload / topology | Whole view: density / mesh / payload / topology | Exact query: density / detail / collision |
|---|---|---|---|
| SurfaceWorld | ace56c8f205238ad / a9c603a1248de6b8 / ea55f1f4fcc6a894 / 5ea7bc78a1b678ae | 117547e5e18c6847 / db5fa276315ff2d4 / b4e9d01d70413fea / 03caf881fa047f1c | c949c4df2ce6f9d9 / 114c501e2d830d94 / e6011d3a248d4849 |
| FloatingIslands | ed550e0c8b182695 / 0e410a8e80a3e7f7 / 0751926f0c67d8a4 / f01208bc2f7841ec | 70ab5bc87dd174d2 / 6f357f9085a496f0 / 08ce7d4e036da65a / eb5eef04a5cb85d9 | 7b7c50ac24ccadcc / efcba20c0c70c415 / e604823a249029bf |
The 24 m crossing matches fresh L27-source builds on both compilers and recipes:
SurfaceWorld, 314 tiles: density 3561ea23485e359f, mesh e8331620fb0fb908,
payload 765f7dad67c852aa, topology 3ccc09ac81c05d71.
FloatingIslands: density e3d0ea833bca4215, mesh 6b3d0f148ed83d93,
payload d6ed85f16acd895e, topology 9745683036522461.
Across 4 styles × 2 recipes, capsule walkability, open air, naturalness, no-long-bridge,
strict-validator controls, route bindings and passage audits pass on final Clang/GCC binaries.
Final passage logs are
Saved/Linux/linux28-r28-finalpassage-{clang,gcc}-{surface-world,floating-islands}-style{0..3}.log.
The final route/proof/readiness matrix also passes in all 16 compiler/style/recipe runs:
L13 route parity verifies 8/8 page pairs and 14 higher-LOD exact pairs with zero mismatches;
the readiness guard catches its negative control; L9 reports zero proof violations.
SurfaceWorld has 0 unknown blocks; FloatingIslands has 21,504 fallback/unknown blocks for each
style. The diagnostic full-view profile queried 26,946 FI proof boxes with only 40 path-bound and
1 column-bound tests, so far boxes reject at the BVH root. These unknowns are not evidence of a
fat passage bound. A fresh rerun of the saved L27 binary with the default owner input also reports
21,504; older L27 logs showing 17,408 did not reproduce. Final matrix logs are
Saved/Linux/linux28-r28-finalmatrix-{clang,gcc}-{surface-world,floating-islands}-style{0..3}.log.
Two inherited SurfaceWorld FloorProfileBlend checks remain over the 2-voxel threshold in styles
1 and 3: 2.544 and 3.645 voxels. The same failures and values are in L27 logs for both compilers;
other styles pass.
## Invariants, edits and run history
Changed Core files: Standalone/Core/src/ShapeWorldInternal.h and
Standalone/Core/src/VoxelForgeCore.cpp. Navigation/design docs and this report were updated.
No Unreal edits or Unreal compile. L3 FP-contract preamble/self-test, L4 FLazy, L7 shared cache,
L9/L12 proof rules, L11 readiness, L13 route binding, L18/L19 shared ceiling, L21/L22 layout and
bands, L23–L27 passage behavior, ARCHITECTURE §8.10 and CLAUDE.md async rules remain intact.
Failed or retried runs:
- First parity/readiness command omitted --stream-benchmark and --stream-pipeline; rerun with both.
- Initial readiness negative-control crashed on an empty proof BVH in a temporary native-only
shape. Added the conservative no-index fallback; final readiness checks pass.
- The first final W1 hash command omitted the explicit owner data path and failed to open data
under Saved/Linux. Preserved as linux28-r28-hash-finalfix-clang-surface-world-w1-attempt1-failed.log;
rerun with Standalone/data/owner_world.json and all final W1/W8 hashes passed.
- Initial moving-crossing comparison used stale L27 logs. Rebuilt L27 from source and reran; all
four compiler/recipe hashes then matched.
- Older saved L27 proof logs reported 17,408 FloatingIslands unknown blocks. Reran the saved L27
binaries and compared with final L28: both report 21,504, zero violations.
- An exploratory composed-bound FloatingIslands W12 hash differed; the density-threshold variant
restored equality and is the version measured here. An interrupted/empty Clang R15 build was
rerun successfully.
No run exceeded 30 minutes or remained stuck for 15 minutes. Builds were Clang 19 and GCC 14,
W12 CPU affinity 0–11, with the 6 GiB address-space cap. No commit, push or stash was performed.
## Orchestrator review (Claude)
- Re-run by me (W12, no hashing, interleaved): SW L24 1.455/1.485/1.484 s vs L28 1.502/1.539/1.555 s (median +3.7%).
Accepted as the cost of the passage/column feature. FI is back to L24 cost.
- Bit-identical to L27 (12/12 configs + crossing). With this, `wip/l25-helix` (L25–L28) is merged into
`assembler/world`.
- Note for later: the pre-existing SW FloorProfileBlend check over threshold in styles 1/3 (2.544/3.645 voxels)
is unchanged since L27.