wip: Tunnel Network cave planner on Core caves (L134, route support still failing)
Seed + 21/47 legacy fields -> 30 compound rooms (72 primitives), 3 branches, destination chamber; mouths fixed; 8.67 deg max grade. Passage composer no longer skips native cave layers (incoming passage 21,425/21,425 open). Open: route-support audit has 380 unsupported samples near the arrival mouth. Not for merge. 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
0065ee68e6
commit
75c6ad251d
9 files changed
+1008
-47
No files matched your search
@@ -74,6 +74,7 @@ namespace
|
||||
bool DensityDispatchReport=false;
|
||||
bool CavesRecipe=false;
|
||||
bool LatticeRecipe=false;
|
||||
bool LegacyTunnelNetwork=false;
|
||||
int LatticeStrateIndex=-1;
|
||||
bool AuditHeightfieldProfile=false;
|
||||
bool LandmarkChamberDemo=false;
|
||||
@@ -1434,6 +1435,7 @@ namespace
|
||||
" --core-snapshot=FILE (benchmark the exact resolved Core snapshot from Unreal)\n"
|
||||
" --profile-column-overlap --far-horizon-lod8-step=N --force-generate\n"
|
||||
" --seed=N --recipe=surface-world|floating-islands|caves|lattice\n"
|
||||
" --legacy-tunnel-network (keep TN on its compiled-analytic comparison path)\n"
|
||||
" --native-owner-fi (build the existing owner FI plan; leave other strates intact)\n"
|
||||
" --density-dispatch-report (print bound path per strate and tile hashes)\n"
|
||||
" --lattice-strate=N (opt one Maze or VerticalShafts strate into Core.Lattice)\n"
|
||||
@@ -1497,6 +1499,7 @@ namespace
|
||||
else if(arg=="--native-owner-fi")options.NativeOwnerFloatingIslandsRecipe=true;
|
||||
else if(arg=="--density-dispatch-report")
|
||||
{options.DensityDispatchReport=true;options.StreamHash=true;options.StreamHashTiles=true;}
|
||||
else if(arg=="--legacy-tunnel-network")options.LegacyTunnelNetwork=true;
|
||||
else if(arg=="--audit-landmark-reachability")options.AuditLandmarkReachability=true;
|
||||
else if(arg=="--audit-native-caves")options.AuditNativeCaves=true;
|
||||
else if(arg=="--audit-native-lattice")options.AuditNativeLattice=true;
|
||||
@@ -1737,7 +1740,8 @@ int main(int argc,char** argv)
|
||||
options.DataPath.string().c_str(),options.WorkerCount,options.ClipRadius,options.MaxClipLevel,
|
||||
options.MaxLevel,options.StreamHash?"on":"off",
|
||||
options.StreamHashAllowUniformSkips?"on":"off",options.ProfileColumnOverlap?"on":"off",
|
||||
options.ForceGenerate?"on":"off",options.CavesRecipe?"caves"
|
||||
options.ForceGenerate?"on":"off",options.LegacyTunnelNetwork?"legacy-tunnel-network"
|
||||
:options.CavesRecipe?"caves"
|
||||
:options.LatticeRecipe?"lattice"
|
||||
:options.FloatingIslandsRecipe?"floating-islands":"surface-world",
|
||||
options.LatticeStrateIndex,
|
||||
@@ -1858,7 +1862,9 @@ int main(int argc,char** argv)
|
||||
}
|
||||
VoxelForgeCore::ResolvedLayoutSnapshotV3 resolvedLayout;
|
||||
{
|
||||
const std::string variant=options.LatticeRecipe
|
||||
const std::string variant=options.LegacyTunnelNetwork
|
||||
?"legacy-tunnel-network"
|
||||
:options.LatticeRecipe
|
||||
?"lattice/"+std::to_string(options.LatticeStrateIndex)
|
||||
:options.CavesRecipe?"caves"
|
||||
:(options.FloatingIslandsRecipe||options.NativeOwnerFloatingIslandsRecipe)
|
||||
@@ -1871,6 +1877,11 @@ int main(int argc,char** argv)
|
||||
*ownerWorld,worldAssembly,variant,resolvedLayout,error);
|
||||
if(!layoutResolved)
|
||||
{std::fprintf(stderr,"Resolved-layout snapshot failed: %s\n",error.c_str());return 2;}
|
||||
if(useExportedCorePlan&&!options.NativeOwnerFloatingIslandsRecipe
|
||||
&&!options.LegacyTunnelNetwork&&!options.CavesRecipe
|
||||
&&!StandaloneWorldAssemblyPrototype::PlanOwnerTunnelNetworkCaves(
|
||||
*ownerWorld,resolvedLayout,error))
|
||||
{std::fprintf(stderr,"Imported owner Tunnel Network cave planning failed: %s\n",error.c_str());return 2;}
|
||||
if(useExportedCorePlan&&!options.NativeOwnerFloatingIslandsRecipe
|
||||
&&!StandaloneWorldAssemblyPrototype::ValidateExportedCorePlanMatchesOwner(
|
||||
*ownerWorld,worldAssembly,resolvedLayout,error))
|
||||
@@ -1953,8 +1964,11 @@ int main(int argc,char** argv)
|
||||
{
|
||||
std::vector<VoxelForgeCore::ResolvedCavernRoomV1> rooms;
|
||||
std::vector<VoxelForgeCore::ResolvedCavernEdgeV1> edges;
|
||||
if(!MakeNativeCavesSpine(resolvedLayout,rooms,edges,error)
|
||||
||!VoxelForgeCore::ResolveResolvedCavernGraphV1(
|
||||
if(!MakeNativeCavesSpine(resolvedLayout,rooms,edges,error))
|
||||
{std::fprintf(stderr,"Native caves spine resolution failed: %s\n",error.c_str());return 2;}
|
||||
rooms.insert(rooms.end(),resolvedLayout.cavernRooms.begin(),resolvedLayout.cavernRooms.end());
|
||||
edges.insert(edges.end(),resolvedLayout.cavernEdges.begin(),resolvedLayout.cavernEdges.end());
|
||||
if(!VoxelForgeCore::ResolveResolvedCavernGraphV1(
|
||||
resolvedLayout,seed,rooms,edges,error))
|
||||
{std::fprintf(stderr,"Native caves spine resolution failed: %s\n",error.c_str());return 2;}
|
||||
}
|
||||
@@ -1965,8 +1979,11 @@ int main(int argc,char** argv)
|
||||
std::vector<VoxelForgeCore::ResolvedCavernRoomV1> rooms;
|
||||
std::vector<VoxelForgeCore::ResolvedCavernEdgeV1> edges;
|
||||
if(!MakeNativeLatticeSpine(resolvedLayout,seed,options.LatticeStrateIndex,
|
||||
options.LatticeOrphanControl,rooms,edges,error)
|
||||
||!VoxelForgeCore::ResolveResolvedCavernGraphV1(
|
||||
options.LatticeOrphanControl,rooms,edges,error))
|
||||
{std::fprintf(stderr,"Native lattice graph resolution failed: %s\n",error.c_str());return 2;}
|
||||
rooms.insert(rooms.end(),resolvedLayout.cavernRooms.begin(),resolvedLayout.cavernRooms.end());
|
||||
edges.insert(edges.end(),resolvedLayout.cavernEdges.begin(),resolvedLayout.cavernEdges.end());
|
||||
if(!VoxelForgeCore::ResolveResolvedCavernGraphV1(
|
||||
resolvedLayout,seed,rooms,edges,error))
|
||||
{std::fprintf(stderr,"Native lattice graph resolution failed: %s\n",error.c_str());return 2;}
|
||||
}
|
||||
@@ -2087,12 +2104,16 @@ int main(int argc,char** argv)
|
||||
const bool hasNativeLatticeRecipe=std::any_of(resolvedLayout.strates.begin(),resolvedLayout.strates.end(),
|
||||
[](const auto& stratum){return VoxelForgeCore::IsNativeLatticeFieldStratum(stratum)
|
||||
&&stratum.passageCapabilities.hasNativeLatticeField;});
|
||||
const auto actualRecipe=hasNativeLatticeRecipe?std::string_view("lattice")
|
||||
:hasNativeCavesRecipe?std::string_view("caves")
|
||||
const auto actualRecipe=requestedRecipe=="lattice"&&hasNativeLatticeRecipe
|
||||
?std::string_view("lattice")
|
||||
:requestedRecipe=="caves"&&hasNativeCavesRecipe
|
||||
?std::string_view("caves")
|
||||
:requestedRecipe=="surface-world"&&hasSurfaceWorldPlan
|
||||
?std::string_view("surface-world")
|
||||
:requestedRecipe=="floating-islands"&&hasFloatingIslands
|
||||
?std::string_view("floating-islands")
|
||||
:hasNativeLatticeRecipe?std::string_view("lattice")
|
||||
:hasNativeCavesRecipe?std::string_view("caves")
|
||||
:hasFloatingIslands?std::string_view("floating-islands")
|
||||
:hasSurfaceWorldPlan?std::string_view("surface-world")
|
||||
:std::string_view("missing");
|
||||
|
||||
@@ -1595,6 +1595,205 @@ namespace StandaloneStreamBenchmark
|
||||
return passed;
|
||||
}
|
||||
|
||||
inline bool AuditNativeTunnelNetworkField(const VoxelForgeCore::World& shape,
|
||||
const VoxelForgeCore::ResolvedLayoutSnapshotV3& layout,
|
||||
std::uint32_t voxelMicrometres)
|
||||
{
|
||||
using namespace VoxelForgeCore;
|
||||
constexpr double Pi=3.14159265358979323846;
|
||||
const double vm=std::max<std::uint32_t>(1,voxelMicrometres)/1.0e6;
|
||||
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=="TunnelNetwork"
|
||||
&&IsNativeCaveFieldStratum(s)&&s.passageCapabilities.hasNativeCaveField;});
|
||||
if(cave==layout.strates.end()||layout.cavernGraphVocabularyVersion!=1)
|
||||
{std::printf("[TunnelNetworkCavernAudit] recipe=%s graph=%u status=FAIL\n",
|
||||
cave==layout.strates.end()?"missing":"present",
|
||||
layout.cavernGraphVocabularyVersion);return false;}
|
||||
|
||||
const ResolvedLayoutPassageV1* arrival=nullptr;
|
||||
const ResolvedLayoutPassageV1* departure=nullptr;
|
||||
for(const auto& passage:layout.passages)
|
||||
{
|
||||
if(passage.lowerStrateIndex==cave->strateIndex
|
||||
&&passage.upperStrateIndex<cave->strateIndex)arrival=&passage;
|
||||
if(passage.upperStrateIndex==cave->strateIndex
|
||||
&&passage.lowerStrateIndex>cave->strateIndex)departure=&passage;
|
||||
}
|
||||
std::vector<const ResolvedCavernRoomV1*> rooms;
|
||||
std::vector<const ResolvedCavernEdgeV1*> routeEdges;
|
||||
for(const auto& room:layout.cavernRooms)
|
||||
if(room.strateIndex==cave->strateIndex)rooms.push_back(&room);
|
||||
for(const auto& edge:layout.cavernEdges)
|
||||
if(edge.strateIndex==cave->strateIndex
|
||||
&&edge.stableId.find("/route-edge-")!=std::string::npos)
|
||||
routeEdges.push_back(&edge);
|
||||
const auto routeIndex=[](const ResolvedCavernEdgeV1* edge)
|
||||
{
|
||||
const auto marker=edge->stableId.rfind("/route-edge-");
|
||||
return marker==std::string::npos?std::uint64_t{0}
|
||||
:static_cast<std::uint64_t>(std::stoull(edge->stableId.substr(marker+12)));
|
||||
};
|
||||
std::sort(routeEdges.begin(),routeEdges.end(),[&](const auto* a,const auto* b)
|
||||
{return routeIndex(a)<routeIndex(b);});
|
||||
std::unordered_map<std::string,std::size_t> compoundCounts;
|
||||
std::unordered_map<std::string,double> compoundRadii;
|
||||
double largestRoomRadius=0.0,largestOtherRoomRadius=0.0;
|
||||
std::string largestRoomGroup;
|
||||
for(const auto* room:rooms)
|
||||
{
|
||||
const auto marker=room->stableId.rfind("/primitive-");
|
||||
const std::string group=marker==std::string::npos
|
||||
?room->stableId:room->stableId.substr(0,marker);
|
||||
++compoundCounts[group];
|
||||
const double radius=std::max(room->halfExtentsMicrometres[0],
|
||||
room->halfExtentsMicrometres[1])/1.0e6;
|
||||
compoundRadii[group]=std::max(compoundRadii[group],radius);
|
||||
if(radius>largestRoomRadius)
|
||||
{largestRoomRadius=radius;largestRoomGroup=group;}
|
||||
}
|
||||
for(const auto& [group,radius]:compoundRadii)
|
||||
if(group!=largestRoomGroup)
|
||||
largestOtherRoomRadius=std::max(largestOtherRoomRadius,radius);
|
||||
const bool compoundRooms=!compoundCounts.empty()
|
||||
&&std::all_of(compoundCounts.begin(),compoundCounts.end(),
|
||||
[](const auto& row){return row.second>=2&&row.second<=4;});
|
||||
const bool destination=largestOtherRoomRadius>0.0
|
||||
&&largestRoomRadius>largestOtherRoomRadius*1.10;
|
||||
|
||||
std::vector<const ResolvedLayoutAnchorV2*> routeAnchors;
|
||||
for(const auto& anchor:cave->anchors)
|
||||
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
|
||||
routeAnchors.push_back(&anchor);
|
||||
double maximumGrade=0.0;
|
||||
bool gradeOk=routeAnchors.size()>=2;
|
||||
for(std::size_t i=1;i<routeAnchors.size();++i)
|
||||
{
|
||||
const auto& a=routeAnchors[i-1]->values;
|
||||
const auto& b=routeAnchors[i]->values;
|
||||
const double run=std::hypot(static_cast<double>(b[0]-a[0]),
|
||||
static_cast<double>(b[1]-a[1]));
|
||||
const double rise=std::abs(static_cast<double>(b[2]-a[2]));
|
||||
if(run<=0.0){gradeOk=false;continue;}
|
||||
const double grade=std::atan2(rise,run)*180.0/Pi;
|
||||
maximumGrade=std::max(maximumGrade,grade);
|
||||
gradeOk=gradeOk&&grade<=15.0+1.0e-6;
|
||||
}
|
||||
bool mouthBinding=arrival&&departure&&routeAnchors.size()>=2;
|
||||
if(mouthBinding)
|
||||
{
|
||||
const auto qOffset=static_cast<std::int64_t>(std::llround(
|
||||
250000.0*WorldAssemblySubvoxelsPerVoxel/layout.coreVoxelSizeMicrometres));
|
||||
const auto& start=routeAnchors.front()->values;
|
||||
const auto& end=routeAnchors.back()->values;
|
||||
mouthBinding=start[0]==arrival->lowerMouthSubvoxels[0]
|
||||
&&start[1]==arrival->lowerMouthSubvoxels[1]
|
||||
&&start[2]==arrival->lowerMouthSubvoxels[2]+qOffset
|
||||
&&end[0]==departure->upperMouthSubvoxels[0]
|
||||
&&end[1]==departure->upperMouthSubvoxels[1]
|
||||
&&end[2]==departure->upperMouthSubvoxels[2]+qOffset;
|
||||
}
|
||||
|
||||
std::uint64_t airSamples=0,floorSamples=0,shellSamples=0;
|
||||
bool centerAir=true,floorSolid=true,shellSolid=true;
|
||||
for(const auto* edge:routeEdges)
|
||||
{
|
||||
const auto& p=edge->controlPointsMicrometres;
|
||||
const double x=(p[0][0]+3.0*p[1][0]+3.0*p[2][0]+p[3][0])/8.0e6;
|
||||
const double y=(p[0][1]+3.0*p[1][1]+3.0*p[2][1]+p[3][1])/8.0e6;
|
||||
const double floor=(p[0][2]+3.0*p[1][2]+3.0*p[2][2]+p[3][2])/8.0e6;
|
||||
const double bore=edge->boreRadiusMicrometres/1.0e6;
|
||||
if(density(x,y,floor+1.0)<=0.0)centerAir=false;
|
||||
++airSamples;
|
||||
if(density(x,y,floor-0.10)>=0.0)floorSolid=false;
|
||||
++floorSamples;
|
||||
const double dx=(p[3][0]-p[0][0])/1.0e6;
|
||||
const double dy=(p[3][1]-p[0][1])/1.0e6;
|
||||
const double length=std::hypot(dx,dy);
|
||||
if(length<=1.0e-6){shellSolid=false;continue;}
|
||||
const double nx=-dy/length,ny=dx/length;
|
||||
if(density(x+nx*(bore+0.90),y+ny*(bore+0.90),floor+2.0)>=0.0
|
||||
||density(x-nx*(bore+0.90),y-ny*(bore+0.90),floor+2.0)>=0.0)
|
||||
shellSolid=false;
|
||||
shellSamples+=2;
|
||||
}
|
||||
|
||||
const ResolvedCavernEdgeV1* rippleEdge=nullptr;
|
||||
double longest=0.0;
|
||||
for(const auto* edge:routeEdges)
|
||||
{
|
||||
const auto& p=edge->controlPointsMicrometres;
|
||||
const double length=std::hypot(static_cast<double>(p[3][0]-p[0][0]),
|
||||
static_cast<double>(p[3][1]-p[0][1]));
|
||||
if(length>longest){longest=length;rippleEdge=edge;}
|
||||
}
|
||||
std::vector<double> rippleRadii;
|
||||
if(rippleEdge)
|
||||
{
|
||||
const auto& p=rippleEdge->controlPointsMicrometres;
|
||||
const double radius=rippleEdge->boreRadiusMicrometres/1.0e6;
|
||||
constexpr std::array<double,7> stations{{0.18,0.29,0.40,0.51,0.62,0.73,0.84}};
|
||||
for(double t:stations)
|
||||
{
|
||||
const auto bezier=[&](double u,std::size_t axis)
|
||||
{
|
||||
const double one=1.0-u;
|
||||
return (one*one*one*p[0][axis]+3.0*one*one*u*p[1][axis]
|
||||
+3.0*one*u*u*p[2][axis]+u*u*u*p[3][axis])/1.0e6;
|
||||
};
|
||||
const double x=bezier(t,0),y=bezier(t,1),floor=bezier(t,2);
|
||||
const double tx=bezier(std::min(1.0,t+0.01),0)
|
||||
-bezier(std::max(0.0,t-0.01),0);
|
||||
const double ty=bezier(std::min(1.0,t+0.01),1)
|
||||
-bezier(std::max(0.0,t-0.01),1);
|
||||
const double tangent=std::hypot(tx,ty);
|
||||
if(tangent<=1.0e-6)continue;
|
||||
for(const double side:{-1.0,1.0})
|
||||
{
|
||||
double prior=0.20;
|
||||
double priorDensity=density(x,y,floor+2.0);
|
||||
bool found=false;
|
||||
for(double radial=0.25;radial<=radius+1.8;radial+=0.05)
|
||||
{
|
||||
const double sampleX=x-side*ty/tangent*radial;
|
||||
const double sampleY=y+side*tx/tangent*radial;
|
||||
const double value=density(sampleX,sampleY,floor+2.0);
|
||||
if(priorDensity>=0.0&&value<0.0)
|
||||
{
|
||||
const double fraction=priorDensity/(priorDensity-value);
|
||||
rippleRadii.push_back(prior+(radial-prior)*fraction);
|
||||
found=true;break;
|
||||
}
|
||||
prior=radial;priorDensity=value;
|
||||
}
|
||||
if(!found)continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
double ripplePeakToPeak=0.0;
|
||||
if(!rippleRadii.empty())
|
||||
{
|
||||
const auto limits=std::minmax_element(rippleRadii.begin(),rippleRadii.end());
|
||||
ripplePeakToPeak=*limits.second-*limits.first;
|
||||
}
|
||||
const bool ripple=rippleRadii.size()>=8&&ripplePeakToPeak>=0.02
|
||||
&&ripplePeakToPeak<=1.25;
|
||||
std::printf("[TunnelNetworkCavernAudit] strate=%d recipe=%s rooms=%zu compound_groups=%zu destination_radius_m=%.3f route_edges=%zu route_points=%zu mouth_binding=%s max_grade_deg=%.3f grade_ok=%s center_air=%llu/%llu floor_solid=%llu/%llu shell_solid=%llu/%llu ripple_samples=%zu ripple_peak_to_peak_m=%.4f status=%s\n",
|
||||
cave->strateIndex,cave->recipeId.c_str(),rooms.size(),compoundCounts.size(),
|
||||
largestRoomRadius,routeEdges.size(),routeAnchors.size(),mouthBinding?"PASS":"FAIL",
|
||||
maximumGrade,gradeOk?"PASS":"FAIL",
|
||||
static_cast<unsigned long long>(airSamples),static_cast<unsigned long long>(airSamples),
|
||||
static_cast<unsigned long long>(floorSamples),static_cast<unsigned long long>(floorSamples),
|
||||
static_cast<unsigned long long>(shellSamples),static_cast<unsigned long long>(shellSamples),
|
||||
rippleRadii.size(),ripplePeakToPeak,
|
||||
arrival&&departure&&routeEdges.size()>=2&&!rooms.empty()&&compoundRooms
|
||||
&&destination&&mouthBinding&&gradeOk&¢erAir&&floorSolid&&shellSolid
|
||||
&&ripple?"PASS":"FAIL");
|
||||
return arrival&&departure&&routeEdges.size()>=2&&!rooms.empty()&&compoundRooms
|
||||
&&destination&&mouthBinding&&gradeOk&¢erAir&&floorSolid&&shellSolid&&ripple;
|
||||
}
|
||||
|
||||
inline bool AuditNativeCavesField(const VoxelForgeCore::World& shape,
|
||||
const VoxelForgeCore::ResolvedLayoutSnapshotV3& layout,std::uint32_t voxelMicrometres)
|
||||
{
|
||||
@@ -1605,9 +1804,13 @@ namespace StandaloneStreamBenchmark
|
||||
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
|
||||
[](const auto& s){return IsNativeCaveFieldStratum(s)
|
||||
&&s.passageCapabilities.hasNativeCaveField;});
|
||||
if(cave!=layout.strates.end()&&cave->generatorKind=="TunnelNetwork")
|
||||
return AuditNativeTunnelNetworkField(shape,layout,voxelMicrometres);
|
||||
if(cave==layout.strates.end()||cave->generatorKind!="Caves")
|
||||
{std::printf("[CavesAudit] recipe=missing rooms=%zu edges=%zu status=FAIL\n",
|
||||
layout.cavernRooms.size(),layout.cavernEdges.size());return false;}
|
||||
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;}
|
||||
@@ -4796,6 +4999,7 @@ namespace StandaloneStreamBenchmark
|
||||
}
|
||||
}
|
||||
std::uint64_t routePoints=0,routePasses=0,routeWalkPasses=0,routeBindings=0,routeBindingFailures=0;
|
||||
std::uint64_t tunnelNetworkRoutes=0,tunnelNetworkBlockedControls=0;
|
||||
for(const auto& stratum:ResolvedLayout->strates)
|
||||
{
|
||||
std::vector<const VoxelForgeCore::ResolvedLayoutAnchorV2*> anchors;
|
||||
@@ -4859,36 +5063,56 @@ namespace StandaloneStreamBenchmark
|
||||
supportStepMetres,supportMaximumMetres,minimumSupportProbes,
|
||||
supportProbeRadiusMetres);
|
||||
bool binding=true;
|
||||
const auto equalPosition=[](const auto& a,const auto& b)
|
||||
{return a[0]==b[0]&&a[1]==b[1]&&a[2]==b[2];};
|
||||
const auto boundMouthMatches=[&](const auto& authored,const auto& mouth)
|
||||
{
|
||||
if(stratum.generatorKind!="TunnelNetwork")
|
||||
return equalPosition(authored,mouth);
|
||||
auto expected=mouth;
|
||||
expected[2]+=static_cast<std::int64_t>(std::llround(
|
||||
250000.0*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel
|
||||
/World.CoreVoxelSizeMicrons));
|
||||
return equalPosition(authored,expected);
|
||||
};
|
||||
for(const auto& passage:ResolvedLayout->passages)
|
||||
{
|
||||
const auto equalPosition=[](const auto& a,const auto& b)
|
||||
{return a[0]==b[0]&&a[1]==b[1]&&a[2]==b[2];};
|
||||
if(passage.upperStrateIndex==stratum.strateIndex)
|
||||
{
|
||||
const auto rootedMouth=std::find_if(stratum.anchors.begin(),
|
||||
stratum.anchors.end(),[](const auto& anchor)
|
||||
{return anchor.kind=="passage_anchor"&&anchor.values.size()>=10;});
|
||||
const std::array<std::int64_t,3> departure=
|
||||
rootedMouth!=stratum.anchors.end()
|
||||
?std::array<std::int64_t,3>{{rootedMouth->values[7],
|
||||
rootedMouth->values[8],rootedMouth->values[9]}}
|
||||
:std::array<std::int64_t,3>{{anchors.back()->values[0],
|
||||
anchors.back()->values[1],anchors.back()->values[2]}};
|
||||
binding&=equalPosition(passage.upperMouthSubvoxels,
|
||||
departure);
|
||||
if(stratum.generatorKind=="TunnelNetwork")
|
||||
binding&=boundMouthMatches(anchors.back()->values,
|
||||
passage.upperMouthSubvoxels);
|
||||
else
|
||||
{
|
||||
const auto rootedMouth=std::find_if(stratum.anchors.begin(),
|
||||
stratum.anchors.end(),[](const auto& anchor)
|
||||
{return anchor.kind=="passage_anchor"&&anchor.values.size()>=10;});
|
||||
const std::array<std::int64_t,3> departure=
|
||||
rootedMouth!=stratum.anchors.end()
|
||||
?std::array<std::int64_t,3>{{rootedMouth->values[7],
|
||||
rootedMouth->values[8],rootedMouth->values[9]}}
|
||||
:std::array<std::int64_t,3>{{anchors.back()->values[0],
|
||||
anchors.back()->values[1],anchors.back()->values[2]}};
|
||||
binding&=equalPosition(passage.upperMouthSubvoxels,departure);
|
||||
}
|
||||
}
|
||||
if(passage.lowerStrateIndex==stratum.strateIndex)
|
||||
{
|
||||
const auto passageArrival=std::find_if(stratum.anchors.begin(),
|
||||
stratum.anchors.end(),[](const auto& anchor)
|
||||
{return anchor.kind=="passage_arrival"&&anchor.values.size()>=3;});
|
||||
const std::array<std::int64_t,3> arrival=passageArrival!=stratum.anchors.end()
|
||||
?std::array<std::int64_t,3>{{passageArrival->values[0],
|
||||
passageArrival->values[1],passageArrival->values[2]}}
|
||||
:std::array<std::int64_t,3>{{anchors.front()->values[0],
|
||||
anchors.front()->values[1],anchors.front()->values[2]}};
|
||||
binding&=equalPosition(passage.lowerMouthSubvoxels,
|
||||
arrival);
|
||||
if(stratum.generatorKind=="TunnelNetwork")
|
||||
binding&=boundMouthMatches(anchors.front()->values,
|
||||
passage.lowerMouthSubvoxels);
|
||||
else
|
||||
{
|
||||
const auto passageArrival=std::find_if(stratum.anchors.begin(),
|
||||
stratum.anchors.end(),[](const auto& anchor)
|
||||
{return anchor.kind=="passage_arrival"&&anchor.values.size()>=3;});
|
||||
const std::array<std::int64_t,3> arrival=passageArrival!=stratum.anchors.end()
|
||||
?std::array<std::int64_t,3>{{passageArrival->values[0],
|
||||
passageArrival->values[1],passageArrival->values[2]}}
|
||||
:std::array<std::int64_t,3>{{anchors.front()->values[0],
|
||||
anchors.front()->values[1],anchors.front()->values[2]}};
|
||||
binding&=equalPosition(passage.lowerMouthSubvoxels,arrival);
|
||||
}
|
||||
}
|
||||
}
|
||||
++routePasses;routeWalkPasses+=routeAudit.passed;
|
||||
@@ -4918,6 +5142,18 @@ namespace StandaloneStreamBenchmark
|
||||
stratum.strateIndex,routeAudit.firstUnsupported[i][0],
|
||||
routeAudit.firstUnsupported[i][1],routeAudit.firstUnsupported[i][2],
|
||||
routeAudit.firstUnsupported[i][3]);
|
||||
if(stratum.generatorKind=="TunnelNetwork")
|
||||
{
|
||||
++tunnelNetworkRoutes;
|
||||
const FWalkAudit control=auditPlayerPath(blockedWorld,route,
|
||||
supportStartMetres,supportStepMetres,supportMaximumMetres,
|
||||
minimumSupportProbes,supportProbeRadiusMetres);
|
||||
const bool detected=control.blocked>0&&!control.passed;
|
||||
tunnelNetworkBlockedControls+=detected;
|
||||
std::printf("[TunnelNetworkBlockedRouteControl] strate=%d arrival_to_departure=true blocked_capsule_samples=%llu result=%s expected=FAIL status=%s\n",
|
||||
stratum.strateIndex,static_cast<unsigned long long>(control.blocked),
|
||||
control.passed?"PASS":"FAIL",detected?"PASS":"FAIL");
|
||||
}
|
||||
}
|
||||
for(std::size_t passageIndex=0;
|
||||
passageIndex<ResolvedLayout->passages.size();++passageIndex)
|
||||
@@ -4984,13 +5220,16 @@ namespace StandaloneStreamBenchmark
|
||||
&&shellAuditPassed
|
||||
&&routePasses>0&&routePasses==routeWalkPasses&&routePasses==routeBindings
|
||||
&&routeBindingFailures==0
|
||||
&&tunnelNetworkRoutes==tunnelNetworkBlockedControls
|
||||
&&blockedCompiled&&blockedControls==passageCount;
|
||||
std::printf("[ResolvedPassageAuditSummary] interfaces=%llu passed=%llu natural=%llu/%llu strate_routes=%llu route_walkable=%llu route_points=%llu route_bindings=%llu route_binding_failures=%llu blocked_controls=%llu/%llu status=%s\n",
|
||||
std::printf("[ResolvedPassageAuditSummary] interfaces=%llu passed=%llu natural=%llu/%llu strate_routes=%llu route_walkable=%llu route_points=%llu route_bindings=%llu route_binding_failures=%llu tunnel_network_block_controls=%llu/%llu blocked_controls=%llu/%llu status=%s\n",
|
||||
static_cast<unsigned long long>(passageCount),static_cast<unsigned long long>(passagePasses),
|
||||
static_cast<unsigned long long>(naturalPasses),static_cast<unsigned long long>(passageCount),
|
||||
static_cast<unsigned long long>(routePasses),static_cast<unsigned long long>(routeWalkPasses),
|
||||
static_cast<unsigned long long>(routePoints),
|
||||
static_cast<unsigned long long>(routeBindings),static_cast<unsigned long long>(routeBindingFailures),
|
||||
static_cast<unsigned long long>(tunnelNetworkBlockedControls),
|
||||
static_cast<unsigned long long>(tunnelNetworkRoutes),
|
||||
static_cast<unsigned long long>(blockedControls),static_cast<unsigned long long>(passageCount),
|
||||
auditPassed?"PASS":"FAIL");
|
||||
if(!auditPassed)
|
||||
|
||||
@@ -1069,6 +1069,166 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
inline bool PlanOwnerTunnelNetworkCaves(const FStandaloneOwnerWorld& owner,
|
||||
VoxelForgeCore::ResolvedLayoutSnapshotV3& layout,std::string& error)
|
||||
{
|
||||
using namespace VoxelForgeCore;
|
||||
if(!owner.Manager)
|
||||
{error="Tunnel Network cave planner requires the initialized owner director";return false;}
|
||||
std::vector<const FStrateSlot*> tunnelSlots;
|
||||
for(const FStrateSlot& slot:owner.Manager->GetLayout())
|
||||
{
|
||||
if(!slot.Definition
|
||||
||owner.Manager->GetGeneratorTypeForChunk(FIntVector(0,0,slot.TopChunkZ))
|
||||
!=ECaveGeneratorType::TunnelNetwork)continue;
|
||||
auto stratum=std::find_if(layout.strates.begin(),layout.strates.end(),
|
||||
[&](const auto& item){return item.strateIndex==slot.StrateIndex;});
|
||||
if(stratum==layout.strates.end())
|
||||
{error="Tunnel Network strate is missing from the resolved layout";return false;}
|
||||
stratum->generatorKind="TunnelNetwork";
|
||||
stratum->recipeId="Core.Caves.PrimitiveField";
|
||||
stratum->recipeVersion=1;
|
||||
stratum->stableId=layout.worldIdentity+"/strate/"
|
||||
+std::to_string(slot.StrateIndex)+"/generator/TunnelNetwork";
|
||||
const bool alreadyPlanned=std::any_of(layout.cavernRooms.begin(),
|
||||
layout.cavernRooms.end(),[&](const auto& room)
|
||||
{return room.strateIndex==slot.StrateIndex;})
|
||||
&&std::any_of(layout.cavernEdges.begin(),layout.cavernEdges.end(),
|
||||
[&](const auto& edge){return edge.strateIndex==slot.StrateIndex;});
|
||||
if(!alreadyPlanned)tunnelSlots.push_back(&slot);
|
||||
}
|
||||
if(!layout.finalize(error))return false;
|
||||
const auto toMicrometres=[&](double ownerVoxels)
|
||||
{
|
||||
return static_cast<std::int64_t>(std::llround(ownerVoxels
|
||||
*static_cast<double>(owner.OwnerVoxelSizeMicrons)));
|
||||
};
|
||||
const auto toQ16=[](float value)
|
||||
{
|
||||
return static_cast<std::int32_t>(std::llround(std::clamp(
|
||||
static_cast<double>(value),0.0,1.0)*65536.0));
|
||||
};
|
||||
if(!tunnelSlots.empty())
|
||||
{
|
||||
std::vector<ResolvedCavernRoomV1> plannedRooms=layout.cavernRooms;
|
||||
std::vector<ResolvedCavernEdgeV1> plannedEdges=layout.cavernEdges;
|
||||
for(const FStrateSlot* slot:tunnelSlots)
|
||||
{
|
||||
const FStrateGenerationParams& params=slot->Definition->GenerationParams;
|
||||
ResolvedCavernPlannerSettingsV1 settings;
|
||||
settings.roomSpacingMicrometres=std::max<std::int64_t>(4000000,
|
||||
toMicrometres(params.RoomSpacing));
|
||||
settings.minimumRoomRadiusMicrometres=std::max<std::int64_t>(500000,
|
||||
toMicrometres(params.MinRoomRadius));
|
||||
settings.maximumRoomRadiusMicrometres=std::max(
|
||||
settings.minimumRoomRadiusMicrometres,toMicrometres(params.MaxRoomRadius));
|
||||
settings.destinationRadiusMicrometres=std::max(
|
||||
toMicrometres(params.OriginRoomRadius),
|
||||
settings.maximumRoomRadiusMicrometres*135/100);
|
||||
settings.minimumTunnelRadiusMicrometres=std::max<std::int64_t>(0,
|
||||
toMicrometres(params.TunnelMinRadius));
|
||||
settings.maximumTunnelRadiusMicrometres=std::max(
|
||||
settings.minimumTunnelRadiusMicrometres,toMicrometres(params.TunnelMaxRadius));
|
||||
settings.maximumTunnelLengthMicrometres=std::max<std::int64_t>(4000000,
|
||||
toMicrometres(params.MaxTunnelLength));
|
||||
settings.tunnelWarpMicrometres=std::max<std::int64_t>(0,
|
||||
toMicrometres(params.TunnelWarpStrength));
|
||||
settings.caveWarpMicrometres=std::max<std::int64_t>(0,
|
||||
toMicrometres(params.CaveWarpStrength));
|
||||
settings.smoothWidthMicrometres=std::max<std::int64_t>(1500000,
|
||||
toMicrometres(params.SDFBlendRadius));
|
||||
settings.irregularityAmplitudeMicrometres=std::max<std::int64_t>(0,
|
||||
toMicrometres(params.SurfaceRoughness));
|
||||
settings.irregularityWavelengthMicrometres=params.RoughnessFrequency>0.0f
|
||||
?static_cast<std::int64_t>(std::llround(owner.OwnerVoxelSizeMicrons
|
||||
/static_cast<double>(params.RoughnessFrequency))):0;
|
||||
settings.roomDensityQ16=toQ16(params.RoomDensity);
|
||||
settings.tunnelDensityQ16=toQ16(params.TunnelDensity);
|
||||
settings.roomShapeVarietyQ16=toQ16(params.RoomShapeVariety);
|
||||
settings.roomHeightRatioQ16=toQ16(params.RoomHeightRatio);
|
||||
settings.roomFloorCutMinimumQ16=toQ16(params.RoomFloorCutMin);
|
||||
settings.roomFloorCutMaximumQ16=toQ16(params.RoomFloorCutMax);
|
||||
settings.tunnelHorizontalBiasQ16=toQ16(params.TunnelHorizontalBias);
|
||||
settings.originRoomMaximumConnections=params.OriginRoomMaxConnections;
|
||||
settings.tunnelsFlowTowardOrigin=params.bTunnelsFlowTowardOrigin;
|
||||
std::vector<ResolvedCavernRoomV1> rooms;
|
||||
std::vector<ResolvedCavernEdgeV1> edges;
|
||||
if(!PlanResolvedCavernNetworkV1(layout,owner.Seed,slot->StrateIndex,
|
||||
settings,rooms,edges,error))return false;
|
||||
plannedRooms.insert(plannedRooms.end(),
|
||||
std::make_move_iterator(rooms.begin()),std::make_move_iterator(rooms.end()));
|
||||
plannedEdges.insert(plannedEdges.end(),
|
||||
std::make_move_iterator(edges.begin()),std::make_move_iterator(edges.end()));
|
||||
}
|
||||
if(!ResolveResolvedCavernGraphV1(layout,owner.Seed,
|
||||
plannedRooms,plannedEdges,error))return false;
|
||||
}
|
||||
|
||||
const auto toCoreSubvoxels=[&](std::int64_t micrometres)
|
||||
{
|
||||
return static_cast<std::int64_t>(std::llround(
|
||||
static_cast<double>(micrometres)*WorldAssemblySubvoxelsPerVoxel
|
||||
/layout.coreVoxelSizeMicrometres));
|
||||
};
|
||||
for(const FStrateSlot& slot:owner.Manager->GetLayout())
|
||||
{
|
||||
if(!slot.Definition
|
||||
||owner.Manager->GetGeneratorTypeForChunk(FIntVector(0,0,slot.TopChunkZ))
|
||||
!=ECaveGeneratorType::TunnelNetwork)continue;
|
||||
auto stratum=std::find_if(layout.strates.begin(),layout.strates.end(),
|
||||
[&](const auto& item){return item.strateIndex==slot.StrateIndex;});
|
||||
if(stratum==layout.strates.end())continue;
|
||||
const bool hasRoutePoints=std::count_if(stratum->anchors.begin(),
|
||||
stratum->anchors.end(),[](const auto& anchor)
|
||||
{return anchor.kind=="route_point"&&anchor.values.size()>=3;})>=2;
|
||||
if(hasRoutePoints)continue;
|
||||
const ResolvedLayoutPassageV1* arrival=nullptr;
|
||||
const ResolvedLayoutPassageV1* departure=nullptr;
|
||||
for(const auto& passage:layout.passages)
|
||||
{
|
||||
if(passage.lowerStrateIndex==slot.StrateIndex
|
||||
&&passage.upperStrateIndex<slot.StrateIndex)arrival=&passage;
|
||||
if(passage.upperStrateIndex==slot.StrateIndex
|
||||
&&passage.lowerStrateIndex>slot.StrateIndex)departure=&passage;
|
||||
}
|
||||
if(!arrival||!departure)
|
||||
{error="Tunnel Network route anchors cannot bind both passage mouths";return false;}
|
||||
const auto appendRoutePoint=[&](std::string id,
|
||||
const std::array<std::int64_t,3>& point)
|
||||
{
|
||||
ResolvedLayoutAnchorV2 anchor;anchor.kind="route_point";
|
||||
anchor.stableId="tunnel-network/route/"+std::move(id);
|
||||
anchor.units="1/256 Core voxel";
|
||||
anchor.values={point[0],point[1],point[2]};
|
||||
stratum->anchors.push_back(std::move(anchor));
|
||||
};
|
||||
constexpr std::int64_t PlayerCenterAboveMouthFloorMicrometres=250000;
|
||||
auto start=arrival->lowerMouthSubvoxels;
|
||||
start[2]+=toCoreSubvoxels(PlayerCenterAboveMouthFloorMicrometres);
|
||||
appendRoutePoint("000-arrival",start);
|
||||
for(std::size_t edgeIndex=0;edgeIndex<256;++edgeIndex)
|
||||
{
|
||||
const std::string stableId="tunnel-network/strate-"
|
||||
+std::to_string(slot.StrateIndex)+"/route-edge-"
|
||||
+std::to_string(edgeIndex);
|
||||
const auto edge=std::find_if(layout.cavernEdges.begin(),
|
||||
layout.cavernEdges.end(),[&](const auto& value)
|
||||
{return value.strateIndex==slot.StrateIndex
|
||||
&&value.stableId==stableId;});
|
||||
if(edge==layout.cavernEdges.end())break;
|
||||
auto point=edge->controlPointsMicrometres.back();
|
||||
point[2]+=1000000;
|
||||
appendRoutePoint(std::to_string(edgeIndex+1),
|
||||
{toCoreSubvoxels(point[0]),toCoreSubvoxels(point[1]),
|
||||
toCoreSubvoxels(point[2])});
|
||||
}
|
||||
auto end=departure->upperMouthSubvoxels;
|
||||
end[2]+=toCoreSubvoxels(PlayerCenterAboveMouthFloorMicrometres);
|
||||
appendRoutePoint("999-departure",end);
|
||||
}
|
||||
return layout.finalize(error);
|
||||
}
|
||||
|
||||
inline bool ImportExportedCoreWorldAssembly(const VFJson::Value& root,
|
||||
VoxelForgeCore::WorldAssemblyManifest& out,std::string& error)
|
||||
{
|
||||
@@ -1184,8 +1344,11 @@ 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"
|
||||
const bool nativeSurfaceCaves=recipeVariant=="caves"
|
||||
&&type==ECaveGeneratorType::SurfaceWorld;
|
||||
const bool nativeTunnelCaves=type==ECaveGeneratorType::TunnelNetwork
|
||||
&&recipeVariant!="legacy-tunnel-network"&&recipeVariant!="caves";
|
||||
const bool nativeCaves=nativeSurfaceCaves||nativeTunnelCaves;
|
||||
const bool nativeLattice=recipeVariant==("lattice/"+std::to_string(slot.StrateIndex))
|
||||
&&(type==ECaveGeneratorType::Maze||type==ECaveGeneratorType::VerticalShafts);
|
||||
const std::int64_t bottom=static_cast<std::int64_t>(slot.BottomChunkZ)*owner.StrateLayoutChunkSizeMicrometres;
|
||||
@@ -1195,7 +1358,7 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
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=nativeCaves?"Caves":GeneratorKind(type);
|
||||
s.generatorKind=nativeSurfaceCaves?"Caves":GeneratorKind(type);
|
||||
s.stableId=out.worldIdentity+"/strate/"+std::to_string(slot.StrateIndex)
|
||||
+"/generator/"+s.generatorKind;
|
||||
s.recipeId=nativeLattice?"Core.Lattice.PrimitiveField"
|
||||
@@ -1407,6 +1570,8 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
passageConfigs.push_back(config);
|
||||
}
|
||||
if(!PlanResolvedLayoutPassagesV1(out,owner.Seed,passageConfigs,error))return false;
|
||||
if(recipeVariant!="legacy-tunnel-network"&&recipeVariant!="caves")
|
||||
return PlanOwnerTunnelNetworkCaves(owner,out,error);
|
||||
return out.finalize(error);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user