wip(assembler): round L18 -- FloatingIslands false floors removed; shared strate ceiling

FI geometry v3 drops the all-XY lower boundary seal that created false floors (3.5/8 m); the
ceiling comes from the shared VF_ApplyUpperBoundarySeal and AppendStratumBoundaryCeilings path
for every generator, at each strate's own configured upper boundary (owner: every strate has a
ceiling). Far-LOD ceiling continuity: 0 gaps at LOD4-8. SurfaceWorld bit-identical to L14
(14 configs + 24 m crossing); FI deterministic (28-row matrix); route parity and proofs pass.
Unreal: FI geometryVersion 2->3 and the shared upper-seal helper extracted (UBT compile needed on
the owner PC). Open: small ceiling holes; lower-stratum LOD ring steps seen through the FI void.

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-20 07:55:53 +02:00
1 parent 87041290dd
commit dcf0763ec5
36 files changed
+1690 -54

No files matched your search

+7 -2
View File
@@ -153,8 +153,13 @@ radius range, top flattening, taper, bounded contour warp, roughness, blend, con
route constraints. `PlanFloatingIslandCluster` emits stable island and connector IDs, a connected
cluster graph, conservative influence AABBs, and a centerline route. At least the primary chain is
always planned; `islandDensityQ16` controls up to two optional side sites. Connectors carry a sharp-tag
ready bit for a later hybrid-mesher adapter. The schema accepts geometry version 1 and 2. Manifests
without a geometry version retain version-1 evaluation and canonical hashes.
ready bit for a later hybrid-mesher adapter. The schema accepts geometry versions 1 through 3.
Version 3 keeps the version-2 island form, removes the all-XY lower seal, and obtains its ceiling from
the shared upper-strate boundary seal. The resolved upper bound comes from the strate's own chunk
range; `boundarySealSubvoxels` sets the shared seal thickness and remains a planning margin for island
features. It does not define a FloatingIslands-specific ceiling height. Versions 1 and 2 retain their
recorded two-sided boundary seals. Manifests without a geometry version retain version-1 evaluation
and canonical hashes.
`FloatingIslandFieldV1::densitySubvoxels` evaluates the canonical island and connector field.
`interval` returns unknown for boxes overlapping any influence AABB and a certified all-air range
@@ -392,7 +392,9 @@ namespace VoxelForgeCore
std::uint32_t schemaVersion = 1;
// Profile 1 preserves the original shallow pads. Profile 2 adds a thick crust,
// bounded lobed roots and arched spans without changing the feature-family schema.
std::uint32_t geometryVersion = 2;
// Version 3 keeps the version-2 island shapes, removes the all-XY lower boundary seal,
// and routes its upper seal through the shared strate-boundary primitive.
std::uint32_t geometryVersion = 3;
std::uint32_t unitsPerVoxel = static_cast<std::uint32_t>(WorldAssemblySubvoxelsPerVoxel);
// Import scale is explicit in microns; the reduced ratio maps owner-authored lengths to Core voxels.
// L'echelle d'import est explicite en microns; le rapport reduit convertit les longueurs owner.
+171 -5
View File
@@ -864,6 +864,46 @@ struct FFarMeshColumnIndex
return Hits;
}
bool HasDownwardHit(double X,double Y,double Z,double Tolerance) const
{
const int32 CX=std::clamp(static_cast<int32>(std::floor((X-Origin.X)/Step)),0,Cells-1);
const int32 CY=std::clamp(static_cast<int32>(std::floor((Y-Origin.Y)/Step)),0,Cells-1);
const auto& Candidates=ByColumn[static_cast<size_t>(CY)*Cells+CX];
for(const int32 Triangle:Candidates)
{
const int32 IA=Mesh.Triangles[Triangle*3],IB=Mesh.Triangles[Triangle*3+1],
IC=Mesh.Triangles[Triangle*3+2];
if(IA<0||IB<0||IC<0||IA>=Mesh.Normals.Num()||IB>=Mesh.Normals.Num()
||IC>=Mesh.Normals.Num())continue;
if(Mesh.Normals[IA].Z+Mesh.Normals[IB].Z+Mesh.Normals[IC].Z>=-1.5)continue;
const double AX=PositionAxis(IA,0),AY=PositionAxis(IA,1),AZ=PositionAxis(IA,2);
const double BX=PositionAxis(IB,0),BY=PositionAxis(IB,1),BZ=PositionAxis(IB,2);
const double CXv=PositionAxis(IC,0),CYv=PositionAxis(IC,1),CZ=PositionAxis(IC,2);
const double Den=(BX-AX)*(CYv-AY)-(BY-AY)*(CXv-AX);
if(std::abs(Den)<1.0e-10)continue;
const double U=((X-AX)*(CYv-AY)-(Y-AY)*(CXv-AX))/Den;
const double V=((BX-AX)*(Y-AY)-(BY-AY)*(X-AX))/Den;
const double W=1.0-U-V;
if(U<-1.0e-7||V<-1.0e-7||W<-1.0e-7)continue;
const double Hit=AZ+U*(BZ-AZ)+V*(CZ-AZ);
if(std::abs(Hit-Z)<=Tolerance)return true;
}
return false;
}
bool HasDownwardCellCoverage(double X0,double Y0,double X1,double Y1,
double Z,double Tolerance) const
{
constexpr double Samples[3]={0.125,0.5,0.875};
for(const double V:Samples)for(const double U:Samples)
{
const double X=X0+(X1-X0)*U;
const double Y=Y0+(Y1-Y0)*V;
if(!HasDownwardHit(X,Y,Z,Tolerance))return false;
}
return true;
}
private:
double PositionAxis(int32 Index,int32 Axis) const
{
@@ -974,11 +1014,11 @@ inline void AppendFarStratumShells(const FStandaloneOwnerWorld& World,const FSta
std::vector<FBoundary> Boundaries;
for(const FStrateSlot& Slot:Layout)
{
const FStrateGenerationParams Params=World.Manager->GetGenerationParams(
FIntVector(0,0,(Slot.TopChunkZ+Slot.BottomChunkZ)/2));
const double HalfWidth=std::max(8.0,static_cast<double>(Params.BoundarySealThickness)+8.0);
Boundaries.push_back({static_cast<double>(Slot.BottomChunkZ)*CHUNK_SIZE,HalfWidth});
Boundaries.push_back({static_cast<double>(Slot.TopChunkZ+1)*CHUNK_SIZE,HalfWidth});
FStandaloneShapeWorld::FResolvedStratumBounds Resolved;
if(!Shape.GetResolvedStratumBounds(Slot,Resolved))continue;
const double HalfWidth=std::max(8.0,Resolved.SealThickness+8.0);
Boundaries.push_back({Resolved.BottomZ,HalfWidth});
Boundaries.push_back({Resolved.TopZ,HalfWidth});
}
std::sort(Boundaries.begin(),Boundaries.end(),[](const FBoundary& A,const FBoundary& B){return A.Z<B.Z;});
Boundaries.erase(std::unique(Boundaries.begin(),Boundaries.end(),[](const FBoundary& A,const FBoundary& B)
@@ -1132,3 +1172,129 @@ inline void AppendFarStratumShells(const FStandaloneOwnerWorld& World,const FSta
Mesh.TriangleSkirtFaceMasks=std::move(CombinedFaceMasks);
Mesh.NumSurfaceVertices=OldSurfaceVertices+AddedVertices;
}
inline void AppendStratumBoundaryCeilings(const FStandaloneOwnerWorld& World,
const FStandaloneShapeWorld& Shape,const FVoxelTileKey& Key,int32 Step,FVoxelMeshData& Mesh)
{
if(Key.Level<0||Step<=0||!World.Manager)return;
const FIntVector Origin=Key.OriginVoxels();
const int32 Extent=Key.ExtentVoxels();
const int32 GridStep=Step;
if(Extent<=0||GridStep<=0||Extent%GridStep!=0)return;
const int32 GridSide=Extent/GridStep+1;
struct FCandidate
{
FStandaloneShapeWorld::FResolvedStratumBounds Bounds;
std::vector<uint8> Crossing;
};
std::vector<FCandidate> Candidates;
for(const FStrateSlot& Slot:World.Manager->GetLayout())
{
FCandidate Candidate;
if(!Shape.GetResolvedStratumBounds(Slot,Candidate.Bounds)
||Candidate.Bounds.SealThickness<=0.0
||Candidate.Bounds.TopZ<Origin.Z-1.0e-6
||Candidate.Bounds.TopZ>Origin.Z+Extent+1.0e-6)continue;
const double Epsilon=std::min(0.5,Candidate.Bounds.SealThickness*0.25);
if(Epsilon<=0.0)continue;
Candidate.Crossing.resize(static_cast<std::size_t>(GridSide)*GridSide,0);
bool AnyCrossing=false;
for(int32 Y=0;Y<GridSide;++Y)for(int32 X=0;X<GridSide;++X)
{
const double WorldX=Origin.X+X*GridStep;
const double WorldY=Origin.Y+Y*GridStep;
const float Below=Shape.DensityAt(static_cast<float>(WorldX),static_cast<float>(WorldY),
static_cast<float>(Candidate.Bounds.TopZ-Epsilon));
const float Above=Shape.DensityAt(static_cast<float>(WorldX),static_cast<float>(WorldY),
static_cast<float>(Candidate.Bounds.TopZ+Epsilon));
const bool bCrosses=std::isfinite(Below)&&std::isfinite(Above)&&Below<0.0f&&Above>0.0f;
Candidate.Crossing[static_cast<std::size_t>(Y*GridSide+X)]=bCrosses?1:0;
AnyCrossing=AnyCrossing||bCrosses;
}
if(AnyCrossing)Candidates.push_back(std::move(Candidate));
}
if(Candidates.empty())return;
const int32 Cells=std::max(1,World.CoarseTileCells);
FFarMeshColumnIndex MeshIndex(Mesh,Origin,Extent,Cells,Step);
const int32 OldVertexCount=Mesh.Vertices.Num();
const int32 OldSurfaceVertices=std::clamp(Mesh.NumSurfaceVertices,0,OldVertexCount);
const bool bHasUVs=Mesh.UVs.Num()==OldVertexCount;
const bool bHasColors=Mesh.Colors.Num()==OldVertexCount;
const FColor Color=bHasColors&&OldVertexCount>0?Mesh.Colors[0]:FColor(160,160,160,255);
const double VoxelSizeCm=static_cast<double>(World.CoreVoxelSizeMicrons)/10000.0;
std::vector<FVector> NewVertices,NewNormals;
std::vector<FVector2f> NewUVs;
std::vector<FColor> NewColors;
std::vector<int32> NewTriangles;
for(const FCandidate& Candidate:Candidates)
{
std::vector<int32> VertexMap(static_cast<std::size_t>(GridSide)*GridSide,INDEX_NONE);
const double CeilingZ=Candidate.Bounds.TopZ;
const auto AddVertex=[&](int32 X,int32 Y)
{
const std::size_t GridIndex=static_cast<std::size_t>(Y*GridSide+X);
int32& Mapped=VertexMap[GridIndex];
if(Mapped!=INDEX_NONE)return Mapped;
Mapped=static_cast<int32>(NewVertices.size());
const double WorldX=Origin.X+X*GridStep,WorldY=Origin.Y+Y*GridStep;
NewVertices.emplace_back(WorldX*VoxelSizeCm,WorldY*VoxelSizeCm,CeilingZ*VoxelSizeCm);
NewNormals.emplace_back(0.0,0.0,-1.0);
if(bHasUVs)NewUVs.emplace_back(static_cast<float>(WorldX),static_cast<float>(WorldY));
if(bHasColors)NewColors.push_back(Color);
return Mapped;
};
for(int32 Y=0;Y+1<GridSide;++Y)for(int32 X=0;X+1<GridSide;++X)
{
const std::size_t I00=static_cast<std::size_t>(Y*GridSide+X);
const std::size_t I10=I00+1,I01=I00+GridSide,I11=I01+1;
if(!Candidate.Crossing[I00]||!Candidate.Crossing[I10]
||!Candidate.Crossing[I01]||!Candidate.Crossing[I11])continue;
const double CellX0=Origin.X+X*GridStep,CellY0=Origin.Y+Y*GridStep;
const double CellX1=CellX0+GridStep,CellY1=CellY0+GridStep;
// Suppress a coincident plane only when it covers the full cell. A hit at just the
// center of a curved shell triangle is not enough to leave the rest of this cap cell open.
if(MeshIndex.HasDownwardCellCoverage(CellX0,CellY0,CellX1,CellY1,CeilingZ,0.001))continue;
const int32 A=AddVertex(X,Y),B=AddVertex(X+1,Y);
const int32 C=AddVertex(X+1,Y+1),D=AddVertex(X,Y+1);
NewTriangles.insert(NewTriangles.end(),{A,C,B,A,D,C});
}
}
if(NewTriangles.empty())return;
const int32 AddedVertices=static_cast<int32>(NewVertices.size());
auto InsertVertices=[&](auto& Existing,const auto& Added)
{
using TArrayType=std::remove_reference_t<decltype(Existing)>;
TArrayType Combined;Combined.Reserve(Existing.Num()+static_cast<int32>(Added.size()));
for(int32 I=0;I<OldSurfaceVertices;++I)Combined.Add(Existing[I]);
for(const auto& Value:Added)Combined.Add(Value);
for(int32 I=OldSurfaceVertices;I<Existing.Num();++I)Combined.Add(Existing[I]);
Existing=std::move(Combined);
};
InsertVertices(Mesh.Vertices,NewVertices);InsertVertices(Mesh.Normals,NewNormals);
if(bHasUVs)InsertVertices(Mesh.UVs,NewUVs);
if(bHasColors)InsertVertices(Mesh.Colors,NewColors);
for(int32& Index:Mesh.Triangles)if(Index>=OldSurfaceVertices)Index+=AddedVertices;
const int32 CeilingStart=std::max(0,Mesh.Triangles.Num()-Mesh.NumCeilingTriangles*3);
TArray<int32> CombinedTriangles;CombinedTriangles.Reserve(
Mesh.Triangles.Num()+static_cast<int32>(NewTriangles.size()));
for(int32 I=0;I<CeilingStart;++I)CombinedTriangles.Add(Mesh.Triangles[I]);
for(const int32 Index:NewTriangles)CombinedTriangles.Add(OldSurfaceVertices+Index);
for(int32 I=CeilingStart;I<Mesh.Triangles.Num();++I)CombinedTriangles.Add(Mesh.Triangles[I]);
const int32 OldTriangleCount=Mesh.Triangles.Num()/3;
TArray<uint8> ExistingFaceMasks=Mesh.TriangleSkirtFaceMasks;
ExistingFaceMasks.SetNum(OldTriangleCount);
TArray<uint8> CombinedFaceMasks;CombinedFaceMasks.Reserve(
OldTriangleCount+static_cast<int32>(NewTriangles.size()/3));
for(int32 Triangle=0;Triangle<CeilingStart/3;++Triangle)
CombinedFaceMasks.Add(ExistingFaceMasks[Triangle]);
for(std::size_t Triangle=0;Triangle<NewTriangles.size()/3;++Triangle)
CombinedFaceMasks.Add(0);
for(int32 Triangle=CeilingStart/3;Triangle<OldTriangleCount;++Triangle)
CombinedFaceMasks.Add(ExistingFaceMasks[Triangle]);
Mesh.Triangles=std::move(CombinedTriangles);
Mesh.TriangleSkirtFaceMasks=std::move(CombinedFaceMasks);
Mesh.NumSurfaceVertices=OldSurfaceVertices+AddedVertices;
Mesh.NumCeilingTriangles+=static_cast<int32>(NewTriangles.size()/3);
}
@@ -266,7 +266,7 @@ namespace floating_island_detail
? recipe.ownerVoxelSizeMicrons / ScaleGcd : 0;
const std::uint32_t ExpectedDenominator = ScaleGcd > 0
? recipe.coreVoxelSizeMicrons / ScaleGcd : 0;
if (recipe.schemaVersion != 1 || (recipe.geometryVersion != 1 && recipe.geometryVersion != 2)
if (recipe.schemaVersion != 1 || recipe.geometryVersion < 1 || recipe.geometryVersion > 3
|| (recipe.geometryVersion == 1
&& (recipe.crustDepthBonusQ16 != 0 || recipe.rootDepthRatioQ16 != 0))
|| recipe.unitsPerVoxel != Q
+86 -15
View File
@@ -5,6 +5,7 @@
#include "VoxelCaveMorphology.h"
#include "VoxelNoise.h"
#include "VoxelStrateManager.h"
#include "VoxelDensityPrimitives.h"
#include "VoxelForgeCore/VoxelForgeCore.h"
#include "VoxelForgeFieldShared.h"
@@ -32,6 +33,13 @@
class FStandaloneShapeWorld
{
public:
struct FResolvedStratumBounds
{
double BottomZ=0.0;
double TopZ=0.0;
double SealThickness=0.0;
};
double GetCoreVoxelSizeCm() const
{
return Owner == nullptr || Owner->CoreVoxelSizeMicrons == 0
@@ -47,6 +55,41 @@ public:
return static_cast<double>(CHUNK_SIZE) * OwnerVoxelSizeCm / GetCoreVoxelSizeCm();
}
static bool MakeResolvedStratumBounds(double BottomZ,double TopZ,double SealThickness,
FResolvedStratumBounds& Out)
{
if(!std::isfinite(BottomZ)||!std::isfinite(TopZ)||!std::isfinite(SealThickness)
||TopZ<=BottomZ||SealThickness<0.0)return false;
Out={BottomZ,TopZ,SealThickness};
return true;
}
bool GetResolvedStratumBounds(const FStrateSlot& Slot,FResolvedStratumBounds& Out) const
{
if(!Owner||!Owner->Manager)return false;
const int32 MidChunkZ=(Slot.TopChunkZ+Slot.BottomChunkZ)/2;
if(bUseNativeManifestFields&&WorldAssembly
&&Owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,MidChunkZ))
==ECaveGeneratorType::FloatingIslands)
{
const auto* Plan=FindWorldAssemblyFloatingIslandStratum(Slot.StrateIndex,MidChunkZ);
if(Plan&&Plan->manifest.recipe.geometryVersion>=3)
{
constexpr double Scale=static_cast<double>(
VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
return MakeResolvedStratumBounds(
static_cast<double>(Plan->manifest.recipe.layoutMinimumZSubvoxels)/Scale,
static_cast<double>(Plan->manifest.recipe.layoutMaximumZSubvoxels)/Scale,
static_cast<double>(Plan->manifest.recipe.boundarySealSubvoxels)/Scale,Out);
}
}
const FStrateGenerationParams Params=Owner->Manager->GetGenerationParams(
FIntVector(0,0,MidChunkZ));
return MakeResolvedStratumBounds(static_cast<double>(Slot.BottomChunkZ)*CHUNK_SIZE,
static_cast<double>(Slot.TopChunkZ+1)*CHUNK_SIZE,
static_cast<double>(Params.BoundarySealThickness),Out);
}
enum class EDetailVariant : uint8 { Coherent, Smooth, PerVoxel };
struct FBoxClassification
{
@@ -714,12 +757,12 @@ public:
const double TileZ0=Origin.Z,TileZ1=Origin.Z+static_cast<double>(Cells)*Step;
for(const FStrateSlot& Slot:Owner->Manager->GetLayout())
{
const FStrateGenerationParams Params=Owner->Manager->GetGenerationParams(
FIntVector(0,0,(Slot.TopChunkZ+Slot.BottomChunkZ)/2));
FResolvedStratumBounds Bounds;
if(!GetResolvedStratumBounds(Slot,Bounds))continue;
const float HalfWidth=static_cast<float>(std::max(8.0,
static_cast<double>(Params.BoundarySealThickness)+8.0));
const float Boundaries[2]={static_cast<float>(Slot.BottomChunkZ)*CHUNK_SIZE,
static_cast<float>(Slot.TopChunkZ+1)*CHUNK_SIZE};
Bounds.SealThickness+8.0));
const float Boundaries[2]={static_cast<float>(Bounds.BottomZ),
static_cast<float>(Bounds.TopZ)};
for(const float Z:Boundaries)
if(Z+HalfWidth>=TileZ0&&Z-HalfWidth<=TileZ1)
Out.stratumBoundaries.push_back({Z,HalfWidth});
@@ -1549,13 +1592,18 @@ public:
const std::int64_t Seal=IslandPlan->manifest.recipe.boundarySealSubvoxels;
const std::int64_t BoxBottom=static_cast<std::int64_t>(BMin[2])*Scale;
const std::int64_t BoxTop=static_cast<std::int64_t>(BMax[2])*Scale;
const bool bBottomSealOverlap=BoxBottom<Bottom+Seal&&BoxTop>=Bottom;
const bool bTopSealOverlap=BoxBottom<=Top&&BoxTop>Top-Seal;
const bool bSharedTopSealEnabled=IslandPlan->manifest.recipe.geometryVersion<3
||!VoxelDensityAblation::IsBoundarySealOff();
const bool bTopSealOverlap=bSharedTopSealEnabled
&&BoxBottom<=Top&&BoxTop>Top-Seal;
const bool bBottomSealOverlap=IslandPlan->manifest.recipe.geometryVersion<3
&&BoxBottom<Bottom+Seal&&BoxTop>=Bottom;
if(bBottomSealOverlap)
return Finish(BoxBottom>=Bottom&&BoxTop<=Bottom+Seal
?EVoxelTileClass::AllSolid:EVoxelTileClass::Mixed);
if(bTopSealOverlap)
return Finish(BoxBottom>=Top-Seal&&BoxTop<=Top
return Finish((IslandPlan->manifest.recipe.geometryVersion<3
?BoxBottom>=Top-Seal:BoxBottom>Top-Seal)&&BoxTop<=Top
?EVoxelTileClass::AllSolid:EVoxelTileClass::Mixed);
const VoxelForgeCore::FloatingIslandFieldV1 Block(
IslandPlan->manifest.recipe,IslandPlan->manifest.plan);
@@ -2130,9 +2178,28 @@ private:
const std::int64_t Top=Plan->manifest.recipe.layoutMaximumZSubvoxels;
const std::int64_t Seal=Plan->manifest.recipe.boundarySealSubvoxels;
const std::int64_t Zq=ToFixed(Z);
if(Zq>=Bottom&&Zq<Bottom+Seal)Density=std::min(Density,-std::max<std::int64_t>(1,Zq-Bottom));
if(Zq<=Top&&Zq>Top-Seal)Density=std::min(Density,-std::max<std::int64_t>(1,Top-Zq));
OutDensity=static_cast<float>(static_cast<double>(Density)/Scale);
if(Plan->manifest.recipe.geometryVersion<3)
{
if(Zq>=Bottom&&Zq<Bottom+Seal)
Density=std::min(Density,-std::max<std::int64_t>(1,Zq-Bottom));
if(Zq<=Top&&Zq>Top-Seal)
Density=std::min(Density,-std::max<std::int64_t>(1,Top-Zq));
OutDensity=static_cast<float>(static_cast<double>(Density)/Scale);
}
else
{
OutDensity=static_cast<float>(static_cast<double>(Density)/Scale);
FResolvedStratumBounds Bounds;
if(!MakeResolvedStratumBounds(static_cast<double>(Bottom)/Scale,
static_cast<double>(Top)/Scale,static_cast<double>(Seal)/Scale,Bounds))
return false;
const FFloatingIslandParams Params=Owner->Manager->GetFloatingIslandParamsForChunk(
FIntVector(0,0,ChunkZ));
float InternalDensity=-OutDensity;
VF_ApplyUpperBoundarySeal(InternalDensity,Z,static_cast<float>(Bounds.TopZ),
static_cast<float>(Bounds.SealThickness),Params.BaseDensity);
OutDensity=-InternalDensity;
}
return std::isfinite(OutDensity);
}
@@ -2351,19 +2418,23 @@ private:
int32 Top=MIN_int32,Bottom=MAX_int32;
for(const FStrateSlot& Slot:Layout)
{
Top=std::max(Top,Slot.TopChunkZ);Bottom=std::min(Bottom,Slot.BottomChunkZ);
FResolvedStratumBounds Bounds;
if(!GetResolvedStratumBounds(Slot,Bounds))continue;
const int32 ResolvedBottom=FMath::FloorToInt(static_cast<float>(Bounds.BottomZ));
const int32 ResolvedTop=FMath::CeilToInt(static_cast<float>(Bounds.TopZ));
Top=std::max(Top,ResolvedTop);Bottom=std::min(Bottom,ResolvedBottom);
if(!Slot.Definition)continue;
const int32 MidChunkZ=(Slot.TopChunkZ+Slot.BottomChunkZ)/2;
FLayerField Field;
Field.Lo=Slot.BottomChunkZ*CHUNK_SIZE;
Field.Hi=(Slot.TopChunkZ+1)*CHUNK_SIZE;
Field.Lo=ResolvedBottom;
Field.Hi=ResolvedTop;
Field.StrateIndex=Slot.StrateIndex;
Field.Params=Owner->Manager->GetGenerationParams(FIntVector(0,0,MidChunkZ));
LayerFields.push_back(std::move(Field));
}
if(Top>=Bottom)
{
LayerRanges.push_back({Bottom*CHUNK_SIZE,(Top+1)*CHUNK_SIZE});
LayerRanges.push_back({Bottom,Top});
}
}
+2
View File
@@ -2204,6 +2204,8 @@ namespace VoxelForgeCore
out.diagnostics.horizonGroundTrianglesRemoved=GroundStats.RemovedTriangles;
out.diagnostics.horizonCapTriangles=CapStats.Triangles;
}
if(impl_->worldAssembly&&!bOverlapsPlayerEdit)
AppendStratumBoundaryCeilings(world,farShape,nativeKey,step,mesh);
out.densitySampleCount=static_cast<std::uint64_t>(std::max<int64>(0,sampleCount));
if(bHasWorldAssembly)
@@ -0,0 +1,9 @@
# L18 build resources
Limits: CPUs 0–11, `-j12`, normal priority, 6 GiB address-space cap. Governor stayed `powersave`.
These are the final builds after the cell-coverage and owner ceiling corrections.
- Clang 19, `-ffp-contract=off`: exit 0, 31.689 s, peak process RSS 436,088 KiB, peak VMS 586,240 KiB.
- GCC 14, default FP contraction: exit 0, 48.648 s, peak process RSS 903,168 KiB, peak VMS 1,014,716 KiB.
- Largest measured runtime process was the final Clang gate run at 897,276 KiB RSS. The determinism matrix peaked at 603,172 KiB and the crossing matrix at 548,664 KiB.
- An earlier GCC build was interrupted by owner steering before producing a binary; the final GCC build above completed successfully.
@@ -0,0 +1,52 @@
#!/usr/bin/env python3
"""Render the matched L18 review views as small readable JPEGs."""
import os
import subprocess
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
CAPTURE = ROOT / "Standalone/bin/linux/VoxelForgeOffscreenCapture-linux18-clang"
EXPORTS = ROOT / "Standalone/Research/WorldAssemblerM3/Linux18/exports"
OUT = ROOT / "Standalone/Research/WorldAssemblerM3/Linux18/captures"
LOGS = ROOT / "Saved/Linux"
OUT.mkdir(parents=True, exist_ok=True)
env = os.environ.copy()
env["LP_NUM_THREADS"] = "12"
env["XDG_CACHE_HOME"] = str(ROOT / "Saved/Linux/mesa-cache")
views = [
("floating-islands-flat-square-after", "floating-islands-flat-square.vfmesh",
(-11.612444, -118.221237, -19.8452), 0, 0, True),
("floating-islands-flat-square-no-edges", "floating-islands-flat-square.vfmesh",
(-11.612444, -118.221237, -19.8452), 0, 0, False),
("floating-islands-island-view-after", "floating-islands-island-view.vfmesh",
(-22, -19, -22.8), 0, -4, True),
("floating-islands-ceiling-up", "floating-islands-island-view.vfmesh",
(-22, -19, -22.8), 0, 80, True),
("surface-world-l1-spawn-after", "surface-world-l1-spawn.vfmesh",
(0, -35, -487.94), 0, -22, False),
("surface-world-horizon-after", "surface-world-l1-spawn.vfmesh",
(0, -35, -487.94), 0, 0, False),
]
for name, scene_name, camera, yaw, pitch, tile_edges in views:
scene = EXPORTS / scene_name
png = LOGS / f"l18-{name}.png"
jpg = OUT / f"{name}.jpg"
render_log = LOGS / f"l18-{name}-render.log"
convert_log = LOGS / f"l18-{name}-jpeg.log"
command = [str(CAPTURE), str(scene), str(png), "--size=1600x900",
"--shade=readable", f"--tile-edges={'on' if tile_edges else 'off'}",
"--tile-edge-width-m=0.15", "--camera-m="+",".join(map(str, camera)),
f"--yaw-deg={yaw}", f"--pitch-deg={pitch}"]
with render_log.open("w", encoding="utf-8") as stream:
subprocess.run(command, cwd=ROOT, env=env, stdout=stream,
stderr=subprocess.STDOUT, check=True)
ffmpeg = ["ffmpeg", "-nostdin", "-hide_banner", "-loglevel", "error", "-y",
"-i", str(png), "-frames:v", "1", "-q:v", "4", str(jpg)]
with convert_log.open("w", encoding="utf-8") as stream:
subprocess.run(ffmpeg, cwd=ROOT, env=env, stdout=stream,
stderr=subprocess.STDOUT, check=True)
if jpg.stat().st_size > 300_000:
raise SystemExit(f"{jpg.name} exceeds the 300 KB review limit")
print(f"[Capture] {jpg.relative_to(ROOT)} bytes={jpg.stat().st_size} status=PASS")
@@ -0,0 +1,165 @@
#!/usr/bin/env python3
"""Audit the emitted FloatingIslands roof faces and same-LOD tile-edge continuity."""
import array
import collections
import math
import struct
import sys
def read_exact(stream, size):
data = stream.read(size)
if len(data) != size:
raise EOFError(f"wanted {size} bytes, got {len(data)}")
return data
def height_at(triangles, x, y, preferred_z):
best = None
for (x0, y0, z0), (x1, y1, z1), (x2, y2, z2) in triangles:
den = (y1-y2)*(x0-x2)+(x2-x1)*(y0-y2)
if abs(den) < 1.0e-10:
continue
a = ((y1-y2)*(x-x2)+(x2-x1)*(y-y2))/den
b = ((y2-y0)*(x-x2)+(x0-x2)*(y-y2))/den
c = 1.0-a-b
if min(a,b,c) >= -1.0e-5:
z = a*z0+b*z1+c*z2
if best is None or abs(z-preferred_z) < abs(best-preferred_z):
best = z
return best
def quantile(values, fraction):
if not values:
return math.nan
ordered = sorted(values)
return ordered[math.floor(fraction*(len(ordered)-1))]
if len(sys.argv) != 3:
raise SystemExit("usage: CeilingContinuity.py scene.vfmesh expected-cap-plane-metres")
scene_path = sys.argv[1]
root_z = float(sys.argv[2])
tiles = {}
voxel_size_m = None
with open(scene_path, "rb") as stream:
if read_exact(stream, 8) != b"VFMSH001":
raise SystemExit("invalid VFMSH001 scene")
version, voxel_um, tile_count = struct.unpack("<3I", read_exact(stream, 12))
camera = struct.unpack("<3d", read_exact(stream, 24))
yaw, pitch = struct.unpack("<2d", read_exact(stream, 16))
voxel_size_m = voxel_um/1_000_000.0
for _ in range(tile_count):
key = struct.unpack("<4i", read_exact(stream, 16))
surface_vertices, skirt_count, vertex_count, index_count, mask_count = struct.unpack(
"<5I", read_exact(stream, 20))
positions = array.array("f")
positions.frombytes(read_exact(stream, vertex_count*12))
normals = array.array("f")
normals.frombytes(read_exact(stream, vertex_count*12))
indices = array.array("I")
indices.frombytes(read_exact(stream, index_count*4))
masks = read_exact(stream, mask_count)
if sys.byteorder != "little":
positions.byteswap()
normals.byteswap()
indices.byteswap()
extent = 32*(1 << key[3])*voxel_size_m
z0, z1 = key[2]*extent, (key[2]+1)*extent
ceiling = []
for triangle in range(index_count//3):
if masks and masks[triangle] != 0:
continue
a,b,c = indices[triangle*3:triangle*3+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.65:
continue
points = tuple((positions[v*3],positions[v*3+1],positions[v*3+2])
for v in (a,b,c))
center_z = sum(point[2] for point in points)/3.0
if abs(center_z-root_z) <= 1.0:
ceiling.append(points)
tiles[key] = {
"extent": extent, "x0": key[0]*extent, "y0": key[1]*extent,
"x1": (key[0]+1)*extent, "y1": (key[1]+1)*extent,
"z0": z0, "z1": z1, "ceiling": ceiling,
}
by_lod = collections.defaultdict(dict)
for key, tile in tiles.items():
if tile["z0"] <= root_z+1.0 and tile["z1"] >= root_z-1.0:
by_lod[key[3]][key] = tile
print(f"scene={scene_path} version={version} voxel_um={voxel_um} tiles={tile_count} "
f"camera_m={camera} yaw={yaw} pitch={pitch} expected_cap_m={root_z:.9f}")
print("lod zband_tiles cap_tiles missing_tiles cap_triangles face_z_m_p05/median/p95 "
"tile_samples hit_samples max_abs_root_delta_cm shared_edges shared_hits edge_gaps "
"edge_delta_cm_median/p95/max")
all_pass = True
for lod in range(9):
target = by_lod[lod]
cap_tiles = {key: tile for key,tile in target.items() if tile["ceiling"]}
face_z = [point[2] for tile in cap_tiles.values() for tri in tile["ceiling"]
for point in tri]
deltas = []
sample_count = 0
hit_count = 0
for key,tile in target.items():
if not tile["ceiling"]:
continue
for iy in range(16):
for ix in range(16):
x = tile["x0"]+(ix+0.5)*tile["extent"]/16
y = tile["y0"]+(iy+0.5)*tile["extent"]/16
sample_count += 1
height = height_at(tile["ceiling"],x,y,root_z)
if height is not None:
hit_count += 1
deltas.append(abs(height-root_z)*100.0)
edge_deltas = []
edge_count = 0
edge_gaps = 0
for key,a in cap_tiles.items():
x,y,z,level = key
for neighbor_key,axis in (((x+1,y,z,level),"x"),((x,y+1,z,level),"y")):
b = cap_tiles.get(neighbor_key)
if b is None:
continue
edge_count += 1
epsilon = min(0.02,a["extent"]*1.0e-4)
matched = 0
for index in range(33):
t = (index+0.5)/33
if axis == "x":
fixed = a["x1"]
varying = a["y0"]+(a["y1"]-a["y0"])*t
za = height_at(a["ceiling"],fixed-epsilon,varying,root_z)
zb = height_at(b["ceiling"],fixed+epsilon,varying,root_z)
else:
fixed = a["y1"]
varying = a["x0"]+(a["x1"]-a["x0"])*t
za = height_at(a["ceiling"],varying,fixed-epsilon,root_z)
zb = height_at(b["ceiling"],varying,fixed+epsilon,root_z)
if za is not None and zb is not None:
edge_deltas.append(abs(za-zb)*100.0)
matched += 1
if matched == 0:
edge_gaps += 1
missing = len(target)-len(cap_tiles)
max_root = max(deltas) if deltas else math.nan
print(f"{lod} {len(target)} {len(cap_tiles)} {missing} "
f"{sum(len(tile['ceiling']) for tile in cap_tiles.values())} "
f"{quantile(face_z,.05):.6f}/{quantile(face_z,.5):.6f}/{quantile(face_z,.95):.6f} "
f"{sample_count} {hit_count} {max_root:.3f} {edge_count} {len(edge_deltas)} "
f"{edge_gaps} {quantile(edge_deltas,.5):.3f}/{quantile(edge_deltas,.95):.3f}/"
f"{max(edge_deltas) if edge_deltas else math.nan:.3f}")
if lod >= 4 and target and (missing or hit_count != sample_count or edge_gaps):
all_pass = False
print(f"[CeilingContinuity] result={'PASS' if all_pass else 'FAIL'} "
"criteria=LOD4-8 requested z-band tiles emit an upper-boundary ceiling; 16x16 ray samples hit; "
"shared same-LOD tile edges have paired hits")
raise SystemExit(0 if all_pass else 1)
@@ -0,0 +1,25 @@
#!/usr/bin/env python3
"""Compare all L18 crossing configurations to the L14 24 m crossing hashes."""
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
RUNS = ROOT / "Saved/Linux/l18-crossing"
PATTERN = re.compile(
r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
failed = []
logs = sorted(RUNS.glob("*-w*-r*.log"))
for path in logs:
parts = path.stem.split("-")
compiler = parts[0]
reference_compiler = "clang" if compiler == "clang" else "gcc"
reference_path = ROOT / f"Saved/Linux/l14-final-crossing-24m-{reference_compiler}-w12.log"
expected = PATTERN.findall(reference_path.read_text(encoding="utf-8"))
actual = PATTERN.findall(path.read_text(encoding="utf-8", errors="replace"))
if actual != expected:
failed.append(path.name)
print(f"[CrossingReferenceMismatch] {path.name} expected_rows={len(expected)} "
f"actual_rows={len(actual)}")
print(f"[CrossingSurfaceHashGate] reference=L14-final-crossing-24m-W12 rows={len(logs)} "
f"result={'FAIL' if failed else 'PASS'}")
raise SystemExit(1 if failed or len(logs) != 14 else 0)
@@ -0,0 +1,25 @@
#!/usr/bin/env python3
"""Compare every L18 SurfaceWorld matrix hash row with the approved L14 reference."""
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
RUNS = ROOT / "Saved/Linux/l18-matrix"
REFERENCE = ROOT / "Saved/Linux/l14-final-hash-sw-clang-off-w12-r5.log"
PATTERN = re.compile(r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
expected = PATTERN.findall(REFERENCE.read_text(encoding="utf-8"))
if not expected or len(expected) != 12:
raise SystemExit(f"invalid L14 reference hash rows: {len(expected)}")
logs = sorted(RUNS.glob("surface-world-*.log"))
if len(logs) != 14:
raise SystemExit(f"expected 14 SurfaceWorld matrix logs, found {len(logs)}")
failed = []
for path in logs:
actual = PATTERN.findall(path.read_text(encoding="utf-8", errors="replace"))
if actual != expected:
failed.append(path.name)
print(f"[SurfaceHashMismatch] {path.name} expected={len(expected)} actual={len(actual)}")
print(f"[SurfaceHashGate] reference={REFERENCE.name} rows={len(logs)} expected_rows=14 "
f"result={'FAIL' if failed else 'PASS'}")
raise SystemExit(1 if failed else 0)
@@ -0,0 +1,149 @@
#include "Json.h"
#include "OwnerWorldLoader.h"
#include "StandaloneCoreScheduler.h"
#include "WorldAssemblyPrototype.h"
#include "StandaloneSnapshotAdapter.h"
#include <cmath>
#include <cstdio>
#include <fstream>
#include <iterator>
#include <memory>
#include <string>
namespace
{
constexpr double CoreVoxelsPerMeter=4.0;
constexpr std::int64_t SubvoxelsPerCoreVoxel=
VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
void PrintDensity(VoxelForgeCore::World& World,const char* Label,double X,double Y,double Z)
{
const VoxelForgeCore::Vec3d Point{X*CoreVoxelsPerMeter,Y*CoreVoxelsPerMeter,
Z*CoreVoxelsPerMeter};
const double Density=World.densityAt(Point);
const double Raw=World.unclampedDensityAt(Point);
std::printf("[DensitySample] label=%s xyz_m=%.6f,%.6f,%.6f density_core_vox=%.9f "
"density_cm=%.4f unclamped_core_vox=%.9f sign=%s\n",Label,X,Y,Z,Density,
Density*25.0,Raw,Density<0.0?"solid":"air");
}
void PrintCrossing(VoxelForgeCore::World& World,const char* Label,double X,double Y,
double BottomM,double TopM)
{
const auto DensityAt=[&](double Z)
{
return World.densityAt({X*CoreVoxelsPerMeter,Y*CoreVoxelsPerMeter,
Z*CoreVoxelsPerMeter});
};
double Lo=BottomM,Hi=TopM;
double LoDensity=DensityAt(Lo),HiDensity=DensityAt(Hi);
if((LoDensity<0.0)==(HiDensity<0.0))
{
std::printf("[DensityCrossing] label=%s bracket_m=%.6f..%.6f signs=%s/%s result=none\n",
Label,Lo,Hi,LoDensity<0.0?"solid":"air",HiDensity<0.0?"solid":"air");
return;
}
for(int Iteration=0;Iteration<64;++Iteration)
{
const double Mid=(Lo+Hi)*0.5;
const double MidDensity=DensityAt(Mid);
if((MidDensity<0.0)==(LoDensity<0.0))
{Lo=Mid;LoDensity=MidDensity;}
else
{Hi=Mid;HiDensity=MidDensity;}
}
std::printf("[DensityCrossing] label=%s z_m=%.9f bracket_m=%.6f..%.6f "
"density_core_vox=%.9f/%.9f\n",Label,(Lo+Hi)*0.5,BottomM,TopM,
LoDensity,HiDensity);
}
}
int main(int argc,char** argv)
{
if(argc!=2)
{
std::fprintf(stderr,"usage: %s owner-world.json\n",argv[0]);
return 2;
}
VFJson::Value Root;
std::string Error;
{
std::ifstream Input(argv[1],std::ios::binary);
if(!Input){std::fprintf(stderr,"owner JSON open failed\n");return 2;}
std::string Text{std::istreambuf_iterator<char>(Input),std::istreambuf_iterator<char>{}};
if(!VFJson::Parser(std::move(Text)).Parse(Root,Error))
{std::fprintf(stderr,"owner JSON parse failed: %s\n",Error.c_str());return 2;}
}
if(!StandaloneWorldAssemblyPrototype::SelectTopStrateAsFloatingIslands(Root,Error))
{std::fprintf(stderr,"FI selection failed: %s\n",Error.c_str());return 2;}
auto Owner=std::make_shared<FStandaloneOwnerWorld>();
if(!StandaloneOwnerLoader::Resolve(Root,*Owner,Error))
{std::fprintf(stderr,"owner resolve failed: %s\n",Error.c_str());return 2;}
VoxelForgeCore::WorldAssemblyManifest Assembly;
if(!StandaloneWorldAssemblyPrototype::AttachRound1WorldAssemblyPrototype(
*Owner,Root,Error,{},&Assembly)
||!StandaloneWorldAssemblyPrototype::AttachFloatingIslandWorldAssembly(
*Owner,Root,Assembly,Error))
{std::fprintf(stderr,"plan construction failed: %s\n",Error.c_str());return 2;}
auto Snapshot=VoxelForgeCore::MakeStandaloneSnapshotFromParsedJson(Root,Owner->Seed,3,8,
false,false,Error);
if(!Snapshot)
{std::fprintf(stderr,"snapshot resolve failed: %s\n",Error.c_str());return 2;}
const auto* ResolvedAssembly=Snapshot.worldAssembly();
if(!ResolvedAssembly||ResolvedAssembly->floatingIslands.empty())
{std::fprintf(stderr,"resolved FloatingIslands plan missing\n");return 2;}
VoxelForgeCore::World World(std::move(Snapshot));
VoxelForgeCore::ShapeBuildRequest Request;
Request.minVoxel={-96,-512,-288};
Request.maxVoxel={96,96,64};
Request.clipMinVoxel=Request.minVoxel;
Request.clipMaxVoxel=Request.maxVoxel;
Request.workerLimit=12;
Request.includeFarWorld=false;
FStandaloneCoreScheduler Scheduler;
if(!World.compile(Request,Scheduler,Error))
{std::fprintf(stderr,"shape compile failed: %s\n",Error.c_str());return 2;}
const auto& Plan=ResolvedAssembly->floatingIslands.front();
const auto& Recipe=Plan.manifest.recipe;
std::printf("[FloatingIslandBounds] geometry_version=%u owner_voxel_um=%u core_voxel_um=%u "
"owner_to_core=%u/%u chunks=%d..%d layout_z_m=%.6f..%.6f ceiling_start_m=%.6f "
"seal_m=%.6f bottom_cap=%s\n",Recipe.geometryVersion,Recipe.ownerVoxelSizeMicrons,
Recipe.coreVoxelSizeMicrons,Recipe.ownerToCoreScaleNumerator,
Recipe.ownerToCoreScaleDenominator,Plan.topChunkZ,Plan.bottomChunkZ,
static_cast<double>(Recipe.layoutMinimumZSubvoxels)/SubvoxelsPerCoreVoxel/4.0,
static_cast<double>(Recipe.layoutMaximumZSubvoxels)/SubvoxelsPerCoreVoxel/4.0,
static_cast<double>(Recipe.layoutMaximumZSubvoxels-Recipe.boundarySealSubvoxels)
/SubvoxelsPerCoreVoxel/4.0,
static_cast<double>(Recipe.boundarySealSubvoxels)/SubvoxelsPerCoreVoxel/4.0,
Recipe.geometryVersion<3?"legacy":"removed");
std::printf("[DensityProbe] flat_square_xy_m=-11.612444,-118.221237 "
"compiled=%d core_voxel_size_cm=25\n",World.isCompiled()?1:0);
constexpr double X=-11.612443923950195;
constexpr double Y=-118.22123718261719;
for(double Z:{2.75,2.99,3.00,3.01,3.25,3.49,3.50,3.51,3.75,3.99,4.00,4.01,
4.05,4.125,4.25,4.50,5.00,
7.75,8.00,8.25,-60.25,-60.00,-59.75,-59.50,-59.25})
PrintDensity(World,"flat-square",X,Y,Z);
PrintCrossing(World,"shared-upper-ceiling",X,Y,3.50,3.75);
PrintCrossing(World,"upper-boundary-cap",X,Y,4.00,4.25);
PrintCrossing(World,"old-eight-metre-wrapper",X,Y,7.75,8.25);
PrintCrossing(World,"removed-lower-boundary-seal",X,Y,-60.25,-59.75);
for(const auto& Probe:std::array<std::array<double,2>,3>{{
{{-66.3,-38.7}},{{-62.3,-54.6}},{{-53.34,-22.84}}}})
{
std::printf("[DensityProbeXY] xy_m=%.3f,%.3f\n",Probe[0],Probe[1]);
for(double Z:{3.49,3.50,3.51,3.75,3.875,3.99,4.00,4.01,4.05,4.125})
PrintDensity(World,"boundary-ray",Probe[0],Probe[1],Z);
PrintCrossing(World,"boundary-ray",Probe[0],Probe[1],3.49,4.125);
}
for(const auto& Probe:std::array<std::array<double,2>,4>{{
{{-67.0,-39.0}},{{-66.0,-39.0}},{{-67.0,-38.0}},{{-66.0,-38.0}}}})
{
PrintDensity(World,"boundary-grid-node",Probe[0],Probe[1],3.875);
PrintDensity(World,"boundary-grid-node",Probe[0],Probe[1],4.125);
}
return 0;
}
@@ -0,0 +1,67 @@
#!/usr/bin/env python3
"""Measure peak per-process RSS for a build process tree under the L17 limits."""
import os
import subprocess
import sys
import time
from pathlib import Path
def descendants(root):
pending = [root]
seen = set()
while pending:
pid = pending.pop()
if pid in seen:
continue
seen.add(pid)
try:
children = Path(f"/proc/{pid}/task/{pid}/children").read_text().split()
pending.extend(int(child) for child in children)
except (FileNotFoundError, PermissionError, ProcessLookupError):
pass
return seen
def status(pid):
try:
fields = {}
for line in Path(f"/proc/{pid}/status").read_text().splitlines():
if line.startswith(("Name:", "VmRSS:", "VmSize:")):
key, *value = line.split()
fields[key[:-1]] = value[0] if value else ""
return (int(fields.get("VmRSS", "0")), int(fields.get("VmSize", "0")),
fields.get("Name", ""))
except (FileNotFoundError, PermissionError, ProcessLookupError, ValueError):
return None
if len(sys.argv) < 2:
raise SystemExit("usage: MeasureBuild.py command [arguments ...]")
if not os.sched_getaffinity(0).issubset(set(range(12))):
raise SystemExit("run under taskset -c 0-11")
started = time.monotonic()
process = subprocess.Popen(sys.argv[1:], start_new_session=True)
peak_rss = (0, 0, "")
peak_vms = (0, 0, "")
while process.poll() is None:
for pid in descendants(process.pid):
measured = status(pid)
if measured is None:
continue
rss, vms, name = measured
if rss > peak_rss[0]:
peak_rss = (rss, pid, name)
if vms > peak_vms[0]:
peak_vms = (vms, pid, name)
time.sleep(0.1)
exit_code = process.wait()
elapsed = time.monotonic() - started
print(f"[BuildTreeResources] exit_code={exit_code} wall_s={elapsed:.3f} "
f"peak_process_rss_kib={peak_rss[0]} peak_rss_pid={peak_rss[1]} "
f"peak_rss_name={peak_rss[2]} peak_process_vms_kib={peak_vms[0]} "
f"peak_vms_pid={peak_vms[1]} peak_vms_name={peak_vms[2]} "
"monitor_interval_s=0.1", flush=True)
raise SystemExit(exit_code)
@@ -0,0 +1,100 @@
#!/usr/bin/env python3
"""Summarize exported tile geometry and upward-facing ground for L17."""
import array
import collections
import math
import struct
import sys
def read_exact(stream, size):
data = stream.read(size)
if len(data) != size:
raise EOFError(f"wanted {size} bytes, got {len(data)}")
return data
path = sys.argv[1]
with open(path, "rb") as stream:
if read_exact(stream, 8) != b"VFMSH001":
raise SystemExit("bad VFMSH001 header")
version, voxel_um, tile_count = struct.unpack("<3I", read_exact(stream, 12))
camera = struct.unpack("<3d", read_exact(stream, 24))
yaw, pitch = struct.unpack("<2d", read_exact(stream, 16))
per_lod = collections.defaultdict(lambda: {"tiles": 0, "vertices": 0,
"triangles": 0, "surface_triangles": 0, "up_triangles": 0,
"up_centroids": [], "tile_spans": [], "empty": 0})
tile_rows = []
for _ in range(tile_count):
key = struct.unpack("<4i", read_exact(stream, 16))
surf_vertices, skirt_count, vertex_count, index_count, mask_count = struct.unpack(
"<5I", read_exact(stream, 20))
p = array.array("f")
p.frombytes(read_exact(stream, vertex_count * 12))
n = array.array("f")
n.frombytes(read_exact(stream, vertex_count * 12))
if sys.byteorder != "little":
p.byteswap(); n.byteswap()
indices = array.array("I")
indices.frombytes(read_exact(stream, index_count * 4))
masks = read_exact(stream, mask_count)
if sys.byteorder != "little":
indices.byteswap()
lod = key[3]
row = per_lod[lod]
row["tiles"] += 1
row["vertices"] += vertex_count
row["triangles"] += index_count // 3
row["surface_triangles"] += index_count // 3 - skirt_count
up = []
for tri in range(index_count // 3):
if masks and masks[tri] != 0:
continue
a, b, c = indices[tri * 3:tri * 3 + 3]
if max(a, b, c) >= surf_vertices:
continue
if (n[a * 3 + 2] + n[b * 3 + 2] + n[c * 3 + 2]) / 3.0 < 0.65:
continue
up.append((p[a * 3 + 2] + p[b * 3 + 2] + p[c * 3 + 2]) / 3.0)
row["up_triangles"] += len(up)
row["up_centroids"].extend(up)
if up:
up.sort()
span = up[math.floor(0.95 * (len(up) - 1))] - up[math.floor(0.05 * (len(up) - 1))]
row["tile_spans"].append(span)
tile_rows.append((key, vertex_count, index_count // 3, skirt_count,
len(up), up[0], up[len(up) // 2], up[-1], span))
else:
row["empty"] += 1
tile_rows.append((key, vertex_count, index_count // 3, skirt_count,
0, math.nan, math.nan, math.nan, math.nan))
print(f"version={version} voxel_um={voxel_um} tiles={tile_count} camera_m={camera} yaw={yaw} pitch={pitch}")
print("lod tiles mesh_tiles vertices triangles skirt_triangles upward_surface_triangles "
"upward_centroid_z_m_p05/median/p95 tile_up_z_span_m_p50/p95/max")
for lod in sorted(per_lod):
r = per_lod[lod]
z = sorted(r["up_centroids"])
s = sorted(r["tile_spans"])
def q(values, fraction):
return values[math.floor(fraction * (len(values) - 1))] if values else math.nan
print(f"{lod} {r['tiles']} {r['tiles']-r['empty']} {r['vertices']} {r['triangles']} "
f"{sum(row[3] for row in tile_rows if row[0][3] == lod)} {r['up_triangles']} "
f"{q(z,.05):.4f}/{q(z,.5):.4f}/{q(z,.95):.4f} "
f"{q(s,.5):.4f}/{q(s,.95):.4f}/{max(s) if s else math.nan:.4f}")
print("near_camera_up_tiles (sorted by XY tile-centre distance):")
near = []
for row in tile_rows:
key = row[0]
lod = key[3]
extent = 32 * (1 << lod) * voxel_um / 1_000_000.0
cx = (key[0] + 0.5) * extent
cy = (key[1] + 0.5) * extent
near.append((math.hypot(cx-camera[0], cy-camera[1]), row))
for distance, row in sorted(near)[:30]:
key, verts, tris, skirts, ups, lo, med, hi, span = row
if ups:
print(f"key={key} xy_distance_m={distance:.2f} vertices={verts} triangles={tris} "
f"skirt_triangles={skirts} upward_triangles={ups} z_m={lo:.3f}/{med:.3f}/{hi:.3f} "
f"tile_span_p05_p95_m={span:.3f}")
@@ -0,0 +1,152 @@
#!/usr/bin/env python3
"""Trace review-image pixels through the exported clipmap mesh."""
import array
import math
import struct
import sys
def read_exact(f, count):
data = f.read(count)
if len(data) != count:
raise EOFError((len(data), count))
return data
def floor_div(n, d):
q, r = divmod(n, d)
return q - (r < 0)
def visible_mask(key, desired):
x, y, z, lod = key
extent = 32 << lod
origin = (x * extent, y * extent, z * extent)
mask = 0
for axis in range(3):
for side in range(2):
probe = [origin[i] + extent // 2 for i in range(3)]
probe[axis] = origin[axis] + (extent if side else -1)
neighbor = None
for level in range(9):
neighbor_extent = 32 << level
coord = tuple(floor_div(probe[i], neighbor_extent) for i in range(3)) + (level,)
if coord in desired:
neighbor = level
break
if neighbor is None or neighbor != lod:
mask |= 1 << (axis * 2 + side)
return mask
def ray_box(origin, direction, low, high):
lo, hi = 0.1, 20000.0
for axis in range(3):
if abs(direction[axis]) < 1e-12:
if origin[axis] < low[axis] or origin[axis] > high[axis]:
return None
else:
a = (low[axis] - origin[axis]) / direction[axis]
b = (high[axis] - origin[axis]) / direction[axis]
if a > b:
a, b = b, a
lo, hi = max(lo, a), min(hi, b)
if lo > hi:
return None
return lo, hi
def ray_tri(origin, direction, a, b, c):
e1 = tuple(b[i] - a[i] for i in range(3))
e2 = tuple(c[i] - a[i] for i in range(3))
p = (direction[1]*e2[2]-direction[2]*e2[1],
direction[2]*e2[0]-direction[0]*e2[2],
direction[0]*e2[1]-direction[1]*e2[0])
det = sum(e1[i]*p[i] for i in range(3))
if abs(det) < 1e-10:
return None
inv = 1.0/det
tvec = tuple(origin[i]-a[i] for i in range(3))
u = sum(tvec[i]*p[i] for i in range(3))*inv
if u < 0 or u > 1:
return None
q = (tvec[1]*e1[2]-tvec[2]*e1[1],
tvec[2]*e1[0]-tvec[0]*e1[2],
tvec[0]*e1[1]-tvec[1]*e1[0])
v = sum(direction[i]*q[i] for i in range(3))*inv
if v < 0 or u+v > 1:
return None
t = sum(e2[i]*q[i] for i in range(3))*inv
return t if t >= 0.1 else None
if len(sys.argv) < 8 or len(sys.argv) % 2 != 1:
raise SystemExit("usage: ProbeCaptureRays.py scene.vfmesh camX camY camZ yaw pitch pixelX pixelY [pixelX pixelY ...]")
scene_path = sys.argv[1]
cam = tuple(map(float, sys.argv[2:5]))
yaw, pitch = map(math.radians, map(float, sys.argv[5:7]))
pixels = [(int(sys.argv[i]), int(sys.argv[i+1])) for i in range(7, len(sys.argv), 2)]
width, height, fov = 1600, 900, math.radians(90)
tan_h = math.tan(fov/2)
tan_v = tan_h / (width/height)
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])
with open(scene_path, "rb") as f:
if read_exact(f, 8) != b"VFMSH001":
raise SystemExit("bad scene")
version, voxel_um, count = struct.unpack("<3I", read_exact(f, 12))
scene_cam = struct.unpack("<3d", read_exact(f, 24))
scene_yaw, scene_pitch = struct.unpack("<2d", read_exact(f, 16))
voxel_m = voxel_um / 1_000_000.0
tiles = []
desired = set()
for _ in range(count):
key = struct.unpack("<4i", read_exact(f, 16))
surface_vertices, skirt_count, vertex_count, index_count, mask_count = struct.unpack("<5I", read_exact(f, 20))
positions = array.array("f"); positions.frombytes(read_exact(f, vertex_count*12))
normals = array.array("f"); normals.frombytes(read_exact(f, vertex_count*12))
indices = array.array("I"); indices.frombytes(read_exact(f, index_count*4))
masks = read_exact(f, mask_count)
if sys.byteorder != "little":
positions.byteswap(); normals.byteswap(); indices.byteswap()
desired.add(key)
tiles.append((key, surface_vertices, skirt_count, positions, normals, indices, masks))
print(f"scene={scene_path} tiles={count} camera={cam} yaw={math.degrees(yaw):.3f} pitch={math.degrees(pitch):.3f}")
for px, py in pixels:
nx = 2*px/width - 1
ny = 1 - 2*py/height
direction0 = tuple(forward[i] + nx*tan_h*right[i] + ny*tan_v*up[i] for i in range(3))
length = math.sqrt(sum(v*v for v in direction0))
direction = tuple(v/length for v in direction0)
hits = []
candidate_tiles = 0
for key, surface_vertices, skirt_count, positions, normals, indices, masks in tiles:
x,y,z,lod = key
extent = 32*(1<<lod)*voxel_m
low = (x*extent, y*extent, z*extent)
high = tuple(v+extent for v in low)
if ray_box(cam, direction, low, high) is None:
continue
candidate_tiles += 1
show_mask = visible_mask(key, desired)
for tri in range(len(indices)//3):
m = masks[tri] if masks else 0
if m and (m & show_mask) == 0:
continue
ia,ib,ic = indices[tri*3:tri*3+3]
a,b,c = [],[],[]
for idx, out in ((ia,a),(ib,b),(ic,c)):
base=idx*3
out.extend((positions[base],positions[base+1],positions[base+2]))
t=ray_tri(cam,direction,a,b,c)
if t is None: continue
p=tuple(cam[i]+t*direction[i] for i in range(3))
nz=(normals[ia*3+2]+normals[ib*3+2]+normals[ic*3+2])/3
hits.append((t,key,tri,m,p,nz,ia>=surface_vertices or ib>=surface_vertices or ic>=surface_vertices))
hits.sort(key=lambda h:h[0])
print(f"pixel=({px},{py}) ray={direction} candidate_tiles={candidate_tiles} hits={len(hits)}")
for t,key,tri,m,p,nz,skirt in hits[:12]:
print(f" t={t:.3f}m key={key} tri={tri} mask={m} skirt={skirt} z={p[2]:.3f} normal_z={nz:.3f} xyz=({p[0]:.3f},{p[1]:.3f},{p[2]:.3f})")
@@ -0,0 +1,132 @@
# Linux18 — FloatingIslands stratum boundaries
**Result:** FloatingIslands geometry v3 removes its false lower all-XY seal, keeps the ceiling from the shared upper-boundary seal, and projects missing ceiling cells for every generator. SurfaceWorld hashes match the approved L14 baseline at `8704129` in every checked configuration.
## Scale in the Unreal route
`AVoxelWorld::CoreVoxelSizeCm` defaults to 25 cm, and `GetActiveVoxelSizeCm()` resolves the runtime value ([VoxelWorld.h:300](../../../../Source/VoxelForge/Public/VoxelWorld.h), [VoxelWorld.cpp:7495](../../../../Source/VoxelForge/Private/VoxelWorld.cpp)). The adapter writes the resolved size to the snapshot ([VoxelForgeCoreAdapter.cpp:1019](../../../../Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp)); `AVoxelWorld` imports it with `ResolvedWorldSnapshot::FromJson` ([VoxelWorld.cpp:7597](../../../../Source/VoxelForge/Private/VoxelWorld.cpp), [VoxelForgeCore.cpp:916](../../../../Standalone/Core/src/VoxelForgeCore.cpp)). S1 records this single active size source and the 32-Core-voxel tile rule ([Round5 REPORT.md](../Round5/REPORT.md#1-one-core-voxel-size-source)).
At the current 25 cm setting, **SurfaceWorld strata use 8 m per slot chunk**: the Unreal surface plan multiplies the slot range by 32 Core voxels ([VoxelForgeCoreAdapter.cpp:280](../../../../Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp); `CHUNK_SIZE=32` in [VoxelTypes.h:211](../../../../Source/VoxelForge/Public/VoxelTypes.h)). **FloatingIslands uses 4 m per owner-authored chunk**: the adapter’s owner size is `VOXEL_SIZE` ([VoxelForgeCoreAdapter.cpp:33](../../../../Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp); 12.5 cm in [VoxelTypes.h:213](../../../../Source/VoxelForge/Public/VoxelTypes.h)), and its plan multiplies 32 owner voxels by the serialized owner/Core ratio, currently 12.5/25 = 1/2, or 16 Core voxels ([VoxelForgeCoreAdapter.cpp:441](../../../../Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp)). For chunks 0…−15 that gives z bounds −60…+4 m. This is the two explicit coordinate conventions in the game route, not a global change from 8 m to 4 m.
Standalone uses the same owner/Core ratio and chunk formula ([OwnerWorldLoader.h:302](../../../../Standalone/Source/OwnerWorldLoader.h), [WorldAssemblyPrototype.h:488](../../../../Standalone/Source/WorldAssemblyPrototype.h)); no current Unreal/standalone scale disagreement was found. L18 also removed the standalone-only two-Core-voxel bounds inset so recipe bounds match the Unreal plan. The owner decides whether either authored scale changes.
## Fix and ceiling
Geometry version 3 preserves the island shapes and removes only the lower all-XY seal. The upper seal calls the shared `VF_ApplyUpperBoundarySeal` density primitive; the shared two-sided seal uses the same upper-band math ([VoxelDensityPrimitives.h:44](../../../../Source/VoxelForge/Public/VoxelDensityPrimitives.h)). Resolved bounds come from each strate’s own range. The shared `AppendStratumBoundaryCeilings` mesh path runs for every generator and LOD, adding a plane at the resolved upper boundary where the mesh lacks coverage. It suppresses an existing plane only when nine probes show full cell coverage ([ShapeWorldInternal.h:36](../../../../Standalone/Core/src/ShapeWorldInternal.h), [FarShellsInternal.h:894](../../../../Standalone/Core/src/FarShellsInternal.h), [VoxelForgeCore.cpp:2207](../../../../Standalone/Core/src/VoxelForgeCore.cpp)). The underside normal points into the room, so the existing material lighting renders the interior face. This adds no FloatingIslands-specific height.
For the captured FloatingIslands strate, the upper bound is **z=+4 m** and its configured 0.5 m seal gives an interior-facing ceiling surface beginning around **z=+3.5 m**. The density probe crosses into the seal at 3.5 m and exits at z=4.019 m; z=7.75, 8.0, and 8.25 m remain air with no crossing. The lower all-XY seal is removed; remaining geometry below −60 m belongs to the lower world and is visible in the ray traces. Thus the apparent 3.5 m “floor” is the retained ceiling when viewed from inside; the 8 m floor is absent. No chunk size changed.
The owner’s decision and version-3 recipe say an underground strate has a ceiling at its configured upper boundary. “Farther to breathe” comes from a taller configured range. I updated the stale architecture-table wording “open void” to say the islands sit beneath the shared ceiling.
## Ceiling continuity
The final audit used 16×16 interior samples per tile and paired samples along same-LOD tile edges. Far LODs pass continuously:
| LOD | Tiles with cap | Interior hits | Max root delta | Edge gaps |
|---:|---:|---:|---:|---:|
| 4 | 48/48 | 12,288/12,288 | 0 cm | 0 |
| 5 | 48/48 | 12,288/12,288 | 0 cm | 0 |
| 6 | 48/48 | 12,288/12,288 | 0 cm | 0 |
| 7 | 48/48 | 12,288/12,288 | 0 cm | 0 |
| 8 | 153/153 | 39,168/39,168 | 0 cm | 0 |
LOD0, 2, and 3 also hit all tested samples. LOD1 missed 3 of 12,288 interior samples (0.387 cm maximum root delta) and had no shared-edge gaps. The readable upward view is `captures/floating-islands-ceiling-up.jpg`.
## Dark tile-aligned patches
They are lower SurfaceWorld triangles visible below the removed false lower cap, not empty rays and not a viewer dark-square overlay. Traces in the final island export found:
- Pixel (700,720): first hit at 104.924 m on non-skirt LOD2 tile `(-2,2,-2,2)`, z=−62.925 m, normal-z 0.266; another LOD2 tile hits z=−69.942 m.
- Pixel (1050,650): first hit at 168.051 m on non-skirt LOD3 tile `(0,2,-2,3)`, z=−72.594 m, normal-z 0.895; lower LOD4 geometry is also hit at z=−112.229 m and −120 m.
The ring probe measures real LOD height steps on those lower surfaces. I left them visible: suppressing those triangles would hide valid lower-stratum geometry, while changing the lower strata or SurfaceWorld would exceed this local fix. The sampled rectangles are not missing tiles or floor holes.
## Cross-LOD ring steps
Entries are median / p95 / maximum absolute edge-height difference in centimetres, then gap count. The L18 column is the final matched island camera. The large remaining steps are lower-stratum terrain, not the upper ceiling.
| LOD edge | L17 before | L17 after | L18 final |
|---|---:|---:|---:|
| 0→1 | 3.451 / 8.920 / 449.158; 14 | —; 0 samples | —; 0 samples |
| 1→2 | 26.642 / 148.262 / 149.862; 0 | 0 / 0 / 0; 0 | 147.141 / 148.403 / 149.862; 0 |
| 2→3 | 0.134 / 198.730 / 773.318; 8 | 0 / 0 / 0; 0 | 128.603 / 200.789 / 773.318; 8 |
| 3→4 | 3.269 / 47.875 / 188.531; 27 | 0 / 0 / 0; 0 | 3.269 / 47.774 / 188.531; 27 |
| 4→5 | 29.975 / 130.496 / 445.444; 6 | 0 / 0 / 30.957; 1 | 29.992 / 124.373 / 443.482; 6 |
| 5→6 | 12.234 / 127.224 / 536.573; 32 | 12.234 / 127.224 / 536.573; 32 | 12.234 / 127.224 / 536.573; 21 |
| 6→7 | 29.013 / 301.752 / 42,624.479; 0 | 28.892 / 291.861 / 945.477; 0 | 29.013 / 301.752 / 42,624.479; 0 |
| 7→8 | 83.843 / 1,225.416 / 3,755.407; 32 | 83.843 / 1,225.416 / 3,755.407; 0 | 83.843 / 1,225.416 / 3,755.407; 0 |
## SurfaceWorld regression hashes
The 14-run check compared each final output to `Saved/Linux/l14-final-hash-sw-clang-off-w12-r5.log` from the L14/`8704129` baseline: Clang `-ffp-contract=off` and GCC default, W1/W8/W12, with five W12 repeats. All 14 matched the whole-view, full-detail, LOD0–8, and exact-query rows. The 24 m crossing check likewise passed all 14 compiler/worker configurations against their L14 compiler-specific references.
Whole view D/M/P/T: `c10697a8f48380c7 / e75c9119a1498cd5 / 49a9a8d1835a6c8c / edcb23288b5e8724`.
Full detail D/M/P/T: `0ee0579bd15cbbe4 / 32e5d4964d610fee / 1932b96ee4b1d910 / 69743c6bad3447dc`.
| LOD | Density | Mesh | Payload | Topology |
|---:|---|---|---|---|
| 0 | `769f2a706d585a72` | `33eeeb39fa443816` | `645621a1d968983f` | `c2457ed905036c9d` |
| 1 | `69cc5a5c0a2c60af` | `b48335f5410c57d5` | `d15c5480162ab8ba` | `feca1bd025823604` |
| 2 | `85deed5683a77202` | `53469895987aeea2` | `e55f8ae7e1532033` | `d5feb12a0e85c09b` |
| 3 | `6518caefb3be7e7f` | `ec513c90b853a614` | `f62fa1137ff17f82` | `ccf1dcf681965816` |
| 4 | `62d4be96021992f4` | `f1e4c6efcc2615a9` | `9dc5f9c88be02f33` | `fe6f55125a07c3bf` |
| 5 | `5e4a8c38b8cea6fc` | `08ee56998aaf21e5` | `541eea8558f26cc8` | `6539374a8b3336d8` |
| 6 | `e036e95b9006945b` | `624a33f941a99b02` | `81b73e14f68bdf86` | `4de4c63ce1f80a56` |
| 7 | `8ee358af56a30d8d` | `46ab3c6184048b7c` | `0a6164b7e68e80c5` | `0c26b85bb03bc831` |
| 8 | `c3458e1cbdb9c471` | `46c03d0994d75192` | `8f89fc5bde12d230` | `d16dc0cf62031898` |
Exact query: 9 probes; density/unclamped `e4d4a8eb95b35933`, detail `efcba20c0c70c415`, collision `e604823a249029bf`.
The 24 m crossing W12×5 hashes match those same aggregate whole-view, full-detail, and exact-query values on both compiler references; the full check compared all 12 rows per run.
FloatingIslands’ representative W12×5 whole-view D/M/P/T is `ca7f375cded78f1d / da003331c554f8f3 / 0d2e74862b4ee500 / b7a7177af8915a21`; full-detail is `fc71abf85f5b9373 / e139b32d42ecfa29 / 28a2414e41de59bd / d61ca68ca64f74ca`. Exact query is `4ef614485815ee30` density/unclamped, `efcba20c0c70c415` detail, `e604823a249029bf` collision. Clang and GCC representative hashes agree, and all nine LOD rows stayed stable across the 28-run matrix.
## Other invariants and Unreal edits
- Final matrix: 28/28 rows passed; repeated runs were stable within each compiler. Route parity had 0 mismatches across 782,026 FI and 910,651 SurfaceWorld exact samples. Proof audits had 0 violations: FI 9,048 blocks / 12,042,888 samples; SurfaceWorld 27,479 / 36,574,549. Readiness negative controls were caught; walkability passed 4,000/4,000 FI and 5,642/5,642 SurfaceWorld samples.
- L3 FP-contract preamble/self-test, L4 `FLazy`, L7 shared cache, L9/L12 proofs, L11 readiness/crash handling, L13 route binding, ARCHITECTURE §8.10, and async/MPSC/Epoch paths remain intact. Both final compiler runs started without the Core FP-contract self-test refusing the build. No async route was added.
- Unreal-side edits are limited to [VoxelForgeCoreAdapter.cpp](../../../../Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp:453), bumping FI geometry version 2→3 so serialized recipes pick up the lower-seal removal, and the shared [VoxelDensityPrimitives.h](../../../../Source/VoxelForge/Public/VoxelDensityPrimitives.h:44), extracting the common upper-seal math used by v3. The Unreal target could not be compiled on this Linux host.
## Captures
All JPEGs are below 300 KB. Before/after pairs use matched cameras: FI flat square `(-11.6124,-118.2212,-19.8452), yaw/pitch 0/0`; FI island `(-22,-19,-22.8), 0/−4`; SurfaceWorld spawn `(0,-35,-487.94), 0/−22`; horizon uses the same spawn camera with pitch 0. The ceiling view uses the island camera with pitch 80. The SurfaceWorld pairs look the same; the exact hash checks certify its output bit-for-bit.
- FI flat square: `captures/floating-islands-flat-square-before.jpg`, `captures/floating-islands-flat-square-after.jpg`.
- FI low island: `captures/floating-islands-island-view-before.jpg`, `captures/floating-islands-island-view-after.jpg`.
- FI upward ceiling: `captures/floating-islands-ceiling-up.jpg`.
- SurfaceWorld L1 spawn: `captures/surface-world-l1-spawn-before.jpg`, `captures/surface-world-l1-spawn-after.jpg`.
- SurfaceWorld horizon: `captures/surface-world-horizon-before.jpg`, `captures/surface-world-horizon-after.jpg`.
- Flat-square diagnostic without tile edges: `captures/floating-islands-flat-square-no-edges.jpg`.
## Failed or retried runs
- The first density-probe link omitted legacy compatibility objects and failed with undefined references. Relinking with the legacy implementation objects succeeded; both boundary probes then ran.
- The initial 4×4 continuity audit failed at far LODs 4–6 and found edge gaps. The shared all-LOD boundary cap and final full-cell coverage check replaced the center-only deduplication; final 16×16 LOD4–8 continuity passed with zero edge gaps.
- An early matrix checker compared Clang contraction-off hashes directly with GCC default hashes and reported FI mismatches. The checker was corrected to test repeated runs within each compiler and the L14 compiler-specific references; the final 28-row matrix passed.
- An early offscreen capture drew all skirts and showed an artificial slab. The capture now applies the viewer’s hidden-skirt mask; final matched captures were regenerated.
- A GCC build interrupted by owner steering produced no binary and was rerun. A pre-correction Clang build was superseded by the final build. No final build or matrix run failed or exceeded the runtime limits.
## Resources and owner note
Final builds: Clang 19 exit 0 in 31.689 s, peak RSS 436,088 KiB; GCC 14 exit 0 in 48.648 s, peak RSS 903,168 KiB. The largest measured runtime process was 897,276 KiB RSS. All processes stayed below the 6 GiB address-space cap; CPU affinity was 0–11, worker maximum 12, normal priority, and governor `powersave`. No run exceeded 30 minutes or stalled for 15 minutes. See [BuildResources.md](BuildResources.md).
**Owner note:** SurfaceWorld stayed bit-identical to L14. FloatingIslands now retains a ceiling at its configured upper strate boundary while removing the false lower all-XY floor. At the current plan that is a z=+4 m boundary with a 0.5 m seal; its distance above the islands comes from the configured range. The small Unreal edits are listed above and need an owner-machine UBT compile.
## Orchestrator review (Claude)
- **Merged.** SurfaceWorld is bit-identical (14 configs plus the 24 m crossing); the FI false all-XY lower seal
is removed; the ceiling comes from the shared, strate-parameter upper-boundary seal and ceiling path
(owner rule: every strate has a ceiling, height from its own parameters). The Unreal edits are a pure
extraction of the existing upper-seal math plus the FI `geometryVersion` 2→3; they need a UBT compile on
the owner PC.
- **The scale answer:** both conventions are deliberate and agree between Unreal and the standalone
(SurfaceWorld 32 Core voxels per chunk; FloatingIslands 32 owner voxels = 16 Core voxels). Nothing
rescaled.
- **Still open (L19):**
1. Small black holes in the FI ceiling, visible near the top of `floating-islands-flat-square-after.jpg`;
compare LOD1 missing 3/12,288 continuity samples.
2. The owner's ledges are most likely the LOWER stratum's terrain seen through the floorless FI void:
ring steps of 147 cm median at LOD1→2 and 129 cm at 2→3 remain in that terrain. "Valid lower-stratum
geometry" with metre-scale cross-LOD steps is still a bug the owner can see.
- Exports (229 MB `.vfmesh`) are not committed.
@@ -0,0 +1,151 @@
#!/usr/bin/env python3
"""Compare upward mesh heights on shared XY edges between adjacent LOD rings."""
import array
import collections
import math
import struct
import sys
def read_scene(path):
tiles = []
with open(path, "rb") as f:
if f.read(8) != b"VFMSH001":
raise SystemExit(f"{path}: invalid mesh header")
version, voxel_um, count = struct.unpack("<3I", f.read(12))
camera = struct.unpack("<3d", f.read(24))
yaw, pitch = struct.unpack("<2d", f.read(16))
voxel_m = voxel_um / 1_000_000.0
for _ in range(count):
key = struct.unpack("<4i", f.read(16))
surface_vertices, skirt_count, vertex_count, index_count, mask_count = struct.unpack(
"<5I", f.read(20))
positions = array.array("f")
positions.frombytes(f.read(vertex_count * 12))
normals = array.array("f")
normals.frombytes(f.read(vertex_count * 12))
indices = array.array("I")
indices.frombytes(f.read(index_count * 4))
masks = f.read(mask_count)
if sys.byteorder != "little":
positions.byteswap(); normals.byteswap(); indices.byteswap()
triangles = []
for i in range(0, index_count, 3):
if masks and masks[i // 3] != 0:
continue
a, b, c = indices[i:i + 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.65:
continue
tri = tuple((positions[j * 3], positions[j * 3 + 1], positions[j * 3 + 2])
for j in (a, b, c))
triangles.append(tri)
if triangles:
lod = key[3]
extent = 32 * (1 << lod) * voxel_m
tiles.append({"key": key, "lod": lod, "extent": extent,
"x0": key[0] * extent, "y0": key[1] * extent,
"x1": (key[0] + 1) * extent, "y1": (key[1] + 1) * extent,
"z0": key[2] * extent, "z1": (key[2] + 1) * extent,
"triangles": triangles})
return camera, yaw, pitch, tiles
def height_at(tile, x, y):
best = None
for tri in tile["triangles"]:
(x0, y0, z0), (x1, y1, z1), (x2, y2, z2) = tri
den = (y1 - y2) * (x0 - x2) + (x2 - x1) * (y0 - y2)
if abs(den) < 1.0e-10:
continue
a = ((y1 - y2) * (x - x2) + (x2 - x1) * (y - y2)) / den
b = ((y2 - y0) * (x - x2) + (x0 - x2) * (y - y2)) / den
c = 1.0 - a - b
if min(a, b, c) >= -1.0e-5:
z = a * z0 + b * z1 + c * z2
if best is None or z > best:
best = z
return best
path = sys.argv[1]
camera, yaw, pitch, tiles = read_scene(path)
by_lod = collections.defaultdict(list)
for tile in tiles:
by_lod[tile["lod"]].append(tile)
print(f"scene={path} camera_m={camera} yaw={yaw} pitch={pitch} nonempty_mesh_tiles={len(tiles)}")
print("lod_pair shared_edges samples_both median_abs_cm p95_abs_cm max_abs_cm median_delta_cm gaps")
for lod in range(8):
fine = by_lod[lod]
coarse = by_lod[lod + 1]
deltas = []
edge_rows = []
edge_count = 0
gap_count = 0
# Shared XY edges occur only where tile extents touch and overlap on the other axis.
for a in fine:
for b in coarse:
zlo, zhi = max(a["z0"], b["z0"]), min(a["z1"], b["z1"])
if zhi - zlo <= 1.0e-4:
continue
edge = None
if abs(a["x1"] - b["x0"]) < 1.0e-4 or abs(b["x1"] - a["x0"]) < 1.0e-4:
lo, hi = max(a["y0"], b["y0"]), min(a["y1"], b["y1"])
if hi - lo > 1.0e-4:
edge = ("x", a["x1"] if abs(a["x1"] - b["x0"]) < 1.0e-4 else a["x0"], lo, hi)
if edge is None and (abs(a["y1"] - b["y0"]) < 1.0e-4 or abs(b["y1"] - a["y0"]) < 1.0e-4):
lo, hi = max(a["x0"], b["x0"]), min(a["x1"], b["x1"])
if hi - lo > 1.0e-4:
edge = ("y", a["y1"] if abs(a["y1"] - b["y0"]) < 1.0e-4 else a["y0"], lo, hi)
if edge is None:
continue
edge_count += 1
axis, fixed, lo, hi = edge
epsilon = min(0.02, a["extent"] * 0.001, b["extent"] * 0.001)
samples = 9
matched = 0
edge_deltas = []
for i in range(samples):
t = (i + 0.5) / samples
varying = lo + (hi - lo) * t
if axis == "x":
side_a = 1.0 if abs(a["x1"] - fixed) < 1.0e-4 else -1.0
side_b = 1.0 if abs(b["x1"] - fixed) < 1.0e-4 else -1.0
za = height_at(a, fixed - side_a * epsilon, varying)
zb = height_at(b, fixed - side_b * epsilon, varying)
else:
side_a = 1.0 if abs(a["y1"] - fixed) < 1.0e-4 else -1.0
side_b = 1.0 if abs(b["y1"] - fixed) < 1.0e-4 else -1.0
za = height_at(a, varying, fixed - side_a * epsilon)
zb = height_at(b, varying, fixed - side_b * epsilon)
if za is not None and not zlo - 1.0e-3 <= za <= zhi + 1.0e-3:
za = None
if zb is not None and not zlo - 1.0e-3 <= zb <= zhi + 1.0e-3:
zb = None
if za is not None and zb is not None:
delta = (za - zb) * 100.0
deltas.append(delta)
edge_deltas.append((delta, za, zb, varying))
matched += 1
if matched == 0:
gap_count += 1
elif edge_deltas:
edge_rows.append((max(abs(row[0]) for row in edge_deltas),
a["key"], b["key"], edge, edge_deltas))
if deltas:
absolute = sorted(abs(x) for x in deltas)
signed = sorted(deltas)
p95 = absolute[math.floor(0.95 * (len(absolute) - 1))]
median_abs = absolute[len(absolute) // 2]
median_delta = signed[len(signed) // 2]
print(f"{lod}->{lod+1} {edge_count} {len(deltas)} {median_abs:.3f} {p95:.3f} {max(absolute):.3f} {median_delta:.3f} {gap_count}")
for worst, fine_key, coarse_key, edge, rows in sorted(edge_rows, reverse=True)[:2]:
row = max(rows, key=lambda item: abs(item[0]))
print(f" worst_edge max_abs_cm={worst:.3f} fine={fine_key} coarse={coarse_key} "
f"axis={edge[0]} fixed_m={edge[1]:.3f} span_m={edge[2]:.3f}..{edge[3]:.3f} "
f"sample_v_m={row[3]:.3f} fine_z_m={row[1]:.3f} "
f"coarse_z_m={row[2]:.3f} delta_cm={row[0]:.3f}")
else:
print(f"{lod}->{lod+1} {edge_count} 0 n/a n/a n/a n/a {gap_count}")
@@ -0,0 +1,82 @@
#!/usr/bin/env python3
"""Run the L18 24 m SurfaceWorld crossing hash matrix."""
import os
import re
import signal
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l18-crossing"
OUT.mkdir(parents=True, exist_ok=True)
if not os.sched_getaffinity(0).issubset(set(range(12))):
raise SystemExit("run under taskset -c 0-11")
BINARIES = {
"clang-off": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-linux18-clang",
"gcc-default": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-linux18-gcc",
}
PATTERN = re.compile(r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
rows = []
passed = True
for compiler, binary in BINARIES.items():
for workers, repeat in [(1, 1), (8, 1), (12, 1), (12, 2), (12, 3), (12, 4), (12, 5)]:
name = f"{compiler}-w{workers}-r{repeat}"
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=0,-35,-487.94,0,-22",
"--camera-step=24,0,0", "--region-crossing", "--recipe=surface-world"]
started = time.monotonic()
peak_rss = 0
with logfile.open("w", encoding="utf-8") as stream:
process = subprocess.Popen(command, stdout=stream, stderr=subprocess.STDOUT,
start_new_session=True)
while process.poll() is None:
pending = [process.pid]
seen = set()
while pending:
pid = pending.pop()
if pid in seen:
continue
seen.add(pid)
try:
pending.extend(int(value) for value in
Path(f"/proc/{pid}/task/{pid}/children").read_text().split())
for line in Path(f"/proc/{pid}/status").read_text().splitlines():
if line.startswith("VmRSS:"):
peak_rss = max(peak_rss, int(line.split()[1]))
break
except (FileNotFoundError, PermissionError, ProcessLookupError, ValueError):
pass
if time.monotonic() - started > 900:
os.killpg(process.pid, signal.SIGTERM)
try:
process.wait(timeout=5)
except subprocess.TimeoutExpired:
os.killpg(process.pid, signal.SIGKILL)
process.wait()
passed = False
break
time.sleep(0.1)
code = process.wait()
elapsed = time.monotonic() - started
actual = PATTERN.findall(logfile.read_text(encoding="utf-8", errors="replace"))
ok = code == 0 and len(actual) == 12
passed &= ok
rows.append((compiler, workers, repeat, code, elapsed, peak_rss, ok, actual))
print(f"[CrossingRun] {name} exit={code} seconds={elapsed:.3f} "
f"peak_rss_kib={peak_rss} hash_rows={len(actual)} status={'PASS' if ok else 'FAIL'}",
flush=True)
reference = None
for compiler, workers, repeat, code, elapsed, peak_rss, ok, hashes in rows:
if reference is None:
reference = hashes
elif hashes != reference:
passed = False
print(f"[CrossingHashMismatch] {compiler} workers={workers} repeat={repeat}", flush=True)
print(f"[CrossingSummary] rows={len(rows)} expected=14 result={'PASS' if passed else 'FAIL'} "
f"path={OUT}", flush=True)
raise SystemExit(0 if passed and len(rows) == 14 else 1)
@@ -0,0 +1,132 @@
#!/usr/bin/env python3
"""Run the L18 deterministic W1/W8/W12 x5 matrix on both supported Linux builds."""
import os
import re
import signal
import subprocess
import sys
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l18-matrix"
OUT.mkdir(parents=True, exist_ok=True)
if not os.sched_getaffinity(0).issubset(set(range(12))):
raise SystemExit("run under taskset -c 0-11")
BINARIES = {
"clang-off": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-linux18-clang",
"gcc-default": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-linux18-gcc",
}
RECIPES = {
"floating-islands": "-10,-31,-22,0,-8",
"surface-world": "0,-35,-487.94,0,-22",
}
HASH_PATTERNS = {
"full": re.compile(r"^\[PipelineFullDetailHash\].*$", re.M),
"whole": re.compile(r"^\[PipelineWholeViewHash\].*$", re.M),
"lod": re.compile(r"^\[PipelineLODHash\].*$", re.M),
"exact": re.compile(r"^\[PipelineExactQueryHash\].*$", re.M),
}
rows = []
all_pass = True
for recipe, camera in RECIPES.items():
for compiler, binary in BINARIES.items():
if not binary.exists():
raise SystemExit(f"missing binary: {binary}")
for workers, repeat in [(1, 1), (8, 1), (12, 1), (12, 2), (12, 3), (12, 4), (12, 5)]:
name = f"{recipe}-{compiler}-w{workers}-r{repeat}"
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}", f"--camera={camera}",
f"--recipe={recipe}"]
started = time.monotonic()
peak_rss_kib = 0
with logfile.open("w", encoding="utf-8") as stream:
process = subprocess.Popen(command, stdout=stream, stderr=subprocess.STDOUT,
start_new_session=True)
while process.poll() is None:
pending = [process.pid]
seen = set()
while pending:
pid = pending.pop()
if pid in seen:
continue
seen.add(pid)
try:
children = Path(f"/proc/{pid}/task/{pid}/children").read_text().split()
pending.extend(int(value) for value in children)
for line in Path(f"/proc/{pid}/status").read_text().splitlines():
if line.startswith("VmRSS:"):
peak_rss_kib = max(peak_rss_kib, int(line.split()[1]))
break
except (FileNotFoundError, PermissionError, ProcessLookupError, ValueError):
pass
if time.monotonic() - started > 900:
os.killpg(process.pid, signal.SIGTERM)
try:
process.wait(timeout=5)
except subprocess.TimeoutExpired:
os.killpg(process.pid, signal.SIGKILL)
process.wait()
all_pass = False
break
time.sleep(0.1)
code = process.wait()
elapsed = time.monotonic() - started
output = logfile.read_text(encoding="utf-8", errors="replace")
matched = {key: pattern.findall(output) for key, pattern in HASH_PATTERNS.items()}
ok = code == 0 and all(matched.values()) and len(matched["lod"]) == 9
if not ok:
all_pass = False
row = {"recipe": recipe, "compiler": compiler, "workers": workers,
"repeat": repeat, "exit": code, "seconds": elapsed,
"peak_rss_kib": peak_rss_kib, "ok": ok,
"full": matched["full"][-1] if matched["full"] else "",
"whole": matched["whole"][-1] if matched["whole"] else "",
"lod": " || ".join(matched["lod"]),
"exact": matched["exact"][-1] if matched["exact"] else ""}
rows.append(row)
print(f"[MatrixRun] {name} exit={code} seconds={elapsed:.3f} "
f"peak_rss_kib={peak_rss_kib} hash_rows={sum(map(len,matched.values()))} "
f"status={'PASS' if ok else 'FAIL'}", flush=True)
summary = OUT / "summary.tsv"
columns = ["recipe", "compiler", "workers", "repeat", "exit", "seconds", "peak_rss_kib",
"ok", "full", "whole", "lod", "exact"]
with summary.open("w", encoding="utf-8") as f:
f.write("\t".join(columns) + "\n")
for row in rows:
f.write("\t".join(str(row[key]) for key in columns) + "\n")
for recipe in RECIPES:
for compiler in BINARIES:
reference = None
for row in rows:
if row["recipe"] != recipe or row["compiler"] != compiler:
continue
signature = (row["full"], row["whole"], row["lod"], row["exact"])
if reference is None:
reference = signature
elif signature != reference:
print(f"[MatrixMismatch] recipe={recipe} workers={row['workers']} "
f"compiler={compiler} repeat={row['repeat']}", flush=True)
all_pass = False
# Compiler defaults are intentionally recorded separately: Clang uses contraction off,
# while GCC default is the owner-requested control. Report rather than hide any cross-build
# difference; worker/repeat determinism is asserted within each compiler above.
compiler_signatures = {}
for row in rows:
if row["recipe"] == recipe:
compiler_signatures.setdefault(row["compiler"],
(row["full"], row["whole"], row["lod"], row["exact"]))
if len(set(compiler_signatures.values())) > 1:
print(f"[MatrixCompilerDifference] recipe={recipe} compilers="
f"{','.join(compiler_signatures)}", flush=True)
print(f"[MatrixSummary] rows={len(rows)} expected=28 result={'PASS' if all_pass else 'FAIL'} "
f"path={summary}", flush=True)
raise SystemExit(0 if all_pass and len(rows) == 28 else 1)
Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

+92 -2
View File
@@ -16,6 +16,7 @@
#include <limits>
#include <string>
#include <string_view>
#include <unordered_set>
#include <vector>
namespace
@@ -38,10 +39,69 @@ namespace
float MaximumHeight=-std::numeric_limits<float>::infinity();
std::vector<FVertex> Vertices;
std::vector<std::uint32_t> Indices;
struct FTileRange
{
std::array<std::int32_t,4> Key{};
std::size_t IndexBegin=0;
std::uint32_t IndexCount=0;
std::vector<std::uint8_t> FaceMasks;
};
std::vector<FTileRange> Tiles;
std::uint64_t Triangles=0,SkirtTriangles=0;
std::uint64_t VisibleSkirtTriangles=0,HiddenSameLodSkirtTriangles=0;
std::uint64_t DrawnTriangles=0;
std::uint32_t TileCount=0;
};
struct FTileKeyHash
{
std::size_t operator()(const std::array<std::int32_t,4>& Key) const noexcept
{
std::size_t Hash=0xcbf29ce484222325ull;
for(const std::int32_t Value:Key)
{
Hash^=static_cast<std::uint32_t>(Value);
Hash*=0x100000001b3ull;
}
return Hash;
}
};
std::int32_t FloorDiv(std::int64_t Value,std::int64_t Divisor)
{
const std::int64_t Quotient=Value/Divisor, Remainder=Value%Divisor;
return static_cast<std::int32_t>(Quotient-((Remainder<0)?1:0));
}
std::uint8_t ComputeVisibleSkirtFaceMask(
const std::array<std::int32_t,4>& Tile,
const std::unordered_set<std::array<std::int32_t,4>,FTileKeyHash>& Desired)
{
const std::int64_t Extent=static_cast<std::int64_t>(32)<<Tile[3];
const std::int64_t Origin[3]={static_cast<std::int64_t>(Tile[0])*Extent,
static_cast<std::int64_t>(Tile[1])*Extent,static_cast<std::int64_t>(Tile[2])*Extent};
std::uint8_t Mask=0;
for(std::int32_t Axis=0;Axis<3;++Axis)for(std::int32_t Side=0;Side<2;++Side)
{
std::int64_t Probe[3]={Origin[0]+Extent/2,Origin[1]+Extent/2,
Origin[2]+Extent/2};
Probe[Axis]=Side?Origin[Axis]+Extent:Origin[Axis]-1;
std::int32_t NeighborLevel=-1;
for(std::int32_t Level=0;Level<=8;++Level)
{
const std::int64_t NeighborExtent=static_cast<std::int64_t>(32)<<Level;
const std::array<std::int32_t,4> Neighbor={
FloorDiv(Probe[0],NeighborExtent),FloorDiv(Probe[1],NeighborExtent),
FloorDiv(Probe[2],NeighborExtent),Level};
if(Desired.contains(Neighbor))
{NeighborLevel=Level;break;}
}
if(NeighborLevel<0||NeighborLevel!=Tile[3])
Mask|=static_cast<std::uint8_t>(1u<<(Axis*2+Side));
}
return Mask;
}
template<typename T> bool Read(std::ifstream& File,T& Value)
{
File.read(reinterpret_cast<char*>(&Value),sizeof(Value));
@@ -126,11 +186,36 @@ namespace
}
Scene.Triangles+=IndexCount/3;
Scene.SkirtTriangles+=SkirtCount;
Scene.Tiles.push_back({{Key[0],Key[1],Key[2],Key[3]},IndexOffset,
IndexCount,std::move(Masks)});
TotalVertices+=VertexCount;TotalIndices+=IndexCount;
}
if(File.peek()!=std::char_traits<char>::eof())
{Error="mesh scene has trailing bytes";return false;}
if(Scene.Indices.empty()){Error="mesh scene contains no triangles";return false;}
std::unordered_set<std::array<std::int32_t,4>,FTileKeyHash> Desired;
Desired.reserve(Scene.Tiles.size());
for(const auto& Tile:Scene.Tiles)Desired.insert(Tile.Key);
std::vector<std::uint32_t> VisibleIndices;
VisibleIndices.reserve(Scene.Indices.size());
for(const auto& Tile:Scene.Tiles)
{
const std::uint8_t VisibleMask=ComputeVisibleSkirtFaceMask(Tile.Key,Desired);
for(std::uint32_t Triangle=0;Triangle<Tile.IndexCount/3;++Triangle)
{
const std::size_t MaskIndex=Triangle;
const std::uint8_t FaceMask=MaskIndex<Tile.FaceMasks.size()
?Tile.FaceMasks[MaskIndex]:0;
if(FaceMask!=0&&(FaceMask&VisibleMask)==0)
{++Scene.HiddenSameLodSkirtTriangles;continue;}
if(FaceMask!=0)++Scene.VisibleSkirtTriangles;
const std::size_t Index=Tile.IndexBegin+static_cast<std::size_t>(Triangle)*3;
VisibleIndices.insert(VisibleIndices.end(),Scene.Indices.begin()+Index,
Scene.Indices.begin()+Index+3);
}
}
Scene.Indices=std::move(VisibleIndices);
Scene.DrawnTriangles=Scene.Indices.size()/3;
return true;
}
@@ -399,9 +484,14 @@ void main()
{
std::error_code SizeError;
const auto Bytes=std::filesystem::file_size(Output,SizeError);
std::printf("[OffscreenCapture] triangles=%llu tiles=%u skirts=%llu shading=%s tile_edges=%s edge_width_m=%.3f png_bytes=%llu status=PASS path=%s\n",
static_cast<unsigned long long>(Scene.Triangles),Scene.TileCount,
std::printf("[OffscreenCapture] source_triangles=%llu drawn_triangles=%llu tiles=%u "
"skirts=%llu visible_skirt_triangles=%llu hidden_same_lod_skirt_triangles=%llu "
"shading=%s tile_edges=%s edge_width_m=%.3f png_bytes=%llu status=PASS path=%s\n",
static_cast<unsigned long long>(Scene.Triangles),
static_cast<unsigned long long>(Scene.DrawnTriangles),Scene.TileCount,
static_cast<unsigned long long>(Scene.SkirtTriangles),
static_cast<unsigned long long>(Scene.VisibleSkirtTriangles),
static_cast<unsigned long long>(Scene.HiddenSameLodSkirtTriangles),
ReadableMode?"readable":"normal",TileEdges?"on":"off",TileEdgeWidthMeters,
static_cast<unsigned long long>(SizeError?0:Bytes),Output.string().c_str());
}
@@ -41,21 +41,27 @@
// SealFactor > 0 et BaseDensity > 0, le résultat est solide GARANTI quelle qu'ait été l'entrée.
// C'est exactement ce qu'encode `IVoxelDensityOp::ClassifyBox` (voir VoxelDensityOp.h), et la
// raison pour laquelle cette méthode existe.
FORCEINLINE void VF_ApplyBoundarySeal(float& Density, float WorldZ,
float StrateTopZ, float StrateBottomZ,
float Thickness, float BaseDensity)
FORCEINLINE void VF_ApplyUpperBoundarySealValue(float& Density, float WorldZ,
float StrateTopZ, float Thickness, float BaseDensity)
{
if (VoxelDensityAblation::IsBoundarySealOff() || Thickness <= 0.0f) return;
const float DistTop = StrateTopZ - WorldZ; // + si on est sous le plafond
const float DistBot = WorldZ - StrateBottomZ; // + si on est au-dessus du sol
const float DistTop = StrateTopZ - WorldZ;
if (DistTop >= 0.0f && DistTop < Thickness)
{
float SealFactor = 1.0f - (DistTop / Thickness);
SealFactor = SmoothStep01(SealFactor);
Density = FMath::Max(Density, SealFactor * BaseDensity);
}
}
FORCEINLINE void VF_ApplyBoundarySeal(float& Density, float WorldZ,
float StrateTopZ, float StrateBottomZ,
float Thickness, float BaseDensity)
{
if (VoxelDensityAblation::IsBoundarySealOff() || Thickness <= 0.0f) return;
const float DistBot = WorldZ - StrateBottomZ; // + si on est au-dessus du sol
VF_ApplyUpperBoundarySealValue(Density, WorldZ, StrateTopZ, Thickness, BaseDensity);
if (DistBot >= 0.0f && DistBot < Thickness)
{
float SealFactor = 1.0f - (DistBot / Thickness);
@@ -64,6 +70,16 @@ FORCEINLINE void VF_ApplyBoundarySeal(float& Density, float WorldZ,
}
}
// The same structural seal, limited to a strate's upper boundary. Native finite fields use this
// when their lower boundary intentionally has no seal; keeping the math here makes that ceiling
// use the same shared density primitive as every two-sided strate seal.
FORCEINLINE void VF_ApplyUpperBoundarySeal(float& Density, float WorldZ,
float StrateTopZ, float Thickness, float BaseDensity)
{
if (VoxelDensityAblation::IsBoundarySealOff() || Thickness <= 0.0f) return;
VF_ApplyUpperBoundarySealValue(Density, WorldZ, StrateTopZ, Thickness, BaseDensity);
}
//=============================================================================
// CARVE DE PASSAGE / PASSAGE CARVING
//=============================================================================
+2 -4
View File
@@ -494,11 +494,9 @@ namespace StandaloneWorldAssemblyPrototype
const std::int64_t coreVoxelsPerOwnerChunk=static_cast<std::int64_t>(CHUNK_SIZE)
*recipe.ownerToCoreScaleNumerator/recipe.ownerToCoreScaleDenominator;
const std::int64_t minimumZ=static_cast<std::int64_t>(islandSlot->BottomChunkZ)
*coreVoxelsPerOwnerChunk*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel
+2*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
*coreVoxelsPerOwnerChunk*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
const std::int64_t maximumZ=static_cast<std::int64_t>(islandSlot->TopChunkZ+1)
*coreVoxelsPerOwnerChunk*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel
-2*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
*coreVoxelsPerOwnerChunk*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
if(maximumZ<=minimumZ)
{error="FloatingIslands strate is too short for a bounded Core recipe";return false;}
recipe.layoutMinimumZSubvoxels=minimumZ;