wip(assembler): round L30 -- FI floor ring defect fixed; transition cost recovered

The Gradient interface left native solid density in the air-side gap and snapped the face to
voxel levels (concentric ring steps on the FI floor). The owner Gradient path now writes signed
distance both sides; match error RMS 0.36 -> 0.11 m. Band and fade-tile culling cut transition
cost: whole view vs L24 SW +2.7%, FI +0.2%. Evaluator 16. Deterministic across compilers and
workers; interface and passage audits pass. Found: on the real FloatingIslands recipe, the
rock-column connected-ends audit fails (all styles); earlier matrices ran SurfaceWorld twice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-21 11:40:27 +02:00
1 parent 63c7e563d0
commit 103d8b471e
15 files changed
+809 -8

No files matched your search

@@ -249,8 +249,8 @@ namespace VoxelForgeCore
// L'assembleur v1 encode coordonnees et scalaires en 1/256 de voxel, independamment des tuiles. // L'assembleur v1 encode coordonnees et scalaires en 1/256 de voxel, independamment des tuiles.
inline constexpr std::int64_t WorldAssemblySubvoxelsPerVoxel = 256; inline constexpr std::int64_t WorldAssemblySubvoxelsPerVoxel = 256;
inline constexpr std::uint32_t WorldAssemblySchemaVersion = 2; inline constexpr std::uint32_t WorldAssemblySchemaVersion = 2;
// Version 14 adds shared passage columns for long native open-air crossings. // Version 14 adds shared passage columns; version 16 smooths owner Gradient faces.
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 15; inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 16;
enum class FieldChannel : std::uint32_t enum class FieldChannel : std::uint32_t
{ {
+286 -6
View File
@@ -53,6 +53,10 @@ public:
:shape_(&shape),minimumSubvoxels_(minimumSubvoxels), :shape_(&shape),minimumSubvoxels_(minimumSubvoxels),
maximumSubvoxels_(maximumSubvoxels),previous_(ActivePassageTileQueries) maximumSubvoxels_(maximumSubvoxels),previous_(ActivePassageTileQueries)
{ {
mayContainResolvedBoundaryBand_=shape.ResolvedBoundaryBandsIntersectSubvoxelRange(
minimumSubvoxels_[2],maximumSubvoxels_[2]);
mayContainTransitionPassageFade_=shape.ResolvedTransitionPassageFadeIntersectsTileXY(
minimumSubvoxels_,maximumSubvoxels_);
featureIndices_=shape.BuildPassageFeatureTileList( featureIndices_=shape.BuildPassageFeatureTileList(
minimumSubvoxels_,maximumSubvoxels_); minimumSubvoxels_,maximumSubvoxels_);
passageRoutes_.assign(shape.ResolvedLayout?shape.ResolvedLayout->passages.size():0,0); passageRoutes_.assign(shape.ResolvedLayout?shape.ResolvedLayout->passages.size():0,0);
@@ -92,6 +96,13 @@ public:
{return passageIndex<passageMouthSupports_.size()&&passageMouthSupports_[passageIndex]!=0;} {return passageIndex<passageMouthSupports_.size()&&passageMouthSupports_[passageIndex]!=0;}
bool hasRouteFeatureOverlap() const{return routeFeatureOverlap_;} bool hasRouteFeatureOverlap() const{return routeFeatureOverlap_;}
bool hasColumnZOverlap() const{return columnZOverlap_;} bool hasColumnZOverlap() const{return columnZOverlap_;}
bool mayContainResolvedBoundaryBand() const{return mayContainResolvedBoundaryBand_;}
bool mayContainTransitionPassageFade() const{return mayContainTransitionPassageFade_;}
bool mayContainResolvedBoundaryBandAtZ(double zMicrometres) const
{
return mayContainResolvedBoundaryBand_
&&shape_->ResolvedBoundaryBandMayAffectAtZMicrometres(zMicrometres);
}
std::size_t featureCount() const{return featureIndices_.size();} std::size_t featureCount() const{return featureIndices_.size();}
private: private:
@@ -100,6 +111,8 @@ public:
std::vector<std::uint32_t> featureIndices_; std::vector<std::uint32_t> featureIndices_;
std::vector<std::uint8_t> passageRoutes_,passageColumns_,passageMouthSupports_; std::vector<std::uint8_t> passageRoutes_,passageColumns_,passageMouthSupports_;
bool routeFeatureOverlap_=false,columnZOverlap_=false; bool routeFeatureOverlap_=false,columnZOverlap_=false;
bool mayContainResolvedBoundaryBand_=true;
bool mayContainTransitionPassageFade_=true;
const FScopedPassageTileQueries* previous_; const FScopedPassageTileQueries* previous_;
}; };
@@ -174,6 +187,83 @@ public:
&&Upper.transitionHonouredInCore; &&Upper.transitionHonouredInCore;
} }
bool ResolvedBoundaryBandsIntersectSubvoxelRange(std::int64_t minimumZSubvoxels,
std::int64_t maximumZSubvoxels) const
{
if(!ResolvedLayout||!Owner||Owner->CoreVoxelSizeMicrons==0
||minimumZSubvoxels>maximumZSubvoxels)return false;
constexpr double Subvoxels=static_cast<double>(
VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
const double queryMinimumMicrometres=static_cast<double>(minimumZSubvoxels)
*Owner->CoreVoxelSizeMicrons/Subvoxels;
const double queryMaximumMicrometres=static_cast<double>(maximumZSubvoxels)
*Owner->CoreVoxelSizeMicrons/Subvoxels;
const double sampleMarginMicrometres=static_cast<double>(Owner->CoreVoxelSizeMicrons);
for(const auto& interval:ResolvedBoundaryZIntervalsMicrometres)
{
if(queryMaximumMicrometres+sampleMarginMicrometres>=interval[0]-1.0
&&queryMinimumMicrometres-sampleMarginMicrometres<interval[1]+1.0)
return true;
}
return false;
}
bool ResolvedBoundaryBandMayAffectAtZMicrometres(double zMicrometres) const
{
if(!std::isfinite(zMicrometres)||ResolvedBoundaryZIntervalsMicrometres.empty())return false;
const auto it=std::upper_bound(ResolvedBoundaryZIntervalsMicrometres.begin(),
ResolvedBoundaryZIntervalsMicrometres.end(),zMicrometres,
[](double z,const auto& interval){return z<interval[0];});
return it!=ResolvedBoundaryZIntervalsMicrometres.begin()
&&zMicrometres<std::prev(it)->at(1);
}
void BuildResolvedBoundaryZIntervals()
{
ResolvedBoundaryZIntervalsMicrometres.clear();
if(!ResolvedLayout)return;
for(const auto& stratum:ResolvedLayout->strates)
{
if(stratum.ceiling.presentInCurrentField
&&stratum.ceiling.bottomMicrometres<stratum.ceiling.topMicrometres)
ResolvedBoundaryZIntervalsMicrometres.push_back({
static_cast<double>(stratum.ceiling.bottomMicrometres),
static_cast<double>(stratum.ceiling.topMicrometres)});
if(stratum.bedrock.presentInCurrentField
&&stratum.bedrock.bottomMicrometres<stratum.bedrock.topMicrometres)
ResolvedBoundaryZIntervalsMicrometres.push_back({
static_cast<double>(stratum.bedrock.bottomMicrometres),
static_cast<double>(stratum.bedrock.topMicrometres)});
}
for(std::int32_t order=0;order+1<static_cast<std::int32_t>(ResolvedLayout->strates.size());
++order)
{
const auto* upper=FindResolvedLayoutStratumByOrder(order);
const auto* lower=FindResolvedLayoutStratumByOrder(order+1);
if(!upper||!lower||!IsResolvedLayoutTransitionActive(*upper))continue;
const auto displacement=ResolvedTransitionMaximumFaceDisplacementMicrometres(
*upper,*lower);
ResolvedBoundaryZIntervalsMicrometres.push_back({
static_cast<double>(std::min(upper->bedrock.bottomMicrometres,
lower->ceiling.bottomMicrometres)-displacement),
static_cast<double>(std::max(upper->bedrock.topMicrometres,
lower->ceiling.topMicrometres)+displacement)});
}
std::sort(ResolvedBoundaryZIntervalsMicrometres.begin(),
ResolvedBoundaryZIntervalsMicrometres.end(),
[](const auto& a,const auto& b){return a[0]<b[0];});
std::size_t write=0;
for(const auto& interval:ResolvedBoundaryZIntervalsMicrometres)
{
if(write>0&&interval[0]<=ResolvedBoundaryZIntervalsMicrometres[write-1][1])
ResolvedBoundaryZIntervalsMicrometres[write-1][1]=std::max(
ResolvedBoundaryZIntervalsMicrometres[write-1][1],interval[1]);
else
ResolvedBoundaryZIntervalsMicrometres[write++]=interval;
}
ResolvedBoundaryZIntervalsMicrometres.resize(write);
}
std::int64_t ResolvedTransitionMaximumFaceDisplacementMicrometres( std::int64_t ResolvedTransitionMaximumFaceDisplacementMicrometres(
const VoxelForgeCore::ResolvedLayoutStratumV2& Upper, const VoxelForgeCore::ResolvedLayoutStratumV2& Upper,
const VoxelForgeCore::ResolvedLayoutStratumV2& Lower) const const VoxelForgeCore::ResolvedLayoutStratumV2& Lower) const
@@ -189,11 +279,68 @@ public:
return (maximumCrossing+1)/2; return (maximumCrossing+1)/2;
} }
bool ResolvedTransitionPassageFadeIntersectsTileXY(
const std::array<std::int64_t,3>& minimumSubvoxels,
const std::array<std::int64_t,3>& maximumSubvoxels) const
{
if(!Owner||!ResolvedLayout||Owner->CoreVoxelSizeMicrons==0)return true;
for(std::size_t axis=0;axis<2;++axis)
if(minimumSubvoxels[axis]>maximumSubvoxels[axis])return true;
constexpr std::int64_t Subvoxels=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
constexpr std::int64_t SupportMarginMicrometres=1000000;
constexpr std::int64_t FadeWidthMicrometres=2000000;
const double voxelMicrometres=static_cast<double>(Owner->CoreVoxelSizeMicrons);
const double minimumX=static_cast<double>(minimumSubvoxels[0])*voxelMicrometres
/Subvoxels-voxelMicrometres;
const double maximumX=static_cast<double>(maximumSubvoxels[0])*voxelMicrometres
/Subvoxels+voxelMicrometres;
const double minimumY=static_cast<double>(minimumSubvoxels[1])*voxelMicrometres
/Subvoxels-voxelMicrometres;
const double maximumY=static_cast<double>(maximumSubvoxels[1])*voxelMicrometres
/Subvoxels+voxelMicrometres;
for(std::int32_t order=0;order+1<static_cast<std::int32_t>(ResolvedLayout->strates.size());
++order)
{
const auto* upper=FindResolvedLayoutStratumByOrder(order);
const auto* lower=FindResolvedLayoutStratumByOrder(order+1);
if(!upper||!lower||!IsResolvedLayoutTransitionActive(*upper))continue;
const std::int64_t seam=upper->bedrock.bottomMicrometres;
for(const auto& passage:ResolvedLayout->passages)
{
if(passage.upperStrateIndex!=upper->strateIndex
||passage.lowerStrateIndex!=lower->strateIndex)continue;
const std::int64_t mouthRadius=std::max<std::int64_t>(0,
passage.mouthRadiusSubvoxels*Owner->CoreVoxelSizeMicrons/Subvoxels);
const std::int64_t protectedRadius=mouthRadius+SupportMarginMicrometres;
const double fadeEnd=static_cast<double>(protectedRadius+FadeWidthMicrometres);
for(const auto* mouth:{&passage.upperMouthSubvoxels,&passage.lowerMouthSubvoxels})
{
const std::int64_t mouthZ=mouth->at(2)*Owner->CoreVoxelSizeMicrons/Subvoxels;
if(std::abs(mouthZ-seam)
>upper->transitionWidthMicrometres/2+mouthRadius)continue;
const double mouthX=static_cast<double>(mouth->at(0)
*Owner->CoreVoxelSizeMicrons/Subvoxels);
const double mouthY=static_cast<double>(mouth->at(1)
*Owner->CoreVoxelSizeMicrons/Subvoxels);
const double dx=mouthX<minimumX?minimumX-mouthX
:mouthX>maximumX?mouthX-maximumX:0.0;
const double dy=mouthY<minimumY?minimumY-mouthY
:mouthY>maximumY?mouthY-maximumY:0.0;
if(dx*dx+dy*dy<fadeEnd*fadeEnd)return true;
}
}
}
return false;
}
std::int64_t ResolvedTransitionPassageWeightQ16( std::int64_t ResolvedTransitionPassageWeightQ16(
const VoxelForgeCore::ResolvedLayoutStratumV2& Upper, const VoxelForgeCore::ResolvedLayoutStratumV2& Upper,
const VoxelForgeCore::ResolvedLayoutStratumV2& Lower, const VoxelForgeCore::ResolvedLayoutStratumV2& Lower,
std::int64_t XMicrometres,std::int64_t YMicrometres) const std::int64_t XMicrometres,std::int64_t YMicrometres) const
{ {
#if defined(VF_L30_ENABLE_COST_COUNTERS)
++ThreadClassificationStats().TransitionPassageFadeCalls;
#endif
constexpr std::int64_t Q16=1ll<<16; constexpr std::int64_t Q16=1ll<<16;
constexpr std::int64_t SupportMarginMicrometres=1000000; constexpr std::int64_t SupportMarginMicrometres=1000000;
constexpr std::int64_t FadeWidthMicrometres=2000000; constexpr std::int64_t FadeWidthMicrometres=2000000;
@@ -212,6 +359,9 @@ public:
+SupportMarginMicrometres; +SupportMarginMicrometres;
for(const auto* mouth:{&passage.upperMouthSubvoxels,&passage.lowerMouthSubvoxels}) for(const auto* mouth:{&passage.upperMouthSubvoxels,&passage.lowerMouthSubvoxels})
{ {
#if defined(VF_L30_ENABLE_COST_COUNTERS)
++ThreadClassificationStats().TransitionPassageMouthTests;
#endif
const std::int64_t mouthZMicrometres=mouth->at(2)*coreMicrometres const std::int64_t mouthZMicrometres=mouth->at(2)*coreMicrometres
/subvoxelsPerVoxel; /subvoxelsPerVoxel;
if(std::abs(mouthZMicrometres-seam) if(std::abs(mouthZMicrometres-seam)
@@ -236,6 +386,31 @@ public:
bool IsInsideResolvedStrateBoundaryBand(float Z) const bool IsInsideResolvedStrateBoundaryBand(float Z) const
{ {
#if defined(VF_L30_ENABLE_COST_COUNTERS)
++ThreadClassificationStats().BoundaryBandCalls;
#endif
const auto* tileQueries=ActivePassageTileQueries;
if(tileQueries&&tileQueries->matches(*this))
{
if(!tileQueries->mayContainResolvedBoundaryBand())
{
#if defined(VF_L30_ENABLE_COST_COUNTERS)
++ThreadClassificationStats().BoundaryTileCullRejects;
#endif
return false;
}
if(Owner&&!tileQueries->mayContainResolvedBoundaryBandAtZ(
static_cast<double>(Z)*Owner->CoreVoxelSizeMicrons))
{
#if defined(VF_L30_ENABLE_COST_COUNTERS)
++ThreadClassificationStats().BoundaryPointCullRejects;
#endif
return false;
}
// This predicate only selects the scalar path for boundary-capable Z
// samples. The merged intervals are conservative, so a positive is safe.
return true;
}
if(!ResolvedLayout||!Owner||!Owner->Manager||!std::isfinite(Z))return false; if(!ResolvedLayout||!Owner||!Owner->Manager||!std::isfinite(Z))return false;
const double worldZCentimetres=static_cast<double>(Z)*GetCoreVoxelSizeCm(); const double worldZCentimetres=static_cast<double>(Z)*GetCoreVoxelSizeCm();
const int32 strateIndex=Owner->Manager->GetStrateIndex(static_cast<float>(worldZCentimetres)); const int32 strateIndex=Owner->Manager->GetStrateIndex(static_cast<float>(worldZCentimetres));
@@ -248,6 +423,9 @@ public:
const auto mayTouchTransition=[&](const VoxelForgeCore::ResolvedLayoutStratumV2* Upper, const auto mayTouchTransition=[&](const VoxelForgeCore::ResolvedLayoutStratumV2* Upper,
const VoxelForgeCore::ResolvedLayoutStratumV2* Lower) const VoxelForgeCore::ResolvedLayoutStratumV2* Lower)
{ {
#if defined(VF_L30_ENABLE_COST_COUNTERS)
++ThreadClassificationStats().TransitionWindowChecks;
#endif
if(!Upper||!Lower||!IsResolvedLayoutTransitionActive(*Upper))return false; if(!Upper||!Lower||!IsResolvedLayoutTransitionActive(*Upper))return false;
const auto maxDisplacement=ResolvedTransitionMaximumFaceDisplacementMicrometres( const auto maxDisplacement=ResolvedTransitionMaximumFaceDisplacementMicrometres(
*Upper,*Lower); *Upper,*Lower);
@@ -255,7 +433,11 @@ public:
Lower->ceiling.bottomMicrometres)-maxDisplacement; Lower->ceiling.bottomMicrometres)-maxDisplacement;
const double maximum=std::max(Upper->bedrock.topMicrometres, const double maximum=std::max(Upper->bedrock.topMicrometres,
Lower->ceiling.topMicrometres)+maxDisplacement; Lower->ceiling.topMicrometres)+maxDisplacement;
return zMicrometres>=minimum&&zMicrometres<maximum; const bool touches=zMicrometres>=minimum&&zMicrometres<maximum;
#if defined(VF_L30_ENABLE_COST_COUNTERS)
if(touches)++ThreadClassificationStats().TransitionWindowHits;
#endif
return touches;
}; };
const auto* lower=FindResolvedLayoutStratumByOrder(stratum->order+1); const auto* lower=FindResolvedLayoutStratumByOrder(stratum->order+1);
const auto* upper=stratum->order>0?FindResolvedLayoutStratumByOrder(stratum->order-1):nullptr; const auto* upper=stratum->order>0?FindResolvedLayoutStratumByOrder(stratum->order-1):nullptr;
@@ -265,7 +447,29 @@ public:
float ApplyResolvedStrateBoundaryBand(float density,float X,float Y,float Z) const float ApplyResolvedStrateBoundaryBand(float density,float X,float Y,float Z) const
{ {
#if defined(VF_L30_ENABLE_COST_COUNTERS)
++ThreadClassificationStats().BoundaryBandCalls;
#endif
(void)X;(void)Y; (void)X;(void)Y;
const auto* tileQueries=ActivePassageTileQueries;
if(tileQueries&&tileQueries->matches(*this))
{
if(!tileQueries->mayContainResolvedBoundaryBand())
{
#if defined(VF_L30_ENABLE_COST_COUNTERS)
++ThreadClassificationStats().BoundaryTileCullRejects;
#endif
return density;
}
if(Owner&&!tileQueries->mayContainResolvedBoundaryBandAtZ(
static_cast<double>(Z)*Owner->CoreVoxelSizeMicrons))
{
#if defined(VF_L30_ENABLE_COST_COUNTERS)
++ThreadClassificationStats().BoundaryPointCullRejects;
#endif
return density;
}
}
if(!ResolvedLayout||!Owner||!Owner->Manager||!std::isfinite(Z))return density; if(!ResolvedLayout||!Owner||!Owner->Manager||!std::isfinite(Z))return density;
const double worldZCentimetres=static_cast<double>(Z)*GetCoreVoxelSizeCm(); const double worldZCentimetres=static_cast<double>(Z)*GetCoreVoxelSizeCm();
const int32 strateIndex=Owner->Manager->GetStrateIndex(static_cast<float>(worldZCentimetres)); const int32 strateIndex=Owner->Manager->GetStrateIndex(static_cast<float>(worldZCentimetres));
@@ -280,6 +484,9 @@ public:
const auto applyInterface=[&](const VoxelForgeCore::ResolvedLayoutStratumV2* upper, const auto applyInterface=[&](const VoxelForgeCore::ResolvedLayoutStratumV2* upper,
const VoxelForgeCore::ResolvedLayoutStratumV2* lower) const VoxelForgeCore::ResolvedLayoutStratumV2* lower)
{ {
#if defined(VF_L30_ENABLE_COST_COUNTERS)
++ThreadClassificationStats().TransitionWindowChecks;
#endif
if(!upper||!lower||!IsResolvedLayoutTransitionActive(*upper))return false; if(!upper||!lower||!IsResolvedLayoutTransitionActive(*upper))return false;
const auto maxDisplacement=ResolvedTransitionMaximumFaceDisplacementMicrometres( const auto maxDisplacement=ResolvedTransitionMaximumFaceDisplacementMicrometres(
*upper,*lower); *upper,*lower);
@@ -288,6 +495,10 @@ public:
const double maximum=std::max(upper->bedrock.topMicrometres, const double maximum=std::max(upper->bedrock.topMicrometres,
lower->ceiling.topMicrometres)+maxDisplacement; lower->ceiling.topMicrometres)+maxDisplacement;
if(zMicrometres<minimum||zMicrometres>=maximum)return false; if(zMicrometres<minimum||zMicrometres>=maximum)return false;
#if defined(VF_L30_ENABLE_COST_COUNTERS)
++ThreadClassificationStats().TransitionWindowHits;
++ThreadClassificationStats().TransitionEvaluatorCalls;
#endif
if(upper==stratum)bBedrockHandledByTransition=true; if(upper==stratum)bBedrockHandledByTransition=true;
if(lower==stratum)bCeilingHandledByTransition=true; if(lower==stratum)bCeilingHandledByTransition=true;
const std::int64_t xMicrometres=static_cast<std::int64_t>(std::llround( const std::int64_t xMicrometres=static_cast<std::int64_t>(std::llround(
@@ -298,9 +509,15 @@ public:
Owner->Seed,ResolvedLayout->strateLayoutChunkSizeMicrometres, Owner->Seed,ResolvedLayout->strateLayoutChunkSizeMicrometres,
xMicrometres,yMicrometres); xMicrometres,yMicrometres);
auto resolvedBands=bands; auto resolvedBands=bands;
const std::int64_t transitionWeight=ResolvedTransitionPassageWeightQ16(
*upper,*lower,xMicrometres,yMicrometres);
constexpr std::int64_t TransitionQ16=1ll<<16; constexpr std::int64_t TransitionQ16=1ll<<16;
const auto* tileQueries=ActivePassageTileQueries;
const bool skipPassageFade=tileQueries&&tileQueries->matches(*this)
&&!tileQueries->mayContainTransitionPassageFade();
const std::int64_t transitionWeight=skipPassageFade?TransitionQ16
:ResolvedTransitionPassageWeightQ16(*upper,*lower,xMicrometres,yMicrometres);
#if defined(VF_L30_ENABLE_COST_COUNTERS)
if(skipPassageFade)++ThreadClassificationStats().TransitionPassageTileSkips;
#endif
if(transitionWeight<TransitionQ16) if(transitionWeight<TransitionQ16)
{ {
const std::int64_t seam=upper->bedrock.bottomMicrometres; const std::int64_t seam=upper->bedrock.bottomMicrometres;
@@ -325,9 +542,36 @@ public:
&&zMicrometres<resolvedBands.upperBedrockTopMicrometres; &&zMicrometres<resolvedBands.upperBedrockTopMicrometres;
const bool lowerCeiling=zMicrometres>=resolvedBands.lowerCeilingBottomMicrometres const bool lowerCeiling=zMicrometres>=resolvedBands.lowerCeilingBottomMicrometres
&&zMicrometres<resolvedBands.lowerCeilingTopMicrometres; &&zMicrometres<resolvedBands.lowerCeilingTopMicrometres;
if(!upperBedrock&&!lowerCeiling)return false; if(!upperBedrock&&!lowerCeiling)
{
// FloatingIslands can retain native solid density above a negatively
// displaced Gradient face, creating a second surface instead of the
// resolved transition top. Keep that air-side gap open in the owner field.
const std::int64_t seam=upper->bedrock.bottomMicrometres;
if(upper->generatorKind=="FloatingIslands"
&&upper->transitionMode=="Gradient"
&&resolvedBands.upperBedrockTopMicrometres<seam
&&zMicrometres>=resolvedBands.upperBedrockTopMicrometres
&&zMicrometres<seam)
{
const double distanceVoxels=static_cast<double>(zMicrometres
-resolvedBands.upperBedrockTopMicrometres)
/std::max<std::uint32_t>(1,Owner->CoreVoxelSizeMicrons);
density=std::max(0.05f,static_cast<float>(distanceVoxels));
return true;
}
return false;
}
const auto& band=upperBedrock?upper->bedrock:lower->ceiling; const auto& band=upperBedrock?upper->bedrock:lower->ceiling;
density=-std::max(0.05f,static_cast<float>(band.solidDensityQ16)/65536.0f); if(upperBedrock&&upper->generatorKind=="FloatingIslands"
&&upper->transitionMode=="Gradient")
{
const double distanceVoxels=static_cast<double>(
resolvedBands.upperBedrockTopMicrometres-zMicrometres)
/std::max<std::uint32_t>(1,Owner->CoreVoxelSizeMicrons);
density=-std::max(0.05f,static_cast<float>(distanceVoxels));
}
else density=-std::max(0.05f,static_cast<float>(band.solidDensityQ16)/65536.0f);
return true; return true;
}; };
@@ -435,23 +679,49 @@ public:
{ {
if(!crossing.needsColumn||crossing.passageIndex!=passageIndex if(!crossing.needsColumn||crossing.passageIndex!=passageIndex
||qz>crossing.topZSubvoxels||qz<crossing.bottomZSubvoxels)continue; ||qz>crossing.topZSubvoxels||qz<crossing.bottomZSubvoxels)continue;
#if defined(VF_L30_ENABLE_COST_COUNTERS)
++ThreadClassificationStats().ColumnZOverlapCalls;
#endif
double columnDistance; double columnDistance;
const bool tileProvesColumnExterior=passageTileScopeMatches const bool tileProvesColumnExterior=passageTileScopeMatches
&&!ActivePassageTileQueries->hasFeature(passageIndex,true); &&!ActivePassageTileQueries->hasFeature(passageIndex,true);
if(tileProvesColumnExterior) if(tileProvesColumnExterior)
{ {
if(composed<0.0f)continue; #if defined(VF_L30_ENABLE_COST_COUNTERS)
++ThreadClassificationStats().ColumnGuardCalls;
#endif
if(composed<0.0f)
{
#if defined(VF_L30_ENABLE_COST_COUNTERS)
++ThreadClassificationStats().ColumnGuardNonpositiveSkips;
#endif
continue;
}
const double dx=qx-crossing.centerXSubvoxels; const double dx=qx-crossing.centerXSubvoxels;
const double dy=qy-crossing.centerYSubvoxels; const double dy=qy-crossing.centerYSubvoxels;
const double horizontalSquared=dx*dx+dy*dy; const double horizontalSquared=dx*dx+dy*dy;
const double maximumRadius=static_cast<double>(crossing.maximumRadiusSubvoxels); const double maximumRadius=static_cast<double>(crossing.maximumRadiusSubvoxels);
const double exteriorLimit=maximumRadius const double exteriorLimit=maximumRadius
+static_cast<double>(composed)*Q+1.0e-4; +static_cast<double>(composed)*Q+1.0e-4;
#if defined(VF_L30_ENABLE_COST_COUNTERS)
if(horizontalSquared>exteriorLimit*exteriorLimit)
{
++ThreadClassificationStats().ColumnGuardRadialRejects;
continue;
}
++ThreadClassificationStats().ColumnSignedDistanceCalls;
#else
if(horizontalSquared>exteriorLimit*exteriorLimit)continue; if(horizontalSquared>exteriorLimit*exteriorLimit)continue;
#endif
columnDistance=PassageColumnSignedDistance(crossing,X,Y,Z); columnDistance=PassageColumnSignedDistance(crossing,X,Y,Z);
} }
else else
{
#if defined(VF_L30_ENABLE_COST_COUNTERS)
++ThreadClassificationStats().ColumnSignedDistanceCalls;
#endif
columnDistance=PassageColumnSignedDistance(crossing,X,Y,Z); columnDistance=PassageColumnSignedDistance(crossing,X,Y,Z);
}
composed=std::min(composed,static_cast<float>(columnDistance/Q)); composed=std::min(composed,static_cast<float>(columnDistance/Q));
} }
const bool tilePassageFree=passageTileScopeMatches const bool tilePassageFree=passageTileScopeMatches
@@ -867,6 +1137,14 @@ public:
uint64 SolidBoxes=0,AirBoxes=0,MixedBoxes=0,UnknownBoxes=0; uint64 SolidBoxes=0,AirBoxes=0,MixedBoxes=0,UnknownBoxes=0;
uint64 NativeFloatFieldCalls=0,NativeLatticeFieldCalls=0,NativeUnclampedFieldCalls=0; uint64 NativeFloatFieldCalls=0,NativeLatticeFieldCalls=0,NativeUnclampedFieldCalls=0;
double Milliseconds=0.0; double Milliseconds=0.0;
#if defined(VF_L30_ENABLE_COST_COUNTERS)
uint64 BoundaryBandCalls=0,TransitionWindowChecks=0,TransitionWindowHits=0;
uint64 TransitionEvaluatorCalls=0,TransitionPassageFadeCalls=0,TransitionPassageMouthTests=0;
uint64 BoundaryTileCullRejects=0,BoundaryPointCullRejects=0;
uint64 ColumnZOverlapCalls=0,ColumnGuardCalls=0,ColumnGuardNonpositiveSkips=0;
uint64 ColumnGuardRadialRejects=0,ColumnSignedDistanceCalls=0;
uint64 TransitionPassageTileSkips=0;
#endif
}; };
static void ResetThreadClassificationStats() { ThreadClassificationStats()={}; } static void ResetThreadClassificationStats() { ThreadClassificationStats()={}; }
static FClassificationThreadStats GetThreadClassificationStats() static FClassificationThreadStats GetThreadClassificationStats()
@@ -2793,6 +3071,7 @@ private:
void BuildPassageVoidCrossingPlan() void BuildPassageVoidCrossingPlan()
{ {
BuildResolvedBoundaryZIntervals();
PassageVoidCrossings.clear(); PassageVoidCrossings.clear();
if(!Owner||!ResolvedLayout||ResolvedLayout->passages.empty())return; if(!Owner||!ResolvedLayout||ResolvedLayout->passages.empty())return;
if(!Owner->PassageVoidCrossingCache) if(!Owner->PassageVoidCrossingCache)
@@ -7231,6 +7510,7 @@ private:
FIntVector NativeOnlyMaximum=FIntVector::ZeroValue; FIntVector NativeOnlyMaximum=FIntVector::ZeroValue;
mutable std::atomic<uint32> NativeOnlyViolations{0}; mutable std::atomic<uint32> NativeOnlyViolations{0};
bool FarBuilt=false; bool FarBuilt=false;
std::vector<std::array<double,2>> ResolvedBoundaryZIntervalsMicrometres;
std::vector<FInterval> LayerRanges; std::vector<FInterval> LayerRanges;
std::vector<FLayerField> LayerFields; std::vector<FLayerField> LayerFields;
std::vector<FPrimitive> Primitives; std::vector<FPrimitive> Primitives;
+34
View File
@@ -40,6 +40,34 @@ namespace VoxelForgeCore
namespace namespace
{ {
#if defined(VF_L30_ENABLE_COST_COUNTERS)
void PrintL30CostCounters(const FStandaloneShapeWorld::FClassificationThreadStats& stats)
{
if(stats.BoundaryBandCalls==0&&stats.ColumnZOverlapCalls==0)return;
std::printf("[L30CostTile] boundary_band_calls=%llu transition_window_checks=%llu "
"transition_window_hits=%llu transition_evaluations=%llu passage_fade_calls=%llu "
"passage_fade_mouth_tests=%llu column_z_overlap_calls=%llu column_guard_calls=%llu "
"column_guard_nonpositive_skips=%llu column_guard_radial_rejects=%llu "
"boundary_tile_cull_rejects=%llu boundary_point_cull_rejects=%llu "
"transition_passage_tile_skips=%llu "
"column_sdf_calls=%llu\n",
static_cast<unsigned long long>(stats.BoundaryBandCalls),
static_cast<unsigned long long>(stats.TransitionWindowChecks),
static_cast<unsigned long long>(stats.TransitionWindowHits),
static_cast<unsigned long long>(stats.TransitionEvaluatorCalls),
static_cast<unsigned long long>(stats.TransitionPassageFadeCalls),
static_cast<unsigned long long>(stats.TransitionPassageMouthTests),
static_cast<unsigned long long>(stats.ColumnZOverlapCalls),
static_cast<unsigned long long>(stats.ColumnGuardCalls),
static_cast<unsigned long long>(stats.ColumnGuardNonpositiveSkips),
static_cast<unsigned long long>(stats.ColumnGuardRadialRejects),
static_cast<unsigned long long>(stats.BoundaryTileCullRejects),
static_cast<unsigned long long>(stats.BoundaryPointCullRejects),
static_cast<unsigned long long>(stats.TransitionPassageTileSkips),
static_cast<unsigned long long>(stats.ColumnSignedDistanceCalls));
}
#endif
FIntVector ToNative(Vec3i value) { return {value.x,value.y,value.z}; } FIntVector ToNative(Vec3i value) { return {value.x,value.y,value.z}; }
FVector ToNative(Vec3d value) { return {value.x,value.y,value.z}; } FVector ToNative(Vec3d value) { return {value.x,value.y,value.z}; }
Vec3d FromNative(const FVector& value) { return {value.X,value.Y,value.Z}; } Vec3d FromNative(const FVector& value) { return {value.X,value.Y,value.Z}; }
@@ -1995,6 +2023,9 @@ namespace VoxelForgeCore
out.diagnostics.classificationExclusiveMilliseconds=std::max(0.0, out.diagnostics.classificationExclusiveMilliseconds=std::max(0.0,
Counts.Milliseconds-classificationColumnComputeMilliseconds); Counts.Milliseconds-classificationColumnComputeMilliseconds);
out.diagnostics.columnComputeMilliseconds=Columns.columnComputeMilliseconds; out.diagnostics.columnComputeMilliseconds=Columns.columnComputeMilliseconds;
#if defined(VF_L30_ENABLE_COST_COUNTERS)
PrintL30CostCounters(Counts);
#endif
if(options.captureColumnCoordinates) if(options.captureColumnCoordinates)
{ {
out.diagnostics.columnEvaluationWorkerHash=static_cast<std::uint64_t>( out.diagnostics.columnEvaluationWorkerHash=static_cast<std::uint64_t>(
@@ -2356,6 +2387,9 @@ namespace VoxelForgeCore
if(bHasWorldAssembly) if(bHasWorldAssembly)
{ {
const auto Counts=FStandaloneShapeWorld::GetThreadClassificationStats(); const auto Counts=FStandaloneShapeWorld::GetThreadClassificationStats();
#if defined(VF_L30_ENABLE_COST_COUNTERS)
PrintL30CostCounters(Counts);
#endif
const auto& Columns=VoxelForgeCore::Heightfield2D::threadColumnCacheStats(); const auto& Columns=VoxelForgeCore::Heightfield2D::threadColumnCacheStats();
out.diagnostics.fieldCalls=Columns.pointCalls; out.diagnostics.fieldCalls=Columns.pointCalls;
out.diagnostics.floatFieldCalls=Counts.NativeFloatFieldCalls; out.diagnostics.floatFieldCalls=Counts.NativeFloatFieldCalls;
@@ -0,0 +1,239 @@
#!/usr/bin/env python3
"""Rasterize upward mesh triangles into a top-down field height map."""
import argparse
import array
import json
import math
import struct
import sys
Q16 = 1 << 16
MASK64 = (1 << 64) - 1
def trunc_div(numerator, denominator):
return numerator // denominator if numerator >= 0 else -((-numerator) // denominator)
def transition_hash(seed, interface, x, y, z, salt):
value = 14695981039346656037
for word in (seed & 0xffffffff, interface & 0xffffffff,
x & MASK64, y & MASK64, z & MASK64, salt):
for byte in range(8):
value ^= (word >> (byte * 8)) & 0xff
value = (value * 1099511628211) & MASK64
value ^= value >> 30
value = (value * 0xbf58476d1ce4e5b9) & MASK64
value ^= value >> 27
value = (value * 0x94d049bb133111eb) & MASK64
value ^= value >> 31
return ((value >> 32) >> 15) - Q16
def transition_fade(value):
value = max(0, min(Q16, value))
square = value * value // Q16
return square * (3 * Q16 - 2 * value) // Q16
def gradient_height_um(layout, x_um, y_um):
upper = next(item for item in layout["strates"] if item["order"] == 0)
lower = next(item for item in layout["strates"] if item["order"] == 1)
width = upper["transition_width_um"]
cell = max(1_000_000, layout["strate_layout_chunk_size_um"] * 4)
ix = x_um // cell
iy = y_um // cell
tx = transition_fade(((x_um - ix * cell) * Q16) // cell)
ty = transition_fade(((y_um - iy * cell) * Q16) // cell)
def at(dx, dy):
noise = transition_hash(0, upper["strate_index"], ix + dx, iy + dy, 0,
0x47524144)
return noise
def lerp(a, b, t):
return a + trunc_div((b - a) * t, Q16)
low = lerp(lerp(at(0, 0), at(1, 0), tx),
lerp(at(0, 1), at(1, 1), tx), ty)
displacement = trunc_div(low * (width // 8), Q16)
seam = upper["bedrock"]["bottom_um"]
thickness = upper["bedrock"]["top_um"] - seam
return seam + displacement + thickness
def read_scene(path):
triangles = []
with open(path, "rb") as stream:
if stream.read(8) != b"VFMSH001":
raise SystemExit(f"{path}: invalid scene header")
version, voxel_um, count = struct.unpack("<3I", stream.read(12))
stream.read(40)
if version != 1 or count > 100000:
raise SystemExit("unsupported scene")
for _ in range(count):
key = struct.unpack("<4i", stream.read(16))
surface_vertices, skirt_count, vertex_count, index_count, mask_count = struct.unpack(
"<5I", stream.read(20))
positions = array.array("f")
positions.frombytes(stream.read(vertex_count * 12))
normals = array.array("f")
normals.frombytes(stream.read(vertex_count * 12))
indices = array.array("I")
indices.frombytes(stream.read(index_count * 4))
masks = stream.read(mask_count)
if sys.byteorder != "little":
positions.byteswap()
normals.byteswap()
indices.byteswap()
for index in range(0, index_count, 3):
if masks and masks[index // 3]:
continue
a, b, c = indices[index:index + 3]
if max(a, b, c) >= surface_vertices:
continue
nz = (normals[a * 3 + 2] + normals[b * 3 + 2]
+ normals[c * 3 + 2]) / 3.0
if nz < 0.35:
continue
vertices = tuple((positions[v * 3], positions[v * 3 + 1],
positions[v * 3 + 2]) for v in (a, b, c))
triangles.append(vertices)
return voxel_um, triangles
def rasterize(triangles, bounds, width, height):
x0, x1, y0, y1 = bounds
values = [None] * (width * height)
sx, sy = width / (x1 - x0), height / (y1 - y0)
for tri in triangles:
(ax, ay, az), (bx, by, bz), (cx, cy, cz) = tri
den = (by - cy) * (ax - cx) + (cx - bx) * (ay - cy)
if abs(den) < 1.0e-12:
continue
left = max(0, int(math.floor((min(ax, bx, cx) - x0) * sx)))
right = min(width - 1, int(math.ceil((max(ax, bx, cx) - x0) * sx)))
top = max(0, int(math.floor((y1 - max(ay, by, cy)) * sy)))
bottom = min(height - 1, int(math.ceil((y1 - min(ay, by, cy)) * sy)))
for py in range(top, bottom + 1):
y = y1 - (py + 0.5) / sy
row = py * width
for px in range(left, right + 1):
x = x0 + (px + 0.5) / sx
u = ((by - cy) * (x - cx) + (cx - bx) * (y - cy)) / den
v = ((cy - ay) * (x - cx) + (ax - cx) * (y - cy)) / den
w = 1.0 - u - v
if min(u, v, w) < -1.0e-6:
continue
z = u * az + v * bz + w * cz
idx = row + px
if values[idx] is None or z > values[idx]:
values[idx] = z
return values
def main():
parser = argparse.ArgumentParser()
parser.add_argument("scene")
parser.add_argument("output_ppm")
parser.add_argument("--bounds", nargs=4, type=float, required=True,
metavar=("XMIN", "XMAX", "YMIN", "YMAX"))
parser.add_argument("--size", nargs=2, type=int, default=(640, 640))
parser.add_argument("--range", nargs=2, type=float, default=(-122.0, -118.0),
metavar=("ZMIN", "ZMAX"))
parser.add_argument("--ring-center", nargs=2, type=float,
metavar=("X", "Y"))
parser.add_argument("--layout")
args = parser.parse_args()
voxel_um, triangles = read_scene(args.scene)
width, height = args.size
sx, sy = width / (args.bounds[1] - args.bounds[0]), height / (args.bounds[3] - args.bounds[2])
values = rasterize(triangles, args.bounds, width, height)
finite = sorted(value for value in values if value is not None)
if not finite:
raise SystemExit("no upward-facing field samples in bounds")
low, high = args.range
if high <= low:
raise SystemExit("height range must increase")
stops = ((0.00, (31, 52, 92)), (0.24, (41, 119, 153)),
(0.50, (85, 177, 143)), (0.73, (226, 207, 105)),
(1.00, (181, 71, 49)))
with open(args.output_ppm, "wb") as output:
output.write(f"P6\n{width} {height}\n255\n".encode())
for value in values:
if value is None:
output.write(bytes((18, 22, 30)))
continue
t = max(0.0, min(1.0, (value - low) / (high - low)))
for index in range(len(stops) - 1):
if t <= stops[index + 1][0]:
ta, ca = stops[index]
tb, cb = stops[index + 1]
blend = (t - ta) / (tb - ta)
rgb = tuple(round(ca[c] + blend * (cb[c] - ca[c]))
for c in range(3))
break
else:
rgb = stops[-1][1]
output.write(bytes(rgb))
def quantile(p):
return finite[round((len(finite) - 1) * p)]
print(f"[RingFieldRaster] voxel_um={voxel_um} triangles={len(triangles)} "
f"pixels={len(finite)}/{len(values)} height_min_m={finite[0]:.6f} "
f"height_p05_m={quantile(.05):.6f} height_median_m={quantile(.5):.6f} "
f"height_p95_m={quantile(.95):.6f} height_max_m={finite[-1]:.6f} "
f"height_range_m={finite[-1]-finite[0]:.6f} "
f"map={args.output_ppm}")
if args.ring_center:
cx, cy = args.ring_center
def at_world(x, y):
fx = (x - args.bounds[0]) * sx - 0.5
fy = (args.bounds[3] - y) * sy - 0.5
ix, iy = math.floor(fx), math.floor(fy)
tx, ty = fx - ix, fy - iy
samples = []
for px, py in ((ix, iy), (ix + 1, iy), (ix, iy + 1), (ix + 1, iy + 1)):
if not (0 <= px < width and 0 <= py < height):
return None
value = values[py * width + px]
if value is None:
return None
samples.append(value)
return ((samples[0] * (1 - tx) + samples[1] * tx) * (1 - ty)
+ (samples[2] * (1 - tx) + samples[3] * tx) * ty)
print(f"[RingRadialProbe] center_m=({cx:.3f},{cy:.3f}) "
"radius_m mean_m angular_sd_m min_m max_m samples")
for radius in range(1, 17):
row = [at_world(cx + radius * math.cos(a * math.tau / 32),
cy + radius * math.sin(a * math.tau / 32))
for a in range(32)]
row = [value for value in row if value is not None]
if row:
mean = sum(row) / len(row)
sd = math.sqrt(sum((value - mean) ** 2 for value in row) / len(row))
print(f" {radius:>2} {mean:.6f} {sd:.6f} {min(row):.6f} "
f"{max(row):.6f} {len(row)}")
if args.layout:
layout = json.load(open(args.layout, encoding="utf-8"))
if next(item for item in layout["strates"] if item["order"] == 0)["transition_mode"] != "Gradient":
raise SystemExit("--layout must describe a Gradient owner interface")
errors = []
for index, actual in enumerate(values):
if actual is None:
continue
px, py = index % width, index // width
x = args.bounds[0] + (px + 0.5) / sx
y = args.bounds[3] - (py + 0.5) / sy
expected = gradient_height_um(layout, round(x * 1_000_000),
round(y * 1_000_000)) / 1_000_000.0
errors.append(actual - expected)
absolute = sorted(abs(error) for error in errors)
rms = math.sqrt(sum(error * error for error in errors) / len(errors))
print(f"[RingGradientMatch] samples={len(errors)} rms_m={rms:.6f} "
f"p95_abs_m={absolute[round((len(absolute)-1)*.95)]:.6f} "
f"max_abs_m={absolute[-1]:.6f} "
"expected=owner-resolved-2d-hash-value-noise")
if __name__ == "__main__":
main()
@@ -0,0 +1,28 @@
#include "CoreNamespacePreamble.h"
#include "ResolvedLayoutInternal.h"
int main()
{
VoxelForgeCore::ResolvedLayoutStratumV2 upper, lower;
upper.strateIndex=0;upper.order=0;upper.transitionMode="Gradient";
upper.transitionWidthMicrometres=16000000;
upper.bedrock.bottomMicrometres=-120000000;
upper.bedrock.topMicrometres=-119500000;
upper.bedrock.presentInCurrentField=true;
lower.strateIndex=1;lower.order=1;
lower.ceiling.bottomMicrometres=-126250000;
lower.ceiling.topMicrometres=-120000000;
lower.ceiling.presentInCurrentField=true;
for(const auto& point:std::array<std::array<std::int64_t,2>,5>{
std::array<std::int64_t,2>{64000000,-32000000},
{65000000,-32000000},{64000000,-31000000},{70000000,-32000000},
{80000000,-35000000}})
{
const auto bands=VoxelForgeCore::EvaluateResolvedLayoutTransitionBandsV1(
upper,lower,0,8000000,point[0],point[1]);
std::printf("x_m=%.3f y_m=%.3f top_m=%.6f bottom_m=%.6f\n",
point[0]/1000000.0,point[1]/1000000.0,
bands.upperBedrockTopMicrometres/1000000.0,
bands.upperBedrockBottomMicrometres/1000000.0);
}
}
@@ -0,0 +1,121 @@
# VoxelForge L30
Work is on `assembler/world`. The speed and look patches are saved separately under
`Saved/Linux/l30/`; evaluator version is now 16 for the field change. No Unreal files were edited.
Nothing was committed, pushed, or stashed.
## Ring measurement and fix
I rasterized upward-facing triangles from the L29 mesh (skirt triangles excluded) over the same
409,600 pixels as the after field. At `(64, -32)`, the L29 field stayed almost flat at `-120.063 m` from
radii 1–11 m, then broke at 12 m. The expected Gradient at radius 1 m is `-121.360 m`. This is a
field defect, not shading: the probe is about 67 m from the passage mouth, and the raster has no
skirts or readable-shading tint. The passage column flare is at that distant mouth as well.
The upper FloatingIslands generator could leave native solid density in the air-side gap above a
negatively displaced Gradient face. The constant band densities also snapped the face to Core
voxel levels. The owner Gradient path now clears that gap with positive signed distance and writes
negative signed distance below the face, allowing the mesh to interpolate its height. The after
profile follows the resolved hash surface at every sampled radius. Match error fell from RMS
`0.361765 m`, p95 `1.016178 m`, max `1.304169 m` to RMS `0.110357 m`, p95 `0.218531 m`, max
`0.248818 m` (one Core voxel is `0.25 m`).
Before/after captures use the same color ramp and bounds:
- [L29 height map](captures/fi-floor-gradient-before-heightmap.jpg), [fixed height map](captures/fi-floor-gradient-after-heightmap.jpg)
- [L29 readable render](captures/fi-floor-gradient-before.jpg), [fixed readable render](captures/fi-floor-gradient-after.jpg)
## Transition captures
The maps cover the full 640×640 surface and are all below 300 KB. Gradient is the owner world
without a mode override. Hard and Interleaved use fixture overrides; the resolved layouts record
those modes.
- [Hard](captures/transition-hard-heightmap.jpg) — flat interface.
- [Gradient](captures/transition-gradient-heightmap.jpg) — owner field.
- [Interleaved](captures/transition-interleaved-heightmap.jpg) — fixture override.
## Cost
Five alternating W12 pairs, hashing off and `cache_hits=0`. First table is the separable speed-only
build, whose hashes remain L29-identical. Second table is the final version-16 build.
| Recipe | L24 | L29 | L30 speed-only | vs L24 | L30 final | vs L24 |
|---|---:|---:|---:|---:|---:|---:|
| SurfaceWorld | 1.566 s | 1.653 s | 1.606 s | +2.55% | 1.530 s | +2.68% |
| FloatingIslands | 16.625 s | 16.779 s | 16.470 s | −0.93% | 16.543 s | +0.21% |
Counter-enabled runs on the same plain W12 workload locate the saved work:
| Recipe | Boundary calls | Window checks → evaluations | Fade calls | Boundary tile / point culls | Column Z tests → guards → radial rejects | SDF calls |
|---|---:|---:|---:|---:|---:|---:|
| SurfaceWorld | 19.95 M | 3.54 M → 739 k | 8.2 k | 8.99 M / 8.96 M | 1.87 M → 1.85 M → 1.58 M | 18.4 k |
| FloatingIslands | 66.33 M | 8.00 M → 2.68 M | 8.6 k | 38.25 M / 23.63 M | 8.17 M → 8.14 M → 6.82 M | 83.4 k |
The fade-tile cull skips 731 k SurfaceWorld and 2.67 M FloatingIslands fade evaluations; the
column guard exits on nonpositive field values 272 k and 1.27 M times. Timing comes from
uninstrumented binaries; the counts above use counter logging.
## Hashes and audits
The speed-only v15 build matches all 12 L29 hash records for each recipe/compiler/worker matrix
(W1/W8/W12), and all 12 records for each 24 m crossing matrix. That is 24 exact comparisons;
all pass. The final v16 build is deterministic across both compilers and all worker counts, in the
normal and 24 m crossing runs. Representative W12 hashes (`density / mesh / payload / topology`):
| Recipe | Full detail | Whole view |
|---|---|---|
| SurfaceWorld | `ace56c8f205238ad / a9c603a1248de6b8 / ea55f1f4fcc6a894 / 5ea7bc78a1b678ae` | `0e8c5af01df96b14 / 70099e89b77811ed / dbce0fdcf6764126 / ddaf81a1228f9b4b` |
| FloatingIslands | `ed550e0c8b182695 / 0e410a8e80a3e7f7 / 0751926f0c67d8a4 / f01208bc2f7841ec` | `4ee19ea6b6406ca6 / be648f94c899b47e / 310b525f31604f98 / 10366eb6dfce2384` |
Exact-query hashes also match across v16 runs: SurfaceWorld
`c949c4df2ce6f9d9 / 114c501e2d830d94 / e6011d3a248d4849`; FloatingIslands
`7b7c50ac24ccadcc / efcba20c0c70c415 / e604823a249029bf`.
- Interface audit: all 12 compiler/recipe/mode combinations pass; 4,221 columns each, zero holes
and air samples, both negative controls reject.
- The 16 L29 fixture-matrix replays pass passage safety/open-air, validator controls (10/10), L13
route parity, L9 proof checks (zero violations), and walkability. `FloorProfileBlend` still
reports 2.544 vox in style 1 and 3.645 vox in style 3, unchanged from L29.
- Audit scope caveat: the L29 files named `floating-islands` were run with
`--recipe=surface-world`. With the actual `--recipe=floating-islands` option, the rock-column
audit fails on all four styles on both L29 and L30: connected ends are 1/2, 3/4, 1/2, 3/4.
The open-air audit passes. This is inherited, but means the L29 report's “two recipes” matrix did
not exercise the FloatingIslands CLI recipe.
## Failed and retried runs
- Broad air-gap experiments first lost a SurfaceWorld style-2 support sample. Route/radius masks
and support restoration then caused open-air violations. Widening the fade restored that case,
but measurement placed the ring far outside the fade, so those variants were discarded. The
final owner-only gate and signed-distance face pass the targeted support audit.
- Early speed variants missed the target: tile-only `+4.04%`, Z-point `+3.11%`, scalar path
`+4.33%` vs L24. The final band and fade-tile culls meet the target.
- The first counter-instrumentation compile failed; the counter hook was corrected and rebuilt
with Clang and GCC. One duplicated timing sequence was discarded; only the clean five-pair runs
are reported.
- The first 24 m crossing comparison supplied an extra camera override and mismatched. Rerunning
with the L29 camera setup and only `--camera-step=24,0,0` matches all hashes.
- An early Hard export hit `std::bad_alloc`; retry passed. The first EGL render used an unwritable
cache; retry with `XDG_CACHE_HOME` under `Saved/Linux/l30` passed. The first map conversion also
stopped before the Hard/Interleaved maps; both were rerun individually and have full raster
coverage.
- The first passage-matrix summary script assumed a different marker order and reported failure;
checking each audit marker by name confirms the core matrix passes. A `check=True` true-recipe
control runner stopped on the expected L29 exit code 10; rerunning while recording return codes
confirmed the same four inherited rock-column failures on L29 and L30.
No build or run exceeded 30 minutes or remained stuck for 15 minutes. Builds and runs used CPUs
0–11, at most 12 workers, normal priority, and the 6 GiB address-space cap. No Unreal build was
attempted.
## Orchestrator review (Claude)
- Merged: the ring was a real field defect (air-side gap left solid, face snapped to voxel levels), now fixed
(RMS 0.36 → 0.11 m). Cost back to SW +2.7% / FI +0.2% vs L24. The speed part is bit-identical; final v16 is
deterministic.
- Still visible: `fi-floor-gradient-after.jpg` shows faint concentric lines. Unknown whether that is residual
geometry (≤0.25 m steps) or the readable shader's height-tint banding; L31 measures it.
- **Important finding (thanks to Luna's caveat):** L29's "both recipes" passage matrix ran SurfaceWorld twice. On
the real `--recipe=floating-islands`, the rock-column connected-ends audit fails in all 4 styles (1/2 or 3/4).
Since L27, FI column results were never really tested. L31 priority.
@@ -0,0 +1,55 @@
#!/usr/bin/env python3
"""Interleave five W12 hash-off whole-view cost triplets for L24/L29/L30."""
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l30"
OUT.mkdir(parents=True, exist_ok=True)
BINARIES = {
"l24": ROOT / "Saved/Linux/l24-source/bin/linux/VoxelForgeCoreHeadless-linux24-clang19",
"l29": ROOT / "Saved/Linux/bin/VoxelForgeCoreHeadless-linux29-clang",
"l30": ROOT / "Saved/Linux/l30/bin/VoxelForgeCoreHeadless-linux30-speed-fade",
}
ORDERS = (
("l24", "l29", "l30"),
("l29", "l30", "l24"),
("l30", "l24", "l29"),
("l24", "l30", "l29"),
("l29", "l24", "l30"),
)
WHOLE_READY = re.compile(r"\bwhole_view_ready=([0-9.]+)s")
rows = []
for recipe in ("surface-world", "floating-islands"):
for pair, order in enumerate(ORDERS, 1):
for build in order:
name = f"cost-speedfade-{recipe}-pair{pair}-{build}"
logfile = OUT / f"{name}.log"
command = [
"prlimit", "--as=6442450944", "--", str(BINARIES[build]),
str(ROOT / "Standalone/data/owner_world.json"),
"--workers=12", "--world-assembler-prototype", "--stream-benchmark",
"--stream-pipeline", "--force-generate", "--clip-radius=3",
"--max-clip-level=8", "--max-level=-1", f"--recipe={recipe}",
]
started = time.monotonic()
with logfile.open("w", encoding="utf-8") as stream:
subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
check=True, timeout=1800)
elapsed = time.monotonic() - started
text = logfile.read_text(encoding="utf-8", errors="replace")
matches = WHOLE_READY.findall(text)
if len(matches) != 1 or "hash=off" not in text or "cache_hits=0" not in text:
raise SystemExit(f"invalid cost run: {name}; see {logfile}")
ready = float(matches[0])
rows.append((recipe, pair, build, ready, elapsed))
print(f"{recipe} pair={pair} build={build} whole_view_ready={ready:.3f}s "
f"process_elapsed={elapsed:.3f}s", flush=True)
summary = OUT / "cost-runs-speedfade.csv"
summary.write_text("recipe,pair,build,whole_view_ready_seconds,process_elapsed_seconds\n" +
"".join(f"{recipe},{pair},{build},{ready:.6f},{elapsed:.6f}\n"
for recipe, pair, build, ready, elapsed in rows), encoding="utf-8")
print(f"wrote {summary}", flush=True)
@@ -0,0 +1,44 @@
#!/usr/bin/env python3
"""Verify speed-only L30 hashes on the 24 m crossing against L29."""
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l30"
OUT.mkdir(parents=True, exist_ok=True)
BINARIES = {
"clang": ROOT / "Saved/Linux/l30/bin/VoxelForgeCoreHeadless-linux30-speed-fade",
"gcc": ROOT / "Saved/Linux/l30/bin/VoxelForgeCoreHeadless-linux30-speed-fade-gcc",
}
PATTERN = re.compile(r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
passed = True
for compiler, binary in BINARIES.items():
for recipe in ("surface-world", "floating-islands"):
reference_path = ROOT / f"Saved/Linux/linux29-final-crossing24-{compiler}-{recipe}.log"
reference = PATTERN.findall(reference_path.read_text(encoding="utf-8"))
for workers in (1, 8, 12):
name = f"crossing-speedonly-default-{compiler}-{recipe}-w{workers}"
logfile = OUT / f"{name}.log"
command = [
"prlimit", "--as=6442450944", "--", str(binary),
str(ROOT / "Standalone/data/owner_world.json"),
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--stream-hash", "--force-generate", "--clip-radius=3",
"--max-clip-level=8", f"--workers={workers}",
"--camera-step=24,0,0",
"--region-crossing", f"--recipe={recipe}",
]
started = time.monotonic()
with logfile.open("w", encoding="utf-8") as stream:
subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT,
check=True, timeout=1800)
elapsed = time.monotonic() - started
rows = PATTERN.findall(logfile.read_text(encoding="utf-8", errors="replace"))
ok = len(reference) == 12 and rows == reference
passed &= ok
print(f"{compiler} {recipe} W{workers} elapsed={elapsed:.3f}s "
f"hash_rows={len(rows)} {'MATCH' if ok else 'MISMATCH'}", flush=True)
print(f"[CrossingSpeedOnly] runs=12 status={'PASS' if passed else 'FAIL'}", flush=True)
raise SystemExit(0 if passed else 1)
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB