passages: two-pass swept-tube tunnels (owner design, L99, v215) - pending owner visual review

Route plan unchanged. Per tile, route segments whose bounded influence (tube + 4 m) touches the
tile write min-combined tube distance (base sweep); relief/floor/mouth shaping only in the shell
(detail sweep); 0.5 m edge blend into the strate field. Near-ready @W12: SW 1.01->0.80,
FI 2.23->1.43, TN 2.18->1.37, VS 1.09->0.98 s; whole view FI 3.38->2.37 s. All passage walk
audits pass (21,425/21,425 canonical, 3/3 interfaces); 24-run Clang/GCC W1/W8/W12 determinism;
0 sign flips. Change: mean 0.02 cm / 0.05 deg; max 25.9 cm (passage walls/roofs ~3.5 m from
route, LOD2) and 49.8 deg (rock walls 13-25 m from routes, LOD2). Captures with diffs in
Linux99/captures. Fast tier PASS (v215 pins).

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 14:38:45 +01:00
1 parent 36140b573f
commit fa6363b31a
67 files changed
+2022 -45

No files matched your search

@@ -162,6 +162,21 @@ namespace VoxelForgeCore
std::uint64_t passageMouthRouteSegmentChecks = 0;
std::uint64_t passageReliefFieldCalls = 0;
std::uint64_t passageSmoothstepEvaluations = 0;
// L99 tile-local swept-tube base and detail passes. These are populated only
// by the opt-in passage composer profile, except the mesh composition itself.
std::uint64_t passageSweepRouteSegmentCandidates = 0;
std::uint64_t passageSweepSegmentSamplePairs = 0;
std::uint64_t passageSweepOutsideInfluencePairs = 0;
std::uint64_t passageSweepMinimumUpdates = 0;
std::uint64_t passageSweepBaseInfluenceSamples = 0;
std::uint64_t passageSweepDetailShellSamples = 0;
std::uint64_t passageSweepGridSamples = 0;
std::uint64_t passageSweepNoInfluenceGridSamples = 0;
std::uint64_t passageSweepLookups = 0;
std::uint64_t passageSweepCacheHits = 0;
std::uint64_t passageSweepBaseInfluenceHits = 0;
std::uint64_t passageSweepOutOfInfluenceHits = 0;
std::uint64_t passageSweepMissingSamples = 0;
std::uint64_t floatFieldCalls = 0;
std::uint64_t latticeFieldCalls = 0;
std::uint64_t unclampedFieldCalls = 0;
@@ -461,7 +476,7 @@ namespace VoxelForgeCore
// Version 210 keeps bulk native-cave sampling on the scalar field path at strata edges.
// Version 211 makes coordinate biasing in the shared cell hash unsigned, removing signed-overflow UB.
// Version 212 balances generated entry routes against existing walk lanes and preserves their shared treads.
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 214;
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 215;
enum class FieldChannel : std::uint32_t
{
+451 -31
View File
@@ -100,16 +100,9 @@ static double VF_L91PassageSourceFloorVoxels()
static double VF_L93PassageCarveReachMetres()
{
static const double Value=[]
{
const char* Raw=std::getenv("VOXELFORGE_L93_PASSAGE_CARVE_REACH_METRES");
if(!Raw||!*Raw)return 0.0;
char* End=nullptr;
const double Parsed=std::strtod(Raw,&End);
return End!=Raw&&*End=='\0'&&std::isfinite(Parsed)&&Parsed>0.0
?Parsed:0.0;
}();
return Value;
// The generated world uses a fixed bounded tube influence. An explicit diagnostic
// option can still select the legacy point-evaluation path for comparisons.
return 4.0;
}
// Standalone-only world compiler. It compiles the runtime room/tunnel/passages into
@@ -259,6 +252,7 @@ public:
{
std::size_t segmentIndex=0;
std::array<std::int64_t,3> minimumSubvoxels{},maximumSubvoxels{};
std::array<std::int64_t,3> sweepMinimumSubvoxels{},sweepMaximumSubvoxels{};
};
struct FSegmentRangeCache
{
@@ -280,6 +274,31 @@ public:
std::uint64_t generation=0;
double numerator=0.0;
};
struct FPassageSweepSample
{
double signedDistance=std::numeric_limits<double>::infinity();
double t=0.0;
std::uint32_t segmentIndex=std::numeric_limits<std::uint32_t>::max();
float wallRelief=0.0f,mouthSurfaceRelief=0.0f,mouthTreadFieldRelief=0.0f;
bool valid=false,hasWallRelief=false,hasMouthSurfaceRelief=false,
hasMouthTreadFieldRelief=false;
};
struct FPassageTileSweepData
{
struct FPassageSamples
{
std::size_t passageIndex=0;
std::vector<FPassageSweepSample> samples;
};
std::array<std::int64_t,3> gridMinimumSubvoxels{};
std::int64_t stepSubvoxels=0;
std::int32_t dimension=0;
double influenceSubvoxels=0.0;
std::vector<std::int32_t> passageToSweep;
std::vector<FPassageSamples> passages;
std::atomic<std::uint64_t> lookups{0},cacheHits{0},baseInfluenceHits{0},
outOfInfluenceHits{0},missingSamples{0};
};
FScopedPassageTileQueries(const FStandaloneShapeWorld& shape,
const std::array<std::int64_t,3>& minimumSubvoxels,
const std::array<std::int64_t,3>& maximumSubvoxels,
@@ -319,6 +338,7 @@ public:
? shape.ResolvedLayout->passages.size() : 0, 0);
passageColumns_=passageRoutes_;
passageMouthSupports_=passageRoutes_;
passageRouteSegments_.resize(passageRoutes_.size());
passageMouthSupportSegments_.resize(passageRoutes_.size());
passageSegmentRangeCaches_.resize(passageRoutes_.size());
if(!disablePassageComposition&&shape.ResolvedLayout
@@ -367,8 +387,20 @@ public:
const auto& feature=shape.PassageTileFeatures[featureIndex];
if(feature.passageIndex>=passageRoutes_.size())continue;
const auto& featureBounds=shape.PassageFeatureBounds[featureIndex];
const FCandidateSegment candidate{feature.segmentIndex,
featureBounds.minimumSubvoxels,featureBounds.maximumSubvoxels};
FCandidateSegment candidate;
candidate.segmentIndex=feature.segmentIndex;
candidate.minimumSubvoxels=featureBounds.minimumSubvoxels;
candidate.maximumSubvoxels=featureBounds.maximumSubvoxels;
if(!feature.bColumn&&!feature.bMouthSupport
&&feature.passageIndex<shape.PassageSegmentData.size()
&&feature.segmentIndex<shape.PassageSegmentData[
feature.passageIndex].size())
{
const auto& segment=shape.PassageSegmentData[feature.passageIndex][
feature.segmentIndex];
candidate.sweepMinimumSubvoxels=segment.sweepMinimumSubvoxels;
candidate.sweepMaximumSubvoxels=segment.sweepMaximumSubvoxels;
}
if(feature.bColumn)passageColumns_[feature.passageIndex]=1;
else if(feature.bMouthSupport)
{
@@ -378,10 +410,18 @@ public:
else
{
passageRoutes_[feature.passageIndex]=1;
passageRouteSegments_[feature.passageIndex].push_back(candidate);
++routeSegmentCandidateCount_;
routeFeatureOverlap_=true;
}
}
for(auto& values:passageRouteSegments_)
{
std::sort(values.begin(),values.end(),[](const auto& a,const auto& b)
{return a.segmentIndex<b.segmentIndex;});
values.erase(std::unique(values.begin(),values.end(),[](const auto& a,const auto& b)
{return a.segmentIndex==b.segmentIndex;}),values.end());
}
for(auto& values:passageMouthSupportSegments_)
{
std::sort(values.begin(),values.end(),[](const auto& a,const auto& b)
@@ -405,6 +445,7 @@ public:
stampIndices_(source.stampIndices_),cavernFeatureIndices_(source.cavernFeatureIndices_),
passageRoutes_(source.passageRoutes_),passageColumns_(source.passageColumns_),
passageMouthSupports_(source.passageMouthSupports_),
passageRouteSegments_(source.passageRouteSegments_),
passageMouthSupportSegments_(source.passageMouthSupportSegments_),
passageSegmentRangeCaches_(source.passageSegmentRangeCaches_.size()),
upperMouthSupportPassages_(source.upperMouthSupportPassages_),
@@ -412,10 +453,13 @@ public:
routeFeatureOverlap_(source.routeFeatureOverlap_),columnZOverlap_(source.columnZOverlap_),
mayContainResolvedBoundaryBand_(source.mayContainResolvedBoundaryBand_),
mayContainTransitionPassageFade_(source.mayContainTransitionPassageFade_),
previous_(ActivePassageTileQueries),profileLod_(source.profileLod_),
previous_(ActivePassageTileQueries),profileStats_(source.profileStats_),
profileLod_(source.profileLod_),
candidateProfiled_(source.candidateProfiled_),
passageSourceFloorVoxels_(source.passageSourceFloorVoxels_),
passageCarveReachMetres_(source.passageCarveReachMetres_),
passageSweepData_(source.passageSweepData_),
ownsPassageSweepDiagnostics_(false),
clampCounters_(source.clampCounters_),ownsClampCounters_(false),
projectionCacheSize_(source.projectionCacheSize_),
reuseCounters_(source.reuseCounters_),ownsReuseCounters_(false)
@@ -423,6 +467,19 @@ public:
~FScopedPassageTileQueries()
{
ActivePassageTileQueries=previous_;
if(ownsPassageSweepDiagnostics_&&profileStats_&&passageSweepData_)
{
profileStats_->passageSweepLookups=passageSweepData_->lookups.load(
std::memory_order_relaxed);
profileStats_->passageSweepCacheHits=passageSweepData_->cacheHits.load(
std::memory_order_relaxed);
profileStats_->passageSweepBaseInfluenceHits=
passageSweepData_->baseInfluenceHits.load(std::memory_order_relaxed);
profileStats_->passageSweepOutOfInfluenceHits=
passageSweepData_->outOfInfluenceHits.load(std::memory_order_relaxed);
profileStats_->passageSweepMissingSamples=passageSweepData_->missingSamples.load(
std::memory_order_relaxed);
}
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_,
@@ -500,6 +557,25 @@ public:
FScopedPassageTileQueries& operator=(const FScopedPassageTileQueries&)=delete;
bool matches(const FStandaloneShapeWorld& shape) const{return shape_==&shape;}
void prepareSweptTubeBase(const FIntVector& origin,std::int32_t step,
std::int32_t cells,std::uint32_t seed);
const FPassageSweepSample* sweptSample(std::size_t passageIndex,
double qx,double qy,double qz) const;
bool hasSweptTubeBase() const noexcept{return passageSweepData_!=nullptr;}
void copySweptTubeRuntimeCounts() const noexcept
{
if(!profileStats_||!passageSweepData_)return;
profileStats_->passageSweepLookups=passageSweepData_->lookups.load(
std::memory_order_relaxed);
profileStats_->passageSweepCacheHits=passageSweepData_->cacheHits.load(
std::memory_order_relaxed);
profileStats_->passageSweepBaseInfluenceHits=
passageSweepData_->baseInfluenceHits.load(std::memory_order_relaxed);
profileStats_->passageSweepOutOfInfluenceHits=
passageSweepData_->outOfInfluenceHits.load(std::memory_order_relaxed);
profileStats_->passageSweepMissingSamples=passageSweepData_->missingSamples.load(
std::memory_order_relaxed);
}
bool beginProjectionColumn() const
{
const bool previous=projectionColumnActive_;
@@ -670,6 +746,7 @@ public:
std::vector<std::uint32_t> stampIndices_;
std::vector<std::uint32_t> cavernFeatureIndices_;
std::vector<std::uint8_t> passageRoutes_,passageColumns_,passageMouthSupports_;
std::vector<std::vector<FCandidateSegment>> passageRouteSegments_;
std::vector<std::vector<FCandidateSegment>> passageMouthSupportSegments_;
mutable std::vector<std::array<FSegmentRangeCache,4>> passageSegmentRangeCaches_;
std::vector<std::size_t> upperMouthSupportPassages_;
@@ -683,6 +760,8 @@ public:
bool candidateProfiled_=false;
double passageSourceFloorVoxels_=0.0;
double passageCarveReachMetres_=0.0;
std::shared_ptr<FPassageTileSweepData> passageSweepData_;
bool ownsPassageSweepDiagnostics_=true;
mutable std::shared_ptr<FPassageSourceClampCounters> clampCounters_;
bool ownsClampCounters_=true;
std::size_t projectionCacheSize_=0;
@@ -1761,6 +1840,8 @@ public:
const bool surfaceToTunnelSupport=passageConstants.surfaceToTunnelSupport;
const double firstFloorQ=passageConstants.firstFloorQ;
const double lastFloorQ=passageConstants.lastFloorQ;
if(!passageTileScopeMatches
||passageTileQueries->hasFeature(passageIndex,true))
for(const auto& crossing:PassageVoidCrossings)
{
if(!crossing.needsColumn||crossing.passageIndex!=passageIndex
@@ -1829,6 +1910,14 @@ public:
if(l90CandidateProfiled)recordColumnProof(columnDistance,composed);
composed=std::min(composed,static_cast<float>(columnDistance/Q));
}
const bool usePreparedPassageSweep=passageTileScopeMatches
&&passageCarveReachEnabled&&passageTileQueries->hasSweptTubeBase();
const auto* preparedSweepSample=usePreparedPassageSweep
?passageTileQueries->sweptSample(passageIndex,qx,qy,qz):nullptr;
// The tile sweep has already visited every segment whose bounded route or
// support envelope reaches this lattice point. No route query can affect it.
if(usePreparedPassageSweep&&(!preparedSweepSample
||preparedSweepSample->signedDistance>passageCarveReachQ))continue;
const bool tilePassageFree=passageTileScopeMatches
&&!ActivePassageTileQueries->hasFeature(passageIndex,false);
if(composed>=0.0f&&tilePassageFree)
@@ -2017,7 +2106,15 @@ public:
// the long descent and inspect both endpoint neighborhoods directly.
// 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.
if(passageTileScopeMatches)
if(usePreparedPassageSweep)
{
bestSignedDistance=preparedSweepSample->signedDistance;
bestT=preparedSweepSample->t;
bestIndex=static_cast<std::ptrdiff_t>(preparedSweepSample->segmentIndex);
bestScanPhase=0;
bestScanIndex=bestIndex;
}
else if(passageTileScopeMatches)
{
VisitNearestPassageSegmentRange(passageIndex,carveBegin,carveEnd,qx,qy,qz,
0,bestSignedDistance,considerCarveSegment);
@@ -2065,10 +2162,16 @@ public:
{
if(!hasCachedWallRelief)
{
cachedWallRelief=VF_FieldPassageWallRelief(static_cast<float>(X),
static_cast<float>(Y),static_cast<float>(Z),passageReliefSeed);
if(usePreparedPassageSweep)
cachedWallRelief=preparedSweepSample->hasWallRelief
?preparedSweepSample->wallRelief:0.0f;
else
{
cachedWallRelief=VF_FieldPassageWallRelief(static_cast<float>(X),
static_cast<float>(Y),static_cast<float>(Z),passageReliefSeed);
if(passageProfile)++passageProfile->passageReliefFieldCalls;
}
hasCachedWallRelief=true;
if(passageProfile)++passageProfile->passageReliefFieldCalls;
}
return cachedWallRelief;
};
@@ -2076,11 +2179,17 @@ public:
{
if(!hasCachedMouthSurfaceRelief)
{
cachedMouthSurfaceRelief=VF_FieldSurfaceRelief(
static_cast<float>(X)*1.5f,static_cast<float>(Y)*1.5f,
static_cast<float>(Z)*1.5f,passageReliefSeed^0x4D4F5554u);
if(usePreparedPassageSweep
&&preparedSweepSample->hasMouthSurfaceRelief)
cachedMouthSurfaceRelief=preparedSweepSample->mouthSurfaceRelief;
else
{
cachedMouthSurfaceRelief=VF_FieldSurfaceRelief(
static_cast<float>(X)*1.5f,static_cast<float>(Y)*1.5f,
static_cast<float>(Z)*1.5f,passageReliefSeed^0x4D4F5554u);
if(passageProfile)++passageProfile->passageReliefFieldCalls;
}
hasCachedMouthSurfaceRelief=true;
if(passageProfile)++passageProfile->passageReliefFieldCalls;
}
return cachedMouthSurfaceRelief;
};
@@ -2088,11 +2197,18 @@ public:
{
if(!hasCachedMouthTreadFieldRelief)
{
cachedMouthTreadFieldRelief=VF_FieldSurfaceRelief(
static_cast<float>(X)*0.75f,static_cast<float>(Y)*0.75f,
static_cast<float>(Z)*0.75f,passageReliefSeed^0x54485244u);
if(usePreparedPassageSweep
&&preparedSweepSample->hasMouthTreadFieldRelief)
cachedMouthTreadFieldRelief=
preparedSweepSample->mouthTreadFieldRelief;
else
{
cachedMouthTreadFieldRelief=VF_FieldSurfaceRelief(
static_cast<float>(X)*0.75f,static_cast<float>(Y)*0.75f,
static_cast<float>(Z)*0.75f,passageReliefSeed^0x54485244u);
if(passageProfile)++passageProfile->passageReliefFieldCalls;
}
hasCachedMouthTreadFieldRelief=true;
if(passageProfile)++passageProfile->passageReliefFieldCalls;
}
return cachedMouthTreadFieldRelief;
};
@@ -4597,6 +4713,7 @@ private:
struct FPassageSegmentData
{
std::array<double,3> a{},b{},delta{},catmullBefore{},catmullAfter{};
std::array<std::int64_t,3> sweepMinimumSubvoxels{},sweepMaximumSubvoxels{};
double radiusA=0.0,radiusB=0.0,radiusDelta=0.0;
double catmullRadiusBefore=0.0,catmullRadiusAfter=0.0;
double lengthSq=0.0,horizontalLengthSq=0.0;
@@ -5944,6 +6061,14 @@ private:
-std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity()};
std::array<double,3> tubeMinimum={
std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::infinity()};
std::array<double,3> tubeMaximum={
-std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity()};
const auto& p0=bMayUseSmoothCarve
?passage.controlPoints[segment-1]:a;
const auto& p3=bMayUseSmoothCarve
@@ -6019,17 +6144,37 @@ private:
{
featureMinimum[axis]=std::min(featureMinimum[axis],proof.minimum[axis]);
featureMaximum[axis]=std::max(featureMaximum[axis],proof.maximum[axis]);
tubeMinimum[axis]=std::min(tubeMinimum[axis],
centerMinimum[axis]-relievedRadius);
tubeMaximum[axis]=std::max(tubeMaximum[axis],
centerMaximum[axis]+relievedRadius);
}
PassageProofBounds.push_back(proof);
}
auto& preparedSegment=PassageSegmentData[passageIndex][segment];
for(std::size_t axis=0;axis<3;++axis)
{
preparedSegment.sweepMinimumSubvoxels[axis]=
static_cast<std::int64_t>(std::floor(
(tubeMinimum[axis]-4.0*voxelsPerMetre)*Q));
preparedSegment.sweepMaximumSubvoxels[axis]=
static_cast<std::int64_t>(std::ceil(
(tubeMaximum[axis]+4.0*voxelsPerMetre)*Q));
}
const std::array<std::int64_t,3> featureMin={
static_cast<std::int64_t>(std::floor(featureMinimum[0]*Q)),
static_cast<std::int64_t>(std::floor(featureMinimum[1]*Q)),
static_cast<std::int64_t>(std::floor(featureMinimum[2]*Q))};
static_cast<std::int64_t>(std::floor(
(featureMinimum[0]-4.0*voxelsPerMetre)*Q)),
static_cast<std::int64_t>(std::floor(
(featureMinimum[1]-4.0*voxelsPerMetre)*Q)),
static_cast<std::int64_t>(std::floor(
(featureMinimum[2]-4.0*voxelsPerMetre)*Q))};
const std::array<std::int64_t,3> featureMax={
static_cast<std::int64_t>(std::ceil(featureMaximum[0]*Q)),
static_cast<std::int64_t>(std::ceil(featureMaximum[1]*Q)),
static_cast<std::int64_t>(std::ceil(featureMaximum[2]*Q))};
static_cast<std::int64_t>(std::ceil(
(featureMaximum[0]+4.0*voxelsPerMetre)*Q)),
static_cast<std::int64_t>(std::ceil(
(featureMaximum[1]+4.0*voxelsPerMetre)*Q)),
static_cast<std::int64_t>(std::ceil(
(featureMaximum[2]+4.0*voxelsPerMetre)*Q))};
PassageFeatureBounds.push_back({static_cast<std::uint64_t>(PassageTileFeatures.size()),
featureMin,featureMax});
PassageTileFeatures.push_back({passageIndex,segment,false});
@@ -13891,3 +14036,278 @@ private:
FPassageCandidateBrickKeyHash> L90PassageCandidateBricks;
inline static thread_local const FPassageCandidateBrick* ActivePassageCandidateBrick=nullptr;
};
inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBase(
const FIntVector& origin,std::int32_t step,std::int32_t cells,std::uint32_t seed)
{
constexpr std::int64_t Q=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
constexpr double InfluenceMetres=4.0;
if(!shape_||step<=0||cells<=0||!std::isfinite(passageCarveReachMetres_)
||std::abs(passageCarveReachMetres_-InfluenceMetres)>1.0e-9)return;
auto sweep=std::make_shared<FPassageTileSweepData>();
sweep->gridMinimumSubvoxels={
(static_cast<std::int64_t>(origin.X)-step)*Q,
(static_cast<std::int64_t>(origin.Y)-step)*Q,
(static_cast<std::int64_t>(origin.Z)-step)*Q};
sweep->stepSubvoxels=static_cast<std::int64_t>(step)*Q;
sweep->dimension=cells+3;
const std::size_t gridSampleCount=static_cast<std::size_t>(sweep->dimension)
*static_cast<std::size_t>(sweep->dimension)*static_cast<std::size_t>(sweep->dimension);
sweep->influenceSubvoxels=InfluenceMetres*static_cast<double>(Q)*1000000.0
/std::max<std::uint32_t>(1,shape_->ResolvedLayout
?shape_->ResolvedLayout->coreVoxelSizeMicrometres:250000);
sweep->passageToSweep.assign(passageRouteSegments_.size(),-1);
std::vector<std::uint8_t> anyPassageInfluence(gridSampleCount,0);
const double qPerMetre=static_cast<double>(Q)*1000000.0/
std::max<std::uint32_t>(1,shape_->ResolvedLayout
?shape_->ResolvedLayout->coreVoxelSizeMicrometres:250000);
const double wallReliefShell=0.41*qPerMetre;
const double floorDetailShell=1.0*qPerMetre;
const bool profile=profileStats_!=nullptr;
if(profile)
{
profileStats_->passageSweepRouteSegmentCandidates=routeSegmentCandidateCount_;
profileStats_->passageSweepGridSamples=gridSampleCount;
}
const auto gridRange=[&](std::size_t axis,std::int64_t minimum,std::int64_t maximum,
std::int32_t& first,std::int32_t& last)
{
const double relativeMinimum=static_cast<double>(minimum
-sweep->gridMinimumSubvoxels[axis])/sweep->stepSubvoxels;
const double relativeMaximum=static_cast<double>(maximum
-sweep->gridMinimumSubvoxels[axis])/sweep->stepSubvoxels;
first=std::max<std::int32_t>(0,static_cast<std::int32_t>(std::ceil(relativeMinimum)));
last=std::min<std::int32_t>(sweep->dimension-1,
static_cast<std::int32_t>(std::floor(relativeMaximum)));
};
for(std::size_t passageIndex=0;passageIndex<passageRouteSegments_.size();++passageIndex)
{
const auto& candidates=passageRouteSegments_[passageIndex];
if(candidates.empty())continue;
const std::int32_t sweepIndex=static_cast<std::int32_t>(sweep->passages.size());
sweep->passageToSweep[passageIndex]=sweepIndex;
auto& passageSamples=sweep->passages.emplace_back();
passageSamples.passageIndex=passageIndex;
passageSamples.samples.resize(gridSampleCount);
for(const auto& candidate:candidates)
{
if(candidate.segmentIndex>=shape_->PassageSegmentData[passageIndex].size())continue;
std::array<std::int32_t,3> first{},last{};
for(std::size_t axis=0;axis<3;++axis)
gridRange(axis,candidate.sweepMinimumSubvoxels[axis],
candidate.sweepMaximumSubvoxels[axis],first[axis],last[axis]);
if(first[0]>last[0]||first[1]>last[1]||first[2]>last[2])continue;
const auto& constants=shape_->PassageComposerConstants[passageIndex];
const auto& segment=shape_->PassageSegmentData[passageIndex][candidate.segmentIndex];
if(segment.lengthSq<=0.0)continue;
const std::ptrdiff_t segmentIndex=static_cast<std::ptrdiff_t>(candidate.segmentIndex);
const bool smoothCarveJoin=segmentIndex>2
&&segmentIndex+(constants.internalChainPassage?2:12)<constants.lowerMouthIndex
&&segment.lengthSubvoxels<=8.0*qPerMetre
&&!shape_->IsPassageShortSpanSegment(passageIndex,candidate.segmentIndex-1)
&&!shape_->IsPassageShortSpanSegment(passageIndex,candidate.segmentIndex)
&&!shape_->IsPassageShortSpanSegment(passageIndex,candidate.segmentIndex+1);
for(std::int32_t gz=first[2];gz<=last[2];++gz)
for(std::int32_t gy=first[1];gy<=last[1];++gy)
for(std::int32_t gx=first[0];gx<=last[0];++gx)
{
const double qx=static_cast<double>(sweep->gridMinimumSubvoxels[0]
+static_cast<std::int64_t>(gx)*sweep->stepSubvoxels);
const double qy=static_cast<double>(sweep->gridMinimumSubvoxels[1]
+static_cast<std::int64_t>(gy)*sweep->stepSubvoxels);
const double qz=static_cast<double>(sweep->gridMinimumSubvoxels[2]
+static_cast<std::int64_t>(gz)*sweep->stepSubvoxels);
const double projectionXY=(qx-segment.a[0])*segment.delta[0]
+(qy-segment.a[1])*segment.delta[1];
const double t=std::clamp((projectionXY
+(qz-segment.a[2])*segment.delta[2])/segment.lengthSq,0.0,1.0);
double cx=segment.a[0]+t*segment.delta[0];
double cy=segment.a[1]+t*segment.delta[1];
double cz=segment.a[2]+t*segment.delta[2];
if(smoothCarveJoin)
{
const double t2=t*t,t3=t2*t;
for(std::size_t axis=0;axis<3;++axis)
{
const double v0=segment.catmullBefore[axis];
const double v1=segment.a[axis];
const double v2=segment.b[axis];
const double v3=segment.catmullAfter[axis];
const double curve=0.5*((2.0*v1)+(-v0+v2)*t
+(2.0*v0-5.0*v1+4.0*v2-v3)*t2
+(-v0+3.0*v1-3.0*v2+v3)*t3);
const double line=axis==0?cx:axis==1?cy:cz;
const double value=line+(curve-line);
if(axis==0)cx=value;
else if(axis==1)cy=value;
else cz=value;
}
}
const double radius=segment.radiusA+t*segment.radiusDelta;
const double ex=qx-cx,ey=qy-cy,ez=qz-cz;
const double signedDistance=FStandaloneShapeWorld::DeterministicSqrt(
ex*ex+ey*ey+ez*ez)-radius;
if(profile)++profileStats_->passageSweepSegmentSamplePairs;
if(signedDistance>sweep->influenceSubvoxels)
{
if(profile)++profileStats_->passageSweepOutsideInfluencePairs;
}
const std::size_t sampleIndex=(static_cast<std::size_t>(gz)
*static_cast<std::size_t>(sweep->dimension)+static_cast<std::size_t>(gy))
*static_cast<std::size_t>(sweep->dimension)+static_cast<std::size_t>(gx);
auto& sample=passageSamples.samples[sampleIndex];
if(!sample.valid||signedDistance<sample.signedDistance
||(signedDistance==sample.signedDistance
&&candidate.segmentIndex<sample.segmentIndex))
{
sample.signedDistance=signedDistance;
sample.t=t;
sample.segmentIndex=static_cast<std::uint32_t>(candidate.segmentIndex);
sample.valid=true;
sample.hasWallRelief=false;
sample.hasMouthSurfaceRelief=false;
sample.hasMouthTreadFieldRelief=false;
if(profile)++profileStats_->passageSweepMinimumUpdates;
}
}
}
}
for(auto& passageSamples:sweep->passages)
{
const std::size_t passageIndex=passageSamples.passageIndex;
const auto& constants=shape_->PassageComposerConstants[passageIndex];
const auto& segments=shape_->PassageSegmentData[passageIndex];
for(std::size_t sampleIndex=0;sampleIndex<passageSamples.samples.size();++sampleIndex)
{
auto& sample=passageSamples.samples[sampleIndex];
if(!sample.valid)continue;
const std::int32_t gx=static_cast<std::int32_t>(sampleIndex
%static_cast<std::size_t>(sweep->dimension));
const std::int32_t gy=static_cast<std::int32_t>((sampleIndex
/static_cast<std::size_t>(sweep->dimension))
%static_cast<std::size_t>(sweep->dimension));
const std::int32_t gz=static_cast<std::int32_t>(sampleIndex
/(static_cast<std::size_t>(sweep->dimension)
*static_cast<std::size_t>(sweep->dimension)));
const double qx=static_cast<double>(sweep->gridMinimumSubvoxels[0]
+static_cast<std::int64_t>(gx)*sweep->stepSubvoxels);
const double qy=static_cast<double>(sweep->gridMinimumSubvoxels[1]
+static_cast<std::int64_t>(gy)*sweep->stepSubvoxels);
const double qz=static_cast<double>(sweep->gridMinimumSubvoxels[2]
+static_cast<std::int64_t>(gz)*sweep->stepSubvoxels);
const auto& segment=segments[sample.segmentIndex];
const double radius=segment.radiusA+sample.t*segment.radiusDelta;
const double centerZ=segment.a[2]+sample.t*segment.delta[2];
if(sample.signedDistance<=sweep->influenceSubvoxels)
{
anyPassageInfluence[sampleIndex]=1;
if(profile)++profileStats_->passageSweepBaseInfluenceSamples;
}
if(std::abs(sample.signedDistance)<=wallReliefShell)
{
const float x=static_cast<float>(qx/Q);
const float y=static_cast<float>(qy/Q);
const float z=static_cast<float>(qz/Q);
sample.wallRelief=VF_FieldPassageWallRelief(x,y,z,seed);
sample.hasWallRelief=true;
if(profile)
{
++profileStats_->passageSweepDetailShellSamples;
++profileStats_->passageReliefFieldCalls;
}
}
const std::size_t segmentIndex=sample.segmentIndex;
const double floorDistance=std::abs(qz-(centerZ-radius));
const double dx=qx-(segment.a[0]+sample.t*segment.delta[0]);
const double dy=qy-(segment.a[1]+sample.t*segment.delta[1]);
const double horizontalDistance=FStandaloneShapeWorld::DeterministicSqrt(
dx*dx+dy*dy);
const bool upperMouthSegment=segmentIndex<=2;
const auto lowerMouth=static_cast<std::size_t>(
std::max<std::ptrdiff_t>(0,constants.lowerMouthIndex));
const bool lowerMouthSegment=segmentIndex+2>=lowerMouth
&&segmentIndex<=lowerMouth+2;
const float x=static_cast<float>(qx/Q);
const float y=static_cast<float>(qy/Q);
const float z=static_cast<float>(qz/Q);
if(upperMouthSegment&&floorDistance<=4.0*qPerMetre
&&horizontalDistance<=3.0*qPerMetre)
{
sample.mouthSurfaceRelief=VF_FieldSurfaceRelief(x*1.5f,y*1.5f,z*1.5f,
seed^0x4D4F5554u);
sample.hasMouthSurfaceRelief=true;
if(profile)++profileStats_->passageReliefFieldCalls;
}
if((upperMouthSegment||lowerMouthSegment)
&&floorDistance<=floorDetailShell&&horizontalDistance<=2.5*qPerMetre)
{
sample.mouthTreadFieldRelief=VF_FieldSurfaceRelief(x*0.75f,y*0.75f,
z*0.75f,seed^0x54485244u);
sample.hasMouthTreadFieldRelief=true;
if(profile)++profileStats_->passageReliefFieldCalls;
}
}
}
if(profile)
{
profileStats_->passageSweepNoInfluenceGridSamples=
static_cast<std::uint64_t>(std::count(anyPassageInfluence.begin(),
anyPassageInfluence.end(),static_cast<std::uint8_t>(0)));
}
passageSweepData_=std::move(sweep);
}
inline const FStandaloneShapeWorld::FScopedPassageTileQueries::FPassageSweepSample*
FStandaloneShapeWorld::FScopedPassageTileQueries::sweptSample(
std::size_t passageIndex,double qx,double qy,double qz) const
{
const auto sweep=passageSweepData_;
if(!sweep)return nullptr;
sweep->lookups.fetch_add(1,std::memory_order_relaxed);
if(passageIndex>=sweep->passageToSweep.size())
{
sweep->missingSamples.fetch_add(1,std::memory_order_relaxed);
return nullptr;
}
const std::int32_t passageSweepIndex=sweep->passageToSweep[passageIndex];
if(passageSweepIndex<0||static_cast<std::size_t>(passageSweepIndex)>=sweep->passages.size())
{
sweep->missingSamples.fetch_add(1,std::memory_order_relaxed);
return nullptr;
}
const std::array<double,3> query={qx,qy,qz};
std::array<std::int64_t,3> grid{};
for(std::size_t axis=0;axis<3;++axis)
{
const double coordinate=(query[axis]-sweep->gridMinimumSubvoxels[axis])
/sweep->stepSubvoxels;
const auto rounded=static_cast<std::int64_t>(std::llround(coordinate));
if(rounded<0||rounded>=sweep->dimension
||std::abs(coordinate-static_cast<double>(rounded))>1.0e-5)
{
sweep->missingSamples.fetch_add(1,std::memory_order_relaxed);
return nullptr;
}
grid[axis]=rounded;
}
const std::size_t index=(static_cast<std::size_t>(grid[2])
*static_cast<std::size_t>(sweep->dimension)+static_cast<std::size_t>(grid[1]))
*static_cast<std::size_t>(sweep->dimension)+static_cast<std::size_t>(grid[0]);
const auto& samples=sweep->passages[static_cast<std::size_t>(passageSweepIndex)].samples;
if(index>=samples.size()||!samples[index].valid)
{
sweep->missingSamples.fetch_add(1,std::memory_order_relaxed);
return nullptr;
}
sweep->cacheHits.fetch_add(1,std::memory_order_relaxed);
if(samples[index].signedDistance<=sweep->influenceSubvoxels)
sweep->baseInfluenceHits.fetch_add(1,std::memory_order_relaxed);
else sweep->outOfInfluenceHits.fetch_add(1,std::memory_order_relaxed);
return &samples[index];
}
+4 -1
View File
@@ -2795,11 +2795,13 @@ namespace VoxelForgeCore
// 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(
FStandaloneShapeWorld::FScopedPassageTileQueries passageFeatureScope(
tileShape,queryMinimum,queryMaximum,
options.profilePassageComposer?&out.diagnostics:nullptr,key.lod,
options.diagnosticPassageSourceFloorVoxels,
options.diagnosticPassageCarveReachMetres);
passageFeatureScope.prepareSweptTubeBase(origin,MeshStep,MeshCells,
static_cast<std::uint32_t>(world.Seed));
static std::atomic<bool> l98ProjectionProbeStarted{false};
const char* l98ProjectionProbeSwitch=std::getenv(
"VOXELFORGE_L98_PASSAGE_REUSE_PROBE");
@@ -2953,6 +2955,7 @@ namespace VoxelForgeCore
options.profileMesherStages?&McStageTimings:nullptr,
options.auditUniformBlockProofs?&BlockProofAudit:nullptr,
options.forceFullDensityCapture);
passageFeatureScope.copySweptTubeRuntimeCounts();
if(options.auditUniformBlockProofs)
{
out.diagnostics.uniformBlockProofsChecked=BlockProofAudit.CheckedBlocks;
@@ -0,0 +1,406 @@
#!/usr/bin/env python3
"""Reframe and verify the ten L99 passage-change hotspot captures."""
from __future__ import annotations
import json
import math
import struct
import subprocess
import sys
import time
import zlib
from array import array
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
HERE = Path(__file__).resolve().parent
CAPTURES = HERE / "captures"
RUNS = ROOT / "Saved/Linux/l99/captures-fix"
LAYOUT = ROOT / "Saved/Linux/l99/resolved-sw.json"
REACH = ROOT / "Saved/Linux/l99/carve-reach/summary.json"
DATA = ROOT / "Standalone/data/owner_world.json"
RENDERER = ROOT / "Saved/Linux/l99/bin/VoxelForgeOffscreenCapture-l99-clang"
BINARIES = {
"before-l98": ROOT / "Saved/Linux/l98/bin/VoxelForgeCoreHeadless-l98-clang19",
"after-l99": ROOT / "Saved/Linux/l99/bin/VoxelForgeCoreHeadless-l99-clang",
}
sys.path.insert(0, str(HERE))
from RunL99Captures import angles, fmt, make_passages, nearest_route, norm, add, mul, sub # noqa: E402
def screen_projection(camera, target, yaw_degrees, pitch_degrees, width=1600, height=900,
horizontal_fov=56.0):
yaw, pitch = math.radians(yaw_degrees), math.radians(pitch_degrees)
forward = (math.sin(yaw) * math.cos(pitch), math.cos(yaw) * math.cos(pitch),
math.sin(pitch))
right = (math.cos(yaw), -math.sin(yaw), 0.0)
up = (right[1] * forward[2] - right[2] * forward[1],
right[2] * forward[0] - right[0] * forward[2],
right[0] * forward[1] - right[1] * forward[0])
delta = sub(target, camera)
depth = sum(delta[i] * forward[i] for i in range(3))
x = sum(delta[i] * right[i] for i in range(3))
y = sum(delta[i] * up[i] for i in range(3))
tan_h = math.tan(math.radians(horizontal_fov) * 0.5)
tan_v = tan_h / (width / height)
return (0.5 + x / (2.0 * depth * tan_h),
0.5 - y / (2.0 * depth * tan_v))
def run(name: str, command: list[str]) -> dict:
log = RUNS / f"{name}.log"
wrapped = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1740s", *map(str, command)]
start = time.monotonic()
with log.open("w", encoding="utf-8") as stream:
result = subprocess.run(wrapped, cwd=ROOT, stdout=stream,
stderr=subprocess.STDOUT, check=False, timeout=1745)
row = {"name": name, "exit_code": result.returncode,
"wall_s": round(time.monotonic() - start, 3),
"log": str(log.relative_to(ROOT))}
print("[L99CaptureFixRun] " + json.dumps(row, sort_keys=True), flush=True)
if result.returncode != 0:
raise RuntimeError(f"capture step failed: {name}; see {log}")
return row
def closest_on_triangle(p, a, b, c):
"""Ericson closest point, with barycentric weights for normal interpolation."""
ab, ac, ap = sub(b, a), sub(c, a), sub(p, a)
d1, d2 = sum(ab[i] * ap[i] for i in range(3)), sum(ac[i] * ap[i] for i in range(3))
if d1 <= 0.0 and d2 <= 0.0:
return a, (1.0, 0.0, 0.0)
bp = sub(p, b)
d3, d4 = sum(ab[i] * bp[i] for i in range(3)), sum(ac[i] * bp[i] for i in range(3))
if d3 >= 0.0 and d4 <= d3:
return b, (0.0, 1.0, 0.0)
vc = d1 * d4 - d3 * d2
if vc <= 0.0 and d1 >= 0.0 and d3 <= 0.0:
v = d1 / (d1 - d3)
return add(a, mul(ab, v)), (1.0 - v, v, 0.0)
cp = sub(p, c)
d5, d6 = sum(ab[i] * cp[i] for i in range(3)), sum(ac[i] * cp[i] for i in range(3))
if d6 >= 0.0 and d5 <= d6:
return c, (0.0, 0.0, 1.0)
vb = d5 * d2 - d1 * d6
if vb <= 0.0 and d2 >= 0.0 and d6 <= 0.0:
w = d2 / (d2 - d6)
return add(a, mul(ac, w)), (1.0 - w, 0.0, w)
va = d3 * d6 - d5 * d4
if va <= 0.0 and (d4 - d3) >= 0.0 and (d5 - d6) >= 0.0:
w = (d4 - d3) / ((d4 - d3) + (d5 - d6))
return add(b, mul(sub(c, b), w)), (0.0, 1.0 - w, w)
denominator = 1.0 / (va + vb + vc)
v, w = vb * denominator, vc * denominator
return add(a, add(mul(ab, v), mul(ac, w))), (1.0 - v - w, v, w)
def nearest_surface(mesh: Path, point):
"""Find the closest non-skirt LOD2 triangle in the point's tile neighbourhood."""
best = None
target_key = tuple(math.floor(value / 32.0) for value in point)
with mesh.open("rb") as stream:
magic = stream.read(8)
materials = magic == b"VFMSH002"
if magic not in (b"VFMSH001", b"VFMSH002"):
raise RuntimeError(f"unsupported mesh header {magic!r}: {mesh}")
version, voxel_um, tile_count = struct.unpack("<3I", stream.read(12))
if version != (2 if materials else 1):
raise RuntimeError(f"mesh header version mismatch: {mesh}")
stream.seek(40, 1)
lod_counts = {}
relevant_tiles = []
for _ in range(tile_count):
key = struct.unpack("<4i", stream.read(16))
surface_count, _, vertex_count, index_count, mask_count = struct.unpack(
"<5I", stream.read(20))
lod_counts[key[3]] = lod_counts.get(key[3], 0) + 1
positions = array("f")
positions.frombytes(stream.read(vertex_count * 12))
normals = array("f")
normals.frombytes(stream.read(vertex_count * 12))
if materials:
stream.seek(vertex_count * 10, 1)
indices = array("I")
indices.frombytes(stream.read(index_count * 4))
masks = stream.read(mask_count)
if struct.pack("=I", 1) != struct.pack("<I", 1):
positions.byteswap()
normals.byteswap()
indices.byteswap()
if key[3] != 2 or any(abs(key[axis] - target_key[axis]) > 1
for axis in range(3)):
continue
relevant_tiles.append(key)
xyz = [tuple(float(positions[i * 3 + axis]) for axis in range(3))
for i in range(vertex_count)]
nrm = [tuple(float(normals[i * 3 + axis]) for axis in range(3))
for i in range(vertex_count)]
triangle_count = index_count // 3
for tri in range(triangle_count):
if masks and masks[tri] != 0:
continue
ids = tuple(int(indices[tri * 3 + corner]) for corner in range(3))
if any(index >= surface_count for index in ids):
continue
hit, weights = closest_on_triangle(point, *(xyz[index] for index in ids))
distance_sq = sum((hit[axis] - point[axis]) ** 2 for axis in range(3))
if best is not None and distance_sq >= best[0]:
continue
raw_normal = tuple(sum(weights[k] * nrm[ids[k]][axis] for k in range(3))
for axis in range(3))
length = math.sqrt(sum(value * value for value in raw_normal))
if length < 1.0e-8:
continue
best = (distance_sq, hit, tuple(value / length for value in raw_normal), key,
ids, weights)
if best is None:
raise RuntimeError(f"no LOD2 surface triangles near {point}; lods={lod_counts}")
return {"distance_m": math.sqrt(best[0]), "position_m": list(best[1]),
"normal": list(best[2]), "tile": list(best[3]),
"triangle_vertices": list(best[4]), "barycentric": list(best[5]),
"lod_counts": lod_counts, "nearby_lod2_tiles": len(relevant_tiles)}
def decode_rgba(path: Path) -> tuple[int, int, bytes]:
command = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1740s", "ffmpeg", "-v", "error", "-i", str(path),
"-frames:v", "1", "-f", "rawvideo", "-pix_fmt", "rgba", "pipe:1"]
result = subprocess.run(command, cwd=ROOT, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, check=False, timeout=1745)
if result.returncode != 0:
raise RuntimeError(f"PNG decode failed for {path}: {result.stderr.decode(errors='replace')}")
# ffmpeg rawvideo has no dimensions. Read the PNG IHDR separately for an exact size.
with path.open("rb") as stream:
if stream.read(8) != b"\x89PNG\r\n\x1a\n":
raise RuntimeError(f"invalid PNG signature: {path}")
stream.seek(16)
width, height = struct.unpack(">II", stream.read(8))
if len(result.stdout) != width * height * 4:
raise RuntimeError(f"decoded RGBA size mismatch for {path}: {len(result.stdout)}")
return width, height, result.stdout
def write_png(path: Path, width: int, height: int, rgba: bytes):
def chunk(kind: bytes, data: bytes) -> bytes:
payload = kind + data
return struct.pack(">I", len(data)) + payload + struct.pack(">I", zlib.crc32(payload) & 0xffffffff)
rows = bytearray()
stride = width * 4
for y in range(height):
rows.append(0)
rows.extend(rgba[y * stride:(y + 1) * stride])
png = (b"\x89PNG\r\n\x1a\n"
+ chunk(b"IHDR", struct.pack(">2I5B", width, height, 8, 6, 0, 0, 0))
+ chunk(b"IDAT", zlib.compress(bytes(rows), 9))
+ chunk(b"IEND", b""))
path.write_bytes(png)
def pixel_diff(before: Path, after: Path, output: Path, amplify: int = 64):
width, height, a = decode_rgba(before)
width_b, height_b, b = decode_rgba(after)
if (width, height) != (width_b, height_b):
raise RuntimeError(f"image dimensions differ: {before} and {after}")
diff = bytearray(len(a))
changed = 0
channel_sum = 0
max_delta = 0
for offset in range(0, len(a), 4):
dr = abs(a[offset] - b[offset])
dg = abs(a[offset + 1] - b[offset + 1])
db = abs(a[offset + 2] - b[offset + 2])
if dr or dg or db:
changed += 1
channel_sum += dr + dg + db
max_delta = max(max_delta, dr, dg, db)
diff[offset] = min(255, dr * amplify)
diff[offset + 1] = min(255, dg * amplify)
diff[offset + 2] = min(255, db * amplify)
diff[offset + 3] = 255
write_png(output, width, height, bytes(diff))
return {"width": width, "height": height, "changed_pixels": changed,
"total_pixels": width * height, "changed_percent": round(100.0 * changed / (width * height), 5),
"sum_abs_rgb": channel_sum, "max_channel_delta": max_delta,
"diff_amplification": amplify}
def surface_description(region, outward_normal, route_height_delta, route_radius,
route_distance):
nz = outward_normal[2]
if region == 2:
return "passage mouth surface"
if region == 1:
return "passage floor"
if region == 0:
if route_distance > route_radius + 2.0:
if nz < -0.55:
return "rock overhang / underside near passage"
if abs(nz) < 0.35:
return "other strata rock wall near passage"
return "sloping strata surface near passage"
if route_height_delta > 0.55 * route_radius:
return "passage upper wall / roof"
if route_height_delta < -0.55 * route_radius:
return "passage floor"
return "passage wall"
if nz < -0.55:
return "rock overhang / island underside outside the passage envelope"
if nz > 0.7:
return "upper terrain surface outside the passage envelope"
if abs(nz) < 0.35:
return "other strata rock wall outside the passage envelope"
return "sloping strata surface outside the passage envelope"
def main():
RUNS.mkdir(parents=True, exist_ok=True)
CAPTURES.mkdir(parents=True, exist_ok=True)
for path in (*BINARIES.values(), RENDERER, DATA, LAYOUT, REACH):
if not path.is_file():
raise SystemExit(f"missing input: {path}")
layout = json.loads(LAYOUT.read_text(encoding="utf-8"))
reach = json.loads(REACH.read_text(encoding="utf-8"))
_, routes = make_passages(layout)
sites = []
for kind, rows, value_key, point_key, region_key in (
("displacement", reach["top_displacement_tiles"], "max_displacement_cm",
"displacement_location_metres", "displacement_region"),
("normal", reach["top_normal_tiles"], "max_normal_degrees",
"normal_location_metres", "normal_region"),
):
for index, row in enumerate(rows, 1):
point = tuple(float(v) for v in row[point_key])
route, passage_id = min(((nearest_route(route_points, point), stable_id)
for stable_id, route_points in routes),
key=lambda candidate: candidate[0][0])
sites.append({"name": f"top-{kind}-{index:02d}", "kind": kind,
"point": point, "strate": row["strate"],
"value": float(row[value_key]), "region": int(row[region_key]),
"nearest_passage": passage_id,
"passage_distance_m": float(route[0]),
"route_tangent": list(route[2]),
"route_height_delta_m": point[2] - route[1][2],
"route_radius_m": float(route[4]),
"row": row})
records = []
for site in sites:
point = site["point"]
# Place the export camera far enough away that the audited location is in the LOD2 shell.
# The offscreen renderer then moves back to the exact changed surface for a close view.
direction = -1.0 if point[0] >= 0.0 else 1.0
export_camera = (point[0] + direction * 80.0, point[1], point[2])
yaw_export, pitch_export = 0.0, 0.0
meshes = {}
exports = {}
for label, binary in BINARIES.items():
mesh = RUNS / f"{site['name']}-{label}.vfmesh"
exports[label] = run(f"{site['name']}-{label}-export", [binary, DATA,
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--workers=8", "--force-generate", "--clip-radius=3",
"--max-clip-level=2", "--max-level=2", "--render-distance-chunks=0",
f"--camera={fmt((*export_camera, yaw_export, pitch_export))}",
f"--mesh-export={mesh}"])
if not mesh.is_file() or mesh.stat().st_size <= 0:
raise RuntimeError(f"empty exported scene for {site['name']} {label}")
meshes[label] = mesh
anchor = nearest_surface(meshes["before-l98"], point)
after_anchor = nearest_surface(meshes["after-l99"], point)
expected_tile = list(site["row"]["tile"])
if anchor["tile"] != expected_tile or after_anchor["tile"] != expected_tile:
raise RuntimeError(f"change tile not represented by the LOD2 export for {site['name']}: "
f"expected={expected_tile} before={anchor['tile']} "
f"after={after_anchor['tile']}")
if anchor["distance_m"] > 0.1 or after_anchor["distance_m"] > 0.1:
raise RuntimeError(f"change point too far from exported surface for {site['name']}: "
f"before={anchor['distance_m']:.3f}m "
f"after={after_anchor['distance_m']:.3f}m")
camera_distance = 3.25
# Marching-cubes normals point into the solid in these exports, so the air-side view
# starts along their negative direction.
air_normal = mul(tuple(anchor["normal"]), -1.0)
camera_direction = air_normal
if site["kind"] == "displacement":
route_tangent = tuple(site["route_tangent"])
tangent = sub(route_tangent, mul(air_normal,
sum(route_tangent[i] * air_normal[i] for i in range(3))))
tangent = norm(tangent)
camera_direction = norm(add(mul(air_normal, 0.70710678),
mul(tangent, 0.70710678)))
camera = add(tuple(anchor["position_m"]), mul(camera_direction, camera_distance))
yaw, pitch = angles(camera, point)
actual_camera_distance = math.sqrt(sum((camera[axis] - point[axis]) ** 2
for axis in range(3)))
if actual_camera_distance > 5.0 or actual_camera_distance < 2.0:
raise RuntimeError(f"changed point outside close framing range for {site['name']}: "
f"camera-to-point={actual_camera_distance:.3f}m; "
f"surface-offset={anchor['distance_m']:.3f}m")
target_uv = screen_projection(camera, point, yaw, pitch)
projection_error_px = math.hypot((target_uv[0] - 0.5) * 1600,
(target_uv[1] - 0.5) * 900)
if projection_error_px > 0.01:
raise RuntimeError(f"changed point is off-axis for {site['name']}: {target_uv}")
view = {**site, "focus": list(point), "camera": list(camera),
"target": list(point), "yaw": yaw, "pitch": pitch,
"target_screen_uv": list(target_uv),
"target_projection_error_px": projection_error_px,
"change_tile_lod2": expected_tile,
"surface_match_distance_m": anchor["distance_m"],
"camera_to_surface_m": camera_distance,
"camera_to_changed_point_m": actual_camera_distance,
"camera_side": "air side, 45-degree passage-tangent view"
if site["kind"] == "displacement" else "air side (negative exported normal)",
"surface_anchor": anchor, "after_surface_anchor": after_anchor,
"surface_type": surface_description(site["region"],
tuple(-value for value in anchor["normal"]),
site["route_height_delta_m"], site["route_radius_m"],
site["passage_distance_m"]),
"export_camera": list(export_camera), "export_lod_counts": anchor["lod_counts"]}
render_records = {}
render_shade = "normal" if site["kind"] == "normal" else "readable"
view["render_shade"] = render_shade
for label in BINARIES:
suffix = "before-l98" if label == "before-l98" else "after-l99"
image = CAPTURES / f"{site['name']}-{suffix}.png"
render_args = [RENDERER, meshes[label], image, "--size=1600x900",
"--fov-deg=56", f"--camera-m={fmt(camera)}", f"--yaw-deg={yaw:.8f}",
f"--pitch-deg={pitch:.8f}", f"--shade={render_shade}", "--headlamp=on",
"--height-tint=on", "--tile-edges=off", "--tile-skirts=off",
f"--height-center-m={point[2]:.6f}", "--height-range-m=8",
f"--focus-region={fmt((*point, 10.0))}"]
render_records[label] = run(f"{site['name']}-{label}-render", render_args)
render_records[label]["image"] = str(image.relative_to(ROOT))
render_records[label]["image_bytes"] = image.stat().st_size
for mesh in meshes.values():
mesh.unlink()
diff_path = CAPTURES / f"{site['name']}-diff-amplified.png"
pixel_stats = pixel_diff(CAPTURES / f"{site['name']}-before-l98.png",
CAPTURES / f"{site['name']}-after-l99.png", diff_path)
record = {"view": view, "before": render_records["before-l98"],
"after": render_records["after-l99"],
"diff": str(diff_path.relative_to(ROOT)), "pixel_diff": pixel_stats,
"exports": exports}
records.append(record)
print("[L99CaptureFixSite] " + json.dumps({"name": site["name"],
"lod_counts": anchor["lod_counts"], "surface_distance_m": anchor["distance_m"],
"camera_distance_m": camera_distance,
"changed_pixels": pixel_stats["changed_pixels"],
"surface_type": view["surface_type"], "passage": site["nearest_passage"],
"passage_distance_m": site["passage_distance_m"]}, sort_keys=True), flush=True)
summary = {"source_layout_identity": layout["stable_identity"],
"capture_lod": 2, "image_size": [1600, 900],
"camera_surface_distance_m": 3.25,
"sites": records}
(RUNS / "fixed-summary.json").write_text(json.dumps(summary, indent=2) + "\n",
encoding="utf-8")
print("[L99CaptureFixSummary] " + json.dumps({
"sites": len(records),
"changed_pixels": {r["view"]["name"]: r["pixel_diff"]["changed_pixels"]
for r in records},
"fixed_summary": str((RUNS / "fixed-summary.json").relative_to(ROOT))},
sort_keys=True), flush=True)
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,114 @@
# Linux 99 — bounded swept-tube passages
Branch: `assembler/world` · evaluator version: **215** · no commit made.
## Design
The route plan and its three passage splines are unchanged. L98 and L99 resolve the same layout identity (`resolved-layout-v3-fnv64:0589300eb07dfe06`) and exact passage control points. L99 prepares each tile's sorted route-segment candidates from tube bounds expanded by 4 m, min-combines their tube distances, then applies localized wall/floor relief and mouth shaping in the detail sweep. A 0.5 m edge blend bounds the integration with the strate field. Existing tread, clearance, mouth fade, and void-crossing support remain in generation.
## Speed
L80 protocol: three interleaved measured L98/L99 pairs at W8 and W12, with warmups, for each stratum. Entries are median seconds (`L98 → L99`). Stratum labels are explicit: SW = Surface World, FI = Floating Islands, TN = Tunnel Network, VS = Vertical Shafts. Whole/near tile counts are shown to keep the world labels fixed.
| Workers | Stratum | Whole / LOD0–2 tiles | Near ready (s) | Whole-view ready (s) |
|---:|---|---:|---:|---:|
| 8 | SW | 2,850 / 1,408 | 1.179 → 0.970 | 2.605 → 2.241 |
| 8 | FI | 2,249 / 1,216 | 2.342 → 1.499 | 3.583 → 2.524 |
| 8 | TN | 2,233 / 1,344 | 2.357 → 1.506 | 3.420 → 2.449 |
| 8 | VS | 2,329 / 1,344 | 2.204 → 1.721 | 3.429 → 3.028 |
| 12 | SW | 2,850 / 1,408 | 1.006 → 0.799 | 2.114 → 1.845 |
| 12 | FI | 2,249 / 1,216 | 2.225 → 1.432 | 3.382 → 2.373 |
| 12 | TN | 2,233 / 1,344 | 2.183 → 1.369 | 3.188 → 2.237 |
| 12 | VS | 2,329 / 1,344 | 1.093 → 0.983 | 2.019 → 1.884 |
All eight cells beat L98. FI/TN approach the ~1 s target, but remain above the passage-off ceilings (0.81/0.85 s). Native Clang and GCC builds took about 11m30s and 1m38s combined; these are accounted separately from readiness times. The Unreal `VFHostEditor Linux Development` build succeeded after timing in 71.50 s with `-NoUBA -MaxParallelActions=4`.
The fast tier is **PASS**: 80 runs, 301.4 s, 269 passing checks and 5 retained historical-hash diagnostics. Both v215 owner hash pins pass.
## Passage audits and measured work
The full-view canonical audit (`max-level=-1`, W12) passes **21,425/21,425** centerline samples. All three resolved interfaces passed:
| Interface | Center / capsule samples | Blocked | Support samples / unsupported | Grade failures | Mouth content / radius | Result |
|---|---:|---:|---:|---:|---:|---|
| 0–1 | 4,151 / 261,513 | 0 | 27,206 / 0 | 0 | 2/2 · 2/2 | PASS |
| 1–2 | 239 / 15,057 | 0 | 1,309 / 0 | 0 | 2/2 · 2/2 | PASS |
| 2–3 | 1,027 / 64,701 | 0 | 5,651 / 0 | 0 | 2/2 · 2/2 | PASS |
The audit summary reports naturalness 3/3, all three interfaces passed, and zero route-binding failures. The fast-tier owner style-1 and style-2 audits also pass, as does the arrival/departure chain (`interfaces=3/3 all_walkable=true`). Capsule clearance, support, headroom, grade, floor and mouth profiles, route binding, open-air, no-long-bridge, rock-column and ripple checks pass.
L77 near-LOD passage CPU share (three repeats, measured mean):
| Stratum | L98 | L99 |
|---|---:|---:|
| SW | 42.83% | 19.75% |
| FI | 56.22% | 34.78% |
| TN | 55.58% | 28.81% |
| VS | 30.21% | 8.56% |
Measured LOD0–2 sweep counters (W8):
| Stratum | Bounded segment candidates | Segment/sample pairs (outside-bound rejects) | Grid samples (no influence) | Base / detail samples |
|---|---:|---:|---:|---:|
| SW | 892 | 8,568,233 (4,699,672) | 14,011,370 (11,949,427) | 2,061,943 / 210,393 |
| FI | 1,439 | 15,261,585 (8,730,625) | 21,565,975 (18,604,965) | 3,377,667 / 347,335 |
| TN | 1,560 | 14,723,215 (8,313,402) | 17,969,538 (15,233,648) | 3,152,585 / 332,547 |
| VS | 1,558 | 14,092,586 (7,348,748) | 20,749,529 (18,066,064) | 2,683,465 / 273,636 |
| **Total** | **5,449** | **52,645,619 (29,092,447)** | **74,296,412 (63,854,104)** | **11,275,660 / 1,163,911** |
The profile covered 478/629/614/699 near tiles (SW/FI/TN/VS). Counts are measured. The bounded-reject and no-influence counts mean those segment tests and tube samples do not proceed to distance composition; the remaining 43,354,950 prepared-sweep lookups had 11,996,257 hits and 31,358,693 misses.
## World change and captures
Carve-reach comparisons covered every desired near-set tile: SW 1,408/1,408, FI 1,216/1,216, TN 1,344/1,344, VS 1,344/1,344. Every stratum had zero sign flips and zero edge-crossing mismatches.
| Stratum | Max / mean displacement | Max / mean normal change | Moved crossings | Pairing failures |
|---|---:|---:|---:|---:|
| SW | 20.11 / 0.0180 cm | 21.35 / 0.0874° | 9,214 | 21 |
| FI | 19.80 / 0.0185 cm | 29.91 / 0.0477° | 10,238 | 114 |
| TN | 21.74 / 0.0387 cm | 41.79 / 0.0291° | 11,370 | 116 |
| VS | 25.88 / 0.0178 cm | 49.82 / 0.00731° | 5,087 | 32 |
The audit's `FAIL` status is only its existing 1 cm displacement / 2° normal-change diagnostic threshold; those thresholds were not an L99 gate. Maxima and their coordinates are in the capture index. Paired L98/L99 captures include the top five displacement sites, top five normal-change sites, both mouths of all three passages, and a midpoint view for each passage: **19 matched views / 38 before-and-after PNGs** in [`captures/INDEX.md`](captures/INDEX.md), plus ten amplified hotspot diffs.
## Determinism and version
All 24 whole-view runs passed: Clang/GCC at W1/W8/W12 across SW/FI/TN/VS. The 20 peer comparisons had zero tile-count, per-tile hash, or whole-hash mismatches. Representative Clang W1 whole hashes (tile count followed by four components):
| Stratum | Whole hash |
|---|---|
| SW | `2850, a962f8cb29383f0e, 3deecb1a8f537726, fa367c6df153636f, 0b0a55b24243bd1e` |
| FI | `2249, 1d867417832047f9, 3f5bf03b78621a43, 601921b84046915d, c00d44660831d266` |
| TN | `2233, 3c0f10f94c18944d, e213da2d3bc34fe1, 318aaa3a18a997be, 7a1cd0183cc57c07` |
| VS | `2329, 3005885856f8adef, 2e3181ca8152f93c, 0cddc16aa45946bb, 69eb58192bab4302` |
Version 215 pins: [`owner-hash-floating-islands-style2-v215.baseline`](../../../../Tools/verify/baselines/owner-hash/canonical/owner-hash-floating-islands-style2-v215.baseline) and [`owner-hash-surface-world-style1-v215.baseline`](../../../../Tools/verify/baselines/owner-hash/canonical/owner-hash-surface-world-style1-v215.baseline). Source stamp: `0d31d588192e30bd`.
## Failed runs
- The first fast-tier attempt stopped at stale pre-L99 hash pins; v215 pins were added and the full rerun passed.
- A zero-radius smoke configuration aborted with `std::bad_alloc` on both L98 and L99; it was an invalid diagnostic setup. Capture probes first rejected an overly strict mouth-anchor assumption, then hit open-contour clip-cap errors; a camera-coordinate bug also produced an empty render. Those probes were corrected, and the final matched views rendered successfully without clipping.
- The displacement/normal diagnostic returned exit 14 because its legacy 1 cm / 2° thresholds were exceeded. This reports the measured world change above, not a passage walkability failure.
All measured workload processes were capped below 30 minutes and run on CPUs 0–11; headless processes used the 6 GiB address-space limit. Logs and summaries are under `Saved/Linux/l99/`. No install, commit, push, or stash was made.
## Captures (fixed)
The first hotspot views exported LOD0, while these maxima come from LOD2. The fixed views export the matching LOD2 audit tile, put the exact audit point at image center, and place the camera 3.25 m from its L98 surface (3.25–3.29 m from the audit point). Each before-and-after surface triangle is within 0.09 m of the reported point in the matching LOD2 tile. Displacement views look obliquely along the passage; normal-change views use normal-color shading. Pixel counts below are measured decoded RGB pixels with any changed channel; each diff image is absolute RGB difference amplified 64×. All ten pairs differ.
Strata labels: SW = Surface World, FI = Floating Islands, TN = Tunnel Network, VS = Vertical Shafts. Passage distances are measured from the site to the nearest route centerline; surface labels use the nearest LOD2 surface and passage-region class.
| Site | Strate · change · focus (m) | Surface · nearest passage | Changed pixels | Before / after / diff |
|---|---|---|---:|---|
| `top-displacement-01` | VS · 25.88 cm · (18.000, -2.000, -817.035) | passage mouth surface · passage/2-3/0, 3.17 m | 557,549 | [L98](captures/top-displacement-01-before-l98.png) / [L99](captures/top-displacement-01-after-l99.png) / [Δ×64](captures/top-displacement-01-diff-amplified.png) |
| `top-displacement-02` | TN · 21.74 cm · (26.000, -4.000, -692.765) | passage upper wall/roof · passage/0-1/0, 3.47 m | 565,587 | [L98](captures/top-displacement-02-before-l98.png) / [L99](captures/top-displacement-02-after-l99.png) / [Δ×64](captures/top-displacement-02-diff-amplified.png) |
| `top-displacement-03` | SW · 20.11 cm · (-23.127, 4.000, -490.000) | passage upper wall/roof · passage/0-1/0, 3.45 m | 760,804 | [L98](captures/top-displacement-03-before-l98.png) / [L99](captures/top-displacement-03-after-l99.png) / [Δ×64](captures/top-displacement-03-diff-amplified.png) |
| `top-displacement-04` | FI · 19.80 cm · (16.000, -17.133, -668.000) | passage upper wall/roof · passage/0-1/0, 3.47 m | 729,645 | [L98](captures/top-displacement-04-before-l98.png) / [L99](captures/top-displacement-04-after-l99.png) / [Δ×64](captures/top-displacement-04-diff-amplified.png) |
| `top-displacement-05` | SW · 19.01 cm · (20.000, 15.067, -500.000) | passage wall · passage/0-1/0, 3.51 m | 372,637 | [L98](captures/top-displacement-05-before-l98.png) / [L99](captures/top-displacement-05-after-l99.png) / [Δ×64](captures/top-displacement-05-diff-amplified.png) |
| `top-normal-01` | VS · 49.82° · (39.696, 6.000, -820.000) | other strata rock wall outside passage envelope · passage/2-3/0, 24.47 m | 423,185 | [L98](captures/top-normal-01-before-l98.png) / [L99](captures/top-normal-01-after-l99.png) / [Δ×64](captures/top-normal-01-diff-amplified.png) |
| `top-normal-02` | TN · 41.79° · (34.000, 5.849, -892.000) | other strata rock wall outside passage envelope · passage/2-3/0, 19.85 m | 539,369 | [L98](captures/top-normal-02-before-l98.png) / [L99](captures/top-normal-02-after-l99.png) / [Δ×64](captures/top-normal-02-diff-amplified.png) |
| `top-normal-03` | TN · 35.51° · (38.000, 3.770, -844.000) | other strata rock wall outside passage envelope · passage/2-3/0, 23.26 m | 738,068 | [L98](captures/top-normal-03-before-l98.png) / [L99](captures/top-normal-03-after-l99.png) / [Δ×64](captures/top-normal-03-diff-amplified.png) |
| `top-normal-04` | VS · 35.05° · (-6.234, 28.000, -824.000) | rock overhang/underside near passage · passage/2-3/0, 13.06 m | 770,128 | [L98](captures/top-normal-04-before-l98.png) / [L99](captures/top-normal-04-after-l99.png) / [Δ×64](captures/top-normal-04-diff-amplified.png) |
| `top-normal-05` | FI · 29.91° · (-39.904, -16.000, -690.000) | rock overhang/island underside outside passage envelope · passage/0-1/0, 24.12 m | 291,241 | [L98](captures/top-normal-05-before-l98.png) / [L99](captures/top-normal-05-after-l99.png) / [Δ×64](captures/top-normal-05-diff-amplified.png) |
The earlier LOD0 pairs and a first LOD2 placement did not include the audited tile; the latter placed displacement-01's nearest surface 22.73 m away and failed the close-framing check. The final LOD2 tile keys match all ten audit rows and all ten image pairs pass the raw-pixel comparison. Export and render logs plus exact camera, triangle, and pixel-diff metadata are in `Saved/Linux/l99/captures-fix/`.
@@ -0,0 +1,380 @@
#!/usr/bin/env python3
"""Capture matched L98/L99 tunnel change and passage views."""
from __future__ import annotations
import json
import math
import struct
import subprocess
import time
from array import array
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l99/captures"
IMAGE_DIR = Path(__file__).resolve().parent / "captures"
DATA = ROOT / "Standalone/data/owner_world.json"
LAYOUT_PATH = ROOT / "Saved/Linux/l99/resolved-sw.json"
REACH_SUMMARY = ROOT / "Saved/Linux/l99/carve-reach/summary.json"
BINARIES = {
"before-l98": ROOT / "Saved/Linux/l98/bin/VoxelForgeCoreHeadless-l98-clang19",
"after-l99": ROOT / "Saved/Linux/l99/bin/VoxelForgeCoreHeadless-l99-clang",
}
RENDERER = ROOT / "Saved/Linux/l99/bin/VoxelForgeOffscreenCapture-l99-clang"
VOXEL_Q = 256.0
CORE_UM_PER_VOXEL = 250_000.0
def add(a, b):
return tuple(x + y for x, y in zip(a, b))
def sub(a, b):
return tuple(x - y for x, y in zip(a, b))
def mul(a, s):
return tuple(x * s for x in a)
def dot(a, b):
return sum(x * y for x, y in zip(a, b))
def norm(a):
length = math.sqrt(dot(a, a))
if length < 1.0e-8:
raise ValueError("degenerate capture direction")
return tuple(x / length for x in a)
def fmt(values):
return ",".join(f"{value:.9f}" for value in values)
def angles(camera, target):
delta = sub(target, camera)
return (math.degrees(math.atan2(delta[0], delta[1])),
math.degrees(math.atan2(delta[2], math.hypot(delta[0], delta[1]))))
def interpolate(points, distance):
remaining = max(0.0, distance)
for a, b in zip(points, points[1:]):
delta = sub(b[0], a[0])
length = math.sqrt(dot(delta, delta))
if length < 1.0e-8:
continue
if remaining <= length:
t = remaining / length
return (tuple(a[0][i] + delta[i] * t for i in range(3)),
a[1] + (b[1] - a[1]) * t)
remaining -= length
return points[-1]
def route_frame(points, distance):
center, radius = interpolate(points, distance)
before, _ = interpolate(points, max(0.0, distance - 0.25))
after, _ = interpolate(points, min(sum(
math.sqrt(dot(sub(b[0], a[0]), sub(b[0], a[0])))
for a, b in zip(points, points[1:])), distance + 0.25))
tangent = norm(sub(after, before))
horizontal = norm((tangent[0], tangent[1], 0.0))
side = (-horizontal[1], horizontal[0], 0.0)
return center, radius, tangent, side
def route_length(points):
return sum(math.sqrt(dot(sub(b[0], a[0]), sub(b[0], a[0])))
for a, b in zip(points, points[1:]))
def nearest_route(points, point):
best = None
distance_along = 0.0
for a, b in zip(points, points[1:]):
delta = sub(b[0], a[0])
length_sq = dot(delta, delta)
if length_sq < 1.0e-10:
continue
segment_length = math.sqrt(length_sq)
t = max(0.0, min(1.0, dot(sub(point, a[0]), delta) / length_sq))
center = add(a[0], mul(delta, t))
tangent = norm(delta)
radial = sub(point, center)
radial = sub(radial, mul(tangent, dot(radial, tangent)))
distance = math.sqrt(dot(sub(point, center), sub(point, center)))
if best is None or distance < best[0]:
radius = a[1] + (b[1] - a[1]) * t
best = (distance, center, tangent, radial, radius,
distance_along + t * segment_length)
distance_along += segment_length
if best is None:
raise ValueError("passage route has no non-degenerate segment")
return best
def run(name, command, headless):
log = OUT / f"{name}.log"
wrapped = ["taskset", "-c", "0-11"]
if headless:
wrapped += ["prlimit", "--as=6442450944", "--"]
wrapped += ["timeout", "1740s", *map(str, command)]
started = time.monotonic()
with log.open("w", encoding="utf-8") as stream:
result = subprocess.run(wrapped, cwd=ROOT, stdout=stream,
stderr=subprocess.STDOUT, check=False, timeout=1745)
row = {"name": name, "exit_code": result.returncode,
"wall_s": round(time.monotonic() - started, 3),
"log": str(log.relative_to(ROOT))}
print("[L99CaptureRun] " + json.dumps(row, sort_keys=True), flush=True)
if result.returncode != 0:
raise RuntimeError(f"capture step failed: {name}; see {log}")
return row
def nearest_surface_anchor(mesh, point):
"""Return the nearest surface vertex and outward mesh normal from a VFMSH scene."""
best_distance_sq = float("inf")
best_position = best_normal = None
with mesh.open("rb") as stream:
magic = stream.read(8)
has_materials = magic == b"VFMSH002"
if magic not in (b"VFMSH001", b"VFMSH002"):
raise RuntimeError(f"unsupported mesh scene header: {mesh}")
version, voxel_um, tile_count = struct.unpack("<3I", stream.read(12))
if version != (2 if has_materials else 1):
raise RuntimeError(f"mesh scene version mismatch: {mesh}")
stream.seek(40, 1) # stored camera position, yaw, and pitch
for _ in range(tile_count):
key = struct.unpack("<4i", stream.read(16))
surface_count, _, vertex_count, index_count, mask_count = struct.unpack(
"<5I", stream.read(20))
extent = (32 << key[3]) * voxel_um / 1_000_000.0
origin = (key[0] * extent, key[1] * extent, key[2] * extent)
if any(point[axis] < origin[axis] - 20.0 or
point[axis] > origin[axis] + extent + 20.0 for axis in range(3)):
stream.seek(vertex_count * 24 + (10 * vertex_count if has_materials else 0)
+ index_count * 4 + mask_count, 1)
continue
positions = array("f")
positions.frombytes(stream.read(vertex_count * 12))
normals = array("f")
normals.frombytes(stream.read(vertex_count * 12))
if has_materials:
stream.seek(vertex_count * 10, 1)
stream.seek(index_count * 4 + mask_count, 1)
if struct.pack("=I", 1) != struct.pack("<I", 1):
positions.byteswap()
normals.byteswap()
for index in range(min(surface_count, vertex_count)):
offset = index * 3
# VFMSH stores vertex coordinates in world space; tile origin
# is metadata for edge rendering and must not be added again.
position = tuple(float(positions[offset + axis]) for axis in range(3))
delta = sub(position, point)
distance_sq = dot(delta, delta)
if distance_sq >= best_distance_sq:
continue
raw_normal = tuple(float(normals[offset + axis]) for axis in range(3))
if dot(raw_normal, raw_normal) < 1.0e-10:
continue
best_distance_sq = distance_sq
best_position = position
best_normal = norm(raw_normal)
if best_position is None:
raise RuntimeError(f"no nearby surface vertices in mesh scene: {mesh}")
return {"position_m": best_position, "normal": best_normal,
"distance_m": math.sqrt(best_distance_sq)}
def make_passages(layout):
q_to_m = layout["core_voxel_size_um"] / (VOXEL_Q * 1_000_000.0)
passages = []
all_routes = []
for passage in layout["passages"]:
controls = [(tuple(float(q) * q_to_m for q in p["position_q"]),
float(p["radius_q"]) * q_to_m)
for p in passage["control_points"]]
upper = tuple(float(q) * q_to_m for q in passage["upper_mouth_q"])
lower = tuple(float(q) * q_to_m for q in passage["lower_mouth_q"])
ui = min(range(len(controls)), key=lambda i: dot(sub(controls[i][0], upper),
sub(controls[i][0], upper)))
li = min(range(len(controls)), key=lambda i: dot(sub(controls[i][0], lower),
sub(controls[i][0], lower)))
points = controls[ui:li + 1]
if len(points) < 2:
raise RuntimeError(f"invalid mouth path: {passage['stable_id']}")
upper_offset = math.sqrt(dot(sub(points[0][0], upper), sub(points[0][0], upper)))
lower_offset = math.sqrt(dot(sub(points[-1][0], lower), sub(points[-1][0], lower)))
length = route_length(points)
passage_views = []
for endpoint, distance, camera_distance, outward_sign in (
("upper-mouth", 0.0, min(length, 7.4), -1.0),
("lower-mouth", length, max(0.0, length - 5.5), 1.0)):
center, _, tangent, side = route_frame(points, distance)
camera_center, _, _, camera_side = route_frame(points, camera_distance)
camera = (camera_center[0] + camera_side[0] * 2.5,
camera_center[1] + camera_side[1] * 2.5, camera_center[2])
outward = mul(tangent, outward_sign)
target = (center[0] + outward[0] * 8.0 + side[0] * 2.5,
center[1] + outward[1] * 8.0 + side[1] * 2.5, center[2])
yaw, pitch = angles(camera, target)
passage_views.append({"name": f"{passage['stable_id']}-{endpoint}",
"kind": "mouth", "focus": center, "camera": camera, "target": target,
"yaw": yaw, "pitch": pitch, "clip": None, "fov": 80.0,
"focus_radius": 20.0, "passage": passage["stable_id"],
"camera_location": "passage-air"})
mid = length * 0.5
center, radius, tangent, side = route_frame(points, mid)
camera = (center[0] + side[0] * 0.75, center[1] + side[1] * 0.75,
center[2] - radius + 1.6)
target_pos, target_radius = interpolate(points, min(length, mid + 14.0))
target = (target_pos[0] + side[0] * 2.35, target_pos[1] + side[1] * 2.35,
target_pos[2] - target_radius + 1.8)
yaw, pitch = angles(camera, target)
passage_views.append({"name": f"{passage['stable_id']}-mid-tunnel",
"kind": "mid-tunnel", "focus": center, "camera": camera,
"target": target, "yaw": yaw, "pitch": pitch, "clip": None,
"fov": 78.0, "focus_radius": 24.0, "passage": passage["stable_id"]})
passages.append({"stable_id": passage["stable_id"], "route_length_m": length,
"control_points": len(points), "upper_mouth_anchor_offset_m": upper_offset,
"lower_mouth_anchor_offset_m": lower_offset, "views": passage_views})
all_routes.append((passage["stable_id"], points))
return passages, all_routes
def make_hotspot_views(routes, reach_summary):
views = []
for series, rows, value_key, location_key, region_key in (
("displacement", reach_summary["top_displacement_tiles"],
"max_displacement_cm", "displacement_location_metres", "displacement_region"),
("normal", reach_summary["top_normal_tiles"],
"max_normal_degrees", "normal_location_metres", "normal_region"),
):
for index, row in enumerate(rows, 1):
point = tuple(row[location_key])
candidates = [(nearest_route(route, point), name, route)
for name, route in routes]
(distance, center, tangent, radial, radius, along), nearest_id, route = min(
candidates, key=lambda item: item[0][0])
if math.sqrt(dot(radial, radial)) < 0.25:
horizontal = norm((tangent[0], tangent[1], 0.0))
radial = (-horizontal[1], horizontal[0], 0.0)
outward = norm(radial)
camera = add(point, mul(outward, 20.0))
target = point
plane = None
yaw, pitch = angles(camera, target)
views.append({"name": f"top-{series}-{index:02d}", "kind": f"top-{series}",
"focus": point, "camera": camera, "target": target,
"yaw": yaw, "pitch": pitch, "clip": plane, "fov": 68.0,
"focus_radius": 18.0, "strate": row["strate"],
"value": row[value_key], "region": row[region_key],
"camera_anchor": "nearest-surface-l98-v5", "nearest_passage": nearest_id,
"route_distance_m": distance})
return views
def export_and_render(view, label, binary):
safe = view["name"].replace("/", "-")
stem = f"{safe}-{label}"
mesh = OUT / f"{stem}.vfmesh"
image = IMAGE_DIR / f"{safe}-{label}.png"
camera, target = view["camera"], view["target"]
yaw, pitch = angles(camera, target)
export_args = [binary, DATA, "--world-assembler-prototype", "--stream-benchmark",
"--stream-pipeline", "--workers=8", "--force-generate", "--clip-radius=3",
"--max-clip-level=0", "--max-level=0", "--render-distance-chunks=0",
f"--camera={fmt((*camera, yaw, pitch))}", f"--mesh-export={mesh}"]
export_row = run(f"{stem}-export", export_args, headless=True)
export_row["scene_bytes"] = mesh.stat().st_size if mesh.exists() else 0
if export_row["scene_bytes"] <= 0:
raise RuntimeError(f"empty mesh scene: {mesh}")
if label == "before-l98" and view.get("camera_anchor") == "nearest-surface-l98-v5":
anchor = nearest_surface_anchor(mesh, view["focus"])
camera = add(anchor["position_m"], mul(anchor["normal"], 8.0))
view["camera"] = camera
view["target"] = anchor["position_m"]
view["yaw"], view["pitch"] = angles(camera, view["target"])
view["clip"] = None
view["surface_anchor"] = anchor
yaw, pitch = view["yaw"], view["pitch"]
render_args = [RENDERER, mesh, image, "--size=1600x900",
f"--fov-deg={view['fov']:.1f}", f"--camera-m={fmt(camera)}",
f"--yaw-deg={yaw:.6f}", f"--pitch-deg={pitch:.6f}", "--shade=readable",
"--headlamp=on", "--height-tint=on", "--tile-edges=off", "--tile-skirts=off",
f"--height-center-m={view['focus'][2]:.6f}", "--height-range-m=10",
f"--focus-region={fmt((*view['focus'], view['focus_radius']))}"]
if view["clip"] is not None:
render_args.append(f"--clip-plane={fmt(view['clip'])}")
try:
render_row = run(f"{stem}-render", render_args, headless=True)
except RuntimeError:
if view["clip"] is None:
raise
retry_args = [arg for arg in render_args
if not (isinstance(arg, str) and arg.startswith("--clip-plane="))]
render_row = run(f"{stem}-render-no-clip-retry", retry_args, headless=True)
render_row["clip_fallback"] = True
render_row["image"] = str(image.relative_to(ROOT))
render_row["image_bytes"] = image.stat().st_size if image.exists() else 0
if render_row["image_bytes"] <= 0:
raise RuntimeError(f"empty image: {image}")
mesh.unlink()
return export_row, render_row
def main():
OUT.mkdir(parents=True, exist_ok=True)
IMAGE_DIR.mkdir(parents=True, exist_ok=True)
for path in (*BINARIES.values(), RENDERER, DATA, LAYOUT_PATH, REACH_SUMMARY):
if not path.is_file():
raise SystemExit(f"missing input: {path}")
layout = json.loads(LAYOUT_PATH.read_text(encoding="utf-8"))
reach_summary = json.loads(REACH_SUMMARY.read_text(encoding="utf-8"))
passages, routes = make_passages(layout)
views = [view for passage in passages for view in passage["views"]]
views += make_hotspot_views(routes, reach_summary)
partial_path = OUT / "runs-partial.json"
existing_records = {}
if partial_path.is_file():
for item in json.loads(partial_path.read_text(encoding="utf-8")):
existing_records[item["view"]["name"]] = item
records = []
for view in views:
prior = existing_records.get(view["name"])
camera_matches = (prior is not None and
prior["view"].get("camera_anchor") == view.get("camera_anchor"))
if camera_matches:
for key in ("camera", "target", "yaw", "pitch", "clip", "surface_anchor"):
if key in prior["view"]:
view[key] = prior["view"][key]
if camera_matches and all(version in prior and
(ROOT / prior[version]["render"]["image"]).is_file()
for version in BINARIES):
records.append(prior)
print(f"[L99CaptureSkip] complete_pair={view['name']}", flush=True)
continue
pair = {"view": view}
for label, binary in BINARIES.items():
export_row, render_row = export_and_render(view, label, binary)
pair[label] = {"export": export_row, "render": render_row}
(OUT / "runs-partial.json").write_text(json.dumps(records + [pair], indent=2) + "\n",
encoding="utf-8")
records.append(pair)
summary = {"source_layout_identity": layout["stable_identity"],
"view_count": len(views), "paired_png_count": len(views) * 2,
"passages": passages, "views": records}
(OUT / "summary.json").write_text(json.dumps(summary, indent=2) + "\n",
encoding="utf-8")
print("[L99CaptureSummary] " + json.dumps({
"views": len(views), "paired_png_count": len(views) * 2,
"images": [row[version]["render"]["image"] for row in records
for version in BINARIES]}, sort_keys=True), flush=True)
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,137 @@
#!/usr/bin/env python3
"""Compare the current unbounded passage carve with the L99 four-metre tube."""
from __future__ import annotations
import json
import re
import subprocess
import sys
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l99/carve-reach"
DATA = ROOT / "Standalone/data/owner_world.json"
HEADLESS = ROOT / "Saved/Linux/l99/bin/VoxelForgeCoreHeadless-l99-clang"
CAMERAS = {
"SW": (8.0, 16.0, -372.2626953125),
"FI": (25.8369140625, 3.19921875, -737.0),
"TN": (19.166015625, -14.2001953125, -753.3740234375),
"VS": (-20.0625, -5.068359375, -900.353515625),
}
SUMMARY = re.compile(r"^\[L93PassageReachSummary\] reach_m=4\.00 (.*)$", re.M)
COVERAGE = re.compile(r"^\[L93PassageReachCoverage\] (.*)$", re.M)
TILE = re.compile(
r"^\[L93PassageReachTile\] reach_m=4\.00 key=\(([-\d]+),([-\d]+),([-\d]+),lod(\d+)\) "
r"samples=(\d+) changed=(\d+) sign_flips=(\d+) moved_edges=(\d+) "
r"max_displacement_cm=([\deE+.-]+) max_displacement_xyz_vox=\(([^)]*)\) "
r"displacement_region=(\d+) normal_vertices=(\d+) max_normal_degrees=([\deE+.-]+) "
r"max_normal_xyz_vox=\(([^)]*)\) normal_region=(\d+) changed_mesh=(\d+)$", re.M)
VOXEL_SIZE_M = 0.25
def xyz(text: str) -> list[float]:
return [float(part) for part in text.split(",")]
def parse_locations(output: str, strate: str) -> tuple[list[dict], list[dict]]:
rows = []
for match in TILE.finditer(output):
(x, y, z, lod, samples, changed, flips, moved, displacement, displacement_xyz,
displacement_region, normal_vertices, angle, normal_xyz, normal_region,
changed_mesh) = match.groups()
disp_vox = xyz(displacement_xyz)
normal_vox = xyz(normal_xyz)
rows.append({
"strate": strate, "tile": [int(x), int(y), int(z), int(lod)],
"samples": int(samples), "changed_samples": int(changed),
"sign_flips": int(flips), "moved_edges": int(moved),
"max_displacement_cm": float(displacement),
"displacement_location_voxels": disp_vox,
"displacement_location_metres": [value * VOXEL_SIZE_M for value in disp_vox],
"displacement_region": int(displacement_region),
"normal_vertices": int(normal_vertices), "max_normal_degrees": float(angle),
"normal_location_voxels": normal_vox,
"normal_location_metres": [value * VOXEL_SIZE_M for value in normal_vox],
"normal_region": int(normal_region), "changed_mesh": bool(int(changed_mesh)),
})
top_displacement = sorted(rows, key=lambda row: row["max_displacement_cm"], reverse=True)[:5]
top_normals = sorted(rows, key=lambda row: row["max_normal_degrees"], reverse=True)[:5]
return top_displacement, top_normals
def top_unique(rows: list[dict], value_key: str, location_key: str) -> list[dict]:
chosen, seen = [], set()
for row in sorted(rows, key=lambda item: item[value_key], reverse=True):
if row[value_key] <= 0.0:
continue
location = tuple(round(value, 4) for value in row[location_key])
if location in seen:
continue
seen.add(location)
chosen.append(row)
if len(chosen) == 5:
break
return chosen
def run(strate: str) -> dict:
OUT.mkdir(parents=True, exist_ok=True)
log = OUT / f"reach-{strate.lower()}.log"
camera = ",".join(f"{value:.12f}" for value in CAMERAS[strate]) + ",0,0"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1740s", str(HEADLESS), str(DATA), "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", "--workers=8", "--clip-radius=3",
"--max-clip-level=8", "--max-level=8", f"--camera={camera}",
"--audit-passage-carve-reach",
]
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")
summary_match, coverage_match = SUMMARY.search(output), COVERAGE.search(output)
if summary_match is None or coverage_match is None or result.returncode in (-9, 124):
raise RuntimeError(f"incomplete carve-reach audit: exit={result.returncode} {log}")
reach = dict(re.findall(r"([a-zA-Z0-9_]+)=([^ ]+)", summary_match.group(1)))
coverage = dict(re.findall(r"([a-zA-Z0-9_]+)=([^ ]+)", coverage_match.group(1)))
top_displacement, top_normals = parse_locations(output, strate)
row = {
"strate": strate, "exit_code": result.returncode,
"wall_s": round(time.monotonic() - start, 3), "coverage": coverage,
"reach_4m_summary": reach,
"reach_4m_tile_rows": len(TILE.findall(output)),
"top_displacement_tiles": top_displacement,
"top_normal_tiles": top_normals,
"log": str(log.relative_to(ROOT)),
}
print("[L99CarveReachRun] " + json.dumps(row, sort_keys=True), flush=True)
return row
def main() -> int:
if not HEADLESS.is_file() or not DATA.is_file():
raise SystemExit(f"missing input: {HEADLESS if not HEADLESS.is_file() else DATA}")
if "--summarize-only" in sys.argv:
rows = json.loads((OUT / "runs-partial.json").read_text(encoding="utf-8"))
else:
rows = []
for strate in CAMERAS:
rows.append(run(strate))
(OUT / "runs-partial.json").write_text(json.dumps(rows, indent=2) + "\n",
encoding="utf-8")
all_displacements = [row for result in rows for row in result["top_displacement_tiles"]]
all_normals = [row for result in rows for row in result["top_normal_tiles"]]
top_displacement = top_unique(all_displacements, "max_displacement_cm",
"displacement_location_metres")
top_normals = top_unique(all_normals, "max_normal_degrees", "normal_location_metres")
summary = {"runs": rows, "top_displacement_tiles": top_displacement,
"top_normal_tiles": top_normals}
(OUT / "summary.json").write_text(json.dumps(summary, indent=2) + "\n",
encoding="utf-8")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,98 @@
#!/usr/bin/env python3
"""Three interleaved L77 passage-share censuses for L98 and L99, all four strata."""
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/l99/census"
DATA = ROOT / "Standalone/data/owner_world.json"
BINARIES = {
"L98": ROOT / "Saved/Linux/l98/profile-post-bin/VoxelForgeCoreHeadless-l98-post-profile",
"L99": ROOT / "Saved/Linux/l99/profile-bin/VoxelForgeCoreHeadless-l99-profile-clang",
}
CAMERAS = {
"SW": (8.0, 16.0, -372.2626953125),
"FI": (25.8369140625, 3.19921875, -737.0),
"TN": (19.166015625, -14.2001953125, -753.3740234375),
"VS": (-20.0625, -5.068359375, -900.353515625),
}
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(strate: str, label: str, rep: int) -> dict:
OUT.mkdir(parents=True, exist_ok=True)
log = OUT / f"{label.lower()}-{strate.lower()}-r{rep}.log"
camera = ",".join(f"{value:.12f}" for value in CAMERAS[strate]) + ",0,0"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1740s", str(BINARIES[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"L77 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 = {
"strate": strate, "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("[L99CensusRun] " + 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 = (("L98", "L99"), ("L99", "L98"), ("L98", "L99"))
for rep, order in enumerate(orders, 1):
for label in order:
for strate in CAMERAS:
rows.append(run(strate, label, rep))
(OUT / "runs-partial.json").write_text(json.dumps(rows, indent=2) + "\n",
encoding="utf-8")
summary = []
for strate in CAMERAS:
item = {"strate": strate, "pairs": 3}
for label in BINARIES:
subset = [row for row in rows if row["strate"] == strate
and row["binary"] == label]
values = [row["near_lod_passage_share_pct"] for row in subset]
item[f"{label}_runs_pct"] = values
item[f"{label}_mean_pct"] = statistics.mean(values)
item["change_pp"] = item["L99_mean_pct"] - item["L98_mean_pct"]
summary.append(item)
(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,124 @@
#!/usr/bin/env python3
"""Check whole-view tile hashes across W1/W8/W12 and Clang/GCC."""
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/l99/hashes"
DATA = ROOT / "Standalone/data/owner_world.json"
BINARIES = {
"Clang": ROOT / "Saved/Linux/l99/bin/VoxelForgeCoreHeadless-l99-clang",
"GCC": ROOT / "Saved/Linux/l99/bin/VoxelForgeCoreHeadless-l99-gcc",
}
CAMERAS = {
"SW": (8.0, 16.0, -372.2626953125),
"FI": (25.8369140625, 3.19921875, -737.0),
"TN": (19.166015625, -14.2001953125, -753.3740234375),
"VS": (-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: str, compiler: str, workers: int) -> dict:
OUT.mkdir(parents=True, exist_ok=True)
log = OUT / f"{strate.lower()}-{compiler.lower()}-w{workers}.log"
camera = ",".join(f"{value:.12f}" for value in CAMERAS[strate]) + ",0,0"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1740s", str(BINARIES[compiler]), 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}", "--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)
stamp = re.search(r"^\[VoxelForgeSourceStamp\] sha256=([0-9a-f]{64})$", output, re.M)
if (result.returncode or whole is None or stamp is None
or len(hashes) != int(whole.group(1))):
raise RuntimeError(f"hash run failed or incomplete: exit={result.returncode} {log}")
row = {
"strate": strate, "compiler": compiler, "workers": workers,
"source_stamp": stamp.group(1), "exit_code": result.returncode,
"wall_s": round(time.monotonic() - start, 3), "tile_count": len(hashes),
"tiles_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": list(whole.groups()), "tile_hashes": hashes,
"log": str(log.relative_to(ROOT)),
}
print("[L99HashRun] " + json.dumps(
{key: value for key, value in row.items() if key != "tile_hashes"},
sort_keys=True), flush=True)
return row
def compare(strate: str, reference: dict, candidate: dict) -> dict:
keys = set(reference["tile_hashes"]) | set(candidate["tile_hashes"])
mismatches = {
field: sum(reference["tile_hashes"].get(key, (None,) * 4)[index]
!= candidate["tile_hashes"].get(key, (None,) * 4)[index]
for key in keys)
for index, field in enumerate(FIELDS)
}
row = {
"strate": strate, "reference": f"{reference['compiler']}-w{reference['workers']}",
"candidate": f"{candidate['compiler']}-w{candidate['workers']}",
"tile_counts_match": reference["tile_count"] == candidate["tile_count"],
"whole_hashes_match": reference["whole_hash"] == candidate["whole_hash"],
"tile_hashes_match": all(value == 0 for value in mismatches.values())
and reference["tile_hashes"].keys() == candidate["tile_hashes"].keys(),
"component_mismatch_tiles": mismatches,
"tiles_reference": reference["tile_count"], "tiles_candidate": candidate["tile_count"],
"reference_whole_hash": reference["whole_hash"],
"candidate_whole_hash": candidate["whole_hash"],
}
print("[L99HashComparison] " + 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}")
detail, comparisons = [], []
for strate in CAMERAS:
group = {(compiler, workers): run(strate, compiler, workers)
for compiler in BINARIES for workers in (1, 8, 12)}
reference = group[("Clang", 1)]
for key, candidate in group.items():
if key == ("Clang", 1):
continue
comparisons.append(compare(strate, reference, candidate))
detail.extend({key: value for key, value in row.items() if key != "tile_hashes"}
for row in group.values())
(OUT / "runs-partial.json").write_text(json.dumps(
{"runs": detail, "comparisons": comparisons}, indent=2) + "\n",
encoding="utf-8")
summary = {"runs": detail, "comparisons": comparisons}
(OUT / "summary.json").write_text(json.dumps(summary, indent=2) + "\n",
encoding="utf-8")
return 0 if all(row["tile_counts_match"] and row["whole_hashes_match"]
and row["tile_hashes_match"] for row in comparisons) else 1
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,103 @@
#!/usr/bin/env python3
"""L80 cold-readiness protocol: three interleaved L98/L99 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/l99/readiness"
DATA = ROOT / "Standalone/data/owner_world.json"
BINARIES = {
"L98": ROOT / "Saved/Linux/l98/bin/VoxelForgeCoreHeadless-l98-clang19",
"L99": ROOT / "Saved/Linux/l99/bin/VoxelForgeCoreHeadless-l99-clang",
}
# Resolved owner camera index -> stratum. Keep the labels tied to the canonical layout.
CAMERAS = {
"SW": (8.0, 16.0, -372.2626953125),
"FI": (25.8369140625, 3.19921875, -737.0),
"TN": (19.166015625, -14.2001953125, -753.3740234375),
"VS": (-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: str, workers: int, label: str, phase: str, rep: int) -> dict:
OUT.mkdir(parents=True, exist_ok=True)
log = OUT / f"w{workers}-{strate.lower()}-{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("[L99ReadinessRun] " + 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 = (("L98", "L99"), ("L99", "L98"), ("L98", "L99"))
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,78 @@
#!/usr/bin/env python3
"""Count bounded-sweep candidates, outside-volume rejects, and cache lookups."""
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/l99/sweep-counts"
DATA = ROOT / "Standalone/data/owner_world.json"
HEADLESS = ROOT / "Saved/Linux/l99/bin/VoxelForgeCoreHeadless-l99-clang"
CAMERAS = {
"SW": (8.0, 16.0, -372.2626953125),
"FI": (25.8369140625, 3.19921875, -737.0),
"TN": (19.166015625, -14.2001953125, -753.3740234375),
"VS": (-20.0625, -5.068359375, -900.353515625),
}
LINE = re.compile(r"^\[PassageComposerTile\] key=\([^,]+,[^,]+,[^,]+,lod(\d+)\) (.*)$", re.M)
FIELDS = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
COUNT_KEYS = (
"route_candidates", "sweep_segments", "sweep_pairs", "sweep_outside_pairs",
"sweep_min_updates", "sweep_base_samples", "sweep_detail_samples",
"sweep_grid_samples", "sweep_no_influence_samples", "sweep_lookups",
"sweep_cache_hits", "sweep_base_hits", "sweep_outside_hits", "sweep_missing",
)
def run(strate: str) -> dict:
OUT.mkdir(parents=True, exist_ok=True)
log = OUT / f"profile-sweep-{strate.lower()}.log"
camera = ",".join(f"{value:.12f}" for value in CAMERAS[strate]) + ",0,0"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1740s", str(HEADLESS), 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-passage-composer",
]
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")
if result.returncode != 0:
raise RuntimeError(f"sweep count run failed: exit={result.returncode} {log}")
rows = []
for level_text, tail in LINE.findall(output):
level = int(level_text)
if level > 2:
continue
fields = dict(FIELDS.findall(tail))
rows.append({"lod": level, **{key: int(fields.get(key, 0)) for key in COUNT_KEYS}})
if not rows:
raise RuntimeError(f"no profiled near-set passage tiles: {log}")
totals = {key: sum(row[key] for row in rows) for key in COUNT_KEYS}
row = {
"strate": strate, "workers": 8, "near_profiled_tiles": len(rows),
"near_tiles_by_lod": {str(level): sum(1 for item in rows if item["lod"] == level)
for level in range(3)},
"counters": totals, "wall_s": round(time.monotonic() - start, 3),
"log": str(log.relative_to(ROOT)),
}
print("[L99SweepCounts] " + json.dumps(row, sort_keys=True), flush=True)
return row
def main() -> int:
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())
@@ -0,0 +1,44 @@
# L99 matched captures
Each row links a before image from L98 Clang and an after image from L99 Clang. Images are 1600×900; focus coordinates are world metres.
| View | Focus / measured change | Before | After |
|---|---|---|---|
| `passage-0-1-0-upper-mouth` | passage/0-1/0 · (8.000, 16.000, -371.893) | [L98](passage-0-1-0-upper-mouth-before-l98.png) | [L99](passage-0-1-0-upper-mouth-after-l99.png) |
| `passage-0-1-0-lower-mouth` | passage/0-1/0 · (25.837, 3.199, -733.630) | [L98](passage-0-1-0-lower-mouth-before-l98.png) | [L99](passage-0-1-0-lower-mouth-after-l99.png) |
| `passage-0-1-0-mid-tunnel` | passage/0-1/0 · (-5.507, 20.753, -552.274) | [L98](passage-0-1-0-mid-tunnel-before-l98.png) | [L99](passage-0-1-0-mid-tunnel-after-l99.png) |
| `passage-1-2-0-upper-mouth` | passage/1-2/0 · (19.166, -14.200, -728.831) | [L98](passage-1-2-0-upper-mouth-before-l98.png) | [L99](passage-1-2-0-upper-mouth-after-l99.png) |
| `passage-1-2-0-lower-mouth` | passage/1-2/0 · (19.166, -14.200, -750.004) | [L98](passage-1-2-0-lower-mouth-before-l98.png) | [L99](passage-1-2-0-lower-mouth-after-l99.png) |
| `passage-1-2-0-mid-tunnel` | passage/1-2/0 · (-8.533, 12.539, -740.857) | [L98](passage-1-2-0-mid-tunnel-before-l98.png) | [L99](passage-1-2-0-mid-tunnel-after-l99.png) |
| `passage-2-3-0-upper-mouth` | passage/2-3/0 · (-20.062, -5.068, -809.025) | [L98](passage-2-3-0-upper-mouth-before-l98.png) | [L99](passage-2-3-0-upper-mouth-after-l99.png) |
| `passage-2-3-0-lower-mouth` | passage/2-3/0 · (-20.062, -5.068, -896.983) | [L98](passage-2-3-0-lower-mouth-before-l98.png) | [L99](passage-2-3-0-lower-mouth-after-l99.png) |
| `passage-2-3-0-mid-tunnel` | passage/2-3/0 · (15.521, 4.241, -854.448) | [L98](passage-2-3-0-mid-tunnel-before-l98.png) | [L99](passage-2-3-0-mid-tunnel-after-l99.png) |
| `top-displacement-01` | VS · 25.88 cm · (18.000, -2.000, -817.035) | [L98](top-displacement-01-before-l98.png) | [L99](top-displacement-01-after-l99.png) |
| `top-displacement-02` | TN · 21.74 cm · (26.000, -4.000, -692.765) | [L98](top-displacement-02-before-l98.png) | [L99](top-displacement-02-after-l99.png) |
| `top-displacement-03` | SW · 20.11 cm · (-23.127, 4.000, -490.000) | [L98](top-displacement-03-before-l98.png) | [L99](top-displacement-03-after-l99.png) |
| `top-displacement-04` | FI · 19.80 cm · (16.000, -17.133, -668.000) | [L98](top-displacement-04-before-l98.png) | [L99](top-displacement-04-after-l99.png) |
| `top-displacement-05` | SW · 19.01 cm · (20.000, 15.067, -500.000) | [L98](top-displacement-05-before-l98.png) | [L99](top-displacement-05-after-l99.png) |
| `top-normal-01` | VS · 49.82° · (39.696, 6.000, -820.000) | [L98](top-normal-01-before-l98.png) | [L99](top-normal-01-after-l99.png) |
| `top-normal-02` | TN · 41.79° · (34.000, 5.849, -892.000) | [L98](top-normal-02-before-l98.png) | [L99](top-normal-02-after-l99.png) |
| `top-normal-03` | TN · 35.51° · (38.000, 3.770, -844.000) | [L98](top-normal-03-before-l98.png) | [L99](top-normal-03-after-l99.png) |
| `top-normal-04` | VS · 35.05° · (-6.234, 28.000, -824.000) | [L98](top-normal-04-before-l98.png) | [L99](top-normal-04-after-l99.png) |
| `top-normal-05` | FI · 29.91° · (-39.904, -16.000, -690.000) | [L98](top-normal-05-before-l98.png) | [L99](top-normal-05-after-l99.png) |
## Captures (fixed)
The ten hotspot pairs now export their matching LOD2 audit tile. Each audit point is centered and 3.25–3.29 m from the camera; the nearest before/after surface triangles lie within 0.09 m of it. Displacement images use an oblique passage view; normal-change images use normal-color shading. Changed-pixel counts are measured on decoded 1600×900 RGB pixels (any channel differs); each difference image is absolute RGB diff amplified 64×. No pair is identical.
| View | Strate | Surface; nearest passage distance | Changed pixels | L98 | L99 | Abs diff ×64 |
|---|---|---|---:|---|---|---|
| `top-displacement-01` | VS | passage mouth; passage/2-3/0 · 3.17 m | 557,549 | [Before](top-displacement-01-before-l98.png) | [After](top-displacement-01-after-l99.png) | [Diff](top-displacement-01-diff-amplified.png) |
| `top-displacement-02` | TN | passage upper wall/roof; passage/0-1/0 · 3.47 m | 565,587 | [Before](top-displacement-02-before-l98.png) | [After](top-displacement-02-after-l99.png) | [Diff](top-displacement-02-diff-amplified.png) |
| `top-displacement-03` | SW | passage upper wall/roof; passage/0-1/0 · 3.45 m | 760,804 | [Before](top-displacement-03-before-l98.png) | [After](top-displacement-03-after-l99.png) | [Diff](top-displacement-03-diff-amplified.png) |
| `top-displacement-04` | FI | passage upper wall/roof; passage/0-1/0 · 3.47 m | 729,645 | [Before](top-displacement-04-before-l98.png) | [After](top-displacement-04-after-l99.png) | [Diff](top-displacement-04-diff-amplified.png) |
| `top-displacement-05` | SW | passage wall; passage/0-1/0 · 3.51 m | 372,637 | [Before](top-displacement-05-before-l98.png) | [After](top-displacement-05-after-l99.png) | [Diff](top-displacement-05-diff-amplified.png) |
| `top-normal-01` | VS | other strata rock wall outside envelope; passage/2-3/0 · 24.47 m | 423,185 | [Before](top-normal-01-before-l98.png) | [After](top-normal-01-after-l99.png) | [Diff](top-normal-01-diff-amplified.png) |
| `top-normal-02` | TN | other strata rock wall outside envelope; passage/2-3/0 · 19.85 m | 539,369 | [Before](top-normal-02-before-l98.png) | [After](top-normal-02-after-l99.png) | [Diff](top-normal-02-diff-amplified.png) |
| `top-normal-03` | TN | other strata rock wall outside envelope; passage/2-3/0 · 23.26 m | 738,068 | [Before](top-normal-03-before-l98.png) | [After](top-normal-03-after-l99.png) | [Diff](top-normal-03-diff-amplified.png) |
| `top-normal-04` | VS | rock overhang/underside near passage; passage/2-3/0 · 13.06 m | 770,128 | [Before](top-normal-04-before-l98.png) | [After](top-normal-04-after-l99.png) | [Diff](top-normal-04-diff-amplified.png) |
| `top-normal-05` | FI | rock overhang/island underside outside envelope; passage/0-1/0 · 24.12 m | 291,241 | [Before](top-normal-05-before-l98.png) | [After](top-normal-05-after-l99.png) | [Diff](top-normal-05-diff-amplified.png) |
**Complete:** 19 matched views, 38 before/after PNGs, and 10 amplified hotspot diffs.
Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

+23 -2
View File
@@ -140,6 +140,8 @@ namespace StandaloneStreamBenchmark
double maxAbsDensityDelta=0.0,maxSurfaceDisplacementCm=0.0;
double sumSurfaceDisplacementCm=0.0,maxNormalAngleDegrees=0.0;
double sumNormalAngleDegrees=0.0;
std::array<double,3> maxSurfaceLocationVoxels{},maxNormalLocationVoxels{};
std::int32_t maxSurfaceRegion=3,maxNormalRegion=3;
std::array<std::uint64_t,4> movedEdgeRegions{},normalVertexRegions{};
bool meshChanged=false,valid=false;
};
@@ -253,6 +255,7 @@ namespace StandaloneStreamBenchmark
const double ta=static_cast<double>(a0)/(static_cast<double>(a0)-a1);
const double tb=static_cast<double>(b0)/(static_cast<double>(b0)-b1);
const double displacementCm=std::abs(ta-tb)*step*voxelSizeCm;
const bool newMaximum=displacementCm>result.maxSurfaceDisplacementCm;
result.maxSurfaceDisplacementCm=std::max(result.maxSurfaceDisplacementCm,
displacementCm);
result.sumSurfaceDisplacementCm+=displacementCm;
@@ -266,6 +269,11 @@ namespace StandaloneStreamBenchmark
location[2]+=(nz-z)*step*(ta+tb)*0.5;
const int region=ClassifyPassageRegion(Layout,location).category;
++result.movedEdgeRegions[static_cast<std::size_t>(region)];
if(newMaximum)
{
result.maxSurfaceLocationVoxels=location;
result.maxSurfaceRegion=region;
}
};
for(std::int32_t z=0;z<dim;++z)
for(std::int32_t y=0;y<dim;++y)
@@ -292,6 +300,7 @@ namespace StandaloneStreamBenchmark
const double cosine=std::clamp((a.x*b.x+a.y*b.y+a.z*b.z)
/(lengthA*lengthB),-1.0,1.0);
const double angle=std::acos(cosine)*RadiansToDegrees;
const bool newMaximum=angle>result.maxNormalAngleDegrees;
result.maxNormalAngleDegrees=std::max(result.maxNormalAngleDegrees,angle);
result.sumNormalAngleDegrees+=angle;
++result.normalVertices;
@@ -304,6 +313,11 @@ namespace StandaloneStreamBenchmark
position.y/geometryVoxelSizeCm,position.z/geometryVoxelSizeCm};
const int region=ClassifyPassageRegion(Layout,location).category;
++result.normalVertexRegions[static_cast<std::size_t>(region)];
if(newMaximum)
{
result.maxNormalLocationVoxels=location;
result.maxNormalRegion=region;
}
}
}
}
@@ -497,7 +511,7 @@ namespace StandaloneStreamBenchmark
movedRegions[floorIndex][region]+=comparison.movedEdgeRegions[region];
normalRegions[floorIndex][region]+=comparison.normalVertexRegions[region];
}
std::printf("[%s] %s=%.2f key=(%d,%d,%d,lod%d) samples=%llu changed=%llu sign_flips=%llu moved_edges=%llu max_displacement_cm=%.6g normal_vertices=%llu max_normal_degrees=%.6g changed_mesh=%d\n",
std::printf("[%s] %s=%.2f key=(%d,%d,%d,lod%d) samples=%llu changed=%llu sign_flips=%llu moved_edges=%llu max_displacement_cm=%.6g max_displacement_xyz_vox=(%.6g,%.6g,%.6g) displacement_region=%d normal_vertices=%llu max_normal_degrees=%.6g max_normal_xyz_vox=(%.6g,%.6g,%.6g) normal_region=%d changed_mesh=%d\n",
tileTag,valueName,Candidates[floorIndex],key.coordinate.x,
key.coordinate.y,key.coordinate.z,key.lod,
static_cast<unsigned long long>(comparison.samples),
@@ -505,8 +519,15 @@ namespace StandaloneStreamBenchmark
static_cast<unsigned long long>(comparison.signFlips),
static_cast<unsigned long long>(comparison.movedCrossingEdges),
comparison.maxSurfaceDisplacementCm,
comparison.maxSurfaceLocationVoxels[0],
comparison.maxSurfaceLocationVoxels[1],
comparison.maxSurfaceLocationVoxels[2],comparison.maxSurfaceRegion,
static_cast<unsigned long long>(comparison.normalVertices),
comparison.maxNormalAngleDegrees,comparison.meshChanged?1:0);
comparison.maxNormalAngleDegrees,
comparison.maxNormalLocationVoxels[0],
comparison.maxNormalLocationVoxels[1],
comparison.maxNormalLocationVoxels[2],comparison.maxNormalRegion,
comparison.meshChanged?1:0);
}
}
bool acceptedAtAllFloors=generationFailures==0&&invalidComparisons==0
+15 -2
View File
@@ -1493,7 +1493,7 @@ private:
static_cast<unsigned long long>(D.islandSharedCacheConflictReplacements),
static_cast<double>(D.islandSharedCacheLockWaitNanoseconds)/1000.0);
if(D.passageComposerProfiled)
std::printf("[PassageComposerTile] key=(%d,%d,%d,lod%d) worker_ms=%.3f samples=%llu native_field_calls=%llu features=%llu route_candidates=%llu mouth_candidates=%llu boundary_band_queries=%llu queries=%llu passage_iterations=%llu tile_bound_checks=%llu tile_bound_rejects=%llu tile_bound_skipped_iterations=%llu upper_mouth_checks=%llu column_candidates=%llu column_distance_calls=%llu negative_bounds=%llu negative_rejects=%llu positive_bounds=%llu positive_rejects=%llu carve_segment_visits=%llu smooth_carve_joins=%llu support_segment_visits=%llu smooth_support_joins=%llu support_capsule_checks=%llu later_bore_checks=%llu other_capsule_checks=%llu mouth_support_calls=%llu mouth_route_segment_checks=%llu relief_field_calls=%llu smoothstep_evaluations=%llu\n",
std::printf("[PassageComposerTile] key=(%d,%d,%d,lod%d) worker_ms=%.3f samples=%llu native_field_calls=%llu features=%llu route_candidates=%llu mouth_candidates=%llu boundary_band_queries=%llu queries=%llu passage_iterations=%llu tile_bound_checks=%llu tile_bound_rejects=%llu tile_bound_skipped_iterations=%llu upper_mouth_checks=%llu column_candidates=%llu column_distance_calls=%llu negative_bounds=%llu negative_rejects=%llu positive_bounds=%llu positive_rejects=%llu carve_segment_visits=%llu smooth_carve_joins=%llu support_segment_visits=%llu smooth_support_joins=%llu support_capsule_checks=%llu later_bore_checks=%llu other_capsule_checks=%llu mouth_support_calls=%llu mouth_route_segment_checks=%llu relief_field_calls=%llu smoothstep_evaluations=%llu sweep_segments=%llu sweep_pairs=%llu sweep_outside_pairs=%llu sweep_min_updates=%llu sweep_base_samples=%llu sweep_detail_samples=%llu sweep_grid_samples=%llu sweep_no_influence_samples=%llu sweep_lookups=%llu sweep_cache_hits=%llu sweep_base_hits=%llu sweep_outside_hits=%llu sweep_missing=%llu\n",
Event.Key.Coord.X,Event.Key.Coord.Y,Event.Key.Coord.Z,Event.Key.Level,
Event.WorkerMilliseconds,static_cast<unsigned long long>(Event.DensitySamples),
static_cast<unsigned long long>(D.fieldCalls),
@@ -1523,7 +1523,20 @@ private:
static_cast<unsigned long long>(D.passageMouthSupportCalls),
static_cast<unsigned long long>(D.passageMouthRouteSegmentChecks),
static_cast<unsigned long long>(D.passageReliefFieldCalls),
static_cast<unsigned long long>(D.passageSmoothstepEvaluations));
static_cast<unsigned long long>(D.passageSmoothstepEvaluations),
static_cast<unsigned long long>(D.passageSweepRouteSegmentCandidates),
static_cast<unsigned long long>(D.passageSweepSegmentSamplePairs),
static_cast<unsigned long long>(D.passageSweepOutsideInfluencePairs),
static_cast<unsigned long long>(D.passageSweepMinimumUpdates),
static_cast<unsigned long long>(D.passageSweepBaseInfluenceSamples),
static_cast<unsigned long long>(D.passageSweepDetailShellSamples),
static_cast<unsigned long long>(D.passageSweepGridSamples),
static_cast<unsigned long long>(D.passageSweepNoInfluenceGridSamples),
static_cast<unsigned long long>(D.passageSweepLookups),
static_cast<unsigned long long>(D.passageSweepCacheHits),
static_cast<unsigned long long>(D.passageSweepBaseInfluenceHits),
static_cast<unsigned long long>(D.passageSweepOutOfInfluenceHits),
static_cast<unsigned long long>(D.passageSweepMissingSamples));
}
void RecordTimelineEvent(FStandaloneCpuTileTimelineEvent::EType Type,
const FVoxelTileKey& Key,int32 WorkerId,size_t QueueDepth,size_t ActiveWorkers,
@@ -0,0 +1,12 @@
[PipelineFullDetailHash] tiles=960 density=0x91931a57a067fd92 mesh=0xa818654be89790ac payload=0x370ff80451105a39 topology=0x06e507557aa70173
[PipelineWholeViewHash] tiles=2370 density=0x285abaef671ff116 mesh=0x4b43c7d0d08937f6 payload=0x44538e758cd149a1 topology=0x516431dce3692be9
[PipelineLODHash] lod=0 tiles=512 density=0x2fd4c78685a43cfe mesh=0x50478cff1c1f8ca0 payload=0x54e48e57e0af26de topology=0x860c011cfdb9833a
[PipelineLODHash] lod=1 tiles=448 density=0x4b8d9a6dcd2942ad mesh=0x17e89b7087bfe711 payload=0x2389c109eb780db6 topology=0x7eccd4cf560d0630
[PipelineLODHash] lod=2 tiles=320 density=0x24a9b0376e3c164b mesh=0x27d42082f5c03ed5 payload=0xea854ab664780a57 topology=0xe204fce51372bb3c
[PipelineLODHash] lod=3 tiles=208 density=0x81fa7be0a6aca688 mesh=0xf1b18f88e9bd7707 payload=0xe5e7efba37b1c944 topology=0x76293c71d3a45adf
[PipelineLODHash] lod=4 tiles=240 density=0xfb11591772a3d18e mesh=0xebf2baee8764591b payload=0xe491d7ac9c0061ad topology=0xd618ac33b95ce494
[PipelineLODHash] lod=5 tiles=144 density=0x2f8b7b7fb1e67258 mesh=0x4cd508cbe97344fd payload=0x2b6e96a6050458c9 topology=0x4ff6d0eda8988573
[PipelineLODHash] lod=6 tiles=96 density=0x0114a34c8b8d6113 mesh=0xb1fec4d01ae1fa44 payload=0x3e50cfd429b8e950 topology=0x08090c96d15c1618
[PipelineLODHash] lod=7 tiles=96 density=0x3836ed8cfce73418 mesh=0x40bc6589c5fc1aac payload=0x677349bcae16790f topology=0x36e3aa53192e6a6b
[PipelineLODHash] lod=8 tiles=306 density=0x144f3c1fd722708b mesh=0xef77b28bf7fd9453 payload=0xe3f47b37fcd61489 topology=0x5a3c73e295494e04
[PipelineExactQueryHash] probes=9 density=0x7b7c50ac24ccadcc unclamped=0x7b7c50ac24ccadcc detail=0xefcba20c0c70c415 collision=0xe604823a249029bf path=direct-Core-queries
@@ -0,0 +1,12 @@
[PipelineFullDetailHash] tiles=960 density=0xace56c8f205238ad mesh=0xa9c603a1248de6b8 payload=0xea55f1f4fcc6a894 topology=0x5ea7bc78a1b678ae
[PipelineWholeViewHash] tiles=2370 density=0x7c6191c9804afca7 mesh=0xdc06f81d14362625 payload=0xc0eb6276f4fee860 topology=0xee7b1c0ab0dea11e
[PipelineLODHash] lod=0 tiles=512 density=0xcd6ab28cfa504aca mesh=0xf03dc80a669fca31 payload=0x397aded1c3d2d297 topology=0xb4a1dc84fabc1fe3
[PipelineLODHash] lod=1 tiles=448 density=0xce6e713828b2a20a mesh=0x1cf408c25d67d18c payload=0x8a2b5d20571c997e topology=0x51cd985fe8e7d100
[PipelineLODHash] lod=2 tiles=320 density=0xbaa13f29ce43342a mesh=0xdc366298929cc2b7 payload=0xc420c83aa736a68e topology=0x6f77046d18282765
[PipelineLODHash] lod=3 tiles=208 density=0x4cf203e1273ffe89 mesh=0xfa6a01f5a9ad7b62 payload=0x6d709b319f2595bf topology=0x4e3dffd3b59bdd16
[PipelineLODHash] lod=4 tiles=240 density=0x0be8798407724eaf mesh=0x131c303cbbe51434 payload=0x2a90ecf0a2411ca0 topology=0xe3e054a371152e08
[PipelineLODHash] lod=5 tiles=144 density=0xf20897ad272103a9 mesh=0x13d00b6253ae1f72 payload=0x0db3880693a962fa topology=0x462bd87a80f15e89
[PipelineLODHash] lod=6 tiles=96 density=0xc040fcac457d6313 mesh=0x1ed132eeb718fb62 payload=0xcd0214e35577c6e3 topology=0x2c56c69e6fdcfd64
[PipelineLODHash] lod=7 tiles=96 density=0xc2d419446355fdb8 mesh=0x4ea44452574fc84b payload=0xfd9779bedfc9ae0f topology=0x8948546e770c9b72
[PipelineLODHash] lod=8 tiles=306 density=0x62db31696f2da831 mesh=0xd373f9e9481532fe payload=0x540a60526e0e3e46 topology=0xc725cb901e6dc335
[PipelineExactQueryHash] probes=9 density=0xc949c4df2ce6f9d9 unclamped=0xc949c4df2ce6f9d9 detail=0x114c501e2d830d94 collision=0xe6011d3a248d4849 path=direct-Core-queries
+5 -8
View File
@@ -122,14 +122,11 @@ RECIPE_RE = re.compile(
def owner_hash_baseline(recipe: str, style: int) -> tuple[Path, str]:
if recipe == "surface-world" and style == 1:
return (BASELINES / "owner-hash/canonical/owner-hash-surface-world-style1-v212.baseline",
"L67-v212")
return (BASELINES / "owner-hash/canonical/owner-hash-surface-world-style1-v215.baseline",
"L99-v215-swept-tube")
if recipe == "floating-islands" and style == 2:
# L75 explicitly materializes every reference density sample. The older L74
# signature omitted samples skipped inside native uniform blocks; retain that
# fixture and pin the current full-grid signature separately.
return (BASELINES / "owner-hash/canonical/owner-hash-floating-islands-style2-l75-fullgrid.baseline",
"L75-full-grid")
return (BASELINES / "owner-hash/canonical/owner-hash-floating-islands-style2-v215.baseline",
"L99-v215-swept-tube")
raise VerificationFailure(f"no pinned owner hash baseline for {recipe} style {style}")
@@ -666,7 +663,7 @@ def run_hash_pairs(runner: VerificationRunner,
diagnostic = {"name": "owner-hash-surface-world-matches-L52-diagnostic",
"status": "DIAGNOSTIC",
"detail": f"matches={hashes[12] == l52_hashes} L52 rows={len(l52_hashes)}; "
"the v212 generated-route change has its own pinned baseline"}
"the v215 bounded swept-tube passage change has its own pinned baseline"}
runner.steps.append(diagnostic)
print(f"[{runner.tier}] {diagnostic['name']} {diagnostic['detail']}", flush=True)
baseline = BASELINES / "owner-hash/l46" / f"owner-hash-{recipe}-style{style}-w12.baseline"