wip(assembler): round S4 -- continuous far ceiling at the strate's upper boundary

S3's far cap sat up to 8.2 m above the near roof and read as floating pieces.
The far render cap now projects onto the owning strate's upper boundary
(density unchanged), on a global XY lattice, LOD4 16-voxel grid + LOD5-8
128-voxel. Cap agreement 0.05 cm (100 cm control detected), 2520/2520 upward
rays hit roof on the owner walk. Far recipe v2; LOD0-3 hashes equal S2/S3.
Tile p95 flat; cold clipmap +4.4%. Far-cliff fluting left open (coarse MC step).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-19 00:41:13 +02:00
1 parent d0f97bf79a
commit 9d3c50ca95
14 files changed
+1376 -34

No files matched your search

+40
View File
@@ -1395,6 +1395,46 @@ measure eight warmed keys per LOD and compare the shared LOD7/8 surface lattice
density roots, including a 100 cm datum-offset negative control. See density roots, including a 100 cm datum-offset negative control. See
`Standalone/Research/WorldAssemblerM3/Round7/REPORT.md` for results and the visual evidence limits. `Standalone/Research/WorldAssemblerM3/Round7/REPORT.md` for results and the visual evidence limits.
### 8.25 Continuous planar ceiling — World Assembler S4 / M3 Round 8
The cap remains a family declaration in `WorldAssemblyPlanV1::strates` and is evaluated by the shared
Core heightfield path. `Heightfield2D::farSurfaceCapHeightSubvoxels()` clips only the rendered far
surface to `(topChunkZ + 1) * 32` voxels, the owning strate's upper boundary; it leaves
`Heightfield2D::point()` and its density values unchanged. `capHeightBounds()` reports conservative
bounds for that clipped surface. Evaluator v5 opts into this render-surface contract; older serialized
evaluators keep their recorded cap behavior. The clip joins the exact near density root at the owning
boundary without changing LOD0–3 density hashes. Future families with planar roofs must declare their
cap height and bounds through the family plan/provider contract; the shared tile generator consumes
that declaration. FloatingIslands declares no planar roof, so open sky above a surface island is the
expected view.
For each requested far SurfaceWorld XY column at LOD4–8, desired-tile selection adds cap-height
bands only when they do not overlap an existing or already accepted 3D clipmap tile. The original
clipmap remains a non-overlapping partition. Cap samples use a global XY lattice, are clipped to the
owned strate boundary, tile Z bounds and `cap >= ground`, and share edges across tiles. LOD4 uses a 16-voxel cap step;
LOD5–8 use 128 voxels. Core-off stays on its legacy path, and edits continue through the authoritative
edited field. Runtime material sections are explicitly created/updated from their ground/cap index
ranges so a cap-only far tile cannot rely on an auto-created section being present.
The far recipe version is 2. It salts mesh, payload and topology hashes for LOD4–8; density hashes
and LOD0–LOD3 identities do not change. The owner audit counts requested cap bands and emitted
positive-area bands, compares 128 shared cap points with the near density root at 2 cm tolerance,
checks a 100 cm datum-offset negative control, and traces upward rays from route and camera points
against emitted triangles. Round 8 records these counters, the matched captures, warmed BuildHost
timings, clipmap overlap validation and remaining failures in
`Standalone/Research/WorldAssemblerM3/Round8/REPORT.md`.
The Round 8 owner walk audit emitted every requested cap band at LOD4–8 (385/385), agreed with 128
near roots to 0.0504 cm (2 cm gate), detected a 100 cm offset control, and hit the cap on all 2,520
upward rays from 35 walk/camera points. No route ray missed the cap or clipmap boundary.
The far-cliff fluting is coarse marching-cubes geometry on steep faces: with 16 coarse cells per tile,
LOD7 samples every 256 voxels (64 m at 25 cm) and LOD8 every 512 voxels (128 m). The native ground
replacement removes only upward-facing coarse faces; near-vertical faces remain on that lattice.
LOD7/8 shell repair is skipped for certified SurfaceWorld tiles, the renderer does not cull
backfaces, and the strips predate S4 in S2/S3. Refining all steep faces would multiply far mesh work;
S4 leaves that cost/quality tradeoff open.
## 9. Multiplayer model (listen-server first, dedicated-friendly) ## 9. Multiplayer model (listen-server first, dedicated-friendly)
> **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a > **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a
+13
View File
@@ -979,6 +979,19 @@ time, repair field calls, skipped repairs and replaced triangles. `farHorizonVer
mesh/payload/topology hashes only. The BuildHost commandlet owns warmed eight-key timings and the mesh/payload/topology hashes only. The BuildHost commandlet owns warmed eight-key timings and the
exact-density-root LOD7/8 agreement audit with its datum-offset negative control. exact-density-root LOD7/8 agreement audit with its datum-offset negative control.
World Assembler S4 repairs the planar cap datum and far tile selection. `Heightfield2D::farSurfaceCapHeightSubvoxels`
clips the rendered cap to `(topChunkZ + 1) * 32` voxels while preserving `Heightfield2D::point` density;
`capHeightBounds` provides the matching conservative bands. `World::desiredTiles` adds only non-overlapping
cap-height keys at LOD4–8, and `RefineFarNativeCap` emits the globally aligned cap surface (16 voxels at
LOD4, 128 at LOD5–8). Evaluator v5 opts into this render-surface contract; older serialized evaluator
versions retain their cap behavior. Far recipe v2 salts mesh/payload/topology from LOD4 upward. The
standalone viewer audit measures cap-band census, near-root agreement and upward-ray coverage; the editor
BuildHost check measures the staged owner actor and W1/W8/W16. `VoxelWorld::ApplyMeshToTile` uses explicit
section ranges for ground and cap. Likely compile spots are the Core public/internal heightfield API and
`VoxelForgeCore.cpp`, `VoxelWorld.cpp`, the editor commandlet, and the standalone viewer. See
ARCHITECTURE §8.25 and
[`Standalone/Research/WorldAssemblerM3/Round8/REPORT.md`](Standalone/Research/WorldAssemblerM3/Round8/REPORT.md).
## 4. The density pipeline (most-edited hot path) ## 4. The density pipeline (most-edited hot path)
### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749 ### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749
+14 -4
View File
@@ -24,6 +24,7 @@
#include "VoxelClipmapDesiredTiles.h" #include "VoxelClipmapDesiredTiles.h"
#include "VoxelCollisionDesiredTiles.h" #include "VoxelCollisionDesiredTiles.h"
#include "VoxelPreEditDensityCache.h" #include "VoxelPreEditDensityCache.h"
#include "RealtimeMeshLibrary.h"
// IWYU (FPSemantics = Precise ⇒ plus de PCH partagé) : GetPlayerPosition déréférence le pawn, donc // IWYU (FPSemantics = Precise ⇒ plus de PCH partagé) : GetPlayerPosition déréférence le pawn, donc
// APawn doit être COMPLET — `Casts.h` n'en donne qu'une déclaration avant. APlayerController n'est // APawn doit être COMPLET — `Casts.h` n'en donne qu'une déclaration avant. APlayerController n'est
// complet que par transitivité : on l'inclut explicitement. // complet que par transitivité : on l'inclut explicitement.
@@ -12471,21 +12472,30 @@ bool AVoxelWorld::ApplyMeshToTile(FChunkResult& Result)
// (default config already maps material slot = polygroup index); only config sections that // (default config already maps material slot = polygroup index); only config sections that
// exist — the bHas* flags come from the worker. Collision at level 0 only (T1.c), both groups. // exist — the bHas* flags come from the worker. Collision at level 0 only (T1.c), both groups.
const uint64 CollisionConfigStartCycles = bProfileApply ? FPlatformTime::Cycles64() : 0; const uint64 CollisionConfigStartCycles = bProfileApply ? FPlatformTime::Cycles64() : 0;
const int32 NumIndices = Result.NumTriangles * 3;
const int32 FirstCapIndex = FMath::Clamp(
NumIndices - Result.NumCeilingTriangles * 3, 0, NumIndices);
if (bHasGroundTris) if (bHasGroundTris)
{ {
FRealtimeMeshSectionConfig GroundConfig(0); FRealtimeMeshSectionConfig GroundConfig(0);
GroundConfig.bCastsShadow = bCastShadow; GroundConfig.bCastsShadow = bCastShadow;
RTMesh->UpdateSectionConfig( const FRealtimeMeshStreamRange GroundRange =
URealtimeMeshBlueprintFunctionLibrary::MakeStreamRange(
0, Result.NumVertices, 0, FirstCapIndex);
RTMesh->CreateSection(
FRealtimeMeshSectionKey::CreateForPolyGroup(GroupKey, 0), FRealtimeMeshSectionKey::CreateForPolyGroup(GroupKey, 0),
GroundConfig, /*bShouldCreateCollision*/ bWantsCollision); GroundConfig, GroundRange, /*bShouldCreateCollision*/ bWantsCollision);
} }
if (bHasCeilingTris) if (bHasCeilingTris)
{ {
FRealtimeMeshSectionConfig CapConfig(1); FRealtimeMeshSectionConfig CapConfig(1);
CapConfig.bCastsShadow = false; // the cap never casts (see above) CapConfig.bCastsShadow = false; // the cap never casts (see above)
RTMesh->UpdateSectionConfig( const FRealtimeMeshStreamRange CapRange =
URealtimeMeshBlueprintFunctionLibrary::MakeStreamRange(
0, Result.NumVertices, FirstCapIndex, NumIndices);
RTMesh->CreateSection(
FRealtimeMeshSectionKey::CreateForPolyGroup(GroupKey, 1), FRealtimeMeshSectionKey::CreateForPolyGroup(GroupKey, 1),
CapConfig, /*bShouldCreateCollision*/ bWantsCollision); CapConfig, CapRange, /*bShouldCreateCollision*/ bWantsCollision);
} }
const double CollisionConfigSeconds = bProfileApply const double CollisionConfigSeconds = bProfileApply
? FPlatformTime::ToSeconds64(FPlatformTime::Cycles64() - CollisionConfigStartCycles) ? FPlatformTime::ToSeconds64(FPlatformTime::Cycles64() - CollisionConfigStartCycles)
@@ -15221,7 +15221,8 @@ bool RunCoreFarHorizonAgreementAudit(VoxelForgeCore::World& World,
if(Other==Ground8.ByXY.end())continue; if(Other==Ground8.ByXY.end())continue;
const auto Column=Field.point(Pair.first.first,Pair.first.second,0); const auto Column=Field.point(Pair.first.first,Pair.first.second,0);
const std::int64_t Ground=Column.groundHeightSubvoxels; const std::int64_t Ground=Column.groundHeightSubvoxels;
const std::int64_t Cap=Column.capHeightSubvoxels; const std::int64_t Cap=Field.farSurfaceCapHeightSubvoxels(
Pair.first.first,Pair.first.second);
if(Ground>=Cap)continue; if(Ground>=Cap)continue;
const double GroundVoxel=static_cast<double>(Ground)/SubvoxelsPerVoxel; const double GroundVoxel=static_cast<double>(Ground)/SubvoxelsPerVoxel;
if(GroundVoxel<Bounds7[2]||GroundVoxel>Bounds7[5] if(GroundVoxel<Bounds7[2]||GroundVoxel>Bounds7[5]
@@ -15263,7 +15264,8 @@ bool RunCoreFarHorizonAgreementAudit(VoxelForgeCore::World& World,
const std::int64_t YSub=static_cast<std::int64_t>(std::llround( const std::int64_t YSub=static_cast<std::int64_t>(std::llround(
static_cast<double>(RouteYSub)/GridPeriod))*GridPeriod; static_cast<double>(RouteYSub)/GridPeriod))*GridPeriod;
const auto WalkColumn=Field.point(XSub,YSub,0); const auto WalkColumn=Field.point(XSub,YSub,0);
if(WalkColumn.groundHeightSubvoxels>=WalkColumn.capHeightSubvoxels)continue; const std::int64_t WalkCap=Field.farSurfaceCapHeightSubvoxels(XSub,YSub);
if(WalkColumn.groundHeightSubvoxels>=WalkCap)continue;
const std::int64_t XVoxel=XSub/SubvoxelsPerVoxel,YVoxel=YSub/SubvoxelsPerVoxel; const std::int64_t XVoxel=XSub/SubvoxelsPerVoxel,YVoxel=YSub/SubvoxelsPerVoxel;
const std::int64_t WalkGroundVoxel=WalkColumn.groundHeightSubvoxels/SubvoxelsPerVoxel; const std::int64_t WalkGroundVoxel=WalkColumn.groundHeightSubvoxels/SubvoxelsPerVoxel;
const auto MakeKey=[](std::int64_t X,std::int64_t Y,std::int64_t Z,std::int64_t Extent,int32 LOD) const auto MakeKey=[](std::int64_t X,std::int64_t Y,std::int64_t Z,std::int64_t Extent,int32 LOD)
@@ -15293,7 +15295,8 @@ bool RunCoreFarHorizonAgreementAudit(VoxelForgeCore::World& World,
if(Other==Ground8.ByXY.end())continue; if(Other==Ground8.ByXY.end())continue;
const auto Column=Field.point(Pair.first.first,Pair.first.second,0); const auto Column=Field.point(Pair.first.first,Pair.first.second,0);
const std::int64_t Ground=Column.groundHeightSubvoxels; const std::int64_t Ground=Column.groundHeightSubvoxels;
if(Ground>=Column.capHeightSubvoxels)continue; if(Ground>=Field.farSurfaceCapHeightSubvoxels(
Pair.first.first,Pair.first.second))continue;
const auto Bounds7=TileBounds(Key7),Bounds8=TileBounds(Key8); const auto Bounds7=TileBounds(Key7),Bounds8=TileBounds(Key8);
const double GroundVoxel=static_cast<double>(Ground)/SubvoxelsPerVoxel; const double GroundVoxel=static_cast<double>(Ground)/SubvoxelsPerVoxel;
if(GroundVoxel<Bounds7[2]||GroundVoxel>Bounds7[5] if(GroundVoxel<Bounds7[2]||GroundVoxel>Bounds7[5]
@@ -15373,6 +15376,554 @@ bool RunCoreFarHorizonAgreementAudit(VoxelForgeCore::World& World,
return true; return true;
} }
bool RunCoreFarCapTileCensus(VoxelForgeCore::World& World,
const VoxelForgeCore::WorldAssemblyManifest& Assembly,FString& OutError)
{
OutError.Reset();
constexpr std::int64_t SubvoxelsPerVoxel=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
constexpr std::int64_t ProfileGridStep=128;
using FTileId=std::array<std::int32_t,4>;
using FXYLod=std::array<std::int32_t,3>;
const auto IdFor=[](const VoxelForgeCore::TileKey& Key)
{return FTileId{Key.coordinate.x,Key.coordinate.y,Key.coordinate.z,Key.lod};};
const auto FloorDiv64=[](std::int64_t Value,std::int64_t Divisor)
{
const std::int64_t Quotient=Value/Divisor, Remainder=Value%Divisor;
return Quotient-((Remainder!=0&&Remainder<0)?1:0);
};
const VoxelForgeCore::WorldAssemblyStratumPlanV1* Plan=nullptr;
for(const auto& Candidate:Assembly.plan.strates)
if(!Candidate.route.empty()) {Plan=&Candidate;break;}
if(Plan==nullptr)
{OutError=TEXT("Far-cap census has no SurfaceWorld walk route.");return false;}
const VoxelForgeCore::Heightfield2D Field(Assembly.recipe,*Plan);
if(!Field.isValid())
{OutError=TEXT("Far-cap census received an invalid SurfaceWorld heightfield.");return false;}
const auto& WalkStart=Plan->route.front();
const VoxelForgeCore::Vec3d Camera{
static_cast<double>(WalkStart.positionSubvoxels[0])/SubvoxelsPerVoxel,
static_cast<double>(WalkStart.positionSubvoxels[1])/SubvoxelsPerVoxel,
static_cast<double>(WalkStart.positionSubvoxels[2])/SubvoxelsPerVoxel};
const std::vector<VoxelForgeCore::TileKey> WalkKeys=World.desiredTiles(Camera);
std::set<FTileId> Desired;
std::set<FXYLod> FarXYTiles;
double ClipMaxZ=-std::numeric_limits<double>::infinity();
for(const auto& Key:WalkKeys)
{
Desired.insert(IdFor(Key));
const double Extent=static_cast<double>(32<<Key.lod);
ClipMaxZ=std::max(ClipMaxZ,(static_cast<double>(Key.coordinate.z)+1.0)*Extent);
if(Key.lod>=5&&Key.lod<=8)
FarXYTiles.insert({Key.coordinate.x,Key.coordinate.y,Key.lod});
}
std::set<FTileId> CapBands;
std::uint64_t ProfileGridSamples=0, ProfileSamplesWithoutRoof=0;
std::set<FXYLod> FarXYTilesWithoutRoof;
for(const FXYLod& XY:FarXYTiles)
{
const std::int32_t Lod=XY[2];
const std::int64_t Extent=static_cast<std::int64_t>(32)<<Lod;
const std::int64_t OriginX=static_cast<std::int64_t>(XY[0])*Extent;
const std::int64_t OriginY=static_cast<std::int64_t>(XY[1])*Extent;
for(std::int64_t Y=0;Y<=Extent;Y+=ProfileGridStep)
for(std::int64_t X=0;X<=Extent;X+=ProfileGridStep)
{
++ProfileGridSamples;
const auto Column=Field.point((OriginX+X)*SubvoxelsPerVoxel,
(OriginY+Y)*SubvoxelsPerVoxel,0);
const std::int64_t FarCap=Field.farSurfaceCapHeightSubvoxels(
(OriginX+X)*SubvoxelsPerVoxel,(OriginY+Y)*SubvoxelsPerVoxel);
if(FarCap<=Column.groundHeightSubvoxels)
{
++ProfileSamplesWithoutRoof;
FarXYTilesWithoutRoof.insert(XY);
continue;
}
const std::int64_t Band=FloorDiv64(FarCap,
Extent*SubvoxelsPerVoxel);
if(Band>=MIN_int32&&Band<=MAX_int32)
CapBands.insert({XY[0],XY[1],static_cast<std::int32_t>(Band),Lod});
if(FarCap%(Extent*SubvoxelsPerVoxel)==0
&&Band>MIN_int32)
CapBands.insert({XY[0],XY[1],static_cast<std::int32_t>(Band-1),Lod});
}
}
std::uint64_t RequestedCapBands=0, EmittedCapBands=0, RequestedWithoutTriangles=0;
std::uint64_t CulledCapBands=0;
std::map<std::int32_t,std::array<std::uint64_t,4>> ByLod;
VoxelForgeCore::TileGenerationOptions Options;
std::string CoreError;
struct FCapAgreementSample
{ std::int64_t XSub=0,YSub=0;double FarZVoxel=0.0; };
using FCapXYKey=std::pair<std::int64_t,std::int64_t>;
std::set<FCapXYKey> CapAgreementSeen;
std::vector<FCapAgreementSample> CapAgreementSamples;
CapAgreementSamples.reserve(128);
const double VoxelSizeCm=static_cast<double>(World.configuration().voxelSizeMicrons)/10000.0;
constexpr std::int64_t CapGridPeriod=128*SubvoxelsPerVoxel;
for(const FTileId& Id:CapBands)
{
const std::int32_t Lod=Id[3];
auto& Counts=ByLod[Lod];
++Counts[0];
if(Desired.find(Id)==Desired.end())
{
++CulledCapBands;++Counts[1];continue;
}
++RequestedCapBands;
VoxelForgeCore::TileKey Key{{Id[0],Id[1],Id[2]},Lod};
const auto Mesh=World.generateTile(Key,Options,CoreError);
if(!Mesh)
{
OutError=FString::Printf(TEXT("Far-cap census tile generation failed at (%d,%d,%d) LOD%d: %s"),
Id[0],Id[1],Id[2],Lod,UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
if(Mesh->diagnostics.horizonCapTriangles>0)
{
++EmittedCapBands;++Counts[2];
const std::size_t CapIndexStart=Mesh->indices.size()
-static_cast<std::size_t>(Mesh->diagnostics.horizonCapTriangles)*3u;
for(std::size_t Index=CapIndexStart;Index<Mesh->indices.size()
&&CapAgreementSamples.size()<128;++Index)
{
const std::uint32_t VertexIndex=Mesh->indices[Index];
if(VertexIndex>=Mesh->vertices.size())continue;
const auto& Vertex=Mesh->vertices[VertexIndex];
if(Vertex.normal.z>=-0.35)continue;
const std::int64_t XSub=static_cast<std::int64_t>(std::llround(
Vertex.position.x/VoxelSizeCm*SubvoxelsPerVoxel));
const std::int64_t YSub=static_cast<std::int64_t>(std::llround(
Vertex.position.y/VoxelSizeCm*SubvoxelsPerVoxel));
if(XSub%CapGridPeriod!=0||YSub%CapGridPeriod!=0)continue;
if(!CapAgreementSeen.insert({XSub,YSub}).second)continue;
CapAgreementSamples.push_back({XSub,YSub,
Vertex.position.z/VoxelSizeCm});
}
}
else
{++RequestedWithoutTriangles;++Counts[3];}
}
constexpr double CapAgreementToleranceCm=2.0;
constexpr double CapNegativeDatumOffsetCm=100.0;
const auto FindNearCapRoot=[&](std::int64_t XSub,std::int64_t YSub,
double ProfileCapVoxel,double& OutRootVoxel)
{
const double X=static_cast<double>(XSub)/SubvoxelsPerVoxel;
const double Y=static_cast<double>(YSub)/SubvoxelsPerVoxel;
for(double Radius=4.0;Radius<=4096.0;Radius*=2.0)
{
double Lo=ProfileCapVoxel-Radius,Hi=ProfileCapVoxel+Radius;
if(!(World.densityAt({X,Y,Lo})>=0.0f&&World.densityAt({X,Y,Hi})<0.0f))continue;
for(int Iteration=0;Iteration<16;++Iteration)
{
const double Mid=(Lo+Hi)*0.5;
if(World.densityAt({X,Y,Mid})>=0.0f)Lo=Mid;else Hi=Mid;
}
OutRootVoxel=(Lo+Hi)*0.5;
return true;
}
return false;
};
double MaximumCapAgreementErrorCm=0.0,MaximumCapNegativeControlErrorCm=0.0;
std::uint64_t CapAgreementCompared=0;
for(const FCapAgreementSample& Sample:CapAgreementSamples)
{
const auto Column=Field.point(Sample.XSub,Sample.YSub,0);
const std::int64_t FarCap=Field.farSurfaceCapHeightSubvoxels(
Sample.XSub,Sample.YSub);
if(FarCap<=Column.groundHeightSubvoxels)continue;
double NearRootVoxel=0.0;
if(!FindNearCapRoot(Sample.XSub,Sample.YSub,
static_cast<double>(FarCap)/SubvoxelsPerVoxel,NearRootVoxel))continue;
const double FarCapCm=Sample.FarZVoxel*VoxelSizeCm;
const double NearCapCm=NearRootVoxel*VoxelSizeCm;
MaximumCapAgreementErrorCm=std::max(MaximumCapAgreementErrorCm,
std::abs(FarCapCm-NearCapCm));
MaximumCapNegativeControlErrorCm=std::max(MaximumCapNegativeControlErrorCm,
std::abs(FarCapCm+CapNegativeDatumOffsetCm-NearCapCm));
++CapAgreementCompared;
}
const bool bCapNegativeControlDetected=CapAgreementCompared>0
&&MaximumCapNegativeControlErrorCm>CapAgreementToleranceCm;
UE_LOG(LogTemp,Display,
TEXT("[VoxelForgeFarCapAgreement] status=%s shared_points=%llu max_far_near_cm=%.4f tolerance_cm=%.2f near_source=exact_density_root negative_control=100cm_datum_offset max_error_cm=%.4f detected=%d"),
CapAgreementCompared>=32&&MaximumCapAgreementErrorCm<=CapAgreementToleranceCm
&&bCapNegativeControlDetected?TEXT("PASS"):TEXT("FAIL"),
static_cast<unsigned long long>(CapAgreementCompared),MaximumCapAgreementErrorCm,
CapAgreementToleranceCm,MaximumCapNegativeControlErrorCm,bCapNegativeControlDetected?1:0);
if(CapAgreementCompared<32||MaximumCapAgreementErrorCm>CapAgreementToleranceCm
||!bCapNegativeControlDetected)
{
OutError=FString::Printf(TEXT("Far-cap agreement failed: points=%llu max_error_cm=%.4f tolerance_cm=%.2f negative_control_cm=%.4f detected=%d."),
static_cast<unsigned long long>(CapAgreementCompared),MaximumCapAgreementErrorCm,
CapAgreementToleranceCm,MaximumCapNegativeControlErrorCm,bCapNegativeControlDetected?1:0);
return false;
}
// Les rayons couvrent l'hemisphere superieur depuis chaque point de marche.
// Cast into the upper view hemisphere from every authored walk point.
constexpr double Pi=3.14159265358979323846;
constexpr std::array<double,8> AzimuthDegrees={0.0,45.0,90.0,135.0,180.0,225.0,270.0,315.0};
constexpr std::array<double,6> ElevationDegrees={15.0,30.0,45.0,60.0,75.0,90.0};
constexpr double RayStepVoxels=128.0;
double ClipMinX=std::numeric_limits<double>::infinity();
double ClipMinY=std::numeric_limits<double>::infinity();
double ClipMaxX=-std::numeric_limits<double>::infinity();
double ClipMaxY=-std::numeric_limits<double>::infinity();
for(const FXYLod& XY:FarXYTiles)
{
const double Extent=static_cast<double>(32<<XY[2]);
ClipMinX=std::min(ClipMinX,static_cast<double>(XY[0])*Extent);
ClipMinY=std::min(ClipMinY,static_cast<double>(XY[1])*Extent);
ClipMaxX=std::max(ClipMaxX,(static_cast<double>(XY[0])+1.0)*Extent);
ClipMaxY=std::max(ClipMaxY,(static_cast<double>(XY[1])+1.0)*Extent);
}
enum class ECapRayTileResult : std::uint8_t
{ EmittedHit, CulledByClipmap, RequestedWithoutTriangles, EmittedWithoutHit };
struct FCapRayTileGeometry
{
std::vector<std::array<VoxelForgeCore::Vec3d,3>> Triangles;
};
std::uint64_t RayCount=0, RayHits=0, RayMissedClipEdge=0;
std::uint64_t RayMissedNoRoof=0, RayMissedCulledByClipmap=0;
std::uint64_t RayMissedRequestedWithoutTriangles=0, RayMissedEmittedWithoutHit=0;
std::uint64_t RayStartedAboveCap=0;
std::uint64_t RayProfileCrossings=0,RayProfileRoofCrossings=0,RayProfileRoofWithoutMeshHit=0;
std::array<std::array<std::uint64_t,6>,6> RayResultsByElevation{};
std::vector<double> RayHitDistancesMeters;
RayHitDistancesMeters.reserve(Plan->route.size()*AzimuthDegrees.size()*ElevationDegrees.size());
std::map<FTileId,FCapRayTileGeometry> RayTileGeometryCache;
const auto GetCapRayTileGeometry=[&](const FTileId& Id)->const FCapRayTileGeometry*
{
auto Found=RayTileGeometryCache.find(Id);
if(Found!=RayTileGeometryCache.end())return &Found->second;
const VoxelForgeCore::TileKey Key{{Id[0],Id[1],Id[2]},Id[3]};
const auto Mesh=World.generateTile(Key,Options,CoreError);
if(!Mesh)
{
OutError=FString::Printf(TEXT("Far-cap ray tile generation failed at (%d,%d,%d) LOD%d: %s"),
Id[0],Id[1],Id[2],Id[3],UTF8_TO_TCHAR(CoreError.c_str()));
return nullptr;
}
FCapRayTileGeometry Geometry;
Geometry.Triangles.reserve(Mesh->groups.ceilingTriangleCount);
for(std::size_t Index=0;Index+2<Mesh->indices.size();Index+=3)
{
const std::uint32_t IA=Mesh->indices[Index],IB=Mesh->indices[Index+1],IC=Mesh->indices[Index+2];
if(IA>=Mesh->vertices.size()||IB>=Mesh->vertices.size()||IC>=Mesh->vertices.size())continue;
const double AverageNormalZ=(Mesh->vertices[IA].normal.z+Mesh->vertices[IB].normal.z
+Mesh->vertices[IC].normal.z)/3.0;
// Native marching-cubes roofs and refined far caps both face out of the solid
// toward decreasing Z density. Include those ceiling faces at LOD0-8.
// Les plafonds MC natifs et les caps lointains pointent hors du solide, vers -Z.
if(AverageNormalZ>=-0.05)continue;
Geometry.Triangles.push_back({
VoxelForgeCore::Vec3d{Mesh->vertices[IA].position.x/VoxelSizeCm,
Mesh->vertices[IA].position.y/VoxelSizeCm,Mesh->vertices[IA].position.z/VoxelSizeCm},
VoxelForgeCore::Vec3d{Mesh->vertices[IB].position.x/VoxelSizeCm,
Mesh->vertices[IB].position.y/VoxelSizeCm,Mesh->vertices[IB].position.z/VoxelSizeCm},
VoxelForgeCore::Vec3d{Mesh->vertices[IC].position.x/VoxelSizeCm,
Mesh->vertices[IC].position.y/VoxelSizeCm,Mesh->vertices[IC].position.z/VoxelSizeCm}});
}
return &RayTileGeometryCache.emplace(Id,std::move(Geometry)).first->second;
};
const auto Dot3=[](const VoxelForgeCore::Vec3d& A,const VoxelForgeCore::Vec3d& B)
{return A.x*B.x+A.y*B.y+A.z*B.z;};
const auto Sub3=[](const VoxelForgeCore::Vec3d& A,const VoxelForgeCore::Vec3d& B)
{return VoxelForgeCore::Vec3d{A.x-B.x,A.y-B.y,A.z-B.z};};
const auto Cross3=[](const VoxelForgeCore::Vec3d& A,const VoxelForgeCore::Vec3d& B)
{return VoxelForgeCore::Vec3d{A.y*B.z-A.z*B.y,A.z*B.x-A.x*B.z,A.x*B.y-A.y*B.x};};
const auto RayTriangleDistance=[&](const VoxelForgeCore::Vec3d& Origin,
const VoxelForgeCore::Vec3d& Direction,const std::array<VoxelForgeCore::Vec3d,3>& Triangle,
double MinimumDistance,double MaximumDistance,double& OutDistance)
{
const auto Edge1=Sub3(Triangle[1],Triangle[0]);
const auto Edge2=Sub3(Triangle[2],Triangle[0]);
const auto P=Cross3(Direction,Edge2);
const double Determinant=Dot3(Edge1,P);
if(std::abs(Determinant)<1.0e-12)return false;
const double Inverse=1.0/Determinant;
const auto T=Sub3(Origin,Triangle[0]);
const double U=Dot3(T,P)*Inverse;
if(U< -1.0e-8||U>1.0+1.0e-8)return false;
const auto Q=Cross3(T,Edge1);
const double V=Dot3(Direction,Q)*Inverse;
if(V< -1.0e-8||U+V>1.0+1.0e-8)return false;
const double Distance=Dot3(Edge2,Q)*Inverse;
if(Distance<MinimumDistance-1.0e-6||Distance>MaximumDistance+1.0e-6)return false;
OutDistance=Distance;
return true;
};
const auto TraceEmittedCapRay=[&](const VoxelForgeCore::Vec3d& Origin,
const VoxelForgeCore::Vec3d& Direction,double MaximumDistance,double& OutHitDistance)
{
bool bFoundRequestedKey=false,bFoundEmptyRequestedKey=false,bFoundGeometryWithoutHit=false;
bool bFoundHit=false;
for(const FTileId& Id:Desired)
{
const double Extent=static_cast<double>(32<<Id[3]);
const std::array<double,3> BoundsMin={static_cast<double>(Id[0])*Extent,
static_cast<double>(Id[1])*Extent,static_cast<double>(Id[2])*Extent};
const std::array<double,3> BoundsMax={BoundsMin[0]+Extent,BoundsMin[1]+Extent,
BoundsMin[2]+Extent};
double TileEnter=0.0,TileExit=MaximumDistance;
bool bIntersects=true;
for(std::size_t Axis=0;Axis<3;++Axis)
{
const double RayOrigin=Axis==0?Origin.x:(Axis==1?Origin.y:Origin.z);
const double RayDirection=Axis==0?Direction.x:(Axis==1?Direction.y:Direction.z);
if(std::abs(RayDirection)<1.0e-12)
{
if(RayOrigin<BoundsMin[Axis]||RayOrigin>BoundsMax[Axis])
{bIntersects=false;break;}
continue;
}
double Near=(BoundsMin[Axis]-RayOrigin)/RayDirection;
double Far=(BoundsMax[Axis]-RayOrigin)/RayDirection;
if(Near>Far)std::swap(Near,Far);
TileEnter=std::max(TileEnter,Near);
TileExit=std::min(TileExit,Far);
if(TileEnter>TileExit){bIntersects=false;break;}
}
if(!bIntersects||TileExit<0.0||TileEnter>MaximumDistance)continue;
bFoundRequestedKey=true;
const FCapRayTileGeometry* Geometry=GetCapRayTileGeometry(Id);
if(Geometry==nullptr)continue;
if(Geometry->Triangles.empty())
{bFoundEmptyRequestedKey=true;continue;}
bFoundGeometryWithoutHit=true;
for(const auto& Triangle:Geometry->Triangles)
{
double HitDistance=0.0;
if(!RayTriangleDistance(Origin,Direction,Triangle,
std::max(0.0,TileEnter),std::min(MaximumDistance,TileExit),HitDistance))continue;
if(!bFoundHit||HitDistance<OutHitDistance)OutHitDistance=HitDistance;
bFoundHit=true;
}
if(!OutError.IsEmpty())return ECapRayTileResult::RequestedWithoutTriangles;
}
if(bFoundHit)return ECapRayTileResult::EmittedHit;
if(bFoundRequestedKey&&bFoundGeometryWithoutHit)return ECapRayTileResult::EmittedWithoutHit;
if(bFoundRequestedKey&&bFoundEmptyRequestedKey)return ECapRayTileResult::RequestedWithoutTriangles;
return ECapRayTileResult::CulledByClipmap;
};
for(std::size_t RouteIndex=0;RouteIndex<Plan->route.size();++RouteIndex)
{
const auto& RoutePoint=Plan->route[RouteIndex];
const double StartX=static_cast<double>(RoutePoint.positionSubvoxels[0])/SubvoxelsPerVoxel;
const double StartY=static_cast<double>(RoutePoint.positionSubvoxels[1])/SubvoxelsPerVoxel;
const double StartZ=static_cast<double>(RoutePoint.positionSubvoxels[2])/SubvoxelsPerVoxel;
const auto StartColumn=Field.point(RoutePoint.positionSubvoxels[0],
RoutePoint.positionSubvoxels[1],0);
const double StartCap=static_cast<double>(Field.farSurfaceCapHeightSubvoxels(
RoutePoint.positionSubvoxels[0],RoutePoint.positionSubvoxels[1]))/SubvoxelsPerVoxel;
for(const double Elevation:ElevationDegrees)
for(const double Azimuth:AzimuthDegrees)
{
++RayCount;
const std::size_t ElevationIndex=static_cast<std::size_t>(
std::find(ElevationDegrees.begin(),ElevationDegrees.end(),Elevation)
-ElevationDegrees.begin());
if(!(StartCap>StartZ))
{++RayStartedAboveCap;++RayMissedNoRoof;++RayResultsByElevation[ElevationIndex][4];continue;}
const double ElevationRadians=Elevation*Pi/180.0;
const double AzimuthRadians=Azimuth*Pi/180.0;
const double DX=std::cos(ElevationRadians)*std::cos(AzimuthRadians);
const double DY=std::cos(ElevationRadians)*std::sin(AzimuthRadians);
const double DZ=std::sin(ElevationRadians);
double EdgeDistance=std::numeric_limits<double>::infinity();
if(DX>1.0e-9)EdgeDistance=std::min(EdgeDistance,(ClipMaxX-StartX)/DX);
else if(DX< -1.0e-9)EdgeDistance=std::min(EdgeDistance,(ClipMinX-StartX)/DX);
if(DY>1.0e-9)EdgeDistance=std::min(EdgeDistance,(ClipMaxY-StartY)/DY);
else if(DY< -1.0e-9)EdgeDistance=std::min(EdgeDistance,(ClipMinY-StartY)/DY);
if(DZ>1.0e-9)
EdgeDistance=std::min(EdgeDistance,(ClipMaxZ-StartZ)/DZ);
if(!std::isfinite(EdgeDistance)||EdgeDistance<=0.0)
{++RayMissedClipEdge;++RayResultsByElevation[ElevationIndex][5];continue;}
bool bProfileCrossing=false,bProfileRoofAtCrossing=false;
double ProfileHitDistance=std::numeric_limits<double>::infinity();
double ProfileHitX=0.0,ProfileHitY=0.0,ProfileHitZ=0.0;
std::int64_t ProfileHitCapSubvoxels=0;
double PreviousDistance=0.0,PreviousDelta=StartZ-StartCap;
for(double Distance=std::min(RayStepVoxels,EdgeDistance);;)
{
const double X=StartX+DX*Distance,Y=StartY+DY*Distance;
const double Z=StartZ+DZ*Distance;
const std::int64_t CapSubvoxels=Field.farSurfaceCapHeightSubvoxels(
static_cast<std::int64_t>(std::llround(X*SubvoxelsPerVoxel)),
static_cast<std::int64_t>(std::llround(Y*SubvoxelsPerVoxel)));
const double Cap=static_cast<double>(CapSubvoxels)/SubvoxelsPerVoxel;
const double Delta=Z-Cap;
if(PreviousDelta<0.0&&Delta>=0.0)
{
const double Fraction=-PreviousDelta/(Delta-PreviousDelta);
ProfileHitDistance=PreviousDistance+(Distance-PreviousDistance)*Fraction;
const double HitX=StartX+DX*ProfileHitDistance;
const double HitY=StartY+DY*ProfileHitDistance;
const auto HitColumn=Field.point(
static_cast<std::int64_t>(std::llround(HitX*SubvoxelsPerVoxel)),
static_cast<std::int64_t>(std::llround(HitY*SubvoxelsPerVoxel)),0);
const std::int64_t HitCapSubvoxels=Field.farSurfaceCapHeightSubvoxels(
static_cast<std::int64_t>(std::llround(HitX*SubvoxelsPerVoxel)),
static_cast<std::int64_t>(std::llround(HitY*SubvoxelsPerVoxel)));
bProfileCrossing=true;
bProfileRoofAtCrossing=HitCapSubvoxels>HitColumn.groundHeightSubvoxels;
ProfileHitX=HitX;ProfileHitY=HitY;
ProfileHitZ=StartZ+DZ*ProfileHitDistance;
ProfileHitCapSubvoxels=HitCapSubvoxels;
++RayProfileCrossings;
if(bProfileRoofAtCrossing)++RayProfileRoofCrossings;
break;
}
PreviousDistance=Distance;PreviousDelta=Delta;
if(Distance>=EdgeDistance)break;
Distance=std::min(Distance+RayStepVoxels,EdgeDistance);
}
const VoxelForgeCore::Vec3d RayOrigin{StartX,StartY,StartZ};
const VoxelForgeCore::Vec3d RayDirection{DX,DY,DZ};
double MeshHitDistance=std::numeric_limits<double>::infinity();
const ECapRayTileResult TileResult=TraceEmittedCapRay(
RayOrigin,RayDirection,EdgeDistance,MeshHitDistance);
if(!OutError.IsEmpty())return false;
switch(TileResult)
{
case ECapRayTileResult::EmittedHit:
++RayHits;++RayResultsByElevation[ElevationIndex][0];
RayHitDistancesMeters.push_back(MeshHitDistance*VoxelSizeCm/100.0);break;
case ECapRayTileResult::CulledByClipmap:
++RayMissedCulledByClipmap;++RayResultsByElevation[ElevationIndex][1];break;
case ECapRayTileResult::RequestedWithoutTriangles:
++RayMissedRequestedWithoutTriangles;++RayResultsByElevation[ElevationIndex][2];break;
case ECapRayTileResult::EmittedWithoutHit:
{
++RayMissedEmittedWithoutHit;++RayResultsByElevation[ElevationIndex][3];
if(bProfileCrossing&&bProfileRoofAtCrossing)++RayProfileRoofWithoutMeshHit;
std::uint32_t FarRequestedMask=0,FarEmittedMask=0;
std::uint32_t RootRequestedMask=0,RootEmittedMask=0;
std::uint64_t RootDownwardTriangles=0;
if(bProfileCrossing&&bProfileRoofAtCrossing)
{
for(std::int32_t Lod=0;Lod<=8;++Lod)
{
const std::int64_t Extent=static_cast<std::int64_t>(32)<<Lod;
const std::int64_t TileX=FloorDiv64(static_cast<std::int64_t>(std::floor(ProfileHitX)),Extent);
const std::int64_t TileY=FloorDiv64(static_cast<std::int64_t>(std::floor(ProfileHitY)),Extent);
const std::int64_t TileZ=FloorDiv64(ProfileHitCapSubvoxels,Extent*SubvoxelsPerVoxel);
const FTileId Id{static_cast<std::int32_t>(TileX),static_cast<std::int32_t>(TileY),
static_cast<std::int32_t>(TileZ),Lod};
if(Desired.find(Id)==Desired.end())continue;
RootRequestedMask|=1u<<static_cast<std::uint32_t>(Lod);
const FCapRayTileGeometry* Geometry=GetCapRayTileGeometry(Id);
if(Geometry!=nullptr)
{
RootDownwardTriangles+=Geometry->Triangles.size();
if(!Geometry->Triangles.empty())
RootEmittedMask|=1u<<static_cast<std::uint32_t>(Lod);
}
if(Lod>=5)
{
FarRequestedMask|=1u<<static_cast<std::uint32_t>(Lod-5);
if(Geometry!=nullptr&&!Geometry->Triangles.empty())
FarEmittedMask|=1u<<static_cast<std::uint32_t>(Lod-5);
}
}
}
UE_LOG(LogTemp,Display,
TEXT("[VoxelForgeFarCapRayMiss] route_point=%llu elevation_deg=%.0f azimuth_deg=%.0f profile_crossing=%d profile_roof=%d profile_distance_vox=%.1f clip_distance_vox=%.1f profile_hit_vox=(%.1f,%.1f,%.1f) root_lod_requested_mask=0x%X root_lod_emitted_mask=0x%X root_downward_triangles=%llu far_requested_mask=0x%X far_emitted_mask=0x%X"),
static_cast<unsigned long long>(RouteIndex),Elevation,Azimuth,
bProfileCrossing?1:0,bProfileRoofAtCrossing?1:0,
ProfileHitDistance,EdgeDistance,ProfileHitX,ProfileHitY,ProfileHitZ,
RootRequestedMask,RootEmittedMask,
static_cast<unsigned long long>(RootDownwardTriangles),
FarRequestedMask,FarEmittedMask);
break;
}
}
}
}
const auto WalkColumn=Field.point(WalkStart.positionSubvoxels[0],
WalkStart.positionSubvoxels[1],0);
const std::int64_t WalkFarCap=Field.farSurfaceCapHeightSubvoxels(
WalkStart.positionSubvoxels[0],WalkStart.positionSubvoxels[1]);
std::size_t RayTriangleCount=0;
for(const auto& [Id,Geometry]:RayTileGeometryCache)
{ (void)Id;RayTriangleCount+=Geometry.Triangles.size(); }
std::sort(RayHitDistancesMeters.begin(),RayHitDistancesMeters.end());
const double HitDistanceP50Meters=RayHitDistancesMeters.empty()?0.0:
RayHitDistancesMeters[(RayHitDistancesMeters.size()-1)*50/100];
const double HitDistanceP95Meters=RayHitDistancesMeters.empty()?0.0:
RayHitDistancesMeters[(RayHitDistancesMeters.size()-1)*95/100];
const double HitDistanceMaximumMeters=RayHitDistancesMeters.empty()?0.0:
RayHitDistancesMeters.back();
UE_LOG(LogTemp,Display,
TEXT("[VoxelForgeFarCapCensus] source=surface_route_front route_points=%llu walk_keys=%llu far_xy_tiles=%llu cap_bands=%llu requested=%llu emitted=%llu requested_without_triangles=%llu culled_by_clipmap=%llu grid_samples=%llu samples_without_roof=%llu xy_tiles_with_roof_gaps=%llu walk_z_vox=%.3f ground_z_vox=%.3f cap_z_vox=%.3f"),
static_cast<unsigned long long>(Plan->route.size()),
static_cast<unsigned long long>(WalkKeys.size()),
static_cast<unsigned long long>(FarXYTiles.size()),
static_cast<unsigned long long>(CapBands.size()),
static_cast<unsigned long long>(RequestedCapBands),
static_cast<unsigned long long>(EmittedCapBands),
static_cast<unsigned long long>(RequestedWithoutTriangles),
static_cast<unsigned long long>(CulledCapBands),
static_cast<unsigned long long>(ProfileGridSamples),
static_cast<unsigned long long>(ProfileSamplesWithoutRoof),
static_cast<unsigned long long>(FarXYTilesWithoutRoof.size()),
Camera.z,static_cast<double>(WalkColumn.groundHeightSubvoxels)/SubvoxelsPerVoxel,
static_cast<double>(WalkFarCap)/SubvoxelsPerVoxel);
const double CoveragePercent=RayCount>0?100.0*static_cast<double>(RayHits)/RayCount:0.0;
UE_LOG(LogTemp,Display,
TEXT("[VoxelForgeFarCapRayCoverage] rays=%llu mesh_hits=%llu coverage_pct=%.3f missed_clip_edge=%llu missed_no_roof=%llu missed_cap_key_culled=%llu missed_requested_without_triangles=%llu missed_emitted_without_mesh_hit=%llu start_above_cap=%llu profile_crossings=%llu profile_roof_crossings=%llu profile_roof_without_mesh_hit=%llu traced_tiles=%llu traced_downward_triangles=%llu first_hit_distance_m_p50=%.2f_p95=%.2f_max=%.2f elevations_deg=15,30,45,60,75,90 azimuths=8 ray_method=all_downward_emitted_triangles_with_tile_aabb clip_edge=desired_far_xy_bounds lods_checked=0-8"),
static_cast<unsigned long long>(RayCount),static_cast<unsigned long long>(RayHits),CoveragePercent,
static_cast<unsigned long long>(RayMissedClipEdge),
static_cast<unsigned long long>(RayMissedNoRoof),
static_cast<unsigned long long>(RayMissedCulledByClipmap),
static_cast<unsigned long long>(RayMissedRequestedWithoutTriangles),
static_cast<unsigned long long>(RayMissedEmittedWithoutHit),
static_cast<unsigned long long>(RayStartedAboveCap),
static_cast<unsigned long long>(RayProfileCrossings),
static_cast<unsigned long long>(RayProfileRoofCrossings),
static_cast<unsigned long long>(RayProfileRoofWithoutMeshHit),
static_cast<unsigned long long>(RayTileGeometryCache.size()),
static_cast<unsigned long long>(RayTriangleCount),HitDistanceP50Meters,
HitDistanceP95Meters,HitDistanceMaximumMeters);
for(std::size_t Index=0;Index<ElevationDegrees.size();++Index)
UE_LOG(LogTemp,Display,
TEXT("[VoxelForgeFarCapRayElevation] degrees=%.0f rays=%llu mesh_hits=%llu key_culled=%llu requested_empty=%llu emitted_without_mesh_hit=%llu no_roof_or_above=%llu missed_clip_edge=%llu"),
ElevationDegrees[Index],
static_cast<unsigned long long>(AzimuthDegrees.size()*Plan->route.size()),
static_cast<unsigned long long>(RayResultsByElevation[Index][0]),
static_cast<unsigned long long>(RayResultsByElevation[Index][1]),
static_cast<unsigned long long>(RayResultsByElevation[Index][2]),
static_cast<unsigned long long>(RayResultsByElevation[Index][3]),
static_cast<unsigned long long>(RayResultsByElevation[Index][4]),
static_cast<unsigned long long>(RayResultsByElevation[Index][5]));
if(RayHits!=RayCount)
{
OutError=FString::Printf(TEXT("Far-cap ray coverage failed: rays=%llu mesh_hits=%llu clip_edge=%llu no_roof=%llu culled_keys=%llu requested_empty=%llu emitted_without_hit=%llu."),
static_cast<unsigned long long>(RayCount),static_cast<unsigned long long>(RayHits),
static_cast<unsigned long long>(RayMissedClipEdge),
static_cast<unsigned long long>(RayMissedNoRoof),
static_cast<unsigned long long>(RayMissedCulledByClipmap),
static_cast<unsigned long long>(RayMissedRequestedWithoutTriangles),
static_cast<unsigned long long>(RayMissedEmittedWithoutHit));
return false;
}
for(const auto& [Lod,Counts]:ByLod)
UE_LOG(LogTemp,Display,
TEXT("[VoxelForgeFarCapCensusLOD] lod=%d cap_bands=%llu culled=%llu emitted=%llu requested_without_triangles=%llu"),
Lod,static_cast<unsigned long long>(Counts[0]),
static_cast<unsigned long long>(Counts[1]),
static_cast<unsigned long long>(Counts[2]),
static_cast<unsigned long long>(Counts[3]));
return true;
}
// Paired engine timings include preparation and derived shells, with canonical output hashes. // Paired engine timings include preparation and derived shells, with canonical output hashes.
// Mesures moteur appariees : preparation et coques derivees incluses, avec hashes canoniques. // Mesures moteur appariees : preparation et coques derivees incluses, avec hashes canoniques.
bool RunCoreSharedFeatureCostAudit(VoxelForgeCore::World& World, bool RunCoreSharedFeatureCostAudit(VoxelForgeCore::World& World,
@@ -15441,8 +15992,12 @@ bool RunCoreSharedFeatureCostAudit(VoxelForgeCore::World& World,
P95(ConnectorShellTimes),P95(ConnectorFieldCalls),P95(ShellTriangles), P95(ConnectorShellTimes),P95(ConnectorFieldCalls),P95(ShellTriangles),
P95(GroundTriangles),P95(GroundRemoved),P95(CapTriangles)); P95(GroundTriangles),P95(GroundRemoved),P95(CapTriangles));
} }
if(Assembly!=nullptr&&!Assembly->plan.strates.empty() if(Assembly!=nullptr&&!Assembly->plan.strates.empty())
&&!RunCoreFarHorizonAgreementAudit(World,Keys,*Assembly,OutError))return false; {
if(FParse::Param(FCommandLine::Get(),TEXT("corefeatureauditonly"))
&&!RunCoreFarCapTileCensus(World,*Assembly,OutError))return false;
if(!RunCoreFarHorizonAgreementAudit(World,Keys,*Assembly,OutError))return false;
}
return true; return true;
} }
@@ -195,7 +195,7 @@ 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 = 1; inline constexpr std::uint32_t WorldAssemblySchemaVersion = 1;
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 4; inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 5;
enum class FieldChannel : std::uint32_t enum class FieldChannel : std::uint32_t
{ {
@@ -662,6 +662,13 @@ namespace VoxelForgeCore
bool isValid() const noexcept; bool isValid() const noexcept;
HeightfieldPointSample point(std::int64_t xSubvoxels, std::int64_t ySubvoxels, HeightfieldPointSample point(std::int64_t xSubvoxels, std::int64_t ySubvoxels,
std::int64_t zSubvoxels) const noexcept; std::int64_t zSubvoxels) const noexcept;
// The far render surface is clipped to this strate's owned upper boundary; density stays unchanged.
// La surface lointaine est bornee au haut de la strate; la densite reste inchangee.
std::int64_t farSurfaceCapHeightSubvoxels(std::int64_t xSubvoxels,
std::int64_t ySubvoxels) const noexcept;
// Conservative global Z extent of this declared far roof, including biome and cap features.
// Bornes Z prudentes du plafond lointain declare, avec les biomes et les fonctions de cap.
std::array<std::int64_t, 2> capHeightBounds() const noexcept;
WorldAssemblyFeatureTileListV1 buildFeatureTileList( WorldAssemblyFeatureTileListV1 buildFeatureTileList(
const std::array<std::int64_t, 3>& tileMinimumSubvoxels, const std::array<std::int64_t, 3>& tileMinimumSubvoxels,
const std::array<std::int64_t, 3>& tileMaximumSubvoxels) const; const std::array<std::int64_t, 3>& tileMaximumSubvoxels) const;
@@ -995,11 +1002,11 @@ namespace VoxelForgeCore
// Validation control only; never participates in recipe/world identity. // Validation control only; never participates in recipe/world identity.
// Controle de validation seulement, sans effet sur l'identite du monde. // Controle de validation seulement, sans effet sur l'identite du monde.
bool useAssemblyFeatureQueries = true; bool useAssemblyFeatureQueries = true;
// Bump when the far-horizon mesh representation changes. Far tile mesh hashes are // Bump when the distant cap representation changes. LOD4-8 mesh hashes are salted;
// salted with this version; density and LOD0-3 hashes are intentionally unchanged. // density and LOD0-3 hashes remain unchanged.
// Incremente quand la representation d'horizon lointain change; les hashes de maillage // Incremente quand le plafond lointain change. Les hashes LOD4-8 incluent cette version;
// lointain incluent cette version, sans toucher a la densite ni aux LOD0-3. // densite et hashes LOD0-3 restent inchanges.
std::uint32_t farHorizonVersion = 1; std::uint32_t farHorizonVersion = 2;
}; };
class VFCORE_API World class VFCORE_API World
+81 -15
View File
@@ -10,6 +10,7 @@
#include <chrono> #include <chrono>
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
#include <set>
namespace vfcore::detail namespace vfcore::detail
{ {
@@ -331,11 +332,11 @@ namespace VoxelForgeCore
for(const auto& Plan:Manifest.plan.strates) for(const auto& Plan:Manifest.plan.strates)
{ {
const VoxelForgeCore::Heightfield2D Block(Manifest.recipe,Plan);
if(!Block.isValid())continue;
const int64 StratumMinZ=static_cast<int64>(Plan.bottomChunkZ)*CHUNK_SIZE*Scale; const int64 StratumMinZ=static_cast<int64>(Plan.bottomChunkZ)*CHUNK_SIZE*Scale;
const int64 StratumMaxZ=(static_cast<int64>(Plan.topChunkZ)+1)*CHUNK_SIZE*Scale; const int64 StratumMaxZ=(static_cast<int64>(Plan.topChunkZ)+1)*CHUNK_SIZE*Scale;
if(TileMaxZ<StratumMinZ||TileMinZ>StratumMaxZ)continue; if(TileMaxZ<StratumMinZ||TileMinZ>StratumMaxZ)continue;
const VoxelForgeCore::Heightfield2D Block(Manifest.recipe,Plan);
if(!Block.isValid())continue;
struct FGridSample struct FGridSample
{ {
double Ground=0.0,Cap=0.0; double Ground=0.0,Cap=0.0;
@@ -576,10 +577,10 @@ namespace VoxelForgeCore
const WorldAssemblyManifest& Manifest,const FVoxelTileKey& Key,FVoxelMeshData& Mesh) const WorldAssemblyManifest& Manifest,const FVoxelTileKey& Key,FVoxelMeshData& Mesh)
{ {
FNativeCapRefinementStats Stats; FNativeCapRefinementStats Stats;
if(Key.Level<5||!Owner.Generator)return Stats; if(Key.Level<4||!Owner.Generator)return Stats;
constexpr int32 GridStepVoxels=128; const int32 GridStepVoxels=Key.Level<5?std::min(128,1<<Key.Level):128;
constexpr int64 Scale=WorldAssemblySubvoxelsPerVoxel; constexpr int64 Scale=WorldAssemblySubvoxelsPerVoxel;
constexpr int64 GridStepSubvoxels=GridStepVoxels*Scale; const int64 GridStepSubvoxels=static_cast<int64>(GridStepVoxels)*Scale;
const FIntVector TileOrigin=Key.OriginVoxels(); const FIntVector TileOrigin=Key.OriginVoxels();
const int32 TileExtent=Key.ExtentVoxels(); const int32 TileExtent=Key.ExtentVoxels();
if(TileExtent<=0||TileExtent%GridStepVoxels!=0)return Stats; if(TileExtent<=0||TileExtent%GridStepVoxels!=0)return Stats;
@@ -599,11 +600,10 @@ namespace VoxelForgeCore
for(const auto& Plan:Manifest.plan.strates) for(const auto& Plan:Manifest.plan.strates)
{ {
const int64 StratumMinZ=static_cast<int64>(Plan.bottomChunkZ)*CHUNK_SIZE*Scale;
const int64 StratumMaxZ=(static_cast<int64>(Plan.topChunkZ)+1)*CHUNK_SIZE*Scale;
if(TileMaxZ<StratumMinZ||TileMinZ>StratumMaxZ)continue;
const VoxelForgeCore::Heightfield2D Block(Manifest.recipe,Plan); const VoxelForgeCore::Heightfield2D Block(Manifest.recipe,Plan);
if(!Block.isValid())continue; if(!Block.isValid())continue;
const auto CapHeightBounds=Block.capHeightBounds();
if(TileMaxZ<CapHeightBounds[0]||TileMinZ>CapHeightBounds[1])continue;
struct FGridSample struct FGridSample
{ {
@@ -623,7 +623,7 @@ namespace VoxelForgeCore
const auto Column=Block.point(XSub,YSub,0); const auto Column=Block.point(XSub,YSub,0);
auto& Sample=Grid[GridIndex(X,Y)]; auto& Sample=Grid[GridIndex(X,Y)];
Sample.Ground=static_cast<double>(Column.groundHeightSubvoxels); Sample.Ground=static_cast<double>(Column.groundHeightSubvoxels);
Sample.Cap=static_cast<double>(Column.capHeightSubvoxels); Sample.Cap=static_cast<double>(Block.farSurfaceCapHeightSubvoxels(XSub,YSub));
int32 Dominant=0,Neighbor=0;float Blend=0.0f; int32 Dominant=0,Neighbor=0;float Blend=0.0f;
Owner.Generator->GetBiomeMaterialAt(static_cast<float>(XSub)/Scale, Owner.Generator->GetBiomeMaterialAt(static_cast<float>(XSub)/Scale,
static_cast<float>(YSub)/Scale,static_cast<float>(Sample.Cap/Scale), static_cast<float>(YSub)/Scale,static_cast<float>(Sample.Cap/Scale),
@@ -1160,6 +1160,71 @@ namespace VoxelForgeCore
VoxelClipmapDesiredTiles::Build(center,minZ,maxZ,parameters,nativeTiles); VoxelClipmapDesiredTiles::Build(center,minZ,maxZ,parameters,nativeTiles);
std::vector<TileKey> tiles;tiles.reserve(static_cast<std::size_t>(nativeTiles.Num())); std::vector<TileKey> tiles;tiles.reserve(static_cast<std::size_t>(nativeTiles.Num()));
for(const FVoxelTileKey& key:nativeTiles)tiles.push_back(FromNative(key)); for(const FVoxelTileKey& key:nativeTiles)tiles.push_back(FromNative(key));
if(impl_->worldAssembly&&!impl_->worldAssembly->plan.strates.empty())
{
constexpr std::int64_t SubvoxelsPerVoxel=WorldAssemblySubvoxelsPerVoxel;
const auto FloorDiv64=[](std::int64_t Value,std::int64_t Divisor)
{
const std::int64_t Quotient=Value/Divisor,Remainder=Value%Divisor;
return Quotient-((Remainder!=0&&Remainder<0)?1:0);
};
std::set<std::array<std::int32_t,3>> FarSurfaceColumns;
std::set<std::array<std::int32_t,4>> ExistingKeys;
for(const TileKey& Key:tiles)
{
ExistingKeys.insert({Key.coordinate.x,Key.coordinate.y,Key.coordinate.z,Key.lod});
if(Key.lod>=4&&Key.lod<=8)
FarSurfaceColumns.insert({Key.coordinate.x,Key.coordinate.y,Key.lod});
}
for(const auto& Plan:impl_->worldAssembly->plan.strates)
{
const Heightfield2D Field(impl_->worldAssembly->recipe,Plan);
if(!Field.isValid())continue;
const auto CapBounds=Field.capHeightBounds();
for(const auto& Column:FarSurfaceColumns)
{
const std::int32_t Lod=Column[2];
const std::int64_t Extent=static_cast<std::int64_t>(32)<<Lod;
const std::int64_t TileHeightSubvoxels=Extent*SubvoxelsPerVoxel;
const std::int64_t MinimumBand=FloorDiv64(CapBounds[0],TileHeightSubvoxels);
const std::int64_t MaximumBand=FloorDiv64(CapBounds[1],TileHeightSubvoxels);
for(std::int64_t Band=MinimumBand;Band<=MaximumBand;++Band)
{
if(Band<MIN_int32||Band>MAX_int32)continue;
const std::array<std::int32_t,4> Identity={Column[0],Column[1],
static_cast<std::int32_t>(Band),Lod};
if(!ExistingKeys.insert(Identity).second)continue;
const std::int64_t CandidateMinX=static_cast<std::int64_t>(Identity[0])*Extent;
const std::int64_t CandidateMinY=static_cast<std::int64_t>(Identity[1])*Extent;
const std::int64_t CandidateMinZ=
static_cast<std::int64_t>(Identity[2])*Extent;
bool bOverlapsClipmapTile=false;
for(const TileKey& Existing:tiles)
{
const std::int64_t ExistingExtent=static_cast<std::int64_t>(32)<<Existing.lod;
const std::int64_t ExistingMinX=static_cast<std::int64_t>(Existing.coordinate.x)*ExistingExtent;
const std::int64_t ExistingMinY=static_cast<std::int64_t>(Existing.coordinate.y)*ExistingExtent;
const std::int64_t ExistingMinZ=static_cast<std::int64_t>(Existing.coordinate.z)*ExistingExtent;
const bool bOverlapX=ExistingMinX<CandidateMinX+Extent
&&CandidateMinX<ExistingMinX+ExistingExtent;
const bool bOverlapY=ExistingMinY<CandidateMinY+Extent
&&CandidateMinY<ExistingMinY+ExistingExtent;
const bool bOverlapZ=ExistingMinZ<CandidateMinZ+Extent
&&CandidateMinZ<ExistingMinZ+ExistingExtent;
if(bOverlapX&&bOverlapY&&bOverlapZ)
{bOverlapsClipmapTile=true;break;}
}
// Preserve the clipmap's non-overlapping 3D partition. Where another
// requested tile already occupies this cap volume, that tile owns the
// shared roof surface; never add an overlapping LOD sibling.
// Garder la partition 3D sans recouvrement. Une tuile deja presente
// possede la surface de toit partagee; ne pas ajouter un voisin LOD superpose.
if(bOverlapsClipmapTile)continue;
tiles.push_back({{Identity[0],Identity[1],Identity[2]},Identity[3]});
}
}
}
}
std::sort(tiles.begin(),tiles.end(),[&](const TileKey& a,const TileKey& b) std::sort(tiles.begin(),tiles.end(),[&](const TileKey& a,const TileKey& b)
{ {
const double da=TileDistanceSquared(a,{center.X,center.Y,center.Z}); const double da=TileDistanceSquared(a,{center.X,center.Y,center.Z});
@@ -1201,7 +1266,7 @@ namespace VoxelForgeCore
&&options.mesherRecipe!=MesherRecipe::worldAssemblerHybridV1 &&options.mesherRecipe!=MesherRecipe::worldAssemblerHybridV1
&&options.mesherRecipe!=MesherRecipe::worldAssemblerHybridV2) &&options.mesherRecipe!=MesherRecipe::worldAssemblerHybridV2)
{error="tile mesher recipe version is unsupported";return std::nullopt;} {error="tile mesher recipe version is unsupported";return std::nullopt;}
if(level>=5&&options.farHorizonVersion==0) if(level>=4&&options.farHorizonVersion==0)
{error="far-horizon recipe version must be nonzero";return std::nullopt;} {error="far-horizon recipe version must be nonzero";return std::nullopt;}
TArray<FVoxelHybridSharpFeatureBox> sharpFeatures; TArray<FVoxelHybridSharpFeatureBox> sharpFeatures;
if(options.mesherRecipe!=MesherRecipe::marchingCubesV1&&level==0&&impl_->worldAssembly) if(options.mesherRecipe!=MesherRecipe::marchingCubesV1&&level==0&&impl_->worldAssembly)
@@ -1501,7 +1566,7 @@ namespace VoxelForgeCore
(options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr); (options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr);
const double MeshingMilliseconds=std::chrono::duration<double,std::milli>( const double MeshingMilliseconds=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-MeshingStart).count(); std::chrono::steady_clock::now()-MeshingStart).count();
out.diagnostics.farHorizonVersion=key.lod>=5?options.farHorizonVersion:0; out.diagnostics.farHorizonVersion=key.lod>=4?options.farHorizonVersion:0;
const auto FarShellStart=std::chrono::steady_clock::now(); const auto FarShellStart=std::chrono::steady_clock::now();
const FStandaloneShapeWorld& farShape=tileShape.HasFarWorld()?tileShape:*impl_->shape; const FStandaloneShapeWorld& farShape=tileShape.HasFarWorld()?tileShape:*impl_->shape;
out.diagnostics.farShellRepairSkipped=bSkipFarShellRepair; out.diagnostics.farShellRepairSkipped=bSkipFarShellRepair;
@@ -1533,11 +1598,12 @@ namespace VoxelForgeCore
} }
out.diagnostics.farShellMilliseconds=std::chrono::duration<double,std::milli>( out.diagnostics.farShellMilliseconds=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-FarShellStart).count(); std::chrono::steady_clock::now()-FarShellStart).count();
if(impl_->worldAssembly&&key.lod>=5&&!bOverlapsPlayerEdit) if(impl_->worldAssembly&&key.lod>=4&&!bOverlapsPlayerEdit)
{ {
const auto HorizonStart=std::chrono::steady_clock::now(); const auto HorizonStart=std::chrono::steady_clock::now();
const auto GroundStats=RefineFarNativeGround( FNativeCapRefinementStats GroundStats;
world,*impl_->worldAssembly,nativeKey,mesh); if(key.lod>=4)
GroundStats=RefineFarNativeGround(world,*impl_->worldAssembly,nativeKey,mesh);
const auto CapStats=RefineFarNativeCap(world,*impl_->worldAssembly,nativeKey,mesh); const auto CapStats=RefineFarNativeCap(world,*impl_->worldAssembly,nativeKey,mesh);
out.diagnostics.farHorizonMilliseconds=std::chrono::duration<double,std::milli>( out.diagnostics.farHorizonMilliseconds=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-HorizonStart).count(); std::chrono::steady_clock::now()-HorizonStart).count();
@@ -1612,7 +1678,7 @@ namespace VoxelForgeCore
{ densityHash^=std::bit_cast<std::uint32_t>(value);densityHash*=1099511628211ull; } { densityHash^=std::bit_cast<std::uint32_t>(value);densityHash*=1099511628211ull; }
out.densityHash=densityHash;out.meshHash=HashViewerMesh(mesh); out.densityHash=densityHash;out.meshHash=HashViewerMesh(mesh);
out.payloadHash=HashPayload(mesh);out.topologyHash=HashTopology(mesh); out.payloadHash=HashPayload(mesh);out.topologyHash=HashTopology(mesh);
if(key.lod>=5) if(key.lod>=4)
{ {
out.meshHash=SaltFarHorizonHash(out.meshHash,options.farHorizonVersion); out.meshHash=SaltFarHorizonHash(out.meshHash,options.farHorizonVersion);
out.payloadHash=SaltFarHorizonHash(out.payloadHash,options.farHorizonVersion); out.payloadHash=SaltFarHorizonHash(out.payloadHash,options.farHorizonVersion);
+61 -1
View File
@@ -1463,8 +1463,9 @@ namespace world_assembly_detail
+secondaryHeight*(65536-primaryWeight))/65536; +secondaryHeight*(65536-primaryWeight))/65536;
out.biomeId=primary->stableId;out.neighborBiomeId=secondary->stableId; out.biomeId=primary->stableId;out.neighborBiomeId=secondary->stableId;
out.biomeWeightQ16=primaryWeight; out.biomeWeightQ16=primaryWeight;
out.capHeightSubvoxels=plan.capHeightSubvoxels+MulQ16(plan.capUndulationSubvoxels, std::int64_t cap=plan.capHeightSubvoxels+MulQ16(plan.capUndulationSubvoxels,
Noise2D(x,y,plan.capWavelengthSubvoxels,recipe.seed,0x4341505f554e4431ull)); Noise2D(x,y,plan.capWavelengthSubvoxels,recipe.seed,0x4341505f554e4431ull));
out.capHeightSubvoxels=cap;
out.waterSurfaceSubvoxels=plan.waterSurfaceSubvoxels; out.waterSurfaceSubvoxels=plan.waterSurfaceSubvoxels;
return out; return out;
} }
@@ -1816,6 +1817,65 @@ bool Heightfield2D::isValid() const noexcept
}); });
} }
std::array<std::int64_t,2> Heightfield2D::capHeightBounds() const noexcept
{
if(!isValid())return {0,0};
long double Minimum=std::numeric_limits<long double>::infinity();
long double Maximum=-std::numeric_limits<long double>::infinity();
const auto IncludeBiome=[&](const WorldAssemblyBiomeProfileV1& Biome)
{
const long double Base=Biome.hasCapProfile
?static_cast<long double>(Biome.capHeightSubvoxels)
:static_cast<long double>(plan_->capHeightSubvoxels);
const long double Undulation=Biome.hasCapProfile
?static_cast<long double>(Biome.capUndulationSubvoxels)
:static_cast<long double>(plan_->capUndulationSubvoxels);
const long double Detail=Biome.hasCapProfile
?static_cast<long double>(Biome.capDetailAmplitudeSubvoxels)
:static_cast<long double>(plan_->capDetailAmplitudeSubvoxels);
const long double Ridge=Biome.hasCapProfile
?static_cast<long double>(Biome.capRidgeAmplitudeSubvoxels)
:static_cast<long double>(plan_->capRidgeAmplitudeSubvoxels);
Minimum=std::min(Minimum,Base-Undulation-Detail-Ridge);
Maximum=std::max(Maximum,Base+Undulation);
};
if(recipe_->biomes.empty())
{
const WorldAssemblyBiomeProfileV1 Fallback{};
IncludeBiome(Fallback);
}
else for(const auto& Biome:recipe_->biomes)IncludeBiome(Biome);
long double FeatureAmplitude=0.0L;
for(const auto& Feature:plan_->features)
if(Feature.type==3)
FeatureAmplitude+=std::abs(static_cast<long double>(Feature.parametersSubvoxels[0]));
Minimum-=FeatureAmplitude;Maximum+=FeatureAmplitude;
if(recipe_->evaluatorVersion>=5)
{
const long double TopSubvoxels=(static_cast<long double>(plan_->topChunkZ)+1.0L)
*CoreTileVoxelCount*WorldAssemblySubvoxelsPerVoxel;
Minimum=std::min(Minimum,TopSubvoxels);
Maximum=std::min(Maximum,TopSubvoxels);
}
const long double Int64Minimum=static_cast<long double>(std::numeric_limits<std::int64_t>::min());
const long double Int64Maximum=static_cast<long double>(std::numeric_limits<std::int64_t>::max());
return {static_cast<std::int64_t>(std::clamp(Minimum,Int64Minimum,Int64Maximum)),
static_cast<std::int64_t>(std::clamp(Maximum,Int64Minimum,Int64Maximum))};
}
std::int64_t Heightfield2D::farSurfaceCapHeightSubvoxels(std::int64_t x,
std::int64_t y) const noexcept
{
if(!isValid())return 0;
const HeightfieldPointSample column=point(x,y,0);
if(recipe_->evaluatorVersion<5)return column.capHeightSubvoxels;
// Clip only the far render surface. Keep the near density evaluator bit-identical.
// Borner uniquement le plafond lointain. L'evaluateur de densite proche reste identique.
const std::int64_t ownedUpperBoundary=(static_cast<std::int64_t>(plan_->topChunkZ)+1)
*CoreTileVoxelCount*WorldAssemblySubvoxelsPerVoxel;
return std::min(column.capHeightSubvoxels,ownedUpperBoundary);
}
WorldAssemblyFeatureTileListV1 Heightfield2D::buildFeatureTileList( WorldAssemblyFeatureTileListV1 Heightfield2D::buildFeatureTileList(
const std::array<std::int64_t,3>& minimum, const std::array<std::int64_t,3>& minimum,
const std::array<std::int64_t,3>& maximum) const const std::array<std::int64_t,3>& maximum) const
@@ -76,6 +76,25 @@ Nothing about the brief or the order of your runs made this round harder. The se
— Luna — Luna
## World assembler S4 / M3 Round 8 — 2026-10-19
SurfaceWorld's distant ceiling now uses the shared planar heightfield and closes at its owning
strate boundary. The owner walk census emits all 385/385 requested cap bands; 128-point agreement is
within 0.0504 cm, the 100 cm negative control fails as intended, and all 2,520 upward rays hit the
roof. LOD0–LOD3 hashes remain identical to S3/S2. Warmed SurfaceWorld LOD7/8 p95 changes are +0.03%
and +0.11%; the full cold clipmap adds 132 ms. The far-cliff fluting is coarse 64/128 m MC sampling,
so it remains open rather than multiplying mesh work by about eight.
Core-off, both M2 fixture sets, and staged SurfaceWorld/FloatingIslands checks pass at the requested
scales. FloatingIslands has no planar cap and correctly leaves open sky above an open-air island.
Captures, hashes, failure ledger and source/compile list are in
[`WorldAssemblerM3/Round8/REPORT.md`](../../WorldAssemblerM3/Round8/REPORT.md). CODEMAP section 3 and
ARCHITECTURE section 8.25 are updated. No new source file, owner asset edit, commit, push or stash.
— Luna
## Claude, after step 26 (2026-10-08, night) ## Claude, after step 26 (2026-10-08, night)
Thank you, Luna. Naming `DEVICE_HUNG` down to the tile and the slot was the best kind of answer: Thank you, Luna. Naming `DEVICE_HUNG` down to the tile and the slot was the best kind of answer:
the owner's PC has had unexplained hard reboots, and "it was our out-of-bounds copy, in an audit-only the owner's PC has had unexplained hard reboots, and "it was our out-of-bounds copy, in an audit-only
@@ -0,0 +1,137 @@
# World assembler S4 / M3 Round 8
Date: 2026-10-19. Base: S3 / `d0f97bf`. No commit, push or stash.
## Result
SurfaceWorld now has a continuous far ceiling at LOD5–8, projected from the same declared planar
heightfield and closed at the owning strate's upper boundary. The staged owner's real plan and walk
view pass cap census, root agreement and upward-ray coverage. LOD0–LOD3 hashes still match S3 and S2.
The far recipe is version 2. FloatingIslands still shows open sky overhead; it has no declared planar
cap, and that is expected for an open-air island strate.
## Count first: why S3 looked detached
The pre-fix owner-camera audit found 361 far XY tiles and 786,153 profile samples; all profile samples
had a roof. Of 401 cap bands requested, 401 emitted triangles, with 0 requested bands empty and 0
clipmap culls. So the proposed ground-height gate was not leaving requested cap bands unmeshed on this
walk view. The 128-point cap-to-near-root check did fail: maximum vertical error was 823.8525 cm
(one sample was -447.0469 voxels at the far cap versus -480.001 at the near root). The failure was
the cap datum sitting above its owning boundary, which made the roof read as detached pieces. The
128-voxel lattice itself was not leaving empty bands.
The fix is a render-surface projection to `(topChunkZ + 1) * 32` voxels, the owning strate's upper
boundary. `Heightfield2D::point()` and density values remain unchanged. The far cap and its bounds use
the shared projected height; the tile selector adds only non-overlapping cap bands. All samples use a
global XY lattice so neighboring cap tiles share edges. LOD4 uses a 16-voxel grid step; LOD5–8 use
128 voxels.
Post-fix audit: 385/385 requested cap bands emitted positive-area triangles (LOD4 88/88, LOD5 48/48,
LOD6 48/48, LOD7 48/48, LOD8 153/153); 0 empty and 0 culled. Across 128 shared XY points, far cap to
near-root error is at most 0.0504 cm (2 cm tolerance). A 100 cm datum-offset negative control measured
100.0015 cm and was detected. The S3 ground agreement still passes: max LOD7/8 to near-root error
0.0427 cm, cross-LOD error 0, and its 100 cm control is detected.
Coverage uses emitted cap triangles, not the height profile alone. At each of 33 owner route points
and two camera points, 72 upward rays span elevations 5, 10, 12, 15, 30, 45, 60, 75 and 90 degrees
over eight azimuths. Result: 2,520/2,520 hit the cap before the far clip or clipmap edge (100%);
0 profile-roof, mesh or clip-edge misses. All 35 points individually reached 100%. This verifies the
owner route and camera samples, not every possible point in the full strate volume.
## Visuals and fluting
Matched captures use the Round 7 views, the same staged plan and camera positions, 60 fps/VSync, and
the viewer closed after capture. Each S4 JPEG is 640x400 and under 19 KB.
| View | S3 | S4 | Readout |
|---|---|---|---|
| Owner walk horizon | [S3](../Round7/captures/after_s3_surface.jpg) | [S4](captures/s4_walk_horizon.jpg) | Walk horizon view after cap repair. |
| Cap at distance | [S3](../Round7/captures/after_s3_cap.jpg) | [S4](captures/s4_cap_distance.jpg) | The isolated-looking pieces are gone. |
| Far view, y=-1800 m | [S3](../Round7/captures/after_s3_far.jpg) | [S4](captures/s4_far_y_minus_1800.jpg) | Far-cliff fluting remains. |
| Looking straight up, far distance | — | [S4](captures/s4_far_straight_up.jpg) | Dark and low contrast; geometry/ray audit establishes the roof, pixels alone do not distinguish roof from dark sky. |
Fluting is coarse marching-cubes sampling on steep ground faces. Each far tile spans 16 coarse cells
per side: at 25 cm, LOD7's step is 256 voxels (64 m), LOD8's is 512 voxels (128 m). The strips cross
tile faces rather than appearing only at LOD seams; they predate S4 in S2/S3. Certified SurfaceWorld
tiles skip shell repair, native replacement affects upward-facing ground only, and this is not a
normal or backface-culling artifact. Refining all steep faces would multiply far 3D mesh work by
roughly eight; that is not a cheap shared fix, so S4 leaves it open.
## Timing, determinism and regression checks
SurfaceWorld timings are warmed p95s over the same eight staged 25 cm owner keys per LOD as S3.
Runs were serial at normal priority with no overlapping build or Unreal process. These are tile times,
not frame times.
| LOD | S3 full p95 | S4 full p95 | Delta |
|---:|---:|---:|---:|
| 7 | 19.801 ms | 19.8069 ms | +0.0059 ms (+0.03%) |
| 8 | 58.346 ms | 58.4113 ms | +0.0653 ms (+0.11%) |
The standalone cold full-clipmap benchmark took 3.107 s versus S3's 2.975 s on the same 2,553-tile,
12-worker workload: +132 ms (+4.4%). Far closure is therefore effectively flat in warmed owner tile
p95, with a small whole-clipmap cost. LOD8 cap refinement covered 385 tiles / 757,540 grid cells and
generated 1,515,082 cap triangles.
* SurfaceWorld BuildHost at 25 cm: PASS; W1/W8/W16 stable at density `0cd2a1a4152ee4aa`, mesh
`80a5b054b904b1a2`, payload `bf2f1b40dff38acc`, topology `56074198682d5e18`.
* FloatingIslands BuildHost at 25 cm: PASS; W1/W8/W16 stable and standalone hashes match. Its plan
contains 4 islands, 3 connectors and 28 route segments; water remains fixed.
* Core off / legacy at 12.5 cm: PASS.
* M2 HybridV1 and HybridV2 fixtures at 25 cm: PASS; each reports five analytic fixtures and one
border fixture.
* Standalone W1/W8/W16 determinism: PASS. Owner clipmap density hash remains
`c10697a8f48380c7`.
* LOD0–LOD3 hashes match S3 and S2 exactly:
| 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` |
Far output changes are salted by far recipe v2 in mesh, payload and topology hashes from LOD4 upward.
The final Standalone build and BuildHost Core compile/link-safety check pass. The editor commandlet
uses explicit RealtimeMesh section ranges for cap-only and ground/cap tiles. Final reruns had no
ensure. No new `UECC-*` folder appeared; the only crash directory is the pre-existing 2026-09-17
folder. No run hit
`0xC0000142`; all commandlets stayed below 4 GiB peak private memory. The surface commandlet was the
longest run at about 10 minutes, within the 15-minute limit.
The Standalone build also printed nonfatal environment/compiler warnings: `vswhere.exe` was not
recognized by the build script, and C4838 remains at `VoxelStrateManager.cpp:3974–3975`.
### Failed and retried runs
* The first post-fix coverage pass used a camera above the roof and reported 97.143% (72 rays, all
misses from that invalid origin). Re-running from the staged owner walk height passed 100%.
* The first clamp used the wrong chunk boundary and altered point density, changing the LOD3 density
hash. It was corrected to the upper boundary and render-only API; final LOD0–LOD3 hashes match S3.
* One hash invocation omitted `--stream-benchmark` and entered the interactive viewer; it was stopped
after 146 s and rerun with the correct mode. An absolute capture output path was split at the
workspace spaces by `Start-Process`; relative paths worked. A JPEG helper syntax error was corrected.
* An early BuildHost mesh apply raised an ensure when a cap material section did not yet exist.
Explicit `CreateSection` ranges fixed it; final SurfaceWorld and fixture runs pass without a new
crash or ensure. No `0xC0000142` retry was needed.
## Source and compile spots
No new source file was added. Modified sources:
* `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h`,
`Standalone/Core/src/WorldAssemblerInternal.h`, `Standalone/Core/src/VoxelForgeCore.cpp` — shared
cap declaration API, clipped render surface, conservative bounds, cap tile selection/emission,
and far recipe version; compile through the Standalone Core build and BuildHost Core.
* `Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp` — owner cap census, agreement and
ray coverage audit; compile through the editor module / BuildHost commandlet.
* `Source/VoxelForge/Private/VoxelWorld.cpp` — explicit realtime mesh section ranges; compile through
the VoxelForge runtime module.
* `Standalone/Source/StandaloneViewer.cpp` — standalone audit and matched capture support; compile
through `Standalone/Build.ps1 -Target All`.
`CODEMAP.md` section 3 and `ARCHITECTURE.md` section 8.25 are updated. The API leaves the roof family
choice in the declared plan, so future cave-in-mountain families can get a far roof by declaring one;
no SurfaceWorld-only geometry rule is required. The owner need not change assets or settings: keep
Core enabled at 25 cm. Sprint streaming order and the 1.57 s cold-view target were not changed or
re-measured.
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

