feat(core): ore vein records and a materialAt query, off by default (L50); research R18, R19
Versioned capsule vein records (stable ID, resource ID, endpoints, radius, seeded irregularity) resolved into the snapshot only when requested, placed beside the route and away from passages, landings and landmarks (forced-passage control rejected). The bound field table gains materialAt (host + two resource IDs + Q16 weights) behind a per-tile BVH; mesh export v2 carries per-vertex materials; a measurement-only yield computes removed resource volume (0 on already-air space). With veins on, every density, mesh, payload and topology hash is unchanged; demo off: owner hashes identical to L47. fast/medium/full PASS. Research R18 (cavern-graph planner) and R19 (why FloatingIslands costs ~10x SurfaceWorld). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
1 parent
b9ccff4ad6
commit
ca85e876ad
15 files changed
+1573
-19
No files matched your search
@@ -34,6 +34,7 @@ namespace
|
||||
float Height;
|
||||
float WorldPosition[3];
|
||||
float TileData[4];
|
||||
float Material[4]{};
|
||||
};
|
||||
|
||||
struct FScene
|
||||
@@ -121,11 +122,12 @@ namespace
|
||||
if(!File){Error="cannot open mesh scene";return false;}
|
||||
char Magic[8]{};
|
||||
File.read(Magic,sizeof(Magic));
|
||||
if(!File||std::memcmp(Magic,"VFMSH001",8)!=0)
|
||||
const bool HasMaterials=File&&std::memcmp(Magic,"VFMSH002",8)==0;
|
||||
if(!File||(!HasMaterials&&std::memcmp(Magic,"VFMSH001",8)!=0))
|
||||
{Error="mesh scene magic is invalid";return false;}
|
||||
std::uint32_t Version=0,ExpectedTiles=0;
|
||||
if(!Read(File,Version)||!Read(File,Scene.VoxelSizeMicrons)||!Read(File,ExpectedTiles)
|
||||
||Version!=1||Scene.VoxelSizeMicrons==0||ExpectedTiles>1000000)
|
||||
||Version!=(HasMaterials?2u:1u)||Scene.VoxelSizeMicrons==0||ExpectedTiles>1000000)
|
||||
{Error="mesh scene header is malformed";return false;}
|
||||
for(double& Value:Scene.CameraMeters)if(!Read(File,Value))
|
||||
{Error="mesh scene camera is truncated";return false;}
|
||||
@@ -161,6 +163,19 @@ namespace
|
||||
Scene.MinimumHeight=std::min(Scene.MinimumHeight,Scene.Vertices[I].Height);
|
||||
Scene.MaximumHeight=std::max(Scene.MaximumHeight,Scene.Vertices[I].Height);
|
||||
}
|
||||
if(HasMaterials)
|
||||
for(std::size_t I=Base;I<Base+VertexCount;++I)
|
||||
{
|
||||
std::uint16_t Host=0,Ids[2]{},Weights[2]{};
|
||||
if(!Read(File,Host)||!Read(File,Ids[0])||!Read(File,Ids[1])
|
||||
||!Read(File,Weights[0])||!Read(File,Weights[1]))
|
||||
{Error="tile material attributes are truncated";return false;}
|
||||
Scene.Vertices[I].Material[0]=static_cast<float>(Ids[0]);
|
||||
Scene.Vertices[I].Material[1]=static_cast<float>(Weights[0])/65535.0f;
|
||||
Scene.Vertices[I].Material[2]=static_cast<float>(Ids[1]);
|
||||
Scene.Vertices[I].Material[3]=static_cast<float>(Weights[1])/65535.0f;
|
||||
(void)Host;
|
||||
}
|
||||
const double TileVoxels=static_cast<double>(32u<<Key[3]);
|
||||
const float TileExtentMeters=static_cast<float>(TileVoxels
|
||||
*static_cast<double>(Scene.VoxelSizeMicrons)/1000000.0);
|
||||
@@ -540,10 +555,12 @@ layout(location=1) in vec3 inNormal;
|
||||
layout(location=2) in float inHeight;
|
||||
layout(location=3) in vec3 inWorldPosition;
|
||||
layout(location=4) in vec4 inTileData;
|
||||
layout(location=5) in vec4 inMaterial;
|
||||
out vec3 vNormal;
|
||||
out float vHeight;
|
||||
out vec3 vWorldPosition;
|
||||
out vec4 vTileData;
|
||||
out vec4 vMaterial;
|
||||
uniform float uTanHalfHorizontal;
|
||||
uniform float uTanHalfVertical;
|
||||
uniform float uNear;
|
||||
@@ -558,6 +575,7 @@ void main()
|
||||
vHeight=inHeight;
|
||||
vWorldPosition=inWorldPosition;
|
||||
vTileData=inTileData;
|
||||
vMaterial=inMaterial;
|
||||
})GLSL";
|
||||
static constexpr char FragmentSource[]=R"GLSL(#version 300 es
|
||||
precision highp float;
|
||||
@@ -565,6 +583,7 @@ in vec3 vNormal;
|
||||
in float vHeight;
|
||||
in vec3 vWorldPosition;
|
||||
in vec4 vTileData;
|
||||
in vec4 vMaterial;
|
||||
out vec4 outColor;
|
||||
uniform int uReadableMode;
|
||||
uniform int uTileEdges;
|
||||
@@ -582,6 +601,7 @@ void main()
|
||||
if(uClipEnabled!=0&&dot(vec4(vWorldPosition,1.0),uClipPlane)<0.0)discard;
|
||||
vec3 n=normalize(vNormal);
|
||||
vec3 color;
|
||||
float illumination=1.0;
|
||||
if(uReadableMode==0)
|
||||
{
|
||||
vec3 normalColor=abs(n)*0.78+vec3(0.12);
|
||||
@@ -597,10 +617,32 @@ void main()
|
||||
?mix(vec3(0.62,0.78,1.0),vec3(1.0,0.82,0.62),smoothHeightT)
|
||||
:vec3(1.0);
|
||||
float diffuse=max(dot(n,normalize(uLightDirection)),0.0);
|
||||
float illumination=uGrazingLight!=0?0.32+1.20*diffuse:
|
||||
illumination=uGrazingLight!=0?0.32+1.20*diffuse:
|
||||
uHeadlamp!=0?0.78+0.34*diffuse:0.62+0.38*diffuse;
|
||||
color=vec3(0.64,0.62,0.56)*heightTint*illumination;
|
||||
}
|
||||
if(uReadableMode!=0)
|
||||
{
|
||||
vec3 resourceColor=vec3(0.0);
|
||||
float resourceWeight=0.0;
|
||||
if(vMaterial.x>0.5)
|
||||
{
|
||||
float w=clamp(vMaterial.y,0.0,1.0);
|
||||
vec3 oreColor=vMaterial.x<1.5?vec3(0.94,0.36,0.12):
|
||||
vMaterial.x<2.5?vec3(0.20,0.48,0.92):vec3(0.25,0.88,0.84);
|
||||
resourceColor+=oreColor*w;resourceWeight+=w;
|
||||
}
|
||||
if(vMaterial.z>0.5)
|
||||
{
|
||||
float w=clamp(vMaterial.w,0.0,1.0);
|
||||
vec3 oreColor=vMaterial.z<1.5?vec3(0.94,0.36,0.12):
|
||||
vMaterial.z<2.5?vec3(0.20,0.48,0.92):vec3(0.25,0.88,0.84);
|
||||
resourceColor+=oreColor*w;resourceWeight+=w;
|
||||
}
|
||||
if(resourceWeight>0.0)
|
||||
color=mix(color,resourceColor/max(resourceWeight,0.001),
|
||||
clamp(resourceWeight,0.0,0.95))*illumination;
|
||||
}
|
||||
if(uTileEdges!=0&&vTileData.z>0.0)
|
||||
{
|
||||
vec2 local=mod(vWorldPosition.xy-vTileData.xy,vTileData.z);
|
||||
@@ -749,6 +791,8 @@ void main()
|
||||
reinterpret_cast<void*>(offsetof(FVertex,WorldPosition)));
|
||||
glEnableVertexAttribArray(4);glVertexAttribPointer(4,4,GL_FLOAT,GL_FALSE,sizeof(FVertex),
|
||||
reinterpret_cast<void*>(offsetof(FVertex,TileData)));
|
||||
glEnableVertexAttribArray(5);glVertexAttribPointer(5,4,GL_FLOAT,GL_FALSE,sizeof(FVertex),
|
||||
reinterpret_cast<void*>(offsetof(FVertex,Material)));
|
||||
glViewport(0,0,Width,Height);glDisable(GL_CULL_FACE);glEnable(GL_DEPTH_TEST);
|
||||
glDepthFunc(GL_LEQUAL);glClearColor(0.035f,0.055f,0.075f,1.0f);
|
||||
glClearDepthf(1.0f);glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -57,6 +58,8 @@ namespace
|
||||
bool FloatingIslandsRecipe=false;
|
||||
bool LandmarkChamberDemo=false;
|
||||
bool LandmarkL45RampControl=false;
|
||||
bool OreVeinsDemo=false;
|
||||
bool AuditOreVeinYield=false;
|
||||
bool AuditLandmarkReachability=false;
|
||||
bool TlsProbeRequested=false;
|
||||
int TlsProbeWorkers=0;
|
||||
@@ -620,6 +623,138 @@ namespace
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MakeOreVeinDemoRequests(
|
||||
const VoxelForgeCore::ResolvedLayoutSnapshotV3& layout,
|
||||
std::vector<VoxelForgeCore::ResolvedLayoutOreVeinRequestV1>& requests,
|
||||
std::string& error)
|
||||
{
|
||||
using namespace VoxelForgeCore;
|
||||
requests.clear();
|
||||
const auto surface=std::find_if(layout.strates.begin(),layout.strates.end(),
|
||||
[](const auto& value){return value.generatorKind=="SurfaceWorld";});
|
||||
if(surface==layout.strates.end()||layout.coreVoxelSizeMicrometres==0)
|
||||
{error="ore-vein demo needs a SurfaceWorld strate";return false;}
|
||||
constexpr double Q=static_cast<double>(WorldAssemblySubvoxelsPerVoxel);
|
||||
const double coreMicrometres=layout.coreVoxelSizeMicrometres;
|
||||
const auto addCapsule=[&](std::string id,std::uint16_t resourceId,
|
||||
std::array<double,3> base,std::array<double,3> tangent,
|
||||
std::array<double,3> side,double routeClearRadius,
|
||||
std::int64_t radius,std::int64_t irregularity,std::int64_t blend,
|
||||
std::int64_t length)
|
||||
{
|
||||
const auto normalize=[](std::array<double,3>& value)
|
||||
{
|
||||
const double length=std::sqrt(value[0]*value[0]+value[1]*value[1]
|
||||
+value[2]*value[2]);
|
||||
if(length<=1.0e-9)return false;
|
||||
for(double& component:value)component/=length;
|
||||
return true;
|
||||
};
|
||||
if(!normalize(tangent))return false;
|
||||
const double sideLength=std::sqrt(side[0]*side[0]+side[1]*side[1]);
|
||||
if(sideLength<=1.0e-9)return false;
|
||||
side[0]/=sideLength;side[1]/=sideLength;
|
||||
const double offset=routeClearRadius+radius+irregularity+200000.0;
|
||||
base[0]+=side[0]*offset;base[1]+=side[1]*offset;
|
||||
ResolvedLayoutOreVeinRequestV1 request;
|
||||
request.stableId=std::move(id);request.strateIndex=surface->strateIndex;
|
||||
request.resourceMaterialId=resourceId;request.placementRadiusMicrometres=1500000;
|
||||
request.radiusMicrometres=radius;
|
||||
request.irregularityAmplitudeMicrometres=irregularity;
|
||||
request.irregularityWavelengthMicrometres=1400000;
|
||||
request.blendWidthMicrometres=blend;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
const double half=static_cast<double>(length)*0.5*tangent[axis];
|
||||
request.anchorEndpointsMicrometres[0][axis]=static_cast<std::int64_t>(
|
||||
std::llround(base[axis]-half));
|
||||
request.anchorEndpointsMicrometres[1][axis]=static_cast<std::int64_t>(
|
||||
std::llround(base[axis]+half));
|
||||
}
|
||||
requests.push_back(std::move(request));return true;
|
||||
};
|
||||
std::vector<const ResolvedLayoutAnchorV2*> route;
|
||||
for(const auto& anchor:surface->anchors)
|
||||
if(anchor.kind=="route_point"&&anchor.values.size()>=3)route.push_back(&anchor);
|
||||
if(route.size()<12)
|
||||
{error="ore-vein demo needs at least twelve resolved SurfaceWorld route points";return false;}
|
||||
const auto routePoint=[&](const ResolvedLayoutAnchorV2& anchor)
|
||||
{
|
||||
return std::array<double,3>{
|
||||
anchor.values[0]*coreMicrometres/Q,
|
||||
anchor.values[1]*coreMicrometres/Q,
|
||||
anchor.values[2]*coreMicrometres/Q};
|
||||
};
|
||||
const auto routeRadius=[&](const ResolvedLayoutAnchorV2& anchor)
|
||||
{
|
||||
return anchor.values.size()>=4?anchor.values[3]*coreMicrometres/Q:0.0;
|
||||
};
|
||||
for(const auto [pointIndex,id,resourceId,sideSign]:{
|
||||
std::tuple<std::size_t,const char*,std::uint16_t,double>{7,
|
||||
"ore/sw-arrival-route-copper-v1",1,1.0},
|
||||
std::tuple<std::size_t,const char*,std::uint16_t,double>{10,
|
||||
"ore/sw-arrival-route-cobalt-v1",2,-1.0}})
|
||||
{
|
||||
const auto a=routePoint(*route[pointIndex]);
|
||||
const auto b=routePoint(*route[pointIndex+1]);
|
||||
std::array<double,3> tangent={b[0]-a[0],b[1]-a[1],b[2]-a[2]};
|
||||
std::array<double,3> side={-tangent[1]*sideSign,tangent[0]*sideSign,0.0};
|
||||
std::array<double,3> center={(a[0]+b[0])*0.5,(a[1]+b[1])*0.5,
|
||||
(a[2]+b[2])*0.5};
|
||||
const double clearance=std::max(routeRadius(*route[pointIndex]),
|
||||
routeRadius(*route[pointIndex+1]));
|
||||
if(!addCapsule(id,resourceId,center,tangent,side,clearance,
|
||||
resourceId==1?650000:750000,100000,600000,2500000))
|
||||
{error="ore-vein route capsule has a degenerate tangent";return false;}
|
||||
}
|
||||
const auto edge=std::find_if(layout.cavernEdges.begin(),layout.cavernEdges.end(),
|
||||
[&](const auto& value){return value.strateIndex==surface->strateIndex;});
|
||||
if(edge!=layout.cavernEdges.end())
|
||||
{
|
||||
constexpr double t=0.12,one=1.0-t;
|
||||
const std::array<double,4> weights={one*one*one,3.0*one*one*t,
|
||||
3.0*one*t*t,t*t*t};
|
||||
const std::array<double,4> derivative={-3.0*one*one,
|
||||
3.0*one*one-6.0*one*t,6.0*one*t-3.0*t*t,3.0*t*t};
|
||||
std::array<double,3> center{},tangent{};
|
||||
for(std::size_t control=0;control<4;++control)
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
const double point=edge->controlPointsMicrometres[control][axis];
|
||||
center[axis]+=weights[control]*point;
|
||||
tangent[axis]+=derivative[control]*point;
|
||||
}
|
||||
center[2]+=edge->boreHeightMicrometres*0.5;
|
||||
// The room lies west of the entry tangent; use the east wall for the
|
||||
// approach outcrop so its conservative bounds stay outside the room.
|
||||
std::array<double,3> side={tangent[1],-tangent[0],0.0};
|
||||
const double wallOffset=edge->boreRadiusMicrometres+800000.0+100000.0+200000.0;
|
||||
const double sideLength=std::sqrt(side[0]*side[0]+side[1]*side[1]);
|
||||
if(sideLength>1.0e-9)
|
||||
{side[0]*=wallOffset/sideLength;side[1]*=wallOffset/sideLength;}
|
||||
ResolvedLayoutOreVeinRequestV1 request;
|
||||
request.stableId="ore/sw-chamber-approach-quartz-v1";
|
||||
request.strateIndex=surface->strateIndex;request.resourceMaterialId=3;
|
||||
request.placementRadiusMicrometres=1000000;
|
||||
request.radiusMicrometres=800000;
|
||||
request.irregularityAmplitudeMicrometres=100000;
|
||||
request.irregularityWavelengthMicrometres=1800000;
|
||||
request.blendWidthMicrometres=600000;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
const double half=1250000.0*tangent[axis]
|
||||
/std::sqrt(tangent[0]*tangent[0]+tangent[1]*tangent[1]
|
||||
+tangent[2]*tangent[2]);
|
||||
request.anchorEndpointsMicrometres[0][axis]=static_cast<std::int64_t>(
|
||||
std::llround(center[axis]+side[axis]-half));
|
||||
request.anchorEndpointsMicrometres[1][axis]=static_cast<std::int64_t>(
|
||||
std::llround(center[axis]+side[axis]+half));
|
||||
}
|
||||
requests.push_back(std::move(request));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OverrideTransitions(VFJson::Value& root,int mode,int widthChunks,std::string& error)
|
||||
{
|
||||
auto* object=std::get_if<VFJson::Value::Object>(&root.Data);
|
||||
@@ -674,6 +809,8 @@ namespace
|
||||
" --profile-column-overlap --far-horizon-lod8-step=N --force-generate\n"
|
||||
" --seed=N --recipe=surface-world|floating-islands\n"
|
||||
" --landmark-chamber-demo (opt-in SW cavern room and swept tunnel)\n"
|
||||
" --ore-veins-demo (opt-in material-only SW capsule veins)\n"
|
||||
" --audit-ore-vein-yield (measure one deterministic sphere carve)\n"
|
||||
" --landmark-l45-ramp-control (use the buried L45 stamp geometry as an audit control)\n"
|
||||
" --resolved-layout-output=FILE (canonical plan-time layout snapshot)\n"
|
||||
" --resolved-layout-only (exit after plan and snapshot validation)\n"
|
||||
@@ -744,6 +881,8 @@ namespace
|
||||
}
|
||||
else if(arg=="--landmark-chamber-demo")options.LandmarkChamberDemo=true;
|
||||
else if(arg=="--landmark-l45-ramp-control")options.LandmarkL45RampControl=true;
|
||||
else if(arg=="--ore-veins-demo")options.OreVeinsDemo=true;
|
||||
else if(arg=="--audit-ore-vein-yield")options.AuditOreVeinYield=true;
|
||||
else if(arg.starts_with("--resolved-layout-output="))
|
||||
options.ResolvedLayoutOutputPath=std::string(arg.substr(std::string_view("--resolved-layout-output=").size()));
|
||||
else if(arg.starts_with("--seed="))
|
||||
@@ -828,6 +967,8 @@ namespace
|
||||
{std::fprintf(stderr,"--region-crossing requires --camera-step=X,Y,Z\n");return false;}
|
||||
if(options.LandmarkL45RampControl&&!options.LandmarkChamberDemo)
|
||||
{std::fprintf(stderr,"--landmark-l45-ramp-control requires --landmark-chamber-demo\n");return false;}
|
||||
if(options.AuditOreVeinYield&&!options.OreVeinsDemo)
|
||||
{std::fprintf(stderr,"--audit-ore-vein-yield requires --ore-veins-demo\n");return false;}
|
||||
if(options.TlsProbeRequested)return true;
|
||||
if(!options.WorldAssemblerPrototype||!options.StreamBenchmark||!options.StreamPipeline)
|
||||
{
|
||||
@@ -969,6 +1110,43 @@ int main(int argc,char** argv)
|
||||
resolvedLayout.cavernRooms.size(),resolvedLayout.cavernEdges.size(),
|
||||
resolvedLayout.stamps.size(),resolvedLayout.cavernGraphVocabularyVersion);
|
||||
}
|
||||
if(options.OreVeinsDemo)
|
||||
{
|
||||
std::vector<VoxelForgeCore::ResolvedLayoutOreVeinRequestV1> requests;
|
||||
if(!MakeOreVeinDemoRequests(resolvedLayout,requests,error)
|
||||
||!VoxelForgeCore::ResolveResolvedLayoutOreVeinsV1(
|
||||
resolvedLayout,seed,requests,error))
|
||||
{std::fprintf(stderr,"Ore-vein demo resolution failed: %s\n",error.c_str());return 2;}
|
||||
const auto audit=VoxelForgeCore::AuditResolvedLayoutOreVeinPlacementV1(resolvedLayout);
|
||||
std::printf("[ResolvedOreVeinPlacementAudit] vocabulary=%u records=%zu forced_clear_passage=rejected controls=%u/%u status=%s\n",
|
||||
resolvedLayout.oreVeinVocabularyVersion,resolvedLayout.oreVeins.size(),
|
||||
audit.negativeControlsRejected,audit.negativeControlsRun,
|
||||
audit.passed?"PASS":"FAIL");
|
||||
if(!audit.passed)
|
||||
{std::fprintf(stderr,"Resolved ore-vein placement negative control failed.\n");return 2;}
|
||||
for(const auto& vein:resolvedLayout.oreVeins)
|
||||
std::printf("[ResolvedOreVein] id=%s strate=%d resource=%u shape=capsule endpoints_m=(%.3f,%.3f,%.3f)->(%.3f,%.3f,%.3f) radius_m=%.2f irregularity_m=%.2f/%.2f blend_m=%.2f bounds_um=[%lld,%lld)x[%lld,%lld)x[%lld,%lld) seed=%llu\n",
|
||||
vein.stableId.c_str(),vein.strateIndex,vein.resourceMaterialId,
|
||||
vein.endpointsMicrometres[0][0]/1000000.0,
|
||||
vein.endpointsMicrometres[0][1]/1000000.0,
|
||||
vein.endpointsMicrometres[0][2]/1000000.0,
|
||||
vein.endpointsMicrometres[1][0]/1000000.0,
|
||||
vein.endpointsMicrometres[1][1]/1000000.0,
|
||||
vein.endpointsMicrometres[1][2]/1000000.0,
|
||||
vein.radiusMicrometres/1000000.0,
|
||||
vein.irregularityAmplitudeMicrometres/1000000.0,
|
||||
vein.irregularityWavelengthMicrometres/1000000.0,
|
||||
vein.blendWidthMicrometres/1000000.0,
|
||||
static_cast<long long>(vein.boundsMinimumMicrometres[0]),
|
||||
static_cast<long long>(vein.boundsMaximumMicrometres[0]),
|
||||
static_cast<long long>(vein.boundsMinimumMicrometres[1]),
|
||||
static_cast<long long>(vein.boundsMaximumMicrometres[1]),
|
||||
static_cast<long long>(vein.boundsMinimumMicrometres[2]),
|
||||
static_cast<long long>(vein.boundsMaximumMicrometres[2]),
|
||||
static_cast<unsigned long long>(vein.irregularitySeed));
|
||||
std::printf("[ResolvedOreVeinDemo] enabled=true seed=%d records=%zu stable_seed=seed,strate,stable_id placement=side-pocket-and-chamber-approach geometry_changes=0 status=PASS\n",
|
||||
seed,resolvedLayout.oreVeins.size());
|
||||
}
|
||||
const std::string layoutJson=resolvedLayout.ToJson();
|
||||
const auto roundTrip=VoxelForgeCore::ResolvedLayoutSnapshotV3::FromJson(layoutJson,error);
|
||||
if(!roundTrip||roundTrip->ToJson()!=layoutJson||roundTrip->stableIdentity!=resolvedLayout.stableIdentity)
|
||||
@@ -1240,5 +1418,6 @@ int main(int argc,char** argv)
|
||||
options.DiagnosticSlowShapeCompileMs,options.AuditBasePageEquivalence,
|
||||
options.AuditRouteParity,options.AuditResolvedLayoutBoundaries,
|
||||
options.AuditResolvedLayoutPassages,options.AuditStrateTransitions,
|
||||
&resolvedLayout,options.StreamHashTiles,options.AuditLandmarkReachability);
|
||||
&resolvedLayout,options.StreamHashTiles,options.AuditLandmarkReachability,
|
||||
options.AuditOreVeinYield);
|
||||
}
|
||||
@@ -808,6 +808,7 @@ namespace StandaloneStreamBenchmark
|
||||
{
|
||||
std::ofstream File;
|
||||
bool bGood=true;
|
||||
bool bIncludeMaterialAttributes=false;
|
||||
std::uint32_t TileCount=0;
|
||||
|
||||
template<typename T> void Write(const T& Value)
|
||||
@@ -818,7 +819,7 @@ namespace StandaloneStreamBenchmark
|
||||
|
||||
bool Open(const std::filesystem::path& Path,std::uint32_t VoxelSizeMicrons,
|
||||
const FVector& CameraVoxels,float YawDegrees,float PitchDegrees,
|
||||
std::uint32_t ExpectedTiles)
|
||||
std::uint32_t ExpectedTiles,bool IncludeMaterialAttributes)
|
||||
{
|
||||
std::error_code Error;
|
||||
if(!Path.parent_path().empty())
|
||||
@@ -826,9 +827,11 @@ namespace StandaloneStreamBenchmark
|
||||
if(Error)return false;
|
||||
File.open(Path,std::ios::binary|std::ios::trunc);
|
||||
if(!File)return false;
|
||||
constexpr char Magic[8]={'V','F','M','S','H','0','0','1'};
|
||||
bIncludeMaterialAttributes=IncludeMaterialAttributes;
|
||||
const char Magic[8]={'V','F','M','S','H','0','0',
|
||||
static_cast<char>(bIncludeMaterialAttributes?'2':'1')};
|
||||
File.write(Magic,sizeof(Magic));
|
||||
const std::uint32_t Version=1;
|
||||
const std::uint32_t Version=bIncludeMaterialAttributes?2u:1u;
|
||||
Write(Version);Write(VoxelSizeMicrons);Write(ExpectedTiles);
|
||||
const double MicronsToMeters=static_cast<double>(VoxelSizeMicrons)/1000000.0;
|
||||
const double Camera[3]={CameraVoxels.X*MicronsToMeters,
|
||||
@@ -855,6 +858,13 @@ namespace StandaloneStreamBenchmark
|
||||
for(const float Value:Vertex.Position)Write(Value);
|
||||
for(const FStandaloneCpuVertex& Vertex:Tile.Vertices)
|
||||
for(const float Value:Vertex.Normal)Write(Value);
|
||||
if(bIncludeMaterialAttributes)
|
||||
for(const FStandaloneCpuVertex& Vertex:Tile.Vertices)
|
||||
{
|
||||
Write(Vertex.HostMaterialId);
|
||||
for(const std::uint16_t id:Vertex.ResourceMaterialIds)Write(id);
|
||||
for(const std::uint16_t weight:Vertex.ResourceWeightsQ16)Write(weight);
|
||||
}
|
||||
if(IndexCount)
|
||||
File.write(reinterpret_cast<const char*>(Tile.Indices.data()),
|
||||
static_cast<std::streamsize>(sizeof(std::uint32_t)*Tile.Indices.size()));
|
||||
@@ -1097,7 +1107,8 @@ namespace StandaloneStreamBenchmark
|
||||
bool bAuditResolvedLayoutPassages=false,
|
||||
bool bAuditStrateTransitions=false,
|
||||
const VoxelForgeCore::ResolvedLayoutSnapshotV3* ResolvedLayout=nullptr,
|
||||
bool bPrintTileHashes=false,bool bAuditLandmarkReachability=false)
|
||||
bool bPrintTileHashes=false,bool bAuditLandmarkReachability=false,
|
||||
bool bAuditOreVeinYield=false)
|
||||
{
|
||||
using Clock=std::chrono::steady_clock;
|
||||
FStandaloneOwnerWorld& World=*OwnerWorld;
|
||||
@@ -1282,6 +1293,65 @@ namespace StandaloneStreamBenchmark
|
||||
Streamer.Stop();
|
||||
std::fprintf(stderr,"[StreamPipeline] full shape build failed: %s\n",BuildError.c_str());return 2;
|
||||
}
|
||||
if(bAuditOreVeinYield)
|
||||
{
|
||||
if(!ResolvedLayout||ResolvedLayout->oreVeins.empty())
|
||||
{std::fprintf(stderr,"[OreMiningYield] missing resolved ore-vein records\n");return 2;}
|
||||
const auto vein=std::find_if(ResolvedLayout->oreVeins.begin(),
|
||||
ResolvedLayout->oreVeins.end(),[](const auto& value)
|
||||
{return value.stableId.find("arrival-route-copper")!=std::string::npos;});
|
||||
const auto& selected=vein==ResolvedLayout->oreVeins.end()
|
||||
?ResolvedLayout->oreVeins.front():*vein;
|
||||
const auto micrometresToVoxel=[&](const std::array<std::int64_t,3>& point)
|
||||
{
|
||||
const double size=static_cast<double>(World.CoreVoxelSizeMicrons);
|
||||
return VoxelForgeCore::Vec3d{point[0]/size,point[1]/size,point[2]/size};
|
||||
};
|
||||
const std::array<std::int64_t,3> editCenterMicrometres={
|
||||
(selected.endpointsMicrometres[0][0]+selected.endpointsMicrometres[1][0])/2,
|
||||
(selected.endpointsMicrometres[0][1]+selected.endpointsMicrometres[1][1])/2,
|
||||
(selected.endpointsMicrometres[0][2]+selected.endpointsMicrometres[1][2])/2};
|
||||
const VoxelForgeCore::Vec3d editCenter=micrometresToVoxel(editCenterMicrometres);
|
||||
const auto mined=Shape.measureOreMiningYieldForSphere(editCenter,1.25,0.25);
|
||||
std::uint64_t weightedSamples=0;double resourceVolume=0.0;
|
||||
for(const auto& resource:mined.resources)
|
||||
{weightedSamples+=resource.weightedSamplesQ16;resourceVolume+=resource.volumeCubicMetres;}
|
||||
const bool yieldPassed=weightedSamples>0&&resourceVolume>0.0;
|
||||
std::printf("[OreMiningYield] edit=sphere record=%s center_voxel=(%.3f,%.3f,%.3f) radius_m=1.250 resolution_m=%.3f removed_solid_samples=%llu resource_volume_m3=%.6f status=%s\n",
|
||||
selected.stableId.c_str(),editCenter.x,editCenter.y,editCenter.z,
|
||||
mined.sampleSpacingMetres,static_cast<unsigned long long>(mined.solidSamplesRemoved),
|
||||
resourceVolume,yieldPassed?"PASS":"FAIL");
|
||||
for(const auto& resource:mined.resources)
|
||||
std::printf("[OreMiningYieldResource] id=%u volume_m3=%.6f weighted_samples_q16=%llu\n",
|
||||
resource.resourceMaterialId,resource.volumeCubicMetres,
|
||||
static_cast<unsigned long long>(resource.weightedSamplesQ16));
|
||||
VoxelForgeCore::Vec3d airCenter{};
|
||||
if(!ResolvedLayout->cavernEdges.empty())
|
||||
{
|
||||
const auto& edge=ResolvedLayout->cavernEdges.front();
|
||||
airCenter=micrometresToVoxel(edge.controlPointsMicrometres[0]);
|
||||
airCenter.z+=edge.boreHeightMicrometres*0.5
|
||||
/static_cast<double>(World.CoreVoxelSizeMicrons);
|
||||
}
|
||||
else if(!ResolvedLayout->passages.empty())
|
||||
{
|
||||
const auto& mouth=ResolvedLayout->passages.front().lowerMouthSubvoxels;
|
||||
const double divisor=static_cast<double>(
|
||||
VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
||||
airCenter={mouth[0]/divisor,mouth[1]/divisor,mouth[2]/divisor};
|
||||
}
|
||||
const auto air=Shape.measureOreMiningYieldForSphere(airCenter,1.0,0.25);
|
||||
std::uint64_t airWeightedSamples=0;
|
||||
double airResourceVolume=0.0;
|
||||
for(const auto& resource:air.resources)
|
||||
{airWeightedSamples+=resource.weightedSamplesQ16;airResourceVolume+=resource.volumeCubicMetres;}
|
||||
const bool airPassed=airWeightedSamples==0&&airResourceVolume==0.0;
|
||||
std::printf("[OreMiningYieldControl] case=already-air center_voxel=(%.3f,%.3f,%.3f) radius_m=1.000 resolution_m=0.250 removed_solid_samples=%llu resource_volume_m3=%.6f status=%s\n",
|
||||
airCenter.x,airCenter.y,airCenter.z,
|
||||
static_cast<unsigned long long>(air.solidSamplesRemoved),
|
||||
airResourceVolume,airPassed?"PASS":"FAIL");
|
||||
if(!yieldPassed||!airPassed)return 14;
|
||||
}
|
||||
if(bAuditLandmarkReachability)
|
||||
{
|
||||
Streamer.Stop();
|
||||
@@ -3956,7 +4026,8 @@ namespace StandaloneStreamBenchmark
|
||||
{
|
||||
bSceneExportPassing=SceneExport.Open(MeshExportPath,World.CoreVoxelSizeMicrons,
|
||||
CameraPositionVoxels,CameraYawDegrees,CameraPitchDegrees,
|
||||
static_cast<std::uint32_t>(Update.DesiredTiles));
|
||||
static_cast<std::uint32_t>(Update.DesiredTiles),
|
||||
ResolvedLayout&&!ResolvedLayout->oreVeins.empty());
|
||||
if(!bSceneExportPassing)
|
||||
{
|
||||
Streamer.Stop();
|
||||
@@ -3966,7 +4037,9 @@ namespace StandaloneStreamBenchmark
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<FHashedTile> DensityRows,MeshRows,PayloadRows,TopologyRows;
|
||||
std::vector<FHashedTile> DensityRows,MeshRows,PayloadRows,TopologyRows,MaterialRows;
|
||||
std::uint64_t MaterialQueries=0,MaterialCandidateChecks=0,MaterialOutsideTiles=0;
|
||||
double MaterialQueryMilliseconds=0.0;
|
||||
int32 Finished=0,NeighborhoodFinished=0,FullDetailFinished=0;
|
||||
int32 FullDetailExpected=0;
|
||||
uint64 WholeNonEmptyMeshTiles=0,WholeTriangleCount=0;
|
||||
@@ -4197,12 +4270,17 @@ namespace StandaloneStreamBenchmark
|
||||
NativeColumnComputeMilliseconds+=Tile.CoreDiagnostics.columnComputeMilliseconds;
|
||||
NativeMeshingMilliseconds+=Tile.CoreDiagnostics.meshingMilliseconds;
|
||||
}
|
||||
MaterialQueries+=Tile.CoreDiagnostics.materialQueries;
|
||||
MaterialCandidateChecks+=Tile.CoreDiagnostics.materialCandidateChecks;
|
||||
MaterialOutsideTiles+=Tile.CoreDiagnostics.materialTileOutsideEveryVein?1u:0u;
|
||||
MaterialQueryMilliseconds+=Tile.CoreDiagnostics.materialQueryMilliseconds;
|
||||
if(bHashDensity)
|
||||
{
|
||||
if(Tile.bHasDensityHash)DensityRows.push_back({Tile.Key,Tile.DensityHash});
|
||||
if(Tile.bHasMeshHash)MeshRows.push_back({Tile.Key,Tile.MeshHash});
|
||||
if(Tile.bHasPayloadHash)PayloadRows.push_back({Tile.Key,Tile.PayloadHash});
|
||||
if(Tile.bHasTopologyHash)TopologyRows.push_back({Tile.Key,Tile.TopologyHash});
|
||||
if(Tile.bHasMaterialHash)MaterialRows.push_back({Tile.Key,Tile.MaterialHash});
|
||||
}
|
||||
}
|
||||
if(!bFirstTileReported&&FirstTileReady.time_since_epoch().count()!=0)
|
||||
@@ -4580,6 +4658,11 @@ namespace StandaloneStreamBenchmark
|
||||
static_cast<unsigned long long>(NativeColumnMisses),
|
||||
NativeColumnComputeMilliseconds,NativeClassificationMilliseconds,
|
||||
NativeMeshingMilliseconds);
|
||||
std::printf("[MaterialQueryCost] queries=%llu candidate_checks=%llu outside_vein_tiles=%llu tiles=%d elapsed_ms=%.3f outside_query_ms=0 status=%s\n",
|
||||
static_cast<unsigned long long>(MaterialQueries),
|
||||
static_cast<unsigned long long>(MaterialCandidateChecks),
|
||||
static_cast<unsigned long long>(MaterialOutsideTiles),Finished,
|
||||
MaterialQueryMilliseconds,MaterialQueries==0?"zero":"measured");
|
||||
if(bAuditUniformBlockProofs)
|
||||
std::printf("[UniformBlockProofAudit] checked_blocks=%llu checked_samples=%llu fallback_blocks=%llu unknown_blocks=%llu violations=%llu\n",
|
||||
static_cast<unsigned long long>(BlockProofBlocksChecked),
|
||||
@@ -4594,7 +4677,8 @@ namespace StandaloneStreamBenchmark
|
||||
bHashesPassing=DensityRows.size()==Desired.Tiles.size()
|
||||
&&MeshRows.size()==Desired.Tiles.size()
|
||||
&&PayloadRows.size()==Desired.Tiles.size()
|
||||
&&TopologyRows.size()==Desired.Tiles.size();
|
||||
&&TopologyRows.size()==Desired.Tiles.size()
|
||||
&&MaterialRows.size()==Desired.Tiles.size();
|
||||
if(bPrintTileHashes)
|
||||
{
|
||||
const auto makeHashIndex=[](const std::vector<FHashedTile>& rows)
|
||||
@@ -4607,6 +4691,7 @@ namespace StandaloneStreamBenchmark
|
||||
const auto MeshIndex=makeHashIndex(MeshRows);
|
||||
const auto PayloadIndex=makeHashIndex(PayloadRows);
|
||||
const auto TopologyIndex=makeHashIndex(TopologyRows);
|
||||
const auto MaterialIndex=makeHashIndex(MaterialRows);
|
||||
const auto findHash=[](const auto& index,const FVoxelTileKey& key)
|
||||
{
|
||||
const auto found=index.find(key);
|
||||
@@ -4619,6 +4704,10 @@ namespace StandaloneStreamBenchmark
|
||||
static_cast<unsigned long long>(findHash(MeshIndex,row.Key)),
|
||||
static_cast<unsigned long long>(findHash(PayloadIndex,row.Key)),
|
||||
static_cast<unsigned long long>(findHash(TopologyIndex,row.Key)));
|
||||
for(const FHashedTile& row:MaterialRows)
|
||||
std::printf("[PipelineTileMaterialHash] lod=%d x=%d y=%d z=%d material=0x%016llx\n",
|
||||
row.Key.Level,row.Key.Coord.X,row.Key.Coord.Y,row.Key.Coord.Z,
|
||||
static_cast<unsigned long long>(row.Hash));
|
||||
}
|
||||
std::vector<FHashedTile> FullDensity,FullMesh,FullPayload,FullTopology;
|
||||
for(const FHashedTile& Row:DensityRows)if(Row.Key.Level<World.FullResClipLevels)FullDensity.push_back(Row);
|
||||
@@ -4635,6 +4724,8 @@ namespace StandaloneStreamBenchmark
|
||||
static_cast<unsigned long long>(HashTileRows(MeshRows)),
|
||||
static_cast<unsigned long long>(HashTileRows(PayloadRows)),
|
||||
static_cast<unsigned long long>(HashTileRows(TopologyRows)));
|
||||
std::printf("[PipelineMaterialHash] tiles=%zu material=0x%016llx\n",
|
||||
MaterialRows.size(),static_cast<unsigned long long>(HashTileRows(MaterialRows)));
|
||||
for(std::size_t LOD=0;LOD<TileCountByLOD.size();++LOD)
|
||||
{
|
||||
const auto FilterLOD=[LOD](const std::vector<FHashedTile>& Rows)
|
||||
|
||||
@@ -60,6 +60,8 @@ struct FStandaloneCpuTile
|
||||
bool bHasPayloadHash=false;
|
||||
uint64 TopologyHash=14695981039346656037ull;
|
||||
bool bHasTopologyHash=false;
|
||||
uint64 MaterialHash=14695981039346656037ull;
|
||||
bool bHasMaterialHash=false;
|
||||
bool bGenerationFailed=false;
|
||||
uint64 MeshPayloadAllocatedBytes=0;
|
||||
VoxelForgeCore::TileGenerationStats CoreDiagnostics{};
|
||||
@@ -846,15 +848,18 @@ private:
|
||||
Out.MeshPayloadAllocatedBytes=Result->meshPayloadAllocatedBytes;
|
||||
Out.DensityHash=Result->densityHash;Out.MeshHash=Result->meshHash;
|
||||
Out.PayloadHash=Result->payloadHash;Out.TopologyHash=Result->topologyHash;
|
||||
Out.MaterialHash=Result->materialHash;
|
||||
Out.bHasDensityHash=Result->hasDensityHash;Out.bHasMeshHash=Result->hasMeshHash;
|
||||
Out.bHasPayloadHash=Result->hasPayloadHash;Out.bHasTopologyHash=Result->hasTopologyHash;
|
||||
Out.bHasMaterialHash=Result->hasMaterialHash;
|
||||
Out.Vertices.reserve(Result->vertices.size());
|
||||
for(const auto& Vertex:Result->vertices)
|
||||
Out.Vertices.push_back({
|
||||
{static_cast<float>(Vertex.position.x*0.01),static_cast<float>(Vertex.position.y*0.01),
|
||||
static_cast<float>(Vertex.position.z*0.01)},
|
||||
{static_cast<float>(Vertex.normal.x),static_cast<float>(Vertex.normal.y),
|
||||
static_cast<float>(Vertex.normal.z)}});
|
||||
static_cast<float>(Vertex.normal.z)},
|
||||
Vertex.hostMaterialId,Vertex.resourceMaterialIds,Vertex.resourceWeightsQ16});
|
||||
Out.Indices=std::move(Result->indices);
|
||||
Out.TriangleSkirtFaceMasks=std::move(Result->skirtFaceMasks);
|
||||
if(bProfile)
|
||||
|
||||
@@ -5,10 +5,19 @@
|
||||
#include "VoxelMarchingCubesMesher.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <thread>
|
||||
|
||||
struct FStandaloneCpuVertex { float Position[3]; float Normal[3]; };
|
||||
struct FStandaloneCpuVertex
|
||||
{
|
||||
float Position[3];
|
||||
float Normal[3];
|
||||
std::uint16_t HostMaterialId=1;
|
||||
std::array<std::uint16_t,2> ResourceMaterialIds{};
|
||||
std::array<std::uint16_t,2> ResourceWeightsQ16{};
|
||||
};
|
||||
|
||||
struct FStandaloneCpuLevel
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user