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
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user