+438 -3
View File
@@ -22,6 +22,7 @@
#include <windowsx.h> #include <windowsx.h>
#include <atomic> #include <atomic>
#include <array>
#include <bit> #include <bit>
#include <complex> #include <complex>
#include <cstdio> #include <cstdio>
@@ -37,6 +38,7 @@
#include <mutex> #include <mutex>
#include <numeric> #include <numeric>
#include <optional> #include <optional>
#include <set>
#include <sstream> #include <sstream>
#include <thread> #include <thread>
#include <unordered_map> #include <unordered_map>
@@ -8311,8 +8313,435 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
return true; return true;
} }
bool RunWorldAssemblerCapAudit(FStandaloneOwnerWorld& owner,
const VoxelForgeCore::ResolvedWorldSnapshot& snapshot,VoxelForgeCore::World& world,
VoxelForgeCore::Vec3d walkCameraVoxels)
{
// This audit follows the shipped owner manifest and Core clipmap, not the transient BuildHost fixture.
// Cet audit suit le manifeste owner livre et le clipmap Core, pas le fixture BuildHost transitoire.
constexpr std::int64_t Scale=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
constexpr double Pi=3.14159265358979323846;
constexpr double RayStepVoxels=128.0;
constexpr double AgreementToleranceCm=2.0;
constexpr double NegativeDatumOffsetCm=100.0;
using FTileId=std::array<std::int32_t,4>;
using FXYLod=std::array<std::int32_t,3>;
const double VoxelSizeCm=static_cast<double>(world.configuration().voxelSizeMicrons)/10000.0;
const auto IdFor=[](const VoxelForgeCore::TileKey& key)
{return FTileId{key.coordinate.x,key.coordinate.y,key.coordinate.z,key.lod};};
const auto FloorDiv64=[](std::int64_t value,std::int64_t divisor)
{
const std::int64_t quotient=value/divisor,remainder=value%divisor;
return quotient-((remainder!=0&&remainder<0)?1:0);
};
const auto* manifest=snapshot.worldAssembly();
if(!manifest||manifest->plan.strates.empty())
{std::fprintf(stderr,"[WorldAssemblerFarCapAudit] no SurfaceWorld plan; status=FAIL\n");return false;}
const auto planIt=std::find_if(manifest->plan.strates.begin(),manifest->plan.strates.end(),
[](const auto& plan){return !plan.route.empty();});
if(planIt==manifest->plan.strates.end())
{std::fprintf(stderr,"[WorldAssemblerFarCapAudit] no authored walk route; status=FAIL\n");return false;}
const auto& plan=*planIt;
const VoxelForgeCore::Heightfield2D field(manifest->recipe,plan);
if(!field.isValid())
{std::fprintf(stderr,"[WorldAssemblerFarCapAudit] invalid Heightfield2D; status=FAIL\n");return false;}
struct FView
{
VoxelForgeCore::Vec3d Origin{};
std::vector<VoxelForgeCore::TileKey> Keys;
std::set<FTileId> KeySet;
VoxelForgeCore::TileGenerationOptions Options;
double ClipMinX=std::numeric_limits<double>::infinity();
double ClipMinY=std::numeric_limits<double>::infinity();
double ClipMaxX=-std::numeric_limits<double>::infinity();
double ClipMaxY=-std::numeric_limits<double>::infinity();
double ClipMaxZ=-std::numeric_limits<double>::infinity();
};
const auto MakeView=[&](VoxelForgeCore::Vec3d origin)
{
FView view;view.Origin=origin;
const FStandaloneDesiredTileSet native=StandaloneWorldMesher::BuildDesiredSet(owner,
FVector(origin.x,origin.y,origin.z));
view.Options.bandMinZVoxel=native.bHasStrate
?native.StrateBottomChunkZ*CHUNK_SIZE:MIN_int32;
view.Options.bandMaxZVoxel=native.bHasStrate
?(native.StrateTopChunkZ+1)*CHUNK_SIZE:MAX_int32;
view.Keys=world.desiredTiles(origin);
for(const auto& key:view.Keys)
{
view.KeySet.insert(IdFor(key));
const double extent=static_cast<double>(CHUNK_SIZE<<key.lod);
view.ClipMaxZ=std::max(view.ClipMaxZ,(key.coordinate.z+1.0)*extent);
if(key.lod<4||key.lod>8)continue;
view.ClipMinX=std::min(view.ClipMinX,key.coordinate.x*extent);
view.ClipMinY=std::min(view.ClipMinY,key.coordinate.y*extent);
view.ClipMaxX=std::max(view.ClipMaxX,(key.coordinate.x+1.0)*extent);
view.ClipMaxY=std::max(view.ClipMaxY,(key.coordinate.y+1.0)*extent);
}
return view;
};
const FView cameraView=MakeView(walkCameraVoxels);
std::set<FXYLod> farXY;
for(const auto& key:cameraView.Keys)
if(key.lod>=4&&key.lod<=8)farXY.insert({key.coordinate.x,key.coordinate.y,key.lod});
std::set<FTileId> capBands;
std::uint64_t profileSamples=0,profileSamplesWithoutRoof=0;
for(const FXYLod& xy:farXY)
{
const std::int32_t lod=xy[2];
const std::int64_t extent=static_cast<std::int64_t>(CHUNK_SIZE)<<lod;
const std::int64_t originX=static_cast<std::int64_t>(xy[0])*extent;
const std::int64_t originY=static_cast<std::int64_t>(xy[1])*extent;
const std::int64_t gridStep=lod<5?std::min<std::int64_t>(128,1ll<<lod):128;
for(std::int64_t y=0;y<=extent;y+=gridStep)
for(std::int64_t x=0;x<=extent;x+=gridStep)
{
++profileSamples;
const auto column=field.point((originX+x)*Scale,(originY+y)*Scale,0);
const std::int64_t farCap=field.farSurfaceCapHeightSubvoxels(
(originX+x)*Scale,(originY+y)*Scale);
if(farCap<=column.groundHeightSubvoxels)
{++profileSamplesWithoutRoof;continue;}
const std::int64_t band=FloorDiv64(farCap,extent*Scale);
if(band>=MIN_int32&&band<=MAX_int32)
capBands.insert({xy[0],xy[1],static_cast<std::int32_t>(band),lod});
if(farCap%(extent*Scale)==0&&band>MIN_int32)
capBands.insert({xy[0],xy[1],static_cast<std::int32_t>(band-1),lod});
}
}
struct FAgreement {std::int64_t X=0,Y=0;double FarZ=0.0;};
struct FGeometry
{
std::vector<std::array<VoxelForgeCore::Vec3d,3>> DownwardTriangles;
std::uint64_t CapTriangles=0;
std::vector<FAgreement> AgreementVertices;
};
constexpr std::int64_t AgreementGrid=128*Scale;
std::map<FTileId,FGeometry> geometryCache;
std::string coreError;
const auto GetGeometry=[&](const FTileId& id,const VoxelForgeCore::TileGenerationOptions& options)
->const FGeometry*
{
if(const auto found=geometryCache.find(id);found!=geometryCache.end())return &found->second;
const VoxelForgeCore::TileKey key{{id[0],id[1],id[2]},id[3]};
const auto mesh=world.generateTile(key,options,coreError);
if(!mesh)return nullptr;
FGeometry geometry;
geometry.CapTriangles=mesh->diagnostics.horizonCapTriangles;
const std::size_t capIndexStart=mesh->indices.size()
-static_cast<std::size_t>(geometry.CapTriangles)*3u;
for(std::size_t index=0;index+2<mesh->indices.size();index+=3)
{
const auto ia=mesh->indices[index],ib=mesh->indices[index+1],ic=mesh->indices[index+2];
if(ia>=mesh->vertices.size()||ib>=mesh->vertices.size()||ic>=mesh->vertices.size())continue;
const auto& a=mesh->vertices[ia];const auto& b=mesh->vertices[ib];const auto& c=mesh->vertices[ic];
if((a.normal.z+b.normal.z+c.normal.z)/3.0>=-0.05)continue;
geometry.DownwardTriangles.push_back({
VoxelForgeCore::Vec3d{a.position.x/VoxelSizeCm,a.position.y/VoxelSizeCm,a.position.z/VoxelSizeCm},
VoxelForgeCore::Vec3d{b.position.x/VoxelSizeCm,b.position.y/VoxelSizeCm,b.position.z/VoxelSizeCm},
VoxelForgeCore::Vec3d{c.position.x/VoxelSizeCm,c.position.y/VoxelSizeCm,c.position.z/VoxelSizeCm}});
}
for(std::size_t index=capIndexStart;index<mesh->indices.size();++index)
{
const std::uint32_t vertexIndex=mesh->indices[index];
if(vertexIndex>=mesh->vertices.size())continue;
const auto& vertex=mesh->vertices[vertexIndex];
if(vertex.normal.z>=-0.35)continue;
const std::int64_t x=static_cast<std::int64_t>(std::llround(
vertex.position.x/VoxelSizeCm*Scale));
const std::int64_t y=static_cast<std::int64_t>(std::llround(
vertex.position.y/VoxelSizeCm*Scale));
if(x%AgreementGrid!=0||y%AgreementGrid!=0)continue;
geometry.AgreementVertices.push_back({x,y,vertex.position.z/VoxelSizeCm});
}
return &geometryCache.emplace(id,std::move(geometry)).first->second;
};
std::uint64_t requested=0,emitted=0,requestedWithoutTriangles=0,culled=0;
std::uint64_t zeroAreaBoundaryTouches=0,unexplainedEmptyBands=0;
std::map<std::int32_t,std::array<std::uint64_t,4>> byLod;
std::set<std::pair<std::int64_t,std::int64_t>> agreementXY;
std::vector<FAgreement> agreementSamples;agreementSamples.reserve(128);
for(const FTileId& id:capBands)
{
auto& count=byLod[id[3]];++count[0];
if(!cameraView.KeySet.contains(id)){++culled;++count[1];continue;}
++requested;
const FGeometry* geometry=GetGeometry(id,cameraView.Options);
if(geometry==nullptr)
{
std::fprintf(stderr,"[WorldAssemblerFarCapAudit] tile generation failed key=(%d,%d,%d,lod%d): %s\n",
id[0],id[1],id[2],id[3],coreError.c_str());return false;
}
if(geometry->CapTriangles==0)
{
++requestedWithoutTriangles;++count[3];
const std::int64_t extent=static_cast<std::int64_t>(VoxelForgeCore::CoreTileVoxelCount)<<id[3];
const std::int64_t step=id[3]<5?std::min<std::int64_t>(128,1ll<<id[3]):128;
const std::int64_t minZ=static_cast<std::int64_t>(id[2])*extent*Scale;
const std::int64_t maxZ=minZ+extent*Scale;
const std::int64_t originX=static_cast<std::int64_t>(id[0])*extent;
const std::int64_t originY=static_cast<std::int64_t>(id[1])*extent;
std::uint64_t validSamples=0,boundarySamples=0,interiorSamples=0;
for(std::int64_t y=0;y<=extent;y+=step)
for(std::int64_t x=0;x<=extent;x+=step)
{
const auto column=field.point((originX+x)*Scale,(originY+y)*Scale,0);
const std::int64_t farCap=field.farSurfaceCapHeightSubvoxels(
(originX+x)*Scale,(originY+y)*Scale);
if(farCap<=column.groundHeightSubvoxels||farCap<minZ||farCap>maxZ)continue;
++validSamples;
if(farCap==minZ||farCap==maxZ)++boundarySamples;
else ++interiorSamples;
}
const bool zeroAreaTouch=validSamples>0&&boundarySamples==validSamples
&&interiorSamples==0;
if(zeroAreaTouch)++zeroAreaBoundaryTouches;else ++unexplainedEmptyBands;
std::printf("[WorldAssemblerFarCapEmptyBand] key=(%d,%d,%d,lod%d) valid_profile_samples=%llu boundary_samples=%llu interior_samples=%llu classification=%s\n",
id[0],id[1],id[2],id[3],static_cast<unsigned long long>(validSamples),
static_cast<unsigned long long>(boundarySamples),static_cast<unsigned long long>(interiorSamples),
zeroAreaTouch?"zero_area_boundary_touch":"UNEXPLAINED");
continue;
}
++emitted;++count[2];
for(const FAgreement& sample:geometry->AgreementVertices)
{
if(agreementSamples.size()>=128)break;
if(!agreementXY.insert({sample.X,sample.Y}).second)continue;
agreementSamples.push_back(sample);
}
}
std::uint64_t agreementCompared=0;
double maximumAgreementCm=0.0,maximumNegativeControlCm=0.0;
FAgreement maximumAgreementSample{};double maximumAgreementNearRoot=0.0;
for(const FAgreement& sample:agreementSamples)
{
const auto column=field.point(sample.X,sample.Y,0);
const std::int64_t farCap=field.farSurfaceCapHeightSubvoxels(sample.X,sample.Y);
if(farCap<=column.groundHeightSubvoxels)continue;
const double x=static_cast<double>(sample.X)/Scale,y=static_cast<double>(sample.Y)/Scale;
const double capVoxel=static_cast<double>(farCap)/Scale;
bool foundRoot=false;double nearRoot=0.0;
for(double radius=4.0;radius<=4096.0;radius*=2.0)
{
double lo=capVoxel-radius,hi=capVoxel+radius;
if(!(world.densityAt({x,y,lo})>=0.0f&&world.densityAt({x,y,hi})<0.0f))continue;
for(int iteration=0;iteration<16;++iteration)
{const double mid=(lo+hi)*0.5;if(world.densityAt({x,y,mid})>=0.0f)lo=mid;else hi=mid;}
nearRoot=(lo+hi)*0.5;foundRoot=true;break;
}
if(!foundRoot)continue;
const double sampleErrorCm=std::abs(sample.FarZ-nearRoot)*VoxelSizeCm;
if(sampleErrorCm>maximumAgreementCm)
{maximumAgreementCm=sampleErrorCm;maximumAgreementSample=sample;maximumAgreementNearRoot=nearRoot;}
maximumNegativeControlCm=std::max(maximumNegativeControlCm,
std::abs(sample.FarZ+NegativeDatumOffsetCm/VoxelSizeCm-nearRoot)*VoxelSizeCm);
++agreementCompared;
}
if(agreementCompared>0)
{
const double capVoxel=static_cast<double>(field.farSurfaceCapHeightSubvoxels(
maximumAgreementSample.X,maximumAgreementSample.Y))/Scale;
const double x=static_cast<double>(maximumAgreementSample.X)/Scale;
const double y=static_cast<double>(maximumAgreementSample.Y)/Scale;
std::printf("[WorldAssemblerFarCapAgreementMax] xy_vox=(%.1f,%.1f) profile_cap_vox=%.4f far_cap_vox=%.4f near_root_vox=%.4f density_at_cap=%.4f density_below=%.4f density_above=%.4f\n",
x,y,capVoxel,maximumAgreementSample.FarZ,maximumAgreementNearRoot,
world.densityAt({x,y,capVoxel}),world.densityAt({x,y,capVoxel-4.0}),
world.densityAt({x,y,capVoxel+4.0}));
}
const bool negativeControlDetected=agreementCompared>0
&&maximumNegativeControlCm>AgreementToleranceCm;
std::printf("[WorldAssemblerFarCapCensus] view=owner_camera far_xy_tiles=%zu profile_samples=%llu samples_without_roof=%llu cap_bands=%zu requested=%llu emitted=%llu requested_without_triangles=%llu zero_area_boundary_touches=%llu unexplained_empty_bands=%llu culled_by_clipmap=%llu\n",
farXY.size(),static_cast<unsigned long long>(profileSamples),
static_cast<unsigned long long>(profileSamplesWithoutRoof),capBands.size(),
static_cast<unsigned long long>(requested),static_cast<unsigned long long>(emitted),
static_cast<unsigned long long>(requestedWithoutTriangles),
static_cast<unsigned long long>(zeroAreaBoundaryTouches),
static_cast<unsigned long long>(unexplainedEmptyBands),static_cast<unsigned long long>(culled));
for(const auto& [lod,count]:byLod)
std::printf("[WorldAssemblerFarCapLod] lod=%d cap_bands=%llu culled=%llu emitted=%llu requested_without_triangles=%llu\n",
lod,static_cast<unsigned long long>(count[0]),static_cast<unsigned long long>(count[1]),
static_cast<unsigned long long>(count[2]),static_cast<unsigned long long>(count[3]));
std::printf("[WorldAssemblerFarCapAgreement] status=%s shared_points=%llu max_far_near_cm=%.4f tolerance_cm=%.2f negative_control=100cm_datum_offset max_error_cm=%.4f detected=%d\n",
agreementCompared>=32&&maximumAgreementCm<=AgreementToleranceCm&&negativeControlDetected?"PASS":"FAIL",
static_cast<unsigned long long>(agreementCompared),maximumAgreementCm,AgreementToleranceCm,
maximumNegativeControlCm,negativeControlDetected?1:0);
constexpr std::array<double,8> azimuths={0.0,45.0,90.0,135.0,180.0,225.0,270.0,315.0};
constexpr std::array<double,9> elevations={5.0,10.0,12.0,15.0,30.0,45.0,60.0,75.0,90.0};
const auto dot3=[](const VoxelForgeCore::Vec3d& a,const VoxelForgeCore::Vec3d& b)
{return a.x*b.x+a.y*b.y+a.z*b.z;};
const auto sub3=[](const VoxelForgeCore::Vec3d& a,const VoxelForgeCore::Vec3d& b)
{return VoxelForgeCore::Vec3d{a.x-b.x,a.y-b.y,a.z-b.z};};
const auto cross3=[](const VoxelForgeCore::Vec3d& a,const VoxelForgeCore::Vec3d& b)
{return VoxelForgeCore::Vec3d{a.y*b.z-a.z*b.y,a.z*b.x-a.x*b.z,a.x*b.y-a.y*b.x};};
const auto rayTriangle=[&](const VoxelForgeCore::Vec3d& origin,const VoxelForgeCore::Vec3d& direction,
const std::array<VoxelForgeCore::Vec3d,3>& triangle,double minDistance,double maxDistance)
{
const auto edge1=sub3(triangle[1],triangle[0]),edge2=sub3(triangle[2],triangle[0]);
const auto p=cross3(direction,edge2);const double determinant=dot3(edge1,p);
if(std::abs(determinant)<1.0e-12)return false;
const double inverse=1.0/determinant;const auto t=sub3(origin,triangle[0]);
const double u=dot3(t,p)*inverse;if(u< -1.0e-8||u>1.0+1.0e-8)return false;
const auto q=cross3(t,edge1);const double v=dot3(direction,q)*inverse;
if(v< -1.0e-8||u+v>1.0+1.0e-8)return false;
const double distance=dot3(edge2,q)*inverse;
return distance>=minDistance-1.0e-6&&distance<=maxDistance+1.0e-6;
};
std::vector<VoxelForgeCore::Vec3d> walkOrigins;
walkOrigins.reserve(plan.route.size()+1);
for(const auto& point:plan.route)
walkOrigins.push_back({static_cast<double>(point.positionSubvoxels[0])/Scale,
static_cast<double>(point.positionSubvoxels[1])/Scale,
static_cast<double>(point.positionSubvoxels[2])/Scale});
const auto sameCamera=std::find_if(walkOrigins.begin(),walkOrigins.end(),[&](const auto& point)
{return std::abs(point.x-walkCameraVoxels.x)<1.0&&std::abs(point.y-walkCameraVoxels.y)<1.0
&&std::abs(point.z-walkCameraVoxels.z)<1.0;});
if(sameCamera==walkOrigins.end())walkOrigins.push_back(walkCameraVoxels);
const VoxelForgeCore::Vec3d farStraightUpCamera{0.0,-1800.0*4.0,walkCameraVoxels.z};
const std::size_t farStraightUpIndex=walkOrigins.size();
walkOrigins.push_back(farStraightUpCamera);
std::uint64_t rayCount=0,rayHits=0,profileCrossings=0,profileRoofCrossings=0;
std::uint64_t missesNoProfileRoof=0,missesMesh=0,missesClipEdge=0;
std::array<std::uint64_t,9> raysByElevation{},hitsByElevation{};
std::vector<std::string> firstMisses;firstMisses.reserve(8);
for(std::size_t viewIndex=0;viewIndex<walkOrigins.size();++viewIndex)
{
const std::uint64_t hitsBeforeView=rayHits;
const std::uint64_t raysBeforeView=rayCount;
const auto view=MakeView(walkOrigins[viewIndex]);
if(view.ClipMinX>view.ClipMaxX||view.ClipMinY>view.ClipMaxY)continue;
const auto startColumn=field.point(
static_cast<std::int64_t>(std::llround(view.Origin.x*Scale)),
static_cast<std::int64_t>(std::llround(view.Origin.y*Scale)),0);
const double startCap=static_cast<double>(field.farSurfaceCapHeightSubvoxels(
static_cast<std::int64_t>(std::llround(view.Origin.x*Scale)),
static_cast<std::int64_t>(std::llround(view.Origin.y*Scale))))/Scale;
for(std::size_t elevationIndex=0;elevationIndex<elevations.size();++elevationIndex)
for(const double azimuth:azimuths)
{
++rayCount;++raysByElevation[elevationIndex];
const double elevation=elevations[elevationIndex]*Pi/180.0;
const double radians=azimuth*Pi/180.0;
const VoxelForgeCore::Vec3d direction{
std::cos(elevation)*std::cos(radians),std::cos(elevation)*std::sin(radians),std::sin(elevation)};
double edgeDistance=std::numeric_limits<double>::infinity();
if(direction.x>1.0e-9)edgeDistance=std::min(edgeDistance,(view.ClipMaxX-view.Origin.x)/direction.x);
else if(direction.x< -1.0e-9)edgeDistance=std::min(edgeDistance,(view.ClipMinX-view.Origin.x)/direction.x);
if(direction.y>1.0e-9)edgeDistance=std::min(edgeDistance,(view.ClipMaxY-view.Origin.y)/direction.y);
else if(direction.y< -1.0e-9)edgeDistance=std::min(edgeDistance,(view.ClipMinY-view.Origin.y)/direction.y);
if(direction.z>1.0e-9)edgeDistance=std::min(edgeDistance,(view.ClipMaxZ-view.Origin.z)/direction.z);
if(!std::isfinite(edgeDistance)||edgeDistance<=0.0)
{++missesClipEdge;continue;}
bool profileCrossing=false,profileRoof=false;
double previousDistance=0.0,previousDelta=view.Origin.z-startCap;
for(double distance=std::min(RayStepVoxels,edgeDistance);;)
{
const auto column=field.point(
static_cast<std::int64_t>(std::llround((view.Origin.x+direction.x*distance)*Scale)),
static_cast<std::int64_t>(std::llround((view.Origin.y+direction.y*distance)*Scale)),0);
const double cap=static_cast<double>(field.farSurfaceCapHeightSubvoxels(
static_cast<std::int64_t>(std::llround((view.Origin.x+direction.x*distance)*Scale)),
static_cast<std::int64_t>(std::llround((view.Origin.y+direction.y*distance)*Scale))))/Scale;
const double delta=view.Origin.z+direction.z*distance-cap;
if(previousDelta<0.0&&delta>=0.0)
{
const double fraction=-previousDelta/(delta-previousDelta);
const double hitX=view.Origin.x+direction.x*(previousDistance+(distance-previousDistance)*fraction);
const double hitY=view.Origin.y+direction.y*(previousDistance+(distance-previousDistance)*fraction);
const auto hitColumn=field.point(static_cast<std::int64_t>(std::llround(hitX*Scale)),
static_cast<std::int64_t>(std::llround(hitY*Scale)),0);
profileCrossing=true;++profileCrossings;
profileRoof=field.farSurfaceCapHeightSubvoxels(
static_cast<std::int64_t>(std::llround(hitX*Scale)),
static_cast<std::int64_t>(std::llround(hitY*Scale)))
>hitColumn.groundHeightSubvoxels;
if(profileRoof)++profileRoofCrossings;
break;
}
previousDistance=distance;previousDelta=delta;
if(distance>=edgeDistance)break;
distance=std::min(distance+RayStepVoxels,edgeDistance);
}
bool meshHit=false,intersectedRequestedTile=false,intersectedEmptyTile=false;
const auto& rayOrigin=view.Origin;
for(const auto& key:view.Keys)
{
const double extent=static_cast<double>(CHUNK_SIZE<<key.lod);
const double minBounds[3]={key.coordinate.x*extent,key.coordinate.y*extent,key.coordinate.z*extent};
const double maxBounds[3]={minBounds[0]+extent,minBounds[1]+extent,minBounds[2]+extent};
const double origins[3]={rayOrigin.x,rayOrigin.y,rayOrigin.z};
const double directions[3]={direction.x,direction.y,direction.z};
double enter=0.0,exit=edgeDistance;bool intersects=true;
for(int axis=0;axis<3;++axis)
{
if(std::abs(directions[axis])<1.0e-12)
{if(origins[axis]<minBounds[axis]||origins[axis]>maxBounds[axis]){intersects=false;break;}continue;}
double tNear=(minBounds[axis]-origins[axis])/directions[axis];
double tFar=(maxBounds[axis]-origins[axis])/directions[axis];
if(tNear>tFar)std::swap(tNear,tFar);
enter=std::max(enter,tNear);exit=std::min(exit,tFar);
if(enter>exit){intersects=false;break;}
}
if(!intersects||exit<0.0||enter>edgeDistance)continue;
intersectedRequestedTile=true;
const FGeometry* geometry=GetGeometry(IdFor(key),view.Options);
if(geometry==nullptr)
{std::fprintf(stderr,"[WorldAssemblerFarCapAudit] ray tile generation failed: %s\n",coreError.c_str());return false;}
if(geometry->DownwardTriangles.empty()){intersectedEmptyTile=true;continue;}
for(const auto& triangle:geometry->DownwardTriangles)
if(rayTriangle(rayOrigin,direction,triangle,std::max(0.0,enter),std::min(edgeDistance,exit)))
{meshHit=true;break;}
if(meshHit)break;
}
if(meshHit){++rayHits;++hitsByElevation[elevationIndex];continue;}
if(!profileCrossing||!profileRoof)++missesNoProfileRoof;
else ++missesMesh;
if(firstMisses.size()<8)
{
std::ostringstream message;
message<<"view="<<viewIndex<<" elevation="<<elevations[elevationIndex]
<<" azimuth="<<azimuth<<" profile_crossing="<<(profileCrossing?1:0)
<<" profile_roof="<<(profileRoof?1:0)<<" requested_tile="<<(intersectedRequestedTile?1:0)
<<" empty_tile="<<(intersectedEmptyTile?1:0);
firstMisses.push_back(message.str());
}
}
const char* viewLabel=viewIndex<plan.route.size()?"route_point":
(viewIndex==farStraightUpIndex?"far_straight_up":"owner_camera");
std::printf("[WorldAssemblerFarCapCoverageView] view=%s index=%zu origin_vox=(%.1f,%.1f,%.1f) rays=%llu hits=%llu coverage_pct=%.3f\n",
viewLabel,viewIndex,walkOrigins[viewIndex].x,walkOrigins[viewIndex].y,
walkOrigins[viewIndex].z,static_cast<unsigned long long>(rayCount-raysBeforeView),
static_cast<unsigned long long>(rayHits-hitsBeforeView),
rayCount>raysBeforeView?100.0*static_cast<double>(rayHits-hitsBeforeView)/(rayCount-raysBeforeView):0.0);
}
std::printf("[WorldAssemblerFarCapCoverage] rays=%llu hits=%llu coverage_pct=%.3f profile_crossings=%llu profile_roof_crossings=%llu misses_no_profile_roof=%llu misses_mesh=%llu misses_clip_edge=%llu view_points=%zu\n",
static_cast<unsigned long long>(rayCount),static_cast<unsigned long long>(rayHits),
rayCount?100.0*static_cast<double>(rayHits)/rayCount:0.0,
static_cast<unsigned long long>(profileCrossings),static_cast<unsigned long long>(profileRoofCrossings),
static_cast<unsigned long long>(missesNoProfileRoof),static_cast<unsigned long long>(missesMesh),
static_cast<unsigned long long>(missesClipEdge),walkOrigins.size());
for(std::size_t i=0;i<elevations.size();++i)
std::printf("[WorldAssemblerFarCapCoverageElevation] elevation_deg=%.0f rays=%llu hits=%llu\n",
elevations[i],static_cast<unsigned long long>(raysByElevation[i]),
static_cast<unsigned long long>(hitsByElevation[i]));
for(const auto& miss:firstMisses)std::printf("[WorldAssemblerFarCapCoverageMiss] %s\n",miss.c_str());
const bool censusPassing=requested>0&&unexplainedEmptyBands==0&&culled==0;
const bool agreementPassing=agreementCompared>=32&&maximumAgreementCm<=AgreementToleranceCm
&&negativeControlDetected;
const bool coveragePassing=rayCount>0&&rayHits==rayCount;
std::printf("[WorldAssemblerFarCapAudit] census=%s agreement=%s coverage=%s status=%s\n",
censusPassing?"PASS":"FAIL",agreementPassing?"PASS":"FAIL",
coveragePassing?"PASS":"FAIL",censusPassing&&agreementPassing&&coveragePassing?"PASS":"FAIL");
return censusPassing&&agreementPassing&&coveragePassing;
}
int RunWorldAssemblerAudit(FStandaloneOwnerWorld& owner, int RunWorldAssemblerAudit(FStandaloneOwnerWorld& owner,
const VoxelForgeCore::ResolvedWorldSnapshot& snapshot,const std::string& captureDirectory) const VoxelForgeCore::ResolvedWorldSnapshot& snapshot,const std::string& captureDirectory,
VoxelForgeCore::World* compiledWorld,VoxelForgeCore::Vec3d walkCameraVoxels)
{ {
const auto* manifest=snapshot.worldAssembly(); const auto* manifest=snapshot.worldAssembly();
if(!manifest||manifest->plan.strates.empty()) if(!manifest||manifest->plan.strates.empty())
@@ -8858,9 +9287,11 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
std::printf("[WorldAssemblerReadiness] local_field_query=%s analytic_collision_validation=%s physics_collision_cooking=deferred_to_Unreal_adapter render_mesh_readiness=reported_by_matched_view_runs\n", std::printf("[WorldAssemblerReadiness] local_field_query=%s analytic_collision_validation=%s physics_collision_cooking=deferred_to_Unreal_adapter render_mesh_readiness=reported_by_matched_view_runs\n",
coreQueryPass?"ready_without_render_meshes":"not_ready", coreQueryPass?"ready_without_render_meshes":"not_ready",
routeAudit.passed?"route_sweep_ready":"not_ready"); routeAudit.passed?"route_sweep_ready":"not_ready");
const bool farCapAuditPassing=compiledWorld!=nullptr
&&RunWorldAssemblerCapAudit(owner,snapshot,*compiledWorld,walkCameraVoxels);
return bounds.passed&&shelfThickness.passed&&routeAudit.passed return bounds.passed&&shelfThickness.passed&&routeAudit.passed
&&routeSupportNegativeControl&&cliffRegressionPassing&&seamClassificationPassing &&routeSupportNegativeControl&&cliffRegressionPassing&&seamClassificationPassing
&&deterministic&&coreQueryPass?0:11; &&deterministic&&coreQueryPass&&farCapAuditPassing?0:11;
} }
} }
@@ -9254,7 +9685,11 @@ int main(int Argc,char** Argv)
App.bRoundTunnelEnhanced,Error); App.bRoundTunnelEnhanced,Error);
if(!App.Snapshot) if(!App.Snapshot)
{std::fprintf(stderr,"World assembler snapshot setup failed: %s\n",Error.c_str());return 2;} {std::fprintf(stderr,"World assembler snapshot setup failed: %s\n",Error.c_str());return 2;}
return RunWorldAssemblerAudit(*App.World,App.Snapshot,WorldAssemblyCaptureDir); App.bShapeWorld=true;
if(!App.BuildShapeWorldForCamera(Error,true,false,true))
{std::fprintf(stderr,"World assembler cap audit shape build failed: %s\n",Error.c_str());return 2;}
return RunWorldAssemblerAudit(*App.World,App.Snapshot,WorldAssemblyCaptureDir,
App.ShapeWorld.get(),{App.Position.X*4.0,App.Position.Y*4.0,App.Position.Z*4.0});
} }
const bool bNeedFarShapeWorld=bStreamBenchmark const bool bNeedFarShapeWorld=bStreamBenchmark
?(StreamBenchmarkMaxLevel<0||StreamBenchmarkMaxLevel>=App.World->FullResClipLevels) ?(StreamBenchmarkMaxLevel<0||StreamBenchmarkMaxLevel>=App.World->FullResClipLevels)