feat(core): native Core caves generator, slice 1 -- spine + room, whole view 0.73 s (L54); research R23
Core.Caves.PrimitiveField v1, opt-in per strate (legacy TunnelNetwork assets stay legacy): solid host rock and one smooth union of two elliptical spine tubes and an ellipsoid room, each intersected with its rising floor by cubic smooth-max (W 2.4 m, measured fillet 0.70 m, no trailing floor cap). Bound in the L40 field table and L42 capabilities, BVH feature candidates, isTileShapeFree aware, exact interval boxes (0 misses). Capsule flood fill arrival -> departure passes (headroom >= 0.3 m, grade <= 15 deg); the generalized whole-surface audit passes with its controls failing. Cold whole view 0.727 s, near ring 0.19 s. Graph ordering/serialization changes are scoped to native caves (the flagged L47 demo is unchanged); verify tiers write captures under --out only. fast/medium/full PASS, Clang and GCC. Research R23 (deepened): per-tile LOD0 kernel speed plan. 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
20f6c98ec5
commit
9458217d34
24 files changed
+471136
-59
No files matched your search
@@ -56,12 +56,14 @@ namespace
|
||||
bool HasRenderDistanceOverride=false;
|
||||
int RenderDistanceOverride=0;
|
||||
bool FloatingIslandsRecipe=false;
|
||||
bool CavesRecipe=false;
|
||||
bool AuditHeightfieldProfile=false;
|
||||
bool LandmarkChamberDemo=false;
|
||||
bool LandmarkL45RampControl=false;
|
||||
bool OreVeinsDemo=false;
|
||||
bool AuditOreVeinYield=false;
|
||||
bool AuditLandmarkReachability=false;
|
||||
bool AuditNativeCaves=false;
|
||||
bool TlsProbeRequested=false;
|
||||
int TlsProbeWorkers=0;
|
||||
std::string TlsProbeTouch="none";
|
||||
@@ -779,6 +781,163 @@ namespace
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MakeNativeCavesSpine(const VoxelForgeCore::ResolvedLayoutSnapshotV3& layout,
|
||||
std::vector<VoxelForgeCore::ResolvedCavernRoomV1>& rooms,
|
||||
std::vector<VoxelForgeCore::ResolvedCavernEdgeV1>& edges,
|
||||
std::string& error)
|
||||
{
|
||||
using namespace VoxelForgeCore;
|
||||
rooms.clear();edges.clear();error.clear();
|
||||
const auto cave=std::find_if(layout.strates.begin(),layout.strates.end(),
|
||||
[](const auto& value){return value.generatorKind=="Caves"
|
||||
&&value.recipeId=="Core.Caves.PrimitiveField"&&value.recipeVersion==1;});
|
||||
if(cave==layout.strates.end())
|
||||
{error="native caves recipe has no version-1 cave strate";return false;}
|
||||
std::vector<const ResolvedLayoutPassageV1*> mouths;
|
||||
for(const auto& passage:layout.passages)
|
||||
if(passage.lowerStrateIndex==cave->strateIndex)
|
||||
mouths.push_back(&passage);
|
||||
std::sort(mouths.begin(),mouths.end(),[](const auto* a,const auto* b)
|
||||
{return a->stableId<b->stableId;});
|
||||
if(mouths.size()!=2)
|
||||
{
|
||||
std::fprintf(stderr,"[CavesMouthBindingAudit] cave_strate=%d passages=%zu matching=%zu",
|
||||
cave->strateIndex,layout.passages.size(),mouths.size());
|
||||
for(const auto& passage:layout.passages)
|
||||
std::fprintf(stderr," [%s upper=%d lower=%d]",passage.stableId.c_str(),
|
||||
passage.upperStrateIndex,passage.lowerStrateIndex);
|
||||
std::fprintf(stderr,"\n");
|
||||
error="native caves spine fixture requires exactly two resolved arrival/departure mouths";return false;
|
||||
}
|
||||
const double micrometresPerSubvoxel=static_cast<double>(layout.coreVoxelSizeMicrometres)
|
||||
/WorldAssemblySubvoxelsPerVoxel;
|
||||
std::array<std::array<std::int64_t,3>,2> mouthMicrometres{};
|
||||
for(std::size_t mouth=0;mouth<2;++mouth)
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
mouthMicrometres[mouth][axis]=static_cast<std::int64_t>(std::llround(
|
||||
mouths[mouth]->lowerMouthSubvoxels[axis]*micrometresPerSubvoxel));
|
||||
const double x0=mouthMicrometres[0][0]/1000000.0;
|
||||
const double y0=mouthMicrometres[0][1]/1000000.0;
|
||||
const double x1=mouthMicrometres[1][0]/1000000.0;
|
||||
const double y1=mouthMicrometres[1][1]/1000000.0;
|
||||
const double floorZ=0.5*(mouthMicrometres[0][2]+mouthMicrometres[1][2])/1000000.0;
|
||||
const double dx=x1-x0,dy=y1-y0;
|
||||
const double length=std::hypot(dx,dy);
|
||||
std::printf("[CavesResolvedMouths] arrival=%s xyz_m=(%.3f,%.3f,%.3f) departure=%s xyz_m=(%.3f,%.3f,%.3f) horizontal_separation_m=%.3f\n",
|
||||
mouths[0]->stableId.c_str(),x0,y0,mouthMicrometres[0][2]/1e6,
|
||||
mouths[1]->stableId.c_str(),x1,y1,mouthMicrometres[1][2]/1e6,length);
|
||||
const double mouthZ0=mouthMicrometres[0][2]/1000000.0;
|
||||
const double mouthZ1=mouthMicrometres[1][2]/1000000.0;
|
||||
const std::array<double,2> startFloorZ={mouthZ0-0.75,mouthZ1-0.75};
|
||||
double directionX=0.0,directionY=0.0;
|
||||
for(const auto* passage:mouths)
|
||||
{
|
||||
if(passage->controlPoints.size()<2)
|
||||
{error="native caves lower mouth has no resolved passage tangent";return false;}
|
||||
const auto last=std::min(passage->lowerMouthControlIndex,
|
||||
passage->controlPoints.size()-1);
|
||||
const auto previous=last>0?last-1:passage->controlPoints.size()-2;
|
||||
const double tx=static_cast<double>(passage->controlPoints[last].positionSubvoxels[0]
|
||||
-passage->controlPoints[previous].positionSubvoxels[0]);
|
||||
const double ty=static_cast<double>(passage->controlPoints[last].positionSubvoxels[1]
|
||||
-passage->controlPoints[previous].positionSubvoxels[1]);
|
||||
const double tangentLength=std::hypot(tx,ty);
|
||||
if(!(tangentLength>1.0e-9))
|
||||
{error="native caves lower mouth has a degenerate passage tangent";return false;}
|
||||
directionX+=tx/tangentLength;directionY+=ty/tangentLength;
|
||||
}
|
||||
const double directionLength=std::hypot(directionX,directionY);
|
||||
if(!(directionLength>1.0e-9))
|
||||
{error="native caves lower mouths have opposing passage tangents";return false;}
|
||||
directionX/=directionLength;directionY/=directionLength;
|
||||
if(std::hypot(length,mouthZ1-mouthZ0)<4.0)
|
||||
{error="native caves arrival and departure mouths resolve to the same location";return false;}
|
||||
|
||||
ResolvedCavernRoomV1 room;
|
||||
room.stableId="caves/spine/chamber-v1";room.strateIndex=cave->strateIndex;
|
||||
const double roomOffset=std::max(32.0,
|
||||
std::max(std::abs(floorZ-startFloorZ[0]),
|
||||
std::abs(floorZ-startFloorZ[1]))/0.2679491924311227+27.0);
|
||||
const double perpendicularX=-directionY;
|
||||
const double perpendicularY=directionX;
|
||||
room.centerMicrometres={static_cast<std::int64_t>(std::llround(
|
||||
((x0+x1)*0.5+directionX*roomOffset)*1000000.0)),
|
||||
static_cast<std::int64_t>(std::llround(
|
||||
((y0+y1)*0.5+directionY*roomOffset)*1000000.0)),
|
||||
static_cast<std::int64_t>(std::llround((floorZ+3.5)*1000000.0))};
|
||||
room.halfExtentsMicrometres={7000000,6500000,4500000};
|
||||
room.floorMicrometres=static_cast<std::int64_t>(std::llround(floorZ*1000000.0));
|
||||
room.smoothWidthMicrometres=2400000;
|
||||
rooms.push_back(room);
|
||||
|
||||
for(std::size_t route=0;route<2;++route)
|
||||
{
|
||||
const auto& mouth=mouthMicrometres[route];
|
||||
const auto* passage=mouths[route];
|
||||
const auto tangentLast=std::min(passage->lowerMouthControlIndex,
|
||||
passage->controlPoints.size()-1);
|
||||
const auto tangentPrevious=tangentLast>0?tangentLast-1:
|
||||
passage->controlPoints.size()-2;
|
||||
const double tangentX=static_cast<double>(
|
||||
passage->controlPoints[tangentLast].positionSubvoxels[0]
|
||||
-passage->controlPoints[tangentPrevious].positionSubvoxels[0]);
|
||||
const double tangentY=static_cast<double>(
|
||||
passage->controlPoints[tangentLast].positionSubvoxels[1]
|
||||
-passage->controlPoints[tangentPrevious].positionSubvoxels[1]);
|
||||
const double tangentLength=std::hypot(tangentX,tangentY);
|
||||
if(!(tangentLength>1.0e-9))
|
||||
{error="native caves lower-mouth overlap has a degenerate passage tangent";return false;}
|
||||
constexpr double mouthOverlap=3.0;
|
||||
const double startX=mouth[0]/1000000.0+tangentX/tangentLength*mouthOverlap;
|
||||
const double startY=mouth[1]/1000000.0+tangentY/tangentLength*mouthOverlap;
|
||||
const double roomX=room.centerMicrometres[0]/1000000.0;
|
||||
const double roomY=room.centerMicrometres[1]/1000000.0;
|
||||
const double toRoomX=roomX-startX,toRoomY=roomY-startY;
|
||||
const double toRoomLength=std::hypot(toRoomX,toRoomY);
|
||||
if(!(toRoomLength>5.0))
|
||||
{error="native caves mouth does not clear the chamber portal";return false;}
|
||||
const double portalSide=route==0?-3.5:3.5;
|
||||
const double portalX=roomX+perpendicularX*portalSide;
|
||||
const double portalY=roomY+perpendicularY*portalSide;
|
||||
const double endZ=floorZ;
|
||||
const double portalRun=std::hypot(portalX-startX,portalY-startY);
|
||||
const double edgeStartFloorZ=startFloorZ[route]
|
||||
+(endZ-startFloorZ[route])*mouthOverlap/(portalRun+mouthOverlap);
|
||||
const double verticalRun=std::abs(endZ-edgeStartFloorZ);
|
||||
const double maximumGrade=verticalRun/portalRun;
|
||||
if(maximumGrade>0.2679492)
|
||||
{error="native caves spine segment exceeds the 15 degree walk grade";return false;}
|
||||
const double edgeX=portalX-startX,edgeY=portalY-startY;
|
||||
ResolvedCavernEdgeV1 edge;
|
||||
edge.stableId=route==0?"caves/spine/arrival-edge-v1"
|
||||
:"caves/spine/departure-edge-v1";
|
||||
edge.startAnchorStableId=mouths[route]->stableId+"/lower-mouth";
|
||||
edge.endRoomStableId=room.stableId;edge.strateIndex=cave->strateIndex;
|
||||
// Match the resolved lower-mouth opening so the native spine does not
|
||||
// create a sudden shoulder where the legacy passage ends.
|
||||
edge.boreRadiusMicrometres=4500000;
|
||||
edge.boreHeightMicrometres=4500000;
|
||||
edge.floorSupportDepthMicrometres=0;
|
||||
edge.smoothWidthMicrometres=2400000;
|
||||
const std::array<std::array<double,3>,4> points={{
|
||||
{startX,startY,edgeStartFloorZ},
|
||||
{startX+edgeX/3.0,startY+edgeY/3.0,
|
||||
edgeStartFloorZ+(endZ-edgeStartFloorZ)/3.0},
|
||||
{startX+2.0*edgeX/3.0,startY+2.0*edgeY/3.0,
|
||||
edgeStartFloorZ+2.0*(endZ-edgeStartFloorZ)/3.0},
|
||||
{portalX,portalY,endZ}}};
|
||||
for(std::size_t point=0;point<points.size();++point)
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
edge.controlPointsMicrometres[point][axis]=static_cast<std::int64_t>(
|
||||
std::llround(points[point][axis]*1000000.0));
|
||||
edges.push_back(std::move(edge));
|
||||
}
|
||||
std::printf("[CavesSpinePlan] recipe=Core.Caves.PrimitiveField version=1 density_sign=negative_solid_positive_air arrival=%s departure=%s room=%s edges=%zu bore_radius_m=4.500 bore_height_m=4.500 mouth_overlap_m=3.000 horizontal_separation_m=%.3f heading=(%.3f,%.3f) room_offset_m=%.3f lower_mouth_floor_offset_m=-0.750 floor_blend_width_m=2.400000 ideal_fillet_radius_m=0.848528 grade_limit_deg=15.000 shared_layer=shared-bedrock status=PASS\n",
|
||||
mouths[0]->stableId.c_str(),mouths[1]->stableId.c_str(),room.stableId.c_str(),
|
||||
edges.size(),length,directionX,directionY,roomOffset);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MakeOreVeinDemoRequests(
|
||||
const VoxelForgeCore::ResolvedLayoutSnapshotV3& layout,
|
||||
std::vector<VoxelForgeCore::ResolvedLayoutOreVeinRequestV1>& requests,
|
||||
@@ -964,7 +1123,7 @@ namespace
|
||||
" --mesh-export=FILE (write the exact generated tile meshes for offline rendering)\n"
|
||||
" --world-assembler-manifest-output=FILE (write the canonical Core recipe and plan)\n"
|
||||
" --profile-column-overlap --far-horizon-lod8-step=N --force-generate\n"
|
||||
" --seed=N --recipe=surface-world|floating-islands\n"
|
||||
" --seed=N --recipe=surface-world|floating-islands|caves\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"
|
||||
@@ -977,6 +1136,7 @@ namespace
|
||||
" --audit-resolved-layout-passages (capsule, support, route-binding and blocked-control audit)\n"
|
||||
" --audit-heightfield-profile (brute-force the profiled field intervals and route)\n"
|
||||
" --audit-landmark-reachability (capsule, support, grade and buried-L43 control)\n"
|
||||
" --audit-native-caves (arrival-to-departure capsule flood fill and interval oracle)\n"
|
||||
" --audit-surface-clearance (sample biome terrain/caps against the resolved ceiling)\n"
|
||||
" --auto-surface-camera (place the 27-tile cube on this seed's surface)\n"
|
||||
" --mesh-coverage (report non-empty tiles and indexed triangles)\n"
|
||||
@@ -1003,6 +1163,7 @@ namespace
|
||||
else if(arg=="--audit-resolved-layout-boundaries")options.AuditResolvedLayoutBoundaries=true;
|
||||
else if(arg=="--audit-resolved-layout-passages")options.AuditResolvedLayoutPassages=true;
|
||||
else if(arg=="--audit-landmark-reachability")options.AuditLandmarkReachability=true;
|
||||
else if(arg=="--audit-native-caves")options.AuditNativeCaves=true;
|
||||
else if(arg=="--audit-surface-clearance")options.AuditSurfaceClearance=true;
|
||||
else if(arg=="--audit-strate-transitions")options.AuditStrateTransitions=true;
|
||||
else if(arg.starts_with("--transition-mode="))
|
||||
@@ -1037,8 +1198,12 @@ namespace
|
||||
else if(arg.starts_with("--recipe="))
|
||||
{
|
||||
const std::string_view recipe=arg.substr(std::string_view("--recipe=").size());
|
||||
if(recipe=="surface-world")options.FloatingIslandsRecipe=false;
|
||||
else if(recipe=="floating-islands")options.FloatingIslandsRecipe=true;
|
||||
if(recipe=="surface-world")
|
||||
{options.FloatingIslandsRecipe=false;options.CavesRecipe=false;}
|
||||
else if(recipe=="floating-islands")
|
||||
{options.FloatingIslandsRecipe=true;options.CavesRecipe=false;}
|
||||
else if(recipe=="caves")
|
||||
{options.FloatingIslandsRecipe=false;options.CavesRecipe=true;}
|
||||
else {std::fprintf(stderr,"Invalid --recipe value: %.*s\n",static_cast<int>(recipe.size()),recipe.data());return false;}
|
||||
}
|
||||
else if(arg=="--landmark-chamber-demo")options.LandmarkChamberDemo=true;
|
||||
@@ -1154,7 +1319,8 @@ int main(int argc,char** argv)
|
||||
std::printf("[HeadlessConfig] data=%s workers=%d clip_radius=%d max_clip_level=%d max_level=%d hash=%s column_overlap=%s force_generate=%s recipe=%s shared_column_cache=%s camera_step_m=(%.3f,%.3f,%.3f) seed_override=%s render_distance_override=%s\n",
|
||||
options.DataPath.string().c_str(),options.WorkerCount,options.ClipRadius,options.MaxClipLevel,
|
||||
options.MaxLevel,options.StreamHash?"on":"off",options.ProfileColumnOverlap?"on":"off",
|
||||
options.ForceGenerate?"on":"off",options.FloatingIslandsRecipe?"floating-islands":"surface-world",
|
||||
options.ForceGenerate?"on":"off",options.CavesRecipe?"caves"
|
||||
:options.FloatingIslandsRecipe?"floating-islands":"surface-world",
|
||||
options.UseSharedColumnCache?"on":"off",options.CameraStepMeters.X,options.CameraStepMeters.Y,
|
||||
options.CameraStepMeters.Z,
|
||||
options.HasSeedOverride?"yes":"no",options.HasRenderDistanceOverride?"yes":"no");
|
||||
@@ -1227,7 +1393,8 @@ int main(int argc,char** argv)
|
||||
}
|
||||
VoxelForgeCore::ResolvedLayoutSnapshotV3 resolvedLayout;
|
||||
{
|
||||
const std::string_view variant=options.FloatingIslandsRecipe?"floating-islands":"surface-world";
|
||||
const std::string_view variant=options.CavesRecipe?"caves"
|
||||
:options.FloatingIslandsRecipe?"floating-islands":"surface-world";
|
||||
if(!StandaloneWorldAssemblyPrototype::BuildResolvedLayoutSnapshot(
|
||||
*ownerWorld,worldAssembly,variant,resolvedLayout,error))
|
||||
{std::fprintf(stderr,"Resolved-layout snapshot failed: %s\n",error.c_str());return 2;}
|
||||
@@ -1277,6 +1444,15 @@ int main(int argc,char** argv)
|
||||
resolvedLayout.cavernRooms.size(),resolvedLayout.cavernEdges.size(),
|
||||
resolvedLayout.stamps.size(),resolvedLayout.cavernGraphVocabularyVersion);
|
||||
}
|
||||
if(options.CavesRecipe)
|
||||
{
|
||||
std::vector<VoxelForgeCore::ResolvedCavernRoomV1> rooms;
|
||||
std::vector<VoxelForgeCore::ResolvedCavernEdgeV1> edges;
|
||||
if(!MakeNativeCavesSpine(resolvedLayout,rooms,edges,error)
|
||||
||!VoxelForgeCore::ResolveResolvedCavernGraphV1(
|
||||
resolvedLayout,seed,rooms,edges,error))
|
||||
{std::fprintf(stderr,"Native caves spine resolution failed: %s\n",error.c_str());return 2;}
|
||||
}
|
||||
if(options.OreVeinsDemo)
|
||||
{
|
||||
std::vector<VoxelForgeCore::ResolvedLayoutOreVeinRequestV1> requests;
|
||||
@@ -1371,8 +1547,9 @@ int main(int argc,char** argv)
|
||||
slot.StrateIndex,resolvedStrate->generatorKind.c_str(),
|
||||
static_cast<std::uint32_t>(slot.Definition->PassageConfig.Style));
|
||||
}
|
||||
const auto requestedRecipe=options.FloatingIslandsRecipe
|
||||
?std::string_view("floating-islands"):std::string_view("surface-world");
|
||||
const auto requestedRecipe=options.CavesRecipe?std::string_view("caves")
|
||||
:options.FloatingIslandsRecipe?std::string_view("floating-islands")
|
||||
:std::string_view("surface-world");
|
||||
const auto topStratum=std::max_element(resolvedLayout.strates.begin(),resolvedLayout.strates.end(),
|
||||
[](const auto& a,const auto& b)
|
||||
{return a.interiorTopMicrometres<b.interiorTopMicrometres;});
|
||||
@@ -1386,7 +1563,12 @@ int main(int argc,char** argv)
|
||||
&&std::any_of(worldAssembly.plan.strates.begin(),worldAssembly.plan.strates.end(),
|
||||
[&](const auto& plan){return plan.strateIndex==stratum.strateIndex;});
|
||||
});
|
||||
const auto actualRecipe=hasFloatingIslands?std::string_view("floating-islands")
|
||||
const bool hasNativeCavesRecipe=std::any_of(resolvedLayout.strates.begin(),resolvedLayout.strates.end(),
|
||||
[](const auto& stratum){return stratum.generatorKind=="Caves"
|
||||
&&stratum.recipeId=="Core.Caves.PrimitiveField"&&stratum.recipeVersion==1
|
||||
&&stratum.passageCapabilities.hasNativeCaveField;});
|
||||
const auto actualRecipe=hasNativeCavesRecipe?std::string_view("caves")
|
||||
:hasFloatingIslands?std::string_view("floating-islands")
|
||||
:hasSurfaceWorldPlan?std::string_view("surface-world")
|
||||
:std::string_view("missing");
|
||||
const char* topGenerator=topStratum==resolvedLayout.strates.end()
|
||||
@@ -1586,5 +1768,5 @@ int main(int argc,char** argv)
|
||||
options.AuditRouteParity,options.AuditResolvedLayoutBoundaries,
|
||||
options.AuditResolvedLayoutPassages,options.AuditStrateTransitions,
|
||||
&resolvedLayout,options.StreamHashTiles,options.AuditLandmarkReachability,
|
||||
options.AuditOreVeinYield);
|
||||
options.AuditOreVeinYield,options.AuditNativeCaves);
|
||||
}
|
||||
@@ -1089,6 +1089,320 @@ namespace StandaloneStreamBenchmark
|
||||
return passed;
|
||||
}
|
||||
|
||||
inline bool AuditNativeCavesField(const VoxelForgeCore::World& shape,
|
||||
const VoxelForgeCore::ResolvedLayoutSnapshotV3& layout,std::uint32_t voxelMicrometres)
|
||||
{
|
||||
using namespace VoxelForgeCore;
|
||||
const double vm=std::max<std::uint32_t>(1,voxelMicrometres)/1e6;
|
||||
const auto metres=[](const std::array<std::int64_t,3>& p)
|
||||
{return std::array<double,3>{p[0]/1e6,p[1]/1e6,p[2]/1e6};};
|
||||
const auto density=[&](double x,double y,double z)
|
||||
{return static_cast<double>(shape.densityAt({x/vm,y/vm,z/vm}))*vm;};
|
||||
const auto cave=std::find_if(layout.strates.begin(),layout.strates.end(),
|
||||
[](const auto& s){return s.generatorKind=="Caves"
|
||||
&&s.recipeId=="Core.Caves.PrimitiveField"&&s.recipeVersion==1
|
||||
&&s.passageCapabilities.hasNativeCaveField;});
|
||||
if(cave==layout.strates.end()||layout.cavernRooms.size()!=1||layout.cavernEdges.size()!=2)
|
||||
{std::printf("[CavesAudit] recipe=%s rooms=%zu edges=%zu status=FAIL\n",
|
||||
cave==layout.strates.end()?"missing":"present",layout.cavernRooms.size(),layout.cavernEdges.size());return false;}
|
||||
std::vector<const ResolvedLayoutPassageV1*> mouths;
|
||||
for(const auto& p:layout.passages)if(p.lowerStrateIndex==cave->strateIndex)mouths.push_back(&p);
|
||||
std::sort(mouths.begin(),mouths.end(),[](auto* a,auto* b){return a->stableId<b->stableId;});
|
||||
if(mouths.size()!=2)return false;
|
||||
std::array<const ResolvedCavernEdgeV1*,2> edges{};
|
||||
for(std::size_t i=0;i<2;++i)
|
||||
{
|
||||
const auto it=std::find_if(layout.cavernEdges.begin(),layout.cavernEdges.end(),
|
||||
[&](const auto& e){return e.startAnchorStableId==mouths[i]->stableId+"/lower-mouth";});
|
||||
if(it==layout.cavernEdges.end()||it->polylineMicrometres.size()<2
|
||||
||it->strateIndex!=cave->strateIndex)return false;
|
||||
edges[i]=&*it;
|
||||
}
|
||||
const auto& room=layout.cavernRooms.front();
|
||||
const auto rc=metres(room.centerMicrometres);
|
||||
const double floor=room.floorMicrometres/1e6;
|
||||
struct Segment {std::array<double,3> a,b;};
|
||||
std::vector<Segment> segments;
|
||||
std::array<double,4> bounds={1e30,-1e30,1e30,-1e30};
|
||||
double minFloor=floor,maxFloor=floor;
|
||||
for(const auto* edge:edges)
|
||||
{
|
||||
for(std::size_t i=0;i<edge->polylineMicrometres.size();++i)
|
||||
{
|
||||
const auto p=metres(edge->polylineMicrometres[i]);
|
||||
bounds[0]=std::min(bounds[0],p[0]);bounds[1]=std::max(bounds[1],p[0]);
|
||||
bounds[2]=std::min(bounds[2],p[1]);bounds[3]=std::max(bounds[3],p[1]);
|
||||
minFloor=std::min(minFloor,p[2]);maxFloor=std::max(maxFloor,p[2]);
|
||||
if(i)segments.push_back({metres(edge->polylineMicrometres[i-1]),p});
|
||||
}
|
||||
}
|
||||
const double roomX=room.halfExtentsMicrometres[0]/1e6;
|
||||
const double roomY=room.halfExtentsMicrometres[1]/1e6;
|
||||
bounds[0]=std::min(bounds[0],rc[0]-roomX);bounds[1]=std::max(bounds[1],rc[0]+roomX);
|
||||
bounds[2]=std::min(bounds[2],rc[1]-roomY);bounds[3]=std::max(bounds[3],rc[1]+roomY);
|
||||
constexpr double H=0.50,GradeLimit=0.2679491924311227;
|
||||
bounds[0]-=4.0;bounds[1]+=4.0;bounds[2]-=4.0;bounds[3]+=4.0;
|
||||
const int minX=static_cast<int>(std::floor(bounds[0]/H));
|
||||
const int maxX=static_cast<int>(std::ceil(bounds[1]/H));
|
||||
const int minY=static_cast<int>(std::floor(bounds[2]/H));
|
||||
const int maxY=static_cast<int>(std::ceil(bounds[3]/H));
|
||||
const int nx=maxX-minX+1,ny=maxY-minY+1;
|
||||
if(nx<=0||ny<=0||static_cast<std::uint64_t>(nx)*ny>4000000)return false;
|
||||
struct Node {int gx=0,gy=0;double x=0.0,y=0.0,floor=0.0;bool walkable=false;};
|
||||
std::vector<Node> nodes;
|
||||
std::vector<std::vector<int>> columns(static_cast<std::size_t>(nx)*ny);
|
||||
const auto columnIndex=[&](int x,int y){return static_cast<std::size_t>(y-minY)*nx+(x-minX);};
|
||||
const double bore=edges[0]->boreRadiusMicrometres/1e6;
|
||||
std::uint64_t capsules=0,supports=0,floorQueries=0;
|
||||
const auto testCapsule=[&](double x,double y,double surface)
|
||||
{
|
||||
bool clear=true;constexpr double radius=0.34,height=1.76;
|
||||
for(double h=0.08;h<=height+1e-9;h+=0.18)
|
||||
{
|
||||
double cap=0;if(h<radius)cap=radius-h;else if(h>height-radius)cap=h-(height-radius);
|
||||
const double radial=std::sqrt(std::max(0.0,radius*radius-cap*cap));
|
||||
for(double oy=-radial;oy<=radial+1e-9;oy+=0.17)
|
||||
for(double ox=-radial;ox<=radial+1e-9;ox+=0.17)
|
||||
{if(ox*ox+oy*oy>radial*radial+1e-9)continue;++capsules;
|
||||
if(density(x+ox,y+oy,surface+0.12+h)<0.025)clear=false;}
|
||||
}
|
||||
constexpr std::array<std::array<double,2>,5> head={{{0,0},{0.24,0},{-0.24,0},{0,0.24},{0,-0.24}}};
|
||||
for(auto o:head)for(double h=0;h<=0.30+1e-9;h+=0.05)
|
||||
if(density(x+o[0],y+o[1],surface+0.12+height+h)<0)clear=false;
|
||||
constexpr std::array<std::array<double,2>,5> foot={{{0,0},{0.28,0},{-0.28,0},{0,0.28},{0,-0.28}}};
|
||||
unsigned support=0;
|
||||
for(auto o:foot){++supports;bool hit=false;for(double d=0.1;d<=0.55;d+=0.08)
|
||||
if(density(x+o[0],y+o[1],surface-d)<0){hit=true;break;}if(hit)++support;}
|
||||
return clear&&support>=3;
|
||||
};
|
||||
for(int gy=minY;gy<=maxY;++gy)for(int gx=minX;gx<=maxX;++gx)
|
||||
{
|
||||
const double x=gx*H,y=gy*H;double nearest=1e30;
|
||||
for(const auto& s:segments)
|
||||
{
|
||||
const double dx=s.b[0]-s.a[0],dy=s.b[1]-s.a[1],l2=dx*dx+dy*dy;
|
||||
const double t=l2>0?std::clamp(((x-s.a[0])*dx+(y-s.a[1])*dy)/l2,0.0,1.0):0.0;
|
||||
nearest=std::min(nearest,std::hypot(x-(s.a[0]+t*dx),y-(s.a[1]+t*dy)));
|
||||
}
|
||||
const bool roomXY=std::abs(x-rc[0])<=roomX+0.5&&std::abs(y-rc[1])<=roomY+0.5;
|
||||
if(nearest>bore+0.9&&!roomXY)continue;
|
||||
double priorZ=minFloor-0.75,priorDensity=density(x,y,priorZ);
|
||||
for(double z=priorZ+0.05;z<=maxFloor+0.75+1e-9;z+=0.05)
|
||||
{
|
||||
const double value=density(x,y,z);++floorQueries;
|
||||
if(priorDensity<0.0&&value>=0.0)
|
||||
{
|
||||
double lo=z-0.05,hi=z;
|
||||
for(int q=0;q<12;++q){const double mid=(lo+hi)*0.5;if(density(x,y,mid)<0.0)lo=mid;else hi=mid;}
|
||||
const double surface=(lo+hi)*0.5;
|
||||
Node node;node.gx=gx;node.gy=gy;node.x=x;node.y=y;node.floor=surface;
|
||||
node.walkable=testCapsule(x,y,surface);
|
||||
const int id=static_cast<int>(nodes.size());nodes.push_back(node);
|
||||
columns[columnIndex(gx,gy)].push_back(id);
|
||||
}
|
||||
priorZ=z;priorDensity=value;
|
||||
}
|
||||
}
|
||||
const auto mouth=[&](const ResolvedLayoutPassageV1& p)
|
||||
{return std::array<double,3>{p.lowerMouthSubvoxels[0]*vm/WorldAssemblySubvoxelsPerVoxel,
|
||||
p.lowerMouthSubvoxels[1]*vm/WorldAssemblySubvoxelsPerVoxel,
|
||||
p.lowerMouthSubvoxels[2]*vm/WorldAssemblySubvoxelsPerVoxel};};
|
||||
const auto start=mouth(*mouths[0]),end=mouth(*mouths[1]);
|
||||
std::vector<int> starts,goals;
|
||||
double nearestStart=std::numeric_limits<double>::infinity(),nearestEnd=nearestStart;
|
||||
int nearestStartNode=-1,nearestEndNode=-1;
|
||||
for(std::size_t id=0;id<nodes.size();++id)
|
||||
{
|
||||
const auto& n=nodes[id];if(!n.walkable)continue;
|
||||
const double ds=std::hypot(std::hypot(n.x-start[0],n.y-start[1]),n.floor-start[2]);
|
||||
const double de=std::hypot(std::hypot(n.x-end[0],n.y-end[1]),n.floor-end[2]);
|
||||
if(ds<nearestStart){nearestStart=ds;nearestStartNode=static_cast<int>(id);}
|
||||
if(de<nearestEnd){nearestEnd=de;nearestEndNode=static_cast<int>(id);}
|
||||
if(ds<=2.0)starts.push_back(static_cast<int>(id));
|
||||
if(de<=2.0)goals.push_back(static_cast<int>(id));
|
||||
}
|
||||
constexpr std::array<std::array<int,2>,8> dirs={{{1,0},{-1,0},{0,1},{0,-1},{1,1},{1,-1},{-1,1},{-1,-1}}};
|
||||
const auto flood=[&](bool enforceGrade,std::uint64_t* rejected)
|
||||
{
|
||||
std::vector<int> parent(nodes.size(),-1);std::queue<int> open;
|
||||
for(int id:starts){parent[static_cast<std::size_t>(id)]=-2;open.push(id);}
|
||||
while(!open.empty())
|
||||
{
|
||||
const int current=open.front();open.pop();const auto& a=nodes[static_cast<std::size_t>(current)];
|
||||
for(const auto& d:dirs)
|
||||
{
|
||||
const int gx=a.gx+d[0],gy=a.gy+d[1];if(gx<minX||gx>maxX||gy<minY||gy>maxY)continue;
|
||||
const double run=H*std::hypot(d[0],d[1]);
|
||||
for(const int next:columns[columnIndex(gx,gy)])
|
||||
{
|
||||
if(parent[static_cast<std::size_t>(next)]!=-1)continue;
|
||||
const auto& b=nodes[static_cast<std::size_t>(next)];if(!b.walkable)continue;
|
||||
if(enforceGrade&&std::abs(b.floor-a.floor)/run>GradeLimit)
|
||||
{if(rejected)++*rejected;continue;}
|
||||
parent[static_cast<std::size_t>(next)]=current;open.push(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
return parent;
|
||||
};
|
||||
auto rawParent=flood(false,nullptr);std::uint64_t gradeRejected=0;
|
||||
auto parent=flood(true,&gradeRejected);
|
||||
const auto reachableGoal=[&](const std::vector<int>& parents)
|
||||
{
|
||||
int selected=-1;double distance=std::numeric_limits<double>::infinity();
|
||||
for(int candidate:goals)if(parents[static_cast<std::size_t>(candidate)]!=-1)
|
||||
{
|
||||
const auto& n=nodes[static_cast<std::size_t>(candidate)];
|
||||
const double d=std::hypot(std::hypot(n.x-end[0],n.y-end[1]),n.floor-end[2]);
|
||||
if(d<distance){distance=d;selected=candidate;}
|
||||
}
|
||||
return std::pair<int,double>{selected,distance};
|
||||
};
|
||||
const auto [rawGoal,rawGoalD]=reachableGoal(rawParent);
|
||||
const auto [goal,goalD]=reachableGoal(parent);
|
||||
const bool rawReached=rawGoal>=0;
|
||||
std::size_t gradedReachable=0,roomWalkable=0,roomReachable=0;
|
||||
double reachableFloorMin=std::numeric_limits<double>::infinity();
|
||||
double reachableFloorMax=-std::numeric_limits<double>::infinity();
|
||||
for(std::size_t id=0;id<nodes.size();++id)
|
||||
{
|
||||
const auto& n=nodes[id];if(!n.walkable)continue;
|
||||
const bool insideRoom=std::abs(n.x-rc[0])<=roomX+0.5
|
||||
&&std::abs(n.y-rc[1])<=roomY+0.5;
|
||||
if(insideRoom)++roomWalkable;
|
||||
if(parent[id]!=-1)
|
||||
{
|
||||
++gradedReachable;reachableFloorMin=std::min(reachableFloorMin,n.floor);
|
||||
reachableFloorMax=std::max(reachableFloorMax,n.floor);
|
||||
if(insideRoom)++roomReachable;
|
||||
}
|
||||
}
|
||||
std::printf("[CavesFloodFillGraph] graded_reachable=%zu room_walkable=%zu room_reachable=%zu reachable_floor_m=%.3f..%.3f arrival_floor_m=%.3f departure_floor_m=%.3f\n",
|
||||
gradedReachable,roomWalkable,roomReachable,reachableFloorMin,reachableFloorMax,
|
||||
nearestStartNode>=0?nodes[static_cast<std::size_t>(nearestStartNode)].floor:0.0,
|
||||
nearestEndNode>=0?nodes[static_cast<std::size_t>(nearestEndNode)].floor:0.0);
|
||||
for(std::size_t edgeIndex=0;edgeIndex<edges.size();++edgeIndex)
|
||||
{
|
||||
const auto& polyline=edges[edgeIndex]->polylineMicrometres;
|
||||
std::vector<std::array<double,3>> linePoints;linePoints.reserve(polyline.size());
|
||||
std::vector<double> cumulativeLength(polyline.size(),0.0);
|
||||
for(const auto& p:polyline)linePoints.push_back(metres(p));
|
||||
for(std::size_t i=1;i<linePoints.size();++i)
|
||||
cumulativeLength[i]=cumulativeLength[i-1]+std::hypot(
|
||||
std::hypot(linePoints[i][0]-linePoints[i-1][0],
|
||||
linePoints[i][1]-linePoints[i-1][1]),
|
||||
linePoints[i][2]-linePoints[i-1][2]);
|
||||
std::printf("[CavesFloodFillProgress] edge=%s",
|
||||
edges[edgeIndex]->stableId.c_str());
|
||||
for(int sample=0;sample<=10;++sample)
|
||||
{
|
||||
const double distance=cumulativeLength.back()*sample/10.0;
|
||||
const auto upper=std::lower_bound(cumulativeLength.begin(),
|
||||
cumulativeLength.end(),distance);
|
||||
const std::size_t upperIndex=std::clamp<std::size_t>(
|
||||
static_cast<std::size_t>(upper-cumulativeLength.begin()),1,
|
||||
cumulativeLength.size()-1);
|
||||
const std::size_t lowerIndex=upperIndex-1;
|
||||
const double span=cumulativeLength[upperIndex]-cumulativeLength[lowerIndex];
|
||||
const double t=span>0.0?(distance-cumulativeLength[lowerIndex])/span:0.0;
|
||||
const std::array<double,3> target={
|
||||
linePoints[lowerIndex][0]+(linePoints[upperIndex][0]-linePoints[lowerIndex][0])*t,
|
||||
linePoints[lowerIndex][1]+(linePoints[upperIndex][1]-linePoints[lowerIndex][1])*t,
|
||||
linePoints[lowerIndex][2]+(linePoints[upperIndex][2]-linePoints[lowerIndex][2])*t};
|
||||
double nearestWalkable=std::numeric_limits<double>::infinity();
|
||||
double nearestReachable=nearestWalkable;
|
||||
int nearestWalkableNode=-1,nearestReachableNode=-1;
|
||||
for(std::size_t id=0;id<nodes.size();++id)
|
||||
{
|
||||
const auto& n=nodes[id];if(!n.walkable)continue;
|
||||
const double d=std::hypot(std::hypot(n.x-target[0],n.y-target[1]),
|
||||
n.floor-target[2]);
|
||||
if(d<nearestWalkable){nearestWalkable=d;nearestWalkableNode=static_cast<int>(id);}
|
||||
if(parent[id]!=-1&&d<nearestReachable)
|
||||
{nearestReachable=d;nearestReachableNode=static_cast<int>(id);}
|
||||
}
|
||||
const double fieldFloor=density(target[0],target[1],target[2]);
|
||||
const double fieldAbove=density(target[0],target[1],target[2]+0.3);
|
||||
const double walkFloor=nearestWalkableNode>=0
|
||||
?nodes[static_cast<std::size_t>(nearestWalkableNode)].floor:0.0;
|
||||
const double reachFloor=nearestReachableNode>=0
|
||||
?nodes[static_cast<std::size_t>(nearestReachableNode)].floor:0.0;
|
||||
std::printf(" t%d=%.2f/%.2f(f%.2f/r%.2f):%.2f/%.2f",sample,
|
||||
nearestWalkable,nearestReachable,walkFloor,reachFloor,fieldFloor,fieldAbove);
|
||||
}
|
||||
std::printf("\n");
|
||||
}
|
||||
double rawMaximumGrade=0.0;std::size_t rawPathCells=0;
|
||||
if(rawReached)for(int p=rawGoal;p>=0;){++rawPathCells;const int prior=rawParent[static_cast<std::size_t>(p)];if(prior==-2)break;
|
||||
const auto& a=nodes[static_cast<std::size_t>(p)];const auto& b=nodes[static_cast<std::size_t>(prior)];
|
||||
const double run=H*std::hypot(a.gx-b.gx,a.gy-b.gy);rawMaximumGrade=std::max(rawMaximumGrade,
|
||||
std::atan2(std::abs(a.floor-b.floor),run)*180.0/3.14159265358979323846);p=prior;}
|
||||
const bool reached=goal>=0;std::vector<int> path;
|
||||
if(reached){for(int p=goal;p>=0;){path.push_back(p);const int prior=parent[static_cast<std::size_t>(p)];if(prior==-2)break;p=prior;}std::reverse(path.begin(),path.end());}
|
||||
double headroom=std::numeric_limits<double>::infinity();
|
||||
for(int id:path){const auto& n=nodes[static_cast<std::size_t>(id)];double h=0.30;
|
||||
for(double rise=0;rise<=0.30+1e-9;rise+=0.05)if(density(n.x,n.y,n.floor+0.12+1.76+rise)<0){h=std::max(0.0,rise-0.05);break;}
|
||||
headroom=std::min(headroom,h);}
|
||||
std::printf("[CavesFloodFillReachability] ungraded_connected=%s ungraded_path_cells=%zu ungraded_max_grade_deg=%.3f grade_rejected_edges=%llu\n",
|
||||
rawReached?"yes":"no",rawPathCells,rawMaximumGrade,static_cast<unsigned long long>(gradeRejected));
|
||||
std::printf("[CavesFloodFillEndpoints] start_seeds=%zu departure_candidates=%zu raw_goal=%s graded_goal=%s nearest_arrival_m=%.3f nearest_departure_m=%.3f selected_departure_m=%.3f raw_selected_departure_m=%.3f\n",
|
||||
starts.size(),goals.size(),rawGoal>=0?"yes":"no",goal>=0?"yes":"no",nearestStart,nearestEnd,goalD,rawGoalD);
|
||||
|
||||
// Fit the actual zero contour in the wall-foot cross section; the local quadratic
|
||||
// curvature is the fillet radius, W/(2 sqrt(2)) for this cubic smooth-max.
|
||||
const auto& edge=*edges[0];const auto p0=metres(edge.polylineMicrometres[edge.polylineMicrometres.size()/2-1]);
|
||||
const auto p1=metres(edge.polylineMicrometres[edge.polylineMicrometres.size()/2]);
|
||||
const double tx=p1[0]-p0[0],ty=p1[1]-p0[1],tl=std::hypot(tx,ty),width=edge.smoothWidthMicrometres/1e6;
|
||||
double measured=0;
|
||||
const double horizontal=std::hypot(tx,ty),grade=horizontal>1e-9?(p1[2]-p0[2])/horizontal:0.0;
|
||||
if(tl>1e-9&&horizontal>1e-9){const double nx=-ty/horizontal,ny=tx/horizontal;
|
||||
const double upX=-grade*tx/horizontal/std::sqrt(1.0+grade*grade);
|
||||
const double upY=-grade*ty/horizontal/std::sqrt(1.0+grade*grade);
|
||||
const double upZ=1.0/std::sqrt(1.0+grade*grade);
|
||||
const double cx=(p0[0]+p1[0])*0.5,cy=(p0[1]+p1[1])*0.5,cz=(p0[2]+p1[2])*0.5,r=edge.boreRadiusMicrometres/1e6;
|
||||
const auto contour=[&](double v){double lo=0,hi=width;const auto f=[&](double u){const double in=(u+v)/std::sqrt(2.0),rise=(u-v)/std::sqrt(2.0);return density(cx+nx*(r-in)+upX*rise,cy+ny*(r-in)+upY*rise,cz+upZ*rise);};
|
||||
if(!(f(lo)<0&&f(hi)>=0))return std::numeric_limits<double>::quiet_NaN();for(int i=0;i<28;++i){double m=(lo+hi)*0.5;if(f(m)<0)lo=m;else hi=m;}return (lo+hi)*0.5;};
|
||||
constexpr double delta=0.02;const double a=contour(-delta),b=contour(0),c=contour(delta);if(std::isfinite(a)&&std::isfinite(b)&&std::isfinite(c))measured=delta*delta/(a+c-2*b);}
|
||||
const double expected=width/(2.0*std::sqrt(2.0));
|
||||
std::printf("[CavesFilletAudit] smooth_max=cubic width_m=%.6f measured_composed_radius_m=%.6f isolated_formula_radius_m=%.6f status=%s\n",
|
||||
width,measured,expected,measured>=0.5?"PASS":"FAIL");
|
||||
|
||||
struct Check {const char* name;std::array<double,3> p;};
|
||||
const auto lineStart=metres(edge.polylineMicrometres.front());
|
||||
const auto lineEnd=metres(edge.polylineMicrometres.back());
|
||||
const std::array<double,3> mid={
|
||||
(lineStart[0]+lineEnd[0])*0.5,(lineStart[1]+lineEnd[1])*0.5,
|
||||
(lineStart[2]+lineEnd[2])*0.5};
|
||||
const std::array<Check,6> checks={{{"host-rock",{bounds[1]+1.5,rc[1],floor+2}},
|
||||
{"spine-air",{mid[0],mid[1],mid[2]+1.6}},
|
||||
{"room-air-center",{rc[0],rc[1],floor+2}},
|
||||
{"room-air-east",{rc[0]+2.0,rc[1],floor+2}},
|
||||
{"room-air-north",{rc[0],rc[1]+2.0,floor+2}},
|
||||
{"wall-foot",{mid[0],mid[1]+bore-0.25,mid[2]+0.25}}}};
|
||||
unsigned air=0,solid=0,miss=0,wallMixed=0;bool falseAir=false,falseSolid=false;
|
||||
for(const auto& c:checks){const Vec3i o{static_cast<int>(std::llround(c.p[0]/vm)),static_cast<int>(std::llround(c.p[1]/vm)),static_cast<int>(std::llround(c.p[2]/vm))};
|
||||
const auto verdict=shape.classifyBox(o,1,1);bool allAir=true,allSolid=true;std::uint64_t samples=0;
|
||||
for(int z=0;z<=6;++z)for(int y=0;y<=6;++y)for(int x=0;x<=6;++x){const Vec3d q{o.x-1.0+0.5*x,o.y-1.0+0.5*y,o.z-1.0+0.5*z};const double d=density(q.x*vm,q.y*vm,q.z*vm);allAir&=d>=0;allSolid&=d<0;++samples;}
|
||||
const bool bad=(verdict==TileClass::allAir&&!allAir)||(verdict==TileClass::allSolid&&!allSolid);miss+=bad;air+=verdict==TileClass::allAir;solid+=verdict==TileClass::allSolid;
|
||||
if(std::string_view(c.name)=="host-rock")falseAir=verdict==TileClass::allSolid&&allSolid;
|
||||
if(std::string_view(c.name).starts_with("room-air"))
|
||||
falseSolid|=verdict==TileClass::allAir&&allAir;
|
||||
if(std::string_view(c.name)=="wall-foot")wallMixed=verdict==TileClass::mixed;
|
||||
std::printf("[CavesIntervalBox] id=%s proof=%s brute_air=%s brute_solid=%s samples=%llu miss=%s\n",c.name,
|
||||
verdict==TileClass::allAir?"all-air":verdict==TileClass::allSolid?"all-solid":"mixed",allAir?"yes":"no",allSolid?"yes":"no",static_cast<unsigned long long>(samples),bad?"yes":"no");}
|
||||
const bool intervalOk=air>=2&&solid>=1&&miss==0&&wallMixed&&falseAir&&falseSolid;
|
||||
const bool reachable=reached&&path.size()>=2&&headroom>=0.30;
|
||||
std::printf("[CavesCapsuleFloodFill] arrival=%s departure=%s grid_m=%.2f candidates=%zu walkable=%zu path_cells=%zu floor_queries=%llu capsule_samples=%llu support_samples=%llu max_grade_deg=15.000 min_headroom_m=%.3f required_headroom_m=0.300 reachable=%s status=%s\n",
|
||||
mouths[0]->stableId.c_str(),mouths[1]->stableId.c_str(),H,nodes.size(),
|
||||
std::count_if(nodes.begin(),nodes.end(),[](const auto& n){return n.walkable;}),path.size(),
|
||||
static_cast<unsigned long long>(floorQueries),static_cast<unsigned long long>(capsules),
|
||||
static_cast<unsigned long long>(supports),headroom,
|
||||
reachable?"yes":"no",reachable?"PASS":"FAIL");
|
||||
std::printf("[CavesIntervalOracle] boxes=%zu all_air=%u all_solid=%u misses=%u false_all_air=%s false_all_solid=%s wall_foot=%s status=%s\n",
|
||||
checks.size(),air,solid,miss,falseAir?"REJECTED":"FAILED",falseSolid?"REJECTED":"FAILED",wallMixed?"mixed":"not-mixed",intervalOk?"PASS":"FAIL");
|
||||
return reachable&&intervalOk&&measured>=0.5;
|
||||
}
|
||||
|
||||
inline int RunPipelinedStreamingBenchmark(std::shared_ptr<FStandaloneOwnerWorld> OwnerWorld,
|
||||
const VoxelForgeCore::ResolvedWorldSnapshot& CoreSnapshot,
|
||||
const FVector& CameraPositionVoxels,int32 WorkerCount,int32 MaxLevel,
|
||||
@@ -1108,7 +1422,7 @@ namespace StandaloneStreamBenchmark
|
||||
bool bAuditStrateTransitions=false,
|
||||
const VoxelForgeCore::ResolvedLayoutSnapshotV3* ResolvedLayout=nullptr,
|
||||
bool bPrintTileHashes=false,bool bAuditLandmarkReachability=false,
|
||||
bool bAuditOreVeinYield=false)
|
||||
bool bAuditOreVeinYield=false,bool bAuditNativeCaves=false)
|
||||
{
|
||||
using Clock=std::chrono::steady_clock;
|
||||
FStandaloneOwnerWorld& World=*OwnerWorld;
|
||||
@@ -1293,6 +1607,13 @@ namespace StandaloneStreamBenchmark
|
||||
Streamer.Stop();
|
||||
std::fprintf(stderr,"[StreamPipeline] full shape build failed: %s\n",BuildError.c_str());return 2;
|
||||
}
|
||||
if(bAuditNativeCaves)
|
||||
{
|
||||
Streamer.Stop();
|
||||
if(!ResolvedLayout||!CoreSnapshot.resolvedLayout())
|
||||
{std::fprintf(stderr,"[CavesAudit] missing resolved layout\n");return 2;}
|
||||
return AuditNativeCavesField(Shape,*ResolvedLayout,World.CoreVoxelSizeMicrons)?0:14;
|
||||
}
|
||||
if(bAuditOreVeinYield)
|
||||
{
|
||||
if(!ResolvedLayout||ResolvedLayout->oreVeins.empty())
|
||||
|
||||
@@ -1015,17 +1015,23 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
if(!slot.Definition)continue;
|
||||
const auto type=owner.Manager->GetGeneratorTypeForChunk(FIntVector(0,0,slot.TopChunkZ));
|
||||
const auto authored=sealAndDensity(*slot.Definition,type);
|
||||
const bool nativeCaves=recipeVariant=="caves"
|
||||
&&type==ECaveGeneratorType::SurfaceWorld;
|
||||
const std::int64_t bottom=static_cast<std::int64_t>(slot.BottomChunkZ)*owner.StrateLayoutChunkSizeMicrometres;
|
||||
const std::int64_t top=static_cast<std::int64_t>(slot.TopChunkZ+1)*owner.StrateLayoutChunkSizeMicrometres;
|
||||
const std::int64_t seal=static_cast<std::int64_t>(std::llround(
|
||||
std::max(0.0f,authored[0])*owner.OwnerVoxelSizeMicrons));
|
||||
if(seal<=0||2*seal>=top-bottom)
|
||||
{error="strate "+std::to_string(slot.StrateIndex)+" has an invalid BoundarySealThickness for two boundary bands";return false;}
|
||||
ResolvedLayoutStratumV2 s;s.strateIndex=slot.StrateIndex;s.generatorKind=GeneratorKind(type);
|
||||
s.stableId=out.worldIdentity+"/strate/"+std::to_string(slot.StrateIndex)+"/generator/"+GeneratorKind(type);
|
||||
s.recipeId=type==ECaveGeneratorType::SurfaceWorld?"SurfaceWorld.Heightfield2D"
|
||||
ResolvedLayoutStratumV2 s;s.strateIndex=slot.StrateIndex;
|
||||
s.generatorKind=nativeCaves?"Caves":GeneratorKind(type);
|
||||
s.stableId=out.worldIdentity+"/strate/"+std::to_string(slot.StrateIndex)
|
||||
+"/generator/"+s.generatorKind;
|
||||
s.recipeId=nativeCaves?"Core.Caves.PrimitiveField"
|
||||
:type==ECaveGeneratorType::SurfaceWorld?"SurfaceWorld.Heightfield2D"
|
||||
:type==ECaveGeneratorType::FloatingIslands?"FloatingIslands.ClusterField":"legacy."+std::string(GeneratorKind(type));
|
||||
s.recipeVersion=type==ECaveGeneratorType::SurfaceWorld?assembly.recipe.schemaVersion
|
||||
s.recipeVersion=nativeCaves?1
|
||||
:type==ECaveGeneratorType::SurfaceWorld?assembly.recipe.schemaVersion
|
||||
:type==ECaveGeneratorType::FloatingIslands&&!assembly.floatingIslands.empty()
|
||||
?assembly.floatingIslands.front().manifest.recipe.geometryVersion:0;
|
||||
s.voxelSizeMicrometres=owner.CoreVoxelSizeMicrons;s.latticeOriginMicrometres={0,0,0};
|
||||
@@ -1144,7 +1150,7 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
s.content={"content",bottom+seal,top-seal,true,true,false,"resolved generator content","none","none",solidDensity};
|
||||
s.bedrock={"bedrock",bottom,bottom+seal,true,true,false,"solid floor from strate BoundarySealThickness; breakable by default",
|
||||
"interior-facing upper face","shared resolved-layout density band",solidDensity};
|
||||
if(type==ECaveGeneratorType::SurfaceWorld)
|
||||
if(type==ECaveGeneratorType::SurfaceWorld&&!nativeCaves)
|
||||
{
|
||||
if(assembly.recipe.surfaceProfile.version!=0)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user